From 535822198b30bf21cad04530e631e668e46e9b70 Mon Sep 17 00:00:00 2001 From: Tropicananass Date: Mon, 23 Jun 2025 17:28:04 +0000 Subject: [PATCH] Working Diag & artnet proto with imported led driver smi lib C --- .vscode/launch.json | 6 +- .vscode/settings.json | 4 +- Cargo.lock | 629 +++++++++++++++++- Cargo.toml | 2 +- README.md | 42 +- lightsabre_backend/Cargo.toml | 6 + lightsabre_backend/build.rs | 4 + .../drivers/lib/libRpiLedBars_drivers.a | Bin 0 -> 23060 bytes lightsabre_backend/drivers/lib/liblogc.a | Bin 0 -> 4338 bytes lightsabre_backend/drivers/lib/log.h | 49 ++ lightsabre_backend/drivers/src/CMakeLists.txt | 22 + lightsabre_backend/drivers/src/common.c | 56 ++ lightsabre_backend/drivers/src/common.h | 50 ++ lightsabre_backend/drivers/src/dma/rpi_dma.c | 90 +++ lightsabre_backend/drivers/src/dma/rpi_dma.h | 33 + .../drivers/src/dma/rpi_videocore.c | 128 ++++ .../drivers/src/dma/rpi_videocore.h | 33 + .../drivers/src/gpio/rpi_gpio.c | 88 +++ .../drivers/src/gpio/rpi_gpio.h | 26 + .../drivers/src/leddriver/rpi_leddriver.c | 210 ++++++ .../drivers/src/leddriver/rpi_leddriver.h | 18 + .../drivers/src/libs/CMakeLists.txt | 13 + lightsabre_backend/drivers/src/smi/rpi_smi.c | 223 +++++++ lightsabre_backend/drivers/src/smi/rpi_smi.h | 14 + lightsabre_backend/src/channels.rs | 6 + lightsabre_backend/src/config.rs | 1 + lightsabre_backend/src/cputasks.rs | 1 + lightsabre_backend/src/cputasks/modes.rs | 107 +++ .../src/cputasks/modes/artnet.rs | 248 +++++++ .../src/cputasks/modes/diagnostics.rs | 83 +++ .../src/cputasks/modes/manual.rs | 24 + .../src/cputasks/modes/standalone.rs | 41 ++ lightsabre_backend/src/devices.rs | 2 + lightsabre_backend/src/devices/led_driver.rs | 90 +-- .../{ => led_driver}/led_autoconvert.rs | 0 .../src/devices/led_driver/led_driver.rs | 61 ++ .../src/devices/led_driver/memory.rs | 143 ++++ .../src/devices/led_driver/videocore.rs | 95 +++ lightsabre_backend/src/devices/mod.rs | 6 - lightsabre_backend/src/devices/selector.rs | 39 +- lightsabre_backend/src/iotasks.rs | 1 + lightsabre_backend/src/iotasks/selector.rs | 44 ++ lightsabre_backend/src/lib.rs | 121 ++++ lightsabre_backend/src/main.rs | 28 +- lightsabre_backend/src/statemachine.rs | 157 +++++ rust_sandbox/Cargo.toml | 7 + rust_sandbox/src/main.rs | 28 + tools/remote_debug.sh | 4 +- tools/run.sh | 30 + 49 files changed, 3014 insertions(+), 99 deletions(-) create mode 100644 lightsabre_backend/build.rs create mode 100644 lightsabre_backend/drivers/lib/libRpiLedBars_drivers.a create mode 100644 lightsabre_backend/drivers/lib/liblogc.a create mode 100644 lightsabre_backend/drivers/lib/log.h create mode 100644 lightsabre_backend/drivers/src/CMakeLists.txt create mode 100644 lightsabre_backend/drivers/src/common.c create mode 100644 lightsabre_backend/drivers/src/common.h create mode 100644 lightsabre_backend/drivers/src/dma/rpi_dma.c create mode 100644 lightsabre_backend/drivers/src/dma/rpi_dma.h create mode 100644 lightsabre_backend/drivers/src/dma/rpi_videocore.c create mode 100644 lightsabre_backend/drivers/src/dma/rpi_videocore.h create mode 100644 lightsabre_backend/drivers/src/gpio/rpi_gpio.c create mode 100644 lightsabre_backend/drivers/src/gpio/rpi_gpio.h create mode 100644 lightsabre_backend/drivers/src/leddriver/rpi_leddriver.c create mode 100644 lightsabre_backend/drivers/src/leddriver/rpi_leddriver.h create mode 100644 lightsabre_backend/drivers/src/libs/CMakeLists.txt create mode 100644 lightsabre_backend/drivers/src/smi/rpi_smi.c create mode 100644 lightsabre_backend/drivers/src/smi/rpi_smi.h create mode 100644 lightsabre_backend/src/channels.rs create mode 100644 lightsabre_backend/src/config.rs create mode 100644 lightsabre_backend/src/cputasks.rs create mode 100644 lightsabre_backend/src/cputasks/modes.rs create mode 100644 lightsabre_backend/src/cputasks/modes/artnet.rs create mode 100644 lightsabre_backend/src/cputasks/modes/diagnostics.rs create mode 100644 lightsabre_backend/src/cputasks/modes/manual.rs create mode 100644 lightsabre_backend/src/cputasks/modes/standalone.rs create mode 100644 lightsabre_backend/src/devices.rs rename lightsabre_backend/src/devices/{ => led_driver}/led_autoconvert.rs (100%) create mode 100644 lightsabre_backend/src/devices/led_driver/led_driver.rs create mode 100644 lightsabre_backend/src/devices/led_driver/memory.rs create mode 100644 lightsabre_backend/src/devices/led_driver/videocore.rs delete mode 100644 lightsabre_backend/src/devices/mod.rs create mode 100644 lightsabre_backend/src/iotasks.rs create mode 100644 lightsabre_backend/src/iotasks/selector.rs create mode 100644 lightsabre_backend/src/lib.rs create mode 100644 lightsabre_backend/src/statemachine.rs create mode 100644 rust_sandbox/Cargo.toml create mode 100644 rust_sandbox/src/main.rs create mode 100755 tools/run.sh diff --git a/.vscode/launch.json b/.vscode/launch.json index 8aa13ee..770d59d 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -15,9 +15,9 @@ "processCreateCommands": [ "gdb-remote raspberrypi.local:17777" ], - "env": { - "RUST_LOG": "debug" - } + // "env": { + // "RUST_LOG": "trace" + // } } ] } \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index bd9cba6..4c22c00 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,5 @@ { - "rust-analyzer.cargo.target": "armv7-unknown-linux-gnueabihf" + "rust-analyzer.cargo.target": "armv7-unknown-linux-gnueabihf", + "editor.formatOnSave": true, + "editor.formatOnPaste": true } \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index c6b5456..9d52884 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + [[package]] name = "aho-corasick" version = "1.1.3" @@ -11,6 +26,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "0.6.18" @@ -47,7 +77,7 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -58,7 +88,103 @@ checksum = "6680de5231bd6ee4c6191b8a1325daa282b415391ec9d3a37bd34f2060dc73fa" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "artnet_protocol" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f1ca8f664c611598975a2af1084042d2e44c0532db8b72a7b551153f4753627" +dependencies = [ + "bitflags 2.9.1", + "byteorder", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cc" +version = "1.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16595d3be041c03b09d08d0858631facccee9221e579704070e6e9e4915d3bc7" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", ] [[package]] @@ -67,6 +193,78 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "crossbeam" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "ctrlc" +version = "3.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46f93780a459b7d656ef7f071fe699c4d3d2cb201c4b24d085b6ddc505276e73" +dependencies = [ + "nix 0.30.1", + "windows-sys 0.59.0", +] + [[package]] name = "env_filter" version = "0.1.3" @@ -90,6 +288,36 @@ dependencies = [ "log", ] +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -120,6 +348,16 @@ dependencies = [ "syn", ] +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + [[package]] name = "libc" version = "0.2.172" @@ -130,11 +368,26 @@ checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" name = "lightsabre_backend" version = "0.1.0" dependencies = [ + "artnet_protocol", + "crossbeam", + "ctrlc", "env_logger", "log", + "nix 0.30.1", + "rpi-mailbox", "rppal", ] +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.27" @@ -147,12 +400,125 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.59.0", +] + +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset", + "pin-utils", +] + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + [[package]] name = "once_cell_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "portable-atomic" version = "1.11.0" @@ -186,6 +552,15 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_syscall" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" +dependencies = [ + "bitflags 2.9.1", +] + [[package]] name = "regex" version = "1.11.1" @@ -215,6 +590,19 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "rpi-mailbox" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "161a8b2c05deee778809bf165433051db0e057fc32b4999ca0247924c705386e" +dependencies = [ + "bitflags 2.9.1", + "chrono", + "log", + "nix 0.26.4", + "thiserror", +] + [[package]] name = "rppal" version = "0.22.1" @@ -224,6 +612,31 @@ dependencies = [ "libc", ] +[[package]] +name = "rust_sandbox" +version = "0.1.0" +dependencies = [ + "tokio", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustversion" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "serde" version = "1.0.219" @@ -244,6 +657,37 @@ dependencies = [ "syn", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +dependencies = [ + "libc", +] + +[[package]] +name = "smallvec" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "syn" version = "2.0.101" @@ -255,6 +699,55 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio" +version = "1.45.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "unicode-ident" version = "1.0.18" @@ -267,6 +760,138 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.59.0" diff --git a/Cargo.toml b/Cargo.toml index 0a637e4..1cc0ad9 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,3 @@ [workspace] resolver = "3" -members = ["lightsabre_backend"] +members = ["lightsabre_backend", "rust_sandbox"] diff --git a/README.md b/README.md index 4e63c65..2fcb420 100755 --- a/README.md +++ b/README.md @@ -6,8 +6,8 @@ LightSabre is a project designed to control WS281x RGB LEDs using the Secondary - **WS281x LED Control:** High-performance driving of addressable RGB LEDs via the Raspberry Pi SMI. - **ArtNet Support:** Receives color data over the ArtNet protocol, allowing the Raspberry Pi to function as a DMX controller. -- **Autonomous Mode:** Analyzes audio input from an I2S microphone, performing spectral decomposition to generate dynamic lighting effects. -- **Web Interface:** Local web server for configuring and tweaking autonomous behavior. +- **Standalone Mode:** Analyzes audio input from an I2S microphone, performing spectral decomposition to generate dynamic lighting effects. +- **Web Interface:** Local web server for configuring and tweaking standalone behavior. - **MIDI Integration:** Supports real-time control and parameter adjustment via MIDI controllers. ## Technology Stack @@ -26,18 +26,46 @@ LightSabre is a project designed to control WS281x RGB LEDs using the Secondary 1. **Hardware Requirements** - Raspberry Pi (any model with SMI support) - WS281x-compatible RGB LED strip - - I2S microphone (for autonomous mode) + - I2S microphone (for standalone mode) - Optional: MIDI controller -2. **Software Setup** - - Install dependencies (see `requirements.txt`) - - Configure network for ArtNet or connect MIDI controller - - Access the web interface via your browser +### System install + +The project is design for running on top of [Raspberry Pi OS Lite 32bits](https://www.raspberrypi.com/software/) (formerly Raspbian). + +Raspberry Pi OS Lite provides device access through file system, networking tools for network interface management, POSIX sockets, native threading, and process control, covering all system features needed by LightSabre. + +What about Realtime ? + +#### Network AP / infra + +`sudo raspi-config` + +See. `nmcli` +`nmcli c up preconfigured` + + + +#### Rust +After installing Raspberry Pi OS we need to install Rust toolchain: +``` +sudo apt update +sudo apt full upgrade -y +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` + +For more details : https://rustup.rs/ + +#### I2S Microphone + +[I2S microphone](https://learn.adafruit.com/adafruit-i2s-mems-microphone-breakout/raspberry-pi-wiring-test) + ## Documentation - [ArtNet Protocol](https://art-net.org.uk/) - [WS281x LEDs](https://cdn-shop.adafruit.com/datasheets/WS2812.pdf) +- [I2S microphone](https://learn.adafruit.com/adafruit-i2s-mems-microphone-breakout/raspberry-pi-wiring-test) - [Raspberry Pi SMI](https://www.raspberrypi.com/documentation/computers/raspberry-pi.html) - [MIDI Protocol](https://www.midi.org/) diff --git a/lightsabre_backend/Cargo.toml b/lightsabre_backend/Cargo.toml index f71f473..7fdbced 100644 --- a/lightsabre_backend/Cargo.toml +++ b/lightsabre_backend/Cargo.toml @@ -4,9 +4,15 @@ version = "0.1.0" edition = "2024" [dependencies] +artnet_protocol = "0.4.3" +crossbeam = "0.8.4" +ctrlc = { version = "3.4.7", features = ["termination"] } env_logger = "0.11.8" log = "0.4.27" +nix = "0.30.1" +rpi-mailbox = "0.3.0" rppal = "0.22.1" +# tokio = { version = "1.45.1", features = ["full"] } [features] default = ["rpizero2", "16channel"] diff --git a/lightsabre_backend/build.rs b/lightsabre_backend/build.rs new file mode 100644 index 0000000..8a9b9a8 --- /dev/null +++ b/lightsabre_backend/build.rs @@ -0,0 +1,4 @@ +fn main() { + println!("cargo::rustc-link-search=/workspaces/LightSabre/lightsabre_backend/drivers/lib/"); + // println!("cargo:rustc-link-search=/workspaces/LightSabre/lightsabre_backend/drivers/lib"); +} \ No newline at end of file diff --git a/lightsabre_backend/drivers/lib/libRpiLedBars_drivers.a b/lightsabre_backend/drivers/lib/libRpiLedBars_drivers.a new file mode 100644 index 0000000000000000000000000000000000000000..682820cb9b1034db1dd3983f79c0cd8ba238b39b GIT binary patch literal 23060 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 MX*V%}PZulxU+nqlyZ`_I literal 0 HcmV?d00001 diff --git a/lightsabre_backend/drivers/lib/liblogc.a b/lightsabre_backend/drivers/lib/liblogc.a new file mode 100644 index 0000000000000000000000000000000000000000..eb7a245f17a49f4867464695da09ddd214baa90e GIT binary patch literal 4338 zcmcInO>7&-6@E*u6h@-kZNMU|r6${!M3h9NS2iuEf>216e^^L%48=x%3`C|xDke&a zD!B?0z(CR3hoG(i*FLmBTbL+Xpobg`G>0H?05`~?=phl1OOumGc@s4akQC^#i1K~2 zGt!2&6JI*Q@SAVmn>X*xytmr@&P1uPc>V`{2iz|$Upx^R>zgmD+sMZ&X*F7fv)Q{(P;z_~Si7ja~MWwQ9vztW++~FP3Vxg_2h0#L}vg z!4NUOn6K{!a5v9K3kBtI!5a$)?zz679-TdASr!}*nGv*fird<9+-arj z$Tfv&thoaHc>C&yv_XD}d<5Jb2omnqAngnUDd$%~>efsr;j9O?yB>(Q9()b9lWwd1 zv~xY+UR-bN+D>R++-rL>*p54`cAtd4DSggKtF3$m+8Z++dHId)bBHyq;uWb8b-bq# zZxQhpb-YIq@1NoCWAbB@FL<&A|=k5^OFLhk4NL<6-^}vP=kter$MfwIcPr+A#Z!y@maBhco4zvX} zc}B{NK9_r0k9GgE-oB9w1bLLixh8O~+%JLHc>lZ?*|^-XZ~XrDLuy}Px5RqR(^iPdb3~R9`u>ZJ>HDkqsKdoe1G#x_sHnl7*sy?!>86(!s`!mA|Gww zUK6d4#HaHS#qtZw`eXo zSm!Vou+G7Le6BDr@5fm%hs<*&^k=5CTfc6rt@Lpg#1!^)FlHW@eQXc;rgL@z`xLOx z81|WP`-5Q*wePmJc$PML0zHXN8!7ZOy6yco7{WP>!F~of0Zao6+z+tzq zoZ0s}P4ANr==;}EQ%TIT-V5+6E^~Me$?O01SYOxF-mEdkauYTZ-sR3P?9nIA{oyn9 zEZs_C?%TK%o@aONFmL!QVlFw*;~7Nv!&Ccp&e>i2wIcgb&fFVzXF4(L%X()$@&36t z$Gm^|{`U#w(VhJ0&f#}=cJ}{={+oL!zOij@tanUJtGmZf0^LF9bJs;5Lif)1Mn63_HGLF#GaN#fDDxhLj;$EHqC%>oC`e!sA+@X)Bn zgBlNM9MO1~m@STHPmE`u#89$LH`~nS4mStRddn^>dCS@6c(y#QIONadvdg(_*^MmX z8mz!VD!L-ar>Do=p}A6Xb)i~X!J9+=VBX7(6drG^R!1JyT&D8auT&{5RK?0**fT1< zy$S1nt1tF|owB}ZJs3}0-?H|{2CavBzZ?B04Q00Ilr8_>31`FK_!p!iar{MFTzb?i z4Ck)EkOhvKi?p7|nCpAHi3n%Tkxv4R{UZ@M{V7H`{q-Z9 z@)skV@>e6A^4B7q^4k$k`F4a;p5DE-(BHubS8^Lls%d$IQ+_JKY5#PDvxvycKof5f z{{+IedytC8RmB<^^WnZdf>9i|_iSXw!I*YUBVRNGNM7sWjV}HYIM3SyK7f4q1(4@E zsrj#gJWnJ46FAS0@2=GU3s9XGLLq;P9Py56`Ex+i=+8Tb0LkA&e+~M-(d*k?^6*7m zW+hXW=-Yl%v&DL?-e^juBu&3uU-S1~>X)ju${t%=QJ)3Xm8w6FPl6R6`i&*Ox?GVL zmR9lo;4jJ2>YBeOOG~x2=K1hL;Q#P>fsYQgtNMJwhX&lPG#ZjY+=D3AE5ye|ULg*P z+#=>gHi&qbZ31IRi`zux>9)p?^?VBFNINwm?7U2bo!=2*=M^ICu%>C}77=#d(wwy$ z!+o$OiL5=!S!3j^B_eA^A;!cB^ua@6EyQrK7swAI9^xo+3^`A|St64|`->^%M?zy# zBXfiPr8M&SMn0&~)tJ#Zq!FPiJ;^s;TJ}o|K)(^uYh`tmYUB)?MkEBWIm&*YjuzZ3IQb0_j8AD^2GYksAPt?s$uy%W_Tc*2K`^>fM) zjCl@h1L%CFOrod|M@j2_YWM%hj1UBSYBj8PWKKL^M&F|lGOvryRPKsRIW8{kpBOE|xZQ34vh F&jH!S$?5 +#include +#include +#include + +#define LOG_VERSION "0.1.0" + +typedef struct { + va_list ap; + const char *fmt; + const char *file; + struct tm *time; + void *udata; + int line; + int level; +} log_Event; + +typedef void (*log_LogFn)(log_Event *ev); +typedef void (*log_LockFn)(bool lock, void *udata); + +enum { LOG_TRACE, LOG_DEBUG, LOG_INFO, LOG_WARN, LOG_ERROR, LOG_FATAL }; + +#define log_trace(...) log_log(LOG_TRACE, __FILE__, __LINE__, __VA_ARGS__) +#define log_debug(...) log_log(LOG_DEBUG, __FILE__, __LINE__, __VA_ARGS__) +#define log_info(...) log_log(LOG_INFO, __FILE__, __LINE__, __VA_ARGS__) +#define log_warn(...) log_log(LOG_WARN, __FILE__, __LINE__, __VA_ARGS__) +#define log_error(...) log_log(LOG_ERROR, __FILE__, __LINE__, __VA_ARGS__) +#define log_fatal(...) log_log(LOG_FATAL, __FILE__, __LINE__, __VA_ARGS__) + +const char* log_level_string(int level); +void log_set_lock(log_LockFn fn, void *udata); +void log_set_level(int level); +void log_set_quiet(bool enable); +int log_add_callback(log_LogFn fn, void *udata, int level); +int log_add_fp(FILE *fp, int level); + +void log_log(int level, const char *file, int line, const char *fmt, ...); + +#endif diff --git a/lightsabre_backend/drivers/src/CMakeLists.txt b/lightsabre_backend/drivers/src/CMakeLists.txt new file mode 100644 index 0000000..0bb8564 --- /dev/null +++ b/lightsabre_backend/drivers/src/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.12) + +# set the project name +project(RpiLedBars VERSION 0.5 LANGUAGES C) + +set(CMAKE_C_STANDARD 99) + +add_subdirectory(libs) + +# add the executable +add_library(${PROJECT_NAME}_drivers + common.c + dma/rpi_dma.c + dma/rpi_videocore.c + gpio/rpi_gpio.c + leddriver/rpi_leddriver.c + smi/rpi_smi.c) + +target_link_libraries(${PROJECT_NAME}_drivers PRIVATE wiringPi) +target_link_libraries(${PROJECT_NAME}_drivers PRIVATE logc) + +target_include_directories(${PROJECT_NAME}_drivers PUBLIC dma gpio leddriver smi) \ No newline at end of file diff --git a/lightsabre_backend/drivers/src/common.c b/lightsabre_backend/drivers/src/common.c new file mode 100644 index 0000000..612095f --- /dev/null +++ b/lightsabre_backend/drivers/src/common.c @@ -0,0 +1,56 @@ +#include "common.h" + +#include +#include +#include +#include +#include +#include + +#include "log.h" + +// Use mmap to obtain virtual address, given physical +void *map_periph(MEM_MAP *mp, void *phys, int size) { + mp->phys = phys; + mp->size = PAGE_ROUNDUP(size); + mp->bus = (void *)((uint32_t)phys - PHYS_REG_BASE + BUS_REG_BASE); + mp->virt = map_segment(phys, mp->size); + return (mp->virt); +} + +// Free mapped peripheral or memory +void unmap_periph_mem(MEM_MAP *mp) { + if (mp) { + unmap_segment(mp->virt, mp->size); + } +} + +// ----- VIRTUAL MEMORY ----- + +// Get virtual memory segment for peripheral regs or physical mem +void *map_segment(void *addr, int size) { + int fd; + void *mem; + + size = PAGE_ROUNDUP(size); + if ((fd = open("/dev/mem", O_RDWR | O_SYNC | O_CLOEXEC)) < 0) { + log_fatal("can't open /dev/mem, run using sudo"); + exit(-EXIT_FAILURE); + } + + mem = mmap(0, size, PROT_WRITE | PROT_READ, MAP_SHARED, fd, (uint32_t)addr); + close(fd); + log_info("Map %p -> %p", (void *)addr, mem); + if (mem == MAP_FAILED) { + log_fatal("can't map memory"); + exit(-EXIT_FAILURE); + } + return (mem); +} + +// Free mapped memory +void unmap_segment(void *mem, int size) { + if (mem) { + munmap(mem, PAGE_ROUNDUP(size)); + } +} \ No newline at end of file diff --git a/lightsabre_backend/drivers/src/common.h b/lightsabre_backend/drivers/src/common.h new file mode 100644 index 0000000..5652292 --- /dev/null +++ b/lightsabre_backend/drivers/src/common.h @@ -0,0 +1,50 @@ +#if !defined(__COMMON_H__) +#define __COMMON_H__ + +// Location of peripheral registers in physical memory +#define PHYS_REG_BASE PI_23_REG_BASE +#define PI_01_REG_BASE 0x20000000 // Pi Zero or 1 +#define PI_23_REG_BASE 0x3F000000 // Pi 2 or 3 +#define PI_4_REG_BASE 0xFE000000 // Pi 4 + +#define CLOCK_HZ 250000000 // Pi 2 - 4 +//#define CLOCK_HZ 400000000 // Pi Zero + +// Location of peripheral registers in bus memory +#define BUS_REG_BASE 0x7E000000 + +// Get virtual 8 and 32-bit pointers to register +#define REG8(m, x) ((volatile uint8_t *)((uint32_t)(m.virt) + (uint32_t)(x))) +#define REG32(m, x) ((volatile uint32_t *)((uint32_t)(m.virt) + (uint32_t)(x))) +// Get bus address of register +#define REG_BUS_ADDR(m, x) ((uint32_t)(m.bus) + (uint32_t)(x)) +// Convert uncached memory virtual address to bus address +#define MEM_BUS_ADDR(mp, a) ((uint32_t)a - (uint32_t)mp->virt + (uint32_t)mp->bus) +// Convert bus address to physical address (for mmap) +#define BUS_PHYS_ADDR(a) ((void *)((uint32_t)(a) & ~0xC0000000)) + +// Size of memory page +#define PAGE_SIZE 0x1000 +// Round up to nearest page +#define PAGE_ROUNDUP(n) ((n) % PAGE_SIZE == 0 ? (n) : ((n) + PAGE_SIZE) & ~(PAGE_SIZE - 1)) + +// Structure for mapped peripheral or memory +typedef struct { + int fd, // File descriptor + h, // Memory handle + size; // Memory size + void *bus, // Bus address + *virt, // Virtual address + *phys; // Physical address +} MEM_MAP; + +// Use mmap to obtain virtual address, given physical +void *map_periph(MEM_MAP *mp, void *phys, int size); + +// Free mapped peripheral or memory +void unmap_periph_mem(MEM_MAP *mp); + +void *map_segment(void *addr, int size); +void unmap_segment(void *addr, int size); + +#endif // __COMMON_H__ \ No newline at end of file diff --git a/lightsabre_backend/drivers/src/dma/rpi_dma.c b/lightsabre_backend/drivers/src/dma/rpi_dma.c new file mode 100644 index 0000000..8c9510b --- /dev/null +++ b/lightsabre_backend/drivers/src/dma/rpi_dma.c @@ -0,0 +1,90 @@ +#include "rpi_dma.h" + +#include + +#include "rpi_videocore.h" + +// DMA channels and data requests +#define DMA_SMI_DREQ 4 +#define DMA_PWM_DREQ 5 +#define DMA_SPI_TX_DREQ 6 +#define DMA_SPI_RX_DREQ 7 +#define DMA_BASE (PHYS_REG_BASE + 0x007000) +// DMA register addresses offset by 0x100 * chan_num +#define DMA_CS 0x00 +#define DMA_CONBLK_AD 0x04 +#define DMA_TI 0x08 +#define DMA_SRCE_AD 0x0c +#define DMA_DEST_AD 0x10 +#define DMA_TXFR_LEN 0x14 +#define DMA_STRIDE 0x18 +#define DMA_NEXTCONBK 0x1c +#define DMA_DEBUG 0x20 +#define DMA_REG(ch, r) ((r) == DMA_ENABLE ? DMA_ENABLE : (ch)*0x100 + (r)) +#define DMA_ENABLE 0xff0 +// DMA register values +#define DMA_WAIT_RESP (1 << 3) +#define DMA_CB_DEST_INC (1 << 4) +#define DMA_DEST_DREQ (1 << 6) +#define DMA_CB_SRCE_INC (1 << 8) +#define DMA_SRCE_DREQ (1 << 10) +#define DMA_PRIORITY(n) ((n) << 16) + +// Virtual memory pointers to acceess GPIO, DMA and PWM from user space +MEM_MAP dma_regs; + +char *dma_regstrs[] = {"DMA CS", "CB_AD", "TI", "SRCE_AD", "DEST_AD", + "TFR_LEN", "STRIDE", "NEXT_CB", "DEBUG", ""}; + +void dma_setup(MEM_MAP *mp, int chan, int nsamp, uint8_t **txdata, int offset, uint32_t dest_ad) { + map_periph(&dma_regs, (void *)DMA_BASE, PAGE_SIZE); + + DMA_CB *cbs = mp->virt; + + *txdata = (uint8_t *)(cbs + offset); + enable_dma(chan); + cbs[0].ti = DMA_DEST_DREQ | (DMA_SMI_DREQ << 16) | DMA_CB_SRCE_INC | DMA_WAIT_RESP; + cbs[0].tfr_len = nsamp; + cbs[0].srce_ad = MEM_BUS_ADDR(mp, *txdata); + cbs[0].dest_ad = dest_ad; +} + +void dma_close() { unmap_periph_mem(&dma_regs); } + +// Enable and reset DMA +void enable_dma(int chan) { + *REG32(dma_regs, DMA_ENABLE) |= (1 << chan); + *REG32(dma_regs, DMA_REG(chan, DMA_CS)) = 1 << 31; +} + +// Start DMA, given first control block +void start_dma(MEM_MAP *mp, int chan, DMA_CB *cbp, uint32_t csval) { + *REG32(dma_regs, DMA_REG(chan, DMA_CONBLK_AD)) = MEM_BUS_ADDR(mp, cbp); + *REG32(dma_regs, DMA_REG(chan, DMA_CS)) = 2; // Clear 'end' flag + *REG32(dma_regs, DMA_REG(chan, DMA_DEBUG)) = 7; // Clear error bits + *REG32(dma_regs, DMA_REG(chan, DMA_CS)) = 1 | csval; // Start DMA +} + +// Return remaining transfer length +uint32_t dma_transfer_len(int chan) { return (*REG32(dma_regs, DMA_REG(chan, DMA_TXFR_LEN))); } + +// Check if DMA is active +uint32_t dma_active(int chan) { return ((*REG32(dma_regs, DMA_REG(chan, DMA_CS))) & 1); } + +// Halt current DMA operation by resetting controller +void stop_dma(int chan) { + if (dma_regs.virt) + *REG32(dma_regs, DMA_REG(chan, DMA_CS)) = 1 << 31; +} + +// Display DMA registers +void disp_dma(int chan) { + volatile uint32_t *p = REG32(dma_regs, DMA_REG(chan, DMA_CS)); + int i = 0; + + while (dma_regstrs[i][0]) { + printf("%-7s %08X ", dma_regstrs[i++], *p++); + if (i % 5 == 0 || dma_regstrs[i][0] == 0) + printf("\n"); + } +} \ No newline at end of file diff --git a/lightsabre_backend/drivers/src/dma/rpi_dma.h b/lightsabre_backend/drivers/src/dma/rpi_dma.h new file mode 100644 index 0000000..0d96c5e --- /dev/null +++ b/lightsabre_backend/drivers/src/dma/rpi_dma.h @@ -0,0 +1,33 @@ +#if !defined(__RPI_DMA_H__) +#define __RPI_DMA_H__ + +#include + +#include "../common.h" + +#define DMA_CHAN_A 10 +#define DMA_CHAN_B 11 + +// DMA control block (must be 32-byte aligned) +typedef struct { + uint32_t ti, // Transfer info + srce_ad, // Source address + dest_ad, // Destination address + tfr_len, // Transfer length + stride, // Transfer stride + next_cb, // Next control block + debug, // Debug register, zero in control block + unused; +} DMA_CB __attribute__((aligned(32))); + +void dma_setup(MEM_MAP *mp, int chan, int nsamp, uint8_t **txdata, int offset, uint32_t dest_ad); +void dma_close(); + +void enable_dma(int chan); +void start_dma(MEM_MAP *mp, int chan, DMA_CB *cbp, uint32_t csval); +uint32_t dma_transfer_len(int chan); +uint32_t dma_active(int chan); +void stop_dma(int chan); +void disp_dma(int chan); + +#endif // __RPI_DMA_H__ \ No newline at end of file diff --git a/lightsabre_backend/drivers/src/dma/rpi_videocore.c b/lightsabre_backend/drivers/src/dma/rpi_videocore.c new file mode 100644 index 0000000..81fc9ed --- /dev/null +++ b/lightsabre_backend/drivers/src/dma/rpi_videocore.c @@ -0,0 +1,128 @@ +#include "rpi_videocore.h" + +#include +#include +#include +#include + +#include "log.h" + +// Mailbox command/response structure +typedef struct { + uint32_t len, // Overall length (bytes) + req, // Zero for request, 1<<31 for response + tag, // Command number + blen, // Buffer length (bytes) + dlen; // Data length (bytes) + uint32_t uints[32 - 5]; // Data (108 bytes maximum) +} VC_MSG __attribute__((aligned(16))); + +void disp_vc_msg(VC_MSG *msgp); + +int open_mbox(void); +void close_mbox(int fd); +uint32_t msg_mbox(int fd, VC_MSG *msgp); +void *map_uncached_mem(MEM_MAP *mp, int size); + +void videocore_setup(MEM_MAP *mp, int size) { map_uncached_mem(mp, size); } +void videocore_close(MEM_MAP *mp) { + unmap_periph_mem(mp); + if (mp->fd) { + unlock_vc_mem(mp->fd, mp->h); + free_vc_mem(mp->fd, mp->h); + close_mbox(mp->fd); + } +} + +// Allocate uncached memory, get bus & phys addresses +void *map_uncached_mem(MEM_MAP *mp, int size) { + void *ret; + mp->size = PAGE_ROUNDUP(size); + mp->fd = open_mbox(); + ret = (mp->h = alloc_vc_mem(mp->fd, mp->size, DMA_MEM_FLAGS)) > 0 && + (mp->bus = lock_vc_mem(mp->fd, mp->h)) != 0 && + (mp->virt = map_segment(BUS_PHYS_ADDR(mp->bus), mp->size)) != 0 + ? mp->virt + : 0; + log_info("VC mem handle %u, phys %p, virt %p", mp->h, mp->bus, mp->virt); + return (ret); +} + +// Allocate memory on PAGE_SIZE boundary, return handle +uint32_t alloc_vc_mem(int fd, uint32_t size, VC_ALLOC_FLAGS flags) { + VC_MSG msg = { + .tag = 0x3000c, .blen = 12, .dlen = 12, .uints = {PAGE_ROUNDUP(size), PAGE_SIZE, flags}}; + return (msg_mbox(fd, &msg)); +} +// Lock allocated memory, return bus address +void *lock_vc_mem(int fd, int h) { + VC_MSG msg = {.tag = 0x3000d, .blen = 4, .dlen = 4, .uints = {h}}; + return (h ? (void *)msg_mbox(fd, &msg) : 0); +} +// Unlock allocated memory +uint32_t unlock_vc_mem(int fd, int h) { + VC_MSG msg = {.tag = 0x3000e, .blen = 4, .dlen = 4, .uints = {h}}; + return (h ? msg_mbox(fd, &msg) : 0); +} +// Free memory +uint32_t free_vc_mem(int fd, int h) { + VC_MSG msg = {.tag = 0x3000f, .blen = 4, .dlen = 4, .uints = {h}}; + return (h ? msg_mbox(fd, &msg) : 0); +} +uint32_t set_vc_clock(int fd, int id, uint32_t freq) { + VC_MSG msg1 = {.tag = 0x38001, .blen = 8, .dlen = 8, .uints = {id, 1}}; + VC_MSG msg2 = {.tag = 0x38002, .blen = 12, .dlen = 12, .uints = {id, freq, 0}}; + msg_mbox(fd, &msg1); + disp_vc_msg(&msg1); + msg_mbox(fd, &msg2); + disp_vc_msg(&msg2); + return (0); +} + +// Display mailbox message +void disp_vc_msg(VC_MSG *msgp) { + int i; + + printf("VC msg len=%X, req=%X, tag=%X, blen=%x, dlen=%x, data ", msgp->len, msgp->req, msgp->tag, + msgp->blen, msgp->dlen); + for (i = 0; i < msgp->blen / 4; i++) + printf("%08X ", msgp->uints[i]); + printf("\n"); +} + +// Open mailbox interface, return file descriptor +int open_mbox(void) { + int fd; + + if ((fd = open("/dev/vcio", 0)) < 0) + log_error("can't open VC mailbox"); + return (fd); +} +// Close mailbox interface +void close_mbox(int fd) { + if (fd >= 0) + close(fd); +} + +// Send message to mailbox, return first response int, 0 if error +uint32_t msg_mbox(int fd, VC_MSG *msgp) { + uint32_t ret = 0, i; + + for (i = msgp->dlen / 4; i <= msgp->blen / 4; i += 4) + msgp->uints[i++] = 0; + msgp->len = (msgp->blen + 6) * 4; + msgp->req = 0; + if (ioctl(fd, _IOWR(100, 0, void *), msgp) < 0) { + log_error("VC IOCTL failed"); + } else if ((msgp->req & 0x80000000) == 0) { + log_error("VC IOCTL error"); + } else if (msgp->req == 0x80000001) { + log_error("VC IOCTL partial error"); + } else { + ret = msgp->uints[0]; + } +#if DEBUG + disp_vc_msg(msgp); +#endif + return (ret); +} \ No newline at end of file diff --git a/lightsabre_backend/drivers/src/dma/rpi_videocore.h b/lightsabre_backend/drivers/src/dma/rpi_videocore.h new file mode 100644 index 0000000..e83951b --- /dev/null +++ b/lightsabre_backend/drivers/src/dma/rpi_videocore.h @@ -0,0 +1,33 @@ +#if !defined(__RPI_VIDEOCORE_H__) +#define __RPI_VIDEOCORE_H__ + +#include + +#include "../common.h" + +// Videocore mailbox memory allocation flags, see: +// https://github.com/raspberrypi/firmware/wiki/Mailbox-property-interface +typedef enum { + MEM_FLAG_DISCARDABLE = 1 << 0, // can be resized to 0 at any time. Use for cached data + MEM_FLAG_NORMAL = 0 << 2, // normal allocating alias. Don't use from ARM + MEM_FLAG_DIRECT = 1 << 2, // 0xC alias uncached + MEM_FLAG_COHERENT = 2 << 2, // 0x8 alias. Non-allocating in L2 but coherent + MEM_FLAG_ZERO = 1 << 4, // initialise buffer to all zeros + MEM_FLAG_NO_INIT = 1 << 5, // don't initialise (default is initialise to all ones) + MEM_FLAG_HINT_PERMALOCK = 1 << 6, // Likely to be locked for long periods of time + MEM_FLAG_L1_NONALLOCATING = (MEM_FLAG_DIRECT | MEM_FLAG_COHERENT) // Allocating in L2 +} VC_ALLOC_FLAGS; + +// VC flags for unchached DMA memory +#define DMA_MEM_FLAGS (MEM_FLAG_DIRECT | MEM_FLAG_ZERO) + +void videocore_setup(MEM_MAP *mp, int size); +void videocore_close(MEM_MAP *mp); + +uint32_t alloc_vc_mem(int fd, uint32_t size, VC_ALLOC_FLAGS flags); +void *lock_vc_mem(int fd, int h); +uint32_t unlock_vc_mem(int fd, int h); +uint32_t free_vc_mem(int fd, int h); +uint32_t set_vc_clock(int fd, int id, uint32_t freq); + +#endif // __RPI_VIDEOCORE_H__ \ No newline at end of file diff --git a/lightsabre_backend/drivers/src/gpio/rpi_gpio.c b/lightsabre_backend/drivers/src/gpio/rpi_gpio.c new file mode 100644 index 0000000..57cfa71 --- /dev/null +++ b/lightsabre_backend/drivers/src/gpio/rpi_gpio.c @@ -0,0 +1,88 @@ +#include "rpi_gpio.h" + +#include +#include +#include +#include + +#include "../common.h" + +// GPIO register definitions +#define GPIO_BASE (PHYS_REG_BASE + 0x200000) +#define GPIO_MODE0 0x00 +#define GPIO_SET0 0x1c +#define GPIO_CLR0 0x28 +#define GPIO_LEV0 0x34 +#define GPIO_GPPUD 0x94 +#define GPIO_GPPUDCLK0 0x98 + +#define GPIO_MODE_STRS "IN", "OUT", "ALT5", "ALT4", "ALT0", "ALT1", "ALT2", "ALT3" + +bool isInitialized = false; + +// Virtual memory pointers to acceess GPIO, DMA and PWM from user space +MEM_MAP gpio_regs; + +char *gpio_mode_strs[] = {GPIO_MODE_STRS}; + +// definitions + +void gpio_setup() { + if (!isInitialized) { + map_periph(&gpio_regs, (void *)GPIO_BASE, PAGE_SIZE); + isInitialized = true; + } +} + +void gpio_close() { + if (isInitialized) { + unmap_periph_mem(&gpio_regs); + isInitialized = true; + } +} + +// Set input or output with pullups +void gpio_set(int pin, int mode, int pull) { + gpio_mode(pin, mode); + gpio_pull(pin, pull); +} +// Set I/P pullup or pulldown +void gpio_pull(int pin, int pull) { + volatile uint32_t *reg = REG32(gpio_regs, GPIO_GPPUDCLK0) + pin / 32; + + *REG32(gpio_regs, GPIO_GPPUD) = pull; + usleep(2); + *reg = pin << (pin % 32); + usleep(2); + *REG32(gpio_regs, GPIO_GPPUD) = 0; + *reg = 0; +} + +// Set input or output +void gpio_mode(int pin, int mode) { + if (gpio_regs.virt) { + volatile uint32_t *reg = REG32(gpio_regs, GPIO_MODE0) + pin / 10, shift = (pin % 10) * 3; + *reg = (*reg & ~(7 << shift)) | (mode << shift); + } +} + +// Set an O/P pin +void gpio_out(int pin, int val) { + volatile uint32_t *reg = REG32(gpio_regs, val ? GPIO_SET0 : GPIO_CLR0) + pin / 32; + *reg = 1 << (pin % 32); +} + +// Get an I/P pin value +uint8_t gpio_in(int pin) { + volatile uint32_t *reg = REG32(gpio_regs, GPIO_LEV0) + pin / 32; + return (((*reg) >> (pin % 32)) & 1); +} + +// Display the values in a GPIO mode register +void disp_mode_vals(uint32_t mode) { + int i; + + for (i = 0; i < 10; i++) + printf("%u:%-4s ", i, gpio_mode_strs[(mode >> (i * 3)) & 7]); + printf("\n"); +} \ No newline at end of file diff --git a/lightsabre_backend/drivers/src/gpio/rpi_gpio.h b/lightsabre_backend/drivers/src/gpio/rpi_gpio.h new file mode 100644 index 0000000..9f39848 --- /dev/null +++ b/lightsabre_backend/drivers/src/gpio/rpi_gpio.h @@ -0,0 +1,26 @@ +#if !defined(__RPI_GPIO_H__) +#define __RPI_GPIO_H__ + +// GPIO I/O definitions +#define GPIO_IN 0 +#define GPIO_OUT 1 +#define GPIO_ALT0 4 +#define GPIO_ALT1 5 +#define GPIO_ALT2 6 +#define GPIO_ALT3 7 +#define GPIO_ALT4 3 +#define GPIO_ALT5 2 + +#define GPIO_NOPULL 0 +#define GPIO_PULLDN 1 +#define GPIO_PULLUP 2 + +void gpio_setup(); + +void gpio_close(); + +void gpio_pull(int pin, int pull); + +void gpio_mode(int pin, int mode); + +#endif // __RPI_GPIO_H__ \ No newline at end of file diff --git a/lightsabre_backend/drivers/src/leddriver/rpi_leddriver.c b/lightsabre_backend/drivers/src/leddriver/rpi_leddriver.c new file mode 100644 index 0000000..6b4743e --- /dev/null +++ b/lightsabre_backend/drivers/src/leddriver/rpi_leddriver.c @@ -0,0 +1,210 @@ +#include "rpi_leddriver.h" + +#include +#include +#include + +#include "../../rpi_param.h" +#include "../common.h" + +#include "../dma/rpi_dma.h" +#include "../dma/rpi_videocore.h" +#include "../gpio/rpi_gpio.h" +#include "../smi/rpi_smi.h" + +#if PHYS_REG_BASE == PI_4_REG_BASE // Timings for RPi v4 (1.5 GHz) +#define SMI_TIMING 10, 15, 30, 15 // 400 ns cycle time +#else // Timings for RPi v0-3 (1 GHz) +#define SMI_TIMING 10, 10, 20, 10 // 400 ns cycle time +#endif + +#define TX_TEST 0 // If non-zero, use dummy Tx data +#define LED_NBITS 24 // Number of data bits per LED +#define LED_PREBITS 4 // Number of zero bits before LED data +#define LED_POSTBITS 4 // Number of zero bits after LED data +#define BIT_NPULSES 3 // Number of O/P pulses per LED bit + +// Length of data for 1 row (1 LED on each channel) +#define LED_DLEN (LED_NBITS * BIT_NPULSES) + +// Transmit data type, 8 or 16 bits +#if LED_NCHANS > 8 +#define TXDATA_T uint16_t +#else +#define TXDATA_T uint8_t +#endif + +// Ofset into Tx data buffer, given LED number in chan +#define LED_TX_OSET(n) (LED_PREBITS + (LED_DLEN * (n))) + +// Size of data buffers & NV memory, given number of LEDs per chan +#define TX_BUFF_LEN(n) (LED_TX_OSET(n) + LED_POSTBITS) +#define TX_BUFF_SIZE(n) (TX_BUFF_LEN(n) * sizeof(TXDATA_T)) +#define VC_MEM_SIZE (PAGE_SIZE + TX_BUFF_SIZE(CHAN_MAXLEDS)) + +/* Global */ +MEM_MAP vc_mem; +TXDATA_T *txdata; +TXDATA_T tx_buffer[TX_BUFF_LEN(CHAN_MAXLEDS)]; + +void swap_bytes(); + +void leddriver_setup() { + videocore_setup(&vc_mem, VC_MEM_SIZE); + gpio_setup(); + smi_setup(LED_NCHANS, SMI_TIMING, &vc_mem, TX_BUFF_LEN(CHAN_MAXLEDS), &txdata); +} + +void leddriver_close() { + videocore_close(&vc_mem); + smi_close(LED_NCHANS); + gpio_close(); +} + +void set_color(uint32_t rgb, int index) { + int msk; + TXDATA_T *txd = &(tx_buffer[LED_TX_OSET(index)]); + + // For each bit of the 24-bit RGB values.. + for (size_t n = 0; n < LED_NBITS; n++) { + // Mask to convert RGB to GRB, M.S bit first + msk = n == 0 ? 0x800000 : n == 8 ? 0x8000 : n == 16 ? 0x80 : msk >> 1; + // 1st byte or word is a high pulse on all lines + txd[0] = (TXDATA_T)0xffff; + // 2nd has high or low bits from data + // 3rd is a low pulse + txd[1] = txd[2] = 0; + if (rgb & msk) { + txd[1] = (TXDATA_T)0xffff; + } + txd += BIT_NPULSES; + } +} + +// Set Tx data for 8 or 16 chans, 1 LED per chan, given 1 RGB val per chan +// Logic 1 is 0.8us high, 0.4 us low, logic 0 is 0.4us high, 0.8us low +void rgb_txdata(int *rgbs, int index) { + int i, n, msk; + TXDATA_T *txd = &(tx_buffer[LED_TX_OSET(index)]); + + // For each bit of the 24-bit RGB values.. + for (n = 0; n < LED_NBITS; n++) { + // Mask to convert RGB to GRB, M.S bit first + msk = n == 0 ? 0x800000 : n == 8 ? 0x8000 : n == 16 ? 0x80 : msk >> 1; + // 1st byte or word is a high pulse on all lines + txd[0] = (TXDATA_T)0xffff; + // 2nd has high or low bits from data + // 3rd is a low pulse + txd[1] = txd[2] = 0; + for (i = 0; i < LED_NCHANS; i++) { + if (rgbs[i] & msk) + txd[1] |= (1 << i); + } + txd += BIT_NPULSES; + } +} + +void leddriver_refresh() { +#if LED_NCHANS <= 8 + swap_bytes(); +#endif + + while (dma_active(DMA_CHAN_A)) { + usleep(10); + } + + memcpy(txdata, tx_buffer, TX_BUFF_SIZE(CHAN_MAXLEDS)); + enable_dma(DMA_CHAN_A); + start_smi(&vc_mem); + usleep(10); +} + +// Swap adjacent bytes in transmit data +void swap_bytes() { + uint16_t *wp = (uint16_t *)tx_buffer; + int len = TX_BUFF_SIZE(CHAN_MAXLEDS); + + len = (len + 1) / 2; + while (len-- > 0) { + *wp = __builtin_bswap16(*wp); + wp++; + } +} + +/* source : + * https://github.com/adafruit/Adafruit_NeoPixel/blob/216ccdbff399750f5b02d4cc804c598399e39713/Adafruit_NeoPixel.cpp#L2414 + */ +uint32_t ColorHSV(uint16_t hue, uint8_t sat, uint8_t val) { + + uint8_t r, g, b; + + // Remap 0-65535 to 0-1529. Pure red is CENTERED on the 64K rollover; + // 0 is not the start of pure red, but the midpoint...a few values above + // zero and a few below 65536 all yield pure red (similarly, 32768 is the + // midpoint, not start, of pure cyan). The 8-bit RGB hexcone (256 values + // each for red, green, blue) really only allows for 1530 distinct hues + // (not 1536, more on that below), but the full unsigned 16-bit type was + // chosen for hue so that one's code can easily handle a contiguous color + // wheel by allowing hue to roll over in either direction. + hue = (hue * 1530L + 32768) / 65536; + // Because red is centered on the rollover point (the +32768 above, + // essentially a fixed-point +0.5), the above actually yields 0 to 1530, + // where 0 and 1530 would yield the same thing. Rather than apply a + // costly modulo operator, 1530 is handled as a special case below. + + // So you'd think that the color "hexcone" (the thing that ramps from + // pure red, to pure yellow, to pure green and so forth back to red, + // yielding six slices), and with each color component having 256 + // possible values (0-255), might have 1536 possible items (6*256), + // but in reality there's 1530. This is because the last element in + // each 256-element slice is equal to the first element of the next + // slice, and keeping those in there this would create small + // discontinuities in the color wheel. So the last element of each + // slice is dropped...we regard only elements 0-254, with item 255 + // being picked up as element 0 of the next slice. Like this: + // Red to not-quite-pure-yellow is: 255, 0, 0 to 255, 254, 0 + // Pure yellow to not-quite-pure-green is: 255, 255, 0 to 1, 255, 0 + // Pure green to not-quite-pure-cyan is: 0, 255, 0 to 0, 255, 254 + // and so forth. Hence, 1530 distinct hues (0 to 1529), and hence why + // the constants below are not the multiples of 256 you might expect. + + // Convert hue to R,G,B (nested ifs faster than divide+mod+switch): + if (hue < 510) { // Red to Green-1 + b = 0; + if (hue < 255) { // Red to Yellow-1 + r = 255; + g = hue; // g = 0 to 254 + } else { // Yellow to Green-1 + r = 510 - hue; // r = 255 to 1 + g = 255; + } + } else if (hue < 1020) { // Green to Blue-1 + r = 0; + if (hue < 765) { // Green to Cyan-1 + g = 255; + b = hue - 510; // b = 0 to 254 + } else { // Cyan to Blue-1 + g = 1020 - hue; // g = 255 to 1 + b = 255; + } + } else if (hue < 1530) { // Blue to Red-1 + g = 0; + if (hue < 1275) { // Blue to Magenta-1 + r = hue - 1020; // r = 0 to 254 + b = 255; + } else { // Magenta to Red-1 + r = 255; + b = 1530 - hue; // b = 255 to 1 + } + } else { // Last 0.5 Red (quicker than % operator) + r = 255; + g = b = 0; + } + + // Apply saturation and value to R,G,B, pack into 32-bit result: + uint32_t v1 = 1 + val; // 1 to 256; allows >>8 instead of /255 + uint16_t s1 = 1 + sat; // 1 to 256; same reason + uint8_t s2 = 255 - sat; // 255 to 0 + return ((((((r * s1) >> 8) + s2) * v1) & 0xff00) << 8) | + (((((g * s1) >> 8) + s2) * v1) & 0xff00) | (((((b * s1) >> 8) + s2) * v1) >> 8); +} diff --git a/lightsabre_backend/drivers/src/leddriver/rpi_leddriver.h b/lightsabre_backend/drivers/src/leddriver/rpi_leddriver.h new file mode 100644 index 0000000..0a64e76 --- /dev/null +++ b/lightsabre_backend/drivers/src/leddriver/rpi_leddriver.h @@ -0,0 +1,18 @@ +#if !defined(__RPI_LEDDRIVER_H__) +#define __RPI_LEDDRIVER_H__ + +#include + +void leddriver_setup(); + +void leddriver_close(); + +void set_color(uint32_t rgb, int index); + +void rgb_txdata(int *rgbs, int index); + +void leddriver_refresh(); + +uint32_t ColorHSV(uint16_t hue, uint8_t sat, uint8_t val); + +#endif // __RPI_LEDDRIVER_H__ \ No newline at end of file diff --git a/lightsabre_backend/drivers/src/libs/CMakeLists.txt b/lightsabre_backend/drivers/src/libs/CMakeLists.txt new file mode 100644 index 0000000..bcc67b0 --- /dev/null +++ b/lightsabre_backend/drivers/src/libs/CMakeLists.txt @@ -0,0 +1,13 @@ +include(FetchContent) + +#set(FETCHCONTENT_FULLY_DISCONNECTED ON) + +#set(BUILD_SHARED_LIBS off) + +FetchContent_Declare( + logc + GIT_REPOSITORY "https://github.com/Tropicananass/log.c.git" +) + +# add the log.c +FetchContent_MakeAvailable(logc) diff --git a/lightsabre_backend/drivers/src/smi/rpi_smi.c b/lightsabre_backend/drivers/src/smi/rpi_smi.c new file mode 100644 index 0000000..c6ed788 --- /dev/null +++ b/lightsabre_backend/drivers/src/smi/rpi_smi.c @@ -0,0 +1,223 @@ +#include "rpi_smi.h" + +#include + +#include "../dma/rpi_dma.h" +#include "../gpio/rpi_gpio.h" + +// GPIO first pin +#define SMI_SD0_PIN 8 + +// Data widths +#define SMI_8_BITS 0 +#define SMI_16_BITS 1 +#define SMI_18_BITS 2 +#define SMI_9_BITS 3 + +// Clock registers and values +#define CLK_BASE (PHYS_REG_BASE + 0x101000) +// #define CLK_PWM_CTL 0xa0 +// #define CLK_PWM_DIV 0xa4 +#define CLK_SMI_CTL 0xb0 +#define CLK_SMI_DIV 0xb4 +#define CLK_PASSWD 0x5a000000 +#define PWM_CLOCK_ID 0xa + +// DMA request threshold +#define REQUEST_THRESH 2 + +// Register definitions +#define SMI_BASE (PHYS_REG_BASE + 0x600000) +#define SMI_CS 0x00 // Control & status +#define SMI_L 0x04 // Transfer length +#define SMI_A 0x08 // Address +#define SMI_D 0x0c // Data +#define SMI_DSR0 0x10 // Read settings device 0 +#define SMI_DSW0 0x14 // Write settings device 0 +#define SMI_DSR1 0x18 // Read settings device 1 +#define SMI_DSW1 0x1c // Write settings device 1 +#define SMI_DSR2 0x20 // Read settings device 2 +#define SMI_DSW2 0x24 // Write settings device 2 +#define SMI_DSR3 0x28 // Read settings device 3 +#define SMI_DSW3 0x2c // Write settings device 3 +#define SMI_DMC 0x30 // DMA control +#define SMI_DCS 0x34 // Direct control/status +#define SMI_DCA 0x38 // Direct address +#define SMI_DCD 0x3c // Direct data +#define SMI_FD 0x40 // FIFO debug +#define SMI_REGLEN (SMI_FD * 4) + +// Union of 32-bit value with register bitfields +#define REG_DEF(name, fields) \ + typedef union { \ + struct { \ + volatile uint32_t fields; \ + }; \ + volatile uint32_t value; \ + } name + +// Control and status register +#define SMI_CS_FIELDS \ + enable: \ + 1, done : 1, active : 1, start : 1, clear : 1, write : 1, _x1 : 2, teen : 1, intd : 1, intt : 1, \ + intr : 1, pvmode : 1, seterr : 1, pxldat : 1, edreq : 1, _x2 : 8, _x3 : 1, aferr : 1, \ + txw : 1, rxr : 1, txd : 1, rxd : 1, txe : 1, rxf : 1 +REG_DEF(SMI_CS_REG, SMI_CS_FIELDS); + +// Data length register +#define SMI_L_FIELDS \ + len: \ + 32 +REG_DEF(SMI_L_REG, SMI_L_FIELDS); + +// Address & device number +#define SMI_A_FIELDS \ + addr: \ + 6, _x1 : 2, dev : 2 +REG_DEF(SMI_A_REG, SMI_A_FIELDS); + +// Data FIFO +#define SMI_D_FIELDS \ + data: \ + 32 +REG_DEF(SMI_D_REG, SMI_D_FIELDS); + +// DMA control register +#define SMI_DMC_FIELDS \ + reqw: \ + 6, reqr : 6, panicw : 6, panicr : 6, dmap : 1, _x1 : 3, dmaen : 1 +REG_DEF(SMI_DMC_REG, SMI_DMC_FIELDS); + +// Device settings: read (1 of 4) +#define SMI_DSR_FIELDS \ + rstrobe: \ + 7, rdreq : 1, rpace : 7, rpaceall : 1, rhold : 6, fsetup : 1, mode68 : 1, rsetup : 6, rwidth : 2 +REG_DEF(SMI_DSR_REG, SMI_DSR_FIELDS); + +// Device settings: write (1 of 4) +#define SMI_DSW_FIELDS \ + wstrobe: \ + 7, wdreq : 1, wpace : 7, wpaceall : 1, whold : 6, wswap : 1, wformat : 1, wsetup : 6, wwidth : 2 +REG_DEF(SMI_DSW_REG, SMI_DSW_FIELDS); + +// Direct control register +#define SMI_DCS_FIELDS \ + enable: \ + 1, start : 1, done : 1, write : 1 +REG_DEF(SMI_DCS_REG, SMI_DCS_FIELDS); + +// Direct control address & device number +#define SMI_DCA_FIELDS \ + addr: \ + 6, _x1 : 2, dev : 2 +REG_DEF(SMI_DCA_REG, SMI_DCA_FIELDS); + +// Direct control data +#define SMI_DCD_FIELDS \ + data: \ + 32 +REG_DEF(SMI_DCD_REG, SMI_DCD_FIELDS); + +// Debug register +#define SMI_FLVL_FIELDS \ + fcnt: \ + 6, _x1 : 2, flvl : 6 +REG_DEF(SMI_FLVL_REG, SMI_FLVL_FIELDS); + +// Pointers to SMI registers +volatile SMI_CS_REG *smi_cs; +volatile SMI_L_REG *smi_l; +volatile SMI_A_REG *smi_a; +volatile SMI_D_REG *smi_d; +volatile SMI_DMC_REG *smi_dmc; +volatile SMI_DSR_REG *smi_dsr; +volatile SMI_DSW_REG *smi_dsw; +volatile SMI_DCS_REG *smi_dcs; +volatile SMI_DCA_REG *smi_dca; +volatile SMI_DCD_REG *smi_dcd; + +MEM_MAP smi_regs, clk_regs; + +void setup_smi_dma(MEM_MAP *mp, int nsamp, uint8_t **txdata, int len); + +void smi_setup(int channels, int ns, int setup, int strobe, int hold, MEM_MAP *mp, int nsamp, + uint8_t **txdata) { + map_periph(&smi_regs, (void *)SMI_BASE, PAGE_SIZE); + map_periph(&clk_regs, (void *)CLK_BASE, PAGE_SIZE); + + int width = channels > 8 ? SMI_16_BITS : SMI_8_BITS; + int i, divi = ns / 2; + + smi_cs = (SMI_CS_REG *)REG32(smi_regs, SMI_CS); + smi_l = (SMI_L_REG *)REG32(smi_regs, SMI_L); + smi_a = (SMI_A_REG *)REG32(smi_regs, SMI_A); + smi_d = (SMI_D_REG *)REG32(smi_regs, SMI_D); + smi_dmc = (SMI_DMC_REG *)REG32(smi_regs, SMI_DMC); + smi_dsr = (SMI_DSR_REG *)REG32(smi_regs, SMI_DSR0); + smi_dsw = (SMI_DSW_REG *)REG32(smi_regs, SMI_DSW0); + smi_dcs = (SMI_DCS_REG *)REG32(smi_regs, SMI_DCS); + smi_dca = (SMI_DCA_REG *)REG32(smi_regs, SMI_DCA); + smi_dcd = (SMI_DCD_REG *)REG32(smi_regs, SMI_DCD); + smi_cs->value = smi_l->value = smi_a->value = 0; + smi_dsr->value = smi_dsw->value = smi_dcs->value = smi_dca->value = 0; + if (*REG32(clk_regs, CLK_SMI_DIV) != divi << 12) { + *REG32(clk_regs, CLK_SMI_CTL) = CLK_PASSWD | (1 << 5); + usleep(10); + while (*REG32(clk_regs, CLK_SMI_CTL) & (1 << 7)) + ; + usleep(10); + *REG32(clk_regs, CLK_SMI_DIV) = CLK_PASSWD | (divi << 12); + usleep(10); + *REG32(clk_regs, CLK_SMI_CTL) = CLK_PASSWD | 6 | (1 << 4); + usleep(10); + while ((*REG32(clk_regs, CLK_SMI_CTL) & (1 << 7)) == 0) + ; + usleep(100); + } + if (smi_cs->seterr) + smi_cs->seterr = 1; + smi_dsr->rsetup = smi_dsw->wsetup = setup; + smi_dsr->rstrobe = smi_dsw->wstrobe = strobe; + smi_dsr->rhold = smi_dsw->whold = hold; + smi_dmc->panicr = smi_dmc->panicw = 8; + smi_dmc->reqr = smi_dmc->reqw = REQUEST_THRESH; + smi_dsr->rwidth = smi_dsw->wwidth = width; + for (i = 0; i < channels; i++) + gpio_mode(SMI_SD0_PIN + i, GPIO_ALT1); + + setup_smi_dma(mp, nsamp, txdata, width + 1); +} + +void smi_close(int channels) { + for (size_t i = 0; i < channels; ++i) + gpio_mode(SMI_SD0_PIN + i, GPIO_IN); + if (smi_regs.virt) { + *REG32(smi_regs, SMI_CS) = 0; + } + stop_dma(DMA_CHAN_A); + unmap_periph_mem(&clk_regs); + unmap_periph_mem(&smi_regs); + dma_close(); +} + +// Start SMI DMA transfers +void start_smi(MEM_MAP *mp) { + DMA_CB *cbs = mp->virt; + + start_dma(mp, DMA_CHAN_A, &cbs[0], 0); + smi_cs->start = 1; +} + +// private + +// Set up SMI transfers using DMA +void setup_smi_dma(MEM_MAP *mp, int nsamp, uint8_t **txdata, int len) { + smi_dmc->dmaen = 1; + smi_cs->enable = 1; + smi_cs->clear = 1; + smi_cs->pxldat = 1; + smi_l->len = nsamp * len; + smi_cs->write = 1; + + dma_setup(mp, DMA_CHAN_A, nsamp, txdata, len, REG_BUS_ADDR(smi_regs, SMI_D)); +} \ No newline at end of file diff --git a/lightsabre_backend/drivers/src/smi/rpi_smi.h b/lightsabre_backend/drivers/src/smi/rpi_smi.h new file mode 100644 index 0000000..79ea29d --- /dev/null +++ b/lightsabre_backend/drivers/src/smi/rpi_smi.h @@ -0,0 +1,14 @@ +#if !defined(__RPI_SMI_H__) +#define __RPI_SMI_H__ + +#include "../common.h" +#include + +void smi_setup(int channels, int ns, int setup, int strobe, int hold, MEM_MAP *mp, int nsamp, + uint8_t **txdata); + +void smi_close(int channels); + +void start_smi(MEM_MAP *mp); + +#endif // __RPI_SMI_H__ \ No newline at end of file diff --git a/lightsabre_backend/src/channels.rs b/lightsabre_backend/src/channels.rs new file mode 100644 index 0000000..89b3905 --- /dev/null +++ b/lightsabre_backend/src/channels.rs @@ -0,0 +1,6 @@ +use crate::cputasks::modes::AppMode; + +pub enum Message { + ModeChanged { mode: AppMode }, + // Other messages... +} diff --git a/lightsabre_backend/src/config.rs b/lightsabre_backend/src/config.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/lightsabre_backend/src/config.rs @@ -0,0 +1 @@ + diff --git a/lightsabre_backend/src/cputasks.rs b/lightsabre_backend/src/cputasks.rs new file mode 100644 index 0000000..552556d --- /dev/null +++ b/lightsabre_backend/src/cputasks.rs @@ -0,0 +1 @@ +pub mod modes; diff --git a/lightsabre_backend/src/cputasks/modes.rs b/lightsabre_backend/src/cputasks/modes.rs new file mode 100644 index 0000000..26362d5 --- /dev/null +++ b/lightsabre_backend/src/cputasks/modes.rs @@ -0,0 +1,107 @@ +pub mod artnet; +pub mod diagnostics; +pub mod manual; +pub mod standalone; + +use std::{ + collections::HashMap, + sync::{Arc, Mutex}, +}; + +use crossbeam::channel::Receiver; + +use crate::channels::Message; +use crate::devices::led_driver::LedDriver; + +pub trait AppModeHandler { + fn enter(&mut self); + fn run(&mut self, led_driver: &mut LedDriver); + fn exit(&mut self); +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum AppMode { + Diagnostics, + ArtNet, + Standalone, + Manual, +} + +impl AppMode { + pub fn for_each(mut f: F) { + f(AppMode::Diagnostics); + f(AppMode::ArtNet); + f(AppMode::Standalone); + f(AppMode::Manual); + } +} + +impl From for AppMode { + fn from(value: usize) -> Self { + match value { + 1 => AppMode::ArtNet, + 2 => AppMode::Standalone, + 3 => AppMode::Manual, + _ => AppMode::Diagnostics, + } + } +} + +pub struct ModeManager { + mode_handler_map: HashMap>, + mode: Arc>, + mode_rx: Receiver, +} + +impl ModeManager { + pub fn new(mode_rx: Receiver) -> Self { + let mut handlers: HashMap> = HashMap::new(); + handlers.insert(AppMode::Manual, Box::new(manual::ManualMode::new())); + handlers.insert( + AppMode::Standalone, + Box::new(standalone::StandaloneMode::new()), + ); + handlers.insert( + AppMode::Diagnostics, + Box::new(diagnostics::DiagnosticsMode::new()), + ); + handlers.insert(AppMode::ArtNet, Box::new(artnet::ArtNetMode::new())); + + let mode_manager = ModeManager { + mode_handler_map: handlers, + mode: Arc::new(Mutex::new(AppMode::Diagnostics)), + mode_rx: mode_rx.clone(), + }; + + mode_manager + } + + pub fn run(&mut self, led_driver: &mut LedDriver) { + if let Ok(Message::ModeChanged { mode: next }) = self.mode_rx.try_recv() { + let current = self.get_current_mode(); + if current != next { + log::info!(" Changing mode from {:?} to {:?}", current, next); + self.get_handler(Some(current)).exit(); + self.get_handler(Some(next)).enter(); + self.set_current_mode(next); + } + } else { + self.get_handler(None).run(led_driver); + } + } + + pub fn get_current_mode(&self) -> AppMode { + *self.mode.lock().unwrap() + } + + fn set_current_mode(&self, mode: AppMode) { + *self.mode.lock().unwrap() = mode; + } + + fn get_handler(&mut self, mode: Option) -> &mut Box { + let mode = &mode.unwrap_or_else(|| self.get_current_mode()); + self.mode_handler_map + .get_mut(mode) + .expect(&format!("No handler found for mode: {:?}", mode)) + } +} diff --git a/lightsabre_backend/src/cputasks/modes/artnet.rs b/lightsabre_backend/src/cputasks/modes/artnet.rs new file mode 100644 index 0000000..7f83f79 --- /dev/null +++ b/lightsabre_backend/src/cputasks/modes/artnet.rs @@ -0,0 +1,248 @@ +use artnet_protocol::{ArtCommand, PollReply}; +use std::fmt::Display; +use std::net::{Ipv4Addr, UdpSocket}; +use std::time::{Duration, Instant}; + +use crate::cputasks::modes::AppModeHandler; +use crate::devices::led_driver::LedDriver; + +#[derive(Debug, Default)] +pub struct ArtNetMode { + socket: Option, + last_frame_time: Option, + statistics: ArtNetModeStatistics, +} + +impl ArtNetMode { + const _SHORT: &str = "LightSabre\0"; + const _SHORT_PAD: [u8; 18 - Self::_SHORT.len()] = [0; 7]; + + pub fn new() -> Self { + ArtNetMode::default() + } +} + +impl AppModeHandler for ArtNetMode { + fn enter(&mut self) { + log::debug!("[ArtNet] Entering ArtNet Mode"); + + let mut attempts = 0_usize; + let socket = loop { + match UdpSocket::bind(("0.0.0.0", 6454)) { + Ok(socket) => break socket, + Err(e) => { + log::error!("[ArtNet] Failed to bind ArtNet socket: {e}, retrying..."); + std::thread::sleep(std::time::Duration::from_millis(5)); + } + } + attempts += 1; + if attempts > 10 { + panic!("[ArtNet] Failed to bind ArtNet socket after multiple attempts"); + } + }; + socket.set_broadcast(true).unwrap(); + socket.set_nonblocking(true).unwrap(); + self.socket = Some(socket); + log::debug!("[ArtNet] ArtNet Mode initialized and listening on port 6454"); + } + + fn run(&mut self, led_driver: &mut LedDriver) { + log::trace!("[ArtNet] Running..."); + let buf = &mut [0; 530]; + let mut is_first_data_frame = true; + loop { + match self + .socket + .as_mut() + .expect("ArtNet socket not initialized") + .recv_from(buf) + { + Ok((num_bytes_read, from)) => { + log::trace!("[ArtNet] Received {} bytes from {}", num_bytes_read, from); + + let command = + ArtCommand::from_buffer(buf).expect("Failed to parse ArtNet command"); + match command { + ArtCommand::Poll(_) => { + log::trace!("[ArtNet] Received Poll command, responding..."); + let poll_reply: PollReply = PollReply { + address: Ipv4Addr::from_bits(0), + port: 6454, + version: [0, 1], + port_address: [0; 2], + oem: [0x01, 0x90], + ubea_version: 0, + status_1: 0, + esta_code: 0, + short_name: b"LightSabre\0\0\0\0\0\0\0\0".to_owned(), + long_name: b"LightSabre Artnet Node\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0".to_owned(), + node_report: [0; 64], + num_ports: [0, 1], + port_types: [0x80, 0, 0, 0], + good_input: [0; 4], + good_output: [0; 4], + swin: [0; 4], + swout: [0; 4], + sw_video: 0, + sw_macro: 0, + sw_remote: 0, + spare: [0; 3], + style: 0, + mac: [0; 6], + bind_ip: [0; 4], + bind_index: 0, + status_2: 0, + filler: [0; 26], + }; + let response = ArtCommand::PollReply(Box::new(poll_reply)) + .write_to_buffer() + .unwrap(); + self.socket + .as_mut() + .unwrap() + .send_to(&response, from) + .expect("Failed to send Poll response"); + } + ArtCommand::Output(output) => { + log::trace!("[ArtNet] Received Output command with data: {:?}", output); + /* compute statistics */ + if is_first_data_frame { + self.statistics.update(self.last_frame_time); + self.last_frame_time = Some(Instant::now()); + is_first_data_frame = false; + } + + //output.port_address + for i in 0..led_driver.get_led_per_strips() { + let data_index = i * 3; + let g = *output.data.as_ref().get(data_index).unwrap_or(&0); + let r = *output.data.as_ref().get(data_index + 1).unwrap_or(&0); + let b = *output.data.as_ref().get(data_index + 2).unwrap_or(&0); + // let color = (r as u32) << 16 + (g as u32) << 8 + b; + let color: u32 = + ((r as u32) << 16) | ((g as u32) << 8) | (b as u32); + led_driver.set_color(color, i); + } + led_driver.refresh(); + // Here you would typically handle the output data, e.g., send it to the LED driver + // For now, we just log it + } + ArtCommand::PollReply(_) => { + log::trace!("[ArtNet] Received PollReply command, ignoring"); + } + _ => { + log::warn!("[ArtNet] Received unhandled command: {:?}", command); + } + } + } + Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { + // No data received, continue running + break; + } + Err(e) => panic!("encountered IO error: {e}"), + } + } + } + + fn exit(&mut self) { + log::debug!("[ArtNet] Exiting ArtNet Mode"); + log::info!("[ArtNet] Statistics: {}", self.statistics); + self.socket = None; + self.last_frame_time = None; + self.statistics = ArtNetModeStatistics::default(); + } +} + +#[derive(Debug, Default)] +struct ArtNetModeStatistics { + frame_count: u32, + min_time: Option, + max_time: Option, + total_time: Duration, + avg_delta: Option, +} + +impl ArtNetModeStatistics { + fn update(&mut self, last_frame_time: Option) { + self.frame_count += 1; + if let Some(last_frame_time) = last_frame_time { + let elapsed = last_frame_time.elapsed(); + if self.frame_count > 30 { + let avg = self.total_time / self.frame_count; + if elapsed > avg * 2 { + log::debug!("[ArtNet] Frame took too long: {:?}ms", elapsed.as_millis()); + } + if elapsed < avg / 2 { + log::debug!("[ArtNet] Frame took too short: {:?}ms", elapsed.as_millis()); + } + let delta = elapsed.abs_diff(avg); + self.avg_delta = if let Some(avg_delta) = self.avg_delta { + Some((avg_delta * (self.frame_count - 30) + delta) / (self.frame_count - 29)) + } else { + Some(delta) + }; + } + self.total_time += elapsed; + self.max_time = Some(match self.max_time { + Some(max_time) => std::cmp::max(max_time, elapsed), + None => elapsed, + }); + self.min_time = Some(match self.min_time { + Some(min_time) => std::cmp::min(min_time, elapsed), + None => elapsed, + }); + } + } +} + +impl Display for ArtNetModeStatistics { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let average_time = if self.frame_count > 0 { + self.total_time / self.frame_count as u32 + } else { + Duration::default() + }; + write!( + f, + "\n======================================================\n ArtNet Statistics\n" + )?; + write!( + f, + "frame_count: {}, total_time: {:?}, avg_delta: {}µs\n", + self.frame_count, + self.total_time, + self.avg_delta + .unwrap_or_else(|| Duration::default()) + .as_micros() + )?; + write!( + f, + "min_time: {}, average_time: {}, max_time: {}\n", + self.min_time + .unwrap_or_else(|| Duration::default()) + .as_micros() as f64 + / 1000_f64, + average_time.as_micros() as f64 / 1000_f64, + self.max_time + .unwrap_or_else(|| Duration::default()) + .as_micros() as f64 + / 1000_f64 + )?; + write!( + f, + "min_framerate: {}, average_framerate: {}, max_framerate: {}\n", + 1_f64 + / self + .max_time + .unwrap_or_else(|| Duration::from_secs(0)) + .as_secs_f64(), + 1_f64 / average_time.as_secs_f64(), + 1_f64 + / self + .min_time + .unwrap_or_else(|| Duration::from_secs(0)) + .as_secs_f64() + )?; + write!(f, "==================================================") + } +} diff --git a/lightsabre_backend/src/cputasks/modes/diagnostics.rs b/lightsabre_backend/src/cputasks/modes/diagnostics.rs new file mode 100644 index 0000000..490b5c3 --- /dev/null +++ b/lightsabre_backend/src/cputasks/modes/diagnostics.rs @@ -0,0 +1,83 @@ +use crate::cputasks::modes::AppModeHandler; + +use crate::devices::led_driver::LedDriver; +use std::iter::Peekable; +use std::ops::Range; +use std::slice::Iter; + +pub struct DiagnosticsMode { + cycle_count: usize, + color_iterator: Peekable>, + led_iterator: Range, +} + +impl DiagnosticsMode { + const TEST_COLORS: [u32; 7] = [ + 0x1f0000, 0x001f00, 0x00001f, 0x5f5f00, 0x1f001f, 0x001f1f, 0x1f1f1f, + ]; + + pub fn new() -> Self { + DiagnosticsMode { + cycle_count: 0, + color_iterator: Self::color_iterator(), + led_iterator: 0..0, + } + } + + fn color_iterator() -> Peekable> { + Self::TEST_COLORS.iter().peekable() + } + + fn init_led_iterator(led_driver: &LedDriver) -> Range { + // Initialize the LED iterator to cover all LEDs + 0..led_driver.get_led_per_strips() + } +} + +impl AppModeHandler for DiagnosticsMode { + fn enter(&mut self) { + log::debug!("[Diagnostics] Entering Diagnostics Mode"); + self.cycle_count = 0; + } + + fn run(&mut self, led_driver: &mut LedDriver) { + log::trace!("[Diagnostics] Running..."); + if self.cycle_count % 50 == 0 { + let (led, color) = match self.led_iterator.next() { + Some(led) => { + led_driver.set_color(**self.color_iterator.peek().unwrap(), led); + (led, *self.color_iterator.peek().unwrap()) + } + None => { + // Reset the LED iterator if it reaches the end + self.led_iterator = DiagnosticsMode::init_led_iterator(led_driver); + let led = self.led_iterator.next().unwrap(); + self.color_iterator.next(); + ( + led, + match self.color_iterator.peek() { + Some(color) => { + led_driver.set_color(**color, led); + *color + } + None => { + // Reset the color iterator if it reaches the end + self.color_iterator = DiagnosticsMode::color_iterator(); + let color = self.color_iterator.peek().unwrap(); + led_driver.set_color(**color, led); + *color + } + }, + ) + } + }; + log::trace!("Setting LED {} to color {:06x}", led, color); + led_driver.refresh(); + } + self.cycle_count += 1; + } + + fn exit(&mut self) { + log::debug!("[Diagnostics] Exiting Diagnostics Mode"); + } +} diff --git a/lightsabre_backend/src/cputasks/modes/manual.rs b/lightsabre_backend/src/cputasks/modes/manual.rs new file mode 100644 index 0000000..2960b34 --- /dev/null +++ b/lightsabre_backend/src/cputasks/modes/manual.rs @@ -0,0 +1,24 @@ +use crate::cputasks::modes::AppModeHandler; +use crate::devices::led_driver::LedDriver; + +pub struct ManualMode; + +impl ManualMode { + pub fn new() -> Self { + ManualMode + } +} + +impl AppModeHandler for ManualMode { + fn enter(&mut self) { + log::debug!("[Manual] Entering Manual Mode"); + } + + fn run(&mut self, _: &mut LedDriver) { + log::trace!("[Manual] Running..."); + } + + fn exit(&mut self) { + log::debug!("[Manual] Exiting Manual Mode"); + } +} diff --git a/lightsabre_backend/src/cputasks/modes/standalone.rs b/lightsabre_backend/src/cputasks/modes/standalone.rs new file mode 100644 index 0000000..26ece22 --- /dev/null +++ b/lightsabre_backend/src/cputasks/modes/standalone.rs @@ -0,0 +1,41 @@ +use rppal::gpio::Gpio; + +use crate::cputasks::modes::AppModeHandler; +use crate::devices::led_driver::LedDriver; + +pub struct StandaloneMode { + _i2s_mic_pin: Vec, +} + +impl StandaloneMode { + const I2S_PINS: [u8; 3] = [20, 19, 18]; + + pub fn new() -> Self { + let gpio = Gpio::new().expect("Failed to initialize GPIO"); + let i2s_mic_pin: Vec = Self::I2S_PINS + .iter() + .map(|&pin| { + gpio.get(pin) + .expect(&format!("Failed to get GPIO pin {}", pin)) + .into_io(rppal::gpio::Mode::Alt0) + }) + .collect(); + StandaloneMode { + _i2s_mic_pin: i2s_mic_pin, + } + } +} + +impl AppModeHandler for StandaloneMode { + fn enter(&mut self) { + log::debug!("[Standalone] Entering Standalone Mode"); + } + + fn run(&mut self, _: &mut LedDriver) { + log::trace!("[Standalone] Running..."); + } + + fn exit(&mut self) { + log::debug!("[Standalone] Exiting Standalone Mode"); + } +} diff --git a/lightsabre_backend/src/devices.rs b/lightsabre_backend/src/devices.rs new file mode 100644 index 0000000..ef6a898 --- /dev/null +++ b/lightsabre_backend/src/devices.rs @@ -0,0 +1,2 @@ +pub mod led_driver; +pub mod selector; diff --git a/lightsabre_backend/src/devices/led_driver.rs b/lightsabre_backend/src/devices/led_driver.rs index 274296d..4328318 100644 --- a/lightsabre_backend/src/devices/led_driver.rs +++ b/lightsabre_backend/src/devices/led_driver.rs @@ -1,59 +1,61 @@ -// Constants -const LED_NBITS: usize = 24; // Number of data bits per LED -const LED_PREBITS: usize = 4; // Number of zero bits before LED data -const LED_POSTBITS: usize = 4; // Number of zero bits after LED data -const BIT_NPULSES: usize = 3; // Number of O/P pulses per LED bit - -#[cfg(feature = "rpi4")] // Timings for RPi v4 (1.5 GHz) -const SMI_TIMING: [u32; 4] = [10, 15, 30, 15]; // 400 ns cycle time -#[cfg(not(feature = "rpi4"))] // Timings for RPi v0-3 (1 GHz) -const SMI_TIMING: [u32; 4] = [10, 10, 20, 10]; // 400 ns cycle time - -// Use 16-bit data type for TxDataT -#[cfg(feature = "16channel")] -type TxDataT = u16; -// Use 8-bit data type for TxDataT -#[cfg(not(feature = "16channel"))] -type TxDataT = u8; - -// Helper macros as functions -#[inline] -const fn led_dlen() -> usize { - LED_NBITS * BIT_NPULSES +#[link(name = "logc", kind = "static")] +unsafe extern "C" { + unsafe fn log_log(level: u32, file: *const u8, line: i32, fmt: *const u8, ...); } -#[inline] -const fn led_tx_oset(n: usize) -> usize { - LED_PREBITS + (led_dlen() * n) -} +#[link(name = "RpiLedBars_drivers", kind = "static")] +unsafe extern "C" { -#[inline] -const fn tx_buff_len(n: usize) -> usize { - led_tx_oset(n) + LED_POSTBITS + unsafe fn leddriver_setup(); + + unsafe fn leddriver_close(); + + unsafe fn set_color(rgb: u32, index: usize); + + // unsafe fn rgb_txdata(rgbs: *mut u32, index: usize); + + unsafe fn leddriver_refresh(); + + // unsafe fn ColorHSV(hue: u16, sat: u8, val: u8) -> u32; } pub struct LedDriver { - // Placeholder for LED driver state - tx_buffer: [TxDataT; tx_buff_len(30)], + led_per_strips: usize, } impl LedDriver { - pub fn new() -> Result> { - // Initialize the LED driver - Ok(LedDriver { - tx_buffer: [TxDataT::default(); 4856], - }) + pub fn new(led_per_strips: usize) -> LedDriver { + unsafe { + log_log( + 0, + "coucou".as_bytes().as_ptr(), + 13, + "hello".as_bytes().as_ptr(), + ) + }; + unsafe { leddriver_setup() }; + LedDriver { led_per_strips } } - pub fn set_color(&self, rgb: u32, index: usize) -> Result<(), Box> { - // Set the color for the LED at the specified index - // Placeholder for actual implementation - Ok(()) + pub fn get_led_per_strips(&self) -> usize { + self.led_per_strips } - pub fn refresh(&self) -> Result<(), Box> { - // Refresh the LED states - // Placeholder for actual implementation - Ok(()) + pub fn set_color(&self, rgb: u32, index: usize) { + unsafe { set_color(rgb, index) }; + } + + pub fn refresh(&self) { + unsafe { leddriver_refresh() }; + } + + // pub fn color_hsv(&self, hue: u16, sat: u8, val: u8) -> u32 { + // unsafe { ColorHSV(hue, sat, val) } + // } +} + +impl Drop for LedDriver { + fn drop(&mut self) { + unsafe { leddriver_close() }; } } diff --git a/lightsabre_backend/src/devices/led_autoconvert.rs b/lightsabre_backend/src/devices/led_driver/led_autoconvert.rs similarity index 100% rename from lightsabre_backend/src/devices/led_autoconvert.rs rename to lightsabre_backend/src/devices/led_driver/led_autoconvert.rs diff --git a/lightsabre_backend/src/devices/led_driver/led_driver.rs b/lightsabre_backend/src/devices/led_driver/led_driver.rs new file mode 100644 index 0000000..0d2387f --- /dev/null +++ b/lightsabre_backend/src/devices/led_driver/led_driver.rs @@ -0,0 +1,61 @@ +// Constants +const LED_NBITS: usize = 24; // Number of data bits per LED +const LED_PREBITS: usize = 4; // Number of zero bits before LED data +const LED_POSTBITS: usize = 4; // Number of zero bits after LED data +const BIT_NPULSES: usize = 3; // Number of O/P pulses per LED bit + +#[cfg(feature = "rpi4")] // Timings for RPi v4 (1.5 GHz) +const SMI_TIMING: [u32; 4] = [10, 15, 30, 15]; // 400 ns cycle time +#[cfg(not(feature = "rpi4"))] // Timings for RPi v0-3 (1 GHz) +const SMI_TIMING: [u32; 4] = [10, 10, 20, 10]; // 400 ns cycle time + +// Use 16-bit data type for TxDataT +#[cfg(feature = "16channel")] +type TxDataT = u16; +// Use 8-bit data type for TxDataT +#[cfg(not(feature = "16channel"))] +type TxDataT = u8; + +// Helper macros as functions +// Length of data for 1 row (1 LED on each channel) +#[inline] +const fn led_dlen() -> usize { + LED_NBITS * BIT_NPULSES +} + +// Offset into Tx data buffer, given LED number in chan +#[inline] +const fn led_tx_offset(n: usize) -> usize { + LED_PREBITS + (led_dlen() * n) +} + +#[inline] +const fn tx_buff_len(n: usize) -> usize { + led_tx_offset(n) + LED_POSTBITS +} + +pub struct LedDriver { + // Placeholder for LED driver state + tx_buffer: [TxDataT; tx_buff_len(30)], +} + +impl LedDriver { + pub fn new() -> LedDriver { + // Initialize the LED driver + LedDriver { + tx_buffer: [TxDataT::default(); tx_buff_len(30)], + } + } + + pub fn set_color(&self, rgb: u32, index: usize) -> Result<(), Box> { + // Set the color for the LED at the specified index + // Placeholder for actual implementation + Ok(()) + } + + pub fn refresh(&self) -> Result<(), Box> { + // Refresh the LED states + // Placeholder for actual implementation + Ok(()) + } +} diff --git a/lightsabre_backend/src/devices/led_driver/memory.rs b/lightsabre_backend/src/devices/led_driver/memory.rs new file mode 100644 index 0000000..d470cb8 --- /dev/null +++ b/lightsabre_backend/src/devices/led_driver/memory.rs @@ -0,0 +1,143 @@ +use std::fs::OpenOptions; +use std::io; +use std::os::unix::io::AsRawFd; +use std::ptr; +use libc::{mmap, munmap, MAP_FAILED, MAP_SHARED, PROT_READ, PROT_WRITE, O_RDWR, O_SYNC, O_CLOEXEC}; +use log::{info}; + +// Location of peripheral registers in physical memory +#[cfg(feature = "rpi")] +const PHYS_REG_BASE: usize = 0x2000_0000; // Pi Zero or 1 +#[cfg(any(feature = "rpi2", feature = "rpi3"))] +const PHYS_REG_BASE: usize = 0x3F00_0000; // Pi 2 or 3 or Zero 2 +#[cfg(feature = "rpi4")] +const PHYS_REG_BASE: usize = 0xFE00_0000; // Pi 4 + +// Clock frequency +#[cfg(feature = "rpi")] +const CLOCK_HZ: usize = 400_000_000; // Pi Zero +#[cfg(any(feature = "rpi2", feature = "rpi3", feature = "rpi4"))] +const CLOCK_HZ: usize = 250_000_000; // Pi 2 - 4 + +// Location of peripheral registers in bus memory +const BUS_REG_BASE: usize = 0x7E00_0000; + +const MEM_DEVICE_FILE: &str = "/dev/mem"; // Memory device file + + +// Get virtual 8 and 32-bit pointers to register +#[inline] +const unsafe fn reg8(m: &MemMap, x: usize) -> *mut u8 { + (m.virt as usize + x) as *mut u8 +} + +#[inline] +const unsafe fn reg32(m: &MemMap, x: usize) -> *mut u32 { + (m.virt as usize + x) as *mut u32 +} + +// Get bus address of register +#[inline] +const fn reg_bus_addr(m: &MemMap, x: usize) -> usize { + m.bus as usize + x +} + +// Convert uncached memory virtual address to bus address +#[inline] +const fn mem_bus_addr(mp: &MemMap, a: usize) -> usize { + a - mp.virt as usize + mp.bus as usize +} + +// Convert bus address to physical address (for mmap) +#[inline] +const fn bus_phys_addr(a: usize) -> usize { + a & !0xC000_0000 +} + +const PAGE_SIZE: usize = 0x1000; // 4 KiB page size +const fn page_roundup(size: usize) -> usize { + (size + PAGE_SIZE - 1) & !(PAGE_SIZE - 1) +} + +// Structure for mapped peripheral or memory +#[repr(C)] +pub struct MemMap { + pub fd: i32, // File descriptor + pub h: i32, // Memory handle + pub size: usize, // Memory size + pub bus: *mut libc::c_void, // Bus address + pub virt: *mut libc::c_void, // Virtual address + pub phys: *mut libc::c_void, // Physical address +} + +impl MemMap { + // Create a new memory map + pub fn new(size: usize, phys: *mut libc::c_void) -> io::Result { + let size = page_roundup(size); + let bus_addr = phys as usize - PHYS_REG_BASE + BUS_REG_BASE; + let virt_addr = unsafe { map_segment(phys as usize, size) }; + + if virt_addr.is_null() { + return Err(io::Error::new(io::ErrorKind::Other, "Memory mapping failed")); + } + + Ok(MemMap { + fd: -1, + h: -1, + size: size, + bus: virt_addr, + virt: virt_addr, + phys: phys, + }) + } +} + +// Implement Drop to automatically unmap memory when MemMap goes out of scope +impl Drop for MemMap { + fn drop(&mut self) { + unsafe { + unmap_segment(self.virt, self.size); + } + self.virt = ptr::null_mut(); + } +} + + +unsafe fn map_segment(addr: usize, size: usize) -> *mut libc::c_void { + let size = page_roundup(size); + + let file = OpenOptions::new() + .read(true) + .write(true) + .custom_flags(O_SYNC | O_CLOEXEC) + .open("/dev/mem") + .unwrap_or_else(|_| { + panic!("can't open /dev/mem, run using sudo"); + }); + + let fd = file.as_raw_fd(); + + let mem = mmap( + ptr::null_mut(), + size, + PROT_READ | PROT_WRITE, + MAP_SHARED, + fd, + addr as libc::off_t, + ); + drop(file); + + info("Map {:p} -> {:p}", addr as *const (), mem); + + if mem == MAP_FAILED { + panic!("Memory mapping {:p} -> {:p} failed", addr as *const (), mem); + } + mem +} + +// Free mapped memory +unsafe fn unmap_segment(mem: *mut libc::c_void, size: usize) { + if !mem.is_null() { + munmap(mem, page_roundup(size)); + } +} \ No newline at end of file diff --git a/lightsabre_backend/src/devices/led_driver/videocore.rs b/lightsabre_backend/src/devices/led_driver/videocore.rs new file mode 100644 index 0000000..bf50571 --- /dev/null +++ b/lightsabre_backend/src/devices/led_driver/videocore.rs @@ -0,0 +1,95 @@ +use std::fs::OpenOptions; +use std::os::unix::io::{AsRawFd, RawFd}; +use std::io; +use std::mem; +use std::os::raw::c_void; + + + + +bitflags::bitflags! { + pub struct VCAllocFlags: u32 { + const MEM_FLAG_DISCARDABLE = 1 << 0; // can be resized to 0 at any time. Use for cached data + const MEM_FLAG_NORMAL = 0 << 2; // normal allocating alias. Don't use from ARM + const MEM_FLAG_DIRECT = 1 << 2; // 0xC alias uncached + const MEM_FLAG_COHERENT = 2 << 2; // 0x8 alias. Non-allocating in L2 but coherent + const MEM_FLAG_ZERO = 1 << 4; // initialise buffer to all zeros + const MEM_FLAG_NO_INIT = 1 << 5; // don't initialise (default is initialise to all ones) + const MEM_FLAG_HINT_PERMALOCK = 1 << 6; // Likely to be locked for long periods of time + const MEM_FLAG_L1_NONALLOCATING = Self::MEM_FLAG_DIRECT.bits | Self::MEM_FLAG_COHERENT.bits; // Allocating in L2 + } +} + +pub const DMA_MEM_FLAGS: VCAllocFlags = VCAllocFlags::MEM_FLAG_DIRECT | VCAllocFlags::MEM_FLAG_ZERO; + +#[repr(C, align(16))] +pub struct VcMsg { + pub len: u32, // Overall length (bytes) + pub req: u32, // Zero for request, 1<<31 for response + pub tag: u32, // Command number + pub blen: u32, // Buffer length (bytes) + pub dlen: u32, // Data length (bytes) + pub uints: [u32; 27], // Data (108 bytes maximum) +} + +pub fn open_mbox() -> io::Result { + let file = OpenOptions::new().read(true).open("/dev/vcio"); + match file { + Ok(f) => Ok(f.as_raw_fd()), + Err(e) => { + log::error!("can't open VC mailbox: {}", e); + Err(e) + } + } +} + +pub fn close_mbox(fd: RawFd) { + if fd >= 0 { + // SAFETY: closing a valid file descriptor + unsafe { libc::close(fd) }; + } +} + +const VC_IOC_MAGIC: u8 = 100; +const VC_IOC_MBOX: u64 = nix::request_code_readwrite!(VC_IOC_MAGIC, 0, mem::size_of::()); + +pub fn msg_mbox(fd: RawFd, msg: &mut VcMsg) -> u32 { + // Zero out unused message buffer + let dlen_words = (msg.dlen / 4) as usize; + let blen_words = (msg.blen / 4) as usize; + for i in dlen_words..=blen_words { + if i < msg.uints.len() { + msg.uints[i] = 0; + } + } + msg.len = ((msg.blen + 6) * 4) as u32; + msg.req = 0; + + let ret = ioctl_write_ptr!(fd, VC_IOC_MAGIC, 0, VcMsg, msg); + + if ret < 0 { + log::error!("VC IOCTL failed"); + 0 + } else if (msg.req & 0x8000_0000) == 0 { + log::error!("VC IOCTL error"); + 0 + } else if msg.req == 0x8000_0001 { + log::error!("VC IOCTL partial error"); + 0 + } else { + msg.uints[0] + } + disp_vc_msg(msgp); +} + +pub fn disp_vc_msg(msg: &VcMsg) { + print!( + "VC msg len={:X}, req={:X}, tag={:X}, blen={:x}, dlen={:x}, data ", + msg.len, msg.req, msg.tag, msg.blen, msg.dlen + ); + let blen_words = (msg.blen / 4) as usize; + for i in 0..blen_words.min(msg.uints.len()) { + print!("{:08X} ", msg.uints[i]); + } + println!(); +} \ No newline at end of file diff --git a/lightsabre_backend/src/devices/mod.rs b/lightsabre_backend/src/devices/mod.rs deleted file mode 100644 index be63e16..0000000 --- a/lightsabre_backend/src/devices/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub mod led_driver; -pub mod selector; - -pub trait Device { - fn read(&self) -> Vec; -} diff --git a/lightsabre_backend/src/devices/selector.rs b/lightsabre_backend/src/devices/selector.rs index 089636a..37586dd 100644 --- a/lightsabre_backend/src/devices/selector.rs +++ b/lightsabre_backend/src/devices/selector.rs @@ -1,31 +1,48 @@ -use rppal::gpio::{Gpio, InputPin}; +use rppal::gpio::{Event, Gpio, InputPin}; -use super::Device; use rppal::gpio::Error; - -const SELECTOR_PINS: [u8; 4] = [27, 5, 6, 26]; // GPIO pin number for the LED +// GPIO pin number for the LED pub struct Selector { selector_pins: Vec, } impl Selector { + const SELECTOR_PINS: [u8; 4] = [27, 5, 6, 26]; + pub fn new() -> Result { let gpio = Gpio::new()?; // Set up the GPIO pins for the selector, use pull-down resistors to ensure a known state when not pressed - let selector_pins: Vec = SELECTOR_PINS + log::debug!( + "Setting up selector with pins {:?} as input_pullup", + Self::SELECTOR_PINS + ); + let selector_pins: Vec = Self::SELECTOR_PINS .iter() - .map(|&pin| gpio.get(pin).unwrap().into_input_pulldown()) + .map(|&pin| gpio.get(pin).unwrap().into_input_pullup()) .collect(); Ok(Selector { selector_pins }) } -} -impl Device for Selector { - fn read(&self) -> Vec { + pub fn set_callback(&mut self, index: usize, callback: F) + where + F: FnMut(Event) + Send + 'static, + { + log::debug!("Setting callback for selector pin at index {}", index); + assert!(index < self.selector_pins.len(), "Index out of bounds"); + self.selector_pins[index] + .set_async_interrupt( + rppal::gpio::Trigger::FallingEdge, + Some(std::time::Duration::from_millis(10)), + callback, + ) + .expect("Failed to set interrupt"); + } + + pub fn get_current_index(&self) -> usize { self.selector_pins .iter() - .map(|pin| pin.read() == rppal::gpio::Level::High) - .collect() + .position(|pin| pin.is_low()) + .unwrap_or(0) // Default to 0 if no pin is low } } diff --git a/lightsabre_backend/src/iotasks.rs b/lightsabre_backend/src/iotasks.rs new file mode 100644 index 0000000..199a414 --- /dev/null +++ b/lightsabre_backend/src/iotasks.rs @@ -0,0 +1 @@ +pub mod selector; diff --git a/lightsabre_backend/src/iotasks/selector.rs b/lightsabre_backend/src/iotasks/selector.rs new file mode 100644 index 0000000..0830e77 --- /dev/null +++ b/lightsabre_backend/src/iotasks/selector.rs @@ -0,0 +1,44 @@ +use crossbeam::channel::Sender; +use rppal::gpio::Event; +use std::error::Error; +use std::sync::{Arc, Mutex}; + +use crate::channels::Message; +use crate::cputasks::modes::AppMode; +use crate::devices::selector::Selector as SelectorDevice; + +pub struct SelectorTask { + _selector_device: SelectorDevice, +} + +impl SelectorTask { + pub fn new(tx: Sender) -> Result> { + log::debug!("Setting up selector task"); + let mut selector_device: SelectorDevice = SelectorDevice::new()?; + let tx: Arc>> = Arc::new(Mutex::new(tx)); + AppMode::for_each(|mode| { + log::debug!( + "Setting up selector callback for mode: ({}){:?}", + mode as usize, + mode + ); + let tx = tx.clone(); + selector_device.set_callback(mode as usize, get_mode_callback(mode, tx)); + }); + get_mode_callback(selector_device.get_current_index().into(), tx)(Event::default()); + Ok(SelectorTask { + _selector_device: selector_device, + }) + } +} + +fn get_mode_callback( + mode: AppMode, + tx: Arc>>, +) -> impl Fn(Event) + Send + 'static { + move |_| { + log::trace!("Selector mode changed: {:?}", mode); + let tx: std::sync::MutexGuard<'_, Sender> = tx.lock().unwrap(); + let _ = tx.send(Message::ModeChanged { mode }); + } +} diff --git a/lightsabre_backend/src/lib.rs b/lightsabre_backend/src/lib.rs new file mode 100644 index 0000000..f9fb3ec --- /dev/null +++ b/lightsabre_backend/src/lib.rs @@ -0,0 +1,121 @@ +mod channels; +mod config; +mod cputasks; +mod devices; +mod iotasks; + +use crate::cputasks::modes::ModeManager; +use crate::devices::led_driver::LedDriver; +use crate::iotasks::selector::SelectorTask; + +use crossbeam::channel::unbounded; +use std::time::{Duration, Instant}; + +struct GlobalContext { + mode_manager: ModeManager, + _selector_task: SelectorTask, + led_driver: LedDriver, +} + +pub struct LightSabre; +pub struct LightSabreIntitialized { + ctx: GlobalContext, +} +pub struct LightSabreRunning { + join_handle: std::thread::JoinHandle<()>, +} + +fn setup() -> GlobalContext { + log::info!("Setting up LightSabre..."); + ctrlc::set_handler(|| { + cleanup(); + std::process::exit(0); + }) + .expect("Error setting SIGINT/SIGTERM/SIGHUP handler"); + let led_driver = LedDriver::new(5); + + let (tx, rx) = unbounded(); + let mode_manager = ModeManager::new(rx); + let selector_task = SelectorTask::new(tx.clone()).expect("Failed to create selector task"); + + // Initialization code here + log::info!("Setup complete."); + GlobalContext { + mode_manager, + _selector_task: selector_task, + led_driver, + } +} + +fn run(ctx: &mut GlobalContext) { + // let period = Duration::from_secs(2); + let period = Duration::from_millis(10); + loop { + let start = Instant::now(); + ctx.mode_manager.run(&mut ctx.led_driver); + let elapsed = start.elapsed(); + + if elapsed > period { + log::warn!( + "Mode {:?} execution took too long: {:?}/{:?}ms", + ctx.mode_manager.get_current_mode(), + elapsed, + period + ); + } + + std::thread::sleep(period.saturating_sub(elapsed)); + } +} + +fn cleanup() { + log::info!("Cleaning up before quitting..."); + // Perform cleanup here +} + +impl LightSabre { + pub fn setup(self) -> LightSabreIntitialized { + self.into() + } +} + +impl LightSabreIntitialized { + pub fn run(self) -> LightSabreRunning { + self.into() + } +} + +impl LightSabreRunning { + pub fn wait(self) -> LightSabre { + // Wait for the running state to finish + // This is a placeholder; actual waiting logic would depend on the application + self.join_handle.join().expect("Thread panicked"); + LightSabre + } + + pub fn stop(self) -> LightSabre { + self.into() + } +} + +impl From for LightSabreIntitialized { + fn from(_: LightSabre) -> Self { + let ctx = setup(); + LightSabreIntitialized { ctx } + } +} + +impl From for LightSabreRunning { + fn from(mut value: LightSabreIntitialized) -> Self { + LightSabreRunning { + join_handle: std::thread::spawn(move || run(&mut value.ctx)), + } + } +} + +impl From for LightSabre { + fn from(_: LightSabreRunning) -> Self { + cleanup(); + LightSabre + } +} diff --git a/lightsabre_backend/src/main.rs b/lightsabre_backend/src/main.rs index 6b23abd..756a52a 100644 --- a/lightsabre_backend/src/main.rs +++ b/lightsabre_backend/src/main.rs @@ -1,26 +1,10 @@ -use devices::Device; -use devices::led_driver::LedDriver; -use devices::selector::Selector; -use log::{debug, info}; -use rppal::system::DeviceInfo; - -mod devices; +use lightsabre_backend::LightSabre; fn main() -> Result<(), Box> { env_logger::init(); - info!("Executing on device: {}", DeviceInfo::new()?.model()); - - // Initialize GPIO - let selector = Selector::new()?; - let ledDriver = devices::led_driver::LedDriver::new()?; - debug!("Selector initialized."); - // Main loop - loop { - // Read the selector state - let selector_state = selector.read(); - debug!("Selector state: {:?}", selector_state); - - // Sleep for a short duration to avoid busy-waiting - std::thread::sleep(std::time::Duration::from_millis(100)); - } + // let mut scheduler = statemachine::Scheduler::new(statemachine::ExecModeTest::new())?; + // scheduler.start() + let ls = LightSabre; + ls.setup().run().wait(); + Ok(()) } diff --git a/lightsabre_backend/src/statemachine.rs b/lightsabre_backend/src/statemachine.rs new file mode 100644 index 0000000..803911c --- /dev/null +++ b/lightsabre_backend/src/statemachine.rs @@ -0,0 +1,157 @@ +use async_trait::async_trait; +use std::error::Error; +use std::ops::Index; + +use env_logger::fmt::style::Color; +// If 'devices' is an external crate, add it to Cargo.toml and use: +// use devices::led_driver::LedDriver; +// use devices::selector::Selector; +use log::{debug, info, trace}; +use rppal::system::DeviceInfo; + +use crate::devices::{self, Device, led_driver}; +use devices::led_driver::LedDriver; +use devices::selector::Selector; + +pub struct Scheduler { + mode: Box, + led_driver: LedDriver, + selector: Selector, +} + +// trait UserCode { +// fn setup(&self); +// fn execute(&self); +// } + +impl Scheduler { + pub fn new(startup_mode: T) -> Result, Box> { + //setup devices + info!("Executing on device: {}", DeviceInfo::new()?.model()); + // Initialize GPIO + let selector = Selector::new()?; + // #[allow(unused_variables)] + let led_driver = LedDriver::new(); + debug!("Selector initialized."); + // Main loop + Ok(Scheduler { + mode: startup_mode, + led_driver, + selector, + }) + } + + #[tokio::main] + pub async fn start(&mut self) -> Result<(), Box> { + loop { + self.mode.execute(&mut self.led_driver).await; + // Read the selector state + let selector_state = self.selector.read(); + trace!("Selector state: {:?}", selector_state); + match selector_state.iter().position(|x| *x) { + Some(index) => { + // If a button is pressed, change the mode based on the index + match index { + 0 => { + info!("Switching to Test Mode"); + self.set_mode(ExecModeTest::new()); + } + 1 => { + info!("Switching to ArtNet Mode"); + self.set_mode(ExecModeArtNet); + } + 2 => { + info!("Switching to Standalone Mode"); + self.set_mode(ExecModeStandalone); + } + 3 => { + info!("Switching to Manual Mode"); + self.set_mode(ExecModeManual); + } + _ => { + warn!("Unknown selector index: {}", index); + } + } + } + None => { + // If no button is pressed, continue with the current mode + warning!("No selector input is it connected, continuing in current mode."); + continue; + } + } + + std::thread::sleep(std::time::Duration::from_millis(500)); + } + } + + fn set_mode(&mut self, next_mode: T) { + self.mode = next_mode; + } +} + +#[async_trait] +pub trait Mode { + async fn execute(&mut self, led_driver: &mut LedDriver); +} + +pub struct ExecModeTest<'a> { + color_iterator: std::iter::Peekable>, + led_iterator: std::ops::Range, +} +pub struct ExecModeArtNet; +pub struct ExecModeStandalone; +pub struct ExecModeManual; + +impl ExecModeTest<'_> { + const TEST_COLORS: [u32; 7] = [ + 0x1f0000, 0x001f00, 0x00001f, 0x5f5f00, 0x1f001f, 0x001f1f, 0x1f1f1f, + ]; + + pub fn new() -> Self { + ExecModeTest { + color_iterator: ExecModeTest::TEST_COLORS.iter().peekable(), + led_iterator: 0..5, + } + } +} + +impl Mode for ExecModeTest<'_> { + async fn execute(&mut self, led_driver: &mut LedDriver) { + let (led, color) = match self.led_iterator.next() { + Some(led) => { + led_driver.set_color(**self.color_iterator.peek().unwrap(), led); + (led, *self.color_iterator.peek().unwrap()) + } + None => { + // Reset the LED iterator if it reaches the end + self.led_iterator = 0..5; + let led = self.led_iterator.next().unwrap(); + self.color_iterator.next(); + ( + led, + match self.color_iterator.peek() { + Some(color) => { + led_driver.set_color(**color, led); + *color + } + None => { + // Reset the color iterator if it reaches the end + self.color_iterator = ExecModeTest::TEST_COLORS.iter().peekable(); + let color = self.color_iterator.peek().unwrap(); + led_driver.set_color(**color, led); + *color + } + }, + ) + } + }; + trace!("Setting LED {} to color {:06x}", led, color); + led_driver.refresh(); + } +} + +impl Mode for ExecModeArtNet { + async fn execute(&mut self, _led_driver: &mut LedDriver) { + info!("Executing ArtNet mode"); + } +} diff --git a/rust_sandbox/Cargo.toml b/rust_sandbox/Cargo.toml new file mode 100644 index 0000000..8c8560f --- /dev/null +++ b/rust_sandbox/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "rust_sandbox" +version = "0.1.0" +edition = "2024" + +[dependencies] +tokio = { version = "1.45.1", features = ["full"] } diff --git a/rust_sandbox/src/main.rs b/rust_sandbox/src/main.rs new file mode 100644 index 0000000..89d6900 --- /dev/null +++ b/rust_sandbox/src/main.rs @@ -0,0 +1,28 @@ +use tokio::{ + spawn, + time::{Duration, sleep}, +}; + +async fn say_hello() { + // Wait for a while before printing to make it a more interesting race. + sleep(Duration::from_millis(100)).await; + print!("hello "); +} + +async fn say_world() { + sleep(Duration::from_millis(100)).await; + print!("world "); +} + +#[tokio::main] +async fn main() { + loop { + let handle1 = spawn(say_hello()); + let handle2 = spawn(say_world()); + + let _ = handle1.await; + let _ = handle2.await; + + println!("!"); + } +} diff --git a/tools/remote_debug.sh b/tools/remote_debug.sh index 510554b..b7a5d52 100755 --- a/tools/remote_debug.sh +++ b/tools/remote_debug.sh @@ -11,7 +11,7 @@ TARGET_USER="pi" TARGET_BIN_FILE="/tmp/${APP}" TARGET_CWD="/tmp" -ssh "${TARGET_USER}@${SSH_REMOTE}" "killall lldb-server ${APP}" +ssh "${TARGET_USER}@${SSH_REMOTE}" "sudo killall lldb-server ${APP}" if ! rsync -avz "${BUILD_BIN_FILE}" "${TARGET_USER}@${SSH_REMOTE}:${TARGET_BIN_FILE}"; then # If rsync doesn't work, it may not be available on target. Fallback to trying SSH copy. @@ -20,4 +20,4 @@ if ! rsync -avz "${BUILD_BIN_FILE}" "${TARGET_USER}@${SSH_REMOTE}:${TARGET_BIN_F fi fi -ssh -f "${TARGET_USER}@${SSH_REMOTE}" "sh -c 'cd ${TARGET_CWD}; RUST_LOG=debug nohup lldb-server g *:${GDBPORT} ${TARGET_BIN_FILE} > /dev/null 2>&1 &'" \ No newline at end of file +ssh -f "${TARGET_USER}@${SSH_REMOTE}" "sh -c 'cd ${TARGET_CWD}; sudo RUST_LOG=trace nohup lldb-server g *:${GDBPORT} ${TARGET_BIN_FILE} > /dev/null 2>&1 &'" \ No newline at end of file diff --git a/tools/run.sh b/tools/run.sh new file mode 100755 index 0000000..98d495d --- /dev/null +++ b/tools/run.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +VSCODE_WS="$(pwd)" +SSH_REMOTE="raspberrypi.local" + +APP="lightsabre_backend" +TARGET_ARCH="armv7-unknown-linux-gnueabihf" +BUILD_BIN_FILE="${VSCODE_WS}/target/${TARGET_ARCH}/debug/${APP}" +TARGET_USER="pi" +TARGET_BIN_FILE="/tmp/${APP}" +TARGET_CWD="/tmp" + +cargo build + +ssh "${TARGET_USER}@${SSH_REMOTE}" "sudo killall lldb-server ${APP}" + +if ! rsync -avz "${BUILD_BIN_FILE}" "${TARGET_USER}@${SSH_REMOTE}:${TARGET_BIN_FILE}"; then + # If rsync doesn't work, it may not be available on target. Fallback to trying SSH copy. + if ! scp "${BUILD_BIN_FILE}" "${TARGET_USER}@${SSH_REMOTE}:${TARGET_BIN_FILE}"; then + exit 2 + fi +fi + +if [[ "$1" == "--debug" ]]; then + echo "Running in debug mode" + ssh "${TARGET_USER}@${SSH_REMOTE}" "sh -c 'cd ${TARGET_CWD}; sudo RUST_LOG=debug lldb -o run ${TARGET_BIN_FILE}'" +else + echo "Running in release mode" + ssh "${TARGET_USER}@${SSH_REMOTE}" "sh -c 'cd ${TARGET_CWD}; sudo RUST_LOG=info ${TARGET_BIN_FILE}'" +fi \ No newline at end of file