aboutsummaryrefslogtreecommitdiff
path: root/examples/stm32f4/src
diff options
context:
space:
mode:
Diffstat (limited to 'examples/stm32f4/src')
-rw-r--r--examples/stm32f4/src/bin/usb_uac_speaker.rs375
1 files changed, 375 insertions, 0 deletions
diff --git a/examples/stm32f4/src/bin/usb_uac_speaker.rs b/examples/stm32f4/src/bin/usb_uac_speaker.rs
new file mode 100644
index 000000000..77c693ace
--- /dev/null
+++ b/examples/stm32f4/src/bin/usb_uac_speaker.rs
@@ -0,0 +1,375 @@
1#![no_std]
2#![no_main]
3
4use core::cell::RefCell;
5
6use defmt::{panic, *};
7use embassy_executor::Spawner;
8use embassy_stm32::time::Hertz;
9use embassy_stm32::{bind_interrupts, interrupt, peripherals, timer, usb, Config};
10use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex};
11use embassy_sync::blocking_mutex::Mutex;
12use embassy_sync::signal::Signal;
13use embassy_sync::zerocopy_channel;
14use embassy_usb::class::uac1;
15use embassy_usb::class::uac1::speaker::{self, Speaker};
16use embassy_usb::driver::EndpointError;
17use heapless::Vec;
18use micromath::F32Ext;
19use static_cell::StaticCell;
20use {defmt_rtt as _, panic_probe as _};
21
22bind_interrupts!(struct Irqs {
23 OTG_FS => usb::InterruptHandler<peripherals::USB_OTG_FS>;
24});
25
26static TIMER: Mutex<CriticalSectionRawMutex, RefCell<Option<timer::low_level::Timer<peripherals::TIM2>>>> =
27 Mutex::new(RefCell::new(None));
28
29// A counter signal that is written by the feedback timer, once every `FEEDBACK_REFRESH_PERIOD`.
30// At that point, a feedback value is sent to the host.
31pub static FEEDBACK_SIGNAL: Signal<CriticalSectionRawMutex, u32> = Signal::new();
32
33// Stereo input
34pub const INPUT_CHANNEL_COUNT: usize = 2;
35
36// This example uses a fixed sample rate of 48 kHz.
37pub const SAMPLE_RATE_HZ: u32 = 48_000;
38pub const FEEDBACK_COUNTER_TICK_RATE: u32 = 42_000_000;
39
40// Use 32 bit samples, which allow for a lot of (software) volume adjustment without degradation of quality.
41pub const SAMPLE_WIDTH: uac1::SampleWidth = uac1::SampleWidth::Width4Byte;
42pub const SAMPLE_WIDTH_BIT: usize = SAMPLE_WIDTH.in_bit();
43pub const SAMPLE_SIZE: usize = SAMPLE_WIDTH as usize;
44pub const SAMPLE_SIZE_PER_S: usize = (SAMPLE_RATE_HZ as usize) * INPUT_CHANNEL_COUNT * SAMPLE_SIZE;
45
46// Size of audio samples per 1 ms - for the full-speed USB frame period of 1 ms.
47pub const USB_FRAME_SIZE: usize = SAMPLE_SIZE_PER_S.div_ceil(1000);
48
49// Select front left and right audio channels.
50pub const AUDIO_CHANNELS: [uac1::Channel; INPUT_CHANNEL_COUNT] = [uac1::Channel::LeftFront, uac1::Channel::RightFront];
51
52// Factor of two as a margin for feedback (this is an excessive amount)
53pub const USB_MAX_PACKET_SIZE: usize = 2 * USB_FRAME_SIZE;
54pub const USB_MAX_SAMPLE_COUNT: usize = USB_MAX_PACKET_SIZE / SAMPLE_SIZE;
55
56// The data type that is exchanged via the zero-copy channel (a sample vector).
57pub type SampleBlock = Vec<u32, USB_MAX_SAMPLE_COUNT>;
58
59// Feedback is provided in 10.14 format for full-speed endpoints.
60pub const FEEDBACK_REFRESH_PERIOD: uac1::FeedbackRefresh = uac1::FeedbackRefresh::Period8Frames;
61const FEEDBACK_SHIFT: usize = 14;
62
63const TICKS_PER_SAMPLE: f32 = (FEEDBACK_COUNTER_TICK_RATE as f32) / (SAMPLE_RATE_HZ as f32);
64
65struct Disconnected {}
66
67impl From<EndpointError> for Disconnected {
68 fn from(val: EndpointError) -> Self {
69 match val {
70 EndpointError::BufferOverflow => panic!("Buffer overflow"),
71 EndpointError::Disabled => Disconnected {},
72 }
73 }
74}
75
76/// Sends feedback messages to the host.
77async fn feedback_handler<'d, T: usb::Instance + 'd>(
78 feedback: &mut speaker::Feedback<'d, usb::Driver<'d, T>>,
79 feedback_factor: f32,
80) -> Result<(), Disconnected> {
81 let mut packet: Vec<u8, 4> = Vec::new();
82
83 // Collects the fractional component of the feedback value that is lost by rounding.
84 let mut rest = 0.0_f32;
85
86 loop {
87 let counter = FEEDBACK_SIGNAL.wait().await;
88
89 packet.clear();
90
91 let raw_value = counter as f32 * feedback_factor + rest;
92 let value = raw_value.round();
93 rest = raw_value - value;
94
95 let value = value as u32;
96 packet.push(value as u8).unwrap();
97 packet.push((value >> 8) as u8).unwrap();
98 packet.push((value >> 16) as u8).unwrap();
99
100 feedback.write_packet(&packet).await?;
101 }
102}
103
104/// Handles streaming of audio data from the host.
105async fn stream_handler<'d, T: usb::Instance + 'd>(
106 stream: &mut speaker::Stream<'d, usb::Driver<'d, T>>,
107 sender: &mut zerocopy_channel::Sender<'static, NoopRawMutex, SampleBlock>,
108) -> Result<(), Disconnected> {
109 loop {
110 let mut usb_data = [0u8; USB_MAX_PACKET_SIZE];
111 let data_size = stream.read_packet(&mut usb_data).await?;
112
113 let word_count = data_size / SAMPLE_SIZE;
114
115 if word_count * SAMPLE_SIZE == data_size {
116 // Obtain a buffer from the channel
117 let samples = sender.send().await;
118 samples.clear();
119
120 for w in 0..word_count {
121 let byte_offset = w * SAMPLE_SIZE;
122 let sample = u32::from_le_bytes(usb_data[byte_offset..byte_offset + SAMPLE_SIZE].try_into().unwrap());
123
124 // Fill the sample buffer with data.
125 samples.push(sample).unwrap();
126 }
127
128 sender.send_done();
129 } else {
130 debug!("Invalid USB buffer size of {}, skipped.", data_size);
131 }
132 }
133}
134
135/// Receives audio samples from the USB streaming task and can play them back.
136#[embassy_executor::task]
137async fn audio_receiver_task(mut usb_audio_receiver: zerocopy_channel::Receiver<'static, NoopRawMutex, SampleBlock>) {
138 loop {
139 let _samples = usb_audio_receiver.receive().await;
140 // Use the samples, for example play back via the SAI peripheral.
141
142 // Notify the channel that the buffer is now ready to be reused
143 usb_audio_receiver.receive_done();
144 }
145}
146
147/// Receives audio samples from the host.
148#[embassy_executor::task]
149async fn usb_streaming_task(
150 mut stream: speaker::Stream<'static, usb::Driver<'static, peripherals::USB_OTG_FS>>,
151 mut sender: zerocopy_channel::Sender<'static, NoopRawMutex, SampleBlock>,
152) {
153 loop {
154 stream.wait_connection().await;
155 _ = stream_handler(&mut stream, &mut sender).await;
156 }
157}
158
159/// Sends sample rate feedback to the host.
160///
161/// The `feedback_factor` scales the feedback timer's counter value so that the result is the number of samples that
162/// this device played back or "consumed" during one SOF period (1 ms) - in 10.14 format.
163///
164/// Ideally, the `feedback_factor` that is calculated below would be an integer for avoiding numerical errors.
165/// This is achieved by having `TICKS_PER_SAMPLE` be a power of two. For audio applications at a sample rate of 48 kHz,
166/// 24.576 MHz would be one such option.
167///
168/// A good choice for the STM32F4, which also has to generate a 48 MHz clock from its HSE (e.g. running at 8 MHz)
169/// for USB, is to clock the feedback timer from the MCLK output of the SAI peripheral. The SAI peripheral then uses an
170/// external clock. In that case, wiring the MCLK output to the timer clock input is required.
171///
172/// This simple example just uses the internal clocks for supplying the feedback timer,
173/// and does not even set up a SAI peripheral.
174#[embassy_executor::task]
175async fn usb_feedback_task(mut feedback: speaker::Feedback<'static, usb::Driver<'static, peripherals::USB_OTG_FS>>) {
176 let feedback_factor =
177 ((1 << FEEDBACK_SHIFT) as f32 / TICKS_PER_SAMPLE) / FEEDBACK_REFRESH_PERIOD.frame_count() as f32;
178
179 // Should be 2.3405714285714287...
180 info!("Using a feedback factor of {}.", feedback_factor);
181
182 loop {
183 feedback.wait_connection().await;
184 _ = feedback_handler(&mut feedback, feedback_factor).await;
185 }
186}
187
188#[embassy_executor::task]
189async fn usb_task(mut usb_device: embassy_usb::UsbDevice<'static, usb::Driver<'static, peripherals::USB_OTG_FS>>) {
190 usb_device.run().await;
191}
192
193/// Checks for changes on the control monitor of the class.
194///
195/// In this case, monitor changes of volume or mute state.
196#[embassy_executor::task]
197async fn usb_control_task(control_monitor: speaker::ControlMonitor<'static>) {
198 loop {
199 control_monitor.changed().await;
200
201 for channel in AUDIO_CHANNELS {
202 let volume = control_monitor.volume(channel).unwrap();
203 info!("Volume changed to {} on channel {}.", volume, channel);
204 }
205 }
206}
207
208/// Feedback value measurement and calculation
209///
210/// Used for measuring/calculating the number of samples that were received from the host during the
211/// `FEEDBACK_REFRESH_PERIOD`.
212///
213/// Configured in this example with
214/// - a refresh period of 8 ms, and
215/// - a tick rate of 42 MHz.
216///
217/// This gives an (ideal) counter value of 336.000 for every update of the `FEEDBACK_SIGNAL`.
218#[interrupt]
219fn TIM2() {
220 static mut LAST_TICKS: u32 = 0;
221 static mut FRAME_COUNT: usize = 0;
222
223 critical_section::with(|cs| {
224 // Read timer counter.
225 let ticks = TIMER.borrow(cs).borrow().as_ref().unwrap().regs_gp32().cnt().read();
226
227 // Clear trigger interrupt flag.
228 TIMER
229 .borrow(cs)
230 .borrow_mut()
231 .as_mut()
232 .unwrap()
233 .regs_gp32()
234 .sr()
235 .modify(|r| r.set_tif(false));
236
237 // Count up frames and emit a signal, when the refresh period is reached (here, every 8 ms).
238 *FRAME_COUNT += 1;
239 if *FRAME_COUNT >= FEEDBACK_REFRESH_PERIOD.frame_count() {
240 *FRAME_COUNT = 0;
241 FEEDBACK_SIGNAL.signal(ticks.wrapping_sub(*LAST_TICKS));
242 *LAST_TICKS = ticks;
243 }
244 });
245}
246
247// If you are trying this and your USB device doesn't connect, the most
248// common issues are the RCC config and vbus_detection
249//
250// See https://embassy.dev/book/#_the_usb_examples_are_not_working_on_my_board_is_there_anything_else_i_need_to_configure
251// for more information.
252#[embassy_executor::main]
253async fn main(spawner: Spawner) {
254 info!("Hello World!");
255
256 let mut config = Config::default();
257 {
258 use embassy_stm32::rcc::*;
259 config.rcc.hse = Some(Hse {
260 freq: Hertz(8_000_000),
261 mode: HseMode::Bypass,
262 });
263 config.rcc.pll_src = PllSource::HSE;
264 config.rcc.pll = Some(Pll {
265 prediv: PllPreDiv::DIV4,
266 mul: PllMul::MUL168,
267 divp: Some(PllPDiv::DIV2), // ((8 MHz / 4) * 168) / 2 = 168 Mhz.
268 divq: Some(PllQDiv::DIV7), // ((8 MHz / 4) * 168) / 7 = 48 Mhz.
269 divr: None,
270 });
271 config.rcc.ahb_pre = AHBPrescaler::DIV1;
272 config.rcc.apb1_pre = APBPrescaler::DIV4;
273 config.rcc.apb2_pre = APBPrescaler::DIV2;
274 config.rcc.sys = Sysclk::PLL1_P;
275 config.rcc.mux.clk48sel = mux::Clk48sel::PLL1_Q;
276 }
277 let p = embassy_stm32::init(config);
278
279 // Configure all required buffers in a static way.
280 debug!("USB packet size is {} byte", USB_MAX_PACKET_SIZE);
281 static CONFIG_DESCRIPTOR: StaticCell<[u8; 256]> = StaticCell::new();
282 let config_descriptor = CONFIG_DESCRIPTOR.init([0; 256]);
283
284 static BOS_DESCRIPTOR: StaticCell<[u8; 32]> = StaticCell::new();
285 let bos_descriptor = BOS_DESCRIPTOR.init([0; 32]);
286
287 const CONTROL_BUF_SIZE: usize = 64;
288 static CONTROL_BUF: StaticCell<[u8; CONTROL_BUF_SIZE]> = StaticCell::new();
289 let control_buf = CONTROL_BUF.init([0; CONTROL_BUF_SIZE]);
290
291 const FEEDBACK_BUF_SIZE: usize = 4;
292 static EP_OUT_BUFFER: StaticCell<[u8; FEEDBACK_BUF_SIZE + CONTROL_BUF_SIZE + USB_MAX_PACKET_SIZE]> =
293 StaticCell::new();
294 let ep_out_buffer = EP_OUT_BUFFER.init([0u8; FEEDBACK_BUF_SIZE + CONTROL_BUF_SIZE + USB_MAX_PACKET_SIZE]);
295
296 static STATE: StaticCell<speaker::State> = StaticCell::new();
297 let state = STATE.init(speaker::State::new());
298
299 // Create the driver, from the HAL.
300 let mut usb_config = usb::Config::default();
301
302 // Do not enable vbus_detection. This is a safe default that works in all boards.
303 // However, if your USB device is self-powered (can stay powered on if USB is unplugged), you need
304 // to enable vbus_detection to comply with the USB spec. If you enable it, the board
305 // has to support it or USB won't work at all. See docs on `vbus_detection` for details.
306 usb_config.vbus_detection = false;
307
308 let usb_driver = usb::Driver::new_fs(p.USB_OTG_FS, Irqs, p.PA12, p.PA11, ep_out_buffer, usb_config);
309
310 // Basic USB device configuration
311 let mut config = embassy_usb::Config::new(0xc0de, 0xcafe);
312 config.manufacturer = Some("Embassy");
313 config.product = Some("USB-audio-speaker example");
314 config.serial_number = Some("12345678");
315
316 // Required for windows compatibility.
317 // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help
318 config.device_class = 0xEF;
319 config.device_sub_class = 0x02;
320 config.device_protocol = 0x01;
321 config.composite_with_iads = true;
322
323 let mut builder = embassy_usb::Builder::new(
324 usb_driver,
325 config,
326 config_descriptor,
327 bos_descriptor,
328 &mut [], // no msos descriptors
329 control_buf,
330 );
331
332 // Create the UAC1 Speaker class components
333 let (stream, feedback, control_monitor) = Speaker::new(
334 &mut builder,
335 state,
336 USB_MAX_PACKET_SIZE as u16,
337 uac1::SampleWidth::Width4Byte,
338 &[SAMPLE_RATE_HZ],
339 &AUDIO_CHANNELS,
340 FEEDBACK_REFRESH_PERIOD,
341 );
342
343 // Create the USB device
344 let usb_device = builder.build();
345
346 // Establish a zero-copy channel for transferring received audio samples between tasks
347 static SAMPLE_BLOCKS: StaticCell<[SampleBlock; 2]> = StaticCell::new();
348 let sample_blocks = SAMPLE_BLOCKS.init([Vec::new(), Vec::new()]);
349
350 static CHANNEL: StaticCell<zerocopy_channel::Channel<'_, NoopRawMutex, SampleBlock>> = StaticCell::new();
351 let channel = CHANNEL.init(zerocopy_channel::Channel::new(sample_blocks));
352 let (sender, receiver) = channel.split();
353
354 // Run a timer for counting between SOF interrupts.
355 let mut tim2 = timer::low_level::Timer::new(p.TIM2);
356 tim2.set_tick_freq(Hertz(FEEDBACK_COUNTER_TICK_RATE));
357 tim2.set_trigger_source(timer::low_level::TriggerSource::ITR1); // The USB SOF signal.
358 tim2.set_slave_mode(timer::low_level::SlaveMode::TRIGGER_MODE);
359 tim2.regs_gp16().dier().modify(|r| r.set_tie(true)); // Enable the trigger interrupt.
360 tim2.start();
361
362 TIMER.lock(|p| p.borrow_mut().replace(tim2));
363
364 // Unmask the TIM2 interrupt.
365 unsafe {
366 cortex_m::peripheral::NVIC::unmask(interrupt::TIM2);
367 }
368
369 // Launch USB audio tasks.
370 unwrap!(spawner.spawn(usb_control_task(control_monitor)));
371 unwrap!(spawner.spawn(usb_streaming_task(stream, sender)));
372 unwrap!(spawner.spawn(usb_feedback_task(feedback)));
373 unwrap!(spawner.spawn(usb_task(usb_device)));
374 unwrap!(spawner.spawn(audio_receiver_task(receiver)));
375}