finga
7d8f5f6870
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Create `print_u8()` helper function to print `u8` primitives to lcd. Refactor contrast and backlight variables to also keep them in a global `AtomicU8`.
213 lines
5.6 KiB
Rust
213 lines
5.6 KiB
Rust
#![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::<MHz8>::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;
|
|
}
|
|
}
|
|
}
|