aboutsummaryrefslogtreecommitdiffstats
path: root/sw
diff options
context:
space:
mode:
authorMatthias P. Braendli <matthias.braendli@mpb.li>2023-03-13 23:14:32 +0100
committerMatthias P. Braendli <matthias.braendli@mpb.li>2023-03-13 23:14:32 +0100
commite04fec33f079db4c5d42aa412f7b670784ec1b68 (patch)
tree3bed3e90faafcd3967dafae293277ad0b6dddd10 /sw
parentde7b4aa1b04e1fcb226c596c819b5ff996c73238 (diff)
downloadpicardy-e04fec33f079db4c5d42aa412f7b670784ec1b68.tar.gz
picardy-e04fec33f079db4c5d42aa412f7b670784ec1b68.tar.bz2
picardy-e04fec33f079db4c5d42aa412f7b670784ec1b68.zip
Add WSPR TX to eval-clock-cw-tx
Diffstat (limited to 'sw')
-rw-r--r--sw/deps/si5351/src/lib.rs164
-rw-r--r--sw/eval-clock-cw-tx/README.md29
-rwxr-xr-xsw/eval-clock-cw-tx/gui.py16
-rw-r--r--sw/eval-clock-cw-tx/src/main.rs156
-rw-r--r--sw/eval-clock-cw-tx/src/si_clock.rs36
-rw-r--r--sw/eval-clock-cw-tx/src/state.rs11
-rw-r--r--sw/eval-clock-cw-tx/src/ui.rs4
-rw-r--r--sw/eval-clock-cw-tx/src/usb.rs45
8 files changed, 220 insertions, 241 deletions
diff --git a/sw/deps/si5351/src/lib.rs b/sw/deps/si5351/src/lib.rs
index a23d4f9..9f3a2a9 100644
--- a/sw/deps/si5351/src/lib.rs
+++ b/sw/deps/si5351/src/lib.rs
@@ -6,77 +6,6 @@
http://opensource.org/licenses/MIT>, at your option. This file may not be
copied, modified, or distributed except according to those terms.
*/
-/*!
-A platform agnostic Rust driver for the [Si5351], based on the
-[`embedded-hal`] traits.
-
-## The Device
-
-The Silicon Labs [Si5351] is an any-frequency CMOS clock generator.
-
-The device has an I²C interface.
-
-## Usage
-
-Import this crate and an `embedded_hal` implementation:
-
-```
-extern crate stm32f103xx_hal as hal;
-extern crate si5351;
-```
-
-Initialize I²C bus (differs between `embedded_hal` implementations):
-
-```no_run
-# extern crate stm32f103xx_hal as hal;
-use hal::i2c::I2c;
-type I2C = ...;
-
-# fn main() {
-let i2c: I2C = initialize_i2c();
-# }
-```
-
-Then instantiate the device:
-
-```no_run
-# extern crate stm32f103xx_hal as hal;
-# extern crate si5351;
-use si5351;
-use si5351::{Si5351, Si5351Device};
-
-# fn main() {
-let mut clock = Si5351Device<I2C>::new(i2c, false, 25_000_000);
-clock.init(si5351::CrystalLoad::_10)?;
-# }
-```
-
-Or, if you have an [Adafruit module], you can use shortcut functions to initializate it:
-```no_run
-# extern crate stm32f103xx_hal as hal;
-# extern crate si5351;
-use si5351;
-use si5351::{Si5351, Si5351Device};
-
-# fn main() {
-let mut clock = Si5351Device<I2C>::new_adafruit_module(i2c);
-clock.init_adafruit_module()?;
-# }
-```
-
-And set frequency on one of the outputs:
-
-```no_run
-use si5351;
-
-clock.set_frequency(si5351::PLL::A, si5351::ClockOutput::Clk0, 14_175_000)?;
-```
-
-[Si5351]: https://www.silabs.com/documents/public/data-sheets/Si5351-B.pdf
-[`embedded-hal`]: https://github.com/japaric/embedded-hal
-[Adafruit module]: https://www.adafruit.com/product/2045
-*/
-//#![deny(missing_docs)]
#![deny(warnings)]
#![no_std]
@@ -304,33 +233,6 @@ impl OutputDivider {
fn bits(&self) -> u8 {
*self as u8
}
-
- fn min_divider(desired_divider: u16) -> Result<OutputDivider, Error> {
- match 16 - (desired_divider.max(1) - 1).leading_zeros() {
- 0 => Ok(OutputDivider::Div1),
- 1 => Ok(OutputDivider::Div2),
- 2 => Ok(OutputDivider::Div4),
- 3 => Ok(OutputDivider::Div8),
- 4 => Ok(OutputDivider::Div16),
- 5 => Ok(OutputDivider::Div32),
- 6 => Ok(OutputDivider::Div64),
- 7 => Ok(OutputDivider::Div128),
- _ => Err(Error::InvalidParameter)
- }
- }
-
- fn denominator_u8(&self) -> u8 {
- match *self {
- OutputDivider::Div1 => 1,
- OutputDivider::Div2 => 2,
- OutputDivider::Div4 => 4,
- OutputDivider::Div8 => 8,
- OutputDivider::Div16 => 16,
- OutputDivider::Div32 => 32,
- OutputDivider::Div64 => 64,
- OutputDivider::Div128 => 128,
- }
- }
}
fn i2c_error<E>(_: E) -> Error {
@@ -341,7 +243,6 @@ fn i2c_error<E>(_: E) -> Error {
pub struct Si5351Device<I2C> {
i2c: I2C,
address: u8,
- xtal_freq: u32,
clk_enabled_mask: u8,
ms_int_mode_mask: u8,
ms_src_mask: u8,
@@ -352,10 +253,6 @@ pub trait Si5351 {
fn init(&mut self, xtal_load: CrystalLoad) -> Result<(), Error>;
fn read_device_status(&mut self) -> Result<DeviceStatusBits, Error>;
- fn find_int_dividers_for_max_pll_freq(&self, max_pll_freq: u32, freq: u32) -> Result<(u16, OutputDivider), Error>;
- fn find_pll_coeffs_for_dividers(&self, total_div: u32, denom: u32, freq: u32) -> Result<(u8, u32), Error>;
-
- fn set_frequency(&mut self, pll: PLL, clk: ClockOutput, freq: u32) -> Result<(), Error>;
fn set_clock_enabled(&mut self, clk: ClockOutput, enabled: bool);
fn flush_output_enabled(&mut self) -> Result<(), Error>;
@@ -373,11 +270,10 @@ impl<I2C, E> Si5351Device<I2C>
I2C: WriteRead<Error=E> + Write<Error=E>,
{
/// Creates a new driver from a I2C peripheral
- pub fn new(i2c: I2C, address_bit: bool, xtal_freq: u32) -> Self {
+ pub fn new(i2c: I2C, address_bit: bool) -> Self {
let si5351 = Si5351Device {
i2c,
address: ADDRESS | if address_bit { 1 } else { 0 },
- xtal_freq,
clk_enabled_mask: 0,
ms_int_mode_mask: 0,
ms_src_mask: 0,
@@ -386,10 +282,6 @@ impl<I2C, E> Si5351Device<I2C>
si5351
}
- pub fn new_adafruit_module(i2c: I2C) -> Self {
- Si5351Device::new(i2c, false, 25_000_000)
- }
-
fn write_ms_config<MS: FractionalMultisynth + Copy>(&mut self, ms: MS, int: u16, frac_num: u32, frac_denom: u32, r_div: OutputDivider) -> Result<(), Error> {
if frac_denom == 0 {
return Err(Error::InvalidParameter);
@@ -500,60 +392,6 @@ impl<I2C, E> Si5351 for Si5351Device<I2C> where
Ok(DeviceStatusBits::from_bits_truncate(self.read_register(Register::DeviceStatus)?))
}
- fn find_int_dividers_for_max_pll_freq(&self, max_pll_freq: u32, freq: u32) -> Result<(u16, OutputDivider), Error> {
- let total_divider = (max_pll_freq / freq) as u16;
-
- let r_div = OutputDivider::min_divider(total_divider / 900)?;
-
- let ms_div = (total_divider / (2 * r_div.denominator_u8() as u16) * 2).max(6);
- if ms_div > 1800 {
- return Err(Error::InvalidParameter);
- }
-
- Ok((ms_div, r_div))
- }
-
- fn find_pll_coeffs_for_dividers(&self, total_div: u32, denom: u32, freq: u32) -> Result<(u8, u32), Error> {
- if denom == 0 || denom > 0xfffff {
- return Err(Error::InvalidParameter);
- }
-
- let pll_freq = freq * total_div;
-
- let mult = (pll_freq / self.xtal_freq) as u8;
- let f = ((pll_freq % self.xtal_freq) as u64 * denom as u64 / self.xtal_freq as u64) as u32;
-
- Ok((mult, f))
- }
-
- fn set_frequency(&mut self, pll: PLL, clk: ClockOutput, freq: u32) -> Result<(), Error> {
- let denom: u32 = 1048575;
-
- let (ms_divider, r_div) = self.find_int_dividers_for_max_pll_freq(900_000_000, freq)?;
- let total_div = ms_divider as u32 * r_div.denominator_u8() as u32;
- let (mult, num) = self.find_pll_coeffs_for_dividers(total_div, denom, freq)?;
-
- let ms = match clk {
- ClockOutput::Clk0 => Multisynth::MS0,
- ClockOutput::Clk1 => Multisynth::MS1,
- ClockOutput::Clk2 => Multisynth::MS2,
- ClockOutput::Clk3 => Multisynth::MS3,
- ClockOutput::Clk4 => Multisynth::MS4,
- ClockOutput::Clk5 => Multisynth::MS5,
- _ => return Err(Error::InvalidParameter),
- };
-
- self.setup_pll(pll, mult, num, denom)?;
- self.setup_multisynth_int(ms, ms_divider, r_div)?;
- self.select_clock_pll(clk, pll);
- self.set_clock_enabled(clk, true);
- self.flush_clock_control(clk)?;
- self.reset_pll(pll)?;
- self.flush_output_enabled()?;
-
- Ok(())
- }
-
fn set_clock_enabled(&mut self, clk: ClockOutput, enabled: bool) {
let bit = 1u8 << clk.ix();
if enabled {
diff --git a/sw/eval-clock-cw-tx/README.md b/sw/eval-clock-cw-tx/README.md
index 5a22503..c83d0ba 100644
--- a/sw/eval-clock-cw-tx/README.md
+++ b/sw/eval-clock-cw-tx/README.md
@@ -1,5 +1,5 @@
-CW TX firmware for eval-clock
-=============================
+CW TX firmware for eval-clock (2021)
+====================================
Sends CW and FELDHELL https://www.qsl.net/zl1bpu/HELL/Feld.htm
@@ -11,6 +11,31 @@ pios are different than on picardy: both Si5351 and display are on same i2c
PB3 is PTTOUTn
PB15 is CWOUTn
+WSPR beacon (Added 2023)
+------------------------
+
+Let's try to make a WSPR beacon too. Transmit trigger happens with the USB message `wspr\n`, the message itself
+is fixed at compile time.
+
+Centered on 10140.200 kHz plus/minus 100Hz, which is set by USB message too.
+
+From https://swharden.com/software/FSKview/wspr/ :
+Each symbol represents a frequency shift of 12000 / 8192 Hz (1.46Hz) per symbol value giving four-level multi-FSK
+modulation. The transmitted symbol length is the reciprocal of the tone spacing, or approximately 0.683 seconds, so the
+complete message of 162 symbols takes around 110.6 seconds to send and occupies a bandwidth of approximately 6Hz.
+
+12000/8192 = 375/256
+
+Transmission starts one second into an even UTC minute.
+
+HB9EGM JN36 1W is `75 1D C4 F7 A0 17 80`. Tones:
+
+ 1, 1, 2, 0, 0, 2, 2, 2, 1, 2, 0, 0, 3, 3, 1, 0, 2, 2, 3, 2, 0, 1, 0, 1, 3, 3, 1, 0, 2, 2, 0, 0, 2, 0, 3, 2, 0, 3, 0,
+ 1, 2, 0, 2, 0, 2, 0, 3, 2, 1, 1, 0, 2, 3, 3, 0, 3, 0, 2, 2, 3, 1, 0, 3, 0, 2, 2, 0, 1, 3, 2, 3, 0, 3, 2, 3, 0, 1, 0,
+ 0, 3, 0, 0, 1, 2, 1, 3, 0, 2, 2, 1, 3, 2, 3, 0, 3, 2, 0, 0, 3, 2, 0, 0, 0, 2, 3, 2, 0, 1, 2, 0, 1, 1, 1, 0, 3, 1, 2,
+ 2, 1, 1, 0, 3, 2, 2, 2, 1, 1, 1, 0, 0, 2, 2, 2, 3, 0, 3, 2, 2, 3, 3, 0, 0, 2, 2, 2, 2, 0, 3, 3, 0, 1, 2, 1, 3, 0, 0,
+ 0, 1, 1, 2, 2, 0
+
Programming
-----------
diff --git a/sw/eval-clock-cw-tx/gui.py b/sw/eval-clock-cw-tx/gui.py
index bf7e54e..a1614a5 100755
--- a/sw/eval-clock-cw-tx/gui.py
+++ b/sw/eval-clock-cw-tx/gui.py
@@ -2,7 +2,7 @@
#
# The MIT License (MIT)
#
-# Copyright (c) 2021 Matthias P. Braendli
+# Copyright (c) 2023 Matthias P. Braendli
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
@@ -144,6 +144,9 @@ class GUI(Frame):
def toggle_to_rx(self):
self.root.after(1000, self.send_serial, b'rx\n')
+ def send_wspr(self):
+ self.root.after(1000, self.send_serial, b'wspr\n')
+
def load_macro(self, i):
self.tx_messages.append(self.macro_widgets[i]["var"].get())
self.tx_messages_var.set(self.tx_messages)
@@ -254,10 +257,13 @@ class GUI(Frame):
self.timer_enable_checkbox.grid(column=0, row=3, columnspan=1)
- self.tx_frame = Frame(self.control_frame, borderwidth=1, relief="ridge")
- self.tx_frame.grid(column=0, row=3, sticky=(N, S, E, W))
- self.tx_button = Button(self.tx_frame, text="Receive!", command=self.toggle_to_rx)
- self.tx_button.grid(column=0, row=0, columnspan=2)
+ self.trx_frame = Frame(self.control_frame, borderwidth=1, relief="ridge")
+ self.trx_frame.grid(column=0, row=3, sticky=(N, S, E, W))
+ self.rx_button = Button(self.trx_frame, text="Receive!", command=self.toggle_to_rx)
+ self.rx_button.grid(column=0, row=0, columnspan=2)
+
+ self.tx_button = Button(self.trx_frame, text="WSPR!", command=self.send_wspr)
+ self.tx_button.grid(column=0, row=2, columnspan=1)
self.transmit_frame = Frame(self.top_frame, borderwidth=1, relief="ridge")
self.transmit_frame.grid(column=0, row=1, columnspan=2)
diff --git a/sw/eval-clock-cw-tx/src/main.rs b/sw/eval-clock-cw-tx/src/main.rs
index b256926..52193b4 100644
--- a/sw/eval-clock-cw-tx/src/main.rs
+++ b/sw/eval-clock-cw-tx/src/main.rs
@@ -25,6 +25,8 @@
#![no_main]
#![no_std]
use core::mem::MaybeUninit;
+use core::fmt::Write;
+use arrayvec::ArrayString;
use cortex_m_rt::ExceptionFrame;
use cortex_m_semihosting::hprintln;
use panic_semihosting as _;
@@ -51,12 +53,23 @@ pub mod si_clock;
use state::*;
-const TICKS_PER_SECOND : u32 = 100;
+// One WSPR symbol is 256/375s
+const WSPR_SYMBOL_TICKS : u32 = 256;
+// TIM2 runs at that rate, and increments TICK_COUNTER
+const TICKS_PER_SECOND : u32 = 375;
+
+// See README.md
+const WSPR_SYMBOLS : [u8; 162] = [
+ 1, 1, 2, 0, 0, 2, 2, 2, 1, 2, 0, 0, 3, 3, 1, 0, 2, 2, 3, 2, 0, 1, 0, 1, 3, 3, 1, 0, 2, 2, 0, 0, 2, 0, 3, 2, 0, 3, 0,
+ 1, 2, 0, 2, 0, 2, 0, 3, 2, 1, 1, 0, 2, 3, 3, 0, 3, 0, 2, 2, 3, 1, 0, 3, 0, 2, 2, 0, 1, 3, 2, 3, 0, 3, 2, 3, 0, 1, 0,
+ 0, 3, 0, 0, 1, 2, 1, 3, 0, 2, 2, 1, 3, 2, 3, 0, 3, 2, 0, 0, 3, 2, 0, 0, 0, 2, 3, 2, 0, 1, 2, 0, 1, 1, 1, 0, 3, 1, 2,
+ 2, 1, 1, 0, 3, 2, 2, 2, 1, 1, 1, 0, 0, 2, 2, 2, 3, 0, 3, 2, 2, 3, 3, 0, 0, 2, 2, 2, 2, 0, 3, 3, 0, 1, 2, 1, 3, 0, 0,
+ 0, 1, 1, 2, 2, 0];
struct SharedWithISR {
state: State,
last_sequence_state_change: u32,
- feldhell_ptt: bool,
+ usb_data_transmit: usb::Transmit,
cw_ptt_timestamp: u32,
cw_key_out_n: gpio::gpioa::PA15<gpio::Output<gpio::PushPull>>,
ui: ui::UI,
@@ -66,7 +79,10 @@ struct SharedWithISR {
cw_paddle_ring: gpio::gpiob::PB9<gpio::Input<gpio::PullUp>>,
ptt_out: gpio::gpiob::PB3<gpio::Output<gpio::PushPull>>,
seq_switch: gpio::gpiob::PB5<gpio::Output<gpio::PushPull>>,
- led : gpio::gpiob::PB14<gpio::Output<gpio::PushPull>>,
+ led: gpio::gpiob::PB14<gpio::Output<gpio::PushPull>>,
+
+ wspr_symbol_ix: usize,
+ wspr_symbol_duration_ticks: u32,
}
static mut SHARED: MaybeUninit<SharedWithISR> = MaybeUninit::uninit();
@@ -188,13 +204,15 @@ fn main() -> ! {
*shared = SharedWithISR {
state: State::new(),
last_sequence_state_change: 0,
- feldhell_ptt: false,
+ usb_data_transmit: usb::Transmit::None,
cw_ptt_timestamp: 0,
cw_key_out_n,
ui,
cw_pwm,
cw_keyer: cw::Keyer::new(12, TICKS_PER_SECOND),
cw_paddle_tip, cw_paddle_ring, ptt_out, seq_switch, led,
+ wspr_symbol_ix: 0,
+ wspr_symbol_duration_ticks: 0,
};
si_clock::SiClock::new(i2c_busmanager.acquire_i2c(), 0, shared.state.vfo_display())
@@ -219,32 +237,70 @@ fn main() -> ! {
unsafe { pac::NVIC::unmask(pac::Interrupt::TIM2); }
let mut last_disp_update_counter = 1;
- let mut previous_usb_freq = usb.frequency;
let mut previous_vfo = 0;
let mut previous_state = SequenceState::Rx;
- loop {
- let mut update_disp_required = false;
+ const MESSAGE_LEN: usize = 32;
+ let mut usb_message: ArrayString::<MESSAGE_LEN> = ArrayString::new();
+ loop {
usb.handle();
- if previous_usb_freq != usb.frequency {
- previous_usb_freq = usb.frequency;
+ let (state_copy, update_siclock_required) = cortex_m::interrupt::free(|_cs| {
+ let shared = unsafe { &mut *SHARED.as_mut_ptr() };
- cortex_m::interrupt::free(|_cs| {
- let shared = unsafe { &mut *SHARED.as_mut_ptr() };
- shared.state.set_vfo(usb.frequency);
- });
+ if shared.wspr_symbol_ix == WSPR_SYMBOLS.len() && shared.wspr_symbol_duration_ticks == WSPR_SYMBOL_TICKS {
+ usb.clear_transmit();
+ shared.wspr_symbol_ix = 0;
+ shared.wspr_symbol_duration_ticks = 0;
+ }
- update_disp_required = true;
- }
+ let mut update_siclock_required = false;
- let state = cortex_m::interrupt::free(|_cs| unsafe {
- let shared = SHARED.as_mut_ptr();
- (*shared).feldhell_ptt = usb.is_transmit();
- (*shared).state.clone()
+ match shared.state.mode {
+ Mode::CW(_) => {
+ let vfo = shared.state.vfo_display();
+ if previous_vfo != vfo || previous_state != shared.state.sequence_state {
+ shared.state.set_vfo(vfo);
+ update_siclock_required = true;
+ }
+ previous_vfo = vfo;
+ }
+ Mode::FeldHell => {
+ if let Some(f) = usb.take_frequency() {
+ shared.state.set_vfo(f);
+ update_siclock_required = true;
+ }
+ },
+ Mode::WSPR => {
+ if shared.state.wspr_freq_offset_updated {
+ update_siclock_required = true;
+ }
+ shared.state.wspr_freq_offset_updated = false;
+ }
+ };
+ shared.usb_data_transmit = usb.transmit();
+
+ (shared.state.clone(), update_siclock_required)
});
+ if update_siclock_required {
+ let f = state_copy.vfo_siclock() * 100
+ + match state_copy.sequence_state {
+ SequenceState::Tx(SequenceMode::WSPR) => state_copy.wspr_freq_offset_centihz,
+ _ => 0,
+ };
+
+ usb_message.clear();
+ if write!(&mut usb_message, "F{}\n", f).is_ok() {
+ usb.send_message(usb_message.as_bytes());
+ }
+ siclock.set_vfo_centihertz(f);
+ }
+
+ previous_state = state_copy.sequence_state.clone();
+
+ let mut update_disp_required = update_siclock_required;
let encoder_count : u16 = qei.count();
if encoder_count != last_encoder_count {
@@ -262,26 +318,19 @@ fn main() -> ! {
update_disp_required = true;
}
- match (previous_state.clone(), state.sequence_state.clone()) {
+ match (previous_state.clone(), state_copy.sequence_state.clone()) {
(SequenceState::Rx, SequenceState::Switching(_)) => usb.send_transmit(),
(SequenceState::Switching(_), SequenceState::Rx) => usb.send_receive(),
_ => (),
}
- let vfo = state.vfo_display();
- if previous_vfo != vfo || previous_state != state.sequence_state {
- siclock.set_vfo(state.vfo_siclock());
- }
- previous_vfo = vfo;
- previous_state = state.sequence_state.clone();
-
- if last_disp_update_counter != state.update_disp_counter {
+ if last_disp_update_counter != state_copy.update_disp_counter {
update_disp_required = true;
- last_disp_update_counter = state.update_disp_counter;
+ last_disp_update_counter = state_copy.update_disp_counter;
}
if update_disp_required {
- ui::update_disp(&mut lcd, &state, &mut delay);
+ ui::update_disp(&mut lcd, &state_copy, &mut delay);
}
last_encoder_count = encoder_count;
@@ -321,14 +370,24 @@ fn TIM2() {
}
},
Mode::FeldHell => {
- shared.feldhell_ptt
+ shared.usb_data_transmit == usb::Transmit::FELDHELL
+ }
+ Mode::WSPR => {
+ if shared.usb_data_transmit == usb::Transmit::WSPR &&
+ shared.wspr_symbol_ix != WSPR_SYMBOLS.len() &&
+ shared.wspr_symbol_duration_ticks != WSPR_SYMBOL_TICKS {
+ true
+ }
+ else {
+ false
+ }
}
};
let cw_beep = match shared.state.mode {
Mode::CW(CWMode::StraightKey) => cw_paddle_tip_low,
Mode::CW(CWMode::Iambic) => shared.cw_keyer.tick(*ticks, cw_paddle_tip_low, cw_paddle_ring_low),
- Mode::FeldHell => false, // Done in usb.c
+ _ => false,
};
let next_state = match shared.state.sequence_state {
@@ -336,11 +395,10 @@ fn TIM2() {
shared.ptt_out.set_low();
shared.seq_switch.set_low();
if ptt {
- if shared.state.mode == Mode::FeldHell {
- SequenceState::Switching(SequenceMode::FeldHell)
- }
- else {
- SequenceState::Switching(SequenceMode::CW)
+ match shared.state.mode {
+ state::Mode::WSPR => SequenceState::Switching(SequenceMode::WSPR),
+ state::Mode::FeldHell => SequenceState::Switching(SequenceMode::FeldHell),
+ state::Mode::CW(_) => SequenceState::Switching(SequenceMode::CW),
}
}
else {
@@ -385,6 +443,30 @@ fn TIM2() {
SequenceState::Tx(SequenceMode::FeldHell) => {
shared.led.set_low();
},
+ SequenceState::Tx(SequenceMode::WSPR) => {
+ shared.cw_key_out_n.set_low();
+ shared.cw_pwm.off();
+ shared.led.set_low();
+
+ if shared.wspr_symbol_duration_ticks < WSPR_SYMBOL_TICKS {
+ shared.wspr_symbol_duration_ticks += 1;
+ }
+
+ if shared.wspr_symbol_duration_ticks == WSPR_SYMBOL_TICKS {
+ if shared.wspr_symbol_ix < WSPR_SYMBOLS.len() {
+ shared.state.wspr_freq_offset_centihz = WSPR_SYMBOLS[shared.wspr_symbol_ix] as u32 * 100 * 375 / 256;
+ shared.state.wspr_freq_offset_updated = true;
+ shared.wspr_symbol_ix += 1;
+ }
+ else {
+ shared.state.wspr_freq_offset_centihz = 0;
+ }
+
+ if shared.wspr_symbol_ix != WSPR_SYMBOLS.len() {
+ shared.wspr_symbol_duration_ticks = 0;
+ }
+ }
+ },
_ => {
shared.led.set_high();
shared.cw_pwm.off();
diff --git a/sw/eval-clock-cw-tx/src/si_clock.rs b/sw/eval-clock-cw-tx/src/si_clock.rs
index 6de4c25..4bd2686 100644
--- a/sw/eval-clock-cw-tx/src/si_clock.rs
+++ b/sw/eval-clock-cw-tx/src/si_clock.rs
@@ -1,7 +1,7 @@
/*
The MIT License (MIT)
- Copyright (c) 2020 Matthias P. Braendli
+ Copyright (c) 2023 Matthias P. Braendli
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -26,10 +26,10 @@ use core::convert::TryInto;
use si5351::{Si5351, Si5351Device};
use embedded_hal::blocking::i2c::{WriteRead, Write};
-const REF_CLOCK : u32 = 25_000_000;
+const REF_CLOCK_CHZ : u32 = 25_000_000 * 100;
const PLL_A_MULT : u32 = 32;
-fn gcd(x: u32, y: u32) -> u32 {
+fn gcd(x: u64, y: u64) -> u64 {
let mut x = x;
let mut y = y;
while y != 0 {
@@ -40,30 +40,32 @@ fn gcd(x: u32, y: u32) -> u32 {
x
}
-fn clock_settings_for_pll(freq: u32, pll: u32) -> (u16, u32, u32) {
+// All calculations in centihertz
+
+fn clock_settings_for_pll(freq: u64, pll: u64) -> (u16, u32, u32) {
let a = pll / freq;
let b = pll - (a * freq);
let gcd = gcd(b, freq);
let b = b / gcd;
let c = freq / gcd;
- (a.try_into().unwrap(), b, c)
+ (a.try_into().unwrap(), b.try_into().unwrap(), c.try_into().unwrap())
}
-fn clock_settings_with_pll_calculation(freq: u32) -> (u16, u8, u32, u32) {
- let mut divider : u32 = 900_000_000 / freq; // Calculate the division ratio. 900,000,000 is the maximum internal
+fn clock_settings_with_pll_calculation(freq_chz: u32) -> (u16, u8, u32, u32) {
+ let mut divider : u32 = (100*900_000_000u64 / freq_chz as u64).try_into().unwrap(); // Calculate the division ratio. 900,000,000 is the maximum internal
if (divider % 2) == 1 {
divider -= 1 // Ensure an even integer division ratio
}
- let pll_freq = divider * freq;
+ let pll_freq : u128 = divider as u128 * freq_chz as u128;
// mult is an integer that must be in the range 15..90
- let mult = pll_freq / REF_CLOCK;
- let l = pll_freq % REF_CLOCK;
+ let mult = pll_freq / (REF_CLOCK_CHZ as u128);
+ let l : u32 = (pll_freq % (REF_CLOCK_CHZ as u128)).try_into().unwrap();
let denom = 1048575;
- let num = f64::from(l) * f64::from(denom) / f64::from(REF_CLOCK);
+ let num = f64::from(l) * f64::from(denom) / f64::from(REF_CLOCK_CHZ);
(divider.try_into().unwrap(), mult.try_into().unwrap(), num as u32, denom)
}
@@ -74,7 +76,7 @@ fn set_bfo(siclock: &mut dyn Si5351, freq: u32) -> Result<(), si5351::Error>
siclock.set_clock_enabled(si5351::ClockOutput::Clk2, false);
}
else {
- let (a, b, c) = clock_settings_for_pll(freq, PLL_A_MULT * REF_CLOCK);
+ let (a, b, c) = clock_settings_for_pll(freq.into(), PLL_A_MULT as u64 * REF_CLOCK_CHZ as u64);
siclock.setup_multisynth(si5351::Multisynth::MS2, a, b, c, si5351::OutputDivider::Div1)?;
siclock.select_clock_pll(si5351::ClockOutput::Clk2, si5351::PLL::A);
siclock.set_clock_enabled(si5351::ClockOutput::Clk2, true);
@@ -82,7 +84,7 @@ fn set_bfo(siclock: &mut dyn Si5351, freq: u32) -> Result<(), si5351::Error>
siclock.flush_clock_control(si5351::ClockOutput::Clk2)
}
-fn set_vfo(siclock: &mut dyn Si5351, freq: u32)
+fn set_vfo_centihertz(siclock: &mut dyn Si5351, freq: u32)
{
if freq == 0 {
siclock.set_clock_enabled(si5351::ClockOutput::Clk0, false);
@@ -107,7 +109,7 @@ impl<I2C, E> SiClock<I2C>
I2C: WriteRead<Error = E> + Write<Error = E>,
{
pub fn new(i2c: I2C, bfo: u32, vfo: u32) -> SiClock<I2C> {
- let mut siclock = Si5351Device::new(i2c, false, REF_CLOCK);
+ let mut siclock = Si5351Device::new(i2c, false);
siclock.init(si5351::CrystalLoad::_10).unwrap();
// See freqplan.py for Si5351 frequency plan
@@ -118,7 +120,7 @@ impl<I2C, E> SiClock<I2C>
siclock.reset_pll(si5351::PLL::A).unwrap();
- set_vfo(&mut siclock, vfo);
+ set_vfo_centihertz(&mut siclock, vfo * 100);
siclock.reset_pll(si5351::PLL::B).unwrap();
@@ -127,8 +129,8 @@ impl<I2C, E> SiClock<I2C>
SiClock{siclock}
}
- pub fn set_vfo(&mut self, freq: u32) {
- set_vfo(&mut self.siclock, freq)
+ pub fn set_vfo_centihertz(&mut self, freq: u32) {
+ set_vfo_centihertz(&mut self.siclock, freq)
}
pub fn set_bfo(&mut self, freq: u32) -> Result<(), si5351::Error> {
diff --git a/sw/eval-clock-cw-tx/src/state.rs b/sw/eval-clock-cw-tx/src/state.rs
index 44c64d2..4016a51 100644
--- a/sw/eval-clock-cw-tx/src/state.rs
+++ b/sw/eval-clock-cw-tx/src/state.rs
@@ -1,4 +1,4 @@
-const INITIAL_VFO : u32 = 10_100_000;
+const INITIAL_VFO : u32 = 10_140_000;
// Defines which parameter is changed by the encoder
#[derive(Clone, Copy, PartialEq, Eq)]
@@ -23,12 +23,14 @@ pub enum CWMode {
pub enum Mode {
CW(CWMode),
FeldHell,
+ WSPR,
}
#[derive(PartialEq, Eq, Clone, Copy)]
pub enum SequenceMode {
CW,
FeldHell,
+ WSPR,
}
#[derive(Clone, PartialEq, Eq)]
@@ -48,19 +50,23 @@ pub struct State {
pub sequence_state : SequenceState,
pub update_disp_counter : u8,
pub cw_wpm : u32,
+ pub wspr_freq_offset_centihz : u32,
+ pub wspr_freq_offset_updated : bool,
}
impl State {
pub fn new() -> Self {
State {
ui_sel : UISelection::VFO,
- mode : Mode::CW(CWMode::StraightKey),
+ mode : Mode::WSPR,
vfo_sel : VFOSelection::A,
vfo_a : INITIAL_VFO,
vfo_b : INITIAL_VFO,
sequence_state : SequenceState::Rx,
update_disp_counter : 0,
cw_wpm : 14,
+ wspr_freq_offset_centihz : 0,
+ wspr_freq_offset_updated : false,
}
}
@@ -88,6 +94,7 @@ impl State {
}
}
+
pub fn allow_feldhell_keying(&self) -> bool {
self.sequence_state == SequenceState::Tx(SequenceMode::FeldHell)
}
diff --git a/sw/eval-clock-cw-tx/src/ui.rs b/sw/eval-clock-cw-tx/src/ui.rs
index 9f3d985..0ee6163 100644
--- a/sw/eval-clock-cw-tx/src/ui.rs
+++ b/sw/eval-clock-cw-tx/src/ui.rs
@@ -172,7 +172,8 @@ impl UI {
let (new_ui_sel, new_filter_shift) = match (state.ui_sel, state.mode) {
(UISelection::Mode, Mode::CW(CWMode::StraightKey)) => (UISelection::Mode, Mode::CW(CWMode::Iambic)),
(UISelection::Mode, Mode::CW(CWMode::Iambic)) => (UISelection::Mode, Mode::FeldHell),
- (UISelection::Mode, Mode::FeldHell) => (UISelection::Mode, Mode::CW(CWMode::StraightKey)),
+ (UISelection::Mode, Mode::FeldHell) => (UISelection::Mode, Mode::WSPR),
+ (UISelection::Mode, Mode::WSPR) => (UISelection::Mode, Mode::CW(CWMode::StraightKey)),
(_, f) => (UISelection::Mode, f),
};
@@ -264,6 +265,7 @@ pub fn update_disp<T: hd44780_driver::bus::DataBus, D: DelayUs<u16> + DelayMs<u8
Mode::CW(CWMode::StraightKey) => "CWs",
Mode::CW(CWMode::Iambic) => "CWp",
Mode::FeldHell => "HEL",
+ Mode::WSPR => "WSP",
};
write!(string, "{} ", mode).unwrap();
diff --git a/sw/eval-clock-cw-tx/src/usb.rs b/sw/eval-clock-cw-tx/src/usb.rs
index d75c287..b7e8536 100644
--- a/sw/eval-clock-cw-tx/src/usb.rs
+++ b/sw/eval-clock-cw-tx/src/usb.rs
@@ -52,6 +52,9 @@ static mut USB_DEVICE: Option<UsbDevice<UsbBusType>> = None;
*
* Set FELDHELL narrow font
* fontn
+ *
+ * Trigger a WSPR transmission
+ * wspr
*/
@@ -75,10 +78,13 @@ pub fn enable_interrupts() {
}
}
+#[derive(PartialEq, Clone, Copy)]
+pub enum Transmit { None, FELDHELL, WSPR }
+
pub struct USBData {
font : Font,
- pub frequency : u32,
- pub transmit : bool,
+ frequency : Option<u32>,
+ transmit : Transmit,
current_str : ArrayVec::<u8, 96>,
send_ix : usize,
@@ -121,15 +127,15 @@ impl USBData {
}
USBData {
- font : Font::Wide,
- frequency : 10121000,
- transmit : false,
- current_str : ArrayVec::new(),
- send_ix : 0,
+ font: Font::Wide,
+ frequency: None,
+ transmit: Transmit::None,
+ current_str: ArrayVec::new(),
+ send_ix: 0,
}
}
- fn send_once(&mut self, message : &[u8]) {
+ pub fn send_message(&mut self, message : &[u8]) {
cortex_m::interrupt::free(|_cs| {
let serial = unsafe { USB_SERIAL.as_mut().unwrap() };
serial.write(message).ok();
@@ -137,11 +143,11 @@ impl USBData {
}
pub fn send_receive(&mut self) {
- self.send_once(b"Switch RX\n");
+ self.send_message(b"Switch RX\n");
}
pub fn send_transmit(&mut self) {
- self.send_once(b"Switch TX\n");
+ self.send_message(b"Switch TX\n");
}
pub fn handle(&mut self) {
@@ -189,12 +195,12 @@ impl USBData {
if let Some(m) = in_message {
if m.as_str() == "tx\n" {
outgoing.try_extend_from_slice(b"TX ok\n").ok();
- self.transmit = true;
+ self.transmit = Transmit::FELDHELL;
}
else if m.as_str() == "rx\n" {
outgoing.try_extend_from_slice(b"RX ok\n").ok();
self.current_str.clear();
- self.transmit = false;
+ self.transmit = Transmit::None;
}
else if m.chars().nth(0).unwrap() == 'm' {
self.current_str.clear();
@@ -211,6 +217,10 @@ impl USBData {
self.font = Font::Narrow;
outgoing.try_extend_from_slice(b"fontn ok\n").ok();
}
+ else if m.as_str() == "wspr\n" {
+ outgoing.try_extend_from_slice(b"wspr ok\n").ok();
+ self.transmit = Transmit::WSPR;
+ }
else if m.chars().nth(0).unwrap() == 'f' {
let mut as_str = ArrayString::<MESSAGE_LEN>::new();
@@ -220,7 +230,7 @@ impl USBData {
match u32::from_str_radix(as_str.as_str().trim(), 10) {
Ok(val) => {
- self.frequency = val;
+ self.frequency = Some(val);
outgoing.try_extend_from_slice(b"f ok\n").ok();
}
_ => {
@@ -280,7 +290,14 @@ impl USBData {
}
}
- pub fn is_transmit(&self) -> bool { self.transmit }
+ pub fn take_frequency(&mut self) -> Option<u32> { self.frequency.take() }
+
+ pub fn transmit(&self) -> Transmit { self.transmit }
+
+ pub fn clear_transmit(&mut self) {
+ self.send_message(b"WSPR done\n");
+ self.transmit = Transmit::None
+ }
}
#[allow(non_snake_case)]