trinket-streamdeck/src/main.rs

292 lines
8 KiB
Rust

#![no_std]
#![no_main]
use atsamd_hal::prelude::{_atsamd21_hal_time_U32Ext, _embedded_hal_timer_CountDown};
use panic_halt as _; // you can put a breakpoint on `rust_begin_unwind` to catch panics
use trinket_m0 as hal;
use core::cell::RefCell;
use cortex_m::{
asm::delay as cycle_delay,
interrupt::Mutex,
peripheral::{NVIC, SCB, SYST},
};
use hal::{
clock::GenericClockController,
entry,
pac::{interrupt, CorePeripherals, Peripherals},
prelude::_atsamd_hal_embedded_hal_digital_v2_OutputPin,
usb::UsbBus,
};
use once_cell::unsync::OnceCell;
use smart_leds::{SmartLedsWrite, RGB8};
use streamdeck_common::{handle_input, Handshake, UnsafeSync};
use usb_device::{bus::UsbBusAllocator, prelude::*};
use usbd_serial::{SerialPort, USB_CLASS_CDC};
mod bsp;
mod trinket_buttons;
use bsp::{Dotstar, Pins};
use trinket_buttons::Buttons;
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));
#[entry]
fn main() -> ! {
let mut peripherals = Peripherals::take().unwrap();
let mut core = CorePeripherals::take().unwrap();
let mut clocks = GenericClockController::with_internal_32kosc(
peripherals.GCLK,
&mut peripherals.PM,
&mut peripherals.SYSCTRL,
&mut peripherals.NVMCTRL,
);
let pins = Pins::new(peripherals.PORT);
let mut red_led = pins.d13.into_push_pull_output();
red_led.set_low().unwrap();
let usb_allocator = hal::usb_allocator(
peripherals.USB,
&mut clocks,
&mut peripherals.PM,
pins.usb_dm,
pins.usb_dp,
);
// SAFETY: No interrupt will access USB_ALLOCATOR before this point, since it is only
// referenced from inside the USB_SERIAL mutex and the USB_BUS mutex, which have not been set
// yet.
//
// SAFETY: This is the only line of the program that modifies USB_ALLOCATOR
let allocator = unsafe {
USB_ALLOCATOR.get().set(usb_allocator).ok().unwrap();
USB_ALLOCATOR.get().get().unwrap()
};
cortex_m::interrupt::free(|cs| {
let serial = USB_SERIAL.borrow(cs);
let mut serial = serial.borrow_mut();
*serial = Some(SerialPort::new(allocator));
});
cortex_m::interrupt::free(|cs| {
let bus = USB_BUS.borrow(cs);
let mut bus = bus.borrow_mut();
*bus = Some(
UsbDeviceBuilder::new(allocator, UsbVidPid(0x16c0, 0x27dd))
.manufacturer("Aode Lion")
.product("Streamdeck")
.serial_number("AAAA")
.device_class(USB_CLASS_CDC)
.build(),
);
});
unsafe {
core.NVIC.set_priority(interrupt::USB, 1);
NVIC::unmask(interrupt::USB);
}
let mut buttons = Buttons {
d0: pins.d0,
d1: pins.d1,
d2: pins.d2,
d3: pins.d3,
d4: pins.d4,
}
.init();
let timer_clock = clocks.gclk0();
let tc45 = &clocks.tc4_tc5(&timer_clock).unwrap();
let mut timer =
atsamd_hal::timer::TimerCounter::tc4_(tc45, peripherals.TC4, &mut peripherals.PM);
timer.start(128_u32.hz());
let mut rgb = Dotstar {
ci: pins.dotstar_ci,
di: pins.dotstar_di,
nc: pins.dotstar_nc,
}
.init(timer);
let off = RGB8 { r: 0, g: 0, b: 0 };
rgb.write([off].iter().cloned()).unwrap();
loop {
cycle_delay(1024 * 15);
if let Some(byte) = buttons.tick(SYST::get_current()) {
with_op_lock(|| {
let _ = cortex_m::interrupt::free(|cs| {
let mut serial = USB_SERIAL.borrow(cs).borrow_mut();
let serial = serial.as_mut().unwrap();
let _ = serial.write(&[byte])?;
serial.flush()
});
});
let color = color_from_byte(byte);
let _ = rgb.write([color].iter().cloned());
}
}
}
#[interrupt]
fn USB() {
static mut HANDSHAKE: Handshake = Handshake::new();
with_op_lock(|| {
poll_usb(HANDSHAKE);
});
}
fn poll_usb(handshake: &mut Handshake) -> Option<()> {
let ready = cortex_m::interrupt::free(|cs1| {
cortex_m::interrupt::free(|cs2| {
let mut usb_dev = USB_BUS.borrow(cs1).borrow_mut();
let usb_dev = usb_dev.as_mut()?;
let mut serial = USB_SERIAL.borrow(cs2).borrow_mut();
let serial = serial.as_mut()?;
Some(usb_dev.poll(&mut [&mut *serial]))
})
})?;
if !ready {
return Some(());
}
let mut input = [0u8; 32];
let count = cortex_m::interrupt::free(|cs| {
USB_SERIAL
.borrow(cs)
.borrow_mut()
.as_mut()?
.read(&mut input)
.ok()
})?;
let current_time = SYST::get_current();
if count == 32 && handshake.perform(&input, current_time, &mut Device)? {
return Some(());
}
let is_complete = handshake.is_complete();
if !is_complete {
return None;
}
handle_input(&mut Device, &input, count)
}
fn with_op_lock_spin(cs: &cortex_m::interrupt::CriticalSection) -> bool {
let mut operation_lock = OPERATION_LOCK.borrow(cs).borrow_mut();
let prev = *operation_lock;
*operation_lock = true;
prev
}
fn with_op_lock(mut f: impl FnMut()) {
while cortex_m::interrupt::free(with_op_lock_spin) {
cycle_delay(1024);
}
(f)();
cortex_m::interrupt::free(|cs| {
*OPERATION_LOCK.borrow(cs).borrow_mut() = false;
});
}
struct Device;
impl streamdeck_common::Device<16, 1> for Device {
// section 10.3.3 of datasheet for SAMD21
fn serial_number(&mut self) -> [u8; 16] {
let generator: [*const [u8; 4]; 4] = [
0x0080A00C as *const [u8; 4],
0x0080A040 as *const [u8; 4],
0x0080A044 as *const [u8; 4],
0x0080A048 as *const [u8; 4],
];
let mut id = [0u8; 16];
for i in 0..4 {
id[i * 4] = unsafe { (*generator[i])[3] };
id[i * 4 + 1] = unsafe { (*generator[i])[2] };
id[i * 4 + 2] = unsafe { (*generator[i])[1] };
id[i * 4 + 3] = unsafe { (*generator[i])[0] };
}
id
}
fn write(&mut self, bytes: &[u8]) -> Option<()> {
cortex_m::interrupt::free(|cs| {
let mut serial = USB_SERIAL.borrow(cs).borrow_mut();
let serial = serial.as_mut()?;
serial.write(bytes).ok()?;
serial.flush().ok()
})
}
fn extras() -> [(&'static str, &'static str); 1] {
[("version", env!("FIRMWARE_VERSION"))]
}
// Based on
// - https://github.com/arduino/ArduinoCore-samd/issues/197
// - https://github.com/tinygo-org/tinygo/blob/master/src/machine/board_trinket.go
// - https://github.com/tinygo-org/tinygo/blob/master/src/machine/machine_atsamd21.go
fn reset(&mut self) {
cortex_m::interrupt::disable();
let reset_magic_value: usize = 0xf01669ef;
let reset_address: *mut usize = 0x20007FFC as *mut usize;
unsafe {
*reset_address = reset_magic_value;
}
SCB::sys_reset();
}
}
// 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),
}
}
}
}