Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update pico usb serial interrupt #37

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
209 changes: 118 additions & 91 deletions boards/rp-pico/examples/pico_usb_serial_interrupt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,31 @@
#![no_std]
#![no_main]

// A cell to be used with `critical_section::Mutex`
use core::cell::RefCell;

// Trait required to use `writeln!(…)`
use core::fmt::Write;

// A system wide mutex synchrinising IRQ & cores
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a typo (synchrinising -> synchronising or synchronizing).

use critical_section::Mutex;

// Import common embedded_hal traits
use embedded_hal::prelude::*;

// The macro for marking out exception function
use cortex_m_rt::exception;

// The macro for our start-up function
use rp_pico::entry;

// The macro for marking our interrupt functions
use rp_pico::hal::pac::interrupt;

// GPIO traits
use embedded_hal::digital::v2::OutputPin;

// Ensure we halt the program on panic (if we don't mention this crate it won't
// be linked)
use panic_halt as _;

// Pull in any important traits
use rp_pico::hal::prelude::*;

// A shorter alias for the Peripheral Access Crate, which provides low-level
// register access
use rp_pico::hal::pac;
Expand All @@ -42,14 +51,28 @@ use usb_device::{class_prelude::*, prelude::*};
// USB Communications Class Device support
use usbd_serial::SerialPort;

/// The USB Device Driver (shared with the interrupt).
static mut USB_DEVICE: Option<UsbDevice<hal::usb::UsbBus>> = None;
struct StaticUsb<'a> {
usb_device: UsbDevice<'a, hal::usb::UsbBus>,
usb_serial: SerialPort<'a, hal::usb::UsbBus>,
}

/// The USB Bus Driver (shared with the interrupt).
static mut USB_BUS: Option<UsbBusAllocator<hal::usb::UsbBus>> = None;
// shared with the interrupt
static STATIC_USB: Mutex<RefCell<Option<StaticUsb>>> = Mutex::new(RefCell::new(None));

/// The USB Serial Device Driver (shared with the interrupt).
static mut USB_SERIAL: Option<SerialPort<hal::usb::UsbBus>> = None;
/// Call the closure within a critical section passing the content of `STATIC_USB`.
///
/// This function `panic!`s if `STATIC_USB` does not contain `Some` value.
fn do_with_usb<T>(closure: impl FnOnce(&mut StaticUsb) -> T) -> T {
critical_section::with(|cs| {
// Create a mutable local binding for the RefMut type wrapping our Option<StaticUsb>.
let mut usb = STATIC_USB.borrow_ref_mut(cs);
// Borrow the content of that binding.
let usb = usb
.as_mut()
.expect("Usb must be setup before calling this function.");
closure(usb)
})
}

/// Entry point to our bare-metal application.
///
Expand All @@ -60,9 +83,12 @@ static mut USB_SERIAL: Option<SerialPort<hal::usb::UsbBus>> = None;
/// infinite loop.
#[entry]
fn main() -> ! {
Copy link
Contributor

@Agent59 Agent59 Oct 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't a comment like "never ending function, except when the pico is reset to usb boot." not be more appropriate?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good spot, this text is actually inconsistent with the example.

// The USB Bus Driver (shared with the interrupt).
static mut USB_BUS: Option<UsbBusAllocator<hal::usb::UsbBus>> = None;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the last part of the comment "(shared with the interrupt)" not incorrect? The interrupt function (fn USBCTRL_IRQ()) shouldn't be able to reference the USB_BUS, because USB_BUS is out of scope.


// Grab our singleton objects
let mut pac = pac::Peripherals::take().unwrap();
let core = pac::CorePeripherals::take().unwrap();
let mut core = pac::CorePeripherals::take().unwrap();

// Set up the watchdog driver - needed by the clock setup code
let mut watchdog = hal::Watchdog::new(pac.WATCHDOG);
Expand All @@ -82,75 +108,82 @@ fn main() -> ! {
.ok()
.unwrap();

// The delay object lets us wait for specified amounts of time (in milliseconds)
let mut delay = hal::Timer::new(pac.TIMER, &mut pac.RESETS, &clocks);

// Set up the USB driver
let usb_bus = UsbBusAllocator::new(hal::usb::UsbBus::new(
*USB_BUS = Some(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() };
let bus_ref = USB_BUS.as_mut().unwrap();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this part of the comment "We are promising to the compiler not to take mutable access to this global variable whilst this reference exists!" not incorrect too? The compiler will stop you from taking a second mutable reference and the USB_BUS is local to the #[entry] function.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, this comment is no longer relevant thanks to the #[entry] trick, the compiler is able to check we're not doing anything silly there.


// Set up the USB Communications Class Device driver
let serial = SerialPort::new(bus_ref);
unsafe {
USB_SERIAL = Some(serial);
}

let usb_serial = SerialPort::new(bus_ref);
// Create a USB device with a fake VID and PID
let usb_dev = UsbDeviceBuilder::new(bus_ref, UsbVidPid(0x16c0, 0x27dd))
let usb_device = UsbDeviceBuilder::new(bus_ref, UsbVidPid(0x16c0, 0x27dd))
.manufacturer("Fake company")
.product("Serial port")
.serial_number("TEST")
.device_class(2) // from: https://www.usb.org/defined-class-codes
.build();
unsafe {
// Note (safety): This is safe as interrupts haven't been started yet
USB_DEVICE = Some(usb_dev);
}

// Setup the shared usb items in their global storage.
critical_section::with(|cs| {
STATIC_USB.replace(
cs,
Some(StaticUsb {
usb_device,
usb_serial,
}),
);
});

// How many system clock ticks are there in a 100th of a second (10ms)?
let ticks = pac::SYST::get_ticks_per_10ms();

// Setup SysTick for a 10ms periodic interrupt.
core.SYST.set_reload(ticks);
core.SYST.enable_counter();
core.SYST.enable_interrupt();

// Enable the USB interrupt
unsafe {
pac::NVIC::unmask(hal::pac::Interrupt::USBCTRL_IRQ);
};

// No more USB code after this point in main! We can do anything we want in
// here since USB is handled in the interrupt - let's blink an LED!

// 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().to_Hz());

// The single-cycle I/O block controls our GPIO pins
let sio = hal::Sio::new(pac.SIO);
// wait for the usb device to be configured (aka enumerated on a host).
while do_with_usb(|u| u.usb_device.state()) != UsbDeviceState::Configured {
delay.delay_ms(1);
}

// Set the pins up according to their function on this particular board
let pins = rp_pico::Pins::new(
pac.IO_BANK0,
pac.PADS_BANK0,
sio.gpio_bank0,
&mut pac.RESETS,
);
// Print a loop count every 2 seconds up to 10 times, then reset to usb boot.
for i in 0..10 {
delay.delay_ms(2_000);

// Set the LED to be an output
let mut led_pin = pins.led.into_push_pull_output();
let mut text = heapless::String::<20>::new();
writeln!(&mut text, "loop number: {}", i).unwrap();

// Blink the LED at 1 Hz
loop {
led_pin.set_high().unwrap();
delay.delay_ms(500);
led_pin.set_low().unwrap();
delay.delay_ms(500);
write_serial(text.as_bytes());
}
delay.delay_ms(2_000);
hal::rom_data::reset_to_usb_boot(0, 0);
unreachable!();
}

/// Writes to the serial port.
///
/// We do this with in a system wide critical-section to avoid a race hazard with the USB IRQ and
/// the other core (although this example does not use it).
fn write_serial(byte_array: &[u8]) {
let _ = do_with_usb(|u| u.usb_serial.write(byte_array));
}

/// This function is called whenever the USB Hardware generates an Interrupt
Expand All @@ -160,48 +193,42 @@ fn main() -> ! {
/// knowing nothing about USB.
#[allow(non_snake_case)]
#[interrupt]
unsafe fn USBCTRL_IRQ() {
use core::sync::atomic::{AtomicBool, Ordering};

/// Note whether we've already printed the "hello" message.
static SAID_HELLO: AtomicBool = AtomicBool::new(false);

fn USBCTRL_IRQ() {
// Grab the global objects. This is OK as we only access them under interrupt.
let usb_dev = USB_DEVICE.as_mut().unwrap();
let serial = USB_SERIAL.as_mut().unwrap();

// Say hello exactly once on start-up
if !SAID_HELLO.load(Ordering::Relaxed) {
SAID_HELLO.store(true, Ordering::Relaxed);
let _ = serial.write(b"Hello, World!\r\n");
}

// Poll the USB driver with all of our supported USB Classes
if usb_dev.poll(&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() {
let _ = serial.write(wr_ptr).map(|len| {
wr_ptr = &wr_ptr[len..];
do_with_usb(|usb| {
// Poll the USB driver with all of our supported USB Classes
if usb.usb_device.poll(&mut [&mut usb.usb_serial]) {
let mut buf = [0u8; 64];
match usb.usb_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() {
let _ = usb.usb_serial.write(wr_ptr).map(|len| {
wr_ptr = &wr_ptr[len..];
});
}
}
}
}
}
});
}

#[allow(non_snake_case)]
#[exception]
fn SysTick() {
do_with_usb(|usb| usb.usb_device.poll(&mut [&mut usb.usb_serial]));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A comment like "Keeps the usb_device updated in case the usb interrupt is not called often." or a better explanation why this is advisable might be useful here for people like me who only have a superficial understanding of embedded software development.

}

// End of file
Loading