Compare commits

...

10 Commits

20 changed files with 4015 additions and 698 deletions
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",
]
+13 -2
View File
@@ -1,10 +1,21 @@
[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"
authors = ["Keenan Tims <ktims@gotroot.ca>"] authors = ["Keenan Tims <ktims@gotroot.ca>"]
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
keywords = ["no-std", "usb-device"] keywords = ["no-std", "usb-device", "audio"]
[features] [features]
defmt = ["dep:defmt", "usb-device/defmt"] defmt = ["dep:defmt", "usb-device/defmt"]
@@ -15,4 +26,4 @@ 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
+48
View File
@@ -0,0 +1,48 @@
# usbd-uac2 Examples
This repository contains example implementations of a USB Audio Class 2 (UAC2) device.
Two example backends are provided, both based on the LPCXpresso55S28 demo board, using the onboard WM8904 DAC:
- **Interrupt-driven example** (`lpc55s28-evk`)
- **DMA-based example** (`lpc55s28-evk-dma`)
## Examples Overview
### Interrupt-driven (`lpc55s28-evk`)
Running at 32bit/48khz. It can't keep up at 96khz. Works on USBFS and USBHS.
This is a minimal implementation intended to demonstrate the fundamental
structure of the class driver. It fills a `bbqueue` as data comes in from USB,
and drains it into the I2S FIFO in the I2S interrupt. This requires a lot of
time-critical CPU work managing buffers. Particularly, the USB peripheral driver
uses a lot of interrupt-free critical sections which can cause late interrupts
and underruns.
This is intended primarily as a learning/reference implementation
### DMA-based (`lpc55s28-evk-dma`)
Running at 32bit/96khz. 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.
## Running the Examples
You can flash and run either example using `cargo embed`:
```sh
cargo embed --release --example lpc55s28-evk --features usbfs
@@ -0,0 +1,19 @@
[target.thumbv8m.main-none-eabi]
rustflags = [
"-C", "link-arg=-Tlink.x",
"-C", "link-arg=-Tdefmt.x",
"-C", "debug-assertions",
]
[target.thumbv8m.main-none-eabihf]
rustflags = [
"-C", "link-arg=-Tlink.x",
"-C", "link-arg=-Tdefmt.x",
# "-C", "debug-assertions",
]
[build]
target = "thumbv8m.main-none-eabihf"
[env]
DEFMT_LOG = "off"
+695
View File
@@ -0,0 +1,695 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "atomic-polyfill"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4"
dependencies = [
"critical-section",
]
[[package]]
name = "autocfg"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "bare-metal"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3"
dependencies = [
"rustc_version 0.2.3",
]
[[package]]
name = "bitfield"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array 0.14.7",
]
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "byteorder-embedded-io"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed6bb9472871706c9b1f648ca527031e33d647a95706d6ab5659f22ca28d419"
dependencies = [
"byteorder",
"embedded-io",
]
[[package]]
name = "cipher"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
"crypto-common",
"inout",
]
[[package]]
name = "cortex-m"
version = "0.7.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9"
dependencies = [
"bare-metal",
"bitfield",
"critical-section",
"embedded-hal 0.2.7",
"volatile-register",
]
[[package]]
name = "cortex-m-rt"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "801d4dec46b34c299ccf6b036717ae0fce602faa4f4fe816d9013b9a7c9f5ba6"
dependencies = [
"cortex-m-rt-macros",
]
[[package]]
name = "cortex-m-rt-macros"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e37549a379a9e0e6e576fd208ee60394ccb8be963889eebba3ffe0980364f472"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "critical-section"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
[[package]]
name = "crypto-common"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
dependencies = [
"generic-array 0.14.7",
"typenum",
]
[[package]]
name = "defmt"
version = "0.3.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad"
dependencies = [
"defmt 1.0.1",
]
[[package]]
name = "defmt"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "548d977b6da32fa1d1fda2876453da1e7df63ad0304c8b3dae4dbe7b96f39b78"
dependencies = [
"bitflags",
"defmt-macros",
]
[[package]]
name = "defmt-macros"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d4fc12a85bcf441cfe44344c4b72d58493178ce635338a3f3b78943aceb258e"
dependencies = [
"defmt-parser",
"proc-macro-error2",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "defmt-parser"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e"
dependencies = [
"thiserror",
]
[[package]]
name = "defmt-rtt"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93d5a25c99d89c40f5676bec8cefe0614f17f0f40e916f98e345dae941807f9e"
dependencies = [
"critical-section",
"defmt 1.0.1",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]]
name = "embedded-hal"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff"
dependencies = [
"nb 0.1.3",
"void",
]
[[package]]
name = "embedded-hal"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89"
[[package]]
name = "embedded-io"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9eb1aa714776b75c7e67e1da744b81a129b3ff919c8712b5e1b32252c1f07cc7"
[[package]]
name = "embedded-time"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7a4b4d10ac48d08bfe3db7688c402baadb244721f30a77ce360bd24c3dffe58"
dependencies = [
"num",
]
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "generic-array"
version = "1.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eaf57c49a95fd1fe24b90b3033bee6dc7e8f1288d51494cb44e627c295e38542"
dependencies = [
"rustversion",
"typenum",
]
[[package]]
name = "hash32"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67"
dependencies = [
"byteorder",
]
[[package]]
name = "hash32"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606"
dependencies = [
"byteorder",
]
[[package]]
name = "heapless"
version = "0.7.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f"
dependencies = [
"atomic-polyfill",
"hash32 0.2.1",
"rustc_version 0.4.1",
"spin",
"stable_deref_trait",
]
[[package]]
name = "heapless"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad"
dependencies = [
"hash32 0.3.1",
"stable_deref_trait",
]
[[package]]
name = "inout"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
dependencies = [
"generic-array 0.14.7",
]
[[package]]
name = "lock_api"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
dependencies = [
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "log-to-defmt"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9454118d78a9089a15d702fbaf413da2fa23f331b8af6c5eed5784a4369173c6"
dependencies = [
"defmt 0.3.100",
"heapless 0.7.17",
"log",
]
[[package]]
name = "lpc55-hal"
version = "0.5.0"
source = "git+https://github.com/ktims/lpc55-hal?branch=main#8dfefd62aff4abd2de535f23107812dda68437be"
dependencies = [
"block-buffer",
"cipher",
"cortex-m",
"digest",
"embedded-hal 0.2.7",
"embedded-time",
"generic-array 1.3.5",
"lpc55-pac",
"nb 1.1.0",
"rand_core",
"usb-device",
"vcell",
"void",
]
[[package]]
name = "lpc55-pac"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a4952baed9d9e7e82a6bbc87333f939b90eb41df3e6c0be5e35d0ec61005f91"
dependencies = [
"cortex-m",
"cortex-m-rt",
"vcell",
]
[[package]]
name = "lpc55s28-evk-dma"
version = "0.1.0"
dependencies = [
"cortex-m",
"cortex-m-rt",
"defmt 1.0.1",
"defmt-rtt",
"embedded-hal 1.0.0",
"embedded-io",
"log-to-defmt",
"lpc55-hal",
"nb 1.1.0",
"panic-probe",
"static_cell",
"usb-device",
"usbd-uac2",
]
[[package]]
name = "modular-bitfield"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2956e537fc68236d2aa048f55704f231cc93f1c4de42fe1ecb5bd7938061fc4a"
dependencies = [
"modular-bitfield-impl",
"static_assertions",
]
[[package]]
name = "modular-bitfield-impl"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59b43b4fd69e3437618106f7754f34021b831a514f9e1a98ae863cabcd8d8dad"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "nb"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f"
dependencies = [
"nb 1.1.0",
]
[[package]]
name = "nb"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d"
[[package]]
name = "num"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b7a8e9be5e039e2ff869df49155f1c06bd01ade2117ec783e56ab0932b67a8f"
dependencies = [
"num-complex",
"num-integer",
"num-iter",
"num-rational",
"num-traits",
]
[[package]]
name = "num-complex"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "747d632c0c558b87dbabbe6a82f3b4ae03720d0646ac5b7b4dae89394be5f2c5"
dependencies = [
"num-traits",
]
[[package]]
name = "num-integer"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
dependencies = [
"num-traits",
]
[[package]]
name = "num-iter"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "panic-probe"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd402d00b0fb94c5aee000029204a46884b1262e0c443f166d86d2c0747e1a1a"
dependencies = [
"cortex-m",
"defmt 1.0.1",
]
[[package]]
name = "portable-atomic"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
[[package]]
name = "proc-macro-error-attr2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
dependencies = [
"proc-macro2",
"quote",
]
[[package]]
name = "proc-macro-error2"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
dependencies = [
"proc-macro-error-attr2",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
[[package]]
name = "rustc_version"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
dependencies = [
"semver 0.9.0",
]
[[package]]
name = "rustc_version"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
dependencies = [
"semver 1.0.28",
]
[[package]]
name = "rustversion"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "semver"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
dependencies = [
"semver-parser",
]
[[package]]
name = "semver"
version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
[[package]]
name = "semver-parser"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
dependencies = [
"lock_api",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "static_cell"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0530892bb4fa575ee0da4b86f86c667132a94b74bb72160f58ee5a4afec74c23"
dependencies = [
"portable-atomic",
]
[[package]]
name = "syn"
version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "typenum"
version = "1.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de"
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "usb-device"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98816b1accafbb09085168b90f27e93d790b4bfa19d883466b5e53315b5f06a6"
dependencies = [
"defmt 0.3.100",
"heapless 0.8.0",
"portable-atomic",
]
[[package]]
name = "usbd-uac2"
version = "0.1.0"
dependencies = [
"byteorder-embedded-io",
"defmt 1.0.1",
"embedded-io",
"modular-bitfield",
"num-traits",
"usb-device",
]
[[package]]
name = "vcell"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "void"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
[[package]]
name = "volatile-register"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc"
dependencies = [
"vcell",
]
+32
View File
@@ -0,0 +1,32 @@
[package]
name = "lpc55s28-evk-dma"
version = "0.1.0"
edition = "2024"
publish = false
[features]
default = ["usbhs"]
usbfs = []
usbhs = []
[dependencies]
cortex-m = { version = "0.7.7", features = ["critical-section-single-core"] }
cortex-m-rt = "0.7.5"
defmt = "1.0.1"
defmt-rtt = "1.1.0"
embedded-hal = "1.0.0"
embedded-io = "0.7.1"
log-to-defmt = "0.1.0"
nb = "1.1.0"
panic-probe = { version = "1.0.0", features = ["print-defmt"] }
static_cell = "2.1.1"
# Includes update to usb-device 0.3, fix for isochronous and smaller critical sections
lpc55-hal = { git = "https://github.com/ktims/lpc55-hal", branch = "main" }
usb-device.workspace = true
usbd-uac2 = { workspace = true, features = ["defmt"] }
[profile.release]
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);
}
+21
View File
@@ -0,0 +1,21 @@
MEMORY
{
FLASH : ORIGIN = 0x00000000, LENGTH = 512K
/* for use with standard link.x */
RAM : ORIGIN = 0x20000000, LENGTH = 192K
/* would be used with proper link.x */
/* needs changes to r0 (initialization code) */
/* SRAM0 : ORIGIN = 0x20000000, LENGTH = 64K */
/* SRAM1 : ORIGIN = 0x20010000, LENGTH = 64K */
/* SRAM2 : ORIGIN = 0x20020000, LENGTH = 64K */
/* SRAM3 : ORIGIN = 0x20030000, LENGTH = 64K */
/* CASPER SRAM regions */
/* SRAMX0: ORIGIN = 0x1400_0000, LENGTH = 4K /1* to 0x1400_0FFF *1/ */
/* SRAMX1: ORIGIN = 0x1400_4000, LENGTH = 4K /1* to 0x1400_4FFF *1/ */
/* USB1 SRAM regin */
/* USB1_SRAM : ORIGIN = 0x40100000, LENGTH = 16K */
}
+427
View File
@@ -0,0 +1,427 @@
use hal::Syscon;
use hal::peripherals::syscon::ClockControl;
use crate::{hal, pac};
use core::cell::UnsafeCell;
use core::convert::Infallible;
use core::ptr::copy_nonoverlapping;
use core::sync::atomic::{AtomicUsize, Ordering, compiler_fence};
pub const DMA0_FLEXCOMM7_TX: u8 = 19;
#[repr(C)]
#[derive(Copy, Clone)]
pub struct DmaDescriptor {
pub xfercfg: u32,
pub src_end: *const u8,
pub dst_end: *mut u32,
pub next: *const DmaDescriptor,
}
impl defmt::Format for DmaDescriptor {
fn format(&self, fmt: defmt::Formatter) {
defmt::write!(
fmt,
"xfercfg={:x} src_end={:x} dst_end={:x} next={:x}",
self.xfercfg,
self.src_end,
self.dst_end,
self.next
)
}
}
// Channel descriptor table; linked from SRAMBASE
#[repr(C, align(512))]
pub struct DescriptorTable {
pub d: [DmaDescriptor; 32],
}
// Our ring that we will transition to once the transfer begins
#[repr(C)]
pub struct RingDescriptors<const N: usize> {
pub d: [DmaDescriptor; N],
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct PushResult {
pub written: usize,
pub dropped: usize,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum ConfigError {
SlotTooLarge,
SlotTooSmall,
SlotNotAligned,
UnsupportedWidth,
}
#[derive(Debug)]
pub enum DmaError {
Underrun,
}
impl core::error::Error for DmaError {}
impl core::fmt::Display for DmaError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("DmaUnderrun")
}
}
/// Slot-based DMA ring
pub struct DmaRing<const N: usize, const MAX_SLOT_BYTES: usize> {
dma: pac::DMA0,
/// Destination peripheral register (FIFO write register)
dst_reg: *mut u32,
// SAFETY: only written by USB task (on start)
pub(crate) channel_desc: UnsafeCell<DescriptorTable>,
// SAFETY: only written by USB task (on start)
pub(crate) desc: UnsafeCell<RingDescriptors<N>>,
slots: UnsafeCell<[[u8; MAX_SLOT_BYTES]; N]>,
/// Effective bytes per slot. Maybe be smaller than MAX_SLOT_BYTES (e.g. at lower sample rates), as the setup is designed for constant rate not constant size.
slot_bytes: usize,
/// How many bytes to transfer to the FIFO
word_bytes: usize,
// SAFETY: producer only
write_slot: UnsafeCell<usize>,
write_off: UnsafeCell<usize>,
produced: AtomicUsize,
consumed: AtomicUsize,
/// Leave at least one slot empty so producer never overwrites a slot DMA may still read.
safety_gap: usize,
pub produced_bytes: AtomicUsize,
pub consumed_bytes: AtomicUsize,
}
impl<const N: usize, const MAX_SLOT_BYTES: usize> DmaRing<N, MAX_SLOT_BYTES> {
/// Construct using PAC DMA0 + &mut SYSCON + a destination FIFO register.
pub fn new(
dma: pac::DMA0,
syscon: &mut Syscon,
dst_reg: *mut u32,
word_bytes: usize,
) -> Result<Self, ConfigError> {
if word_bytes != 1 && word_bytes != 2 && word_bytes != 4 {
return Err(ConfigError::UnsupportedWidth);
}
// Start the DMA0 clock
dma.enable_clock(syscon);
Ok(Self {
dma,
dst_reg: dst_reg,
channel_desc: UnsafeCell::new(DescriptorTable {
d: [DmaDescriptor {
xfercfg: 0,
src_end: core::ptr::null(),
dst_end: core::ptr::null_mut(),
next: core::ptr::null(),
}; 32],
}),
desc: UnsafeCell::new(RingDescriptors {
d: [DmaDescriptor {
xfercfg: 0,
src_end: core::ptr::null(),
dst_end: core::ptr::null_mut(),
next: core::ptr::null(),
}; N],
}),
slots: UnsafeCell::new([[0u8; MAX_SLOT_BYTES]; N]),
slot_bytes: MAX_SLOT_BYTES,
word_bytes,
write_slot: UnsafeCell::new(0),
write_off: UnsafeCell::new(0),
produced: AtomicUsize::new(0),
consumed: AtomicUsize::new(0),
safety_gap: 1,
produced_bytes: AtomicUsize::new(0),
consumed_bytes: AtomicUsize::new(0),
})
}
/// Optional: adjust safety gap (defaults to 1 empty slot).
pub fn set_safety_gap(&mut self, gap_slots: usize) {
self.safety_gap = gap_slots.min(N);
}
pub fn slot_size(&self) -> usize {
self.slot_bytes
}
pub fn set_slot_size(&mut self, slot_bytes: usize) -> Result<(), ConfigError> {
if slot_bytes == 0 {
return Err(ConfigError::SlotTooSmall);
}
if slot_bytes > MAX_SLOT_BYTES {
return Err(ConfigError::SlotTooLarge);
}
if slot_bytes % self.word_bytes != 0 {
return Err(ConfigError::SlotNotAligned);
}
self.slot_bytes = slot_bytes;
self.reset_producer();
Ok(())
}
/// Producer: copy into ring; commits whole slots; reports overflow by returning dropped bytes.
pub fn push(&self, mut data: &[u8]) -> PushResult {
let mut written = 0usize;
let write_slot = unsafe { &mut *self.write_slot.get() };
let write_off = unsafe { &mut *self.write_off.get() };
let slots = unsafe { &mut *self.slots.get() };
defmt::debug!(
"produced={} consumed={} fill={}",
self.produced(),
self.consumed(),
self.fill_slots()
);
while !data.is_empty() {
if self.is_full_for_producer() {
break;
}
let cap = self.slot_bytes - *write_off;
let n = core::cmp::min(cap, data.len());
unsafe {
let dst = slots[*write_slot].as_mut_ptr().add(*write_off);
copy_nonoverlapping(data.as_ptr(), dst, n);
}
*write_off += n;
written += n;
data = &data[n..];
if *write_off == self.slot_bytes {
// publish completed slot
compiler_fence(Ordering::Release);
self.produced.fetch_add(1, Ordering::Release);
*write_slot = (*write_slot + 1) % N;
*write_off = 0;
}
}
self.produced_bytes.fetch_add(written, Ordering::Release);
PushResult {
written,
dropped: data.len(),
}
}
/// Call from DMA IRQ bookkeeping when a slot has been consumed.
pub fn advance_consumed(&self, slots: usize) -> Result<(), DmaError> {
let produced = self.produced.load(Ordering::Acquire);
let consumed = self.consumed.load(Ordering::Relaxed);
if consumed < produced {
self.consumed.fetch_add(slots, Ordering::Release);
self.consumed_bytes
.fetch_add(slots * self.slot_bytes, Ordering::Relaxed);
Ok(())
} else {
defmt::error!("DMA underrun!");
Err(DmaError::Underrun)
}
}
pub fn produced(&self) -> usize {
self.produced.load(Ordering::Acquire)
}
pub fn produced_bytes(&self) -> usize {
self.produced_bytes.load(Ordering::Acquire)
}
pub fn consumed(&self) -> usize {
self.consumed.load(Ordering::Acquire)
}
pub fn consumed_bytes(&self) -> usize {
loop {
let consumed_start = self.consumed.load(Ordering::Acquire);
let reg_1 = self.dma.channel19.xfercfg.read().bits() as usize >> 16 & 0x3ff;
let reg_2 = self.dma.channel19.xfercfg.read().bits() as usize >> 16 & 0x3ff;
let consumed_end = self.consumed.load(Ordering::Acquire);
if consumed_start == consumed_end && reg_1 == reg_2 {
// 1. Map the hardware remaining countdown into a clean byte count
let remaining_bytes = if reg_1 == 0x3ff {
0 // 0x3FF means all transfers completed, 0 bytes remaining
} else {
// Formula from NXP manual: (XFERCOUNT + 1) * Data Width
(reg_1 + 1) * self.word_bytes
};
// 2. Total bytes consumed in this specific active slot
let active_slot_consumed = self.slot_bytes - remaining_bytes;
// 3. Combine with your software index history accumulator
return consumed_start * self.slot_bytes + active_slot_consumed;
}
}
}
pub fn fill_slots(&self) -> usize {
self.produced().wrapping_sub(self.consumed())
}
pub fn init(&self) {
self.init_descriptors();
// Descriptor table base
let desc = unsafe { &*self.desc.get() };
let base = self.channel_desc.get() as u32;
self.dma.srambase.write(|w| unsafe { w.bits(base) });
self.dma
.channel19
.cfg
.write(|w| w.periphreqen().enabled().hwtrigen().disabled());
self.dma
.channel19
.xfercfg
.write(|w| unsafe { w.bits(desc.d[0].xfercfg) });
self.dma.enableclr0.write(|w| unsafe { w.bits(1 << 19) });
self.dma.ctrl.write(|w| w.enable().enabled());
self.dma.setvalid0.write(|w| unsafe { w.bits(1 << 19) });
self.dma.intenset0.write(|w| unsafe { w.bits(1 << 19) });
self.dma.settrig0.write(|w| unsafe { w.bits(1 << 19) });
}
pub fn run(&self) {
self.dma.enableset0.write(|w| unsafe { w.bits(1 << 19) });
}
pub fn stop(&self) {
self.dma.enableclr0.write(|w| unsafe { w.bits(1 << 19) });
nb::block!(if (self.dma.busy0.read().bits() & 1 << 19) == 0 {
Ok(())
} else {
Err(nb::Error::<Infallible>::WouldBlock)
});
self.dma.abort0.write(|w| unsafe { w.bits(1 << 19) });
self.reset_producer();
}
fn reset_producer(&self) {
unsafe {
*(&mut *self.write_slot.get()) = 0;
*(&mut *self.write_off.get()) = 0;
}
self.produced.store(0, Ordering::Relaxed);
self.produced_bytes.store(0, Ordering::Relaxed);
self.consumed.store(0, Ordering::Relaxed);
self.consumed_bytes.store(0, Ordering::Relaxed);
}
fn is_full_for_producer(&self) -> bool {
let fill = self.fill_slots();
fill >= N.wrapping_sub(self.safety_gap)
}
fn reset_producer_init_only(&self) {
unsafe {
*self.write_slot.get() = 0;
}
unsafe {
*self.write_off.get() = 0;
}
self.produced.store(0, Ordering::Relaxed);
self.consumed.store(0, Ordering::Relaxed);
self.produced_bytes.store(0, Ordering::Relaxed);
self.consumed_bytes.store(0, Ordering::Relaxed);
}
fn init_descriptors(&self) {
let slots = unsafe { &mut *self.slots.get() };
let desc = unsafe { &mut *self.desc.get() };
let chan_desc = unsafe { &mut *self.channel_desc.get() };
defmt::debug!("slots base: &{:x}", self.slots.get());
// Pre-fill with silence so underrun replays silence.
for i in 0..N {
slots[i][..self.slot_bytes].fill(0);
}
let transfers = (self.slot_bytes / self.word_bytes) as u32;
for i in 0..N {
let src_start = slots[i].as_ptr() as usize;
let src_end = (src_start + self.slot_bytes - self.word_bytes) as *const u8;
let next = &desc.d[(i + 1) % N] as *const DmaDescriptor;
desc.d[i] = DmaDescriptor {
xfercfg: encode_xfercfg(
true, // valid
true, // reload
false, // swtrig (we use XFERCFG SWTRIG kick)
false, // clrtrig
true, // intA
false, // intB
self.word_bytes as u32,
1, // src_inc
0, // dst_inc
transfers,
),
src_end,
dst_end: self.dst_reg,
next,
};
}
chan_desc.d[19] = desc.d[0];
chan_desc.d[19].xfercfg = 0;
// reset producer indices + counters (init-only action)
self.reset_producer_init_only();
}
}
unsafe impl<const N: usize, const MAX_SLOT_BYTES: usize> Sync for DmaRing<N, MAX_SLOT_BYTES> {}
/// XFERCFG encoding follows the common LPC DMA layout:
/// - SETINTA at bit4, SETINTB at bit5
/// - WIDTH at bits 9:8
/// - SRCINC at bits 13:12
/// - DSTINC at bits 15:14
/// - XFERCOUNT at bits 25:16
/// This layout is shown in LPC DMA examples. [5](https://www.kernel.org/doc/html/latest/core-api/dma-api-howto.html)
fn encode_xfercfg(
cfgvalid: bool,
reload: bool,
swtrig: bool,
clrtrig: bool,
inta: bool,
intb: bool,
width_bytes: u32,
src_inc: u32,
dst_inc: u32,
transfers: u32,
) -> u32 {
let width_code = match width_bytes {
1 => 0,
2 => 1,
4 => 2,
_ => 0,
};
let count_field = transfers.saturating_sub(1) & 0x3FF;
((cfgvalid as u32) << 0)
| ((reload as u32) << 1)
| ((swtrig as u32) << 2)
| ((clrtrig as u32) << 3)
| ((inta as u32) << 4)
| ((intb as u32) << 5)
| ((width_code & 0x3) << 8)
| ((src_inc & 0x3) << 12)
| ((dst_inc & 0x3) << 14)
| (count_field << 16)
}
+308
View File
@@ -0,0 +1,308 @@
//! Contains hardware setup unrelated to Usb Audio Class implementation
use crate::Syscon;
use crate::hal;
use crate::{MCLK_FREQ, SAMPLE_RATE, pac};
use core::cell::UnsafeCell;
use core::mem::MaybeUninit;
use defmt::{debug, info};
use hal::{
Enabled, Iocon, Pin,
drivers::pins,
traits::wg::digital::v2::{OutputPin, ToggleableOutputPin},
typestates::pin::{gpio::direction::Output, state::Gpio},
};
pub(crate) struct PllConstants {
pub m: u16, // 1-65535
pub n: u8, // 1-255
pub p: u8, // 1-31
pub selp: u8, // 5 bits
pub seli: u8, // 6 bits
}
impl PllConstants {
pub(crate) const fn new(n: u8, m: u16, p: u8) -> Self {
assert!(n != 0, "1 <= N <= 255");
assert!(m != 0, "1 <= M <= 65535");
assert!(p != 0 && p <= 31, "1 <= P <= 31");
// Following ripped from lpc55-hal and made const
// UM 4.6.6.3.2
let selp = {
let v = (m >> 2) + 1;
if v < 31 { v } else { 31 }
} as u8;
let seli = {
let v = match m {
m if m >= 8000 => 1,
m if m >= 122 => 8000 / m,
_ => 2 * (m >> 2) + 3,
};
if v < 63 { v } else { 63 }
} as u8;
// let seli = min(2*(m >> 2) + 3, 63);
Self {
n,
m,
p,
selp,
seli,
}
}
}
impl defmt::Format for PllConstants {
fn format(&self, fmt: defmt::Formatter) {
let factor = f32::from(self.m) / (f32::from(self.n) * 2.0 * f32::from(self.p));
defmt::write!(
fmt,
"m: {} n: {} p: {} selp: {} seli: {} fout: fin * {}",
self.m,
self.n,
self.p,
self.selp,
self.seli,
factor
);
}
}
// Fo = M/(N*2*P) * Fin
// Fo = 3072/(125*2*8) * 16MHz = 24.576MHz
const AUDIO_PLL: PllConstants = PllConstants::new(125, 3072, 8);
// Set PLL0 to 24.576MHz, start, and wait for lock
// This is not exposed by lpc55-hal, unfortunately. Copy their implementation here.
pub(crate) fn init_audio_pll() {
let syscon = unsafe { &*pac::SYSCON::ptr() };
let pmc = unsafe { &*pac::PMC::ptr() };
let anactrl = unsafe { &*pac::ANACTRL::ptr() };
debug!("start clk_in");
pmc.pdruncfg0
.modify(|_, w| w.pden_xtal32m().poweredon().pden_ldoxo32m().poweredon());
syscon.clock_ctrl.modify(|_, w| w.clkin_ena().enable());
anactrl
.xo32m_ctrl
.modify(|_, w| w.enable_system_clk_out().enable());
debug!("init pll0: {}", AUDIO_PLL);
pmc.pdruncfg0
.modify(|_, w| w.pden_pll0().poweredoff().pden_pll0_sscg().poweredoff());
syscon.pll0clksel.write(|w| w.sel().enum_0x1()); // clk_in
syscon.pll0ctrl.write(|w| unsafe {
w.clken()
.enable()
.seli()
.bits(AUDIO_PLL.seli)
.selp()
.bits(AUDIO_PLL.selp)
});
syscon
.pll0ndec
.write(|w| unsafe { w.ndiv().bits(AUDIO_PLL.n) });
syscon.pll0ndec.write(|w| unsafe {
w.ndiv().bits(AUDIO_PLL.n).nreq().set_bit() // latch
});
syscon
.pll0pdec
.write(|w| unsafe { w.pdiv().bits(AUDIO_PLL.p) });
syscon.pll0pdec.write(|w| unsafe {
w.pdiv().bits(AUDIO_PLL.p).preq().set_bit() // latch
});
syscon.pll0sscg0.write(|w| unsafe { w.md_lbs().bits(0) });
syscon
.pll0sscg1
.write(|w| unsafe { w.mdiv_ext().bits(AUDIO_PLL.m).sel_ext().set_bit() });
syscon.pll0sscg1.write(|w| unsafe {
w.mdiv_ext()
.bits(AUDIO_PLL.m)
.sel_ext()
.set_bit()
.mreq()
.set_bit() // latch
.md_req()
.set_bit() // latch
});
pmc.pdruncfg0
.modify(|_, w| w.pden_pll0().poweredon().pden_pll0_sscg().poweredon());
info!("pll0 wait for lock");
let mut i = 0usize;
while syscon.pll0stat.read().lock().bit_is_clear() {
i += 1;
}
info!("pll0 locked after {} loops", i);
}
pub struct I2sTx {
pub i2s: pac::I2S7,
}
pub fn init_i2s(mut fc7: pac::FLEXCOMM7, i2s7: pac::I2S7, syscon: &mut Syscon) -> I2sTx {
defmt::debug!("init i2s");
syscon.reset(&mut fc7);
syscon.enable_clock(&mut fc7);
unsafe {
pac::IOCON::ptr().as_ref().unwrap().pio1_31.modify(|_, w| {
w.func()
.alt1()
.mode()
.inactive()
.slew()
.fast()
.invert()
.disabled()
.digimode()
.digital()
.od()
.normal()
});
pac::SYSCON::ptr()
.as_ref()
.unwrap()
.fcclksel7()
.modify(|_, w| w.sel().enum_0x5()); // MCLK
pac::SYSCON::ptr()
.as_ref()
.unwrap()
.mclkclksel
.modify(|_, w| w.sel().enum_0x1()); // PLL0
pac::SYSCON::ptr()
.as_ref()
.unwrap()
.mclkdiv
.modify(|_, w| w.div().bits(1).halt().run().reset().released()); // div by 2 = PLL0 fout / 2 = 12.288MHz, max for WM8904 @ 96k
pac::SYSCON::ptr()
.as_ref()
.unwrap()
.mclkio
.modify(|_, w| w.mclkio().output());
};
// Select I2S TX function
fc7.pselid.write(|w| w.persel().i2s_transmit());
let regs = i2s7;
// Enable TX FIFO only
regs.fifocfg.modify(|_, w| {
w.enabletx()
.enabled()
.enablerx()
.disabled()
.dmatx()
.disabled()
.txi2se0()
.zero()
});
regs.fifotrig.modify(|_, w| unsafe { w.txlvl().bits(6) });
// Flush
regs.fifocfg.modify(|_, w| w.emptytx().set_bit());
regs.cfg2
.modify(|_, w| unsafe { w.position().bits(0).framelen().bits(63) }); // framelen = 64
let bclk_div = (MCLK_FREQ / SAMPLE_RATE / 64) as u16;
regs.div
.modify(|_, w| unsafe { w.div().bits(bclk_div - 1) }); // Clock source is MCLK (12.288MHz) / 4 = 3MHz
// Config
regs.cfg1.modify(|_, w| unsafe {
w.mstslvcfg()
.normal_master()
.onechannel()
.dual_channel()
.datalen()
.bits(31)
.mainenable()
.enabled()
.mode()
.classic_mode()
.datapause()
.normal()
});
I2sTx { i2s: regs }
}
pub struct SharedLed<T: OutputPin> {
inner: UnsafeCell<T>,
}
unsafe impl<T: OutputPin> Sync for SharedLed<T> {}
impl<T: OutputPin> SharedLed<T> {
pub fn new(inner: T) -> Self {
Self {
inner: UnsafeCell::new(inner),
}
}
pub fn on(&self) {
unsafe {
(*self.inner.get()).set_low().ok();
}
}
pub fn off(&self) {
unsafe {
(*self.inner.get()).set_high().ok();
}
}
}
impl<T: OutputPin + ToggleableOutputPin> SharedLed<T> {
pub fn toggle(&self) {
unsafe {
(*self.inner.get()).toggle().ok();
}
}
}
type RedLed = Pin<pins::Pio1_6, Gpio<Output>>;
type GreenLed = Pin<pins::Pio1_7, Gpio<Output>>;
type BlueLed = Pin<pins::Pio1_4, Gpio<Output>>;
pub static RED_LED: MaybeUninit<SharedLed<RedLed>> = MaybeUninit::uninit();
pub static GREEN_LED: MaybeUninit<SharedLed<GreenLed>> = MaybeUninit::uninit();
pub static BLUE_LED: MaybeUninit<SharedLed<BlueLed>> = MaybeUninit::uninit();
pub fn init_leds(iocon: &mut Iocon<Enabled>, gpio: &mut hal::Gpio<Enabled>) {
let red_led = SharedLed::new(
pins::Pio1_6::take()
.unwrap()
.into_gpio_pin(iocon, gpio)
.into_output_low(),
);
let green_led = SharedLed::new(
pins::Pio1_7::take()
.unwrap()
.into_gpio_pin(iocon, gpio)
.into_output_low(),
);
let blue_led = SharedLed::new(
pins::Pio1_4::take()
.unwrap()
.into_gpio_pin(iocon, gpio)
.into_output_low(),
);
unsafe {
core::ptr::write(RED_LED.as_ptr() as *mut SharedLed<RedLed>, red_led);
core::ptr::write(GREEN_LED.as_ptr() as *mut SharedLed<GreenLed>, green_led);
core::ptr::write(BLUE_LED.as_ptr() as *mut SharedLed<BlueLed>, blue_led);
}
}
pub fn red_led() -> &'static SharedLed<RedLed> {
unsafe { &*RED_LED.as_ptr() }
}
pub fn green_led() -> &'static SharedLed<GreenLed> {
unsafe { &*GREEN_LED.as_ptr() }
}
pub fn blue_led() -> &'static SharedLed<BlueLed> {
unsafe { &*BLUE_LED.as_ptr() }
}
+401
View File
@@ -0,0 +1,401 @@
//! DMA based audio output example for the LPCXpresso55S28 demo board
//!
//! Uses the onboard WM8904 DAC at 96KHz. Clock is generated by PLL0. Simple proportional feedback is implemented.
//!
//! USB walks around a static ring of slots, filling them as data comes in from
//! the host. DMA chases it, filling the I2S FIFO as it drains to the DAC.
//! Feedback ensures that the host doesn't overrun or underrun the ring.
//!
//! This implementation is more suitable for real use than the interrupt-based
//! example, but it is still missing many niceties and behaves worse in
//! anomalous situations since the DMA just keeps trucking over the ring
//! regardless of the data validity.
#![no_main]
#![no_std]
#[cfg(all(feature = "usbfs", feature = "usbhs"))]
compile_error!("Choose one USB peripheral, usbfs and usbhs cannot be used together");
extern crate panic_probe;
#[defmt::panic_handler]
fn panic() -> ! {
panic_probe::hard_fault()
}
use core::sync::atomic::{AtomicBool, Ordering};
use cortex_m_rt::entry;
use defmt::debug;
use defmt_rtt as _;
use hal::raw as pac;
use hal::{
Syscon,
drivers::{Timer, UsbBus, pins},
prelude::*,
time::Hertz,
};
use lpc55_hal as hal;
use pac::interrupt;
use static_cell::StaticCell;
use usb_device::{
bus::{self},
device::{StringDescriptors, UsbDeviceBuilder, UsbVidPid},
};
use usbd_uac2::TerminalConfig;
use usbd_uac2::{
self, AudioHandler, ClockSource, RangeEntry, UsbAudioClassConfig, UsbIsochronousFeedback,
UsbSpeed, constants::FunctionCode, descriptors::ClockType,
};
use crate::dma::DmaRing;
use crate::hw::{I2sTx, blue_led, green_led, red_led};
mod dma;
mod hw;
mod wm8904;
const CODEC_I2C_ADDR: u8 = 0b0011010;
const MCLK_FREQ: u32 = 12288000;
const SAMPLE_RATE: u32 = 96000;
const USB_FRAME_RATE: u32 = if cfg!(feature = "usbhs") { 8000 } else { 1000 };
//latency ≈ (current_fill × FRAMES_PER_SLOT)
// + FRAMES_PER_SLOT/2 - average DMA transfer position
// + 8 - FIFO depth @ 32-bit samples
// with example values, ~2.3ms
const BYTES_PER_SAMPLE: usize = 4; // 32 bit samples
const BYTES_PER_FRAME: usize = BYTES_PER_SAMPLE * 2; // 2 channels
const FRAMES_PER_SLOT: usize = SAMPLE_RATE as usize / 2000; // run the DMA at 2khz
const BYTES_PER_SLOT: usize = FRAMES_PER_SLOT * BYTES_PER_FRAME;
const N_SLOTS: usize = 8;
const FILL_TARGET_BYTES: i32 = (BYTES_PER_SLOT * N_SLOTS) as i32 / 2;
const LOG_PERIOD: u32 = 1000;
static DMA_RING: StaticCell<DmaRing<N_SLOTS, BYTES_PER_SLOT>> = StaticCell::new();
static mut DMA_RING_REF: Option<&'static DmaRing<N_SLOTS, BYTES_PER_SLOT>> = None;
#[inline]
fn dma_ring() -> &'static DmaRing<N_SLOTS, BYTES_PER_SLOT> {
unsafe { DMA_RING_REF.unwrap() }
}
#[interrupt]
fn DMA0() {
let dma = unsafe { &*pac::DMA0::ptr() };
let inta = dma.inta0.read().bits();
let err = dma.errint0.read().bits();
if (err & (1 << 19)) != 0 {
let live = dma.channel19.xfercfg.read().bits();
let desc = unsafe { &*dma_ring().channel_desc.get() };
let mem = desc.d[19];
defmt::error!(
"DMA error ch19: live={=u32:08x} INTA={=u32:x} ERR={=u32:x}\n desc: {}",
live,
inta,
err,
mem
);
red_led().on();
dma.errint0.write(|w| unsafe { w.bits(1 << 19) });
}
if (inta & (1 << 19)) != 0 {
dma.inta0.write(|w| unsafe { w.bits(1 << 19) });
if dma_ring().advance_consumed(1).is_err() {
red_led().on();
}
}
}
struct Audio<'a, const N: usize, const MAX_SLOT_BYTES: usize> {
running: AtomicBool,
i2s: I2sTx,
dma: &'a DmaRing<N, MAX_SLOT_BYTES>,
log_counter: u32,
}
impl<const N: usize, const MAX_SLOT_BYTES: usize> Audio<'_, N, MAX_SLOT_BYTES> {
const RATES: [RangeEntry<u32>; 1] = [RangeEntry::new_fixed(SAMPLE_RATE)];
fn start(&mut self) {
red_led().off(); // clear any dma error
self.running.store(false, Ordering::Relaxed);
defmt::info!("playback armed (DMA)");
let i2s = &self.i2s.i2s;
i2s.fifotrig
.modify(|_, w| unsafe { w.txlvl().bits(6).txlvlena().enabled() });
// Enable TX FIFO
i2s.fifocfg
.modify(|_, w| w.enabletx().enabled().dmatx().enabled());
dma_ring().init();
// Enable DMA interrupt (channel 19)
unsafe { pac::NVIC::unmask(pac::Interrupt::DMA0) };
green_led().on();
}
fn stop(&self) {
// If we don't disable interrupts while stopped, we will underflow constantly and continuously refill the fifo with 0s
// We could actually stop the I2S here, but sometimes that makes the DAC misbehave. The peripheral is configured to send
// 0s when the FIFO is empty, so this is fine.
pac::NVIC::mask(pac::Interrupt::DMA0);
self.running.store(false, Ordering::Relaxed);
dma_ring().stop();
defmt::info!("playback stopped");
green_led().off();
blue_led().off();
}
}
impl<const N: usize, const MAX_SLOT_BYTES: usize, B: bus::UsbBus> AudioHandler<'_, B>
for Audio<'_, N, MAX_SLOT_BYTES>
{
fn alternate_setting_changed(&mut self, _terminal: usb_device::UsbDirection, alt_setting: u8) {
// alt setting 0 means stopped
match alt_setting {
0 => self.stop(),
1 => self.start(),
_ => defmt::error!("unexpected alt setting {}", alt_setting),
}
}
fn audio_data_rx(
&mut self,
ep: &usb_device::endpoint::Endpoint<'_, B, usb_device::endpoint::Out>,
) {
// Buffer must fit 125us of audio data (based on how `usbd_uac2` sets up the descriptors).
// Buffer must have room for one additional frame in case the host clock runs slower than the device.
let mut buf = [0; (SAMPLE_RATE.div_ceil(USB_FRAME_RATE) + 1) as usize * BYTES_PER_FRAME];
let len = match ep.read(&mut buf) {
Ok(len) => len,
Err(_) => {
defmt::error!("usb error in rx callback");
return;
}
};
let buf = &buf[..len];
let res = self.dma.push(buf);
if res.dropped != 0 {
// Overflow: some or all bytes couldn't be queued.
blue_led().toggle();
defmt::error!(
"overflowed dma ring, asked {}, wrote {}, dropped {}",
buf.len(),
res.written,
res.dropped
);
}
// If we're not running yet, wait until we reach 50% full then enable DMA requests
if !self.running.load(Ordering::Relaxed) && self.dma.fill_slots() >= (N_SLOTS / 2) {
defmt::info!(
"buffer warmed ({} slots) starting playback",
self.dma.fill_slots()
);
self.dma.run();
self.running.store(true, Ordering::Relaxed)
}
}
/// Provide rate feedback to the host. P-only is stable and works fine, with
/// most hosts. The host can either filter it internally or treat it
/// instantaneously and send more data specifically when the error gets
/// large; we will absorb reasonable clock drifts with our ring buffer.
fn feedback(&mut self, nominal_rate: UsbIsochronousFeedback) -> Option<UsbIsochronousFeedback> {
// Don't want to signal an absurd rate when not consuming; let the
// buffer fill before starting feedback.
if !self.running.load(Ordering::Acquire) {
return Some(nominal_rate);
}
let produced_bytes = self.dma.produced_bytes();
let consumed_bytes = self.dma.consumed_bytes();
if produced_bytes < consumed_bytes || produced_bytes == 0 {
defmt::error!("[fb] dma underrun detected!");
red_led().on();
return Some(nominal_rate);
}
let current_bytes = (produced_bytes - consumed_bytes) as i32;
// normalize error wrt. frame size etc.
let error_permille = ((current_bytes - FILL_TARGET_BYTES) * 1000) / FILL_TARGET_BYTES;
let nominal_v = nominal_rate.to_u32_12_13() as i32;
// 0.2% which is a huge clock error
let max_allowed_deviation = nominal_v / 500;
let p_term = -(error_permille * nominal_v) / 256000; // this works reasonably well to keep the buffer
let i_term = 0; // placeholder
let mut v = nominal_v + p_term + i_term;
v = v.clamp(
nominal_v - max_allowed_deviation,
nominal_v + max_allowed_deviation,
);
self.log_counter += 1;
if self.log_counter.is_multiple_of(LOG_PERIOD) {
defmt::info!(
"fill:{}% err_pm:{} p:{} i:{} fb_delta:{} fb:{=u32:x}",
(current_bytes * 100) / (N_SLOTS * BYTES_PER_SLOT) as i32,
error_permille,
p_term,
i_term,
v - nominal_v,
v as u32
);
}
Some(UsbIsochronousFeedback::new(v as u32))
}
}
impl<const N: usize, const MAX_SLOT_BYTES: usize> ClockSource for Audio<'_, N, MAX_SLOT_BYTES> {
const CLOCK_TYPE: usbd_uac2::descriptors::ClockType = ClockType::InternalFixed;
const SOF_SYNC: bool = false;
fn sample_rate(&self) -> u32 {
Self::RATES[0].min
}
fn sample_rates(
&self,
) -> core::result::Result<&[usbd_uac2::RangeEntry<u32>], usbd_uac2::UsbAudioClassError> {
Ok(&Self::RATES)
}
fn clock_validity(&self) -> core::result::Result<bool, usbd_uac2::UsbAudioClassError> {
Ok(true)
}
}
#[entry]
fn main() -> ! {
let hal = hal::new();
let mut anactrl = hal.anactrl;
let mut pmc = hal.pmc;
let mut syscon = hal.syscon;
let mut gpio = hal.gpio.enabled(&mut syscon);
let mut iocon = hal.iocon.enabled(&mut syscon);
debug!("start");
hw::init_leds(&mut iocon, &mut gpio);
debug!("iocon");
let usb0_vbus_pin = pins::Pio0_22::take()
.unwrap()
.into_usb0_vbus_pin(&mut iocon);
let codec_i2c_pins = (
pins::Pio1_20::take().unwrap().into_i2c4_scl_pin(&mut iocon),
pins::Pio1_21::take().unwrap().into_i2c4_sda_pin(&mut iocon),
);
// We can initialize and iocon these, but there is no peripheral driver, so they do not get used
let _codec_i2s_pins = (
pins::Pio0_21::take().unwrap().into_spi7_sck_pin(&mut iocon),
pins::Pio0_20::take().unwrap().into_i2s7_sda_pin(&mut iocon),
pins::Pio0_19::take().unwrap().into_i2s7_ws_pin(&mut iocon),
pins::Pio1_31::take().unwrap(), // MCLK
);
debug!("clocks");
let clocks = hal::ClockRequirements::default()
.system_frequency(96.MHz())
.configure(&mut anactrl, &mut pmc, &mut syscon)
.unwrap();
let mut usb_delay_timer = Timer::new(
hal.ctimer
.0
.enabled(&mut syscon, clocks.support_1mhz_fro_token().unwrap()),
);
// Start PLL0 at 24.576MHz as the audio clock. The FRO cannot evenly divide
// any common audio frequencies and is not particularly stable anyway.
hw::init_audio_pll();
debug!("peripherals");
let i2c_peripheral = hal
.flexcomm
.4
.enabled_as_i2c(&mut syscon, &clocks.support_flexcomm_token().unwrap());
let mut i2c_bus = I2cMaster::new(
i2c_peripheral,
codec_i2c_pins,
Hertz::try_from(400.kHz()).unwrap(),
);
let i2s_peripheral = {
let fc7 = hal.flexcomm.7.release();
hw::init_i2s(fc7.0, fc7.2, &mut syscon)
};
#[cfg(feature = "usbhs")]
let (usb_speed, usb_peripheral) = (
UsbSpeed::High,
hal.usbhs.enabled_as_device(
&mut anactrl,
&mut pmc,
&mut syscon,
&mut usb_delay_timer,
clocks.support_usbhs_token().unwrap(),
),
);
#[cfg(feature = "usbfs")]
let (usb_speed, usb_peripheral) = (
UsbSpeed::Full,
hal.usbfs.enabled_as_device(
&mut anactrl,
&mut pmc,
&mut syscon,
clocks.support_usbfs_token().unwrap(),
),
);
let usb_bus = UsbBus::new(usb_peripheral, usb0_vbus_pin);
defmt::debug!("codec init");
wm8904::init_codec(&mut i2c_bus);
defmt::debug!("dma init");
let i2s_dma_addr = &i2s_peripheral.i2s.fifowr as *const _ as *mut u32;
let dma =
DmaRing::<N_SLOTS, BYTES_PER_SLOT>::new(hal.dma.release(), &mut syscon, i2s_dma_addr, 4)
.unwrap();
let dma_ref = DMA_RING.init(dma);
unsafe { DMA_RING_REF = Some(dma_ref) };
let mut audio = Audio {
i2s: i2s_peripheral,
dma: dma_ring(),
running: AtomicBool::new(false),
log_counter: 0,
};
defmt::debug!("usb init");
let config = UsbAudioClassConfig::new(usb_speed, FunctionCode::IoBox, &mut audio)
.with_output_config(TerminalConfig::builder().base_id(2).build());
let mut uac2 = config.build(&usb_bus).unwrap();
let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x1209, 0xcc1d))
.composite_with_iads()
.strings(&[StringDescriptors::default()
.manufacturer("Generic")
.product("usbd_uac2 device")
.serial_number("123456789")])
.unwrap()
.max_packet_size_0(64)
.unwrap()
.device_class(0xef)
.device_sub_class(0x02)
.device_protocol(0x01)
.build();
defmt::info!("main loop");
loop {
usb_dev.poll(&mut [&mut uac2]);
}
}
+110
View File
@@ -0,0 +1,110 @@
use cortex_m::prelude::{_embedded_hal_blocking_i2c_Write, _embedded_hal_blocking_i2c_WriteRead};
use defmt::warn;
use crate::{BYTES_PER_FRAME, CODEC_I2C_ADDR, MCLK_FREQ, SAMPLE_RATE};
// copied from NXP SDK WM8904_Init
pub(crate) fn init_codec<T>(i2c: &mut T)
where
T: _embedded_hal_blocking_i2c_WriteRead + _embedded_hal_blocking_i2c_Write,
{
let mut buf = [0u8; 2];
match i2c.write_read(CODEC_I2C_ADDR, &[0], &mut buf) {
Ok(_) => {
let chip_id = ((buf[0] as u16) << 8) | buf[1] as u16;
defmt::info!("Read chip ID: {:x}", chip_id)
}
Err(_) => defmt::error!("Error reading I2C"),
}
i2c.write(CODEC_I2C_ADDR, &[0x16, 0x00, 0x0f]).ok(); // clock rates 2 = OPCLK_ENA | CLK_SYS_ENA | CLK_DSP_ENA | TOCLK_ENA
i2c.write(CODEC_I2C_ADDR, &[0x6c, 0x01, 0x00]).ok(); // write sequencer 0 ENA
i2c.write(CODEC_I2C_ADDR, &[0x6f, 0x01, 0x00]).ok(); // write sequencer 3 START, INDEX=0
// wait on write sequencer
defmt::debug!("[codec] waiting on write seq");
loop {
let mut buf = [0; 2];
i2c.write_read(CODEC_I2C_ADDR, &[0x70], &mut buf).ok();
if buf[1] & 1 == 0 {
break;
}
}
defmt::debug!("[codec] write seq done");
i2c.write(CODEC_I2C_ADDR, &[0x14, 0x00, 0x00]).ok(); // clock rates 0
i2c.write(CODEC_I2C_ADDR, &[0x0c, 0x00, 0x00]).ok(); // power management 0 = IN PGAs disabled
i2c.write(CODEC_I2C_ADDR, &[0x0e, 0x00, 0x03]).ok(); // power management 2 = HPL_PGA_ENA | HPR_PGA_ENA
i2c.write(CODEC_I2C_ADDR, &[0x0f, 0x00, 0x00]).ok(); // power management 3 = line outs disabled
i2c.write(CODEC_I2C_ADDR, &[0x12, 0x00, 0x0c]).ok(); // power management 6 = DACL_ENA | DACR_ENA
i2c.write(CODEC_I2C_ADDR, &[0x0a, 0x00, 0x00]).ok(); // analog adc 0 = ADC_OSR128
i2c.write(CODEC_I2C_ADDR, &[0x18, 0x00, 0x50]).ok(); // audio if 0 = AIFADCR_SRC | AIFDACR_SRC
i2c.write(CODEC_I2C_ADDR, &[0x21, 0x00, 0x40]).ok(); // dac digital 1 = DAC_OSR128
i2c.write(CODEC_I2C_ADDR, &[0x2c, 0x00, 0x05]).ok(); // analog lin 0 = 0dB (unmute)
i2c.write(CODEC_I2C_ADDR, &[0x2d, 0x00, 0x05]).ok(); // analog rin 0 = 0dB (unmute)
i2c.write(CODEC_I2C_ADDR, &[0x39, 0x00, 0x39]).ok(); // analog out1 left = vol=0dB
i2c.write(CODEC_I2C_ADDR, &[0x3a, 0x00, 0x39]).ok(); // analog out1 right = vol=0dB
i2c.write(CODEC_I2C_ADDR, &[0x3b, 0x00, 0x39]).ok(); // analog out2 left = vol=0dB
i2c.write(CODEC_I2C_ADDR, &[0x3c, 0x00, 0x39]).ok(); // analog out2 right = vol=0dB
i2c.write(CODEC_I2C_ADDR, &[0x43, 0x00, 0x03]).ok(); // dc server 0 = HPOUTL_ENA | HPOUTR_ENA
i2c.write(CODEC_I2C_ADDR, &[0x5a, 0x00, 0xff]).ok(); // analog hp 0 = remove all shorts etc
i2c.write(CODEC_I2C_ADDR, &[0x5e, 0x00, 0xff]).ok(); // analog lineout 0 = remove all shorts etc
i2c.write(CODEC_I2C_ADDR, &[0x68, 0x00, 0x01]).ok(); // enable class w charge pump
i2c.write(CODEC_I2C_ADDR, &[0x62, 0x00, 0x01]).ok(); // enable charge pump
let aif_wl = match BYTES_PER_FRAME {
4 => 0, // 16 bits per sample
8 => 3, // 32 bits per sample
_ => {
warn!("only handling 16 or 32 bit samples for now");
8
}
};
i2c.write(CODEC_I2C_ADDR, &[0x19, 0x00, (aif_wl << 2) | 2])
.ok(); // audio if 1 = i2s, aif_wl
// Calculate sysclk vs. fs ratio. SYSCLK = MCLK
let fs_ratio = MCLK_FREQ / SAMPLE_RATE;
if !MCLK_FREQ.is_multiple_of(SAMPLE_RATE) {
warn!("sample rate should be a multiple of mclk")
}
let clk_sys_rate: u16 = match fs_ratio {
64 => 0,
128 => 1,
192 => 2,
256 => 3,
384 => 4,
512 => 5,
768 => 6,
1024 => 7,
1408 => 8,
1536 => 9,
_ => {
warn!("unsupport ratio {}", fs_ratio);
0
}
};
let sample_rate: u16 = match SAMPLE_RATE {
r if r < 11025 => 0, // 0-11024
r if r < 16000 => 1, // 11025 - 15999
r if r < 22050 => 2, // 16000 - 22049
r if r < 32000 => 3, // 22050 - 31999
r if r < 44100 => 4, // 32000 - 44099
_ => 5, // 44100+
};
let clock_rates_1 = ((clk_sys_rate << 10) | sample_rate).to_be_bytes();
i2c.write(CODEC_I2C_ADDR, &[0x15, clock_rates_1[0], clock_rates_1[1]])
.ok(); // sys clock rate 512fs, sample rate 48
i2c.write(CODEC_I2C_ADDR, &[0x16, 0x00, 0x0f]).ok(); // clock rates 2 = CLK_SYS_ENA
// Calculate bclk_div
let bits_per_frame = BYTES_PER_FRAME * 8;
let bits_per_second = bits_per_frame as u32 * SAMPLE_RATE;
let bclk_div = MCLK_FREQ / bits_per_second;
i2c.write(CODEC_I2C_ADDR, &[0x1a, 0x00, bclk_div as u8])
.ok(); // audio interface 2 = no gpio, bclk_div
i2c.write(CODEC_I2C_ADDR, &[0x1b, 0x00, 0x00]).ok(); // audio interface 3 = input lrclock
i2c.write(CODEC_I2C_ADDR, &[0x3d, 0x00, 0x00]).ok(); // analog out12 zc = play source = dac
i2c.write(CODEC_I2C_ADDR, &[0x1e, 0x01, 0xff]).ok(); // dac vol left = update left/right = 0dB
}
+1 -1
View File
@@ -16,4 +16,4 @@ rustflags = [
target = "thumbv8m.main-none-eabihf" target = "thumbv8m.main-none-eabihf"
[env] [env]
DEFMT_LOG = "debug" DEFMT_LOG = "off"
+325 -26
View File
@@ -2,6 +2,15 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 4 version = 4
[[package]]
name = "aho-corasick"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "atomic-polyfill" name = "atomic-polyfill"
version = "1.0.3" version = "1.0.3"
@@ -26,6 +35,17 @@ dependencies = [
"rustc_version 0.2.3", "rustc_version 0.2.3",
] ]
[[package]]
name = "bbqueue"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68917624e17aad88607cb5a5936f6da9b607c48c711e4e9ed101e7189aed28c2"
dependencies = [
"const-init",
"critical-section",
"maitake-sync",
]
[[package]] [[package]]
name = "bitfield" name = "bitfield"
version = "0.13.2" version = "0.13.2"
@@ -63,6 +83,22 @@ dependencies = [
"embedded-io", "embedded-io",
] ]
[[package]]
name = "cc"
version = "1.2.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]] [[package]]
name = "cipher" name = "cipher"
version = "0.4.4" version = "0.4.4"
@@ -73,6 +109,22 @@ dependencies = [
"inout", "inout",
] ]
[[package]]
name = "const-init"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bd422bfb4f24a97243f60b6a4443e63d810c925d8da4bb2d8fde26a7c1d57ec"
[[package]]
name = "cordyceps"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "688d7fbb8092b8de775ef2536f36c8c31f2bc4006ece2e8d8ad2d17d00ce0a2a"
dependencies = [
"loom",
"tracing",
]
[[package]] [[package]]
name = "cortex-m" name = "cortex-m"
version = "0.7.7" version = "0.7.7"
@@ -214,6 +266,27 @@ dependencies = [
"num", "num",
] ]
[[package]]
name = "find-msvc-tools"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "generator"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52f04ae4152da20c76fe800fa48659201d5cf627c5149ca0b707b69d7eef6cf9"
dependencies = [
"cc",
"cfg-if",
"libc",
"log",
"rustversion",
"windows-link",
"windows-result",
]
[[package]] [[package]]
name = "generic-array" name = "generic-array"
version = "0.14.7" version = "0.14.7"
@@ -275,16 +348,6 @@ dependencies = [
"stable_deref_trait", "stable_deref_trait",
] ]
[[package]]
name = "heapless"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25ba4bd83f9415b58b4ed8dc5714c76e626a105be4646c02630ad730ad3b5aa4"
dependencies = [
"hash32 0.3.1",
"stable_deref_trait",
]
[[package]] [[package]]
name = "inout" name = "inout"
version = "0.1.4" version = "0.1.4"
@@ -294,6 +357,18 @@ dependencies = [
"generic-array 0.14.7", "generic-array 0.14.7",
] ]
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.186"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
version = "0.4.14" version = "0.4.14"
@@ -320,9 +395,23 @@ dependencies = [
"log", "log",
] ]
[[package]]
name = "loom"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca"
dependencies = [
"cfg-if",
"generator",
"scoped-tls",
"tracing",
"tracing-subscriber",
]
[[package]] [[package]]
name = "lpc55-hal" name = "lpc55-hal"
version = "0.5.0" version = "0.5.0"
source = "git+https://github.com/ktims/lpc55-hal?branch=main#8dfefd62aff4abd2de535f23107812dda68437be"
dependencies = [ dependencies = [
"block-buffer", "block-buffer",
"cipher", "cipher",
@@ -354,23 +443,51 @@ dependencies = [
name = "lpc55s28-evk" name = "lpc55s28-evk"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bbqueue",
"cortex-m", "cortex-m",
"cortex-m-rt", "cortex-m-rt",
"defmt 1.0.1", "defmt 1.0.1",
"defmt-rtt", "defmt-rtt",
"embedded-hal 1.0.0", "embedded-hal 1.0.0",
"embedded-io", "embedded-io",
"heapless 0.9.3",
"log-to-defmt", "log-to-defmt",
"lpc55-hal", "lpc55-hal",
"nb 1.1.0",
"panic-halt",
"panic-probe", "panic-probe",
"static_cell",
"usb-device", "usb-device",
"usbd-uac2", "usbd-uac2",
] ]
[[package]]
name = "maitake-sync"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d77c365d697828821727b9bc09e6bc3c518b8c63804e79e1be5a5ae091a7c5f"
dependencies = [
"cordyceps",
"critical-section",
"loom",
"mutex-traits",
"mycelium-bitfield",
"pin-project",
"portable-atomic",
"tracing",
]
[[package]]
name = "matchers"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
dependencies = [
"regex-automata",
]
[[package]]
name = "memchr"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]] [[package]]
name = "modular-bitfield" name = "modular-bitfield"
version = "0.13.1" version = "0.13.1"
@@ -392,6 +509,18 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "mutex-traits"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3929f2b5633d29cf7b6624992e5f3c1e9334f1193423e12d17be4faf678cde3f"
[[package]]
name = "mycelium-bitfield"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24e0cc5e2c585acbd15c5ce911dff71e1f4d5313f43345873311c4f5efd741cc"
[[package]] [[package]]
name = "nb" name = "nb"
version = "0.1.3" version = "0.1.3"
@@ -407,6 +536,15 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d"
[[package]]
name = "nu-ansi-term"
version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [
"windows-sys",
]
[[package]] [[package]]
name = "num" name = "num"
version = "0.3.1" version = "0.3.1"
@@ -470,10 +608,10 @@ dependencies = [
] ]
[[package]] [[package]]
name = "panic-halt" name = "once_cell"
version = "1.0.0" version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a513e167849a384b7f9b746e517604398518590a9142f4846a32e3c2a4de7b11" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]] [[package]]
name = "panic-probe" name = "panic-probe"
@@ -485,6 +623,32 @@ dependencies = [
"defmt 1.0.1", "defmt 1.0.1",
] ]
[[package]]
name = "pin-project"
version = "1.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbf0d9e68100b3a7989b4901972f265cd542e560a3a8a724e1e20322f4d06ce9"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a990e22f43e84855daf260dded30524ef4a9021cc7541c26540500a50b624389"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "pin-project-lite"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]] [[package]]
name = "portable-atomic" name = "portable-atomic"
version = "1.13.1" version = "1.13.1"
@@ -537,6 +701,23 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
[[package]]
name = "regex-automata"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
[[package]] [[package]]
name = "rustc_version" name = "rustc_version"
version = "0.2.3" version = "0.2.3"
@@ -561,6 +742,12 @@ version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "scoped-tls"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
[[package]] [[package]]
name = "scopeguard" name = "scopeguard"
version = "1.2.0" version = "1.2.0"
@@ -588,6 +775,27 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "sharded-slab"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
dependencies = [
"lazy_static",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]] [[package]]
name = "spin" name = "spin"
version = "0.9.8" version = "0.9.8"
@@ -609,15 +817,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "static_cell"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0530892bb4fa575ee0da4b86f86c667132a94b74bb72160f58ee5a4afec74c23"
dependencies = [
"portable-atomic",
]
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.117" version = "2.0.117"
@@ -649,6 +848,76 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "thread_local"
version = "1.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
dependencies = [
"cfg-if",
]
[[package]]
name = "tracing"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
dependencies = [
"pin-project-lite",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-attributes"
version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tracing-core"
version = "0.1.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
dependencies = [
"once_cell",
"valuable",
]
[[package]]
name = "tracing-log"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
dependencies = [
"log",
"once_cell",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319"
dependencies = [
"matchers",
"nu-ansi-term",
"once_cell",
"regex-automata",
"sharded-slab",
"smallvec",
"thread_local",
"tracing",
"tracing-core",
"tracing-log",
]
[[package]] [[package]]
name = "typenum" name = "typenum"
version = "1.20.0" version = "1.20.0"
@@ -684,6 +953,12 @@ dependencies = [
"usb-device", "usb-device",
] ]
[[package]]
name = "valuable"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[package]] [[package]]
name = "vcell" name = "vcell"
version = "0.1.3" version = "0.1.3"
@@ -710,3 +985,27 @@ checksum = "de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc"
dependencies = [ dependencies = [
"vcell", "vcell",
] ]
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-result"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
+11 -7
View File
@@ -2,23 +2,27 @@
name = "lpc55s28-evk" name = "lpc55s28-evk"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
publish = false
[features]
default = ["usbhs"]
usbfs = []
usbhs = []
[dependencies] [dependencies]
bbqueue = "0.7.0"
cortex-m = { version = "0.7.7", features = ["critical-section-single-core"] } cortex-m = { version = "0.7.7", features = ["critical-section-single-core"] }
cortex-m-rt = "0.7.5" cortex-m-rt = "0.7.5"
defmt = "1.0.1" defmt = "1.0.1"
defmt-rtt = "1.1.0" defmt-rtt = "1.1.0"
embedded-hal = "1.0.0" embedded-hal = "1.0.0"
embedded-io = "0.7.1" embedded-io = "0.7.1"
heapless = "0.9.3"
log-to-defmt = "0.1.0" log-to-defmt = "0.1.0"
lpc55-hal = { version = "0.5.0", path = "../lpc55-hal" }
nb = "1.1.0"
panic-halt = "1.0.0"
panic-probe = { version = "1.0.0", features = ["print-defmt"] } panic-probe = { version = "1.0.0", features = ["print-defmt"] }
static_cell = "2.1.1" # Includes update to usb-device 0.3, fix for isochronous and smaller critical sections
usb-device = "0.3" lpc55-hal = { git = "https://github.com/ktims/lpc55-hal", branch = "main" }
usbd-uac2 = { version = "0.1.0", path = "../..", features = ["defmt"]} usb-device.workspace = true
usbd-uac2 = { workspace = true, features = ["defmt"] }
[profile.release] [profile.release]
opt-level = "z" opt-level = "z"
+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);
}
+308
View File
@@ -0,0 +1,308 @@
//! Contains hardware setup unrelated to Usb Audio Class implementation
use crate::Syscon;
use crate::hal;
use crate::{MCLK_FREQ, SAMPLE_RATE, pac};
use core::cell::UnsafeCell;
use core::mem::MaybeUninit;
use defmt::debug;
use hal::{
Enabled, Iocon, Pin,
drivers::pins,
traits::wg::digital::v2::{OutputPin, ToggleableOutputPin},
typestates::pin::{gpio::direction::Output, state::Gpio},
};
pub(crate) struct PllConstants {
pub m: u16, // 1-65535
pub n: u8, // 1-255
pub p: u8, // 1-31
pub selp: u8, // 5 bits
pub seli: u8, // 6 bits
}
impl PllConstants {
pub(crate) const fn new(n: u8, m: u16, p: u8) -> Self {
assert!(n != 0, "1 <= N <= 255");
assert!(m != 0, "1 <= M <= 65535");
assert!(p != 0 && p <= 31, "1 <= P <= 31");
// Following ripped from lpc55-hal and made const
// UM 4.6.6.3.2
let selp = {
let v = (m >> 2) + 1;
if v < 31 { v } else { 31 }
} as u8;
let seli = {
let v = match m {
m if m >= 8000 => 1,
m if m >= 122 => 8000 / m,
_ => 2 * (m >> 2) + 3,
};
if v < 63 { v } else { 63 }
} as u8;
// let seli = min(2*(m >> 2) + 3, 63);
Self {
n,
m,
p,
selp,
seli,
}
}
}
impl defmt::Format for PllConstants {
fn format(&self, fmt: defmt::Formatter) {
let factor = f32::from(self.m) / (f32::from(self.n) * 2.0 * f32::from(self.p));
defmt::write!(
fmt,
"m: {} n: {} p: {} selp: {} seli: {} fout: fin * {}",
self.m,
self.n,
self.p,
self.selp,
self.seli,
factor
);
}
}
// Fo = M/(N*2*P) * Fin
// Fo = 3072/(125*2*8) * 16MHz = 24.576MHz
const AUDIO_PLL: PllConstants = PllConstants::new(125, 3072, 8);
// Set PLL0 to 24.576MHz, start, and wait for lock
// This is not exposed by lpc55-hal, unfortunately. Copy their implementation here.
pub(crate) fn init_audio_pll() {
let syscon = unsafe { &*pac::SYSCON::ptr() };
let pmc = unsafe { &*pac::PMC::ptr() };
let anactrl = unsafe { &*pac::ANACTRL::ptr() };
debug!("start clk_in");
pmc.pdruncfg0
.modify(|_, w| w.pden_xtal32m().poweredon().pden_ldoxo32m().poweredon());
syscon.clock_ctrl.modify(|_, w| w.clkin_ena().enable());
anactrl
.xo32m_ctrl
.modify(|_, w| w.enable_system_clk_out().enable());
debug!("init pll0: {}", AUDIO_PLL);
pmc.pdruncfg0
.modify(|_, w| w.pden_pll0().poweredoff().pden_pll0_sscg().poweredoff());
syscon.pll0clksel.write(|w| w.sel().enum_0x1()); // clk_in
syscon.pll0ctrl.write(|w| unsafe {
w.clken()
.enable()
.seli()
.bits(AUDIO_PLL.seli)
.selp()
.bits(AUDIO_PLL.selp)
});
syscon
.pll0ndec
.write(|w| unsafe { w.ndiv().bits(AUDIO_PLL.n) });
syscon.pll0ndec.write(|w| unsafe {
w.ndiv().bits(AUDIO_PLL.n).nreq().set_bit() // latch
});
syscon
.pll0pdec
.write(|w| unsafe { w.pdiv().bits(AUDIO_PLL.p) });
syscon.pll0pdec.write(|w| unsafe {
w.pdiv().bits(AUDIO_PLL.p).preq().set_bit() // latch
});
syscon.pll0sscg0.write(|w| unsafe { w.md_lbs().bits(0) });
syscon
.pll0sscg1
.write(|w| unsafe { w.mdiv_ext().bits(AUDIO_PLL.m).sel_ext().set_bit() });
syscon.pll0sscg1.write(|w| unsafe {
w.mdiv_ext()
.bits(AUDIO_PLL.m)
.sel_ext()
.set_bit()
.mreq()
.set_bit() // latch
.md_req()
.set_bit() // latch
});
pmc.pdruncfg0
.modify(|_, w| w.pden_pll0().poweredon().pden_pll0_sscg().poweredon());
debug!("pll0 wait for lock");
let mut i = 0usize;
while syscon.pll0stat.read().lock().bit_is_clear() {
i += 1;
}
debug!("pll0 locked after {} tries", i);
}
pub struct I2sTx {
pub i2s: pac::I2S7,
}
pub fn init_i2s(mut fc7: pac::FLEXCOMM7, i2s7: pac::I2S7, syscon: &mut Syscon) -> I2sTx {
defmt::debug!("init i2s");
// Enable BOTH
syscon.reset(&mut fc7);
syscon.enable_clock(&mut fc7);
unsafe {
pac::IOCON::ptr().as_ref().unwrap().pio1_31.modify(|_, w| {
w.func()
.alt1()
.mode()
.inactive()
.slew()
.fast()
.invert()
.disabled()
.digimode()
.digital()
.od()
.normal()
});
pac::SYSCON::ptr()
.as_ref()
.unwrap()
.fcclksel7()
.modify(|_, w| w.sel().enum_0x5()); // MCLK
pac::SYSCON::ptr()
.as_ref()
.unwrap()
.mclkclksel
.modify(|_, w| w.sel().enum_0x1()); // PLL0
pac::SYSCON::ptr()
.as_ref()
.unwrap()
.mclkdiv
.modify(|_, w| w.div().bits(1).halt().run().reset().released()); // div by 2 = PLL0 fout / 2 = 12.288MHz, max for WM8904 @ 96k
pac::SYSCON::ptr()
.as_ref()
.unwrap()
.mclkio
.modify(|_, w| w.mclkio().output());
};
// Select I2S TX function
fc7.pselid.write(|w| w.persel().i2s_transmit());
let regs = i2s7;
// Enable TX FIFO only
regs.fifocfg.modify(|_, w| {
w.enabletx()
.enabled()
.enablerx()
.disabled()
.dmatx()
.disabled()
.txi2se0()
.zero()
});
// Flush
regs.fifocfg.modify(|_, w| w.emptytx().set_bit());
regs.cfg2
.modify(|_, w| unsafe { w.position().bits(0).framelen().bits(63) }); // framelen = 64
let bclk_div = (MCLK_FREQ / SAMPLE_RATE / 64) as u16;
regs.div
.modify(|_, w| unsafe { w.div().bits(bclk_div - 1) }); // Clock source is MCLK (12.288MHz) / 4 = 3MHz
// Config
regs.cfg1.modify(|_, w| unsafe {
w.mstslvcfg()
.normal_master()
.onechannel()
.dual_channel()
.datalen()
.bits(31)
.mainenable()
.enabled()
.mode()
.classic_mode()
.datapause()
.normal()
});
I2sTx { i2s: regs }
}
pub struct SharedLed<T: OutputPin> {
inner: UnsafeCell<T>,
}
unsafe impl<T: OutputPin> Sync for SharedLed<T> {}
impl<T: OutputPin> SharedLed<T> {
pub fn new(inner: T) -> Self {
Self {
inner: UnsafeCell::new(inner),
}
}
pub fn on(&self) {
unsafe {
(*self.inner.get()).set_low().ok();
}
}
pub fn off(&self) {
unsafe {
(*self.inner.get()).set_high().ok();
}
}
}
impl<T: OutputPin + ToggleableOutputPin> SharedLed<T> {
pub fn toggle(&self) {
unsafe {
(*self.inner.get()).toggle().ok();
}
}
}
type RedLed = Pin<pins::Pio1_6, Gpio<Output>>;
type GreenLed = Pin<pins::Pio1_7, Gpio<Output>>;
type BlueLed = Pin<pins::Pio1_4, Gpio<Output>>;
pub static RED_LED: MaybeUninit<SharedLed<RedLed>> = MaybeUninit::uninit();
pub static GREEN_LED: MaybeUninit<SharedLed<GreenLed>> = MaybeUninit::uninit();
pub static BLUE_LED: MaybeUninit<SharedLed<BlueLed>> = MaybeUninit::uninit();
pub fn init_leds(iocon: &mut Iocon<Enabled>, gpio: &mut hal::Gpio<Enabled>) {
let red_led = SharedLed::new(
pins::Pio1_6::take()
.unwrap()
.into_gpio_pin(iocon, gpio)
.into_output_low(),
);
let green_led = SharedLed::new(
pins::Pio1_7::take()
.unwrap()
.into_gpio_pin(iocon, gpio)
.into_output_low(),
);
let blue_led = SharedLed::new(
pins::Pio1_4::take()
.unwrap()
.into_gpio_pin(iocon, gpio)
.into_output_low(),
);
unsafe {
core::ptr::write(RED_LED.as_ptr() as *mut SharedLed<RedLed>, red_led);
core::ptr::write(GREEN_LED.as_ptr() as *mut SharedLed<GreenLed>, green_led);
core::ptr::write(BLUE_LED.as_ptr() as *mut SharedLed<BlueLed>, blue_led);
}
}
pub fn red_led() -> &'static SharedLed<RedLed> {
unsafe { &*RED_LED.as_ptr() }
}
pub fn green_led() -> &'static SharedLed<GreenLed> {
unsafe { &*GREEN_LED.as_ptr() }
}
pub fn blue_led() -> &'static SharedLed<BlueLed> {
unsafe { &*BLUE_LED.as_ptr() }
}
+169 -481
View File
@@ -1,514 +1,235 @@
//! Interrupt driven example for the LPCXpresso55S28 demo board.
//!
//! This is a minimal implementation intended to demonstrate only the minimum
//! essential implementation, to clarify usage of the class driver.
//!
//! Uses the EVK's WM8904 DAC at 48KHz. Clock is generated by PLL0. Simple
//! proportional feedback is implemented.
//!
//! Packets from USB are placed in a `bbqueue`. 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)
#![no_main] #![no_main]
#![no_std] #![no_std]
#[cfg(all(feature = "usbfs", feature = "usbhs"))]
compile_error!("Choose one USB peripheral, usbfs and usbhs cannot be used together");
extern crate panic_probe; extern crate panic_probe;
#[defmt::panic_handler] #[defmt::panic_handler]
fn panic() -> ! { fn panic() -> ! {
panic_probe::hard_fault() panic_probe::hard_fault()
} }
use core::cell::UnsafeCell; use bbqueue::{
use core::ptr::null_mut; nicknames::Churrasco,
use core::sync::atomic::{AtomicBool, AtomicI32, AtomicUsize, Ordering}; prod_cons::stream::{StreamConsumer, StreamProducer},
traits::bbqhdl::BbqHandle,
};
use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use cortex_m_rt::entry; use cortex_m_rt::entry;
use defmt;
use defmt::debug; use defmt::debug;
use defmt_rtt as _; use defmt_rtt as _;
use hal::Syscon;
use hal::drivers::{Timer, UsbBus, pins};
use hal::prelude::*;
use hal::raw as pac; use hal::raw as pac;
use hal::time::Hertz; use hal::{
use heapless::spsc::Consumer; Syscon,
use lpc55_hal::{self as hal}; drivers::{Timer, UsbBus, pins},
prelude::*,
time::Hertz,
};
use lpc55_hal 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, UsbDeviceBuilder, 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, blue_led, green_led, red_led};
mod hw;
mod wm8904; mod wm8904;
struct PllConstants {
m: u16, // 1-65535
n: u8, // 1-255
p: u8, // 1-31
selp: u8, // 5 bits
seli: u8, // 6 bits
}
impl PllConstants {
const fn new(n: u8, m: u16, p: u8) -> Self {
assert!(n != 0, "1 <= N <= 255");
assert!(m != 0, "1 <= M <= 65535");
assert!(p != 0 && p <= 31, "1 <= P <= 31");
// Following ripped from lpc55-hal and made const
// UM 4.6.6.3.2
let selp = {
let v = (m >> 2) + 1;
if v < 31 { v } else { 31 }
} as u8;
let seli = {
let v = match m {
m if m >= 8000 => 1,
m if m >= 122 => 8000 / m,
_ => 2 * (m >> 2) + 3,
};
if v < 63 { v } else { 63 }
} as u8;
// let seli = min(2*(m >> 2) + 3, 63);
Self {
n,
m,
p,
selp,
seli,
}
}
}
impl defmt::Format for PllConstants {
fn format(&self, fmt: defmt::Formatter) {
let factor = f32::from(self.m) / (f32::from(self.n) * 2.0 * f32::from(self.p));
defmt::write!(
fmt,
"m: {} n: {} p: {} selp: {} seli: {} fout: fin * {}",
self.m,
self.n,
self.p,
self.selp,
self.seli,
factor
);
}
}
const CODEC_I2C_ADDR: u8 = 0b0011010; const CODEC_I2C_ADDR: u8 = 0b0011010;
// Fo = M/(N*2*P) * Fin const FIFO_LENGTH: usize = 256; // frames
// Fo = 3072/(125*2*8) * 16MHz = 24.576MHz const MCLK_FREQ: u32 = 12288000;
const AUDIO_PLL: PllConstants = PllConstants::new(125, 3072, 8);
const FIFO_LENGTH: usize = 2048; // frames
const MCLK_FREQ: u32 = 24576000;
const SAMPLE_RATE: u32 = 48000; const SAMPLE_RATE: u32 = 48000;
// USB microframe rate
#[cfg(feature = "usbhs")]
const USB_FRAME_RATE: usize = 8000;
#[cfg(feature = "usbfs")]
const USB_FRAME_RATE: usize = 1000;
type SampleType = (i32, i32); type SampleType = (i32, i32);
// const SINE_LUT: [i32; 32] = [ const BYTES_PER_FRAME: usize = 8;
// 0, 1636536, 3210180, 4660460, 5931640, 6974871, 7750062, 8227422, 8388607, 8227422, 7750062, const QUEUE_BYTES: usize = FIFO_LENGTH * BYTES_PER_FRAME;
// 6974871, 5931640, 4660460, 3210180, 1636536, 0, -1636536, -3210180, -4660460, -5931640, // We use bbqueue here for performance in the USB driver that runs almost entirely in interrupt free critical section.
// -6974871, -7750062, -8227422, -8388607, -8227422, -7750062, -6974871, -5931640, -4660460, static QUEUE: Churrasco<QUEUE_BYTES> = Churrasco::new();
// -3210180, -1636536, // Used for feedback calculation of current fifo state
// ]; static PRODUCED: AtomicU32 = AtomicU32::new(0);
static CONSUMED: AtomicU32 = AtomicU32::new(0);
// pub fn i2s_sine_test(i2s: &pac::I2S7) -> ! { #[inline]
// let mut idx = 0; fn try_write_one_frame<T: BbqHandle>(
// let mut count = 0usize; 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());
// defmt::debug!("starting sine test"); i2s.fifowr.write(|w| unsafe { w.bits(l) });
i2s.fifowr.write(|w| unsafe { w.bits(r) });
// loop { // consume exactly one frame (8 bytes)
// if i2s.fifostat.read().txnotfull().bit_is_set() { rgr.release(BYTES_PER_FRAME);
// let sample = SINE_LUT[idx] * 32; CONSUMED.fetch_add(BYTES_PER_FRAME as u32, Ordering::Relaxed);
return true;
// // ✅ Left channel } else {
// i2s.fifowr.write(|w| unsafe { w.bits(sample as u32) }); // Not enough bytes for a full frame: leave it in the queue.
return false;
// // wait for space if needed
// while !i2s.fifostat.read().txnotfull().bit_is_set() {}
// // ✅ Right channel
// i2s.fifowr.write(|w| unsafe { w.bits(sample as u32) });
// idx = (idx + 1) & (SINE_LUT.len() - 1);
// count += 1;
// if count.is_multiple_of(48000) {
// defmt::debug!("frames sent: {}", count)
// }
// }
// }
// }
struct Clock {}
impl Clock {
const RATES: [RangeEntry<u32>; 1] = [RangeEntry::new_fixed(48000)];
}
impl UsbAudioClockImpl for Clock {
const CLOCK_TYPE: usbd_uac2::descriptors::ClockType = ClockType::InternalFixed;
const SOF_SYNC: bool = false;
fn get_sample_rate(&self) -> core::result::Result<u32, usbd_uac2::UsbAudioClassError> {
Ok(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)
} }
} }
false
#[derive(Default)]
struct PerfCounters {
frames: AtomicUsize,
avg_fill: AtomicI32, // i32 to simplify averaging
queue_underflows: AtomicUsize,
queue_overflows: AtomicUsize,
audio_underflows: AtomicUsize,
} }
impl defmt::Format for PerfCounters {
fn format(&self, fmt: defmt::Formatter) {
defmt::write!(
fmt,
"frames: {} avg fill: {} a_underflows: {} q_underflows: {} q_overflows: {}",
self.frames.load(Ordering::Relaxed),
self.avg_fill.load(Ordering::Relaxed),
self.audio_underflows.load(Ordering::Relaxed),
self.queue_underflows.load(Ordering::Relaxed),
self.queue_overflows.load(Ordering::Relaxed)
)
}
}
static FIFO_CONSUMER_STORE: StaticCell<Consumer<SampleType>> = StaticCell::new();
static mut FIFO_CONSUMER: *mut Consumer<SampleType> = null_mut();
#[interrupt] #[interrupt]
fn FLEXCOMM7() { fn FLEXCOMM7() {
let i2s = unsafe { &*pac::I2S7::ptr() }; let i2s = unsafe { &*pac::I2S7::ptr() };
// refill until fifo has >6 words
// refil the buffer to 4 frames / 8 samples let mut cons = QUEUE.stream_consumer();
while i2s.fifostat.read().txlvl().bits() <= 6 { while i2s.fifostat.read().txlvl().bits() <= 6 {
let fifo = unsafe { &mut *FIFO_CONSUMER }; if !try_write_one_frame(&mut cons, i2s) {
if let Some((l, r)) = fifo.dequeue() { // No complete frame available: write silence to keep FIFO above threshold
i2s.fifowr.write(|w| unsafe { w.bits(l as u32) }); defmt::error!("underflow");
i2s.fifowr.write(|w| unsafe { w.bits(r as u32) }); red_led().toggle();
} else {
i2s.fifowr.write(|w| unsafe { w.bits(0) }); i2s.fifowr.write(|w| unsafe { w.bits(0) });
i2s.fifowr.write(|w| unsafe { w.bits(0) }); i2s.fifowr.write(|w| unsafe { w.bits(0) });
break;
} }
} }
} }
struct Audio<'a> { struct Audio<T: BbqHandle> {
running: AtomicBool, running: AtomicBool,
i2s: I2sTx, i2s: I2sTx,
producer: UnsafeCell<heapless::spsc::Producer<'a, SampleType>>, producer: StreamProducer<T>,
perf: PerfCounters,
integrator: AtomicI32,
} }
impl<'a> Audio<'a> { impl<T: BbqHandle> Audio<T> {
// fn poll(&self) { const RATES: [RangeEntry<u32>; 1] = [RangeEntry::new_fixed(SAMPLE_RATE)];
// if !self.running.load(Ordering::Relaxed) {
// return;
// }
// if self.i2s.i2s.fifostat.read().txerr().bit_is_set() {
// self.i2s.i2s.fifostat.modify(|_, w| w.txerr().set_bit());
// // defmt::error!("fifo tx error, txlvl: {}", stat.txlvl().bits());
// }
// if self.i2s.i2s.fifostat.read().txlvl().bits() == 0 {
// self.perf.audio_underflows.fetch_add(1, Ordering::Relaxed);
// }
// while self.i2s.i2s.fifostat.read().txlvl().bits() <= 6 {
// // fifo is 8 deep at 32 bit samples
// let fifo = self.consumer.get();
// if let Some(sample) = unsafe { (*fifo).dequeue() } {
// self.i2s
// .i2s
// .fifowr
// .write(|w| unsafe { w.bits(sample.0 as u32) });
// self.i2s
// .i2s
// .fifowr
// .write(|w| unsafe { w.bits(sample.1 as u32) });
// } else {
// if self.running.load(Ordering::Relaxed) {
// self.perf.queue_underflows.fetch_add(1, Ordering::Relaxed);
// }
// break;
// // self.i2s.i2s.fifowr.write(|w| unsafe { w.bits(0 as u32) });
// // self.i2s.i2s.fifowr.write(|w| unsafe { w.bits(0 as u32) });
// }
// self.perf.frames.fetch_add(1, Ordering::Relaxed);
// }
// }
fn start(&self) { fn start(&self) {
self.running.store(true, Ordering::Relaxed); self.running.store(true, Ordering::Relaxed);
defmt::info!("playback starting, enabling interrupts"); defmt::info!("playback starting, enabling interrupts");
self.i2s.i2s.fifointenclr.write(|w| w.txlvl().set_bit()); self.i2s.i2s.fifointenclr.write(|w| w.txlvl().set_bit());
// FIFO threshold trigger enable // FIFO trigger threshold = <= 6 entries
self.i2s self.i2s
.i2s .i2s
.fifotrig .fifotrig
.modify(|_, w| unsafe { w.txlvl().bits(4).txlvlena().enabled() }); .modify(|_, w| unsafe { w.txlvl().bits(6).txlvlena().enabled() });
// FIFO level interrupt enable // FIFO level interrupt enable
self.i2s.i2s.fifointenset.modify(|_, w| w.txlvl().enabled()); self.i2s.i2s.fifointenset.modify(|_, w| w.txlvl().enabled());
unsafe { pac::NVIC::unmask(pac::Interrupt::FLEXCOMM7) }; unsafe { pac::NVIC::unmask(pac::Interrupt::FLEXCOMM7) };
green_led().on();
} }
fn stop(&self) { 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); self.running.store(true, Ordering::Relaxed);
defmt::info!("playback stopped: {}", self.perf); defmt::info!("playback stopped");
pac::NVIC::mask(pac::Interrupt::FLEXCOMM7); pac::NVIC::mask(pac::Interrupt::FLEXCOMM7);
green_led().off();
} }
} }
impl<'a, B: bus::UsbBus> UsbAudioClass<'a, B> for Audio<'_> {
fn alternate_setting_changed<CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>>( impl<T: BbqHandle> ClockSource for Audio<T> {
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, &self,
ac: &mut usbd_uac2::AudioClass<'a, B, CS, AU>, ) -> core::result::Result<&[usbd_uac2::RangeEntry<u32>], usbd_uac2::UsbAudioClassError> {
terminal: usb_device::UsbDirection, Ok(&Self::RATES)
alt_setting: u8, }
) { fn clock_validity(&self) -> core::result::Result<bool, usbd_uac2::UsbAudioClassError> {
Ok(true)
}
}
impl<T: BbqHandle, B: bus::UsbBus> AudioHandler<'_, B> for Audio<T> {
fn alternate_setting_changed(&mut self, _terminal: usb_device::UsbDirection, alt_setting: u8) {
// alt setting 0 means stopped
match alt_setting { match alt_setting {
0 => self.stop(), 0 => self.stop(),
1 => self.start(), 1 => self.start(),
_ => defmt::error!("unexpected alt setting {}", alt_setting), _ => defmt::error!("unexpected alt setting {}", alt_setting),
} }
} }
fn audio_data_rx(&self, ep: &usb_device::endpoint::Endpoint<'a, B, usb_device::endpoint::Out>) { fn audio_data_rx(
let mut buf = [0; 384]; &mut self,
ep: &usb_device::endpoint::Endpoint<'_, B, usb_device::endpoint::Out>,
) {
// Buffer must fit one microframe's (125us @ HS, 1ms @ FS) of audio data
// (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) * core::mem::size_of::<SampleType>()];
let len = match ep.read(&mut buf) { let len = match ep.read(&mut buf) {
Ok(len) => len, Ok(len) => len,
Err(e) => { Err(_) => {
defmt::error!("usb error in rx callback"); defmt::error!("usb error in rx callback");
return; return;
} }
}; };
let buf = &buf[..len]; let buf = &buf[..len];
for sample in buf.chunks_exact(8).map(|b| {
( if let Ok(mut wg) = self.producer.grant_exact(buf.len()) {
i32::from_le_bytes(b[..4].try_into().unwrap()), wg.copy_from_slice(buf);
i32::from_le_bytes(b[4..].try_into().unwrap()), wg.commit(buf.len());
) PRODUCED.fetch_add(buf.len() as u32, Ordering::Relaxed);
}) { } else {
if let Err(e) = unsafe { (*self.producer.get()).enqueue(sample) } { blue_led().on();
self.perf.queue_overflows.fetch_add(1, Ordering::Relaxed); defmt::error!("overflowed bbq, asked {}", buf.len());
// defmt::error!("overflowed fifo, len: {}", unsafe {
// (*self.producer.get()).len()
// });
} }
} }
}
fn feedback(&self) -> Option<UsbIsochronousFeedback> {
const TARGET: i32 = FIFO_LENGTH as i32 / 2 - 64;
const NOMINAL: i32 = 48 << 16;
let queuelen = unsafe { (*self.producer.get()).len() as i32 }; /// Provide rate feedback to the host, so that it doesn't over- or underflow
let error = (queuelen - TARGET).clamp(-32, 32); /// our queue. This implementation is not tuned.
fn feedback(&mut self, nominal_rate: UsbIsochronousFeedback) -> Option<UsbIsochronousFeedback> {
let target = FIFO_LENGTH as i32 / 2;
// --- integrator --- let fill = PRODUCED
let scaled_error = error / 64; .load(Ordering::Acquire)
.wrapping_sub(CONSUMED.load(Ordering::Acquire)) as i32
/ BYTES_PER_FRAME as i32;
let new_i = self.integrator.fetch_add(scaled_error, Ordering::Relaxed) + scaled_error; let error = fill - target;
let clamped = new_i.clamp(-131072, 131072); // Clamp excursions.
let (i, _f) = nominal_rate.parts();
let error = error.clamp(-(i as i32 * 4), i as i32 * 4);
// leak + store final value let p = error << 8;
let leaked = clamped - (clamped >> 8);
self.integrator.store(leaked, Ordering::Relaxed);
// reset on large deviation let correction = -p;
if error.abs() > 96 { let nominal_v = nominal_rate.to_u32_12_13() as i32;
self.integrator.store(0, Ordering::Relaxed);
}
// --- gains --- let mut v = nominal_v + correction;
let p = error / 128;
let i = leaked / 32768;
// correction // Clamp corrections
let correction = (-(p + i)).clamp(-32, 32); v = v.clamp(nominal_v - (1 << 14), nominal_v + (1 << 14));
let v = NOMINAL + (correction << 10);
// EMA (unchanged, already correct) // Note: this log will cause continuous underflows
let ema = self.perf.avg_fill.load(Ordering::Relaxed); defmt::debug!("fill:{} err:{} fb:{=u32:x}", fill, error, v as u32);
let new = ((ema * 1023) + queuelen + 512) >> 10;
self.perf.avg_fill.store(new, Ordering::Relaxed);
defmt::debug!(
"q:{} p:{} i:{} err:{} fb:{}+{}",
queuelen,
p,
i,
error,
NOMINAL >> 16,
correction
);
Some(UsbIsochronousFeedback::new(v as u32)) Some(UsbIsochronousFeedback::new(v as u32))
} }
} }
// Set PLL0 to 24.576MHz, start, and wait for lock
// This is not exposed by lpc55-hal, unfortunately. Copy their implementation here.
fn init_audio_pll() {
let syscon = unsafe { &*pac::SYSCON::ptr() };
let pmc = unsafe { &*pac::PMC::ptr() };
let anactrl = unsafe { &*pac::ANACTRL::ptr() };
debug!("start clk_in");
pmc.pdruncfg0
.modify(|_, w| w.pden_xtal32m().poweredon().pden_ldoxo32m().poweredon());
syscon.clock_ctrl.modify(|_, w| w.clkin_ena().enable());
anactrl
.xo32m_ctrl
.modify(|_, w| w.enable_system_clk_out().enable());
debug!("init pll: {}", AUDIO_PLL);
pmc.pdruncfg0
.modify(|_, w| w.pden_pll0().poweredoff().pden_pll0_sscg().poweredoff());
syscon.pll0clksel.write(|w| w.sel().enum_0x1()); // clk_in
syscon.pll0ctrl.write(|w| unsafe {
w.clken()
.enable()
.seli()
.bits(AUDIO_PLL.seli)
.selp()
.bits(AUDIO_PLL.selp)
});
syscon
.pll0ndec
.write(|w| unsafe { w.ndiv().bits(AUDIO_PLL.n) });
syscon.pll0ndec.write(|w| unsafe {
w.ndiv().bits(AUDIO_PLL.n).nreq().set_bit() // latch
});
syscon
.pll0pdec
.write(|w| unsafe { w.pdiv().bits(AUDIO_PLL.p) });
syscon.pll0pdec.write(|w| unsafe {
w.pdiv().bits(AUDIO_PLL.p).preq().set_bit() // latch
});
syscon.pll0sscg0.write(|w| unsafe { w.md_lbs().bits(0) });
syscon
.pll0sscg1
.write(|w| unsafe { w.mdiv_ext().bits(AUDIO_PLL.m).sel_ext().set_bit() });
syscon.pll0sscg1.write(|w| unsafe {
w.mdiv_ext()
.bits(AUDIO_PLL.m)
.sel_ext()
.set_bit()
.mreq()
.set_bit() // latch
.md_req()
.set_bit() // latch
});
pmc.pdruncfg0
.modify(|_, w| w.pden_pll0().poweredon().pden_pll0_sscg().poweredon());
debug!("pll0 wait for lock");
let mut i = 0usize;
while syscon.pll0stat.read().lock().bit_is_clear() {
i += 1;
}
debug!("pll0 locked after {} tries", i);
}
pub struct I2sTx {
pub i2s: pac::I2S7,
}
pub fn init_i2s(mut fc7: pac::FLEXCOMM7, i2s7: pac::I2S7, syscon: &mut Syscon) -> I2sTx {
defmt::debug!("init i2s");
// Enable BOTH
syscon.reset(&mut fc7);
syscon.enable_clock(&mut fc7);
unsafe {
pac::IOCON::ptr().as_ref().unwrap().pio1_31.modify(|_, w| {
w.func()
.alt1()
.mode()
.inactive()
.slew()
.fast()
.invert()
.disabled()
.digimode()
.digital()
.od()
.normal()
});
pac::SYSCON::ptr()
.as_ref()
.unwrap()
.fcclksel7()
.modify(|_, w| w.sel().enum_0x5()); // MCLK
pac::SYSCON::ptr()
.as_ref()
.unwrap()
.mclkclksel
.modify(|_, w| w.sel().enum_0x1()); // PLL0
pac::SYSCON::ptr()
.as_ref()
.unwrap()
.mclkdiv
.modify(|_, w| w.div().bits(0).halt().run().reset().released()); // div by 1 = PLL0 fout
pac::SYSCON::ptr()
.as_ref()
.unwrap()
.mclkio
.modify(|_, w| w.mclkio().output());
};
// Select I2S TX function
fc7.pselid.write(|w| w.persel().i2s_transmit());
let regs = i2s7;
// Enable TX FIFO only
regs.fifocfg.modify(|_, w| {
w.enabletx()
.enabled()
.enablerx()
.disabled()
.dmatx()
.disabled()
.txi2se0()
.zero()
});
// Flush
regs.fifocfg.modify(|_, w| w.emptytx().set_bit());
regs.cfg2
.modify(|_, w| unsafe { w.position().bits(0).framelen().bits(63) });
regs.div.modify(|_, w| unsafe { w.div().bits(7) }); // Clock source is MCLK (24MHz on FRO96) / 8 = 3MHz
// Config
regs.cfg1.modify(|_, w| unsafe {
w.mstslvcfg()
.normal_master()
.onechannel()
.dual_channel()
.datalen()
.bits(31)
.mainenable()
.enabled()
.mode()
.classic_mode()
.datapause()
.normal()
});
I2sTx { i2s: regs }
}
#[entry] #[entry]
fn main() -> ! { fn main() -> ! {
let hal = hal::new(); let hal = hal::new();
@@ -522,10 +243,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(hal::drivers::pins::Level::Low); // start turned on
debug!("iocon"); debug!("iocon");
let usb0_vbus_pin = pins::Pio0_22::take() let usb0_vbus_pin = pins::Pio0_22::take()
@@ -535,20 +253,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),
); );
let codec_i2s_pins = ( // We can initialize and iocon these, but there is no peripheral, so they do not get used
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_31::take().unwrap(), // MCLK pins::Pio1_31::take().unwrap(), // MCLK
); );
// iocon.disabled(&mut syscon).release(); // save the environment :)
debug!("clocks"); debug!("clocks");
// TODO: figure out how to configure the PLL for a more suitable audio clock.
let clocks = hal::ClockRequirements::default() let clocks = hal::ClockRequirements::default()
// .system_frequency(24.mhz())
// .system_frequency(72.mhz())
.system_frequency(96.MHz()) .system_frequency(96.MHz())
.configure(&mut anactrl, &mut pmc, &mut syscon) .configure(&mut anactrl, &mut pmc, &mut syscon)
.unwrap(); .unwrap();
@@ -557,9 +271,8 @@ fn main() -> ! {
.0 .0
.enabled(&mut syscon, clocks.support_1mhz_fro_token().unwrap()), .enabled(&mut syscon, clocks.support_1mhz_fro_token().unwrap()),
); );
// Start PLL0 at 24.576MHz as the audio clock. The FRO cannot evenly divide any common audio frequencies.
debug!("pll0"); hw::init_audio_pll();
init_audio_pll();
debug!("peripherals"); debug!("peripherals");
@@ -575,78 +288,55 @@ fn main() -> ! {
let i2s_peripheral = { let i2s_peripheral = {
let fc7 = hal.flexcomm.7.release(); let fc7 = hal.flexcomm.7.release();
init_i2s(fc7.0, fc7.2, &mut syscon) hw::init_i2s(fc7.0, fc7.2, &mut syscon)
}; };
let usb_peripheral = hal.usbhs.enabled_as_device( #[cfg(feature = "usbhs")]
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")]
let (usb_speed, usb_peripheral) = (
UsbSpeed::Full,
hal.usbfs.enabled_as_device(
&mut anactrl,
&mut pmc,
&mut syscon,
clocks.support_usbfs_token().unwrap(),
),
); );
let usb_bus = UsbBus::new(usb_peripheral, usb0_vbus_pin); let usb_bus = UsbBus::new(usb_peripheral, usb0_vbus_pin);
let clock = Clock {};
defmt::debug!("codec init"); defmt::debug!("codec init");
wm8904::init_codec(&mut i2c_bus); wm8904::init_codec(&mut i2c_bus);
let queue = cortex_m::singleton!(
: heapless::spsc::Queue<SampleType, FIFO_LENGTH>
= heapless::spsc::Queue::new()
)
.unwrap();
let (producer, consumer) = queue.split();
let consumer_ref = unsafe { FIFO_CONSUMER_STORE.init(consumer) }; // let consumer_ref = FIFO_CONSUMER_STORE.init(consumer);
unsafe { FIFO_CONSUMER = consumer_ref as *mut _ }; // unsafe { FIFO_CONSUMER = consumer_ref as *mut _ };
// i2s_sine_test(&i2s_peripheral.i2s); let mut audio = Audio {
let audio = Audio {
i2s: i2s_peripheral, i2s: i2s_peripheral,
producer: UnsafeCell::new(producer), producer: QUEUE.stream_producer(),
running: AtomicBool::new(false), running: AtomicBool::new(false),
perf: PerfCounters::default(),
integrator: AtomicI32::new(0),
}; };
let config = AudioClassConfig::new(UsbSpeed::High, FunctionCode::Other, &clock, &audio) let config = UsbAudioClassConfig::new(usb_speed, FunctionCode::IoBox, &mut audio)
.with_input_config(TerminalConfig::new( .with_output_config(TerminalConfig::builder().base_id(2).build());
2,
1,
2,
FormatType1 {
bit_resolution: 32,
bytes_per_sample: 4,
},
TerminalType::ExtLineConnector,
ChannelConfig::default_chans(2),
IsochronousSynchronizationType::Asynchronous,
LockDelay::Undefined(0),
None,
))
.with_output_config(TerminalConfig::new(
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 = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x1209, 0xcc1d))
.composite_with_iads() .composite_with_iads()
.strings(&[StringDescriptors::default() .strings(&[StringDescriptors::default()
.manufacturer("VE7XEN") .manufacturer("Generic")
.product("Guac") .product("usbd_uac2 device")
.serial_number("123456789")]) .serial_number("123456789")])
.unwrap() .unwrap()
.max_packet_size_0(64) .max_packet_size_0(64)
@@ -660,7 +350,5 @@ fn main() -> ! {
loop { loop {
usb_dev.poll(&mut [&mut uac2]); usb_dev.poll(&mut [&mut uac2]);
// audio.poll();
red_led.set_high().ok(); // Turn off
} }
} }
+284 -151
View File
@@ -6,28 +6,20 @@ mod cursor;
pub mod descriptors; pub mod descriptors;
mod log; mod log;
use core::cell::OnceCell;
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, WriteBytesExt}; use byteorder_embedded_io::{LittleEndian, ReadBytesExt, WriteBytesExt};
use constants::*; use constants::*;
use descriptors::*; use descriptors::*;
use log::*; use log::*;
use modular_bitfield::prelude::*; use num_traits::{ConstZero, ToPrimitive};
use num_traits::{ConstZero, Zero};
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;
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 {}
} }
@@ -68,6 +60,9 @@ impl RangeType for u8 {}
impl RangeType for u16 {} impl RangeType for u16 {}
impl RangeType for u32 {} impl RangeType for u32 {}
/// Represent a range request/response.
///
/// ref: UAC2 5.2.2
#[derive(PartialEq, Eq, Ord)] #[derive(PartialEq, Eq, Ord)]
pub struct RangeEntry<T: RangeType> { pub struct RangeEntry<T: RangeType> {
pub min: T, pub min: T,
@@ -87,10 +82,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())?;
@@ -98,48 +90,42 @@ 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) self.min.partial_cmp(&other.min)
} }
} }
/// Fixed point 10.14, packed to the least significant 3-bytes of a 4-byte USB feedback endpoint response /// Represent Isochronous feedback as integer and fractional parts, internally as 16.16.
///
/// Provides conversions to and from the different USB formats.
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub struct UsbIsochronousFeedback { pub struct UsbIsochronousFeedback(u32);
int: u16,
frac: u16,
}
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)
} }
/// Assumed 16.16, not either of the USB formats /// Convert float to fixed point
pub fn new(value: u32) -> Self { pub fn new_float(rate: f32) -> Self {
Self { let fb = (rate * 65536.0 + 0.5) as u32;
int: (value >> 16) as u16, Self(fb)
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] {
@@ -154,38 +140,27 @@ 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(&self, ep: &Endpoint<'a, B, endpoint::Out>) {} fn audio_data_rx(&mut self, ep: &Endpoint<'a, B, endpoint::Out>);
/// 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. Should not be considered a great /// return the correct feedback payload. Feedback always runs at 1ms (in
/// timing reference. Better to track sample timing using other means (even /// this implementation), and will be passed the nominal frame size.
/// `audio_data_rx`).
/// ///
/// 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(&self) -> 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<CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>>( ///
&self, /// The implementation will also call `alternate_setting_changed(_, 0)` when
ac: &mut AudioClass<'a, B, CS, AU>, /// the host disconnects / resets.
terminal: UsbDirection, fn alternate_setting_changed(&mut self, terminal: UsbDirection, alt_setting: u8);
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
@@ -196,13 +171,18 @@ 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 requests the current sample rate. Returns the sample rate in Hz. /// Called when the host or class needs the current sample rate. Returns the
fn get_sample_rate(&self) -> core::result::Result<u32, UsbAudioClassError>; /// sample rate in Hz. It should be cheap and infallible as it gets called in every cycle
/// Called when the host requests to set the sample rate. Should reconfigure the clock source /// of the feedback loop. Use clock validity to signal to the host if the clock is not usable.
/// if necessary. ///
/// Should never return 0 as it may be used in divides in the feedback loop
/// and that would cause a hard fault.
fn sample_rate(&self) -> u32;
/// 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.
fn set_sample_rate( fn set_sample_rate(
&mut self, &mut self,
sample_rate: u32, sample_rate: u32,
@@ -211,14 +191,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,28 +216,29 @@ 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>;
/// Build the ClockSource descriptor. It is not intended to override this method. /// Build the ClockSource descriptor. It is not intended to override this
/// method, but provided so you can.
/// ///
/// 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: id,
clock_type: Self::CLOCK_TYPE, clock_type: Self::CLOCK_TYPE,
sof_sync: Self::SOF_SYNC, sof_sync: Self::SOF_SYNC,
@@ -275,6 +256,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,
@@ -289,9 +366,8 @@ 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( fn new(
base_id: u8, base_id: u8,
clock_source_id: u8, clock_source_id: u8,
num_channels: u8, num_channels: u8,
@@ -319,8 +395,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<In> { }
impl<'a> 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,
@@ -357,7 +437,7 @@ impl<'a> TerminalConfigurationDescriptors for TerminalConfig<In> {
// fn get_interface_descriptor(&self, id: InterfaceIndex) ) // fn get_interface_descriptor(&self, id: InterfaceIndex) )
} }
impl<'a> TerminalConfigurationDescriptors for TerminalConfig<Out> { impl<'a> 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,
@@ -419,30 +499,21 @@ 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 CS, pub audio_impl: &'a mut AU,
pub audio_impl: &'a AU, pub input_config: Option<TerminalConfig<In>>,
pub input_config: Option<TerminalConfig<Out>>, pub output_config: Option<TerminalConfig<Out>>,
pub output_config: Option<TerminalConfig<In>>,
pub additional_descriptors: Option<&'a [AudioClassDescriptor]>, pub additional_descriptors: Option<&'a [AudioClassDescriptor]>,
_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 CS,
audio_impl: &'a 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,
@@ -450,11 +521,11 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>>
_bus: PhantomData, _bus: PhantomData,
} }
} }
pub fn with_input_config(mut self, input_config: TerminalConfig<Out>) -> Self { pub fn with_input_config(mut self, input_config: TerminalConfig<In>) -> Self {
self.input_config = Some(input_config); self.input_config = Some(input_config);
self self
} }
pub fn with_output_config(mut self, output_config: TerminalConfig<In>) -> Self { pub fn with_output_config(mut self, output_config: TerminalConfig<Out>) -> Self {
self.output_config = Some(output_config); self.output_config = Some(output_config);
self self
} }
@@ -467,16 +538,16 @@ 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 = match speed { let (interval, fb_interval, audio_rate) = match speed {
UsbSpeed::Full => 1, UsbSpeed::Full => (1, 1, 1000),
UsbSpeed::High | UsbSpeed::Super => 4, // rate = 2^(4-1) * 125us = 1ms, same as full speed UsbSpeed::High | UsbSpeed::Super => (1, 4, 8000), //
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()
@@ -484,9 +555,12 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>>
.max; .max;
let control_iface = alloc.interface(); let control_iface = alloc.interface();
let mut ac = AudioClass { let nominal_fb = UsbIsochronousFeedback::new_float(
self.audio_impl.sample_rate().to_f32().unwrap() / audio_rate.to_f32().unwrap(),
);
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,
@@ -499,6 +573,8 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>>
out_ep: 0, out_ep: 0,
fb_ep: 0, fb_ep: 0,
speed, speed,
nominal_fb,
audio_rate,
}; };
if let Some(config) = self.output_config { if let Some(config) = self.output_config {
@@ -506,20 +582,20 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>>
let endpoint = alloc.isochronous( let endpoint = alloc.isochronous(
config.sync_type, config.sync_type,
IsochronousUsageType::Data, IsochronousUsageType::Data,
((config.bytes_per_frame() * max_rate) / 1000) as u16, ((max_rate.div_ceil(audio_rate) + 1) * config.bytes_per_frame()) as u16, // headroom of 1 sample for rate control
interval, interval,
); );
let feedback_ep = alloc.isochronous( let feedback_ep = alloc.isochronous(
IsochronousSynchronizationType::NoSynchronization, IsochronousSynchronizationType::NoSynchronization,
IsochronousUsageType::Feedback, IsochronousUsageType::Feedback,
4, 4,
interval, fb_interval,
); );
let alt_setting = DEFAULT_ALTERNATE_SETTING; let alt_setting = DEFAULT_ALTERNATE_SETTING;
ac.in_iface = interface.into(); ac.out_iface = interface.into();
ac.in_ep = endpoint.address().index(); ac.out_ep = endpoint.address().index();
ac.fb_ep = feedback_ep.address().index(); ac.fb_ep = feedback_ep.address().index();
ac.input = Some(AudioStream { ac.output = Some(AudioStream {
config, config,
interface, interface,
endpoint, endpoint,
@@ -533,13 +609,14 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>>
let endpoint = alloc.isochronous( let endpoint = alloc.isochronous(
config.sync_type, config.sync_type,
IsochronousUsageType::Data, IsochronousUsageType::Data,
((config.bytes_per_frame() * max_rate) / 1000) as u16, ((max_rate.div_ceil(audio_rate) + 1) * config.bytes_per_frame()) as u16, // headroom of 1 sample for rate control
interval, interval,
); );
let alt_setting = DEFAULT_ALTERNATE_SETTING; let alt_setting = DEFAULT_ALTERNATE_SETTING;
ac.out_iface = interface.into(); ac.in_iface = interface.into();
ac.out_ep = endpoint.address().index(); ac.in_ep = endpoint.address().index();
ac.output = Some(AudioStream {
ac.input = Some(AudioStream {
config, config,
interface, interface,
endpoint, endpoint,
@@ -649,10 +726,9 @@ 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 CS, audio_impl: &'a mut AU,
audio_impl: &'a 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>>,
feedback: Option<Endpoint<'a, B, In>>, feedback: Option<Endpoint<'a, B, In>>,
@@ -664,10 +740,12 @@ pub struct AudioClass<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a
out_ep: usize, out_ep: usize,
fb_ep: usize, fb_ep: usize,
speed: UsbSpeed, speed: UsbSpeed,
nominal_fb: UsbIsochronousFeedback,
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,
@@ -675,7 +753,7 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> UsbClass<B>
) -> usb_device::Result<()> { ) -> usb_device::Result<()> {
info!(" AudioClass::get_configuration_descriptors"); info!(" AudioClass::get_configuration_descriptors");
// Control + 0-2 streaming // Control + 0-2 streaming
let n_interfaces = 1 + (self.input.is_some() as u8) + (self.input.is_some() as u8); let n_interfaces = 1 + (self.input.is_some() as u8) + (self.output.is_some() as u8);
debug!("writer.iad()"); debug!("writer.iad()");
// UAC2 4.6 Interface Association Descriptor // UAC2 4.6 Interface Association Descriptor
@@ -699,7 +777,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) => {
@@ -805,42 +883,37 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> UsbClass<B>
} }
fn endpoint_out(&mut self, addr: EndpointAddress) { fn endpoint_out(&mut self, addr: EndpointAddress) {
debug!("EP {} out data", addr); debug!("EP {} out data", addr);
static COUNTER: AtomicUsize = AtomicUsize::new(0);
if addr.index() == self.out_ep { if addr.index() == self.out_ep {
self.audio_impl self.audio_impl
.audio_data_rx(&self.output.as_ref().unwrap().endpoint); .audio_data_rx(&self.output.as_ref().unwrap().endpoint);
let new_count = COUNTER.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
if new_count.is_multiple_of(1 as usize) {
if let Some(fb) = self.audio_impl.feedback() {
debug!(" emitting feedback IN {:08x}", fb.to_u32_12_13());
let r = match self.speed {
UsbSpeed::Low | UsbSpeed::Full => {
self.feedback.as_ref().unwrap().write(&fb.to_bytes_10_14())
}
UsbSpeed::High | UsbSpeed::Super => {
self.feedback.as_ref().unwrap().write(&fb.to_bytes_12_13())
}
};
if let Err(e) = r {
warn!(" feedback IN failed {:?}", e);
}
}
}
} else { } else {
debug!(" unexpected OUT on {}", addr); debug!(" unexpected OUT on {}", addr);
} }
} }
fn endpoint_in_complete(&mut self, addr: EndpointAddress) {
debug!("EP {} IN complete", addr);
if let Some(fb_ep) = self.feedback.as_ref()
&& addr.index() == self.fb_ep
{
self.emit_feedback();
} else {
debug!(" unexpected IN on {}", addr);
}
}
fn poll(&mut self) { fn poll(&mut self) {
debug!("poll"); debug!("poll");
// no streaming in alt 0 // no streaming in alt 0
if self.output.as_ref().unwrap().alt_setting != 1 { if self.output.as_ref().is_none_or(|o| o.alt_setting != 0)
|| self.input.as_ref().is_none_or(|i| i.alt_setting != 0)
{
return; return;
} }
loop { loop {
if let Some(o) = self.output.as_ref() {
let mut buf = [0; 1024]; let mut buf = [0; 1024];
match self.output.as_ref().unwrap().endpoint.read(&mut buf) { match o.endpoint.read(&mut buf) {
Ok(len) if len > 0 => { Ok(len) if len > 0 => {
debug!("EP OUT data {:?}", len); debug!("EP OUT data {:?}", len);
} }
@@ -857,7 +930,32 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> UsbClass<B>
} }
} }
impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> AudioClass<'a, B, CS, AU> { 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, AU: AudioHandler<'a, B> + ClockSource> UsbAudioClass<'a, B, AU> {
fn emit_feedback(&mut self) {
if let Some(fb_ep) = self.feedback.as_ref() {
if let Some(fb) = self.audio_impl.feedback(self.nominal_fb) {
debug!(" emitting feedback IN {:08x}", fb.to_u32_12_13());
let r = match self.speed {
UsbSpeed::Low | UsbSpeed::Full => fb_ep.write(&fb.to_bytes_10_14()),
UsbSpeed::High | UsbSpeed::Super => fb_ep.write(&fb.to_bytes_12_13()),
};
if let Err(e) = r {
warn!(" feedback IN failed {:?}", e);
}
} else {
debug!(" feedback callback returned None");
}
}
}
fn standard_request_out(&mut self, xfer: ControlOut<B>) { fn standard_request_out(&mut self, xfer: ControlOut<B>) {
let req = xfer.request(); let req = xfer.request();
match (req.recipient, req.request) { match (req.recipient, req.request) {
@@ -918,7 +1016,7 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> AudioClass<
let old_alt = self.input.as_ref().unwrap().alt_setting; let old_alt = self.input.as_ref().unwrap().alt_setting;
if old_alt != alt_setting { if old_alt != alt_setting {
self.audio_impl self.audio_impl
.alternate_setting_changed(self, UsbDirection::In, alt_setting); .alternate_setting_changed(UsbDirection::In, alt_setting);
self.input.as_mut().unwrap().alt_setting = alt_setting; self.input.as_mut().unwrap().alt_setting = alt_setting;
xfer.accept().ok(); xfer.accept().ok();
} }
@@ -926,7 +1024,9 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> AudioClass<
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.audio_impl self.audio_impl
.alternate_setting_changed(self, UsbDirection::Out, alt_setting); .alternate_setting_changed(UsbDirection::Out, alt_setting);
// Start the IN cycle running
self.emit_feedback();
self.output.as_mut().unwrap().alt_setting = alt_setting; self.output.as_mut().unwrap().alt_setting = alt_setting;
xfer.accept().ok(); xfer.accept().ok();
} }
@@ -1023,6 +1123,10 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> AudioClass<
channel: u8, channel: u8,
control: u8, control: u8,
) { ) {
match entity {
1 => return self.set_clock_cur(xfer, channel, control),
_ => {}
}
debug!(" Unimplemented."); debug!(" Unimplemented.");
} }
@@ -1108,14 +1212,13 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> AudioClass<
channel channel
); );
} }
xfer.accept(|mut buf| match self.clock_impl.get_sample_rate() { xfer.accept(|mut buf| {
Ok(rate) => { let rate = self.audio_impl.sample_rate();
debug!(" {}", rate); debug!(" {}", rate);
buf.write_u32::<LittleEndian>(rate) buf.write_u32::<LittleEndian>(rate)
.map_err(|_e| UsbError::BufferOverflow)?; .map_err(|_e| UsbError::BufferOverflow)?;
Ok(4) Ok(4)
}
Err(_e) => Err(UsbError::InvalidState),
}) })
.ok(); .ok();
} }
@@ -1127,11 +1230,12 @@ 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)
.map_err(|_e| UsbError::BufferOverflow)?; .map_err(|_e| UsbError::BufferOverflow)
.ok();
Ok(1) Ok(1)
} }
Err(_e) => Err(UsbError::InvalidState), Err(_e) => Err(UsbError::InvalidState),
@@ -1143,6 +1247,35 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> AudioClass<
} }
} }
} }
fn set_clock_cur(&mut self, xfer: ControlOut<B>, channel: u8, control: u8) {
match control.try_into() {
Ok(ClockSourceControlSelector::SamFreqControl) => {
debug!(" SamplingFreqControl");
if channel != 0 {
error!(
" Invalid channel {} for SamplingFreqControl GET CUR. Ignoring.",
channel
);
}
match xfer.data().read_u32::<LittleEndian>() {
Ok(rate) => {
debug!(" SET SamplingFreqControl CUR {}", rate);
self.audio_impl.set_sample_rate(rate).ok();
self.nominal_fb = UsbIsochronousFeedback::new_float(
rate.to_f32().unwrap() / self.audio_rate.to_f32().unwrap(),
);
xfer.accept().ok();
}
Err(e) => {
error!(" SET SamplingFreqControl CUR ERROR BAD DATA");
}
}
}
_ => {
debug!(" Unimplemented.");
}
}
}
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.try_into() {
Ok(ClockSourceControlSelector::SamFreqControl) => { Ok(ClockSourceControlSelector::SamFreqControl) => {
@@ -1153,7 +1286,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)?;