#![no_std] #![no_main] use atmega_hal::{ clock::MHz8, delay::Delay, pins, port::{mode::Output, Pin, PB0, PB1}, spi::{DataOrder, SerialClockRate, Settings, Spi}, Peripherals, }; use avr_device::interrupt::{self, Mutex}; use avr_eeprom::{eeprom, Eeprom}; use core::cell::RefCell; use embedded_hal::{ blocking::delay::DelayMs, spi::{FullDuplex, Mode, Phase, Polarity}, }; use nb::block; use panic_halt as _; eeprom! { static eeprom CONTRAST: u8 = 8; static eeprom BACKLIGHT: u8 = 1; } // TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized const SACRED_CHAO: [[u8; 40]; 5] = [ [ 0x00, 0x00, 0x00, 0x00, 0x80, 0xC0, 0xE0, 0xF0, 0xF0, 0xF8, 0xFC, 0xFC, 0xFE, 0xFE, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFE, 0xFE, 0xFC, 0xFC, 0xF8, 0xF0, 0xF0, 0xE0, 0xC0, 0x80, 0x00, 0x00, 0x00, 0x00, ], [ 0x80, 0xF0, 0xFC, 0xFE, 0xFF, 0xFF, 0x7F, 0x3F, 0x1F, 0x3F, 0x3F, 0x7F, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0x1F, 0x0F, 0x0F, 0x07, 0x07, 0x07, 0x03, 0xC3, 0xE3, 0x73, 0x37, 0x17, 0x07, 0x0F, 0x1E, 0x3C, 0xF0, 0x80, ], [ 0xFF, 0xFF, 0xFF, 0xE7, 0xC3, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0xFC, 0xFE, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFC, 0x30, 0x00, 0x00, 0xFF, ], [ 0x01, 0x0F, 0x3F, 0x4F, 0x9F, 0x3F, 0x3E, 0x3C, 0x7C, 0x7C, 0x7C, 0x7C, 0x3E, 0x3E, 0x3E, 0x1F, 0x1F, 0x0F, 0x07, 0x01, 0x00, 0x00, 0x00, 0x01, 0x03, 0x07, 0x07, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x07, 0x07, 0x03, 0x81, 0x40, 0x30, 0x0E, 0x01, ], [ 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x04, 0x08, 0x08, 0x10, 0x20, 0x20, 0x40, 0x40, 0x40, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x40, 0x40, 0x40, 0x20, 0x20, 0x10, 0x08, 0x08, 0x04, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, ], ]; // TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized const ONDERS_ORG: [[u8; 48]; 2] = [ [ 0xE0, 0x60, 0xE0, 0x00, 0x00, 0xE0, 0x60, 0xE0, 0x00, 0x00, 0xE0, 0x60, 0xF8, 0x00, 0x00, 0xE0, 0xA0, 0xE0, 0x00, 0x00, 0xE0, 0x20, 0x60, 0x00, 0x00, 0xE0, 0xA0, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x60, 0xE0, 0x00, 0x00, 0xE0, 0x20, 0x60, 0x00, 0x00, 0xE0, 0x60, 0xE0, ], [ 0x03, 0x02, 0x03, 0x00, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x03, 0x02, 0x03, 0x00, 0x00, 0x03, 0x02, 0x02, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x02, 0x02, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x02, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x0B, 0x0A, 0x0F, ], ]; // TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized const SYM_0: [&[u8]; 2] = [ &[0xF8, 0xFC, 0x0C, 0xFC, 0xF8], &[0x1F, 0x3F, 0x30, 0x3F, 0x1F], ]; // TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized const SYM_1: [&[u8]; 2] = [ &[0x30, 0x30, 0xFC, 0xFC, 0x00], &[0x30, 0x30, 0x3F, 0x3F, 0x30], ]; // TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized const SYM_2: [&[u8]; 2] = [ &[0x18, 0x1C, 0x8C, 0xFC, 0xF8], &[0x38, 0x3E, 0x3F, 0x33, 0x30], ]; // TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized const SYM_3: [&[u8]; 2] = [ &[0x18, 0x9C, 0x8C, 0xFC, 0x78], &[0x18, 0x39, 0x31, 0x3F, 0x1E], ]; // TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized const SYM_4: [&[u8]; 2] = [ &[0x80, 0xE0, 0x78, 0xFC, 0xFC], &[0x07, 0x07, 0x06, 0x3F, 0x3F], ]; // TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized const SYM_5: [&[u8]; 2] = [ &[0xFC, 0xFC, 0x8C, 0x8C, 0x0C], &[0x1C, 0x3D, 0x31, 0x3F, 0x1F], ]; // TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized const SYM_6: [&[u8]; 2] = [ &[0xF8, 0xFC, 0x8C, 0xBC, 0x38], &[0x1F, 0x3F, 0x31, 0x3F, 0x1F], ]; // TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized const SYM_7: [&[u8]; 2] = [ &[0x0C, 0x0C, 0xEC, 0xFC, 0x1C], &[0x00, 0x3E, 0x3F, 0x01, 0x00], ]; // TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized const SYM_8: [&[u8]; 2] = [ &[0x78, 0xFC, 0x8C, 0xFC, 0x78], &[0x1E, 0x3F, 0x31, 0x3F, 0x1E], ]; // TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized const SYM_9: [&[u8]; 2] = [ &[0xF8, 0xFC, 0x8C, 0xFC, 0xF8], &[0x1C, 0x3D, 0x31, 0x3F, 0x1F], ]; // TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized const SYM_C: [&[u8]; 2] = [ &[0xF8, 0xFC, 0x0C, 0x1C, 0x18], &[0x1F, 0x3F, 0x30, 0x38, 0x18], ]; // TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized const SYM_E: [&[u8]; 2] = [ &[0xFC, 0xFC, 0x8C, 0x8C, 0x0C, 0x0C], &[0x3F, 0x3F, 0x31, 0x31, 0x30, 0x30], ]; // 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_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_S: [&[u8]; 2] = [ &[0xF8, 0xFC, 0x8C, 0x8C, 0x9C, 0x18], &[0x18, 0x39, 0x31, 0x31, 0x3F, 0x1F], ]; // TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized const SYM_T: [&[u8]; 2] = [ &[0x0C, 0x0C, 0xFC, 0xFC, 0x0C, 0x0C], &[0x00, 0x00, 0x3F, 0x3F, 0x00, 0x00], ]; // TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized const SYM_U: [&[u8]; 2] = [ &[0xFC, 0xFC, 0x00, 0x00, 0xFC, 0xFC], &[0x1F, 0x3F, 0x30, 0x30, 0x3F, 0x1F], ]; // TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized const SYM_INVALID: [&[u8]; 2] = [ &[0x80, 0xE0, 0x98, 0xCC, 0x4C, 0x18, 0xE0, 0x80], &[0x01, 0x07, 0x1F, 0x24, 0x25, 0x1F, 0x07, 0x01], ]; // TODO: Use https://github.com/rust-lang/rust/issues/85077 when stabilized const SYMBOL_TABLE: [&[&[u8]; 2]; 48] = [ &SYM_0, // '0' &SYM_1, // '1' &SYM_2, // '2' &SYM_3, // '3' &SYM_4, // '4' &SYM_5, // '5' &SYM_6, // '6' &SYM_7, // '7' &SYM_8, // '8' &SYM_9, // '9' &SYM_INVALID, // ':' &SYM_INVALID, // ';' &SYM_INVALID, // '<' &SYM_INVALID, // '=' &SYM_INVALID, // '>' &SYM_INVALID, // '?' &SYM_INVALID, // '@' &SYM_INVALID, // 'A' &SYM_INVALID, // 'B' &SYM_C, // 'C' &SYM_INVALID, // 'D' &SYM_E, // 'E' &SYM_INVALID, // 'F' &SYM_INVALID, // 'G' &SYM_H, // 'H' &SYM_INVALID, // 'I' &SYM_INVALID, // 'J' &SYM_INVALID, // 'K' &SYM_INVALID, // 'L' &SYM_INVALID, // 'M' &SYM_INVALID, // 'N' &SYM_INVALID, // 'O' &SYM_P, // 'P' &SYM_INVALID, // 'Q' &SYM_INVALID, // 'R' &SYM_S, // 'S' &SYM_T, // 'T' &SYM_U, // 'U' &SYM_INVALID, // 'V' &SYM_INVALID, // 'W' &SYM_INVALID, // 'X' &SYM_INVALID, // 'Y' &SYM_INVALID, // 'Z' &SYM_INVALID, // '[' &SYM_INVALID, // '\' &SYM_INVALID, // ']' &SYM_INVALID, // '^' &SYM_INVALID, // '_' ]; pub struct Lcd { spi: Spi, cd: Pin, rst: Pin, } impl Lcd { fn init(&mut self, contrast: u8) { let mut delay = Delay::::new(); // TODO: Test if delay is really needed delay.delay_ms(1_u8); self.rst.set_high(); // TODO: Try to reduce delay to a minimum delay.delay_ms(1_u8); block!(self.spi.send(0x40)).unwrap(); // (6) Set Scroll Line: Display start line 0 block!(self.spi.send(0xA1)).unwrap(); // (13) Set SEG direction: SEG reverse block!(self.spi.send(0xC0)).unwrap(); // (14) Set COM direction: Normal COM0 - COM63 // block!(spi.send(0xA4)).unwrap(); // (10) Set All Pixel On: Disable -> Set All Pixel to ON */ block!(self.spi.send(0xA6)).unwrap(); // (11) Set Inverse Display: Display inverse off block!(self.spi.send(0xA2)).unwrap(); // (17) Set LCD Bias Ratio: Set Bias 1/9 (Duty 1/65) block!(self.spi.send(0x2F)).unwrap(); // (5) Set Power Control: Booster, Regulator and Follower on block!(self.spi.send(0x27)).unwrap(); // (8) Set VLCD Resistor Ratio: Set Contrast // block!(spi.send(0xEE)).unwrap(); // (18) Reset Cursor Update Mode block!(self.spi.send(0x81)).unwrap(); // (9) Set Electronic Volume: Set Contrast block!(self.spi.send(contrast)).unwrap(); // (9) Set Electronic Volume: Set Contrast // block!(spi.send(0xFA)).unwrap(); // (25) Set Adv. Program Control 0: Set Temperature compensation curve to -0.11%/°C // block!(spi.send(0x90)).unwrap(); // (25) Set Adv. Program Control 0: Set Temperature compensation curve to -0.11%/°C block!(self.spi.send(0xAF)).unwrap(); // (12) Set Display Enable: Display on // TODO: This delay fixes issues, try find a better solution delay.delay_ms(1_u8); } fn move_cursor(&mut self, segment: u8, page: u8) { assert!(segment < 102); assert!(page < 8); block!(self.spi.send(0x0F & segment)).unwrap(); block!(self.spi.send(0x10 + (segment >> 4))).unwrap(); block!(self.spi.send(0xB0 + page)).unwrap(); } fn clear(&mut self) { let mut delay = Delay::::new(); for page in 0..8 { self.move_cursor(0, page as u8); // TODO: This delay fixes issues, try find a better solution delay.delay_ms(1_u8); self.cd.set_high(); for _ in 0..102 { block!(self.spi.send(0x00)).unwrap(); } // TODO: This delay fixes issues, try find a better solution delay.delay_ms(1_u8); self.cd.set_low(); } } fn print(&mut self, segment: u8, page: u8, string: &str) { let mut delay = Delay::::new(); for i in 0..2 { self.move_cursor(segment, page + i); // TODO: This delay fixes issues, try find a better solution delay.delay_ms(1_u8); self.cd.set_high(); block!(self.spi.send(0x00)).unwrap(); block!(self.spi.send(0x00)).unwrap(); for c in string.chars() { for segment in SYMBOL_TABLE[c as usize - 48][i as usize] { block!(self.spi.send(*segment)).unwrap(); } block!(self.spi.send(0x00)).unwrap(); } block!(self.spi.send(0x00)).unwrap(); // TODO: This delay fixes issues, try find a better solution delay.delay_ms(1_u8); self.cd.set_low(); } } fn print_inverted(&mut self, segment: u8, page: u8, string: &str) { let mut delay = Delay::::new(); for i in 0..2 { self.move_cursor(segment, page + i); // TODO: This delay fixes issues, try find a better solution delay.delay_ms(1_u8); self.cd.set_high(); block!(self.spi.send(0xFF)).unwrap(); block!(self.spi.send(0xFF)).unwrap(); for c in string.chars() { for segment in SYMBOL_TABLE[c as usize - 48][i as usize] { block!(self.spi.send(!*segment)).unwrap(); } block!(self.spi.send(0xFF)).unwrap(); } block!(self.spi.send(0xFF)).unwrap(); // TODO: This delay fixes issues, try find a better solution delay.delay_ms(1_u8); self.cd.set_low(); } } fn screen(&mut self) { self.clear(); interrupt::free(|cs| match &*SCREEN.borrow(cs).borrow() { Screens::Splash(splash) => { splash.draw(self); } Screens::Home(home) => { home.draw(self); } }) } } pub trait Draw { fn draw(&self, lcd: &mut Lcd); } struct SplashScreen; impl Draw for SplashScreen { fn draw(&self, lcd: &mut Lcd) { let mut delay = Delay::::new(); for (i, page) in SACRED_CHAO.iter().enumerate() { lcd.move_cursor(31, 1 + i as u8); // TODO: This delay fixes issues, try find a better solution delay.delay_ms(1_u8); lcd.cd.set_high(); for segment in page { block!(lcd.spi.send(*segment)).unwrap(); } // TODO: This delay fixes issues, try find a better solution delay.delay_ms(1_u8); lcd.cd.set_low(); } for (i, page) in ONDERS_ORG.iter().enumerate() { lcd.move_cursor(27, 6 + i as u8); // TODO: This delay fixes issues, try find a better solution delay.delay_ms(1_u8); lcd.cd.set_high(); for segment in page { block!(lcd.spi.send(*segment)).unwrap(); } // TODO: This delay fixes issues, try find a better solution delay.delay_ms(1_u8); lcd.cd.set_low(); } } } enum HomeSelection { Ch1, Ch2, Ch3, Setup, } struct HomeScreen { active: HomeSelection, } impl Draw for HomeScreen { fn draw(&self, lcd: &mut Lcd) { match &self.active { HomeSelection::Ch1 => { lcd.print_inverted(0, 0, "CH1"); lcd.print(0, 2, "CH2"); lcd.print(0, 4, "CH3"); lcd.print(33, 6, "SETUP"); } HomeSelection::Ch2 => { lcd.print(0, 0, "CH1"); lcd.print_inverted(0, 2, "CH2"); lcd.print(0, 4, "CH3"); lcd.print(33, 6, "SETUP"); } HomeSelection::Ch3 => { lcd.print(0, 0, "CH1"); lcd.print(0, 2, "CH2"); lcd.print_inverted(0, 4, "CH3"); lcd.print(33, 6, "SETUP"); } HomeSelection::Setup => { lcd.print(0, 0, "CH1"); lcd.print(0, 2, "CH2"); lcd.print(0, 4, "CH3"); lcd.print_inverted(33, 6, "SETUP"); } } } } enum Screens { Splash(SplashScreen), Home(HomeScreen), } // TODO: Investigate if this should also be volatile static SCREEN: Mutex> = Mutex::new(RefCell::new(Screens::Splash(SplashScreen))); #[atmega_hal::entry] fn main() -> ! { interrupt::free(|cs| { // Get peripherals, pins, eeprom and delay let dp = Peripherals::take().unwrap(); let pins = pins!(dp); let eeprom = Eeprom::new(dp.EEPROM); let mut delay = Delay::::new(); // Get contrast and backlight from EEPROM let contrast = eeprom.read_value(&CONTRAST); let backlight = eeprom.read_value(&BACKLIGHT); // Init display backlight let tc0 = dp.TC0; tc0.tccr0a.write(|w| { w.wgm0().pwm_fast(); w.com0b().match_clear(); w }); tc0.tccr0b.write(|w| { w.wgm02().set_bit(); w.cs0().prescale_64(); w }); tc0.ocr0a.write(|w| unsafe { w.bits(255) }); tc0.ocr0b.write(|w| unsafe { w.bits(backlight) }); pins.pd5.into_output(); // Init SPI let (spi, _) = Spi::new( dp.SPI, pins.pb5.into_output(), pins.pb3.into_output(), pins.pb4.into_pull_up_input(), pins.pb2.into_output(), Settings { data_order: DataOrder::MostSignificantFirst, clock: SerialClockRate::OscfOver2, mode: Mode { polarity: Polarity::IdleLow, phase: Phase::CaptureOnFirstTransition, }, }, ); // Init LCD let mut lcd = Lcd { spi, cd: pins.pb0.into_output(), rst: pins.pb1.into_output(), }; lcd.init(contrast); // Draw splash screen lcd.screen(); // Show splash screen for a moment delay.delay_ms(2000_u16); // Set home screen let screen = SCREEN.borrow(cs); *screen.borrow_mut() = Screens::Home(HomeScreen { active: HomeSelection::Ch1, }); // Draw screen lcd.screen(); // Show splash screen for a moment delay.delay_ms(1000_u16); // Demo select CH2 *screen.borrow_mut() = Screens::Home(HomeScreen { active: HomeSelection::Ch2, }); // Draw screen lcd.screen(); // Show splash screen for a moment delay.delay_ms(1000_u16); // Demo select CH3 *screen.borrow_mut() = Screens::Home(HomeScreen { active: HomeSelection::Ch3, }); // Draw screen lcd.screen(); // Show splash screen for a moment delay.delay_ms(1000_u16); // Demo select SETUP *screen.borrow_mut() = Screens::Home(HomeScreen { active: HomeSelection::Setup, }); // Draw screen lcd.screen(); }); #[allow(clippy::empty_loop)] loop {} }