From 7ad02d88a8c5e99ffd5d82bc89e4e5cdd673ce76 Mon Sep 17 00:00:00 2001 From: Vitalii Popov Date: Sun, 8 Feb 2026 18:11:50 +0200 Subject: [PATCH] initial --- .cargo/config.toml | 19 ++ .gitignore | 1 + .idea/.gitignore | 10 + .idea/misc.xml | 7 + .idea/modules.xml | 8 + .idea/odesa.iml | 11 + .idea/vcs.xml | 6 + Cargo.lock | 723 ++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 54 ++++ Embed.toml | 4 + build.rs | 38 +++ memory.x | 5 + rust-toolchain.toml | 5 + src/fmt.rs | 241 +++++++++++++++ src/main.rs | 412 +++++++++++++++++++++++++ 15 files changed, 1544 insertions(+) create mode 100644 .cargo/config.toml create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/odesa.iml create mode 100644 .idea/vcs.xml create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 Embed.toml create mode 100644 build.rs create mode 100644 memory.x create mode 100644 rust-toolchain.toml create mode 100644 src/fmt.rs create mode 100644 src/main.rs diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..4ce4e33 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,19 @@ +[target.thumbv7em-none-eabihf] +runner = 'probe-rs run --chip nRF52840_xxAA' +rustflags = [ + "-C", "target-feature=+vfp4d16sp", # Tells the compiler to use the FPU +] + +#[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +#runner = "probe-rs run --no-location --chip nRF52840_xxAA" + +[build] +target = "thumbv7em-none-eabihf" +#target = "thumbv7em-none-eabi" + +[env] +DEFMT_LOG = "trace" + +[unstable] +build-std = ["core"] +build-std-features = ["panic_immediate_abort"] diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..ab1f416 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..5f3c745 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..71877d8 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/odesa.iml b/.idea/odesa.iml new file mode 100644 index 0000000..cf84ae4 --- /dev/null +++ b/.idea/odesa.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..2ae7ad3 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,723 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[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 = "bitfield" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cortex-m" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" +dependencies = [ + "bare-metal", + "bitfield", + "critical-section", + "embedded-hal 0.2.7", + "volatile-register", +] + +[[package]] +name = "cortex-m-rt" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d4dec46b34c299ccf6b036717ae0fce602faa4f4fe816d9013b9a7c9f5ba6" +dependencies = [ + "cortex-m-rt-macros", +] + +[[package]] +name = "cortex-m-rt-macros" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e37549a379a9e0e6e576fd208ee60394ccb8be963889eebba3ffe0980364f472" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[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", +] + +[[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", +] + +[[package]] +name = "defmt" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "548d977b6da32fa1d1fda2876453da1e7df63ad0304c8b3dae4dbe7b96f39b78" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d4fc12a85bcf441cfe44344c4b72d58493178ce635338a3f3b78943aceb258e" +dependencies = [ + "defmt-parser", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror", +] + +[[package]] +name = "defmt-rtt" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d5a25c99d89c40f5676bec8cefe0614f17f0f40e916f98e345dae941807f9e" +dependencies = [ + "critical-section", + "defmt", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + +[[package]] +name = "embassy-embedded-hal" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "554e3e840696f54b4c9afcf28a0f24da431c927f4151040020416e7393d6d0d8" +dependencies = [ + "defmt", + "embassy-futures", + "embassy-hal-internal", + "embassy-sync", + "embassy-time", + "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.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06070468370195e0e86f241c8e5004356d696590a678d47d6676795b2e439c6b" +dependencies = [ + "cortex-m", + "critical-section", + "defmt", + "document-features", + "embassy-executor-macros", + "embassy-executor-timer-queue", +] + +[[package]] +name = "embassy-executor-macros" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfdddc3a04226828316bf31393b6903ee162238576b1584ee2669af215d55472" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "embassy-executor-timer-queue" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fc328bf943af66b80b98755db9106bf7e7471b0cf47dc8559cd9a6be504cc9c" + +[[package]] +name = "embassy-futures" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc2d050bdc5c21e0862a89256ed8029ae6c290a93aecefc73084b3002cdebb01" +dependencies = [ + "defmt", +] + +[[package]] +name = "embassy-hal-internal" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95285007a91b619dc9f26ea8f55452aa6c60f7115a4edc05085cd2bd3127cd7a" +dependencies = [ + "cortex-m", + "critical-section", + "defmt", + "num-traits", +] + +[[package]] +name = "embassy-nrf" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de748eaafda21aab46925973e593f377ed4482e9f4de63a729c79a7b0e4a327a" +dependencies = [ + "bitflags 2.10.0", + "cfg-if", + "cortex-m", + "cortex-m-rt", + "critical-section", + "defmt", + "document-features", + "embassy-embedded-hal", + "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-io", + "embedded-io-async", + "embedded-storage", + "embedded-storage-async", + "fixed", + "nrf-pac", + "rand_core 0.6.4", + "rand_core 0.9.5", +] + +[[package]] +name = "embassy-sync" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73974a3edbd0bd286759b3d483540f0ebef705919a5f56f4fc7709066f71689b" +dependencies = [ + "cfg-if", + "critical-section", + "defmt", + "embedded-io-async", + "futures-core", + "futures-sink", + "heapless", +] + +[[package]] +name = "embassy-time" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4fa65b9284d974dad7a23bb72835c4ec85c0b540d86af7fc4098c88cff51d65" +dependencies = [ + "cfg-if", + "critical-section", + "defmt", + "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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0a244c7dc22c8d0289379c8d8830cae06bb93d8f990194d0de5efb3b5ae7ba6" +dependencies = [ + "document-features", +] + +[[package]] +name = "embassy-time-queue-utils" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e2ee86063bd028a420a5fb5898c18c87a8898026da1d4c852af2c443d0a454" +dependencies = [ + "embassy-executor-timer-queue", + "heapless", +] + +[[package]] +name = "embassy-usb-driver" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17119855ccc2d1f7470a39756b12068454ae27a3eabb037d940b5c03d9c77b7a" +dependencies = [ + "defmt", + "embedded-io-async", +] + +[[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-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "embedded-io-async" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff09972d4073aa8c299395be75161d582e7629cd663171d62af73c8d50dba3f" +dependencies = [ + "embedded-io", +] + +[[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 = "fixed" +version = "1.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c566da967934c6c7ee0458a9773de9b2a685bd2ce26a3b28ddfc740e640182f5" +dependencies = [ + "az", + "bytemuck", + "half", + "typenum", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[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 = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "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 = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[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 = "nrf-pac" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae76c372d1838abeccf1d397688cd4bd90fe3213410c51cb74c6769fdf075067" +dependencies = [ + "cortex-m", + "cortex-m-rt", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "odesa" +version = "0.1.0" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "defmt", + "defmt-rtt", + "embassy-executor", + "embassy-futures", + "embassy-nrf", + "embassy-sync", + "embassy-time", + "panic-halt", + "panic-probe", +] + +[[package]] +name = "panic-halt" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a513e167849a384b7f9b746e517604398518590a9142f4846a32e3c2a4de7b11" + +[[package]] +name = "panic-probe" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd402d00b0fb94c5aee000029204a46884b1262e0c443f166d86d2c0747e1a1a" +dependencies = [ + "cortex-m", + "defmt", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +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 = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[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 = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "vcell" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" + +[[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 = "zerocopy" +version = "0.8.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0fe77b5 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,54 @@ +# This file was automatically generated. + +[package] +edition = "2024" +name = "odesa" +version = "0.1.0" + +[dependencies] +cortex-m = { version = "0.7.7", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.5" +defmt = { version = "1.0.1", optional = true } +defmt-rtt = { version = "1.1.0", optional = true } +embassy-executor = { version = "0.9.1", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] } +embassy-futures = "0.1.2" +embassy-nrf = { version = "0.9.0", features = ["nrf52840", "gpiote", "time-driver-rtc1", "defmt", "unstable-pac", "time", "nfc-pins-as-gpio"] } +embassy-sync = { version = "0.7.2", features = ["defmt"] } +embassy-time = { version = "0.5.0", features = ["tick-hz-32_768", "defmt", "defmt-timestamp-uptime"] } +panic-halt = "1.0.0" +panic-probe = { version = "1.0.0", features = ["print-defmt"], optional = true } +#nrf-softdevice = { version = "0.1.0", features = ["s140", "ble-peripheral", "nrf52840"] } + +[[bin]] +name = "odesa" +test = false +bench = false + +[profile.dev] +debug = true +lto = true +opt-level = "z" +incremental = true + +[profile.release] +debug = false +lto = true +opt-level = "z" +incremental = true + +[features] +defmt = ["dep:defmt"] +defmt-rtt = ["dep:defmt-rtt"] +panic-probe = ["dep:panic-probe"] +default = ["debug"] +debug = [ + "defmt", + "defmt-rtt", + "panic-probe", + "embassy-executor/defmt", + "embassy-sync/defmt", + "embassy-futures/defmt", + "embassy-time/defmt", + "embassy-time/defmt-timestamp-uptime", + "embassy-nrf/defmt", +] diff --git a/Embed.toml b/Embed.toml new file mode 100644 index 0000000..0dfa68f --- /dev/null +++ b/Embed.toml @@ -0,0 +1,4 @@ +# This file was automatically generated. + +[default.general] +chip = "nRF52840_xxAA" diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..3d7c12f --- /dev/null +++ b/build.rs @@ -0,0 +1,38 @@ +// This file was automatically generated. + +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + 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()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + #[cfg(feature = "defmt")] + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); +} diff --git a/memory.x b/memory.x new file mode 100644 index 0000000..346a27d --- /dev/null +++ b/memory.x @@ -0,0 +1,5 @@ +MEMORY +{ + FLASH : ORIGIN = 0x00000000 + 156K, LENGTH = 1024K - 156K + RAM : ORIGIN = 0x20000000 + 31K, LENGTH = 256K - 31K +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..ab310d2 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,5 @@ +[toolchain] +channel = "1.90" +components = ["rust-src", "rustfmt"] +targets = ["thumbv7em-none-eabihf"] +#, "thumbv7em-none-eabi" \ No newline at end of file diff --git a/src/fmt.rs b/src/fmt.rs new file mode 100644 index 0000000..c56dc51 --- /dev/null +++ b/src/fmt.rs @@ -0,0 +1,241 @@ +// This file was automatically generated. + +#![allow(unused)] + +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +#[cfg(not(feature = "defmt"))] +macro_rules! unreachable { + ($($x:tt)*) => { + ::core::unreachable!($($x)*) + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unreachable { + ($($x:tt)*) => { + ::defmt::unreachable!($($x)*) + }; +} + +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(feature="defmt")] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(feature="defmt"))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(feature="defmt"))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! _warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(feature="defmt"))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(feature="defmt"))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(_) => { + ::core::panic!(); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(_) => { + ::core::panic!(); + } + } + }; +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} + +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +#[cfg(feature = "defmt")] +impl defmt::Format for Bytes<'_> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} + +pub(crate) use _warn as warn; +pub(crate) use assert; +pub(crate) use assert_eq; +pub(crate) use assert_ne; +pub(crate) use debug; +pub(crate) use debug_assert; +pub(crate) use debug_assert_eq; +pub(crate) use debug_assert_ne; +pub(crate) use error; +pub(crate) use info; +pub(crate) use panic; +pub(crate) use todo; +pub(crate) use trace; +pub(crate) use unreachable; +pub(crate) use unwrap; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..76cb3ff --- /dev/null +++ b/src/main.rs @@ -0,0 +1,412 @@ +#![no_std] +#![no_main] + +mod fmt; + +use core::mem; +use core::ops::{AddAssign, SubAssign}; +use defmt::Format; +#[cfg(not(feature = "defmt"))] +use panic_halt as _; +#[cfg(feature = "defmt")] +use {defmt_rtt as _, panic_probe as _}; +#[cfg(feature = "defmt")] +use defmt::info; + +use embassy_executor::Spawner; +use embassy_futures::join::{join3, join4}; +use embassy_nrf::gpio::{AnyPin, Input, Level, Output, OutputDrive, Pull}; +use embassy_nrf::pwm::{DutyCycle, Prescaler, SimplePwm}; +use embassy_nrf::Peri; +use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; +use embassy_sync::mutex::Mutex; +use embassy_sync::signal::Signal; +use embassy_time::{with_timeout, Duration, Timer}; +// use nrf_softdevice::{raw, Softdevice}; + +#[derive(Copy, Clone, Eq, PartialEq, Format)] +enum SysAction { + PresentenceOn, + PresentenceOff, + ConstantOn, +} + +#[derive(Copy, Clone, Eq, PartialEq, Format)] +enum TextLightType { + White, + ConstantColor, + Hue +} + +#[derive(Copy, Clone, Eq, PartialEq, Format)] +enum HeartLightType { + ConstantColor, + Hue, + HeartBeat +} + +#[derive(Copy, Clone, Eq, PartialEq, Format)] +pub enum TouchAction { + Tap, + DoubleTap, + TripleTap, + Hold, +} + +static TOUCH_SIGNAL: Signal = Signal::new(); +static RADAR_SIGNAL: Signal = Signal::new(); +static TEXT_ANIMATION_SIGNAL: Signal = Signal::new(); +static HEART_ANIMATION_SIGNAL: Signal = Signal::new(); +static SYS_ACTION: Mutex = Mutex::new(SysAction::PresentenceOn); +static TEXT_ANIMATION: Mutex = Mutex::new(TextLightType::Hue); +static HEART_ANIMATION: Mutex = Mutex::new(HeartLightType::HeartBeat); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + // let config = nrf_softdevice::Config { + // clock: Some(raw::nrf_clock_lf_cfg_t { + // source: raw::NRF_CLOCK_LF_SRC_RC as u8, + // rc_ctiv: 16, + // rc_temp_ctiv: 2, + // accuracy: raw::NRF_CLOCK_LF_ACCURACY_500_PPM as u8, + // }), + // conn_gap: Some(raw::ble_gap_conn_cfg_t { + // conn_count: 6, + // event_length: 24, + // }), + // conn_gatt: Some(raw::ble_gatt_conn_cfg_t { att_mtu: 256 }), + // gatts_attr_tab_size: Some(raw::ble_gatts_cfg_attr_tab_size_t { + // attr_tab_size: raw::BLE_GATTS_ATTR_TAB_SIZE_DEFAULT, + // }), + // gap_role_count: Some(raw::ble_gap_cfg_role_count_t { + // adv_set_count: 1, + // periph_role_count: 3, + // central_role_count: 3, + // central_sec_count: 0, + // _bitfield_1: raw::ble_gap_cfg_role_count_t::new_bitfield_1(0), + // }), + // gap_device_name: Some(raw::ble_gap_cfg_device_name_t { + // p_value: b"HelloRust" as *const u8 as _, + // current_len: 9, + // max_len: 9, + // write_perm: unsafe { mem::zeroed() }, + // _bitfield_1: raw::ble_gap_cfg_device_name_t::new_bitfield_1(raw::BLE_GATTS_VLOC_STACK as u8), + // }), + // ..Default::default() + // }; + // + // let _sd = Softdevice::enable(&config); + + let p = embassy_nrf::init(Default::default()); + let pwm_text = SimplePwm::new_3ch(p.PWM0, p.P1_15, p.P1_13, p.P1_11, &Default::default()); + let pwm_heart = SimplePwm::new_3ch(p.PWM2, p.P0_31, p.P0_29, p.P0_02, &Default::default()); + spawner.spawn(radar_task()).expect("failed to spawn radar task"); + spawner.spawn(actions_task()).expect("failed to spawn actions task"); + + join4(touch_button(p.P0_20.into()), moving_radar(p.P0_22.into()), heartbeat_task(pwm_heart), light_task(pwm_text)).await; +} + +async fn touch_button(pin: Peri<'static, AnyPin>) { + let mut button = Input::new(pin, Pull::Down); + + loop { + info!("Wait for button"); + button.wait_for_high().await; + let mut tap_count = 0; + let mut is_hold = false; + + info!("Wait for low 1"); + match with_timeout(Duration::from_millis(400), button.wait_for_low()).await { + Err(_) => { + info!("Look holding"); + is_hold = true; + TOUCH_SIGNAL.signal(TouchAction::Hold); + button.wait_for_low().await; + } + Ok(_) => { + tap_count = 1; + + info!("Wait for next tap ({})", tap_count); + loop { + match with_timeout(Duration::from_millis(300), button.wait_for_high()).await { + Ok(_) => { + info!("Wait for tap release ({})", tap_count); + button.wait_for_low().await; + tap_count += 1; + if tap_count >= 3 { break; } + } + Err(_) => { + break; + } + } + } + } + } + + // 4. Dispatch the action if it wasn't a hold + if !is_hold { + match tap_count { + 1 => TOUCH_SIGNAL.signal(TouchAction::Tap), + 2 => TOUCH_SIGNAL.signal(TouchAction::DoubleTap), + 3 => TOUCH_SIGNAL.signal(TouchAction::TripleTap), + _ => {} + } + } + + // Debounce / Cool-down + Timer::after_millis(100).await; + } +} + +#[embassy_executor::task] +async fn actions_task() { + loop { + let signal = TOUCH_SIGNAL.wait().await; + info!("Action: {:?}", signal); + match signal { + // constant on vs presentence + TouchAction::Tap => { + let mut action = SYS_ACTION.lock().await; + match *action { + SysAction::PresentenceOn | + SysAction::PresentenceOff => { + *action = SysAction::ConstantOn; + info!("Constant light"); + } + SysAction::ConstantOn => { + *action = SysAction::PresentenceOn; + info!("Light by presentence"); + } + } + info!("Light mode {:?}", *action); + } + // change text animation + TouchAction::DoubleTap => { + let mut current = TEXT_ANIMATION.lock().await; + let next = match *current { + TextLightType::ConstantColor => { + TextLightType::White + } + TextLightType::Hue => { + TextLightType::ConstantColor + } + TextLightType::White => { + TextLightType::Hue + } + }; + *current = next; + info!("Set text to {:?}", next); + TEXT_ANIMATION_SIGNAL.signal(next); + } + // change anchor animation + TouchAction::TripleTap => { + let mut current = HEART_ANIMATION.lock().await; + let next = match *current { + HeartLightType::ConstantColor => { + HeartLightType::Hue + } + HeartLightType::Hue => { + HeartLightType::HeartBeat + } + HeartLightType::HeartBeat => { + HeartLightType::ConstantColor + } + }; + info!("Set heart to {:?}", next); + *current = next; + } + // enter pairing mode + TouchAction::Hold => { + } + } + } +} + +#[embassy_executor::task] +async fn radar_task() { + const MAX_TIME: usize = 24; + let mut time = MAX_TIME; + loop { + let action = { + let current = SYS_ACTION.lock().await; + *current + }; + if action != SysAction::ConstantOn { + if let Err(_) = with_timeout(Duration::from_secs(1), RADAR_SIGNAL.wait()).await { + time = time.saturating_sub(1); + if time == 0 { + let mut action = SYS_ACTION.lock().await; + if *action == SysAction::PresentenceOn { + *action = SysAction::PresentenceOff; + info!("Off light due to no presentence"); + } + } + } else { + if time == 0 { + let mut action = SYS_ACTION.lock().await; + if *action == SysAction::PresentenceOff { + *action = SysAction::PresentenceOn; + info!("On light due to presentence"); + } + } + time = MAX_TIME; + } + } else { + time = MAX_TIME; + Timer::after_millis(500).await; + } + } +} + +async fn moving_radar(pin: Peri<'static, AnyPin>) { + let mut radar = Input::new(pin, Pull::Down); + loop { + info!("Waiting for radar"); + radar.wait_for_high().await; + RADAR_SIGNAL.signal(0); + while let Err(_) = with_timeout(Duration::from_secs(1), radar.wait_for_low()).await { + info!("Moving!!"); + RADAR_SIGNAL.signal(0); + } + } +} + +async fn light_task(mut pwm: SimplePwm<'static>) { + info!("Starting light task"); + const MAX_DUTY: u16 = 32767; + + pwm.set_prescaler(Prescaler::Div1); + pwm.set_max_duty(MAX_DUTY); + pwm.set_ch1_drive(OutputDrive::Standard); + pwm.set_ch2_drive(OutputDrive::Standard); + pwm.set_ch3_drive(OutputDrive::Standard); + pwm.set_all_duties([DutyCycle::normal(0), DutyCycle::normal(0), DutyCycle::normal(0), DutyCycle::normal(0)]); + + let mut intensity = 0i32; + let mut hue: u16 = 0; + let mut animation_type = TextLightType::Hue; + + loop { + let action = { + let guard = SYS_ACTION.lock().await; + guard.clone() + }; + match action { + SysAction::PresentenceOn if intensity < 100 => { + intensity.add_assign(1); + info!("Increase intensity to {}", intensity); + }, + SysAction::PresentenceOff if intensity > 0 => { + intensity.sub_assign(1); + info!("Decrease intensity to {}", intensity); + }, + _ => { + // ignore + } + } + + if let Ok(val) = with_timeout(Duration::from_millis(10), TEXT_ANIMATION_SIGNAL.wait()).await { + animation_type = val; + } + match animation_type { + TextLightType::Hue => { + let (r, g, b) = hue_to_rgb(hue, MAX_DUTY); + + pwm.set_all_duties([ + DutyCycle::normal(((r as i32) * intensity / 100) as u16), + DutyCycle::normal(((g as i32) * intensity / 100) as u16), + DutyCycle::normal(((b as i32) * intensity / 100) as u16), + DutyCycle::normal(0), // Ch4 unused + ]); + + hue = (hue + 1) % 1536; + } + TextLightType::White => { + let r = 12288; // Lowered significantly + let g = 16384; + let b = 24576; + + // Set them once. No loop, no flicker. + pwm.set_all_duties([ + DutyCycle::normal(((r) * intensity / 100) as u16), + DutyCycle::normal(((g) * intensity / 100) as u16), + DutyCycle::normal(((b) * intensity / 100) as u16), + DutyCycle::normal(0), + ]); + } + TextLightType::ConstantColor => { + let (r, g, b) = hue_to_rgb(hue, MAX_DUTY); + + pwm.set_all_duties([ + DutyCycle::normal(((r as i32) * intensity / 100) as u16), + DutyCycle::normal(((g as i32) * intensity / 100) as u16), + DutyCycle::normal(((b as i32) * intensity / 100) as u16), + DutyCycle::normal(0), // Ch4 unused + ]); + } + } + } +} + +fn hue_to_rgb(hue: u16, max: u16) -> (u16, u16, u16) { + let sector = (hue / 256) as u8; + let rising = (hue % 256) as u32 * max as u32 / 255; + let falling = max as u32 - rising; + + let (r, g, b) = match sector { + 0 => (max as u32, rising, 0), // Red to Yellow + 1 => (falling, max as u32, 0), // Yellow to Green + 2 => (0, max as u32, rising), // Green to Cyan + 3 => (0, falling, max as u32), // Cyan to Blue + 4 => (rising, 0, max as u32), // Blue to Magenta + _ => (max as u32, 0, falling), // Magenta to Red + }; + + (r as u16, g as u16, b as u16) +} + +async fn heartbeat_task(mut pwm: SimplePwm<'static>) { + const MAX: u16 = 4096; + const MIN: u16 = 512; + pwm.set_max_duty(MAX); + pwm.set_prescaler(Prescaler::Div1); + + loop { + // 1. The "Lub" (Stronger, slightly longer) + // We pulse from 0 to 100% of our target intensity + pulse_sweep(&mut pwm, 20, 1.0, MIN, MAX).await; + + // 2. Short pause between beats (The "comma" in the rhythm) + pwm.set_duty(0, DutyCycle::normal(MIN)); + Timer::after_millis(60).await; + + // 3. The "Dub" (Sharper, weaker) + // We only go to 60% of max brightness for this one + pulse_sweep(&mut pwm, 15, 0.6, MIN, MAX).await; + + // 4. The "Diastole" (Long resting pause) + pwm.set_duty(0, DutyCycle::normal(MIN)); + Timer::after_millis(900).await; + } +} + +async fn pulse_sweep(pwm: &mut SimplePwm<'static>, steps: u32, intensity: f32, min_duty: u16, max_duty: u16) { + + // RAMP UP + for i in 0..=steps { + let progress = i as f32 / steps as f32; + // Cubic gamma for smoothness + let val = progress * progress * progress; + let duty = ((val * max_duty as f32 * intensity) as u16).saturating_add(min_duty).min(max_duty); + pwm.set_duty(0, DutyCycle::normal(duty)); + Timer::after_millis(8).await; + } + // RAMP DOWN + for i in (0..steps).rev() { + let progress = i as f32 / steps as f32; + let val = progress * progress * progress; + let duty = ((val * max_duty as f32 * intensity) as u16).saturating_add(min_duty).min(max_duty); + pwm.set_duty(0, DutyCycle::normal(duty)); + Timer::after_millis(8).await; + } +} \ No newline at end of file