diff --git a/Cargo.toml b/Cargo.toml index 4072439..a55e334 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,9 +6,11 @@ name = "trinket-streamdeck" version = "0.1.0" [dependencies] +atsamd-hal = "0.12" cortex-m = "0.6.0" cortex-m-rt = "0.6.10" cortex-m-semihosting = "0.3.3" +embedded-hal = "0.2" once_cell = { version = "1.7.2", default-features = false } panic-halt = "0.2.0" smart-leds = "0.3" diff --git a/src/buttons.rs b/src/buttons.rs new file mode 100644 index 0000000..f278b61 --- /dev/null +++ b/src/buttons.rs @@ -0,0 +1,181 @@ +use atsamd_hal::{ + common::gpio::{ + v2::{ + pin::{PA02, PA06, PA07, PA08, PA09}, + Floating, Input, PullUp, + }, + Pin, Port, + }, + samd21::usb::UsbBus, +}; +use cortex_m::peripheral::SYST; +use embedded_hal::digital::v2::InputPin; +use smart_leds::{SmartLedsWrite, RGB8}; +use usbd_serial::SerialPort; + +const DEBOUNCE_TIMEOUT: u32 = 100; + +pub(crate) struct Buttons { + pub(crate) d0: Pin>, + pub(crate) d1: Pin>, + pub(crate) d2: Pin>, + pub(crate) d3: Pin>, + pub(crate) d4: Pin>, +} + +pub(crate) struct ButtonState { + pins: Pins, + debounce_times: [u32; 5], + last_states: [bool; 5], + current_states: [bool; 5], +} + +struct Pins { + d0: Pin>, + d1: Pin>, + d2: Pin>, + d3: Pin>, + d4: Pin>, +} + +struct Inputs<'a> { + buttons: [&'a dyn InputPin; 5], + count: usize, +} + +impl Buttons { + pub(crate) fn init(self, port: &mut Port) -> ButtonState { + ButtonState { + pins: Pins { + d0: self.d0.into_pull_up_input(port), + d1: self.d1.into_pull_up_input(port), + d2: self.d2.into_pull_up_input(port), + d3: self.d3.into_pull_up_input(port), + d4: self.d4.into_pull_up_input(port), + }, + debounce_times: [0u32; 5], + last_states: [false; 5], + current_states: [false; 5], + } + } +} + +impl ButtonState { + pub(crate) fn tick( + &mut self, + rgb: &mut impl SmartLedsWrite, + serial: &mut SerialPort, + ) -> Option<()> { + if self.check_buttons()? { + let byte = self.calculate_press_state(); + self.reset_state(); + + serial.write(&[byte]).ok()?; + + let color = color_from_byte(byte); + rgb.write([color].iter().cloned()).ok()?; + } + + Some(()) + } + + fn check_buttons(&mut self) -> Option { + let mut needs_update = false; + let current_time = SYST::get_current(); + + let iter = self + .pins + .iter() + .zip(&mut self.debounce_times) + .zip(&mut self.last_states) + .zip(&mut self.current_states); + + for (((input, debounce_time), last_state), current_state) in iter { + let reading = input.is_high().ok()?; + + if reading != *last_state { + *last_state = reading; + *debounce_time = current_time; + } + + if *debounce_time + DEBOUNCE_TIMEOUT < current_time { + if *current_state != *last_state { + *current_state = *last_state; + + if *current_state { + needs_update |= true; + } + } + } + } + + Some(needs_update) + } + + fn reset_state(&mut self) { + let current_time = SYST::get_current(); + + for time in &mut self.debounce_times { + *time = current_time; + } + } + + fn calculate_press_state(&self) -> u8 { + self.current_states.iter().rev().fold(0u8, |acc, state| { + // state keeps track of HIGH, but we | on low + if *state { + acc << 1 + } else { + (acc << 1) | 1 + } + }) + } +} + +impl Pins { + fn iter(&self) -> Inputs<'_> { + Inputs { + buttons: [&self.d0, &self.d1, &self.d2, &self.d3, &self.d4], + count: 0, + } + } +} + +// Expect byte to be in range 0..32 +fn color_from_byte(byte: u8) -> RGB8 { + let position: u8 = byte * 8; // max value here is 248 + + match position { + 0..=85 => RGB8 { + r: (255 - position * 3), + g: (position * 3), + b: 0, + }, + 86..=170 => { + let position = position - 85; + RGB8 { + r: 0, + g: (255 - position * 3), + b: (position * 3), + } + } + _ => { + let position = position - 170; + RGB8 { + r: (position * 3), + g: 0, + b: (255 - position * 3), + } + } + } +} + +impl<'a> Iterator for Inputs<'a> { + type Item = &'a dyn InputPin; + + fn next(&mut self) -> Option { + let prev_count = self.count; + self.count += 1; + self.buttons.get(prev_count).map(|r| *r) + } +} diff --git a/src/handshake.rs b/src/handshake.rs index ffabc7c..4a85736 100644 --- a/src/handshake.rs +++ b/src/handshake.rs @@ -2,6 +2,8 @@ use crate::hal::usb::UsbBus; use cortex_m::peripheral::SYST; use usbd_serial::SerialPort; +const HANDSHAKE_TIMEOUT: u32 = 100; + pub(crate) struct Handshake { step_1_complete: bool, step_2_complete: bool, @@ -48,7 +50,7 @@ impl Handshake { if self.step_1_complete && !self.step_2_complete - && self.timestamp > SYST::get_current() + 100 + && self.timestamp > SYST::get_current() + HANDSHAKE_TIMEOUT { self.step_1_complete = false; } diff --git a/src/main.rs b/src/main.rs index ebb1edd..7d7ddf2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,9 +20,11 @@ use smart_leds::{SmartLedsWrite, RGB8}; use usb_device::{bus::UsbBusAllocator, prelude::*}; use usbd_serial::{SerialPort, USB_CLASS_CDC}; +mod buttons; mod handshake; mod unsafe_sync; +use buttons::Buttons; use handshake::Handshake; use unsafe_sync::UnsafeSync; @@ -33,6 +35,7 @@ static REPO: &'static [u8] = b"https://git.asonix.dog/asonix/trinket-streamdeck" static USB_ALLOCATOR: UnsafeSync>> = UnsafeSync::new(OnceCell::new()); +static OPERATION_LOCK: Mutex> = Mutex::new(RefCell::new(false)); static USB_BUS: Mutex>>> = Mutex::new(RefCell::new(None)); static USB_SERIAL: Mutex>>> = Mutex::new(RefCell::new(None)); static HANDSHAKE: Mutex> = Mutex::new(RefCell::new(Handshake::new())); @@ -110,6 +113,15 @@ fn main() -> ! { NVIC::unmask(interrupt::USB); } + let mut buttons = Buttons { + d0: pins.d0, + d1: pins.d1, + d2: pins.d2, + d3: pins.d3, + d4: pins.d4, + } + .init(&mut pins.port); + let mut rgb = Dotstar { ci: pins.dotstar_ci, di: pins.dotstar_di, @@ -121,7 +133,14 @@ fn main() -> ! { red_led.set_low().unwrap(); loop { - cycle_delay(15 * 1024 * 1024); + cycle_delay(1024 * 15); + with_op_lock(|| { + cortex_m::interrupt::free(|cs| { + let mut serial = USB_SERIAL.borrow(cs).borrow_mut(); + let serial = serial.as_mut().unwrap(); + buttons.tick(&mut rgb, serial); + }); + }); } } @@ -204,7 +223,26 @@ fn write_length_delim(bytes: &[u8]) -> Option<()> { }) } +fn with_op_lock(mut f: impl FnMut()) { + while cortex_m::interrupt::free(|cs| { + let mut operation_lock = OPERATION_LOCK.borrow(cs).borrow_mut(); + let prev = *operation_lock; + *operation_lock = true; + prev + }) { + cycle_delay(1024); + } + + (f)(); + + cortex_m::interrupt::free(|cs| { + *OPERATION_LOCK.borrow(cs).borrow_mut() = false; + }); +} + #[interrupt] fn USB() { - poll_usb(); + with_op_lock(|| { + poll_usb(); + }); }