Compare commits
6 Commits
719d4391b8
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
2231c8db9c
|
|||
|
610ec6597d
|
|||
|
98e0fb0927
|
|||
|
a848e236d4
|
|||
|
008c62318e
|
|||
|
2b39987ef5
|
@@ -1 +1,3 @@
|
|||||||
/target
|
/target
|
||||||
|
.zed
|
||||||
|
.gdbinit
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
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
|
||||||
+12
@@ -12,10 +12,16 @@ usbd-uac2 = { path = "." }
|
|||||||
[package]
|
[package]
|
||||||
name = "usbd-uac2"
|
name = "usbd-uac2"
|
||||||
description = "USB Audio Class 2.0 for usb-device"
|
description = "USB Audio Class 2.0 for usb-device"
|
||||||
|
readme = "README.md"
|
||||||
authors = ["Keenan Tims <ktims@gotroot.ca>"]
|
authors = ["Keenan Tims <ktims@gotroot.ca>"]
|
||||||
|
repository = "https://github.com/ktims/usbd-uac2"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
license = "MIT"
|
||||||
keywords = ["no-std", "usb-device", "audio"]
|
keywords = ["no-std", "usb-device", "audio"]
|
||||||
|
exclude = [
|
||||||
|
"examples/**"
|
||||||
|
]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
defmt = ["dep:defmt", "usb-device/defmt"]
|
defmt = ["dep:defmt", "usb-device/defmt"]
|
||||||
@@ -27,3 +33,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
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
Copyright 2026 Keenan Tims
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the “Software”), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
 
|
||||||
|
|
||||||
|
# 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
@@ -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",
|
||||||
|
|||||||
@@ -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
|
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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::*;
|
||||||
|
|||||||
Reference in New Issue
Block a user