Last active
April 4, 2022 12:34
-
-
Save 9names/97aa7dd63f1a44cf6632973331ff03f8 to your computer and use it in GitHub Desktop.
Pico USB serial abstraction
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| //! # 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