Compare commits

...

3 Commits

Author SHA1 Message Date
ktims a848e236d4 add pre-commit 2026-05-17 22:58:14 -07:00
ktims 008c62318e examples improvements 2026-05-17 22:58:00 -07:00
ktims 2b39987ef5 readme, doc comments, clippies 2026-05-17 22:53:33 -07:00
12 changed files with 176 additions and 48 deletions
+2
View File
@@ -1 +1,3 @@
/target /target
.zed
.gdbinit
+39
View File
@@ -0,0 +1,39 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: fix-byte-order-marker
- id: check-case-conflict
- id: check-merge-conflict
- id: check-symlinks
- id: check-yaml
- id: end-of-file-fixer
- id: mixed-line-ending
- id: trailing-whitespace
- repo: https://github.com/pre-commit/pre-commit
rev: v3.6.2
hooks:
- id: validate_manifest
- repo: https://github.com/jorisroovers/gitlint
rev: v0.19.1
hooks:
- id: gitlint
- repo: https://github.com/FeryET/pre-commit-rust
rev: v1.2.1
hooks:
- id: fmt
- id: cargo-check
- id: clippy
- id: build
- repo: local
hooks:
- id: readme
name: readme
language: system
description: Rebuild README.md
types: [file]
files: src/lib.rs
stages:
- pre-commit
entry: cargo readme > README.md
pass_filenames: false
+6
View File
@@ -27,3 +27,9 @@ embedded-io = "0.7.1"
modular-bitfield = "0.13.1" modular-bitfield = "0.13.1"
num-traits = { version = "0.2.19", default-features = false } num-traits = { version = "0.2.19", default-features = false }
usb-device.workspace = true usb-device.workspace = true
[profile.release]
opt-level = "z"
lto = true
debug = true
codegen-units = 1
+45
View File
@@ -0,0 +1,45 @@
# usbd-uac2
USB Audio Class 2.0
This crate provides a USB Audio Class 2.0 implementation for
[usb-device](https://crates.io/crates/usb-device). It implements all
required elements of the specification, however many controls are not
implemented (e.g. mixers, effects).
Device behaviour is driven by implementing the `ClockSource` and
`AudioHandler` traits to configure the audio pipeline and source/sink data
to/from USB.
Example (creates a UAC2 device with in and out streams):
```rust
let mut audio = YourTraitImpl {...};
let config = UsbAudioClassConfig::new(usb_speed, FunctionCode::IoBox, &mut audio)
// base_id is USB entity ID, id 1 is always taken by the clock source, and each stream builds 2 entities
.with_output_config(TerminalConfig::builder().base_id(2).build())
.with_input_config(TerminalConfig::builder().base_id(4).build());
let mut uac2 = config.build(&usb_bus).unwrap();
let mut usb_dev = usbd_uac2::builder(&usb_bus, UsbVidPid(0x1209, 0x0001))
.strings(&[StringDescriptors::default()
.manufacturer("usbd_uac2")
.product("example")])
.unwrap()
.max_packet_size_0(64) // Required to be 64 on HS
.unwrap()
.build();
```
No work needs to be done in the poll loop, the class implementation will
call your trait callbacks as required, just call the usb poll as usual:
```rust
loop {
usb_dev.poll(&mut [&mut uac2]);
}
```
See the trait documentation or examples for additional details.
+1 -1
View File
@@ -3,7 +3,7 @@ Two example backends are provided, both based on the LPCXpresso55S28 demo board,
## LPC55S28 (LPCXpresso55S28) ## LPC55S28 (LPCXpresso55S28)
These examples work on the These examples work on the
(LPCXpresso55S28)[https://www.nxp.com/design/design-center/software/development-software/mcuxpresso-software-and-tools-/lpcxpresso-boards/lpcxpresso55s28-development-board:LPC55S28-EVK] [LPCXpresso55S28](https://www.nxp.com/design/design-center/software/development-software/mcuxpresso-software-and-tools-/lpcxpresso-boards/lpcxpresso55s28-development-board:LPC55S28-EVK)
development board. They use the onboard WM8904 codec, so don't require any development board. They use the onboard WM8904 codec, so don't require any
additional hardware for audio I/O. additional hardware for audio I/O.
@@ -1,10 +1,3 @@
[target.thumbv8m.main-none-eabi]
rustflags = [
"-C", "link-arg=-Tlink.x",
"-C", "link-arg=-Tdefmt.x",
"-C", "debug-assertions",
]
[target.thumbv8m.main-none-eabihf] [target.thumbv8m.main-none-eabihf]
rustflags = [ rustflags = [
"-C", "link-arg=-Tlink.x", "-C", "link-arg=-Tlink.x",
-6
View File
@@ -24,9 +24,3 @@ static_cell = "2.1.1"
lpc55-hal = { git = "https://github.com/ktims/lpc55-hal", branch = "main" } lpc55-hal = { git = "https://github.com/ktims/lpc55-hal", branch = "main" }
usb-device.workspace = true usb-device.workspace = true
usbd-uac2 = { workspace = true, features = ["defmt"] } usbd-uac2 = { workspace = true, features = ["defmt"] }
[profile.release]
opt-level = "z"
lto = true
debug = true
codegen-units = 1
+16 -10
View File
@@ -38,7 +38,7 @@ use pac::interrupt;
use static_cell::StaticCell; use static_cell::StaticCell;
use usb_device::{ use usb_device::{
bus::{self}, bus::{self},
device::{StringDescriptors, UsbDeviceBuilder, UsbVidPid}, device::{StringDescriptors, UsbVidPid},
}; };
use usbd_uac2::TerminalConfig; use usbd_uac2::TerminalConfig;
use usbd_uac2::{ use usbd_uac2::{
@@ -53,6 +53,12 @@ mod dma;
mod hw; mod hw;
mod wm8904; mod wm8904;
// pid.codes test IDs
const USB_VID: u16 = 0x1209;
const USB_PID: u16 = 0x0001;
const USB_MANUFACTURER: &str = "usbd_uac2";
const USB_PRODUCT: &str = "DMA example device";
const CODEC_I2C_ADDR: u8 = 0b0011010; const CODEC_I2C_ADDR: u8 = 0b0011010;
const MCLK_FREQ: u32 = 12288000; const MCLK_FREQ: u32 = 12288000;
@@ -199,6 +205,11 @@ impl<const N: usize, const MAX_SLOT_BYTES: usize, B: bus::UsbBus> AudioHandler<'
self.running.store(true, Ordering::Relaxed) self.running.store(true, Ordering::Relaxed)
} }
} }
fn audio_data_tx(
&mut self,
_ep: &usb_device::endpoint::Endpoint<'_, B, usb_device::endpoint::In>,
) {
}
/// Provide rate feedback to the host. P-only is stable and works fine, with /// Provide rate feedback to the host. P-only is stable and works fine, with
/// most hosts. The host can either filter it internally or treat it /// most hosts. The host can either filter it internally or treat it
@@ -379,18 +390,13 @@ fn main() -> ! {
.with_output_config(TerminalConfig::builder().base_id(2).build()); .with_output_config(TerminalConfig::builder().base_id(2).build());
let mut uac2 = config.build(&usb_bus).unwrap(); let mut uac2 = config.build(&usb_bus).unwrap();
let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x1209, 0xcc1d)) let mut usb_dev = usbd_uac2::builder(&usb_bus, UsbVidPid(USB_VID, USB_PID))
.composite_with_iads()
.strings(&[StringDescriptors::default() .strings(&[StringDescriptors::default()
.manufacturer("Generic") .manufacturer(USB_MANUFACTURER)
.product("usbd_uac2 device") .product(USB_PRODUCT)])
.serial_number("123456789")])
.unwrap() .unwrap()
.max_packet_size_0(64) .max_packet_size_0(64) // Required to be 64 on HS, allowed on FS
.unwrap() .unwrap()
.device_class(0xef)
.device_sub_class(0x02)
.device_protocol(0x01)
.build(); .build();
defmt::info!("main loop"); defmt::info!("main loop");
-7
View File
@@ -1,10 +1,3 @@
[target.thumbv8m.main-none-eabi]
rustflags = [
"-C", "link-arg=-Tlink.x",
"-C", "link-arg=-Tdefmt.x",
"-C", "debug-assertions",
]
[target.thumbv8m.main-none-eabihf] [target.thumbv8m.main-none-eabihf]
rustflags = [ rustflags = [
"-C", "link-arg=-Tlink.x", "-C", "link-arg=-Tlink.x",
-6
View File
@@ -23,9 +23,3 @@ panic-probe = { version = "1.0.0", features = ["print-defmt"] }
lpc55-hal = { git = "https://github.com/ktims/lpc55-hal", branch = "main" } lpc55-hal = { git = "https://github.com/ktims/lpc55-hal", branch = "main" }
usb-device.workspace = true usb-device.workspace = true
usbd-uac2 = { workspace = true, features = ["defmt"] } usbd-uac2 = { workspace = true, features = ["defmt"] }
[profile.release]
opt-level = "z"
lto = true
debug = true
codegen-units = 1
+50 -5
View File
@@ -1,3 +1,47 @@
//! USB Audio Class 2.0
//!
//! This crate provides a USB Audio Class 2.0 implementation for
//! [usb-device](https://crates.io/crates/usb-device). It implements all
//! required elements of the specification, however many controls are not
//! implemented (e.g. mixers, effects).
//!
//! Device behaviour is driven by implementing the `ClockSource` and
//! `AudioHandler` traits to configure the audio pipeline and source/sink data
//! to/from USB.
//!
//! Example (creates a UAC2 device with in and out streams):
//!
//! ```ignore
//! let mut audio = YourTraitImpl {...};
//!
//! let config = UsbAudioClassConfig::new(usb_speed, FunctionCode::IoBox, &mut audio)
//! // base_id is USB entity ID, id 1 is always taken by the clock source, and each stream builds 2 entities
//! .with_output_config(TerminalConfig::builder().base_id(2).build())
//! .with_input_config(TerminalConfig::builder().base_id(4).build());
//!
//! let mut uac2 = config.build(&usb_bus).unwrap();
//!
//! let mut usb_dev = usbd_uac2::builder(&usb_bus, UsbVidPid(0x1209, 0x0001))
//! .strings(&[StringDescriptors::default()
//! .manufacturer("usbd_uac2")
//! .product("example")])
//! .unwrap()
//! .max_packet_size_0(64) // Required to be 64 on HS
//! .unwrap()
//! .build();
//! ```
//!
//! No work needs to be done in the poll loop, the class implementation will
//! call your trait callbacks as required, just call the usb poll as usual:
//!
//! ```ignore
//! loop {
//! usb_dev.poll(&mut [&mut uac2]);
//! }
//! ```
//!
//! See the trait documentation or examples for additional details.
#![no_std] #![no_std]
#![allow(dead_code)] #![allow(dead_code)]
@@ -9,11 +53,12 @@ mod log;
use core::cmp::Ordering; use core::cmp::Ordering;
use core::marker::PhantomData; use core::marker::PhantomData;
use byteorder_embedded_io::{LittleEndian, ReadBytesExt, WriteBytesExt};
use constants::*; use constants::*;
use descriptors::*; use descriptors::*;
#[allow(unused_imports)]
use log::*; use log::*;
use byteorder_embedded_io::{LittleEndian, ReadBytesExt, WriteBytesExt};
use num_traits::{ConstZero, ToPrimitive}; use num_traits::{ConstZero, ToPrimitive};
use usb_device::control::{Recipient, Request, RequestType}; use usb_device::control::{Recipient, Request, RequestType};
use usb_device::device::{DEFAULT_ALTERNATE_SETTING, UsbDeviceBuilder, UsbVidPid}; use usb_device::device::{DEFAULT_ALTERNATE_SETTING, UsbDeviceBuilder, UsbVidPid};
@@ -936,8 +981,8 @@ impl<'a, B: UsbBus, AU: AudioHandler<'a, B> + ClockSource> UsbClass<B>
break; break;
} }
Err(UsbError::WouldBlock) => break, Err(UsbError::WouldBlock) => break,
Err(err) => { Err(_err) => {
debug!("EP OUT error {:?}", err); debug!("EP OUT error {:?}", _err);
} }
} }
} }
@@ -962,8 +1007,8 @@ impl<'a, B: UsbBus, AU: AudioHandler<'a, B> + ClockSource> UsbAudioClass<'a, B,
UsbSpeed::Low | UsbSpeed::Full => fb_ep.write(&fb.to_bytes_10_14()), UsbSpeed::Low | UsbSpeed::Full => fb_ep.write(&fb.to_bytes_10_14()),
UsbSpeed::High | UsbSpeed::Super => fb_ep.write(&fb.to_bytes_12_13()), UsbSpeed::High | UsbSpeed::Super => fb_ep.write(&fb.to_bytes_12_13()),
}; };
if let Err(e) = r { if let Err(_e) = r {
warn!(" feedback IN failed {:?}", e); warn!(" feedback IN failed {:?}", _e);
} }
} else { } else {
debug!(" feedback callback returned None"); debug!(" feedback callback returned None");
+16 -5
View File
@@ -7,25 +7,36 @@ pub use defmt::{debug, error, info, trace, warn};
mod no_defmt { mod no_defmt {
#[macro_export] #[macro_export]
macro_rules! trace { macro_rules! trace {
($($t:tt)*) => {}; ($($t:tt)*) => {
()
};
} }
#[macro_export] #[macro_export]
macro_rules! debug { macro_rules! debug {
($($t:tt)*) => {}; ($($t:tt)*) => {
()
};
} }
#[macro_export] #[macro_export]
macro_rules! info { macro_rules! info {
($($t:tt)*) => {}; ($($t:tt)*) => {
()
};
} }
#[macro_export] #[macro_export]
macro_rules! warn { macro_rules! warn {
($($t:tt)*) => {}; ($($t:tt)*) => {
()
};
} }
#[macro_export] #[macro_export]
macro_rules! error { macro_rules! error {
($($t:tt)*) => {}; ($($t:tt)*) => {
()
};
} }
} }
#[allow(unused_imports)]
#[cfg(not(feature = "defmt"))] #[cfg(not(feature = "defmt"))]
pub use no_defmt::*; pub use no_defmt::*;