aboutsummaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
authorChris Maniewski <[email protected]>2024-04-22 01:37:24 +0200
committerChris Maniewski <[email protected]>2024-04-27 23:14:16 +0200
commit095af927910b06f7af291f76c5236e0da8322402 (patch)
tree5911faf9cc2b4469a2ddd805acb81e2e832c2f69 /examples
parentda86c086510490602ffdd688760fb59cc7a1e524 (diff)
feature: WebUSB capability implementation
This adds the WebUSB implementation as per https://wicg.github.io/webusb/, using one in-endpoint and one out-endpoint as well as an example for the RP2040 to illustrate this capability.
Diffstat (limited to 'examples')
-rw-r--r--examples/rp/src/bin/usb_webusb.rs137
1 files changed, 137 insertions, 0 deletions
diff --git a/examples/rp/src/bin/usb_webusb.rs b/examples/rp/src/bin/usb_webusb.rs
new file mode 100644
index 000000000..09f2c1cfd
--- /dev/null
+++ b/examples/rp/src/bin/usb_webusb.rs
@@ -0,0 +1,137 @@
1//! This example shows how to use USB (Universal Serial Bus) in the RP2040 chip.
2//!
3//! This creates a WebUSB capable device that echoes data back to the host.
4//!
5//! To test this in the browser (ideally host this on localhost:8080, to test the landing page
6//! feature):
7//! ```js
8//! (async () => {
9//! const device = await navigator.usb.requestDevice({ filters: [{ vendorId: 0xf569 }] });
10//! await device.open();
11//! await device.claimInterface(1);
12//! device.transferIn(1, 64).then(data => console.log(data));
13//! await device.transferOut(1, new Uint8Array([1,2,3]));
14//! })();
15//! ```
16
17#![no_std]
18#![no_main]
19
20use defmt::info;
21use embassy_executor::Spawner;
22use embassy_futures::join::join;
23use embassy_rp::bind_interrupts;
24use embassy_rp::peripherals::USB;
25use embassy_rp::usb::{Driver as UsbDriver, InterruptHandler};
26use embassy_usb::class::web_usb::{Config as WebUsbConfig, State, Url, WebUsb};
27use embassy_usb::driver::{Driver, Endpoint, EndpointIn, EndpointOut};
28use embassy_usb::{Builder, Config};
29use {defmt_rtt as _, panic_probe as _};
30
31bind_interrupts!(struct Irqs {
32 USBCTRL_IRQ => InterruptHandler<USB>;
33});
34
35#[embassy_executor::main]
36async fn main(_spawner: Spawner) {
37 let p = embassy_rp::init(Default::default());
38
39 // Create the driver, from the HAL.
40 let driver = UsbDriver::new(p.USB, Irqs);
41
42 // Create embassy-usb Config
43 let mut config = Config::new(0xf569, 0x0001);
44 config.manufacturer = Some("Embassy");
45 config.product = Some("WebUSB example");
46 config.serial_number = Some("12345678");
47 config.max_power = 100;
48 config.max_packet_size_0 = 64;
49
50 // Required for windows compatibility.
51 // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help
52 config.device_class = 0xff;
53 config.device_sub_class = 0x00;
54 config.device_protocol = 0x00;
55
56 // Create embassy-usb DeviceBuilder using the driver and config.
57 // It needs some buffers for building the descriptors.
58 let mut config_descriptor = [0; 256];
59 let mut bos_descriptor = [0; 256];
60 let mut control_buf = [0; 64];
61
62 let webusb_config = WebUsbConfig {
63 max_packet_size: 64,
64 vendor_code: 1,
65 // If defined, shows a landing page which the device manufacturer would like the user to visit in order to control their device. Suggest the user to navigate to this URL when the device is connected.
66 landing_url: Some(Url::new("http://localhost:8080")),
67 };
68
69 let mut state = State::new();
70
71 let mut builder = Builder::new(
72 driver,
73 config,
74 &mut config_descriptor,
75 &mut bos_descriptor,
76 &mut [], // no msos descriptors
77 &mut control_buf,
78 );
79
80 // Create classes on the builder (WebUSB just needs some setup, but doesn't return anything)
81 WebUsb::configure(&mut builder, &mut state, &webusb_config);
82 // Create some USB bulk endpoints for testing.
83 let mut endpoints = WebEndpoints::new(&mut builder, &webusb_config);
84
85 // Build the builder.
86 let mut usb = builder.build();
87
88 // Run the USB device.
89 let usb_fut = usb.run();
90
91 // Do some WebUSB transfers.
92 let webusb_fut = async {
93 loop {
94 endpoints.wait_connected().await;
95 info!("Connected");
96 endpoints.echo().await;
97 }
98 };
99
100 // Run everything concurrently.
101 // If we had made everything `'static` above instead, we could do this using separate tasks instead.
102 join(usb_fut, webusb_fut).await;
103}
104
105struct WebEndpoints<'d, D: Driver<'d>> {
106 write_ep: D::EndpointIn,
107 read_ep: D::EndpointOut,
108}
109
110impl<'d, D: Driver<'d>> WebEndpoints<'d, D> {
111 fn new(builder: &mut Builder<'d, D>, config: &'d WebUsbConfig<'d>) -> Self {
112 let mut func = builder.function(0xff, 0x00, 0x00);
113 let mut iface = func.interface();
114 let mut alt = iface.alt_setting(0xff, 0x00, 0x00, None);
115
116 let write_ep = alt.endpoint_bulk_in(config.max_packet_size);
117 let read_ep = alt.endpoint_bulk_out(config.max_packet_size);
118
119 WebEndpoints { write_ep, read_ep }
120 }
121
122 // Wait until the device's endpoints are enabled.
123 async fn wait_connected(&mut self) {
124 self.read_ep.wait_enabled().await
125 }
126
127 // Echo data back to the host.
128 async fn echo(&mut self) {
129 let mut buf = [0; 64];
130 loop {
131 let n = self.read_ep.read(&mut buf).await.unwrap();
132 let data = &buf[..n];
133 info!("Data read: {:x}", data);
134 self.write_ep.write(data).await.unwrap();
135 }
136 }
137}