Compare commits

...

4 Commits

9 changed files with 2122 additions and 42 deletions
@@ -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"
+701
View File
@@ -0,0 +1,701 @@
# 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"
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-halt",
"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-halt"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a513e167849a384b7f9b746e517604398518590a9142f4846a32e3c2a4de7b11"
[[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",
]
+31
View File
@@ -0,0 +1,31 @@
[package]
name = "lpc55s28-evk-dma"
version = "0.1.0"
edition = "2024"
[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"
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"] }
static_cell = "2.1.1"
usb-device = "0.3"
usbd-uac2 = { version = "0.1.0", path = "../..", features = ["defmt"]}
[profile.release]
opt-level = "z"
lto = true
debug = true
codegen-units = 1
+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 */
}
+403
View File
@@ -0,0 +1,403 @@
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 consumed(&self) -> usize {
self.consumed.load(Ordering::Acquire)
}
pub fn consumed_bytes(&self) -> usize {
self.consumed.load(Ordering::Acquire) * self.slot_bytes
+ (self.dma.channel19.xfercfg.read().bits() as usize >> 16 & 0x3ff)
}
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::info!("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)
}
+367
View File
@@ -0,0 +1,367 @@
//! Contains hardware setup unrelated to Usb Audio Class implementation
use crate::hal;
use core::cell::{OnceCell, UnsafeCell};
use core::mem::MaybeUninit;
use core::ptr::null_mut;
use crate::Syscon;
use crate::{MCLK_FREQ, SAMPLE_RATE, pac};
use defmt::debug;
use hal::{
Iocon, Pin,
drivers::pins,
prelude::*,
traits::wg::digital::v2::{OutputPin, ToggleableOutputPin},
typestates::pin::{gpio::direction::Output, state::Gpio},
};
use lpc55_hal::Enabled;
use static_cell::StaticCell;
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);
}
const SYS_PLL: PllConstants = PllConstants::new(4, 75, 1); // 150MHz
pub(crate) fn init_sys_pll1() {
let syscon = unsafe { &*pac::SYSCON::ptr() };
let pmc = unsafe { &*pac::PMC::ptr() };
let anactrl = unsafe { &*pac::ANACTRL::ptr() };
debug!("start clk_in");
pmc.pdruncfg0
.modify(|_, w| w.pden_xtal32m().poweredon().pden_ldoxo32m().poweredon());
syscon.clock_ctrl.modify(|_, w| w.clkin_ena().enable());
anactrl
.xo32m_ctrl
.modify(|_, w| w.enable_system_clk_out().enable());
debug!("init pll1: {}", SYS_PLL);
pmc.pdruncfg0.modify(|_, w| w.pden_pll1().poweredoff());
syscon.pll1clksel.write(|w| w.sel().enum_0x1()); // clk_in
syscon.pll1ctrl.write(|w| unsafe {
w.clken()
.enable()
.seli()
.bits(SYS_PLL.seli)
.selp()
.bits(SYS_PLL.selp)
});
syscon
.pll1ndec
.write(|w| unsafe { w.ndiv().bits(SYS_PLL.n) });
syscon.pll1ndec.write(|w| unsafe {
w.ndiv().bits(SYS_PLL.n).nreq().set_bit() // latch
});
syscon
.pll1mdec
.write(|w| unsafe { w.mdiv().bits(SYS_PLL.m) });
syscon
.pll1pdec
.write(|w| unsafe { w.pdiv().bits(SYS_PLL.p) });
syscon.pll1pdec.write(|w| unsafe {
w.pdiv().bits(SYS_PLL.p).preq().set_bit() // latch
});
pmc.pdruncfg0.modify(|_, w| w.pden_pll1().poweredon());
debug!("pll1 wait for lock");
let mut i = 0usize;
while syscon.pll1stat.read().lock().bit_is_clear() {
i += 1;
}
debug!("pll1 locked after {} tries", i);
// switch system clock to pll1
syscon.fmccr.modify(|_, w| w.flashtim().flashtim11());
syscon.mainclkselb.modify(|_, w| w.sel().enum_0x2()); // pll1
}
pub struct I2sTx {
pub 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()
});
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() }
}
+400
View File
@@ -0,0 +1,400 @@
//! Interrupt driven example for the LPCXpresso55S28 demo board
//!
//! Uses the onboard WM8904 DAC at 48KHz. Clock is generated by PLL0. Simple PI feedback
//! is implemented.
//!
//! Packets from USB are placed a `heapless::spsc::Queue`. They are consumed
//! by the I2S FIFO in the FLEXCOMM7 interrupt.
#![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},
endpoint::IsochronousSynchronizationType,
};
use usbd_uac2::UsbIsochronousFeedback;
use usbd_uac2::{
self, AudioClassConfig, RangeEntry, TerminalConfig, UsbAudioClass, UsbAudioClockImpl, UsbSpeed,
constants::{FunctionCode, TerminalType},
descriptors::{ChannelConfig, ClockType, FormatType1, LockDelay},
};
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;
//latency ≈ (current_fill × FRAMES_PER_SLOT)
// + FRAMES_PER_SLOT/2 - average DMA transfer position
// + 8 - FIFO depth @ 32-bit samples
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 SLOT_SIZE_BYTES: usize = FRAMES_PER_SLOT * BYTES_PER_FRAME; // run the DMA at 2khz
const N_SLOTS: usize = 32;
const FILL_TARGET: i32 = (FRAMES_PER_SLOT * N_SLOTS) as i32 / 2;
struct Clock {}
impl Clock {
const RATES: [RangeEntry<u32>; 1] = [RangeEntry::new_fixed(SAMPLE_RATE)];
}
impl UsbAudioClockImpl for Clock {
const CLOCK_TYPE: usbd_uac2::descriptors::ClockType = ClockType::InternalFixed;
const SOF_SYNC: bool = false;
fn get_sample_rate(&self) -> u32 {
Clock::RATES[0].min
}
fn get_rates(
&self,
) -> core::result::Result<&[usbd_uac2::RangeEntry<u32>], usbd_uac2::UsbAudioClassError> {
Ok(&Clock::RATES)
}
fn get_clock_validity(&self) -> core::result::Result<bool, usbd_uac2::UsbAudioClassError> {
Ok(true)
}
}
static DMA_RING: StaticCell<DmaRing<N_SLOTS, SLOT_SIZE_BYTES>> = StaticCell::new();
static mut DMA_RING_REF: Option<&'static DmaRing<N_SLOTS, SLOT_SIZE_BYTES>> = None;
#[inline]
fn dma_ring() -> &'static DmaRing<N_SLOTS, SLOT_SIZE_BYTES> {
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>,
}
impl<const N: usize, const MAX_SLOT_BYTES: usize> Audio<'_, N, MAX_SLOT_BYTES> {
fn start(&self) {
red_led().off(); // clear any dma error
self.running.store(false, Ordering::Relaxed);
defmt::info!("playback starting (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.
self.running.store(false, Ordering::Relaxed);
dma_ring().stop();
defmt::info!("playback stopped");
pac::NVIC::mask(pac::Interrupt::DMA0);
green_led().off();
blue_led().off();
}
}
impl<const N: usize, const MAX_SLOT_BYTES: usize, B: bus::UsbBus> UsbAudioClass<'_, 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).
let mut buf = [0; SAMPLE_RATE.div_ceil(8000) 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::debug!(
"buffer has {} slots, start dma transfers",
self.dma.fill_slots()
);
self.dma.run();
self.running.store(true, Ordering::Relaxed)
}
}
/// Provide rate feedback to the host, so that it doesn't over- or underflow
/// the buffer. Proportional-only control is stable with normal hosts,
/// adding an I term with proper tuning (quite weak) would stabilize the
/// rate reported to the host but is not necessary for basic playback.
fn feedback(&mut self, nominal_rate: UsbIsochronousFeedback) -> Option<UsbIsochronousFeedback> {
const MAX_CORRECTION: i32 = 1 << 10; // ~1.6%
let produced_bytes = self.dma.produced_bytes.load(Ordering::Acquire);
let consumed_bytes = self.dma.consumed_bytes();
let valid = produced_bytes >= consumed_bytes; // else we are in underrun condition
let fill_frames = if valid {
(produced_bytes - consumed_bytes) as i32 / BYTES_PER_FRAME as i32
} else {
// we will emit a canonical error in the DMA ISR
defmt::debug!("[fb] dma underrun detected");
0
};
let mut error = fill_frames - FILL_TARGET;
error = error.clamp(-32, 32); // avoid huge spikes
let p = error * 256;
let i = 0; // placeholder
let correction = -(p + i);
let nominal_v = nominal_rate.to_u32_12_13() as i32;
let mut v = nominal_v + correction;
v = v.clamp(nominal_v - MAX_CORRECTION, nominal_v + MAX_CORRECTION);
defmt::debug!(
"valid:{} fill:{} err:{} fb:{=u32:x}",
valid,
fill_frames,
error,
v as u32
);
Some(UsbIsochronousFeedback::new(v as u32))
}
}
#[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, 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");
// Run the system clock at 96MHz. The lpc55-hal will run it from the FRO. But we won't actually use these clocks, we just need the guards...
let clocks = hal::ClockRequirements::default()
.system_frequency(96.MHz())
.configure(&mut anactrl, &mut pmc, &mut syscon)
.unwrap();
let mut _delay_timer = Timer::new(
hal.ctimer
.0
.enabled(&mut syscon, clocks.support_1mhz_fro_token().unwrap()),
);
// Start PLL1 at 150MHz as main system clock
hw::init_sys_pll1();
// Start PLL0 at 24.576MHz as the audio clock. The FRO cannot evenly divide
// 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 _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::<32, SLOT_SIZE_BYTES>::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 clock = Clock {};
let mut audio = Audio {
i2s: i2s_peripheral,
dma: dma_ring(),
running: AtomicBool::new(false),
};
defmt::debug!("usb init");
let config = AudioClassConfig::new(usb_speed, FunctionCode::Other, &mut clock, &mut audio)
.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 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
}
+70 -42
View File
@@ -15,7 +15,7 @@ use constants::*;
use descriptors::*;
use log::*;
use num_traits::ConstZero;
use num_traits::{ConstZero, ToPrimitive};
use usb_device::control::{Recipient, Request, RequestType};
use usb_device::device::DEFAULT_ALTERNATE_SETTING;
use usb_device::endpoint::{self, Endpoint, EndpointDirection, In, Out};
@@ -106,8 +106,8 @@ impl<T: RangeType + PartialOrd> PartialOrd for RangeEntry<T> {
/// Fixed point 10.14, packed to the least significant 3-bytes of a 4-byte USB feedback endpoint response
#[derive(Copy, Clone)]
pub struct UsbIsochronousFeedback {
int: u16,
frac: u16,
pub int: u16,
pub frac: u16,
}
impl UsbIsochronousFeedback {
@@ -115,6 +115,10 @@ impl UsbIsochronousFeedback {
pub fn new_frac(int: u16, frac: u16) -> Self {
Self { int, frac }
}
pub fn new_float(rate: f32) -> Self {
let fb = (rate * 65536.0 + 0.5) as u32;
Self::new(fb)
}
/// Assumed 16.16, not either of the USB formats
pub fn new(value: u32) -> Self {
Self {
@@ -164,13 +168,12 @@ pub trait UsbAudioClass<'a, B: UsbBus> {
fn audio_data_rx(&mut self, ep: &Endpoint<'a, B, endpoint::Out>) {}
/// Called when it's time to send an isochronous feedback update. Should
/// return the correct feedback payload. Should not be considered a great
/// timing reference. Better to track sample timing using other means (even
/// `audio_data_rx`).
/// return the correct feedback payload. Feedback always runs at 1ms (in
/// this implementation), and will be passed the nominal frame size.
///
/// Required for isochronous asynchronous mode to work properly. If None is
/// returned, no IN packet will be emitted at feedback time.
fn feedback(&mut self) -> Option<UsbIsochronousFeedback> {
fn feedback(&mut self, nominal_rate: UsbIsochronousFeedback) -> Option<UsbIsochronousFeedback> {
None
}
@@ -191,8 +194,13 @@ pub trait UsbAudioClass<'a, B: UsbBus> {
pub trait UsbAudioClockImpl {
const CLOCK_TYPE: ClockType;
const SOF_SYNC: bool;
/// Called when the host requests the current sample rate. Returns the sample rate in Hz.
fn get_sample_rate(&self) -> core::result::Result<u32, UsbAudioClassError>;
/// Called when the host or class needs the current sample rate. Returns the
/// sample rate in Hz. It should be cheap and infallible as it gets called in every cycle
/// of the feedback loop. Use clock validity to signal to the host if the clock is not usable.
///
/// Should never return 0 as it may be used in divides in the feedback loop
/// and that would cause a hard fault.
fn get_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(
@@ -468,9 +476,9 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>>
/// Allocate the various USB IDs, and build the class implementation
pub fn build(self, alloc: &'a UsbBusAllocator<B>) -> Result<AudioClass<'a, B, CS, AU>> {
let speed = self.speed;
let interval = match speed {
UsbSpeed::Full => 1,
UsbSpeed::High | UsbSpeed::Super => 4, // rate = 2^(4-1) * 125us = 1ms, same as full speed
let (interval, fb_interval, audio_rate) = match speed {
UsbSpeed::Full => (1, 1, 1000),
UsbSpeed::High | UsbSpeed::Super => (1, 4, 8000), //
UsbSpeed::Low => return Err(Error::InvalidSpeed),
};
let max_rate = self
@@ -483,6 +491,10 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>>
.max;
let control_iface = alloc.interface();
let nominal_fb = UsbIsochronousFeedback::new_float(
self.clock_impl.get_sample_rate().to_f32().unwrap() / audio_rate.to_f32().unwrap(),
);
let mut ac = AudioClass {
control_iface,
clock_impl: self.clock_impl,
@@ -498,6 +510,8 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>>
out_ep: 0,
fb_ep: 0,
speed,
nominal_fb,
audio_rate,
};
if let Some(config) = self.output_config {
@@ -505,14 +519,14 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>>
let endpoint = alloc.isochronous(
config.sync_type,
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,
);
let feedback_ep = alloc.isochronous(
IsochronousSynchronizationType::NoSynchronization,
IsochronousUsageType::Feedback,
4,
interval,
fb_interval,
);
let alt_setting = DEFAULT_ALTERNATE_SETTING;
ac.out_iface = interface.into();
@@ -532,7 +546,7 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>>
let endpoint = alloc.isochronous(
config.sync_type,
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,
);
let alt_setting = DEFAULT_ALTERNATE_SETTING;
@@ -664,6 +678,8 @@ pub struct AudioClass<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a
out_ep: usize,
fb_ep: usize,
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>
@@ -805,33 +821,25 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> UsbClass<B>
}
fn endpoint_out(&mut self, addr: EndpointAddress) {
debug!("EP {} out data", addr);
static COUNTER: AtomicUsize = AtomicUsize::new(0);
if addr.index() == self.out_ep {
self.audio_impl
.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 {
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) {
debug!("poll");
// no streaming in alt 0
@@ -862,6 +870,22 @@ 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 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>) {
let req = xfer.request();
match (req.recipient, req.request) {
@@ -933,6 +957,8 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> AudioClass<
self.clock_impl.alt_setting(alt_setting).ok();
self.audio_impl
.alternate_setting_changed(UsbDirection::Out, alt_setting);
// Start the IN cycle running
self.emit_feedback();
self.output.as_mut().unwrap().alt_setting = alt_setting;
xfer.accept().ok();
}
@@ -1118,14 +1144,13 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> AudioClass<
channel
);
}
xfer.accept(|mut buf| match self.clock_impl.get_sample_rate() {
Ok(rate) => {
debug!(" {}", rate);
buf.write_u32::<LittleEndian>(rate)
.map_err(|_e| UsbError::BufferOverflow)?;
Ok(4)
}
Err(_e) => Err(UsbError::InvalidState),
xfer.accept(|mut buf| {
let rate = self.clock_impl.get_sample_rate();
debug!(" {}", rate);
buf.write_u32::<LittleEndian>(rate)
.map_err(|_e| UsbError::BufferOverflow)?;
Ok(4)
})
.ok();
}
@@ -1168,6 +1193,9 @@ impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> AudioClass<
Ok(rate) => {
debug!(" SET SamplingFreqControl CUR {}", rate);
self.clock_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) => {