//! Take USB keyboard reports and echo them over I²C. #![no_std] #![no_main] mod dotstar; mod logger; mod macros; mod rtc; use atsamd_usb_host::SAMDHost; use bootkbd::BootKeyboard; use clint::HandlerArray; use core::mem; use core::panic::PanicInfo; use cortex_m::asm::wfi; use cortex_m_rt::{entry, exception, ExceptionFrame}; use embedded_hal::{blocking::i2c::Write, digital::v2::OutputPin}; use log::{info, LevelFilter}; use smart_leds::colors; use smart_leds_trait::SmartLedsWrite; use trinket_m0::{ self as hal, clock::GenericClockController, gpio::{OpenDrain, Output, Pa10, Pa6, Pa7, PfD}, sercom, target_device::{interrupt, Interrupt}, time::*, CorePeripherals, Peripherals, }; use usb_host::Driver; // A very unsafe copy of an LED to turn on when things go really, really wrong. static mut LED: usize = 0; // I²C address to send keyboard reports to. const NRF_WIREADDR: u8 = 4; // Interrupt handler table. static HANDLERS: HandlerArray = HandlerArray::new(); #[entry] fn main() -> ! { let mut cp = CorePeripherals::take().expect("taking core peripherals"); let mut dp = Peripherals::take().expect("taking device peripherals"); let mut clocks = GenericClockController::with_internal_32kosc( dp.GCLK, &mut dp.PM, &mut dp.SYSCTRL, &mut dp.NVMCTRL, ); let mut pins = hal::Pins::new(dp.PORT); let uart = hal::uart( &mut clocks, 115_200.hz(), dp.SERCOM0, &mut cp.NVIC, &mut dp.PM, pins.d3, pins.d4, &mut pins.port, ); let mut i2c_master = hal::i2c_master( &mut clocks, 400_000.hz(), dp.SERCOM2, &mut dp.PM, pins.d0, pins.d2, &mut pins.port, ); let mut red_led = pins.d13.into_open_drain_output(&mut pins.port); red_led.set_low().expect("turning off red LED"); unsafe { LED = mem::transmute(&red_led) } let mut dotstar = dotstar::new( dp.SERCOM1, pins.swdio, pins.dotstar_di, pins.dotstar_ci, &mut pins.port, &mut dp.PM, &mut clocks, ); let black = [colors::BLACK]; dotstar .write(black.iter().cloned()) .expect("turning off dotstar"); // We do the transmute because, while all the underlying data is // static, we're unable to get a referecence to the UART or LED // until run-time. Another option would be to use Option in the // SerialLogger definition, but that requires a check every time // they might be used. let uart_wrapped = logger::WriteWrapper::new(uart); let logger = logger::SerialLogger::new(uart_wrapped, red_led); // Wow, would I love to not be annotating this type. let logger_ref: &'static logger::SerialLogger< sercom::UART0>, sercom::Sercom0Pad2>, (), ()>, Pa10>, > = unsafe { mem::transmute(&logger) }; unsafe { log::set_logger_racy(logger_ref).expect("couldn't set logger") }; log::set_max_level(LevelFilter::Trace); let mut rtc_handler = rtc::setup(dp.RTC, &mut clocks); let (mut usb_host, mut usb_handler) = SAMDHost::new( dp.USB, pins.usb_sof, pins.usb_dm, pins.usb_dp, Some(pins.usb_host_enable), &mut pins.port, &mut clocks, &mut dp.PM, &rtc::millis, ); let mut bootkbd = BootKeyboard::new(|addr, buf| { info!("{}: {:?}", addr, buf); let hdr: [u8; 2] = [I2CMessageType::Keyboard as u8, buf.len() as u8]; i2c_master.write(NRF_WIREADDR, &hdr).ok(); i2c_master.write(NRF_WIREADDR, &buf).ok(); }); let mut drivers: [&mut dyn Driver; 1] = [&mut bootkbd]; HANDLERS.with_overrides(|hs| { hs.register(0, &mut rtc_handler); unsafe { cp.NVIC.set_priority(Interrupt::USB, 0) }; cp.NVIC.enable(Interrupt::RTC); hs.register(1, &mut usb_handler); unsafe { cp.NVIC.set_priority(Interrupt::USB, 1) }; cp.NVIC.enable(Interrupt::USB); info!("Bootstrap complete."); loop { usb_host.task(&mut drivers[..]); wfi() } }); unreachable!(); } #[allow(unused)] #[repr(u8)] enum I2CMessageType { Debug = 0x00, Keyboard = 0x01, Invalid = 0xff, } #[panic_handler] fn panic_handler(pi: &PanicInfo) -> ! { let red_led: &mut Pa10> = unsafe { mem::transmute(LED) }; red_led.set_high().ok(); logln_now!("~~~ PANIC ~~~"); logln_now!("{}", pi); logln_now!("flushing log"); loop { log::logger().flush(); wfi() } } #[exception] fn HardFault(ef: &ExceptionFrame) -> ! { let red_led: &mut Pa10> = unsafe { mem::transmute(LED) }; red_led.set_high().ok(); log::logger().flush(); logln_now!("!!! Hard Fault - ef: {:?} !!!", ef); logln_now!("flushing log"); loop { log::logger().flush(); wfi() } } #[interrupt] fn RTC() { HANDLERS.call(0); } #[interrupt] fn USB() { HANDLERS.call(1); }