From e2edb1d468335b3e927cb972e27b1ea765c8c15f Mon Sep 17 00:00:00 2001 From: Keenan Tims Date: Thu, 14 May 2026 01:51:40 -0700 Subject: [PATCH] examples: add dma example for lpc55s28-evk --- examples/lpc55s28-evk-dma/.cargo/config.toml | 19 + examples/lpc55s28-evk-dma/Cargo.lock | 701 +++++++++++++++++++ examples/lpc55s28-evk-dma/Cargo.toml | 31 + examples/lpc55s28-evk-dma/memory.x | 21 + examples/lpc55s28-evk-dma/src/dma.rs | 403 +++++++++++ examples/lpc55s28-evk-dma/src/hw.rs | 367 ++++++++++ examples/lpc55s28-evk-dma/src/main.rs | 400 +++++++++++ examples/lpc55s28-evk-dma/src/wm8904.rs | 110 +++ 8 files changed, 2052 insertions(+) create mode 100644 examples/lpc55s28-evk-dma/.cargo/config.toml create mode 100644 examples/lpc55s28-evk-dma/Cargo.lock create mode 100644 examples/lpc55s28-evk-dma/Cargo.toml create mode 100644 examples/lpc55s28-evk-dma/memory.x create mode 100644 examples/lpc55s28-evk-dma/src/dma.rs create mode 100644 examples/lpc55s28-evk-dma/src/hw.rs create mode 100644 examples/lpc55s28-evk-dma/src/main.rs create mode 100644 examples/lpc55s28-evk-dma/src/wm8904.rs diff --git a/examples/lpc55s28-evk-dma/.cargo/config.toml b/examples/lpc55s28-evk-dma/.cargo/config.toml new file mode 100644 index 0000000..48fb7f4 --- /dev/null +++ b/examples/lpc55s28-evk-dma/.cargo/config.toml @@ -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" diff --git a/examples/lpc55s28-evk-dma/Cargo.lock b/examples/lpc55s28-evk-dma/Cargo.lock new file mode 100644 index 0000000..62cff03 --- /dev/null +++ b/examples/lpc55s28-evk-dma/Cargo.lock @@ -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", +] diff --git a/examples/lpc55s28-evk-dma/Cargo.toml b/examples/lpc55s28-evk-dma/Cargo.toml new file mode 100644 index 0000000..a2ea54d --- /dev/null +++ b/examples/lpc55s28-evk-dma/Cargo.toml @@ -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 diff --git a/examples/lpc55s28-evk-dma/memory.x b/examples/lpc55s28-evk-dma/memory.x new file mode 100644 index 0000000..5a68a5f --- /dev/null +++ b/examples/lpc55s28-evk-dma/memory.x @@ -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 */ +} diff --git a/examples/lpc55s28-evk-dma/src/dma.rs b/examples/lpc55s28-evk-dma/src/dma.rs new file mode 100644 index 0000000..6ffb0c6 --- /dev/null +++ b/examples/lpc55s28-evk-dma/src/dma.rs @@ -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 { + 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 { + 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, + // SAFETY: only written by USB task (on start) + pub(crate) desc: UnsafeCell>, + 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, + write_off: UnsafeCell, + + 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 DmaRing { + /// 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 { + 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::::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 Sync for DmaRing {} + +/// 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) +} diff --git a/examples/lpc55s28-evk-dma/src/hw.rs b/examples/lpc55s28-evk-dma/src/hw.rs new file mode 100644 index 0000000..c6a1b3d --- /dev/null +++ b/examples/lpc55s28-evk-dma/src/hw.rs @@ -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 { + inner: UnsafeCell, +} +unsafe impl Sync for SharedLed {} +impl SharedLed { + 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 SharedLed { + pub fn toggle(&self) { + unsafe { + (*self.inner.get()).toggle().ok(); + } + } +} + +type RedLed = Pin>; +type GreenLed = Pin>; +type BlueLed = Pin>; +pub static RED_LED: MaybeUninit> = MaybeUninit::uninit(); +pub static GREEN_LED: MaybeUninit> = MaybeUninit::uninit(); +pub static BLUE_LED: MaybeUninit> = MaybeUninit::uninit(); + +pub fn init_leds(iocon: &mut Iocon, gpio: &mut hal::Gpio) { + 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, red_led); + core::ptr::write(GREEN_LED.as_ptr() as *mut SharedLed, green_led); + core::ptr::write(BLUE_LED.as_ptr() as *mut SharedLed, blue_led); + } +} +pub fn red_led() -> &'static SharedLed { + unsafe { &*RED_LED.as_ptr() } +} +pub fn green_led() -> &'static SharedLed { + unsafe { &*GREEN_LED.as_ptr() } +} +pub fn blue_led() -> &'static SharedLed { + unsafe { &*BLUE_LED.as_ptr() } +} diff --git a/examples/lpc55s28-evk-dma/src/main.rs b/examples/lpc55s28-evk-dma/src/main.rs new file mode 100644 index 0000000..0df56d3 --- /dev/null +++ b/examples/lpc55s28-evk-dma/src/main.rs @@ -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; 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], usbd_uac2::UsbAudioClassError> { + Ok(&Clock::RATES) + } + fn get_clock_validity(&self) -> core::result::Result { + Ok(true) + } +} + +static DMA_RING: StaticCell> = StaticCell::new(); +static mut DMA_RING_REF: Option<&'static DmaRing> = None; +#[inline] +fn dma_ring() -> &'static DmaRing { + 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, +} +impl 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 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 { + 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]); + } +} diff --git a/examples/lpc55s28-evk-dma/src/wm8904.rs b/examples/lpc55s28-evk-dma/src/wm8904.rs new file mode 100644 index 0000000..7a2f28b --- /dev/null +++ b/examples/lpc55s28-evk-dma/src/wm8904.rs @@ -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(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 +}