Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add raw USB example using control transfers #2143

Merged
merged 1 commit into from
Nov 5, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 201 additions & 0 deletions examples/stm32f4/src/bin/usb_raw.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
//! Example of using USB without a pre-defined class, but instead responding to
//! raw USB control requests.
//!
//! The host computer can either:
//! * send a command, with a 16-bit request ID, a 16-bit value, and an optional data buffer
//! * request some data, with a 16-bit request ID, a 16-bit value, and a length of data to receive
//!
//! For higher throughput data, you can add some bulk endpoints after creating the alternate,
//! but for low rate command/response, plain control transfers can be very simple and effective.
//!
//! Example code to send/receive data using `nusb`:
//!
//! ```ignore
//! use futures_lite::future::block_on;
//! use nusb::transfer::{ControlIn, ControlOut, ControlType, Recipient};
//!
//! fn main() {
//! let di = nusb::list_devices()
//! .unwrap()
//! .find(|d| d.vendor_id() == 0xc0de && d.product_id() == 0xcafe)
//! .expect("no device found");
//! let device = di.open().expect("error opening device");
//! let interface = device.claim_interface(0).expect("error claiming interface");
//!
//! // Send "hello world" to device
//! let result = block_on(interface.control_out(ControlOut {
//! control_type: ControlType::Vendor,
//! recipient: Recipient::Interface,
//! request: 100,
//! value: 200,
//! index: 0,
//! data: b"hello world",
//! }));
//! println!("{result:?}");
//!
//! // Receive "hello" from device
//! let result = block_on(interface.control_in(ControlIn {
//! control_type: ControlType::Vendor,
//! recipient: Recipient::Interface,
//! request: 101,
//! value: 201,
//! index: 0,
//! length: 5,
//! }));
//! println!("{result:?}");
//! }
//! ```
#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]

use defmt::*;
use embassy_executor::Spawner;
use embassy_stm32::time::Hertz;
use embassy_stm32::usb_otg::Driver;
use embassy_stm32::{bind_interrupts, peripherals, usb_otg, Config};
use embassy_usb::control::{InResponse, OutResponse, Recipient, Request, RequestType};
use embassy_usb::types::InterfaceNumber;
use embassy_usb::{Builder, Handler};
use {defmt_rtt as _, panic_probe as _};

bind_interrupts!(struct Irqs {
OTG_FS => usb_otg::InterruptHandler<peripherals::USB_OTG_FS>;
});

#[embassy_executor::main]
async fn main(_spawner: Spawner) {
info!("Hello World!");

let mut config = Config::default();
{
use embassy_stm32::rcc::*;
config.rcc.hse = Some(Hse {
freq: Hertz(8_000_000),
mode: HseMode::Bypass,
});
config.rcc.pll_src = PllSource::HSE;
config.rcc.pll = Some(Pll {
prediv: PllPreDiv::DIV4,
mul: PllMul::MUL168,
divp: Some(Pllp::DIV2), // 8mhz / 4 * 168 / 2 = 168Mhz.
divq: Some(Pllq::DIV7), // 8mhz / 4 * 168 / 7 = 48Mhz.
divr: None,
});
config.rcc.ahb_pre = AHBPrescaler::DIV1;
config.rcc.apb1_pre = APBPrescaler::DIV4;
config.rcc.apb2_pre = APBPrescaler::DIV2;
config.rcc.sys = Sysclk::PLL1_P;
}
let p = embassy_stm32::init(config);

// Create the driver, from the HAL.
let mut ep_out_buffer = [0u8; 256];
let mut config = embassy_stm32::usb_otg::Config::default();
config.vbus_detection = true;
let driver = Driver::new_fs(p.USB_OTG_FS, Irqs, p.PA12, p.PA11, &mut ep_out_buffer, config);

// Create embassy-usb Config
let mut config = embassy_usb::Config::new(0xc0de, 0xcafe);
config.manufacturer = Some("Embassy");
config.product = Some("USB-raw example");
config.serial_number = Some("12345678");

// 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 handler = ControlHandler {
if_num: InterfaceNumber(0),
};

let mut builder = Builder::new(
driver,
config,
&mut device_descriptor,
&mut config_descriptor,
&mut bos_descriptor,
&mut control_buf,
);

// Add a vendor-specific function (class 0xFF), and corresponding interface,
// that uses our custom handler.
let mut function = builder.function(0xFF, 0, 0);
let mut interface = function.interface();
let _alternate = interface.alt_setting(0xFF, 0, 0, None);
handler.if_num = interface.interface_number();
drop(function);
builder.handler(&mut handler);

// Build the builder.
let mut usb = builder.build();

// Run the USB device.
usb.run().await;
}

/// Handle CONTROL endpoint requests and responses. For many simple requests and responses
/// you can get away with only using the control endpoint.
struct ControlHandler {
if_num: InterfaceNumber,
}

impl Handler for ControlHandler {
/// Respond to HostToDevice control messages, where the host sends us a command and
/// optionally some data, and we can only acknowledge or reject it.
fn control_out<'a>(&'a mut self, req: Request, buf: &'a [u8]) -> Option<OutResponse> {
// Log the request before filtering to help with debugging.
info!("Got control_out, request={}, buf={:a}", req, buf);

// Only handle Vendor request types to an Interface.
if req.request_type != RequestType::Vendor || req.recipient != Recipient::Interface {
return None;
}

// Ignore requests to other interfaces.
if req.index != self.if_num.0 as u16 {
return None;
}

// Accept request 100, value 200, reject others.
if req.request == 100 && req.value == 200 {
Some(OutResponse::Accepted)
} else {
Some(OutResponse::Rejected)
}
}

/// Respond to DeviceToHost control messages, where the host requests some data from us.
fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> Option<InResponse<'a>> {
info!("Got control_in, request={}", req);

// Only handle Vendor request types to an Interface.
if req.request_type != RequestType::Vendor || req.recipient != Recipient::Interface {
return None;
}

// Ignore requests to other interfaces.
if req.index != self.if_num.0 as u16 {
return None;
}

// Respond "hello" to request 101, value 201, when asked for 5 bytes, otherwise reject.
if req.request == 101 && req.value == 201 && req.length == 5 {
buf[..5].copy_from_slice(b"hello");
Some(InResponse::Accepted(&buf[..5]))
} else {
Some(InResponse::Rejected)
}
}
}