Skip to content

Instantly share code, notes, and snippets.

@9names
Last active April 4, 2022 12:34
Show Gist options
  • Save 9names/97aa7dd63f1a44cf6632973331ff03f8 to your computer and use it in GitHub Desktop.
Save 9names/97aa7dd63f1a44cf6632973331ff03f8 to your computer and use it in GitHub Desktop.
Pico USB serial abstraction
//! # Pico USB Serial - with format, panics.
//! Should move into it's own crate.
//!
//! heavily inspired by https://github.com/eterevsky/rp2040-blink/blob/main/src/main.rs and https://github.com/mvirkkunen/rtt-target
#![no_std]
#![no_main]
use core::cell::RefCell;
use core::fmt::Write;
use cortex_m::interrupt::Mutex;
// The macro for our start-up function
use cortex_m_rt::entry;
// The macro for marking our interrupt functions
use bsp::hal::pac::interrupt;
// GPIO traits
use embedded_hal::digital::v2::OutputPin;
// Time handling traits
use embedded_time::rate::*;
use rp_pico as bsp;
// Pull in any important traits
use bsp::hal::prelude::*;
// A shorter alias for the Peripheral Access Crate, which provides low-level
// register access
use bsp::hal::pac;
// A shorter alias for the Hardware Abstraction Layer, which provides
// higher-level drivers.
use bsp::hal;
// USB Device support
use usb_device::class_prelude::*;
/// The USB Bus Driver (shared with the interrupt).
static mut USB_BUS: Option<UsbBusAllocator<hal::usb::UsbBus>> = None;
use usbd_serial::SerialPort;
use usb_device::{
bus::UsbBusAllocator,
device::{UsbDevice, UsbDeviceBuilder, UsbVidPid},
};
/// Hold the context for our UsbSerial logging infrastructure
pub struct UsbLogger {
usb_dev: UsbDevice<'static, rp2040_hal::usb::UsbBus>,
serial: SerialPort<'static, rp2040_hal::usb::UsbBus>,
}
impl UsbLogger {
/// Create a new USB Serial logger
pub fn new(usb_bus: &'static UsbBusAllocator<rp2040_hal::usb::UsbBus>) -> Self {
let serial = usbd_serial::SerialPort::new(usb_bus);
let usb_dev = UsbDeviceBuilder::new(usb_bus, UsbVidPid(0x2E8A, 0x000a))
.manufacturer("rp-rs")
.product("pico debug logger")
.serial_number("PICO")
.device_class(2)
.device_protocol(1)
.build();
UsbLogger { usb_dev, serial }
}
/// Poll the internal serial device
pub fn poll(&mut self) {
if self.usb_dev.poll(&mut [&mut self.serial]) {
}
}
/// Reads bytes from the port into `data` and returns the number of bytes read.
///
/// # Errors
///
/// * [`WouldBlock`](usb_device::UsbError::WouldBlock) - No bytes available for reading.
///
/// Other errors from `usb-device` may also be propagated.
pub fn read(&mut self, data: &mut [u8]) -> Result<usize, UsbError> {
self.serial.read(data)
}
/// Writes bytes from `data` into the port and returns the number of bytes written.
///
/// # Errors
///
/// * [`WouldBlock`](usb_device::UsbError::WouldBlock) - No bytes could be written because the
/// buffers are full.
///
/// Other errors from `usb-device` may also be propagated.
pub fn write(&mut self, data: &[u8]) -> Result<usize, UsbError> {
self.serial.write(data)
}
/// Reads bytes from the port into `data` and returns the number of bytes read.
/// Will block until some bytes are read.
/// # Errors
///
/// Other errors from `usb-device` may also be propagated.
pub fn read_blocking(&mut self, data: &mut [u8]) -> Result<usize, UsbError> {
loop {
let read = self.read(data);
match read {
Ok(b) => return Ok(b),
Err(e) => match e {
// If we have no data, poll and try again
UsbError::WouldBlock => {self.poll()},
_ => return Err(e)
},
}
}
}
/// Writes bytes from `data` into the port and returns the number of bytes written.
/// Blocks until all bytes are written.
///
/// # Errors
///
/// * [`WouldBlock`](usb_device::UsbError::WouldBlock) - No bytes could be written because the
/// buffers are full.
///
/// Other errors from `usb-device` may also be propagated.
pub fn write_blocking(&mut self, data: &[u8]) -> Result<usize, UsbError> {
let mut written = 0;
loop {
let dataslice = &data[written..];
let write = self.write(dataslice);
match write {
Ok(b) => {
written += b;
if written == data.len() {
return Ok(written)
}
},
Err(e) => match e {
// If we wrote no data, poll and try again
UsbError::WouldBlock => {self.poll()},
_ => return Err(e)
},
}
}
}
}
impl core::fmt::Write for UsbLogger {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
self.write_blocking(s.as_bytes()).unwrap();
Ok(())
}
}
// Container for holding our USB serial port.
// # SAFETY: bare_metal::Mutex is not multicore safe.
// DO NOT INTERACT WITH THIS FROM THE SECOND CORE!
static USB_LOGGER: Mutex<RefCell<Option<UsbLogger>>> = Mutex::new(RefCell::new(None));
/// Entry point to our bare-metal application.
///
/// The `#[entry]` macro ensures the Cortex-M start-up code calls this function
/// as soon as all global variables are initialised.
///
/// The function configures the RP2040 peripherals, then blinks the LED in an
/// infinite loop.
#[entry]
fn main() -> ! {
// Grab our singleton objects
let mut pac = pac::Peripherals::take().unwrap();
let core = pac::CorePeripherals::take().unwrap();
// Set up the watchdog driver - needed by the clock setup code
let mut watchdog = hal::Watchdog::new(pac.WATCHDOG);
// Configure the clocks
//
// The default is to generate a 125 MHz system clock
let clocks = hal::clocks::init_clocks_and_plls(
bsp::XOSC_CRYSTAL_FREQ,
pac.XOSC,
pac.CLOCKS,
pac.PLL_SYS,
pac.PLL_USB,
&mut pac.RESETS,
&mut watchdog,
)
.ok()
.unwrap();
// Set up the USB driver
let usb_bus = UsbBusAllocator::new(hal::usb::UsbBus::new(
pac.USBCTRL_REGS,
pac.USBCTRL_DPRAM,
clocks.usb_clock,
true,
&mut pac.RESETS,
));
unsafe {
// Note (safety): This is safe as interrupts haven't been started yet
USB_BUS = Some(usb_bus);
}
// Grab a reference to the USB Bus allocator. We are promising to the
// compiler not to take mutable access to this global variable whilst this
// reference exists!
let bus_ref = unsafe { USB_BUS.as_ref().unwrap() };
cortex_m::interrupt::free(|cs| {
USB_LOGGER.borrow(cs).replace(Some(UsbLogger::new(bus_ref)));
});
// Enable the USB interrupt
unsafe {
pac::NVIC::unmask(hal::pac::Interrupt::USBCTRL_IRQ);
};
// The delay object lets us wait for specified amounts of time (in
// milliseconds)
let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().integer());
// The single-cycle I/O block controls our GPIO pins
let sio = hal::Sio::new(pac.SIO);
// Set the pins up according to their function on this particular board
let pins = bsp::Pins::new(
pac.IO_BANK0,
pac.PADS_BANK0,
sio.gpio_bank0,
&mut pac.RESETS,
);
// Set the LED to be an output
let mut led_pin = pins.gpio20.into_push_pull_output();
let mut counter = 0;
// Blink the LED at 1 Hz
loop {
// unsafe {USB_LOGGER.as_mut().unwrap().poll();}
led_pin.set_high().unwrap();
delay.delay_ms(500);
led_pin.set_low().unwrap();
delay.delay_ms(500);
cortex_m::interrupt::free(|cs| {
if let Some(logger) = USB_LOGGER.borrow(cs).borrow_mut().as_mut(){
// This yields a result! Ignoring that by assigning it to _
let _ = write!(logger, "Loop number {}\r\n", counter);
}
});
counter += 1;
if counter > 10 {
panic!("panic just to show we can!");
}
}
}
/// This function is called whenever the USB Hardware generates an Interrupt
/// Request.
#[allow(non_snake_case)]
#[interrupt]
unsafe fn USBCTRL_IRQ() {
cortex_m::interrupt::free(|cs| {
if let Some(logger) = USB_LOGGER.borrow(cs).borrow_mut().as_mut(){
logger.poll();
}
});
}
use core::panic::PanicInfo;
#[inline(never)]
#[panic_handler]
fn panic(panic_info: &PanicInfo) -> ! {
use core::sync::atomic::Ordering::SeqCst;
cortex_m::interrupt::disable();
cortex_m::interrupt::free(|cs| {
if let Some(logger) = USB_LOGGER.borrow(cs).borrow_mut().as_mut(){
// This yields a result! Ignoring that by assigning it to _
let _ = write!(logger, "{}\r\n", panic_info);
logger.poll();
}
});
loop {
cortex_m::interrupt::free(|cs| {
if let Some(logger) = USB_LOGGER.borrow(cs).borrow_mut().as_mut(){
// This yields a result! Ignoring that by assigning it to _
logger.poll()
}
});
core::sync::atomic::compiler_fence(SeqCst);
}
}
// End of file
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment