diff options
author | Marc Poulhiès <dkm@kataplop.net> | 2022-07-07 21:51:38 +0200 |
---|---|---|
committer | Marc Poulhiès <dkm@kataplop.net> | 2022-07-22 21:06:23 +0200 |
commit | 3c71e3a2a3af39d579c2649fddf0c8ba835bfa01 (patch) | |
tree | f383b855123ae2fb66ebb3537d36153388418ca8 /firmware/rust/src | |
parent | 972760b3e68156c72129b833d5624d4e6f60619b (diff) |
Move rust firmware in subdir
... for the upcoming Ada one.
Diffstat (limited to 'firmware/rust/src')
-rw-r--r-- | firmware/rust/src/main.rs | 551 |
1 files changed, 551 insertions, 0 deletions
diff --git a/firmware/rust/src/main.rs b/firmware/rust/src/main.rs new file mode 100644 index 0000000..e9b544c --- /dev/null +++ b/firmware/rust/src/main.rs @@ -0,0 +1,551 @@ +#![no_main] +#![no_std] + +// set the panic handler +use panic_halt as _; + +use core::convert::Infallible; + +use keyberon::layout::Layout; +use keyberon::matrix::{Matrix, PressedKeys}; +use rtic::app; +use stm32f0xx_hal as hal; +use usb_device::bus::UsbBusAllocator; +use usb_device::class::UsbClass as _; + +extern crate smart_leds; +extern crate ws2812_spi; +use smart_leds::{brightness, colors, SmartLedsWrite, RGB8}; + +use ws2812_spi as ws2812; + +use hal::delay::Delay; +use hal::gpio::{gpioa, Alternate, Input, Output, Pin, PullUp, PushPull, AF0}; +use hal::prelude::*; + +use embedded_hal::spi::FullDuplex; + +use hal::usb; +use hal::{ + spi::{EightBit, Mode, Phase, Polarity}, + stm32, timers, +}; + +use keyberon::action::Action; +use keyberon::debounce::Debouncer; +use keyberon::key_code::KbHidReport; +use keyberon::key_code::KeyCode; + +type Spi = hal::spi::Spi< + stm32::SPI1, + gpioa::PA5<Alternate<AF0>>, + gpioa::PA6<Alternate<AF0>>, + gpioa::PA7<Alternate<AF0>>, + EightBit, +>; + +type UsbClass = keyberon::Class<'static, usb::UsbBusType, Leds<Spi>>; + +type UsbDevice = usb_device::device::UsbDevice<'static, usb::UsbBusType>; + +trait ResultExt<T> { + fn get(self) -> T; +} +impl<T> ResultExt<T> for Result<T, Infallible> { + fn get(self) -> T { + match self { + Ok(v) => v, + Err(e) => match e {}, + } + } +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum CustomActions { + LightUp, + LightDown, + + ModeCycle, + ColorCycle, + FreqUp, + FreqDown, +} + +#[cfg(not(feature = "testmode"))] +#[rustfmt::skip] + +pub static LAYERS: keyberon::layout::Layers<CustomActions> = keyberon::layout::layout! { + { + [Kb1 Kb2 Kb3 Kb4 Kb5 Grave Kb6 Kb7 Kb8 Kb9 Kb0 Minus] + [Q W E R T Tab Y U I O P LBracket] + [A S D F G BSpace H J K L SColon Quote] + [Z X C V B Enter N M Comma Dot Slash Bslash ] + [LCtrl (1) LGui LShift LAlt Space RAlt RBracket Equal Delete RShift RCtrl] + } + { + [F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12] + [SysReq NumLock t t t Escape Insert PgUp PgDown VolUp VolDown Mute ] + [t t t t t t Home Up End t t t ] + [NonUsBslash {Action::Custom(CustomActions::ColorCycle)} {Action::Custom(CustomActions::FreqUp)} {Action::Custom(CustomActions::FreqDown)} t t Left Down Right t t PgUp ] + [{Action::Custom(CustomActions::LightUp)} t {Action::Custom(CustomActions::LightDown)} {Action::Custom(CustomActions::ModeCycle)} t t t t t t t PgDown] + } +}; + +#[cfg(feature = "testmode")] +#[rustfmt::skip] +pub static LAYERS: keyberon::layout::Layers<CustomActions> = keyberon::layout::layout! { + { + [A, A, A, A, A, A, A, A, A, A, A, A], + [A, A, A, A, A, A, A, A, A, A, A, A], + [A, A, A, A, A, A, A, A, A, A, A, A], + [A, A, A, A, A, A, A, A, A, A, A, A], + [A, A, A, A, A, A, A, A, A, A, A, A], + } +}; + +pub struct Leds<SPI> { + ws: ws2812::Ws2812<SPI>, + leds: [RGB8; 10], +} + +impl<SPI, E> keyberon::keyboard::Leds for Leds<SPI> +where + SPI: FullDuplex<u8, Error = E>, +{ + fn caps_lock(&mut self, status: bool) { + if status { + self.leds[0] = colors::BLUE; + } else { + self.leds[0] = colors::BLACK; + } + if self.ws.write(brightness(self.leds.iter().cloned(), 10)).is_err() { + panic!(); + } + } + + fn num_lock(&mut self, status: bool) { + if status { + self.leds[1] = colors::GREEN; + } else { + self.leds[1] = colors::BLACK; + } + if self.ws.write(brightness(self.leds.iter().cloned(), 10)).is_err() { + panic!(); + } + } + + fn compose(&mut self, status: bool) { + if status { + self.leds[3] = colors::VIOLET; + } else { + self.leds[3] = colors::BLACK; + } + if self.ws.write(brightness(self.leds.iter().cloned(), 10)).is_err() { + panic!(); + } + + } +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum BacklightMode { + Off, + Solid(RGB8), + Circling(RGB8, usize, usize, usize, bool), + Breath(RGB8, usize, usize, bool), +} + +pub struct Backlight { + mode: BacklightMode, + brightness: u8, +} + +trait ColorSeq { + fn next_color(&self) -> RGB8; +} + +const COLORS_SEQ: [RGB8; 5] = [ + colors::RED, + colors::GREEN, + colors::BLUE, + colors::VIOLET, + colors::YELLOW, +]; + +impl ColorSeq for RGB8 { + fn next_color(&self) -> RGB8 { + let mut next = false; + + for c in &COLORS_SEQ { + if next { + return *c; + } + + if *self == *c { + next = true; + } + } + + COLORS_SEQ[0] + } +} + +impl Backlight { + pub fn next_mode(&mut self) { + self.mode = match self.mode { + BacklightMode::Off => BacklightMode::Solid(colors::RED), + BacklightMode::Solid(_) => BacklightMode::Circling(colors::RED, 100, 0, 0, true), + BacklightMode::Circling(_, _, _, _, _) => { + BacklightMode::Breath(colors::RED, 10, 0, true) + } + BacklightMode::Breath(_, _, _, _) => BacklightMode::Off, + } + } + + pub fn change_freq(&mut self, up: bool) { + self.mode = match self.mode { + BacklightMode::Breath(c, tstep, step, dir) => { + let tstep = if up { + if tstep - 10 > 10 { + tstep - 10 + } else { + 10 + } + } else if tstep + 10 < 1000 { + tstep + 10 + } else { + 1000 + }; + BacklightMode::Breath(c, tstep, step, dir) + } + BacklightMode::Circling(c, tstep, step, i, dir) => { + let tstep = if up { + if tstep - 10 > 10 { + tstep - 10 + } else { + 10 + } + } else if tstep + 10 < 1000 { + tstep + 10 + } else { + 1000 + }; + BacklightMode::Circling(c, tstep, step, i, dir) + } + any => any, + } + } + + pub fn next_color(&mut self) { + self.mode = match self.mode { + BacklightMode::Solid(c) => BacklightMode::Solid(c.next_color()), + BacklightMode::Breath(c, tstep, step, dir) => { + BacklightMode::Breath(c.next_color(), tstep, step, dir) + } + BacklightMode::Circling(c, ts, s, i, dir) => { + BacklightMode::Circling(c.next_color(), ts, s, i, dir) + } + any => any, + } + } + + pub fn refresh_leds(&mut self, leds: &mut Leds<Spi>) { + self.mode = match self.mode { + BacklightMode::Off => { + for l in leds.leds[4..].iter_mut() { + *l = colors::BLACK; + } + BacklightMode::Off + } + + BacklightMode::Solid(c) => { + for l in leds.leds[4..].iter_mut() { + *l = c; + } + BacklightMode::Solid(c) + } + + BacklightMode::Breath(c, tstep, step, dir) => { + let mut step = step + 1; + let mut new_dir = dir; + + if step >= tstep { + step = 0; + + for l in leds.leds[4..].iter_mut() { + *l = c; + } + + if dir { + if self.brightness == 100 { + self.brightness -= 1; + new_dir = false; + } + self.brightness += 1; + } else { + if self.brightness == 5 { + self.brightness += 1; + new_dir = true; + } + self.brightness -= 1; + } + } + + BacklightMode::Breath(c, tstep, step, new_dir) + } + + BacklightMode::Circling(c, tstep, step, index, dir) => { + let mut new_dir = dir; + let mut new_index = index; + + let mut step = step + 1; + + if step >= tstep { + step = 0; + + if new_index == 0 && !dir { + new_index = 0; + new_dir = true; + } else if new_index == 6 && dir { + new_index = 6; + new_dir = false; + } else { + new_index = if dir { index + 1 } else { index - 1 }; + } + } + + for (i, l) in leds.leds[4..].iter_mut().enumerate() { + let ni = if new_index == 0 { 5 } else { new_index - 1 }; + if i == ni { + *l = c; + } else { + *l = colors::BLACK; + } + } + BacklightMode::Circling(c, tstep, step, new_index as usize, new_dir) + } + }; + + if leds.ws + .write(brightness(leds.leds.iter().cloned(), self.brightness)).is_err() { + panic!(); + } + } +} + +#[app(device = crate::hal::pac, peripherals = true)] +const APP: () = { + struct Resources { + usb_dev: UsbDevice, + usb_class: UsbClass, + matrix: Matrix<Pin<Input<PullUp>>, Pin<Output<PushPull>>, 12, 5>, + debouncer: Debouncer<PressedKeys<12, 5>>, + layout: Layout<CustomActions>, + timer: timers::Timer<stm32::TIM3>, + + backlight: Backlight, + } + + #[init] + fn init(mut c: init::Context) -> init::LateResources { + static mut USB_BUS: Option<UsbBusAllocator<usb::UsbBusType>> = None; + let mut rcc = c + .device + .RCC + .configure() + .hsi48() + .enable_crs(c.device.CRS) + .sysclk(48.mhz()) + .pclk(24.mhz()) + .freeze(&mut c.device.FLASH); + + let gpioa = c.device.GPIOA.split(&mut rcc); + let gpiob = c.device.GPIOB.split(&mut rcc); + + let usb = usb::Peripheral { + usb: c.device.USB, + pin_dm: gpioa.pa11, + pin_dp: gpioa.pa12, + }; + *USB_BUS = Some(usb::UsbBusType::new(usb)); + let usb_bus = USB_BUS.as_ref().unwrap(); + + // Handling of ws2812 leds + + let pa5 = gpioa.pa5; // sck + let pa6 = gpioa.pa6; // miso + let pa7 = gpioa.pa7; // mosi + + // Configure pins for SPI + let (sck, miso, mosi) = cortex_m::interrupt::free(move |cs| { + ( + pa5.into_alternate_af0(cs), + pa6.into_alternate_af0(cs), + pa7.into_alternate_af0(cs), + ) + }); + + const MODE: Mode = Mode { + polarity: Polarity::IdleHigh, + phase: Phase::CaptureOnSecondTransition, + }; + let spi = Spi::spi1( + c.device.SPI1, + (sck, miso, mosi), + MODE, + 3_000_000.hz(), + &mut rcc, + ); + + // ws2812 + let mut ws = ws2812::Ws2812::new(spi); + + // Do a simple smooth blink at start + let mut delay = Delay::new(c.core.SYST, &rcc); + let tmpleds = [colors::GREEN; 10]; + for i in (0..100).chain((0..100).rev()) { + ws.write(brightness(tmpleds.iter().cloned(), i)).unwrap(); + delay.delay_ms(5u8); + } + + let mut leds = Leds { + ws, + leds: [colors::BLACK; 10], + }; + + leds.ws.write(leds.leds.iter().cloned()).unwrap(); + + let usb_class = keyberon::new_class(usb_bus, leds); + let usb_dev = keyberon::new_device(usb_bus); + + let mut timer = timers::Timer::tim3(c.device.TIM3, 1.khz(), &mut rcc); + timer.listen(timers::Event::TimeOut); + + let pa15 = gpioa.pa15; + let pa1 = gpioa.pa1; + let pa0 = gpioa.pa0; + + let matrix = cortex_m::interrupt::free(move |cs| { + Matrix::new( + [ + pa0.into_pull_up_input(cs).downgrade(), + pa1.into_pull_up_input(cs).downgrade(), + gpiob.pb13.into_pull_up_input(cs).downgrade(), + gpiob.pb12.into_pull_up_input(cs).downgrade(), + gpiob.pb14.into_pull_up_input(cs).downgrade(), + gpiob.pb15.into_pull_up_input(cs).downgrade(), + pa15.into_pull_up_input(cs).downgrade(), + gpiob.pb3.into_pull_up_input(cs).downgrade(), + gpiob.pb4.into_pull_up_input(cs).downgrade(), + gpiob.pb5.into_pull_up_input(cs).downgrade(), + gpiob.pb8.into_pull_up_input(cs).downgrade(), + gpiob.pb9.into_pull_up_input(cs).downgrade(), + ], + [ + gpiob.pb0.into_push_pull_output(cs).downgrade(), + gpiob.pb1.into_push_pull_output(cs).downgrade(), + gpiob.pb2.into_push_pull_output(cs).downgrade(), + gpiob.pb10.into_push_pull_output(cs).downgrade(), + gpiob.pb11.into_push_pull_output(cs).downgrade(), + + ], + )}); + + init::LateResources { + usb_dev, + usb_class, + timer, + debouncer: Debouncer::new(PressedKeys::default(), PressedKeys::default(), 5), + matrix: matrix.get(), + layout: Layout::new(LAYERS), + + backlight: Backlight { + mode: BacklightMode::Off, + brightness: 8, + }, + } + } + + #[task(binds = USB, priority = 4, resources = [usb_dev, usb_class])] + fn usb_rx(c: usb_rx::Context) { + if c.resources.usb_dev.poll(&mut [c.resources.usb_class]) { + c.resources.usb_class.poll(); + } + } + + #[task( + binds = TIM3, + priority = 2, + resources = [matrix, debouncer, timer, layout, usb_class, backlight], + )] + fn tick(c: tick::Context) { + c.resources.timer.wait().ok(); + + for event in c + .resources + .debouncer + .events(c.resources.matrix.get().unwrap()) + { + c.resources.layout.event(event); + } + let mut usb_class = c.resources.usb_class; + let backlight = c.resources.backlight; + + match c.resources.layout.tick() { + keyberon::layout::CustomEvent::Release(CustomActions::LightUp) => { + let bl_val = &mut backlight.brightness; + *bl_val = if *bl_val == 100 { 100 } else { *bl_val + 1 }; + usb_class.lock(|k| { + let leds = k.device_mut().leds_mut(); + if leds.ws + .write(brightness(leds.leds.iter().cloned(), *bl_val)).is_err() { + panic!(); + } + }); + } + keyberon::layout::CustomEvent::Release(CustomActions::LightDown) => { + let bl_val = &mut backlight.brightness; + *bl_val = if *bl_val == 0 { 0 } else { *bl_val - 1 }; + usb_class.lock(|k| { + let leds = k.device_mut().leds_mut(); + if leds.ws + .write(brightness(leds.leds.iter().cloned(), *bl_val)).is_err() { + panic!(); + } + }); + } + keyberon::layout::CustomEvent::Release(CustomActions::ColorCycle) => { + backlight.next_color(); + } + keyberon::layout::CustomEvent::Release(CustomActions::ModeCycle) => { + backlight.next_mode(); + } + keyberon::layout::CustomEvent::Release(CustomActions::FreqUp) => { + backlight.change_freq(true); + } + keyberon::layout::CustomEvent::Release(CustomActions::FreqDown) => { + backlight.change_freq(false); + } + _ => (), + } + + usb_class.lock(|k| { + backlight.refresh_leds(k.device_mut().leds_mut()); + }); + + c.resources.layout.tick(); + send_report(c.resources.layout.keycodes(), &mut usb_class); + } + + extern "C" { + fn CEC_CAN(); + } +}; + +fn send_report(iter: impl Iterator<Item = KeyCode>, usb_class: &mut resources::usb_class<'_>) { + use rtic::Mutex; + let report: KbHidReport = iter.collect(); + if usb_class.lock(|k| k.device_mut().set_keyboard_report(report.clone())) { + while let Ok(0) = usb_class.lock(|k| k.write(report.as_bytes())) {} + } +} |