diff --git a/Cargo.toml b/Cargo.toml index e05e3ad..635ab10 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,7 +57,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 = [ @@ -74,17 +74,21 @@ default = ["rt", "memory-x", "defmt", "embassy", "time", "exti"] rt = ["py32-metapac/rt"] -defmt = ["dep:defmt", "dep:defmt-rtt"] +defmt = ["dep:defmt", "dep:defmt-rtt", "embassy-usb-driver/defmt"] memory-x = ["py32-metapac/memory-x"] embassy = ["dep:embassy-sync", "dep:embassy-futures", "dep:embassy-time-driver"] - 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 = [] + py32f030k28 = ["py32-metapac/py32f030k28"] py32f030f16 = ["py32-metapac/py32f030f16"] py32f072c1b = ["py32-metapac/py32f072c1b"] diff --git a/README.md b/README.md index 6fc2240..8a98eb8 100644 --- a/README.md +++ b/README.md @@ -37,9 +37,7 @@ Supported chip flags: `py32f030f16`, `py32f030k28`, `py32f072c1b`, More is comin Note: Currently the program behavior has nothing to do with chip packaging. - - -others should work if you are careful as most peripherals are similar enough.In fact, the IPs of peripherals in different PY32 series may be consistent. Moreover, some series use the same die, so it might not require much work. +Others should work if you are careful as most peripherals are similar enough.In fact, the IPs of peripherals in different PY32 series may be consistent. Moreover, some series use the same die, so it might not require much work. For a full list of chip capabilities and peripherals, check the [py32-data](https://github.com/py32-rs/py32-data) repository. @@ -57,14 +55,13 @@ For a full list of chip capabilities and peripherals, check the [py32-data](http | ADC | | ✅+ | ✅ | | | RTC | | | | | | Timer(PWM) | | ✅ | ❓ | | -| USB/OTG | N/A | N/A | | | +| USB | N/A | N/A | ✅+ | | -- ✅ : Expected to work -- ❌ : Not implemented -- ❓ : Not tested -- `+` : marks the async driver -- TODO: I haven't got a dev board yet, help-wanted -- N/A: Not available +- ✅ : Implemented +- Blank : Not implemented +- ❓ : Requires demo verification +- `+` : Async support +- N/A : Not available ## TODOs @@ -92,6 +89,8 @@ Embassy requires that any TIM used as a time-driver has at least two channels, s `time-driver-systick`: Although we do not recommend using it and there are some shortcomings, it does work. For details, please see [systick-demo](examples/systick-time-driver-f030/README.md) +For PY32F07x, F040, you can use TIM15, TIM3 or TIM1. + ## Minimum supported Rust version(MSRV) This project is developed with a recent **nightly** version of Rust compiler. And is expected to work with beta versions of Rust. @@ -114,3 +113,7 @@ All kinds of contributions are welcome. ## License This project is licensed under the MIT or Apache-2.0 license, at your option. + + + +Some peripheral driver code has been modified from [embassy-stm32]([embassy/embassy-stm32 at main · embassy-rs/embassy](https://github.com/embassy-rs/embassy/tree/main/embassy-stm32)). Big thanks to this project and its awesome contributors! diff --git a/examples/py32f030/.cargo/config.toml b/examples/py32f030/.cargo/config.toml index 4d73c4a..e8e20c9 100644 --- a/examples/py32f030/.cargo/config.toml +++ b/examples/py32f030/.cargo/config.toml @@ -2,6 +2,10 @@ # probe-rs chip list | grep -i PY32 runner = 'probe-rs run --chip PY32F030x8' +# rustflags = [ +# "-C", "linker=flip-link", +# ] + [build] target = "thumbv6m-none-eabi" diff --git a/examples/py32f030/src/bin/button_exti.rs b/examples/py32f030/src/bin/button_exti.rs index d81ed7e..02f7d13 100644 --- a/examples/py32f030/src/bin/button_exti.rs +++ b/examples/py32f030/src/bin/button_exti.rs @@ -8,8 +8,6 @@ use py32_hal::exti::ExtiInput; use py32_hal::gpio::Pull; use {defmt_rtt as _, panic_probe as _}; -use py32_hal::interrupt; - #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = py32_hal::init(Default::default()); diff --git a/examples/py32f030/src/bin/raw_rtt.rs b/examples/py32f030/src/bin/raw_rtt.rs index 2b64227..43f967a 100644 --- a/examples/py32f030/src/bin/raw_rtt.rs +++ b/examples/py32f030/src/bin/raw_rtt.rs @@ -4,9 +4,11 @@ use cortex_m_rt::entry; use defmt::*; use defmt_rtt as _; -use hal::pac; use panic_halt as _; + use py32_hal as hal; +#[allow(unused_imports)] +use hal::pac; #[entry] fn main() -> ! { diff --git a/examples/py32f030/src/bin/usart.rs b/examples/py32f030/src/bin/usart.rs index c294b22..fd9eb79 100644 --- a/examples/py32f030/src/bin/usart.rs +++ b/examples/py32f030/src/bin/usart.rs @@ -5,7 +5,6 @@ use embassy_executor::Spawner; use defmt::*; use py32_hal::usart::{Config, Uart}; -use py32_hal::{bind_interrupts, peripherals, usart}; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] diff --git a/examples/py32f030/src/bin/usart_buffered.rs b/examples/py32f030/src/bin/usart_buffered.rs index f3e0248..3a2f36e 100644 --- a/examples/py32f030/src/bin/usart_buffered.rs +++ b/examples/py32f030/src/bin/usart_buffered.rs @@ -7,7 +7,6 @@ use embassy_executor::Spawner; use py32_hal::usart::{BufferedUart, Config}; use py32_hal::{bind_interrupts, peripherals, usart}; use py32_hal::time::Hertz; -use py32_hal::rcc::{Pll, PllSource, Sysclk}; use embedded_io_async::Read; use embedded_io_async::Write; use {defmt_rtt as _, panic_probe as _}; diff --git a/examples/py32f072/.cargo/config.toml b/examples/py32f072/.cargo/config.toml index af5600b..10d6f7a 100644 --- a/examples/py32f072/.cargo/config.toml +++ b/examples/py32f072/.cargo/config.toml @@ -1,11 +1,13 @@ [target.thumbv6m-none-eabi] # probe-rs chip list | grep -i PY32 -runner = 'probe-rs run --chip PY32F072xb' +runner = "probe-rs run --chip PY32F072xB" + +# rustflags = [ +# "-C", "linker=flip-link", +# ] [build] -target = "thumbv6m-none-eabi" +target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+ [env] -DEFMT_LOG = "trace" - -# rustflags = ["-C", "link-arg=-Tlink.x"] +DEFMT_LOG = "info" \ No newline at end of file diff --git a/examples/py32f072/Cargo.toml b/examples/py32f072/Cargo.toml index 2603b8f..e0e09a1 100644 --- a/examples/py32f072/Cargo.toml +++ b/examples/py32f072/Cargo.toml @@ -24,6 +24,15 @@ py32-hal = { path = "../../", features = [ "time-driver-tim15", "py32f072c1b"]} defmt = "0.3" defmt-rtt = "0.4" +embassy-futures = "0.1.1" +embassy-usb = { version = "0.3.0", features = [ "defmt"]} +usbd-hid = "0.8.2" + +# embassy-usb-logger = "0.2.0" +log = "0.4" + +portable-atomic = { version = "1.5", features = ["critical-section"] } +static_cell = "2.1" # cargo build/run [profile.dev] diff --git a/examples/py32f072/src/bin/raw_rtt.rs b/examples/py32f072/src/bin/raw_rtt.rs index 2b64227..43f967a 100644 --- a/examples/py32f072/src/bin/raw_rtt.rs +++ b/examples/py32f072/src/bin/raw_rtt.rs @@ -4,9 +4,11 @@ use cortex_m_rt::entry; use defmt::*; use defmt_rtt as _; -use hal::pac; use panic_halt as _; + use py32_hal as hal; +#[allow(unused_imports)] +use hal::pac; #[entry] fn main() -> ! { diff --git a/examples/py32f072/src/bin/usb_hid_keyboard.rs b/examples/py32f072/src/bin/usb_hid_keyboard.rs new file mode 100644 index 0000000..6ea5672 --- /dev/null +++ b/examples/py32f072/src/bin/usb_hid_keyboard.rs @@ -0,0 +1,212 @@ +#![no_std] +#![no_main] +#![feature(impl_trait_in_assoc_type)] + +use core::sync::atomic::{AtomicBool, Ordering}; + +use defmt::*; +use {defmt_rtt as _, panic_probe as _}; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use py32_hal::bind_interrupts; +use py32_hal::gpio::Pull; +use py32_hal::exti::ExtiInput; +use py32_hal::time::Hertz; +use py32_hal::rcc::{Pll, PllSource, Sysclk, PllMul}; +use py32_hal::usb::{Driver, InterruptHandler}; + + +use embassy_usb::class::hid::{HidReaderWriter, ReportId, RequestHandler, State}; +use embassy_usb::control::OutResponse; +use embassy_usb::{Builder, Handler}; +use usbd_hid::descriptor::{KeyboardReport, SerializedDescriptor}; + + +bind_interrupts!(struct Irqs { + USB => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + 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); + + // Create the driver, from the HAL. + + let driver = Driver::new(p.USB, Irqs, p.PA12, p.PA11); + + // Create embassy-usb Config + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("HID keyboard example"); + config.serial_number = Some("12345678"); + config.max_power = 100; + config.max_packet_size_0 = 64; + + // Required for windows compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + // You can also add a Microsoft OS descriptor. + let mut msos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut request_handler = MyRequestHandler {}; + let mut device_handler = MyDeviceHandler::new(); + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut msos_descriptor, + &mut control_buf, + ); + + builder.handler(&mut device_handler); + + // Create classes on the builder. + let config = embassy_usb::class::hid::Config { + report_descriptor: KeyboardReport::desc(), + request_handler: None, + poll_ms: 60, + max_packet_size: 8, + }; + + let hid = HidReaderWriter::<_, 1, 8>::new(&mut builder, &mut state, config); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + let (reader, mut writer) = hid.split(); + + let mut button = ExtiInput::new(p.PB0, p.EXTI0, Pull::Down); + + // Do stuff with the class! + let in_fut = async { + loop { + button.wait_for_rising_edge().await; + // signal_pin.wait_for_high().await; + info!("Button pressed!"); + // Create a report with the A key pressed. (no shift modifier) + let report = KeyboardReport { + keycodes: [4, 0, 0, 0, 0, 0], + leds: 0, + modifier: 0, + reserved: 0, + }; + // Send the report. + match writer.write_serialize(&report).await { + Ok(()) => {} + Err(e) => warn!("Failed to send report: {:?}", e), + }; + + button.wait_for_falling_edge().await; + // signal_pin.wait_for_low().await; + info!("Button released!"); + let report = KeyboardReport { + keycodes: [0, 0, 0, 0, 0, 0], + leds: 0, + modifier: 0, + reserved: 0, + }; + match writer.write_serialize(&report).await { + Ok(()) => {} + Err(e) => warn!("Failed to send report: {:?}", e), + }; + } + }; + + let out_fut = async { + reader.run(false, &mut request_handler).await; + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, join(in_fut, out_fut)).await; +} + +struct MyRequestHandler {} + +impl RequestHandler for MyRequestHandler { + fn get_report(&mut self, id: ReportId, _buf: &mut [u8]) -> Option { + info!("Get report for {:?}", id); + None + } + + fn set_report(&mut self, id: ReportId, data: &[u8]) -> OutResponse { + info!("Set report for {:?}: {=[u8]}", id, data); + OutResponse::Accepted + } + + fn set_idle_ms(&mut self, id: Option, dur: u32) { + info!("Set idle rate for {:?} to {:?}", id, dur); + } + + fn get_idle_ms(&mut self, id: Option) -> Option { + info!("Get idle rate for {:?}", id); + None + } +} + +struct MyDeviceHandler { + configured: AtomicBool, +} + +impl MyDeviceHandler { + fn new() -> Self { + MyDeviceHandler { + configured: AtomicBool::new(false), + } + } +} + +impl Handler for MyDeviceHandler { + fn enabled(&mut self, enabled: bool) { + self.configured.store(false, Ordering::Relaxed); + if enabled { + info!("Device enabled"); + } else { + info!("Device disabled"); + } + } + + fn reset(&mut self) { + self.configured.store(false, Ordering::Relaxed); + info!("Bus reset, the Vbus current limit is 100mA"); + } + + fn addressed(&mut self, addr: u8) { + self.configured.store(false, Ordering::Relaxed); + info!("USB address set to: {}", addr); + } + + fn configured(&mut self, configured: bool) { + self.configured.store(configured, Ordering::Relaxed); + if configured { + info!("Device configured, it may now draw up to the configured current limit from Vbus.") + } else { + info!("Device is no longer configured, the Vbus current limit is 100mA."); + } + } +} diff --git a/examples/py32f072/src/bin/usb_logger.rs b/examples/py32f072/src/bin/usb_logger.rs new file mode 100644 index 0000000..c5c6978 --- /dev/null +++ b/examples/py32f072/src/bin/usb_logger.rs @@ -0,0 +1,63 @@ +#![no_std] +#![no_main] +#![feature(impl_trait_in_assoc_type)] + +// This example works well, but there is a dependency conflict. +// Please remove the `embassy-usb` and `usbd-hid` dependencies from `Cargo.toml`, and then add: +// ```toml +// embassy-usb-logger = "0.2.0" +// ``` +// This issue may be resolved in a future release of Embassy. + +// Delete me +#[embassy_executor::main] +async fn main(_spawner: embassy_executor::Spawner) { + let _p = py32_hal::init(Default::default()); +} + + +use {defmt_rtt as _, panic_probe as _}; +// use embassy_executor::Spawner; +// use embassy_time::Timer; +// use py32_hal::bind_interrupts; +// use py32_hal::time::Hertz; +// use py32_hal::rcc::{Pll, PllSource, Sysclk, PllMul}; +// use py32_hal::usb::{Driver, InterruptHandler}; + + +// bind_interrupts!(struct Irqs { +// USB => InterruptHandler; +// }); + + +// #[embassy_executor::task] +// async fn logger_task(driver: Driver<'static, py32_hal::peripherals::USB>) { +// embassy_usb_logger::run!(512, log::LevelFilter::Info, driver); +// } + +// #[embassy_executor::main] +// async fn main(spawner: Spawner) { +// 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); + +// // Create the driver, from the HAL. + +// let driver = Driver::new(p.USB, Irqs, p.PA12, p.PA11); + +// spawner.spawn(logger_task(driver)).unwrap(); + +// let mut counter = 0; +// loop { +// counter += 1; +// log::info!("Tick {}", counter); +// Timer::after_secs(1).await; +// } +// } diff --git a/examples/py32f072/src/bin/usb_midi.rs b/examples/py32f072/src/bin/usb_midi.rs new file mode 100644 index 0000000..3ed93f4 --- /dev/null +++ b/examples/py32f072/src/bin/usb_midi.rs @@ -0,0 +1,116 @@ +#![no_std] +#![no_main] +#![feature(impl_trait_in_assoc_type)] + +use defmt::*; +use {defmt_rtt as _, panic_probe as _}; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use py32_hal::bind_interrupts; +use py32_hal::time::Hertz; +use py32_hal::rcc::{Pll, PllSource, Sysclk, PllMul}; +use py32_hal::usb::{Driver, InterruptHandler, Instance}; + +use embassy_usb::class::midi::MidiClass; +use embassy_usb::driver::EndpointError; +use embassy_usb::{Builder, Config}; + + +bind_interrupts!(struct Irqs { + USB => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + 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); + + // Create the driver, from the HAL. + + let driver = Driver::new(p.USB, Irqs, p.PA12, p.PA11); + + // Create embassy-usb Config + let mut config = Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-MIDI example"); + config.serial_number = Some("12345678"); + config.max_power = 100; + config.max_packet_size_0 = 64; + + // Required for windows compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut builder = Builder::new( + driver, + config, + &mut config_descriptor, + &mut bos_descriptor, + &mut [], // no msos descriptors + &mut control_buf, + ); + + // Create classes on the builder. + let mut class = MidiClass::new(&mut builder, 1, 1, 64); + + // The `MidiClass` can be split into `Sender` and `Receiver`, to be used in separate tasks. + // let (sender, receiver) = class.split(); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Use the Midi class! + let midi_fut = async { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = midi_echo(&mut class).await; + info!("Disconnected"); + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, midi_fut).await; +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => defmt::panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +async fn midi_echo<'d, T: Instance + 'd>(class: &mut MidiClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = class.read_packet(&mut buf).await?; + let data = &buf[..n]; + info!("data: {:x}", data); + class.write_packet(data).await?; + } +} \ No newline at end of file diff --git a/examples/py32f072/src/bin/usb_serial.rs b/examples/py32f072/src/bin/usb_serial.rs new file mode 100644 index 0000000..9aae997 --- /dev/null +++ b/examples/py32f072/src/bin/usb_serial.rs @@ -0,0 +1,126 @@ +#![no_std] +#![no_main] +#![feature(impl_trait_in_assoc_type)] + + +use defmt::*; +use {defmt_rtt as _, panic_probe as _}; +use embassy_executor::Spawner; +use py32_hal::bind_interrupts; +use py32_hal::time::Hertz; +use py32_hal::rcc::{Pll, PllSource, Sysclk, PllMul}; +use py32_hal::usb::{Driver, InterruptHandler, Instance}; +use static_cell::StaticCell; + +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::UsbDevice; + + +bind_interrupts!(struct Irqs { + USB => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + 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); + + // Create the driver, from the HAL. + + let driver = Driver::new(p.USB, Irqs, p.PA12, p.PA11); + + // Create embassy-usb Config + let config = { + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-serial example"); + config.serial_number = Some("12345678"); + config.max_power = 100; + config.max_packet_size_0 = 64; + + // Required for windows compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + config + }; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut builder = { + static CONFIG_DESCRIPTOR: StaticCell<[u8; 256]> = StaticCell::new(); + static BOS_DESCRIPTOR: StaticCell<[u8; 256]> = StaticCell::new(); + static CONTROL_BUF: StaticCell<[u8; 64]> = StaticCell::new(); + + let builder = embassy_usb::Builder::new( + driver, + config, + CONFIG_DESCRIPTOR.init([0; 256]), + BOS_DESCRIPTOR.init([0; 256]), + &mut [], // no msos descriptors + CONTROL_BUF.init([0; 64]), + ); + builder + }; + + // Create classes on the builder. + let mut class = { + static STATE: StaticCell = StaticCell::new(); + let state = STATE.init(State::new()); + CdcAcmClass::new(&mut builder, state, 64) + }; + + // Build the builder. + let usb = builder.build(); + + // Run the USB device. + unwrap!(spawner.spawn(usb_task(usb))); + + // Do stuff with the class! + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); + } +} + +type MyUsbDriver = Driver<'static, py32_hal::peripherals::USB>; +type MyUsbDevice = UsbDevice<'static, MyUsbDriver>; + +#[embassy_executor::task] +async fn usb_task(mut usb: MyUsbDevice) -> ! { + usb.run().await +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => defmt::panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = class.read_packet(&mut buf).await?; + let data = &buf[..n]; + info!("data: {:x}", data); + class.write_packet(data).await?; + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 38d6535..43cdbb5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -47,6 +47,8 @@ pub mod time_driver; #[cfg(feature = "time-driver-systick")] pub mod systick_time_driver; pub mod gpio; +#[cfg(usb)] +pub mod usb; #[cfg(feature = "exti")] pub mod exti; diff --git a/src/rcc/f0.rs b/src/rcc/f0.rs index d4f0688..a1d781f 100644 --- a/src/rcc/f0.rs +++ b/src/rcc/f0.rs @@ -298,6 +298,7 @@ pub(crate) unsafe fn init(config: Config) { sys: Some(sys).into(), hsi: hsi.into(), lse: None.into(), + pll: pll.into(), }; crate::rcc::set_freqs(clocks); } diff --git a/src/rcc/mod.rs b/src/rcc/mod.rs index 937c249..45f6e66 100644 --- a/src/rcc/mod.rs +++ b/src/rcc/mod.rs @@ -1,3 +1,5 @@ +/// Reset and Clock Control (RCC) + use core::mem::MaybeUninit; use critical_section::CriticalSection; @@ -23,6 +25,7 @@ pub struct Clocks { pub hsi: crate::time::MaybeHertz, pub lse: crate::time::MaybeHertz, + pub pll: crate::time::MaybeHertz, // pub rtc: crate::time::MaybeHertz, // pub sys: Option, // pub usb: Option, diff --git a/src/usb/bus.rs b/src/usb/bus.rs new file mode 100644 index 0000000..1dc14db --- /dev/null +++ b/src/usb/bus.rs @@ -0,0 +1,253 @@ +use super::*; + +/// USB bus. +pub struct Bus<'d, T: Instance> { + pub(super) phantom: PhantomData<&'d mut T>, + pub(super) ep_confs: [EndPointConfig; EP_COUNT], + pub(super) inited: bool, +} + +impl<'d, T: Instance> driver::Bus for Bus<'d, T> { + async fn poll(&mut self) -> Event { + poll_fn(move |cx| { + BUS_WAKER.register(cx.waker()); + + let regs = T::regs(); + + // TODO: implement VBUS detection. + if !self.inited { + self.inited = true; + return Poll::Ready(Event::PowerDetected); + } + + if IRQ_RESUME.load(Ordering::Acquire) { + IRQ_RESUME.store(false, Ordering::Relaxed); + return Poll::Ready(Event::Resume); + } + + if IRQ_RESET.load(Ordering::Acquire) { + IRQ_RESET.store(false, Ordering::Relaxed); + + regs.power().write(|w| w.set_suspend_mode(true)); + // for index in 1..EP_COUNT { + // regs.index().write(|w| w.set_index(index as _)); + // regs.in_csr1().modify(|w| w.set_flush_fifo(true)); + // } + + trace!("RESET"); + + for w in &EP_IN_WAKERS { + w.wake() + } + for w in &EP_OUT_WAKERS { + w.wake() + } + + return Poll::Ready(Event::Reset); + } + + if IRQ_SUSPEND.load(Ordering::Acquire) { + IRQ_SUSPEND.store(false, Ordering::Relaxed); + return Poll::Ready(Event::Suspend); + } + + Poll::Pending + }) + .await + } + + fn endpoint_set_stalled(&mut self, ep_addr: EndpointAddress, stalled: bool) { + // This can race, so do a retry loop. + let reg = T::regs(); + let ep_index = ep_addr.index(); + if ep_index != 0 { + reg.index().write(|w| w.set_index(ep_index as _)); + } + match ep_addr.direction() { + Direction::In => { + if ep_index == 0 { + // usb_ep0_state = USB_EP0_STATE_STALL; + + reg.ep0_csr().write(|w| { + w.set_send_stall(stalled); + if stalled { w.set_serviced_out_pkt_rdy(true); } + }); + + // while !reg.ep0_csr().read().sent_stall() {} + } + else { + reg.in_csr1().write(|w| { + w.set_send_stall(stalled); + if !stalled { + w.set_sent_stall(false); + w.set_clr_data_tog(true); + } + }); + // while !reg.in_csr1().read().sent_stall() {} + } + EP_IN_WAKERS[ep_addr.index()].wake(); + } + Direction::Out => { + if ep_index == 0 { + // usb_ep0_state = USB_EP0_STATE_STALL; + + reg.ep0_csr().write(|w| { + w.set_send_stall(stalled); + if stalled { w.set_serviced_out_pkt_rdy(true); } + }); + // while !reg.ep0_csr().read().sent_stall() {} + } + else { + reg.out_csr1().write(|w| { + w.set_send_stall(stalled); + if !stalled { + w.set_sent_stall(false); + w.set_clr_data_tog(true); + } + }); + // while !reg.out_csr1().read().sent_stall() {} + } + EP_IN_WAKERS[ep_addr.index()].wake(); + EP_OUT_WAKERS[ep_addr.index()].wake(); + } + } + } + + fn endpoint_is_stalled(&mut self, ep_addr: EndpointAddress) -> bool { + let reg = T::regs(); + let ep_index = ep_addr.index(); + if ep_index != 0 { + reg.index().write(|w| w.set_index(ep_index as _)); + } + + if ep_index == 0 { + // TODO: py32 offiial CherryUsb port returns false directly for EP0 + reg.ep0_csr().read().send_stall() + } else { + match ep_addr.direction() { + Direction::In => reg.in_csr1().read().send_stall(), + Direction::Out => reg.out_csr1().read().send_stall(), + } + } + } + + fn endpoint_set_enabled(&mut self, ep_addr: EndpointAddress, enabled: bool) { + trace!("set_enabled {:x} {}", ep_addr, enabled); + let ep_index = ep_addr.index(); + + if enabled { + T::regs().index().write(|w| w.set_index(ep_index as u8)); + match ep_addr.direction() { + Direction::Out => { + if ep_index == 0 { + T::regs().int_in1e().modify(|w| + w.set_ep0(true)) + } else { + T::regs().int_out1e().modify(|w| + w.set_epout(ep_index - 1, true) + ); + } + + // T::regs().out_csr2().write(|w| { + // w.set_auto_clear(true); + // }); + + T::regs().max_pkt_out().write(|w| + w.set_max_pkt_size(self.ep_confs[ep_index].out_max_fifo_size_btyes) + ); + + T::regs().out_csr1().write(|w| { + w.set_clr_data_tog(true); + }); + + //TODO: DMA + + if self.ep_confs[ep_index].ep_type == EndpointType::Isochronous { + T::regs().out_csr2().write(|w| { + w.set_iso(true); + }); + } + + if T::regs().out_csr1().read().out_pkt_rdy() { + T::regs().out_csr1().modify(|w| + w.set_flush_fifo(true) + ); + } + + let flags = EP_OUT_ENABLED.load(Ordering::Acquire) | ep_index as u8; + EP_OUT_ENABLED.store(flags, Ordering::Release); + // Wake `Endpoint::wait_enabled()` + EP_OUT_WAKERS[ep_index].wake(); + } + Direction::In => { + if ep_index == 0 { + T::regs().int_in1e().modify(|w| + w.set_ep0(true)) + } else { + T::regs().int_in1e().modify(|w| + w.set_epin(ep_index - 1, true) + ); + } + + // T::regs().in_csr2().write(|w| { + // w.set_auto_set(true); + // }); + + // TODO: DMA + + T::regs().max_pkt_in().write(|w| + w.set_max_pkt_size(self.ep_confs[ep_index].in_max_fifo_size_btyes) + ); + + T::regs().in_csr1().write(|w| { + w.set_clr_data_tog(true); + }); + + if self.ep_confs[ep_index].ep_type == EndpointType::Isochronous { + T::regs().in_csr2().write(|w| { + w.set_iso(true); + }); + } + T::regs().in_csr2().write(|w| w.set_mode(Mode::IN)); + + if T::regs().in_csr1().read().fifo_not_empty() { + T::regs().in_csr1().modify(|w| + w.set_flush_fifo(true) + ); + } + + let flags = EP_IN_ENABLED.load(Ordering::Acquire) | ep_index as u8; + EP_IN_ENABLED.store(flags, Ordering::Release); + // Wake `Endpoint::wait_enabled()` + EP_IN_WAKERS[ep_index].wake(); + } + } + } + else { + // py32 offiial CherryUsb port does nothing when disable an endpoint + match ep_addr.direction() { + Direction::Out => { + let flags = EP_OUT_ENABLED.load(Ordering::Acquire) & !(ep_index as u8); + EP_OUT_ENABLED.store(flags, Ordering::Release); + } + Direction::In => { + let flags = EP_IN_ENABLED.load(Ordering::Acquire) & !(ep_index as u8); + EP_IN_ENABLED.store(flags, Ordering::Release); + } + } + } + } + + async fn enable(&mut self) { + T::regs().int_usb().write(|w| { + w.set_reset(true); + w.set_suspend(true); + w.set_resume(true); + }); + } + async fn disable(&mut self) {} + + async fn remote_wakeup(&mut self) -> Result<(), Unsupported> { + Err(Unsupported) + } +} \ No newline at end of file diff --git a/src/usb/control_pipe.rs b/src/usb/control_pipe.rs new file mode 100644 index 0000000..ab7a61a --- /dev/null +++ b/src/usb/control_pipe.rs @@ -0,0 +1,185 @@ +use super::*; + +/// USB control pipe. +pub struct ControlPipe<'d, T: Instance> { + pub(super) _phantom: PhantomData<&'d mut T>, + pub(super) max_packet_size: u16, + pub(super) ep_in: Endpoint<'d, T, In>, + pub(super) ep_out: Endpoint<'d, T, Out>, +} + +impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> { + fn max_packet_size(&self) -> usize { + usize::from(self.max_packet_size) + } + + async fn setup(&mut self) -> [u8; 8] { + let regs = T::regs(); + loop { + trace!("SETUP read waiting"); + poll_fn(|cx| { + EP_OUT_WAKERS[0].register(cx.waker()); + + regs.index().write(|w| w.set_index(0)); + + if regs.ep0_csr().read().out_pkt_rdy() { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + + if regs.ep0_count().read().count() != 8 { + trace!("SETUP read failed: {:?}", regs.ep0_count().read().count()); + continue; + } + + let mut buf = [0; 8]; + (&mut buf).into_iter().for_each(|b| + *b = regs.fifo(0).read().data() + ); + regs.ep0_csr().modify(|w| w.set_serviced_out_pkt_rdy(true)); + + trace!("SETUP read ok"); + return buf; + } + } + + async fn data_out(&mut self, buf: &mut [u8], first: bool, last: bool) -> Result { + trace!("control: data_out len={} first={} last={}", buf.len(), first, last); + + let regs = T::regs(); + + let _ = poll_fn(|cx| { + EP_OUT_WAKERS[0].register(cx.waker()); + // STC uses same usb IP with py32 (mentor usb), + // which said it is nessery to set index to 0 + regs.index().write(|w| w.set_index(0)); + let ready = regs.ep0_csr().read().out_pkt_rdy(); + if ready { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + + regs.index().write(|w| w.set_index(0)); + + let read_count = regs.ep0_count().read().count(); + if read_count as usize > buf.len() { + return Err(EndpointError::BufferOverflow); + } + + if read_count as u16 > self.ep_out.info.max_packet_size { + return Err(EndpointError::BufferOverflow); + } + + buf.into_iter().for_each(|b| + *b = regs.fifo(0).read().data() + ); + regs.ep0_csr().modify(|w| w.set_serviced_out_pkt_rdy(true)); + trace!("READ OK, rx_len = {}", read_count); + + Ok(read_count as usize) + } + + async fn data_in(&mut self, data: &[u8], first: bool, last: bool) -> Result<(), EndpointError> { + trace!("control: data_in len={} first={} last={}", data.len(), first, last); + + if data.len() > self.ep_in.info.max_packet_size as usize { + return Err(EndpointError::BufferOverflow); + } + + let regs = T::regs(); + + trace!("WRITE WAITING"); + + let _ = poll_fn(|cx| { + EP_IN_WAKERS[0].register(cx.waker()); + regs.index().write(|w| w.set_index(0)); + + // TODO: use fifo_not_empty? + let unready = regs.ep0_csr().read().in_pkt_rdy(); + + if unready { + Poll::Pending + } else { + Poll::Ready(()) + } + }) + .await; + + regs.index().write(|w| w.set_index(0)); + + data.into_iter().for_each(|b| + regs.fifo(0).write(|w| w.set_data(*b)) + ); + + regs.ep0_csr().modify(|w| { + w.set_in_pkt_rdy(true); + if last { w.set_data_end(true); } + }); + Ok(()) + } + + async fn accept(&mut self) { + trace!("control: accept"); + + let regs = T::regs(); + regs.index().write(|w| w.set_index(0)); + + // zero length + regs.ep0_csr().modify(|w| { + w.set_in_pkt_rdy(true); + // w.set_data_end(true); + }); + + cortex_m::asm::delay(10000); + + // Wait is needed, so that we don't set the address too soon, breaking the status stage. + // (embassy-usb sets the address after accept() returns) + poll_fn(|cx| { + EP_IN_WAKERS[0].register(cx.waker()); + regs.index().write(|w| w.set_index(0)); + + // A zero-length OUT data packet is used to indicate the end of a Control transfer. In normal operation, such packets should only + // be received after the entire length of the device request has been transferred (i.e. after the CPU has set DataEnd). If, however, the + // host sends a zero-length OUT data packet before the entire length of device request has been transferred, this signals the + // premature end of the transfer. In this case, the MUSBMHDRC will automatically flush any IN token loaded by CPU ready for the + // Data phase from the FIFO and set SetupEnd. + if regs.ep0_csr().read().setup_end() { + regs.ep0_csr().write(|w| w.set_serviced_setup_end(false)); + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + + trace!("control: accept OK"); + } + + async fn reject(&mut self) { + let regs = T::regs(); + trace!("control: reject"); + + // Set IN+OUT to stall + regs.index().write(|w| w.set_index(0)); + regs.ep0_csr().modify(|w| { + w.set_send_stall(true); + w.set_serviced_out_pkt_rdy(true); + }); + + // TODO: async waiting for Sent Stall? + } + + async fn accept_set_address(&mut self, addr: u8) { + self.accept().await; + + let regs = T::regs(); + trace!("setting addr: {}", addr); + regs.addr().write(|w| w.set_addr(addr)); + } +} \ No newline at end of file diff --git a/src/usb/driver.rs b/src/usb/driver.rs new file mode 100644 index 0000000..6cf64e3 --- /dev/null +++ b/src/usb/driver.rs @@ -0,0 +1,193 @@ +use super::*; + +/// USB driver. +pub struct Driver<'d, T: Instance> { + phantom: PhantomData<&'d mut T>, + alloc: [EndpointData; EP_COUNT], +} + +impl<'d, T: Instance> Driver<'d, T> { + /// Create a new USB driver. + pub fn new( + _usb: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, + _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::(); + + let regs = T::regs(); + + regs.index().write(|w| w.set_index(0)); + + #[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); + + // Initialize the bus so that it signals that power is available + BUS_WAKER.wake(); + + Self { + phantom: PhantomData, + alloc: [EndpointData { + ep_conf: EndPointConfig { + ep_type: EndpointType::Bulk, + in_max_fifo_size_btyes: 1, + out_max_fifo_size_btyes: 1, + }, + used_in: false, + used_out: false, + }; EP_COUNT], + } + } + + fn alloc_endpoint( + &mut self, + ep_type: EndpointType, + max_packet_size: u16, + interval_ms: u8, + is_ep0: bool, + ) -> Result, driver::EndpointAllocError> { + trace!( + "allocating type={:?} mps={:?} interval_ms={}, dir={:?}", + ep_type, + max_packet_size, + interval_ms, + D::dir() + ); + + + let index = if is_ep0 { + Some((0, &mut self.alloc[0])) + } + else { + self.alloc.iter_mut().enumerate().find(|(i, ep)| { + if *i == 0 { + return false; // reserved for control pipe + } + let used = ep.used_out || ep.used_in; + + #[cfg(all(not(feature = "allow-ep-shared-fifo"), py32f072))] + if used { return false } + + #[cfg(py32f072)] + if ((max_packet_size + 7) / 8) as u8 > MAX_FIFO_SIZE_BTYES[*i] { + return false; + } + + #[cfg(py32f403)] + if ((max_packet_size + 7) / 8) as u8 > MAX_FIFO_SIZE_BTYES { + panic!("max_packet_size > MAX_FIFO_SIZE"); + } + + let used_dir = match D::dir() { + Direction::Out => ep.used_out, + Direction::In => ep.used_in, + }; + !used || (ep.ep_conf.ep_type == ep_type && !used_dir) + }) + }; + + let (index, ep) = match index { + Some(x) => x, + None => return Err(EndpointAllocError), + }; + + ep.ep_conf.ep_type = ep_type; + + + T::regs().index().write(|w| w.set_index(index as u8)); + match D::dir() { + Direction::Out => { + assert!(!ep.used_out); + ep.used_out = true; + + ep.ep_conf.out_max_fifo_size_btyes = calc_max_fifo_size_btyes(max_packet_size); + } + Direction::In => { + assert!(!ep.used_in); + ep.used_in = true; + + ep.ep_conf.in_max_fifo_size_btyes = calc_max_fifo_size_btyes(max_packet_size); + } + }; + + Ok(Endpoint { + _phantom: PhantomData, + info: EndpointInfo { + addr: EndpointAddress::from_parts(index, D::dir()), + ep_type, + max_packet_size, + interval_ms, + }, + }) + } +} + +impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> { + type EndpointOut = Endpoint<'d, T, Out>; + type EndpointIn = Endpoint<'d, T, In>; + type ControlPipe = ControlPipe<'d, T>; + type Bus = Bus<'d, T>; + + fn alloc_endpoint_in( + &mut self, + ep_type: EndpointType, + max_packet_size: u16, + interval_ms: u8, + ) -> Result { + self.alloc_endpoint(ep_type, max_packet_size, interval_ms, false) + } + + fn alloc_endpoint_out( + &mut self, + ep_type: EndpointType, + max_packet_size: u16, + interval_ms: u8, + ) -> Result { + self.alloc_endpoint(ep_type, max_packet_size, interval_ms, false) + } + + fn start(mut self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) { + let ep_out = self + .alloc_endpoint(EndpointType::Control, control_max_packet_size, 0, true) + .unwrap(); + let ep_in = self + .alloc_endpoint(EndpointType::Control, control_max_packet_size, 0, true) + .unwrap(); + + trace!("enabled"); + + let mut ep_confs = [EndPointConfig { + ep_type: EndpointType::Bulk, + in_max_fifo_size_btyes: 1, + out_max_fifo_size_btyes: 1, + }; EP_COUNT]; + + for i in 0..EP_COUNT { + ep_confs[i] = self.alloc[i].ep_conf; + } + + ( + Bus { + phantom: PhantomData, + ep_confs, + inited: false, + }, + ControlPipe { + _phantom: PhantomData, + max_packet_size: control_max_packet_size, + ep_out, + ep_in, + }, + ) + } +} \ No newline at end of file diff --git a/src/usb/endpoint.rs b/src/usb/endpoint.rs new file mode 100644 index 0000000..c217b27 --- /dev/null +++ b/src/usb/endpoint.rs @@ -0,0 +1,133 @@ +use super::*; + +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(super) struct EndpointData { + pub(super) ep_conf: EndPointConfig, // only valid if used_in || used_out + pub(super) used_in: bool, + pub(super) used_out: bool, +} + +/// USB endpoint. +pub struct Endpoint<'d, T: Instance, D> { + pub(super) _phantom: PhantomData<(&'d mut T, D)>, + pub(super) info: EndpointInfo, +} + +// impl<'d, T: Instance, > driver::Endpoint for Endpoint<'d, T, In> { +impl<'d, T: Instance, D: Dir> driver::Endpoint for Endpoint<'d, T, D> { + fn info(&self) -> &EndpointInfo { + &self.info + } + + async fn wait_enabled(&mut self) { + let _ = poll_fn(|cx| { + let index = self.info.addr.index(); + + let enabled = match self.info.addr.direction() { + Direction::Out => { + EP_OUT_WAKERS[index].register(cx.waker()); + EP_OUT_ENABLED.load(Ordering::Acquire) & (index as u8) != 0 + }, + Direction::In => { + EP_IN_WAKERS[index].register(cx.waker()); + EP_IN_ENABLED.load(Ordering::Acquire) & (index as u8) != 0 + } + }; + if enabled { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + trace!("Endpoint {:#X} wait enabled OK", self.info.addr); + } +} + +impl<'d, T: Instance> driver::EndpointOut for Endpoint<'d, T, Out> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + trace!("READ WAITING, buf.len() = {}", buf.len()); + let index = self.info.addr.index(); + let regs = T::regs(); + + let _ = poll_fn(|cx| { + EP_OUT_WAKERS[index].register(cx.waker()); + regs.index().write(|w| w.set_index(index as _)); + let ready = regs.out_csr1().read().out_pkt_rdy(); + + if ready { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + + regs.index().write(|w| w.set_index(index as _)); + + let read_count = regs.out_count().read().count(); + + if read_count as usize > buf.len() { + return Err(EndpointError::BufferOverflow); + } + + buf.into_iter().for_each(|b| + *b = regs.fifo(index).read().data() + ); + regs.out_csr1().modify(|w| w.set_out_pkt_rdy(false)); + trace!("READ OK, rx_len = {}", read_count); + + Ok(read_count as usize) + } +} + +impl<'d, T: Instance> driver::EndpointIn for Endpoint<'d, T, In> { + async fn write(&mut self, buf: &[u8]) -> Result<(), EndpointError> { + if buf.len() > self.info.max_packet_size as usize { + return Err(EndpointError::BufferOverflow); + } + + let index = self.info.addr.index(); + let regs = T::regs(); + + trace!("WRITE WAITING len = {}", buf.len()); + + let _ = poll_fn(|cx| { + EP_IN_WAKERS[index].register(cx.waker()); + regs.index().write(|w| w.set_index(index as _)); + + let unready = regs.in_csr1().read().in_pkt_rdy(); + + if unready { + Poll::Pending + } else { + Poll::Ready(()) + } + }) + .await; + + regs.index().write(|w| w.set_index(index as _)); + + if buf.len() == 0 { + regs.in_csr1().modify(|w| w.set_in_pkt_rdy(true)); + } else { + buf.into_iter().for_each(|b| + regs.fifo(index).write(|w| w.set_data(*b)) + ); + + regs.in_csr1().modify(|w| w.set_in_pkt_rdy(true)); + } + trace!("WRITE OK"); + + Ok(()) + } +} + +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub(super) struct EndPointConfig { + pub(super) ep_type: EndpointType, + pub(super) in_max_fifo_size_btyes: u8, + pub(super) out_max_fifo_size_btyes: u8, +} \ No newline at end of file diff --git a/src/usb/mod.rs b/src/usb/mod.rs new file mode 100644 index 0000000..8480ecd --- /dev/null +++ b/src/usb/mod.rs @@ -0,0 +1,166 @@ +/// Universal Serial Bus (USB) +/// +/// The USB peripheral IP in PY32 is a mini Mentor USB (musb), +/// featuring a fixed FIFO size and with some register functionalities masked. +/// +/// 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::future::poll_fn; +use core::marker::PhantomData; +use core::sync::atomic::{AtomicBool, AtomicU8, Ordering}; +use core::task::Poll; + +use embassy_sync::waitqueue::AtomicWaker; +use embassy_usb_driver as driver; +use embassy_usb_driver::{ + Direction, EndpointAddress, EndpointAllocError, EndpointError, EndpointInfo, EndpointType, Event, Unsupported, +}; + +use crate::pac::usb::vals::Mode; +use crate::rcc::{self, RccPeripheral}; +use crate::{interrupt, Peripheral}; +use crate::interrupt::typelevel::Interrupt; + +mod endpoint; +pub use endpoint::Endpoint; +use endpoint::{EndpointData, EndPointConfig}; + +#[path ="driver.rs"] +mod usb_driver; +pub use usb_driver::Driver; + +mod bus; +pub use bus::Bus; + +mod control_pipe; +pub use control_pipe::ControlPipe; + +#[cfg(py32f072)] +const EP_COUNT: usize = 6; +#[cfg(py32f403)] +const EP_COUNT: usize = 8; + +#[cfg(py32f072)] +const MAX_FIFO_SIZE_BTYES: [u8; EP_COUNT] = [8, 8, 16, 16, 16, 64]; + +#[cfg(py32f403)] +const MAX_FIFO_SIZE_BTYES: u8 = 8; + +const NEW_AW: AtomicWaker = AtomicWaker::new(); + +static BUS_WAKER: AtomicWaker = NEW_AW; + +static EP_IN_WAKERS: [AtomicWaker; EP_COUNT] = [NEW_AW; EP_COUNT]; +static EP_OUT_WAKERS: [AtomicWaker; EP_COUNT] = [NEW_AW; EP_COUNT]; + +static IRQ_RESET: AtomicBool = AtomicBool::new(false); +static IRQ_SUSPEND: AtomicBool = AtomicBool::new(false); +static IRQ_RESUME: AtomicBool = AtomicBool::new(false); +static EP_IN_ENABLED: AtomicU8 = AtomicU8::new(0); +static EP_OUT_ENABLED: AtomicU8 = AtomicU8::new(0); + +fn calc_max_fifo_size_btyes(len: u16) -> u8 { + let btyes = ((len + 7) / 8) as u8; + if btyes > 8 { + panic!("Invalid length: {}", len); + } + btyes +} + +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let int_usb = T::regs().int_usb().read(); + if int_usb.reset() { + IRQ_RESET.store(true, Ordering::SeqCst); + BUS_WAKER.wake(); + } + if int_usb.suspend() { + IRQ_SUSPEND.store(true, Ordering::SeqCst); + BUS_WAKER.wake(); + } + if int_usb.resume() { + IRQ_RESUME.store(true, Ordering::SeqCst); + BUS_WAKER.wake(); + } + + let int_in = T::regs().int_in1().read(); + let int_out = T::regs().int_out1().read(); + if int_in.ep0() { + EP_IN_WAKERS[0].wake(); + EP_OUT_WAKERS[0].wake(); + } + + for index in 1..EP_COUNT { + if int_in.epin(index - 1) { + EP_IN_WAKERS[index].wake(); + } + if int_out.epout(index - 1) { + EP_OUT_WAKERS[index].wake(); + } + if T::regs().in_csr1().read().underrun(){ + T::regs().in_csr1().modify(|w| w.set_underrun(false)); + warn!("Underrun: ep {}", index); + } + + } + + } + +} + +trait Dir { + fn dir() -> Direction; +} + +/// Marker type for the "IN" direction. +pub enum In {} +impl Dir for In { + fn dir() -> Direction { + Direction::In + } +} + +/// Marker type for the "OUT" direction. +pub enum Out {} +impl Dir for Out { + fn dir() -> Direction { + Direction::Out + } +} + +trait SealedInstance { + fn regs() -> crate::pac::usb::Usb; +} + +/// USB instance trait. +#[allow(private_bounds)] +pub trait Instance: SealedInstance + RccPeripheral + 'static { + /// Interrupt for this USB instance. + type Interrupt: interrupt::typelevel::Interrupt; +} + +// Internal PHY pins +pin_trait!(DpPin, Instance); +pin_trait!(DmPin, Instance); + +foreach_interrupt!( + ($inst:ident, usb, $block:ident, LP, $irq:ident) => { + impl SealedInstance for crate::peripherals::$inst { + fn regs() -> crate::pac::usb::Usb { + crate::pac::$inst + } + } + + impl Instance for crate::peripherals::$inst { + type Interrupt = crate::interrupt::typelevel::$irq; + } + }; +); \ No newline at end of file