diff options
Diffstat (limited to 'sw/dart-70/src/ui.rs')
-rw-r--r-- | sw/dart-70/src/ui.rs | 362 |
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(); +} |