From 7fdc6a2c1b5799221f5d89d5f935cfe7dad4ccd2 Mon Sep 17 00:00:00 2001 From: Brian Cully Date: Sun, 11 Aug 2019 19:01:34 -0400 Subject: Move usb controller stuff to usb directory. --- usb/src/dotstar.rs | 42 ++++++++++++ usb/src/logger.rs | 139 +++++++++++++++++++++++++++++++++++++ usb/src/macros.rs | 15 ++++ usb/src/main.rs | 196 +++++++++++++++++++++++++++++++++++++++++++++++++++++ usb/src/rtc.rs | 75 ++++++++++++++++++++ 5 files changed, 467 insertions(+) create mode 100644 usb/src/dotstar.rs create mode 100644 usb/src/logger.rs create mode 100644 usb/src/macros.rs create mode 100644 usb/src/main.rs create mode 100644 usb/src/rtc.rs (limited to 'usb/src') diff --git a/usb/src/dotstar.rs b/usb/src/dotstar.rs new file mode 100644 index 0000000..8e930d0 --- /dev/null +++ b/usb/src/dotstar.rs @@ -0,0 +1,42 @@ +use trinket_m0::{ + clock::GenericClockController, + gpio::{self, Floating, Input}, + prelude::*, + sercom::{self, PadPin, SPIMaster1}, + PM, SERCOM1, +}; + +use apa102_spi::Apa102; + +pub fn new( + sercom: SERCOM1, + miso: gpio::Pa31>, + mosi: gpio::Pa0>, + sck: gpio::Pa1>, + port: &mut gpio::Port, + pm: &mut PM, + clocks: &mut GenericClockController, +) -> Apa102< + SPIMaster1< + sercom::Sercom1Pad3>, + sercom::Sercom1Pad0>, + sercom::Sercom1Pad1>, + >, +> { + let gclk = clocks.gclk0(); + let miso = miso.into_pad(port); + let mosi = mosi.into_pad(port); + let sck = sck.into_pad(port); + let spi = SPIMaster1::new( + &clocks + .sercom1_core(&gclk) + .expect("setting up sercom1 clock"), + 3.mhz(), + apa102_spi::MODE, + sercom, + pm, + (miso, mosi, sck), + ); + + Apa102::new(spi) +} diff --git a/usb/src/logger.rs b/usb/src/logger.rs new file mode 100644 index 0000000..be9d391 --- /dev/null +++ b/usb/src/logger.rs @@ -0,0 +1,139 @@ +use crate::rtc; +use starb::{Reader, RingBuffer, Writer}; + +use core::cell::UnsafeCell; +use core::fmt::{self, Write}; +use embedded_hal::{digital::v2::OutputPin, serial}; +use log::{Metadata, Record}; +use trinket_m0::{ + gpio::{Pa6, Pa7, PfD}, + sercom::{Sercom0Pad2, Sercom0Pad3, UART0}, +}; + +static mut UART0: usize = 0; + +struct JoinedRingBuffer<'a> { + lbr: Reader<'a, u8>, + lbw: Writer<'a, u8>, +} + +impl<'a> JoinedRingBuffer<'a> { + const fn new(rb: &'a RingBuffer) -> Self { + let (lbr, lbw) = rb.split(); + Self { lbr: lbr, lbw: lbw } + } +} + +impl fmt::Write for JoinedRingBuffer<'_> { + fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { + for b in s.bytes() { + if let Err(_) = self.lbw.unshift(b) { + // Ignore buffer full errors for logging. + return Ok(()); + } + } + Ok(()) + } +} + +static mut LB: RingBuffer = RingBuffer::::new(0); +static mut JRB: JoinedRingBuffer = unsafe { JoinedRingBuffer::new(&LB) }; + +// The UART isn't necessarily Sync, so wrap it in something that +// is. As long as flush() is only called from one thread, we're fine, +// but this is a guarantee that the logger module doesn't make. +pub struct WriteWrapper { + w: W, +} +impl WriteWrapper +where + W: serial::Write, +{ + pub fn new(writer: W) -> Self { + Self { w: writer } + } +} +unsafe impl Sync for WriteWrapper {} + +pub struct SerialLogger { + writer: UnsafeCell>, + led: UnsafeCell, +} + +impl SerialLogger +where + W: serial::Write, + L: OutputPin + Send + Sync, +{ + pub fn new(writer: WriteWrapper, led: L) -> Self { + // Stash this for unsafe usage in case there's an issue with + // the rest of the logging. + unsafe { UART0 = core::mem::transmute(&writer.w) }; + Self { + writer: UnsafeCell::new(writer), + led: UnsafeCell::new(led), + } + } +} +unsafe impl Send for SerialLogger {} +unsafe impl Sync for SerialLogger {} + +impl log::Log for SerialLogger +where + W: serial::Write, + L: OutputPin + Send + Sync, +{ + fn enabled(&self, metadata: &Metadata) -> bool { + metadata.level() <= log::max_level() + } + + fn log(&self, record: &Record) { + if !self.enabled(record.metadata()) { + return; + } + + let jrb = unsafe { &mut JRB }; + write!( + jrb, + "[{}] {} {} -- {}\r\n", + rtc::millis(), + record.level(), + record.target(), + record.args() + ) + .ok(); + } + + fn flush(&self) { + // Unsafe due to mutable static. We can only deal with the + // tail position of the buffer here to keep things safe. + let jrb = unsafe { &mut JRB }; + if jrb.lbr.is_empty() { + return; + } + + let led = unsafe { &mut (*self.led.get()) }; + let writer = unsafe { &mut (*self.writer.get()) }; + + led.set_high().ok(); + while let Some(b) = jrb.lbr.shift() { + nb::block!(writer.w.write(b)).ok(); + } + led.set_low().ok(); + } +} + +// Write to the UART right now, instead of putting it on a ring +// buffer. This function is a huge hack, and only useful for debugging +// either before the main loop starts or if the ring buffer is broken. +pub unsafe fn write_fmt_now(args: fmt::Arguments, nl: bool) { + if UART0 == 0 { + return; + } + let uart: &mut UART0>, Sercom0Pad2>, (), ()> = + core::mem::transmute(UART0); + fmt::write(uart, args).expect("writing fmt now to uart"); + if nl { + uart.write_str("\r\n").expect("writing nl now to uart"); + } +} diff --git a/usb/src/macros.rs b/usb/src/macros.rs new file mode 100644 index 0000000..46d2d07 --- /dev/null +++ b/usb/src/macros.rs @@ -0,0 +1,15 @@ +#[macro_export] +macro_rules! logln_now { + ($($arg:tt)*) => { + unsafe {crate::logger::write_fmt_now(format_args!($($arg)*), true);} + }; + (_) => {}; +} + +#[macro_export] +macro_rules! log_now { + ($($arg:tt)*) => { + unsafe {crate::logger::write_fmt_now(format_args!($($arg)*), false);} + }; + (_) => {}; +} diff --git a/usb/src/main.rs b/usb/src/main.rs new file mode 100644 index 0000000..36e4d1e --- /dev/null +++ b/usb/src/main.rs @@ -0,0 +1,196 @@ +//! 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); +} diff --git a/usb/src/rtc.rs b/usb/src/rtc.rs new file mode 100644 index 0000000..fa7b347 --- /dev/null +++ b/usb/src/rtc.rs @@ -0,0 +1,75 @@ +use core::sync::atomic::{AtomicUsize, Ordering}; +use log; +use trinket_m0::{clock::GenericClockController, RTC}; + +struct Clock(AtomicUsize); +impl Clock { + const fn new() -> Self { + Self(AtomicUsize::new(0)) + } + + fn set(&self, millis: usize) { + self.0.store(millis, Ordering::SeqCst) + } + + // Slightly less than 1ms, due to using a 32,768Hz clock, we can't + // hit exactly 1ms, so we shoot for a bit under. + fn millis(&self) -> usize { + self.0.load(Ordering::SeqCst) + } +} + +static CLOCK: Clock = Clock::new(); + +// Set to run every ~500µs. This has been loosely calibrated from a +// nrf52 clock running Arduino and a Linux host. +static COUNTER: u32 = 13; + +pub fn setup(mut rtc: RTC, clocks: &mut GenericClockController) -> impl FnMut() { + let rtc_clock = &clocks.gclk1(); + clocks.rtc(&rtc_clock); + + rtc.mode0().ctrl.write(|w| w.swrst().set_bit()); + while rtc.mode0().status.read().syncbusy().bit_is_set() {} + + rtc.mode0().ctrl.write(|w| { + w.mode().count32(); + + // Neither the prescaler nor matchlr values seem to work. Not + // sure why. + //w.prescaler().div1024(); + w.matchclr().set_bit() // Reset on match for periodic + }); + + rtc.mode0().comp[0].write(|w| unsafe { w.bits(COUNTER) }); + rtc.mode0().intflag.write(|w| w.cmp0().set_bit()); + rtc.mode0().intenset.write(|w| w.cmp0().set_bit()); + + // Enable the RTC and wait for sync. + rtc.mode0().ctrl.write(|w| w.enable().set_bit()); + while rtc.mode0().status.read().syncbusy().bit_is_set() {} + + move || handler(&mut rtc) +} + +pub fn millis() -> usize { + CLOCK.millis() +} + +fn handler(rtc: &mut RTC) { + // FIXME: matchclr doesn't seem to work to reset the counter? + rtc.mode0().count.write(|w| unsafe { w.bits(0) }); + rtc.mode0().intflag.write(|w| w.cmp0().set_bit()); + + static mut TICKS: usize = 0; + static mut ADD: bool = false; + unsafe { + if ADD { + TICKS += 1; + CLOCK.set(TICKS); + } + ADD = !ADD; + + log::logger().flush(); + } +} -- cgit v1.2.3