aboutsummaryrefslogtreecommitdiffstats
path: root/sw
diff options
context:
space:
mode:
authorMatthias P. Braendli <matthias.braendli@mpb.li>2021-01-31 20:41:01 +0100
committerMatthias P. Braendli <matthias.braendli@mpb.li>2021-01-31 20:41:01 +0100
commit536f36abca366d24ff863c3781b5a037c408c35b (patch)
tree438dad2ff550e8fad07a558f52909703f3420dcc /sw
parent4c506aadc3f709d5bcc424630fa46ea50a840dfd (diff)
downloadpicardy-536f36abca366d24ff863c3781b5a037c408c35b.tar.gz
picardy-536f36abca366d24ff863c3781b5a037c408c35b.tar.bz2
picardy-536f36abca366d24ff863c3781b5a037c408c35b.zip
Add CW keyer
Diffstat (limited to 'sw')
-rw-r--r--sw/picardy/src/cw.rs153
-rw-r--r--sw/picardy/src/main.rs160
-rw-r--r--sw/picardy/src/si_clock.rs13
-rw-r--r--sw/picardy/src/state.rs23
-rw-r--r--sw/picardy/src/ui.rs52
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<MorseSign>},
+ Pause{current: MorseSign, next: Option<MorseSign>},
+ LastPause{next: Option<MorseSign>},
+}
+
+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<gpio::Output<gpio::OpenDrain>>,
ui : ui::UI,
cw_pwm: cw::CWPWM,
+ cw_keyer: cw::Keyer,
cw_paddle_tip: gpio::gpiob::PB8<gpio::Input<gpio::PullUp>>,
+ cw_paddle_ring: gpio::gpiob::PB9<gpio::Input<gpio::PullUp>>,
seq0n: gpio::gpiob::PB3<gpio::Output<gpio::PushPull>>,
seq1_pa: gpio::gpiob::PB4<gpio::Output<gpio::PushPull>>,
seq2_switch: gpio::gpiob::PB5<gpio::Output<gpio::PushPull>>,
mute_spkr : gpio::gpioa::PA2<gpio::Output<gpio::PushPull>>,
mute_micn : gpio::gpioa::PA1<gpio::Output<gpio::PushPull>>,
+ led : gpio::gpiob::PB14<gpio::Output<gpio::PushPull>>,
}
static mut SHARED: MaybeUninit<SharedWithISR> = 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)]
@@ -28,18 +28,26 @@ pub enum TuneSpeed {
}
#[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<T: hd44780_driver::bus::DataBus>(lcd: &mut HD44780<T>, 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<T: hd44780_driver::bus::DataBus>(lcd: &mut HD44780<T>, 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 {