From 536f36abca366d24ff863c3781b5a037c408c35b Mon Sep 17 00:00:00 2001 From: "Matthias P. Braendli" Date: Sun, 31 Jan 2021 20:41:01 +0100 Subject: Add CW keyer --- sw/picardy/src/cw.rs | 153 +++++++++++++++++++++++++++++++++++++++++++ sw/picardy/src/main.rs | 160 +++++++++++++++++++++++++++------------------ sw/picardy/src/si_clock.rs | 13 ++-- sw/picardy/src/state.rs | 23 +++++-- sw/picardy/src/ui.rs | 52 +++++++++++---- 5 files changed, 317 insertions(+), 84 deletions(-) diff --git a/sw/picardy/src/cw.rs b/sw/picardy/src/cw.rs index ce99e39..4ebebf9 100644 --- a/sw/picardy/src/cw.rs +++ b/sw/picardy/src/cw.rs @@ -33,3 +33,156 @@ impl CWPWM { } } +#[derive(PartialEq, Eq, Clone, Copy)] +enum MorseSign { + Dot, + Dash +} + +impl MorseSign { + fn length(&self) -> u32 { + match self { + Self::Dot => 1, + Self::Dash => 3, + } + } + + fn other(&self) -> Self { + match self { + Self::Dot => Self::Dash, + Self::Dash => Self::Dot, + } + } +} + +fn other_pressed(sign: MorseSign, dot_pressed: bool, dash_pressed: bool) -> bool { + match sign { + MorseSign::Dot => dash_pressed, + MorseSign::Dash => dot_pressed, + } +} + +#[derive(PartialEq, Eq, Clone, Copy)] +enum KeyerState { + Idle, + Beep{current: MorseSign, next: Option}, + Pause{current: MorseSign, next: Option}, + LastPause{next: Option}, +} + +pub struct Keyer { + // All durations are in ticks + dot_length : u32, + state : KeyerState, + time_last_state_change : u32, +} + +impl Keyer { + pub fn new(wpm : u32, ticks_per_s: u32) -> Keyer { + /* PARIS standard: 20 words per minute = dot length of 60 ms, inversely proportional: + * 1 wpm = 1200 ms, 2 wpm = 600 ms */ + + Keyer{ + dot_length : 1200 * ticks_per_s / (1000 * wpm), + state : KeyerState::Idle, + time_last_state_change : 0, + } + } + pub fn set_speed(&mut self, wpm : u32, ticks_per_s: u32) { self.dot_length = 1200 * ticks_per_s / (1000 * wpm) } + + pub fn tick(&mut self, ticks_now: u32, dot_pressed: bool, dash_pressed: bool) -> bool { + let mut transmit = false; + + let next_state = match self.state { + KeyerState::Idle => { + if dot_pressed { + transmit = true; + KeyerState::Beep{current: MorseSign::Dot, next: None} + } + else if dash_pressed { + transmit = true; + KeyerState::Beep{current: MorseSign::Dash, next: None} + } + else { + KeyerState::Idle + } + }, + KeyerState::Beep{current, next} => { + transmit = true; + + let next = if other_pressed(current, dot_pressed, dash_pressed) { + Some(current.other()) + } + else { + next + }; + + if self.time_last_state_change + self.dot_length * current.length() <= ticks_now { + KeyerState::Pause{current, next} + } + else { + KeyerState::Beep{current, next} + } + }, + KeyerState::Pause{current, next} => { + let next = if other_pressed(current, dot_pressed, dash_pressed) { + Some(current.other()) + } + else { + next + }; + + if self.time_last_state_change + self.dot_length <= ticks_now { + match next { + Some(state) => { + transmit = true; + KeyerState::Beep{current: state, next: None} + }, + None => KeyerState::LastPause{next: None} + } + } + else { + KeyerState::Pause{current, next} + } + }, + KeyerState::LastPause{next} => { + let next = if dot_pressed { + Some(MorseSign::Dot) + } + else if dash_pressed { + Some(MorseSign::Dash) + } + else { + next + }; + + if self.time_last_state_change + self.dot_length <= ticks_now { + KeyerState::LastPause{next} + } + else { + match next { + Some(MorseSign::Dot) => { + transmit = true; + KeyerState::Beep{current: MorseSign::Dot, next: None} + }, + Some(MorseSign::Dash) => { + transmit = true; + KeyerState::Beep{current: MorseSign::Dash, next: None} + }, + None => { + KeyerState::Idle + }, + } + } + }, + }; + + if next_state != self.state { + self.time_last_state_change = ticks_now; + self.state = next_state; + } + + transmit + } +} + diff --git a/sw/picardy/src/main.rs b/sw/picardy/src/main.rs index ffe5a47..a6a3e4a 100644 --- a/sw/picardy/src/main.rs +++ b/sw/picardy/src/main.rs @@ -60,14 +60,18 @@ struct SharedWithISR { state : State, last_sequence_state_change : u32, cw_ptt_timestamp : u32, + cw_key_n : gpio::gpioa::PA15>, ui : ui::UI, cw_pwm: cw::CWPWM, + cw_keyer: cw::Keyer, cw_paddle_tip: gpio::gpiob::PB8>, + cw_paddle_ring: gpio::gpiob::PB9>, seq0n: gpio::gpiob::PB3>, seq1_pa: gpio::gpiob::PB4>, seq2_switch: gpio::gpiob::PB5>, mute_spkr : gpio::gpioa::PA2>, mute_micn : gpio::gpioa::PA1>, + led : gpio::gpiob::PB14>, } static mut SHARED: MaybeUninit = MaybeUninit::uninit(); @@ -114,14 +118,15 @@ fn main() -> ! { let adc1 = dp.ADC1; let ui = ui::UI::new(mic_sw1, mic_sw2, pb0, pb1, adc1, &mut rcc.apb2, &clocks, pb12, pb13, pc15); - let (cw_pwm, cw_paddle_tip) = { + let cw_pwm = { let pa8 = gpioa.pa8.into_alternate_push_pull(&mut gpioa.crh); // CW PWM output using TIM1 Ch1 let tim1 = Timer::tim1(dp.TIM1, &clocks, &mut rcc.apb2); - let cw_pwm = cw::CWPWM::new(pa8, tim1, &mut afio.mapr); - let cw_paddle_tip = gpiob.pb8.into_pull_up_input(&mut gpiob.crh); // CW paddle tip - (cw_pwm, cw_paddle_tip) + cw::CWPWM::new(pa8, tim1, &mut afio.mapr) }; + let cw_paddle_tip = gpiob.pb8.into_pull_up_input(&mut gpiob.crh); // CW paddle tip + let cw_paddle_ring = gpiob.pb9.into_pull_up_input(&mut gpiob.crh); // CW paddle ring + let mut s_meter = gpioa.pa5.into_analog(&mut gpioa.crl); let mut adc2 = adc::Adc::adc2(dp.ADC2, &mut rcc.apb2, clocks); let mut last_s_meter_update_time = 0; @@ -131,7 +136,7 @@ fn main() -> ! { led.set_low().unwrap(); let (pa15, pb3, pb4) = afio.mapr.disable_jtag(gpioa.pa15, gpiob.pb3, gpiob.pb4); - let _cw_key_n = pa15.into_push_pull_output_with_state(&mut gpioa.crh, gpio::State::High); // TODO output + let cw_key_n = pa15.into_open_drain_output_with_state(&mut gpioa.crh, gpio::State::High); let seq0n = pb3.into_push_pull_output_with_state(&mut gpiob.crl, gpio::State::High); let seq1_pa = pb4.into_push_pull_output_with_state(&mut gpiob.crl, gpio::State::Low); @@ -215,9 +220,11 @@ fn main() -> ! { state : State::new(), last_sequence_state_change : 0, cw_ptt_timestamp : 0, + cw_key_n, ui, cw_pwm, - cw_paddle_tip, seq0n, seq1_pa, seq2_switch, mute_spkr, mute_micn + cw_keyer : cw::Keyer::new(12, TICKS_PER_SECOND), + cw_paddle_tip, cw_paddle_ring, seq0n, seq1_pa, seq2_switch, mute_spkr, mute_micn, led }; si_clock::SiClock::new(i2c_busmanager.acquire_i2c(), shared.state.bfo(), shared.state.vfo()) @@ -243,15 +250,15 @@ fn main() -> ! { unsafe { pac::NVIC::unmask(pac::Interrupt::TIM2); } let mut last_disp_update_counter = 1; + let mut previous_vfo = 0; + let mut previous_bfo = 0; + loop { let mut update_disp_required = false; let mut bfo_tune_fail = false; let state = get_state_copy(); - let previous_vfo = state.vfo(); - let previous_bfo = state.bfo(); - let encoder_count : u16 = qei.count(); if encoder_count != last_encoder_count { let delta = encoder_count.wrapping_sub(last_encoder_count); @@ -259,7 +266,11 @@ fn main() -> ! { let require_bfo_update = cortex_m::interrupt::free(|_cs| { let shared = unsafe { &mut *SHARED.as_mut_ptr() }; - shared.ui.update_encoder(&mut shared.state, delta) + let r = shared.ui.update_encoder(&mut shared.state, delta); + if let Mode::CW(CWMode::Iambic) = shared.state.mode { + shared.cw_keyer.set_speed(shared.state.cw_wpm, TICKS_PER_SECOND) + } + r }); if require_bfo_update { @@ -269,23 +280,17 @@ fn main() -> ! { update_disp_required = true; } - if previous_bfo != state.bfo() { - bfo_tune_fail = !siclock.set_bfo(state.bfo()).is_ok(); + let bfo = state.bfo(); + if previous_bfo != bfo { + bfo_tune_fail = !siclock.set_bfo(bfo).is_ok(); } + previous_bfo = bfo; - if previous_vfo != state.vfo() { - siclock.set_vfo(state.vfo()); - } - - match state.sequence_state { - SequenceState::Rx => { - led.set_high().unwrap(); - }, - SequenceState::Switching => {} - SequenceState::Tx => { - led.set_low().unwrap(); - } + let vfo = state.vfo(); + if previous_vfo != vfo { + siclock.set_vfo(vfo); } + previous_vfo = vfo; let s_meter_value: u16 = adc2.read(&mut s_meter).unwrap(); @@ -333,22 +338,27 @@ fn TIM2() { } let cw_paddle_tip_low = shared.cw_paddle_tip.is_low().unwrap(); - - const CW_PTT_DELAY : u32 = TICKS_PER_SECOND * 800 / 1000; - let cw_ptt = if shared.state.mode == Mode::CW { - if cw_paddle_tip_low { - shared.cw_ptt_timestamp = *ticks; - true - } - else { - shared.cw_ptt_timestamp + CW_PTT_DELAY > *ticks - } - } - else { - false + let cw_paddle_ring_low = shared.cw_paddle_ring.is_low().unwrap(); + + let cw_ptt_delay : u32 = TICKS_PER_SECOND * 800 / 1000; + let cw_ptt = match shared.state.mode { + Mode::CW(_) => { + if cw_paddle_tip_low || cw_paddle_ring_low { + shared.cw_ptt_timestamp = *ticks; + true + } + else { + shared.cw_ptt_timestamp + cw_ptt_delay > *ticks + } + }, + _ => false, }; - let cw_beep = shared.state.mode == Mode::CW && cw_paddle_tip_low; + 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), + _ => false, + }; let next_state = match shared.state.sequence_state { SequenceState::Rx => { @@ -356,60 +366,86 @@ fn TIM2() { shared.mute_micn.set_low().unwrap(); shared.seq2_switch.set_low().unwrap(); shared.seq0n.set_high().unwrap(); - shared.cw_pwm.off(); - - if button_result.ptt || cw_ptt { - SequenceState::Switching + if button_result.ptt { + SequenceState::SwitchingSSB + } + else if cw_ptt { + SequenceState::SwitchingCW } else { SequenceState::Rx } }, - SequenceState::Switching => { + SequenceState::SwitchingSSB => { shared.mute_spkr.set_high().unwrap(); shared.seq2_switch.set_high().unwrap(); shared.seq0n.set_low().unwrap(); shared.seq1_pa.set_low().unwrap(); + shared.mute_micn.set_high().unwrap(); - if cw_beep { - shared.cw_pwm.on(); + if button_result.ptt { + SequenceState::TxSSB } else { - shared.cw_pwm.off(); - } - - if button_result.ptt { - shared.mute_micn.set_high().unwrap(); - SequenceState::Tx + SequenceState::Rx } - else if cw_ptt { - shared.mute_micn.set_low().unwrap(); - SequenceState::Tx + }, + SequenceState::SwitchingCW => { + shared.mute_spkr.set_high().unwrap(); + shared.seq2_switch.set_high().unwrap(); + shared.seq0n.set_low().unwrap(); + shared.seq1_pa.set_low().unwrap(); + shared.mute_micn.set_low().unwrap(); + if cw_ptt { + SequenceState::TxCW } else { SequenceState::Rx } }, - SequenceState::Tx => { + SequenceState::TxSSB => { shared.seq1_pa.set_high().unwrap(); - if cw_beep { - shared.cw_pwm.on(); + if button_result.ptt { + SequenceState::TxSSB } else { - shared.cw_pwm.off(); + SequenceState::SwitchingSSB } + }, + SequenceState::TxCW => { + shared.seq1_pa.set_high().unwrap(); - if button_result.ptt || cw_ptt { - SequenceState::Tx + if cw_ptt { + SequenceState::TxCW } else { - SequenceState::Switching + SequenceState::SwitchingCW } }, }; - const SWITCHING_DELAY : u32 = TICKS_PER_SECOND * 20 / 1000; + match shared.state.sequence_state { + SequenceState::TxCW => { + if cw_beep { + shared.led.set_low().unwrap(); + shared.cw_pwm.on(); + shared.cw_key_n.set_low().unwrap(); + } + else { + shared.led.set_high().unwrap(); + shared.cw_pwm.off(); + shared.cw_key_n.set_high().unwrap(); + } + }, + _ => { + shared.led.set_high().unwrap(); + shared.cw_pwm.off(); + shared.cw_key_n.set_high().unwrap(); + }, + } + + const SWITCHING_DELAY : u32 = TICKS_PER_SECOND * 40 / 1000; if shared.state.sequence_state != next_state && shared.last_sequence_state_change + SWITCHING_DELAY <= *ticks { shared.state.sequence_state = next_state; diff --git a/sw/picardy/src/si_clock.rs b/sw/picardy/src/si_clock.rs index 8ebf702..a4b0e7a 100644 --- a/sw/picardy/src/si_clock.rs +++ b/sw/picardy/src/si_clock.rs @@ -70,10 +70,15 @@ fn clock_settings_with_pll_calculation(freq: u32) -> (u16, u8, u32, u32) { fn set_bfo(siclock: &mut dyn Si5351, freq: u32) -> Result<(), si5351::Error> { - let (a, b, c) = clock_settings_for_pll(freq, PLL_A_MULT * REF_CLOCK); - 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); + if freq == 0 { + siclock.set_clock_enabled(si5351::ClockOutput::Clk2, false); + } + else { + let (a, b, c) = clock_settings_for_pll(freq, PLL_A_MULT * REF_CLOCK); + 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); + } siclock.flush_clock_control(si5351::ClockOutput::Clk2) } diff --git a/sw/picardy/src/state.rs b/sw/picardy/src/state.rs index 239cf18..7f4909f 100644 --- a/sw/picardy/src/state.rs +++ b/sw/picardy/src/state.rs @@ -11,7 +11,7 @@ pub const QRG_CORRECTION : i32 = -1_600; pub enum UISelection { VFO, RIT, - IFShift, + Mode, } #[derive(Clone, Copy)] @@ -27,19 +27,27 @@ pub enum TuneSpeed { Fast } +#[derive(PartialEq, Eq, Clone, Copy)] +pub enum CWMode { + StraightKey, + Iambic, +} + #[derive(PartialEq, Eq, Clone, Copy)] pub enum Mode { LSB, USB, CustomShift(u32), - CW, + CW(CWMode), } #[derive(Clone, PartialEq, Eq)] pub enum SequenceState { Rx, - Switching, - Tx, + SwitchingSSB, + TxSSB, + SwitchingCW, + TxCW, } impl SequenceState { @@ -59,6 +67,7 @@ pub struct State { pub tune_speed : TuneSpeed, pub sequence_state : SequenceState, pub update_disp_counter : u8, + pub cw_wpm : u32, } impl State { @@ -73,6 +82,7 @@ impl State { tune_speed : TuneSpeed::Mid, sequence_state : SequenceState::Rx, update_disp_counter : 0, + cw_wpm : 12, } } @@ -81,7 +91,10 @@ impl State { Mode::LSB => BFO_LSB, Mode::USB => BFO_USB, Mode::CustomShift(fs) => fs, - Mode::CW => BFO_CW, + Mode::CW(_) => match self.sequence_state { + SequenceState::SwitchingCW | SequenceState::TxCW => 0, + _ => BFO_CW, + }, } } diff --git a/sw/picardy/src/ui.rs b/sw/picardy/src/ui.rs index b15a289..9d5a4e4 100644 --- a/sw/picardy/src/ui.rs +++ b/sw/picardy/src/ui.rs @@ -196,11 +196,12 @@ impl UI { if button_updates.c { let (new_ui_sel, new_filter_shift) = match (state.ui_sel, state.mode) { - (UISelection::IFShift, Mode::USB) => (UISelection::IFShift, Mode::LSB), - (UISelection::IFShift, Mode::LSB) => (UISelection::IFShift, Mode::CW), - (UISelection::IFShift, Mode::CW) => (UISelection::IFShift, Mode::USB), - (UISelection::IFShift, Mode::CustomShift(_)) => (UISelection::IFShift, Mode::USB), - (_, f) => (UISelection::IFShift, f), + (UISelection::Mode, Mode::USB) => (UISelection::Mode, Mode::LSB), + (UISelection::Mode, Mode::LSB) => (UISelection::Mode, Mode::CW(CWMode::StraightKey)), + (UISelection::Mode, Mode::CW(CWMode::StraightKey)) => (UISelection::Mode, Mode::CW(CWMode::Iambic)), + (UISelection::Mode, Mode::CW(CWMode::Iambic)) => (UISelection::Mode, Mode::USB), + (UISelection::Mode, Mode::CustomShift(_)) => (UISelection::Mode, Mode::USB), + (_, f) => (UISelection::Mode, f), }; state.ui_sel = new_ui_sel; @@ -224,7 +225,7 @@ impl UI { UISelection::RIT => { state.rit = 0; }, - UISelection::IFShift => { + UISelection::Mode => { state.mode = Mode::USB; }, } @@ -253,10 +254,25 @@ impl UI { state.rit = state.rit + delta * state.rit_incr(); false }, - UISelection::IFShift => { - let new_bfo = (state.bfo() as i32 + delta * state.bfo_incr()) as u32; - state.mode = Mode::CustomShift(new_bfo); - true + UISelection::Mode => { + match state.mode { + Mode::CW(CWMode::Iambic) => { + let mut new_wpm = state.cw_wpm as i32 + delta / 4; + if new_wpm < 1 { + new_wpm = 1; + } + + let wpm = new_wpm as u32; + state.cw_wpm = wpm; + state.mode = Mode::CW(CWMode::Iambic); + false + }, + _ => { + let new_bfo = (state.bfo() as i32 + delta * state.bfo_incr()) as u32; + state.mode = Mode::CustomShift(new_bfo); + true + }, + } }, } } @@ -270,7 +286,16 @@ pub fn update_disp(lcd: &mut HD44780, state: let disp_freq = (state.vhf_qrg() as i32) - (VHF_BAND_EDGE as i32); write!(string, "{:<03}.{:<03} ", disp_freq / 1000, disp_freq % 1000).unwrap(); - write!(string, "{}{:<03}", if state.rit >= 0 { "+" } else { "-" }, state.rit.abs()/10).unwrap(); + /* By default, show RIT, unless UIselection is on mode, and mode is CW Iambic, in which case we show speed */ + match (state.ui_sel, state.mode) { + (UISelection::Mode, Mode::CW(CWMode::Iambic)) => { + write!(string, "CW{:<02}", state.cw_wpm).unwrap(); + }, + _ => { + write!(string, "{}{:<03}", if state.rit >= 0 { "+" } else { "-" }, state.rit.abs()/10).unwrap(); + }, + } + if string.len() <= 16 - 4 { // Avoids crash when frequency is very negative write!(string, "S{:3}", s_meter_value).unwrap(); @@ -289,13 +314,14 @@ pub fn update_disp(lcd: &mut HD44780, state: write!(string, "{}", if state.ui_sel == UISelection::RIT { ">RIT" } else { " RIT" }).unwrap(); - write!(string, "{}", if state.ui_sel == UISelection::IFShift { ">" } else { " " }).unwrap(); + write!(string, "{}", if state.ui_sel == UISelection::Mode { ">" } else { " " }).unwrap(); let mode = match state.mode { Mode::USB => "USB", Mode::LSB => "LSB", Mode::CustomShift(_) => "IFs", - Mode::CW => "CW ", + Mode::CW(CWMode::StraightKey) => "CWs", + Mode::CW(CWMode::Iambic) => "CWp", }; let speed = match state.tune_speed { -- cgit v1.2.3