This commit is contained in:
Vitalii 2026-02-08 18:11:50 +02:00
commit cbfc8743f7
Signed by: SymbX
GPG Key ID: FF51F4E4BCE459EE
15 changed files with 1614 additions and 0 deletions

19
.cargo/config.toml Normal file
View File

@ -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"]

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

10
.idea/.gitignore vendored Normal file
View File

@ -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/

7
.idea/misc.xml Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="McpProjectServerCommands">
<commands />
<urls />
</component>
</project>

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/odesa.iml" filepath="$PROJECT_DIR$/.idea/odesa.iml" />
</modules>
</component>
</project>

11
.idea/odesa.iml Normal file
View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="EMPTY_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

723
Cargo.lock generated Normal file
View File

@ -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",
]

54
Cargo.toml Normal file
View File

@ -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",
]

4
Embed.toml Normal file
View File

@ -0,0 +1,4 @@
# This file was automatically generated.
[default.general]
chip = "nRF52840_xxAA"

38
build.rs Normal file
View File

@ -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");
}

5
memory.x Normal file
View File

@ -0,0 +1,5 @@
MEMORY
{
FLASH : ORIGIN = 0x00000000 + 156K, LENGTH = 1024K - 156K
RAM : ORIGIN = 0x20000000 + 31K, LENGTH = 256K - 31K
}

5
rust-toolchain.toml Normal file
View File

@ -0,0 +1,5 @@
[toolchain]
channel = "1.90"
components = ["rust-src", "rustfmt"]
targets = ["thumbv7em-none-eabihf"]
#, "thumbv7em-none-eabi"

241
src/fmt.rs Normal file
View File

@ -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<Self::Ok, Self::Error>;
}
impl<T> Try for Option<T> {
type Ok = T;
type Error = NoneError;
#[inline]
fn into_result(self) -> Result<T, NoneError> {
self.ok_or(NoneError)
}
}
impl<T, E> Try for Result<T, E> {
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;

482
src/main.rs Normal file
View File

@ -0,0 +1,482 @@
#![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<ThreadModeRawMutex, TouchAction> = Signal::new();
static RADAR_SIGNAL: Signal<ThreadModeRawMutex, u8> = Signal::new();
static TEXT_ANIMATION_SIGNAL: Signal<ThreadModeRawMutex, TextLightType> = Signal::new();
static HEART_ANIMATION_SIGNAL: Signal<ThreadModeRawMutex, HeartLightType> = Signal::new();
static SYS_ACTION: Mutex<ThreadModeRawMutex, SysAction> = Mutex::new(SysAction::PresentenceOn);
static TEXT_ANIMATION: Mutex<ThreadModeRawMutex, TextLightType> = Mutex::new(TextLightType::Hue);
static HEART_ANIMATION: Mutex<ThreadModeRawMutex, HeartLightType> = 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;
HEART_ANIMATION_SIGNAL.signal(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 = 16384;
const MIN: u16 = 2048;
pwm.set_max_duty(MAX);
pwm.set_prescaler(Prescaler::Div1);
let mut intensity = 0i32;
let mut animation_type = HeartLightType::HeartBeat;
let mut hue: u16 = 0;
let mut heart_beat = 0;
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), HEART_ANIMATION_SIGNAL.wait()).await {
animation_type = val;
}
match animation_type {
HeartLightType::ConstantColor => {
let (r, g, b) = hue_to_rgb(hue, MAX);
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
]);
}
HeartLightType::Hue => {
let (r, g, b) = hue_to_rgb(hue, MAX);
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;
}
HeartLightType::HeartBeat => {
let beat = get_heartbeat(heart_beat);
pwm.set_all_duties([
DutyCycle::normal(((beat as i32) * intensity / 100) as u16),
DutyCycle::normal(0),
DutyCycle::normal(0),
DutyCycle::normal(0), // Ch4 unused
]);
heart_beat += 10;
if heart_beat >= 32768 {
heart_beat = 0;
}
}
}
}
}
const BPM: u32 = 24;
const MIN_GLOW: u32 = 2048;
const MAX_VAL: u32 = 32768;
const PERIOD_MS: u32 = 60_000 / BPM;
/// Heartbeat logic optimized for MCU (no floats, precalculated constants)
pub fn get_heartbeat(sys_time_ms: u32) -> u16 {
let time_tick = (((sys_time_ms % PERIOD_MS) as u64 * MAX_VAL as u64) / PERIOD_MS as u64) as u32;
// 1. Calculate the pulse intensity (0..MAX_VAL)
let pulse = if time_tick > 2000 && time_tick < 7000 {
scale_pulse(time_tick, 2000, 4000, 7000, MAX_VAL)
} else if time_tick > 8500 && time_tick < 12500 {
scale_pulse(time_tick, 8500, 10000, 12500, MAX_VAL / 2)
} else {
0
};
// 2. Square it for that organic "ease-in" feel
let eased_pulse = (pulse * pulse) >> 15;
// 3. Map the 0..MAX_VAL pulse into the MIN_GLOW..MAX_VAL range
// Formula: MIN + (eased_pulse * (MAX - MIN) / MAX)
let range = MAX_VAL - MIN_GLOW;
let final_val = MIN_GLOW + ((eased_pulse * range) >> 15);
final_val as u16
}
#[inline(always)]
fn scale_pulse(t: u32, start: u32, peak: u32, end: u32, max: u32) -> u32 {
if t <= peak {
((t - start) * max) / (peak - start)
} else {
((end - t) * max) / (end - peak)
}
}