From 36c08f039ac1aff9905f7adb465250c278215c96 Mon Sep 17 00:00:00 2001 From: djstrickland <96876452+dstric-aqueduct@users.noreply.github.com> Date: Mon, 11 Dec 2023 14:44:44 -0500 Subject: [PATCH] add USB serial example - modify library to demo USB serial on Portenta using USB_HTG_HS - add "usb" feature to resolve conflict with pin PB0, which is used in UART0 and USB - lock `embassy` dependencies to specific commit to avoid conflicts --- .cargo/config.toml | 5 ++ Cargo.toml | 25 ++++++--- examples/usb_serial.rs | 119 +++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 63 +++++++++++++++++++++- 4 files changed, 202 insertions(+), 10 deletions(-) create mode 100644 examples/usb_serial.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index a0aa49b..f95dce9 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -7,10 +7,15 @@ oe = "objcopy --example" blinky = "be blinky" blinky-probe = "ee blinky" blinky-bin = "oe blinky --release -- -O binary target/thumbv7em-none-eabihf/release/examples/blinky.bin" + serial = "be serial" serial-probe = "ee serial" serial-bin = "oe serial --release -- -O binary target/thumbv7em-none-eabihf/release/examples/serial.bin" +usb-serial = "be usb_serial --features=usb" +usb-serial-probe = "ee usb_serial --features=usb" +usb-serial-bin = "oe usb_serial --release --features=usb -- -O binary target/thumbv7em-none-eabihf/release/examples/usb_serial.bin" + [build] target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU) diff --git a/Cargo.toml b/Cargo.toml index ab07eae..bb3e3ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,13 @@ name = "portenta-h7-async" version = "0.1.0" edition = "2021" +[[example]] +name = "usb_serial" +required-features = ["usb"] + +[features] +usb = [] + [dependencies] cortex-m = { version = "0.7.6", features = [ "inline-asm", @@ -10,9 +17,7 @@ cortex-m = { version = "0.7.6", features = [ ] } cortex-m-rt = "0.7" -embassy-stm32 = { version = "0.1.0", git = "https://github.com/embassy-rs/embassy.git", features = [ - "nightly", - "unstable-traits", +embassy-stm32 = { rev = "14f41a71b6ea9dedb4ee5b9c741fe10575772c7d", git = "https://github.com/embassy-rs/embassy.git", features = [ "defmt", "stm32h747xi-cm7", "unstable-pac", @@ -22,10 +27,12 @@ embassy-stm32 = { version = "0.1.0", git = "https://github.com/embassy-rs/embass "embedded-sdmmc", "chrono", ] } -embassy-sync = { version = "0.4.0", git = "https://github.com/embassy-rs/embassy.git", features = [ + +embassy-sync = { rev = "14f41a71b6ea9dedb4ee5b9c741fe10575772c7d", git = "https://github.com/embassy-rs/embassy.git", features = [ "defmt", ] } -embassy-executor = { version = "0.3.1", git = "https://github.com/embassy-rs/embassy.git", features = [ + +embassy-executor = { rev = "14f41a71b6ea9dedb4ee5b9c741fe10575772c7d", git = "https://github.com/embassy-rs/embassy.git", features = [ "nightly", "arch-cortex-m", "executor-thread", @@ -33,15 +40,17 @@ embassy-executor = { version = "0.3.1", git = "https://github.com/embassy-rs/emb "defmt", "integrated-timers", ] } -embassy-time = { version = "0.1.2", git = "https://github.com/embassy-rs/embassy.git", features = [ + +embassy-time = { rev = "14f41a71b6ea9dedb4ee5b9c741fe10575772c7d", git = "https://github.com/embassy-rs/embassy.git", features = [ "defmt", "defmt-timestamp-uptime", - "unstable-traits", "tick-hz-32_768", ] } -embassy-usb = { version = "0.1.0", git = "https://github.com/embassy-rs/embassy.git", features = [ + +embassy-usb = { rev = "14f41a71b6ea9dedb4ee5b9c741fe10575772c7d", git = "https://github.com/embassy-rs/embassy.git", features = [ "defmt", ] } + defmt = "=0.3.2" defmt-rtt = "0.4.0" embedded-hal = "0.2.6" diff --git a/examples/usb_serial.rs b/examples/usb_serial.rs new file mode 100644 index 0000000..8f7046a --- /dev/null +++ b/examples/usb_serial.rs @@ -0,0 +1,119 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::info; +use defmt_rtt as _; +use embassy_executor::{main, task, Spawner}; +use embassy_time::Timer; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::Builder; +use futures::future::join; +use panic_probe as _; +use portenta_h7_async::led; + +use embassy_stm32::usb_otg::{Driver, Instance}; + +#[main] +async fn main(spawner: Spawner) { + info!("Init"); + + let portenta_h7_async::Board { + led_blue, + mut led_red, + usb, + .. + } = portenta_h7_async::Board::take(); + + // Create embassy-usb 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_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 device_descriptor = [0; 256]; + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut state = State::new(); + + let mut builder = Builder::new( + usb, + config, + &mut device_descriptor, + &mut config_descriptor, + &mut bos_descriptor, + &mut [], // no msos descriptors + &mut control_buf, + ); + + // Create classes on the builder. + let mut class = CdcAcmClass::new(&mut builder, &mut state, 512); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let echo_fut = async { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class, &mut led_red).await; + info!("Disconnected"); + } + }; + + spawner.spawn(blink_led_blue(led_blue)).unwrap(); + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, echo_fut).await; +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +async fn echo<'d, T: Instance + 'd>( + class: &mut CdcAcmClass<'d, Driver<'d, T>>, + led_red: &mut led::user::Red, +) -> Result<(), Disconnected> { + let mut buf = [0; 512]; + loop { + let n = class.read_packet(&mut buf).await?; + let data = &buf[..n]; + info!("data: {:x}", data); + led_red.toggle(); + class.write_packet(data).await?; + } +} + +#[task] +async fn blink_led_blue(mut led: led::user::Blue) { + loop { + led.toggle(); + Timer::after_millis(1_000).await; + } +} diff --git a/src/lib.rs b/src/lib.rs index c698c4f..027e3fc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ #![no_std] +#![feature(type_alias_impl_trait)] pub mod led; mod sys; @@ -12,21 +13,38 @@ use embassy_stm32::{ Config, }; +#[cfg(feature="usb")] +use embassy_stm32::usb_otg::{self, Driver}; + +#[cfg(not(feature="usb"))] bind_interrupts!(struct Irqs { USART1 => usart::InterruptHandler; UART4 => usart::InterruptHandler; }); +#[cfg(feature="usb")] +bind_interrupts!(struct Irqs { + USART1 => usart::InterruptHandler; + UART4 => usart::InterruptHandler; + OTG_HS => usb_otg::InterruptHandler; +}); + // Naming according to breakout board pub type Uart0 = Uart<'static, peripherals::UART4, peripherals::DMA1_CH0, peripherals::DMA1_CH1>; pub type Uart1 = Uart<'static, peripherals::USART1, peripherals::DMA1_CH2, peripherals::DMA1_CH3>; +#[cfg(feature="usb")] +pub type Usb = Driver<'static, peripherals::USB_OTG_HS>; + pub struct Board { pub led_red: Output<'static, peripherals::PK5>, pub led_green: Output<'static, peripherals::PK6>, pub led_blue: Output<'static, peripherals::PK7>, + #[cfg(not(feature="usb"))] pub uart0: Uart0, pub uart1: Uart1, + #[cfg(feature="usb")] + pub usb: Usb, } impl Board { @@ -39,7 +57,15 @@ impl Board { pub fn setup() -> Self { sys::Clk::new().reset().enable_ext_clock(); // TODO Configure 480 MHz (sys) and 240 MHz (per) - let config = Config::default(); + let mut config = Config::default(); + + { + use embassy_stm32::rcc::*; + config.rcc.hsi48 = Some(Hsi48Config { + sync_from_usb: true, + }); + } + let p = embassy_stm32::init(config); // User leds @@ -47,7 +73,7 @@ impl Board { let led_green = Output::new(p.PK6, Level::High, Speed::Low); let led_blue = Output::new(p.PK7, Level::High, Speed::Low); - // Uart0 of breakout board + #[cfg(not(feature="usb"))] let uart0 = Uart::new_with_rtscts( p.UART4, p.PI9, @@ -75,12 +101,45 @@ impl Board { ) .unwrap(); + #[cfg(feature="usb")] + let usb; + + #[cfg(feature="usb")] + { + use static_cell::make_static; + + // Create the USB driver, from the HAL. + let mut config = embassy_stm32::usb_otg::Config::default(); + config.vbus_detection = true; + usb = Driver::new_hs_ulpi( + p.USB_OTG_HS, + Irqs, + p.PA5, + p.PI11, + p.PH4, + p.PC0, + p.PA3, + p.PB0, + p.PB1, + p.PB10, + p.PB11, + p.PB12, + p.PB13, + p.PB5, + &mut make_static!([0; 1024])[..], + config, + ); + } + Self { led_red, led_green, led_blue, + #[cfg(not(feature="usb"))] uart0, uart1, + #[cfg(feature="usb")] + usb } } }