trinket-streamdeck/src/buttons.rs
2021-06-06 17:11:21 -05:00

182 lines
4.5 KiB
Rust

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<PA08, Input<Floating>>,
pub(crate) d1: Pin<PA02, Input<Floating>>,
pub(crate) d2: Pin<PA09, Input<Floating>>,
pub(crate) d3: Pin<PA07, Input<Floating>>,
pub(crate) d4: Pin<PA06, Input<Floating>>,
}
pub(crate) struct ButtonState {
pins: Pins,
debounce_times: [u32; 5],
last_states: [bool; 5],
current_states: [bool; 5],
}
struct Pins {
d0: Pin<PA08, Input<PullUp>>,
d1: Pin<PA02, Input<PullUp>>,
d2: Pin<PA09, Input<PullUp>>,
d3: Pin<PA07, Input<PullUp>>,
d4: Pin<PA06, Input<PullUp>>,
}
struct Inputs<'a> {
buttons: [&'a dyn InputPin<Error = ()>; 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<Color = RGB8>,
serial: &mut SerialPort<UsbBus>,
) -> 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<bool> {
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<Error = ()>;
fn next(&mut self) -> Option<Self::Item> {
let prev_count = self.count;
self.count += 1;
self.buttons.get(prev_count).map(|r| *r)
}
}