Last active
June 20, 2023 11:25
-
-
Save 9names/3936d1a1254f15a7ee6c6fa03d94cfa3 to your computer and use it in GitHub Desktop.
implementing 2 cdc serial ports on rp2040
This file contains 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 Two Serial port Example | |
//! | |
//! Creates two USB Serial devices on a Pico board, with the USB driver running in | |
//! the main thread. | |
//! | |
//! This will create two USB Serial device echoing anything they receives. | |
//! On the first port incoming ASCII characters are converted to uppercase. | |
//! On the second port incoming ASCII characters are converted to lowercase. | |
//! | |
//! See the `Cargo.toml` file for Copyright and license details. | |
#![no_std] | |
#![no_main] | |
// The macro for our start-up function | |
use rp_pico::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 rp_pico::hal::pac; | |
// A shorter alias for the Hardware Abstraction Layer, which provides | |
// higher-level drivers. | |
use rp_pico::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( | |
rp_pico::XOSC_CRYSTAL_FREQ, | |
pac.XOSC, | |
pac.CLOCKS, | |
pac.PLL_SYS, | |
pac.PLL_USB, | |
&mut pac.RESETS, | |
&mut watchdog, | |
) | |
.ok() | |
.unwrap(); | |
#[cfg(feature = "rp2040-e5")] | |
{ | |
let sio = hal::Sio::new(pac.SIO); | |
let _pins = rp_pico::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); | |
let mut serial2 = 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)) | |
.manufacturer("Fake company") | |
.product("Serial port") | |
.serial_number("TEST") | |
.device_class(2) // from: https://www.usb.org/defined-class-codes | |
.build(); | |
let timer = hal::Timer::new(pac.TIMER, &mut pac.RESETS); | |
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, &mut serial2]) { | |
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, | |
}; | |
} | |
} | |
} | |
match serial2.read(&mut buf) { | |
Err(_e) => { | |
// Do nothing | |
} | |
Ok(0) => { | |
// Do nothing | |
} | |
Ok(count) => { | |
// Convert to lower case | |
buf.iter_mut().take(count).for_each(|b| { | |
b.make_ascii_lowercase(); | |
}); | |
// Send back to the host | |
let mut wr_ptr = &buf[..count]; | |
while !wr_ptr.is_empty() { | |
match serial2.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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment