wspr-beacon/src/main.rs

311 lines
9.0 KiB
Rust

#![no_main]
#![no_std]
use defmt_rtt as _; // global logger
use panic_probe as _;
use stm32f1xx_hal as _;
// 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()
}
use rtic::app;
mod loc;
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;
}
});
}
}