aboutsummaryrefslogtreecommitdiff
path: root/examples/stm32f4/src/bin/usb_raw.rs
diff options
context:
space:
mode:
authorAdam Greig <[email protected]>2023-11-05 15:41:31 +0000
committerAdam Greig <[email protected]>2023-11-05 16:46:45 +0000
commitfa45dcd0348379e7b9714f9b845fa3eb75671890 (patch)
tree6c45ad024d73b61cbdc7136fb3273d502229a5b4 /examples/stm32f4/src/bin/usb_raw.rs
parent6ff91851b162d311e11277dae91443868db05cfd (diff)
Add raw USB example using control transfers
Diffstat (limited to 'examples/stm32f4/src/bin/usb_raw.rs')
-rw-r--r--examples/stm32f4/src/bin/usb_raw.rs201
1 files changed, 201 insertions, 0 deletions
diff --git a/examples/stm32f4/src/bin/usb_raw.rs b/examples/stm32f4/src/bin/usb_raw.rs
new file mode 100644
index 000000000..8d4e6c7d6
--- /dev/null
+++ b/examples/stm32f4/src/bin/usb_raw.rs
@@ -0,0 +1,201 @@
1//! Example of using USB without a pre-defined class, but instead responding to
2//! raw USB control requests.
3//!
4//! The host computer can either:
5//! * send a command, with a 16-bit request ID, a 16-bit value, and an optional data buffer
6//! * request some data, with a 16-bit request ID, a 16-bit value, and a length of data to receive
7//!
8//! For higher throughput data, you can add some bulk endpoints after creating the alternate,
9//! but for low rate command/response, plain control transfers can be very simple and effective.
10//!
11//! Example code to send/receive data using `nusb`:
12//!
13//! ```ignore
14//! use futures_lite::future::block_on;
15//! use nusb::transfer::{ControlIn, ControlOut, ControlType, Recipient};
16//!
17//! fn main() {
18//! let di = nusb::list_devices()
19//! .unwrap()
20//! .find(|d| d.vendor_id() == 0xc0de && d.product_id() == 0xcafe)
21//! .expect("no device found");
22//! let device = di.open().expect("error opening device");
23//! let interface = device.claim_interface(0).expect("error claiming interface");
24//!
25//! // Send "hello world" to device
26//! let result = block_on(interface.control_out(ControlOut {
27//! control_type: ControlType::Vendor,
28//! recipient: Recipient::Interface,
29//! request: 100,
30//! value: 200,
31//! index: 0,
32//! data: b"hello world",
33//! }));
34//! println!("{result:?}");
35//!
36//! // Receive "hello" from device
37//! let result = block_on(interface.control_in(ControlIn {
38//! control_type: ControlType::Vendor,
39//! recipient: Recipient::Interface,
40//! request: 101,
41//! value: 201,
42//! index: 0,
43//! length: 5,
44//! }));
45//! println!("{result:?}");
46//! }
47//! ```
48
49#![no_std]
50#![no_main]
51#![feature(type_alias_impl_trait)]
52
53use defmt::*;
54use embassy_executor::Spawner;
55use embassy_stm32::time::Hertz;
56use embassy_stm32::usb_otg::Driver;
57use embassy_stm32::{bind_interrupts, peripherals, usb_otg, Config};
58use embassy_usb::control::{InResponse, OutResponse, Recipient, Request, RequestType};
59use embassy_usb::types::InterfaceNumber;
60use embassy_usb::{Builder, Handler};
61use {defmt_rtt as _, panic_probe as _};
62
63bind_interrupts!(struct Irqs {
64 OTG_FS => usb_otg::InterruptHandler<peripherals::USB_OTG_FS>;
65});
66
67#[embassy_executor::main]
68async fn main(_spawner: Spawner) {
69 info!("Hello World!");
70
71 let mut config = Config::default();
72 {
73 use embassy_stm32::rcc::*;
74 config.rcc.hse = Some(Hse {
75 freq: Hertz(8_000_000),
76 mode: HseMode::Bypass,
77 });
78 config.rcc.pll_src = PllSource::HSE;
79 config.rcc.pll = Some(Pll {
80 prediv: PllPreDiv::DIV4,
81 mul: PllMul::MUL168,
82 divp: Some(Pllp::DIV2), // 8mhz / 4 * 168 / 2 = 168Mhz.
83 divq: Some(Pllq::DIV7), // 8mhz / 4 * 168 / 7 = 48Mhz.
84 divr: None,
85 });
86 config.rcc.ahb_pre = AHBPrescaler::DIV1;
87 config.rcc.apb1_pre = APBPrescaler::DIV4;
88 config.rcc.apb2_pre = APBPrescaler::DIV2;
89 config.rcc.sys = Sysclk::PLL1_P;
90 }
91 let p = embassy_stm32::init(config);
92
93 // Create the driver, from the HAL.
94 let mut ep_out_buffer = [0u8; 256];
95 let mut config = embassy_stm32::usb_otg::Config::default();
96 config.vbus_detection = true;
97 let driver = Driver::new_fs(p.USB_OTG_FS, Irqs, p.PA12, p.PA11, &mut ep_out_buffer, config);
98
99 // Create embassy-usb Config
100 let mut config = embassy_usb::Config::new(0xc0de, 0xcafe);
101 config.manufacturer = Some("Embassy");
102 config.product = Some("USB-raw example");
103 config.serial_number = Some("12345678");
104
105 // Required for windows compatibility.
106 // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help
107 config.device_class = 0xEF;
108 config.device_sub_class = 0x02;
109 config.device_protocol = 0x01;
110 config.composite_with_iads = true;
111
112 // Create embassy-usb DeviceBuilder using the driver and config.
113 // It needs some buffers for building the descriptors.
114 let mut device_descriptor = [0; 256];
115 let mut config_descriptor = [0; 256];
116 let mut bos_descriptor = [0; 256];
117 let mut control_buf = [0; 64];
118
119 let mut handler = ControlHandler {
120 if_num: InterfaceNumber(0),
121 };
122
123 let mut builder = Builder::new(
124 driver,
125 config,
126 &mut device_descriptor,
127 &mut config_descriptor,
128 &mut bos_descriptor,
129 &mut control_buf,
130 );
131
132 // Add a vendor-specific function (class 0xFF), and corresponding interface,
133 // that uses our custom handler.
134 let mut function = builder.function(0xFF, 0, 0);
135 let mut interface = function.interface();
136 let _alternate = interface.alt_setting(0xFF, 0, 0, None);
137 handler.if_num = interface.interface_number();
138 drop(function);
139 builder.handler(&mut handler);
140
141 // Build the builder.
142 let mut usb = builder.build();
143
144 // Run the USB device.
145 usb.run().await;
146}
147
148/// Handle CONTROL endpoint requests and responses. For many simple requests and responses
149/// you can get away with only using the control endpoint.
150struct ControlHandler {
151 if_num: InterfaceNumber,
152}
153
154impl Handler for ControlHandler {
155 /// Respond to HostToDevice control messages, where the host sends us a command and
156 /// optionally some data, and we can only acknowledge or reject it.
157 fn control_out<'a>(&'a mut self, req: Request, buf: &'a [u8]) -> Option<OutResponse> {
158 // Log the request before filtering to help with debugging.
159 info!("Got control_out, request={}, buf={:a}", req, buf);
160
161 // Only handle Vendor request types to an Interface.
162 if req.request_type != RequestType::Vendor || req.recipient != Recipient::Interface {
163 return None;
164 }
165
166 // Ignore requests to other interfaces.
167 if req.index != self.if_num.0 as u16 {
168 return None;
169 }
170
171 // Accept request 100, value 200, reject others.
172 if req.request == 100 && req.value == 200 {
173 Some(OutResponse::Accepted)
174 } else {
175 Some(OutResponse::Rejected)
176 }
177 }
178
179 /// Respond to DeviceToHost control messages, where the host requests some data from us.
180 fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> Option<InResponse<'a>> {
181 info!("Got control_in, request={}", req);
182
183 // Only handle Vendor request types to an Interface.
184 if req.request_type != RequestType::Vendor || req.recipient != Recipient::Interface {
185 return None;
186 }
187
188 // Ignore requests to other interfaces.
189 if req.index != self.if_num.0 as u16 {
190 return None;
191 }
192
193 // Respond "hello" to request 101, value 201, when asked for 5 bytes, otherwise reject.
194 if req.request == 101 && req.value == 201 && req.length == 5 {
195 buf[..5].copy_from_slice(b"hello");
196 Some(InResponse::Accepted(&buf[..5]))
197 } else {
198 Some(InResponse::Rejected)
199 }
200 }
201}