Compare commits

...

11 Commits

25 changed files with 2373 additions and 689 deletions
+2
View File
@@ -1 +1,3 @@
/target /target
.zed
.gdbinit
+39
View File
@@ -0,0 +1,39 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: fix-byte-order-marker
- id: check-case-conflict
- id: check-merge-conflict
- id: check-symlinks
- id: check-yaml
- id: end-of-file-fixer
- id: mixed-line-ending
- id: trailing-whitespace
- repo: https://github.com/pre-commit/pre-commit
rev: v3.6.2
hooks:
- id: validate_manifest
- repo: https://github.com/jorisroovers/gitlint
rev: v0.19.1
hooks:
- id: gitlint
- repo: https://github.com/FeryET/pre-commit-rust
rev: v1.2.1
hooks:
- id: fmt
- id: cargo-check
- id: clippy
- id: build
- repo: local
hooks:
- id: readme
name: readme
language: system
description: Rebuild README.md
types: [file]
files: src/lib.rs
stages:
- pre-commit
entry: cargo readme > README.md
pass_filenames: false
Generated
+802 -8
View File
@@ -2,18 +2,71 @@
# 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]]
name = "atomic-polyfill"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4"
dependencies = [
"critical-section",
]
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.5.0" version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" 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 = "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]]
name = "bitfield"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.3.2" version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 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]] [[package]]
name = "byteorder" name = "byteorder"
version = "1.5.0" version = "1.5.0"
@@ -30,20 +83,111 @@ dependencies = [
"embedded-io", "embedded-io",
] ]
[[package]]
name = "cc"
version = "1.2.62"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98"
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]]
name = "cipher"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
"crypto-common",
"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]]
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]] [[package]]
name = "defmt" name = "defmt"
version = "0.3.100" version = "0.3.100"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad" checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad"
dependencies = [ dependencies = [
"defmt 1.0.1", "defmt 1.1.0",
] ]
[[package]] [[package]]
name = "defmt" name = "defmt"
version = "1.0.1" 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 = "548d977b6da32fa1d1fda2876453da1e7df63ad0304c8b3dae4dbe7b96f39b78" checksum = "a6e524506490a1953d237cb87b1cfc1e46f88c18f10a22dfe0f507dc6bfc7f7f"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"defmt-macros", "defmt-macros",
@@ -51,9 +195,9 @@ dependencies = [
[[package]] [[package]]
name = "defmt-macros" name = "defmt-macros"
version = "1.0.1" 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 = "3d4fc12a85bcf441cfe44344c4b72d58493178ce635338a3f3b78943aceb258e" checksum = "f0a27770e9c8f719a79d8b638281f4d828f77d8fd61e0bd94451b9b85e576a0b"
dependencies = [ dependencies = [
"defmt-parser", "defmt-parser",
"proc-macro-error2", "proc-macro-error2",
@@ -71,12 +215,107 @@ dependencies = [
"thiserror", "thiserror",
] ]
[[package]]
name = "defmt-rtt"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0f73a4a4a91609e977ae3b7bd831ffa292edfd42ad140a3244a61d805b0e05e"
dependencies = [
"critical-section",
"defmt 1.1.0",
]
[[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]] [[package]]
name = "embedded-io" name = "embedded-io"
version = "0.7.1" version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9eb1aa714776b75c7e67e1da744b81a129b3ff919c8712b5e1b32252c1f07cc7" 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 = "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]]
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.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dab9e9188e97a93276e1fe7b56401b851e2b45a46d045ca658100c1303ada649"
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]] [[package]]
name = "hash32" name = "hash32"
version = "0.3.1" version = "0.3.1"
@@ -86,16 +325,188 @@ dependencies = [
"byteorder", "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]] [[package]]
name = "heapless" name = "heapless"
version = "0.8.0" version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad"
dependencies = [ dependencies = [
"hash32", "hash32 0.3.1",
"stable_deref_trait", "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 = "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]]
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 = "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]]
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.4.1",
"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"
version = "0.1.0"
dependencies = [
"bbqueue",
"cortex-m",
"cortex-m-rt",
"defmt 1.1.0",
"defmt-rtt",
"embedded-hal 1.0.0",
"embedded-io",
"log-to-defmt",
"lpc55-hal",
"panic-probe",
"usb-device",
"usbd-uac2",
]
[[package]]
name = "lpc55s28-evk-dma"
version = "0.1.0"
dependencies = [
"cortex-m",
"cortex-m-rt",
"defmt 1.1.0",
"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 = "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"
@@ -117,6 +528,95 @@ 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]]
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 = "nu-ansi-term"
version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [
"windows-sys",
]
[[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]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.19" version = "0.2.19"
@@ -126,6 +626,48 @@ dependencies = [
"autocfg", "autocfg",
] ]
[[package]]
name = "once_cell"
version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[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.1.0",
]
[[package]]
name = "pin-project"
version = "1.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b"
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"
@@ -172,6 +714,116 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
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 = "scoped-tls"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
[[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 = "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]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
dependencies = [
"lock_api",
]
[[package]] [[package]]
name = "stable_deref_trait" name = "stable_deref_trait"
version = "1.2.1" version = "1.2.1"
@@ -184,6 +836,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 = "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"
@@ -215,6 +876,82 @@ 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]]
name = "typenum"
version = "1.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.24" version = "1.0.24"
@@ -228,7 +965,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98816b1accafbb09085168b90f27e93d790b4bfa19d883466b5e53315b5f06a6" checksum = "98816b1accafbb09085168b90f27e93d790b4bfa19d883466b5e53315b5f06a6"
dependencies = [ dependencies = [
"defmt 0.3.100", "defmt 0.3.100",
"heapless", "heapless 0.8.0",
"portable-atomic", "portable-atomic",
] ]
@@ -237,9 +974,66 @@ name = "usbd-uac2"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"byteorder-embedded-io", "byteorder-embedded-io",
"defmt 1.0.1", "defmt 1.1.0",
"embedded-io", "embedded-io",
"modular-bitfield", "modular-bitfield",
"num-traits", "num-traits",
"usb-device", "usb-device",
] ]
[[package]]
name = "valuable"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[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",
]
[[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",
]
+25 -2
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>"]
repository = "https://github.com/ktims/usbd_uac2"
version = "0.1.0" version = "0.1.0"
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.
+45
View File
@@ -0,0 +1,45 @@
# usbd-uac2
USB Audio Class 2.0
This crate provides a USB Audio Class 2.0 implementation for
[usb-device](https://crates.io/crates/usb-device). It implements all
required elements of the specification, however many controls are not
implemented (e.g. mixers, effects).
Device behaviour is driven by implementing the `ClockSource` and
`AudioHandler` traits to configure the audio pipeline and source/sink data
to/from USB.
Example (creates a UAC2 device with in and out streams):
```rust
let mut audio = YourTraitImpl {...};
let config = UsbAudioClassConfig::new(usb_speed, FunctionCode::IoBox, &mut audio)
// base_id is USB entity ID, id 1 is always taken by the clock source, and each stream builds 2 entities
.with_output_config(TerminalConfig::builder().base_id(2).build())
.with_input_config(TerminalConfig::builder().base_id(4).build());
let mut uac2 = config.build(&usb_bus).unwrap();
let mut usb_dev = usbd_uac2::builder(&usb_bus, UsbVidPid(0x1209, 0x0001))
.strings(&[StringDescriptors::default()
.manufacturer("usbd_uac2")
.product("example")])
.unwrap()
.max_packet_size_0(64) // Required to be 64 on HS
.unwrap()
.build();
```
No work needs to be done in the poll loop, the class implementation will
call your trait callbacks as required, just call the usb poll as usual:
```rust
loop {
usb_dev.poll(&mut [&mut uac2]);
}
```
See the trait documentation or examples for additional details.
+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.
@@ -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",
+1 -7
View File
@@ -313,6 +313,7 @@ dependencies = [
[[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",
@@ -353,7 +354,6 @@ dependencies = [
"log-to-defmt", "log-to-defmt",
"lpc55-hal", "lpc55-hal",
"nb 1.1.0", "nb 1.1.0",
"panic-halt",
"panic-probe", "panic-probe",
"static_cell", "static_cell",
"usb-device", "usb-device",
@@ -458,12 +458,6 @@ dependencies = [
"autocfg", "autocfg",
] ]
[[package]]
name = "panic-halt"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a513e167849a384b7f9b746e517604398518590a9142f4846a32e3c2a4de7b11"
[[package]] [[package]]
name = "panic-probe" name = "panic-probe"
version = "1.0.0" version = "1.0.0"
+5 -10
View File
@@ -2,6 +2,7 @@
name = "lpc55s28-evk-dma" name = "lpc55s28-evk-dma"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
publish = false
[features] [features]
default = ["usbhs"] default = ["usbhs"]
@@ -16,16 +17,10 @@ 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"
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" 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" static_cell = "2.1.1"
usb-device = "0.3" # Includes update to usb-device 0.3, fix for isochronous and smaller critical sections
usbd-uac2 = { version = "0.1.0", path = "../..", features = ["defmt"]} lpc55-hal = { git = "https://github.com/ktims/lpc55-hal", branch = "main" }
usb-device.workspace = true
[profile.release] usbd-uac2 = { workspace = true, features = ["defmt"] }
opt-level = "z"
lto = true
debug = true
codegen-units = 1
+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);
}
+28 -4
View File
@@ -233,13 +233,37 @@ impl<const N: usize, const MAX_SLOT_BYTES: usize> DmaRing<N, MAX_SLOT_BYTES> {
pub fn produced(&self) -> usize { pub fn produced(&self) -> usize {
self.produced.load(Ordering::Acquire) self.produced.load(Ordering::Acquire)
} }
pub fn produced_bytes(&self) -> usize {
self.produced_bytes.load(Ordering::Acquire)
}
pub fn consumed(&self) -> usize { pub fn consumed(&self) -> usize {
self.consumed.load(Ordering::Acquire) self.consumed.load(Ordering::Acquire)
} }
pub fn consumed_bytes(&self) -> usize { pub fn consumed_bytes(&self) -> usize {
self.consumed.load(Ordering::Acquire) * self.slot_bytes loop {
+ (self.dma.channel19.xfercfg.read().bits() as usize >> 16 & 0x3ff) 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 { pub fn fill_slots(&self) -> usize {
@@ -319,7 +343,7 @@ impl<const N: usize, const MAX_SLOT_BYTES: usize> DmaRing<N, MAX_SLOT_BYTES> {
let slots = unsafe { &mut *self.slots.get() }; let slots = unsafe { &mut *self.slots.get() };
let desc = unsafe { &mut *self.desc.get() }; let desc = unsafe { &mut *self.desc.get() };
let chan_desc = unsafe { &mut *self.channel_desc.get() }; let chan_desc = unsafe { &mut *self.channel_desc.get() };
defmt::info!("slots base: &{:x}", self.slots.get()); defmt::debug!("slots base: &{:x}", self.slots.get());
// Pre-fill with silence so underrun replays silence. // Pre-fill with silence so underrun replays silence.
for i in 0..N { for i in 0..N {
+9 -68
View File
@@ -1,22 +1,19 @@
//! Contains hardware setup unrelated to Usb Audio Class implementation //! Contains hardware setup unrelated to Usb Audio Class implementation
use crate::hal;
use core::cell::{OnceCell, UnsafeCell};
use core::mem::MaybeUninit;
use core::ptr::null_mut;
use crate::Syscon; use crate::Syscon;
use crate::hal;
use crate::{MCLK_FREQ, SAMPLE_RATE, pac}; use crate::{MCLK_FREQ, SAMPLE_RATE, pac};
use defmt::debug;
use core::cell::UnsafeCell;
use core::mem::MaybeUninit;
use defmt::{debug, info};
use hal::{ use hal::{
Iocon, Pin, Enabled, Iocon, Pin,
drivers::pins, drivers::pins,
prelude::*,
traits::wg::digital::v2::{OutputPin, ToggleableOutputPin}, traits::wg::digital::v2::{OutputPin, ToggleableOutputPin},
typestates::pin::{gpio::direction::Output, state::Gpio}, typestates::pin::{gpio::direction::Output, state::Gpio},
}; };
use lpc55_hal::Enabled;
use static_cell::StaticCell;
pub(crate) struct PllConstants { pub(crate) struct PllConstants {
pub m: u16, // 1-65535 pub m: u16, // 1-65535
pub n: u8, // 1-255 pub n: u8, // 1-255
@@ -138,67 +135,12 @@ pub(crate) fn init_audio_pll() {
pmc.pdruncfg0 pmc.pdruncfg0
.modify(|_, w| w.pden_pll0().poweredon().pden_pll0_sscg().poweredon()); .modify(|_, w| w.pden_pll0().poweredon().pden_pll0_sscg().poweredon());
debug!("pll0 wait for lock"); info!("pll0 wait for lock");
let mut i = 0usize; let mut i = 0usize;
while syscon.pll0stat.read().lock().bit_is_clear() { while syscon.pll0stat.read().lock().bit_is_clear() {
i += 1; i += 1;
} }
debug!("pll0 locked after {} tries", i); info!("pll0 locked after {} loops", i);
}
const SYS_PLL: PllConstants = PllConstants::new(4, 75, 1); // 150MHz
pub(crate) fn init_sys_pll1() {
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 pll1: {}", SYS_PLL);
pmc.pdruncfg0.modify(|_, w| w.pden_pll1().poweredoff());
syscon.pll1clksel.write(|w| w.sel().enum_0x1()); // clk_in
syscon.pll1ctrl.write(|w| unsafe {
w.clken()
.enable()
.seli()
.bits(SYS_PLL.seli)
.selp()
.bits(SYS_PLL.selp)
});
syscon
.pll1ndec
.write(|w| unsafe { w.ndiv().bits(SYS_PLL.n) });
syscon.pll1ndec.write(|w| unsafe {
w.ndiv().bits(SYS_PLL.n).nreq().set_bit() // latch
});
syscon
.pll1mdec
.write(|w| unsafe { w.mdiv().bits(SYS_PLL.m) });
syscon
.pll1pdec
.write(|w| unsafe { w.pdiv().bits(SYS_PLL.p) });
syscon.pll1pdec.write(|w| unsafe {
w.pdiv().bits(SYS_PLL.p).preq().set_bit() // latch
});
pmc.pdruncfg0.modify(|_, w| w.pden_pll1().poweredon());
debug!("pll1 wait for lock");
let mut i = 0usize;
while syscon.pll1stat.read().lock().bit_is_clear() {
i += 1;
}
debug!("pll1 locked after {} tries", i);
// switch system clock to pll1
syscon.fmccr.modify(|_, w| w.flashtim().flashtim11());
syscon.mainclkselb.modify(|_, w| w.sel().enum_0x2()); // pll1
} }
pub struct I2sTx { pub struct I2sTx {
@@ -207,7 +149,6 @@ pub struct I2sTx {
pub fn init_i2s(mut fc7: pac::FLEXCOMM7, i2s7: pac::I2S7, syscon: &mut Syscon) -> I2sTx { pub fn init_i2s(mut fc7: pac::FLEXCOMM7, i2s7: pac::I2S7, syscon: &mut Syscon) -> I2sTx {
defmt::debug!("init i2s"); defmt::debug!("init i2s");
// Enable BOTH
syscon.reset(&mut fc7); syscon.reset(&mut fc7);
syscon.enable_clock(&mut fc7); syscon.enable_clock(&mut fc7);
+112 -105
View File
@@ -1,10 +1,15 @@
//! Interrupt driven example for the LPCXpresso55S28 demo board //! DMA based audio output example for the LPCXpresso55S28 demo board
//! //!
//! Uses the onboard WM8904 DAC at 48KHz. Clock is generated by PLL0. Simple PI feedback //! Uses the onboard WM8904 DAC at 96KHz. Clock is generated by PLL0. Simple proportional feedback is implemented.
//! is implemented.
//! //!
//! Packets from USB are placed a `heapless::spsc::Queue`. They are consumed //! USB walks around a static ring of slots, filling them as data comes in from
//! by the I2S FIFO in the FLEXCOMM7 interrupt. //! 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_main]
#![no_std] #![no_std]
@@ -33,14 +38,12 @@ use pac::interrupt;
use static_cell::StaticCell; use static_cell::StaticCell;
use usb_device::{ use usb_device::{
bus::{self}, bus::{self},
device::{StringDescriptors, UsbDeviceBuilder, UsbVidPid}, device::{StringDescriptors, UsbVidPid},
endpoint::IsochronousSynchronizationType,
}; };
use usbd_uac2::UsbIsochronousFeedback; use usbd_uac2::TerminalConfig;
use usbd_uac2::{ use usbd_uac2::{
self, AudioClassConfig, RangeEntry, TerminalConfig, UsbAudioClass, UsbAudioClockImpl, UsbSpeed, self, AudioHandler, ClockSource, RangeEntry, UsbAudioClassConfig, UsbIsochronousFeedback,
constants::{FunctionCode, TerminalType}, UsbSpeed, constants::FunctionCode, descriptors::ClockType,
descriptors::{ChannelConfig, ClockType, FormatType1, LockDelay},
}; };
use crate::dma::DmaRing; use crate::dma::DmaRing;
@@ -50,43 +53,35 @@ mod dma;
mod hw; mod hw;
mod wm8904; mod wm8904;
// pid.codes test IDs
const USB_VID: u16 = 0x1209;
const USB_PID: u16 = 0x0001;
const USB_MANUFACTURER: &str = "usbd_uac2";
const USB_PRODUCT: &str = "DMA example device";
const CODEC_I2C_ADDR: u8 = 0b0011010; const CODEC_I2C_ADDR: u8 = 0b0011010;
const MCLK_FREQ: u32 = 12288000; const MCLK_FREQ: u32 = 12288000;
const SAMPLE_RATE: u32 = 96000; const SAMPLE_RATE: u32 = 96000;
const USB_FRAME_RATE: u32 = if cfg!(feature = "usbhs") { 8000 } else { 1000 };
//latency ≈ (current_fill × FRAMES_PER_SLOT) //latency ≈ (current_fill × FRAMES_PER_SLOT)
// + FRAMES_PER_SLOT/2 - average DMA transfer position // + FRAMES_PER_SLOT/2 - average DMA transfer position
// + 8 - FIFO depth @ 32-bit samples // + 8 - FIFO depth @ 32-bit samples
// with example values, ~2.3ms
const BYTES_PER_SAMPLE: usize = 4; // 32 bit samples const BYTES_PER_SAMPLE: usize = 4; // 32 bit samples
const BYTES_PER_FRAME: usize = BYTES_PER_SAMPLE * 2; // 2 channels 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 FRAMES_PER_SLOT: usize = SAMPLE_RATE as usize / 2000; // run the DMA at 2khz
const SLOT_SIZE_BYTES: usize = FRAMES_PER_SLOT * BYTES_PER_FRAME; // run the DMA at 2khz const BYTES_PER_SLOT: usize = FRAMES_PER_SLOT * BYTES_PER_FRAME;
const N_SLOTS: usize = 32; const N_SLOTS: usize = 8;
const FILL_TARGET: i32 = (FRAMES_PER_SLOT * N_SLOTS) as i32 / 2; const FILL_TARGET_BYTES: i32 = (BYTES_PER_SLOT * N_SLOTS) as i32 / 2;
struct Clock {} const LOG_PERIOD: u32 = 1000;
impl Clock {
const RATES: [RangeEntry<u32>; 1] = [RangeEntry::new_fixed(SAMPLE_RATE)];
}
impl UsbAudioClockImpl for Clock {
const CLOCK_TYPE: usbd_uac2::descriptors::ClockType = ClockType::InternalFixed;
const SOF_SYNC: bool = false;
fn get_sample_rate(&self) -> u32 {
Clock::RATES[0].min
}
fn get_rates(
&self,
) -> core::result::Result<&[usbd_uac2::RangeEntry<u32>], usbd_uac2::UsbAudioClassError> {
Ok(&Clock::RATES)
}
fn get_clock_validity(&self) -> core::result::Result<bool, usbd_uac2::UsbAudioClassError> {
Ok(true)
}
}
static DMA_RING: StaticCell<DmaRing<N_SLOTS, SLOT_SIZE_BYTES>> = StaticCell::new(); static DMA_RING: StaticCell<DmaRing<N_SLOTS, BYTES_PER_SLOT>> = StaticCell::new();
static mut DMA_RING_REF: Option<&'static DmaRing<N_SLOTS, SLOT_SIZE_BYTES>> = None; static mut DMA_RING_REF: Option<&'static DmaRing<N_SLOTS, BYTES_PER_SLOT>> = None;
#[inline] #[inline]
fn dma_ring() -> &'static DmaRing<N_SLOTS, SLOT_SIZE_BYTES> { fn dma_ring() -> &'static DmaRing<N_SLOTS, BYTES_PER_SLOT> {
unsafe { DMA_RING_REF.unwrap() } unsafe { DMA_RING_REF.unwrap() }
} }
@@ -125,13 +120,15 @@ struct Audio<'a, const N: usize, const MAX_SLOT_BYTES: usize> {
running: AtomicBool, running: AtomicBool,
i2s: I2sTx, i2s: I2sTx,
dma: &'a DmaRing<N, MAX_SLOT_BYTES>, dma: &'a DmaRing<N, MAX_SLOT_BYTES>,
log_counter: u32,
} }
impl<const N: usize, const MAX_SLOT_BYTES: usize> Audio<'_, N, MAX_SLOT_BYTES> { impl<const N: usize, const MAX_SLOT_BYTES: usize> Audio<'_, N, MAX_SLOT_BYTES> {
fn start(&self) { const RATES: [RangeEntry<u32>; 1] = [RangeEntry::new_fixed(SAMPLE_RATE)];
fn start(&mut self) {
red_led().off(); // clear any dma error red_led().off(); // clear any dma error
self.running.store(false, Ordering::Relaxed); self.running.store(false, Ordering::Relaxed);
defmt::info!("playback starting (DMA)"); defmt::info!("playback armed (DMA)");
let i2s = &self.i2s.i2s; let i2s = &self.i2s.i2s;
i2s.fifotrig i2s.fifotrig
@@ -150,15 +147,15 @@ impl<const N: usize, const MAX_SLOT_BYTES: usize> Audio<'_, N, MAX_SLOT_BYTES> {
// If we don't disable interrupts while stopped, we will underflow constantly and continuously refill the fifo with 0s // 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 // 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. // 0s when the FIFO is empty, so this is fine.
pac::NVIC::mask(pac::Interrupt::DMA0);
self.running.store(false, Ordering::Relaxed); self.running.store(false, Ordering::Relaxed);
dma_ring().stop(); dma_ring().stop();
defmt::info!("playback stopped"); defmt::info!("playback stopped");
pac::NVIC::mask(pac::Interrupt::DMA0);
green_led().off(); green_led().off();
blue_led().off(); blue_led().off();
} }
} }
impl<const N: usize, const MAX_SLOT_BYTES: usize, B: bus::UsbBus> UsbAudioClass<'_, B> impl<const N: usize, const MAX_SLOT_BYTES: usize, B: bus::UsbBus> AudioHandler<'_, B>
for Audio<'_, N, MAX_SLOT_BYTES> for Audio<'_, N, MAX_SLOT_BYTES>
{ {
fn alternate_setting_changed(&mut self, _terminal: usb_device::UsbDirection, alt_setting: u8) { fn alternate_setting_changed(&mut self, _terminal: usb_device::UsbDirection, alt_setting: u8) {
@@ -174,7 +171,8 @@ impl<const N: usize, const MAX_SLOT_BYTES: usize, B: bus::UsbBus> UsbAudioClass<
ep: &usb_device::endpoint::Endpoint<'_, B, usb_device::endpoint::Out>, 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 fit 125us of audio data (based on how `usbd_uac2` sets up the descriptors).
let mut buf = [0; SAMPLE_RATE.div_ceil(8000) as usize * BYTES_PER_FRAME]; // 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) { let len = match ep.read(&mut buf) {
Ok(len) => len, Ok(len) => len,
@@ -199,61 +197,90 @@ impl<const N: usize, const MAX_SLOT_BYTES: usize, B: bus::UsbBus> UsbAudioClass<
} }
// If we're not running yet, wait until we reach 50% full then enable DMA requests // 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) { if !self.running.load(Ordering::Relaxed) && self.dma.fill_slots() >= (N_SLOTS / 2) {
defmt::debug!( defmt::info!(
"buffer has {} slots, start dma transfers", "buffer warmed ({} slots) starting playback",
self.dma.fill_slots() self.dma.fill_slots()
); );
self.dma.run(); self.dma.run();
self.running.store(true, Ordering::Relaxed) self.running.store(true, Ordering::Relaxed)
} }
} }
fn audio_data_tx(
&mut self,
_ep: &usb_device::endpoint::Endpoint<'_, B, usb_device::endpoint::In>,
) {
}
/// Provide rate feedback to the host, so that it doesn't over- or underflow /// Provide rate feedback to the host. P-only is stable and works fine, with
/// the buffer. Proportional-only control is stable with normal hosts, /// most hosts. The host can either filter it internally or treat it
/// adding an I term with proper tuning (quite weak) would stabilize the /// instantaneously and send more data specifically when the error gets
/// rate reported to the host but is not necessary for basic playback. /// large; we will absorb reasonable clock drifts with our ring buffer.
fn feedback(&mut self, nominal_rate: UsbIsochronousFeedback) -> Option<UsbIsochronousFeedback> { fn feedback(&mut self, nominal_rate: UsbIsochronousFeedback) -> Option<UsbIsochronousFeedback> {
const MAX_CORRECTION: i32 = 1 << 10; // ~1.6% // 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.load(Ordering::Acquire); let produced_bytes = self.dma.produced_bytes();
let consumed_bytes = self.dma.consumed_bytes(); let consumed_bytes = self.dma.consumed_bytes();
let valid = produced_bytes >= consumed_bytes; // else we are in underrun condition if produced_bytes < consumed_bytes || produced_bytes == 0 {
defmt::error!("[fb] dma underrun detected!");
red_led().on();
return Some(nominal_rate);
}
let fill_frames = if valid { let current_bytes = (produced_bytes - consumed_bytes) as i32;
(produced_bytes - consumed_bytes) as i32 / BYTES_PER_FRAME as i32 // normalize error wrt. frame size etc.
} else { let error_permille = ((current_bytes - FILL_TARGET_BYTES) * 1000) / FILL_TARGET_BYTES;
// we will emit a canonical error in the DMA ISR
defmt::debug!("[fb] dma underrun detected");
0
};
let mut error = fill_frames - FILL_TARGET;
error = error.clamp(-32, 32); // avoid huge spikes
let p = error * 256;
let i = 0; // placeholder
let correction = -(p + i);
let nominal_v = nominal_rate.to_u32_12_13() as i32; let nominal_v = nominal_rate.to_u32_12_13() as i32;
let mut v = nominal_v + correction; // 0.2% which is a huge clock error
let max_allowed_deviation = nominal_v / 500;
v = v.clamp(nominal_v - MAX_CORRECTION, nominal_v + MAX_CORRECTION); let p_term = -(error_permille * nominal_v) / 256000; // this works reasonably well to keep the buffer
let i_term = 0; // placeholder
defmt::debug!( let mut v = nominal_v + p_term + i_term;
"valid:{} fill:{} err:{} fb:{=u32:x}", v = v.clamp(
valid, nominal_v - max_allowed_deviation,
fill_frames, nominal_v + max_allowed_deviation,
error, );
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 v as u32
); );
}
Some(UsbIsochronousFeedback::new(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] #[entry]
fn main() -> ! { fn main() -> ! {
let hal = hal::new(); let hal = hal::new();
@@ -277,7 +304,7 @@ fn main() -> ! {
pins::Pio1_20::take().unwrap().into_i2c4_scl_pin(&mut iocon), pins::Pio1_20::take().unwrap().into_i2c4_scl_pin(&mut iocon),
pins::Pio1_21::take().unwrap().into_i2c4_sda_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, so they do not get used // We can initialize and iocon these, but there is no peripheral driver, so they do not get used
let _codec_i2s_pins = ( let _codec_i2s_pins = (
pins::Pio0_21::take().unwrap().into_spi7_sck_pin(&mut iocon), pins::Pio0_21::take().unwrap().into_spi7_sck_pin(&mut iocon),
pins::Pio0_20::take().unwrap().into_i2s7_sda_pin(&mut iocon), pins::Pio0_20::take().unwrap().into_i2s7_sda_pin(&mut iocon),
@@ -286,18 +313,16 @@ fn main() -> ! {
); );
debug!("clocks"); debug!("clocks");
// Run the system clock at 96MHz. The lpc55-hal will run it from the FRO. But we won't actually use these clocks, we just need the guards...
let clocks = hal::ClockRequirements::default() let clocks = hal::ClockRequirements::default()
.system_frequency(96.MHz()) .system_frequency(96.MHz())
.configure(&mut anactrl, &mut pmc, &mut syscon) .configure(&mut anactrl, &mut pmc, &mut syscon)
.unwrap(); .unwrap();
let mut _delay_timer = Timer::new( let mut usb_delay_timer = Timer::new(
hal.ctimer hal.ctimer
.0 .0
.enabled(&mut syscon, clocks.support_1mhz_fro_token().unwrap()), .enabled(&mut syscon, clocks.support_1mhz_fro_token().unwrap()),
); );
// Start PLL1 at 150MHz as main system clock
hw::init_sys_pll1();
// Start PLL0 at 24.576MHz as the audio clock. The FRO cannot evenly divide // Start PLL0 at 24.576MHz as the audio clock. The FRO cannot evenly divide
// any common audio frequencies and is not particularly stable anyway. // any common audio frequencies and is not particularly stable anyway.
hw::init_audio_pll(); hw::init_audio_pll();
@@ -326,7 +351,7 @@ fn main() -> ! {
&mut anactrl, &mut anactrl,
&mut pmc, &mut pmc,
&mut syscon, &mut syscon,
&mut _delay_timer, &mut usb_delay_timer,
clocks.support_usbhs_token().unwrap(), clocks.support_usbhs_token().unwrap(),
), ),
); );
@@ -348,48 +373,30 @@ fn main() -> ! {
defmt::debug!("dma init"); defmt::debug!("dma init");
let i2s_dma_addr = &i2s_peripheral.i2s.fifowr as *const _ as *mut u32; let i2s_dma_addr = &i2s_peripheral.i2s.fifowr as *const _ as *mut u32;
let dma = DmaRing::<32, SLOT_SIZE_BYTES>::new(hal.dma.release(), &mut syscon, i2s_dma_addr, 4) let dma =
DmaRing::<N_SLOTS, BYTES_PER_SLOT>::new(hal.dma.release(), &mut syscon, i2s_dma_addr, 4)
.unwrap(); .unwrap();
let dma_ref = DMA_RING.init(dma); let dma_ref = DMA_RING.init(dma);
unsafe { DMA_RING_REF = Some(dma_ref) }; unsafe { DMA_RING_REF = Some(dma_ref) };
let mut clock = Clock {};
let mut audio = Audio { let mut audio = Audio {
i2s: i2s_peripheral, i2s: i2s_peripheral,
dma: dma_ring(), dma: dma_ring(),
running: AtomicBool::new(false), running: AtomicBool::new(false),
log_counter: 0,
}; };
defmt::debug!("usb init"); defmt::debug!("usb init");
let config = AudioClassConfig::new(usb_speed, FunctionCode::Other, &mut clock, &mut audio) let config = UsbAudioClassConfig::new(usb_speed, FunctionCode::IoBox, &mut audio)
.with_output_config(TerminalConfig::new( .with_output_config(TerminalConfig::builder().base_id(2).build());
4,
1,
2,
FormatType1 {
bit_resolution: 32,
bytes_per_sample: 4,
},
TerminalType::ExtLineConnector,
ChannelConfig::default_chans(2),
IsochronousSynchronizationType::Asynchronous,
LockDelay::Milliseconds(10),
None,
));
let mut uac2 = config.build(&usb_bus).unwrap(); let mut uac2 = config.build(&usb_bus).unwrap();
let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x1209, 0xcc1d)) let mut usb_dev = usbd_uac2::builder(&usb_bus, UsbVidPid(USB_VID, USB_PID))
.composite_with_iads()
.strings(&[StringDescriptors::default() .strings(&[StringDescriptors::default()
.manufacturer("Generic") .manufacturer(USB_MANUFACTURER)
.product("usbd_uac2 device") .product(USB_PRODUCT)])
.serial_number("123456789")])
.unwrap() .unwrap()
.max_packet_size_0(64) .max_packet_size_0(64) // Required to be 64 on HS, allowed on FS
.unwrap() .unwrap()
.device_class(0xef)
.device_sub_class(0x02)
.device_protocol(0x01)
.build(); .build();
defmt::info!("main loop"); defmt::info!("main loop");
+1 -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 = "info" 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",
]
+6 -13
View File
@@ -2,6 +2,7 @@
name = "lpc55s28-evk" name = "lpc55s28-evk"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
publish = false
[features] [features]
default = ["usbhs"] default = ["usbhs"]
@@ -9,24 +10,16 @@ usbfs = []
usbhs = [] 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);
}
+181 -47
View File
@@ -1,8 +1,20 @@
//! Contains hardware setup unrelated to Usb Audio Class implementation //! Contains hardware setup unrelated to Usb Audio Class implementation
use crate::Syscon; use crate::Syscon;
use crate::hal;
use crate::{MCLK_FREQ, SAMPLE_RATE, pac}; use crate::{MCLK_FREQ, SAMPLE_RATE, pac};
use core::cell::UnsafeCell;
use core::mem::MaybeUninit;
use defmt::debug; 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(crate) struct PllConstants {
pub m: u16, // 1-65535 pub m: u16, // 1-65535
pub n: u8, // 1-255 pub n: u8, // 1-255
@@ -79,7 +91,7 @@ pub(crate) fn init_audio_pll() {
.xo32m_ctrl .xo32m_ctrl
.modify(|_, w| w.enable_system_clk_out().enable()); .modify(|_, w| w.enable_system_clk_out().enable());
debug!("init pll: {}", AUDIO_PLL); debug!("init pll0: {}", AUDIO_PLL);
pmc.pdruncfg0 pmc.pdruncfg0
.modify(|_, w| w.pden_pll0().poweredoff().pden_pll0_sscg().poweredoff()); .modify(|_, w| w.pden_pll0().poweredoff().pden_pll0_sscg().poweredoff());
syscon.pll0clksel.write(|w| w.sel().enum_0x1()); // clk_in syscon.pll0clksel.write(|w| w.sel().enum_0x1()); // clk_in
@@ -132,18 +144,35 @@ pub(crate) fn init_audio_pll() {
debug!("pll0 locked after {} tries", i); debug!("pll0 locked after {} tries", i);
} }
pub struct I2sTx { pub struct I2sHandles {
pub i2s: pac::I2S7, pub tx: pac::I2S7,
pub rx: pac::I2S6,
} }
pub fn init_i2s(mut fc7: pac::FLEXCOMM7, i2s7: pac::I2S7, syscon: &mut Syscon) -> I2sTx { pub fn init_i2s(
fc7: pac::FLEXCOMM7,
i2s7: pac::I2S7,
fc6: pac::FLEXCOMM6,
i2s6: pac::I2S6,
syscon: &mut Syscon,
) -> I2sHandles {
defmt::debug!("init i2s"); defmt::debug!("init i2s");
// Enable BOTH // Enable BOTH
syscon.reset(&mut fc7); fc7.clear_reset(syscon);
syscon.enable_clock(&mut fc7); fc7.enable_clock(syscon);
fc6.clear_reset(syscon);
unsafe { fc6.enable_clock(syscon);
pac::IOCON::ptr().as_ref().unwrap().pio1_31.modify(|_, w| { {
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() w.func()
.alt1() .alt1()
.mode() .mode()
@@ -157,57 +186,65 @@ pub fn init_i2s(mut fc7: pac::FLEXCOMM7, i2s7: pac::I2S7, syscon: &mut Syscon) -
.od() .od()
.normal() .normal()
}); });
pac::SYSCON::ptr() // FC7 clock
.as_ref() sc.fcclksel7().modify(|_, w| w.sel().enum_0x5()); // MCLK
.unwrap() // FC6 clock
.fcclksel7() sc.fcclksel6().modify(|_, w| w.sel().enum_0x5()); // MCLK
.modify(|_, w| w.sel().enum_0x5()); // MCLK // MCLK out
pac::SYSCON::ptr() sc.mclkio.modify(|_, w| w.mclkio().output());
.as_ref()
.unwrap() // Enable clock for sysctl, it's not mapped into Peripherals
.mclkclksel sc.ahbclkctrlset[2].write(|w| unsafe { w.bits(1 << 15) });
.modify(|_, w| w.sel().enum_0x1()); // PLL0 while sc.ahbclkctrl2.read().sysctl().is_disable() {}
pac::SYSCON::ptr() let sysctrl = unsafe { pac::SYSCTL::ptr().as_ref().unwrap() };
.as_ref() sysctrl.sharedctrlset[0].write(|w| w.sharedscksel().flexcomm7().sharedwssel().flexcomm7()); // FC7 drives shared SCK, WS
.unwrap() sysctrl.fcctrlsel[7].write(|w| {
.mclkdiv w.sckinsel()
.modify(|_, w| w.div().bits(1).halt().run().reset().released()); // div by 2 = PLL0 fout / 2 = 12.288MHz, max for WM8904 @ 96k .shared_set0_i2s_signals()
pac::SYSCON::ptr() .wsinsel()
.as_ref() .shared_set0_i2s_signals()
.unwrap() }); // FC7 uses shared set
.mclkio sysctrl.fcctrlsel[6].write(|w| {
.modify(|_, w| w.mclkio().output()); w.sckinsel()
}; .shared_set0_i2s_signals()
.wsinsel()
.shared_set0_i2s_signals()
});
// for _ in 0..1000 {
// cortex_m::asm::nop();
// }
}
// Select I2S TX function // Select I2S TX function
fc7.pselid.write(|w| w.persel().i2s_transmit()); fc7.pselid.write(|w| w.persel().i2s_transmit());
// Select I2S RX function
fc6.pselid.write(|w| w.persel().i2s_receive());
let regs = i2s7; let out_regs = i2s7;
let in_regs = i2s6;
// Enable TX FIFO only // Enable TX FIFO only
regs.fifocfg.modify(|_, w| { out_regs.fifocfg.write(|w| {
w.enabletx() w.enabletx()
.enabled() .enabled()
.enablerx() .txi2se0() // transmit 0s when empty - only supported option for 32b data
.disabled()
.dmatx()
.disabled()
.txi2se0()
.zero() .zero()
.emptytx() // reset the tx queue
.set_bit()
}); });
// Flush out_regs
regs.fifocfg.modify(|_, w| w.emptytx().set_bit()); .cfg2
.write(|w| unsafe { w.position().bits(0).framelen().bits(63) }); // framelen = 64
regs.cfg2
.modify(|_, w| unsafe { w.position().bits(0).framelen().bits(63) }); // framelen = 64
let bclk_div = (MCLK_FREQ / SAMPLE_RATE / 64) as u16; let bclk_div = (MCLK_FREQ / SAMPLE_RATE / 64) as u16;
regs.div out_regs
.modify(|_, w| unsafe { w.div().bits(bclk_div - 1) }); // Clock source is MCLK (12.288MHz) / 4 = 3MHz .div
.write(|w| unsafe { w.div().bits(bclk_div - 1) }); // Clock source is MCLK (12.288MHz) / 4 = 3MHz
// Config // TX Config
regs.cfg1.modify(|_, w| unsafe { out_regs.cfg1.write(|w| unsafe {
w.mstslvcfg() w.mstslvcfg()
.normal_master() .normal_master()
.onechannel() .onechannel()
@@ -222,5 +259,102 @@ pub fn init_i2s(mut fc7: pac::FLEXCOMM7, i2s7: pac::I2S7, syscon: &mut Syscon) -
.normal() .normal()
}); });
I2sTx { i2s: regs } // 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() }
} }
+345 -189
View File
@@ -1,10 +1,24 @@
//! Interrupt driven example for the LPCXpresso55S28 demo board //! Interrupt driven example for the LPCXpresso55S28 demo board.
//! //!
//! Uses the onboard WM8904 DAC at 48KHz. Clock is generated by PLL0. Simple PI feedback //! This is a minimal implementation intended to demonstrate only the minimum
//! is implemented. //! essential implementation, to clarify usage of the class driver.
//! //!
//! Packets from USB are placed a `heapless::spsc::Queue`. They are consumed //! Uses the EVK's WM8904 CODEC at 48KHz. Clock is generated by PLL0. Simple
//! by the I2S FIFO in the FLEXCOMM7 interrupt. //! proportional feedback is implemented.
//!
//! Packets from USB are placed in a `bbqueue` (a lock free ring buffer). They
//! are consumed by the I2S FIFO in the FLEXCOMM7 interrupt. This makes the
//! implementation very sensitive to interrupt::free critical sections (which
//! are widely used in the USB bus driver), but it works well enough at 48k.
//!
//! Audio frames from the CODEC are placed in a separate `bbqueue` from the
//! FLEXCOMM6 interrupt handler. They're drained out to the USB host in the USB
//! callback context.
//!
//! LEDs:
//! - Blue - audio under/overrun - note this toggles a bunch at playback stream startup
//! - Red - recording stream running
//! - Green - playback stream running
#![no_main] #![no_main]
#![no_std] #![no_std]
@@ -17,10 +31,14 @@ fn panic() -> ! {
panic_probe::hard_fault() panic_probe::hard_fault()
} }
use core::ptr::null_mut; use bbqueue::{
use core::sync::atomic::{AtomicBool, AtomicI32, Ordering}; nicknames::Churrasco,
prod_cons::stream::{StreamConsumer, StreamProducer},
traits::bbqhdl::BbqHandle,
};
use core::sync::atomic::{AtomicU32, Ordering};
use cortex_m_rt::entry; use cortex_m_rt::entry;
use defmt::debug; use defmt::{debug, error, info, warn};
use defmt_rtt as _; use defmt_rtt as _;
use hal::raw as pac; use hal::raw as pac;
use hal::{ use hal::{
@@ -29,186 +47,348 @@ use hal::{
prelude::*, prelude::*,
time::Hertz, time::Hertz,
}; };
use heapless::spsc::{Consumer, Producer, Queue}; use lpc55_hal as hal;
use lpc55_hal::{self as hal};
use pac::interrupt; use pac::interrupt;
use static_cell::StaticCell;
use usb_device::{ use usb_device::{
bus::{self}, bus::{self},
device::{StringDescriptors, UsbDeviceBuilder, UsbVidPid}, device::{StringDescriptors, UsbVidPid},
endpoint::IsochronousSynchronizationType,
}; };
use usbd_uac2::UsbIsochronousFeedback;
use usbd_uac2::{ use usbd_uac2::{
self, AudioClassConfig, RangeEntry, TerminalConfig, UsbAudioClass, UsbAudioClockImpl, UsbSpeed, self, AudioHandler, ClockSource, RangeEntry, TerminalConfig, UsbAudioClassConfig,
constants::{FunctionCode, TerminalType}, UsbIsochronousFeedback, UsbSpeed, constants::FunctionCode, descriptors::ClockType,
descriptors::{ChannelConfig, ClockType, FormatType1, LockDelay},
}; };
use crate::hw::I2sTx; use crate::hw::{I2sHandles, blue_led, green_led, red_led};
mod hw; mod hw;
mod wm8904; mod wm8904;
// pid.codes test IDs
const USB_VID: u16 = 0x1209;
const USB_PID: u16 = 0x0001;
const USB_MANUFACTURER: &str = "usbd_uac2";
const USB_PRODUCT: &str = "interrupt example device";
const CODEC_I2C_ADDR: u8 = 0b0011010; const CODEC_I2C_ADDR: u8 = 0b0011010;
const FIFO_LENGTH: usize = 256; // frames const FIFO_LENGTH: usize = 256; // frames
const MCLK_FREQ: u32 = 12288000; const MCLK_FREQ: u32 = 12288000;
const SAMPLE_RATE: u32 = 48000; // example implementation runs okay at 48k but not 96k const SAMPLE_RATE: u32 = 48000;
type SampleType = (i32, i32); // USB microframe rate
#[cfg(feature = "usbhs")]
const USB_FRAME_RATE: usize = 8000;
#[cfg(feature = "usbfs")]
const USB_FRAME_RATE: usize = 1000;
struct Clock {} const BYTES_PER_FRAME: usize = 8;
impl Clock { const QUEUE_BYTES: usize = FIFO_LENGTH * BYTES_PER_FRAME;
const RATES: [RangeEntry<u32>; 1] = [RangeEntry::new_fixed(SAMPLE_RATE)];
static QUEUE_AUDIO_OUT: Churrasco<QUEUE_BYTES> = Churrasco::new();
// Used for feedback calculation of current fifo state
static PRODUCED_OUT: AtomicU32 = AtomicU32::new(0);
static CONSUMED_OUT: AtomicU32 = AtomicU32::new(0);
static QUEUE_AUDIO_IN: Churrasco<QUEUE_BYTES> = Churrasco::new();
// Used for feedback calculation of current fifo state
static PRODUCED_IN: AtomicU32 = AtomicU32::new(0);
static CONSUMED_IN: AtomicU32 = AtomicU32::new(0);
/// Consume one audio output frame and send it to the I2S FIFO. If there's
/// insufficient data in the queue, return false without consuming what was
/// there.
#[inline]
fn try_consume_one_frame<T: BbqHandle>(
cons: &mut StreamConsumer<T>,
i2s: &pac::i2s7::RegisterBlock,
) -> bool {
if let Ok(rgr) = cons.read() {
if rgr.len() >= BYTES_PER_FRAME {
let l = u32::from_le_bytes(rgr[0..4].try_into().unwrap());
let r = u32::from_le_bytes(rgr[4..8].try_into().unwrap());
i2s.fifowr.write(|w| unsafe { w.bits(l) });
i2s.fifowr.write(|w| unsafe { w.bits(r) });
// consume exactly one frame (8 bytes)
rgr.release(BYTES_PER_FRAME);
CONSUMED_OUT.fetch_add(BYTES_PER_FRAME as u32, Ordering::Relaxed);
return true;
} else {
// Not enough bytes for a full frame: leave it in the queue.
return false;
}
}
false
} }
impl UsbAudioClockImpl for Clock {
/// Produce one audio input from from the I2S FIFO into the queue. If there's no
/// room in the queue, discard the sample and return false.
#[inline]
fn try_produce_one_frame<T: BbqHandle>(
prod: &mut StreamProducer<T>,
i2s: &pac::i2s6::RegisterBlock,
) -> bool {
let l = i2s.fiford.read().rxdata().bits();
let r = i2s.fiford.read().rxdata().bits();
if let Ok(mut rgr) = prod.grant_exact(BYTES_PER_FRAME) {
unsafe {
let wp = rgr.as_mut_ptr();
core::ptr::copy_nonoverlapping(&l as *const u32 as *const u8, wp, 4);
core::ptr::copy_nonoverlapping(&r as *const u32 as *const u8, wp.add(4), 4);
}
rgr.commit(BYTES_PER_FRAME * 2);
PRODUCED_IN.fetch_add(BYTES_PER_FRAME as u32, Ordering::Relaxed);
true
} else {
// No space in the queue, discard the sample
false
}
}
/// I2S TX ISR. Consume frames until the FIFO is full. Only write full frames
/// (L+R) so we can't desync left/right.
#[interrupt]
fn FLEXCOMM7() {
let i2s = unsafe { pac::I2S7::ptr().as_ref().unwrap() };
// refill until fifo has >6 words
let mut cons = QUEUE_AUDIO_OUT.stream_consumer();
while i2s.fifostat.read().txlvl().bits() <= 6 {
if !try_consume_one_frame(&mut cons, i2s) {
// No complete frame available: write silence to keep FIFO above threshold
// error!("tx queue underflow");
blue_led().toggle();
i2s.fifowr.write(|w| unsafe { w.bits(0) });
i2s.fifowr.write(|w| unsafe { w.bits(0) });
break;
}
}
}
/// I2S RX ISR. Produce frames until the FIFO is empty (or has only a partial frame).
#[interrupt]
fn FLEXCOMM6() {
let i2s = unsafe { pac::I2S6::ptr().as_ref().unwrap() };
let mut prod = QUEUE_AUDIO_IN.stream_producer();
while i2s.fifostat.read().rxlvl().bits() >= 2 {
if !try_produce_one_frame(&mut prod, i2s) {
blue_led().toggle();
// error!("rx queue overflow");
break;
}
}
}
/// Storage for Audio Class implementation state
struct Audio<T: BbqHandle> {
i2s: I2sHandles,
producer: StreamProducer<T>,
consumer: StreamConsumer<T>,
}
impl<T: BbqHandle> Audio<T> {
const RATES: [RangeEntry<u32>; 1] = [RangeEntry::new_fixed(SAMPLE_RATE)];
/// Start audio playback. Clear anything in the FIFO, enable interrupts.
fn start_audio_out(&self) {
info!("playback starting, enabling interrupts");
self.i2s.tx.fifocfg.modify(|_, w| w.emptytx().set_bit());
self.i2s.tx.fifointenclr.write(|w| w.txlvl().set_bit());
// TX FIFO trigger threshold = <= 6 entries
self.i2s
.tx
.fifotrig
.modify(|_, w| unsafe { w.txlvl().bits(6).txlvlena().enabled() });
// TX FIFO level interrupt enable
self.i2s.tx.fifointenset.write(|w| w.txlvl().enabled());
// enable interrupts
unsafe { pac::NVIC::unmask(pac::Interrupt::FLEXCOMM7) };
green_led().on();
}
/// Start audio recording. Clear the FIFO, enable interrupts, and enable the peripheral.
fn start_audio_in(&self) {
info!("recording starting, enabling interrupts");
self.i2s.rx.fifocfg.modify(|_, w| w.emptyrx().set_bit());
self.i2s.rx.fifointenclr.write(|w| w.rxlvl().set_bit());
// RX FIFO trigger threshold = >= 2 entries
self.i2s
.rx
.fifotrig
.modify(|_, w| unsafe { w.rxlvl().bits(1).rxlvlena().enabled() }); // 1 = generate when 2 pieces of data are in fifo
// RX FIFO level interrupt enable
self.i2s.rx.fifointenset.write(|w| w.rxlvl().enabled());
// Start the peripheral
self.i2s.rx.cfg1.modify(|_, w| w.mainenable().enabled());
unsafe { pac::NVIC::unmask(pac::Interrupt::FLEXCOMM6) };
red_led().on();
}
/// Stop audio playback. Disable interrupts, but leave the peripheral
/// running. It is responsible for clocks, so is needed to clock the CODEC
/// and input I2S. Leaving it running continuously is not harmful as the tx
/// FIFO will empty and then emit 0s to the DAC.
fn stop_audio_out(&self) {
pac::NVIC::mask(pac::Interrupt::FLEXCOMM7);
info!("playback stopped");
green_led().off();
}
/// Stop audio recording. Disable the peripheral and mask any interrupts.
fn stop_audio_in(&self) {
self.i2s.rx.cfg1.modify(|_, w| w.mainenable().disabled());
pac::NVIC::mask(pac::Interrupt::FLEXCOMM6);
info!("recording stopped");
red_led().off();
}
}
/// Implement a fixed internal clock offering a single fixed audio rate, which is always valid.
impl<T: BbqHandle> ClockSource for Audio<T> {
const CLOCK_TYPE: usbd_uac2::descriptors::ClockType = ClockType::InternalFixed; const CLOCK_TYPE: usbd_uac2::descriptors::ClockType = ClockType::InternalFixed;
const SOF_SYNC: bool = false; const SOF_SYNC: bool = false;
fn get_sample_rate(&self) -> core::result::Result<u32, usbd_uac2::UsbAudioClassError> { fn sample_rate(&self) -> u32 {
Ok(Clock::RATES[0].min) Self::RATES[0].min
} }
fn get_rates( fn sample_rates(
&self, &self,
) -> core::result::Result<&[usbd_uac2::RangeEntry<u32>], usbd_uac2::UsbAudioClassError> { ) -> core::result::Result<&[usbd_uac2::RangeEntry<u32>], usbd_uac2::UsbAudioClassError> {
Ok(&Clock::RATES) Ok(&Self::RATES)
} }
fn get_clock_validity(&self) -> core::result::Result<bool, usbd_uac2::UsbAudioClassError> { fn clock_validity(&self) -> core::result::Result<bool, usbd_uac2::UsbAudioClassError> {
Ok(true) Ok(true)
} }
} }
/// Implement the main audio handlers. This is a very naive implementation that
static FIFO_CONSUMER_STORE: StaticCell<Consumer<SampleType>> = StaticCell::new(); /// doesn't handle edge cases properly. For example, while altSetting=1
static mut FIFO_CONSUMER: *mut Consumer<SampleType> = null_mut(); /// indicates the host expects to be able to emit audio data to the endpoint, it
/// does not indicate that the stream has started.
#[interrupt] impl<T: BbqHandle, B: bus::UsbBus> AudioHandler<'_, B> for Audio<T> {
fn FLEXCOMM7() { /// On an alt setting change, start or stop the related audio pipeline. This
let i2s = unsafe { &*pac::I2S7::ptr() }; /// behaviour should be replaced by a state machine with at least an 'armed
/// but no data yet' state for playback.
// refil the buffer to 4 frames / 8 samples fn alternate_setting_changed(&mut self, terminal: usb_device::UsbDirection, alt_setting: u8) {
let fifo = unsafe { &mut *FIFO_CONSUMER }; match (terminal, alt_setting) {
while i2s.fifostat.read().txlvl().bits() <= 6 { (usb_device::UsbDirection::Out, 0) => self.stop_audio_out(),
if let Some((l, r)) = fifo.dequeue() { (usb_device::UsbDirection::Out, 1) => self.start_audio_out(),
i2s.fifowr.write(|w| unsafe { w.bits(l as u32) }); (usb_device::UsbDirection::In, 0) => self.stop_audio_in(),
i2s.fifowr.write(|w| unsafe { w.bits(r as u32) }); (usb_device::UsbDirection::In, 1) => self.start_audio_in(),
} else { _ => error!("unexpected alt setting {} on {}", alt_setting, terminal),
// Queue underflow
defmt::error!("queue underflow");
i2s.fifowr.write(|w| unsafe { w.bits(0) });
i2s.fifowr.write(|w| unsafe { w.bits(0) });
}
}
}
struct Audio<'a> {
running: AtomicBool,
i2s: I2sTx,
producer: Producer<'a, SampleType>,
integrator: AtomicI32,
}
impl<'a> Audio<'a> {
fn start(&self) {
self.running.store(true, Ordering::Relaxed);
defmt::info!("playback starting, enabling interrupts");
self.i2s.i2s.fifointenclr.write(|w| w.txlvl().set_bit());
// FIFO trigger threshold = <= 6 entries
self.i2s
.i2s
.fifotrig
.modify(|_, w| unsafe { w.txlvl().bits(6).txlvlena().enabled() });
// FIFO level interrupt enable
self.i2s.i2s.fifointenset.modify(|_, w| w.txlvl().enabled());
unsafe { pac::NVIC::unmask(pac::Interrupt::FLEXCOMM7) };
}
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.
self.running.store(true, Ordering::Relaxed);
defmt::info!("playback stopped");
pac::NVIC::mask(pac::Interrupt::FLEXCOMM7);
}
}
impl<'a, B: bus::UsbBus> UsbAudioClass<'a, B> for Audio<'_> {
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),
} }
} }
/// Receive audio data from USB. If we can, queue it into the output queue which will be drained by the ISR.
fn audio_data_rx( fn audio_data_rx(
&mut self, &mut self,
ep: &usb_device::endpoint::Endpoint<'a, B, usb_device::endpoint::Out>, ep: &usb_device::endpoint::Endpoint<'_, B, usb_device::endpoint::Out>,
) { ) {
// Buffer must fit 1ms of audio data (based on how `usbd_uac2` sets up the descriptors), calculate that size here. // Buffer must fit one microframe's (125us @ HS, 1ms @ FS) of audio data
let mut buf = [0; SAMPLE_RATE as usize / 1000 * core::mem::size_of::<SampleType>()]; // (based on how `usbd_uac2` sets up the descriptors), calculate that
// size here.
let mut buf = [0; (SAMPLE_RATE as usize / USB_FRAME_RATE + 1) * BYTES_PER_FRAME];
let len = match ep.read(&mut buf) { let len = match ep.read(&mut buf) {
Ok(len) => len, Ok(len) => len,
Err(_) => { Err(_) => {
defmt::error!("usb error in rx callback"); error!("usb error in rx callback");
return; return;
} }
}; };
let buf = &buf[..len]; let buf = &buf[..len];
// Translate the raw USB data into audio frames
for sample in buf if let Ok(mut wg) = self.producer.grant_exact(buf.len()) {
.chunks_exact(core::mem::size_of::<SampleType>()) wg.copy_from_slice(buf);
.map(|b| { wg.commit(buf.len());
// TODO: implement SampleType::from PRODUCED_OUT.fetch_add(buf.len() as u32, Ordering::Relaxed);
( } else {
i32::from_le_bytes(b[..4].try_into().unwrap()), blue_led().on();
i32::from_le_bytes(b[4..].try_into().unwrap()), error!("overflowed bbq, asked {}", buf.len());
)
})
{
if self.producer.enqueue(sample).is_err() {
defmt::error!("overflowed fifo, len: {}", self.producer.len());
} }
} }
/// Transmit audio data to USB. If we have data in the queue, write it to
/// the endpoint in appropriately sized microframes
fn audio_data_tx(
&mut self,
ep: &usb_device::endpoint::Endpoint<'_, B, usb_device::endpoint::In>,
) {
const NOMINAL_SAMPLES: usize = SAMPLE_RATE as usize / USB_FRAME_RATE;
const DRAINING: i32 = ((FIFO_LENGTH * 4) / 10) as i32;
const FILLING: i32 = ((FIFO_LENGTH * 6) / 10) as i32;
// Allocate space for one additional audio frame, in case the host is
// running slower than us and we need to occasionally stuff an extra
// frame.
let mut buf = [0; (NOMINAL_SAMPLES + 1) * BYTES_PER_FRAME];
let fill = PRODUCED_IN
.load(Ordering::Acquire)
.wrapping_sub(CONSUMED_IN.load(Ordering::Acquire)) as i32
/ BYTES_PER_FRAME as i32;
// Decide how many samples to write based on fill, this is how we deal with clock slippage.
// Draining (<40%) -> 1 fewer
// Filling (>50%) -> 1 more
// Nominal -> Nominal
let samples = match fill {
f if f < DRAINING => NOMINAL_SAMPLES - 1,
f if f > FILLING => NOMINAL_SAMPLES + 1,
_ => NOMINAL_SAMPLES,
};
let mut written = 0;
let mut woff = 0usize;
while written < samples {
let Ok(rgr) = self.consumer.read() else {
// warn!("underflowed IN queue, wrote {} of {}", written, samples);
break;
};
let available_frames = rgr.len() / BYTES_PER_FRAME;
if available_frames == 0 {
break;
}
let to_copy = (samples - written).min(available_frames);
let bytes = to_copy * BYTES_PER_FRAME;
buf[woff..woff + bytes].copy_from_slice(&rgr[..bytes]);
woff += bytes;
rgr.release(bytes);
written += to_copy;
CONSUMED_IN.fetch_add(to_copy as u32, Ordering::Relaxed);
}
if let Err(e) = ep.write(&buf[..written * BYTES_PER_FRAME]) {
warn!("Error writing to IN EP: {:?}", e);
}
} }
/// Provide rate feedback to the host, so that it doesn't over- or underflow /// Provide rate feedback to the host, so that it doesn't over- or underflow
/// our queue. /// our queue. This implementation is not tuned and without an I term is
fn feedback(&mut self) -> Option<UsbIsochronousFeedback> { /// pretty unstable, but works fine.
// Samples per USB interval (1ms) fn feedback(&mut self, nominal_rate: UsbIsochronousFeedback) -> Option<UsbIsochronousFeedback> {
const FRAME_SAMPLES: i32 = SAMPLE_RATE as i32 / 1000; let target = FIFO_LENGTH as i32 / 2;
// Keep FIFO around half full, minus one USB packet worth let fill = PRODUCED_OUT
const TARGET: i32 = FIFO_LENGTH as i32 / 2 - FRAME_SAMPLES; .load(Ordering::Acquire)
.wrapping_sub(CONSUMED_OUT.load(Ordering::Acquire)) as i32
/ BYTES_PER_FRAME as i32;
// 16.16 fixed-point nominal feedback value let error = fill - target;
const NOMINAL: i32 = FRAME_SAMPLES << 16; // Clamp excursions.
const MAX_ERROR: i32 = FRAME_SAMPLES / 2; let (i, _f) = nominal_rate.parts();
let error = error.clamp(-(i as i32 * 4), i as i32 * 4);
let queuelen = self.producer.len() as i32; let p = error << 8;
let error = (queuelen - TARGET).clamp(-MAX_ERROR, MAX_ERROR); let correction = -p;
let nominal_v = nominal_rate.to_u32_12_13() as i32;
// slow down accumulation of I let mut v = nominal_v + correction;
const I_ACCUM_DIV: i32 = 8;
let i_delta = error / I_ACCUM_DIV;
let integrator = self.integrator.fetch_add(i_delta, Ordering::Relaxed) + i_delta;
// Integrator saturates at 1024xFRAME_SAMPLES // Clamp corrections
let i_limit = FRAME_SAMPLES << 10; v = v.clamp(nominal_v - (1 << 14), nominal_v + (1 << 14));
let integrator = integrator.clamp(-i_limit, i_limit);
// Gains // Note: this log will cause continuous underflows
let p = error << 7; debug!("fill:{} err:{} fb:{=u32:x}", fill, error, v as u32);
let i = integrator << 3;
// Total correction in 16.16 space
let correction = -(p + i);
let v = NOMINAL + correction;
defmt::debug!(
"q:{} err:{} i:{} fb:{}",
queuelen,
error,
integrator,
v >> 16
);
Some(UsbIsochronousFeedback::new(v as u32)) Some(UsbIsochronousFeedback::new(v as u32))
} }
@@ -227,10 +407,7 @@ fn main() -> ! {
debug!("start"); debug!("start");
let mut red_led = pins::Pio1_6::take() hw::init_leds(&mut iocon, &mut gpio);
.unwrap()
.into_gpio_pin(&mut iocon, &mut gpio)
.into_output_low(); // start turned off
debug!("iocon"); debug!("iocon");
let usb0_vbus_pin = pins::Pio0_22::take() let usb0_vbus_pin = pins::Pio0_22::take()
@@ -240,16 +417,16 @@ fn main() -> ! {
pins::Pio1_20::take().unwrap().into_i2c4_scl_pin(&mut iocon), pins::Pio1_20::take().unwrap().into_i2c4_scl_pin(&mut iocon),
pins::Pio1_21::take().unwrap().into_i2c4_sda_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, so they do not get used // We can initialize and iocon these, but there is no peripheral driver, so the variables do not get used
let _codec_i2s_pins = ( let _codec_i2s_pins = (
pins::Pio0_21::take().unwrap().into_spi7_sck_pin(&mut iocon), pins::Pio0_21::take().unwrap().into_spi7_sck_pin(&mut iocon),
pins::Pio0_20::take().unwrap().into_i2s7_sda_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::Pio0_19::take().unwrap().into_i2s7_ws_pin(&mut iocon),
pins::Pio1_13::take().unwrap().into_i2s6_sda_pin(&mut iocon),
pins::Pio1_31::take().unwrap(), // MCLK pins::Pio1_31::take().unwrap(), // MCLK
); );
debug!("clocks"); debug!("clocks");
// Run the system clock at 96MHz. The lpc55-hal will run it from the FRO.
let clocks = hal::ClockRequirements::default() let clocks = hal::ClockRequirements::default()
.system_frequency(96.MHz()) .system_frequency(96.MHz())
.configure(&mut anactrl, &mut pmc, &mut syscon) .configure(&mut anactrl, &mut pmc, &mut syscon)
@@ -275,84 +452,63 @@ fn main() -> ! {
); );
let i2s_peripheral = { let i2s_peripheral = {
let fc6 = hal.flexcomm.6.release();
let fc7 = hal.flexcomm.7.release(); let fc7 = hal.flexcomm.7.release();
hw::init_i2s(fc7.0, fc7.2, &mut syscon)
hw::init_i2s(fc7.0, fc7.2, fc6.0, fc6.2, &mut syscon)
}; };
debug!("usb");
#[cfg(feature = "usbhs")] #[cfg(feature = "usbhs")]
let usb_peripheral = hal.usbhs.enabled_as_device( let (usb_speed, usb_peripheral) = (
UsbSpeed::High,
hal.usbhs.enabled_as_device(
&mut anactrl, &mut anactrl,
&mut pmc, &mut pmc,
&mut syscon, &mut syscon,
&mut _delay_timer, &mut _delay_timer,
clocks.support_usbhs_token().unwrap(), clocks.support_usbhs_token().unwrap(),
),
); );
#[cfg(feature = "usbfs")] #[cfg(feature = "usbfs")]
let usb_peripheral = hal.usbfs.enabled_as_device( let (usb_speed, usb_peripheral) = (
UsbSpeed::Full,
hal.usbfs.enabled_as_device(
&mut anactrl, &mut anactrl,
&mut pmc, &mut pmc,
&mut syscon, &mut syscon,
clocks.support_usbfs_token().unwrap(), clocks.support_usbfs_token().unwrap(),
),
); );
let usb_bus = UsbBus::new(usb_peripheral, usb0_vbus_pin); let usb_bus = UsbBus::new(usb_peripheral, usb0_vbus_pin);
defmt::debug!("codec init"); debug!("codec init");
wm8904::init_codec(&mut i2c_bus); wm8904::init_codec(&mut i2c_bus);
let queue = cortex_m::singleton!(
: Queue<SampleType, FIFO_LENGTH>
= Queue::new()
)
.unwrap();
let (producer, consumer) = queue.split();
let consumer_ref = FIFO_CONSUMER_STORE.init(consumer);
unsafe { FIFO_CONSUMER = consumer_ref as *mut _ };
let mut clock = Clock {};
let mut audio = Audio { let mut audio = Audio {
i2s: i2s_peripheral, i2s: i2s_peripheral,
producer, producer: QUEUE_AUDIO_OUT.stream_producer(),
running: AtomicBool::new(false), consumer: QUEUE_AUDIO_IN.stream_consumer(),
integrator: AtomicI32::new(0),
}; };
let config = AudioClassConfig::new(UsbSpeed::High, FunctionCode::Other, &mut clock, &mut audio) let config = UsbAudioClassConfig::new(usb_speed, FunctionCode::IoBox, &mut audio)
.with_output_config(TerminalConfig::new( .with_output_config(TerminalConfig::builder().base_id(2).build())
4, .with_input_config(TerminalConfig::builder().base_id(4).build());
1,
2,
FormatType1 {
bit_resolution: 32,
bytes_per_sample: 4,
},
TerminalType::ExtLineConnector,
ChannelConfig::default_chans(2),
IsochronousSynchronizationType::Asynchronous,
LockDelay::Milliseconds(10),
None,
));
let mut uac2 = config.build(&usb_bus).unwrap(); let mut uac2 = config.build(&usb_bus).unwrap();
let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x1209, 0xcc1d)) let mut usb_dev = usbd_uac2::builder(&usb_bus, UsbVidPid(USB_VID, USB_PID))
.composite_with_iads()
.strings(&[StringDescriptors::default() .strings(&[StringDescriptors::default()
.manufacturer("Generic") .manufacturer(USB_MANUFACTURER)
.product("usbd_uac2 device") .product(USB_PRODUCT)])
.serial_number("123456789")])
.unwrap() .unwrap()
.max_packet_size_0(64) .max_packet_size_0(64) // Required to be 64 on HS, allowed on FS
.unwrap() .unwrap()
.device_class(0xef)
.device_sub_class(0x02)
.device_protocol(0x01)
.build(); .build();
defmt::info!("main loop"); debug!("main loop");
loop { loop {
usb_dev.poll(&mut [&mut uac2]); usb_dev.poll(&mut [&mut uac2]);
red_led.set_high().ok(); // Turn off
} }
} }
+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(())
} }
} }
+308 -167
View File
@@ -1,3 +1,47 @@
//! USB Audio Class 2.0
//!
//! This crate provides a USB Audio Class 2.0 implementation for
//! [usb-device](https://crates.io/crates/usb-device). It implements all
//! required elements of the specification, however many controls are not
//! implemented (e.g. mixers, effects).
//!
//! Device behaviour is driven by implementing the `ClockSource` and
//! `AudioHandler` traits to configure the audio pipeline and source/sink data
//! to/from USB.
//!
//! Example (creates a UAC2 device with in and out streams):
//!
//! ```ignore
//! let mut audio = YourTraitImpl {...};
//!
//! let config = UsbAudioClassConfig::new(usb_speed, FunctionCode::IoBox, &mut audio)
//! // base_id is USB entity ID, id 1 is always taken by the clock source, and each stream builds 2 entities
//! .with_output_config(TerminalConfig::builder().base_id(2).build())
//! .with_input_config(TerminalConfig::builder().base_id(4).build());
//!
//! let mut uac2 = config.build(&usb_bus).unwrap();
//!
//! let mut usb_dev = usbd_uac2::builder(&usb_bus, UsbVidPid(0x1209, 0x0001))
//! .strings(&[StringDescriptors::default()
//! .manufacturer("usbd_uac2")
//! .product("example")])
//! .unwrap()
//! .max_packet_size_0(64) // Required to be 64 on HS
//! .unwrap()
//! .build();
//! ```
//!
//! No work needs to be done in the poll loop, the class implementation will
//! call your trait callbacks as required, just call the usb poll as usual:
//!
//! ```ignore
//! loop {
//! usb_dev.poll(&mut [&mut uac2]);
//! }
//! ```
//!
//! See the trait documentation or examples for additional details.
#![no_std] #![no_std]
#![allow(dead_code)] #![allow(dead_code)]
@@ -8,24 +52,19 @@ mod log;
use core::cmp::Ordering; use core::cmp::Ordering;
use core::marker::PhantomData; use core::marker::PhantomData;
use core::sync::atomic::AtomicUsize;
use byteorder_embedded_io::{LittleEndian, ReadBytesExt, WriteBytesExt};
use constants::*; use constants::*;
use descriptors::*; use descriptors::*;
#[allow(unused_imports)]
use log::*; use log::*;
use byteorder_embedded_io::{LittleEndian, ReadBytesExt, WriteBytesExt};
use num_traits::{ConstZero, ToPrimitive}; use num_traits::{ConstZero, ToPrimitive};
use usb_device::control::{Recipient, Request, RequestType}; use usb_device::control::{Recipient, Request, RequestType};
use usb_device::device::DEFAULT_ALTERNATE_SETTING; use usb_device::device::{DEFAULT_ALTERNATE_SETTING, UsbDeviceBuilder, UsbVidPid};
use usb_device::endpoint::{self, Endpoint, EndpointDirection, In, Out}; use usb_device::endpoint::{self, Endpoint, EndpointDirection, In, Out};
use usb_device::{UsbDirection, class_prelude::*}; use usb_device::{UsbDirection, class_prelude::*};
pub use constants::USB_CLASS_AUDIO;
#[cfg(feature = "defmt")]
use defmt;
mod sealed { mod sealed {
pub trait Sealed {} pub trait Sealed {}
} }
@@ -66,7 +105,10 @@ impl RangeType for u8 {}
impl RangeType for u16 {} impl RangeType for u16 {}
impl RangeType for u32 {} impl RangeType for u32 {}
#[derive(PartialEq, Eq, Ord)] /// Represent a range request/response.
///
/// ref: UAC2 5.2.2
#[derive(PartialEq, Eq)]
pub struct RangeEntry<T: RangeType> { pub struct RangeEntry<T: RangeType> {
pub min: T, pub min: T,
pub max: T, pub max: T,
@@ -85,10 +127,7 @@ impl<T: RangeType> RangeEntry<T> {
} }
} }
pub fn write<W: embedded_io::Write>( fn write<W: embedded_io::Write>(&self, mut buf: W) -> core::result::Result<usize, W::Error> {
&self,
mut buf: W,
) -> core::result::Result<usize, W::Error> {
buf.write_all(self.min.to_le_bytes().as_ref())?; buf.write_all(self.min.to_le_bytes().as_ref())?;
buf.write_all(self.max.to_le_bytes().as_ref())?; buf.write_all(self.max.to_le_bytes().as_ref())?;
buf.write_all(self.res.to_le_bytes().as_ref())?; buf.write_all(self.res.to_le_bytes().as_ref())?;
@@ -96,52 +135,46 @@ impl<T: RangeType> RangeEntry<T> {
} }
} }
/// The spec guarantees that ranges do not overlap, so compare by min is correct. // The spec guarantees that ranges do not overlap, so compare by min is correct.
impl<T: RangeType + PartialOrd> PartialOrd for RangeEntry<T> { impl<T: RangeType + PartialOrd> PartialOrd for RangeEntry<T> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.min.partial_cmp(&other.min) Some(self.cmp(other))
} }
} }
impl<T: RangeType + PartialOrd> Ord for RangeEntry<T> {
/// Fixed point 10.14, packed to the least significant 3-bytes of a 4-byte USB feedback endpoint response fn cmp(&self, other: &Self) -> Ordering {
#[derive(Copy, Clone)] self.min.cmp(&other.min)
pub struct UsbIsochronousFeedback { }
pub int: u16,
pub frac: u16,
} }
/// Represent Isochronous feedback as integer and fractional parts, internally as 16.16.
///
/// Provides conversions to and from the different USB formats.
#[derive(Copy, Clone)]
pub struct UsbIsochronousFeedback(u32);
impl UsbIsochronousFeedback { impl UsbIsochronousFeedback {
pub fn new(v: u32) -> Self {
Self(v)
}
/// Accepts all u16 values, saturating the output depending on format /// Accepts all u16 values, saturating the output depending on format
pub fn new_frac(int: u16, frac: u16) -> Self { pub fn new_frac(int: u16, frac: u16) -> Self {
Self { int, frac } Self(((int as u32) << 16) | frac as u32)
} }
/// Convert float to fixed point
pub fn new_float(rate: f32) -> Self { pub fn new_float(rate: f32) -> Self {
let fb = (rate * 65536.0 + 0.5) as u32; let fb = (rate * 65536.0 + 0.5) as u32;
Self::new(fb) Self(fb)
}
/// Assumed 16.16, not either of the USB formats
pub fn new(value: u32) -> Self {
Self {
int: (value >> 16) as u16,
frac: (value & 0xffff) as u16,
} }
pub fn parts(&self) -> (u16, u16) {
((self.0 >> 16) as u16, (self.0 & 0xffff) as u16)
} }
/// Serialize into a u32 in 16.16 representation for USB HS /// Serialize into a u32 in 16.16 representation for USB HS
pub fn to_u32_12_13(&self) -> u32 { pub fn to_u32_12_13(&self) -> u32 {
let int = (self.int as u32) << 16; self.0
// ostensibly 13 bits, so should require << 3, but USB allows us to use
// these bits for 'extra precision'. So we may as well just treat it as
// 16.16. The application can << 3 if it wants to for some reason.
let frac = (self.frac as u32) & 0xffff;
int | frac
} }
/// Serialize into a u32 in 10.14 representation for USB FS (take the 3 LSB) /// Serialize into a u32 in 10.14 representation for USB FS
pub fn to_u32_10_14(&self) -> u32 { pub fn to_u32_10_14(&self) -> u32 {
let int = (self.int as u32) << 14; (self.0 + 2) >> 2
let frac = (self.frac as u32) & 0x3fff;
int | frac
} }
/// Serialize into 16.16 little endian byte array for USB HS /// Serialize into 16.16 little endian byte array for USB HS
pub fn to_bytes_12_13(&self) -> [u8; 4] { pub fn to_bytes_12_13(&self) -> [u8; 4] {
@@ -156,16 +189,15 @@ impl UsbIsochronousFeedback {
/// A trait for implementing USB Audio Class 2 devices /// A trait for implementing USB Audio Class 2 devices
/// ///
/// Contains callback methods which will be called by the class driver. All /// Contains callback methods which will be called by the class driver.
/// callbacks are optional, which may be useful for a tight-loop polling implementation pub trait AudioHandler<'a, B: UsbBus> {
/// but most implementations will want to implement at least `audio_data_rx`.
///
/// Unimplemented callbacks should return `Ok(())` if a result is required.
pub trait UsbAudioClass<'a, B: UsbBus> {
/// Called when audio data is received from the host. `ep` is ready for /// Called when audio data is received from the host. `ep` is ready for
/// `ep.read()`. /// `ep.read()`.
fn audio_data_rx(&mut self, ep: &Endpoint<'a, B, endpoint::Out>) {} fn audio_data_rx(&mut self, ep: &Endpoint<'a, B, endpoint::Out>);
/// Called when we must produce audio data for the host. `ep` is ready for
/// `ep.write()`.
fn audio_data_tx(&mut self, ep: &Endpoint<'a, B, endpoint::In>);
/// Called when it's time to send an isochronous feedback update. Should /// Called when it's time to send an isochronous feedback update. Should
/// return the correct feedback payload. Feedback always runs at 1ms (in /// return the correct feedback payload. Feedback always runs at 1ms (in
@@ -173,14 +205,15 @@ pub trait UsbAudioClass<'a, B: UsbBus> {
/// ///
/// Required for isochronous asynchronous mode to work properly. If None is /// Required for isochronous asynchronous mode to work properly. If None is
/// returned, no IN packet will be emitted at feedback time. /// returned, no IN packet will be emitted at feedback time.
fn feedback(&mut self, nominal_rate: UsbIsochronousFeedback) -> Option<UsbIsochronousFeedback> { fn feedback(&mut self, nominal: UsbIsochronousFeedback) -> Option<UsbIsochronousFeedback>;
None
}
/// Called when the alternate setting of `terminal`'s interface is changed, /// Called when the alternate setting of `terminal`'s interface is changed,
/// before the `AudioStream` is updated. Currently not very useful since we /// before the `AudioStream` is updated. This is how the host signals start/stop
/// don't implement alternate settings. /// of audio streaming. We do not expect audio frames when alt setting is 0.
fn alternate_setting_changed(&mut self, terminal: UsbDirection, alt_setting: u8) {} ///
/// The implementation will also call `alternate_setting_changed(_, 0)` when
/// the host disconnects / resets.
fn alternate_setting_changed(&mut self, terminal: UsbDirection, alt_setting: u8);
} }
/// A trait for implementing Sampling Frequency Control for USB Audio Clock Sources /// A trait for implementing Sampling Frequency Control for USB Audio Clock Sources
@@ -191,7 +224,7 @@ pub trait UsbAudioClass<'a, B: UsbBus> {
/// Unimplemented callbacks should return `Err(UsbAudioClassError::NotImplemented)`. Other /// Unimplemented callbacks should return `Err(UsbAudioClassError::NotImplemented)`. Other
/// errors will panic (the underlying callbacks are not fallible). If you need to handle errors, /// errors will panic (the underlying callbacks are not fallible). If you need to handle errors,
/// you should use the callback to infalliably signal another task. /// you should use the callback to infalliably signal another task.
pub trait UsbAudioClockImpl { pub trait ClockSource {
const CLOCK_TYPE: ClockType; const CLOCK_TYPE: ClockType;
const SOF_SYNC: bool; const SOF_SYNC: bool;
/// Called when the host or class needs the current sample rate. Returns the /// Called when the host or class needs the current sample rate. Returns the
@@ -200,9 +233,10 @@ pub trait UsbAudioClockImpl {
/// ///
/// Should never return 0 as it may be used in divides in the feedback loop /// Should never return 0 as it may be used in divides in the feedback loop
/// and that would cause a hard fault. /// and that would cause a hard fault.
fn get_sample_rate(&self) -> u32; fn sample_rate(&self) -> u32;
/// Called when the host requests to set the sample rate. Not necessarily called at all startups, /// Called when the host requests to set the sample rate. Not necessarily called at all startups,
/// so alt_setting should start/stop the clock. Not required for 'fixed' clocks. /// so alt_setting should start/stop the clock. Not required for 'fixed' clocks.
#[allow(unused_variables)]
fn set_sample_rate( fn set_sample_rate(
&mut self, &mut self,
sample_rate: u32, sample_rate: u32,
@@ -211,14 +245,14 @@ pub trait UsbAudioClockImpl {
} }
/// Called when the host requests to get the clock validity. Returns `true` /// Called when the host requests to get the clock validity. Returns `true`
/// if the clock is stable and on frequency. /// if the clock is stable and on frequency.
fn get_clock_validity(&self) -> core::result::Result<bool, UsbAudioClassError> { fn clock_validity(&self) -> core::result::Result<bool, UsbAudioClassError> {
Err(UsbAudioClassError::NotImplemented) Err(UsbAudioClassError::NotImplemented)
} }
/// Called during descriptor construction to describe if the clock validity can be read (write is not valid). /// Called during descriptor construction to describe if the clock validity can be read (write is not valid).
/// ///
/// By default will call `get_clock_validity` to determine if the clock validity can be read. /// By default will call `get_clock_validity` to determine if the clock validity can be read.
fn get_validity_access(&self) -> core::result::Result<bool, UsbAudioClassError> { fn clock_validity_access(&self) -> core::result::Result<bool, UsbAudioClassError> {
match self.get_clock_validity() { match self.clock_validity() {
Ok(_) => Ok(true), Ok(_) => Ok(true),
Err(UsbAudioClassError::NotImplemented) => Ok(false), Err(UsbAudioClassError::NotImplemented) => Ok(false),
Err(err) => Err(err), Err(err) => Err(err),
@@ -236,36 +270,30 @@ pub trait UsbAudioClockImpl {
/// its MIN and MAX subattribute and the RES subattribute must be set to zero /// its MIN and MAX subattribute and the RES subattribute must be set to zero
/// ///
/// ref: USB Audio Class Specification 2.0 5.2.1 & 5.2.3.3 /// ref: USB Audio Class Specification 2.0 5.2.1 & 5.2.3.3
fn get_rates(&self) -> core::result::Result<&[RangeEntry<u32>], UsbAudioClassError>; fn sample_rates(&self) -> core::result::Result<&[RangeEntry<u32>], UsbAudioClassError>;
/// Called when the audio device's AltSetting is changed. Usually 0 signals shutdown of the /// Build the ClockSource descriptor. It is not intended to override this
/// streaming audio and 1 signals start of streaming. This should be used to start the clock /// method, but provided so you can.
/// (and stop it if desired). If unimplemented, does nothing - keep the clock running at all times.
fn alt_setting(&mut self, alt_setting: u8) -> core::result::Result<(), UsbAudioClassError> {
Ok(())
}
/// Build the ClockSource descriptor. It is not intended to override this method.
/// ///
/// Assumes access control based on clock type. Internal fixed/variable are read only, /// Assumes access control based on clock type. Internal fixed/variable are read only,
/// external and internal programmable are programmable. /// external and internal programmable are programmable.
fn get_configuration_descriptor( fn clock_configuration_descriptor(
&self, &self,
id: u8, id: u8,
string: Option<StringIndex>, string: Option<StringIndex>,
) -> usb_device::Result<ClockSource> { ) -> usb_device::Result<descriptors::ClockSource> {
let frequency_access = match Self::CLOCK_TYPE { let frequency_access = match Self::CLOCK_TYPE {
ClockType::InternalFixed | ClockType::InternalVariable => AccessControl::ReadOnly, ClockType::InternalFixed | ClockType::InternalVariable => AccessControl::ReadOnly,
ClockType::External | ClockType::InternalProgrammable => AccessControl::Programmable, ClockType::External | ClockType::InternalProgrammable => AccessControl::Programmable,
}; };
let validity_access = match self.get_validity_access() { let validity_access = match self.clock_validity_access() {
Ok(true) => AccessControl::ReadOnly, Ok(true) => AccessControl::ReadOnly,
Ok(false) | Err(UsbAudioClassError::NotImplemented) => AccessControl::NotPresent, Ok(false) | Err(UsbAudioClassError::NotImplemented) => AccessControl::NotPresent,
_ => return Err(UsbError::Unsupported), _ => return Err(UsbError::Unsupported),
}; };
let cs = ClockSource { let cs = descriptors::ClockSource {
id: id, id,
clock_type: Self::CLOCK_TYPE, clock_type: Self::CLOCK_TYPE,
sof_sync: Self::SOF_SYNC, sof_sync: Self::SOF_SYNC,
frequency_access, frequency_access,
@@ -282,6 +310,102 @@ trait TerminalConfigurationDescriptors {
fn get_configuration_descriptors(&self) -> (InputTerminal, OutputTerminal); fn get_configuration_descriptors(&self) -> (InputTerminal, OutputTerminal);
} }
pub struct TerminalConfigBuilder<D: EndpointDirection> {
base_id: Option<u8>,
clock_source_id: Option<u8>,
num_channels: Option<u8>,
format: Option<FormatType1>,
terminal_type: Option<TerminalType>,
channel_config: Option<ChannelConfig>,
sync_type: Option<IsochronousSynchronizationType>,
lock_delay: Option<LockDelay>,
string: Option<StringIndex>,
_direction: PhantomData<D>,
}
// Global builder initialization defaults
impl<D: EndpointDirection> TerminalConfigBuilder<D> {
fn new() -> Self {
Self {
base_id: None,
clock_source_id: None,
num_channels: None,
format: None,
terminal_type: None,
channel_config: None,
sync_type: None,
lock_delay: None,
string: None,
_direction: PhantomData,
}
}
pub fn base_id(mut self, id: u8) -> Self {
self.base_id = Some(id);
self
}
pub fn clock_source_id(mut self, id: u8) -> Self {
self.clock_source_id = Some(id);
self
}
pub fn num_channels(mut self, channels: u8) -> Self {
self.num_channels = Some(channels);
self
}
pub fn format(mut self, format: FormatType1) -> Self {
self.format = Some(format);
self
}
pub fn terminal_type(mut self, t: TerminalType) -> Self {
self.terminal_type = Some(t);
self
}
pub fn channel_config(mut self, config: ChannelConfig) -> Self {
self.channel_config = Some(config);
self
}
pub fn sync_type(mut self, sync: IsochronousSynchronizationType) -> Self {
self.sync_type = Some(sync);
self
}
pub fn lock_delay(mut self, delay: LockDelay) -> Self {
self.lock_delay = Some(delay);
self
}
pub fn string(mut self, index: StringIndex) -> Self {
self.string = Some(index);
self
}
pub fn build(self) -> TerminalConfig<D> {
TerminalConfig {
base_id: self.base_id.expect("base_id is required"),
clock_source_id: self.clock_source_id.unwrap_or(1),
num_channels: self.num_channels.unwrap_or(2),
format: self.format.unwrap_or(FormatType1 {
bytes_per_sample: 4,
bit_resolution: 32,
}),
terminal_type: self.terminal_type.unwrap_or(TerminalType::ExtUndefined),
channel_config: self
.channel_config
.unwrap_or(ChannelConfig::default_chans(self.num_channels.unwrap_or(2))),
sync_type: IsochronousSynchronizationType::Asynchronous,
lock_delay: LockDelay::Undefined(0),
string: self.string, // Implicitly handles None or Option mapping
_direction: PhantomData,
}
}
}
pub struct TerminalConfig<D: EndpointDirection> { pub struct TerminalConfig<D: EndpointDirection> {
/// USB terminal in the D direction will have this id, audio terminal will have this id + 1 /// USB terminal in the D direction will have this id, audio terminal will have this id + 1
base_id: u8, base_id: u8,
@@ -296,9 +420,9 @@ pub struct TerminalConfig<D: EndpointDirection> {
_direction: PhantomData<D>, _direction: PhantomData<D>,
} }
// TODO: builder pattern
impl<D: EndpointDirection> TerminalConfig<D> { impl<D: EndpointDirection> TerminalConfig<D> {
pub fn new( #[allow(clippy::too_many_arguments)]
fn new(
base_id: u8, base_id: u8,
clock_source_id: u8, clock_source_id: u8,
num_channels: u8, num_channels: u8,
@@ -326,8 +450,12 @@ impl<D: EndpointDirection> TerminalConfig<D> {
pub fn bytes_per_frame(&self) -> u32 { pub fn bytes_per_frame(&self) -> u32 {
self.format.bytes_per_sample as u32 * self.num_channels as u32 self.format.bytes_per_sample as u32 * self.num_channels as u32
} }
pub fn builder() -> TerminalConfigBuilder<D> {
TerminalConfigBuilder::new()
}
} }
impl<'a> TerminalConfigurationDescriptors for TerminalConfig<Out> { impl TerminalConfigurationDescriptors for TerminalConfig<Out> {
fn get_configuration_descriptors(&self) -> (InputTerminal, OutputTerminal) { fn get_configuration_descriptors(&self) -> (InputTerminal, OutputTerminal) {
let input_terminal = InputTerminal { let input_terminal = InputTerminal {
id: self.base_id, id: self.base_id,
@@ -364,7 +492,7 @@ impl<'a> TerminalConfigurationDescriptors for TerminalConfig<Out> {
// fn get_interface_descriptor(&self, id: InterfaceIndex) ) // fn get_interface_descriptor(&self, id: InterfaceIndex) )
} }
impl<'a> TerminalConfigurationDescriptors for TerminalConfig<In> { impl TerminalConfigurationDescriptors for TerminalConfig<In> {
fn get_configuration_descriptors(&self) -> (InputTerminal, OutputTerminal) { fn get_configuration_descriptors(&self) -> (InputTerminal, OutputTerminal) {
let output_terminal = OutputTerminal { let output_terminal = OutputTerminal {
id: self.base_id, id: self.base_id,
@@ -426,10 +554,9 @@ pub enum UsbSpeed {
/// A single Clock Source is always required, but a fully custom descriptor set can be built by only providing /// A single Clock Source is always required, but a fully custom descriptor set can be built by only providing
/// the Clock Source and additional descriptors, if the Terminal descriptors are inappropriate. /// the Clock Source and additional descriptors, if the Terminal descriptors are inappropriate.
/// ///
pub struct AudioClassConfig<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> { pub struct UsbAudioClassConfig<'a, B: UsbBus, AU: AudioHandler<'a, B> + ClockSource> {
pub speed: UsbSpeed, pub speed: UsbSpeed,
pub device_category: FunctionCode, pub device_category: FunctionCode,
pub clock_impl: &'a mut CS,
pub audio_impl: &'a mut AU, pub audio_impl: &'a mut AU,
pub input_config: Option<TerminalConfig<In>>, pub input_config: Option<TerminalConfig<In>>,
pub output_config: Option<TerminalConfig<Out>>, pub output_config: Option<TerminalConfig<Out>>,
@@ -437,19 +564,11 @@ pub struct AudioClassConfig<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioCl
_bus: PhantomData<B>, _bus: PhantomData<B>,
} }
impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> impl<'a, B: UsbBus, AU: AudioHandler<'a, B> + ClockSource> UsbAudioClassConfig<'a, B, AU> {
AudioClassConfig<'a, B, CS, AU> pub fn new(speed: UsbSpeed, device_category: FunctionCode, audio_impl: &'a mut AU) -> Self {
{
pub fn new(
speed: UsbSpeed,
device_category: FunctionCode,
clock_impl: &'a mut CS,
audio_impl: &'a mut AU,
) -> Self {
Self { Self {
speed, speed,
device_category, device_category,
clock_impl,
audio_impl, audio_impl,
input_config: None, input_config: None,
output_config: None, output_config: None,
@@ -474,7 +593,7 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>>
} }
/// Allocate the various USB IDs, and build the class implementation /// Allocate the various USB IDs, and build the class implementation
pub fn build(self, alloc: &'a UsbBusAllocator<B>) -> Result<AudioClass<'a, B, CS, AU>> { pub fn build(self, alloc: &'a UsbBusAllocator<B>) -> Result<UsbAudioClass<'a, B, AU>> {
let speed = self.speed; let speed = self.speed;
let (interval, fb_interval, audio_rate) = match speed { let (interval, fb_interval, audio_rate) = match speed {
UsbSpeed::Full => (1, 1, 1000), UsbSpeed::Full => (1, 1, 1000),
@@ -482,8 +601,8 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>>
UsbSpeed::Low => return Err(Error::InvalidSpeed), UsbSpeed::Low => return Err(Error::InvalidSpeed),
}; };
let max_rate = self let max_rate = self
.clock_impl .audio_impl
.get_rates() .sample_rates()
.unwrap() .unwrap()
.iter() .iter()
.max() .max()
@@ -492,12 +611,11 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>>
let control_iface = alloc.interface(); let control_iface = alloc.interface();
let nominal_fb = UsbIsochronousFeedback::new_float( let nominal_fb = UsbIsochronousFeedback::new_float(
self.clock_impl.get_sample_rate().to_f32().unwrap() / audio_rate.to_f32().unwrap(), self.audio_impl.sample_rate().to_f32().unwrap() / audio_rate.to_f32().unwrap(),
); );
let mut ac = AudioClass { let mut ac = UsbAudioClass {
control_iface, control_iface,
clock_impl: self.clock_impl,
audio_impl: self.audio_impl, audio_impl: self.audio_impl,
output: None, output: None,
input: None, input: None,
@@ -663,9 +781,8 @@ impl<'a, B: UsbBus, D: EndpointDirection> AudioStream<'a, B, D> {
} }
} }
pub struct AudioClass<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> { pub struct UsbAudioClass<'a, B: UsbBus, AU: AudioHandler<'a, B> + ClockSource> {
control_iface: InterfaceNumber, control_iface: InterfaceNumber,
clock_impl: &'a mut CS,
audio_impl: &'a mut AU, audio_impl: &'a mut AU,
output: Option<AudioStream<'a, B, Out>>, output: Option<AudioStream<'a, B, Out>>,
input: Option<AudioStream<'a, B, In>>, input: Option<AudioStream<'a, B, In>>,
@@ -682,8 +799,8 @@ pub struct AudioClass<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a
audio_rate: u32, // audio packet rate in hz audio_rate: u32, // audio packet rate in hz
} }
impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> UsbClass<B> impl<'a, B: UsbBus, AU: AudioHandler<'a, B> + ClockSource> UsbClass<B>
for AudioClass<'a, B, CS, AU> for UsbAudioClass<'a, B, AU>
{ {
fn get_configuration_descriptors( fn get_configuration_descriptors(
&self, &self,
@@ -715,7 +832,7 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> UsbClass<B>
// BUILD CONFIGURATION DESCRIPTORS // // BUILD CONFIGURATION DESCRIPTORS //
let mut total_length: u16 = 9; // HEADER let mut total_length: u16 = 9; // HEADER
let clock_desc = self.clock_impl.get_configuration_descriptor(1, None)?; let clock_desc = self.audio_impl.clock_configuration_descriptor(1, None)?;
total_length += clock_desc.size() as u16; total_length += clock_desc.size() as u16;
let output_descs = match &self.output { let output_descs = match &self.output {
Some(stream) => { Some(stream) => {
@@ -776,7 +893,7 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> UsbClass<B>
// UAC2 4.7 // UAC2 4.7
if let Some(descs) = additional_descs { if let Some(descs) = additional_descs {
for desc in descs.into_iter() { for desc in descs.iter() {
desc.write_descriptor(writer)?; desc.write_descriptor(writer)?;
} }
} }
@@ -831,10 +948,14 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> UsbClass<B>
fn endpoint_in_complete(&mut self, addr: EndpointAddress) { fn endpoint_in_complete(&mut self, addr: EndpointAddress) {
debug!("EP {} IN complete", addr); debug!("EP {} IN complete", addr);
if let Some(fb_ep) = self.feedback.as_ref() if let Some(_fb_ep) = self.feedback.as_ref()
&& addr.index() == self.fb_ep && addr.index() == self.fb_ep
{ {
self.emit_feedback(); self.emit_feedback();
} else if let Some(in_ep) = self.input.as_ref()
&& addr.index() == self.in_ep
{
self.audio_impl.audio_data_tx(&in_ep.endpoint);
} else { } else {
debug!(" unexpected IN on {}", addr); debug!(" unexpected IN on {}", addr);
} }
@@ -860,16 +981,24 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> UsbClass<B>
break; break;
} }
Err(UsbError::WouldBlock) => break, Err(UsbError::WouldBlock) => break,
Err(err) => { Err(_err) => {
debug!("EP OUT error {:?}", err); debug!("EP OUT error {:?}", _err);
} }
} }
} }
} }
} }
fn reset(&mut self) {
info!("usb reset");
self.audio_impl
.alternate_setting_changed(UsbDirection::In, 0);
self.audio_impl
.alternate_setting_changed(UsbDirection::Out, 0);
}
} }
impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> AudioClass<'a, B, CS, AU> { impl<'a, B: UsbBus, AU: AudioHandler<'a, B> + ClockSource> UsbAudioClass<'a, B, AU> {
fn emit_feedback(&mut self) { fn emit_feedback(&mut self) {
if let Some(fb_ep) = self.feedback.as_ref() { if let Some(fb_ep) = self.feedback.as_ref() {
if let Some(fb) = self.audio_impl.feedback(self.nominal_fb) { if let Some(fb) = self.audio_impl.feedback(self.nominal_fb) {
@@ -878,11 +1007,11 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> AudioClass<
UsbSpeed::Low | UsbSpeed::Full => fb_ep.write(&fb.to_bytes_10_14()), UsbSpeed::Low | UsbSpeed::Full => fb_ep.write(&fb.to_bytes_10_14()),
UsbSpeed::High | UsbSpeed::Super => fb_ep.write(&fb.to_bytes_12_13()), UsbSpeed::High | UsbSpeed::Super => fb_ep.write(&fb.to_bytes_12_13()),
}; };
if let Err(e) = r { if let Err(_e) = r {
warn!(" feedback IN failed {:?}", e); warn!(" feedback IN failed {:?}", _e);
} }
} else { } else {
debug!(" feedback callback returned None") debug!(" feedback callback returned None");
} }
} }
} }
@@ -908,11 +1037,11 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> AudioClass<
fn class_request_out(&mut self, xfer: ControlOut<B>) { fn class_request_out(&mut self, xfer: ControlOut<B>) {
let req = xfer.request(); let req = xfer.request();
match (req.recipient, req.request.try_into()) { match (req.recipient, req.request.into()) {
(Recipient::Interface, Ok(ClassSpecificRequest::Cur)) => self.set_interface_cur(xfer), (Recipient::Interface, ClassSpecificRequest::Cur) => self.set_interface_cur(xfer),
(Recipient::Interface, Ok(ClassSpecificRequest::Range)) => {} (Recipient::Interface, ClassSpecificRequest::Range) => {}
(Recipient::Endpoint, Ok(ClassSpecificRequest::Cur)) => self.set_endpoint_cur(xfer), (Recipient::Endpoint, ClassSpecificRequest::Cur) => self.set_endpoint_cur(xfer),
(Recipient::Endpoint, Ok(ClassSpecificRequest::Range)) => {} (Recipient::Endpoint, ClassSpecificRequest::Range) => {}
_ => { _ => {
debug!(" Unimplemented."); debug!(" Unimplemented.");
} }
@@ -922,13 +1051,11 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> AudioClass<
fn class_request_in(&mut self, xfer: ControlIn<B>) { fn class_request_in(&mut self, xfer: ControlIn<B>) {
let req = xfer.request(); let req = xfer.request();
match (req.recipient, req.request.try_into()) { match (req.recipient, req.request.into()) {
(Recipient::Interface, Ok(ClassSpecificRequest::Cur)) => self.get_interface_cur(xfer), (Recipient::Interface, ClassSpecificRequest::Cur) => self.get_interface_cur(xfer),
(Recipient::Interface, Ok(ClassSpecificRequest::Range)) => { (Recipient::Interface, ClassSpecificRequest::Range) => self.get_interface_range(xfer),
self.get_interface_range(xfer) (Recipient::Endpoint, ClassSpecificRequest::Cur) => self.get_endpoint_cur(xfer),
} (Recipient::Endpoint, ClassSpecificRequest::Range) => self.get_endpoint_range(xfer),
(Recipient::Endpoint, Ok(ClassSpecificRequest::Cur)) => self.get_endpoint_cur(xfer),
(Recipient::Endpoint, Ok(ClassSpecificRequest::Range)) => self.get_endpoint_range(xfer),
_ => { _ => {
debug!(" Unimplemented."); debug!(" Unimplemented.");
} }
@@ -942,19 +1069,21 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> AudioClass<
debug!(" SET_ALT_INTERFACE {} {}", iface, alt_setting); debug!(" SET_ALT_INTERFACE {} {}", iface, alt_setting);
if self.input.is_some() && iface == self.in_iface { if let Some(input) = &mut self.input
let old_alt = self.input.as_ref().unwrap().alt_setting; && iface == self.in_iface
{
let old_alt = input.alt_setting;
if old_alt != alt_setting { if old_alt != alt_setting {
self.clock_impl.alt_setting(alt_setting).ok();
self.audio_impl self.audio_impl
.alternate_setting_changed(UsbDirection::In, alt_setting); .alternate_setting_changed(UsbDirection::In, alt_setting);
self.input.as_mut().unwrap().alt_setting = alt_setting; input.alt_setting = alt_setting;
// Start the IN cycle
self.audio_impl.audio_data_tx(&input.endpoint);
xfer.accept().ok(); xfer.accept().ok();
} }
} else if self.output.is_some() && iface == self.out_iface { } else if self.output.is_some() && iface == self.out_iface {
let old_alt = self.output.as_ref().unwrap().alt_setting; let old_alt = self.output.as_ref().unwrap().alt_setting;
if old_alt != alt_setting { if old_alt != alt_setting {
self.clock_impl.alt_setting(alt_setting).ok();
self.audio_impl self.audio_impl
.alternate_setting_changed(UsbDirection::Out, alt_setting); .alternate_setting_changed(UsbDirection::Out, alt_setting);
// Start the IN cycle running // Start the IN cycle running
@@ -973,13 +1102,15 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> AudioClass<
let req = xfer.request(); let req = xfer.request();
let iface = req.index as u8; let iface = req.index as u8;
debug!(" GET_ALT_INTERFACE {}", iface); debug!(" GET_ALT_INTERFACE {}", iface);
if self.input.is_some() && iface == self.in_iface { if let Some(input) = self.input.as_ref()
xfer.accept_with(&[self.input.as_ref().unwrap().alt_setting]) && iface == self.in_iface
.ok(); {
xfer.accept_with(&[input.alt_setting]).ok();
return; return;
} else if self.output.is_some() && iface == self.out_iface { } else if let Some(output) = self.output.as_ref()
xfer.accept_with(&[self.output.as_ref().unwrap().alt_setting]) && iface == self.out_iface
.ok(); {
xfer.accept_with(&[output.alt_setting]).ok();
return; return;
} }
debug!(" Unimplemented."); debug!(" Unimplemented.");
@@ -1041,9 +1172,8 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> AudioClass<
channel: u8, channel: u8,
control: u8, control: u8,
) { ) {
match entity { if entity == 1 {
1 => return self.get_clock_cur(xfer, channel, control), return self.get_clock_cur(xfer, channel, control);
_ => {}
} }
debug!(" Unimplemented."); debug!(" Unimplemented.");
} }
@@ -1055,49 +1185,48 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> AudioClass<
channel: u8, channel: u8,
control: u8, control: u8,
) { ) {
match entity { if entity == 1 {
1 => return self.set_clock_cur(xfer, channel, control), return self.set_clock_cur(xfer, channel, control);
_ => {}
} }
debug!(" Unimplemented."); debug!(" Unimplemented.");
} }
fn set_streaming_interface_cur( fn set_streaming_interface_cur(
&mut self, &mut self,
xfer: ControlOut<B>, _xfer: ControlOut<B>,
direction: UsbDirection, _direction: UsbDirection,
entity: u8, _entity: u8,
channel: u8, _channel: u8,
control: u8, _control: u8,
) { ) {
debug!(" Unimplemented."); debug!(" Unimplemented.");
} }
fn get_endpoint_cur(&mut self, xfer: ControlIn<B>) { fn get_endpoint_cur(&mut self, xfer: ControlIn<B>) {
let req = xfer.request(); let req = xfer.request();
let entity = (req.index >> 8) as u8; let _entity = (req.index >> 8) as u8;
let interface = (req.index & 0xff) as u8; let _interface = (req.index & 0xff) as u8;
let control = (req.value >> 8) as u8; let _control = (req.value >> 8) as u8;
let channel = (req.value & 0xff) as u8; let _channel = (req.value & 0xff) as u8;
debug!(" Unimplemented."); debug!(" Unimplemented.");
} }
fn get_endpoint_range(&mut self, xfer: ControlIn<B>) { fn get_endpoint_range(&mut self, xfer: ControlIn<B>) {
let req = xfer.request(); let req = xfer.request();
let entity = (req.index >> 8) as u8; let _entity = (req.index >> 8) as u8;
let interface = (req.index & 0xff) as u8; let _interface = (req.index & 0xff) as u8;
let control = (req.value >> 8) as u8; let _control = (req.value >> 8) as u8;
let channel = (req.value & 0xff) as u8; let _channel = (req.value & 0xff) as u8;
debug!(" Unimplemented."); debug!(" Unimplemented.");
} }
fn set_endpoint_cur(&mut self, xfer: ControlOut<B>) { fn set_endpoint_cur(&mut self, xfer: ControlOut<B>) {
let req = xfer.request(); let req = xfer.request();
let entity = (req.index >> 8) as u8; let _entity = (req.index >> 8) as u8;
let interface = (req.index & 0xff) as u8; let _interface = (req.index & 0xff) as u8;
let control = (req.value >> 8) as u8; let _control = (req.value >> 8) as u8;
let channel = (req.value & 0xff) as u8; let _channel = (req.value & 0xff) as u8;
debug!(" Unimplemented."); debug!(" Unimplemented.");
} }
@@ -1135,8 +1264,8 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> AudioClass<
} }
} }
fn get_clock_cur(&mut self, xfer: ControlIn<B>, channel: u8, control: u8) { fn get_clock_cur(&mut self, xfer: ControlIn<B>, channel: u8, control: u8) {
match control.try_into() { match control.into() {
Ok(ClockSourceControlSelector::SamFreqControl) => { ClockSourceControlSelector::SamFreqControl => {
debug!(" SamplingFreqControl"); debug!(" SamplingFreqControl");
if channel != 0 { if channel != 0 {
error!( error!(
@@ -1145,7 +1274,7 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> AudioClass<
); );
} }
xfer.accept(|mut buf| { xfer.accept(|mut buf| {
let rate = self.clock_impl.get_sample_rate(); let rate = self.audio_impl.sample_rate();
debug!(" {}", rate); debug!(" {}", rate);
buf.write_u32::<LittleEndian>(rate) buf.write_u32::<LittleEndian>(rate)
@@ -1154,7 +1283,7 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> AudioClass<
}) })
.ok(); .ok();
} }
Ok(ClockSourceControlSelector::ClockValidControl) => { ClockSourceControlSelector::ClockValidControl => {
debug!(" ClockValidControl"); debug!(" ClockValidControl");
if channel != 0 { if channel != 0 {
error!( error!(
@@ -1162,7 +1291,7 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> AudioClass<
channel channel
); );
} }
xfer.accept(|mut buf| match self.clock_impl.get_clock_validity() { xfer.accept(|mut buf| match self.audio_impl.clock_validity() {
Ok(valid) => { Ok(valid) => {
debug!(" {}", valid); debug!(" {}", valid);
buf.write_u8(valid as u8) buf.write_u8(valid as u8)
@@ -1180,8 +1309,8 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> AudioClass<
} }
} }
fn set_clock_cur(&mut self, xfer: ControlOut<B>, channel: u8, control: u8) { fn set_clock_cur(&mut self, xfer: ControlOut<B>, channel: u8, control: u8) {
match control.try_into() { match control.into() {
Ok(ClockSourceControlSelector::SamFreqControl) => { ClockSourceControlSelector::SamFreqControl => {
debug!(" SamplingFreqControl"); debug!(" SamplingFreqControl");
if channel != 0 { if channel != 0 {
error!( error!(
@@ -1192,13 +1321,13 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> AudioClass<
match xfer.data().read_u32::<LittleEndian>() { match xfer.data().read_u32::<LittleEndian>() {
Ok(rate) => { Ok(rate) => {
debug!(" SET SamplingFreqControl CUR {}", rate); debug!(" SET SamplingFreqControl CUR {}", rate);
self.clock_impl.set_sample_rate(rate).ok(); self.audio_impl.set_sample_rate(rate).ok();
self.nominal_fb = UsbIsochronousFeedback::new_float( self.nominal_fb = UsbIsochronousFeedback::new_float(
rate.to_f32().unwrap() / self.audio_rate.to_f32().unwrap(), rate.to_f32().unwrap() / self.audio_rate.to_f32().unwrap(),
); );
xfer.accept().ok(); xfer.accept().ok();
} }
Err(e) => { Err(_e) => {
error!(" SET SamplingFreqControl CUR ERROR BAD DATA"); error!(" SET SamplingFreqControl CUR ERROR BAD DATA");
} }
} }
@@ -1209,8 +1338,8 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> AudioClass<
} }
} }
fn get_clock_range(&mut self, xfer: ControlIn<B>, channel: u8, control: u8) { fn get_clock_range(&mut self, xfer: ControlIn<B>, channel: u8, control: u8) {
match control.try_into() { match control.into() {
Ok(ClockSourceControlSelector::SamFreqControl) => { ClockSourceControlSelector::SamFreqControl => {
debug!(" SamplingFreqControl"); debug!(" SamplingFreqControl");
if channel != 0 { if channel != 0 {
error!( error!(
@@ -1218,7 +1347,7 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> AudioClass<
channel channel
); );
} }
xfer.accept(|mut buf| match self.clock_impl.get_rates() { xfer.accept(|mut buf| match self.audio_impl.sample_rates() {
Ok(rates) => { Ok(rates) => {
buf.write_u16::<LittleEndian>(rates.len() as u16) buf.write_u16::<LittleEndian>(rates.len() as u16)
.map_err(|_e| UsbError::BufferOverflow)?; .map_err(|_e| UsbError::BufferOverflow)?;
@@ -1253,3 +1382,15 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> AudioClass<
} }
} }
} }
/// Set up a `UsbDeviceBuilder` with the device IDs for Usb Audio Class 2.0
pub fn builder<'a, B: UsbBus>(
alloc: &'a UsbBusAllocator<B>,
vid_pid: UsbVidPid,
) -> UsbDeviceBuilder<'a, B> {
UsbDeviceBuilder::new(alloc, vid_pid)
.composite_with_iads() // required by UAC2 4.6
.device_class(0xef) // required by UAC2 4.2
.device_sub_class(0x02) // required by UAC2 4.2
.device_protocol(0x01) // required by UAC2 4.2
}
+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::*;