Working Diag & artnet proto with imported led driver smi lib C

This commit is contained in:
2025-06-23 17:28:04 +00:00
parent 86fbf1670c
commit 535822198b
49 changed files with 3014 additions and 99 deletions
+3 -3
View File
@@ -15,9 +15,9 @@
"processCreateCommands": [
"gdb-remote raspberrypi.local:17777"
],
"env": {
"RUST_LOG": "debug"
}
// "env": {
// "RUST_LOG": "trace"
// }
}
]
}
+3 -1
View File
@@ -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
}
Generated
+627 -2
View File
@@ -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"
+1 -1
View File
@@ -1,3 +1,3 @@
[workspace]
resolver = "3"
members = ["lightsabre_backend"]
members = ["lightsabre_backend", "rust_sandbox"]
+35 -7
View File
@@ -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/)
+6
View File
@@ -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"]
+4
View File
@@ -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");
}
Binary file not shown.
+49
View File
@@ -0,0 +1,49 @@
/**
* Copyright (c) 2020 rxi
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the MIT license. See `log.c` for details.
*/
#ifndef LOG_H
#define LOG_H
#include <stdio.h>
#include <stdarg.h>
#include <stdbool.h>
#include <time.h>
#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
@@ -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)
+56
View File
@@ -0,0 +1,56 @@
#include "common.h"
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
#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));
}
}
+50
View File
@@ -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__
@@ -0,0 +1,90 @@
#include "rpi_dma.h"
#include <stdio.h>
#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");
}
}
@@ -0,0 +1,33 @@
#if !defined(__RPI_DMA_H__)
#define __RPI_DMA_H__
#include <stdint.h>
#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__
@@ -0,0 +1,128 @@
#include "rpi_videocore.h"
#include <fcntl.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <unistd.h>
#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);
}
@@ -0,0 +1,33 @@
#if !defined(__RPI_VIDEOCORE_H__)
#define __RPI_VIDEOCORE_H__
#include <stdint.h>
#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__
@@ -0,0 +1,88 @@
#include "rpi_gpio.h"
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
#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");
}
@@ -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__
@@ -0,0 +1,210 @@
#include "rpi_leddriver.h"
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#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);
}
@@ -0,0 +1,18 @@
#if !defined(__RPI_LEDDRIVER_H__)
#define __RPI_LEDDRIVER_H__
#include <stdint.h>
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__
@@ -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)
@@ -0,0 +1,223 @@
#include "rpi_smi.h"
#include <unistd.h>
#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));
}
@@ -0,0 +1,14 @@
#if !defined(__RPI_SMI_H__)
#define __RPI_SMI_H__
#include "../common.h"
#include <stdint.h>
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__
+6
View File
@@ -0,0 +1,6 @@
use crate::cputasks::modes::AppMode;
pub enum Message {
ModeChanged { mode: AppMode },
// Other messages...
}
+1
View File
@@ -0,0 +1 @@
+1
View File
@@ -0,0 +1 @@
pub mod modes;
+107
View File
@@ -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<F: FnMut(AppMode)>(mut f: F) {
f(AppMode::Diagnostics);
f(AppMode::ArtNet);
f(AppMode::Standalone);
f(AppMode::Manual);
}
}
impl From<usize> 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<AppMode, Box<dyn AppModeHandler + Send>>,
mode: Arc<Mutex<AppMode>>,
mode_rx: Receiver<Message>,
}
impl ModeManager {
pub fn new(mode_rx: Receiver<Message>) -> Self {
let mut handlers: HashMap<AppMode, Box<dyn AppModeHandler + Send>> = 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<AppMode>) -> &mut Box<dyn AppModeHandler + Send> {
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))
}
}
@@ -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<UdpSocket>,
last_frame_time: Option<Instant>,
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<Duration>,
max_time: Option<Duration>,
total_time: Duration,
avg_delta: Option<Duration>,
}
impl ArtNetModeStatistics {
fn update(&mut self, last_frame_time: Option<Instant>) {
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, "==================================================")
}
}
@@ -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<Iter<'static, u32>>,
led_iterator: Range<usize>,
}
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<Iter<'static, u32>> {
Self::TEST_COLORS.iter().peekable()
}
fn init_led_iterator(led_driver: &LedDriver) -> Range<usize> {
// 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");
}
}
@@ -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");
}
}
@@ -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<rppal::gpio::IoPin>,
}
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<rppal::gpio::IoPin> = 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");
}
}
+2
View File
@@ -0,0 +1,2 @@
pub mod led_driver;
pub mod selector;
+46 -44
View File
@@ -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<Self, Box<dyn std::error::Error>> {
// 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<dyn std::error::Error>> {
// 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<dyn std::error::Error>> {
// 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() };
}
}
@@ -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<dyn std::error::Error>> {
// Set the color for the LED at the specified index
// Placeholder for actual implementation
Ok(())
}
pub fn refresh(&self) -> Result<(), Box<dyn std::error::Error>> {
// Refresh the LED states
// Placeholder for actual implementation
Ok(())
}
}
@@ -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<MemMap> {
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));
}
}
@@ -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<RawFd> {
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::<VcMsg>());
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!();
}
-6
View File
@@ -1,6 +0,0 @@
pub mod led_driver;
pub mod selector;
pub trait Device {
fn read(&self) -> Vec<bool>;
}
+28 -11
View File
@@ -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<InputPin>,
}
impl Selector {
const SELECTOR_PINS: [u8; 4] = [27, 5, 6, 26];
pub fn new() -> Result<Selector, Error> {
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<InputPin> = SELECTOR_PINS
log::debug!(
"Setting up selector with pins {:?} as input_pullup",
Self::SELECTOR_PINS
);
let selector_pins: Vec<InputPin> = 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<bool> {
pub fn set_callback<F>(&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
}
}
+1
View File
@@ -0,0 +1 @@
pub mod selector;
@@ -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<Message>) -> Result<SelectorTask, Box<dyn Error>> {
log::debug!("Setting up selector task");
let mut selector_device: SelectorDevice = SelectorDevice::new()?;
let tx: Arc<Mutex<Sender<Message>>> = 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<Mutex<Sender<Message>>>,
) -> impl Fn(Event) + Send + 'static {
move |_| {
log::trace!("Selector mode changed: {:?}", mode);
let tx: std::sync::MutexGuard<'_, Sender<Message>> = tx.lock().unwrap();
let _ = tx.send(Message::ModeChanged { mode });
}
}
+121
View File
@@ -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<LightSabre> for LightSabreIntitialized {
fn from(_: LightSabre) -> Self {
let ctx = setup();
LightSabreIntitialized { ctx }
}
}
impl From<LightSabreIntitialized> for LightSabreRunning {
fn from(mut value: LightSabreIntitialized) -> Self {
LightSabreRunning {
join_handle: std::thread::spawn(move || run(&mut value.ctx)),
}
}
}
impl From<LightSabreRunning> for LightSabre {
fn from(_: LightSabreRunning) -> Self {
cleanup();
LightSabre
}
}
+6 -22
View File
@@ -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<dyn std::error::Error>> {
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(())
}
+157
View File
@@ -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<dyn Mode>,
led_driver: LedDriver,
selector: Selector,
}
// trait UserCode {
// fn setup(&self);
// fn execute(&self);
// }
impl Scheduler {
pub fn new(startup_mode: T) -> Result<Scheduler<T>, Box<dyn Error>> {
//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<dyn Error>> {
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<std::slice::Iter<'a, u32>>,
led_iterator: std::ops::Range<usize>,
}
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");
}
}
+7
View File
@@ -0,0 +1,7 @@
[package]
name = "rust_sandbox"
version = "0.1.0"
edition = "2024"
[dependencies]
tokio = { version = "1.45.1", features = ["full"] }
+28
View File
@@ -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!("!");
}
}
+2 -2
View File
@@ -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 &'"
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 &'"
Executable
+30
View File
@@ -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