aboutsummaryrefslogtreecommitdiffstats
path: root/sw/dart-70/src/cw.rs
diff options
context:
space:
mode:
authorMatthias P. Braendli <matthias.braendli@mpb.li>2023-02-25 11:17:03 +0100
committerMatthias P. Braendli <matthias.braendli@mpb.li>2023-02-25 11:17:03 +0100
commit2240e0b6683bcb69b1f477f4449de9cc17314ccd (patch)
treeab38c9e75a649faaa466578f0f6fc0b511e3607d /sw/dart-70/src/cw.rs
parent719fcaf1e0b69eefde2c7c9a572d4c807f8bc7ca (diff)
downloadpicardy-2240e0b6683bcb69b1f477f4449de9cc17314ccd.tar.gz
picardy-2240e0b6683bcb69b1f477f4449de9cc17314ccd.tar.bz2
picardy-2240e0b6683bcb69b1f477f4449de9cc17314ccd.zip
Create dart-70 firmware
Diffstat (limited to 'sw/dart-70/src/cw.rs')
-rw-r--r--sw/dart-70/src/cw.rs260
1 files changed, 260 insertions, 0 deletions
diff --git a/sw/dart-70/src/cw.rs b/sw/dart-70/src/cw.rs
new file mode 100644
index 0000000..4656f81
--- /dev/null
+++ b/sw/dart-70/src/cw.rs
@@ -0,0 +1,260 @@
+//! CW output using PWM on PA8, TIM1 CH1
+
+use stm32f1xx_hal::{
+ timer,
+ pac::TIM1,
+};
+
+const CW_MAPPING : [u8; 50] = [ //{{{
+ // Read bits from right to left
+
+ 0b110101, //+ ASCII 43
+ 0b110101, //, ASCII 44
+ 0b1011110, //- ASCII 45
+
+ 0b1010101, //., ASCII 46
+ 0b110110, // / ASCII 47
+
+ 0b100000, // 0, ASCII 48
+ 0b100001, // 1
+ 0b100011,
+ 0b100111,
+ 0b101111,
+ 0b111111,
+ 0b111110,
+ 0b111100,
+ 0b111000,
+ 0b110000, // 9, ASCII 57
+
+ // The following are mostly invalid, but
+ // required to fill the gap in ASCII between
+ // numerals and capital letters
+ 0b10, // :
+ 0b10, // ;
+ 0b10, // <
+ 0b101110, // =
+ 0b10, // >
+ 0b1110011, // ?
+ 0b1101001, //@
+
+ 0b101, // A ASCII 65
+ 0b11110,
+ 0b11010,
+ 0b1110,
+ 0b11,
+ 0b11011,
+ 0b1100,
+ 0b11111,
+ 0b111,
+ 0b10001,
+ 0b1010,
+ 0b11101,
+ 0b100, //M
+ 0b110,
+ 0b1000,
+ 0b11001,
+ 0b10100,
+ 0b1101,
+ 0b1111,
+ 0b10,
+ 0b1011,
+ 0b10111,
+ 0b1001,
+ 0b10110,
+ 0b10010,
+ 0b11100, // Z
+
+ 0b101010, //Start, ASCII [
+ 0b1010111, // SK , ASCII '\'
+]; //}}}
+
+const CW_MACRO : &[u8; 28] = b"CQ DE HB9EGM HB9EGM HB9EGM K";
+
+pub const SIDETONE_FREQ : u32 = 800;
+
+pub struct CWPWM {
+ channel : timer::pwm::PwmChannel<TIM1, { timer::pwm::C1 }>,
+}
+
+impl CWPWM {
+ pub fn new(mut channel: timer::pwm::PwmChannel<TIM1, { timer::pwm::C1 }>) -> Self {
+ channel.enable();
+ channel.set_duty(0);
+ CWPWM { channel }
+ }
+
+ pub fn on(&mut self) {
+ let max = self.channel.get_max_duty();
+ self.channel.set_duty(max / 2);
+ }
+
+ pub fn off(&mut self) {
+ self.channel.set_duty(0);
+ }
+}
+
+#[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(Eq, Clone, Copy)]
+enum KeyerState {
+ Idle,
+ Beep{current: MorseSign, next: Option<MorseSign>},
+ Pause{current: MorseSign, next: Option<MorseSign>},
+ LastPause{next: Option<MorseSign>},
+}
+
+impl PartialEq for KeyerState {
+ fn eq(&self, rhs: &Self) -> bool {
+ match (self, rhs) {
+ (Self::Idle, Self::Idle) => true,
+ (Self::Beep{current : c1, next : _}, Self::Beep{current : c2, next : _}) => c1 == c2,
+ (Self::Pause{current : c1, next : _}, Self::Pause{current : c2, next : _}) => c1 == c2,
+ (Self::LastPause{next : _}, Self::LastPause{next : _}) => true,
+ _ => false,
+ }
+ }
+}
+
+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
+ }
+}
+