From 34b2b455795f3ea63df924504e0ef67b36165d44 Mon Sep 17 00:00:00 2001 From: elagil Date: Thu, 5 Sep 2024 21:31:42 +0200 Subject: [PATCH 1/3] feat(usb): add USB Audio Class 1 --- embassy-usb/src/class/mod.rs | 1 + embassy-usb/src/class/uac1/class_codes.rs | 151 ++++ embassy-usb/src/class/uac1/mod.rs | 134 ++++ embassy-usb/src/class/uac1/speaker.rs | 778 ++++++++++++++++++++ embassy-usb/src/class/uac1/terminal_type.rs | 50 ++ 5 files changed, 1114 insertions(+) create mode 100644 embassy-usb/src/class/uac1/class_codes.rs create mode 100644 embassy-usb/src/class/uac1/mod.rs create mode 100644 embassy-usb/src/class/uac1/speaker.rs create mode 100644 embassy-usb/src/class/uac1/terminal_type.rs diff --git a/embassy-usb/src/class/mod.rs b/embassy-usb/src/class/mod.rs index b883ed4e55..4bd89eb668 100644 --- a/embassy-usb/src/class/mod.rs +++ b/embassy-usb/src/class/mod.rs @@ -3,4 +3,5 @@ pub mod cdc_acm; pub mod cdc_ncm; pub mod hid; pub mod midi; +pub mod uac1; pub mod web_usb; diff --git a/embassy-usb/src/class/uac1/class_codes.rs b/embassy-usb/src/class/uac1/class_codes.rs new file mode 100644 index 0000000000..3f69567718 --- /dev/null +++ b/embassy-usb/src/class/uac1/class_codes.rs @@ -0,0 +1,151 @@ +//! Audio Device Class Codes as defined in Universal Serial Bus Device Class +//! Definition for Audio Devices, Release 1.0, Appendix A and Universal Serial +//! Bus Device Class Definition for Audio Data Formats, Release 1.0, Appendix +//! A.1.1 (Audio Data Format Type I Codes) +#![allow(dead_code)] + +/// The current version of the ADC specification (1.0) +pub const ADC_VERSION: u16 = 0x0100; + +/// The current version of the USB device (1.0) +pub const DEVICE_VERSION: u16 = 0x0100; + +/// Audio Interface Class Code +pub const USB_AUDIO_CLASS: u8 = 0x01; + +// Audio Interface Subclass Codes +pub const USB_UNDEFINED_SUBCLASS: u8 = 0x00; +pub const USB_AUDIOCONTROL_SUBCLASS: u8 = 0x01; +pub const USB_AUDIOSTREAMING_SUBCLASS: u8 = 0x02; +pub const USB_MIDISTREAMING_SUBCLASS: u8 = 0x03; + +// Audio Protocol Code +pub const PROTOCOL_NONE: u8 = 0x00; + +// Audio Class-Specific Descriptor Types +pub const CS_UNDEFINED: u8 = 0x20; +pub const CS_DEVICE: u8 = 0x21; +pub const CS_CONFIGURATION: u8 = 0x22; +pub const CS_STRING: u8 = 0x23; +pub const CS_INTERFACE: u8 = 0x24; +pub const CS_ENDPOINT: u8 = 0x25; + +// Descriptor Subtype +pub const AC_DESCRIPTOR_UNDEFINED: u8 = 0x00; +pub const HEADER_SUBTYPE: u8 = 0x01; +pub const INPUT_TERMINAL: u8 = 0x02; +pub const OUTPUT_TERMINAL: u8 = 0x03; +pub const MIXER_UNIT: u8 = 0x04; +pub const SELECTOR_UNIT: u8 = 0x05; +pub const FEATURE_UNIT: u8 = 0x06; +pub const PROCESSING_UNIT: u8 = 0x07; +pub const EXTENSION_UNIT: u8 = 0x08; + +// Audio Class-Specific AS Interface Descriptor Subtypes +pub const AS_DESCRIPTOR_UNDEFINED: u8 = 0x00; +pub const AS_GENERAL: u8 = 0x01; +pub const FORMAT_TYPE: u8 = 0x02; +pub const FORMAT_SPECIFIC: u8 = 0x03; + +// Processing Unit Process Types +pub const PROCESS_UNDEFINED: u16 = 0x00; +pub const UP_DOWNMIX_PROCESS: u16 = 0x01; +pub const DOLBY_PROLOGIC_PROCESS: u16 = 0x02; +pub const DDD_STEREO_EXTENDER_PROCESS: u16 = 0x03; +pub const REVERBERATION_PROCESS: u16 = 0x04; +pub const CHORUS_PROCESS: u16 = 0x05; +pub const DYN_RANGE_COMP_PROCESS: u16 = 0x06; + +// Audio Class-Specific Endpoint Descriptor Subtypes +pub const EP_DESCRIPTOR_UNDEFINED: u8 = 0x00; +pub const EP_GENERAL: u8 = 0x01; + +// Audio Class-Specific Request Codes +pub const REQUEST_CODE_UNDEFINED: u8 = 0x00; +pub const SET_CUR: u8 = 0x01; +pub const GET_CUR: u8 = 0x81; +pub const SET_MIN: u8 = 0x02; +pub const GET_MIN: u8 = 0x82; +pub const SET_MAX: u8 = 0x03; +pub const GET_MAX: u8 = 0x83; +pub const SET_RES: u8 = 0x04; +pub const GET_RES: u8 = 0x84; +pub const SET_MEM: u8 = 0x05; +pub const GET_MEM: u8 = 0x85; +pub const GET_STAT: u8 = 0xFF; + +// Terminal Control Selectors +pub const TE_CONTROL_UNDEFINED: u8 = 0x00; +pub const COPY_PROTECT_CONTROL: u8 = 0x01; + +// Feature Unit Control Selectors +pub const FU_CONTROL_UNDEFINED: u8 = 0x00; +pub const MUTE_CONTROL: u8 = 0x01; +pub const VOLUME_CONTROL: u8 = 0x02; +pub const BASS_CONTROL: u8 = 0x03; +pub const MID_CONTROL: u8 = 0x04; +pub const TREBLE_CONTROL: u8 = 0x05; +pub const GRAPHIC_EQUALIZER_CONTROL: u8 = 0x06; +pub const AUTOMATIC_GAIN_CONTROL: u8 = 0x07; +pub const DELAY_CONTROL: u8 = 0x08; +pub const BASS_BOOST_CONTROL: u8 = 0x09; +pub const LOUDNESS_CONTROL: u8 = 0x0A; + +// Up/Down-mix Processing Unit Control Selectors +pub const UD_CONTROL_UNDEFINED: u8 = 0x00; +pub const UD_ENABLE_CONTROL: u8 = 0x01; +pub const UD_MODE_SELECT_CONTROL: u8 = 0x02; + +// Dolby Prologic Processing Unit Control Selectors +pub const DP_CONTROL_UNDEFINED: u8 = 0x00; +pub const DP_ENABLE_CONTROL: u8 = 0x01; +pub const DP_MODE_SELECT_CONTROL: u8 = 0x2; + +// 3D Stereo Extender Processing Unit Control Selectors +pub const DDD_CONTROL_UNDEFINED: u8 = 0x00; +pub const DDD_ENABLE_CONTROL: u8 = 0x01; +pub const DDD_SPACIOUSNESS_CONTROL: u8 = 0x03; + +// Reverberation Processing Unit Control Selectors +pub const RV_CONTROL_UNDEFINED: u8 = 0x00; +pub const RV_ENABLE_CONTROL: u8 = 0x01; +pub const REVERB_LEVEL_CONTROL: u8 = 0x02; +pub const REVERB_TIME_CONTROL: u8 = 0x03; +pub const REVERB_FEEDBACK_CONTROL: u8 = 0x04; + +// Chorus Processing Unit Control Selectors +pub const CH_CONTROL_UNDEFINED: u8 = 0x00; +pub const CH_ENABLE_CONTROL: u8 = 0x01; +pub const CHORUS_LEVEL_CONTROL: u8 = 0x02; +pub const CHORUS_RATE_CONTROL: u8 = 0x03; +pub const CHORUS_DEPTH_CONTROL: u8 = 0x04; + +// Dynamic Range Compressor Processing Unit Control Selectors +pub const DR_CONTROL_UNDEFINED: u8 = 0x00; +pub const DR_ENABLE_CONTROL: u8 = 0x01; +pub const COMPRESSION_RATE_CONTROL: u8 = 0x02; +pub const MAXAMPL_CONTROL: u8 = 0x03; +pub const THRESHOLD_CONTROL: u8 = 0x04; +pub const ATTACK_TIME: u8 = 0x05; +pub const RELEASE_TIME: u8 = 0x06; + +// Extension Unit Control Selectors +pub const XU_CONTROL_UNDEFINED: u16 = 0x00; +pub const XU_ENABLE_CONTROL: u16 = 0x01; + +// Endpoint Control Selectors +pub const EP_CONTROL_UNDEFINED: u8 = 0x00; +pub const SAMPLING_FREQ_CONTROL: u8 = 0x01; +pub const PITCH_CONTROL: u8 = 0x02; + +// Format Type Codes +pub const FORMAT_TYPE_UNDEFINED: u8 = 0x00; +pub const FORMAT_TYPE_I: u8 = 0x01; + +// Audio Data Format Type I Codes +pub const TYPE_I_UNDEFINED: u16 = 0x0000; +pub const PCM: u16 = 0x0001; +pub const PCM8: u16 = 0x0002; +pub const IEEE_FLOAT: u16 = 0x0003; +pub const ALAW: u16 = 0x0004; +pub const MULAW: u16 = 0x0005; diff --git a/embassy-usb/src/class/uac1/mod.rs b/embassy-usb/src/class/uac1/mod.rs new file mode 100644 index 0000000000..3d5f4e524f --- /dev/null +++ b/embassy-usb/src/class/uac1/mod.rs @@ -0,0 +1,134 @@ +//! USB Audio Class 1.0 implementations for different applications. +//! +//! Contains: +//! - The `speaker` class with a single audio streaming interface (host to device) + +pub mod speaker; + +mod class_codes; +mod terminal_type; + +/// The maximum supported audio channel index (corresponds to `Top`). +/// FIXME: Use `core::mem::variant_count(...)` when stabilized. +const MAX_AUDIO_CHANNEL_INDEX: usize = 12; + +/// The maximum number of supported audio channels. +/// +/// Includes all twelve channels from `Channel`, plus the Master channel. +const MAX_AUDIO_CHANNEL_COUNT: usize = MAX_AUDIO_CHANNEL_INDEX + 1; + +/// USB Audio Channel +#[derive(Debug, Clone, Copy, PartialEq)] +#[allow(missing_docs)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Channel { + LeftFront, + RightFront, + CenterFront, + Lfe, + LeftSurround, + RightSurround, + LeftOfCenter, + RightOfCenter, + Surround, + SideLeft, + SideRight, + Top, +} + +impl Channel { + /// Map a `Channel` to its corresponding USB Audio `ChannelConfig`. + fn get_channel_config(&self) -> ChannelConfig { + match self { + Channel::LeftFront => ChannelConfig::LeftFront, + Channel::RightFront => ChannelConfig::RightFront, + Channel::CenterFront => ChannelConfig::CenterFront, + Channel::Lfe => ChannelConfig::Lfe, + Channel::LeftSurround => ChannelConfig::LeftSurround, + Channel::RightSurround => ChannelConfig::RightSurround, + Channel::LeftOfCenter => ChannelConfig::LeftOfCenter, + Channel::RightOfCenter => ChannelConfig::RightOfCenter, + Channel::Surround => ChannelConfig::Surround, + Channel::SideLeft => ChannelConfig::SideLeft, + Channel::SideRight => ChannelConfig::SideRight, + Channel::Top => ChannelConfig::Top, + } + } +} + +/// USB Audio Channel configuration +#[repr(u16)] +#[non_exhaustive] +// #[derive(Copy, Clone, Eq, PartialEq, Debug)] +enum ChannelConfig { + None = 0x0000, + LeftFront = 0x0001, + RightFront = 0x0002, + CenterFront = 0x0004, + Lfe = 0x0008, + LeftSurround = 0x0010, + RightSurround = 0x0020, + LeftOfCenter = 0x0040, + RightOfCenter = 0x0080, + Surround = 0x0100, + SideLeft = 0x0200, + SideRight = 0x0400, + Top = 0x0800, +} + +impl From for u16 { + fn from(t: ChannelConfig) -> u16 { + t as u16 + } +} + +/// Feedback period adjustment `bRefresh` [UAC 3.7.2.2] +/// +/// From the specification: "A new Ff value is available every 2^(10 – P) frames with P ranging from 1 to 9. The +/// bRefresh field of the synch standard endpoint descriptor is used to report the exponent (10-P) to the Host." +/// +/// This means: +/// - 512 ms (2^9 frames) to 2 ms (2^1 frames) for USB full-speed +/// - 64 ms (2^9 microframes) to 0.25 ms (2^1 microframes) for USB high-speed +#[repr(u8)] +#[allow(missing_docs)] +#[derive(Clone, Copy)] +pub enum FeedbackRefresh { + Period2Frames = 1, + Period4Frames = 2, + Period8Frames = 3, + Period16Frames = 4, + Period32Frames = 5, + Period64Frames = 6, + Period128Frames = 7, + Period256Frames = 8, + Period512Frames = 9, +} + +impl FeedbackRefresh { + /// Gets the number of frames, after which a new feedback frame is returned. + pub const fn frame_count(&self) -> usize { + 1 << (*self as usize) + } +} + +/// Audio sample width. +/// +/// Stored in number of bytes per sample. +#[repr(u8)] +#[derive(Clone, Copy)] +pub enum SampleWidth { + /// 16 bit audio + Width2Byte = 2, + /// 24 bit audio + Width3Byte = 3, + /// 32 bit audio + Width4Byte = 4, +} + +impl SampleWidth { + /// Get the audio sample resolution in number of bit. + pub const fn in_bit(self) -> usize { + 8 * self as usize + } +} diff --git a/embassy-usb/src/class/uac1/speaker.rs b/embassy-usb/src/class/uac1/speaker.rs new file mode 100644 index 0000000000..96456d94a8 --- /dev/null +++ b/embassy-usb/src/class/uac1/speaker.rs @@ -0,0 +1,778 @@ +//! USB Audio Class 1.0 - Speaker device +//! +//! Provides a class with a single audio streaming interface (host to device), +//! that advertises itself as a speaker. Includes explicit sample rate feedback. +//! +//! Various aspects of the audio stream can be configured, for example: +//! - sample rate +//! - sample resolution +//! - audio channel count and assignment +//! +//! The class provides volume and mute controls for each channel. + +use core::cell::{Cell, RefCell}; +use core::future::poll_fn; +use core::marker::PhantomData; +use core::sync::atomic::{AtomicBool, AtomicU32, Ordering}; +use core::task::Poll; + +use embassy_sync::blocking_mutex::CriticalSectionMutex; +use embassy_sync::waitqueue::WakerRegistration; +use heapless::Vec; + +use super::class_codes::*; +use super::terminal_type::TerminalType; +use super::{Channel, ChannelConfig, FeedbackRefresh, SampleWidth, MAX_AUDIO_CHANNEL_COUNT, MAX_AUDIO_CHANNEL_INDEX}; +use crate::control::{self, InResponse, OutResponse, Recipient, Request, RequestType}; +use crate::descriptor::{SynchronizationType, UsageType}; +use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut, EndpointType}; +use crate::types::InterfaceNumber; +use crate::{Builder, Handler}; + +/// Maximum allowed sampling rate (3 bytes) in Hz. +const MAX_SAMPLE_RATE_HZ: u32 = 0x7FFFFF; + +/// Arbitrary unique identifier for the input unit. +const INPUT_UNIT_ID: u8 = 0x01; + +/// Arbitrary unique identifier for the feature unit. +const FEATURE_UNIT_ID: u8 = 0x02; + +/// Arbitrary unique identifier for the output unit. +const OUTPUT_UNIT_ID: u8 = 0x03; + +// Volume settings go from -25600 to 0, in steps of 256. +// Therefore, the volume settings are 8q8 values in units of dB. +const VOLUME_STEPS_PER_DB: i16 = 256; +const MIN_VOLUME_DB: i16 = -100; +const MAX_VOLUME_DB: i16 = 0; + +// Maximum number of supported discrete sample rates. +const MAX_SAMPLE_RATE_COUNT: usize = 10; + +/// The volume of an audio channel. +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Volume { + /// The channel is muted. + Muted, + /// The channel volume in dB. Ranges from `MIN_VOLUME_DB` (quietest) to `MAX_VOLUME_DB` (loudest). + DeciBel(f32), +} + +/// Internal state for the USB Audio Class. +pub struct State<'d> { + control: Option>, + shared: SharedControl<'d>, +} + +impl<'d> Default for State<'d> { + fn default() -> Self { + Self::new() + } +} + +impl<'d> State<'d> { + /// Create a new `State`. + pub fn new() -> Self { + Self { + control: None, + shared: SharedControl::default(), + } + } +} + +/// Implementation of the USB audio class 1.0. +pub struct Speaker<'d, D: Driver<'d>> { + phantom: PhantomData<&'d D>, +} + +impl<'d, D: Driver<'d>> Speaker<'d, D> { + /// Creates a new [`Speaker`] device, split into a stream, feedback, and a control change notifier. + /// + /// The packet size should be chosen, based on the expected transfer size of samples per (micro)frame. + /// For example, a stereo stream at 32 bit resolution and 48 kHz sample rate yields packets of 384 byte for + /// full-speed USB (1 ms frame interval) or 48 byte for high-speed USB (125 us microframe interval). + /// When using feedback, the packet size varies and thus, the `max_packet_size` should be increased (e.g. to double). + /// + /// # Arguments + /// + /// * `builder` - The builder for the class. + /// * `state` - The internal state of the class. + /// * `max_packet_size` - The maximum packet size per (micro)frame. + /// * `resolution` - The audio sample resolution. + /// * `sample_rates_hz` - The supported sample rates in Hz. + /// * `channels` - The advertised audio channels (up to 12). Entries must be unique, or this function panics. + /// * `feedback_refresh_period` - The refresh period for the feedback value. + pub fn new( + builder: &mut Builder<'d, D>, + state: &'d mut State<'d>, + max_packet_size: u16, + resolution: SampleWidth, + sample_rates_hz: &[u32], + channels: &'d [Channel], + feedback_refresh_period: FeedbackRefresh, + ) -> (Stream<'d, D>, Feedback<'d, D>, ControlMonitor<'d>) { + // The class and subclass fields of the IAD aren't required to match the class and subclass fields of + // the interfaces in the interface collection that the IAD describes. Microsoft recommends that + // the first interface of the collection has class and subclass fields that match the class and + // subclass fields of the IAD. + let mut func = builder.function(USB_AUDIO_CLASS, USB_AUDIOCONTROL_SUBCLASS, PROTOCOL_NONE); + + // Audio control interface (mandatory) [UAC 4.3.1] + let mut interface = func.interface(); + let control_interface = interface.interface_number().into(); + let streaming_interface = u8::from(control_interface) + 1; + let mut alt = interface.alt_setting(USB_AUDIO_CLASS, USB_AUDIOCONTROL_SUBCLASS, PROTOCOL_NONE, None); + + // Terminal topology: + // Input terminal (receives audio stream) -> Feature Unit (mute and volume) -> Output terminal (e.g. towards speaker) + + // ======================================= + // Input Terminal Descriptor [UAC 3.3.2.1] + // Audio input + let terminal_type: u16 = TerminalType::UsbStreaming.into(); + + // Assemble channel configuration field + let mut channel_config: u16 = ChannelConfig::None.into(); + for channel in channels { + let channel: u16 = channel.get_channel_config().into(); + + if channel_config & channel != 0 { + panic!("Invalid channel config, duplicate channel {}.", channel); + } + channel_config |= channel; + } + + let input_terminal_descriptor = [ + INPUT_TERMINAL, // bDescriptorSubtype + INPUT_UNIT_ID, // bTerminalID + terminal_type as u8, + (terminal_type >> 8) as u8, // wTerminalType + 0x00, // bAssocTerminal (none) + channels.len() as u8, // bNrChannels + channel_config as u8, + (channel_config >> 8) as u8, // wChannelConfig + 0x00, // iChannelNames (none) + 0x00, // iTerminal (none) + ]; + + // ======================================== + // Output Terminal Descriptor [UAC 4.3.2.2] + // Speaker output + let terminal_type: u16 = TerminalType::OutSpeaker.into(); + let output_terminal_descriptor = [ + OUTPUT_TERMINAL, // bDescriptorSubtype + OUTPUT_UNIT_ID, // bTerminalID + terminal_type as u8, + (terminal_type >> 8) as u8, // wTerminalType + 0x00, // bAssocTerminal (none) + FEATURE_UNIT_ID, // bSourceID (the feature unit) + 0x00, // iTerminal (none) + ]; + + // ===================================== + // Feature Unit Descriptor [UAC 4.3.2.5] + // Mute and volume control + let controls = MUTE_CONTROL | VOLUME_CONTROL; + + const FEATURE_UNIT_DESCRIPTOR_SIZE: usize = 5; + let mut feature_unit_descriptor: Vec = + Vec::from_slice(&[ + FEATURE_UNIT, // bDescriptorSubtype (Feature Unit) + FEATURE_UNIT_ID, // bUnitID + INPUT_UNIT_ID, // bSourceID + 1, // bControlSize (one byte per control) + FU_CONTROL_UNDEFINED, // Master controls (disabled, use only per-channel control) + ]) + .unwrap(); + + // Add per-channel controls + for _channel in channels { + feature_unit_descriptor.push(controls).unwrap(); + } + feature_unit_descriptor.push(0x00).unwrap(); // iFeature (none) + + // =============================================== + // Format desciptor [UAC 4.5.3] + // Used later, for operational streaming interface + let mut format_descriptor: Vec = Vec::from_slice(&[ + FORMAT_TYPE, // bDescriptorSubtype + FORMAT_TYPE_I, // bFormatType + channels.len() as u8, // bNrChannels + resolution as u8, // bSubframeSize + resolution.in_bit() as u8, // bBitResolution + ]) + .unwrap(); + + format_descriptor.push(sample_rates_hz.len() as u8).unwrap(); + + for sample_rate_hz in sample_rates_hz { + assert!(*sample_rate_hz <= MAX_SAMPLE_RATE_HZ); + format_descriptor.push((sample_rate_hz & 0xFF) as u8).unwrap(); + format_descriptor.push(((sample_rate_hz >> 8) & 0xFF) as u8).unwrap(); + format_descriptor.push(((sample_rate_hz >> 16) & 0xFF) as u8).unwrap(); + } + + // ================================================== + // Class-specific AC Interface Descriptor [UAC 4.3.2] + const DESCRIPTOR_HEADER_SIZE: usize = 2; + const INTERFACE_DESCRIPTOR_SIZE: usize = 7; + + let mut total_descriptor_length = 0; + + for size in [ + INTERFACE_DESCRIPTOR_SIZE, + input_terminal_descriptor.len(), + feature_unit_descriptor.len(), + output_terminal_descriptor.len(), + ] { + total_descriptor_length += size + DESCRIPTOR_HEADER_SIZE; + } + + let interface_descriptor: [u8; INTERFACE_DESCRIPTOR_SIZE] = [ + HEADER_SUBTYPE, // bDescriptorSubtype (Header) + ADC_VERSION as u8, + (ADC_VERSION >> 8) as u8, // bcdADC + total_descriptor_length as u8, + (total_descriptor_length >> 8) as u8, // wTotalLength + 0x01, // bInCollection (1 streaming interface) + streaming_interface, // baInterfaceNr + ]; + + alt.descriptor(CS_INTERFACE, &interface_descriptor); + alt.descriptor(CS_INTERFACE, &input_terminal_descriptor); + alt.descriptor(CS_INTERFACE, &feature_unit_descriptor); + alt.descriptor(CS_INTERFACE, &output_terminal_descriptor); + + // ===================================================== + // Audio streaming interface, zero-bandwidth [UAC 4.5.1] + let mut interface = func.interface(); + let alt = interface.alt_setting(USB_AUDIO_CLASS, USB_AUDIOSTREAMING_SUBCLASS, PROTOCOL_NONE, None); + drop(alt); + + // ================================================== + // Audio streaming interface, operational [UAC 4.5.1] + let mut alt = interface.alt_setting(USB_AUDIO_CLASS, USB_AUDIOSTREAMING_SUBCLASS, PROTOCOL_NONE, None); + + alt.descriptor( + CS_INTERFACE, + &[ + AS_GENERAL, // bDescriptorSubtype + INPUT_UNIT_ID, // bTerminalLink + 0x00, // bDelay (none) + PCM as u8, + (PCM >> 8) as u8, // wFormatTag (PCM format) + ], + ); + + alt.descriptor(CS_INTERFACE, &format_descriptor); + + let streaming_endpoint = alt.alloc_endpoint_out(EndpointType::Isochronous, max_packet_size, 1); + let feedback_endpoint = alt.alloc_endpoint_in( + EndpointType::Isochronous, + 4, // Feedback packets are 24 bit (10.14 format). + 1, + ); + + // Write the descriptor for the streaming endpoint, after knowing the address of the feedback endpoint. + alt.endpoint_descriptor( + streaming_endpoint.info(), + SynchronizationType::Asynchronous, + UsageType::DataEndpoint, + &[ + 0x00, // bRefresh (0) + feedback_endpoint.info().addr.into(), // bSynchAddress (the feedback endpoint) + ], + ); + + alt.descriptor( + CS_ENDPOINT, + &[ + AS_GENERAL, // bDescriptorSubtype (General) + SAMPLING_FREQ_CONTROL, // bmAttributes (support sampling frequency control) + 0x02, // bLockDelayUnits (PCM) + 0x0000 as u8, + (0x0000 >> 8) as u8, // wLockDelay (0) + ], + ); + + // Write the feedback endpoint descriptor after the streaming endpoint descriptor + // This is demanded by the USB audio class specification. + alt.endpoint_descriptor( + feedback_endpoint.info(), + SynchronizationType::NoSynchronization, + UsageType::FeedbackEndpoint, + &[ + feedback_refresh_period as u8, // bRefresh + 0x00, // bSynchAddress (none) + ], + ); + + // Free up the builder. + drop(func); + + // Store channel information + state.shared.channels = channels; + + state.control = Some(Control { + shared: &state.shared, + streaming_endpoint_address: streaming_endpoint.info().addr.into(), + control_interface_number: control_interface, + }); + + builder.handler(state.control.as_mut().unwrap()); + + let control = &state.shared; + + ( + Stream { streaming_endpoint }, + Feedback { feedback_endpoint }, + ControlMonitor { shared: control }, + ) + } +} + +/// Audio settings for the feature unit. +/// +/// Contains volume and mute control. +#[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct AudioSettings { + /// Channel mute states. + muted: [bool; MAX_AUDIO_CHANNEL_COUNT], + /// Channel volume levels in 8.8 format (in dB). + volume_8q8_db: [i16; MAX_AUDIO_CHANNEL_COUNT], +} + +impl Default for AudioSettings { + fn default() -> Self { + AudioSettings { + muted: [true; MAX_AUDIO_CHANNEL_COUNT], + volume_8q8_db: [MAX_VOLUME_DB * VOLUME_STEPS_PER_DB; MAX_AUDIO_CHANNEL_COUNT], + } + } +} + +struct Control<'d> { + control_interface_number: InterfaceNumber, + streaming_endpoint_address: u8, + shared: &'d SharedControl<'d>, +} + +/// Shared data between [`Control`] and the [`Speaker`] class. +struct SharedControl<'d> { + /// The collection of audio settings (volumes, mute states). + audio_settings: CriticalSectionMutex>, + + /// Channel assignments. + channels: &'d [Channel], + + /// The audio sample rate in Hz. + sample_rate_hz: AtomicU32, + + // Notification mechanism. + waker: RefCell, + changed: AtomicBool, +} + +impl<'d> Default for SharedControl<'d> { + fn default() -> Self { + SharedControl { + audio_settings: CriticalSectionMutex::new(Cell::new(AudioSettings::default())), + channels: &[], + sample_rate_hz: AtomicU32::new(0), + waker: RefCell::new(WakerRegistration::new()), + changed: AtomicBool::new(false), + } + } +} + +impl<'d> SharedControl<'d> { + async fn changed(&self) { + poll_fn(|context| { + if self.changed.load(Ordering::Relaxed) { + self.changed.store(false, Ordering::Relaxed); + Poll::Ready(()) + } else { + self.waker.borrow_mut().register(context.waker()); + Poll::Pending + } + }) + .await; + } +} + +/// Used for reading audio frames. +pub struct Stream<'d, D: Driver<'d>> { + streaming_endpoint: D::EndpointOut, +} + +impl<'d, D: Driver<'d>> Stream<'d, D> { + /// Reads a single packet from the OUT endpoint + pub async fn read_packet(&mut self, data: &mut [u8]) -> Result { + self.streaming_endpoint.read(data).await + } + + /// Waits for the USB host to enable this interface + pub async fn wait_connection(&mut self) { + self.streaming_endpoint.wait_enabled().await; + } +} + +/// Used for writing sample rate information over the feedback endpoint. +pub struct Feedback<'d, D: Driver<'d>> { + feedback_endpoint: D::EndpointIn, +} + +impl<'d, D: Driver<'d>> Feedback<'d, D> { + /// Writes a single packet into the IN endpoint. + pub async fn write_packet(&mut self, data: &[u8]) -> Result<(), EndpointError> { + self.feedback_endpoint.write(data).await + } + + /// Waits for the USB host to enable this interface. + pub async fn wait_connection(&mut self) { + self.feedback_endpoint.wait_enabled().await; + } +} + +/// Control status change monitor +/// +/// Await [`ControlMonitor::changed`] for being notified of configuration changes. Afterwards, the updated +/// configuration settings can be read with [`ControlMonitor::volume`] and [`ControlMonitor::sample_rate_hz`]. +pub struct ControlMonitor<'d> { + shared: &'d SharedControl<'d>, +} + +impl<'d> ControlMonitor<'d> { + fn audio_settings(&self) -> AudioSettings { + let audio_settings = self.shared.audio_settings.lock(|x| x.get()); + + audio_settings + } + + fn get_logical_channel(&self, search_channel: Channel) -> Option { + let index = self.shared.channels.iter().position(|&c| c == search_channel)?; + + // The logical channels start at one (zero is the master channel). + Some(index + 1) + } + + /// Get the volume of a selected channel. + pub fn volume(&self, channel: Channel) -> Option { + let channel_index = self.get_logical_channel(channel)?; + + if self.audio_settings().muted[channel_index] { + return Some(Volume::Muted); + } + + Some(Volume::DeciBel( + (self.audio_settings().volume_8q8_db[channel_index] as f32) / 256.0f32, + )) + } + + /// Get the streaming endpoint's sample rate in Hz. + pub fn sample_rate_hz(&self) -> u32 { + self.shared.sample_rate_hz.load(Ordering::Relaxed) + } + + /// Return a future for when the control settings change. + pub async fn changed(&self) { + self.shared.changed().await; + } +} + +impl<'d> Control<'d> { + fn changed(&mut self) { + self.shared.changed.store(true, Ordering::Relaxed); + self.shared.waker.borrow_mut().wake(); + } + + fn interface_set_mute_state( + &mut self, + audio_settings: &mut AudioSettings, + channel_index: u8, + data: &[u8], + ) -> OutResponse { + let mute_state = data[0] != 0; + + match channel_index as usize { + ..=MAX_AUDIO_CHANNEL_INDEX => { + audio_settings.muted[channel_index as usize] = mute_state; + } + _ => { + debug!("Failed to set channel {} mute state: {}", channel_index, mute_state); + return OutResponse::Rejected; + } + } + + debug!("Set channel {} mute state: {}", channel_index, mute_state); + OutResponse::Accepted + } + + fn interface_set_volume( + &mut self, + audio_settings: &mut AudioSettings, + channel_index: u8, + data: &[u8], + ) -> OutResponse { + let volume = i16::from_ne_bytes(data[..2].try_into().expect("Failed to read volume.")); + + match channel_index as usize { + ..=MAX_AUDIO_CHANNEL_INDEX => { + audio_settings.volume_8q8_db[channel_index as usize] = volume; + } + _ => { + debug!("Failed to set channel {} volume: {}", channel_index, volume); + return OutResponse::Rejected; + } + } + + debug!("Set channel {} volume: {}", channel_index, volume); + OutResponse::Accepted + } + + fn interface_set_request(&mut self, req: control::Request, data: &[u8]) -> Option { + let interface_number = req.index as u8; + let entity_index = (req.index >> 8) as u8; + let channel_index = req.value as u8; + let control_unit = (req.value >> 8) as u8; + + if interface_number != self.control_interface_number.into() { + debug!("Unhandled interface set request for interface {}", interface_number); + return None; + } + + if entity_index != FEATURE_UNIT_ID { + debug!("Unsupported interface set request for entity {}", entity_index); + return Some(OutResponse::Rejected); + } + + if req.request != SET_CUR { + debug!("Unsupported interface set request type {}", req.request); + return Some(OutResponse::Rejected); + } + + let mut audio_settings = self.shared.audio_settings.lock(|x| x.get()); + let response = match control_unit { + MUTE_CONTROL => self.interface_set_mute_state(&mut audio_settings, channel_index, data), + VOLUME_CONTROL => self.interface_set_volume(&mut audio_settings, channel_index, data), + _ => OutResponse::Rejected, + }; + + if response == OutResponse::Rejected { + return Some(response); + } + + // Store updated settings + self.shared.audio_settings.lock(|x| x.set(audio_settings)); + + self.changed(); + + Some(OutResponse::Accepted) + } + + fn endpoint_set_request(&mut self, req: control::Request, data: &[u8]) -> Option { + let control_selector = (req.value >> 8) as u8; + let endpoint_address = req.index as u8; + + if endpoint_address != self.streaming_endpoint_address { + debug!( + "Unhandled endpoint set request for endpoint {} and control {} with data {}", + endpoint_address, control_selector, data + ); + return None; + } + + if control_selector != SAMPLING_FREQ_CONTROL { + debug!( + "Unsupported endpoint set request for control selector {}", + control_selector + ); + return Some(OutResponse::Rejected); + } + + let sample_rate_hz: u32 = (data[0] as u32) | (data[1] as u32) << 8 | (data[2] as u32) << 16; + self.shared.sample_rate_hz.store(sample_rate_hz, Ordering::Relaxed); + + debug!("Set endpoint {} sample rate to {} Hz", endpoint_address, sample_rate_hz); + + self.changed(); + + Some(OutResponse::Accepted) + } + + fn interface_get_request<'r>(&'r mut self, req: Request, buf: &'r mut [u8]) -> Option> { + let interface_number = req.index as u8; + let entity_index = (req.index >> 8) as u8; + let channel_index = req.value as u8; + let control_unit = (req.value >> 8) as u8; + + if interface_number != self.control_interface_number.into() { + debug!("Unhandled interface get request for interface {}.", interface_number); + return None; + } + + if entity_index != FEATURE_UNIT_ID { + // Only this function unit can be handled at the moment. + debug!("Unsupported interface get request for entity {}.", entity_index); + return Some(InResponse::Rejected); + } + + let audio_settings = self.shared.audio_settings.lock(|x| x.get()); + + match req.request { + GET_CUR => match control_unit { + VOLUME_CONTROL => { + let volume: i16; + + match channel_index as usize { + ..=MAX_AUDIO_CHANNEL_INDEX => volume = audio_settings.volume_8q8_db[channel_index as usize], + _ => return Some(InResponse::Rejected), + } + + buf[0] = volume as u8; + buf[1] = (volume >> 8) as u8; + + debug!("Got channel {} volume: {}.", channel_index, volume); + return Some(InResponse::Accepted(&buf[..2])); + } + MUTE_CONTROL => { + let mute_state: bool; + + match channel_index as usize { + ..=MAX_AUDIO_CHANNEL_INDEX => mute_state = audio_settings.muted[channel_index as usize], + _ => return Some(InResponse::Rejected), + } + + buf[0] = mute_state.into(); + debug!("Got channel {} mute state: {}.", channel_index, mute_state); + return Some(InResponse::Accepted(&buf[..1])); + } + _ => return Some(InResponse::Rejected), + }, + GET_MIN => match control_unit { + VOLUME_CONTROL => { + let min_volume = MIN_VOLUME_DB * VOLUME_STEPS_PER_DB; + buf[0] = min_volume as u8; + buf[1] = (min_volume >> 8) as u8; + return Some(InResponse::Accepted(&buf[..2])); + } + _ => return Some(InResponse::Rejected), + }, + GET_MAX => match control_unit { + VOLUME_CONTROL => { + let max_volume = MAX_VOLUME_DB * VOLUME_STEPS_PER_DB; + buf[0] = max_volume as u8; + buf[1] = (max_volume >> 8) as u8; + return Some(InResponse::Accepted(&buf[..2])); + } + _ => return Some(InResponse::Rejected), + }, + GET_RES => match control_unit { + VOLUME_CONTROL => { + buf[0] = VOLUME_STEPS_PER_DB as u8; + buf[1] = (VOLUME_STEPS_PER_DB >> 8) as u8; + return Some(InResponse::Accepted(&buf[..2])); + } + _ => return Some(InResponse::Rejected), + }, + _ => return Some(InResponse::Rejected), + } + } + + fn endpoint_get_request<'r>(&'r mut self, req: Request, buf: &'r mut [u8]) -> Option> { + let control_selector = (req.value >> 8) as u8; + let endpoint_address = req.index as u8; + + if endpoint_address != self.streaming_endpoint_address { + debug!("Unhandled endpoint get request for endpoint {}.", endpoint_address); + return None; + } + + if control_selector != SAMPLING_FREQ_CONTROL as u8 { + debug!( + "Unsupported endpoint get request for control selector {}.", + control_selector + ); + return Some(InResponse::Rejected); + } + + let sample_rate_hz = self.shared.sample_rate_hz.load(Ordering::Relaxed); + + buf[0] = (sample_rate_hz & 0xFF) as u8; + buf[1] = ((sample_rate_hz >> 8) & 0xFF) as u8; + buf[2] = ((sample_rate_hz >> 16) & 0xFF) as u8; + + Some(InResponse::Accepted(&buf[..3])) + } +} + +impl<'d> Handler for Control<'d> { + /// Called when the USB device has been enabled or disabled. + fn enabled(&mut self, enabled: bool) { + debug!("USB device enabled: {}", enabled); + } + + /// Called when the host has set the address of the device to `addr`. + fn addressed(&mut self, addr: u8) { + debug!("Host set address to: {}", addr); + } + + /// Called when the host has enabled or disabled the configuration of the device. + fn configured(&mut self, configured: bool) { + debug!("USB device configured: {}", configured); + } + + /// Called when remote wakeup feature is enabled or disabled. + fn remote_wakeup_enabled(&mut self, enabled: bool) { + debug!("USB remote wakeup enabled: {}", enabled); + } + + /// Called when a "set alternate setting" control request is done on the interface. + fn set_alternate_setting(&mut self, iface: InterfaceNumber, alternate_setting: u8) { + debug!( + "USB set interface number {} to alt setting {}.", + iface, alternate_setting + ); + } + + /// Called after a USB reset after the bus reset sequence is complete. + fn reset(&mut self) { + let shared = self.shared; + shared.audio_settings.lock(|x| x.set(AudioSettings::default())); + + shared.changed.store(true, Ordering::Relaxed); + shared.waker.borrow_mut().wake(); + } + + /// Called when the bus has entered or exited the suspend state. + fn suspended(&mut self, suspended: bool) { + debug!("USB device suspended: {}", suspended); + } + + // Handle control set requests. + fn control_out(&mut self, req: control::Request, data: &[u8]) -> Option { + match req.request_type { + RequestType::Class => match req.recipient { + Recipient::Interface => self.interface_set_request(req, data), + Recipient::Endpoint => self.endpoint_set_request(req, data), + _ => Some(OutResponse::Rejected), + }, + _ => None, + } + } + + // Handle control get requests. + fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> Option> { + match req.request_type { + RequestType::Class => match req.recipient { + Recipient::Interface => self.interface_get_request(req, buf), + Recipient::Endpoint => self.endpoint_get_request(req, buf), + _ => None, + }, + _ => None, + } + } +} diff --git a/embassy-usb/src/class/uac1/terminal_type.rs b/embassy-usb/src/class/uac1/terminal_type.rs new file mode 100644 index 0000000000..65474a714e --- /dev/null +++ b/embassy-usb/src/class/uac1/terminal_type.rs @@ -0,0 +1,50 @@ +//! USB Audio Terminal Types from Universal Serial Bus Device Class Definition +//! for Terminal Types, Release 1.0 + +/// USB Audio Terminal Types from "Universal Serial Bus Device Class Definition +/// for Terminal Types, Release 1.0" +#[repr(u16)] +#[non_exhaustive] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[allow(missing_docs)] +pub enum TerminalType { + // USB Terminal Types + UsbUndefined = 0x0100, + UsbStreaming = 0x0101, + UsbVendor = 0x01ff, + + // Input Terminal Types + InUndefined = 0x0200, + InMicrophone = 0x0201, + InDesktopMicrophone = 0x0202, + InPersonalMicrophone = 0x0203, + InOmniDirectionalMicrophone = 0x0204, + InMicrophoneArray = 0x0205, + InProcessingMicrophoneArray = 0x0206, + + // Output Terminal Types + OutUndefined = 0x0300, + OutSpeaker = 0x0301, + OutHeadphones = 0x0302, + OutHeadMountedDisplayAudio = 0x0303, + OutDesktopSpeaker = 0x0304, + OutRoomSpeaker = 0x0305, + OutCommunicationSpeaker = 0x0306, + OutLowFrequencyEffectsSpeaker = 0x0307, + + // External Terminal Types + ExtUndefined = 0x0600, + ExtAnalogConnector = 0x0601, + ExtDigitalAudioInterface = 0x0602, + ExtLineConnector = 0x0603, + ExtLegacyAudioConnector = 0x0604, + ExtSpdifConnector = 0x0605, + Ext1394DaStream = 0x0606, + Ext1394DvStreamSoundtrack = 0x0607, +} + +impl From for u16 { + fn from(t: TerminalType) -> u16 { + t as u16 + } +} From c592721aa57e99fa3c2ec521a994ecb2b6078824 Mon Sep 17 00:00:00 2001 From: elagil Date: Thu, 5 Sep 2024 21:31:52 +0200 Subject: [PATCH 2/3] feat(stm32f4): add usb audio example --- examples/stm32f4/Cargo.toml | 1 + examples/stm32f4/src/bin/usb_uac_speaker.rs | 375 ++++++++++++++++++++ 2 files changed, 376 insertions(+) create mode 100644 examples/stm32f4/src/bin/usb_uac_speaker.rs diff --git a/examples/stm32f4/Cargo.toml b/examples/stm32f4/Cargo.toml index b853615961..c3c4a3d6b1 100644 --- a/examples/stm32f4/Cargo.toml +++ b/examples/stm32f4/Cargo.toml @@ -27,6 +27,7 @@ embedded-io-async = { version = "0.6.1" } panic-probe = { version = "0.3", features = ["print-defmt"] } futures-util = { version = "0.3.30", default-features = false } heapless = { version = "0.8", default-features = false } +critical-section = "1.1" nb = "1.0.0" embedded-storage = "0.3.1" micromath = "2.0.0" 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 0000000000..77c693acec --- /dev/null +++ b/examples/stm32f4/src/bin/usb_uac_speaker.rs @@ -0,0 +1,375 @@ +#![no_std] +#![no_main] + +use core::cell::RefCell; + +use defmt::{panic, *}; +use embassy_executor::Spawner; +use embassy_stm32::time::Hertz; +use embassy_stm32::{bind_interrupts, interrupt, peripherals, timer, usb, Config}; +use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}; +use embassy_sync::blocking_mutex::Mutex; +use embassy_sync::signal::Signal; +use embassy_sync::zerocopy_channel; +use embassy_usb::class::uac1; +use embassy_usb::class::uac1::speaker::{self, Speaker}; +use embassy_usb::driver::EndpointError; +use heapless::Vec; +use micromath::F32Ext; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + OTG_FS => usb::InterruptHandler; +}); + +static TIMER: Mutex>>> = + Mutex::new(RefCell::new(None)); + +// A counter signal that is written by the feedback timer, once every `FEEDBACK_REFRESH_PERIOD`. +// At that point, a feedback value is sent to the host. +pub static FEEDBACK_SIGNAL: Signal = Signal::new(); + +// Stereo input +pub const INPUT_CHANNEL_COUNT: usize = 2; + +// This example uses a fixed sample rate of 48 kHz. +pub const SAMPLE_RATE_HZ: u32 = 48_000; +pub const FEEDBACK_COUNTER_TICK_RATE: u32 = 42_000_000; + +// Use 32 bit samples, which allow for a lot of (software) volume adjustment without degradation of quality. +pub const SAMPLE_WIDTH: uac1::SampleWidth = uac1::SampleWidth::Width4Byte; +pub const SAMPLE_WIDTH_BIT: usize = SAMPLE_WIDTH.in_bit(); +pub const SAMPLE_SIZE: usize = SAMPLE_WIDTH as usize; +pub const SAMPLE_SIZE_PER_S: usize = (SAMPLE_RATE_HZ as usize) * INPUT_CHANNEL_COUNT * SAMPLE_SIZE; + +// Size of audio samples per 1 ms - for the full-speed USB frame period of 1 ms. +pub const USB_FRAME_SIZE: usize = SAMPLE_SIZE_PER_S.div_ceil(1000); + +// Select front left and right audio channels. +pub const AUDIO_CHANNELS: [uac1::Channel; INPUT_CHANNEL_COUNT] = [uac1::Channel::LeftFront, uac1::Channel::RightFront]; + +// Factor of two as a margin for feedback (this is an excessive amount) +pub const USB_MAX_PACKET_SIZE: usize = 2 * USB_FRAME_SIZE; +pub const USB_MAX_SAMPLE_COUNT: usize = USB_MAX_PACKET_SIZE / SAMPLE_SIZE; + +// The data type that is exchanged via the zero-copy channel (a sample vector). +pub type SampleBlock = Vec; + +// Feedback is provided in 10.14 format for full-speed endpoints. +pub const FEEDBACK_REFRESH_PERIOD: uac1::FeedbackRefresh = uac1::FeedbackRefresh::Period8Frames; +const FEEDBACK_SHIFT: usize = 14; + +const TICKS_PER_SAMPLE: f32 = (FEEDBACK_COUNTER_TICK_RATE as f32) / (SAMPLE_RATE_HZ as f32); + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +/// Sends feedback messages to the host. +async fn feedback_handler<'d, T: usb::Instance + 'd>( + feedback: &mut speaker::Feedback<'d, usb::Driver<'d, T>>, + feedback_factor: f32, +) -> Result<(), Disconnected> { + let mut packet: Vec = Vec::new(); + + // Collects the fractional component of the feedback value that is lost by rounding. + let mut rest = 0.0_f32; + + loop { + let counter = FEEDBACK_SIGNAL.wait().await; + + packet.clear(); + + let raw_value = counter as f32 * feedback_factor + rest; + let value = raw_value.round(); + rest = raw_value - value; + + let value = value as u32; + packet.push(value as u8).unwrap(); + packet.push((value >> 8) as u8).unwrap(); + packet.push((value >> 16) as u8).unwrap(); + + feedback.write_packet(&packet).await?; + } +} + +/// Handles streaming of audio data from the host. +async fn stream_handler<'d, T: usb::Instance + 'd>( + stream: &mut speaker::Stream<'d, usb::Driver<'d, T>>, + sender: &mut zerocopy_channel::Sender<'static, NoopRawMutex, SampleBlock>, +) -> Result<(), Disconnected> { + loop { + let mut usb_data = [0u8; USB_MAX_PACKET_SIZE]; + let data_size = stream.read_packet(&mut usb_data).await?; + + let word_count = data_size / SAMPLE_SIZE; + + if word_count * SAMPLE_SIZE == data_size { + // Obtain a buffer from the channel + let samples = sender.send().await; + samples.clear(); + + for w in 0..word_count { + let byte_offset = w * SAMPLE_SIZE; + let sample = u32::from_le_bytes(usb_data[byte_offset..byte_offset + SAMPLE_SIZE].try_into().unwrap()); + + // Fill the sample buffer with data. + samples.push(sample).unwrap(); + } + + sender.send_done(); + } else { + debug!("Invalid USB buffer size of {}, skipped.", data_size); + } + } +} + +/// Receives audio samples from the USB streaming task and can play them back. +#[embassy_executor::task] +async fn audio_receiver_task(mut usb_audio_receiver: zerocopy_channel::Receiver<'static, NoopRawMutex, SampleBlock>) { + loop { + let _samples = usb_audio_receiver.receive().await; + // Use the samples, for example play back via the SAI peripheral. + + // Notify the channel that the buffer is now ready to be reused + usb_audio_receiver.receive_done(); + } +} + +/// Receives audio samples from the host. +#[embassy_executor::task] +async fn usb_streaming_task( + mut stream: speaker::Stream<'static, usb::Driver<'static, peripherals::USB_OTG_FS>>, + mut sender: zerocopy_channel::Sender<'static, NoopRawMutex, SampleBlock>, +) { + loop { + stream.wait_connection().await; + _ = stream_handler(&mut stream, &mut sender).await; + } +} + +/// Sends sample rate feedback to the host. +/// +/// The `feedback_factor` scales the feedback timer's counter value so that the result is the number of samples that +/// this device played back or "consumed" during one SOF period (1 ms) - in 10.14 format. +/// +/// Ideally, the `feedback_factor` that is calculated below would be an integer for avoiding numerical errors. +/// This is achieved by having `TICKS_PER_SAMPLE` be a power of two. For audio applications at a sample rate of 48 kHz, +/// 24.576 MHz would be one such option. +/// +/// A good choice for the STM32F4, which also has to generate a 48 MHz clock from its HSE (e.g. running at 8 MHz) +/// for USB, is to clock the feedback timer from the MCLK output of the SAI peripheral. The SAI peripheral then uses an +/// external clock. In that case, wiring the MCLK output to the timer clock input is required. +/// +/// This simple example just uses the internal clocks for supplying the feedback timer, +/// and does not even set up a SAI peripheral. +#[embassy_executor::task] +async fn usb_feedback_task(mut feedback: speaker::Feedback<'static, usb::Driver<'static, peripherals::USB_OTG_FS>>) { + let feedback_factor = + ((1 << FEEDBACK_SHIFT) as f32 / TICKS_PER_SAMPLE) / FEEDBACK_REFRESH_PERIOD.frame_count() as f32; + + // Should be 2.3405714285714287... + info!("Using a feedback factor of {}.", feedback_factor); + + loop { + feedback.wait_connection().await; + _ = feedback_handler(&mut feedback, feedback_factor).await; + } +} + +#[embassy_executor::task] +async fn usb_task(mut usb_device: embassy_usb::UsbDevice<'static, usb::Driver<'static, peripherals::USB_OTG_FS>>) { + usb_device.run().await; +} + +/// Checks for changes on the control monitor of the class. +/// +/// In this case, monitor changes of volume or mute state. +#[embassy_executor::task] +async fn usb_control_task(control_monitor: speaker::ControlMonitor<'static>) { + loop { + control_monitor.changed().await; + + for channel in AUDIO_CHANNELS { + let volume = control_monitor.volume(channel).unwrap(); + info!("Volume changed to {} on channel {}.", volume, channel); + } + } +} + +/// Feedback value measurement and calculation +/// +/// Used for measuring/calculating the number of samples that were received from the host during the +/// `FEEDBACK_REFRESH_PERIOD`. +/// +/// Configured in this example with +/// - a refresh period of 8 ms, and +/// - a tick rate of 42 MHz. +/// +/// This gives an (ideal) counter value of 336.000 for every update of the `FEEDBACK_SIGNAL`. +#[interrupt] +fn TIM2() { + static mut LAST_TICKS: u32 = 0; + static mut FRAME_COUNT: usize = 0; + + critical_section::with(|cs| { + // Read timer counter. + let ticks = TIMER.borrow(cs).borrow().as_ref().unwrap().regs_gp32().cnt().read(); + + // Clear trigger interrupt flag. + TIMER + .borrow(cs) + .borrow_mut() + .as_mut() + .unwrap() + .regs_gp32() + .sr() + .modify(|r| r.set_tif(false)); + + // Count up frames and emit a signal, when the refresh period is reached (here, every 8 ms). + *FRAME_COUNT += 1; + if *FRAME_COUNT >= FEEDBACK_REFRESH_PERIOD.frame_count() { + *FRAME_COUNT = 0; + FEEDBACK_SIGNAL.signal(ticks.wrapping_sub(*LAST_TICKS)); + *LAST_TICKS = ticks; + } + }); +} + +// If you are trying this and your USB device doesn't connect, the most +// common issues are the RCC config and vbus_detection +// +// See https://embassy.dev/book/#_the_usb_examples_are_not_working_on_my_board_is_there_anything_else_i_need_to_configure +// for more information. +#[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(PllPDiv::DIV2), // ((8 MHz / 4) * 168) / 2 = 168 Mhz. + divq: Some(PllQDiv::DIV7), // ((8 MHz / 4) * 168) / 7 = 48 Mhz. + 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; + config.rcc.mux.clk48sel = mux::Clk48sel::PLL1_Q; + } + let p = embassy_stm32::init(config); + + // Configure all required buffers in a static way. + debug!("USB packet size is {} byte", USB_MAX_PACKET_SIZE); + static CONFIG_DESCRIPTOR: StaticCell<[u8; 256]> = StaticCell::new(); + let config_descriptor = CONFIG_DESCRIPTOR.init([0; 256]); + + static BOS_DESCRIPTOR: StaticCell<[u8; 32]> = StaticCell::new(); + let bos_descriptor = BOS_DESCRIPTOR.init([0; 32]); + + const CONTROL_BUF_SIZE: usize = 64; + static CONTROL_BUF: StaticCell<[u8; CONTROL_BUF_SIZE]> = StaticCell::new(); + let control_buf = CONTROL_BUF.init([0; CONTROL_BUF_SIZE]); + + const FEEDBACK_BUF_SIZE: usize = 4; + static EP_OUT_BUFFER: StaticCell<[u8; FEEDBACK_BUF_SIZE + CONTROL_BUF_SIZE + USB_MAX_PACKET_SIZE]> = + StaticCell::new(); + let ep_out_buffer = EP_OUT_BUFFER.init([0u8; FEEDBACK_BUF_SIZE + CONTROL_BUF_SIZE + USB_MAX_PACKET_SIZE]); + + static STATE: StaticCell = StaticCell::new(); + let state = STATE.init(speaker::State::new()); + + // Create the driver, from the HAL. + let mut usb_config = usb::Config::default(); + + // Do not enable vbus_detection. This is a safe default that works in all boards. + // However, if your USB device is self-powered (can stay powered on if USB is unplugged), you need + // to enable vbus_detection to comply with the USB spec. If you enable it, the board + // has to support it or USB won't work at all. See docs on `vbus_detection` for details. + usb_config.vbus_detection = false; + + let usb_driver = usb::Driver::new_fs(p.USB_OTG_FS, Irqs, p.PA12, p.PA11, ep_out_buffer, usb_config); + + // Basic USB device configuration + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-audio-speaker 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; + + let mut builder = embassy_usb::Builder::new( + usb_driver, + config, + config_descriptor, + bos_descriptor, + &mut [], // no msos descriptors + control_buf, + ); + + // Create the UAC1 Speaker class components + let (stream, feedback, control_monitor) = Speaker::new( + &mut builder, + state, + USB_MAX_PACKET_SIZE as u16, + uac1::SampleWidth::Width4Byte, + &[SAMPLE_RATE_HZ], + &AUDIO_CHANNELS, + FEEDBACK_REFRESH_PERIOD, + ); + + // Create the USB device + let usb_device = builder.build(); + + // Establish a zero-copy channel for transferring received audio samples between tasks + static SAMPLE_BLOCKS: StaticCell<[SampleBlock; 2]> = StaticCell::new(); + let sample_blocks = SAMPLE_BLOCKS.init([Vec::new(), Vec::new()]); + + static CHANNEL: StaticCell> = StaticCell::new(); + let channel = CHANNEL.init(zerocopy_channel::Channel::new(sample_blocks)); + let (sender, receiver) = channel.split(); + + // Run a timer for counting between SOF interrupts. + let mut tim2 = timer::low_level::Timer::new(p.TIM2); + tim2.set_tick_freq(Hertz(FEEDBACK_COUNTER_TICK_RATE)); + tim2.set_trigger_source(timer::low_level::TriggerSource::ITR1); // The USB SOF signal. + tim2.set_slave_mode(timer::low_level::SlaveMode::TRIGGER_MODE); + tim2.regs_gp16().dier().modify(|r| r.set_tie(true)); // Enable the trigger interrupt. + tim2.start(); + + TIMER.lock(|p| p.borrow_mut().replace(tim2)); + + // Unmask the TIM2 interrupt. + unsafe { + cortex_m::peripheral::NVIC::unmask(interrupt::TIM2); + } + + // Launch USB audio tasks. + unwrap!(spawner.spawn(usb_control_task(control_monitor))); + unwrap!(spawner.spawn(usb_streaming_task(stream, sender))); + unwrap!(spawner.spawn(usb_feedback_task(feedback))); + unwrap!(spawner.spawn(usb_task(usb_device))); + unwrap!(spawner.spawn(audio_receiver_task(receiver))); +} From 29de08914b0e0f6d1c9c39a4cae28465280aac74 Mon Sep 17 00:00:00 2001 From: elagil Date: Thu, 5 Sep 2024 21:31:57 +0200 Subject: [PATCH 3/3] feat(stm32h5): add usb audio example --- examples/stm32h5/src/bin/usb_uac_speaker.rs | 367 ++++++++++++++++++++ 1 file changed, 367 insertions(+) create mode 100644 examples/stm32h5/src/bin/usb_uac_speaker.rs diff --git a/examples/stm32h5/src/bin/usb_uac_speaker.rs b/examples/stm32h5/src/bin/usb_uac_speaker.rs new file mode 100644 index 0000000000..6b992690ff --- /dev/null +++ b/examples/stm32h5/src/bin/usb_uac_speaker.rs @@ -0,0 +1,367 @@ +#![no_std] +#![no_main] + +use core::cell::RefCell; + +use defmt::{panic, *}; +use embassy_executor::Spawner; +use embassy_stm32::time::Hertz; +use embassy_stm32::{bind_interrupts, interrupt, peripherals, timer, usb, Config}; +use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}; +use embassy_sync::blocking_mutex::Mutex; +use embassy_sync::signal::Signal; +use embassy_sync::zerocopy_channel; +use embassy_usb::class::uac1; +use embassy_usb::class::uac1::speaker::{self, Speaker}; +use embassy_usb::driver::EndpointError; +use heapless::Vec; +use micromath::F32Ext; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USB_DRD_FS => usb::InterruptHandler; +}); + +static TIMER: Mutex>>> = + Mutex::new(RefCell::new(None)); + +// A counter signal that is written by the feedback timer, once every `FEEDBACK_REFRESH_PERIOD`. +// At that point, a feedback value is sent to the host. +pub static FEEDBACK_SIGNAL: Signal = Signal::new(); + +// Stereo input +pub const INPUT_CHANNEL_COUNT: usize = 2; + +// This example uses a fixed sample rate of 48 kHz. +pub const SAMPLE_RATE_HZ: u32 = 48_000; +pub const FEEDBACK_COUNTER_TICK_RATE: u32 = 31_250_000; + +// Use 32 bit samples, which allow for a lot of (software) volume adjustment without degradation of quality. +pub const SAMPLE_WIDTH: uac1::SampleWidth = uac1::SampleWidth::Width4Byte; +pub const SAMPLE_WIDTH_BIT: usize = SAMPLE_WIDTH.in_bit(); +pub const SAMPLE_SIZE: usize = SAMPLE_WIDTH as usize; +pub const SAMPLE_SIZE_PER_S: usize = (SAMPLE_RATE_HZ as usize) * INPUT_CHANNEL_COUNT * SAMPLE_SIZE; + +// Size of audio samples per 1 ms - for the full-speed USB frame period of 1 ms. +pub const USB_FRAME_SIZE: usize = SAMPLE_SIZE_PER_S.div_ceil(1000); + +// Select front left and right audio channels. +pub const AUDIO_CHANNELS: [uac1::Channel; INPUT_CHANNEL_COUNT] = [uac1::Channel::LeftFront, uac1::Channel::RightFront]; + +// Factor of two as a margin for feedback (this is an excessive amount) +pub const USB_MAX_PACKET_SIZE: usize = 2 * USB_FRAME_SIZE; +pub const USB_MAX_SAMPLE_COUNT: usize = USB_MAX_PACKET_SIZE / SAMPLE_SIZE; + +// The data type that is exchanged via the zero-copy channel (a sample vector). +pub type SampleBlock = Vec; + +// Feedback is provided in 10.14 format for full-speed endpoints. +pub const FEEDBACK_REFRESH_PERIOD: uac1::FeedbackRefresh = uac1::FeedbackRefresh::Period8Frames; +const FEEDBACK_SHIFT: usize = 14; + +const TICKS_PER_SAMPLE: f32 = (FEEDBACK_COUNTER_TICK_RATE as f32) / (SAMPLE_RATE_HZ as f32); + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +/// Sends feedback messages to the host. +async fn feedback_handler<'d, T: usb::Instance + 'd>( + feedback: &mut speaker::Feedback<'d, usb::Driver<'d, T>>, + feedback_factor: f32, +) -> Result<(), Disconnected> { + let mut packet: Vec = Vec::new(); + + // Collects the fractional component of the feedback value that is lost by rounding. + let mut rest = 0.0_f32; + + loop { + let counter = FEEDBACK_SIGNAL.wait().await; + + packet.clear(); + + let raw_value = counter as f32 * feedback_factor + rest; + let value = raw_value.round(); + rest = raw_value - value; + + let value = value as u32; + + packet.push(value as u8).unwrap(); + packet.push((value >> 8) as u8).unwrap(); + packet.push((value >> 16) as u8).unwrap(); + + feedback.write_packet(&packet).await?; + } +} + +/// Handles streaming of audio data from the host. +async fn stream_handler<'d, T: usb::Instance + 'd>( + stream: &mut speaker::Stream<'d, usb::Driver<'d, T>>, + sender: &mut zerocopy_channel::Sender<'static, NoopRawMutex, SampleBlock>, +) -> Result<(), Disconnected> { + loop { + let mut usb_data = [0u8; USB_MAX_PACKET_SIZE]; + let data_size = stream.read_packet(&mut usb_data).await?; + + let word_count = data_size / SAMPLE_SIZE; + + if word_count * SAMPLE_SIZE == data_size { + // Obtain a buffer from the channel + let samples = sender.send().await; + samples.clear(); + + for w in 0..word_count { + let byte_offset = w * SAMPLE_SIZE; + let sample = u32::from_le_bytes(usb_data[byte_offset..byte_offset + SAMPLE_SIZE].try_into().unwrap()); + + // Fill the sample buffer with data. + samples.push(sample).unwrap(); + } + + sender.send_done(); + } else { + debug!("Invalid USB buffer size of {}, skipped.", data_size); + } + } +} + +/// Receives audio samples from the USB streaming task and can play them back. +#[embassy_executor::task] +async fn audio_receiver_task(mut usb_audio_receiver: zerocopy_channel::Receiver<'static, NoopRawMutex, SampleBlock>) { + loop { + let _samples = usb_audio_receiver.receive().await; + // Use the samples, for example play back via the SAI peripheral. + + // Notify the channel that the buffer is now ready to be reused + usb_audio_receiver.receive_done(); + } +} + +/// Receives audio samples from the host. +#[embassy_executor::task] +async fn usb_streaming_task( + mut stream: speaker::Stream<'static, usb::Driver<'static, peripherals::USB>>, + mut sender: zerocopy_channel::Sender<'static, NoopRawMutex, SampleBlock>, +) { + loop { + stream.wait_connection().await; + info!("USB connected."); + _ = stream_handler(&mut stream, &mut sender).await; + info!("USB disconnected."); + } +} + +/// Sends sample rate feedback to the host. +/// +/// The `feedback_factor` scales the feedback timer's counter value so that the result is the number of samples that +/// this device played back or "consumed" during one SOF period (1 ms) - in 10.14 format. +/// +/// Ideally, the `feedback_factor` that is calculated below would be an integer for avoiding numerical errors. +/// This is achieved by having `TICKS_PER_SAMPLE` be a power of two. For audio applications at a sample rate of 48 kHz, +/// 24.576 MHz would be one such option. +#[embassy_executor::task] +async fn usb_feedback_task(mut feedback: speaker::Feedback<'static, usb::Driver<'static, peripherals::USB>>) { + let feedback_factor = + ((1 << FEEDBACK_SHIFT) as f32 / TICKS_PER_SAMPLE) / FEEDBACK_REFRESH_PERIOD.frame_count() as f32; + + loop { + feedback.wait_connection().await; + _ = feedback_handler(&mut feedback, feedback_factor).await; + } +} + +#[embassy_executor::task] +async fn usb_task(mut usb_device: embassy_usb::UsbDevice<'static, usb::Driver<'static, peripherals::USB>>) { + usb_device.run().await; +} + +/// Checks for changes on the control monitor of the class. +/// +/// In this case, monitor changes of volume or mute state. +#[embassy_executor::task] +async fn usb_control_task(control_monitor: speaker::ControlMonitor<'static>) { + loop { + control_monitor.changed().await; + + for channel in AUDIO_CHANNELS { + let volume = control_monitor.volume(channel).unwrap(); + info!("Volume changed to {} on channel {}.", volume, channel); + } + } +} + +/// Feedback value measurement and calculation +/// +/// Used for measuring/calculating the number of samples that were received from the host during the +/// `FEEDBACK_REFRESH_PERIOD`. +/// +/// Configured in this example with +/// - a refresh period of 8 ms, and +/// - a tick rate of 42 MHz. +/// +/// This gives an (ideal) counter value of 336.000 for every update of the `FEEDBACK_SIGNAL`. +#[interrupt] +fn TIM5() { + static mut LAST_TICKS: u32 = 0; + static mut FRAME_COUNT: usize = 0; + + critical_section::with(|cs| { + // Read timer counter. + let ticks = TIMER.borrow(cs).borrow().as_ref().unwrap().regs_gp32().cnt().read(); + + // Clear trigger interrupt flag. + TIMER + .borrow(cs) + .borrow_mut() + .as_mut() + .unwrap() + .regs_gp32() + .sr() + .modify(|r| r.set_tif(false)); + + // Count up frames and emit a signal, when the refresh period is reached (here, every 8 ms). + *FRAME_COUNT += 1; + if *FRAME_COUNT >= FEEDBACK_REFRESH_PERIOD.frame_count() { + *FRAME_COUNT = 0; + FEEDBACK_SIGNAL.signal(ticks.wrapping_sub(*LAST_TICKS)); + *LAST_TICKS = ticks; + } + }); +} + +// If you are trying this and your USB device doesn't connect, the most +// common issues are the RCC config and vbus_detection +// +// See https://embassy.dev/book/#_the_usb_examples_are_not_working_on_my_board_is_there_anything_else_i_need_to_configure +// for more information. +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = None; + config.rcc.hsi48 = Some(Hsi48Config { sync_from_usb: true }); // needed for USB + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + mode: HseMode::BypassDigital, + }); + config.rcc.pll1 = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV2, + mul: PllMul::MUL125, + divp: Some(PllDiv::DIV2), // 250 Mhz + divq: None, + divr: None, + }); + config.rcc.pll2 = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL123, + divp: Some(PllDiv::DIV20), // 12.3 Mhz, close to 12.288 MHz for 48 kHz audio + divq: None, + divr: None, + }); + config.rcc.ahb_pre = AHBPrescaler::DIV2; + config.rcc.apb1_pre = APBPrescaler::DIV4; + config.rcc.apb2_pre = APBPrescaler::DIV2; + config.rcc.apb3_pre = APBPrescaler::DIV4; + config.rcc.sys = Sysclk::PLL1_P; + config.rcc.voltage_scale = VoltageScale::Scale0; + config.rcc.mux.usbsel = mux::Usbsel::HSI48; + config.rcc.mux.sai2sel = mux::Saisel::PLL2_P; + } + let p = embassy_stm32::init(config); + + info!("Hello World!"); + + // Configure all required buffers in a static way. + debug!("USB packet size is {} byte", USB_MAX_PACKET_SIZE); + static CONFIG_DESCRIPTOR: StaticCell<[u8; 256]> = StaticCell::new(); + let config_descriptor = CONFIG_DESCRIPTOR.init([0; 256]); + + static BOS_DESCRIPTOR: StaticCell<[u8; 32]> = StaticCell::new(); + let bos_descriptor = BOS_DESCRIPTOR.init([0; 32]); + + const CONTROL_BUF_SIZE: usize = 64; + static CONTROL_BUF: StaticCell<[u8; CONTROL_BUF_SIZE]> = StaticCell::new(); + let control_buf = CONTROL_BUF.init([0; CONTROL_BUF_SIZE]); + + static STATE: StaticCell = StaticCell::new(); + let state = STATE.init(speaker::State::new()); + + let usb_driver = usb::Driver::new(p.USB, Irqs, p.PA12, p.PA11); + + // Basic USB device configuration + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-audio-speaker 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; + + let mut builder = embassy_usb::Builder::new( + usb_driver, + config, + config_descriptor, + bos_descriptor, + &mut [], // no msos descriptors + control_buf, + ); + + // Create the UAC1 Speaker class components + let (stream, feedback, control_monitor) = Speaker::new( + &mut builder, + state, + USB_MAX_PACKET_SIZE as u16, + uac1::SampleWidth::Width4Byte, + &[SAMPLE_RATE_HZ], + &AUDIO_CHANNELS, + FEEDBACK_REFRESH_PERIOD, + ); + + // Create the USB device + let usb_device = builder.build(); + + // Establish a zero-copy channel for transferring received audio samples between tasks + static SAMPLE_BLOCKS: StaticCell<[SampleBlock; 2]> = StaticCell::new(); + let sample_blocks = SAMPLE_BLOCKS.init([Vec::new(), Vec::new()]); + + static CHANNEL: StaticCell> = StaticCell::new(); + let channel = CHANNEL.init(zerocopy_channel::Channel::new(sample_blocks)); + let (sender, receiver) = channel.split(); + + // Run a timer for counting between SOF interrupts. + let mut tim5 = timer::low_level::Timer::new(p.TIM5); + tim5.set_tick_freq(Hertz(FEEDBACK_COUNTER_TICK_RATE)); + tim5.set_trigger_source(timer::low_level::TriggerSource::ITR12); // The USB SOF signal. + tim5.set_slave_mode(timer::low_level::SlaveMode::TRIGGER_MODE); + tim5.regs_gp16().dier().modify(|r| r.set_tie(true)); // Enable the trigger interrupt. + tim5.start(); + + TIMER.lock(|p| p.borrow_mut().replace(tim5)); + + // Unmask the TIM5 interrupt. + unsafe { + cortex_m::peripheral::NVIC::unmask(interrupt::TIM5); + } + + // Launch USB audio tasks. + unwrap!(spawner.spawn(usb_control_task(control_monitor))); + unwrap!(spawner.spawn(usb_streaming_task(stream, sender))); + unwrap!(spawner.spawn(usb_feedback_task(feedback))); + unwrap!(spawner.spawn(usb_task(usb_device))); + unwrap!(spawner.spawn(audio_receiver_task(receiver))); +}