aboutsummaryrefslogtreecommitdiffstats
path: root/sw/dart-70/src/ui.rs
diff options
context:
space:
mode:
Diffstat (limited to 'sw/dart-70/src/ui.rs')
-rw-r--r--sw/dart-70/src/ui.rs362
1 files changed, 362 insertions, 0 deletions
diff --git a/sw/dart-70/src/ui.rs b/sw/dart-70/src/ui.rs
new file mode 100644
index 0000000..f607b2b
--- /dev/null
+++ b/sw/dart-70/src/ui.rs
@@ -0,0 +1,362 @@
+/*
+ The MIT License (MIT)
+
+ 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
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+*/
+
+use crate::state::*;
+
+use core::fmt;
+use core::fmt::Write;
+
+use stm32f1xx_hal::{
+ gpio::gpioa::*,
+ gpio::gpiob::*,
+ gpio::gpioc::*,
+ gpio::{Input, PullUp, Floating},
+};
+
+use embedded_hal::blocking::delay::{DelayMs, DelayUs};
+use hd44780_driver::HD44780;
+
+#[derive(PartialEq, Eq, Clone, Copy)]
+struct ButtonState {
+ pub a : bool,
+ pub b : bool,
+ pub c : bool,
+ pub d : bool,
+ pub enc : bool,
+ pub ptt : bool,
+}
+
+const VFO_INCR : i32 = 2;
+const RIT_INCR : i32 = 1;
+const BFO_INCR : i32 = 10;
+
+impl ButtonState {
+ fn edge_detection(&self, old_state : &ButtonState) -> ButtonState {
+ ButtonState {
+ a : !old_state.a && self.a,
+ b : !old_state.b && self.b,
+ c : !old_state.c && self.c,
+ d : !old_state.d && self.d,
+ enc : !old_state.enc && self.enc,
+ ptt : self.ptt, // Don't do edge detection for PTT!
+ }
+ }
+}
+
+impl Default for ButtonState {
+ fn default() -> Self {
+ ButtonState {
+ a : false,
+ b : false,
+ c : false,
+ d : false,
+ enc : false,
+ ptt : false,
+ }
+ }
+}
+
+impl fmt::Display for ButtonState {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}{}{}{}{}{}",
+ if self.a { "A" } else { "a" },
+ if self.b { "B" } else { "b" },
+ if self.c { "C" } else { "c" },
+ if self.d { "D" } else { "d" },
+ if self.enc { "X" } else { "x" },
+ if self.ptt { "P" } else { "p" })
+ }
+}
+
+#[derive(Default)]
+pub struct ButtonResult {
+ pub display_update : bool,
+ pub ptt : bool,
+}
+
+pub struct UI {
+ btn0 : PB1<Input<Floating>>,
+ btn1 : PB0<Input<Floating>>,
+ btn2 : PB12<Input<Floating>>,
+ btn3 : PB13<Input<Floating>>,
+
+ btn_enc : PC15<Input<Floating>>,
+
+ mic_ptt : PA3<Input<Floating>>,
+ _vox_ptt_n : PA0<Input<PullUp>>,
+
+ previous_button_state : ButtonState,
+}
+
+impl UI {
+ pub fn new(
+ mic_ptt: PA3<Input<Floating>>,
+ vox_ptt_n: PA0<Input<PullUp>>,
+ btn0: PB1<Input<Floating>>,
+ btn1: PB0<Input<Floating>>,
+ btn2: PB12<Input<Floating>>,
+ btn3: PB13<Input<Floating>>,
+ btn_enc : PC15<Input<Floating>>) -> UI {
+
+ UI {
+ btn0,
+ btn1,
+ btn2,
+ btn3,
+ btn_enc,
+ mic_ptt,
+ _vox_ptt_n: vox_ptt_n,
+ previous_button_state: ButtonState::default(),
+ }
+ }
+
+ fn read_buttons(&mut self) -> ButtonState {
+ let mut buttons = ButtonState::default();
+
+ buttons.ptt = self.mic_ptt.is_high();
+
+ if self.btn0.is_low() {
+ buttons.a = true;
+ }
+
+ if self.btn1.is_low() {
+ buttons.b = true;
+ }
+
+ if self.btn2.is_low() {
+ buttons.c = true;
+ }
+
+ if self.btn3.is_low() {
+ buttons.d = true;
+ }
+
+ if self.btn_enc.is_low() {
+ buttons.enc = true;
+ }
+
+ buttons
+ }
+
+ pub fn handle_buttons(&mut self, state: &mut State) -> ButtonResult {
+ let mut result = ButtonResult::default();
+ let button_state = self.read_buttons();
+ let button_updates = button_state.edge_detection(&self.previous_button_state);
+ self.previous_button_state = button_state;
+
+ result.ptt = button_updates.ptt;
+
+ match state.menu_page {
+ MenuPage::One => {
+ if button_updates.a {
+ state.vfo_sel = match (state.ui_sel, state.vfo_sel) {
+ (UISelection::VFO, VFOSelection::A) => VFOSelection::B,
+ (UISelection::VFO, VFOSelection::B) => VFOSelection::A,
+ _ => state.vfo_sel.clone(),
+ };
+ state.ui_sel = UISelection::VFO;
+ result.display_update = true;
+ }
+
+ if button_updates.b {
+ state.ui_sel = UISelection::RIT;
+ result.display_update = true;
+ }
+
+ if button_updates.c {
+ let (new_ui_sel, new_filter_shift) = match (state.ui_sel, state.mode) {
+ (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;
+ state.mode = new_filter_shift;
+
+ result.display_update = true;
+ }
+
+ if button_updates.d {
+ state.menu_page = MenuPage::Two;
+ result.display_update = true;
+ }
+
+ if button_updates.enc {
+ match state.ui_sel {
+ UISelection::VFO => {},
+ UISelection::RIT => {
+ state.rit = 0;
+ },
+ UISelection::Mode => {
+ state.mode = Mode::USB;
+ },
+ }
+
+ result.display_update = true;
+ }
+ },
+ MenuPage::Two => {
+ state.ui_sel = UISelection::VFO;
+
+ if button_updates.a {
+ state.send_tone = true;
+ }
+
+ if button_updates.b {
+ state.send_tone = false;
+ }
+
+ if button_updates.c {
+ }
+
+ if button_updates.d {
+ state.menu_page = MenuPage::One;
+ result.display_update = true;
+ }
+
+ if button_updates.enc {
+ }
+ },
+ }
+
+ if result.ptt {
+ state.send_tone = false;
+ }
+
+ result
+ }
+
+ // Returns true if bfo must be reprogrammed
+ pub fn update_encoder(&mut self, state: &mut State, counter_delta : i32) -> bool {
+
+ let delta = (17 * counter_delta + 3 * (counter_delta * counter_delta * counter_delta))/20;
+
+ match state.ui_sel {
+ UISelection::VFO => {
+ match state.vfo_sel {
+ VFOSelection::A => {
+ state.vfo_a = (state.vfo_a as i32 + delta * VFO_INCR) as u32;
+ },
+ VFOSelection::B => {
+ state.vfo_b = (state.vfo_b as i32 + delta * VFO_INCR) as u32;
+ },
+ }
+ false
+ },
+ UISelection::RIT => {
+ state.rit = state.rit + delta * RIT_INCR;
+ false
+ },
+ UISelection::Mode => {
+ match state.mode {
+ Mode::CW(CWMode::Iambic) => {
+ let mut new_wpm = state.cw_wpm as i32 + counter_delta / 4;
+ if new_wpm < 1 {
+ new_wpm = 1;
+ }
+
+ if new_wpm > 40 {
+ new_wpm = 40;
+ }
+
+ let wpm = new_wpm as u32;
+ state.cw_wpm = wpm;
+ state.mode = Mode::CW(CWMode::Iambic);
+ false
+ },
+ _ => {
+ let new_bfo = (state.bfo() as i32 + counter_delta * BFO_INCR) as u32;
+ state.mode = Mode::CustomShift(new_bfo);
+ true
+ },
+ }
+ },
+ }
+ }
+}
+
+pub fn update_disp<T: hd44780_driver::bus::DataBus, D: DelayUs<u16> + DelayMs<u8>>(lcd: &mut HD44780<T>, state: &State, delay: &mut D, s_meter_value: u8, bfo_tune_fail: bool)
+{
+ let mut string = arrayvec::ArrayString::<16>::new();
+
+ /* Shorten the QRG to avoid using three digits for nothing */
+ let disp_freq = (state.vhf_qrg() as i32) - (VHF_BAND_EDGE as i32);
+ write!(string, "{:<03}.{:<03} ", disp_freq / 1000, disp_freq % 1000).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();
+ },
+ (UISelection::Mode, Mode::CustomShift(shift)) => {
+ write!(string, "{:<04}", shift/10).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{:1}", s_meter_value).unwrap();
+ }
+
+ lcd.set_cursor_pos(0, delay).unwrap();
+ lcd.write_str(&string, delay).unwrap();
+
+ string.clear();
+
+ match state.menu_page {
+ MenuPage::One => {
+ match (bfo_tune_fail, &state.vfo_sel) {
+ (true, _) => write!(string, "VFO!").unwrap(),
+ (false, VFOSelection::A) => write!(string, "VFOa").unwrap(),
+ (false, VFOSelection::B) => write!(string, "VFOb").unwrap(),
+ }
+
+ write!(string, "{}", if state.ui_sel == UISelection::RIT { ">RIT" } else { " RIT" }).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(CWMode::StraightKey) => "CWs",
+ Mode::CW(CWMode::Iambic) => "CWp",
+ };
+
+ write!(string, "{} 1/2", mode).unwrap();
+ },
+ MenuPage::Two => {
+ write!(string, "TUT STOP --- 2/2").unwrap();
+ },
+ }
+
+ lcd.set_cursor_pos(40, delay).unwrap();
+ lcd.write_str(&string, delay).unwrap();
+}