fw-rust: Handle screens differently, use encoder
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Handle the screens differently and implement rotary encoder support.
This commit is contained in:
parent
4a33986e8d
commit
2ec8d1aeb9
2 changed files with 219 additions and 102 deletions
|
@ -1,3 +1,4 @@
|
||||||
|
#![feature(abi_avr_interrupt)]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
|
|
||||||
|
@ -9,9 +10,9 @@ use atmega_hal::{
|
||||||
spi::{DataOrder, SerialClockRate, Settings, Spi},
|
spi::{DataOrder, SerialClockRate, Settings, Spi},
|
||||||
Peripherals,
|
Peripherals,
|
||||||
};
|
};
|
||||||
use avr_device::interrupt::{self, Mutex};
|
use avr_device::interrupt;
|
||||||
use avr_eeprom::{eeprom, Eeprom};
|
use avr_eeprom::{eeprom, Eeprom};
|
||||||
use core::cell::RefCell;
|
use core::sync::atomic::{AtomicBool, Ordering};
|
||||||
use embedded_hal::{
|
use embedded_hal::{
|
||||||
blocking::delay::DelayMs,
|
blocking::delay::DelayMs,
|
||||||
spi::{FullDuplex, Mode, Phase, Polarity},
|
spi::{FullDuplex, Mode, Phase, Polarity},
|
||||||
|
@ -19,6 +20,9 @@ use embedded_hal::{
|
||||||
use nb::block;
|
use nb::block;
|
||||||
use panic_halt as _;
|
use panic_halt as _;
|
||||||
|
|
||||||
|
mod rotary;
|
||||||
|
use rotary::{Direction, Rotary};
|
||||||
|
|
||||||
eeprom! {
|
eeprom! {
|
||||||
static eeprom CONTRAST: u8 = 8;
|
static eeprom CONTRAST: u8 = 8;
|
||||||
static eeprom BACKLIGHT: u8 = 1;
|
static eeprom BACKLIGHT: u8 = 1;
|
||||||
|
@ -345,20 +349,27 @@ impl Lcd {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn screen(&mut self) {
|
fn draw(&mut self, screen: &Screens) {
|
||||||
self.clear();
|
interrupt::free(|_cs| {
|
||||||
|
self.clear();
|
||||||
|
|
||||||
interrupt::free(|cs| match &*SCREEN.borrow(cs).borrow() {
|
match screen {
|
||||||
Screens::Splash(splash) => {
|
Screens::Splash(splash) => {
|
||||||
splash.draw(self);
|
splash.draw(self);
|
||||||
|
}
|
||||||
|
Screens::Home(home) => {
|
||||||
|
home.draw(self);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Screens::Home(home) => {
|
});
|
||||||
home.draw(self);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum Input {
|
||||||
|
Next,
|
||||||
|
Previous,
|
||||||
|
}
|
||||||
|
|
||||||
pub trait Draw {
|
pub trait Draw {
|
||||||
fn draw(&self, lcd: &mut Lcd);
|
fn draw(&self, lcd: &mut Lcd);
|
||||||
}
|
}
|
||||||
|
@ -414,6 +425,41 @@ struct HomeScreen {
|
||||||
active: HomeSelection,
|
active: HomeSelection,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl HomeScreen {
|
||||||
|
fn input(&self, input: &Input) -> Screens {
|
||||||
|
match input {
|
||||||
|
Input::Next => match self.active {
|
||||||
|
HomeSelection::Ch1 => Screens::Home(HomeScreen {
|
||||||
|
active: HomeSelection::Ch2,
|
||||||
|
}),
|
||||||
|
HomeSelection::Ch2 => Screens::Home(HomeScreen {
|
||||||
|
active: HomeSelection::Ch3,
|
||||||
|
}),
|
||||||
|
HomeSelection::Ch3 => Screens::Home(HomeScreen {
|
||||||
|
active: HomeSelection::Setup,
|
||||||
|
}),
|
||||||
|
HomeSelection::Setup => Screens::Home(HomeScreen {
|
||||||
|
active: HomeSelection::Ch1,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Input::Previous => match self.active {
|
||||||
|
HomeSelection::Ch1 => Screens::Home(HomeScreen {
|
||||||
|
active: HomeSelection::Setup,
|
||||||
|
}),
|
||||||
|
HomeSelection::Ch2 => Screens::Home(HomeScreen {
|
||||||
|
active: HomeSelection::Ch1,
|
||||||
|
}),
|
||||||
|
HomeSelection::Ch3 => Screens::Home(HomeScreen {
|
||||||
|
active: HomeSelection::Ch2,
|
||||||
|
}),
|
||||||
|
HomeSelection::Setup => Screens::Home(HomeScreen {
|
||||||
|
active: HomeSelection::Ch3,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Draw for HomeScreen {
|
impl Draw for HomeScreen {
|
||||||
fn draw(&self, lcd: &mut Lcd) {
|
fn draw(&self, lcd: &mut Lcd) {
|
||||||
match &self.active {
|
match &self.active {
|
||||||
|
@ -450,112 +496,132 @@ enum Screens {
|
||||||
Home(HomeScreen),
|
Home(HomeScreen),
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Investigate if this should also be volatile
|
impl Screens {
|
||||||
static SCREEN: Mutex<RefCell<Screens>> = Mutex::new(RefCell::new(Screens::Splash(SplashScreen)));
|
fn input(&mut self, input: &Input) {
|
||||||
|
match self {
|
||||||
|
Screens::Splash(_) => {}
|
||||||
|
Screens::Home(home) => *self = home.input(input),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static UPDATE_ENCODER: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
|
#[interrupt(atmega328p)]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
fn PCINT0() {
|
||||||
|
interrupt::free(|_cs| {
|
||||||
|
UPDATE_ENCODER.store(true, Ordering::SeqCst);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[atmega_hal::entry]
|
#[atmega_hal::entry]
|
||||||
fn main() -> ! {
|
fn main() -> ! {
|
||||||
interrupt::free(|cs| {
|
// Get peripherals, pins, eeprom and delay
|
||||||
// Get peripherals, pins, eeprom and delay
|
let dp = Peripherals::take().unwrap();
|
||||||
let dp = Peripherals::take().unwrap();
|
let pins = pins!(dp);
|
||||||
let pins = pins!(dp);
|
let eeprom = Eeprom::new(dp.EEPROM);
|
||||||
let eeprom = Eeprom::new(dp.EEPROM);
|
let mut delay = Delay::<MHz8>::new();
|
||||||
let mut delay = Delay::<MHz8>::new();
|
|
||||||
|
|
||||||
// Get contrast and backlight from EEPROM
|
// Get contrast and backlight from EEPROM
|
||||||
let contrast = eeprom.read_value(&CONTRAST);
|
let contrast = eeprom.read_value(&CONTRAST);
|
||||||
let backlight = eeprom.read_value(&BACKLIGHT);
|
let backlight = eeprom.read_value(&BACKLIGHT);
|
||||||
|
|
||||||
// Init display backlight
|
// Init display backlight
|
||||||
let tc0 = dp.TC0;
|
let tc0 = dp.TC0;
|
||||||
tc0.tccr0a.write(|w| {
|
tc0.tccr0a.write(|w| {
|
||||||
w.wgm0().pwm_fast();
|
w.wgm0().pwm_fast();
|
||||||
w.com0b().match_clear();
|
w.com0b().match_clear();
|
||||||
w
|
w
|
||||||
});
|
});
|
||||||
tc0.tccr0b.write(|w| {
|
tc0.tccr0b.write(|w| {
|
||||||
w.wgm02().set_bit();
|
w.wgm02().set_bit();
|
||||||
w.cs0().prescale_64();
|
w.cs0().prescale_64();
|
||||||
w
|
w
|
||||||
});
|
});
|
||||||
tc0.ocr0a.write(|w| unsafe { w.bits(255) });
|
tc0.ocr0a.write(|w| unsafe { w.bits(255) });
|
||||||
tc0.ocr0b.write(|w| unsafe { w.bits(backlight) });
|
tc0.ocr0b.write(|w| unsafe { w.bits(backlight) });
|
||||||
pins.pd5.into_output();
|
pins.pd5.into_output();
|
||||||
|
|
||||||
// Init SPI
|
// Init SPI
|
||||||
let (spi, _) = Spi::new(
|
let (spi, _) = Spi::new(
|
||||||
dp.SPI,
|
dp.SPI,
|
||||||
pins.pb5.into_output(),
|
pins.pb5.into_output(),
|
||||||
pins.pb3.into_output(),
|
pins.pb3.into_output(),
|
||||||
pins.pb4.into_pull_up_input(),
|
pins.pb4.into_pull_up_input(),
|
||||||
pins.pb2.into_output(),
|
pins.pb2.into_output(),
|
||||||
Settings {
|
Settings {
|
||||||
data_order: DataOrder::MostSignificantFirst,
|
data_order: DataOrder::MostSignificantFirst,
|
||||||
clock: SerialClockRate::OscfOver2,
|
clock: SerialClockRate::OscfOver2,
|
||||||
mode: Mode {
|
mode: Mode {
|
||||||
polarity: Polarity::IdleLow,
|
polarity: Polarity::IdleLow,
|
||||||
phase: Phase::CaptureOnFirstTransition,
|
phase: Phase::CaptureOnFirstTransition,
|
||||||
},
|
|
||||||
},
|
},
|
||||||
);
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// Init LCD
|
// Init LCD
|
||||||
let mut lcd = Lcd {
|
let mut lcd = Lcd {
|
||||||
spi,
|
spi,
|
||||||
cd: pins.pb0.into_output(),
|
cd: pins.pb0.into_output(),
|
||||||
rst: pins.pb1.into_output(),
|
rst: pins.pb1.into_output(),
|
||||||
};
|
};
|
||||||
lcd.init(contrast);
|
lcd.init(contrast);
|
||||||
|
|
||||||
// Draw splash screen
|
// Init encoder
|
||||||
lcd.screen();
|
let mut encoder = Rotary::new(pins.pb6.into_pull_up_input(), pins.pb7.into_pull_up_input());
|
||||||
|
encoder.update();
|
||||||
|
|
||||||
// Show splash screen for a moment
|
// Enable interrupts for encoder // TODO: and switch
|
||||||
delay.delay_ms(2000_u16);
|
dp.EXINT.pcicr.write(|w| w.pcie().bits(0b0000_0001));
|
||||||
|
dp.EXINT.pcmsk0.write(|w| w.pcint().bits(0b1100_0000));
|
||||||
|
|
||||||
// Set home screen
|
// Create screen
|
||||||
let screen = SCREEN.borrow(cs);
|
let mut screen = Screens::Splash(SplashScreen);
|
||||||
*screen.borrow_mut() = Screens::Home(HomeScreen {
|
|
||||||
active: HomeSelection::Ch1,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Draw screen
|
// Draw splash screen
|
||||||
lcd.screen();
|
lcd.draw(&screen);
|
||||||
|
|
||||||
// Show splash screen for a moment
|
// Show splash screen for a moment
|
||||||
delay.delay_ms(1000_u16);
|
delay.delay_ms(2000_u16);
|
||||||
|
|
||||||
// Demo select CH2
|
// Set home screen
|
||||||
*screen.borrow_mut() = Screens::Home(HomeScreen {
|
screen = Screens::Home(HomeScreen {
|
||||||
active: HomeSelection::Ch2,
|
active: HomeSelection::Ch1,
|
||||||
});
|
|
||||||
|
|
||||||
// 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();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Draw screen
|
||||||
|
lcd.draw(&screen);
|
||||||
|
|
||||||
|
let mut update_screen = false;
|
||||||
|
|
||||||
|
// Enable interrupts globally
|
||||||
|
unsafe { interrupt::enable() };
|
||||||
|
|
||||||
#[allow(clippy::empty_loop)]
|
#[allow(clippy::empty_loop)]
|
||||||
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_screen {
|
||||||
|
lcd.draw(&screen);
|
||||||
|
update_screen = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
51
firmware/rust/src/rotary.rs
Normal file
51
firmware/rust/src/rotary.rs
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
use embedded_hal::digital::v2::InputPin;
|
||||||
|
|
||||||
|
pub struct Rotary<A, B> {
|
||||||
|
pin_a: A,
|
||||||
|
pin_b: B,
|
||||||
|
state: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Direction {
|
||||||
|
Clockwise,
|
||||||
|
CounterClockwise,
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A, B> Rotary<A, B>
|
||||||
|
where
|
||||||
|
A: InputPin,
|
||||||
|
B: InputPin,
|
||||||
|
{
|
||||||
|
pub fn new(pin_a: A, pin_b: B) -> Self {
|
||||||
|
Self {
|
||||||
|
pin_a,
|
||||||
|
pin_b,
|
||||||
|
state: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self) -> Direction {
|
||||||
|
let a = self.pin_a.is_low();
|
||||||
|
let b = self.pin_b.is_low();
|
||||||
|
|
||||||
|
let new_state = match (a, b) {
|
||||||
|
(Ok(true), Ok(true)) => 0b00,
|
||||||
|
(Ok(true), Ok(false)) => 0b01,
|
||||||
|
(Ok(false), Ok(true)) => 0b10,
|
||||||
|
(Ok(false), Ok(false)) => 0b11,
|
||||||
|
_ => return Direction::None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let direction = match (self.state, new_state) {
|
||||||
|
(0b10, 0b11) => Direction::Clockwise,
|
||||||
|
(0b01, 0b00) => Direction::Clockwise,
|
||||||
|
(0b01, 0b11) => Direction::CounterClockwise,
|
||||||
|
(0b10, 0b00) => Direction::CounterClockwise,
|
||||||
|
_ => Direction::None,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.state = new_state;
|
||||||
|
direction
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue