//! # Pico USB Serial Example //! //! Creates a USB Serial device on a Pico board, with the USB driver running in //! the main thread. //! //! This will create a USB Serial device echoing anything it receives. Incoming //! ASCII characters are converted to upercase, so you can tell it is working //! and not just local-echo! //! //! See the `Cargo.toml` file for Copyright and license details. #![no_std] #![no_main] use adafruit_kb2040 as bsp; // The macro for our start-up function use bsp::entry; // Ensure we halt the program on panic (if we don't mention this crate it won't // be linked) use panic_halt as _; // 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::*, prelude::*}; // USB Communications Class Device support use usbd_serial::SerialPort; // Used to demonstrate writing formatted strings use core::fmt::Write; use heapless::String; /// 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 echoes any characters /// received over USB Serial. #[entry] fn main() -> ! { // Grab our singleton objects let mut pac = pac::Peripherals::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(); let timer = hal::Timer::new(pac.TIMER, &mut pac.RESETS, &clocks); #[cfg(feature = "rp2040-e5")] { let sio = hal::Sio::new(pac.SIO); let _pins = bsp::Pins::new( pac.IO_BANK0, pac.PADS_BANK0, sio.gpio_bank0, &mut pac.RESETS, ); } // 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, )); // Set up the USB Communications Class Device driver let mut serial = SerialPort::new(&usb_bus); // Create a USB device with a fake VID and PID let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x16c0, 0x27dd)) .strings(&[StringDescriptors::default() .manufacturer("Fake company") .product("Serial port") .serial_number("TEST")]) .unwrap() .device_class(2) // from: https://www.usb.org/defined-class-codes .build(); let mut said_hello = false; loop { // A welcome message at the beginning if !said_hello && timer.get_counter().ticks() >= 2_000_000 { said_hello = true; let _ = serial.write(b"Hello, World!\r\n"); let time = timer.get_counter().ticks(); let mut text: String<64> = String::new(); writeln!(&mut text, "Current timer ticks: {}", time).unwrap(); // This only works reliably because the number of bytes written to // the serial port is smaller than the buffers available to the USB // peripheral. In general, the return value should be handled, so that // bytes not transferred yet don't get lost. let _ = serial.write(text.as_bytes()); } // Check for new data if usb_dev.poll(&mut [&mut serial]) { let mut buf = [0u8; 64]; match serial.read(&mut buf) { Err(_e) => { // Do nothing } Ok(0) => { // Do nothing } Ok(count) => { // Convert to upper case buf.iter_mut().take(count).for_each(|b| { b.make_ascii_uppercase(); }); // Send back to the host let mut wr_ptr = &buf[..count]; while !wr_ptr.is_empty() { match serial.write(wr_ptr) { Ok(len) => wr_ptr = &wr_ptr[len..], // On error, just drop unwritten data. // One possible error is Err(WouldBlock), meaning the USB // write buffer is full. Err(_) => break, }; } } } } } } // End of file