From 91e120b25858b7e74a9a53aadf6b0f34442509f5 Mon Sep 17 00:00:00 2001 From: finga Date: Wed, 16 Mar 2022 22:17:23 +0100 Subject: [PATCH] fw-rust: Add setup screen and handle button input To create a setup screen more symbols are created and added to the symbol table. Helper functions are added to draw homogenous areas onto the display. Add two new fields `Click` and `Back` to the `Input` enum. Adapt functions already handling input to those new kinds of input. Add the setup screen construct. Add global atomics to handle the button and the timer for handling the back input when the button is pressed until the timer one overflows. Button press and the timer are handled via interrupts. --- firmware/rust/src/main.rs | 248 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 236 insertions(+), 12 deletions(-) diff --git a/firmware/rust/src/main.rs b/firmware/rust/src/main.rs index ddea84b..dcbae03 100644 --- a/firmware/rust/src/main.rs +++ b/firmware/rust/src/main.rs @@ -133,6 +133,21 @@ const SYM_9: [&[u8]; 2] = [ &[0x1C, 0x3D, 0x31, 0x3F, 0x1F], ]; +// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized +const SYM_COLON: [&[u8]; 2] = [&[0x30, 0x30], &[0x0C, 0x0C]]; + +// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized +const SYM_A: [&[u8]; 2] = [ + &[0xC0, 0xF0, 0x3C, 0x3C, 0xF0, 0xC0], + &[0x3F, 0x3F, 0x06, 0x06, 0x3F, 0x3F], +]; + +// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized +const SYM_B: [&[u8]; 2] = [ + &[0xFC, 0xFC, 0x8C, 0x8C, 0xFC, 0x78], + &[0x3F, 0x3F, 0x31, 0x31, 0x3F, 0x1E], +]; + // TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized const SYM_C: [&[u8]; 2] = [ &[0xF8, 0xFC, 0x0C, 0x1C, 0x18], @@ -145,18 +160,57 @@ const SYM_E: [&[u8]; 2] = [ &[0x3F, 0x3F, 0x31, 0x31, 0x30, 0x30], ]; +// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized +const SYM_G: [&[u8]; 2] = [ + &[0xF8, 0xFC, 0x0C, 0x0C, 0x3C, 0x38], + &[0x1F, 0x3F, 0x30, 0x33, 0x3F, 0x1F], +]; + // TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized const SYM_H: [&[u8]; 2] = [ &[0xFC, 0xFC, 0x80, 0x80, 0xFC, 0xFC], &[0x3F, 0x3F, 0x01, 0x01, 0x3F, 0x3F], ]; +// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized +const SYM_I: [&[u8]; 2] = [&[0x0C, 0xFC, 0xFC, 0x0C], &[0x30, 0x3F, 0x3F, 0x30]]; + +// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized +const SYM_K: [&[u8]; 2] = [ + &[0xFC, 0xFC, 0xC0, 0xF0, 0x7C, 0x1C], + &[0x3F, 0x3F, 0x03, 0x0F, 0x3E, 0x38], +]; + +// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized +const SYM_L: [&[u8]; 2] = [ + &[0xFC, 0xFC, 0x00, 0x00, 0x00], + &[0x3F, 0x3F, 0x30, 0x30, 0x30], +]; + +// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized +const SYM_N: [&[u8]; 2] = [ + &[0xFC, 0xFC, 0xF0, 0xC0, 0x00, 0xFC, 0xFC], + &[0x3F, 0x3F, 0x00, 0x03, 0x0F, 0x3F, 0x3F], +]; + +// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized +const SYM_O: [&[u8]; 2] = [ + &[0xF8, 0xFC, 0x0C, 0x0C, 0xFC, 0xF8], + &[0x1F, 0x3F, 0x30, 0x30, 0x3F, 0x1F], +]; + // TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized const SYM_P: [&[u8]; 2] = [ &[0xFC, 0xFC, 0x8C, 0x8C, 0xFC, 0xF8], &[0x3F, 0x3F, 0x01, 0x01, 0x01, 0x00], ]; +// TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized +const SYM_R: [&[u8]; 2] = [ + &[0xFC, 0xFC, 0x8C, 0x8C, 0xFC, 0xF8], + &[0x3F, 0x3F, 0x01, 0x03, 0x3F, 0x3E], +]; + // TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized const SYM_S: [&[u8]; 2] = [ &[0xF8, 0xFC, 0x8C, 0x8C, 0x9C, 0x18], @@ -193,31 +247,31 @@ const SYMBOL_TABLE: [&[&[u8]; 2]; 48] = [ &SYM_7, // '7' &SYM_8, // '8' &SYM_9, // '9' - &SYM_INVALID, // ':' + &SYM_COLON, // ':' &SYM_INVALID, // ';' &SYM_INVALID, // '<' &SYM_INVALID, // '=' &SYM_INVALID, // '>' &SYM_INVALID, // '?' &SYM_INVALID, // '@' - &SYM_INVALID, // 'A' - &SYM_INVALID, // 'B' + &SYM_A, // 'A' + &SYM_B, // 'B' &SYM_C, // 'C' &SYM_INVALID, // 'D' &SYM_E, // 'E' &SYM_INVALID, // 'F' - &SYM_INVALID, // 'G' + &SYM_G, // 'G' &SYM_H, // 'H' - &SYM_INVALID, // 'I' + &SYM_I, // 'I' &SYM_INVALID, // 'J' - &SYM_INVALID, // 'K' - &SYM_INVALID, // 'L' + &SYM_K, // 'K' + &SYM_L, // 'L' &SYM_INVALID, // 'M' - &SYM_INVALID, // 'N' - &SYM_INVALID, // 'O' + &SYM_N, // 'N' + &SYM_O, // 'O' &SYM_P, // 'P' &SYM_INVALID, // 'Q' - &SYM_INVALID, // 'R' + &SYM_R, // 'R' &SYM_S, // 'S' &SYM_T, // 'T' &SYM_U, // 'U' @@ -297,6 +351,33 @@ impl Lcd { } } + fn fill(&mut self, segment: u8, page: u8, width: u8, data: u8) { + assert!(segment + width <= 102); + let mut delay = Delay::::new(); + + self.move_cursor(segment, page); + + // TODO: This delay fixes issues, try find a better solution + delay.delay_ms(1_u8); + self.cd.set_high(); + + for _ in 0..width { + block!(self.spi.send(data)).unwrap(); + } + + // TODO: This delay fixes issues, try find a better solution + delay.delay_ms(1_u8); + self.cd.set_low(); + } + + fn fill_area(&mut self, segment: u8, page: u8, width: u8, height: u8, data: u8) { + assert!(page + height <= 8); + + for i in 0..height { + self.fill(segment, page + i, width, data); + } + } + fn print(&mut self, segment: u8, page: u8, string: &str) { let mut delay = Delay::::new(); @@ -360,6 +441,9 @@ impl Lcd { Screens::Home(home) => { home.draw(self); } + Screens::Setup(setup) => { + setup.draw(self); + } } }); } @@ -368,6 +452,8 @@ impl Lcd { enum Input { Next, Previous, + Select, + Back, } pub trait Draw { @@ -438,18 +524,26 @@ impl HomeScreen { HomeSelection::Ch1 => match input { Input::Next => HomeSelection::Ch2, Input::Previous => HomeSelection::Setup, + Input::Select => return Screens::Splash(SplashScreen), + Input::Back => HomeSelection::Ch1, }, HomeSelection::Ch2 => match input { Input::Next => HomeSelection::Ch3, Input::Previous => HomeSelection::Ch1, + Input::Select => return Screens::Splash(SplashScreen), + Input::Back => HomeSelection::Ch2, }, HomeSelection::Ch3 => match input { Input::Next => HomeSelection::Setup, Input::Previous => HomeSelection::Ch2, + Input::Select => return Screens::Splash(SplashScreen), + Input::Back => HomeSelection::Ch3, }, HomeSelection::Setup => match input { Input::Next => HomeSelection::Ch1, Input::Previous => HomeSelection::Ch3, + Input::Select => return Screens::Setup(SetupScreen::new()), + Input::Back => HomeSelection::Setup, }, }, }) @@ -487,9 +581,84 @@ impl Draw for HomeScreen { } } +enum SetupSelection { + Contrast, + Backlight, + Back, +} + +struct SetupScreen { + active: SetupSelection, +} + +impl SetupScreen { + fn new() -> Self { + Self { + active: SetupSelection::Contrast, + } + } + + fn input(&self, input: &Input) -> Screens { + Screens::Setup(Self { + active: match self.active { + SetupSelection::Contrast => match input { + Input::Next => SetupSelection::Backlight, + Input::Previous => SetupSelection::Back, + Input::Select => return Screens::Splash(SplashScreen), + Input::Back => return Screens::Home(HomeScreen::new()), + }, + SetupSelection::Backlight => match input { + Input::Next => SetupSelection::Back, + Input::Previous => SetupSelection::Contrast, + Input::Select => return Screens::Splash(SplashScreen), + Input::Back => return Screens::Home(HomeScreen::new()), + }, + SetupSelection::Back => match input { + Input::Next => SetupSelection::Contrast, + Input::Previous => SetupSelection::Backlight, + Input::Select => return Screens::Home(HomeScreen::new()), + Input::Back => return Screens::Home(HomeScreen::new()), + }, + }, + }) + } +} + +impl Draw for SetupScreen { + fn draw(&self, lcd: &mut Lcd) { + match &self.active { + SetupSelection::Contrast => { + lcd.fill_area(0, 0, 33, 2, 0xFF); + lcd.print_inverted(33, 0, "SETUP"); + lcd.fill_area(69, 0, 33, 2, 0xFF); + lcd.print_inverted(0, 2, "CONTRAST:"); + lcd.print(0, 4, "BACKLIGHT:"); + lcd.print(36, 6, "BACK"); + } + SetupSelection::Backlight => { + lcd.fill_area(0, 0, 33, 2, 0xFF); + lcd.print_inverted(33, 0, "SETUP"); + lcd.fill_area(69, 0, 33, 2, 0xFF); + lcd.print(0, 2, "CONTRAST:"); + lcd.print_inverted(0, 4, "BACKLIGHT:"); + lcd.print(36, 6, "BACK"); + } + SetupSelection::Back => { + lcd.fill_area(0, 0, 33, 2, 0xFF); + lcd.print_inverted(33, 0, "SETUP"); + lcd.fill_area(69, 0, 33, 2, 0xFF); + lcd.print(0, 2, "CONTRAST:"); + lcd.print(0, 4, "BACKLIGHT:"); + lcd.print_inverted(36, 6, "BACK"); + } + } + } +} + enum Screens { Splash(SplashScreen), Home(HomeScreen), + Setup(SetupScreen), } impl Screens { @@ -497,11 +666,14 @@ impl Screens { match self { Screens::Splash(_) => {} Screens::Home(home) => *self = home.input(input), + Screens::Setup(setup) => *self = setup.input(input), } } } static UPDATE_ENCODER: AtomicBool = AtomicBool::new(false); +static UPDATE_BUTTON: AtomicBool = AtomicBool::new(false); +static UPDATE_TIMER: AtomicBool = AtomicBool::new(false); #[interrupt(atmega328p)] #[allow(non_snake_case)] @@ -511,6 +683,22 @@ fn PCINT0() { }) } +#[interrupt(atmega328p)] +#[allow(non_snake_case)] +fn PCINT1() { + interrupt::free(|_cs| { + UPDATE_BUTTON.store(true, Ordering::SeqCst); + }) +} + +#[interrupt(atmega328p)] +#[allow(non_snake_case)] +fn TIMER1_COMPA() { + interrupt::free(|_cs| { + UPDATE_TIMER.store(true, Ordering::SeqCst); + }) +} + #[atmega_hal::entry] fn main() -> ! { // Get peripherals, pins, eeprom and delay @@ -568,9 +756,18 @@ fn main() -> ! { let mut encoder = Rotary::new(pins.pb6.into_pull_up_input(), pins.pb7.into_pull_up_input()); encoder.update(); - // Enable interrupts for encoder // TODO: and switch - dp.EXINT.pcicr.write(|w| w.pcie().bits(0b0000_0001)); + // Init button + let button = pins.pc0.into_pull_up_input(); + + // Init Timer/Counter1 + let tc1 = dp.TC1; + tc1.ocr1a.write(|w| unsafe { w.bits(65535) }); + + // Enable interrupts for encoder, button and timer + dp.EXINT.pcicr.write(|w| w.pcie().bits(0b0000_0011)); dp.EXINT.pcmsk0.write(|w| w.pcint().bits(0b1100_0000)); + dp.EXINT.pcmsk1.write(|w| w.pcint().bits(0b0000_0001)); + tc1.timsk1.write(|w| w.ocie1a().set_bit()); // Create screen let mut screen = Screens::Splash(SplashScreen); @@ -613,6 +810,33 @@ fn main() -> ! { }) } + if UPDATE_TIMER.load(Ordering::SeqCst) { + tc1.tccr1b.write(|w| w.cs1().no_clock()); + screen.input(&Input::Back); + update_screen = true; + UPDATE_TIMER.store(false, Ordering::SeqCst); + } + + if UPDATE_BUTTON.load(Ordering::SeqCst) { + delay.delay_ms(2_u8); + + if button.is_low() { + // Press + if tc1.tccr1b.read().cs1().is_no_clock() { + tc1.tcnt1.write(|w| unsafe { w.bits(0) }); + tc1.tccr1b.write(|w| w.cs1().prescale_64()); + } + } else { + // Release + if tc1.tccr1b.read().cs1().is_prescale_64() { + tc1.tccr1b.write(|w| w.cs1().no_clock()); + screen.input(&Input::Select); + update_screen = true; + } + } + UPDATE_BUTTON.store(false, Ordering::SeqCst); + } + if update_screen { lcd.draw(&screen); update_screen = false;