diff options
author | Brian Cully <bjc@kublai.com> | 2019-08-06 08:53:42 -0400 |
---|---|---|
committer | Brian Cully <bjc@kublai.com> | 2019-08-06 08:53:42 -0400 |
commit | 9ff9f51c07f7226d6467bc9db608cc53c82365bd (patch) | |
tree | d3c9d83d24ba0c3ce29cab011deba5c4698e26e9 /src | |
download | bleusb-9ff9f51c07f7226d6467bc9db608cc53c82365bd.tar.gz bleusb-9ff9f51c07f7226d6467bc9db608cc53c82365bd.zip |
Initial commit.
Diffstat (limited to 'src')
-rw-r--r-- | src/dotstar.rs | 42 | ||||
-rw-r--r-- | src/logger.rs | 139 | ||||
-rw-r--r-- | src/macros.rs | 15 | ||||
-rwxr-xr-x | src/main.rs | 144 | ||||
-rw-r--r-- | src/rtc.rs | 74 |
5 files changed, 414 insertions, 0 deletions
diff --git a/src/dotstar.rs b/src/dotstar.rs new file mode 100644 index 0000000..8e930d0 --- /dev/null +++ b/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<Input<Floating>>, + mosi: gpio::Pa0<Input<Floating>>, + sck: gpio::Pa1<Input<Floating>>, + port: &mut gpio::Port, + pm: &mut PM, + clocks: &mut GenericClockController, +) -> Apa102< + SPIMaster1< + sercom::Sercom1Pad3<gpio::Pa31<gpio::PfD>>, + sercom::Sercom1Pad0<gpio::Pa0<gpio::PfD>>, + sercom::Sercom1Pad1<gpio::Pa1<gpio::PfD>>, + >, +> { + 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/src/logger.rs b/src/logger.rs new file mode 100644 index 0000000..be9d391 --- /dev/null +++ b/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<u8>) -> 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<u8> = RingBuffer::<u8>::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: W, +} +impl<W> WriteWrapper<W> +where + W: serial::Write<u8>, +{ + pub fn new(writer: W) -> Self { + Self { w: writer } + } +} +unsafe impl<W> Sync for WriteWrapper<W> {} + +pub struct SerialLogger<W, L> { + writer: UnsafeCell<WriteWrapper<W>>, + led: UnsafeCell<L>, +} + +impl<W, L> SerialLogger<W, L> +where + W: serial::Write<u8>, + L: OutputPin + Send + Sync, +{ + pub fn new(writer: WriteWrapper<W>, 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<W, L> Send for SerialLogger<W, L> {} +unsafe impl<W, L> Sync for SerialLogger<W, L> {} + +impl<W, L> log::Log for SerialLogger<W, L> +where + W: serial::Write<u8>, + 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<Sercom0Pad3<Pa7<PfD>>, Sercom0Pad2<Pa6<PfD>>, (), ()> = + 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/src/macros.rs b/src/macros.rs new file mode 100644 index 0000000..46d2d07 --- /dev/null +++ b/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/src/main.rs b/src/main.rs new file mode 100755 index 0000000..071966a --- /dev/null +++ b/src/main.rs @@ -0,0 +1,144 @@ +#![no_std] +#![no_main] + +mod dotstar; +mod logger; +mod macros; +mod rtc; + +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::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, +}; + +// I²C on trinket is on SERCOM2 + +static mut LED: usize = 0; + +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 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::Sercom0Pad3<Pa7<PfD>>, sercom::Sercom0Pad2<Pa6<PfD>>, (), ()>, + Pa10<Output<OpenDrain>>, + > = 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); + + HANDLERS.with_overrides(|hs| { + hs.register(0, &mut rtc_handler); + cp.NVIC.enable(Interrupt::RTC); + + info!("Bootstrap complete."); + + loop { + wfi() + } + }); + unreachable!(); +} + +#[panic_handler] +fn panic_handler(pi: &PanicInfo) -> ! { + let red_led: &mut Pa10<Output<OpenDrain>> = 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<Output<OpenDrain>> = 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/src/rtc.rs b/src/rtc.rs new file mode 100644 index 0000000..79e450a --- /dev/null +++ b/src/rtc.rs @@ -0,0 +1,74 @@ +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. +static COUNTER: u32 = 16; // 32 ticks requires 1024 cycles at 32,768Hz for 1 second. + +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(); + } +} |