From e04fec33f079db4c5d42aa412f7b670784ec1b68 Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Mon, 13 Mar 2023 23:14:32 +0100 Subject: Add WSPR TX to eval-clock-cw-tx --- sw/deps/si5351/src/lib.rs | 164 +----------------------------------- sw/eval-clock-cw-tx/README.md | 29 ++++++- sw/eval-clock-cw-tx/gui.py | 16 ++-- sw/eval-clock-cw-tx/src/main.rs | 156 ++++++++++++++++++++++++++-------- sw/eval-clock-cw-tx/src/si_clock.rs | 36 ++++---- sw/eval-clock-cw-tx/src/state.rs | 11 ++- sw/eval-clock-cw-tx/src/ui.rs | 4 +- sw/eval-clock-cw-tx/src/usb.rs | 45 +++++++--- 8 files changed, 220 insertions(+), 241 deletions(-) (limited to 'sw') 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::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::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 { - 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) -> Error { @@ -341,7 +243,6 @@ fn i2c_error(_: E) -> Error { pub struct Si5351Device { 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; - 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 Si5351Device I2C: WriteRead + Write, { /// 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 Si5351Device si5351 } - pub fn new_adafruit_module(i2c: I2C) -> Self { - Si5351Device::new(i2c, false, 25_000_000) - } - fn write_ms_config(&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 Si5351 for Si5351Device 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>, ui: ui::UI, @@ -66,7 +79,10 @@ struct SharedWithISR { cw_paddle_ring: gpio::gpiob::PB9>, ptt_out: gpio::gpiob::PB3>, seq_switch: gpio::gpiob::PB5>, - led : gpio::gpiob::PB14>, + led: gpio::gpiob::PB14>, + + wspr_symbol_ix: usize, + wspr_symbol_duration_ticks: u32, } static mut SHARED: MaybeUninit = 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:: = 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 SiClock I2C: WriteRead + Write, { pub fn new(i2c: I2C, bfo: u32, vfo: u32) -> SiClock { - 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 SiClock 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 SiClock 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 + DelayMs "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> = 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, + transmit : Transmit, current_str : ArrayVec::, 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::::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 { 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)] -- cgit v1.2.3