Compare commits
2 Commits
main
...
rtic-migra
Author | SHA1 | Date |
---|---|---|
Sebastian | 36da28fc02 | |
Sebastian | db95b2fceb |
|
@ -1,6 +1,6 @@
|
||||||
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
|
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
|
||||||
# TODO(2) replace `$CHIP` with your chip's name (see `probe-run --list-chips` output)
|
# TODO(2) replace `$CHIP` with your chip's name (see `probe-run --list-chips` output)
|
||||||
runner = "probe-run --chip STM32F103CB"
|
runner = "probe-run --chip STM32F103C8"
|
||||||
rustflags = [
|
rustflags = [
|
||||||
"-C", "linker=flip-link",
|
"-C", "linker=flip-link",
|
||||||
"-C", "link-arg=-Tlink.x",
|
"-C", "link-arg=-Tlink.x",
|
||||||
|
|
44
Cargo.toml
44
Cargo.toml
|
@ -1,24 +1,22 @@
|
||||||
[package]
|
[package]
|
||||||
# TODO(1) fix `authors` and `name` if you didn't use `cargo-generate`
|
|
||||||
authors = ["LongHairedHacker <sebastian@sebastians-site.de>"]
|
authors = ["LongHairedHacker <sebastian@sebastians-site.de>"]
|
||||||
name = "wspr-beacon"
|
name = "wspr-beacon"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
[workspace]
|
|
||||||
members = ["testsuite"]
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
cortex-m = "~0.7.1"
|
cortex-m = "0.7.6"
|
||||||
cortex-m-rt = "~0.6.13"
|
cortex-m-rt = "0.7.2"
|
||||||
defmt = "~0.2.0"
|
cortex-m-rtic = "1.1.3"
|
||||||
defmt-rtt = "~0.2.0"
|
defmt = "0.3.2"
|
||||||
panic-probe = { version = "~0.2.0", features = ["print-defmt"] }
|
defmt-rtt = "0.3.2"
|
||||||
stm32f1xx-hal = { version = "~0.6.1", features = ["stm32f103", "rt"] }
|
panic-probe = { version = "0.3.0", features = ["print-defmt"] }
|
||||||
embedded-hal = {version = "~0.2.3"}
|
stm32f1xx-hal = { version = "0.9.0", features = ["stm32f103", "rt"] }
|
||||||
nb = "~1.0.0"
|
embedded-hal = {version = "0.2.3"}
|
||||||
nmea0183 = "~0.2.3"
|
nb = "1.0.0"
|
||||||
arrayvec = {version = "~0.7.0", default-features = false}
|
nmea0183 = "0.3.0"
|
||||||
|
arrayvec = {version = "0.7.0", default-features = false}
|
||||||
|
systick-monotonic = "1.0.0"
|
||||||
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
@ -42,8 +40,8 @@ codegen-units = 1
|
||||||
debug = 2
|
debug = 2
|
||||||
debug-assertions = true # <-
|
debug-assertions = true # <-
|
||||||
incremental = false
|
incremental = false
|
||||||
opt-level = 3 # <-
|
opt-level = 'z' # <-
|
||||||
overflow-checks = true # <-
|
overflow-checks = true # <-
|
||||||
|
|
||||||
# cargo test
|
# cargo test
|
||||||
[profile.test]
|
[profile.test]
|
||||||
|
@ -51,8 +49,8 @@ codegen-units = 1
|
||||||
debug = 2
|
debug = 2
|
||||||
debug-assertions = true # <-
|
debug-assertions = true # <-
|
||||||
incremental = false
|
incremental = false
|
||||||
opt-level = 3 # <-
|
opt-level = 3 # <-
|
||||||
overflow-checks = true # <-
|
overflow-checks = true # <-
|
||||||
|
|
||||||
# cargo build/run --release
|
# cargo build/run --release
|
||||||
[profile.release]
|
[profile.release]
|
||||||
|
@ -60,9 +58,9 @@ codegen-units = 1
|
||||||
debug = 2
|
debug = 2
|
||||||
debug-assertions = false # <-
|
debug-assertions = false # <-
|
||||||
incremental = false
|
incremental = false
|
||||||
#lto = 'fat'
|
lto = 'fat'
|
||||||
opt-level = 3 # <-
|
opt-level = 3 # <-
|
||||||
overflow-checks = false # <-
|
overflow-checks = false # <-
|
||||||
|
|
||||||
# cargo test --release
|
# cargo test --release
|
||||||
[profile.bench]
|
[profile.bench]
|
||||||
|
@ -71,8 +69,8 @@ debug = 2
|
||||||
debug-assertions = false # <-
|
debug-assertions = false # <-
|
||||||
incremental = false
|
incremental = false
|
||||||
lto = 'fat'
|
lto = 'fat'
|
||||||
opt-level = 3 # <-
|
opt-level = 3 # <-
|
||||||
overflow-checks = false # <-
|
overflow-checks = false # <-
|
||||||
|
|
||||||
# uncomment this to switch from the crates.io version of defmt to its git version
|
# uncomment this to switch from the crates.io version of defmt to its git version
|
||||||
# check app-template's README for instructions
|
# check app-template's README for instructions
|
||||||
|
|
|
@ -1,39 +0,0 @@
|
||||||
use nmea0183::{ParseResult, Parser};
|
|
||||||
use stm32f1xx_hal::{
|
|
||||||
delay::Delay,
|
|
||||||
prelude::*,
|
|
||||||
serial::{Config, Serial},
|
|
||||||
stm32,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::application::App;
|
|
||||||
use crate::loc;
|
|
||||||
use crate::time;
|
|
||||||
|
|
||||||
impl App {
|
|
||||||
pub fn poll_gps(&mut self) -> () {
|
|
||||||
match self.serial.read() {
|
|
||||||
Ok(byte) => {
|
|
||||||
self.board_led.toggle().unwrap();
|
|
||||||
if let Some(result) = self.gps_parser.parse_from_byte(byte) {
|
|
||||||
match result {
|
|
||||||
Ok(ParseResult::GGA(Some(gga))) => {
|
|
||||||
if !self.transmitting {
|
|
||||||
time::set(&gga.time);
|
|
||||||
self.locator = loc::locator_from_coordinates(
|
|
||||||
gga.latitude.as_f64(),
|
|
||||||
gga.longitude.as_f64(),
|
|
||||||
);
|
|
||||||
defmt::info!("Got GGA. New locator: {}", self.locator.as_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(_) => {} // Some other sentences..
|
|
||||||
Err(_) => {} // Got parse error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(nb::Error::WouldBlock) => {}
|
|
||||||
Err(nb::Error::Other(_)) => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,144 +0,0 @@
|
||||||
use cortex_m::{asm::wfi, prelude::*};
|
|
||||||
use embedded_hal::digital::v2::{OutputPin, ToggleableOutputPin};
|
|
||||||
use nb::{self, block};
|
|
||||||
use nmea0183::{ParseResult, Parser};
|
|
||||||
use stm32f1xx_hal::{
|
|
||||||
delay::Delay,
|
|
||||||
gpio::{gpiob, gpioc, Alternate, Floating, Input, OpenDrain, Output, PushPull},
|
|
||||||
i2c::BlockingI2c,
|
|
||||||
pac::I2C1,
|
|
||||||
prelude::*,
|
|
||||||
serial::Serial,
|
|
||||||
stm32,
|
|
||||||
};
|
|
||||||
|
|
||||||
mod gps;
|
|
||||||
mod setup;
|
|
||||||
|
|
||||||
//use crate::exit;
|
|
||||||
pub use setup::setup;
|
|
||||||
|
|
||||||
use crate::si5153;
|
|
||||||
use crate::time;
|
|
||||||
use crate::wspr;
|
|
||||||
|
|
||||||
pub struct App {
|
|
||||||
delay: Delay,
|
|
||||||
board_led: gpioc::PC13<Output<PushPull>>,
|
|
||||||
serial: Serial<
|
|
||||||
stm32::USART3,
|
|
||||||
(
|
|
||||||
gpiob::PB10<Alternate<PushPull>>,
|
|
||||||
gpiob::PB11<Input<Floating>>,
|
|
||||||
),
|
|
||||||
>,
|
|
||||||
i2c: BlockingI2c<
|
|
||||||
I2C1,
|
|
||||||
(
|
|
||||||
gpiob::PB6<Alternate<OpenDrain>>,
|
|
||||||
gpiob::PB7<Alternate<OpenDrain>>,
|
|
||||||
),
|
|
||||||
>,
|
|
||||||
gps_parser: Parser,
|
|
||||||
locator: arrayvec::ArrayString<6>,
|
|
||||||
transmitting: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
// For PLL with 800MHz
|
|
||||||
const WSPR_SYMBOLS: [si5153::PllParams; 4] = [
|
|
||||||
// 7040100.000000: 113 + 44687 / 70401
|
|
||||||
// actual: 7040100.000000
|
|
||||||
si5153::PllParams {
|
|
||||||
p1: 14033,
|
|
||||||
p2: 17455,
|
|
||||||
p3: 70401,
|
|
||||||
},
|
|
||||||
// 7040101.464844: 113 + 447801 / 705503
|
|
||||||
// actual: 7040101.464844
|
|
||||||
si5153::PllParams {
|
|
||||||
p1: 14033,
|
|
||||||
p2: 172785,
|
|
||||||
p3: 705503,
|
|
||||||
},
|
|
||||||
// 7040102.929688: 113 + 38515 / 60682
|
|
||||||
// actual: 7040102.929688
|
|
||||||
si5153::PllParams {
|
|
||||||
p1: 14033,
|
|
||||||
p2: 14678,
|
|
||||||
p3: 60682,
|
|
||||||
},
|
|
||||||
// 7040104.394531: 113 + 123233 / 194166
|
|
||||||
// actual: 7040104.394531
|
|
||||||
si5153::PllParams {
|
|
||||||
p1: 14033,
|
|
||||||
p2: 46378,
|
|
||||||
p3: 194166,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
impl App {
|
|
||||||
pub fn run(mut self) -> ! {
|
|
||||||
let callsign = arrayvec::ArrayString::<6>::from("DL1SSK").unwrap();
|
|
||||||
|
|
||||||
defmt::info!("Application Startup!");
|
|
||||||
|
|
||||||
let mut si_pll = si5153::Si5153::new(&self.i2c);
|
|
||||||
si_pll.init(&mut self.i2c, 25000000, 800000000 - 55_000, 800000000);
|
|
||||||
si_pll.set_ms_source(&mut self.i2c, si5153::Multisynth::MS0, si5153::PLL::A);
|
|
||||||
|
|
||||||
defmt::info!("PLL setup complete.");
|
|
||||||
|
|
||||||
let mut symbol_start = time::get();
|
|
||||||
let mut symbol_idx: usize = 0;
|
|
||||||
let mut message: [u8; 162] = [0; 162];
|
|
||||||
|
|
||||||
loop {
|
|
||||||
self.poll_gps();
|
|
||||||
|
|
||||||
if !self.transmitting {
|
|
||||||
let ts = time::get();
|
|
||||||
if self.locator.len() > 0
|
|
||||||
&& ts.minutes % 2 == 0
|
|
||||||
&& ts.seconds > 1.0
|
|
||||||
&& ts.seconds < 2.0
|
|
||||||
{
|
|
||||||
defmt::info!("Starting tranmission");
|
|
||||||
self.transmitting = true;
|
|
||||||
message = wspr::encode_message(&callsign, &self.locator, 27);
|
|
||||||
symbol_start = ts;
|
|
||||||
symbol_idx = 0;
|
|
||||||
si_pll.write_synth_params(
|
|
||||||
&mut self.i2c,
|
|
||||||
si5153::Multisynth::MS0,
|
|
||||||
&WSPR_SYMBOLS[message[symbol_idx] as usize],
|
|
||||||
);
|
|
||||||
si_pll.enable_ms_output(&mut self.i2c, si5153::Multisynth::MS0);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let ts = time::get();
|
|
||||||
let delta_sec = (ts.seconds - symbol_start.seconds)
|
|
||||||
+ (ts.minutes - symbol_start.minutes) as f32 * 60.0;
|
|
||||||
if delta_sec >= 0.683 {
|
|
||||||
symbol_start = ts;
|
|
||||||
symbol_idx += 1;
|
|
||||||
|
|
||||||
if symbol_idx < 162 {
|
|
||||||
si_pll.write_synth_params(
|
|
||||||
&mut self.i2c,
|
|
||||||
si5153::Multisynth::MS0,
|
|
||||||
&WSPR_SYMBOLS[message[symbol_idx] as usize],
|
|
||||||
);
|
|
||||||
defmt::info!("Tranmitting Symbol {}", symbol_idx);
|
|
||||||
} else {
|
|
||||||
si_pll.disable_ms_output(&mut self.i2c, si5153::Multisynth::MS0);
|
|
||||||
self.transmitting = false;
|
|
||||||
defmt::info!("Transmission ended");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//wfi();
|
|
||||||
}
|
|
||||||
|
|
||||||
//exit();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,91 +0,0 @@
|
||||||
use stm32f1xx_hal::{
|
|
||||||
delay::Delay,
|
|
||||||
i2c,
|
|
||||||
pac::Interrupt,
|
|
||||||
prelude::*,
|
|
||||||
serial::{Config, Serial},
|
|
||||||
stm32,
|
|
||||||
timer::{Event, Timer},
|
|
||||||
};
|
|
||||||
|
|
||||||
use nmea0183::Parser;
|
|
||||||
|
|
||||||
use crate::application::App;
|
|
||||||
use crate::time;
|
|
||||||
|
|
||||||
pub fn setup(cp: cortex_m::peripheral::Peripherals, dp: stm32::Peripherals) -> App {
|
|
||||||
// Take ownership over the raw flash and rcc devices and convert them into the corresponding
|
|
||||||
// HAL structs
|
|
||||||
let mut flash = dp.FLASH.constrain();
|
|
||||||
|
|
||||||
let mut rcc = dp.RCC.constrain();
|
|
||||||
|
|
||||||
// Freeze the configuration of all the clocks in the system and store the frozen frequencies in
|
|
||||||
// `clocks`
|
|
||||||
let clocks = rcc
|
|
||||||
.cfgr
|
|
||||||
.use_hse(8.mhz())
|
|
||||||
.sysclk(72.mhz())
|
|
||||||
.pclk1(36.mhz())
|
|
||||||
.freeze(&mut flash.acr);
|
|
||||||
|
|
||||||
defmt::info!("Clock Setup done");
|
|
||||||
|
|
||||||
// Acquire the GPIOC peripheral
|
|
||||||
let mut gpiob = dp.GPIOB.split(&mut rcc.apb2);
|
|
||||||
let mut gpioc = dp.GPIOC.split(&mut rcc.apb2);
|
|
||||||
|
|
||||||
let mut afio = dp.AFIO.constrain(&mut rcc.apb2);
|
|
||||||
|
|
||||||
let board_led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh);
|
|
||||||
|
|
||||||
let delay = Delay::new(cp.SYST, clocks);
|
|
||||||
|
|
||||||
// USART3
|
|
||||||
// Configure pb10 as a push_pull output, this will be the tx pin
|
|
||||||
let tx = gpiob.pb10.into_alternate_push_pull(&mut gpiob.crh);
|
|
||||||
// Take ownership over pb11
|
|
||||||
let rx = gpiob.pb11;
|
|
||||||
|
|
||||||
// Set up the usart device. Taks ownership over the USART register and tx/rx pins. The rest of
|
|
||||||
// the registers are used to enable and configure the device.
|
|
||||||
let serial = Serial::usart3(
|
|
||||||
dp.USART3,
|
|
||||||
(tx, rx),
|
|
||||||
&mut afio.mapr,
|
|
||||||
Config::default().baudrate(9600.bps()),
|
|
||||||
clocks,
|
|
||||||
&mut rcc.apb1,
|
|
||||||
);
|
|
||||||
|
|
||||||
let gps_parser = Parser::new();
|
|
||||||
|
|
||||||
let scl = gpiob.pb6.into_alternate_open_drain(&mut gpiob.crl);
|
|
||||||
let sda = gpiob.pb7.into_alternate_open_drain(&mut gpiob.crl);
|
|
||||||
let mut i2c = i2c::BlockingI2c::i2c1(
|
|
||||||
dp.I2C1,
|
|
||||||
(scl, sda),
|
|
||||||
&mut afio.mapr,
|
|
||||||
i2c::Mode::Standard {
|
|
||||||
frequency: 400_000.hz(),
|
|
||||||
},
|
|
||||||
clocks,
|
|
||||||
&mut rcc.apb1,
|
|
||||||
5,
|
|
||||||
1,
|
|
||||||
5,
|
|
||||||
5,
|
|
||||||
);
|
|
||||||
|
|
||||||
time::setup(dp.TIM2, &clocks, &mut rcc.apb1);
|
|
||||||
|
|
||||||
App {
|
|
||||||
delay,
|
|
||||||
board_led,
|
|
||||||
serial,
|
|
||||||
i2c,
|
|
||||||
gps_parser,
|
|
||||||
locator: arrayvec::ArrayString::new(),
|
|
||||||
transmitting: false,
|
|
||||||
}
|
|
||||||
}
|
|
30
src/lib.rs
30
src/lib.rs
|
@ -1,30 +0,0 @@
|
||||||
#![no_std]
|
|
||||||
use defmt_rtt as _; // global logger
|
|
||||||
|
|
||||||
use panic_probe as _;
|
|
||||||
use stm32f1xx_hal as _;
|
|
||||||
|
|
||||||
pub mod application;
|
|
||||||
pub mod loc;
|
|
||||||
mod si5153;
|
|
||||||
pub mod time;
|
|
||||||
pub mod wspr;
|
|
||||||
|
|
||||||
// same panicking *behavior* as `panic-probe` but doesn't print a panic message
|
|
||||||
// this prevents the panic message being printed *twice* when `defmt::panic` is invoked
|
|
||||||
#[defmt::panic_handler]
|
|
||||||
fn panic() -> ! {
|
|
||||||
cortex_m::asm::udf()
|
|
||||||
}
|
|
||||||
|
|
||||||
defmt::timestamp!("{=u32}", {
|
|
||||||
// NOTE(no-CAS) `timestamps` runs with interrupts disabled
|
|
||||||
time::get_timestamp()
|
|
||||||
});
|
|
||||||
|
|
||||||
/// Terminates the application and makes `probe-run` exit with exit-code = 0
|
|
||||||
pub fn exit() -> ! {
|
|
||||||
loop {
|
|
||||||
cortex_m::asm::bkpt();
|
|
||||||
}
|
|
||||||
}
|
|
319
src/main.rs
319
src/main.rs
|
@ -1,17 +1,310 @@
|
||||||
#![no_std]
|
|
||||||
#![no_main]
|
#![no_main]
|
||||||
|
#![no_std]
|
||||||
|
use defmt_rtt as _; // global logger
|
||||||
|
|
||||||
use stm32f1xx_hal::pac;
|
use panic_probe as _;
|
||||||
|
use stm32f1xx_hal as _;
|
||||||
|
|
||||||
use wspr_beacon::application;
|
// same panicking *behavior* as `panic-probe` but doesn't print a panic message
|
||||||
|
// this prevents the panic message being printed *twice* when `defmt::panic` is invoked
|
||||||
#[cortex_m_rt::entry]
|
#[defmt::panic_handler]
|
||||||
fn main() -> ! {
|
fn panic() -> ! {
|
||||||
// Get access to the core peripherals from the cortex-m crate
|
cortex_m::asm::udf()
|
||||||
let cp = cortex_m::Peripherals::take().unwrap();
|
}
|
||||||
// Get access to the device specific peripherals from the peripheral access crate
|
|
||||||
let dp = pac::Peripherals::take().unwrap();
|
use rtic::app;
|
||||||
|
|
||||||
let app = application::setup(cp, dp);
|
mod loc;
|
||||||
app.run()
|
mod si5153;
|
||||||
|
mod wspr;
|
||||||
|
|
||||||
|
#[app(device = stm32f1xx_hal::pac, peripherals = true, dispatchers = [TIM3])]
|
||||||
|
mod app {
|
||||||
|
|
||||||
|
use stm32f1xx_hal::{
|
||||||
|
gpio::{gpiob, gpioc, Alternate, Floating, Input, OpenDrain, Output, PushPull},
|
||||||
|
i2c,
|
||||||
|
i2c::BlockingI2c,
|
||||||
|
pac::I2C1,
|
||||||
|
prelude::*,
|
||||||
|
serial::{Config, Serial},
|
||||||
|
stm32,
|
||||||
|
timer::{self, Event},
|
||||||
|
};
|
||||||
|
|
||||||
|
use systick_monotonic::Systick;
|
||||||
|
|
||||||
|
use arrayvec::ArrayString;
|
||||||
|
|
||||||
|
use nmea0183::{datetime::Time, ParseResult, Parser};
|
||||||
|
|
||||||
|
use crate::loc;
|
||||||
|
use crate::si5153;
|
||||||
|
use crate::wspr;
|
||||||
|
|
||||||
|
type AppI2C1 = BlockingI2c<
|
||||||
|
I2C1,
|
||||||
|
(
|
||||||
|
gpiob::PB6<Alternate<OpenDrain>>,
|
||||||
|
gpiob::PB7<Alternate<OpenDrain>>,
|
||||||
|
),
|
||||||
|
>;
|
||||||
|
|
||||||
|
#[monotonic(binds = SysTick, default = true)]
|
||||||
|
type MonoTimer = Systick<1000>;
|
||||||
|
|
||||||
|
#[shared]
|
||||||
|
struct Shared {
|
||||||
|
time: Time,
|
||||||
|
locator: ArrayString<6>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[local]
|
||||||
|
struct Local {
|
||||||
|
gps_parser: Parser,
|
||||||
|
usart: Serial<
|
||||||
|
stm32::USART3,
|
||||||
|
(
|
||||||
|
gpiob::PB10<Alternate<PushPull>>,
|
||||||
|
gpiob::PB11<Input<Floating>>,
|
||||||
|
),
|
||||||
|
>,
|
||||||
|
pll: si5153::Si5153<AppI2C1>,
|
||||||
|
i2c: AppI2C1,
|
||||||
|
board_led: gpioc::PC13<Output<PushPull>>,
|
||||||
|
transmitting: bool,
|
||||||
|
symbol_pos: usize,
|
||||||
|
message: [u8; 162],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[init]
|
||||||
|
fn init(cx: init::Context) -> (Shared, Local, init::Monotonics) {
|
||||||
|
let mut flash = cx.device.FLASH.constrain();
|
||||||
|
let rcc = cx.device.RCC.constrain();
|
||||||
|
|
||||||
|
// Freeze the configuration of all the clocks in the system and store the frozen frequencies in
|
||||||
|
// `clocks`
|
||||||
|
let clocks = rcc
|
||||||
|
.cfgr
|
||||||
|
.use_hse(8.MHz())
|
||||||
|
.sysclk(72.MHz())
|
||||||
|
.pclk1(36.MHz())
|
||||||
|
.freeze(&mut flash.acr);
|
||||||
|
|
||||||
|
defmt::info!("Clock Setup done");
|
||||||
|
|
||||||
|
let mono = Systick::new(cx.core.SYST, clocks.pclk1().to_Hz());
|
||||||
|
|
||||||
|
let mut afio = cx.device.AFIO.constrain();
|
||||||
|
|
||||||
|
let mut timer = timer::Timer2::new(cx.device.TIM2, &clocks).counter_hz();
|
||||||
|
timer.start(1.kHz()).unwrap();
|
||||||
|
// Generate an interrupt when the timer expires
|
||||||
|
timer.listen(Event::Update);
|
||||||
|
|
||||||
|
// Acquire the GPIOC peripheral
|
||||||
|
let mut gpiob = cx.device.GPIOB.split();
|
||||||
|
let mut gpioc = cx.device.GPIOC.split();
|
||||||
|
|
||||||
|
let board_led = gpioc.pc13.into_push_pull_output(&mut gpioc.crh);
|
||||||
|
|
||||||
|
// USART3
|
||||||
|
// Configure pb10 as a push_pull output, this will be the tx pin
|
||||||
|
let tx = gpiob.pb10.into_alternate_push_pull(&mut gpiob.crh);
|
||||||
|
// Take ownership over pb11
|
||||||
|
let rx = gpiob.pb11;
|
||||||
|
|
||||||
|
// Set up the usart device. Taks ownership over the USART register and tx/rx pins. The rest of
|
||||||
|
// the registers are used to enable and configure the device.
|
||||||
|
let usart = Serial::usart3(
|
||||||
|
cx.device.USART3,
|
||||||
|
(tx, rx),
|
||||||
|
&mut afio.mapr,
|
||||||
|
Config::default().baudrate(9600.bps()),
|
||||||
|
clocks,
|
||||||
|
);
|
||||||
|
|
||||||
|
let gps_parser = Parser::new();
|
||||||
|
|
||||||
|
let scl = gpiob.pb6.into_alternate_open_drain(&mut gpiob.crl);
|
||||||
|
let sda = gpiob.pb7.into_alternate_open_drain(&mut gpiob.crl);
|
||||||
|
let i2c = i2c::BlockingI2c::i2c1(
|
||||||
|
cx.device.I2C1,
|
||||||
|
(scl, sda),
|
||||||
|
&mut afio.mapr,
|
||||||
|
i2c::Mode::Standard {
|
||||||
|
frequency: 400.kHz(),
|
||||||
|
},
|
||||||
|
clocks,
|
||||||
|
5,
|
||||||
|
1,
|
||||||
|
5,
|
||||||
|
5,
|
||||||
|
);
|
||||||
|
|
||||||
|
let pll = si5153::Si5153::new(&i2c);
|
||||||
|
|
||||||
|
let time = Time {
|
||||||
|
hours: 0,
|
||||||
|
minutes: 0,
|
||||||
|
seconds: 0.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let locator = ArrayString::new();
|
||||||
|
|
||||||
|
transmit::spawn().unwrap();
|
||||||
|
|
||||||
|
(
|
||||||
|
Shared { time, locator },
|
||||||
|
Local {
|
||||||
|
gps_parser,
|
||||||
|
usart,
|
||||||
|
i2c,
|
||||||
|
pll,
|
||||||
|
board_led,
|
||||||
|
transmitting: false,
|
||||||
|
symbol_pos: 0,
|
||||||
|
message: [0; 162],
|
||||||
|
},
|
||||||
|
init::Monotonics(mono),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const CALLSIGN: &str = "DL1SSk";
|
||||||
|
const POWER: u8 = 37;
|
||||||
|
const WSPR_SYMBOL_LENGTH: u32 = 683;
|
||||||
|
|
||||||
|
// For PLL with 800MHz
|
||||||
|
const WSPR_SYMBOLS: [si5153::PllParams; 4] = [
|
||||||
|
// 7040100.000000: 113 + 44687 / 70401
|
||||||
|
// actual: 7040100.000000
|
||||||
|
si5153::PllParams {
|
||||||
|
p1: 14033,
|
||||||
|
p2: 17455,
|
||||||
|
p3: 70401,
|
||||||
|
},
|
||||||
|
// 7040101.464844: 113 + 447801 / 705503
|
||||||
|
// actual: 7040101.464844
|
||||||
|
si5153::PllParams {
|
||||||
|
p1: 14033,
|
||||||
|
p2: 172785,
|
||||||
|
p3: 705503,
|
||||||
|
},
|
||||||
|
// 7040102.929688: 113 + 38515 / 60682
|
||||||
|
// actual: 7040102.929688
|
||||||
|
si5153::PllParams {
|
||||||
|
p1: 14033,
|
||||||
|
p2: 14678,
|
||||||
|
p3: 60682,
|
||||||
|
},
|
||||||
|
// 7040104.394531: 113 + 123233 / 194166
|
||||||
|
// actual: 7040104.394531
|
||||||
|
si5153::PllParams {
|
||||||
|
p1: 14033,
|
||||||
|
p2: 46378,
|
||||||
|
p3: 194166,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
#[task(local=[pll, i2c, transmitting, symbol_pos, message], shared=[time, locator])]
|
||||||
|
fn transmit(mut ctx: transmit::Context) {
|
||||||
|
if !*ctx.local.transmitting {
|
||||||
|
let loc = ctx.shared.locator.lock(|l| l.clone());
|
||||||
|
if loc.is_empty() {
|
||||||
|
transmit::spawn_after(10.secs().into()).unwrap();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let time = ctx.shared.time.lock(|l| l.clone());
|
||||||
|
if time.minutes % 2 != 0 || time.seconds < 1.0 || time.seconds > 2.0 {
|
||||||
|
transmit::spawn_after(100.millis().into()).unwrap();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
defmt::info!("Starting tranmission");
|
||||||
|
let callsign = arrayvec::ArrayString::<6>::from(CALLSIGN).unwrap();
|
||||||
|
*ctx.local.message = wspr::encode_message(&callsign, &loc, POWER);
|
||||||
|
*ctx.local.symbol_pos = 0;
|
||||||
|
*ctx.local.transmitting = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sym = ctx.local.message[*ctx.local.symbol_pos] as usize;
|
||||||
|
defmt::info!("transmitting symbol {} : {}", *ctx.local.symbol_pos, sym);
|
||||||
|
|
||||||
|
ctx.local.pll.write_synth_params(
|
||||||
|
&mut ctx.local.i2c,
|
||||||
|
si5153::Multisynth::MS0,
|
||||||
|
&WSPR_SYMBOLS[sym],
|
||||||
|
);
|
||||||
|
|
||||||
|
if *ctx.local.symbol_pos == 0 {
|
||||||
|
ctx.local
|
||||||
|
.pll
|
||||||
|
.enable_ms_output(&mut ctx.local.i2c, si5153::Multisynth::MS0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if *ctx.local.symbol_pos < 162 {
|
||||||
|
*ctx.local.symbol_pos += 1;
|
||||||
|
transmit::spawn_after(WSPR_SYMBOL_LENGTH.millis().into()).unwrap();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
defmt::info!("Stopping transmission.");
|
||||||
|
ctx.local
|
||||||
|
.pll
|
||||||
|
.disable_ms_output(&mut ctx.local.i2c, si5153::Multisynth::MS0);
|
||||||
|
*ctx.local.transmitting = false;
|
||||||
|
transmit::spawn_after(100.millis().into()).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[task(binds = USART3, local=[usart, board_led, gps_parser], shared=[time, locator])]
|
||||||
|
fn usart3(mut ctx: usart3::Context) {
|
||||||
|
match ctx.local.usart.read() {
|
||||||
|
Ok(byte) => {
|
||||||
|
ctx.local.board_led.toggle();
|
||||||
|
if let Some(result) = ctx.local.gps_parser.parse_from_byte(byte) {
|
||||||
|
match result {
|
||||||
|
Ok(ParseResult::GGA(Some(gga))) => {
|
||||||
|
ctx.shared.time.lock(|time| {
|
||||||
|
time.hours = gga.time.hours;
|
||||||
|
time.minutes = gga.time.minutes;
|
||||||
|
time.seconds = gga.time.seconds;
|
||||||
|
});
|
||||||
|
|
||||||
|
ctx.shared.locator.lock(|locator| {
|
||||||
|
*locator = loc::locator_from_coordinates(
|
||||||
|
gga.latitude.as_f64(),
|
||||||
|
gga.longitude.as_f64(),
|
||||||
|
);
|
||||||
|
defmt::info!("Got GGA. New locator: {}", locator.as_str());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(_) => {} // Some other sentences..
|
||||||
|
Err(_) => {} // Got parse error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(nb::Error::WouldBlock) => {}
|
||||||
|
Err(nb::Error::Other(_)) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[task(binds = TIM2, shared=[time])]
|
||||||
|
fn tim2(mut ctx: tim2::Context) {
|
||||||
|
ctx.shared.time.lock(|time| {
|
||||||
|
time.seconds += 0.001;
|
||||||
|
|
||||||
|
if time.seconds >= 60.0 {
|
||||||
|
time.seconds -= 60.0;
|
||||||
|
time.minutes += 1;
|
||||||
|
}
|
||||||
|
if time.minutes == 60 {
|
||||||
|
time.minutes = 0;
|
||||||
|
time.hours += 1;
|
||||||
|
}
|
||||||
|
if time.hours == 24 {
|
||||||
|
time.hours = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
104
src/time.rs
104
src/time.rs
|
@ -1,104 +0,0 @@
|
||||||
use core::cell::RefCell;
|
|
||||||
use core::ops::DerefMut;
|
|
||||||
use cortex_m::interrupt::{free, Mutex};
|
|
||||||
use stm32f1xx_hal::{
|
|
||||||
delay::Delay,
|
|
||||||
pac::interrupt,
|
|
||||||
pac::{Interrupt, TIM2},
|
|
||||||
prelude::*,
|
|
||||||
rcc::{Clocks, APB1},
|
|
||||||
serial::{Config, Serial},
|
|
||||||
stm32,
|
|
||||||
timer::{CountDownTimer, Event, Timer},
|
|
||||||
};
|
|
||||||
|
|
||||||
use nmea0183::datetime::Time;
|
|
||||||
|
|
||||||
static TIME: Mutex<RefCell<Time>> = Mutex::new(RefCell::new(Time {
|
|
||||||
hours: 0,
|
|
||||||
minutes: 0,
|
|
||||||
seconds: 0.0,
|
|
||||||
}));
|
|
||||||
|
|
||||||
static TIMER_TIM2: Mutex<RefCell<Option<CountDownTimer<TIM2>>>> = Mutex::new(RefCell::new(None));
|
|
||||||
|
|
||||||
pub fn setup(tim2: TIM2, clocks: &Clocks, apb1: &mut APB1) {
|
|
||||||
let mut timer = Timer::tim2(tim2, clocks, apb1).start_count_down(1.khz());
|
|
||||||
// Generate an interrupt when the timer expires
|
|
||||||
timer.listen(Event::Update);
|
|
||||||
// Move the timer into our global storage
|
|
||||||
cortex_m::interrupt::free(|cs| *TIMER_TIM2.borrow(cs).borrow_mut() = Some(timer));
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
cortex_m::peripheral::NVIC::unmask(Interrupt::TIM2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[interrupt]
|
|
||||||
fn TIM2() {
|
|
||||||
free(|cs| {
|
|
||||||
if let Some(ref mut tim2) = TIMER_TIM2.borrow(cs).borrow_mut().deref_mut() {
|
|
||||||
tim2.clear_update_interrupt_flag();
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut time = TIME.borrow(cs).borrow_mut();
|
|
||||||
time.seconds += 0.001;
|
|
||||||
|
|
||||||
if time.seconds >= 60.0 {
|
|
||||||
time.seconds -= 60.0;
|
|
||||||
time.minutes += 1;
|
|
||||||
}
|
|
||||||
if time.minutes == 60 {
|
|
||||||
time.minutes = 0;
|
|
||||||
time.hours += 1;
|
|
||||||
}
|
|
||||||
if time.hours == 24 {
|
|
||||||
time.hours = 0;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get() -> Time {
|
|
||||||
let mut res = Time {
|
|
||||||
hours: 0,
|
|
||||||
minutes: 0,
|
|
||||||
seconds: 0.0,
|
|
||||||
};
|
|
||||||
|
|
||||||
free(|cs| {
|
|
||||||
let time = TIME.borrow(cs).borrow();
|
|
||||||
res.seconds = time.seconds;
|
|
||||||
res.minutes = time.minutes;
|
|
||||||
res.hours = time.hours;
|
|
||||||
});
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set(new_time: &Time) -> () {
|
|
||||||
free(|cs| {
|
|
||||||
let mut time = TIME.borrow(cs).borrow_mut();
|
|
||||||
let sec_delta = time.seconds - new_time.seconds;
|
|
||||||
if time.hours != new_time.hours
|
|
||||||
|| time.minutes != new_time.minutes
|
|
||||||
|| sec_delta > 0.05
|
|
||||||
|| sec_delta < -0.05
|
|
||||||
{
|
|
||||||
time.seconds = new_time.seconds;
|
|
||||||
time.minutes = new_time.minutes;
|
|
||||||
time.hours = new_time.hours;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_timestamp() -> u32 {
|
|
||||||
let mut res = 0;
|
|
||||||
free(|cs| {
|
|
||||||
let time = TIME.borrow(cs).borrow();
|
|
||||||
res = (time.seconds * 1000.0) as u32;
|
|
||||||
res += (time.minutes as u32) * 60 * 1000;
|
|
||||||
res += (time.hours as u32) * 60 * 60 * 1000;
|
|
||||||
});
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
[package]
|
|
||||||
# TODO(1) fix `authors` if you didn't use `cargo-generate`
|
|
||||||
authors = ["LongHairedHacker <sebastian@sebastians-site.de>"]
|
|
||||||
name = "testsuite"
|
|
||||||
publish = false
|
|
||||||
edition = "2018"
|
|
||||||
version = "0.1.0"
|
|
||||||
|
|
||||||
[[test]]
|
|
||||||
name = "loc"
|
|
||||||
harness = false
|
|
||||||
|
|
||||||
[[test]]
|
|
||||||
name = "wspr"
|
|
||||||
harness = false
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
wspr-beacon = { path = ".." }
|
|
||||||
cortex-m = "0.7.1"
|
|
||||||
cortex-m-rt = "0.6.12"
|
|
||||||
defmt = "0.2.0"
|
|
||||||
defmt-rtt = "0.2.0"
|
|
||||||
defmt-test = "0.2.0"
|
|
||||||
panic-probe = { version = "0.2.0", features = ["print-defmt"] }
|
|
||||||
arrayvec = {version = "~0.7.0", default-features = false}
|
|
||||||
|
|
||||||
[features]
|
|
||||||
# set logging levels here
|
|
||||||
default = [
|
|
||||||
# in tests, enable all logs
|
|
||||||
"defmt-trace",
|
|
||||||
# "dependency-a/defmt-trace",
|
|
||||||
]
|
|
||||||
|
|
||||||
# do NOT modify these features
|
|
||||||
defmt-default = []
|
|
||||||
defmt-trace = []
|
|
||||||
defmt-debug = []
|
|
||||||
defmt-info = []
|
|
||||||
defmt-warn = []
|
|
||||||
defmt-error = []
|
|
|
@ -1,56 +0,0 @@
|
||||||
#![no_std]
|
|
||||||
#![no_main]
|
|
||||||
|
|
||||||
use wspr_beacon as _; // memory layout + panic handler
|
|
||||||
|
|
||||||
// See https://crates.io/crates/defmt-test/0.1.0 for more documentation (e.g. about the 'state'
|
|
||||||
// feature)
|
|
||||||
#[defmt_test::tests]
|
|
||||||
mod tests {
|
|
||||||
use arrayvec::ArrayString;
|
|
||||||
use defmt::{assert, assert_eq};
|
|
||||||
use wspr_beacon::loc;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_locations() {
|
|
||||||
assert_eq!(
|
|
||||||
loc::locator_from_coordinates(49.4395, 7.7635).as_str(),
|
|
||||||
"JN39vk"
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
loc::locator_from_coordinates(74.998306, 148.833363).as_str(),
|
|
||||||
"QQ44kx"
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
loc::locator_from_coordinates(64.701746, -18.640812).as_str(),
|
|
||||||
"IP04qq"
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
loc::locator_from_coordinates(19.251825, -81.541811).as_str(),
|
|
||||||
"EK99fg"
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
loc::locator_from_coordinates(-23.587708, 45.25882).as_str(),
|
|
||||||
"LG26pj"
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
loc::locator_from_coordinates(-77.566746, 167.875652).as_str(),
|
|
||||||
"RB32wk"
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
loc::locator_from_coordinates(-17.632622, -149.472404).as_str(),
|
|
||||||
"BH52gi"
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
loc::locator_from_coordinates(-19.77516, -43.959542).as_str(),
|
|
||||||
"GH80af"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
#![no_std]
|
|
||||||
#![no_main]
|
|
||||||
|
|
||||||
use wspr_beacon as _; // memory layout + panic handler
|
|
||||||
|
|
||||||
// See https://crates.io/crates/defmt-test/0.1.0 for more documentation (e.g. about the 'state'
|
|
||||||
// feature)
|
|
||||||
#[defmt_test::tests]
|
|
||||||
mod tests {
|
|
||||||
use arrayvec::ArrayString;
|
|
||||||
use defmt::{assert, assert_eq, Debug2Format};
|
|
||||||
use wspr_beacon::wspr;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_encoding() {
|
|
||||||
let call_sign = ArrayString::<6>::from(" K1ABC").unwrap();
|
|
||||||
let loc = ArrayString::<6>::from("FN42").unwrap();
|
|
||||||
let symbols = wspr::encode_message(&call_sign, &loc, 37);
|
|
||||||
|
|
||||||
let expected_symbols: [u8; 162] = [
|
|
||||||
3, 3, 0, 0, 2, 0, 0, 0, 1, 0, 2, 0, 1, 3, 1, 2, 2, 2, 1, 0, 0, 3, 2, 3, 1, 3, 3, 2, 2,
|
|
||||||
0, 2, 0, 0, 0, 3, 2, 0, 1, 2, 3, 2, 2, 0, 0, 2, 2, 3, 2, 1, 1, 0, 2, 3, 3, 2, 1, 0, 2,
|
|
||||||
2, 1, 3, 2, 1, 2, 2, 2, 0, 3, 3, 0, 3, 0, 3, 0, 1, 2, 1, 0, 2, 1, 2, 0, 3, 2, 1, 3, 2,
|
|
||||||
0, 0, 3, 3, 2, 3, 0, 3, 2, 2, 0, 3, 0, 2, 0, 2, 0, 1, 0, 2, 3, 0, 2, 1, 1, 1, 2, 3, 3,
|
|
||||||
0, 2, 3, 1, 2, 1, 2, 2, 2, 1, 3, 3, 2, 0, 0, 0, 0, 1, 0, 3, 2, 0, 1, 3, 2, 2, 2, 2, 2,
|
|
||||||
0, 2, 3, 3, 2, 3, 2, 3, 3, 2, 0, 0, 3, 1, 2, 2, 2,
|
|
||||||
];
|
|
||||||
|
|
||||||
for i in 0..162 {
|
|
||||||
assert_eq!(expected_symbols[i], symbols[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue