Initial button impl

This commit is contained in:
Aode 2021-06-06 17:11:21 -05:00
parent 4b11c8c9d9
commit 119643e36f
4 changed files with 226 additions and 3 deletions

View file

@ -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"

181
src/buttons.rs Normal file
View file

@ -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<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)
}
}

View file

@ -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;
}

View file

@ -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<OnceCell<UsbBusAllocator<UsbBus>>> =
UnsafeSync::new(OnceCell::new());
static OPERATION_LOCK: Mutex<RefCell<bool>> = Mutex::new(RefCell::new(false));
static USB_BUS: Mutex<RefCell<Option<UsbDevice<UsbBus>>>> = Mutex::new(RefCell::new(None));
static USB_SERIAL: Mutex<RefCell<Option<SerialPort<UsbBus>>>> = Mutex::new(RefCell::new(None));
static HANDSHAKE: Mutex<RefCell<Handshake>> = 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();
});
}