#![feature(abi_avr_interrupt)] #![no_std] #![no_main] use atmega_hal::{ clock::MHz8, delay::Delay, pins, spi::{DataOrder, SerialClockRate, Settings, Spi}, Peripherals, }; use avr_device::interrupt; use avr_eeprom::{eeprom, Eeprom}; use core::sync::atomic::{AtomicBool, AtomicU8, Ordering}; use embedded_hal::{ blocking::delay::DelayMs, spi::{Mode, Phase, Polarity}, }; use panic_halt as _; mod assets; mod lcd; mod rotary; mod screen; use lcd::Lcd; use rotary::{Direction, Rotary}; use screen::Screens; eeprom! { static eeprom EEPROM_CONTRAST: u8 = 8; static eeprom EEPROM_BACKLIGHT: u8 = 1; } pub enum Input { Next, Previous, Select, Back, } static UPDATE_ENCODER: AtomicBool = AtomicBool::new(false); static UPDATE_BUTTON: AtomicBool = AtomicBool::new(false); static UPDATE_TIMER: AtomicBool = AtomicBool::new(false); static CONTRAST: AtomicU8 = AtomicU8::new(0); static BACKLIGHT: AtomicU8 = AtomicU8::new(0); #[interrupt(atmega328p)] #[allow(non_snake_case)] fn PCINT0() { interrupt::free(|_cs| { UPDATE_ENCODER.store(true, Ordering::SeqCst); }) } #[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 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 CONTRAST.store(eeprom.read_value(&EEPROM_CONTRAST), Ordering::SeqCst); BACKLIGHT.store(eeprom.read_value(&EEPROM_BACKLIGHT), Ordering::SeqCst); // 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.load(Ordering::SeqCst)) }); 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::new(spi, pins.pb0.into_output(), pins.pb1.into_output()); lcd.init(CONTRAST.load(Ordering::SeqCst)); // Init encoder let mut encoder = Rotary::new(pins.pb6.into_pull_up_input(), pins.pb7.into_pull_up_input()); encoder.update(); // 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(screen::Splash); // Draw splash screen lcd.draw(&screen); // Show splash screen for a moment delay.delay_ms(2000_u16); // Set home screen screen = Screens::Home(screen::Home::new()); // Draw screen lcd.draw(&screen); let mut update_screen = false; // Enable interrupts globally unsafe { interrupt::enable() }; #[allow(clippy::empty_loop)] loop { if UPDATE_ENCODER.load(Ordering::SeqCst) { interrupt::free(|_cs| { delay.delay_ms(3_u8); match encoder.update() { Direction::Clockwise => { screen.input(&Input::Next); update_screen = true; } Direction::CounterClockwise => { screen.input(&Input::Previous); update_screen = true; } Direction::None => {} } UPDATE_ENCODER.store(false, Ordering::SeqCst); }) } 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; } } }