aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDario Nieuwenhuis <[email protected]>2023-10-14 23:10:25 +0000
committerGitHub <[email protected]>2023-10-14 23:10:25 +0000
commit2e50bf667a02ac3cb2b48dd61ca394b589ac20df (patch)
treeb6a1a1bf71898d3854c4cdde9ce77f79b7aa2d19
parentba62037642f43308b649e26495eea8b171eeeae7 (diff)
parenta57d383b1d2872759cf1a3ab7148cfa175ea7614 (diff)
Merge pull request #2055 from kalkyl/usb-midi
embassy-usb: Add MIDI class
-rw-r--r--embassy-usb/src/class/midi.rs227
-rw-r--r--embassy-usb/src/class/mod.rs1
-rw-r--r--examples/rp/src/bin/usb_midi.rs110
3 files changed, 338 insertions, 0 deletions
diff --git a/embassy-usb/src/class/midi.rs b/embassy-usb/src/class/midi.rs
new file mode 100644
index 000000000..c5cf8d876
--- /dev/null
+++ b/embassy-usb/src/class/midi.rs
@@ -0,0 +1,227 @@
1//! MIDI class implementation.
2
3use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut};
4use crate::Builder;
5
6/// This should be used as `device_class` when building the `UsbDevice`.
7pub const USB_AUDIO_CLASS: u8 = 0x01;
8
9const USB_AUDIOCONTROL_SUBCLASS: u8 = 0x01;
10const USB_MIDISTREAMING_SUBCLASS: u8 = 0x03;
11const MIDI_IN_JACK_SUBTYPE: u8 = 0x02;
12const MIDI_OUT_JACK_SUBTYPE: u8 = 0x03;
13const EMBEDDED: u8 = 0x01;
14const EXTERNAL: u8 = 0x02;
15const CS_INTERFACE: u8 = 0x24;
16const CS_ENDPOINT: u8 = 0x25;
17const HEADER_SUBTYPE: u8 = 0x01;
18const MS_HEADER_SUBTYPE: u8 = 0x01;
19const MS_GENERAL: u8 = 0x01;
20const PROTOCOL_NONE: u8 = 0x00;
21const MIDI_IN_SIZE: u8 = 0x06;
22const MIDI_OUT_SIZE: u8 = 0x09;
23
24/// Packet level implementation of a USB MIDI device.
25///
26/// This class can be used directly and it has the least overhead due to directly reading and
27/// writing USB packets with no intermediate buffers, but it will not act like a stream-like port.
28/// The following constraints must be followed if you use this class directly:
29///
30/// - `read_packet` must be called with a buffer large enough to hold max_packet_size bytes.
31/// - `write_packet` must not be called with a buffer larger than max_packet_size bytes.
32/// - If you write a packet that is exactly max_packet_size bytes long, it won't be processed by the
33/// host operating system until a subsequent shorter packet is sent. A zero-length packet (ZLP)
34/// can be sent if there is no other data to send. This is because USB bulk transactions must be
35/// terminated with a short packet, even if the bulk endpoint is used for stream-like data.
36pub struct MidiClass<'d, D: Driver<'d>> {
37 read_ep: D::EndpointOut,
38 write_ep: D::EndpointIn,
39}
40
41impl<'d, D: Driver<'d>> MidiClass<'d, D> {
42 /// Creates a new MidiClass with the provided UsbBus, number of input and output jacks and max_packet_size in bytes.
43 /// For full-speed devices, max_packet_size has to be one of 8, 16, 32 or 64.
44 pub fn new(builder: &mut Builder<'d, D>, n_in_jacks: u8, n_out_jacks: u8, max_packet_size: u16) -> Self {
45 let mut func = builder.function(USB_AUDIO_CLASS, USB_AUDIOCONTROL_SUBCLASS, PROTOCOL_NONE);
46
47 // Audio control interface
48 let mut iface = func.interface();
49 let audio_if = iface.interface_number();
50 let midi_if = u8::from(audio_if) + 1;
51 let mut alt = iface.alt_setting(USB_AUDIO_CLASS, USB_AUDIOCONTROL_SUBCLASS, PROTOCOL_NONE, None);
52 alt.descriptor(CS_INTERFACE, &[HEADER_SUBTYPE, 0x00, 0x01, 0x09, 0x00, 0x01, midi_if]);
53
54 // MIDIStreaming interface
55 let mut iface = func.interface();
56 let _midi_if = iface.interface_number();
57 let mut alt = iface.alt_setting(USB_AUDIO_CLASS, USB_MIDISTREAMING_SUBCLASS, PROTOCOL_NONE, None);
58
59 let midi_streaming_total_length = 7
60 + (n_in_jacks + n_out_jacks) as usize * (MIDI_IN_SIZE + MIDI_OUT_SIZE) as usize
61 + 7
62 + (4 + n_out_jacks as usize)
63 + 7
64 + (4 + n_in_jacks as usize);
65
66 alt.descriptor(
67 CS_INTERFACE,
68 &[
69 MS_HEADER_SUBTYPE,
70 0x00,
71 0x01,
72 (midi_streaming_total_length & 0xFF) as u8,
73 ((midi_streaming_total_length >> 8) & 0xFF) as u8,
74 ],
75 );
76
77 // Calculates the index'th external midi in jack id
78 let in_jack_id_ext = |index| 2 * index + 1;
79 // Calculates the index'th embedded midi out jack id
80 let out_jack_id_emb = |index| 2 * index + 2;
81 // Calculates the index'th external midi out jack id
82 let out_jack_id_ext = |index| 2 * n_in_jacks + 2 * index + 1;
83 // Calculates the index'th embedded midi in jack id
84 let in_jack_id_emb = |index| 2 * n_in_jacks + 2 * index + 2;
85
86 for i in 0..n_in_jacks {
87 alt.descriptor(CS_INTERFACE, &[MIDI_IN_JACK_SUBTYPE, EXTERNAL, in_jack_id_ext(i), 0x00]);
88 }
89
90 for i in 0..n_out_jacks {
91 alt.descriptor(CS_INTERFACE, &[MIDI_IN_JACK_SUBTYPE, EMBEDDED, in_jack_id_emb(i), 0x00]);
92 }
93
94 for i in 0..n_out_jacks {
95 alt.descriptor(
96 CS_INTERFACE,
97 &[
98 MIDI_OUT_JACK_SUBTYPE,
99 EXTERNAL,
100 out_jack_id_ext(i),
101 0x01,
102 in_jack_id_emb(i),
103 0x01,
104 0x00,
105 ],
106 );
107 }
108
109 for i in 0..n_in_jacks {
110 alt.descriptor(
111 CS_INTERFACE,
112 &[
113 MIDI_OUT_JACK_SUBTYPE,
114 EMBEDDED,
115 out_jack_id_emb(i),
116 0x01,
117 in_jack_id_ext(i),
118 0x01,
119 0x00,
120 ],
121 );
122 }
123
124 let mut endpoint_data = [
125 MS_GENERAL, 0, // Number of jacks
126 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // Jack mappings
127 ];
128 endpoint_data[1] = n_out_jacks;
129 for i in 0..n_out_jacks {
130 endpoint_data[2 + i as usize] = in_jack_id_emb(i);
131 }
132 let read_ep = alt.endpoint_bulk_out(max_packet_size);
133 alt.descriptor(CS_ENDPOINT, &endpoint_data[0..2 + n_out_jacks as usize]);
134
135 endpoint_data[1] = n_in_jacks;
136 for i in 0..n_in_jacks {
137 endpoint_data[2 + i as usize] = out_jack_id_emb(i);
138 }
139 let write_ep = alt.endpoint_bulk_in(max_packet_size);
140 alt.descriptor(CS_ENDPOINT, &endpoint_data[0..2 + n_in_jacks as usize]);
141
142 MidiClass { read_ep, write_ep }
143 }
144
145 /// Gets the maximum packet size in bytes.
146 pub fn max_packet_size(&self) -> u16 {
147 // The size is the same for both endpoints.
148 self.read_ep.info().max_packet_size
149 }
150
151 /// Writes a single packet into the IN endpoint.
152 pub async fn write_packet(&mut self, data: &[u8]) -> Result<(), EndpointError> {
153 self.write_ep.write(data).await
154 }
155
156 /// Reads a single packet from the OUT endpoint.
157 pub async fn read_packet(&mut self, data: &mut [u8]) -> Result<usize, EndpointError> {
158 self.read_ep.read(data).await
159 }
160
161 /// Waits for the USB host to enable this interface
162 pub async fn wait_connection(&mut self) {
163 self.read_ep.wait_enabled().await
164 }
165
166 /// Split the class into a sender and receiver.
167 ///
168 /// This allows concurrently sending and receiving packets from separate tasks.
169 pub fn split(self) -> (Sender<'d, D>, Receiver<'d, D>) {
170 (
171 Sender {
172 write_ep: self.write_ep,
173 },
174 Receiver { read_ep: self.read_ep },
175 )
176 }
177}
178
179/// Midi class packet sender.
180///
181/// You can obtain a `Sender` with [`MidiClass::split`]
182pub struct Sender<'d, D: Driver<'d>> {
183 write_ep: D::EndpointIn,
184}
185
186impl<'d, D: Driver<'d>> Sender<'d, D> {
187 /// Gets the maximum packet size in bytes.
188 pub fn max_packet_size(&self) -> u16 {
189 // The size is the same for both endpoints.
190 self.write_ep.info().max_packet_size
191 }
192
193 /// Writes a single packet.
194 pub async fn write_packet(&mut self, data: &[u8]) -> Result<(), EndpointError> {
195 self.write_ep.write(data).await
196 }
197
198 /// Waits for the USB host to enable this interface
199 pub async fn wait_connection(&mut self) {
200 self.write_ep.wait_enabled().await
201 }
202}
203
204/// Midi class packet receiver.
205///
206/// You can obtain a `Receiver` with [`MidiClass::split`]
207pub struct Receiver<'d, D: Driver<'d>> {
208 read_ep: D::EndpointOut,
209}
210
211impl<'d, D: Driver<'d>> Receiver<'d, D> {
212 /// Gets the maximum packet size in bytes.
213 pub fn max_packet_size(&self) -> u16 {
214 // The size is the same for both endpoints.
215 self.read_ep.info().max_packet_size
216 }
217
218 /// Reads a single packet.
219 pub async fn read_packet(&mut self, data: &mut [u8]) -> Result<usize, EndpointError> {
220 self.read_ep.read(data).await
221 }
222
223 /// Waits for the USB host to enable this interface
224 pub async fn wait_connection(&mut self) {
225 self.read_ep.wait_enabled().await
226 }
227}
diff --git a/embassy-usb/src/class/mod.rs b/embassy-usb/src/class/mod.rs
index b23e03d40..452eedf17 100644
--- a/embassy-usb/src/class/mod.rs
+++ b/embassy-usb/src/class/mod.rs
@@ -2,3 +2,4 @@
2pub mod cdc_acm; 2pub mod cdc_acm;
3pub mod cdc_ncm; 3pub mod cdc_ncm;
4pub mod hid; 4pub mod hid;
5pub mod midi;
diff --git a/examples/rp/src/bin/usb_midi.rs b/examples/rp/src/bin/usb_midi.rs
new file mode 100644
index 000000000..f0b03c81b
--- /dev/null
+++ b/examples/rp/src/bin/usb_midi.rs
@@ -0,0 +1,110 @@
1//! This example shows how to use USB (Universal Serial Bus) in the RP2040 chip.
2//!
3//! This creates a USB MIDI device that echoes MIDI messages back to the host.
4
5#![no_std]
6#![no_main]
7#![feature(type_alias_impl_trait)]
8
9use defmt::{info, panic};
10use embassy_executor::Spawner;
11use embassy_futures::join::join;
12use embassy_rp::bind_interrupts;
13use embassy_rp::peripherals::USB;
14use embassy_rp::usb::{Driver, Instance, InterruptHandler};
15use embassy_usb::class::midi::MidiClass;
16use embassy_usb::driver::EndpointError;
17use embassy_usb::{Builder, Config};
18use {defmt_rtt as _, panic_probe as _};
19
20bind_interrupts!(struct Irqs {
21 USBCTRL_IRQ => InterruptHandler<USB>;
22});
23
24#[embassy_executor::main]
25async fn main(_spawner: Spawner) {
26 info!("Hello world!");
27
28 let p = embassy_rp::init(Default::default());
29
30 // Create the driver, from the HAL.
31 let driver = Driver::new(p.USB, Irqs);
32
33 // Create embassy-usb Config
34 let mut config = Config::new(0xc0de, 0xcafe);
35 config.manufacturer = Some("Embassy");
36 config.product = Some("USB-MIDI example");
37 config.serial_number = Some("12345678");
38 config.max_power = 100;
39 config.max_packet_size_0 = 64;
40
41 // Required for windows compatibility.
42 // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help
43 config.device_class = 0xEF;
44 config.device_sub_class = 0x02;
45 config.device_protocol = 0x01;
46 config.composite_with_iads = true;
47
48 // Create embassy-usb DeviceBuilder using the driver and config.
49 // It needs some buffers for building the descriptors.
50 let mut device_descriptor = [0; 256];
51 let mut config_descriptor = [0; 256];
52 let mut bos_descriptor = [0; 256];
53 let mut control_buf = [0; 64];
54
55 let mut builder = Builder::new(
56 driver,
57 config,
58 &mut device_descriptor,
59 &mut config_descriptor,
60 &mut bos_descriptor,
61 &mut control_buf,
62 );
63
64 // Create classes on the builder.
65 let mut class = MidiClass::new(&mut builder, 1, 1, 64);
66
67 // The `MidiClass` can be split into `Sender` and `Receiver`, to be used in separate tasks.
68 // let (sender, receiver) = class.split();
69
70 // Build the builder.
71 let mut usb = builder.build();
72
73 // Run the USB device.
74 let usb_fut = usb.run();
75
76 // Use the Midi class!
77 let midi_fut = async {
78 loop {
79 class.wait_connection().await;
80 info!("Connected");
81 let _ = midi_echo(&mut class).await;
82 info!("Disconnected");
83 }
84 };
85
86 // Run everything concurrently.
87 // If we had made everything `'static` above instead, we could do this using separate tasks instead.
88 join(usb_fut, midi_fut).await;
89}
90
91struct Disconnected {}
92
93impl From<EndpointError> for Disconnected {
94 fn from(val: EndpointError) -> Self {
95 match val {
96 EndpointError::BufferOverflow => panic!("Buffer overflow"),
97 EndpointError::Disabled => Disconnected {},
98 }
99 }
100}
101
102async fn midi_echo<'d, T: Instance + 'd>(class: &mut MidiClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> {
103 let mut buf = [0; 64];
104 loop {
105 let n = class.read_packet(&mut buf).await?;
106 let data = &buf[..n];
107 info!("data: {:x}", data);
108 class.write_packet(data).await?;
109 }
110}