311 lines
9.0 KiB
Rust
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;
|
|
}
|
|
});
|
|
}
|
|
}
|