diff --git a/boards/pico/examples/pico_usb_serial_interrupt.rs b/boards/pico/examples/pico_usb_serial_interrupt.rs index cc67de8..559d8a9 100644 --- a/boards/pico/examples/pico_usb_serial_interrupt.rs +++ b/boards/pico/examples/pico_usb_serial_interrupt.rs @@ -104,23 +104,23 @@ fn main() -> ! { USB_BUS = Some(usb_bus); } + // Grab a reference to the USB Bus allocator. We must promise not to take + // mutable access to this global variable whilst the reference exists! + let bus_ref = unsafe { USB_BUS.as_ref().unwrap() }; + // Set up the USB Communications Class Device driver - let serial = SerialPort::new(unsafe { USB_BUS.as_ref().unwrap() }); + let serial = SerialPort::new(bus_ref); unsafe { USB_SERIAL = Some(serial); } // Create a USB device with a fake VID and PID - let usb_dev = UsbDeviceBuilder::new( - // Note (safety): This is safe as interrupts haven't been started yet - unsafe { USB_BUS.as_ref().unwrap() }, - 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 usb_dev = 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); diff --git a/boards/pico/examples/pico_usb_twitchy_mouse.rs b/boards/pico/examples/pico_usb_twitchy_mouse.rs index 25a0782..4871c86 100644 --- a/boards/pico/examples/pico_usb_twitchy_mouse.rs +++ b/boards/pico/examples/pico_usb_twitchy_mouse.rs @@ -1,39 +1,84 @@ +//! # Pico USB 'Twitchy' Mouse Example +//! +//! Creates a USB HID Class Poiting device (i.e. a virtual mouse) on a Pico +//! board, with the USB driver running in the main thread. +//! +//! It generates movement reports which will twitch the cursor up and down by a +//! few pixels, several times a second. +//! +//! See the `Cargo.toml` file for Copyright and licence details. +//! //! This is a port of //! https://github.com/atsamd-rs/atsamd/blob/master/boards/itsybitsy_m0/examples/twitching_usb_mouse.rs + #![no_std] #![no_main] -use cortex_m::interrupt::free as disable_interrupts; +// The macro for our start-up function use cortex_m_rt::entry; -use embedded_time::fixed_point::FixedPoint; + +// The macro for marking our interrupt functions +use pico::hal::pac::interrupt; + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) use panic_halt as _; -use pico::{ - hal::{ - self, - clocks::{init_clocks_and_plls, Clock}, - pac::{self, interrupt}, - usb::UsbBus, - watchdog::Watchdog, - }, - XOSC_CRYSTAL_FREQ, -}; -use usb_device::bus::UsbBusAllocator; -use usb_device::prelude::*; + +// Pull in any important traits +use embedded_time::fixed_point::FixedPoint; +use pico::hal::prelude::*; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access +use pico::hal::pac; + +// A shorter alias for the Hardware Abstraction Layer, which provides +// higher-level drivers. +use pico::hal; + +// USB Device support +use usb_device::{class_prelude::*, prelude::*}; + +// USB Human Interface Device (HID) Class support use usbd_hid::descriptor::generator_prelude::*; use usbd_hid::descriptor::MouseReport; use usbd_hid::hid_class::HIDClass; +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. #[link_section = ".boot2"] #[used] pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER; +/// The USB Device Driver (shared with the interrupt). +static mut USB_DEVICE: Option> = None; + +/// The USB Bus Driver (shared with the interrupt). +static mut USB_BUS: Option> = None; + +/// The USB Human Interface Device Driver (shared with the interrupt). +static mut USB_HID: Option> = 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 submits cursor movement +/// updates periodically. #[entry] fn main() -> ! { + // Grab our singleton objects let mut pac = pac::Peripherals::take().unwrap(); - let mut watchdog = Watchdog::new(pac.WATCHDOG); - let clocks = init_clocks_and_plls( - XOSC_CRYSTAL_FREQ, + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::watchdog::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + // + // Our default is 12 MHz crystal input, 125 MHz system clock + let clocks = hal::clocks::init_clocks_and_plls( + pico::XOSC_CRYSTAL_FREQ, pac.XOSC, pac.CLOCKS, pac.PLL_SYS, @@ -44,7 +89,8 @@ fn main() -> ! { .ok() .unwrap(); - let usb_bus = UsbBusAllocator::new(UsbBus::new( + // Set up the USB driver + let usb_bus = UsbBusAllocator::new(hal::usb::UsbBus::new( pac.USBCTRL_REGS, pac.USBCTRL_DPRAM, clocks.usb_clock, @@ -52,67 +98,79 @@ fn main() -> ! { &mut pac.RESETS, )); unsafe { + // Note (safety): This is safe as interrupts haven't been started yet USB_BUS = Some(usb_bus); } - let usb_hid = HIDClass::new( - unsafe { USB_BUS.as_ref().unwrap() }, - MouseReport::desc(), - 60, - ); + // Grab a reference to the USB Bus allocator. We 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() }; + + // Set up the USB HID Class Device driver, providing Mouse Reports + let usb_hid = HIDClass::new(bus_ref, MouseReport::desc(), 60); unsafe { + // Note (safety): This is safe as interrupts haven't been started yet. USB_HID = Some(usb_hid); } - let usb_dev = UsbDeviceBuilder::new( - unsafe { USB_BUS.as_ref().unwrap() }, - UsbVidPid(0x16c0, 0x27dd), - ) - .manufacturer("Fake company") - .product("Twitchy Mousey") - .serial_number("TEST") - .device_class(0xEF) // misc - .build(); + // Create a USB device with a fake VID and PID + let usb_dev = UsbDeviceBuilder::new(bus_ref, UsbVidPid(0x16c0, 0x27da)) + .manufacturer("Fake company") + .product("Twitchy Mousey") + .serial_number("TEST") + .device_class(0xEF) // misc + .build(); unsafe { + // Note (safety): This is safe as interrupts haven't been started yet USB_DEVICE = Some(usb_dev); } unsafe { + // Enable the USB interrupt pac::NVIC::unmask(hal::pac::Interrupt::USBCTRL_IRQ); }; let core = pac::CorePeripherals::take().unwrap(); let mut delay = cortex_m::delay::Delay::new(core.SYST, clocks.system_clock.freq().integer()); + // Move the cursor up and down every 200ms loop { delay.delay_ms(100); - push_mouse_movement(MouseReport { + + let rep_up = MouseReport { x: 0, y: 4, buttons: 0, wheel: 0, - }) - .ok() - .unwrap_or(0); + }; + push_mouse_movement(rep_up).ok().unwrap_or(0); + delay.delay_ms(100); - push_mouse_movement(MouseReport { + + let rep_down = MouseReport { x: 0, y: -4, buttons: 0, wheel: 0, - }) - .ok() - .unwrap_or(0); + }; + push_mouse_movement(rep_down).ok().unwrap_or(0); } } -static mut USB_DEVICE: Option> = None; -static mut USB_BUS: Option> = None; -static mut USB_HID: Option> = None; - +/// Submit a new mouse movement report to the USB stack. +/// +/// We do this with interrupts disabled, to avoid a race hazard with the USB IRQ. fn push_mouse_movement(report: MouseReport) -> Result { - disable_interrupts(|_| unsafe { USB_HID.as_mut().map(|hid| hid.push_input(&report)) }).unwrap() + cortex_m::interrupt::free(|_| unsafe { + // Now interrupts are disabled, grab the global variable and, if + // available, send it a HID report + USB_HID.as_mut().map(|hid| hid.push_input(&report)) + }) + .unwrap() } +/// This function is called whenever the USB Hardware generates an Interrupt +/// Request. #[allow(non_snake_case)] #[interrupt] unsafe fn USBCTRL_IRQ() { @@ -121,3 +179,5 @@ unsafe fn USBCTRL_IRQ() { let usb_hid = USB_HID.as_mut().unwrap(); usb_dev.poll(&mut [usb_hid]); } + +// End of file