23 Commits
Author SHA1 Message Date
ktims aa825e0a64 bump version 0.1.1 2026-08-25 12:33:55 -07:00
ktims 8dfdc2af6a update .lock 2026-08-25 12:33:25 -07:00
ktims 45550aefbe expose max_packet_size of OP endpoint to application 2026-08-25 12:31:42 -07:00
ktims 53ce6a1229 expose the AudioHandler from UsbAudioClass instance for access from the application 2026-08-25 12:30:56 -07:00
ktims 2231c8db9c badges 2026-05-17 23:32:05 -07:00
ktims 610ec6597d exclude examples from package 2026-05-17 23:14:53 -07:00
ktims 98e0fb0927 add license 2026-05-17 23:11:32 -07:00
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
ktims 719d4391b8 examples: update README 2026-05-16 16:24:57 -07:00
ktims ffbdef1b6d lpc55s28-evk: add audio input 2026-05-16 16:07:45 -07:00
ktims fb050c4a40 usbd_uac2: fix clippies, add builder, support IN data 2026-05-16 16:07:05 -07:00
ktims 24e0083090 factor into workspace, improve features & deps 2026-05-15 18:06:30 -07:00
ktims 08b1d10a92 update lpc55s28-evk for refactoring, improve comments etc. 2026-05-15 16:57:23 -07:00
ktims d64bf1f204 refactor struct names, combine clock and audio handlers, builder pattern 2026-05-15 16:55:18 -07:00
ktims a82120169f move feedback to endpoint_in_complete, where it should be 2026-05-15 13:36:56 -07:00
ktims 066c5dbf96 fix nominal_fb and max packet size for non-integral rates 2026-05-15 13:36:25 -07:00
ktims e2edb1d468 examples: add dma example for lpc55s28-evk 2026-05-14 01:51:40 -07:00
ktims a918ebc554 refactor feedback Trait fn, fix max_packet_size calculation in descriptors 2026-05-07 16:03:54 -07:00
ktims 27c105b0df lpc55s28 example refactoring and improved feedback 2026-05-07 01:30:29 -07:00
ktims 3d862e3b46 refactoring and allow &mut calls of AudioClass 2026-05-07 01:29:41 -07:00
ktims 648b4344a8 some refactoring, improved perf counting 2026-05-06 18:54:25 -07:00
28 changed files with 4584 additions and 859 deletions
+2
View File
@@ -1 +1,3 @@
/target /target
.zed
.gdbinit
+27
View File
@@ -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
Generated
+845 -51
View File
File diff suppressed because it is too large Load Diff
+26 -3
View File
@@ -1,10 +1,27 @@
[workspace]
members = [
"examples/lpc55s28-evk",
"examples/lpc55s28-evk-dma"
]
resolver = "2"
[workspace.dependencies]
usb-device = { version = "0.3", features = ["control-buffer-256"] }
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>"]
version = "0.1.0" repository = "https://github.com/ktims/usbd-uac2"
version = "0.1.1"
edition = "2024" edition = "2024"
keywords = ["no-std", "usb-device"] license = "MIT"
keywords = ["no-std", "usb-device", "audio"]
exclude = [
"examples/**"
]
[features] [features]
defmt = ["dep:defmt", "usb-device/defmt"] defmt = ["dep:defmt", "usb-device/defmt"]
@@ -15,4 +32,10 @@ defmt = { version = "1.0.1", optional = true }
embedded-io = "0.7.1" 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 = { version = "0.3", features = ["control-buffer-256"] } usb-device.workspace = true
[profile.release]
opt-level = "z"
lto = true
debug = true
codegen-units = 1
+18
View File
@@ -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.
+47
View File
@@ -0,0 +1,47 @@
![Crates.io Version](https://img.shields.io/crates/v/usbd-uac2) ![docs.rs](https://img.shields.io/docsrs/usbd-uac2)
# 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.
+56
View File
@@ -0,0 +1,56 @@
Two example backends are provided, both based on the LPCXpresso55S28 demo board, using the onboard WM8904 DAC.
## LPC55S28 (LPCXpresso55S28)
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)
development board. They use the onboard WM8904 codec, so don't require any
additional hardware for audio I/O.
They can be programmed and debugged using the CMSIS-DAP that's on the board,
though due to the security features you may need to reset into the DFU
bootloader first.
```
cargo embed --release
```
Enable logging with `DEFMT_LOG` when building, but beware that enabling defmt
logs can cause timing failures, and _must_ be drained by the host, as it is
blocking.
They work on either USBFS or USBHS, but not both at the same time. The default
is USBHS. If you would like to run the example on USBFS, disable default
features and select USBFS:
```
cargo embed --release --no-default-features --features usbfs
```
### Interrupt-driven (`lpc55s28-evk`)
Running at 32bit/48khz. Simultaneous input and output. Works on USBFS and USBHS.
This is a minimal implementation intended to demonstrate the fundamental
structure of the class driver. The architecture is not recommended for a production
device, but it does work reliably on the happy path.
This is intended primarily as a reference implementation to aid understanding of
the class driver.
### DMA-based (`lpc55s28-evk-dma`)
Running at 32bit/96khz. Output only. Works on USBFS and USBHS.
A more realistic and robust implementation using DMA. It fills a static ring
buffer as data comes in from USB, while the DMA chases it around the ring,
draining into the TX FIFO. This is efficient and decouples interrupt latency
from data delivery. It uses the DMA interrupt to track consumed slots, but as
long as the USB doesn't catch up to the read slot before this happens, there is
a lot of slack for other things to be happening.
This is a more useful demonstration, but is still lacking correct handling of
edge cases, error conditions and so on you would want in a fully fleshed out
implementation. Particularly, it behaves poorly in underrun, since the DMA will
keep emitting from the ring regardless of whether the data is valid, which
sounds terrible.
@@ -0,0 +1,12 @@
[target.thumbv8m.main-none-eabihf]
rustflags = [
"-C", "link-arg=-Tlink.x",
"-C", "link-arg=-Tdefmt.x",
# "-C", "debug-assertions",
]
[build]
target = "thumbv8m.main-none-eabihf"
[env]
DEFMT_LOG = "off"
+1
View File
@@ -0,0 +1 @@
/target
+695
View File
@@ -0,0 +1,695 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "atomic-polyfill"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4"
dependencies = [
"critical-section",
]
[[package]]
name = "autocfg"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "bare-metal"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3"
dependencies = [
"rustc_version 0.2.3",
]
[[package]]
name = "bitfield"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array 0.14.7",
]
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "byteorder-embedded-io"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed6bb9472871706c9b1f648ca527031e33d647a95706d6ab5659f22ca28d419"
dependencies = [
"byteorder",
"embedded-io",
]
[[package]]
name = "cipher"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
"crypto-common",
"inout",
]
[[package]]
name = "cortex-m"
version = "0.7.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9"
dependencies = [
"bare-metal",
"bitfield",
"critical-section",
"embedded-hal 0.2.7",
"volatile-register",
]
[[package]]
name = "cortex-m-rt"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "801d4dec46b34c299ccf6b036717ae0fce602faa4f4fe816d9013b9a7c9f5ba6"
dependencies = [
"cortex-m-rt-macros",
]
[[package]]
name = "cortex-m-rt-macros"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e37549a379a9e0e6e576fd208ee60394ccb8be963889eebba3ffe0980364f472"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "critical-section"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
[[package]]
name = "crypto-common"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
dependencies = [
"generic-array 0.14.7",
"typenum",
]
[[package]]
name = "defmt"
version = "0.3.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad"
dependencies = [
"defmt 1.0.1",
]
[[package]]
name = "defmt"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "548d977b6da32fa1d1fda2876453da1e7df63ad0304c8b3dae4dbe7b96f39b78"
dependencies = [
"bitflags",
"defmt-macros",
]
[[package]]
name = "defmt-macros"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d4fc12a85bcf441cfe44344c4b72d58493178ce635338a3f3b78943aceb258e"
dependencies = [
"defmt-parser",
"proc-macro-error2",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "defmt-parser"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e"
dependencies = [
"thiserror",
]
[[package]]
name = "defmt-rtt"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93d5a25c99d89c40f5676bec8cefe0614f17f0f40e916f98e345dae941807f9e"
dependencies = [
"critical-section",
"defmt 1.0.1",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]]
name = "embedded-hal"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff"
dependencies = [
"nb 0.1.3",
"void",
]
[[package]]
name = "embedded-hal"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89"
[[package]]
name = "embedded-io"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9eb1aa714776b75c7e67e1da744b81a129b3ff919c8712b5e1b32252c1f07cc7"
[[package]]
name = "embedded-time"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7a4b4d10ac48d08bfe3db7688c402baadb244721f30a77ce360bd24c3dffe58"
dependencies = [
"num",
]
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "generic-array"
version = "1.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eaf57c49a95fd1fe24b90b3033bee6dc7e8f1288d51494cb44e627c295e38542"
dependencies = [
"rustversion",
"typenum",
]
[[package]]
name = "hash32"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67"
dependencies = [
"byteorder",
]
[[package]]
name = "hash32"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606"
dependencies = [
"byteorder",
]
[[package]]
name = "heapless"
version = "0.7.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f"
dependencies = [
"atomic-polyfill",
"hash32 0.2.1",
"rustc_version 0.4.1",
"spin",
"stable_deref_trait",
]
[[package]]
name = "heapless"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad"
dependencies = [
"hash32 0.3.1",
"stable_deref_trait",
]
[[package]]
name = "inout"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
dependencies = [
"generic-array 0.14.7",
]
[[package]]
name = "lock_api"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
dependencies = [
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "log-to-defmt"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9454118d78a9089a15d702fbaf413da2fa23f331b8af6c5eed5784a4369173c6"
dependencies = [
"defmt 0.3.100",
"heapless 0.7.17",
"log",
]
[[package]]
name = "lpc55-hal"
version = "0.5.0"
source = "git+https://github.com/ktims/lpc55-hal?branch=main#8dfefd62aff4abd2de535f23107812dda68437be"
dependencies = [
"block-buffer",
"cipher",
"cortex-m",
"digest",
"embedded-hal 0.2.7",
"embedded-time",
"generic-array 1.3.5",
"lpc55-pac",
"nb 1.1.0",
"rand_core",
"usb-device",
"vcell",
"void",
]
[[package]]
name = "lpc55-pac"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a4952baed9d9e7e82a6bbc87333f939b90eb41df3e6c0be5e35d0ec61005f91"
dependencies = [
"cortex-m",
"cortex-m-rt",
"vcell",
]
[[package]]
name = "lpc55s28-evk-dma"
version = "0.1.0"
dependencies = [
"cortex-m",
"cortex-m-rt",
"defmt 1.0.1",
"defmt-rtt",
"embedded-hal 1.0.0",
"embedded-io",
"log-to-defmt",
"lpc55-hal",
"nb 1.1.0",
"panic-probe",
"static_cell",
"usb-device",
"usbd-uac2",
]
[[package]]
name = "modular-bitfield"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2956e537fc68236d2aa048f55704f231cc93f1c4de42fe1ecb5bd7938061fc4a"
dependencies = [
"modular-bitfield-impl",
"static_assertions",
]
[[package]]
name = "modular-bitfield-impl"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59b43b4fd69e3437618106f7754f34021b831a514f9e1a98ae863cabcd8d8dad"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "nb"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f"
dependencies = [
"nb 1.1.0",
]
[[package]]
name = "nb"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d"
[[package]]
name = "num"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b7a8e9be5e039e2ff869df49155f1c06bd01ade2117ec783e56ab0932b67a8f"
dependencies = [
"num-complex",
"num-integer",
"num-iter",
"num-rational",
"num-traits",
]
[[package]]
name = "num-complex"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "747d632c0c558b87dbabbe6a82f3b4ae03720d0646ac5b7b4dae89394be5f2c5"
dependencies = [
"num-traits",
]
[[package]]
name = "num-integer"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
dependencies = [
"num-traits",
]
[[package]]
name = "num-iter"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "panic-probe"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd402d00b0fb94c5aee000029204a46884b1262e0c443f166d86d2c0747e1a1a"
dependencies = [
"cortex-m",
"defmt 1.0.1",
]
[[package]]
name = "portable-atomic"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
[[package]]
name = "proc-macro-error-attr2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
dependencies = [
"proc-macro2",
"quote",
]
[[package]]
name = "proc-macro-error2"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
dependencies = [
"proc-macro-error-attr2",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
[[package]]
name = "rustc_version"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
dependencies = [
"semver 0.9.0",
]
[[package]]
name = "rustc_version"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
dependencies = [
"semver 1.0.28",
]
[[package]]
name = "rustversion"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "semver"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
dependencies = [
"semver-parser",
]
[[package]]
name = "semver"
version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
[[package]]
name = "semver-parser"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
dependencies = [
"lock_api",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "static_cell"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0530892bb4fa575ee0da4b86f86c667132a94b74bb72160f58ee5a4afec74c23"
dependencies = [
"portable-atomic",
]
[[package]]
name = "syn"
version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "typenum"
version = "1.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de"
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "usb-device"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98816b1accafbb09085168b90f27e93d790b4bfa19d883466b5e53315b5f06a6"
dependencies = [
"defmt 0.3.100",
"heapless 0.8.0",
"portable-atomic",
]
[[package]]
name = "usbd-uac2"
version = "0.1.0"
dependencies = [
"byteorder-embedded-io",
"defmt 1.0.1",
"embedded-io",
"modular-bitfield",
"num-traits",
"usb-device",
]
[[package]]
name = "vcell"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "void"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
[[package]]
name = "volatile-register"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc"
dependencies = [
"vcell",
]
+26
View File
@@ -0,0 +1,26 @@
[package]
name = "lpc55s28-evk-dma"
version = "0.1.0"
edition = "2024"
publish = false
[features]
default = ["usbhs"]
usbfs = []
usbhs = []
[dependencies]
cortex-m = { version = "0.7.7", features = ["critical-section-single-core"] }
cortex-m-rt = "0.7.5"
defmt = "1.0.1"
defmt-rtt = "1.1.0"
embedded-hal = "1.0.0"
embedded-io = "0.7.1"
log-to-defmt = "0.1.0"
nb = "1.1.0"
panic-probe = { version = "1.0.0", features = ["print-defmt"] }
static_cell = "2.1.1"
# Includes update to usb-device 0.3, fix for isochronous and smaller critical sections
lpc55-hal = { git = "https://github.com/ktims/lpc55-hal", branch = "main" }
usb-device.workspace = true
usbd-uac2 = { workspace = true, features = ["defmt"] }
+9
View File
@@ -0,0 +1,9 @@
[default.general]
chip = "LPC55S28JBD100"
[default.rtt]
enabled = true
[default.gdb]
enabled = true
[debug.rtt]
enabled = false
+5
View File
@@ -0,0 +1,5 @@
// Find the actual path of memory.x and add it to link search, required for building in workspace
fn main() {
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
println!("cargo:rustc-link-search={}", manifest_dir);
}
+21
View File
@@ -0,0 +1,21 @@
MEMORY
{
FLASH : ORIGIN = 0x00000000, LENGTH = 512K
/* for use with standard link.x */
RAM : ORIGIN = 0x20000000, LENGTH = 192K
/* would be used with proper link.x */
/* needs changes to r0 (initialization code) */
/* SRAM0 : ORIGIN = 0x20000000, LENGTH = 64K */
/* SRAM1 : ORIGIN = 0x20010000, LENGTH = 64K */
/* SRAM2 : ORIGIN = 0x20020000, LENGTH = 64K */
/* SRAM3 : ORIGIN = 0x20030000, LENGTH = 64K */
/* CASPER SRAM regions */
/* SRAMX0: ORIGIN = 0x1400_0000, LENGTH = 4K /1* to 0x1400_0FFF *1/ */
/* SRAMX1: ORIGIN = 0x1400_4000, LENGTH = 4K /1* to 0x1400_4FFF *1/ */
/* USB1 SRAM regin */
/* USB1_SRAM : ORIGIN = 0x40100000, LENGTH = 16K */
}
+427
View File
@@ -0,0 +1,427 @@
use hal::Syscon;
use hal::peripherals::syscon::ClockControl;
use crate::{hal, pac};
use core::cell::UnsafeCell;
use core::convert::Infallible;
use core::ptr::copy_nonoverlapping;
use core::sync::atomic::{AtomicUsize, Ordering, compiler_fence};
pub const DMA0_FLEXCOMM7_TX: u8 = 19;
#[repr(C)]
#[derive(Copy, Clone)]
pub struct DmaDescriptor {
pub xfercfg: u32,
pub src_end: *const u8,
pub dst_end: *mut u32,
pub next: *const DmaDescriptor,
}
impl defmt::Format for DmaDescriptor {
fn format(&self, fmt: defmt::Formatter) {
defmt::write!(
fmt,
"xfercfg={:x} src_end={:x} dst_end={:x} next={:x}",
self.xfercfg,
self.src_end,
self.dst_end,
self.next
)
}
}
// Channel descriptor table; linked from SRAMBASE
#[repr(C, align(512))]
pub struct DescriptorTable {
pub d: [DmaDescriptor; 32],
}
// Our ring that we will transition to once the transfer begins
#[repr(C)]
pub struct RingDescriptors<const N: usize> {
pub d: [DmaDescriptor; N],
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct PushResult {
pub written: usize,
pub dropped: usize,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum ConfigError {
SlotTooLarge,
SlotTooSmall,
SlotNotAligned,
UnsupportedWidth,
}
#[derive(Debug)]
pub enum DmaError {
Underrun,
}
impl core::error::Error for DmaError {}
impl core::fmt::Display for DmaError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("DmaUnderrun")
}
}
/// Slot-based DMA ring
pub struct DmaRing<const N: usize, const MAX_SLOT_BYTES: usize> {
dma: pac::DMA0,
/// Destination peripheral register (FIFO write register)
dst_reg: *mut u32,
// SAFETY: only written by USB task (on start)
pub(crate) channel_desc: UnsafeCell<DescriptorTable>,
// SAFETY: only written by USB task (on start)
pub(crate) desc: UnsafeCell<RingDescriptors<N>>,
slots: UnsafeCell<[[u8; MAX_SLOT_BYTES]; N]>,
/// Effective bytes per slot. Maybe be smaller than MAX_SLOT_BYTES (e.g. at lower sample rates), as the setup is designed for constant rate not constant size.
slot_bytes: usize,
/// How many bytes to transfer to the FIFO
word_bytes: usize,
// SAFETY: producer only
write_slot: UnsafeCell<usize>,
write_off: UnsafeCell<usize>,
produced: AtomicUsize,
consumed: AtomicUsize,
/// Leave at least one slot empty so producer never overwrites a slot DMA may still read.
safety_gap: usize,
pub produced_bytes: AtomicUsize,
pub consumed_bytes: AtomicUsize,
}
impl<const N: usize, const MAX_SLOT_BYTES: usize> DmaRing<N, MAX_SLOT_BYTES> {
/// Construct using PAC DMA0 + &mut SYSCON + a destination FIFO register.
pub fn new(
dma: pac::DMA0,
syscon: &mut Syscon,
dst_reg: *mut u32,
word_bytes: usize,
) -> Result<Self, ConfigError> {
if word_bytes != 1 && word_bytes != 2 && word_bytes != 4 {
return Err(ConfigError::UnsupportedWidth);
}
// Start the DMA0 clock
dma.enable_clock(syscon);
Ok(Self {
dma,
dst_reg: dst_reg,
channel_desc: UnsafeCell::new(DescriptorTable {
d: [DmaDescriptor {
xfercfg: 0,
src_end: core::ptr::null(),
dst_end: core::ptr::null_mut(),
next: core::ptr::null(),
}; 32],
}),
desc: UnsafeCell::new(RingDescriptors {
d: [DmaDescriptor {
xfercfg: 0,
src_end: core::ptr::null(),
dst_end: core::ptr::null_mut(),
next: core::ptr::null(),
}; N],
}),
slots: UnsafeCell::new([[0u8; MAX_SLOT_BYTES]; N]),
slot_bytes: MAX_SLOT_BYTES,
word_bytes,
write_slot: UnsafeCell::new(0),
write_off: UnsafeCell::new(0),
produced: AtomicUsize::new(0),
consumed: AtomicUsize::new(0),
safety_gap: 1,
produced_bytes: AtomicUsize::new(0),
consumed_bytes: AtomicUsize::new(0),
})
}
/// Optional: adjust safety gap (defaults to 1 empty slot).
pub fn set_safety_gap(&mut self, gap_slots: usize) {
self.safety_gap = gap_slots.min(N);
}
pub fn slot_size(&self) -> usize {
self.slot_bytes
}
pub fn set_slot_size(&mut self, slot_bytes: usize) -> Result<(), ConfigError> {
if slot_bytes == 0 {
return Err(ConfigError::SlotTooSmall);
}
if slot_bytes > MAX_SLOT_BYTES {
return Err(ConfigError::SlotTooLarge);
}
if slot_bytes % self.word_bytes != 0 {
return Err(ConfigError::SlotNotAligned);
}
self.slot_bytes = slot_bytes;
self.reset_producer();
Ok(())
}
/// Producer: copy into ring; commits whole slots; reports overflow by returning dropped bytes.
pub fn push(&self, mut data: &[u8]) -> PushResult {
let mut written = 0usize;
let write_slot = unsafe { &mut *self.write_slot.get() };
let write_off = unsafe { &mut *self.write_off.get() };
let slots = unsafe { &mut *self.slots.get() };
defmt::debug!(
"produced={} consumed={} fill={}",
self.produced(),
self.consumed(),
self.fill_slots()
);
while !data.is_empty() {
if self.is_full_for_producer() {
break;
}
let cap = self.slot_bytes - *write_off;
let n = core::cmp::min(cap, data.len());
unsafe {
let dst = slots[*write_slot].as_mut_ptr().add(*write_off);
copy_nonoverlapping(data.as_ptr(), dst, n);
}
*write_off += n;
written += n;
data = &data[n..];
if *write_off == self.slot_bytes {
// publish completed slot
compiler_fence(Ordering::Release);
self.produced.fetch_add(1, Ordering::Release);
*write_slot = (*write_slot + 1) % N;
*write_off = 0;
}
}
self.produced_bytes.fetch_add(written, Ordering::Release);
PushResult {
written,
dropped: data.len(),
}
}
/// Call from DMA IRQ bookkeeping when a slot has been consumed.
pub fn advance_consumed(&self, slots: usize) -> Result<(), DmaError> {
let produced = self.produced.load(Ordering::Acquire);
let consumed = self.consumed.load(Ordering::Relaxed);
if consumed < produced {
self.consumed.fetch_add(slots, Ordering::Release);
self.consumed_bytes
.fetch_add(slots * self.slot_bytes, Ordering::Relaxed);
Ok(())
} else {
defmt::error!("DMA underrun!");
Err(DmaError::Underrun)
}
}
pub fn produced(&self) -> usize {
self.produced.load(Ordering::Acquire)
}
pub fn produced_bytes(&self) -> usize {
self.produced_bytes.load(Ordering::Acquire)
}
pub fn consumed(&self) -> usize {
self.consumed.load(Ordering::Acquire)
}
pub fn consumed_bytes(&self) -> usize {
loop {
let consumed_start = self.consumed.load(Ordering::Acquire);
let reg_1 = self.dma.channel19.xfercfg.read().bits() as usize >> 16 & 0x3ff;
let reg_2 = self.dma.channel19.xfercfg.read().bits() as usize >> 16 & 0x3ff;
let consumed_end = self.consumed.load(Ordering::Acquire);
if consumed_start == consumed_end && reg_1 == reg_2 {
// 1. Map the hardware remaining countdown into a clean byte count
let remaining_bytes = if reg_1 == 0x3ff {
0 // 0x3FF means all transfers completed, 0 bytes remaining
} else {
// Formula from NXP manual: (XFERCOUNT + 1) * Data Width
(reg_1 + 1) * self.word_bytes
};
// 2. Total bytes consumed in this specific active slot
let active_slot_consumed = self.slot_bytes - remaining_bytes;
// 3. Combine with your software index history accumulator
return consumed_start * self.slot_bytes + active_slot_consumed;
}
}
}
pub fn fill_slots(&self) -> usize {
self.produced().wrapping_sub(self.consumed())
}
pub fn init(&self) {
self.init_descriptors();
// Descriptor table base
let desc = unsafe { &*self.desc.get() };
let base = self.channel_desc.get() as u32;
self.dma.srambase.write(|w| unsafe { w.bits(base) });
self.dma
.channel19
.cfg
.write(|w| w.periphreqen().enabled().hwtrigen().disabled());
self.dma
.channel19
.xfercfg
.write(|w| unsafe { w.bits(desc.d[0].xfercfg) });
self.dma.enableclr0.write(|w| unsafe { w.bits(1 << 19) });
self.dma.ctrl.write(|w| w.enable().enabled());
self.dma.setvalid0.write(|w| unsafe { w.bits(1 << 19) });
self.dma.intenset0.write(|w| unsafe { w.bits(1 << 19) });
self.dma.settrig0.write(|w| unsafe { w.bits(1 << 19) });
}
pub fn run(&self) {
self.dma.enableset0.write(|w| unsafe { w.bits(1 << 19) });
}
pub fn stop(&self) {
self.dma.enableclr0.write(|w| unsafe { w.bits(1 << 19) });
nb::block!(if (self.dma.busy0.read().bits() & 1 << 19) == 0 {
Ok(())
} else {
Err(nb::Error::<Infallible>::WouldBlock)
});
self.dma.abort0.write(|w| unsafe { w.bits(1 << 19) });
self.reset_producer();
}
fn reset_producer(&self) {
unsafe {
*(&mut *self.write_slot.get()) = 0;
*(&mut *self.write_off.get()) = 0;
}
self.produced.store(0, Ordering::Relaxed);
self.produced_bytes.store(0, Ordering::Relaxed);
self.consumed.store(0, Ordering::Relaxed);
self.consumed_bytes.store(0, Ordering::Relaxed);
}
fn is_full_for_producer(&self) -> bool {
let fill = self.fill_slots();
fill >= N.wrapping_sub(self.safety_gap)
}
fn reset_producer_init_only(&self) {
unsafe {
*self.write_slot.get() = 0;
}
unsafe {
*self.write_off.get() = 0;
}
self.produced.store(0, Ordering::Relaxed);
self.consumed.store(0, Ordering::Relaxed);
self.produced_bytes.store(0, Ordering::Relaxed);
self.consumed_bytes.store(0, Ordering::Relaxed);
}
fn init_descriptors(&self) {
let slots = unsafe { &mut *self.slots.get() };
let desc = unsafe { &mut *self.desc.get() };
let chan_desc = unsafe { &mut *self.channel_desc.get() };
defmt::debug!("slots base: &{:x}", self.slots.get());
// Pre-fill with silence so underrun replays silence.
for i in 0..N {
slots[i][..self.slot_bytes].fill(0);
}
let transfers = (self.slot_bytes / self.word_bytes) as u32;
for i in 0..N {
let src_start = slots[i].as_ptr() as usize;
let src_end = (src_start + self.slot_bytes - self.word_bytes) as *const u8;
let next = &desc.d[(i + 1) % N] as *const DmaDescriptor;
desc.d[i] = DmaDescriptor {
xfercfg: encode_xfercfg(
true, // valid
true, // reload
false, // swtrig (we use XFERCFG SWTRIG kick)
false, // clrtrig
true, // intA
false, // intB
self.word_bytes as u32,
1, // src_inc
0, // dst_inc
transfers,
),
src_end,
dst_end: self.dst_reg,
next,
};
}
chan_desc.d[19] = desc.d[0];
chan_desc.d[19].xfercfg = 0;
// reset producer indices + counters (init-only action)
self.reset_producer_init_only();
}
}
unsafe impl<const N: usize, const MAX_SLOT_BYTES: usize> Sync for DmaRing<N, MAX_SLOT_BYTES> {}
/// XFERCFG encoding follows the common LPC DMA layout:
/// - SETINTA at bit4, SETINTB at bit5
/// - WIDTH at bits 9:8
/// - SRCINC at bits 13:12
/// - DSTINC at bits 15:14
/// - XFERCOUNT at bits 25:16
/// This layout is shown in LPC DMA examples. [5](https://www.kernel.org/doc/html/latest/core-api/dma-api-howto.html)
fn encode_xfercfg(
cfgvalid: bool,
reload: bool,
swtrig: bool,
clrtrig: bool,
inta: bool,
intb: bool,
width_bytes: u32,
src_inc: u32,
dst_inc: u32,
transfers: u32,
) -> u32 {
let width_code = match width_bytes {
1 => 0,
2 => 1,
4 => 2,
_ => 0,
};
let count_field = transfers.saturating_sub(1) & 0x3FF;
((cfgvalid as u32) << 0)
| ((reload as u32) << 1)
| ((swtrig as u32) << 2)
| ((clrtrig as u32) << 3)
| ((inta as u32) << 4)
| ((intb as u32) << 5)
| ((width_code & 0x3) << 8)
| ((src_inc & 0x3) << 12)
| ((dst_inc & 0x3) << 14)
| (count_field << 16)
}
+308
View File
@@ -0,0 +1,308 @@
//! Contains hardware setup unrelated to Usb Audio Class implementation
use crate::Syscon;
use crate::hal;
use crate::{MCLK_FREQ, SAMPLE_RATE, pac};
use core::cell::UnsafeCell;
use core::mem::MaybeUninit;
use defmt::{debug, info};
use hal::{
Enabled, Iocon, Pin,
drivers::pins,
traits::wg::digital::v2::{OutputPin, ToggleableOutputPin},
typestates::pin::{gpio::direction::Output, state::Gpio},
};
pub(crate) struct PllConstants {
pub m: u16, // 1-65535
pub n: u8, // 1-255
pub p: u8, // 1-31
pub selp: u8, // 5 bits
pub seli: u8, // 6 bits
}
impl PllConstants {
pub(crate) const fn new(n: u8, m: u16, p: u8) -> Self {
assert!(n != 0, "1 <= N <= 255");
assert!(m != 0, "1 <= M <= 65535");
assert!(p != 0 && p <= 31, "1 <= P <= 31");
// Following ripped from lpc55-hal and made const
// UM 4.6.6.3.2
let selp = {
let v = (m >> 2) + 1;
if v < 31 { v } else { 31 }
} as u8;
let seli = {
let v = match m {
m if m >= 8000 => 1,
m if m >= 122 => 8000 / m,
_ => 2 * (m >> 2) + 3,
};
if v < 63 { v } else { 63 }
} as u8;
// let seli = min(2*(m >> 2) + 3, 63);
Self {
n,
m,
p,
selp,
seli,
}
}
}
impl defmt::Format for PllConstants {
fn format(&self, fmt: defmt::Formatter) {
let factor = f32::from(self.m) / (f32::from(self.n) * 2.0 * f32::from(self.p));
defmt::write!(
fmt,
"m: {} n: {} p: {} selp: {} seli: {} fout: fin * {}",
self.m,
self.n,
self.p,
self.selp,
self.seli,
factor
);
}
}
// Fo = M/(N*2*P) * Fin
// Fo = 3072/(125*2*8) * 16MHz = 24.576MHz
const AUDIO_PLL: PllConstants = PllConstants::new(125, 3072, 8);
// Set PLL0 to 24.576MHz, start, and wait for lock
// This is not exposed by lpc55-hal, unfortunately. Copy their implementation here.
pub(crate) fn init_audio_pll() {
let syscon = unsafe { &*pac::SYSCON::ptr() };
let pmc = unsafe { &*pac::PMC::ptr() };
let anactrl = unsafe { &*pac::ANACTRL::ptr() };
debug!("start clk_in");
pmc.pdruncfg0
.modify(|_, w| w.pden_xtal32m().poweredon().pden_ldoxo32m().poweredon());
syscon.clock_ctrl.modify(|_, w| w.clkin_ena().enable());
anactrl
.xo32m_ctrl
.modify(|_, w| w.enable_system_clk_out().enable());
debug!("init pll0: {}", AUDIO_PLL);
pmc.pdruncfg0
.modify(|_, w| w.pden_pll0().poweredoff().pden_pll0_sscg().poweredoff());
syscon.pll0clksel.write(|w| w.sel().enum_0x1()); // clk_in
syscon.pll0ctrl.write(|w| unsafe {
w.clken()
.enable()
.seli()
.bits(AUDIO_PLL.seli)
.selp()
.bits(AUDIO_PLL.selp)
});
syscon
.pll0ndec
.write(|w| unsafe { w.ndiv().bits(AUDIO_PLL.n) });
syscon.pll0ndec.write(|w| unsafe {
w.ndiv().bits(AUDIO_PLL.n).nreq().set_bit() // latch
});
syscon
.pll0pdec
.write(|w| unsafe { w.pdiv().bits(AUDIO_PLL.p) });
syscon.pll0pdec.write(|w| unsafe {
w.pdiv().bits(AUDIO_PLL.p).preq().set_bit() // latch
});
syscon.pll0sscg0.write(|w| unsafe { w.md_lbs().bits(0) });
syscon
.pll0sscg1
.write(|w| unsafe { w.mdiv_ext().bits(AUDIO_PLL.m).sel_ext().set_bit() });
syscon.pll0sscg1.write(|w| unsafe {
w.mdiv_ext()
.bits(AUDIO_PLL.m)
.sel_ext()
.set_bit()
.mreq()
.set_bit() // latch
.md_req()
.set_bit() // latch
});
pmc.pdruncfg0
.modify(|_, w| w.pden_pll0().poweredon().pden_pll0_sscg().poweredon());
info!("pll0 wait for lock");
let mut i = 0usize;
while syscon.pll0stat.read().lock().bit_is_clear() {
i += 1;
}
info!("pll0 locked after {} loops", i);
}
pub struct I2sTx {
pub i2s: pac::I2S7,
}
pub fn init_i2s(mut fc7: pac::FLEXCOMM7, i2s7: pac::I2S7, syscon: &mut Syscon) -> I2sTx {
defmt::debug!("init i2s");
syscon.reset(&mut fc7);
syscon.enable_clock(&mut fc7);
unsafe {
pac::IOCON::ptr().as_ref().unwrap().pio1_31.modify(|_, w| {
w.func()
.alt1()
.mode()
.inactive()
.slew()
.fast()
.invert()
.disabled()
.digimode()
.digital()
.od()
.normal()
});
pac::SYSCON::ptr()
.as_ref()
.unwrap()
.fcclksel7()
.modify(|_, w| w.sel().enum_0x5()); // MCLK
pac::SYSCON::ptr()
.as_ref()
.unwrap()
.mclkclksel
.modify(|_, w| w.sel().enum_0x1()); // PLL0
pac::SYSCON::ptr()
.as_ref()
.unwrap()
.mclkdiv
.modify(|_, w| w.div().bits(1).halt().run().reset().released()); // div by 2 = PLL0 fout / 2 = 12.288MHz, max for WM8904 @ 96k
pac::SYSCON::ptr()
.as_ref()
.unwrap()
.mclkio
.modify(|_, w| w.mclkio().output());
};
// Select I2S TX function
fc7.pselid.write(|w| w.persel().i2s_transmit());
let regs = i2s7;
// Enable TX FIFO only
regs.fifocfg.modify(|_, w| {
w.enabletx()
.enabled()
.enablerx()
.disabled()
.dmatx()
.disabled()
.txi2se0()
.zero()
});
regs.fifotrig.modify(|_, w| unsafe { w.txlvl().bits(6) });
// Flush
regs.fifocfg.modify(|_, w| w.emptytx().set_bit());
regs.cfg2
.modify(|_, w| unsafe { w.position().bits(0).framelen().bits(63) }); // framelen = 64
let bclk_div = (MCLK_FREQ / SAMPLE_RATE / 64) as u16;
regs.div
.modify(|_, w| unsafe { w.div().bits(bclk_div - 1) }); // Clock source is MCLK (12.288MHz) / 4 = 3MHz
// Config
regs.cfg1.modify(|_, w| unsafe {
w.mstslvcfg()
.normal_master()
.onechannel()
.dual_channel()
.datalen()
.bits(31)
.mainenable()
.enabled()
.mode()
.classic_mode()
.datapause()
.normal()
});
I2sTx { i2s: regs }
}
pub struct SharedLed<T: OutputPin> {
inner: UnsafeCell<T>,
}
unsafe impl<T: OutputPin> Sync for SharedLed<T> {}
impl<T: OutputPin> SharedLed<T> {
pub fn new(inner: T) -> Self {
Self {
inner: UnsafeCell::new(inner),
}
}
pub fn on(&self) {
unsafe {
(*self.inner.get()).set_low().ok();
}
}
pub fn off(&self) {
unsafe {
(*self.inner.get()).set_high().ok();
}
}
}
impl<T: OutputPin + ToggleableOutputPin> SharedLed<T> {
pub fn toggle(&self) {
unsafe {
(*self.inner.get()).toggle().ok();
}
}
}
type RedLed = Pin<pins::Pio1_6, Gpio<Output>>;
type GreenLed = Pin<pins::Pio1_7, Gpio<Output>>;
type BlueLed = Pin<pins::Pio1_4, Gpio<Output>>;
pub static RED_LED: MaybeUninit<SharedLed<RedLed>> = MaybeUninit::uninit();
pub static GREEN_LED: MaybeUninit<SharedLed<GreenLed>> = MaybeUninit::uninit();
pub static BLUE_LED: MaybeUninit<SharedLed<BlueLed>> = MaybeUninit::uninit();
pub fn init_leds(iocon: &mut Iocon<Enabled>, gpio: &mut hal::Gpio<Enabled>) {
let red_led = SharedLed::new(
pins::Pio1_6::take()
.unwrap()
.into_gpio_pin(iocon, gpio)
.into_output_low(),
);
let green_led = SharedLed::new(
pins::Pio1_7::take()
.unwrap()
.into_gpio_pin(iocon, gpio)
.into_output_low(),
);
let blue_led = SharedLed::new(
pins::Pio1_4::take()
.unwrap()
.into_gpio_pin(iocon, gpio)
.into_output_low(),
);
unsafe {
core::ptr::write(RED_LED.as_ptr() as *mut SharedLed<RedLed>, red_led);
core::ptr::write(GREEN_LED.as_ptr() as *mut SharedLed<GreenLed>, green_led);
core::ptr::write(BLUE_LED.as_ptr() as *mut SharedLed<BlueLed>, blue_led);
}
}
pub fn red_led() -> &'static SharedLed<RedLed> {
unsafe { &*RED_LED.as_ptr() }
}
pub fn green_led() -> &'static SharedLed<GreenLed> {
unsafe { &*GREEN_LED.as_ptr() }
}
pub fn blue_led() -> &'static SharedLed<BlueLed> {
unsafe { &*BLUE_LED.as_ptr() }
}
+407
View File
@@ -0,0 +1,407 @@
//! DMA based audio output example for the LPCXpresso55S28 demo board
//!
//! Uses the onboard WM8904 DAC at 96KHz. Clock is generated by PLL0. Simple proportional feedback is implemented.
//!
//! USB walks around a static ring of slots, filling them as data comes in from
//! the host. DMA chases it, filling the I2S FIFO as it drains to the DAC.
//! Feedback ensures that the host doesn't overrun or underrun the ring.
//!
//! This implementation is more suitable for real use than the interrupt-based
//! example, but it is still missing many niceties and behaves worse in
//! anomalous situations since the DMA just keeps trucking over the ring
//! regardless of the data validity.
#![no_main]
#![no_std]
#[cfg(all(feature = "usbfs", feature = "usbhs"))]
compile_error!("Choose one USB peripheral, usbfs and usbhs cannot be used together");
extern crate panic_probe;
#[defmt::panic_handler]
fn panic() -> ! {
panic_probe::hard_fault()
}
use core::sync::atomic::{AtomicBool, Ordering};
use cortex_m_rt::entry;
use defmt::debug;
use defmt_rtt as _;
use hal::raw as pac;
use hal::{
Syscon,
drivers::{Timer, UsbBus, pins},
prelude::*,
time::Hertz,
};
use lpc55_hal as hal;
use pac::interrupt;
use static_cell::StaticCell;
use usb_device::{
bus::{self},
device::{StringDescriptors, UsbVidPid},
};
use usbd_uac2::TerminalConfig;
use usbd_uac2::{
self, AudioHandler, ClockSource, RangeEntry, UsbAudioClassConfig, UsbIsochronousFeedback,
UsbSpeed, constants::FunctionCode, descriptors::ClockType,
};
use crate::dma::DmaRing;
use crate::hw::{I2sTx, blue_led, green_led, red_led};
mod dma;
mod hw;
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 MCLK_FREQ: u32 = 12288000;
const SAMPLE_RATE: u32 = 96000;
const USB_FRAME_RATE: u32 = if cfg!(feature = "usbhs") { 8000 } else { 1000 };
//latency ≈ (current_fill × FRAMES_PER_SLOT)
// + FRAMES_PER_SLOT/2 - average DMA transfer position
// + 8 - FIFO depth @ 32-bit samples
// with example values, ~2.3ms
const BYTES_PER_SAMPLE: usize = 4; // 32 bit samples
const BYTES_PER_FRAME: usize = BYTES_PER_SAMPLE * 2; // 2 channels
const FRAMES_PER_SLOT: usize = SAMPLE_RATE as usize / 2000; // run the DMA at 2khz
const BYTES_PER_SLOT: usize = FRAMES_PER_SLOT * BYTES_PER_FRAME;
const N_SLOTS: usize = 8;
const FILL_TARGET_BYTES: i32 = (BYTES_PER_SLOT * N_SLOTS) as i32 / 2;
const LOG_PERIOD: u32 = 1000;
static DMA_RING: StaticCell<DmaRing<N_SLOTS, BYTES_PER_SLOT>> = StaticCell::new();
static mut DMA_RING_REF: Option<&'static DmaRing<N_SLOTS, BYTES_PER_SLOT>> = None;
#[inline]
fn dma_ring() -> &'static DmaRing<N_SLOTS, BYTES_PER_SLOT> {
unsafe { DMA_RING_REF.unwrap() }
}
#[interrupt]
fn DMA0() {
let dma = unsafe { &*pac::DMA0::ptr() };
let inta = dma.inta0.read().bits();
let err = dma.errint0.read().bits();
if (err & (1 << 19)) != 0 {
let live = dma.channel19.xfercfg.read().bits();
let desc = unsafe { &*dma_ring().channel_desc.get() };
let mem = desc.d[19];
defmt::error!(
"DMA error ch19: live={=u32:08x} INTA={=u32:x} ERR={=u32:x}\n desc: {}",
live,
inta,
err,
mem
);
red_led().on();
dma.errint0.write(|w| unsafe { w.bits(1 << 19) });
}
if (inta & (1 << 19)) != 0 {
dma.inta0.write(|w| unsafe { w.bits(1 << 19) });
if dma_ring().advance_consumed(1).is_err() {
red_led().on();
}
}
}
struct Audio<'a, const N: usize, const MAX_SLOT_BYTES: usize> {
running: AtomicBool,
i2s: I2sTx,
dma: &'a DmaRing<N, MAX_SLOT_BYTES>,
log_counter: u32,
}
impl<const N: usize, const MAX_SLOT_BYTES: usize> Audio<'_, N, MAX_SLOT_BYTES> {
const RATES: [RangeEntry<u32>; 1] = [RangeEntry::new_fixed(SAMPLE_RATE)];
fn start(&mut self) {
red_led().off(); // clear any dma error
self.running.store(false, Ordering::Relaxed);
defmt::info!("playback armed (DMA)");
let i2s = &self.i2s.i2s;
i2s.fifotrig
.modify(|_, w| unsafe { w.txlvl().bits(6).txlvlena().enabled() });
// Enable TX FIFO
i2s.fifocfg
.modify(|_, w| w.enabletx().enabled().dmatx().enabled());
dma_ring().init();
// Enable DMA interrupt (channel 19)
unsafe { pac::NVIC::unmask(pac::Interrupt::DMA0) };
green_led().on();
}
fn stop(&self) {
// If we don't disable interrupts while stopped, we will underflow constantly and continuously refill the fifo with 0s
// We could actually stop the I2S here, but sometimes that makes the DAC misbehave. The peripheral is configured to send
// 0s when the FIFO is empty, so this is fine.
pac::NVIC::mask(pac::Interrupt::DMA0);
self.running.store(false, Ordering::Relaxed);
dma_ring().stop();
defmt::info!("playback stopped");
green_led().off();
blue_led().off();
}
}
impl<const N: usize, const MAX_SLOT_BYTES: usize, B: bus::UsbBus> AudioHandler<'_, B>
for Audio<'_, N, MAX_SLOT_BYTES>
{
fn alternate_setting_changed(&mut self, _terminal: usb_device::UsbDirection, alt_setting: u8) {
// alt setting 0 means stopped
match alt_setting {
0 => self.stop(),
1 => self.start(),
_ => defmt::error!("unexpected alt setting {}", alt_setting),
}
}
fn audio_data_rx(
&mut self,
ep: &usb_device::endpoint::Endpoint<'_, B, usb_device::endpoint::Out>,
) {
// Buffer must fit 125us of audio data (based on how `usbd_uac2` sets up the descriptors).
// Buffer must have room for one additional frame in case the host clock runs slower than the device.
let mut buf = [0; (SAMPLE_RATE.div_ceil(USB_FRAME_RATE) + 1) as usize * BYTES_PER_FRAME];
let len = match ep.read(&mut buf) {
Ok(len) => len,
Err(_) => {
defmt::error!("usb error in rx callback");
return;
}
};
let buf = &buf[..len];
let res = self.dma.push(buf);
if res.dropped != 0 {
// Overflow: some or all bytes couldn't be queued.
blue_led().toggle();
defmt::error!(
"overflowed dma ring, asked {}, wrote {}, dropped {}",
buf.len(),
res.written,
res.dropped
);
}
// If we're not running yet, wait until we reach 50% full then enable DMA requests
if !self.running.load(Ordering::Relaxed) && self.dma.fill_slots() >= (N_SLOTS / 2) {
defmt::info!(
"buffer warmed ({} slots) starting playback",
self.dma.fill_slots()
);
self.dma.run();
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
/// most hosts. The host can either filter it internally or treat it
/// instantaneously and send more data specifically when the error gets
/// large; we will absorb reasonable clock drifts with our ring buffer.
fn feedback(&mut self, nominal_rate: UsbIsochronousFeedback) -> Option<UsbIsochronousFeedback> {
// Don't want to signal an absurd rate when not consuming; let the
// buffer fill before starting feedback.
if !self.running.load(Ordering::Acquire) {
return Some(nominal_rate);
}
let produced_bytes = self.dma.produced_bytes();
let consumed_bytes = self.dma.consumed_bytes();
if produced_bytes < consumed_bytes || produced_bytes == 0 {
defmt::error!("[fb] dma underrun detected!");
red_led().on();
return Some(nominal_rate);
}
let current_bytes = (produced_bytes - consumed_bytes) as i32;
// normalize error wrt. frame size etc.
let error_permille = ((current_bytes - FILL_TARGET_BYTES) * 1000) / FILL_TARGET_BYTES;
let nominal_v = nominal_rate.to_u32_12_13() as i32;
// 0.2% which is a huge clock error
let max_allowed_deviation = nominal_v / 500;
let p_term = -(error_permille * nominal_v) / 256000; // this works reasonably well to keep the buffer
let i_term = 0; // placeholder
let mut v = nominal_v + p_term + i_term;
v = v.clamp(
nominal_v - max_allowed_deviation,
nominal_v + max_allowed_deviation,
);
self.log_counter += 1;
if self.log_counter.is_multiple_of(LOG_PERIOD) {
defmt::info!(
"fill:{}% err_pm:{} p:{} i:{} fb_delta:{} fb:{=u32:x}",
(current_bytes * 100) / (N_SLOTS * BYTES_PER_SLOT) as i32,
error_permille,
p_term,
i_term,
v - nominal_v,
v as u32
);
}
Some(UsbIsochronousFeedback::new(v as u32))
}
}
impl<const N: usize, const MAX_SLOT_BYTES: usize> ClockSource for Audio<'_, N, MAX_SLOT_BYTES> {
const CLOCK_TYPE: usbd_uac2::descriptors::ClockType = ClockType::InternalFixed;
const SOF_SYNC: bool = false;
fn sample_rate(&self) -> u32 {
Self::RATES[0].min
}
fn sample_rates(
&self,
) -> core::result::Result<&[usbd_uac2::RangeEntry<u32>], usbd_uac2::UsbAudioClassError> {
Ok(&Self::RATES)
}
fn clock_validity(&self) -> core::result::Result<bool, usbd_uac2::UsbAudioClassError> {
Ok(true)
}
}
#[entry]
fn main() -> ! {
let hal = hal::new();
let mut anactrl = hal.anactrl;
let mut pmc = hal.pmc;
let mut syscon = hal.syscon;
let mut gpio = hal.gpio.enabled(&mut syscon);
let mut iocon = hal.iocon.enabled(&mut syscon);
debug!("start");
hw::init_leds(&mut iocon, &mut gpio);
debug!("iocon");
let usb0_vbus_pin = pins::Pio0_22::take()
.unwrap()
.into_usb0_vbus_pin(&mut iocon);
let codec_i2c_pins = (
pins::Pio1_20::take().unwrap().into_i2c4_scl_pin(&mut iocon),
pins::Pio1_21::take().unwrap().into_i2c4_sda_pin(&mut iocon),
);
// We can initialize and iocon these, but there is no peripheral driver, so they do not get used
let _codec_i2s_pins = (
pins::Pio0_21::take().unwrap().into_spi7_sck_pin(&mut iocon),
pins::Pio0_20::take().unwrap().into_i2s7_sda_pin(&mut iocon),
pins::Pio0_19::take().unwrap().into_i2s7_ws_pin(&mut iocon),
pins::Pio1_31::take().unwrap(), // MCLK
);
debug!("clocks");
let clocks = hal::ClockRequirements::default()
.system_frequency(96.MHz())
.configure(&mut anactrl, &mut pmc, &mut syscon)
.unwrap();
let mut usb_delay_timer = Timer::new(
hal.ctimer
.0
.enabled(&mut syscon, clocks.support_1mhz_fro_token().unwrap()),
);
// Start PLL0 at 24.576MHz as the audio clock. The FRO cannot evenly divide
// any common audio frequencies and is not particularly stable anyway.
hw::init_audio_pll();
debug!("peripherals");
let i2c_peripheral = hal
.flexcomm
.4
.enabled_as_i2c(&mut syscon, &clocks.support_flexcomm_token().unwrap());
let mut i2c_bus = I2cMaster::new(
i2c_peripheral,
codec_i2c_pins,
Hertz::try_from(400.kHz()).unwrap(),
);
let i2s_peripheral = {
let fc7 = hal.flexcomm.7.release();
hw::init_i2s(fc7.0, fc7.2, &mut syscon)
};
#[cfg(feature = "usbhs")]
let (usb_speed, usb_peripheral) = (
UsbSpeed::High,
hal.usbhs.enabled_as_device(
&mut anactrl,
&mut pmc,
&mut syscon,
&mut usb_delay_timer,
clocks.support_usbhs_token().unwrap(),
),
);
#[cfg(feature = "usbfs")]
let (usb_speed, usb_peripheral) = (
UsbSpeed::Full,
hal.usbfs.enabled_as_device(
&mut anactrl,
&mut pmc,
&mut syscon,
clocks.support_usbfs_token().unwrap(),
),
);
let usb_bus = UsbBus::new(usb_peripheral, usb0_vbus_pin);
defmt::debug!("codec init");
wm8904::init_codec(&mut i2c_bus);
defmt::debug!("dma init");
let i2s_dma_addr = &i2s_peripheral.i2s.fifowr as *const _ as *mut u32;
let dma =
DmaRing::<N_SLOTS, BYTES_PER_SLOT>::new(hal.dma.release(), &mut syscon, i2s_dma_addr, 4)
.unwrap();
let dma_ref = DMA_RING.init(dma);
unsafe { DMA_RING_REF = Some(dma_ref) };
let mut audio = Audio {
i2s: i2s_peripheral,
dma: dma_ring(),
running: AtomicBool::new(false),
log_counter: 0,
};
defmt::debug!("usb init");
let config = UsbAudioClassConfig::new(usb_speed, FunctionCode::IoBox, &mut audio)
.with_output_config(TerminalConfig::builder().base_id(2).build());
let mut uac2 = config.build(&usb_bus).unwrap();
let mut usb_dev = usbd_uac2::builder(&usb_bus, UsbVidPid(USB_VID, USB_PID))
.strings(&[StringDescriptors::default()
.manufacturer(USB_MANUFACTURER)
.product(USB_PRODUCT)])
.unwrap()
.max_packet_size_0(64) // Required to be 64 on HS, allowed on FS
.unwrap()
.build();
defmt::info!("main loop");
loop {
usb_dev.poll(&mut [&mut uac2]);
}
}
+110
View File
@@ -0,0 +1,110 @@
use cortex_m::prelude::{_embedded_hal_blocking_i2c_Write, _embedded_hal_blocking_i2c_WriteRead};
use defmt::warn;
use crate::{BYTES_PER_FRAME, CODEC_I2C_ADDR, MCLK_FREQ, SAMPLE_RATE};
// copied from NXP SDK WM8904_Init
pub(crate) fn init_codec<T>(i2c: &mut T)
where
T: _embedded_hal_blocking_i2c_WriteRead + _embedded_hal_blocking_i2c_Write,
{
let mut buf = [0u8; 2];
match i2c.write_read(CODEC_I2C_ADDR, &[0], &mut buf) {
Ok(_) => {
let chip_id = ((buf[0] as u16) << 8) | buf[1] as u16;
defmt::info!("Read chip ID: {:x}", chip_id)
}
Err(_) => defmt::error!("Error reading I2C"),
}
i2c.write(CODEC_I2C_ADDR, &[0x16, 0x00, 0x0f]).ok(); // clock rates 2 = OPCLK_ENA | CLK_SYS_ENA | CLK_DSP_ENA | TOCLK_ENA
i2c.write(CODEC_I2C_ADDR, &[0x6c, 0x01, 0x00]).ok(); // write sequencer 0 ENA
i2c.write(CODEC_I2C_ADDR, &[0x6f, 0x01, 0x00]).ok(); // write sequencer 3 START, INDEX=0
// wait on write sequencer
defmt::debug!("[codec] waiting on write seq");
loop {
let mut buf = [0; 2];
i2c.write_read(CODEC_I2C_ADDR, &[0x70], &mut buf).ok();
if buf[1] & 1 == 0 {
break;
}
}
defmt::debug!("[codec] write seq done");
i2c.write(CODEC_I2C_ADDR, &[0x14, 0x00, 0x00]).ok(); // clock rates 0
i2c.write(CODEC_I2C_ADDR, &[0x0c, 0x00, 0x00]).ok(); // power management 0 = IN PGAs disabled
i2c.write(CODEC_I2C_ADDR, &[0x0e, 0x00, 0x03]).ok(); // power management 2 = HPL_PGA_ENA | HPR_PGA_ENA
i2c.write(CODEC_I2C_ADDR, &[0x0f, 0x00, 0x00]).ok(); // power management 3 = line outs disabled
i2c.write(CODEC_I2C_ADDR, &[0x12, 0x00, 0x0c]).ok(); // power management 6 = DACL_ENA | DACR_ENA
i2c.write(CODEC_I2C_ADDR, &[0x0a, 0x00, 0x00]).ok(); // analog adc 0 = ADC_OSR128
i2c.write(CODEC_I2C_ADDR, &[0x18, 0x00, 0x50]).ok(); // audio if 0 = AIFADCR_SRC | AIFDACR_SRC
i2c.write(CODEC_I2C_ADDR, &[0x21, 0x00, 0x40]).ok(); // dac digital 1 = DAC_OSR128
i2c.write(CODEC_I2C_ADDR, &[0x2c, 0x00, 0x05]).ok(); // analog lin 0 = 0dB (unmute)
i2c.write(CODEC_I2C_ADDR, &[0x2d, 0x00, 0x05]).ok(); // analog rin 0 = 0dB (unmute)
i2c.write(CODEC_I2C_ADDR, &[0x39, 0x00, 0x39]).ok(); // analog out1 left = vol=0dB
i2c.write(CODEC_I2C_ADDR, &[0x3a, 0x00, 0x39]).ok(); // analog out1 right = vol=0dB
i2c.write(CODEC_I2C_ADDR, &[0x3b, 0x00, 0x39]).ok(); // analog out2 left = vol=0dB
i2c.write(CODEC_I2C_ADDR, &[0x3c, 0x00, 0x39]).ok(); // analog out2 right = vol=0dB
i2c.write(CODEC_I2C_ADDR, &[0x43, 0x00, 0x03]).ok(); // dc server 0 = HPOUTL_ENA | HPOUTR_ENA
i2c.write(CODEC_I2C_ADDR, &[0x5a, 0x00, 0xff]).ok(); // analog hp 0 = remove all shorts etc
i2c.write(CODEC_I2C_ADDR, &[0x5e, 0x00, 0xff]).ok(); // analog lineout 0 = remove all shorts etc
i2c.write(CODEC_I2C_ADDR, &[0x68, 0x00, 0x01]).ok(); // enable class w charge pump
i2c.write(CODEC_I2C_ADDR, &[0x62, 0x00, 0x01]).ok(); // enable charge pump
let aif_wl = match BYTES_PER_FRAME {
4 => 0, // 16 bits per sample
8 => 3, // 32 bits per sample
_ => {
warn!("only handling 16 or 32 bit samples for now");
8
}
};
i2c.write(CODEC_I2C_ADDR, &[0x19, 0x00, (aif_wl << 2) | 2])
.ok(); // audio if 1 = i2s, aif_wl
// Calculate sysclk vs. fs ratio. SYSCLK = MCLK
let fs_ratio = MCLK_FREQ / SAMPLE_RATE;
if !MCLK_FREQ.is_multiple_of(SAMPLE_RATE) {
warn!("sample rate should be a multiple of mclk")
}
let clk_sys_rate: u16 = match fs_ratio {
64 => 0,
128 => 1,
192 => 2,
256 => 3,
384 => 4,
512 => 5,
768 => 6,
1024 => 7,
1408 => 8,
1536 => 9,
_ => {
warn!("unsupport ratio {}", fs_ratio);
0
}
};
let sample_rate: u16 = match SAMPLE_RATE {
r if r < 11025 => 0, // 0-11024
r if r < 16000 => 1, // 11025 - 15999
r if r < 22050 => 2, // 16000 - 22049
r if r < 32000 => 3, // 22050 - 31999
r if r < 44100 => 4, // 32000 - 44099
_ => 5, // 44100+
};
let clock_rates_1 = ((clk_sys_rate << 10) | sample_rate).to_be_bytes();
i2c.write(CODEC_I2C_ADDR, &[0x15, clock_rates_1[0], clock_rates_1[1]])
.ok(); // sys clock rate 512fs, sample rate 48
i2c.write(CODEC_I2C_ADDR, &[0x16, 0x00, 0x0f]).ok(); // clock rates 2 = CLK_SYS_ENA
// Calculate bclk_div
let bits_per_frame = BYTES_PER_FRAME * 8;
let bits_per_second = bits_per_frame as u32 * SAMPLE_RATE;
let bclk_div = MCLK_FREQ / bits_per_second;
i2c.write(CODEC_I2C_ADDR, &[0x1a, 0x00, bclk_div as u8])
.ok(); // audio interface 2 = no gpio, bclk_div
i2c.write(CODEC_I2C_ADDR, &[0x1b, 0x00, 0x00]).ok(); // audio interface 3 = input lrclock
i2c.write(CODEC_I2C_ADDR, &[0x3d, 0x00, 0x00]).ok(); // analog out12 zc = play source = dac
i2c.write(CODEC_I2C_ADDR, &[0x1e, 0x01, 0xff]).ok(); // dac vol left = update left/right = 0dB
}
+1 -8
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",
@@ -16,4 +9,4 @@ rustflags = [
target = "thumbv8m.main-none-eabihf" target = "thumbv8m.main-none-eabihf"
[env] [env]
DEFMT_LOG = "debug" DEFMT_LOG = "off"
+325 -26
View File
@@ -2,6 +2,15 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 4 version = 4
[[package]]
name = "aho-corasick"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "atomic-polyfill" name = "atomic-polyfill"
version = "1.0.3" version = "1.0.3"
@@ -26,6 +35,17 @@ dependencies = [
"rustc_version 0.2.3", "rustc_version 0.2.3",
] ]
[[package]]
name = "bbqueue"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68917624e17aad88607cb5a5936f6da9b607c48c711e4e9ed101e7189aed28c2"
dependencies = [
"const-init",
"critical-section",
"maitake-sync",
]
[[package]] [[package]]
name = "bitfield" name = "bitfield"
version = "0.13.2" version = "0.13.2"
@@ -63,6 +83,22 @@ dependencies = [
"embedded-io", "embedded-io",
] ]
[[package]]
name = "cc"
version = "1.2.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]] [[package]]
name = "cipher" name = "cipher"
version = "0.4.4" version = "0.4.4"
@@ -73,6 +109,22 @@ dependencies = [
"inout", "inout",
] ]
[[package]]
name = "const-init"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bd422bfb4f24a97243f60b6a4443e63d810c925d8da4bb2d8fde26a7c1d57ec"
[[package]]
name = "cordyceps"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "688d7fbb8092b8de775ef2536f36c8c31f2bc4006ece2e8d8ad2d17d00ce0a2a"
dependencies = [
"loom",
"tracing",
]
[[package]] [[package]]
name = "cortex-m" name = "cortex-m"
version = "0.7.7" version = "0.7.7"
@@ -214,6 +266,27 @@ dependencies = [
"num", "num",
] ]
[[package]]
name = "find-msvc-tools"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "generator"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52f04ae4152da20c76fe800fa48659201d5cf627c5149ca0b707b69d7eef6cf9"
dependencies = [
"cc",
"cfg-if",
"libc",
"log",
"rustversion",
"windows-link",
"windows-result",
]
[[package]] [[package]]
name = "generic-array" name = "generic-array"
version = "0.14.7" version = "0.14.7"
@@ -275,16 +348,6 @@ dependencies = [
"stable_deref_trait", "stable_deref_trait",
] ]
[[package]]
name = "heapless"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25ba4bd83f9415b58b4ed8dc5714c76e626a105be4646c02630ad730ad3b5aa4"
dependencies = [
"hash32 0.3.1",
"stable_deref_trait",
]
[[package]] [[package]]
name = "inout" name = "inout"
version = "0.1.4" version = "0.1.4"
@@ -294,6 +357,18 @@ dependencies = [
"generic-array 0.14.7", "generic-array 0.14.7",
] ]
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.186"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
version = "0.4.14" version = "0.4.14"
@@ -320,9 +395,23 @@ dependencies = [
"log", "log",
] ]
[[package]]
name = "loom"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca"
dependencies = [
"cfg-if",
"generator",
"scoped-tls",
"tracing",
"tracing-subscriber",
]
[[package]] [[package]]
name = "lpc55-hal" name = "lpc55-hal"
version = "0.5.0" version = "0.5.0"
source = "git+https://github.com/ktims/lpc55-hal?branch=main#8dfefd62aff4abd2de535f23107812dda68437be"
dependencies = [ dependencies = [
"block-buffer", "block-buffer",
"cipher", "cipher",
@@ -354,23 +443,51 @@ dependencies = [
name = "lpc55s28-evk" name = "lpc55s28-evk"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bbqueue",
"cortex-m", "cortex-m",
"cortex-m-rt", "cortex-m-rt",
"defmt 1.0.1", "defmt 1.0.1",
"defmt-rtt", "defmt-rtt",
"embedded-hal 1.0.0", "embedded-hal 1.0.0",
"embedded-io", "embedded-io",
"heapless 0.9.3",
"log-to-defmt", "log-to-defmt",
"lpc55-hal", "lpc55-hal",
"nb 1.1.0",
"panic-halt",
"panic-probe", "panic-probe",
"static_cell",
"usb-device", "usb-device",
"usbd-uac2", "usbd-uac2",
] ]
[[package]]
name = "maitake-sync"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d77c365d697828821727b9bc09e6bc3c518b8c63804e79e1be5a5ae091a7c5f"
dependencies = [
"cordyceps",
"critical-section",
"loom",
"mutex-traits",
"mycelium-bitfield",
"pin-project",
"portable-atomic",
"tracing",
]
[[package]]
name = "matchers"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
dependencies = [
"regex-automata",
]
[[package]]
name = "memchr"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]] [[package]]
name = "modular-bitfield" name = "modular-bitfield"
version = "0.13.1" version = "0.13.1"
@@ -392,6 +509,18 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "mutex-traits"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3929f2b5633d29cf7b6624992e5f3c1e9334f1193423e12d17be4faf678cde3f"
[[package]]
name = "mycelium-bitfield"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24e0cc5e2c585acbd15c5ce911dff71e1f4d5313f43345873311c4f5efd741cc"
[[package]] [[package]]
name = "nb" name = "nb"
version = "0.1.3" version = "0.1.3"
@@ -407,6 +536,15 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d"
[[package]]
name = "nu-ansi-term"
version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [
"windows-sys",
]
[[package]] [[package]]
name = "num" name = "num"
version = "0.3.1" version = "0.3.1"
@@ -470,10 +608,10 @@ dependencies = [
] ]
[[package]] [[package]]
name = "panic-halt" name = "once_cell"
version = "1.0.0" version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a513e167849a384b7f9b746e517604398518590a9142f4846a32e3c2a4de7b11" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]] [[package]]
name = "panic-probe" name = "panic-probe"
@@ -485,6 +623,32 @@ dependencies = [
"defmt 1.0.1", "defmt 1.0.1",
] ]
[[package]]
name = "pin-project"
version = "1.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbf0d9e68100b3a7989b4901972f265cd542e560a3a8a724e1e20322f4d06ce9"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a990e22f43e84855daf260dded30524ef4a9021cc7541c26540500a50b624389"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "pin-project-lite"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]] [[package]]
name = "portable-atomic" name = "portable-atomic"
version = "1.13.1" version = "1.13.1"
@@ -537,6 +701,23 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
[[package]]
name = "regex-automata"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
[[package]] [[package]]
name = "rustc_version" name = "rustc_version"
version = "0.2.3" version = "0.2.3"
@@ -561,6 +742,12 @@ version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "scoped-tls"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
[[package]] [[package]]
name = "scopeguard" name = "scopeguard"
version = "1.2.0" version = "1.2.0"
@@ -588,6 +775,27 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "sharded-slab"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
dependencies = [
"lazy_static",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]] [[package]]
name = "spin" name = "spin"
version = "0.9.8" version = "0.9.8"
@@ -609,15 +817,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "static_cell"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0530892bb4fa575ee0da4b86f86c667132a94b74bb72160f58ee5a4afec74c23"
dependencies = [
"portable-atomic",
]
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.117" version = "2.0.117"
@@ -649,6 +848,76 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "thread_local"
version = "1.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
dependencies = [
"cfg-if",
]
[[package]]
name = "tracing"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
dependencies = [
"pin-project-lite",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-attributes"
version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tracing-core"
version = "0.1.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
dependencies = [
"once_cell",
"valuable",
]
[[package]]
name = "tracing-log"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
dependencies = [
"log",
"once_cell",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319"
dependencies = [
"matchers",
"nu-ansi-term",
"once_cell",
"regex-automata",
"sharded-slab",
"smallvec",
"thread_local",
"tracing",
"tracing-core",
"tracing-log",
]
[[package]] [[package]]
name = "typenum" name = "typenum"
version = "1.20.0" version = "1.20.0"
@@ -684,6 +953,12 @@ dependencies = [
"usb-device", "usb-device",
] ]
[[package]]
name = "valuable"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[package]] [[package]]
name = "vcell" name = "vcell"
version = "0.1.3" version = "0.1.3"
@@ -710,3 +985,27 @@ checksum = "de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc"
dependencies = [ dependencies = [
"vcell", "vcell",
] ]
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-result"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
+11 -13
View File
@@ -2,26 +2,24 @@
name = "lpc55s28-evk" name = "lpc55s28-evk"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
publish = false
[features]
default = ["usbhs"]
usbfs = []
usbhs = []
[dependencies] [dependencies]
bbqueue = "0.7.0"
cortex-m = { version = "0.7.7", features = ["critical-section-single-core"] } cortex-m = { version = "0.7.7", features = ["critical-section-single-core"] }
cortex-m-rt = "0.7.5" cortex-m-rt = "0.7.5"
defmt = "1.0.1" defmt = "1.0.1"
defmt-rtt = "1.1.0" defmt-rtt = "1.1.0"
embedded-hal = "1.0.0" embedded-hal = "1.0.0"
embedded-io = "0.7.1" embedded-io = "0.7.1"
heapless = "0.9.3"
log-to-defmt = "0.1.0" log-to-defmt = "0.1.0"
lpc55-hal = { version = "0.5.0", path = "../lpc55-hal" }
nb = "1.1.0"
panic-halt = "1.0.0"
panic-probe = { version = "1.0.0", features = ["print-defmt"] } panic-probe = { version = "1.0.0", features = ["print-defmt"] }
static_cell = "2.1.1" # Includes update to usb-device 0.3, fix for isochronous and smaller critical sections
usb-device = "0.3" lpc55-hal = { git = "https://github.com/ktims/lpc55-hal", branch = "main" }
usbd-uac2 = { version = "0.1.0", path = "../..", features = ["defmt"]} usb-device.workspace = true
usbd-uac2 = { workspace = true, features = ["defmt"] }
[profile.release]
opt-level = "z"
lto = true
debug = true
codegen-units = 1
+5
View File
@@ -0,0 +1,5 @@
// Find the actual path of memory.x and add it to link search, required for building in workspace
fn main() {
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
println!("cargo:rustc-link-search={}", manifest_dir);
}
+360
View File
@@ -0,0 +1,360 @@
//! Contains hardware setup unrelated to Usb Audio Class implementation
use crate::Syscon;
use crate::hal;
use crate::{MCLK_FREQ, SAMPLE_RATE, pac};
use core::cell::UnsafeCell;
use core::mem::MaybeUninit;
use defmt::debug;
use hal::{
Enabled, Iocon, Pin,
drivers::pins,
peripherals::syscon::{ClockControl, ResetControl},
traits::wg::digital::v2::{OutputPin, ToggleableOutputPin},
typestates::pin::{gpio::direction::Output, state::Gpio},
};
pub(crate) struct PllConstants {
pub m: u16, // 1-65535
pub n: u8, // 1-255
pub p: u8, // 1-31
pub selp: u8, // 5 bits
pub seli: u8, // 6 bits
}
impl PllConstants {
pub(crate) const fn new(n: u8, m: u16, p: u8) -> Self {
assert!(n != 0, "1 <= N <= 255");
assert!(m != 0, "1 <= M <= 65535");
assert!(p != 0 && p <= 31, "1 <= P <= 31");
// Following ripped from lpc55-hal and made const
// UM 4.6.6.3.2
let selp = {
let v = (m >> 2) + 1;
if v < 31 { v } else { 31 }
} as u8;
let seli = {
let v = match m {
m if m >= 8000 => 1,
m if m >= 122 => 8000 / m,
_ => 2 * (m >> 2) + 3,
};
if v < 63 { v } else { 63 }
} as u8;
// let seli = min(2*(m >> 2) + 3, 63);
Self {
n,
m,
p,
selp,
seli,
}
}
}
impl defmt::Format for PllConstants {
fn format(&self, fmt: defmt::Formatter) {
let factor = f32::from(self.m) / (f32::from(self.n) * 2.0 * f32::from(self.p));
defmt::write!(
fmt,
"m: {} n: {} p: {} selp: {} seli: {} fout: fin * {}",
self.m,
self.n,
self.p,
self.selp,
self.seli,
factor
);
}
}
// Fo = M/(N*2*P) * Fin
// Fo = 3072/(125*2*8) * 16MHz = 24.576MHz
const AUDIO_PLL: PllConstants = PllConstants::new(125, 3072, 8);
// Set PLL0 to 24.576MHz, start, and wait for lock
// This is not exposed by lpc55-hal, unfortunately. Copy their implementation here.
pub(crate) fn init_audio_pll() {
let syscon = unsafe { &*pac::SYSCON::ptr() };
let pmc = unsafe { &*pac::PMC::ptr() };
let anactrl = unsafe { &*pac::ANACTRL::ptr() };
debug!("start clk_in");
pmc.pdruncfg0
.modify(|_, w| w.pden_xtal32m().poweredon().pden_ldoxo32m().poweredon());
syscon.clock_ctrl.modify(|_, w| w.clkin_ena().enable());
anactrl
.xo32m_ctrl
.modify(|_, w| w.enable_system_clk_out().enable());
debug!("init pll0: {}", AUDIO_PLL);
pmc.pdruncfg0
.modify(|_, w| w.pden_pll0().poweredoff().pden_pll0_sscg().poweredoff());
syscon.pll0clksel.write(|w| w.sel().enum_0x1()); // clk_in
syscon.pll0ctrl.write(|w| unsafe {
w.clken()
.enable()
.seli()
.bits(AUDIO_PLL.seli)
.selp()
.bits(AUDIO_PLL.selp)
});
syscon
.pll0ndec
.write(|w| unsafe { w.ndiv().bits(AUDIO_PLL.n) });
syscon.pll0ndec.write(|w| unsafe {
w.ndiv().bits(AUDIO_PLL.n).nreq().set_bit() // latch
});
syscon
.pll0pdec
.write(|w| unsafe { w.pdiv().bits(AUDIO_PLL.p) });
syscon.pll0pdec.write(|w| unsafe {
w.pdiv().bits(AUDIO_PLL.p).preq().set_bit() // latch
});
syscon.pll0sscg0.write(|w| unsafe { w.md_lbs().bits(0) });
syscon
.pll0sscg1
.write(|w| unsafe { w.mdiv_ext().bits(AUDIO_PLL.m).sel_ext().set_bit() });
syscon.pll0sscg1.write(|w| unsafe {
w.mdiv_ext()
.bits(AUDIO_PLL.m)
.sel_ext()
.set_bit()
.mreq()
.set_bit() // latch
.md_req()
.set_bit() // latch
});
pmc.pdruncfg0
.modify(|_, w| w.pden_pll0().poweredon().pden_pll0_sscg().poweredon());
debug!("pll0 wait for lock");
let mut i = 0usize;
while syscon.pll0stat.read().lock().bit_is_clear() {
i += 1;
}
debug!("pll0 locked after {} tries", i);
}
pub struct I2sHandles {
pub tx: pac::I2S7,
pub rx: pac::I2S6,
}
pub fn init_i2s(
fc7: pac::FLEXCOMM7,
i2s7: pac::I2S7,
fc6: pac::FLEXCOMM6,
i2s6: pac::I2S6,
syscon: &mut Syscon,
) -> I2sHandles {
defmt::debug!("init i2s");
// Enable BOTH
fc7.clear_reset(syscon);
fc7.enable_clock(syscon);
fc6.clear_reset(syscon);
fc6.enable_clock(syscon);
{
let sc = unsafe { pac::SYSCON::ptr().as_ref().unwrap() };
let ioc = unsafe { pac::IOCON::ptr().as_ref().unwrap() };
// MCLK source
//
sc.mclkclksel.write(|w| w.sel().enum_0x1()); // PLL0
// MCLK div
sc.mclkdiv
.write(|w| unsafe { w.div().bits(1).halt().run().reset().released() }); // div by 2 = PLL0 fout / 2 = 12.288MHz, max for WM8904 @ 96k
// MCLK out config
ioc.pio1_31.modify(|_, w| {
w.func()
.alt1()
.mode()
.inactive()
.slew()
.fast()
.invert()
.disabled()
.digimode()
.digital()
.od()
.normal()
});
// FC7 clock
sc.fcclksel7().modify(|_, w| w.sel().enum_0x5()); // MCLK
// FC6 clock
sc.fcclksel6().modify(|_, w| w.sel().enum_0x5()); // MCLK
// MCLK out
sc.mclkio.modify(|_, w| w.mclkio().output());
// Enable clock for sysctl, it's not mapped into Peripherals
sc.ahbclkctrlset[2].write(|w| unsafe { w.bits(1 << 15) });
while sc.ahbclkctrl2.read().sysctl().is_disable() {}
let sysctrl = unsafe { pac::SYSCTL::ptr().as_ref().unwrap() };
sysctrl.sharedctrlset[0].write(|w| w.sharedscksel().flexcomm7().sharedwssel().flexcomm7()); // FC7 drives shared SCK, WS
sysctrl.fcctrlsel[7].write(|w| {
w.sckinsel()
.shared_set0_i2s_signals()
.wsinsel()
.shared_set0_i2s_signals()
}); // FC7 uses shared set
sysctrl.fcctrlsel[6].write(|w| {
w.sckinsel()
.shared_set0_i2s_signals()
.wsinsel()
.shared_set0_i2s_signals()
});
// for _ in 0..1000 {
// cortex_m::asm::nop();
// }
}
// Select I2S TX function
fc7.pselid.write(|w| w.persel().i2s_transmit());
// Select I2S RX function
fc6.pselid.write(|w| w.persel().i2s_receive());
let out_regs = i2s7;
let in_regs = i2s6;
// Enable TX FIFO only
out_regs.fifocfg.write(|w| {
w.enabletx()
.enabled()
.txi2se0() // transmit 0s when empty - only supported option for 32b data
.zero()
.emptytx() // reset the tx queue
.set_bit()
});
out_regs
.cfg2
.write(|w| unsafe { w.position().bits(0).framelen().bits(63) }); // framelen = 64
let bclk_div = (MCLK_FREQ / SAMPLE_RATE / 64) as u16;
out_regs
.div
.write(|w| unsafe { w.div().bits(bclk_div - 1) }); // Clock source is MCLK (12.288MHz) / 4 = 3MHz
// TX Config
out_regs.cfg1.write(|w| unsafe {
w.mstslvcfg()
.normal_master()
.onechannel()
.dual_channel()
.datalen()
.bits(31)
.mainenable()
.enabled()
.mode()
.classic_mode()
.datapause()
.normal()
});
// Enable RX FIFO only
in_regs
.fifocfg
.write(|w| w.enablerx().enabled().emptyrx().set_bit());
in_regs
.cfg2
.write(|w| unsafe { w.position().bits(0).framelen().bits(63) }); // framelen = 64
in_regs.div.write(|w| unsafe { w.div().bits(0) });
in_regs.cfg1.write(|w| unsafe {
w.mstslvcfg()
.normal_slave_mode()
.onechannel()
.dual_channel()
.datalen()
.bits(31)
.mainenable()
.enabled()
.mode()
.classic_mode()
.datapause()
.normal()
});
I2sHandles {
tx: out_regs,
rx: in_regs,
}
}
pub struct SharedLed<T: OutputPin> {
inner: UnsafeCell<T>,
}
unsafe impl<T: OutputPin> Sync for SharedLed<T> {}
impl<T: OutputPin> SharedLed<T> {
pub fn new(inner: T) -> Self {
Self {
inner: UnsafeCell::new(inner),
}
}
pub fn on(&self) {
unsafe {
(*self.inner.get()).set_low().ok();
}
}
pub fn off(&self) {
unsafe {
(*self.inner.get()).set_high().ok();
}
}
}
impl<T: OutputPin + ToggleableOutputPin> SharedLed<T> {
pub fn toggle(&self) {
unsafe {
(*self.inner.get()).toggle().ok();
}
}
}
type RedLed = Pin<pins::Pio1_6, Gpio<Output>>;
type GreenLed = Pin<pins::Pio1_7, Gpio<Output>>;
type BlueLed = Pin<pins::Pio1_4, Gpio<Output>>;
pub static RED_LED: MaybeUninit<SharedLed<RedLed>> = MaybeUninit::uninit();
pub static GREEN_LED: MaybeUninit<SharedLed<GreenLed>> = MaybeUninit::uninit();
pub static BLUE_LED: MaybeUninit<SharedLed<BlueLed>> = MaybeUninit::uninit();
pub fn init_leds(iocon: &mut Iocon<Enabled>, gpio: &mut hal::Gpio<Enabled>) {
let red_led = SharedLed::new(
pins::Pio1_6::take()
.unwrap()
.into_gpio_pin(iocon, gpio)
.into_output_low(),
);
let green_led = SharedLed::new(
pins::Pio1_7::take()
.unwrap()
.into_gpio_pin(iocon, gpio)
.into_output_low(),
);
let blue_led = SharedLed::new(
pins::Pio1_4::take()
.unwrap()
.into_gpio_pin(iocon, gpio)
.into_output_low(),
);
unsafe {
core::ptr::write(RED_LED.as_ptr() as *mut SharedLed<RedLed>, red_led);
core::ptr::write(GREEN_LED.as_ptr() as *mut SharedLed<GreenLed>, green_led);
core::ptr::write(BLUE_LED.as_ptr() as *mut SharedLed<BlueLed>, blue_led);
}
}
pub fn red_led() -> &'static SharedLed<RedLed> {
unsafe { &*RED_LED.as_ptr() }
}
pub fn green_led() -> &'static SharedLed<GreenLed> {
unsafe { &*GREEN_LED.as_ptr() }
}
pub fn blue_led() -> &'static SharedLed<BlueLed> {
unsafe { &*BLUE_LED.as_ptr() }
}
File diff suppressed because it is too large Load Diff
+14 -8
View File
@@ -1,7 +1,7 @@
use cortex_m::prelude::{_embedded_hal_blocking_i2c_Write, _embedded_hal_blocking_i2c_WriteRead}; use cortex_m::prelude::{_embedded_hal_blocking_i2c_Write, _embedded_hal_blocking_i2c_WriteRead};
use defmt::warn; use defmt::warn;
use crate::{CODEC_I2C_ADDR, MCLK_FREQ, SAMPLE_RATE, SampleType}; use crate::{BYTES_PER_FRAME, CODEC_I2C_ADDR, MCLK_FREQ, SAMPLE_RATE};
// copied from NXP SDK WM8904_Init // copied from NXP SDK WM8904_Init
pub(crate) fn init_codec<T>(i2c: &mut T) pub(crate) fn init_codec<T>(i2c: &mut T)
@@ -31,26 +31,29 @@ where
} }
defmt::debug!("[codec] write seq done"); defmt::debug!("[codec] write seq done");
i2c.write(CODEC_I2C_ADDR, &[0x14, 0x00, 0x00]).ok(); // clock rates 0 i2c.write(CODEC_I2C_ADDR, &[0x14, 0x00, 0x00]).ok(); // clock rates 0
i2c.write(CODEC_I2C_ADDR, &[0x0c, 0x00, 0x00]).ok(); // power management 0 = IN PGAs disabled i2c.write(CODEC_I2C_ADDR, &[0x05, 0x00, 0x03]).ok(); // vmid control 0 = normal | enabled
i2c.write(CODEC_I2C_ADDR, &[0x0c, 0x00, 0x03]).ok(); // power management 0 = INL_ENA | INR_ENA
i2c.write(CODEC_I2C_ADDR, &[0x0e, 0x00, 0x03]).ok(); // power management 2 = HPL_PGA_ENA | HPR_PGA_ENA i2c.write(CODEC_I2C_ADDR, &[0x0e, 0x00, 0x03]).ok(); // power management 2 = HPL_PGA_ENA | HPR_PGA_ENA
i2c.write(CODEC_I2C_ADDR, &[0x0f, 0x00, 0x00]).ok(); // power management 3 = line outs disabled i2c.write(CODEC_I2C_ADDR, &[0x0f, 0x00, 0x00]).ok(); // power management 3 = line outs disabled
i2c.write(CODEC_I2C_ADDR, &[0x12, 0x00, 0x0c]).ok(); // power management 6 = DACL_ENA | DACR_ENA i2c.write(CODEC_I2C_ADDR, &[0x12, 0x00, 0x0f]).ok(); // power management 6 = DACL_ENA | DACR_ENA | ADCL_ENA | ADCR_ENA
i2c.write(CODEC_I2C_ADDR, &[0x0a, 0x00, 0x00]).ok(); // analog adc 0 = ADC_OSR128 i2c.write(CODEC_I2C_ADDR, &[0x0a, 0x00, 0x01]).ok(); // analog adc 0 = ADC_OSR128
i2c.write(CODEC_I2C_ADDR, &[0x18, 0x00, 0x50]).ok(); // audio if 0 = AIFADCR_SRC | AIFDACR_SRC i2c.write(CODEC_I2C_ADDR, &[0x18, 0x00, 0x50]).ok(); // audio if 0 = AIFADCR_SRC | AIFDACR_SRC
i2c.write(CODEC_I2C_ADDR, &[0x21, 0x00, 0x40]).ok(); // dac digital 1 = DAC_OSR128 i2c.write(CODEC_I2C_ADDR, &[0x21, 0x00, 0x40]).ok(); // dac digital 1 = DAC_OSR128
i2c.write(CODEC_I2C_ADDR, &[0x2c, 0x00, 0x05]).ok(); // analog lin 0 = 0dB (unmute) i2c.write(CODEC_I2C_ADDR, &[0x2c, 0x00, 0x05]).ok(); // analog lin 0 = 0dB (unmute)
i2c.write(CODEC_I2C_ADDR, &[0x2d, 0x00, 0x05]).ok(); // analog rin 0 = 0dB (unmute) i2c.write(CODEC_I2C_ADDR, &[0x2d, 0x00, 0x05]).ok(); // analog rin 0 = 0dB (unmute)
i2c.write(CODEC_I2C_ADDR, &[0x2e, 0x00, 1 << 4]).ok(); // analog lin 1 = single ended, inv_input = in2l
i2c.write(CODEC_I2C_ADDR, &[0x2f, 0x00, 1 << 4]).ok(); // analog rin 1 = single ended, inv_input = in2r
i2c.write(CODEC_I2C_ADDR, &[0x39, 0x00, 0x39]).ok(); // analog out1 left = vol=0dB i2c.write(CODEC_I2C_ADDR, &[0x39, 0x00, 0x39]).ok(); // analog out1 left = vol=0dB
i2c.write(CODEC_I2C_ADDR, &[0x3a, 0x00, 0x39]).ok(); // analog out1 right = vol=0dB i2c.write(CODEC_I2C_ADDR, &[0x3a, 0x00, 0x39]).ok(); // analog out1 right = vol=0dB
i2c.write(CODEC_I2C_ADDR, &[0x3b, 0x00, 0x39]).ok(); // analog out2 left = vol=0dB i2c.write(CODEC_I2C_ADDR, &[0x3b, 0x00, 0x39]).ok(); // analog out2 left = vol=0dB
i2c.write(CODEC_I2C_ADDR, &[0x3c, 0x00, 0x39]).ok(); // analog out2 right = vol=0dB i2c.write(CODEC_I2C_ADDR, &[0x3c, 0x00, 0x39]).ok(); // analog out2 right = vol=0dB
i2c.write(CODEC_I2C_ADDR, &[0x43, 0x00, 0x03]).ok(); // dc server 0 = HPOUTL_ENA | HPOUTR_ENA i2c.write(CODEC_I2C_ADDR, &[0x43, 0x00, 0x03]).ok(); // dc servo 0 = HPOUTL_ENA | HPOUTR_ENA
i2c.write(CODEC_I2C_ADDR, &[0x5a, 0x00, 0xff]).ok(); // analog hp 0 = remove all shorts etc i2c.write(CODEC_I2C_ADDR, &[0x5a, 0x00, 0xff]).ok(); // analog hp 0 = remove all shorts etc
i2c.write(CODEC_I2C_ADDR, &[0x5e, 0x00, 0xff]).ok(); // analog lineout 0 = remove all shorts etc i2c.write(CODEC_I2C_ADDR, &[0x5e, 0x00, 0xff]).ok(); // analog lineout 0 = remove all shorts etc
i2c.write(CODEC_I2C_ADDR, &[0x68, 0x00, 0x01]).ok(); // enable class w charge pump i2c.write(CODEC_I2C_ADDR, &[0x68, 0x00, 0x01]).ok(); // enable class w charge pump
i2c.write(CODEC_I2C_ADDR, &[0x62, 0x00, 0x01]).ok(); // enable charge pump i2c.write(CODEC_I2C_ADDR, &[0x62, 0x00, 0x01]).ok(); // enable charge pump
let aif_wl = match core::mem::size_of::<SampleType>() { let aif_wl = match BYTES_PER_FRAME {
4 => 0, // 16 bits per sample 4 => 0, // 16 bits per sample
8 => 3, // 32 bits per sample 8 => 3, // 32 bits per sample
_ => { _ => {
@@ -98,7 +101,7 @@ where
i2c.write(CODEC_I2C_ADDR, &[0x16, 0x00, 0x0f]).ok(); // clock rates 2 = CLK_SYS_ENA i2c.write(CODEC_I2C_ADDR, &[0x16, 0x00, 0x0f]).ok(); // clock rates 2 = CLK_SYS_ENA
// Calculate bclk_div // Calculate bclk_div
let bits_per_frame = core::mem::size_of::<SampleType>() * 8; let bits_per_frame = BYTES_PER_FRAME * 8;
let bits_per_second = bits_per_frame as u32 * SAMPLE_RATE; let bits_per_second = bits_per_frame as u32 * SAMPLE_RATE;
let bclk_div = MCLK_FREQ / bits_per_second; let bclk_div = MCLK_FREQ / bits_per_second;
i2c.write(CODEC_I2C_ADDR, &[0x1a, 0x00, bclk_div as u8]) i2c.write(CODEC_I2C_ADDR, &[0x1a, 0x00, bclk_div as u8])
@@ -106,5 +109,8 @@ where
i2c.write(CODEC_I2C_ADDR, &[0x1b, 0x00, 0x00]).ok(); // audio interface 3 = input lrclock i2c.write(CODEC_I2C_ADDR, &[0x1b, 0x00, 0x00]).ok(); // audio interface 3 = input lrclock
i2c.write(CODEC_I2C_ADDR, &[0x3d, 0x00, 0x00]).ok(); // analog out12 zc = play source = dac i2c.write(CODEC_I2C_ADDR, &[0x3d, 0x00, 0x00]).ok(); // analog out12 zc = play source = dac
i2c.write(CODEC_I2C_ADDR, &[0x1e, 0x01, 0xff]).ok(); // dac vol left = update left/right = 0dB i2c.write(CODEC_I2C_ADDR, &[0x1e, 0x01, 0xc0]).ok(); // dac vol left = update left/right = 0dB
i2c.write(CODEC_I2C_ADDR, &[0x24, 0x01, 0xc0]).ok(); // adc vol left = update left/right = 0dB
i2c.write(CODEC_I2C_ADDR, &[0x2c, 0x00, 0x05]).ok(); // pga gain = 0dB
i2c.write(CODEC_I2C_ADDR, &[0x2d, 0x00, 0x05]).ok(); // pga gain = 0dB
} }
+4 -4
View File
@@ -195,7 +195,7 @@ impl Descriptor for ClockSource {
writer.write_u8(self.bm_attributes())?; // bmAttributes writer.write_u8(self.bm_attributes())?; // bmAttributes
writer.write_u8(self.bm_controls())?; // bmControls writer.write_u8(self.bm_controls())?; // bmControls
writer.write_u8(self.assoc_terminal)?; // bAssocTerminal writer.write_u8(self.assoc_terminal)?; // bAssocTerminal
writer.write_u8(self.string.map_or(0, |n| u8::from(n)))?; // iClockSource writer.write_u8(self.string.map_or(0, u8::from))?; // iClockSource
Ok(()) Ok(())
} }
} }
@@ -247,7 +247,7 @@ impl Descriptor for InputTerminal {
| ((self.overflow_control as u8) << 2) | ((self.overflow_control as u8) << 2)
| ((self.phantom_power_control as u8) << 4), | ((self.phantom_power_control as u8) << 4),
)?; )?;
writer.write_u8(self.string.map_or(0, |s| u8::from(s)))?; writer.write_u8(self.string.map_or(0, u8::from))?;
Ok(()) Ok(())
} }
} }
@@ -289,7 +289,7 @@ impl Descriptor for OutputTerminal {
| ((self.underflow_control as u8) << 6), | ((self.underflow_control as u8) << 6),
)?; )?;
writer.write_u8(self.overflow_control as u8)?; writer.write_u8(self.overflow_control as u8)?;
writer.write_u8(self.string.map_or(0, |n| u8::from(n)))?; // iTerminal writer.write_u8(self.string.map_or(0, u8::from))?; // iTerminal
Ok(()) Ok(())
} }
} }
@@ -839,7 +839,7 @@ impl Descriptor for AudioStreamingInterface {
writer.write_u32::<LittleEndian>(self.format_bitmap as u32)?; writer.write_u32::<LittleEndian>(self.format_bitmap as u32)?;
writer.write_u8(self.num_channels)?; writer.write_u8(self.num_channels)?;
writer.write(&self.channel_config.bytes)?; writer.write(&self.channel_config.bytes)?;
writer.write_u8(self.string.map_or(0, |s| u8::from(s)))?; writer.write_u8(self.string.map_or(0, u8::from))?;
Ok(()) Ok(())
} }
} }
+435 -219
View File
File diff suppressed because it is too large Load Diff
+17 -5
View File
@@ -1,4 +1,5 @@
// src/log.rs (or log/mod.rs) // src/log.rs (or log/mod.rs)
#[allow(unused_imports)]
#[cfg(feature = "defmt")] #[cfg(feature = "defmt")]
pub use defmt::{debug, error, info, trace, warn}; pub use defmt::{debug, error, info, trace, warn};
@@ -6,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::*;