From d2bebc9e7b196f09a9126f1e32fe4ceaf1a5298a Mon Sep 17 00:00:00 2001 From: nils Date: Fri, 21 Aug 2026 08:44:19 +0200 Subject: [PATCH] Initial import: Rust BLE-MIDI keypad for Pico W Port of the C/BTstack BLE-MIDI keypad firmware to Rust (embassy + cyw43 + trouble). 16-pad RGB keypad (APA102 SPI + I2C button expander), BLE-MIDI 1.0 GATT server (full codec: sysex, running-status, real-time), octave display on BOOTSEL, onboard LED heartbeat, USB serial logging. - src/keypad.rs Pimoroni RGB Keypad driver - src/midi_keypad.rs pad->Note On/Off + LED response state machine - src/codec.rs BLE-MIDI 1.0 codec (port of rppicomidi, MIT) - src/main.rs cyw43 BT bring-up + trouble GATT + MIDI pump - src/octave.rs octave digit display on BOOTSEL GPL-3.0-or-later; codec.rs retains its MIT (rppicomidi) attribution. --- .cargo/config.toml | 5 + .gitignore | 1 + Cargo.lock | 2074 +++++++++++++++++++++++++++++++ Cargo.toml | 54 + LICENSE | 652 ++++++++++ README.md | 47 + build.rs | 24 + cyw43-firmware/43439A0.bin | Bin 0 -> 231077 bytes cyw43-firmware/43439A0_btfw.bin | Bin 0 -> 6164 bytes cyw43-firmware/43439A0_clm.bin | Bin 0 -> 984 bytes cyw43-firmware/nvram_rp2040.bin | Bin 0 -> 742 bytes memory.x | 7 + rust-toolchain.toml | 3 + src/codec.rs | 719 +++++++++++ src/keypad.rs | 95 ++ src/main.rs | 394 ++++++ src/midi_keypad.rs | 190 +++ src/octave.rs | 65 + 18 files changed, 4330 insertions(+) create mode 100644 .cargo/config.toml create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 build.rs create mode 100644 cyw43-firmware/43439A0.bin create mode 100644 cyw43-firmware/43439A0_btfw.bin create mode 100644 cyw43-firmware/43439A0_clm.bin create mode 100644 cyw43-firmware/nvram_rp2040.bin create mode 100644 memory.x create mode 100644 rust-toolchain.toml create mode 100644 src/codec.rs create mode 100644 src/keypad.rs create mode 100644 src/main.rs create mode 100644 src/midi_keypad.rs create mode 100644 src/octave.rs diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..fc7c1c6 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,5 @@ +[build] +target = "thumbv6m-none-eabi" # Cortex-M0+ (RP2040) + +[env] +DEFMT_LOG = "info" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..4751bea --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2074 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "aligned" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685" +dependencies = [ + "as-slice", +] + +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + +[[package]] +name = "as-slice" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "ascii-canvas" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891" +dependencies = [ + "term", +] + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "az" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be5eb007b7cacc6c660343e96f650fedf4b5a77512399eb952ca6642cf8d13f7" + +[[package]] +name = "bare-metal" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitfield" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" + +[[package]] +name = "bitfield" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-device-driver" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c051592f59fe68053524b4c4935249b806f72c1f544cfb7abe4f57c3be258e" +dependencies = [ + "aligned", +] + +[[package]] +name = "bt-hci" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63b3d6f0ef006d9b87a075705d6c142b03681ad34090659647f0e227c1c667f" +dependencies = [ + "bt-hci-transport", + "btuuid", + "defmt 1.1.1", + "embassy-sync", + "embedded-io 0.7.1", + "embedded-io-async 0.7.0", + "futures-intrusive", + "heapless 0.9.3", +] + +[[package]] +name = "bt-hci-transport" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffb9819c69cf88877f5eebc8c5ade0ab50f2bf9475bbb3eb714d66c55b0cb64" +dependencies = [ + "defmt 1.1.1", + "embedded-io 0.7.1", + "embedded-io-async 0.7.0", +] + +[[package]] +name = "btuuid" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5f48f1e9b0aad0a4f05d17bdeae0fa20ff798e272a03a6940ca27ad9c5a6ae7" +dependencies = [ + "defmt 0.3.100", + "uuid", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytemuck" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cc" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "509591b7bcd67f4ef775afad7662703b4935daaa6ec0e5605cfb1090b32a2b6d" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "defmt 1.1.1", + "num-traits", + "pure-rust-locales", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "convert_case" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cordyceps" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9ab7e0ca1d179628fa0172b2b97203c7fa0cd81be2448bd446fb9559ca9261" +dependencies = [ + "loom", + "tracing", +] + +[[package]] +name = "cortex-m" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "844b9697e922c99847eed515c6eb6d101e7ce62ff556fcaec243798291427ee8" +dependencies = [ + "bare-metal", + "bitfield 0.13.2", + "cortex-m-macros", + "critical-section", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "volatile-register", +] + +[[package]] +name = "cortex-m-macros" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d1922be58519ad40368fc4ca595a2cefa51a7abf947be3b0c90586dc7dbd0e2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "cortex-m-rt" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c439b23bf18154d8a634543caf1ee73cceb723f2902f7ea243634159a00fd47" +dependencies = [ + "cortex-m-rt-macros", +] + +[[package]] +name = "cortex-m-rt-macros" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "061a75f3e7f3f01d649668d68e02d0ef92c7041a9c97b8fe6bc354ddbf40822d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc-any" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46db9f663dfb869b80fcf59e32d7a80fc6c464a4f6328f3f06a00f5e36d05f8c" +dependencies = [ + "debug-helper", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cyw43" +version = "0.7.0" +source = "git+https://github.com/embassy-rs/embassy.git?rev=3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a#3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a" +dependencies = [ + "aligned", + "bt-hci-transport", + "cortex-m", + "cortex-m-rt", + "defmt 1.1.1", + "embassy-futures 0.1.2 (git+https://github.com/embassy-rs/embassy.git?rev=3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a)", + "embassy-net-driver-channel", + "embassy-sync", + "embassy-time", + "embedded-hal 1.0.0", + "embedded-hal-async", + "embedded-io-async 0.7.0", + "futures", + "heapless 0.9.3", + "sdio", + "trait-set", +] + +[[package]] +name = "cyw43-pio" +version = "0.10.0" +source = "git+https://github.com/embassy-rs/embassy.git?rev=3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a#3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a" +dependencies = [ + "cyw43", + "defmt 1.1.1", + "embassy-futures 0.1.2 (git+https://github.com/embassy-rs/embassy.git?rev=3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a)", + "embassy-rp", + "fixed", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.119", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "debug-helper" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80a4af69c60438a1a82af89d362f4729fd38db7b73f305a237636fad31ceb2bf" + +[[package]] +name = "defmt" +version = "0.3.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad" +dependencies = [ + "defmt 1.1.1", +] + +[[package]] +name = "defmt" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" +dependencies = [ + "defmt-parser", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[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.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05144944c117db54127a8ac17a3c9a28d55e7047bfddb950bcd20b4a94c79b10" +dependencies = [ + "critical-section", + "defmt 1.1.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 = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "either" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" + +[[package]] +name = "embassy-embedded-hal" +version = "0.6.0" +source = "git+https://github.com/embassy-rs/embassy.git?rev=3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a#3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a" +dependencies = [ + "embassy-futures 0.1.2 (git+https://github.com/embassy-rs/embassy.git?rev=3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a)", + "embassy-hal-internal", + "embassy-sync", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "embedded-storage", + "embedded-storage-async", + "nb 1.1.0", +] + +[[package]] +name = "embassy-executor" +version = "0.10.0" +source = "git+https://github.com/embassy-rs/embassy.git?rev=3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a#3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a" +dependencies = [ + "cordyceps", + "cortex-m", + "critical-section", + "defmt 1.1.1", + "document-features", + "embassy-executor-macros", + "embassy-executor-timer-queue", + "unitrait", +] + +[[package]] +name = "embassy-executor-macros" +version = "0.8.0" +source = "git+https://github.com/embassy-rs/embassy.git?rev=3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a#3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "embassy-executor-timer-queue" +version = "0.1.1" +source = "git+https://github.com/embassy-rs/embassy.git?rev=3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a#3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a" +dependencies = [ + "unitrait", +] + +[[package]] +name = "embassy-futures" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc2d050bdc5c21e0862a89256ed8029ae6c290a93aecefc73084b3002cdebb01" + +[[package]] +name = "embassy-futures" +version = "0.1.2" +source = "git+https://github.com/embassy-rs/embassy.git?rev=3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a#3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a" + +[[package]] +name = "embassy-hal-internal" +version = "0.5.0" +source = "git+https://github.com/embassy-rs/embassy.git?rev=3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a#3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a" +dependencies = [ + "cortex-m", + "critical-section", + "defmt 1.1.1", + "num-traits", +] + +[[package]] +name = "embassy-net-driver" +version = "0.2.0" +source = "git+https://github.com/embassy-rs/embassy.git?rev=3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a#3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a" + +[[package]] +name = "embassy-net-driver-channel" +version = "0.4.0" +source = "git+https://github.com/embassy-rs/embassy.git?rev=3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a#3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a" +dependencies = [ + "embassy-futures 0.1.2 (git+https://github.com/embassy-rs/embassy.git?rev=3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a)", + "embassy-net-driver", + "embassy-sync", +] + +[[package]] +name = "embassy-rp" +version = "0.10.0" +source = "git+https://github.com/embassy-rs/embassy.git?rev=3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a#3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a" +dependencies = [ + "cfg-if", + "chrono", + "cortex-m", + "cortex-m-rt", + "critical-section", + "defmt 1.1.1", + "document-features", + "embassy-embedded-hal", + "embassy-futures 0.1.2 (git+https://github.com/embassy-rs/embassy.git?rev=3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a)", + "embassy-hal-internal", + "embassy-sync", + "embassy-time", + "embassy-time-driver", + "embassy-time-queue-utils", + "embassy-usb-driver", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "embedded-hal-nb", + "embedded-io 0.7.1", + "embedded-io-async 0.7.0", + "embedded-storage", + "embedded-storage-async", + "fixed", + "nb 1.1.0", + "pio", + "rand_core 0.10.1", + "rand_core 0.6.4", + "rand_core 0.9.5", + "rp-pac", + "rp2040-boot2", + "sha2-const-stable", + "smart-leds-trait", +] + +[[package]] +name = "embassy-sync" +version = "0.8.0" +source = "git+https://github.com/embassy-rs/embassy.git?rev=3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a#3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a" +dependencies = [ + "cfg-if", + "critical-section", + "defmt 1.1.1", + "embedded-io-async 0.7.0", + "futures-core", + "futures-sink", + "heapless 0.9.3", +] + +[[package]] +name = "embassy-time" +version = "0.5.1" +source = "git+https://github.com/embassy-rs/embassy.git?rev=3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a#3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a" +dependencies = [ + "cfg-if", + "critical-section", + "defmt 1.1.1", + "document-features", + "embassy-time-driver", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "futures-core", +] + +[[package]] +name = "embassy-time-driver" +version = "0.2.2" +source = "git+https://github.com/embassy-rs/embassy.git?rev=3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a#3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a" +dependencies = [ + "document-features", +] + +[[package]] +name = "embassy-time-queue-utils" +version = "0.3.2" +source = "git+https://github.com/embassy-rs/embassy.git?rev=3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a#3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a" +dependencies = [ + "embassy-executor-timer-queue", + "heapless 0.9.3", +] + +[[package]] +name = "embassy-usb" +version = "0.6.0" +source = "git+https://github.com/embassy-rs/embassy.git?rev=3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a#3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a" +dependencies = [ + "bitflags 2.13.1", + "defmt 1.1.1", + "embassy-futures 0.1.2 (git+https://github.com/embassy-rs/embassy.git?rev=3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a)", + "embassy-net-driver-channel", + "embassy-sync", + "embassy-time", + "embassy-usb-driver", + "embedded-io-async 0.7.0", + "heapless 0.9.3", + "ssmarshal", + "usbd-hid", +] + +[[package]] +name = "embassy-usb-driver" +version = "0.2.2" +source = "git+https://github.com/embassy-rs/embassy.git?rev=3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a#3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a" +dependencies = [ + "defmt 1.1.1", + "embedded-io-async 0.6.1", + "embedded-io-async 0.7.0", +] + +[[package]] +name = "embassy-usb-logger" +version = "0.6.0" +source = "git+https://github.com/embassy-rs/embassy.git?rev=3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a#3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a" +dependencies = [ + "embassy-futures 0.1.2 (git+https://github.com/embassy-rs/embassy.git?rev=3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a)", + "embassy-sync", + "embassy-usb", + "log", +] + +[[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-hal-async" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884" +dependencies = [ + "embedded-hal 1.0.0", +] + +[[package]] +name = "embedded-hal-nb" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fba4268c14288c828995299e59b12babdbe170f6c6d73731af1b4648142e8605" +dependencies = [ + "embedded-hal 1.0.0", + "nb 1.1.0", +] + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "embedded-io" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eb1aa714776b75c7e67e1da744b81a129b3ff919c8712b5e1b32252c1f07cc7" +dependencies = [ + "defmt 1.1.1", +] + +[[package]] +name = "embedded-io-async" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff09972d4073aa8c299395be75161d582e7629cd663171d62af73c8d50dba3f" +dependencies = [ + "embedded-io 0.6.1", +] + +[[package]] +name = "embedded-io-async" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2564b9f813c544241430e147d8bc454815ef9ac998878d30cc3055449f7fd4c0" +dependencies = [ + "defmt 1.1.1", + "embedded-io 0.7.1", +] + +[[package]] +name = "embedded-storage" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21dea9854beb860f3062d10228ce9b976da520a73474aed3171ec276bc0c032" + +[[package]] +name = "embedded-storage-async" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1763775e2323b7d5f0aa6090657f5e21cfa02ede71f5dc40eead06d64dcd15cc" +dependencies = [ + "embedded-storage", +] + +[[package]] +name = "ena" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabffdaee24bd1bf95c5ef7cec31260444317e72ea56c4c91750e8b7ee58d5f1" +dependencies = [ + "log", +] + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "find-msvc-tools" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" + +[[package]] +name = "fixed" +version = "1.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9af2cbf772fa6d1c11358f92ef554cb6b386201210bcf0e91fb7fba8a907fb40" +dependencies = [ + "az", + "bytemuck", + "defmt 1.1.1", + "half", + "typenum", +] + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "futures" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", +] + +[[package]] +name = "futures-io" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" + +[[package]] +name = "futures-macro" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-core", + "futures-macro", + "futures-sink", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generator" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3b854b0e584ead1a33f18b2fcad7cf7be18b3875c78816b753639aa501513ae" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows-link", + "windows-result", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "heapless" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ba4bd83f9415b58b4ed8dc5714c76e626a105be4646c02630ad730ad3b5aa4" +dependencies = [ + "defmt 1.1.1", + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "js-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lalrpop" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba4ebbd48ce411c1d10fb35185f5a51a7bfa3d8b24b4e330d30c9e3a34129501" +dependencies = [ + "ascii-canvas", + "bit-set", + "ena", + "itertools", + "lalrpop-util", + "petgraph", + "pico-args", + "regex", + "regex-syntax", + "sha3", + "string_cache", + "term", + "unicode-xid", + "walkdir", +] + +[[package]] +name = "lalrpop-util" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5baa5e9ff84f1aefd264e6869907646538a52147a755d494517a8007fb48733" +dependencies = [ + "regex-automata", + "rustversion", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[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.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[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 = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "panic-probe" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd402d00b0fb94c5aee000029204a46884b1262e0c443f166d86d2c0747e1a1a" +dependencies = [ + "cortex-m", + "defmt 1.1.1", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pico-args" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pio" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0ba4153cee9585abc451271aa437d9e8defdea8b468d48ba6b8f098cbe03d7f" +dependencies = [ + "pio-core", + "pio-proc", +] + +[[package]] +name = "pio-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61d90fddc3d67f21bbf93683bc461b05d6a29c708caf3ffb79947d7ff7095406" +dependencies = [ + "arrayvec", + "num_enum", + "paste", +] + +[[package]] +name = "pio-parser" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "825266c1eaddf54f636d06eefa4bf3c99d774c14ec46a4a6c6e5128a0f10d205" +dependencies = [ + "lalrpop", + "lalrpop-util", + "pio-core", +] + +[[package]] +name = "pio-proc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed4a76571f5fe51af43cc80ac870fe0c79cc0cdd686b9002a6c4c84bfdd0176b" +dependencies = [ + "codespan-reporting", + "lalrpop-util", + "pio-core", + "pio-parser", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "portable-atomic" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" +dependencies = [ + "critical-section", +] + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[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 2.0.119", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pure-rust-locales" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "869675ad2d7541aea90c6d88c81f46a7f4ea9af8cd0395d38f11a95126998a0d" +dependencies = [ + "defmt 1.1.1", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +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 = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.13.1", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "rgb" +version = "0.8.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" + +[[package]] +name = "rgb-keypad" +version = "0.1.0" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "cyw43", + "cyw43-pio", + "defmt 1.1.1", + "defmt-rtt", + "embassy-executor", + "embassy-futures 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "embassy-rp", + "embassy-sync", + "embassy-time", + "embassy-usb", + "embassy-usb-logger", + "embedded-hal-async", + "heapless 0.8.0", + "log", + "panic-probe", + "portable-atomic", + "static_cell", + "trouble-host", +] + +[[package]] +name = "rp-pac" +version = "7.0.0" +source = "git+https://github.com/embassy-rs/rp-pac?rev=c2e27609b021c444f634673155318977d7f0bdf6#c2e27609b021c444f634673155318977d7f0bdf6" +dependencies = [ + "cortex-m", + "cortex-m-rt", +] + +[[package]] +name = "rp2040-boot2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c92f344f63f950ee36cf4080050e4dce850839b9175da38f9d2ffb69b4dbb21" +dependencies = [ + "crc-any", +] + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sdio" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f7328e9dbad1337ef2d94a8404428a42d77a9916cda7c2e51287a1998da9afb" +dependencies = [ + "aligned", + "block-device-driver", + "embedded-hal 1.0.0", + "embedded-hal-async", +] + +[[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-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "sha2-const-stable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f179d4e11094a893b82fff208f74d448a7512f99f5a0acbd5c679b705f83ed9" + +[[package]] +name = "sha3" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "smart-leds-trait" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7f4441a131924d58da6b83a7ad765c460e64630cce504376c3a87a2558c487f" +dependencies = [ + "rgb", +] + +[[package]] +name = "ssmarshal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3e6ad23b128192ed337dfa4f1b8099ced0c2bf30d61e551b65fda5916dbb850" +dependencies = [ + "encode_unicode", + "serde", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[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 = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "term" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8c27177b12a6399ffc08b98f76f7c9a1f4fe9fc967c784c5a071fa8d93cf7e1" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "thread_local" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "trait-set" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b79e2e9c9ab44c6d7c20d5976961b47e8f49ac199154daa514b77cd1ab536625" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "trouble-host" +version = "0.8.0" +source = "git+https://github.com/embassy-rs/trouble?rev=4eea72088fdbb91326a3ac95160b3154fdff802a#4eea72088fdbb91326a3ac95160b3154fdff802a" +dependencies = [ + "bt-hci", + "defmt 1.1.1", + "embassy-futures 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "embassy-sync", + "embassy-time", + "embedded-io 0.7.1", + "futures", + "heapless 0.9.3", + "static_cell", + "trouble-host-macros", + "zerocopy", +] + +[[package]] +name = "trouble-host-macros" +version = "0.6.0" +source = "git+https://github.com/embassy-rs/trouble?rev=4eea72088fdbb91326a3ac95160b3154fdff802a#4eea72088fdbb91326a3ac95160b3154fdff802a" +dependencies = [ + "convert_case", + "darling", + "proc-macro2", + "quote", + "syn 2.0.119", + "uuid", +] + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "unitrait" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66b59b4e118746027256db08772826e9e0765e93b0bf15d2714a0ac0f940fbe" + +[[package]] +name = "usb-device" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98816b1accafbb09085168b90f27e93d790b4bfa19d883466b5e53315b5f06a6" +dependencies = [ + "heapless 0.8.0", + "portable-atomic", +] + +[[package]] +name = "usbd-hid" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837741765c5aca78159bc475a788b71b731da71373fccef292e61744e76e3ac" +dependencies = [ + "usb-device", + "usbd-hid-macros", +] + +[[package]] +name = "usbd-hid-descriptors" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b297f021719c4308d5d0c61b6c1e7c6b3ba383deba774b49aa5484f996bdb8f1" +dependencies = [ + "bitfield 0.14.0", +] + +[[package]] +name = "usbd-hid-macros" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c2e9e96d9ab0215ed5d01c72acf0582d8c1a9b73d990035da607dfc12e0b53" +dependencies = [ + "byteorder", + "log", + "proc-macro2", + "quote", + "serde", + "syn 3.0.3", + "usbd-hid-descriptors", +] + +[[package]] +name = "uuid" +version = "1.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cefc03fd367c0c6d4305de1b312cf00248c4114f4a0418ce6a6af769e3b0bd9" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vcell" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "volatile-register" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc" +dependencies = [ + "vcell", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "zerocopy" +version = "0.8.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..2e8c0ee --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,54 @@ +[package] +name = "rgb-keypad" +version = "0.1.0" +edition = "2021" +resolver = "2" + +[dependencies] +embassy-executor = { version = "0.10", default-features = false, features = ["platform-cortex-m", "executor-thread", "defmt", "executor-interrupt"] } +embassy-time = { version = "0.5", default-features = false, features = ["defmt", "defmt-timestamp-uptime"] } +embassy-rp = { version = "0.10", features = ["defmt", "unstable-pac", "time-driver", "critical-section-impl", "rp2040"] } +embassy-sync = { version = "0.8", features = ["defmt"] } +embassy-futures = "0.1.2" +embassy-usb = { version = "0.6", default-features = false, features = ["defmt"] } +embassy-usb-logger = { version = "0.6", default-features = false } +log = "0.4" + +# BLE: cyw43 (CYW43439 BT controller on Pico W) + trouble (BLE host / GATT). +# trouble-host 0.8.0 is unreleased on crates.io, so we pull it from git; it must +# match the bt-hci version used by cyw43, hence the embassy/cyw43 git patch +# below pins everything to a known-compatible revision (the same one the +# upstream rp-pico-w example uses). +trouble-host = { git = "https://github.com/embassy-rs/trouble", rev = "4eea72088fdbb91326a3ac95160b3154fdff802a", features = ["derive", "gatt", "defmt", "default-packet-pool-mtu-128"] } +cyw43 = { version = "0.7", features = ["defmt", "firmware-logs", "bluetooth"] } +cyw43-pio = { version = "0.10", features = ["defmt"] } + +defmt = "1.0" +defmt-rtt = "1.0" +panic-probe = { version = "1.0", features = ["print-defmt"] } + +cortex-m = { version = "0.7.6" } +cortex-m-rt = "0.7.0" +static_cell = "2" +portable-atomic = { version = "1.5", features = ["critical-section"] } +heapless = "0.8" + +embedded-hal-async = "1.0" + +[profile.release] +debug = 2 +opt-level = "s" + +# Pin the embassy + cyw43 crates to the same git revision the upstream +# rp-pico-w Bluetooth example validates, so cyw43's bt-hci and trouble's +# bt-hci unify on one version (released crates pull incompatible bt-hci majors). +[patch.crates-io] +cyw43 = { git = "https://github.com/embassy-rs/embassy.git", rev = "3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a" } +cyw43-pio = { git = "https://github.com/embassy-rs/embassy.git", rev = "3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a" } +embassy-rp = { git = "https://github.com/embassy-rs/embassy.git", rev = "3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a" } +embassy-sync = { git = "https://github.com/embassy-rs/embassy.git", rev = "3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a" } +embassy-executor = { git = "https://github.com/embassy-rs/embassy.git", rev = "3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a" } +embassy-time = { git = "https://github.com/embassy-rs/embassy.git", rev = "3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a" } +embassy-usb = { git = "https://github.com/embassy-rs/embassy.git", rev = "3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a" } +embassy-usb-driver = { git = "https://github.com/embassy-rs/embassy.git", rev = "3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a" } +embassy-usb-logger = { git = "https://github.com/embassy-rs/embassy.git", rev = "3cd51e6d8eb6aff8b0d64d9e56a75a538bcfc65a" } diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ea28dba --- /dev/null +++ b/LICENSE @@ -0,0 +1,652 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based on +the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that the work is licensed under this License and how to +view a copy of this License. If the interface presents a list of user +commands or options, such as a menu, a prominent item in the list meets +this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes this unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to the +covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of the source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall be +treated as though they were included in this License, to the extent that +they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not +to sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for or in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey the +Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFICULT, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w` and `show c` should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md new file mode 100644 index 0000000..1e18eef --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# pico-pi-midi (Rust) + +BLE-MIDI keypad firmware for the Raspberry Pi Pico W (RP2040 + CYW43439), written in Rust using [embassy](https://embassy.dev), the [cyw43](https://crates.io/crates/cyw43) driver (Bluetooth), and the [TrouBLE](https://github.com/embassy-rs/trouble) BLE host. + +This is the Rust port of the original C/BTstack firmware (now kept separately as `pico-pi-midi-c`). It talks BLE-MIDI 1.0 to iOS / macOS Core MIDI, turning the Pimoroni Pico RGB Keypad into a 16-pad wireless MIDI controller. + +## What it does + +- **16-pad keypad** (Pimoroni RGB Keypad Base): reads buttons over I²C (GPIO expander @ 0x20) and drives APA102 RGB LEDs over SPI (CS=17, SCK=18, MOSI=19). +- **BLE-MIDI 1.0** GATT server (MIDI Service `03B80E5A-…`), advertised so Core MIDI discovers it as a MIDI device. Pad presses → Note On/Off over Bluetooth; incoming Note On lights the pad. +- **Octave display**: the BOOTSEL button cycles the keypad's note offset by +12 and flashes the active octave as a digit on the 4×4 grid for 500 ms. +- **Onboard LED** heartbeat (driven via the CYW43 chip — the Pico W's LED is on the wireless chip, not an RP2040 pin). +- **USB serial logging** via `embassy-usb-logger` — connect and read with `screen /dev/cu.usbmodem* 115200` to see boot/advertising logs. + +## Roadmap + +This is v1: a BLE-MIDI keypad. The goal is a **flexible MIDI router** — MIDI over USB, BLE, keypad, TRS, and 4 stereo CV outputs, with channels configurable at runtime via an LCD. See the architecture notes (kept in the session memory) for the plan. + +## Building + +Requires Rust stable (≥1.93) and the `thumbv6m-none-eabi` target: + +```sh +rustup target add thumbv6m-none-eabi +cargo build --release +``` + +The CYW43 firmware blobs (`cyw43-firmware/*.bin`) are committed here so no network access is needed at build time. They are redistributable under the permissive binary license in the upstream [`embassy cyw43-firmware`](https://github.com/embassy-rs/embassy/tree/main/cyw43-firmware) directory. + +## Flashing + +```sh +cargo build --release +elf2uf2-rs convert target/thumbv6m-none-eabi/release/rgb-keypad target/uf2/rgb-keypad.uf2 +# hold BOOTSEL + plug in the Pico W, then: +picotool load target/uf2/rgb-keypad.uf2 -x +``` + +## License + +GPL-3.0-or-later (see [`LICENSE`](LICENSE)). + +`src/codec.rs` is a Rust port of the BLE-MIDI 1.0 packet codec from +[rppicomidi/pico-w-ble-midi-lib](https://github.com/rppicomidi/pico-w-ble-midi-lib), +which is MIT-licensed; that attribution is retained in the file header. MIT is +GPL-3.0-compatible, so the repository as a whole is GPL-3.0-or-later while that +file preserves its MIT provenance. diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..1cb5fc7 --- /dev/null +++ b/build.rs @@ -0,0 +1,24 @@ +//! Build script: place memory.x on the linker search path and set up the +//! RP2040 + defmt linker scripts. The CYW43 firmware blobs are committed +//! under `cyw43-firmware/` (no network access needed at build time). + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo::rustc-link-search={}", out.display()); + + println!("cargo::rerun-if-changed=memory.x"); + + println!("cargo::rustc-link-arg-bins=--nmagic"); + println!("cargo::rustc-link-arg-bins=-Tlink.x"); + println!("cargo::rustc-link-arg-bins=-Tlink-rp.x"); + println!("cargo::rustc-link-arg-bins=-Tdefmt.x"); +} diff --git a/cyw43-firmware/43439A0.bin b/cyw43-firmware/43439A0.bin new file mode 100644 index 0000000000000000000000000000000000000000..a05482fe957fbbf27d334e9b5c4fa6c53022f0bf GIT binary patch literal 231077 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

w>?fhn1I6lG&!cVPT)KYe*G<|KS;7b>wI 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 Mw%74+XFL}sCCRg@9XeigZBR{>d7oT<#-;) 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 X^C0l)rvY5Xo3-pBxXe^|a|7~sadC2< literal 0 HcmV?d00001 diff --git a/cyw43-firmware/43439A0_clm.bin b/cyw43-firmware/43439A0_clm.bin new file mode 100644 index 0000000000000000000000000000000000000000..dc4ee02523209e1cc843272ead9d95fd285f2bbf GIT binary patch literal 984 zcmbVKJ#Q015S_KdmtzAVp^YrjAfa5&E87WHn)BJQ51*Zr?@UZOKuEEa#Fi5wMT&&z zC@Cpvsfg0j@B=9M0SJknn&x)*qvH!!Fv%H12%4k>XHMhE^%SE?Zt5j<4Cb$$s zNvc+{v_%=XGKX^xZ=zNeI8c{{y?Stjdr#Bf;w2DJ=~wXv7Sx*reG7cgsHNY;M~zvq z9}@Hkb_n(?Xh0`b$ZB+~r%B~zY(3j7(W+Zdi=H;K+H5Rf(SSt*>I6K+lgR3HSgS+9 zFtAj};E>AUQ%iM8b%*pkv!K@^U>iLixyK_%EE@5mA_hlPju{hEFlJ*hkDM?)Ve`qZ ztv-m&JL0{DL&>HYT<@M|DOJGZw87fR{2S~B_@lXe;r5-oOZS#n?&tBDvJ2O)-?(`z Nw`jEfwa=eK{sQ=&t>FLw literal 0 HcmV?d00001 diff --git a/cyw43-firmware/nvram_rp2040.bin b/cyw43-firmware/nvram_rp2040.bin new file mode 100644 index 0000000000000000000000000000000000000000..7a24de5ec16c277bb0ec68b4665683d7c4cd1683 GIT binary patch literal 742 zcmZ9KO^@Rs42Jz<=GaPxd^Qq?-e>19hq+b+NEQuA*g#X}->*q3ZM8&-@;=xmFZRdR z=k?d8*~A;Y-e8jRV70)+HG*Fky$fu31Dlx-#Y$6xHru{c7KTH=$W_N5ZzvJlwmHB` zRWUJ!ce5vP!n#a3iLoPLaB>W}B$o8xD=ji6mKUTb z-LRTJqG$-gyr@+P269Bfs|uv#BT1zeZN&Bc9lS&vL$HyGa0HX0m!q^Zg^_8BmMG4w zZclYxUQXwxY2mtkO;+C&=5tS&R7>@~{bOB*A()7wB45exsci`{(N?d%mqFAyuzzsm zqhF}LGvhMo=^sgT&GrS83NN^6ngdfC{0r9_ash3}!MeELI&1$&y%7Yx^m@v!+5Wjn zXLU3o?pkG)$a7ikyT*sZj5rth8ob^couIi}rXjq=oH;xf(9vd=J)t~J!QWT4 f(4bPkQURm0QT@L4G8pR68YVlF!9J?n(>uUl1QqMk literal 0 HcmV?d00001 diff --git a/memory.x b/memory.x new file mode 100644 index 0000000..6d065b6 --- /dev/null +++ b/memory.x @@ -0,0 +1,7 @@ +MEMORY { + BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 + FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100 + + /* RP2040: 264K RAM (6K stripe + 256K main, used as one block) */ + RAM : ORIGIN = 0x20000000, LENGTH = 264K +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..eaf13e5 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "stable" +targets = ["thumbv6m-none-eabi"] diff --git a/src/codec.rs b/src/codec.rs new file mode 100644 index 0000000..a93909d --- /dev/null +++ b/src/codec.rs @@ -0,0 +1,719 @@ +//! BLE-MIDI 1.0 packet codec — faithful Rust port of `ble_midi_pkt_codec.c` +//! (rppicomidi, MIT). Encodes a MIDI 1.0 byte stream into BLE-MIDI packets and +//! decodes BLE-MIDI packets back into timestamped MIDI messages, with full +//! support for system-exclusive, running-status, and system real-time. +//! +//! Pure `no_std` logic (no allocator, no HAL/BLE deps) so it is independent of +//! the rest of the firmware and could be unit-tested on host. +//! +//! Spec: https://midi.org/specifications/midi-transports-specifications/midi-over-bluetooth-low-energy-ble-midi +//! +//! --- +//! +//! This file is a Rust port of MIT-licensed code by rppicomidi +//! (https://github.com/rppicomidi/pico-w-ble-midi-lib). The original MIT +//! notice and permission are retained below per its terms. The port is +//! contributed to this project under GPL-3.0-or-later (see the repository +//! LICENSE); MIT is GPL-3.0-compatible so both apply here. +//! +//! Original MIT license — Copyright (c) 2023 rppicomidi +//! +//! Permission is hereby granted, free of charge, to any person obtaining a copy +//! of this software and associated documentation files (the "Software"), to deal +//! in the Software without restriction, including without limitation the rights +//! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +//! copies of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be included in +//! all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +//! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +//! THE SOFTWARE. +//! +//! SPDX-License-Identifier: GPL-3.0-or-later + +#![allow(clippy::needless_range_loop)] + +/// Max BLE-MIDI packet payload. Without LE Data Length Extension the ATT default +/// MTU is 23, giving a 20-byte MIDI payload (MTU - 3 ATT header). +pub const MAX_BLE_MIDI_PACKET: usize = 20; + +// Flag bits stored in the upper bits of `BleMidiMessage::nbytes`. +const IS_START_SYSEX: u8 = 0x80; +const IS_SYSEX: u8 = 0x20; +const IS_REAL_TIME: u8 = 0x10; +const IS_CHANNEL: u8 = 0x08; +const NBYTES_MASK: u8 = 0x03; + +const fn header(ts: u16) -> u8 { + 0x80 | ((ts >> 7) & 0x3F) as u8 +} +const fn timestamp_low(ts: u16) -> u8 { + 0x80 | (ts & 0x7F) as u8 +} + +/// A timestamped fragment of a MIDI message (the decode output unit). +/// +/// The upper bits of `nbytes` encode what kind of message is in `msg_bytes`; +/// the low 2 bits hold the number of valid bytes. +#[derive(Clone, Copy, Default)] +pub struct BleMidiMessage { + pub nbytes: u8, + pub msg_bytes: [u8; 3], + pub timestamp_ms: u16, +} + +impl BleMidiMessage { + /// Number of valid bytes in `msg_bytes`. + pub fn byte_count(&self) -> u8 { + self.nbytes & NBYTES_MASK + } +} + +/// A BLE-MIDI 1.0 data packet (the encode output / notify payload). +#[derive(Clone, Copy)] +pub struct BleMidiPacket { + /// Number of valid bytes in `pkt`. + pub nbytes: usize, + pub pkt: [u8; MAX_BLE_MIDI_PACKET], +} + +impl Default for BleMidiPacket { + fn default() -> Self { + Self { + nbytes: 0, + pkt: [0; MAX_BLE_MIDI_PACKET], + } + } +} + +/// Single-producer/single-consumer ring of fixed-size `Copy` elements. +struct Ring { + buf: [T; N], + head: usize, + tail: usize, + len: usize, +} + +impl Ring { + fn new() -> Self { + Self { + buf: core::array::from_fn(|_| T::default()), + head: 0, + tail: 0, + len: 0, + } + } + fn push(&mut self, v: T) -> bool { + if self.len < N { + self.buf[self.tail] = v; + self.tail = (self.tail + 1) % N; + self.len += 1; + true + } else { + false + } + } + fn pop(&mut self) -> Option { + if self.len > 0 { + let v = self.buf[self.head]; + self.head = (self.head + 1) % N; + self.len -= 1; + Some(v) + } else { + None + } + } + fn len(&self) -> usize { + self.len + } +} + +/// Encoder state for one connection (mirrors `to_ble_midi_stream_t`). +#[derive(Clone, Copy, Default)] +struct ToBleStream { + mes: BleMidiMessage, + next_msg_byte_idx: u16, + previous_timestamp: u16, + running_status: u8, + npending_rt: u8, + pending_rt_status: [u8; 4], + pending_rt_timestamp: [u16; 4], + pending_ble_pkt: BleMidiPacket, + pending_ble_midi_pkt_running_status: u8, + pending_ble_midi_pkt_prev_status: u8, +} + +/// Per-connection BLE-MIDI codec context. +pub struct Codec { + to_ble: Ring, + from_ble: Ring, + stream: ToBleStream, + ble_mtu: usize, +} + +impl Codec { + pub fn new() -> Self { + let mut c = Self { + to_ble: Ring::new(), + from_ble: Ring::new(), + stream: ToBleStream::default(), + ble_mtu: MAX_BLE_MIDI_PACKET, + }; + c.reset_stream(); + c + } + + pub fn set_mtu(&mut self, ble_mtu: usize) { + self.ble_mtu = ble_mtu; + } + + /// Update MTU, capped at the packet buffer size (matches the C `update_mtu` + /// which does `MIN(ble_mtu, sizeof(pending_ble_pkt.pkt))`). + pub fn update_mtu(&mut self, ble_mtu: usize) { + self.ble_mtu = core::cmp::min(ble_mtu, MAX_BLE_MIDI_PACKET); + } + + pub fn get_mtu(&self) -> usize { + self.ble_mtu + } + + fn reset_stream(&mut self) { + let s = &mut self.stream; + s.next_msg_byte_idx = 0; + s.previous_timestamp = 0xffff; // illegal sentinel + s.running_status = 0; + s.mes.nbytes = 0; + s.mes.timestamp_ms = 0xffff; + s.npending_rt = 0; + s.pending_ble_pkt.nbytes = 0; + s.pending_ble_midi_pkt_running_status = 0; + s.pending_ble_midi_pkt_prev_status = 0; + } + + // ---- encode (MIDI stream -> BLE-MIDI packets) -------------------------- + + /// Push a MIDI 1.0 byte stream to be encoded. Returns `(bytes_pushed, + /// ready_to_send)` where `ready_to_send` is true if at least one complete + /// BLE-MIDI packet is available to pop and notify. + pub fn push_midi(&mut self, midi_stream: &[u8], now_ms: u16) -> (u16, bool) { + let timestamp = now_ms & 0x1FFF; + let mut bytes_pushed: u16 = 0; + let mut ready_to_send = false; + + let s = &mut self.stream; + let to_ble = &mut self.to_ble; + let ble_mtu = self.ble_mtu; + + while (bytes_pushed as usize) < midi_stream.len() && s.pending_ble_pkt.nbytes < ble_mtu { + let ms_byte = midi_stream[bytes_pushed as usize]; + + if ms_byte & 0x80 != 0 { + // ---- status byte ---- + if ms_byte == 0xF0 { + s.previous_timestamp = timestamp; + s.mes.nbytes = IS_START_SYSEX | IS_SYSEX | 1; + bytes_pushed += 1; + s.running_status = 0; + s.mes.msg_bytes[0] = ms_byte; + s.next_msg_byte_idx = 1; + } else if ms_byte == 0xF7 { + if s.mes.nbytes & IS_SYSEX == 0 { + // EOX without being in a sysex — stream error + discard_stream(s); + return (bytes_pushed, ready_to_send); + } + if s.next_msg_byte_idx != 0 { + encode_bt_pkt(s, to_ble, ble_mtu); + } + s.mes.timestamp_ms = s.previous_timestamp; + s.mes.msg_bytes[0] = ms_byte; + s.mes.nbytes = 1; + bytes_pushed += 1; + encode_full_bt_pkt(s, to_ble, ble_mtu); + s.running_status = 0; + } else if ms_byte >= 0xF8 { + // system real-time (may occur inside sysex) + if s.mes.nbytes & IS_SYSEX != 0 && (s.mes.nbytes & NBYTES_MASK) > 0 { + encode_bt_pkt(s, to_ble, ble_mtu); + s.next_msg_byte_idx = 0; + s.mes.nbytes = IS_SYSEX; + } + if s.npending_rt < s.pending_rt_status.len() as u8 { + let i = s.npending_rt as usize; + s.pending_rt_status[i] = ms_byte; + s.pending_rt_timestamp[i] = timestamp; + s.npending_rt += 1; + } + bytes_pushed += 1; + if s.npending_rt >= s.pending_rt_status.len() as u8 { + discard_stream(s); + return (bytes_pushed, ready_to_send); + } + flush_rt_midi_to_pkt(s, to_ble, ble_mtu); + } else if ms_byte > 0xF0 { + // system common + s.mes.msg_bytes[0] = ms_byte; + s.previous_timestamp = timestamp; + s.mes.timestamp_ms = timestamp; + bytes_pushed += 1; + s.running_status = 0; + if ms_byte == 0xF1 || ms_byte == 0xF3 { + s.mes.nbytes = 2; + s.next_msg_byte_idx = 1; + } else if ms_byte == 0xF2 { + s.mes.nbytes = 3; + s.next_msg_byte_idx = 1; + } else { + s.mes.nbytes = 1; + encode_full_bt_pkt(s, to_ble, ble_mtu); + s.mes.nbytes = 0; + } + } else { + // channel message + s.running_status = ms_byte; + s.mes.msg_bytes[0] = ms_byte; + s.previous_timestamp = timestamp; + s.mes.timestamp_ms = timestamp; + bytes_pushed += 1; + s.next_msg_byte_idx = 1; + s.mes.nbytes = IS_CHANNEL | 3; + let chan_msg_base = (ms_byte >> 4) & 0xF; + if chan_msg_base == 0xC || chan_msg_base == 0xD { + s.mes.nbytes = IS_CHANNEL | 2; + } + } + } else { + // ---- data byte ---- + if s.mes.nbytes & IS_SYSEX != 0 { + s.mes.msg_bytes[s.next_msg_byte_idx as usize] = ms_byte; + s.next_msg_byte_idx += 1; + s.mes.nbytes += 1; + bytes_pushed += 1; + if s.next_msg_byte_idx >= 3 { + encode_full_bt_pkt(s, to_ble, ble_mtu); + s.mes.timestamp_ms = s.previous_timestamp; + s.mes.nbytes = IS_SYSEX; // length 0, still in sysex + } + } else if s.mes.nbytes & IS_CHANNEL != 0 && s.next_msg_byte_idx == 0 { + if s.running_status == 0 || s.running_status != s.mes.msg_bytes[0] { + discard_stream(s); + return (bytes_pushed, ready_to_send); + } else { + s.mes.msg_bytes[1] = ms_byte; + s.mes.timestamp_ms = timestamp; + bytes_pushed += 1; + s.next_msg_byte_idx = 2; + if (s.mes.nbytes & NBYTES_MASK) as u16 == s.next_msg_byte_idx { + encode_full_bt_pkt(s, to_ble, ble_mtu); + } + } + } else { + let len = s.mes.nbytes & NBYTES_MASK; + if (s.next_msg_byte_idx as u8) < len { + s.mes.msg_bytes[s.next_msg_byte_idx as usize] = ms_byte; + s.next_msg_byte_idx += 1; + bytes_pushed += 1; + if len == s.next_msg_byte_idx as u8 { + encode_full_bt_pkt(s, to_ble, ble_mtu); + if s.mes.nbytes & IS_CHANNEL == 0 { + s.mes.nbytes = 0; + } + } + } else { + discard_stream(s); + return (bytes_pushed, ready_to_send); + } + } + } + } + + if bytes_pushed > 0 && self.stream.pending_ble_pkt.nbytes > 0 { + self.to_ble.push(self.stream.pending_ble_pkt); + self.stream.pending_ble_pkt.nbytes = 0; + self.stream.pending_ble_midi_pkt_running_status = 0; + self.stream.pending_ble_midi_pkt_prev_status = 0; + ready_to_send = true; + } + (bytes_pushed, ready_to_send) + } + + /// Pop the next encoded BLE-MIDI packet to send (notify). + pub fn ble_pkt_pop(&mut self) -> Option { + self.to_ble.pop() + } + + /// True if at least one encoded packet is available to send. + pub fn ble_pkt_available(&self) -> bool { + self.to_ble.len() > 0 + } + + // ---- decode (BLE-MIDI packet -> timestamped MIDI messages) ------------ + + /// Decode a BLE-MIDI packet, pushing each resulting timestamped message to + /// the from-BLE ring. Returns the number of packet bytes consumed. + pub fn decode_push(&mut self, pkt: &[u8]) -> usize { + let nbytes = pkt.len(); + if nbytes < 2 { + return 0; + } + // header byte must be present + if (pkt[0] & 0xc0) != 0x80 { + return 0; + } + let mut timestamp: u16 = ((pkt[0] & 0x3f) as u16) << 7; + let mut prev_lsb: u8 = 0; + let mut ndecoded: usize = 1; + let mut running_status: u8 = 0; + let mut running_status_nbytes: u8 = 0; + let mut mes: BleMidiMessage = BleMidiMessage::default(); + + // sysex continuation data at the start of the packet? + if (pkt[ndecoded] & 0x80) == 0 { + mes.nbytes = IS_SYSEX; + if !decode_sysex_data(pkt, &mut mes, &mut self.from_ble, &mut ndecoded, 0) { + return ndecoded; + } + } + + while ndecoded < nbytes { + if ndecoded + 1 < nbytes { + if pkt[ndecoded] & 0x80 != 0 && pkt[ndecoded + 1] & 0x80 != 0 { + // timestamp + status byte (or rt inside sysex, or EOX) + if mes.nbytes & IS_SYSEX == 0 { + // not in sysex: timestamp + status + mes.timestamp_ms = timestamp_decode(&mut timestamp, pkt[ndecoded], &mut prev_lsb); + ndecoded += 1; + mes.msg_bytes[0] = pkt[ndecoded]; + ndecoded += 1; + if mes.msg_bytes[0] >= 0xF8 { + // real-time + mes.nbytes = IS_REAL_TIME + 1; + if !from_ble_push(&mut self.from_ble, &mes) { + return ndecoded; + } + } else if mes.msg_bytes[0] > 0xF0 && mes.msg_bytes[0] <= 0xF7 { + // system common + if mes.msg_bytes[0] == 0xF1 || mes.msg_bytes[0] == 0xF3 { + mes.nbytes = 2; + } else if mes.msg_bytes[0] == 0xF2 { + mes.nbytes = 3; + } else { + mes.nbytes = 1; + } + if ndecoded + (mes.nbytes as usize) - 1 <= nbytes { + for idx in 1..mes.nbytes as usize { + mes.msg_bytes[idx] = pkt[ndecoded]; + ndecoded += 1; + } + if !from_ble_push(&mut self.from_ble, &mes) { + ndecoded -= 1; + return ndecoded; + } + } else { + return ndecoded; + } + } else if mes.msg_bytes[0] < 0xF0 { + // channel message + running_status = mes.msg_bytes[0]; + let stat = (mes.msg_bytes[0] >> 4) & 0xF; + if stat == 0xC || stat == 0xD { + mes.nbytes = 2; + running_status_nbytes = 2; + } else { + running_status_nbytes = 3; + } + if (running_status_nbytes as usize) + ndecoded - 1 <= nbytes { + for idx in 1..running_status_nbytes as usize { + mes.msg_bytes[idx] = pkt[ndecoded]; + ndecoded += 1; + } + mes.nbytes = running_status_nbytes | IS_CHANNEL; + if !from_ble_push(&mut self.from_ble, &mes) { + ndecoded -= 1; + return ndecoded; + } + } else { + // not enough data; leave for next? C returns. + return ndecoded; + } + } else { + // 0xF0: start of sysex + mes.nbytes = IS_SYSEX + 1; + if !decode_sysex_data(pkt, &mut mes, &mut self.from_ble, &mut ndecoded, 1) { + return ndecoded; + } + } + } else if pkt[ndecoded + 1] >= 0xF8 { + // real-time timestamp+status within a sysex + let bytecount = mes.nbytes & NBYTES_MASK; + if bytecount > 0 { + if !from_ble_push(&mut self.from_ble, &mes) { + ndecoded -= bytecount as usize; + return ndecoded; + } + } + let mut mes_rt = BleMidiMessage::default(); + mes_rt.nbytes = IS_REAL_TIME + 1; + mes_rt.timestamp_ms = timestamp_decode(&mut timestamp, pkt[ndecoded], &mut prev_lsb); + ndecoded += 1; + mes_rt.msg_bytes[0] = pkt[ndecoded]; + ndecoded += 1; + if !from_ble_push(&mut self.from_ble, &mes_rt) { + ndecoded -= 2; + return ndecoded; + } + // continue parsing sysex data after the rt interleave + mes.nbytes = IS_SYSEX; + let mut idx: u8 = 0; + while ndecoded < nbytes && (pkt[ndecoded] & 0x80) == 0 { + mes.msg_bytes[idx as usize] = pkt[ndecoded]; + ndecoded += 1; + mes.nbytes += 1; + idx += 1; + if idx == 3 { + if !from_ble_push(&mut self.from_ble, &mes) { + ndecoded -= 3; + return ndecoded; + } + idx = 0; + mes.nbytes = IS_SYSEX; + } + } + let bytecount = mes.nbytes & NBYTES_MASK; + if bytecount > 0 { + if !from_ble_push(&mut self.from_ble, &mes) { + ndecoded -= bytecount as usize; + return ndecoded; + } + mes.nbytes = IS_SYSEX; + } + } else if pkt[ndecoded + 1] == 0xF7 { + // sysex ending: flush pending data; EOX handled next iter + let bytecount = mes.nbytes & NBYTES_MASK; + if bytecount > 0 { + if !from_ble_push(&mut self.from_ble, &mes) { + ndecoded -= bytecount as usize; + return ndecoded; + } + } + mes.nbytes = 0; // clear is_sysex flag + } + // else: timestamp+status where status is not handled — fall through + } else if pkt[ndecoded] & 0x80 != 0 && pkt[ndecoded + 1] & 0x80 == 0 { + // running-status channel message: timestamp + data byte(s) + if running_status != 0 && ndecoded + running_status_nbytes as usize <= nbytes { + mes.timestamp_ms = timestamp_decode(&mut timestamp, pkt[ndecoded], &mut prev_lsb); + ndecoded += 1; + mes.msg_bytes[0] = running_status; + mes.nbytes = running_status_nbytes | IS_CHANNEL; + for idx in 1..running_status_nbytes as usize { + mes.msg_bytes[idx] = pkt[ndecoded]; + ndecoded += 1; + } + if !from_ble_push(&mut self.from_ble, &mes) { + ndecoded -= running_status_nbytes as usize; + return ndecoded; + } + } else { + return ndecoded; + } + } else if pkt[ndecoded] & 0x80 == 0 && pkt[ndecoded + 1] & 0x80 == 0 { + // running status same as previous, same timestamp, new data + if running_status == mes.msg_bytes[0] + && mes.nbytes & IS_CHANNEL != 0 + && ndecoded + running_status_nbytes as usize - 1 <= nbytes + { + for idx in 1..running_status_nbytes as usize { + mes.msg_bytes[idx] = pkt[ndecoded]; + ndecoded += 1; + } + if !from_ble_push(&mut self.from_ble, &mes) { + ndecoded -= (running_status_nbytes as usize) - 1; + return ndecoded; + } + } else { + return ndecoded; + } + } + } else { + // only one byte left and not a timestamp+status pair — stop + break; + } + } + ndecoded + } + + /// Pop the next decoded timestamped MIDI message. + pub fn pop_midi(&mut self) -> Option { + self.from_ble.pop() + } +} + +impl Default for Codec { + fn default() -> Self { + Self::new() + } +} + +// ---- free encode helpers (take stream + ring + mtu to avoid overlapping +// borrows of `self`) ---------------------------------------------------- + +fn discard_stream(s: &mut ToBleStream) { + s.next_msg_byte_idx = 0; + s.pending_ble_pkt.nbytes = 0; + s.previous_timestamp = 0xffff; + s.running_status = 0; + s.mes.nbytes = 0; + s.mes.timestamp_ms = 0xffff; + s.npending_rt = 0; +} + +fn add_header_if_needed(pkt: &mut BleMidiPacket, timestamp: u16) { + if pkt.nbytes == 0 { + pkt.pkt[pkt.nbytes] = header(timestamp); + pkt.nbytes += 1; + } +} + +fn flush_rt_midi_to_pkt(s: &mut ToBleStream, to_ble: &mut Ring, ble_mtu: usize) { + for idx in 0..s.npending_rt as usize { + // make room for a 2-byte (timestamp + status) real-time entry + if s.pending_ble_pkt.nbytes + 2 >= ble_mtu { + to_ble.push(s.pending_ble_pkt); + s.pending_ble_pkt.nbytes = 0; + s.pending_ble_midi_pkt_running_status = 0; + s.pending_ble_midi_pkt_prev_status = 0; + } + if s.pending_ble_pkt.nbytes == 0 { + s.pending_ble_pkt.pkt[s.pending_ble_pkt.nbytes] = header(s.pending_rt_timestamp[0]); + s.pending_ble_pkt.nbytes += 1; + } + s.pending_ble_pkt.pkt[s.pending_ble_pkt.nbytes] = timestamp_low(s.pending_rt_timestamp[idx]); + s.pending_ble_pkt.nbytes += 1; + s.pending_ble_pkt.pkt[s.pending_ble_pkt.nbytes] = s.pending_rt_status[idx]; + s.pending_ble_pkt.nbytes += 1; + s.pending_ble_midi_pkt_prev_status = s.pending_rt_status[idx]; + } + s.npending_rt = 0; +} + +fn encode_bt_pkt(s: &mut ToBleStream, to_ble: &mut Ring, ble_mtu: usize) { + let nbytes = s.mes.nbytes & NBYTES_MASK; + if nbytes == 0 { + return; + } + if s.mes.nbytes & IS_REAL_TIME != IS_REAL_TIME { + flush_rt_midi_to_pkt(s, to_ble, ble_mtu); + } + + // channel messages may omit the status byte if running status matches + let requires_byte0 = (s.mes.nbytes & IS_CHANNEL != IS_CHANNEL) + || (s.pending_ble_midi_pkt_running_status != s.mes.msg_bytes[0]); + + // most messages need a timestamp prefix + let mut needs_timestamp = (s.mes.nbytes & (IS_SYSEX | IS_START_SYSEX) != IS_SYSEX) + && (requires_byte0 || s.previous_timestamp != s.mes.timestamp_ms); + if !needs_timestamp + && s.mes.nbytes & IS_CHANNEL == IS_CHANNEL + && s.pending_ble_midi_pkt_prev_status != s.pending_ble_midi_pkt_running_status + { + needs_timestamp = true; + } + + let first_byte_idx: u8 = if requires_byte0 { 0 } else { 1 }; + let total_bytes = nbytes as usize + (if needs_timestamp { 1 } else { 0 }) - first_byte_idx as usize; + + if s.pending_ble_pkt.nbytes + total_bytes >= ble_mtu { + to_ble.push(s.pending_ble_pkt); + s.pending_ble_pkt.nbytes = 0; + s.pending_ble_midi_pkt_running_status = 0; + s.pending_ble_midi_pkt_prev_status = 0; + needs_timestamp = true; + // requires_byte0 is set true in the C here but only used above; keep + // first_byte_idx consistent by re-evaluating would change total_bytes. + // The C sets requires_byte0=true after the flush but does not recompute + // first_byte_idx/total_bytes, so neither do we. + } + + if s.mes.nbytes & IS_CHANNEL != 0 { + s.pending_ble_midi_pkt_running_status = s.mes.msg_bytes[0]; + } + if s.mes.msg_bytes[0] & 0x80 != 0 { + s.pending_ble_midi_pkt_prev_status = s.mes.msg_bytes[0]; + } + + add_header_if_needed(&mut s.pending_ble_pkt, s.mes.timestamp_ms); + if needs_timestamp { + s.pending_ble_pkt.pkt[s.pending_ble_pkt.nbytes] = timestamp_low(s.mes.timestamp_ms); + s.pending_ble_pkt.nbytes += 1; + } + for idx in first_byte_idx as usize..nbytes as usize { + s.pending_ble_pkt.pkt[s.pending_ble_pkt.nbytes] = s.mes.msg_bytes[idx]; + s.pending_ble_pkt.nbytes += 1; + } +} + +fn encode_full_bt_pkt(s: &mut ToBleStream, to_ble: &mut Ring, ble_mtu: usize) { + encode_bt_pkt(s, to_ble, ble_mtu); + s.next_msg_byte_idx = 0; +} + +// ---- free decode helpers -------------------------------------------------- + +fn from_ble_push(ring: &mut Ring, mes: &BleMidiMessage) -> bool { + ring.push(*mes) +} + +/// Reconstruct a 14-bit timestamp from the accumulated 13-bit MSB and a 7-bit +/// LSB, incrementing the MSB when the LSB wraps backward. +fn timestamp_decode(msb: &mut u16, lsb_byte: u8, prev_lsb: &mut u8) -> u16 { + let lsb = lsb_byte; + if *prev_lsb > lsb { + *msb += 1; + } + *prev_lsb = lsb; + (*msb << 7) | (lsb as u16 & 0x7F) +} + +/// Decode sysex data bytes (MSB clear) starting at `start_idx` into `mes`, +/// flushing 3-byte chunks to the ring. Returns false if the ring is full. +fn decode_sysex_data( + pkt: &[u8], + mes: &mut BleMidiMessage, + ring: &mut Ring, + ndecoded: &mut usize, + start_idx: u8, +) -> bool { + let mut idx = start_idx; + while *ndecoded < pkt.len() && (pkt[*ndecoded] & 0x80) == 0 { + mes.msg_bytes[idx as usize] = pkt[*ndecoded]; + *ndecoded += 1; + mes.nbytes += 1; + idx += 1; + if idx == 3 { + if !ring.push(*mes) { + *ndecoded -= 3; + return false; + } + idx = 0; + mes.nbytes = IS_SYSEX; + } + } + let bytecount = mes.nbytes & NBYTES_MASK; + if bytecount > 0 { + if !ring.push(*mes) { + *ndecoded -= bytecount as usize; + return false; + } + // keep tracking an unterminated sysex message + mes.nbytes = IS_SYSEX; + } + true +} diff --git a/src/keypad.rs b/src/keypad.rs new file mode 100644 index 0000000..c86d40e --- /dev/null +++ b/src/keypad.rs @@ -0,0 +1,95 @@ +//! Pimoroni Pico RGB Keypad driver — Rust port of `pico_rgb_keypad.cpp`. +//! +//! Copyright (C) 2026 Nils — SPDX-License-Identifier: GPL-3.0-or-later +//! This file is part of pico-pi-midi. See LICENSE for the full notice. +//! +//! Hardware: 16 APA102-style RGB LEDs driven over SPI (CS=17, SCK=18, MOSI=19) +//! and a 16-bit button state read over I2C from a GPIO expander at address +//! 0x20 (SDA=4, SCL=5). Reading register 0 returns two bytes (little-endian +//! button bitmap); the C driver inverts them so a pressed pad reads as 1. +//! +//! APA102 frame: 4 start bytes (0x00), then per-LED 4 bytes +//! (0xE0|brightness, B, G, R), then a 4-byte end frame (0xFF). Total 72 bytes, +//! matching the C buffer layout `[start(4)][led0(4)]..[led15(4)][end(4)]`. + +use embassy_rp::gpio::Output; +use embassy_rp::i2c::{self, Blocking as I2cBlocking}; +use embassy_rp::spi::{self, Blocking as SpiBlocking}; + +pub const NUM_PADS: usize = 16; +const KEYPAD_ADDRESS: u8 = 0x20; + +/// 5-bit global brightness (0..=31), stored in each LED's frame header. +/// `set_brightness(1.0)` in the C driver -> 0b11111. +const BRIGHTNESS: u8 = 0b11111; + +pub struct RgbKeypad { + cs: Output<'static>, + spi: spi::Spi<'static, SpiBlocking>, + i2c: i2c::I2c<'static, I2cBlocking>, + /// APA102 frame buffer: 4 start + 16*4 LED + 4 end = 72 bytes. + buffer: [u8; 4 + NUM_PADS * 4 + 4], +} + +impl RgbKeypad { + /// Build the driver from already-constructed SPI (tx-only), I2C and CS + /// output. The peripherals are consumed in `main` and the resulting driver + /// is `'static`, so it can be moved into an embassy task. + pub fn new( + cs: Output<'static>, + spi: spi::Spi<'static, SpiBlocking>, + i2c: i2c::I2c<'static, I2cBlocking>, + ) -> Self { + let mut s = Self { + cs, + spi, + i2c, + buffer: [0; 4 + NUM_PADS * 4 + 4], + }; + + // APA102 start frame: 0x00 0x00 0x00 0x00. + s.buffer[0..4].copy_from_slice(&[0x00, 0x00, 0x00, 0x00]); + // Per-LED brightness header (0xE0 | 5-bit brightness); RGB stays 0 (off). + for i in 0..NUM_PADS { + s.buffer[4 + i * 4] = 0xE0 | BRIGHTNESS; + } + // End frame: 0xFF 0xFF 0xFF 0xFF (>= 1 bit per 2 LEDs). + let end = 4 + NUM_PADS * 4; + s.buffer[end..end + 4].copy_from_slice(&[0xFF, 0xFF, 0xFF, 0xFF]); + + s.update(); + s + } + + /// Push the frame buffer to the LEDs over SPI. + pub fn update(&mut self) { + self.cs.set_low(); + let _ = self.spi.blocking_write(&self.buffer); + self.cs.set_high(); + } + + /// Set an LED by linear index `i` (0..16) to `(r, g, b)`. Mirrors the C + /// `illuminate(i, r, g, b)`: frame byte order is brightness,B,G,R. + pub fn illuminate(&mut self, i: usize, r: u8, g: u8, b: u8) { + if i >= NUM_PADS { + return; + } + let off = 4 + i * 4; + // [0]=brightness (already set), [1]=B, [2]=G, [3]=R + self.buffer[off + 1] = b; + self.buffer[off + 2] = g; + self.buffer[off + 3] = r; + } + + /// Read the 16-bit button state bitmap. Bit `i` is 1 when pad `i` is + /// pressed (the raw expander value is inverted, matching the C driver). + pub fn get_button_states(&mut self) -> u16 { + let mut buf = [0u8; 2]; + // Write register 0, then read 2 bytes (repeated-start, like the C code). + let _ = self + .i2c + .blocking_write_read(KEYPAD_ADDRESS, &[0x00], &mut buf); + // C returns ~((buf[0]) | (buf[1] << 8)) + !(u16::from(buf[0]) | (u16::from(buf[1]) << 8)) + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..1508209 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,394 @@ +//! RGB MIDI Keypad — Rust port for Raspberry Pi Pico W (RP2040 + CYW43439). +//! +//! Copyright (C) 2026 Nils +//! +//! This program is free software: you can redistribute it and/or modify +//! it under the terms of the GNU General Public License as published by +//! the Free Software Foundation, either version 3 of the License, or +//! (at your option) any later version. +//! +//! This program is distributed in the hope that it will be useful, +//! but WITHOUT ANY WARRANTY; without even the implied warranty of +//! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//! GNU General Public License for more details. +//! +//! You should have received a copy of the GNU General Public License +//! along with this program. If not, see . +//! +//! SPDX-License-Identifier: GPL-3.0-or-later +//! +//! Wires the Pimoroni RGB Keypad (16 pads + APA102 LEDs) to a BLE-MIDI 1.0 +//! GATT service so pad presses send Note On/Off over Bluetooth MIDI and +//! incoming Note messages light the pads. +//! +//! Concurrency: +//! - `cyw43_task` : drives the CYW43 controller (WiFi+BT firmware). +//! - `led_task` : blinks the onboard LED as a heartbeat. +//! - `keypad_task` : polls pads, sends Note On/Off on `OUTBOUND`, lights pads +//! from `INBOUND`. +//! - main (BLE) : runs the trouble host; advertises, accepts a connection, +//! and pumps MIDI in both directions through the codec. + +#![no_std] +#![no_main] + +use cyw43::Cyw43439; +use cyw43_pio::PioSpi; +use defmt::*; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_futures::select::{select, Either}; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::peripherals::{DMA_CH0, DMA_CH1, PIO0, USB}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::usb::{Driver, InterruptHandler as UsbInterruptHandler}; +use embassy_rp::{bind_interrupts, dma}; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::channel::{Channel, Receiver, Sender}; +use embassy_time::{Instant, Timer}; +use heapless::Vec; +use static_cell::StaticCell; +use trouble_host::prelude::*; +use {defmt_rtt as _, panic_probe as _}; + +mod codec; +mod keypad; +mod midi_keypad; +mod octave; + +use keypad::RgbKeypad; +use midi_keypad::{MidiKeypad, MidiOut}; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; + DMA_IRQ_0 => dma::InterruptHandler, dma::InterruptHandler; + USBCTRL_IRQ => UsbInterruptHandler; +}); + +#[embassy_executor::task] +async fn logger_task(driver: Driver<'static, USB>) { + embassy_usb_logger::run!(1024, log::LevelFilter::Info, driver); +} + +/// Max number of simultaneous BLE connections. +const CONNECTIONS_MAX: usize = 1; +/// Max L2CAP channels ( signalling + ATT ). +const L2CAP_CHANNELS_MAX: usize = 2; + +/// Keypad -> BLE: each entry is a 3-byte Note On/Off message. +static OUTBOUND: Channel = Channel::new(); +/// BLE -> keypad: each entry is a 3-byte channel message to light a pad. +static INBOUND: Channel = Channel::new(); + +// ---- BLE-MIDI 1.0 GATT service definition -------------------------------- +// +// Service: 03B80E5A-EDE8-4B33-A751-6CE34EC4C700 (MIDI Service) +// Char: 7772E5DB-3868-4112-A1A9-F2669D106BF3 (MIDI I/O) +// Props: read | write-without-response | notify +#[gatt_server] +struct Server { + midi_service: MidiService, +} + +#[gatt_service(uuid = "03b80e5a-ede8-4b33-a751-6ce34ec4c700")] +struct MidiService { + #[characteristic(uuid = "7772e5db-3868-4112-a1a9-f2669d106bf3", read, write_without_response, notify)] + midi_io: (), +} + +// ---- CYW43 runner + heartbeat tasks -------------------------------------- + +#[embassy_executor::task] +async fn cyw43_task( + runner: cyw43::Runner< + 'static, + cyw43::SpiBus, PioSpi<'static, PIO0, 0>>, + Cyw43439, + >, +) { + runner.run().await +} + +#[embassy_executor::task] +async fn led_task(mut control: cyw43::Control<'static>) { + loop { + control.gpio_set(0, true).await; + Timer::after_millis(80).await; + control.gpio_set(0, false).await; + Timer::after_millis(920).await; + } +} + +/// Poll the keypad: send Note On/Off for changed pads, light pads from inbound +/// MIDI, repeat every 10ms (matching the C main loop cadence). +#[embassy_executor::task] +async fn keypad_task(mut keypad: RgbKeypad, mut bootsel: embassy_rp::Peri<'static, embassy_rp::peripherals::BOOTSEL>) { + // Startup blink: all LEDs white, then off — a visible "main is alive and + // the keypad/SPI driver works" signal that does not depend on the radio. + for i in 0..keypad::NUM_PADS { + keypad.illuminate(i, 64, 64, 64); + } + keypad.update(); + Timer::after_millis(400).await; + + let mut midi = MidiKeypad::new(&mut keypad, 48); // note offset C3; paints underlay + let mut out_buf: Vec = Vec::new(); + let outbound = OUTBOUND.sender(); + let inbound = INBOUND.receiver(); + let mut note_offset: u8 = 48; + let mut bootsel_held = false; + + loop { + // 1. read pads -> Note On/Off. Each message is routed two ways, matching + // the C firmware's connect_ports("MidiKeypad","MidiKeypad") local + // feedback + connect_ports("MidiKeypad","BluetoothMidi"): + // (a) local: light the pressed pad immediately, + // (b) outbound: send to the BLE side to notify the host. + out_buf.clear(); + midi.update(&mut keypad, &mut out_buf); + for m in out_buf.iter() { + midi.receive_midi_message(&mut keypad, &m.bytes); + let _ = outbound.try_send(m.bytes); + } + out_buf.clear(); + + // 2. light pads from inbound MIDI (host -> keypad) + while let Ok(msg) = inbound.try_receive() { + midi.receive_midi_message(&mut keypad, &msg); + } + + // 3. BOOTSEL: cycle octave +12 (wrap at 120) and flash the octave digit. + // Edge-triggered so one press = one step (C firmware re-checked every + // loop while held, which multi-triggered; we debounce to a press edge). + let pressed = embassy_rp::bootsel::is_bootsel_pressed(bootsel.reborrow()); + if pressed && !bootsel_held { + note_offset = (note_offset + 12) % 120; + midi.set_note_offset(&mut keypad, note_offset); + crate::octave::display_active_octave(&mut midi, &mut keypad, note_offset).await; + } + bootsel_held = pressed; + + Timer::after_millis(10).await; + } +} + +// ---- TrouBLE host runner -------------------------------------------------- + +async fn ble_task(mut runner: Runner<'_, C, P>) { + loop { + if let Err(e) = runner.run().await { + log::error!("[ble_task] runner error: {:?}", e); + } + } +} + +// ---- Advertising ---------------------------------------------------------- + +async fn advertise<'a, 'b, C: Controller>( + peripheral: &mut Peripheral<'a, C, DefaultPacketPool>, + server: &'b Server<'a>, +) -> Result, BleHostError> { + // adv_data: Flags + MIDI service UUID (so MIDI hosts auto-discover). + // scan_data: device name (won't fit in adv_data alongside the 128-bit UUID). + let mut advertiser_data = [0u8; 31]; + let mut scan_response = [0u8; 31]; + // MIDI Service UUID 03B80E5A-EDE8-4B33-A751-6CE34EC4C700, little-endian. + let midi_service_uuid: [u8; 16] = [ + 0x00, 0xc7, 0xc4, 0x4e, 0xe3, 0x6c, 0x51, 0xa7, 0x33, 0x4b, 0xe8, 0xed, 0x5a, 0x0e, 0xb8, + 0x03, + ]; + let adv_len = AdStructure::encode_slice( + &[ + AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED), + AdStructure::CompleteServiceUuids128(&[midi_service_uuid]), + ], + &mut advertiser_data[..], + )?; + let scan_len = AdStructure::encode_slice( + &[AdStructure::CompleteLocalName("BLE-RGB Keypad".as_bytes())], + &mut scan_response[..], + )?; + log::info!("[adv] starting advertise..."); + let advertiser = peripheral + .advertise( + &Default::default(), + Advertisement::ConnectableScannableUndirected { + adv_data: &advertiser_data[..adv_len], + scan_data: &scan_response[..scan_len], + }, + ) + .await?; + log::info!("[adv] advertising as 'BLE-RGB Keypad'"); + let conn = advertiser.accept().await?.with_attribute_server(server)?; + log::info!("[adv] connection established"); + Ok(conn) +} + +// ---- Connection: pump MIDI both ways ------------------------------------- + +async fn run_connection<'a, 'b, P: PacketPool>( + conn: &GattConnection<'a, 'b, P>, + server: &'b Server<'a>, + codec: &mut codec::Codec, + outbound: &Receiver<'static, CriticalSectionRawMutex, [u8; 3], 16>, + inbound: &Sender<'static, CriticalSectionRawMutex, [u8; 3], 16>, +) { + let midi_handle = server.midi_service.midi_io.handle; + let midi_io = &server.midi_service.midi_io; + + loop { + match select(conn.next(), outbound.receive()).await { + Either::First(ev) => match ev { + GattConnectionEvent::Disconnected { reason } => { + info!("[gatt] disconnected: {:?}", reason); + return; + } + GattConnectionEvent::Gatt { event } => { + let reply = match event { + GattEvent::Write(e) => { + if e.handle() == midi_handle { + // Copy the written bytes out, then decode. + let mut buf = [0u8; codec::MAX_BLE_MIDI_PACKET]; + let mut len = 0usize; + e.with_data(|_offset, data| { + len = data.len().min(buf.len()); + buf[..len].copy_from_slice(&data[..len]); + }); + codec.decode_push(&buf[..len]); + while let Some(m) = codec.pop_midi() { + let bc = m.byte_count() as usize; + if bc == 3 { + let _ = inbound.try_send(m.msg_bytes); + } + } + } + e.accept() + } + other => other.accept(), + }; + if let Ok(reply) = reply { + let _ = reply.send().await; + } + } + _ => {} + }, + Either::Second(msg) => { + // Outbound Note On/Off: stamp + encode + notify. + let now = Instant::now().as_millis() as u16; + let (_, ready) = codec.push_midi(&msg, now); + if ready { + while let Some(pkt) = codec.ble_pkt_pop() { + let _ = midi_io.notify_raw(conn, &pkt.pkt[..pkt.nbytes], false).await; + } + } + } + } + } +} + +// ---- main ----------------------------------------------------------------- + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("[boot] embassy_rp initialised"); + + // ---- USB serial logger (routes log::info!/error! to the serial port) - + let driver = Driver::new(p.USB, Irqs); + let _ = spawner.spawn(unwrap!(logger_task(driver))); + info!("[boot] usb logger task spawned"); + + // ---- Keypad task FIRST (I2C0 + SPI0; independent of the radio) ------ + let mut kp_spi_cfg = embassy_rp::spi::Config::default(); + kp_spi_cfg.frequency = 4_000_000; + let kp_spi = embassy_rp::spi::Spi::new_blocking_txonly(p.SPI0, p.PIN_18, p.PIN_19, kp_spi_cfg); + let mut kp_i2c_cfg = embassy_rp::i2c::Config::default(); + kp_i2c_cfg.frequency = 400_000; + kp_i2c_cfg.sda_pullup = true; + kp_i2c_cfg.scl_pullup = true; + let kp_i2c = embassy_rp::i2c::I2c::new_blocking(p.I2C0, p.PIN_5, p.PIN_4, kp_i2c_cfg); + let kp_cs = Output::new(p.PIN_17, Level::High); + let keypad = RgbKeypad::new(kp_cs, kp_spi, kp_i2c); + let _ = spawner.spawn(unwrap!(keypad_task(keypad, p.BOOTSEL))); + info!("[boot] keypad task spawned"); + + // ---- CYW43 firmware blobs (committed under cyw43-firmware/) ----------- + let fw = cyw43::aligned_bytes!("../cyw43-firmware/43439A0.bin"); + let clm = cyw43::aligned_bytes!("../cyw43-firmware/43439A0_clm.bin"); + let btfw = cyw43::aligned_bytes!("../cyw43-firmware/43439A0_btfw.bin"); + let nvram = cyw43::aligned_bytes!("../cyw43-firmware/nvram_rp2040.bin"); + + // ---- CYW43 PIO-SPI bus (Pico W wiring) ------------------------------- + let pwr = Output::new(p.PIN_23, Level::Low); + let cs = Output::new(p.PIN_25, Level::High); + let mut pio = Pio::new(p.PIO0, Irqs); + let spi = PioSpi::new( + &mut pio.common, + pio.sm0, + cyw43_pio::DEFAULT_CLOCK_DIVIDER, + pio.irq0, + cs, + p.PIN_24, + p.PIN_29, + dma::Channel::new(p.DMA_CH0, Irqs), + dma::Channel::new(p.DMA_CH1, Irqs), + ); + + static STATE: StaticCell = StaticCell::new(); + let state = STATE.init(cyw43::State::new()); + + let (_net_device, bt_device, mut control, runner) = + cyw43::new_with_bluetooth(state, pwr, spi, fw, btfw, nvram).await; + let _ = spawner.spawn(unwrap!(cyw43_task(runner))); + control.init(clm).await; + log::info!("[cyw43] initialised"); + + let _ = spawner.spawn(unwrap!(led_task(control))); + + // ---- TrouBLE BLE host ------------------------------------------------- + let controller: ExternalController<_, 10> = ExternalController::new(bt_device); + let address: Address = Address::random([0xff, 0x8f, 0x1a, 0x05, 0xe4, 0xff]); + log::info!("[ble] controller wrapped, address set"); + + static RES: StaticCell> = + StaticCell::new(); + let resources = RES.init(HostResources::new()); + log::info!("[ble] building stack..."); + let stack = trouble_host::new(controller, resources) + .set_random_address(address) + .build(); + log::info!("[ble] stack built"); + let runner = stack.runner(); + let mut peripheral = stack.peripheral(); + log::info!("[ble] peripheral acquired"); + + let server = Server::new_with_config(GapConfig::Peripheral(PeripheralConfig { + name: "BLE-RGB Keypad", + appearance: &appearance::power_device::GENERIC_POWER_DEVICE, + })) + .unwrap(); + log::info!("[ble] gatt server built"); + + let outbound = OUTBOUND.receiver(); + let inbound = INBOUND.sender(); + log::info!("[ble] entering advertise loop"); + + join(ble_task(runner), async { + let mut codec = codec::Codec::new(); + loop { + match advertise(&mut peripheral, &server).await { + Ok(conn) => { + log::info!("[ble] connected"); + // Fresh codec state per connection. + codec = codec::Codec::new(); + run_connection(&conn, &server, &mut codec, &outbound, &inbound).await; + } + Err(e) => { + log::error!("[adv] error: {:?}", e); + Timer::after_millis(500).await; + } + } + } + }) + .await; +} diff --git a/src/midi_keypad.rs b/src/midi_keypad.rs new file mode 100644 index 0000000..17614b2 --- /dev/null +++ b/src/midi_keypad.rs @@ -0,0 +1,190 @@ +//! MIDI keypad state machine — Rust port of `MidiKeypad.cpp`. +//! +//! Copyright (C) 2026 Nils — SPDX-License-Identifier: GPL-3.0-or-later +//! This file is part of pico-pi-midi. See LICENSE for the full notice. +//! +//! Reads the 16-pad button matrix from [`RgbKeypad`], turns presses/releases +//! into Note On/Off MIDI messages, and lights pads in response to incoming +//! Note messages. The underlay colours distinguish root / white / black keys +//! within the current octave, and an overlay can temporarily take over the +//! LEDs (used by the octave display). + +use crate::keypad::{RgbKeypad, NUM_PADS}; + +/// Root note (MIDI note number where the keypad's pad 0 starts). +pub type NoteOffset = u8; + +/// A MIDI message to send (Note On / Note Off here, 3 bytes each). +#[derive(Clone, Copy)] +pub struct MidiOut { + pub bytes: [u8; 3], +} + +/// Flip a 4x4 grid index so that physical pad 0 (top-left) maps to the +/// bottom-left note, matching the C `transform_index`. +fn transform_index(i: usize) -> usize { + let row = i / 4; + let column = i % 4; + (3 - row) * 4 + column +} + +pub struct MidiKeypad { + pub note_offset: NoteOffset, + previous_button_states: u16, + /// RGB underlay colour per physical pad index. + underlay_colors: [[u8; 3]; NUM_PADS], + overlay_active: bool, + underlay_active: bool, +} + +impl MidiKeypad { + pub fn new(keypad: &mut RgbKeypad, note_offset: NoteOffset) -> Self { + let mut s = Self { + note_offset, + previous_button_states: 0, + underlay_colors: [[0; 3]; NUM_PADS], + overlay_active: false, + underlay_active: true, + }; + s.set_base_colors(keypad); + s + } + + pub fn set_note_offset(&mut self, keypad: &mut RgbKeypad, offset: NoteOffset) { + self.note_offset = offset; + self.set_base_colors(keypad); + } + + /// Poll the button matrix; append a Note On/Off `[status, note, velocity]` + /// to `out` for every pad whose state changed since the last call. + pub fn update(&mut self, keypad: &mut RgbKeypad, out: &mut heapless::Vec) { + if self.overlay_active { + return; + } + let current = keypad.get_button_states(); + let changed = current ^ self.previous_button_states; + if changed != 0 { + for i in 0..NUM_PADS { + let mask = 1u16 << i; + if changed & mask != 0 { + let pressed = current & mask != 0; + let note = (transform_index(i) as u8).wrapping_add(self.note_offset); + let bytes = if pressed { + [0x90, note, 127] // Note On, full velocity + } else { + [0x80, note, 0] // Note Off + }; + let _ = out.push(MidiOut { bytes }); + } + } + } + self.previous_button_states = current; + } + + /// Light a single pad for the overlay (takes over the LEDs until cleared). + pub fn set_overlay_led(&mut self, keypad: &mut RgbKeypad, pad_index: usize, r: u8, g: u8, b: u8) { + let reversed = transform_index(pad_index); + keypad.illuminate(reversed, r, g, b); + keypad.update(); + self.overlay_active = true; + self.underlay_active = false; + } + + /// Mark the overlay active after painting LEDs directly (e.g. a bitmap via + /// `RgbKeypad` rather than `set_overlay_led`), so the keypad update loop + /// suspends button reads and `clear_overlay` knows to repaint the underlay. + pub fn set_overlay_active(&mut self) { + self.overlay_active = true; + self.underlay_active = false; + } + + /// Release the overlay and repaint the underlay. + pub fn clear_overlay(&mut self, keypad: &mut RgbKeypad) { + self.overlay_active = false; + self.underlay_active = true; + self.update_underlay(keypad); + } + + /// Handle an incoming 3-byte MIDI channel message, lighting the matching + /// pad if it falls in the keypad's note range. Mirrors the C + /// `receive_midi_message` (with the duplicated Note-On block de-duplicated). + pub fn receive_midi_message(&mut self, keypad: &mut RgbKeypad, message: &[u8]) { + if message.len() < 3 || self.overlay_active { + return; + } + let status = message[0]; + let note = message[1]; + let velocity = message[2]; + + let original = note as i32 - self.note_offset as i32; + if !(0..NUM_PADS as i32).contains(&original) { + return; + } + let original = original as usize; + let reversed = transform_index(original); + + let mut r = self.underlay_colors[original][0]; + let mut g = self.underlay_colors[original][1]; + let mut b = self.underlay_colors[original][2]; + + // Default to white if the underlay is off. + if r == 0 && g == 0 && b == 0 { + r = 255; + g = 255; + b = 255; + } else { + // Normalise the underlay colour to full brightness. + let max = r.max(g).max(b); + r = (r as u16 * 255 / max as u16) as u8; + g = (g as u16 * 255 / max as u16) as u8; + b = (b as u16 * 255 / max as u16) as u8; + } + + if status == 0x90 && velocity > 0 { + // Note On: scale the normalised colour by velocity. + r = (r as u16 * velocity as u16 / 127) as u8; + g = (g as u16 * velocity as u16 / 127) as u8; + b = (b as u16 * velocity as u16 / 127) as u8; + keypad.illuminate(reversed, r, g, b); + } else if status == 0x80 || (status == 0x90 && velocity == 0) { + // Note Off: revert to the original underlay colour. + keypad.illuminate( + reversed, + self.underlay_colors[original][0], + self.underlay_colors[original][1], + self.underlay_colors[original][2], + ); + } + keypad.update(); + } + + /// Paint the underlay colours across all pads. + fn update_underlay(&self, keypad: &mut RgbKeypad) { + if !self.overlay_active && self.underlay_active { + for i in 0..NUM_PADS { + let j = transform_index(i); + keypad.illuminate( + j, + self.underlay_colors[i][0], + self.underlay_colors[i][1], + self.underlay_colors[i][2], + ); + } + keypad.update(); + } + } + + /// Set the base underlay colours: root note purple, black keys dim, white + /// keys blue — keyed by `(note_offset + i) % 12`. + fn set_base_colors(&mut self, keypad: &mut RgbKeypad) { + for i in 0..NUM_PADS { + let note = (self.note_offset.wrapping_add(i as u8)) % 12; + self.underlay_colors[i] = match note { + 0 => [5, 1, 5], // root (purple) + 1 | 3 | 6 | 8 | 10 => [0, 0, 0], // black keys (dim) + _ => [1, 1, 10], // white keys (blue) + }; + } + self.update_underlay(keypad); + } +} diff --git a/src/octave.rs b/src/octave.rs new file mode 100644 index 0000000..fdde1da --- /dev/null +++ b/src/octave.rs @@ -0,0 +1,65 @@ +//! Octave display — Rust port of the octave bitmaps + `display_active_octave` +//! from `main.cpp`. On a BOOTSEL press, the active octave is shown as a digit +//! on the 4x4 keypad grid for 500ms via the keypad overlay, then cleared. +//! +//! Copyright (C) 2026 Nils — SPDX-License-Identifier: GPL-3.0-or-later +//! This file is part of pico-pi-midi. See LICENSE for the full notice. + +use crate::keypad::{RgbKeypad, NUM_PADS}; +use crate::midi_keypad::MidiKeypad; + +/// 4x4 bitmaps for octaves -1..=9. Each row is 4 bits (MSB = leftmost column), +/// rows ordered top-to-bottom. Index 0 = octave -1, index k = octave k-1. +/// (Direct copy of the C `digit_bitmaps[11][4]`.) +const DIGIT_BITMAPS: [[u8; 4]; 11] = [ + [0b0001, 0b0001, 0b1101, 0b0001], // -1 + [0b0010, 0b0101, 0b0101, 0b0010], // 0 + [0b0001, 0b0001, 0b0001, 0b0001], // 1 + [0b0111, 0b0011, 0b0110, 0b0111], // 2 + [0b0111, 0b0001, 0b0011, 0b0111], // 3 + [0b0101, 0b0111, 0b0001, 0b0001], // 4 + [0b0111, 0b0110, 0b0011, 0b0111], // 5 + [0b0100, 0b0111, 0b0101, 0b0111], // 6 + [0b0111, 0b0001, 0b0010, 0b0100], // 7 + [0b0111, 0b0111, 0b0111, 0b0111], // 8 + [0b0111, 0b0101, 0b0111, 0b0001], // 9 +]; + +/// Paint a 4x4 bitmap onto the keypad overlay (white where the bit is set). +/// Mirrors the C `display_bitmap`: row 0 is top, and the grid is written so +/// that physical row (3-row) is at the top — matching `transform_index`. +fn display_bitmap(keypad: &mut RgbKeypad, bitmap: &[u8; 4]) { + for row in 0..4 { + let row_data = bitmap[row]; + for col in 0..4 { + let on = row_data & (1 << (3 - col)) != 0; + let c = if on { 255 } else { 0 }; + // C: set_overlay_led((3 - row) * 4 + col, ...) + let pad_index = (3 - row) * 4 + col; + keypad.illuminate(pad_index, c, c, c); + } + } + keypad.update(); +} + +/// Show the active octave for `note_offset` on the grid for 500ms, then clear. +/// `octave = note_offset/12 - 1` (MIDI convention: note 0 = C-1). Out of range +/// flashes all pads red. +pub async fn display_active_octave(midi: &mut MidiKeypad, keypad: &mut RgbKeypad, note_offset: u8) { + let octave = (note_offset / 12) as i32 - 1; + + midi.clear_overlay(keypad); + + if (-1..=9).contains(&octave) { + display_bitmap(keypad, &DIGIT_BITMAPS[(octave + 1) as usize]); + midi.set_overlay_active(); + } else { + // Out of range: flash all LEDs red. + for i in 0..NUM_PADS { + midi.set_overlay_led(keypad, i, 255, 0, 0); + } + } + + embassy_time::Timer::after_millis(500).await; + midi.clear_overlay(keypad); +}