From e9ac3acede356be3319217a380a87576107964ba Mon Sep 17 00:00:00 2001 From: decaday Date: Thu, 26 Dec 2024 18:45:01 +0800 Subject: [PATCH 1/2] feat(usb): `usb-device` impl --- Cargo.toml | 16 +++++++------ src/lib.rs | 3 ++- src/usb.rs | 67 ++++++++++++++++++++++++++++++++++++------------------ 3 files changed, 56 insertions(+), 30 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cea70ac..365bb8d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,10 +41,14 @@ log = { version = "0.4", optional = true } critical-section = "1.2" cfg-if = "1.0.0" portable-atomic = { version = "1", features = ["unsafe-assume-single-core", "require-cas"], optional = true } -# musb = { version = "0.1.0", optional = true, features = ["prebuild"] } +# musb = { version = "0.2.0", optional = true, features = ["prebuild"] } musb = { git = "https://github.com/decaday/musb.git", optional = true, features = ["prebuild"] } # musb = { path = "../musb", optional = true , features = ["prebuild"] } +embassy-usb-driver = {version = "0.1.0", optional = true } +usb-device = {version = "0.3.2", optional = true } + + futures-util = { version = "0.3.30", default-features = false } embassy-hal-internal = { version = "0.2.0", features = [ "cortex-m", @@ -60,7 +64,7 @@ embassy-executor = { version = "0.6", features = [ "arch-cortex-m", ] } embassy-embedded-hal = { version = "0.2.0", default-features = false } -embassy-usb-driver = {version = "0.1.0" } + [build-dependencies] # py32-metapac = { path = "../py32-data/build/py32-metapac", default-features = false, features = [ @@ -77,7 +81,7 @@ default = ["rt", "memory-x", "defmt", "embassy", "time", "exti"] rt = ["py32-metapac/rt"] -defmt = ["dep:defmt", "dep:defmt-rtt", "embassy-usb-driver/defmt"] +defmt = ["dep:defmt", "dep:defmt-rtt", "embassy-usb-driver/defmt", "musb?/defmt"] memory-x = ["py32-metapac/memory-x"] @@ -87,10 +91,8 @@ time = ["dep:embassy-time", "embassy-embedded-hal/time"] exti = [] -# PY32F07x: the IN and OUT buffers of the same endpoint being shared -# When this feature is enabled, the In and Out of an endpoint will not be used at the same time, except for ep0. -# PY32F403: IN and OUT do not share FIFO, this feature is invalid -allow-ep-shared-fifo = ["musb/allow-ep-shared-fifo"] +embassy-usb-driver-impl = ["dep:musb","dep:embassy-usb-driver", "musb/embassy-usb-driver-impl"] +usb-device-impl = ["dep:musb","dep:usb-device", "musb/usb-device-impl"] py32f030k28 = ["py32-metapac/py32f030k28"] py32f030f16 = ["py32-metapac/py32f030f16"] diff --git a/src/lib.rs b/src/lib.rs index b657cb6..e6bad85 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,7 +46,8 @@ pub mod usart; pub mod gpio; #[cfg(feature = "time-driver-systick")] pub mod systick_time_driver; -#[cfg(feature = "py32f072c1b")] + +#[cfg(any(feature = "embassy-usb-driver-impl", feature = "usb-device-impl"))] pub mod usb; #[cfg(feature = "exti")] diff --git a/src/usb.rs b/src/usb.rs index 7c9f35e..54e648f 100644 --- a/src/usb.rs +++ b/src/usb.rs @@ -4,21 +4,25 @@ /// featuring a fixed FIFO size and with some register functionalities masked. /// /// See more: https://github.com/decaday/musb -/// -/// For the PY32F07x series, IN and OUT endpoints for the same endpoint share a FIFO. -/// By default, we don't use a single endpoint simultaneously for IN and OUT directions. -/// However, you can enable the `allow-ep-shared-fifo` feature to use an endpoint's IN -/// and OUT capabilities concurrently. + use core::marker::PhantomData; + +#[cfg(feature = "embassy-usb-driver-impl")] use embassy_usb_driver as driver; +#[cfg(feature = "embassy-usb-driver-impl")] +use embassy_usb_driver::EndpointType; +#[cfg(feature = "embassy-usb-driver-impl")] +use musb::{MusbDriver, In, Out, Bus, ControlPipe, Endpoint}; +#[cfg(feature = "usb-device-impl")] +pub use musb::UsbdBus; + +use musb::UsbInstance; use crate::interrupt::typelevel::Interrupt; use crate::rcc::{self, RccPeripheral}; use crate::{interrupt, Peripheral}; -use embassy_usb_driver::EndpointType; -use musb::{Bus, ControlPipe, Endpoint, In, MusbDriver, Out, UsbInstance}; /// Interrupt handler. pub struct InterruptHandler { @@ -31,12 +35,30 @@ impl interrupt::typelevel::Handler for InterruptHandl } } +fn init() { + let freq = T::frequency(); + if freq.0 != 48_000_000 { + panic!("USB clock (PLL) must be 48MHz"); + } + + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + rcc::enable_and_reset::(); + + #[cfg(feature = "time")] + embassy_time::block_for(embassy_time::Duration::from_millis(100)); + #[cfg(not(feature = "time"))] + cortex_m::asm::delay(unsafe { crate::rcc::get_freqs() }.sys.to_hertz().unwrap().0 / 10); +} + +#[cfg(feature = "embassy-usb-driver-impl")] /// USB driver. pub struct Driver<'d, T: Instance> { phantom: PhantomData<&'d mut T>, inner: MusbDriver<'d, UsbInstance>, } +#[cfg(feature = "embassy-usb-driver-impl")] impl<'d, T: Instance> Driver<'d, T> { /// Create a new USB driver. pub fn new( @@ -45,19 +67,7 @@ impl<'d, T: Instance> Driver<'d, T> { _dp: impl Peripheral

> + 'd, _dm: impl Peripheral

> + 'd, ) -> Self { - let freq = T::frequency(); - if freq.0 != 48_000_000 { - panic!("USB clock (PLL) must be 48MHz"); - } - - T::Interrupt::unpend(); - unsafe { T::Interrupt::enable() }; - rcc::enable_and_reset::(); - - #[cfg(feature = "time")] - embassy_time::block_for(embassy_time::Duration::from_millis(100)); - #[cfg(not(feature = "time"))] - cortex_m::asm::delay(unsafe { crate::rcc::get_freqs() }.sys.to_hertz().unwrap().0 / 10); + init::(); Self { inner: MusbDriver::new(), @@ -66,6 +76,7 @@ impl<'d, T: Instance> Driver<'d, T> { } } +#[cfg(feature = "embassy-usb-driver-impl")] impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> { type EndpointOut = Endpoint<'d, UsbInstance, Out>; type EndpointIn = Endpoint<'d, UsbInstance, In>; @@ -79,7 +90,7 @@ impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> { interval_ms: u8, ) -> Result { self.inner - .alloc_endpoint(ep_type, max_packet_size, interval_ms, false) + .alloc_endpoint(ep_type, max_packet_size, interval_ms, None) } fn alloc_endpoint_out( @@ -89,7 +100,7 @@ impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> { interval_ms: u8, ) -> Result { self.inner - .alloc_endpoint(ep_type, max_packet_size, interval_ms, false) + .alloc_endpoint(ep_type, max_packet_size, interval_ms, None) } fn start( @@ -100,6 +111,18 @@ impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> { } } +#[cfg(feature = "usb-device-impl")] +pub fn new_bus<'d, T: Instance>( + _usb: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, + _dp: impl Peripheral

> + 'd, + _dm: impl Peripheral

> + 'd, +) -> UsbdBus { + init::(); + + UsbdBus::new() +} + trait SealedInstance {} /// USB instance trait. From 90f1a8d7153d612ac92d96d404d28e48ba2f70aa Mon Sep 17 00:00:00 2001 From: decaday Date: Thu, 26 Dec 2024 18:45:28 +0800 Subject: [PATCH 2/2] examples: add `usbd-f072` demo --- examples/py32f072/Cargo.toml | 2 +- examples/usbd-f072/.cargo/config.toml | 13 +++ examples/usbd-f072/Cargo.toml | 55 +++++++++++++ examples/usbd-f072/README.md | 13 +++ examples/usbd-f072/build.rs | 10 +++ examples/usbd-f072/src/bin/hid.rs | 111 ++++++++++++++++++++++++++ examples/usbd-f072/src/bin/serial.rs | 89 +++++++++++++++++++++ 7 files changed, 292 insertions(+), 1 deletion(-) create mode 100644 examples/usbd-f072/.cargo/config.toml create mode 100644 examples/usbd-f072/Cargo.toml create mode 100644 examples/usbd-f072/README.md create mode 100644 examples/usbd-f072/build.rs create mode 100644 examples/usbd-f072/src/bin/hid.rs create mode 100644 examples/usbd-f072/src/bin/serial.rs diff --git a/examples/py32f072/Cargo.toml b/examples/py32f072/Cargo.toml index 1214ac6..d58db55 100644 --- a/examples/py32f072/Cargo.toml +++ b/examples/py32f072/Cargo.toml @@ -20,7 +20,7 @@ embassy-time = { version = "0.3.2", features = ["defmt", "defmt-timestamp-uptime embedded-io = { version = "0.6.0" } embedded-io-async = { version = "0.6.1" } -py32-hal = { path = "../../", features = [ "time-driver-tim15", "py32f072c1b"]} +py32-hal = { path = "../../", features = [ "time-driver-tim15", "py32f072c1b", "embassy-usb-driver-impl"]} defmt = "0.3" defmt-rtt = "0.4" diff --git a/examples/usbd-f072/.cargo/config.toml b/examples/usbd-f072/.cargo/config.toml new file mode 100644 index 0000000..08ec816 --- /dev/null +++ b/examples/usbd-f072/.cargo/config.toml @@ -0,0 +1,13 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# TODO(2) replace `$CHIP` with your chip's name (see `probe-rs chip list` output) +runner = "probe-rs run --chip PY32F072xB" + +# rustflags = [ +# "-C", "linker=flip-link", +# ] + +[build] +target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+ + +[env] +DEFMT_LOG = "trace" \ No newline at end of file diff --git a/examples/usbd-f072/Cargo.toml b/examples/usbd-f072/Cargo.toml new file mode 100644 index 0000000..d4f7492 --- /dev/null +++ b/examples/usbd-f072/Cargo.toml @@ -0,0 +1,55 @@ +[package] +name = "py32f072-usbd-examples" +version = "0.2.0" +edition = "2021" + +[dependencies] +panic-halt = "1.0.0" +cortex-m = { version = "0.7.7", features = [ + "critical-section-single-core", + "critical-section", +] } +cortex-m-rt = "0.7.3" +cortex-m-semihosting = { version = "0.5" } +panic-probe = { version = "0.3", features = ["print-defmt"] } + +py32-hal = { path = "../../", default-features = false,features = [ "py32f072c1b", + "time-driver-tim15", + "defmt", + "rt", + "memory-x", + "usb-device-impl", + "embassy" +]} + +defmt = "0.3" +defmt-rtt = "0.4" +embassy-futures = "0.1.1" +embassy-usb = { version = "0.3.0", features = [ "defmt"]} +usbd-human-interface-device = { version = "0.5.0", features = [ "defmt"]} + +portable-atomic = { version = "1.5", features = ["critical-section"] } +static_cell = "2.1" +usbd-serial = "0.2.2" +usb-device = "0.3.2" + + +# cargo build/run +[profile.dev] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 'z' # <- +overflow-checks = true # <- + +# cargo build/run --release +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- + diff --git a/examples/usbd-f072/README.md b/examples/usbd-f072/README.md new file mode 100644 index 0000000..79c0dc4 --- /dev/null +++ b/examples/usbd-f072/README.md @@ -0,0 +1,13 @@ +# `usb-device` (`usbd`) Demo + +> [!WARNING] +> +> This demo is intended to showcase the [usb-device](https://crates.io/crates/usb-device) crate, not the [embassy-usb](https://crates.io/crates/embassy-usb) crate. +> +> If you're looking for examples of USB async drivers, please refer to the [py32f072 examples](https://chatgpt.com/py32f072/src/bin). + +## Usage + +You need to enable the `usb-device-impl` feature and disable the `embassy-usb-driver-impl` feature. + +The PY32 uses a stripped-down version of the MUSB IP. For more information, check out the [musb](https://crates.io/crates/musb) crate. It includes implementations of both [embassy-usb-driver](https://crates.io/crates/embassy-usb-driver) and [usb-device](https://crates.io/crates/usb-device). \ No newline at end of file diff --git a/examples/usbd-f072/build.rs b/examples/usbd-f072/build.rs new file mode 100644 index 0000000..4b3ca97 --- /dev/null +++ b/examples/usbd-f072/build.rs @@ -0,0 +1,10 @@ +fn main() { + // `--nmagic` is required if memory section addresses are not aligned to 0x10000, + // for example the FLASH and RAM sections in your `memory.x`. + // See https://github.com/rust-embedded/cortex-m-quickstart/pull/95 + println!("cargo:rustc-link-arg=--nmagic"); + + println!("cargo:rustc-link-arg=-Tlink.x"); + + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); +} diff --git a/examples/usbd-f072/src/bin/hid.rs b/examples/usbd-f072/src/bin/hid.rs new file mode 100644 index 0000000..57b0940 --- /dev/null +++ b/examples/usbd-f072/src/bin/hid.rs @@ -0,0 +1,111 @@ +#![no_std] +#![no_main] +#![feature(impl_trait_in_assoc_type)] + +use defmt::*; +use py32_hal::bind_interrupts; +use py32_hal::gpio::{Input, Level, Output, Pull, Speed}; +use py32_hal::rcc::{Pll, PllMul, PllSource, Sysclk}; +use py32_hal::time::Hertz; +use py32_hal::usb::{self, InterruptHandler}; +use {defmt_rtt as _, panic_probe as _}; + +use usb_device::{class_prelude::*, prelude::*}; +use usbd_human_interface_device::page::Keyboard; +use usbd_human_interface_device::prelude::*; + +bind_interrupts!(struct Irqs { + USB => InterruptHandler; +}); + +#[cortex_m_rt::entry] +fn main() -> ! { + let mut cfg: py32_hal::Config = Default::default(); + + // PY32 USB uses PLL as the clock source and can only run at 48Mhz. + cfg.rcc.hsi = Some(Hertz::mhz(16)); + cfg.rcc.pll = Some(Pll { + src: PllSource::HSI, + mul: PllMul::MUL3, + }); + cfg.rcc.sys = Sysclk::PLL; + let p = py32_hal::init(cfg); + + let mut led = Output::new(p.PB2, Level::High, Speed::Low); + let button = Input::new(p.PB0, Pull::Up); + + let usb_bus = usb::new_bus(p.USB, Irqs, p.PA12, p.PA11); + + let usb_bus_allocator = UsbBusAllocator::new(usb_bus); + + let mut keyboard = UsbHidClassBuilder::new() + .add_device(usbd_human_interface_device::device::keyboard::BootKeyboardConfig::default()) + .build(&usb_bus_allocator); + + //https://pid.codes + let mut usb_dev = UsbDeviceBuilder::new(&usb_bus_allocator, UsbVidPid(0x1209, 0x0001)) + .strings(&[StringDescriptors::default() + .manufacturer("py32-rs team") + .product("Boot Keyboard") + .serial_number("TEST")]) + .unwrap() + .build(); + + let mut button_pressed = false; + + loop { + if button.is_high() { + if button_pressed { + // Button was just released + button_pressed = false; + // Send release report with no keys pressed + match keyboard.device().write_report([Keyboard::NoEventIndicated]) { + Err(UsbHidError::WouldBlock) => {} + Err(UsbHidError::Duplicate) => {} + Ok(_) => {} + Err(e) => { + core::panic!("Failed to write keyboard report: {:?}", e) + } + }; + } + } else { + if !button_pressed { + // Button was just pressed + button_pressed = true; + info!("Button pressed"); + // Send press report with 'A' key + match keyboard.device().write_report([Keyboard::A]) { + Err(UsbHidError::WouldBlock) => {} + Err(UsbHidError::Duplicate) => {} + Ok(_) => {} + Err(e) => { + core::panic!("Failed to write keyboard report: {:?}", e) + } + }; + } + } + + //Tick once per ms + match keyboard.tick() { + Err(UsbHidError::WouldBlock) => {} + Ok(_) => {} + Err(e) => { + core::panic!("Failed to process keyboard tick: {:?}", e) + } + }; + + if usb_dev.poll(&mut [&mut keyboard]) { + match keyboard.device().read_report() { + Err(UsbError::WouldBlock) => { + //do nothing + } + Err(e) => { + core::panic!("Failed to read keyboard report: {:?}", e) + } + Ok(leds) => { + led.set_level(Level::from(leds.caps_lock)); + } + } + } + } +} \ No newline at end of file diff --git a/examples/usbd-f072/src/bin/serial.rs b/examples/usbd-f072/src/bin/serial.rs new file mode 100644 index 0000000..c992dac --- /dev/null +++ b/examples/usbd-f072/src/bin/serial.rs @@ -0,0 +1,89 @@ +#![no_std] +#![no_main] +#![feature(impl_trait_in_assoc_type)] + +use defmt::*; +use py32_hal::bind_interrupts; +use py32_hal::gpio::{Level, Output, Speed}; +use py32_hal::rcc::{Pll, PllMul, PllSource, Sysclk}; +use py32_hal::time::Hertz; +use py32_hal::usb::{self, InterruptHandler}; +use {defmt_rtt as _, panic_probe as _}; + +use usb_device::{class_prelude::*, prelude::*}; +use usbd_serial::{SerialPort, USB_CLASS_CDC}; + +bind_interrupts!(struct Irqs { + USB => InterruptHandler; +}); + +#[cortex_m_rt::entry] +fn main() -> ! { + let mut cfg: py32_hal::Config = Default::default(); + + // PY32 USB uses PLL as the clock source and can only run at 48Mhz. + cfg.rcc.hsi = Some(Hertz::mhz(16)); + cfg.rcc.pll = Some(Pll { + src: PllSource::HSI, + mul: PllMul::MUL3, + }); + cfg.rcc.sys = Sysclk::PLL; + let p = py32_hal::init(cfg); + + let mut led = Output::new(p.PB2, Level::High, Speed::Low); + + let usb_bus = usb::new_bus(p.USB, Irqs, p.PA12, p.PA11); + + 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("py32-rs team") + .product("Serial") + .serial_number("TEST"); + + let mut usb_dev = UsbDeviceBuilder::new(&usb_bus_allocator, UsbVidPid(0x16c0, 0x27dd)) + .strings(&[string_descriptors]) + .unwrap() + .max_packet_size_0(64) + .unwrap() + .device_class(USB_CLASS_CDC) + .build(); + + loop { + if !usb_dev.poll(&mut [&mut serial]) { + continue; + } + + let mut buf = [0u8; 64]; + + match serial.read(&mut buf) { + Ok(count) if count > 0 => { + led.set_high(); // Turn on + + info!("data: {:x}", &buf[0..count]); + + // Echo back in upper case + for c in buf[0..count].iter_mut() { + if 0x61 <= *c && *c <= 0x7a { + *c &= !0x20; + } + } + + let mut write_offset = 0; + while write_offset < count { + match serial.write(&buf[write_offset..count]) { + Ok(len) if len > 0 => { + write_offset += len; + } + _ => {} + } + } + } + _ => {} + } + + led.set_low(); // Turn off + } +}