diff --git a/arduino-hal/src/lib.rs b/arduino-hal/src/lib.rs index e0d5c69373..a06721a171 100644 --- a/arduino-hal/src/lib.rs +++ b/arduino-hal/src/lib.rs @@ -190,6 +190,22 @@ pub mod eeprom { #[cfg(feature = "board-selected")] pub use eeprom::Eeprom; +#[doc(no_inline)] +#[cfg(any( + feature = "arduino-micro", + feature = "arduino-leonardo", + feature = "sparkfun-promicro" +))] +pub use atmega_hal::default_usb_bus_with_pll; + +#[doc(no_inline)] +#[cfg(any( + feature = "arduino-micro", + feature = "arduino-leonardo", + feature = "sparkfun-promicro" +))] +pub use atmega_hal::default_usb_bus_with_pll_macro; + #[cfg(feature = "board-selected")] pub mod simple_pwm { #[cfg(feature = "mcu-atmega")] diff --git a/examples/arduino-leonardo/Cargo.toml b/examples/arduino-leonardo/Cargo.toml index 34e6eea994..b0d6338ac0 100644 --- a/examples/arduino-leonardo/Cargo.toml +++ b/examples/arduino-leonardo/Cargo.toml @@ -10,6 +10,8 @@ panic-halt = "1.0.0" ufmt = "0.2.0" nb = "1.1.0" embedded-hal = "1.0" +usb-device = "0.3.2" +usbd-serial = "0.2.2" [dependencies.arduino-hal] path = "../../arduino-hal/" diff --git a/examples/arduino-leonardo/src/bin/micro-usb-serial.rs b/examples/arduino-leonardo/src/bin/micro-usb-serial.rs new file mode 120000 index 0000000000..b77fcc4347 --- /dev/null +++ b/examples/arduino-leonardo/src/bin/micro-usb-serial.rs @@ -0,0 +1 @@ +../../../arduino-micro/src/bin/micro-usb-serial.rs \ No newline at end of file diff --git a/examples/arduino-micro/Cargo.toml b/examples/arduino-micro/Cargo.toml index 0ec9e63673..030ce4a924 100644 --- a/examples/arduino-micro/Cargo.toml +++ b/examples/arduino-micro/Cargo.toml @@ -8,6 +8,8 @@ publish = false panic-halt = "1.0.0" ufmt = "0.2.0" nb = "1.1.0" +usb-device = "0.3.2" +usbd-serial = "0.2.2" [dependencies.arduino-hal] path = "../../arduino-hal/" diff --git a/examples/arduino-micro/src/bin/micro-usb-serial.rs b/examples/arduino-micro/src/bin/micro-usb-serial.rs new file mode 100644 index 0000000000..f0f1788bff --- /dev/null +++ b/examples/arduino-micro/src/bin/micro-usb-serial.rs @@ -0,0 +1,55 @@ +#![no_std] +#![no_main] +use arduino_hal::Peripherals; +use panic_halt as _; +use usb_device::bus::UsbBusAllocator; +use usb_device::device::StringDescriptors; +use usb_device::device::UsbDeviceBuilder; +use usb_device::device::UsbVidPid; +use usb_device::LangID; +use usbd_serial::SerialPort; + +#[arduino_hal::entry] +fn main() -> ! { + let dp: Peripherals = arduino_hal::Peripherals::take().unwrap(); + + let usb_bus = arduino_hal::default_usb_bus_with_pll_macro!(dp); + let usb_bus_allocator = UsbBusAllocator::new(usb_bus); + let mut serial = SerialPort::new(&usb_bus_allocator); + + let string_descriptors = StringDescriptors::new(LangID::EN_US) + .manufacturer("test manufacturer") + .product("test product") + .serial_number("test serial number"); + + let rand_ids = UsbVidPid(0x1ea7, 0x4a09); + + let mut usb_dev = UsbDeviceBuilder::new(&usb_bus_allocator, rand_ids) + .strings(&[string_descriptors]) + .unwrap() + .max_packet_size_0(64) + .unwrap() + .device_class(usbd_serial::USB_CLASS_CDC) + .build(); + + loop { + // Wait until we have data + if !usb_dev.poll(&mut [&mut serial]) { + continue; + } + + // Read the data into this buffer + let mut read_buf = [0u8; 10]; + let Ok(read_count) = serial.read(&mut read_buf) else { + continue; + }; + if read_count == 0 { + continue; + } + + let len = serial + .write(&read_buf[0..read_count]) + .expect("The host should be reading data faster than the arduino can write it"); + assert_eq!(len, read_count); + } +} diff --git a/examples/sparkfun-promicro/Cargo.toml b/examples/sparkfun-promicro/Cargo.toml index 72b985f418..eb30bddfef 100644 --- a/examples/sparkfun-promicro/Cargo.toml +++ b/examples/sparkfun-promicro/Cargo.toml @@ -10,6 +10,8 @@ panic-halt = "1.0.0" ufmt = "0.2.0" nb = "1.1.0" embedded-hal = "1.0" +usb-device = "0.3.2" +usbd-serial = "0.2.2" [dependencies.arduino-hal] path = "../../arduino-hal/" diff --git a/examples/sparkfun-promicro/src/bin/micro-usb-serial.rs b/examples/sparkfun-promicro/src/bin/micro-usb-serial.rs new file mode 120000 index 0000000000..b77fcc4347 --- /dev/null +++ b/examples/sparkfun-promicro/src/bin/micro-usb-serial.rs @@ -0,0 +1 @@ +../../../arduino-micro/src/bin/micro-usb-serial.rs \ No newline at end of file diff --git a/mcu/atmega-hal/Cargo.toml b/mcu/atmega-hal/Cargo.toml index 44e4e8a7f6..7aa408fca9 100644 --- a/mcu/atmega-hal/Cargo.toml +++ b/mcu/atmega-hal/Cargo.toml @@ -21,7 +21,7 @@ atmega168 = ["avr-device/atmega168", "device-selected"] atmega328p = ["avr-device/atmega328p", "device-selected"] atmega328pb = ["avr-device/atmega328pb", "device-selected"] atmega32a = ["avr-device/atmega32a", "device-selected"] -atmega32u4 = ["avr-device/atmega32u4", "device-selected"] +atmega32u4 = ["avr-device/atmega32u4", "device-selected", "usb-device"] atmega2560 = ["avr-device/atmega2560", "device-selected"] atmega128a = ["avr-device/atmega128a", "device-selected"] atmega1280 = ["avr-device/atmega1280", "device-selected"] @@ -37,6 +37,7 @@ docsrs = ["atmega328p"] [dependencies] avr-hal-generic = { path = "../../avr-hal-generic/" } +usb-device = { version = "0.3.2", optional = true } [dependencies.avr-device] version = "0.8" diff --git a/mcu/atmega-hal/src/lib.rs b/mcu/atmega-hal/src/lib.rs index d076052345..079b068bf9 100644 --- a/mcu/atmega-hal/src/lib.rs +++ b/mcu/atmega-hal/src/lib.rs @@ -1,4 +1,5 @@ #![no_std] +#![feature(asm_experimental_arch)] //! `atmega-hal` //! ============= @@ -119,6 +120,93 @@ pub use avr_hal_generic::clock; pub use avr_hal_generic::delay; pub use avr_hal_generic::prelude; +#[cfg(feature = "atmega32u4")] +mod usb; + +// TODO: fix bad usb-device::UsbBus link +/// This function provides a safe abstraction layer over the USB hardware, by way of the +/// [UsbBus](usb-device::UsbBus) trait. +/// +/// There are a few notable limitations, however: +/// +/// * This implementation requires exclusive access to the PLL, even though on a hardware +/// level it is possible for the PLL output to be used by both the USB controller and +/// the high-speed timer (TC4) simultaneously. Refer to GitHub issue #TBD for details. +/// +/// TODO if Rahix agrees that this limitation isn't something we need to worry about +/// as part of PR, then create a GitHub issue so that someone else can fix it later: +/// +/// > **Title** +/// > +/// > Allow the USB and TC4 hardware to both use the PLL output simultaneously +/// > +/// > **Description** +/// > +/// > Our current UsbBus implementation prevents TC4 from using PLL output, even though +/// > the hardware supports it. There are two main +/// > problems that we need to solve first: +/// > +/// > 1. The current UsbBus implementation sets the PLL output to 48MHz. This could +/// > cause problems if the user has already configured TC4 to expect a different +/// > clock speed from the PLL. +/// > +/// > 2. We need to make the USB suspend state configurable. Currently when the USB +/// > bus is idle for 3ms or longer, it will disable the PLL to reduce power usage. +/// > However, this may not be desirable if TC4 is also using the PLL. +/// > +/// > **Comment** +/// > +/// > I think we *might* be able to solve this by splitting the constructor's +/// > argument into two separate parts. Instead of passing ownership of the entire PLL +/// > configuration (`pll: avr_device::atmega32u4::PLL`), we'd have one argument for +/// > the registers that config the PLL clock speed (e.g. `pll_config: &PLLFRQ`) and one +/// > optional argument for the registers that we use to turn the PLL on and off +/// > (e.g. `pll_suspend: Option<&mut pllcsr>`). A value of `None` would indicate that +/// > the user wants us to keep the PLL running while USB is idle. +/// > +/// > A few disclaimers: +/// > +/// > * This is a simplification. Instead of `pll_suspend: Option<&mut pllcsr>` we'd +/// > probably want to define a new trait, +/// > similar to what is done [in the `agausmann/atmega-usbd` repo](https://github.com/agausmann/atmega-usbd/blob/5fc68ca813ce0a37dab65dd4d66efe1ec125f2a8/src/lib.rs#L590-L618). +/// > +/// > * This is just one possible solution; there are others. +/// > +/// > * I've not spent much time investigating this, so this proposed solution might not work. +/// +/// * The current implementation does not attempt to minimize power usage. For details, +/// see GitHub issue #TBD. +/// +/// TODO if Rahix agrees that this limitation isn't something we need to worry about +/// as part of PR, then create a GitHub issue so that someone else can fix it later: +/// +/// * Add support for using interrupts, in addition to polling. +/// Similar to `agausmann/atmega-usbd`. +/// +/// * Shutdown the PLL when the USB module is suspended (TODO: do in this PR?) +/// +/// * and more? +/// +/// * The underlying struct that implements `UsbBus` is private. This is done intentionally +/// in order to make it easier to address the other issues without breaking backwards +/// compatibility. +#[cfg(feature = "atmega32u4")] +pub fn default_usb_bus_with_pll( + usb: avr_device::atmega32u4::USB_DEVICE, + pll: avr_device::atmega32u4::PLL, +) -> impl usb_device::class_prelude::UsbBus { + return usb::UsbdBus::new(usb, pll); +} + +/// This macro is exactly equivalent to [default_usb_bus_with_pll](default_usb_bus_with_pll). +#[cfg(feature = "atmega32u4")] +#[macro_export] +macro_rules! default_usb_bus_with_pll_macro { + ($p:expr) => { + $crate::default_usb_bus_with_pll($p.USB_DEVICE, $p.PLL) + }; +} + #[cfg(feature = "device-selected")] pub mod adc; #[cfg(feature = "device-selected")] diff --git a/mcu/atmega-hal/src/usb.rs b/mcu/atmega-hal/src/usb.rs new file mode 100644 index 0000000000..bb832c8571 --- /dev/null +++ b/mcu/atmega-hal/src/usb.rs @@ -0,0 +1,91 @@ +use core::cell::Cell; + +use avr_device::atmega32u4::PLL; +use avr_device::atmega32u4::USB_DEVICE; +use avr_device::interrupt::Mutex; +use usb_device::bus::PollResult; +use usb_device::bus::UsbBus; +use usb_device::endpoint::EndpointAddress; +use usb_device::endpoint::EndpointType; +use usb_device::UsbDirection; +use usb_device::UsbError; + +const MAX_ENDPOINTS: usize = 7; + +struct EndpointTableEntry { + _ep_type: EndpointType, + _direction: UsbDirection, + _max_packet_size: u16, +} + +pub struct UsbdBus { + _usb: Mutex, + _pll: Mutex, + _pending_ins: Mutex>, + _endpoints: [Option; MAX_ENDPOINTS], +} + +impl UsbdBus { + pub fn new(_usb: USB_DEVICE, _pll: PLL) -> Self { + todo!(); + } +} + +impl UsbBus for UsbdBus { + fn alloc_ep( + &mut self, + _direction: UsbDirection, + _ep_addr: Option, + _ep_type: EndpointType, + _max_packet_size: u16, + _interval: u8, + ) -> Result { + todo!(); + } + + fn enable(&mut self) { + todo!(); + } + + fn reset(&self) { + todo!(); + } + + fn set_device_address(&self, _addr: u8) { + todo!(); + } + + fn write(&self, _ep_addr: EndpointAddress, _buf: &[u8]) -> Result { + todo!(); + } + + fn read(&self, _ep_addr: EndpointAddress, _buf: &mut [u8]) -> Result { + todo!(); + } + + fn set_stalled(&self, _ep_addr: EndpointAddress, _stalled: bool) { + todo!(); + } + + fn is_stalled(&self, _ep_addr: EndpointAddress) -> bool { + todo!(); + } + + fn suspend(&self) { + todo!(); + } + + fn resume(&self) { + todo!(); + } + + fn poll(&self) -> PollResult { + todo!(); + } +} + +impl Drop for UsbdBus { + fn drop(&mut self) { + todo!() + } +}