mirror of
https://github.com/embassy-rs/embassy.git
synced 2024-11-28 01:32:37 +00:00
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)
This commit is contained in:
commit
b9408f0510
@ -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;
|
||||
|
151
embassy-usb/src/class/uac1/class_codes.rs
Normal file
151
embassy-usb/src/class/uac1/class_codes.rs
Normal file
@ -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;
|
134
embassy-usb/src/class/uac1/mod.rs
Normal file
134
embassy-usb/src/class/uac1/mod.rs
Normal file
@ -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<ChannelConfig> 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
|
||||
}
|
||||
}
|
778
embassy-usb/src/class/uac1/speaker.rs
Normal file
778
embassy-usb/src/class/uac1/speaker.rs
Normal file
@ -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<Control<'d>>,
|
||||
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<u8, { FEATURE_UNIT_DESCRIPTOR_SIZE + MAX_AUDIO_CHANNEL_COUNT + 1 }> =
|
||||
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<u8, { 6 + 3 * MAX_SAMPLE_RATE_COUNT }> = 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<Cell<AudioSettings>>,
|
||||
|
||||
/// Channel assignments.
|
||||
channels: &'d [Channel],
|
||||
|
||||
/// The audio sample rate in Hz.
|
||||
sample_rate_hz: AtomicU32,
|
||||
|
||||
// Notification mechanism.
|
||||
waker: RefCell<WakerRegistration>,
|
||||
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<usize, EndpointError> {
|
||||
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<usize> {
|
||||
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<Volume> {
|
||||
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<OutResponse> {
|
||||
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<OutResponse> {
|
||||
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<InResponse<'r>> {
|
||||
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<InResponse<'r>> {
|
||||
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<OutResponse> {
|
||||
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<InResponse<'a>> {
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
50
embassy-usb/src/class/uac1/terminal_type.rs
Normal file
50
embassy-usb/src/class/uac1/terminal_type.rs
Normal file
@ -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<TerminalType> for u16 {
|
||||
fn from(t: TerminalType) -> u16 {
|
||||
t as u16
|
||||
}
|
||||
}
|
@ -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"
|
||||
|
387
examples/stm32f4/src/bin/usb_uac_speaker.rs
Normal file
387
examples/stm32f4/src/bin/usb_uac_speaker.rs
Normal file
@ -0,0 +1,387 @@
|
||||
#![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<peripherals::USB_OTG_FS>;
|
||||
});
|
||||
|
||||
static TIMER: Mutex<CriticalSectionRawMutex, RefCell<Option<timer::low_level::Timer<peripherals::TIM2>>>> =
|
||||
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<CriticalSectionRawMutex, u32> = 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<u32, USB_MAX_SAMPLE_COUNT>;
|
||||
|
||||
// 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<EndpointError> 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<u8, 4> = 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 timer = TIMER.borrow(cs).borrow().as_ref().unwrap().regs_gp32();
|
||||
|
||||
let status = timer.sr().read();
|
||||
|
||||
const CHANNEL_INDEX: usize = 0;
|
||||
if status.ccif(CHANNEL_INDEX) {
|
||||
let ticks = timer.ccr(CHANNEL_INDEX).read();
|
||||
|
||||
*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;
|
||||
}
|
||||
};
|
||||
|
||||
// Clear trigger interrupt flag.
|
||||
timer.sr().modify(|r| r.set_tif(false));
|
||||
});
|
||||
}
|
||||
|
||||
// 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<speaker::State> = 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<zerocopy_channel::Channel<'_, NoopRawMutex, SampleBlock>> = 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.
|
||||
|
||||
const TIMER_CHANNEL: timer::Channel = timer::Channel::Ch1;
|
||||
tim2.set_input_ti_selection(TIMER_CHANNEL, timer::low_level::InputTISelection::TRC);
|
||||
tim2.set_input_capture_prescaler(TIMER_CHANNEL, 0);
|
||||
tim2.set_input_capture_filter(TIMER_CHANNEL, timer::low_level::FilterValue::FCK_INT_N2);
|
||||
|
||||
// Reset all interrupt flags.
|
||||
tim2.regs_gp32().sr().write(|r| r.0 = 0);
|
||||
|
||||
// Enable routing of SOF to the timer.
|
||||
tim2.regs_gp32().or().write(|r| *r = 0b10 << 10);
|
||||
|
||||
tim2.enable_channel(TIMER_CHANNEL, true);
|
||||
tim2.enable_input_interrupt(TIMER_CHANNEL, true);
|
||||
|
||||
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)));
|
||||
}
|
378
examples/stm32h5/src/bin/usb_uac_speaker.rs
Normal file
378
examples/stm32h5/src/bin/usb_uac_speaker.rs
Normal file
@ -0,0 +1,378 @@
|
||||
#![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<peripherals::USB>;
|
||||
});
|
||||
|
||||
static TIMER: Mutex<CriticalSectionRawMutex, RefCell<Option<timer::low_level::Timer<peripherals::TIM5>>>> =
|
||||
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<CriticalSectionRawMutex, u32> = 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<u32, USB_MAX_SAMPLE_COUNT>;
|
||||
|
||||
// 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<EndpointError> 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<u8, 4> = 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;
|
||||
|
||||
debug!("Feedback value: {}", value);
|
||||
|
||||
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 timer = TIMER.borrow(cs).borrow().as_ref().unwrap().regs_gp32();
|
||||
|
||||
let status = timer.sr().read();
|
||||
|
||||
const CHANNEL_INDEX: usize = 0;
|
||||
if status.ccif(CHANNEL_INDEX) {
|
||||
let ticks = timer.ccr(CHANNEL_INDEX).read();
|
||||
|
||||
*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;
|
||||
}
|
||||
};
|
||||
|
||||
// Clear trigger interrupt flag.
|
||||
timer.sr().modify(|r| r.set_tif(false));
|
||||
});
|
||||
}
|
||||
|
||||
// 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<speaker::State> = 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<zerocopy_channel::Channel<'_, NoopRawMutex, SampleBlock>> = 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.
|
||||
|
||||
const TIMER_CHANNEL: timer::Channel = timer::Channel::Ch1;
|
||||
tim5.set_input_ti_selection(TIMER_CHANNEL, timer::low_level::InputTISelection::TRC);
|
||||
tim5.set_input_capture_prescaler(TIMER_CHANNEL, 0);
|
||||
tim5.set_input_capture_filter(TIMER_CHANNEL, timer::low_level::FilterValue::FCK_INT_N2);
|
||||
|
||||
// Reset all interrupt flags.
|
||||
tim5.regs_gp32().sr().write(|r| r.0 = 0);
|
||||
|
||||
tim5.enable_channel(TIMER_CHANNEL, true);
|
||||
tim5.enable_input_interrupt(TIMER_CHANNEL, true);
|
||||
|
||||
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)));
|
||||
}
|
Loading…
Reference in New Issue
Block a user