trinket-streamdeck/trinket/src/main.rs

334 lines
9 KiB
Rust

#![no_std]
#![no_main]
use panic_halt as _; // you can put a breakpoint on `rust_begin_unwind` to catch panics
use atsamd_hal::{
clock::{ClockGenId, ClockSource, GenericClockController},
pac::{interrupt, CorePeripherals, Peripherals},
prelude::_atsamd_hal_embedded_hal_digital_v2_OutputPin,
rtc::{Count32Mode, Rtc},
timer::TimerCounter,
usb::UsbBus,
};
use common::{handle_input, Handshake, UnsafeSync};
use core::cell::RefCell;
use cortex_m::{
asm::delay as cycle_delay,
interrupt::Mutex,
peripheral::{NVIC, SCB},
};
use once_cell::unsync::OnceCell;
use smart_leds::{SmartLedsWrite, RGB8};
use usb_device::{bus::UsbBusAllocator, prelude::*};
use usbd_serial::{SerialPort, USB_CLASS_CDC};
mod bsp;
mod buttons;
use bsp::{entry, Dotstar, Pins};
use buttons::Buttons;
static USB_ALLOCATOR: UnsafeSync<OnceCell<UsbBusAllocator<UsbBus>>> =
UnsafeSync::new(OnceCell::new());
static RTC_COUNT: Mutex<RefCell<Option<Rtc<Count32Mode>>>> = Mutex::new(RefCell::new(None));
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()));
fn get_current_time() -> u32 {
cortex_m::interrupt::free(|cs| {
RTC_COUNT
.borrow(cs)
.borrow()
.as_ref()
.map(|rtc| rtc.count32())
.unwrap_or(0)
})
}
fn is_handshake_complete() -> bool {
cortex_m::interrupt::free(|cs| HANDSHAKE.borrow(cs).borrow().is_complete())
}
#[entry]
fn main() -> ! {
let mut peripherals = Peripherals::take().unwrap();
let mut core = CorePeripherals::take().unwrap();
// initializes gclk0 at 48MHz and gclk1 at 32KHz
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 = bsp::usb_allocator(
peripherals.USB,
&mut clocks,
&mut peripherals.PM,
pins.usb_dm,
pins.usb_dp,
)
.unwrap();
// 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("Trinket 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();
// Use 32KHz clock for Rtc, can't use gclk1 for some reason
let rtc_gclk = clocks
.configure_gclk_divider_and_source(ClockGenId::GCLK3, 32, ClockSource::OSC32K, true)
.unwrap();
let rtc_clock = clocks.rtc(&rtc_gclk).unwrap();
let mut counter = Rtc::count32_mode(peripherals.RTC, rtc_clock.freq(), &mut peripherals.PM);
counter.set_count32(0);
cortex_m::interrupt::free(|cs| {
let rtc = RTC_COUNT.borrow(cs);
*rtc.borrow_mut() = Some(counter);
});
// Use 48MHz clock for dotstar
let dotstar_gclk = clocks.gclk0();
let tc4_tc5 = &clocks.tc4_tc5(&dotstar_gclk).unwrap();
let timer = TimerCounter::tc4_(tc4_tc5, peripherals.TC4, &mut peripherals.PM);
let mut updated_at = 0;
const DOTSTAR_DELAY: u32 = 1000;
const DOTSTAR_OFF: RGB8 = RGB8 { r: 0, g: 0, b: 0 };
let mut rgb = Dotstar {
ci: pins.dotstar_ci,
di: pins.dotstar_di,
nc: pins.dotstar_nc,
}
.init(timer);
rgb.write([DOTSTAR_OFF].iter().cloned()).unwrap();
let mut dotstar_off = true;
let mut output = [0; 8];
loop {
let current_time = get_current_time();
if !is_handshake_complete() {
cycle_delay(1024 * 15);
continue;
}
if !dotstar_off && updated_at + DOTSTAR_DELAY < current_time {
let _ = rgb.write([DOTSTAR_OFF].iter().cloned());
dotstar_off = true;
}
if let Some(len) = buttons.tick(current_time, &mut output) {
updated_at = current_time;
let _ = write_serial(&output[0..len]);
let byte = output[1..len]
.iter()
.fold(0u8, |byte, key| byte | (1 << key));
dotstar_off = false;
let color = color_from_byte(byte);
let _ = rgb.write([color].iter().cloned());
}
}
}
#[allow(non_snake_case)]
#[interrupt]
fn USB() {
poll_usb();
}
fn poll_usb() -> 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 = get_current_time();
cortex_m::interrupt::free(|cs| {
let mut handshake = HANDSHAKE.borrow(cs).borrow_mut();
if count == 32 {
if let common::HandshakeResponse::Write { bytes } =
handshake.perform(&input, current_time)?
{
write_serial(bytes)?;
}
return None;
}
Some(())
})?;
let mut output_buffer = [0; 256];
let len = handle_input(&mut Device, &input, &mut output_buffer, count)?;
if len == 0 {
return None;
}
write_serial(&output_buffer[..len])?;
Some(())
}
fn write_serial(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()
})
}
struct Device;
impl 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 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),
}
}
}
}