diff options
| author | Dario Nieuwenhuis <[email protected]> | 2024-11-23 23:51:44 +0000 |
|---|---|---|
| committer | GitHub <[email protected]> | 2024-11-23 23:51:44 +0000 |
| commit | b9408f051080398f38e03f7d0d20bba860213064 (patch) | |
| tree | dc29a8fa2839518af739d9028a0c2f5554f44585 | |
| parent | bc7372d7011c36157e4d55e05d0a3c5a82ba6f1c (diff) | |
| parent | cc4b5ae9cb4d79f9fa378b2a06073eb7ae96d369 (diff) | |
Merge pull request #3212 from elagil/feat_usb_prepare_for_uac
feat(usb): Prepare `embassy-usb` for USB Audio, and add USB Audio Class 1.0 (playback only)
| -rw-r--r-- | embassy-usb/src/class/mod.rs | 1 | ||||
| -rw-r--r-- | embassy-usb/src/class/uac1/class_codes.rs | 151 | ||||
| -rw-r--r-- | embassy-usb/src/class/uac1/mod.rs | 134 | ||||
| -rw-r--r-- | embassy-usb/src/class/uac1/speaker.rs | 778 | ||||
| -rw-r--r-- | embassy-usb/src/class/uac1/terminal_type.rs | 50 | ||||
| -rw-r--r-- | examples/stm32f4/Cargo.toml | 1 | ||||
| -rw-r--r-- | examples/stm32f4/src/bin/usb_uac_speaker.rs | 387 | ||||
| -rw-r--r-- | examples/stm32h5/src/bin/usb_uac_speaker.rs | 378 |
8 files changed, 1880 insertions, 0 deletions
diff --git a/embassy-usb/src/class/mod.rs b/embassy-usb/src/class/mod.rs index b883ed4e5..4bd89eb66 100644 --- a/embassy-usb/src/class/mod.rs +++ b/embassy-usb/src/class/mod.rs | |||
| @@ -3,4 +3,5 @@ pub mod cdc_acm; | |||
| 3 | pub mod cdc_ncm; | 3 | pub mod cdc_ncm; |
| 4 | pub mod hid; | 4 | pub mod hid; |
| 5 | pub mod midi; | 5 | pub mod midi; |
| 6 | pub mod uac1; | ||
| 6 | pub mod web_usb; | 7 | 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 000000000..3f6956771 --- /dev/null +++ b/embassy-usb/src/class/uac1/class_codes.rs | |||
| @@ -0,0 +1,151 @@ | |||
| 1 | //! Audio Device Class Codes as defined in Universal Serial Bus Device Class | ||
| 2 | //! Definition for Audio Devices, Release 1.0, Appendix A and Universal Serial | ||
| 3 | //! Bus Device Class Definition for Audio Data Formats, Release 1.0, Appendix | ||
| 4 | //! A.1.1 (Audio Data Format Type I Codes) | ||
| 5 | #![allow(dead_code)] | ||
| 6 | |||
| 7 | /// The current version of the ADC specification (1.0) | ||
| 8 | pub const ADC_VERSION: u16 = 0x0100; | ||
| 9 | |||
| 10 | /// The current version of the USB device (1.0) | ||
| 11 | pub const DEVICE_VERSION: u16 = 0x0100; | ||
| 12 | |||
| 13 | /// Audio Interface Class Code | ||
| 14 | pub const USB_AUDIO_CLASS: u8 = 0x01; | ||
| 15 | |||
| 16 | // Audio Interface Subclass Codes | ||
| 17 | pub const USB_UNDEFINED_SUBCLASS: u8 = 0x00; | ||
| 18 | pub const USB_AUDIOCONTROL_SUBCLASS: u8 = 0x01; | ||
| 19 | pub const USB_AUDIOSTREAMING_SUBCLASS: u8 = 0x02; | ||
| 20 | pub const USB_MIDISTREAMING_SUBCLASS: u8 = 0x03; | ||
| 21 | |||
| 22 | // Audio Protocol Code | ||
| 23 | pub const PROTOCOL_NONE: u8 = 0x00; | ||
| 24 | |||
| 25 | // Audio Class-Specific Descriptor Types | ||
| 26 | pub const CS_UNDEFINED: u8 = 0x20; | ||
| 27 | pub const CS_DEVICE: u8 = 0x21; | ||
| 28 | pub const CS_CONFIGURATION: u8 = 0x22; | ||
| 29 | pub const CS_STRING: u8 = 0x23; | ||
| 30 | pub const CS_INTERFACE: u8 = 0x24; | ||
| 31 | pub const CS_ENDPOINT: u8 = 0x25; | ||
| 32 | |||
| 33 | // Descriptor Subtype | ||
| 34 | pub const AC_DESCRIPTOR_UNDEFINED: u8 = 0x00; | ||
| 35 | pub const HEADER_SUBTYPE: u8 = 0x01; | ||
| 36 | pub const INPUT_TERMINAL: u8 = 0x02; | ||
| 37 | pub const OUTPUT_TERMINAL: u8 = 0x03; | ||
| 38 | pub const MIXER_UNIT: u8 = 0x04; | ||
| 39 | pub const SELECTOR_UNIT: u8 = 0x05; | ||
| 40 | pub const FEATURE_UNIT: u8 = 0x06; | ||
| 41 | pub const PROCESSING_UNIT: u8 = 0x07; | ||
| 42 | pub const EXTENSION_UNIT: u8 = 0x08; | ||
| 43 | |||
| 44 | // Audio Class-Specific AS Interface Descriptor Subtypes | ||
| 45 | pub const AS_DESCRIPTOR_UNDEFINED: u8 = 0x00; | ||
| 46 | pub const AS_GENERAL: u8 = 0x01; | ||
| 47 | pub const FORMAT_TYPE: u8 = 0x02; | ||
| 48 | pub const FORMAT_SPECIFIC: u8 = 0x03; | ||
| 49 | |||
| 50 | // Processing Unit Process Types | ||
| 51 | pub const PROCESS_UNDEFINED: u16 = 0x00; | ||
| 52 | pub const UP_DOWNMIX_PROCESS: u16 = 0x01; | ||
| 53 | pub const DOLBY_PROLOGIC_PROCESS: u16 = 0x02; | ||
| 54 | pub const DDD_STEREO_EXTENDER_PROCESS: u16 = 0x03; | ||
| 55 | pub const REVERBERATION_PROCESS: u16 = 0x04; | ||
| 56 | pub const CHORUS_PROCESS: u16 = 0x05; | ||
| 57 | pub const DYN_RANGE_COMP_PROCESS: u16 = 0x06; | ||
| 58 | |||
| 59 | // Audio Class-Specific Endpoint Descriptor Subtypes | ||
| 60 | pub const EP_DESCRIPTOR_UNDEFINED: u8 = 0x00; | ||
| 61 | pub const EP_GENERAL: u8 = 0x01; | ||
| 62 | |||
| 63 | // Audio Class-Specific Request Codes | ||
| 64 | pub const REQUEST_CODE_UNDEFINED: u8 = 0x00; | ||
| 65 | pub const SET_CUR: u8 = 0x01; | ||
| 66 | pub const GET_CUR: u8 = 0x81; | ||
| 67 | pub const SET_MIN: u8 = 0x02; | ||
| 68 | pub const GET_MIN: u8 = 0x82; | ||
| 69 | pub const SET_MAX: u8 = 0x03; | ||
| 70 | pub const GET_MAX: u8 = 0x83; | ||
| 71 | pub const SET_RES: u8 = 0x04; | ||
| 72 | pub const GET_RES: u8 = 0x84; | ||
| 73 | pub const SET_MEM: u8 = 0x05; | ||
| 74 | pub const GET_MEM: u8 = 0x85; | ||
| 75 | pub const GET_STAT: u8 = 0xFF; | ||
| 76 | |||
| 77 | // Terminal Control Selectors | ||
| 78 | pub const TE_CONTROL_UNDEFINED: u8 = 0x00; | ||
| 79 | pub const COPY_PROTECT_CONTROL: u8 = 0x01; | ||
| 80 | |||
| 81 | // Feature Unit Control Selectors | ||
| 82 | pub const FU_CONTROL_UNDEFINED: u8 = 0x00; | ||
| 83 | pub const MUTE_CONTROL: u8 = 0x01; | ||
| 84 | pub const VOLUME_CONTROL: u8 = 0x02; | ||
| 85 | pub const BASS_CONTROL: u8 = 0x03; | ||
| 86 | pub const MID_CONTROL: u8 = 0x04; | ||
| 87 | pub const TREBLE_CONTROL: u8 = 0x05; | ||
| 88 | pub const GRAPHIC_EQUALIZER_CONTROL: u8 = 0x06; | ||
| 89 | pub const AUTOMATIC_GAIN_CONTROL: u8 = 0x07; | ||
| 90 | pub const DELAY_CONTROL: u8 = 0x08; | ||
| 91 | pub const BASS_BOOST_CONTROL: u8 = 0x09; | ||
| 92 | pub const LOUDNESS_CONTROL: u8 = 0x0A; | ||
| 93 | |||
| 94 | // Up/Down-mix Processing Unit Control Selectors | ||
| 95 | pub const UD_CONTROL_UNDEFINED: u8 = 0x00; | ||
| 96 | pub const UD_ENABLE_CONTROL: u8 = 0x01; | ||
| 97 | pub const UD_MODE_SELECT_CONTROL: u8 = 0x02; | ||
| 98 | |||
| 99 | // Dolby Prologic Processing Unit Control Selectors | ||
| 100 | pub const DP_CONTROL_UNDEFINED: u8 = 0x00; | ||
| 101 | pub const DP_ENABLE_CONTROL: u8 = 0x01; | ||
| 102 | pub const DP_MODE_SELECT_CONTROL: u8 = 0x2; | ||
| 103 | |||
| 104 | // 3D Stereo Extender Processing Unit Control Selectors | ||
| 105 | pub const DDD_CONTROL_UNDEFINED: u8 = 0x00; | ||
| 106 | pub const DDD_ENABLE_CONTROL: u8 = 0x01; | ||
| 107 | pub const DDD_SPACIOUSNESS_CONTROL: u8 = 0x03; | ||
| 108 | |||
| 109 | // Reverberation Processing Unit Control Selectors | ||
| 110 | pub const RV_CONTROL_UNDEFINED: u8 = 0x00; | ||
| 111 | pub const RV_ENABLE_CONTROL: u8 = 0x01; | ||
| 112 | pub const REVERB_LEVEL_CONTROL: u8 = 0x02; | ||
| 113 | pub const REVERB_TIME_CONTROL: u8 = 0x03; | ||
| 114 | pub const REVERB_FEEDBACK_CONTROL: u8 = 0x04; | ||
| 115 | |||
| 116 | // Chorus Processing Unit Control Selectors | ||
| 117 | pub const CH_CONTROL_UNDEFINED: u8 = 0x00; | ||
| 118 | pub const CH_ENABLE_CONTROL: u8 = 0x01; | ||
| 119 | pub const CHORUS_LEVEL_CONTROL: u8 = 0x02; | ||
| 120 | pub const CHORUS_RATE_CONTROL: u8 = 0x03; | ||
| 121 | pub const CHORUS_DEPTH_CONTROL: u8 = 0x04; | ||
| 122 | |||
| 123 | // Dynamic Range Compressor Processing Unit Control Selectors | ||
| 124 | pub const DR_CONTROL_UNDEFINED: u8 = 0x00; | ||
| 125 | pub const DR_ENABLE_CONTROL: u8 = 0x01; | ||
| 126 | pub const COMPRESSION_RATE_CONTROL: u8 = 0x02; | ||
| 127 | pub const MAXAMPL_CONTROL: u8 = 0x03; | ||
| 128 | pub const THRESHOLD_CONTROL: u8 = 0x04; | ||
| 129 | pub const ATTACK_TIME: u8 = 0x05; | ||
| 130 | pub const RELEASE_TIME: u8 = 0x06; | ||
| 131 | |||
| 132 | // Extension Unit Control Selectors | ||
| 133 | pub const XU_CONTROL_UNDEFINED: u16 = 0x00; | ||
| 134 | pub const XU_ENABLE_CONTROL: u16 = 0x01; | ||
| 135 | |||
| 136 | // Endpoint Control Selectors | ||
| 137 | pub const EP_CONTROL_UNDEFINED: u8 = 0x00; | ||
| 138 | pub const SAMPLING_FREQ_CONTROL: u8 = 0x01; | ||
| 139 | pub const PITCH_CONTROL: u8 = 0x02; | ||
| 140 | |||
| 141 | // Format Type Codes | ||
| 142 | pub const FORMAT_TYPE_UNDEFINED: u8 = 0x00; | ||
| 143 | pub const FORMAT_TYPE_I: u8 = 0x01; | ||
| 144 | |||
| 145 | // Audio Data Format Type I Codes | ||
| 146 | pub const TYPE_I_UNDEFINED: u16 = 0x0000; | ||
| 147 | pub const PCM: u16 = 0x0001; | ||
| 148 | pub const PCM8: u16 = 0x0002; | ||
| 149 | pub const IEEE_FLOAT: u16 = 0x0003; | ||
| 150 | pub const ALAW: u16 = 0x0004; | ||
| 151 | 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 000000000..3d5f4e524 --- /dev/null +++ b/embassy-usb/src/class/uac1/mod.rs | |||
| @@ -0,0 +1,134 @@ | |||
| 1 | //! USB Audio Class 1.0 implementations for different applications. | ||
| 2 | //! | ||
| 3 | //! Contains: | ||
| 4 | //! - The `speaker` class with a single audio streaming interface (host to device) | ||
| 5 | |||
| 6 | pub mod speaker; | ||
| 7 | |||
| 8 | mod class_codes; | ||
| 9 | mod terminal_type; | ||
| 10 | |||
| 11 | /// The maximum supported audio channel index (corresponds to `Top`). | ||
| 12 | /// FIXME: Use `core::mem::variant_count(...)` when stabilized. | ||
| 13 | const MAX_AUDIO_CHANNEL_INDEX: usize = 12; | ||
| 14 | |||
| 15 | /// The maximum number of supported audio channels. | ||
| 16 | /// | ||
| 17 | /// Includes all twelve channels from `Channel`, plus the Master channel. | ||
| 18 | const MAX_AUDIO_CHANNEL_COUNT: usize = MAX_AUDIO_CHANNEL_INDEX + 1; | ||
| 19 | |||
| 20 | /// USB Audio Channel | ||
| 21 | #[derive(Debug, Clone, Copy, PartialEq)] | ||
| 22 | #[allow(missing_docs)] | ||
| 23 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 24 | pub enum Channel { | ||
| 25 | LeftFront, | ||
| 26 | RightFront, | ||
| 27 | CenterFront, | ||
| 28 | Lfe, | ||
| 29 | LeftSurround, | ||
| 30 | RightSurround, | ||
| 31 | LeftOfCenter, | ||
| 32 | RightOfCenter, | ||
| 33 | Surround, | ||
| 34 | SideLeft, | ||
| 35 | SideRight, | ||
| 36 | Top, | ||
| 37 | } | ||
| 38 | |||
| 39 | impl Channel { | ||
| 40 | /// Map a `Channel` to its corresponding USB Audio `ChannelConfig`. | ||
| 41 | fn get_channel_config(&self) -> ChannelConfig { | ||
| 42 | match self { | ||
| 43 | Channel::LeftFront => ChannelConfig::LeftFront, | ||
| 44 | Channel::RightFront => ChannelConfig::RightFront, | ||
| 45 | Channel::CenterFront => ChannelConfig::CenterFront, | ||
| 46 | Channel::Lfe => ChannelConfig::Lfe, | ||
| 47 | Channel::LeftSurround => ChannelConfig::LeftSurround, | ||
| 48 | Channel::RightSurround => ChannelConfig::RightSurround, | ||
| 49 | Channel::LeftOfCenter => ChannelConfig::LeftOfCenter, | ||
| 50 | Channel::RightOfCenter => ChannelConfig::RightOfCenter, | ||
| 51 | Channel::Surround => ChannelConfig::Surround, | ||
| 52 | Channel::SideLeft => ChannelConfig::SideLeft, | ||
| 53 | Channel::SideRight => ChannelConfig::SideRight, | ||
| 54 | Channel::Top => ChannelConfig::Top, | ||
| 55 | } | ||
| 56 | } | ||
| 57 | } | ||
| 58 | |||
| 59 | /// USB Audio Channel configuration | ||
| 60 | #[repr(u16)] | ||
| 61 | #[non_exhaustive] | ||
| 62 | // #[derive(Copy, Clone, Eq, PartialEq, Debug)] | ||
| 63 | enum ChannelConfig { | ||
| 64 | None = 0x0000, | ||
| 65 | LeftFront = 0x0001, | ||
| 66 | RightFront = 0x0002, | ||
| 67 | CenterFront = 0x0004, | ||
| 68 | Lfe = 0x0008, | ||
| 69 | LeftSurround = 0x0010, | ||
| 70 | RightSurround = 0x0020, | ||
| 71 | LeftOfCenter = 0x0040, | ||
| 72 | RightOfCenter = 0x0080, | ||
| 73 | Surround = 0x0100, | ||
| 74 | SideLeft = 0x0200, | ||
| 75 | SideRight = 0x0400, | ||
| 76 | Top = 0x0800, | ||
| 77 | } | ||
| 78 | |||
| 79 | impl From<ChannelConfig> for u16 { | ||
| 80 | fn from(t: ChannelConfig) -> u16 { | ||
| 81 | t as u16 | ||
| 82 | } | ||
| 83 | } | ||
| 84 | |||
| 85 | /// Feedback period adjustment `bRefresh` [UAC 3.7.2.2] | ||
| 86 | /// | ||
| 87 | /// From the specification: "A new Ff value is available every 2^(10 – P) frames with P ranging from 1 to 9. The | ||
| 88 | /// bRefresh field of the synch standard endpoint descriptor is used to report the exponent (10-P) to the Host." | ||
| 89 | /// | ||
| 90 | /// This means: | ||
| 91 | /// - 512 ms (2^9 frames) to 2 ms (2^1 frames) for USB full-speed | ||
| 92 | /// - 64 ms (2^9 microframes) to 0.25 ms (2^1 microframes) for USB high-speed | ||
| 93 | #[repr(u8)] | ||
| 94 | #[allow(missing_docs)] | ||
| 95 | #[derive(Clone, Copy)] | ||
| 96 | pub enum FeedbackRefresh { | ||
| 97 | Period2Frames = 1, | ||
| 98 | Period4Frames = 2, | ||
| 99 | Period8Frames = 3, | ||
| 100 | Period16Frames = 4, | ||
| 101 | Period32Frames = 5, | ||
| 102 | Period64Frames = 6, | ||
| 103 | Period128Frames = 7, | ||
| 104 | Period256Frames = 8, | ||
| 105 | Period512Frames = 9, | ||
| 106 | } | ||
| 107 | |||
| 108 | impl FeedbackRefresh { | ||
| 109 | /// Gets the number of frames, after which a new feedback frame is returned. | ||
| 110 | pub const fn frame_count(&self) -> usize { | ||
| 111 | 1 << (*self as usize) | ||
| 112 | } | ||
| 113 | } | ||
| 114 | |||
| 115 | /// Audio sample width. | ||
| 116 | /// | ||
| 117 | /// Stored in number of bytes per sample. | ||
| 118 | #[repr(u8)] | ||
| 119 | #[derive(Clone, Copy)] | ||
| 120 | pub enum SampleWidth { | ||
| 121 | /// 16 bit audio | ||
| 122 | Width2Byte = 2, | ||
| 123 | /// 24 bit audio | ||
| 124 | Width3Byte = 3, | ||
| 125 | /// 32 bit audio | ||
| 126 | Width4Byte = 4, | ||
| 127 | } | ||
| 128 | |||
| 129 | impl SampleWidth { | ||
| 130 | /// Get the audio sample resolution in number of bit. | ||
| 131 | pub const fn in_bit(self) -> usize { | ||
| 132 | 8 * self as usize | ||
| 133 | } | ||
| 134 | } | ||
diff --git a/embassy-usb/src/class/uac1/speaker.rs b/embassy-usb/src/class/uac1/speaker.rs new file mode 100644 index 000000000..96456d94a --- /dev/null +++ b/embassy-usb/src/class/uac1/speaker.rs | |||
| @@ -0,0 +1,778 @@ | |||
| 1 | //! USB Audio Class 1.0 - Speaker device | ||
| 2 | //! | ||
| 3 | //! Provides a class with a single audio streaming interface (host to device), | ||
| 4 | //! that advertises itself as a speaker. Includes explicit sample rate feedback. | ||
| 5 | //! | ||
| 6 | //! Various aspects of the audio stream can be configured, for example: | ||
| 7 | //! - sample rate | ||
| 8 | //! - sample resolution | ||
| 9 | //! - audio channel count and assignment | ||
| 10 | //! | ||
| 11 | //! The class provides volume and mute controls for each channel. | ||
| 12 | |||
| 13 | use core::cell::{Cell, RefCell}; | ||
| 14 | use core::future::poll_fn; | ||
| 15 | use core::marker::PhantomData; | ||
| 16 | use core::sync::atomic::{AtomicBool, AtomicU32, Ordering}; | ||
| 17 | use core::task::Poll; | ||
| 18 | |||
| 19 | use embassy_sync::blocking_mutex::CriticalSectionMutex; | ||
| 20 | use embassy_sync::waitqueue::WakerRegistration; | ||
| 21 | use heapless::Vec; | ||
| 22 | |||
| 23 | use super::class_codes::*; | ||
| 24 | use super::terminal_type::TerminalType; | ||
| 25 | use super::{Channel, ChannelConfig, FeedbackRefresh, SampleWidth, MAX_AUDIO_CHANNEL_COUNT, MAX_AUDIO_CHANNEL_INDEX}; | ||
| 26 | use crate::control::{self, InResponse, OutResponse, Recipient, Request, RequestType}; | ||
| 27 | use crate::descriptor::{SynchronizationType, UsageType}; | ||
| 28 | use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut, EndpointType}; | ||
| 29 | use crate::types::InterfaceNumber; | ||
| 30 | use crate::{Builder, Handler}; | ||
| 31 | |||
| 32 | /// Maximum allowed sampling rate (3 bytes) in Hz. | ||
| 33 | const MAX_SAMPLE_RATE_HZ: u32 = 0x7FFFFF; | ||
| 34 | |||
| 35 | /// Arbitrary unique identifier for the input unit. | ||
| 36 | const INPUT_UNIT_ID: u8 = 0x01; | ||
| 37 | |||
| 38 | /// Arbitrary unique identifier for the feature unit. | ||
| 39 | const FEATURE_UNIT_ID: u8 = 0x02; | ||
| 40 | |||
| 41 | /// Arbitrary unique identifier for the output unit. | ||
| 42 | const OUTPUT_UNIT_ID: u8 = 0x03; | ||
| 43 | |||
| 44 | // Volume settings go from -25600 to 0, in steps of 256. | ||
| 45 | // Therefore, the volume settings are 8q8 values in units of dB. | ||
| 46 | const VOLUME_STEPS_PER_DB: i16 = 256; | ||
| 47 | const MIN_VOLUME_DB: i16 = -100; | ||
| 48 | const MAX_VOLUME_DB: i16 = 0; | ||
| 49 | |||
| 50 | // Maximum number of supported discrete sample rates. | ||
| 51 | const MAX_SAMPLE_RATE_COUNT: usize = 10; | ||
| 52 | |||
| 53 | /// The volume of an audio channel. | ||
| 54 | #[derive(Debug, Clone, Copy)] | ||
| 55 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 56 | pub enum Volume { | ||
| 57 | /// The channel is muted. | ||
| 58 | Muted, | ||
| 59 | /// The channel volume in dB. Ranges from `MIN_VOLUME_DB` (quietest) to `MAX_VOLUME_DB` (loudest). | ||
| 60 | DeciBel(f32), | ||
| 61 | } | ||
| 62 | |||
| 63 | /// Internal state for the USB Audio Class. | ||
| 64 | pub struct State<'d> { | ||
| 65 | control: Option<Control<'d>>, | ||
| 66 | shared: SharedControl<'d>, | ||
| 67 | } | ||
| 68 | |||
| 69 | impl<'d> Default for State<'d> { | ||
| 70 | fn default() -> Self { | ||
| 71 | Self::new() | ||
| 72 | } | ||
| 73 | } | ||
| 74 | |||
| 75 | impl<'d> State<'d> { | ||
| 76 | /// Create a new `State`. | ||
| 77 | pub fn new() -> Self { | ||
| 78 | Self { | ||
| 79 | control: None, | ||
| 80 | shared: SharedControl::default(), | ||
| 81 | } | ||
| 82 | } | ||
| 83 | } | ||
| 84 | |||
| 85 | /// Implementation of the USB audio class 1.0. | ||
| 86 | pub struct Speaker<'d, D: Driver<'d>> { | ||
| 87 | phantom: PhantomData<&'d D>, | ||
| 88 | } | ||
| 89 | |||
| 90 | impl<'d, D: Driver<'d>> Speaker<'d, D> { | ||
| 91 | /// Creates a new [`Speaker`] device, split into a stream, feedback, and a control change notifier. | ||
| 92 | /// | ||
| 93 | /// The packet size should be chosen, based on the expected transfer size of samples per (micro)frame. | ||
| 94 | /// For example, a stereo stream at 32 bit resolution and 48 kHz sample rate yields packets of 384 byte for | ||
| 95 | /// full-speed USB (1 ms frame interval) or 48 byte for high-speed USB (125 us microframe interval). | ||
| 96 | /// When using feedback, the packet size varies and thus, the `max_packet_size` should be increased (e.g. to double). | ||
| 97 | /// | ||
| 98 | /// # Arguments | ||
| 99 | /// | ||
| 100 | /// * `builder` - The builder for the class. | ||
| 101 | /// * `state` - The internal state of the class. | ||
| 102 | /// * `max_packet_size` - The maximum packet size per (micro)frame. | ||
| 103 | /// * `resolution` - The audio sample resolution. | ||
| 104 | /// * `sample_rates_hz` - The supported sample rates in Hz. | ||
| 105 | /// * `channels` - The advertised audio channels (up to 12). Entries must be unique, or this function panics. | ||
| 106 | /// * `feedback_refresh_period` - The refresh period for the feedback value. | ||
| 107 | pub fn new( | ||
| 108 | builder: &mut Builder<'d, D>, | ||
| 109 | state: &'d mut State<'d>, | ||
| 110 | max_packet_size: u16, | ||
| 111 | resolution: SampleWidth, | ||
| 112 | sample_rates_hz: &[u32], | ||
| 113 | channels: &'d [Channel], | ||
| 114 | feedback_refresh_period: FeedbackRefresh, | ||
| 115 | ) -> (Stream<'d, D>, Feedback<'d, D>, ControlMonitor<'d>) { | ||
| 116 | // The class and subclass fields of the IAD aren't required to match the class and subclass fields of | ||
| 117 | // the interfaces in the interface collection that the IAD describes. Microsoft recommends that | ||
| 118 | // the first interface of the collection has class and subclass fields that match the class and | ||
| 119 | // subclass fields of the IAD. | ||
| 120 | let mut func = builder.function(USB_AUDIO_CLASS, USB_AUDIOCONTROL_SUBCLASS, PROTOCOL_NONE); | ||
| 121 | |||
| 122 | // Audio control interface (mandatory) [UAC 4.3.1] | ||
| 123 | let mut interface = func.interface(); | ||
| 124 | let control_interface = interface.interface_number().into(); | ||
| 125 | let streaming_interface = u8::from(control_interface) + 1; | ||
| 126 | let mut alt = interface.alt_setting(USB_AUDIO_CLASS, USB_AUDIOCONTROL_SUBCLASS, PROTOCOL_NONE, None); | ||
| 127 | |||
| 128 | // Terminal topology: | ||
| 129 | // Input terminal (receives audio stream) -> Feature Unit (mute and volume) -> Output terminal (e.g. towards speaker) | ||
| 130 | |||
| 131 | // ======================================= | ||
| 132 | // Input Terminal Descriptor [UAC 3.3.2.1] | ||
| 133 | // Audio input | ||
| 134 | let terminal_type: u16 = TerminalType::UsbStreaming.into(); | ||
| 135 | |||
| 136 | // Assemble channel configuration field | ||
| 137 | let mut channel_config: u16 = ChannelConfig::None.into(); | ||
| 138 | for channel in channels { | ||
| 139 | let channel: u16 = channel.get_channel_config().into(); | ||
| 140 | |||
| 141 | if channel_config & channel != 0 { | ||
| 142 | panic!("Invalid channel config, duplicate channel {}.", channel); | ||
| 143 | } | ||
| 144 | channel_config |= channel; | ||
| 145 | } | ||
| 146 | |||
| 147 | let input_terminal_descriptor = [ | ||
| 148 | INPUT_TERMINAL, // bDescriptorSubtype | ||
| 149 | INPUT_UNIT_ID, // bTerminalID | ||
| 150 | terminal_type as u8, | ||
| 151 | (terminal_type >> 8) as u8, // wTerminalType | ||
| 152 | 0x00, // bAssocTerminal (none) | ||
| 153 | channels.len() as u8, // bNrChannels | ||
| 154 | channel_config as u8, | ||
| 155 | (channel_config >> 8) as u8, // wChannelConfig | ||
| 156 | 0x00, // iChannelNames (none) | ||
| 157 | 0x00, // iTerminal (none) | ||
| 158 | ]; | ||
| 159 | |||
| 160 | // ======================================== | ||
| 161 | // Output Terminal Descriptor [UAC 4.3.2.2] | ||
| 162 | // Speaker output | ||
| 163 | let terminal_type: u16 = TerminalType::OutSpeaker.into(); | ||
| 164 | let output_terminal_descriptor = [ | ||
| 165 | OUTPUT_TERMINAL, // bDescriptorSubtype | ||
| 166 | OUTPUT_UNIT_ID, // bTerminalID | ||
| 167 | terminal_type as u8, | ||
| 168 | (terminal_type >> 8) as u8, // wTerminalType | ||
| 169 | 0x00, // bAssocTerminal (none) | ||
| 170 | FEATURE_UNIT_ID, // bSourceID (the feature unit) | ||
| 171 | 0x00, // iTerminal (none) | ||
| 172 | ]; | ||
| 173 | |||
| 174 | // ===================================== | ||
| 175 | // Feature Unit Descriptor [UAC 4.3.2.5] | ||
| 176 | // Mute and volume control | ||
| 177 | let controls = MUTE_CONTROL | VOLUME_CONTROL; | ||
| 178 | |||
| 179 | const FEATURE_UNIT_DESCRIPTOR_SIZE: usize = 5; | ||
| 180 | let mut feature_unit_descriptor: Vec<u8, { FEATURE_UNIT_DESCRIPTOR_SIZE + MAX_AUDIO_CHANNEL_COUNT + 1 }> = | ||
| 181 | Vec::from_slice(&[ | ||
| 182 | FEATURE_UNIT, // bDescriptorSubtype (Feature Unit) | ||
| 183 | FEATURE_UNIT_ID, // bUnitID | ||
| 184 | INPUT_UNIT_ID, // bSourceID | ||
| 185 | 1, // bControlSize (one byte per control) | ||
| 186 | FU_CONTROL_UNDEFINED, // Master controls (disabled, use only per-channel control) | ||
| 187 | ]) | ||
| 188 | .unwrap(); | ||
| 189 | |||
| 190 | // Add per-channel controls | ||
| 191 | for _channel in channels { | ||
| 192 | feature_unit_descriptor.push(controls).unwrap(); | ||
| 193 | } | ||
| 194 | feature_unit_descriptor.push(0x00).unwrap(); // iFeature (none) | ||
| 195 | |||
| 196 | // =============================================== | ||
| 197 | // Format desciptor [UAC 4.5.3] | ||
| 198 | // Used later, for operational streaming interface | ||
| 199 | let mut format_descriptor: Vec<u8, { 6 + 3 * MAX_SAMPLE_RATE_COUNT }> = Vec::from_slice(&[ | ||
| 200 | FORMAT_TYPE, // bDescriptorSubtype | ||
| 201 | FORMAT_TYPE_I, // bFormatType | ||
| 202 | channels.len() as u8, // bNrChannels | ||
| 203 | resolution as u8, // bSubframeSize | ||
| 204 | resolution.in_bit() as u8, // bBitResolution | ||
| 205 | ]) | ||
| 206 | .unwrap(); | ||
| 207 | |||
| 208 | format_descriptor.push(sample_rates_hz.len() as u8).unwrap(); | ||
| 209 | |||
| 210 | for sample_rate_hz in sample_rates_hz { | ||
| 211 | assert!(*sample_rate_hz <= MAX_SAMPLE_RATE_HZ); | ||
| 212 | format_descriptor.push((sample_rate_hz & 0xFF) as u8).unwrap(); | ||
| 213 | format_descriptor.push(((sample_rate_hz >> 8) & 0xFF) as u8).unwrap(); | ||
| 214 | format_descriptor.push(((sample_rate_hz >> 16) & 0xFF) as u8).unwrap(); | ||
| 215 | } | ||
| 216 | |||
| 217 | // ================================================== | ||
| 218 | // Class-specific AC Interface Descriptor [UAC 4.3.2] | ||
| 219 | const DESCRIPTOR_HEADER_SIZE: usize = 2; | ||
| 220 | const INTERFACE_DESCRIPTOR_SIZE: usize = 7; | ||
| 221 | |||
| 222 | let mut total_descriptor_length = 0; | ||
| 223 | |||
| 224 | for size in [ | ||
| 225 | INTERFACE_DESCRIPTOR_SIZE, | ||
| 226 | input_terminal_descriptor.len(), | ||
| 227 | feature_unit_descriptor.len(), | ||
| 228 | output_terminal_descriptor.len(), | ||
| 229 | ] { | ||
| 230 | total_descriptor_length += size + DESCRIPTOR_HEADER_SIZE; | ||
| 231 | } | ||
| 232 | |||
| 233 | let interface_descriptor: [u8; INTERFACE_DESCRIPTOR_SIZE] = [ | ||
| 234 | HEADER_SUBTYPE, // bDescriptorSubtype (Header) | ||
| 235 | ADC_VERSION as u8, | ||
| 236 | (ADC_VERSION >> 8) as u8, // bcdADC | ||
| 237 | total_descriptor_length as u8, | ||
| 238 | (total_descriptor_length >> 8) as u8, // wTotalLength | ||
| 239 | 0x01, // bInCollection (1 streaming interface) | ||
| 240 | streaming_interface, // baInterfaceNr | ||
| 241 | ]; | ||
| 242 | |||
| 243 | alt.descriptor(CS_INTERFACE, &interface_descriptor); | ||
| 244 | alt.descriptor(CS_INTERFACE, &input_terminal_descriptor); | ||
| 245 | alt.descriptor(CS_INTERFACE, &feature_unit_descriptor); | ||
| 246 | alt.descriptor(CS_INTERFACE, &output_terminal_descriptor); | ||
| 247 | |||
| 248 | // ===================================================== | ||
| 249 | // Audio streaming interface, zero-bandwidth [UAC 4.5.1] | ||
| 250 | let mut interface = func.interface(); | ||
| 251 | let alt = interface.alt_setting(USB_AUDIO_CLASS, USB_AUDIOSTREAMING_SUBCLASS, PROTOCOL_NONE, None); | ||
| 252 | drop(alt); | ||
| 253 | |||
| 254 | // ================================================== | ||
| 255 | // Audio streaming interface, operational [UAC 4.5.1] | ||
| 256 | let mut alt = interface.alt_setting(USB_AUDIO_CLASS, USB_AUDIOSTREAMING_SUBCLASS, PROTOCOL_NONE, None); | ||
| 257 | |||
| 258 | alt.descriptor( | ||
| 259 | CS_INTERFACE, | ||
| 260 | &[ | ||
| 261 | AS_GENERAL, // bDescriptorSubtype | ||
| 262 | INPUT_UNIT_ID, // bTerminalLink | ||
| 263 | 0x00, // bDelay (none) | ||
| 264 | PCM as u8, | ||
| 265 | (PCM >> 8) as u8, // wFormatTag (PCM format) | ||
| 266 | ], | ||
| 267 | ); | ||
| 268 | |||
| 269 | alt.descriptor(CS_INTERFACE, &format_descriptor); | ||
| 270 | |||
| 271 | let streaming_endpoint = alt.alloc_endpoint_out(EndpointType::Isochronous, max_packet_size, 1); | ||
| 272 | let feedback_endpoint = alt.alloc_endpoint_in( | ||
| 273 | EndpointType::Isochronous, | ||
| 274 | 4, // Feedback packets are 24 bit (10.14 format). | ||
| 275 | 1, | ||
| 276 | ); | ||
| 277 | |||
| 278 | // Write the descriptor for the streaming endpoint, after knowing the address of the feedback endpoint. | ||
| 279 | alt.endpoint_descriptor( | ||
| 280 | streaming_endpoint.info(), | ||
| 281 | SynchronizationType::Asynchronous, | ||
| 282 | UsageType::DataEndpoint, | ||
| 283 | &[ | ||
| 284 | 0x00, // bRefresh (0) | ||
| 285 | feedback_endpoint.info().addr.into(), // bSynchAddress (the feedback endpoint) | ||
| 286 | ], | ||
| 287 | ); | ||
| 288 | |||
| 289 | alt.descriptor( | ||
| 290 | CS_ENDPOINT, | ||
| 291 | &[ | ||
| 292 | AS_GENERAL, // bDescriptorSubtype (General) | ||
| 293 | SAMPLING_FREQ_CONTROL, // bmAttributes (support sampling frequency control) | ||
| 294 | 0x02, // bLockDelayUnits (PCM) | ||
| 295 | 0x0000 as u8, | ||
| 296 | (0x0000 >> 8) as u8, // wLockDelay (0) | ||
| 297 | ], | ||
| 298 | ); | ||
| 299 | |||
| 300 | // Write the feedback endpoint descriptor after the streaming endpoint descriptor | ||
| 301 | // This is demanded by the USB audio class specification. | ||
| 302 | alt.endpoint_descriptor( | ||
| 303 | feedback_endpoint.info(), | ||
| 304 | SynchronizationType::NoSynchronization, | ||
| 305 | UsageType::FeedbackEndpoint, | ||
| 306 | &[ | ||
| 307 | feedback_refresh_period as u8, // bRefresh | ||
| 308 | 0x00, // bSynchAddress (none) | ||
| 309 | ], | ||
| 310 | ); | ||
| 311 | |||
| 312 | // Free up the builder. | ||
| 313 | drop(func); | ||
| 314 | |||
| 315 | // Store channel information | ||
| 316 | state.shared.channels = channels; | ||
| 317 | |||
| 318 | state.control = Some(Control { | ||
| 319 | shared: &state.shared, | ||
| 320 | streaming_endpoint_address: streaming_endpoint.info().addr.into(), | ||
| 321 | control_interface_number: control_interface, | ||
| 322 | }); | ||
| 323 | |||
| 324 | builder.handler(state.control.as_mut().unwrap()); | ||
| 325 | |||
| 326 | let control = &state.shared; | ||
| 327 | |||
| 328 | ( | ||
| 329 | Stream { streaming_endpoint }, | ||
| 330 | Feedback { feedback_endpoint }, | ||
| 331 | ControlMonitor { shared: control }, | ||
| 332 | ) | ||
| 333 | } | ||
| 334 | } | ||
| 335 | |||
| 336 | /// Audio settings for the feature unit. | ||
| 337 | /// | ||
| 338 | /// Contains volume and mute control. | ||
| 339 | #[derive(Clone, Copy, Debug)] | ||
| 340 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 341 | pub struct AudioSettings { | ||
| 342 | /// Channel mute states. | ||
| 343 | muted: [bool; MAX_AUDIO_CHANNEL_COUNT], | ||
| 344 | /// Channel volume levels in 8.8 format (in dB). | ||
| 345 | volume_8q8_db: [i16; MAX_AUDIO_CHANNEL_COUNT], | ||
| 346 | } | ||
| 347 | |||
| 348 | impl Default for AudioSettings { | ||
| 349 | fn default() -> Self { | ||
| 350 | AudioSettings { | ||
| 351 | muted: [true; MAX_AUDIO_CHANNEL_COUNT], | ||
| 352 | volume_8q8_db: [MAX_VOLUME_DB * VOLUME_STEPS_PER_DB; MAX_AUDIO_CHANNEL_COUNT], | ||
| 353 | } | ||
| 354 | } | ||
| 355 | } | ||
| 356 | |||
| 357 | struct Control<'d> { | ||
| 358 | control_interface_number: InterfaceNumber, | ||
| 359 | streaming_endpoint_address: u8, | ||
| 360 | shared: &'d SharedControl<'d>, | ||
| 361 | } | ||
| 362 | |||
| 363 | /// Shared data between [`Control`] and the [`Speaker`] class. | ||
| 364 | struct SharedControl<'d> { | ||
| 365 | /// The collection of audio settings (volumes, mute states). | ||
| 366 | audio_settings: CriticalSectionMutex<Cell<AudioSettings>>, | ||
| 367 | |||
| 368 | /// Channel assignments. | ||
| 369 | channels: &'d [Channel], | ||
| 370 | |||
| 371 | /// The audio sample rate in Hz. | ||
| 372 | sample_rate_hz: AtomicU32, | ||
| 373 | |||
| 374 | // Notification mechanism. | ||
| 375 | waker: RefCell<WakerRegistration>, | ||
| 376 | changed: AtomicBool, | ||
| 377 | } | ||
| 378 | |||
| 379 | impl<'d> Default for SharedControl<'d> { | ||
| 380 | fn default() -> Self { | ||
| 381 | SharedControl { | ||
| 382 | audio_settings: CriticalSectionMutex::new(Cell::new(AudioSettings::default())), | ||
| 383 | channels: &[], | ||
| 384 | sample_rate_hz: AtomicU32::new(0), | ||
| 385 | waker: RefCell::new(WakerRegistration::new()), | ||
| 386 | changed: AtomicBool::new(false), | ||
| 387 | } | ||
| 388 | } | ||
| 389 | } | ||
| 390 | |||
| 391 | impl<'d> SharedControl<'d> { | ||
| 392 | async fn changed(&self) { | ||
| 393 | poll_fn(|context| { | ||
| 394 | if self.changed.load(Ordering::Relaxed) { | ||
| 395 | self.changed.store(false, Ordering::Relaxed); | ||
| 396 | Poll::Ready(()) | ||
| 397 | } else { | ||
| 398 | self.waker.borrow_mut().register(context.waker()); | ||
| 399 | Poll::Pending | ||
| 400 | } | ||
| 401 | }) | ||
| 402 | .await; | ||
| 403 | } | ||
| 404 | } | ||
| 405 | |||
| 406 | /// Used for reading audio frames. | ||
| 407 | pub struct Stream<'d, D: Driver<'d>> { | ||
| 408 | streaming_endpoint: D::EndpointOut, | ||
| 409 | } | ||
| 410 | |||
| 411 | impl<'d, D: Driver<'d>> Stream<'d, D> { | ||
| 412 | /// Reads a single packet from the OUT endpoint | ||
| 413 | pub async fn read_packet(&mut self, data: &mut [u8]) -> Result<usize, EndpointError> { | ||
| 414 | self.streaming_endpoint.read(data).await | ||
| 415 | } | ||
| 416 | |||
| 417 | /// Waits for the USB host to enable this interface | ||
| 418 | pub async fn wait_connection(&mut self) { | ||
| 419 | self.streaming_endpoint.wait_enabled().await; | ||
| 420 | } | ||
| 421 | } | ||
| 422 | |||
| 423 | /// Used for writing sample rate information over the feedback endpoint. | ||
| 424 | pub struct Feedback<'d, D: Driver<'d>> { | ||
| 425 | feedback_endpoint: D::EndpointIn, | ||
| 426 | } | ||
| 427 | |||
| 428 | impl<'d, D: Driver<'d>> Feedback<'d, D> { | ||
| 429 | /// Writes a single packet into the IN endpoint. | ||
| 430 | pub async fn write_packet(&mut self, data: &[u8]) -> Result<(), EndpointError> { | ||
| 431 | self.feedback_endpoint.write(data).await | ||
| 432 | } | ||
| 433 | |||
| 434 | /// Waits for the USB host to enable this interface. | ||
| 435 | pub async fn wait_connection(&mut self) { | ||
| 436 | self.feedback_endpoint.wait_enabled().await; | ||
| 437 | } | ||
| 438 | } | ||
| 439 | |||
| 440 | /// Control status change monitor | ||
| 441 | /// | ||
| 442 | /// Await [`ControlMonitor::changed`] for being notified of configuration changes. Afterwards, the updated | ||
| 443 | /// configuration settings can be read with [`ControlMonitor::volume`] and [`ControlMonitor::sample_rate_hz`]. | ||
| 444 | pub struct ControlMonitor<'d> { | ||
| 445 | shared: &'d SharedControl<'d>, | ||
| 446 | } | ||
| 447 | |||
| 448 | impl<'d> ControlMonitor<'d> { | ||
| 449 | fn audio_settings(&self) -> AudioSettings { | ||
| 450 | let audio_settings = self.shared.audio_settings.lock(|x| x.get()); | ||
| 451 | |||
| 452 | audio_settings | ||
| 453 | } | ||
| 454 | |||
| 455 | fn get_logical_channel(&self, search_channel: Channel) -> Option<usize> { | ||
| 456 | let index = self.shared.channels.iter().position(|&c| c == search_channel)?; | ||
| 457 | |||
| 458 | // The logical channels start at one (zero is the master channel). | ||
| 459 | Some(index + 1) | ||
| 460 | } | ||
| 461 | |||
| 462 | /// Get the volume of a selected channel. | ||
| 463 | pub fn volume(&self, channel: Channel) -> Option<Volume> { | ||
| 464 | let channel_index = self.get_logical_channel(channel)?; | ||
| 465 | |||
| 466 | if self.audio_settings().muted[channel_index] { | ||
| 467 | return Some(Volume::Muted); | ||
| 468 | } | ||
| 469 | |||
| 470 | Some(Volume::DeciBel( | ||
| 471 | (self.audio_settings().volume_8q8_db[channel_index] as f32) / 256.0f32, | ||
| 472 | )) | ||
| 473 | } | ||
| 474 | |||
| 475 | /// Get the streaming endpoint's sample rate in Hz. | ||
| 476 | pub fn sample_rate_hz(&self) -> u32 { | ||
| 477 | self.shared.sample_rate_hz.load(Ordering::Relaxed) | ||
| 478 | } | ||
| 479 | |||
| 480 | /// Return a future for when the control settings change. | ||
| 481 | pub async fn changed(&self) { | ||
| 482 | self.shared.changed().await; | ||
| 483 | } | ||
| 484 | } | ||
| 485 | |||
| 486 | impl<'d> Control<'d> { | ||
| 487 | fn changed(&mut self) { | ||
| 488 | self.shared.changed.store(true, Ordering::Relaxed); | ||
| 489 | self.shared.waker.borrow_mut().wake(); | ||
| 490 | } | ||
| 491 | |||
| 492 | fn interface_set_mute_state( | ||
| 493 | &mut self, | ||
| 494 | audio_settings: &mut AudioSettings, | ||
| 495 | channel_index: u8, | ||
| 496 | data: &[u8], | ||
| 497 | ) -> OutResponse { | ||
| 498 | let mute_state = data[0] != 0; | ||
| 499 | |||
| 500 | match channel_index as usize { | ||
| 501 | ..=MAX_AUDIO_CHANNEL_INDEX => { | ||
| 502 | audio_settings.muted[channel_index as usize] = mute_state; | ||
| 503 | } | ||
| 504 | _ => { | ||
| 505 | debug!("Failed to set channel {} mute state: {}", channel_index, mute_state); | ||
| 506 | return OutResponse::Rejected; | ||
| 507 | } | ||
| 508 | } | ||
| 509 | |||
| 510 | debug!("Set channel {} mute state: {}", channel_index, mute_state); | ||
| 511 | OutResponse::Accepted | ||
| 512 | } | ||
| 513 | |||
| 514 | fn interface_set_volume( | ||
| 515 | &mut self, | ||
| 516 | audio_settings: &mut AudioSettings, | ||
| 517 | channel_index: u8, | ||
| 518 | data: &[u8], | ||
| 519 | ) -> OutResponse { | ||
| 520 | let volume = i16::from_ne_bytes(data[..2].try_into().expect("Failed to read volume.")); | ||
| 521 | |||
| 522 | match channel_index as usize { | ||
| 523 | ..=MAX_AUDIO_CHANNEL_INDEX => { | ||
| 524 | audio_settings.volume_8q8_db[channel_index as usize] = volume; | ||
| 525 | } | ||
| 526 | _ => { | ||
| 527 | debug!("Failed to set channel {} volume: {}", channel_index, volume); | ||
| 528 | return OutResponse::Rejected; | ||
| 529 | } | ||
| 530 | } | ||
| 531 | |||
| 532 | debug!("Set channel {} volume: {}", channel_index, volume); | ||
| 533 | OutResponse::Accepted | ||
| 534 | } | ||
| 535 | |||
| 536 | fn interface_set_request(&mut self, req: control::Request, data: &[u8]) -> Option<OutResponse> { | ||
| 537 | let interface_number = req.index as u8; | ||
| 538 | let entity_index = (req.index >> 8) as u8; | ||
| 539 | let channel_index = req.value as u8; | ||
| 540 | let control_unit = (req.value >> 8) as u8; | ||
| 541 | |||
| 542 | if interface_number != self.control_interface_number.into() { | ||
| 543 | debug!("Unhandled interface set request for interface {}", interface_number); | ||
| 544 | return None; | ||
| 545 | } | ||
| 546 | |||
| 547 | if entity_index != FEATURE_UNIT_ID { | ||
| 548 | debug!("Unsupported interface set request for entity {}", entity_index); | ||
| 549 | return Some(OutResponse::Rejected); | ||
| 550 | } | ||
| 551 | |||
| 552 | if req.request != SET_CUR { | ||
| 553 | debug!("Unsupported interface set request type {}", req.request); | ||
| 554 | return Some(OutResponse::Rejected); | ||
| 555 | } | ||
| 556 | |||
| 557 | let mut audio_settings = self.shared.audio_settings.lock(|x| x.get()); | ||
| 558 | let response = match control_unit { | ||
| 559 | MUTE_CONTROL => self.interface_set_mute_state(&mut audio_settings, channel_index, data), | ||
| 560 | VOLUME_CONTROL => self.interface_set_volume(&mut audio_settings, channel_index, data), | ||
| 561 | _ => OutResponse::Rejected, | ||
| 562 | }; | ||
| 563 | |||
| 564 | if response == OutResponse::Rejected { | ||
| 565 | return Some(response); | ||
| 566 | } | ||
| 567 | |||
| 568 | // Store updated settings | ||
| 569 | self.shared.audio_settings.lock(|x| x.set(audio_settings)); | ||
| 570 | |||
| 571 | self.changed(); | ||
| 572 | |||
| 573 | Some(OutResponse::Accepted) | ||
| 574 | } | ||
| 575 | |||
| 576 | fn endpoint_set_request(&mut self, req: control::Request, data: &[u8]) -> Option<OutResponse> { | ||
| 577 | let control_selector = (req.value >> 8) as u8; | ||
| 578 | let endpoint_address = req.index as u8; | ||
| 579 | |||
| 580 | if endpoint_address != self.streaming_endpoint_address { | ||
| 581 | debug!( | ||
| 582 | "Unhandled endpoint set request for endpoint {} and control {} with data {}", | ||
| 583 | endpoint_address, control_selector, data | ||
| 584 | ); | ||
| 585 | return None; | ||
| 586 | } | ||
| 587 | |||
| 588 | if control_selector != SAMPLING_FREQ_CONTROL { | ||
| 589 | debug!( | ||
| 590 | "Unsupported endpoint set request for control selector {}", | ||
| 591 | control_selector | ||
| 592 | ); | ||
| 593 | return Some(OutResponse::Rejected); | ||
| 594 | } | ||
| 595 | |||
| 596 | let sample_rate_hz: u32 = (data[0] as u32) | (data[1] as u32) << 8 | (data[2] as u32) << 16; | ||
| 597 | self.shared.sample_rate_hz.store(sample_rate_hz, Ordering::Relaxed); | ||
| 598 | |||
| 599 | debug!("Set endpoint {} sample rate to {} Hz", endpoint_address, sample_rate_hz); | ||
| 600 | |||
| 601 | self.changed(); | ||
| 602 | |||
| 603 | Some(OutResponse::Accepted) | ||
| 604 | } | ||
| 605 | |||
| 606 | fn interface_get_request<'r>(&'r mut self, req: Request, buf: &'r mut [u8]) -> Option<InResponse<'r>> { | ||
| 607 | let interface_number = req.index as u8; | ||
| 608 | let entity_index = (req.index >> 8) as u8; | ||
| 609 | let channel_index = req.value as u8; | ||
| 610 | let control_unit = (req.value >> 8) as u8; | ||
| 611 | |||
| 612 | if interface_number != self.control_interface_number.into() { | ||
| 613 | debug!("Unhandled interface get request for interface {}.", interface_number); | ||
| 614 | return None; | ||
| 615 | } | ||
| 616 | |||
| 617 | if entity_index != FEATURE_UNIT_ID { | ||
| 618 | // Only this function unit can be handled at the moment. | ||
| 619 | debug!("Unsupported interface get request for entity {}.", entity_index); | ||
| 620 | return Some(InResponse::Rejected); | ||
| 621 | } | ||
| 622 | |||
| 623 | let audio_settings = self.shared.audio_settings.lock(|x| x.get()); | ||
| 624 | |||
| 625 | match req.request { | ||
| 626 | GET_CUR => match control_unit { | ||
| 627 | VOLUME_CONTROL => { | ||
| 628 | let volume: i16; | ||
| 629 | |||
| 630 | match channel_index as usize { | ||
| 631 | ..=MAX_AUDIO_CHANNEL_INDEX => volume = audio_settings.volume_8q8_db[channel_index as usize], | ||
| 632 | _ => return Some(InResponse::Rejected), | ||
| 633 | } | ||
| 634 | |||
| 635 | buf[0] = volume as u8; | ||
| 636 | buf[1] = (volume >> 8) as u8; | ||
| 637 | |||
| 638 | debug!("Got channel {} volume: {}.", channel_index, volume); | ||
| 639 | return Some(InResponse::Accepted(&buf[..2])); | ||
| 640 | } | ||
| 641 | MUTE_CONTROL => { | ||
| 642 | let mute_state: bool; | ||
| 643 | |||
| 644 | match channel_index as usize { | ||
| 645 | ..=MAX_AUDIO_CHANNEL_INDEX => mute_state = audio_settings.muted[channel_index as usize], | ||
| 646 | _ => return Some(InResponse::Rejected), | ||
| 647 | } | ||
| 648 | |||
| 649 | buf[0] = mute_state.into(); | ||
| 650 | debug!("Got channel {} mute state: {}.", channel_index, mute_state); | ||
| 651 | return Some(InResponse::Accepted(&buf[..1])); | ||
| 652 | } | ||
| 653 | _ => return Some(InResponse::Rejected), | ||
| 654 | }, | ||
| 655 | GET_MIN => match control_unit { | ||
| 656 | VOLUME_CONTROL => { | ||
| 657 | let min_volume = MIN_VOLUME_DB * VOLUME_STEPS_PER_DB; | ||
| 658 | buf[0] = min_volume as u8; | ||
| 659 | buf[1] = (min_volume >> 8) as u8; | ||
| 660 | return Some(InResponse::Accepted(&buf[..2])); | ||
| 661 | } | ||
| 662 | _ => return Some(InResponse::Rejected), | ||
| 663 | }, | ||
| 664 | GET_MAX => match control_unit { | ||
| 665 | VOLUME_CONTROL => { | ||
| 666 | let max_volume = MAX_VOLUME_DB * VOLUME_STEPS_PER_DB; | ||
| 667 | buf[0] = max_volume as u8; | ||
| 668 | buf[1] = (max_volume >> 8) as u8; | ||
| 669 | return Some(InResponse::Accepted(&buf[..2])); | ||
| 670 | } | ||
| 671 | _ => return Some(InResponse::Rejected), | ||
| 672 | }, | ||
| 673 | GET_RES => match control_unit { | ||
| 674 | VOLUME_CONTROL => { | ||
| 675 | buf[0] = VOLUME_STEPS_PER_DB as u8; | ||
| 676 | buf[1] = (VOLUME_STEPS_PER_DB >> 8) as u8; | ||
| 677 | return Some(InResponse::Accepted(&buf[..2])); | ||
| 678 | } | ||
| 679 | _ => return Some(InResponse::Rejected), | ||
| 680 | }, | ||
| 681 | _ => return Some(InResponse::Rejected), | ||
| 682 | } | ||
| 683 | } | ||
| 684 | |||
| 685 | fn endpoint_get_request<'r>(&'r mut self, req: Request, buf: &'r mut [u8]) -> Option<InResponse<'r>> { | ||
| 686 | let control_selector = (req.value >> 8) as u8; | ||
| 687 | let endpoint_address = req.index as u8; | ||
| 688 | |||
| 689 | if endpoint_address != self.streaming_endpoint_address { | ||
| 690 | debug!("Unhandled endpoint get request for endpoint {}.", endpoint_address); | ||
| 691 | return None; | ||
| 692 | } | ||
| 693 | |||
| 694 | if control_selector != SAMPLING_FREQ_CONTROL as u8 { | ||
| 695 | debug!( | ||
| 696 | "Unsupported endpoint get request for control selector {}.", | ||
| 697 | control_selector | ||
| 698 | ); | ||
| 699 | return Some(InResponse::Rejected); | ||
| 700 | } | ||
| 701 | |||
| 702 | let sample_rate_hz = self.shared.sample_rate_hz.load(Ordering::Relaxed); | ||
| 703 | |||
| 704 | buf[0] = (sample_rate_hz & 0xFF) as u8; | ||
| 705 | buf[1] = ((sample_rate_hz >> 8) & 0xFF) as u8; | ||
| 706 | buf[2] = ((sample_rate_hz >> 16) & 0xFF) as u8; | ||
| 707 | |||
| 708 | Some(InResponse::Accepted(&buf[..3])) | ||
| 709 | } | ||
| 710 | } | ||
| 711 | |||
| 712 | impl<'d> Handler for Control<'d> { | ||
| 713 | /// Called when the USB device has been enabled or disabled. | ||
| 714 | fn enabled(&mut self, enabled: bool) { | ||
| 715 | debug!("USB device enabled: {}", enabled); | ||
| 716 | } | ||
| 717 | |||
| 718 | /// Called when the host has set the address of the device to `addr`. | ||
| 719 | fn addressed(&mut self, addr: u8) { | ||
| 720 | debug!("Host set address to: {}", addr); | ||
| 721 | } | ||
| 722 | |||
| 723 | /// Called when the host has enabled or disabled the configuration of the device. | ||
| 724 | fn configured(&mut self, configured: bool) { | ||
| 725 | debug!("USB device configured: {}", configured); | ||
| 726 | } | ||
| 727 | |||
| 728 | /// Called when remote wakeup feature is enabled or disabled. | ||
| 729 | fn remote_wakeup_enabled(&mut self, enabled: bool) { | ||
| 730 | debug!("USB remote wakeup enabled: {}", enabled); | ||
| 731 | } | ||
| 732 | |||
| 733 | /// Called when a "set alternate setting" control request is done on the interface. | ||
| 734 | fn set_alternate_setting(&mut self, iface: InterfaceNumber, alternate_setting: u8) { | ||
| 735 | debug!( | ||
| 736 | "USB set interface number {} to alt setting {}.", | ||
| 737 | iface, alternate_setting | ||
| 738 | ); | ||
| 739 | } | ||
| 740 | |||
| 741 | /// Called after a USB reset after the bus reset sequence is complete. | ||
| 742 | fn reset(&mut self) { | ||
| 743 | let shared = self.shared; | ||
| 744 | shared.audio_settings.lock(|x| x.set(AudioSettings::default())); | ||
| 745 | |||
| 746 | shared.changed.store(true, Ordering::Relaxed); | ||
| 747 | shared.waker.borrow_mut().wake(); | ||
| 748 | } | ||
| 749 | |||
| 750 | /// Called when the bus has entered or exited the suspend state. | ||
| 751 | fn suspended(&mut self, suspended: bool) { | ||
| 752 | debug!("USB device suspended: {}", suspended); | ||
| 753 | } | ||
| 754 | |||
| 755 | // Handle control set requests. | ||
| 756 | fn control_out(&mut self, req: control::Request, data: &[u8]) -> Option<OutResponse> { | ||
| 757 | match req.request_type { | ||
| 758 | RequestType::Class => match req.recipient { | ||
| 759 | Recipient::Interface => self.interface_set_request(req, data), | ||
| 760 | Recipient::Endpoint => self.endpoint_set_request(req, data), | ||
| 761 | _ => Some(OutResponse::Rejected), | ||
| 762 | }, | ||
| 763 | _ => None, | ||
| 764 | } | ||
| 765 | } | ||
| 766 | |||
| 767 | // Handle control get requests. | ||
| 768 | fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> Option<InResponse<'a>> { | ||
| 769 | match req.request_type { | ||
| 770 | RequestType::Class => match req.recipient { | ||
| 771 | Recipient::Interface => self.interface_get_request(req, buf), | ||
| 772 | Recipient::Endpoint => self.endpoint_get_request(req, buf), | ||
| 773 | _ => None, | ||
| 774 | }, | ||
| 775 | _ => None, | ||
| 776 | } | ||
| 777 | } | ||
| 778 | } | ||
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 000000000..65474a714 --- /dev/null +++ b/embassy-usb/src/class/uac1/terminal_type.rs | |||
| @@ -0,0 +1,50 @@ | |||
| 1 | //! USB Audio Terminal Types from Universal Serial Bus Device Class Definition | ||
| 2 | //! for Terminal Types, Release 1.0 | ||
| 3 | |||
| 4 | /// USB Audio Terminal Types from "Universal Serial Bus Device Class Definition | ||
| 5 | /// for Terminal Types, Release 1.0" | ||
| 6 | #[repr(u16)] | ||
| 7 | #[non_exhaustive] | ||
| 8 | #[derive(Copy, Clone, Eq, PartialEq, Debug)] | ||
| 9 | #[allow(missing_docs)] | ||
| 10 | pub enum TerminalType { | ||
| 11 | // USB Terminal Types | ||
| 12 | UsbUndefined = 0x0100, | ||
| 13 | UsbStreaming = 0x0101, | ||
| 14 | UsbVendor = 0x01ff, | ||
| 15 | |||
| 16 | // Input Terminal Types | ||
| 17 | InUndefined = 0x0200, | ||
| 18 | InMicrophone = 0x0201, | ||
| 19 | InDesktopMicrophone = 0x0202, | ||
| 20 | InPersonalMicrophone = 0x0203, | ||
| 21 | InOmniDirectionalMicrophone = 0x0204, | ||
| 22 | InMicrophoneArray = 0x0205, | ||
| 23 | InProcessingMicrophoneArray = 0x0206, | ||
| 24 | |||
| 25 | // Output Terminal Types | ||
| 26 | OutUndefined = 0x0300, | ||
| 27 | OutSpeaker = 0x0301, | ||
| 28 | OutHeadphones = 0x0302, | ||
| 29 | OutHeadMountedDisplayAudio = 0x0303, | ||
| 30 | OutDesktopSpeaker = 0x0304, | ||
| 31 | OutRoomSpeaker = 0x0305, | ||
| 32 | OutCommunicationSpeaker = 0x0306, | ||
| 33 | OutLowFrequencyEffectsSpeaker = 0x0307, | ||
| 34 | |||
| 35 | // External Terminal Types | ||
| 36 | ExtUndefined = 0x0600, | ||
| 37 | ExtAnalogConnector = 0x0601, | ||
| 38 | ExtDigitalAudioInterface = 0x0602, | ||
| 39 | ExtLineConnector = 0x0603, | ||
| 40 | ExtLegacyAudioConnector = 0x0604, | ||
| 41 | ExtSpdifConnector = 0x0605, | ||
| 42 | Ext1394DaStream = 0x0606, | ||
| 43 | Ext1394DvStreamSoundtrack = 0x0607, | ||
| 44 | } | ||
| 45 | |||
| 46 | impl From<TerminalType> for u16 { | ||
| 47 | fn from(t: TerminalType) -> u16 { | ||
| 48 | t as u16 | ||
| 49 | } | ||
| 50 | } | ||
diff --git a/examples/stm32f4/Cargo.toml b/examples/stm32f4/Cargo.toml index 75e315e82..435b0b43c 100644 --- a/examples/stm32f4/Cargo.toml +++ b/examples/stm32f4/Cargo.toml | |||
| @@ -27,6 +27,7 @@ embedded-io-async = { version = "0.6.1" } | |||
| 27 | panic-probe = { version = "0.3", features = ["print-defmt"] } | 27 | panic-probe = { version = "0.3", features = ["print-defmt"] } |
| 28 | futures-util = { version = "0.3.30", default-features = false } | 28 | futures-util = { version = "0.3.30", default-features = false } |
| 29 | heapless = { version = "0.8", default-features = false } | 29 | heapless = { version = "0.8", default-features = false } |
| 30 | critical-section = "1.1" | ||
| 30 | nb = "1.0.0" | 31 | nb = "1.0.0" |
| 31 | embedded-storage = "0.3.1" | 32 | embedded-storage = "0.3.1" |
| 32 | micromath = "2.0.0" | 33 | 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 000000000..8d83afd1a --- /dev/null +++ b/examples/stm32f4/src/bin/usb_uac_speaker.rs | |||
| @@ -0,0 +1,387 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | |||
| 4 | use core::cell::RefCell; | ||
| 5 | |||
| 6 | use defmt::{panic, *}; | ||
| 7 | use embassy_executor::Spawner; | ||
| 8 | use embassy_stm32::time::Hertz; | ||
| 9 | use embassy_stm32::{bind_interrupts, interrupt, peripherals, timer, usb, Config}; | ||
| 10 | use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}; | ||
| 11 | use embassy_sync::blocking_mutex::Mutex; | ||
| 12 | use embassy_sync::signal::Signal; | ||
| 13 | use embassy_sync::zerocopy_channel; | ||
| 14 | use embassy_usb::class::uac1; | ||
| 15 | use embassy_usb::class::uac1::speaker::{self, Speaker}; | ||
| 16 | use embassy_usb::driver::EndpointError; | ||
| 17 | use heapless::Vec; | ||
| 18 | use micromath::F32Ext; | ||
| 19 | use static_cell::StaticCell; | ||
| 20 | use {defmt_rtt as _, panic_probe as _}; | ||
| 21 | |||
| 22 | bind_interrupts!(struct Irqs { | ||
| 23 | OTG_FS => usb::InterruptHandler<peripherals::USB_OTG_FS>; | ||
| 24 | }); | ||
| 25 | |||
| 26 | static 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. | ||
| 31 | pub static FEEDBACK_SIGNAL: Signal<CriticalSectionRawMutex, u32> = Signal::new(); | ||
| 32 | |||
| 33 | // Stereo input | ||
| 34 | pub const INPUT_CHANNEL_COUNT: usize = 2; | ||
| 35 | |||
| 36 | // This example uses a fixed sample rate of 48 kHz. | ||
| 37 | pub const SAMPLE_RATE_HZ: u32 = 48_000; | ||
| 38 | pub 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. | ||
| 41 | pub const SAMPLE_WIDTH: uac1::SampleWidth = uac1::SampleWidth::Width4Byte; | ||
| 42 | pub const SAMPLE_WIDTH_BIT: usize = SAMPLE_WIDTH.in_bit(); | ||
| 43 | pub const SAMPLE_SIZE: usize = SAMPLE_WIDTH as usize; | ||
| 44 | pub 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. | ||
| 47 | pub const USB_FRAME_SIZE: usize = SAMPLE_SIZE_PER_S.div_ceil(1000); | ||
| 48 | |||
| 49 | // Select front left and right audio channels. | ||
| 50 | pub 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) | ||
| 53 | pub const USB_MAX_PACKET_SIZE: usize = 2 * USB_FRAME_SIZE; | ||
| 54 | pub 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). | ||
| 57 | pub type SampleBlock = Vec<u32, USB_MAX_SAMPLE_COUNT>; | ||
| 58 | |||
| 59 | // Feedback is provided in 10.14 format for full-speed endpoints. | ||
| 60 | pub const FEEDBACK_REFRESH_PERIOD: uac1::FeedbackRefresh = uac1::FeedbackRefresh::Period8Frames; | ||
| 61 | const FEEDBACK_SHIFT: usize = 14; | ||
| 62 | |||
| 63 | const TICKS_PER_SAMPLE: f32 = (FEEDBACK_COUNTER_TICK_RATE as f32) / (SAMPLE_RATE_HZ as f32); | ||
| 64 | |||
| 65 | struct Disconnected {} | ||
| 66 | |||
| 67 | impl 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. | ||
| 77 | async 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. | ||
| 105 | async 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] | ||
| 137 | async 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] | ||
| 149 | async 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] | ||
| 175 | async 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] | ||
| 189 | async 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] | ||
| 197 | async 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] | ||
| 219 | fn 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 timer = TIMER.borrow(cs).borrow().as_ref().unwrap().regs_gp32(); | ||
| 226 | |||
| 227 | let status = timer.sr().read(); | ||
| 228 | |||
| 229 | const CHANNEL_INDEX: usize = 0; | ||
| 230 | if status.ccif(CHANNEL_INDEX) { | ||
| 231 | let ticks = timer.ccr(CHANNEL_INDEX).read(); | ||
| 232 | |||
| 233 | *FRAME_COUNT += 1; | ||
| 234 | if *FRAME_COUNT >= FEEDBACK_REFRESH_PERIOD.frame_count() { | ||
| 235 | *FRAME_COUNT = 0; | ||
| 236 | FEEDBACK_SIGNAL.signal(ticks.wrapping_sub(*LAST_TICKS)); | ||
| 237 | *LAST_TICKS = ticks; | ||
| 238 | } | ||
| 239 | }; | ||
| 240 | |||
| 241 | // Clear trigger interrupt flag. | ||
| 242 | timer.sr().modify(|r| r.set_tif(false)); | ||
| 243 | }); | ||
| 244 | } | ||
| 245 | |||
| 246 | // If you are trying this and your USB device doesn't connect, the most | ||
| 247 | // common issues are the RCC config and vbus_detection | ||
| 248 | // | ||
| 249 | // See https://embassy.dev/book/#_the_usb_examples_are_not_working_on_my_board_is_there_anything_else_i_need_to_configure | ||
| 250 | // for more information. | ||
| 251 | #[embassy_executor::main] | ||
| 252 | async fn main(spawner: Spawner) { | ||
| 253 | info!("Hello World!"); | ||
| 254 | |||
| 255 | let mut config = Config::default(); | ||
| 256 | { | ||
| 257 | use embassy_stm32::rcc::*; | ||
| 258 | config.rcc.hse = Some(Hse { | ||
| 259 | freq: Hertz(8_000_000), | ||
| 260 | mode: HseMode::Bypass, | ||
| 261 | }); | ||
| 262 | config.rcc.pll_src = PllSource::HSE; | ||
| 263 | config.rcc.pll = Some(Pll { | ||
| 264 | prediv: PllPreDiv::DIV4, | ||
| 265 | mul: PllMul::MUL168, | ||
| 266 | divp: Some(PllPDiv::DIV2), // ((8 MHz / 4) * 168) / 2 = 168 Mhz. | ||
| 267 | divq: Some(PllQDiv::DIV7), // ((8 MHz / 4) * 168) / 7 = 48 Mhz. | ||
| 268 | divr: None, | ||
| 269 | }); | ||
| 270 | config.rcc.ahb_pre = AHBPrescaler::DIV1; | ||
| 271 | config.rcc.apb1_pre = APBPrescaler::DIV4; | ||
| 272 | config.rcc.apb2_pre = APBPrescaler::DIV2; | ||
| 273 | config.rcc.sys = Sysclk::PLL1_P; | ||
| 274 | config.rcc.mux.clk48sel = mux::Clk48sel::PLL1_Q; | ||
| 275 | } | ||
| 276 | let p = embassy_stm32::init(config); | ||
| 277 | |||
| 278 | // Configure all required buffers in a static way. | ||
| 279 | debug!("USB packet size is {} byte", USB_MAX_PACKET_SIZE); | ||
| 280 | static CONFIG_DESCRIPTOR: StaticCell<[u8; 256]> = StaticCell::new(); | ||
| 281 | let config_descriptor = CONFIG_DESCRIPTOR.init([0; 256]); | ||
| 282 | |||
| 283 | static BOS_DESCRIPTOR: StaticCell<[u8; 32]> = StaticCell::new(); | ||
| 284 | let bos_descriptor = BOS_DESCRIPTOR.init([0; 32]); | ||
| 285 | |||
| 286 | const CONTROL_BUF_SIZE: usize = 64; | ||
| 287 | static CONTROL_BUF: StaticCell<[u8; CONTROL_BUF_SIZE]> = StaticCell::new(); | ||
| 288 | let control_buf = CONTROL_BUF.init([0; CONTROL_BUF_SIZE]); | ||
| 289 | |||
| 290 | const FEEDBACK_BUF_SIZE: usize = 4; | ||
| 291 | static EP_OUT_BUFFER: StaticCell<[u8; FEEDBACK_BUF_SIZE + CONTROL_BUF_SIZE + USB_MAX_PACKET_SIZE]> = | ||
| 292 | StaticCell::new(); | ||
| 293 | let ep_out_buffer = EP_OUT_BUFFER.init([0u8; FEEDBACK_BUF_SIZE + CONTROL_BUF_SIZE + USB_MAX_PACKET_SIZE]); | ||
| 294 | |||
| 295 | static STATE: StaticCell<speaker::State> = StaticCell::new(); | ||
| 296 | let state = STATE.init(speaker::State::new()); | ||
| 297 | |||
| 298 | // Create the driver, from the HAL. | ||
| 299 | let mut usb_config = usb::Config::default(); | ||
| 300 | |||
| 301 | // Do not enable vbus_detection. This is a safe default that works in all boards. | ||
| 302 | // However, if your USB device is self-powered (can stay powered on if USB is unplugged), you need | ||
| 303 | // to enable vbus_detection to comply with the USB spec. If you enable it, the board | ||
| 304 | // has to support it or USB won't work at all. See docs on `vbus_detection` for details. | ||
| 305 | usb_config.vbus_detection = false; | ||
| 306 | |||
| 307 | let usb_driver = usb::Driver::new_fs(p.USB_OTG_FS, Irqs, p.PA12, p.PA11, ep_out_buffer, usb_config); | ||
| 308 | |||
| 309 | // Basic USB device configuration | ||
| 310 | let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); | ||
| 311 | config.manufacturer = Some("Embassy"); | ||
| 312 | config.product = Some("USB-audio-speaker example"); | ||
| 313 | config.serial_number = Some("12345678"); | ||
| 314 | |||
| 315 | // Required for windows compatibility. | ||
| 316 | // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help | ||
| 317 | config.device_class = 0xEF; | ||
| 318 | config.device_sub_class = 0x02; | ||
| 319 | config.device_protocol = 0x01; | ||
| 320 | config.composite_with_iads = true; | ||
| 321 | |||
| 322 | let mut builder = embassy_usb::Builder::new( | ||
| 323 | usb_driver, | ||
| 324 | config, | ||
| 325 | config_descriptor, | ||
| 326 | bos_descriptor, | ||
| 327 | &mut [], // no msos descriptors | ||
| 328 | control_buf, | ||
| 329 | ); | ||
| 330 | |||
| 331 | // Create the UAC1 Speaker class components | ||
| 332 | let (stream, feedback, control_monitor) = Speaker::new( | ||
| 333 | &mut builder, | ||
| 334 | state, | ||
| 335 | USB_MAX_PACKET_SIZE as u16, | ||
| 336 | uac1::SampleWidth::Width4Byte, | ||
| 337 | &[SAMPLE_RATE_HZ], | ||
| 338 | &AUDIO_CHANNELS, | ||
| 339 | FEEDBACK_REFRESH_PERIOD, | ||
| 340 | ); | ||
| 341 | |||
| 342 | // Create the USB device | ||
| 343 | let usb_device = builder.build(); | ||
| 344 | |||
| 345 | // Establish a zero-copy channel for transferring received audio samples between tasks | ||
| 346 | static SAMPLE_BLOCKS: StaticCell<[SampleBlock; 2]> = StaticCell::new(); | ||
| 347 | let sample_blocks = SAMPLE_BLOCKS.init([Vec::new(), Vec::new()]); | ||
| 348 | |||
| 349 | static CHANNEL: StaticCell<zerocopy_channel::Channel<'_, NoopRawMutex, SampleBlock>> = StaticCell::new(); | ||
| 350 | let channel = CHANNEL.init(zerocopy_channel::Channel::new(sample_blocks)); | ||
| 351 | let (sender, receiver) = channel.split(); | ||
| 352 | |||
| 353 | // Run a timer for counting between SOF interrupts. | ||
| 354 | let mut tim2 = timer::low_level::Timer::new(p.TIM2); | ||
| 355 | tim2.set_tick_freq(Hertz(FEEDBACK_COUNTER_TICK_RATE)); | ||
| 356 | tim2.set_trigger_source(timer::low_level::TriggerSource::ITR1); // The USB SOF signal. | ||
| 357 | |||
| 358 | const TIMER_CHANNEL: timer::Channel = timer::Channel::Ch1; | ||
| 359 | tim2.set_input_ti_selection(TIMER_CHANNEL, timer::low_level::InputTISelection::TRC); | ||
| 360 | tim2.set_input_capture_prescaler(TIMER_CHANNEL, 0); | ||
| 361 | tim2.set_input_capture_filter(TIMER_CHANNEL, timer::low_level::FilterValue::FCK_INT_N2); | ||
| 362 | |||
| 363 | // Reset all interrupt flags. | ||
| 364 | tim2.regs_gp32().sr().write(|r| r.0 = 0); | ||
| 365 | |||
| 366 | // Enable routing of SOF to the timer. | ||
| 367 | tim2.regs_gp32().or().write(|r| *r = 0b10 << 10); | ||
| 368 | |||
| 369 | tim2.enable_channel(TIMER_CHANNEL, true); | ||
| 370 | tim2.enable_input_interrupt(TIMER_CHANNEL, true); | ||
| 371 | |||
| 372 | tim2.start(); | ||
| 373 | |||
| 374 | TIMER.lock(|p| p.borrow_mut().replace(tim2)); | ||
| 375 | |||
| 376 | // Unmask the TIM2 interrupt. | ||
| 377 | unsafe { | ||
| 378 | cortex_m::peripheral::NVIC::unmask(interrupt::TIM2); | ||
| 379 | } | ||
| 380 | |||
| 381 | // Launch USB audio tasks. | ||
| 382 | unwrap!(spawner.spawn(usb_control_task(control_monitor))); | ||
| 383 | unwrap!(spawner.spawn(usb_streaming_task(stream, sender))); | ||
| 384 | unwrap!(spawner.spawn(usb_feedback_task(feedback))); | ||
| 385 | unwrap!(spawner.spawn(usb_task(usb_device))); | ||
| 386 | unwrap!(spawner.spawn(audio_receiver_task(receiver))); | ||
| 387 | } | ||
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 000000000..4fd4ccbbd --- /dev/null +++ b/examples/stm32h5/src/bin/usb_uac_speaker.rs | |||
| @@ -0,0 +1,378 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | |||
| 4 | use core::cell::RefCell; | ||
| 5 | |||
| 6 | use defmt::{panic, *}; | ||
| 7 | use embassy_executor::Spawner; | ||
| 8 | use embassy_stm32::time::Hertz; | ||
| 9 | use embassy_stm32::{bind_interrupts, interrupt, peripherals, timer, usb, Config}; | ||
| 10 | use embassy_sync::blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex}; | ||
| 11 | use embassy_sync::blocking_mutex::Mutex; | ||
| 12 | use embassy_sync::signal::Signal; | ||
| 13 | use embassy_sync::zerocopy_channel; | ||
| 14 | use embassy_usb::class::uac1; | ||
| 15 | use embassy_usb::class::uac1::speaker::{self, Speaker}; | ||
| 16 | use embassy_usb::driver::EndpointError; | ||
| 17 | use heapless::Vec; | ||
| 18 | use micromath::F32Ext; | ||
| 19 | use static_cell::StaticCell; | ||
| 20 | use {defmt_rtt as _, panic_probe as _}; | ||
| 21 | |||
| 22 | bind_interrupts!(struct Irqs { | ||
| 23 | USB_DRD_FS => usb::InterruptHandler<peripherals::USB>; | ||
| 24 | }); | ||
| 25 | |||
| 26 | static TIMER: Mutex<CriticalSectionRawMutex, RefCell<Option<timer::low_level::Timer<peripherals::TIM5>>>> = | ||
| 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. | ||
| 31 | pub static FEEDBACK_SIGNAL: Signal<CriticalSectionRawMutex, u32> = Signal::new(); | ||
| 32 | |||
| 33 | // Stereo input | ||
| 34 | pub const INPUT_CHANNEL_COUNT: usize = 2; | ||
| 35 | |||
| 36 | // This example uses a fixed sample rate of 48 kHz. | ||
| 37 | pub const SAMPLE_RATE_HZ: u32 = 48_000; | ||
| 38 | pub const FEEDBACK_COUNTER_TICK_RATE: u32 = 31_250_000; | ||
| 39 | |||
| 40 | // Use 32 bit samples, which allow for a lot of (software) volume adjustment without degradation of quality. | ||
| 41 | pub const SAMPLE_WIDTH: uac1::SampleWidth = uac1::SampleWidth::Width4Byte; | ||
| 42 | pub const SAMPLE_WIDTH_BIT: usize = SAMPLE_WIDTH.in_bit(); | ||
| 43 | pub const SAMPLE_SIZE: usize = SAMPLE_WIDTH as usize; | ||
| 44 | pub 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. | ||
| 47 | pub const USB_FRAME_SIZE: usize = SAMPLE_SIZE_PER_S.div_ceil(1000); | ||
| 48 | |||
| 49 | // Select front left and right audio channels. | ||
| 50 | pub 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) | ||
| 53 | pub const USB_MAX_PACKET_SIZE: usize = 2 * USB_FRAME_SIZE; | ||
| 54 | pub 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). | ||
| 57 | pub type SampleBlock = Vec<u32, USB_MAX_SAMPLE_COUNT>; | ||
| 58 | |||
| 59 | // Feedback is provided in 10.14 format for full-speed endpoints. | ||
| 60 | pub const FEEDBACK_REFRESH_PERIOD: uac1::FeedbackRefresh = uac1::FeedbackRefresh::Period8Frames; | ||
| 61 | const FEEDBACK_SHIFT: usize = 14; | ||
| 62 | |||
| 63 | const TICKS_PER_SAMPLE: f32 = (FEEDBACK_COUNTER_TICK_RATE as f32) / (SAMPLE_RATE_HZ as f32); | ||
| 64 | |||
| 65 | struct Disconnected {} | ||
| 66 | |||
| 67 | impl 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. | ||
| 77 | async 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 | |||
| 97 | debug!("Feedback value: {}", value); | ||
| 98 | |||
| 99 | packet.push(value as u8).unwrap(); | ||
| 100 | packet.push((value >> 8) as u8).unwrap(); | ||
| 101 | packet.push((value >> 16) as u8).unwrap(); | ||
| 102 | |||
| 103 | feedback.write_packet(&packet).await?; | ||
| 104 | } | ||
| 105 | } | ||
| 106 | |||
| 107 | /// Handles streaming of audio data from the host. | ||
| 108 | async fn stream_handler<'d, T: usb::Instance + 'd>( | ||
| 109 | stream: &mut speaker::Stream<'d, usb::Driver<'d, T>>, | ||
| 110 | sender: &mut zerocopy_channel::Sender<'static, NoopRawMutex, SampleBlock>, | ||
| 111 | ) -> Result<(), Disconnected> { | ||
| 112 | loop { | ||
| 113 | let mut usb_data = [0u8; USB_MAX_PACKET_SIZE]; | ||
| 114 | let data_size = stream.read_packet(&mut usb_data).await?; | ||
| 115 | |||
| 116 | let word_count = data_size / SAMPLE_SIZE; | ||
| 117 | |||
| 118 | if word_count * SAMPLE_SIZE == data_size { | ||
| 119 | // Obtain a buffer from the channel | ||
| 120 | let samples = sender.send().await; | ||
| 121 | samples.clear(); | ||
| 122 | |||
| 123 | for w in 0..word_count { | ||
| 124 | let byte_offset = w * SAMPLE_SIZE; | ||
| 125 | let sample = u32::from_le_bytes(usb_data[byte_offset..byte_offset + SAMPLE_SIZE].try_into().unwrap()); | ||
| 126 | |||
| 127 | // Fill the sample buffer with data. | ||
| 128 | samples.push(sample).unwrap(); | ||
| 129 | } | ||
| 130 | |||
| 131 | sender.send_done(); | ||
| 132 | } else { | ||
| 133 | debug!("Invalid USB buffer size of {}, skipped.", data_size); | ||
| 134 | } | ||
| 135 | } | ||
| 136 | } | ||
| 137 | |||
| 138 | /// Receives audio samples from the USB streaming task and can play them back. | ||
| 139 | #[embassy_executor::task] | ||
| 140 | async fn audio_receiver_task(mut usb_audio_receiver: zerocopy_channel::Receiver<'static, NoopRawMutex, SampleBlock>) { | ||
| 141 | loop { | ||
| 142 | let _samples = usb_audio_receiver.receive().await; | ||
| 143 | // Use the samples, for example play back via the SAI peripheral. | ||
| 144 | |||
| 145 | // Notify the channel that the buffer is now ready to be reused | ||
| 146 | usb_audio_receiver.receive_done(); | ||
| 147 | } | ||
| 148 | } | ||
| 149 | |||
| 150 | /// Receives audio samples from the host. | ||
| 151 | #[embassy_executor::task] | ||
| 152 | async fn usb_streaming_task( | ||
| 153 | mut stream: speaker::Stream<'static, usb::Driver<'static, peripherals::USB>>, | ||
| 154 | mut sender: zerocopy_channel::Sender<'static, NoopRawMutex, SampleBlock>, | ||
| 155 | ) { | ||
| 156 | loop { | ||
| 157 | stream.wait_connection().await; | ||
| 158 | info!("USB connected."); | ||
| 159 | _ = stream_handler(&mut stream, &mut sender).await; | ||
| 160 | info!("USB disconnected."); | ||
| 161 | } | ||
| 162 | } | ||
| 163 | |||
| 164 | /// Sends sample rate feedback to the host. | ||
| 165 | /// | ||
| 166 | /// The `feedback_factor` scales the feedback timer's counter value so that the result is the number of samples that | ||
| 167 | /// this device played back or "consumed" during one SOF period (1 ms) - in 10.14 format. | ||
| 168 | /// | ||
| 169 | /// Ideally, the `feedback_factor` that is calculated below would be an integer for avoiding numerical errors. | ||
| 170 | /// This is achieved by having `TICKS_PER_SAMPLE` be a power of two. For audio applications at a sample rate of 48 kHz, | ||
| 171 | /// 24.576 MHz would be one such option. | ||
| 172 | #[embassy_executor::task] | ||
| 173 | async fn usb_feedback_task(mut feedback: speaker::Feedback<'static, usb::Driver<'static, peripherals::USB>>) { | ||
| 174 | let feedback_factor = | ||
| 175 | ((1 << FEEDBACK_SHIFT) as f32 / TICKS_PER_SAMPLE) / FEEDBACK_REFRESH_PERIOD.frame_count() as f32; | ||
| 176 | |||
| 177 | loop { | ||
| 178 | feedback.wait_connection().await; | ||
| 179 | _ = feedback_handler(&mut feedback, feedback_factor).await; | ||
| 180 | } | ||
| 181 | } | ||
| 182 | |||
| 183 | #[embassy_executor::task] | ||
| 184 | async fn usb_task(mut usb_device: embassy_usb::UsbDevice<'static, usb::Driver<'static, peripherals::USB>>) { | ||
| 185 | usb_device.run().await; | ||
| 186 | } | ||
| 187 | |||
| 188 | /// Checks for changes on the control monitor of the class. | ||
| 189 | /// | ||
| 190 | /// In this case, monitor changes of volume or mute state. | ||
| 191 | #[embassy_executor::task] | ||
| 192 | async fn usb_control_task(control_monitor: speaker::ControlMonitor<'static>) { | ||
| 193 | loop { | ||
| 194 | control_monitor.changed().await; | ||
| 195 | |||
| 196 | for channel in AUDIO_CHANNELS { | ||
| 197 | let volume = control_monitor.volume(channel).unwrap(); | ||
| 198 | info!("Volume changed to {} on channel {}.", volume, channel); | ||
| 199 | } | ||
| 200 | } | ||
| 201 | } | ||
| 202 | |||
| 203 | /// Feedback value measurement and calculation | ||
| 204 | /// | ||
| 205 | /// Used for measuring/calculating the number of samples that were received from the host during the | ||
| 206 | /// `FEEDBACK_REFRESH_PERIOD`. | ||
| 207 | /// | ||
| 208 | /// Configured in this example with | ||
| 209 | /// - a refresh period of 8 ms, and | ||
| 210 | /// - a tick rate of 42 MHz. | ||
| 211 | /// | ||
| 212 | /// This gives an (ideal) counter value of 336.000 for every update of the `FEEDBACK_SIGNAL`. | ||
| 213 | #[interrupt] | ||
| 214 | fn TIM5() { | ||
| 215 | static mut LAST_TICKS: u32 = 0; | ||
| 216 | static mut FRAME_COUNT: usize = 0; | ||
| 217 | |||
| 218 | critical_section::with(|cs| { | ||
| 219 | // Read timer counter. | ||
| 220 | let timer = TIMER.borrow(cs).borrow().as_ref().unwrap().regs_gp32(); | ||
| 221 | |||
| 222 | let status = timer.sr().read(); | ||
| 223 | |||
| 224 | const CHANNEL_INDEX: usize = 0; | ||
| 225 | if status.ccif(CHANNEL_INDEX) { | ||
| 226 | let ticks = timer.ccr(CHANNEL_INDEX).read(); | ||
| 227 | |||
| 228 | *FRAME_COUNT += 1; | ||
| 229 | if *FRAME_COUNT >= FEEDBACK_REFRESH_PERIOD.frame_count() { | ||
| 230 | *FRAME_COUNT = 0; | ||
| 231 | FEEDBACK_SIGNAL.signal(ticks.wrapping_sub(*LAST_TICKS)); | ||
| 232 | *LAST_TICKS = ticks; | ||
| 233 | } | ||
| 234 | }; | ||
| 235 | |||
| 236 | // Clear trigger interrupt flag. | ||
| 237 | timer.sr().modify(|r| r.set_tif(false)); | ||
| 238 | }); | ||
| 239 | } | ||
| 240 | |||
| 241 | // If you are trying this and your USB device doesn't connect, the most | ||
| 242 | // common issues are the RCC config and vbus_detection | ||
| 243 | // | ||
| 244 | // See https://embassy.dev/book/#_the_usb_examples_are_not_working_on_my_board_is_there_anything_else_i_need_to_configure | ||
| 245 | // for more information. | ||
| 246 | #[embassy_executor::main] | ||
| 247 | async fn main(spawner: Spawner) { | ||
| 248 | let mut config = Config::default(); | ||
| 249 | { | ||
| 250 | use embassy_stm32::rcc::*; | ||
| 251 | config.rcc.hsi = None; | ||
| 252 | config.rcc.hsi48 = Some(Hsi48Config { sync_from_usb: true }); // needed for USB | ||
| 253 | config.rcc.hse = Some(Hse { | ||
| 254 | freq: Hertz(8_000_000), | ||
| 255 | mode: HseMode::BypassDigital, | ||
| 256 | }); | ||
| 257 | config.rcc.pll1 = Some(Pll { | ||
| 258 | source: PllSource::HSE, | ||
| 259 | prediv: PllPreDiv::DIV2, | ||
| 260 | mul: PllMul::MUL125, | ||
| 261 | divp: Some(PllDiv::DIV2), // 250 Mhz | ||
| 262 | divq: None, | ||
| 263 | divr: None, | ||
| 264 | }); | ||
| 265 | config.rcc.pll2 = Some(Pll { | ||
| 266 | source: PllSource::HSE, | ||
| 267 | prediv: PllPreDiv::DIV4, | ||
| 268 | mul: PllMul::MUL123, | ||
| 269 | divp: Some(PllDiv::DIV20), // 12.3 Mhz, close to 12.288 MHz for 48 kHz audio | ||
| 270 | divq: None, | ||
| 271 | divr: None, | ||
| 272 | }); | ||
| 273 | config.rcc.ahb_pre = AHBPrescaler::DIV2; | ||
| 274 | config.rcc.apb1_pre = APBPrescaler::DIV4; | ||
| 275 | config.rcc.apb2_pre = APBPrescaler::DIV2; | ||
| 276 | config.rcc.apb3_pre = APBPrescaler::DIV4; | ||
| 277 | config.rcc.sys = Sysclk::PLL1_P; | ||
| 278 | config.rcc.voltage_scale = VoltageScale::Scale0; | ||
| 279 | config.rcc.mux.usbsel = mux::Usbsel::HSI48; | ||
| 280 | config.rcc.mux.sai2sel = mux::Saisel::PLL2_P; | ||
| 281 | } | ||
| 282 | let p = embassy_stm32::init(config); | ||
| 283 | |||
| 284 | info!("Hello World!"); | ||
| 285 | |||
| 286 | // Configure all required buffers in a static way. | ||
| 287 | debug!("USB packet size is {} byte", USB_MAX_PACKET_SIZE); | ||
| 288 | static CONFIG_DESCRIPTOR: StaticCell<[u8; 256]> = StaticCell::new(); | ||
| 289 | let config_descriptor = CONFIG_DESCRIPTOR.init([0; 256]); | ||
| 290 | |||
| 291 | static BOS_DESCRIPTOR: StaticCell<[u8; 32]> = StaticCell::new(); | ||
| 292 | let bos_descriptor = BOS_DESCRIPTOR.init([0; 32]); | ||
| 293 | |||
| 294 | const CONTROL_BUF_SIZE: usize = 64; | ||
| 295 | static CONTROL_BUF: StaticCell<[u8; CONTROL_BUF_SIZE]> = StaticCell::new(); | ||
| 296 | let control_buf = CONTROL_BUF.init([0; CONTROL_BUF_SIZE]); | ||
| 297 | |||
| 298 | static STATE: StaticCell<speaker::State> = StaticCell::new(); | ||
| 299 | let state = STATE.init(speaker::State::new()); | ||
| 300 | |||
| 301 | let usb_driver = usb::Driver::new(p.USB, Irqs, p.PA12, p.PA11); | ||
| 302 | |||
| 303 | // Basic USB device configuration | ||
| 304 | let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); | ||
| 305 | config.manufacturer = Some("Embassy"); | ||
| 306 | config.product = Some("USB-audio-speaker example"); | ||
| 307 | config.serial_number = Some("12345678"); | ||
| 308 | |||
| 309 | // Required for windows compatibility. | ||
| 310 | // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help | ||
| 311 | config.device_class = 0xEF; | ||
| 312 | config.device_sub_class = 0x02; | ||
| 313 | config.device_protocol = 0x01; | ||
| 314 | config.composite_with_iads = true; | ||
| 315 | |||
| 316 | let mut builder = embassy_usb::Builder::new( | ||
| 317 | usb_driver, | ||
| 318 | config, | ||
| 319 | config_descriptor, | ||
| 320 | bos_descriptor, | ||
| 321 | &mut [], // no msos descriptors | ||
| 322 | control_buf, | ||
| 323 | ); | ||
| 324 | |||
| 325 | // Create the UAC1 Speaker class components | ||
| 326 | let (stream, feedback, control_monitor) = Speaker::new( | ||
| 327 | &mut builder, | ||
| 328 | state, | ||
| 329 | USB_MAX_PACKET_SIZE as u16, | ||
| 330 | uac1::SampleWidth::Width4Byte, | ||
| 331 | &[SAMPLE_RATE_HZ], | ||
| 332 | &AUDIO_CHANNELS, | ||
| 333 | FEEDBACK_REFRESH_PERIOD, | ||
| 334 | ); | ||
| 335 | |||
| 336 | // Create the USB device | ||
| 337 | let usb_device = builder.build(); | ||
| 338 | |||
| 339 | // Establish a zero-copy channel for transferring received audio samples between tasks | ||
| 340 | static SAMPLE_BLOCKS: StaticCell<[SampleBlock; 2]> = StaticCell::new(); | ||
| 341 | let sample_blocks = SAMPLE_BLOCKS.init([Vec::new(), Vec::new()]); | ||
| 342 | |||
| 343 | static CHANNEL: StaticCell<zerocopy_channel::Channel<'_, NoopRawMutex, SampleBlock>> = StaticCell::new(); | ||
| 344 | let channel = CHANNEL.init(zerocopy_channel::Channel::new(sample_blocks)); | ||
| 345 | let (sender, receiver) = channel.split(); | ||
| 346 | |||
| 347 | // Run a timer for counting between SOF interrupts. | ||
| 348 | let mut tim5 = timer::low_level::Timer::new(p.TIM5); | ||
| 349 | tim5.set_tick_freq(Hertz(FEEDBACK_COUNTER_TICK_RATE)); | ||
| 350 | tim5.set_trigger_source(timer::low_level::TriggerSource::ITR12); // The USB SOF signal. | ||
| 351 | |||
| 352 | const TIMER_CHANNEL: timer::Channel = timer::Channel::Ch1; | ||
| 353 | tim5.set_input_ti_selection(TIMER_CHANNEL, timer::low_level::InputTISelection::TRC); | ||
| 354 | tim5.set_input_capture_prescaler(TIMER_CHANNEL, 0); | ||
| 355 | tim5.set_input_capture_filter(TIMER_CHANNEL, timer::low_level::FilterValue::FCK_INT_N2); | ||
| 356 | |||
| 357 | // Reset all interrupt flags. | ||
| 358 | tim5.regs_gp32().sr().write(|r| r.0 = 0); | ||
| 359 | |||
| 360 | tim5.enable_channel(TIMER_CHANNEL, true); | ||
| 361 | tim5.enable_input_interrupt(TIMER_CHANNEL, true); | ||
| 362 | |||
| 363 | tim5.start(); | ||
| 364 | |||
| 365 | TIMER.lock(|p| p.borrow_mut().replace(tim5)); | ||
| 366 | |||
| 367 | // Unmask the TIM5 interrupt. | ||
| 368 | unsafe { | ||
| 369 | cortex_m::peripheral::NVIC::unmask(interrupt::TIM5); | ||
| 370 | } | ||
| 371 | |||
| 372 | // Launch USB audio tasks. | ||
| 373 | unwrap!(spawner.spawn(usb_control_task(control_monitor))); | ||
| 374 | unwrap!(spawner.spawn(usb_streaming_task(stream, sender))); | ||
| 375 | unwrap!(spawner.spawn(usb_feedback_task(feedback))); | ||
| 376 | unwrap!(spawner.spawn(usb_task(usb_device))); | ||
| 377 | unwrap!(spawner.spawn(audio_receiver_task(receiver))); | ||
| 378 | } | ||
