Compare commits
69 Commits
08a3c65346
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| fb35f88c14 | |||
| d01cddb423 | |||
| a5a2fdc1c7 | |||
| c747a0fbf1 | |||
| fc9bd0b7a7 | |||
| 4977746af1 | |||
| 8a3b2303a2 | |||
| 8ff29caa6f | |||
| 3fff4fdfd1 | |||
| f803b3c2d4 | |||
| ee5e2e2a4b | |||
| db65fbafd8 | |||
| 00d3df315b | |||
| 73e0773942 | |||
| e18425e0ca | |||
| 38f49513f3 | |||
| 6779881523 | |||
| cda0f424b1 | |||
| fb037a6aac | |||
| 3a4ce6344e | |||
| bf2d1c6077 | |||
| e5f96bc87c | |||
| a0580b08e6 | |||
| b031e4c64b | |||
| f64dbae4e5 | |||
| 2213c56ddb | |||
| aa69464258 | |||
| f8e53e85a7 | |||
| 462de0af99 | |||
| 040a419a2f | |||
| 2ad22a2632 | |||
| 3f87e22585 | |||
| e09649592b | |||
| ccb2680954 | |||
| 94144ab4b3 | |||
| 25a6122d53 | |||
| 4e5f053c18 | |||
| 49c201add7 | |||
| 4eafbde5a9 | |||
| d942561900 | |||
| 45d84a91dc | |||
| 74d753e0b3 | |||
| 9ecfd11982 | |||
| 96e128ac67 | |||
| caefdfe131 | |||
| f0d7968843 | |||
| c77ecc9a19 | |||
| 561fef0ed1 | |||
| 6c43d9f2b7 | |||
| 3479fd1bf8 | |||
| c9518498f2 | |||
| f650cfa644 | |||
| 18458bbf27 | |||
| 030575f395 | |||
| e2a26dab5a | |||
| 99a3c61f20 | |||
| c23764c6ef | |||
| a62aefda77 | |||
| 0d8254501b | |||
| 3b763fcf99 | |||
| 315fa633c1 | |||
| 54ebbbaea7 | |||
| 816cc20087 | |||
| e8a7a18539 | |||
| 22d1b4d077 | |||
| 935f30d968 | |||
| f2b2674158 | |||
| 319c812a61 | |||
| 95ebc7820a |
+5
-5
@@ -2,11 +2,11 @@
|
|||||||
runner = "espflash flash --monitor --partition-table ./partitions.csv"
|
runner = "espflash flash --monitor --partition-table ./partitions.csv"
|
||||||
|
|
||||||
[env]
|
[env]
|
||||||
ESP_LOG="info"
|
ESP_HAL_CONFIG_PLACE_SPI_MASTER_DRIVER_IN_RAM="true"
|
||||||
ESP_HAL_CONFIG_PLACE_RMT_DRIVER_IN_RAM="true"
|
ESP_RTOS_CONFIG_SW_TASK_OVERFLOW_DETECTION="true"
|
||||||
ESP_HAL_EMBASSY_CONFIG_TIMER_QUEUE="multiple-integrated"
|
ESP_RTOS_CONFIG_HW_TASK_OVERFLOW_DETECTION="true"
|
||||||
ESP_WIFI_CONFIG_COUNTRY_CODE="DE"
|
REASSEMBLY_BUFFER_COUNT="5"
|
||||||
ESP_RTOS_CONFIG_TICK_RATE_HZ="50"
|
ESP_PHY_CONFIG_PHY_ENABLE_USB="false"
|
||||||
|
|
||||||
[build]
|
[build]
|
||||||
rustflags = [
|
rustflags = [
|
||||||
|
|||||||
Generated
+446
-423
File diff suppressed because it is too large
Load Diff
+31
-29
@@ -1,68 +1,62 @@
|
|||||||
[package]
|
[package]
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
name = "renderbug-embassy"
|
name = "renderbug-bike"
|
||||||
rust-version = "1.86"
|
rust-version = "1.92"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
default-run = "renderbug-embassy"
|
default-run = "renderbug-bike"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "renderbug-embassy"
|
name = "renderbug-bike"
|
||||||
path = "./src/bin/main.rs"
|
path = "./src/bin/main.rs"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["real-output"]
|
default = ["radio", "i2c", "mpu6050", "oled"]
|
||||||
real-output = []
|
|
||||||
dual-core = []
|
dual-core = []
|
||||||
simulation = ["dep:rmp"]
|
simulation = []
|
||||||
radio = [
|
radio = [
|
||||||
"dep:esp-radio",
|
"dep:esp-radio",
|
||||||
"dep:reqwless",
|
"dep:reqwless",
|
||||||
"dep:trouble-host",
|
"dep:trouble-host",
|
||||||
"esp-rtos/esp-radio"
|
"esp-rtos/esp-radio"
|
||||||
]
|
]
|
||||||
motion = ["mpu", "gps"]
|
i2c = ["dep:nmea"]
|
||||||
|
mpu6050 = ["i2c", "dep:mpu6050-dmp"]
|
||||||
max-usb-power = []
|
max-usb-power = []
|
||||||
wokwi = ["max-usb-power"]
|
wokwi = ["max-usb-power"]
|
||||||
mpu = ["dep:mpu6050-dmp"]
|
oled = ["dep:ssd1306", "dep:display-interface"]
|
||||||
gps = ["dep:nmea"]
|
|
||||||
oled = ["dep:ssd1306"]
|
|
||||||
rtt = ["dep:rtt-target"]
|
rtt = ["dep:rtt-target"]
|
||||||
demo = []
|
demo = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# The basic requirements for all features
|
# The basic requirements for all features
|
||||||
figments = { path = "../figments/figments/", features = ["alloc", "embedded-graphics"] }
|
figments = { path = "../figments/figments/", features = ["alloc", "embedded-graphics", "log-04"] }
|
||||||
figments-render = { path = "../figments/figments-render/", features = ["smart-leds", "micromath"], default-features = false }
|
figments-render = { path = "../figments/figments-render/", features = ["smart-leds", "micromath", "log-04"], default-features = false }
|
||||||
|
figments-esp32-ws2812-dma = { path = "../figments/figments-esp32-ws2812-dma/", features = ["esp32s3"] }
|
||||||
esp-bootloader-esp-idf = { version = "0.4.0", features = ["esp32s3"] }
|
esp-bootloader-esp-idf = { version = "0.4.0", features = ["esp32s3"] }
|
||||||
esp-hal = { version = "1.0.0", features = [
|
esp-hal = { version = "1.0.0", features = [
|
||||||
"esp32s3",
|
"esp32s3",
|
||||||
"log-04",
|
|
||||||
"unstable",
|
"unstable",
|
||||||
|
"log-04"
|
||||||
] }
|
] }
|
||||||
esp-alloc = "0.9.0"
|
esp-alloc = { version = "0.9.0", default-features = false, features = ["internal-heap-stats"] }
|
||||||
esp-backtrace = { version = "0.18", features = [
|
esp-backtrace = { version = "0.18", features = ["esp32s3", "halt-cores", "panic-handler", "println"] }
|
||||||
"esp32s3",
|
|
||||||
"panic-handler",
|
|
||||||
"println",
|
|
||||||
] }
|
|
||||||
esp-println = { version = "0.16", features = ["esp32s3", "log-04"] }
|
esp-println = { version = "0.16", features = ["esp32s3", "log-04"] }
|
||||||
# for more networking protocol support see https://crates.io/crates/edge-net
|
# for more networking protocol support see https://crates.io/crates/edge-net
|
||||||
critical-section = "1.2.0"
|
critical-section = "1.2.0"
|
||||||
embassy-executor = { version = "0.9.0", features = [
|
embassy-executor = { version = "0.9.0", default-features = false, features = [
|
||||||
"log",
|
"log",
|
||||||
# "task-arena-size-98304",
|
|
||||||
] }
|
] }
|
||||||
embassy-time = { version = "0.5.0", features = ["log"] }
|
embassy-time = { version = "0.5.0", default-features = false, features = ["log"] }
|
||||||
esp-rtos = { version = "0.2.0", features = [
|
esp-rtos = { version = "0.2.0", features = [
|
||||||
"embassy",
|
"embassy",
|
||||||
"esp-alloc",
|
"esp-alloc",
|
||||||
"esp32s3",
|
"esp32s3",
|
||||||
|
# "rtos-trace",
|
||||||
"log-04"
|
"log-04"
|
||||||
] }
|
] }
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
static_cell = "2.1.1"
|
static_cell = "2.1.1"
|
||||||
rgb = "0.8.52"
|
rgb = "0.8.52"
|
||||||
esp-hal-smartled = { version = "0.17.0", features = ["esp32s3"] }
|
|
||||||
smart-leds = "0.4.0"
|
smart-leds = "0.4.0"
|
||||||
embassy-sync = "0.7.2"
|
embassy-sync = "0.7.2"
|
||||||
embassy-embedded-hal = "0.5.0"
|
embassy-embedded-hal = "0.5.0"
|
||||||
@@ -74,6 +68,9 @@ enumset = "1.1.10"
|
|||||||
enum-map = "2.7.3"
|
enum-map = "2.7.3"
|
||||||
portable-atomic = { version = "1.11", features = ["critical-section"] }
|
portable-atomic = { version = "1.11", features = ["critical-section"] }
|
||||||
embassy-futures = { version = "0.1.2", features = ["log"] }
|
embassy-futures = { version = "0.1.2", features = ["log"] }
|
||||||
|
embedded-graphics = { version = "0.8.1", features = ["nalgebra_support"] }
|
||||||
|
ssd1306 = { version = "0.10.0", features = ["async"], optional = true }
|
||||||
|
display-interface = { version = "0.5.0", optional = true }
|
||||||
|
|
||||||
# Telemetry outputs
|
# Telemetry outputs
|
||||||
esp-radio = { version = "*", optional = true, features = [
|
esp-radio = { version = "*", optional = true, features = [
|
||||||
@@ -99,6 +96,7 @@ nmea = { version = "0.7.0", optional = true, default-features = false, features
|
|||||||
|
|
||||||
"GLL",
|
"GLL",
|
||||||
"GST",
|
"GST",
|
||||||
|
"all-sentences"
|
||||||
] }
|
] }
|
||||||
mpu6050-dmp = { version = "0.6.1", features = ["async"], optional = true }
|
mpu6050-dmp = { version = "0.6.1", features = ["async"], optional = true }
|
||||||
|
|
||||||
@@ -108,10 +106,14 @@ rtt-target = { version = "0.6.2", optional = true }
|
|||||||
# Simulation
|
# Simulation
|
||||||
esp-storage = { version = "0.7.0", features = ["esp32s3", "critical-section"] }
|
esp-storage = { version = "0.7.0", features = ["esp32s3", "critical-section"] }
|
||||||
embedded-storage = "0.3.1"
|
embedded-storage = "0.3.1"
|
||||||
rmp = { path = "../msgpack-rust/rmp/", optional = true, default-features = false }
|
rmp = { path = "../msgpack-rust/rmp/", default-features = false }
|
||||||
display-interface = "0.5.0"
|
heapless = { version = "0.9.1", features = ["portable-atomic"] }
|
||||||
embassy-net = { version = "0.7.1", features = ["alloc", "dns", "medium-ethernet", "proto-ipv4", "tcp", "udp", "dhcpv4"] }
|
num-traits = { version = "0.2.19", default-features = false }
|
||||||
reqwless = { version = "0.13.0", optional = true }
|
rtos-trace = { version = "0.2.1", default-features = false, features = ["trace_impl"] }
|
||||||
|
embedded-sdmmc = "0.9.0"
|
||||||
|
embedded-hal-bus = "0.3.0"
|
||||||
|
embedded-hal = "1.0.0"
|
||||||
|
embedded-io = "0.6.1"
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
# Rust debug is too slow.
|
# Rust debug is too slow.
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# For PCBs designed using KiCad: https://www.kicad.org/
|
||||||
|
# Format documentation: https://kicad.org/help/file-formats/
|
||||||
|
|
||||||
|
# Temporary files
|
||||||
|
*.000
|
||||||
|
*.bak
|
||||||
|
*.bck
|
||||||
|
*.kicad_pcb-bak
|
||||||
|
*.kicad_sch-bak
|
||||||
|
*-backups
|
||||||
|
*-cache*
|
||||||
|
*-bak
|
||||||
|
*-bak*
|
||||||
|
*~
|
||||||
|
~*
|
||||||
|
_autosave-*
|
||||||
|
\#auto_saved_files\#
|
||||||
|
*.tmp
|
||||||
|
*-save.pro
|
||||||
|
*-save.kicad_pcb
|
||||||
|
fp-info-cache
|
||||||
|
~*.lck
|
||||||
|
\#auto_saved_files#
|
||||||
|
|
||||||
|
# Netlist files (exported from Eeschema)
|
||||||
|
*.net
|
||||||
|
|
||||||
|
# Autorouter files (exported from Pcbnew)
|
||||||
|
*.dsn
|
||||||
|
*.ses
|
||||||
|
|
||||||
|
# Exported BOM files
|
||||||
|
*.xml
|
||||||
|
*.csv
|
||||||
|
|
||||||
|
# Archived Backups (KiCad 6.0)
|
||||||
|
**/*-backups/*.zip
|
||||||
|
|
||||||
|
# Local project settings
|
||||||
|
*.kicad_prl
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
EESchema-DOCLIB Version 2.0
|
||||||
|
#
|
||||||
|
$CMP L6982N50DR
|
||||||
|
D 38 V, 2 A synchronous step-down converter with 20 uA quiescent current
|
||||||
|
K
|
||||||
|
F https://www.st.com/resource/en/datasheet/l6982.pdf
|
||||||
|
$ENDCMP
|
||||||
|
#
|
||||||
|
#End Doc Library
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
(kicad_symbol_lib (version 20211014) (generator SamacSys_ECAD_Model)
|
||||||
|
(symbol "L6982N50DR" (in_bom yes) (on_board yes)
|
||||||
|
(property "Reference" "IC" (at 34.29 7.62 0)
|
||||||
|
(effects (font (size 1.27 1.27)) (justify left top))
|
||||||
|
)
|
||||||
|
(property "Value" "L6982N50DR" (at 34.29 5.08 0)
|
||||||
|
(effects (font (size 1.27 1.27)) (justify left top))
|
||||||
|
)
|
||||||
|
(property "Footprint" "SOIC127P600X175-8N" (at 34.29 -94.92 0)
|
||||||
|
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||||
|
)
|
||||||
|
(property "Datasheet" "https://www.st.com/resource/en/datasheet/l6982.pdf" (at 34.29 -194.92 0)
|
||||||
|
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||||
|
)
|
||||||
|
(property "ki_description" "38 V, 2 A synchronous step-down converter with 20 uA quiescent current" (at 34.29 -294.92 0)
|
||||||
|
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||||
|
)
|
||||||
|
(property "Height" "1.75" (at 34.29 -394.92 0)
|
||||||
|
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||||
|
)
|
||||||
|
(property "Manufacturer_Name" "STMicroelectronics" (at 34.29 -494.92 0)
|
||||||
|
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||||
|
)
|
||||||
|
(property "Manufacturer_Part_Number" "L6982N50DR" (at 34.29 -594.92 0)
|
||||||
|
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||||
|
)
|
||||||
|
(property "Mouser Part Number" "511-L6982N50DR" (at 34.29 -694.92 0)
|
||||||
|
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||||
|
)
|
||||||
|
(property "Mouser Price/Stock" "https://www.mouser.co.uk/ProductDetail/STMicroelectronics/L6982N50DR?qs=rQFj71Wb1eViugxt34b%2FVA%3D%3D" (at 34.29 -794.92 0)
|
||||||
|
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||||
|
)
|
||||||
|
(property "Arrow Part Number" "L6982N50DR" (at 34.29 -894.92 0)
|
||||||
|
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||||
|
)
|
||||||
|
(property "Arrow Price/Stock" "https://www.arrow.com/en/products/l6982n50dr/stmicroelectronics?utm_currency=USD®ion=nac" (at 34.29 -994.92 0)
|
||||||
|
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||||
|
)
|
||||||
|
(rectangle
|
||||||
|
(start 5.08 2.54)
|
||||||
|
(end 33.02 -10.16)
|
||||||
|
(stroke (width 0.254) (type default))
|
||||||
|
(fill (type background))
|
||||||
|
)
|
||||||
|
(pin passive line (at 0 0 0) (length 5.08)
|
||||||
|
(name "SW" (effects (font (size 1.27 1.27))))
|
||||||
|
(number "1" (effects (font (size 1.27 1.27))))
|
||||||
|
)
|
||||||
|
(pin passive line (at 0 -2.54 0) (length 5.08)
|
||||||
|
(name "BOOT" (effects (font (size 1.27 1.27))))
|
||||||
|
(number "2" (effects (font (size 1.27 1.27))))
|
||||||
|
)
|
||||||
|
(pin passive line (at 0 -5.08 0) (length 5.08)
|
||||||
|
(name "VCC" (effects (font (size 1.27 1.27))))
|
||||||
|
(number "3" (effects (font (size 1.27 1.27))))
|
||||||
|
)
|
||||||
|
(pin passive line (at 0 -7.62 0) (length 5.08)
|
||||||
|
(name "VOUT/FB" (effects (font (size 1.27 1.27))))
|
||||||
|
(number "4" (effects (font (size 1.27 1.27))))
|
||||||
|
)
|
||||||
|
(pin passive line (at 38.1 0 180) (length 5.08)
|
||||||
|
(name "PGND" (effects (font (size 1.27 1.27))))
|
||||||
|
(number "8" (effects (font (size 1.27 1.27))))
|
||||||
|
)
|
||||||
|
(pin passive line (at 38.1 -2.54 180) (length 5.08)
|
||||||
|
(name "VIN" (effects (font (size 1.27 1.27))))
|
||||||
|
(number "7" (effects (font (size 1.27 1.27))))
|
||||||
|
)
|
||||||
|
(pin passive line (at 38.1 -5.08 180) (length 5.08)
|
||||||
|
(name "AGND" (effects (font (size 1.27 1.27))))
|
||||||
|
(number "6" (effects (font (size 1.27 1.27))))
|
||||||
|
)
|
||||||
|
(pin passive line (at 38.1 -7.62 180) (length 5.08)
|
||||||
|
(name "EN/CLKIN" (effects (font (size 1.27 1.27))))
|
||||||
|
(number "5" (effects (font (size 1.27 1.27))))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
EESchema-LIBRARY Version 2.3
|
||||||
|
#encoding utf-8
|
||||||
|
#SamacSys ECAD Model L6982N50DR
|
||||||
|
#/17168867/1957227/2.50/8/2/Integrated Circuit
|
||||||
|
DEF L6982N50DR IC 0 30 Y Y 1 F N
|
||||||
|
F0 "IC" 1350 300 50 H V L CNN
|
||||||
|
F1 "L6982N50DR" 1350 200 50 H V L CNN
|
||||||
|
F2 "SOIC127P600X175-8N" 1350 100 50 H I L CNN
|
||||||
|
F3 "https://www.st.com/resource/en/datasheet/l6982.pdf" 1350 0 50 H I L CNN
|
||||||
|
F4 "38 V, 2 A synchronous step-down converter with 20 uA quiescent current" 1350 -100 50 H I L CNN "Description"
|
||||||
|
F5 "1.75" 1350 -200 50 H I L CNN "Height"
|
||||||
|
F6 "STMicroelectronics" 1350 -300 50 H I L CNN "Manufacturer_Name"
|
||||||
|
F7 "L6982N50DR" 1350 -400 50 H I L CNN "Manufacturer_Part_Number"
|
||||||
|
F8 "511-L6982N50DR" 1350 -500 50 H I L CNN "Mouser Part Number"
|
||||||
|
F9 "https://www.mouser.co.uk/ProductDetail/STMicroelectronics/L6982N50DR?qs=rQFj71Wb1eViugxt34b%2FVA%3D%3D" 1350 -600 50 H I L CNN "Mouser Price/Stock"
|
||||||
|
F10 "L6982N50DR" 1350 -700 50 H I L CNN "Arrow Part Number"
|
||||||
|
F11 "https://www.arrow.com/en/products/l6982n50dr/stmicroelectronics?utm_currency=USD®ion=nac" 1350 -800 50 H I L CNN "Arrow Price/Stock"
|
||||||
|
DRAW
|
||||||
|
X SW 1 0 0 200 R 50 50 0 0 P
|
||||||
|
X BOOT 2 0 -100 200 R 50 50 0 0 P
|
||||||
|
X VCC 3 0 -200 200 R 50 50 0 0 P
|
||||||
|
X VOUT/FB 4 0 -300 200 R 50 50 0 0 P
|
||||||
|
X PGND 8 1500 0 200 L 50 50 0 0 P
|
||||||
|
X VIN 7 1500 -100 200 L 50 50 0 0 P
|
||||||
|
X AGND 6 1500 -200 200 L 50 50 0 0 P
|
||||||
|
X EN/CLKIN 5 1500 -300 200 L 50 50 0 0 P
|
||||||
|
P 5 0 1 6 200 100 1300 100 1300 -400 200 -400 200 100 N
|
||||||
|
ENDDRAW
|
||||||
|
ENDDEF
|
||||||
|
#
|
||||||
|
#End Library
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
PCBNEW-LibModule-V1 2026-03-07 11:39:32
|
||||||
|
# encoding utf-8
|
||||||
|
Units mm
|
||||||
|
$INDEX
|
||||||
|
SOIC127P600X175-8N
|
||||||
|
$EndINDEX
|
||||||
|
$MODULE SOIC127P600X175-8N
|
||||||
|
Po 0 0 0 15 69ac0e74 00000000 ~~
|
||||||
|
Li SOIC127P600X175-8N
|
||||||
|
Cd SO 8L
|
||||||
|
Kw Integrated Circuit
|
||||||
|
Sc 0
|
||||||
|
At SMD
|
||||||
|
AR
|
||||||
|
Op 0 0 0
|
||||||
|
T0 0 0 1.27 1.27 0 0.254 N V 21 N "IC**"
|
||||||
|
T1 0 0 1.27 1.27 0 0.254 N I 21 N "SOIC127P600X175-8N"
|
||||||
|
DS -3.725 -2.75 3.725 -2.75 0.05 24
|
||||||
|
DS 3.725 -2.75 3.725 2.75 0.05 24
|
||||||
|
DS 3.725 2.75 -3.725 2.75 0.05 24
|
||||||
|
DS -3.725 2.75 -3.725 -2.75 0.05 24
|
||||||
|
DS -1.95 -2.45 1.95 -2.45 0.1 24
|
||||||
|
DS 1.95 -2.45 1.95 2.45 0.1 24
|
||||||
|
DS 1.95 2.45 -1.95 2.45 0.1 24
|
||||||
|
DS -1.95 2.45 -1.95 -2.45 0.1 24
|
||||||
|
DS -1.95 -1.18 -0.68 -2.45 0.1 24
|
||||||
|
DS -1.6 -2.45 1.6 -2.45 0.2 21
|
||||||
|
DS 1.6 -2.45 1.6 2.45 0.2 21
|
||||||
|
DS 1.6 2.45 -1.6 2.45 0.2 21
|
||||||
|
DS -1.6 2.45 -1.6 -2.45 0.2 21
|
||||||
|
DS -3.475 -2.605 -1.95 -2.605 0.2 21
|
||||||
|
$PAD
|
||||||
|
Po -2.712 -1.905
|
||||||
|
Sh "1" R 0.7 1.525 0 0 900
|
||||||
|
At SMD N 00888000
|
||||||
|
Ne 0 ""
|
||||||
|
$EndPAD
|
||||||
|
$PAD
|
||||||
|
Po -2.712 -0.635
|
||||||
|
Sh "2" R 0.7 1.525 0 0 900
|
||||||
|
At SMD N 00888000
|
||||||
|
Ne 0 ""
|
||||||
|
$EndPAD
|
||||||
|
$PAD
|
||||||
|
Po -2.712 0.635
|
||||||
|
Sh "3" R 0.7 1.525 0 0 900
|
||||||
|
At SMD N 00888000
|
||||||
|
Ne 0 ""
|
||||||
|
$EndPAD
|
||||||
|
$PAD
|
||||||
|
Po -2.712 1.905
|
||||||
|
Sh "4" R 0.7 1.525 0 0 900
|
||||||
|
At SMD N 00888000
|
||||||
|
Ne 0 ""
|
||||||
|
$EndPAD
|
||||||
|
$PAD
|
||||||
|
Po 2.712 1.905
|
||||||
|
Sh "5" R 0.7 1.525 0 0 900
|
||||||
|
At SMD N 00888000
|
||||||
|
Ne 0 ""
|
||||||
|
$EndPAD
|
||||||
|
$PAD
|
||||||
|
Po 2.712 0.635
|
||||||
|
Sh "6" R 0.7 1.525 0 0 900
|
||||||
|
At SMD N 00888000
|
||||||
|
Ne 0 ""
|
||||||
|
$EndPAD
|
||||||
|
$PAD
|
||||||
|
Po 2.712 -0.635
|
||||||
|
Sh "7" R 0.7 1.525 0 0 900
|
||||||
|
At SMD N 00888000
|
||||||
|
Ne 0 ""
|
||||||
|
$EndPAD
|
||||||
|
$PAD
|
||||||
|
Po 2.712 -1.905
|
||||||
|
Sh "8" R 0.7 1.525 0 0 900
|
||||||
|
At SMD N 00888000
|
||||||
|
Ne 0 ""
|
||||||
|
$EndPAD
|
||||||
|
$EndMODULE SOIC127P600X175-8N
|
||||||
|
$EndLIBRARY
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
(module "LSM6DSV16BXTR" (layer F.Cu)
|
||||||
|
(descr "LGA-14L 2.5 x 3.0 x 0.74 mm XDM)")
|
||||||
|
(tags "Integrated Circuit")
|
||||||
|
(attr smd)
|
||||||
|
(fp_text reference IC** (at 0.000 -0) (layer F.SilkS)
|
||||||
|
(effects (font (size 1.27 1.27) (thickness 0.254)))
|
||||||
|
)
|
||||||
|
(fp_text user %R (at 0.000 -0) (layer F.Fab)
|
||||||
|
(effects (font (size 1.27 1.27) (thickness 0.254)))
|
||||||
|
)
|
||||||
|
(fp_text value "LSM6DSV16BXTR" (at 0.000 -0) (layer F.SilkS) hide
|
||||||
|
(effects (font (size 1.27 1.27) (thickness 0.254)))
|
||||||
|
)
|
||||||
|
(fp_line (start -1.5 -1.25) (end 1.5 -1.25) (layer F.Fab) (width 0.1))
|
||||||
|
(fp_line (start 1.5 -1.25) (end 1.5 1.25) (layer F.Fab) (width 0.1))
|
||||||
|
(fp_line (start 1.5 1.25) (end -1.5 1.25) (layer F.Fab) (width 0.1))
|
||||||
|
(fp_line (start -1.5 1.25) (end -1.5 -1.25) (layer F.Fab) (width 0.1))
|
||||||
|
(fp_line (start -2.3 -2.05) (end 2.3 -2.05) (layer F.CrtYd) (width 0.1))
|
||||||
|
(fp_line (start 2.3 -2.05) (end 2.3 2.05) (layer F.CrtYd) (width 0.1))
|
||||||
|
(fp_line (start 2.3 2.05) (end -2.3 2.05) (layer F.CrtYd) (width 0.1))
|
||||||
|
(fp_line (start -2.3 2.05) (end -2.3 -2.05) (layer F.CrtYd) (width 0.1))
|
||||||
|
(fp_line (start -1.9 -0.75) (end -1.9 -0.75) (layer F.SilkS) (width 0.1))
|
||||||
|
(fp_line (start -2 -0.75) (end -2 -0.75) (layer F.SilkS) (width 0.1))
|
||||||
|
(fp_arc (start -1.95 -0.75) (end -1.900 -0.75) (angle -180) (layer F.SilkS) (width 0.1))
|
||||||
|
(fp_arc (start -1.95 -0.75) (end -2.000 -0.75) (angle -180) (layer F.SilkS) (width 0.1))
|
||||||
|
(pad 1 smd rect (at -1.150 -0.75 90) (size 0.300 0.600) (layers F.Cu F.Paste F.Mask))
|
||||||
|
(pad 2 smd rect (at -1.150 -0.25 90) (size 0.300 0.600) (layers F.Cu F.Paste F.Mask))
|
||||||
|
(pad 3 smd rect (at -1.150 0.25 90) (size 0.300 0.600) (layers F.Cu F.Paste F.Mask))
|
||||||
|
(pad 4 smd rect (at -1.150 0.75 90) (size 0.300 0.600) (layers F.Cu F.Paste F.Mask))
|
||||||
|
(pad 5 smd rect (at -0.500 0.9 0) (size 0.300 0.600) (layers F.Cu F.Paste F.Mask))
|
||||||
|
(pad 6 smd rect (at 0.000 0.9 0) (size 0.300 0.600) (layers F.Cu F.Paste F.Mask))
|
||||||
|
(pad 7 smd rect (at 0.500 0.9 0) (size 0.300 0.600) (layers F.Cu F.Paste F.Mask))
|
||||||
|
(pad 8 smd rect (at 1.150 0.75 90) (size 0.300 0.600) (layers F.Cu F.Paste F.Mask))
|
||||||
|
(pad 9 smd rect (at 1.150 0.25 90) (size 0.300 0.600) (layers F.Cu F.Paste F.Mask))
|
||||||
|
(pad 10 smd rect (at 1.150 -0.25 90) (size 0.300 0.600) (layers F.Cu F.Paste F.Mask))
|
||||||
|
(pad 11 smd rect (at 1.150 -0.75 90) (size 0.300 0.600) (layers F.Cu F.Paste F.Mask))
|
||||||
|
(pad 12 smd rect (at 0.500 -0.9 0) (size 0.300 0.600) (layers F.Cu F.Paste F.Mask))
|
||||||
|
(pad 13 smd rect (at 0.000 -0.9 0) (size 0.300 0.600) (layers F.Cu F.Paste F.Mask))
|
||||||
|
(pad 14 smd rect (at -0.500 -0.9 0) (size 0.300 0.600) (layers F.Cu F.Paste F.Mask))
|
||||||
|
(model LSM6DSV80XTR.stp
|
||||||
|
(at (xyz 0 0 0))
|
||||||
|
(scale (xyz 1 1 1))
|
||||||
|
(rotate (xyz 0 0 0))
|
||||||
|
)
|
||||||
|
)
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
EESchema-DOCLIB Version 2.0
|
||||||
|
#
|
||||||
|
$CMP LSM6DSV80XTR
|
||||||
|
D 6-axis IMU (inertial measurement unit) with high-g accelerometer, embedded AI, and sensor fusion for wearables and sport trackers
|
||||||
|
K
|
||||||
|
F https://www.st.com/resource/en/datasheet/lsm6dsv80x.pdf
|
||||||
|
$ENDCMP
|
||||||
|
#
|
||||||
|
#End Doc Library
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
(kicad_symbol_lib (version 20211014) (generator SamacSys_ECAD_Model)
|
||||||
|
(symbol "LSM6DSV80XTR" (in_bom yes) (on_board yes)
|
||||||
|
(property "Reference" "IC" (at 31.75 15.24 0)
|
||||||
|
(effects (font (size 1.27 1.27)) (justify left top))
|
||||||
|
)
|
||||||
|
(property "Value" "LSM6DSV80XTR" (at 31.75 12.7 0)
|
||||||
|
(effects (font (size 1.27 1.27)) (justify left top))
|
||||||
|
)
|
||||||
|
(property "Footprint" "LSM6DSV16BXTR" (at 31.75 -87.3 0)
|
||||||
|
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||||
|
)
|
||||||
|
(property "Datasheet" "https://www.st.com/resource/en/datasheet/lsm6dsv80x.pdf" (at 31.75 -187.3 0)
|
||||||
|
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||||
|
)
|
||||||
|
(property "ki_description" "6-axis IMU (inertial measurement unit) with high-g accelerometer, embedded AI, and sensor fusion for wearables and sport trackers" (at 31.75 -287.3 0)
|
||||||
|
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||||
|
)
|
||||||
|
(property "Height" "0.74" (at 31.75 -387.3 0)
|
||||||
|
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||||
|
)
|
||||||
|
(property "Manufacturer_Name" "STMicroelectronics" (at 31.75 -487.3 0)
|
||||||
|
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||||
|
)
|
||||||
|
(property "Manufacturer_Part_Number" "LSM6DSV80XTR" (at 31.75 -587.3 0)
|
||||||
|
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||||
|
)
|
||||||
|
(property "Mouser Part Number" "" (at 31.75 -687.3 0)
|
||||||
|
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||||
|
)
|
||||||
|
(property "Mouser Price/Stock" "" (at 31.75 -787.3 0)
|
||||||
|
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||||
|
)
|
||||||
|
(property "Arrow Part Number" "LSM6DSV80XTR" (at 31.75 -887.3 0)
|
||||||
|
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||||
|
)
|
||||||
|
(property "Arrow Price/Stock" "https://www.arrow.com/en/products/lsm6dsv80xtr/stmicroelectronics?utm_currency=USD®ion=nac" (at 31.75 -987.3 0)
|
||||||
|
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||||
|
)
|
||||||
|
(rectangle
|
||||||
|
(start 5.08 10.16)
|
||||||
|
(end 30.48 -20.32)
|
||||||
|
(stroke (width 0.254) (type default))
|
||||||
|
(fill (type background))
|
||||||
|
)
|
||||||
|
(pin passive line (at 0 0 0) (length 5.08)
|
||||||
|
(name "SDO/SA0" (effects (font (size 1.27 1.27))))
|
||||||
|
(number "1" (effects (font (size 1.27 1.27))))
|
||||||
|
)
|
||||||
|
(pin passive line (at 0 -2.54 0) (length 5.08)
|
||||||
|
(name "SDX" (effects (font (size 1.27 1.27))))
|
||||||
|
(number "2" (effects (font (size 1.27 1.27))))
|
||||||
|
)
|
||||||
|
(pin passive line (at 0 -5.08 0) (length 5.08)
|
||||||
|
(name "SCX" (effects (font (size 1.27 1.27))))
|
||||||
|
(number "3" (effects (font (size 1.27 1.27))))
|
||||||
|
)
|
||||||
|
(pin passive line (at 0 -7.62 0) (length 5.08)
|
||||||
|
(name "INT1" (effects (font (size 1.27 1.27))))
|
||||||
|
(number "4" (effects (font (size 1.27 1.27))))
|
||||||
|
)
|
||||||
|
(pin passive line (at 15.24 -25.4 90) (length 5.08)
|
||||||
|
(name "VDDIO" (effects (font (size 1.27 1.27))))
|
||||||
|
(number "5" (effects (font (size 1.27 1.27))))
|
||||||
|
)
|
||||||
|
(pin passive line (at 17.78 -25.4 90) (length 5.08)
|
||||||
|
(name "GND_1" (effects (font (size 1.27 1.27))))
|
||||||
|
(number "6" (effects (font (size 1.27 1.27))))
|
||||||
|
)
|
||||||
|
(pin passive line (at 20.32 -25.4 90) (length 5.08)
|
||||||
|
(name "GND_2" (effects (font (size 1.27 1.27))))
|
||||||
|
(number "7" (effects (font (size 1.27 1.27))))
|
||||||
|
)
|
||||||
|
(pin passive line (at 35.56 -7.62 180) (length 5.08)
|
||||||
|
(name "VDD" (effects (font (size 1.27 1.27))))
|
||||||
|
(number "8" (effects (font (size 1.27 1.27))))
|
||||||
|
)
|
||||||
|
(pin passive line (at 35.56 -5.08 180) (length 5.08)
|
||||||
|
(name "INT2" (effects (font (size 1.27 1.27))))
|
||||||
|
(number "9" (effects (font (size 1.27 1.27))))
|
||||||
|
)
|
||||||
|
(pin passive line (at 35.56 -2.54 180) (length 5.08)
|
||||||
|
(name "NC_1" (effects (font (size 1.27 1.27))))
|
||||||
|
(number "10" (effects (font (size 1.27 1.27))))
|
||||||
|
)
|
||||||
|
(pin passive line (at 35.56 0 180) (length 5.08)
|
||||||
|
(name "NC_2" (effects (font (size 1.27 1.27))))
|
||||||
|
(number "11" (effects (font (size 1.27 1.27))))
|
||||||
|
)
|
||||||
|
(pin passive line (at 20.32 15.24 270) (length 5.08)
|
||||||
|
(name "CS" (effects (font (size 1.27 1.27))))
|
||||||
|
(number "12" (effects (font (size 1.27 1.27))))
|
||||||
|
)
|
||||||
|
(pin passive line (at 17.78 15.24 270) (length 5.08)
|
||||||
|
(name "SCL" (effects (font (size 1.27 1.27))))
|
||||||
|
(number "13" (effects (font (size 1.27 1.27))))
|
||||||
|
)
|
||||||
|
(pin passive line (at 15.24 15.24 270) (length 5.08)
|
||||||
|
(name "SDA" (effects (font (size 1.27 1.27))))
|
||||||
|
(number "14" (effects (font (size 1.27 1.27))))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
EESchema-LIBRARY Version 2.3
|
||||||
|
#encoding utf-8
|
||||||
|
#SamacSys ECAD Model LSM6DSV80XTR
|
||||||
|
#/20519981/1957227/2.50/14/2/Integrated Circuit
|
||||||
|
DEF LSM6DSV80XTR IC 0 30 Y Y 1 F N
|
||||||
|
F0 "IC" 1250 600 50 H V L CNN
|
||||||
|
F1 "LSM6DSV80XTR" 1250 500 50 H V L CNN
|
||||||
|
F2 "LSM6DSV16BXTR" 1250 400 50 H I L CNN
|
||||||
|
F3 "https://www.st.com/resource/en/datasheet/lsm6dsv80x.pdf" 1250 300 50 H I L CNN
|
||||||
|
F4 "6-axis IMU (inertial measurement unit) with high-g accelerometer, embedded AI, and sensor fusion for wearables and sport trackers" 1250 200 50 H I L CNN "Description"
|
||||||
|
F5 "0.74" 1250 100 50 H I L CNN "Height"
|
||||||
|
F6 "STMicroelectronics" 1250 0 50 H I L CNN "Manufacturer_Name"
|
||||||
|
F7 "LSM6DSV80XTR" 1250 -100 50 H I L CNN "Manufacturer_Part_Number"
|
||||||
|
F8 "" 1250 -200 50 H I L CNN "Mouser Part Number"
|
||||||
|
F9 "" 1250 -300 50 H I L CNN "Mouser Price/Stock"
|
||||||
|
F10 "LSM6DSV80XTR" 1250 -400 50 H I L CNN "Arrow Part Number"
|
||||||
|
F11 "https://www.arrow.com/en/products/lsm6dsv80xtr/stmicroelectronics?utm_currency=USD®ion=nac" 1250 -500 50 H I L CNN "Arrow Price/Stock"
|
||||||
|
DRAW
|
||||||
|
X SDO/SA0 1 0 0 200 R 50 50 0 0 P
|
||||||
|
X SDX 2 0 -100 200 R 50 50 0 0 P
|
||||||
|
X SCX 3 0 -200 200 R 50 50 0 0 P
|
||||||
|
X INT1 4 0 -300 200 R 50 50 0 0 P
|
||||||
|
X VDDIO 5 600 -1000 200 U 50 50 0 0 P
|
||||||
|
X GND_1 6 700 -1000 200 U 50 50 0 0 P
|
||||||
|
X GND_2 7 800 -1000 200 U 50 50 0 0 P
|
||||||
|
X VDD 8 1400 -300 200 L 50 50 0 0 P
|
||||||
|
X INT2 9 1400 -200 200 L 50 50 0 0 P
|
||||||
|
X NC_1 10 1400 -100 200 L 50 50 0 0 P
|
||||||
|
X NC_2 11 1400 0 200 L 50 50 0 0 P
|
||||||
|
X CS 12 800 600 200 D 50 50 0 0 P
|
||||||
|
X SCL 13 700 600 200 D 50 50 0 0 P
|
||||||
|
X SDA 14 600 600 200 D 50 50 0 0 P
|
||||||
|
P 5 0 1 6 200 400 1200 400 1200 -800 200 -800 200 400 N
|
||||||
|
ENDDRAW
|
||||||
|
ENDDEF
|
||||||
|
#
|
||||||
|
#End Library
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
PCBNEW-LibModule-V1 2026-03-08 11:22:49
|
||||||
|
# encoding utf-8
|
||||||
|
Units mm
|
||||||
|
$INDEX
|
||||||
|
LSM6DSV16BXTR
|
||||||
|
$EndINDEX
|
||||||
|
$MODULE LSM6DSV16BXTR
|
||||||
|
Po 0 0 0 15 69ad5c09 00000000 ~~
|
||||||
|
Li LSM6DSV16BXTR
|
||||||
|
Cd LGA-14L 2.5 x 3.0 x 0.74 mm XDM)
|
||||||
|
Kw Integrated Circuit
|
||||||
|
Sc 0
|
||||||
|
At SMD
|
||||||
|
AR
|
||||||
|
Op 0 0 0
|
||||||
|
T0 0.000 -0 1.27 1.27 0 0.254 N V 21 N "IC**"
|
||||||
|
T1 0.000 -0 1.27 1.27 0 0.254 N I 21 N "LSM6DSV16BXTR"
|
||||||
|
DS -1.5 -1.25 1.5 -1.25 0.1 24
|
||||||
|
DS 1.5 -1.25 1.5 1.25 0.1 24
|
||||||
|
DS 1.5 1.25 -1.5 1.25 0.1 24
|
||||||
|
DS -1.5 1.25 -1.5 -1.25 0.1 24
|
||||||
|
DS -2.3 -2.05 2.3 -2.05 0.1 24
|
||||||
|
DS 2.3 -2.05 2.3 2.05 0.1 24
|
||||||
|
DS 2.3 2.05 -2.3 2.05 0.1 24
|
||||||
|
DS -2.3 2.05 -2.3 -2.05 0.1 24
|
||||||
|
DS -1.9 -0.75 -1.9 -0.75 0.1 21
|
||||||
|
DS -2 -0.75 -2 -0.75 0.1 21
|
||||||
|
DA -1.95 -0.75 -1.900 -0.75 -1800 0.1 21
|
||||||
|
DA -1.95 -0.75 -2.000 -0.75 -1800 0.1 21
|
||||||
|
$PAD
|
||||||
|
Po -1.150 -0.75
|
||||||
|
Sh "1" R 0.300 0.600 0 0 900
|
||||||
|
At SMD N 00888000
|
||||||
|
Ne 0 ""
|
||||||
|
$EndPAD
|
||||||
|
$PAD
|
||||||
|
Po -1.150 -0.25
|
||||||
|
Sh "2" R 0.300 0.600 0 0 900
|
||||||
|
At SMD N 00888000
|
||||||
|
Ne 0 ""
|
||||||
|
$EndPAD
|
||||||
|
$PAD
|
||||||
|
Po -1.150 0.25
|
||||||
|
Sh "3" R 0.300 0.600 0 0 900
|
||||||
|
At SMD N 00888000
|
||||||
|
Ne 0 ""
|
||||||
|
$EndPAD
|
||||||
|
$PAD
|
||||||
|
Po -1.150 0.75
|
||||||
|
Sh "4" R 0.300 0.600 0 0 900
|
||||||
|
At SMD N 00888000
|
||||||
|
Ne 0 ""
|
||||||
|
$EndPAD
|
||||||
|
$PAD
|
||||||
|
Po -0.500 0.9
|
||||||
|
Sh "5" R 0.300 0.600 0 0 0
|
||||||
|
At SMD N 00888000
|
||||||
|
Ne 0 ""
|
||||||
|
$EndPAD
|
||||||
|
$PAD
|
||||||
|
Po 0.000 0.9
|
||||||
|
Sh "6" R 0.300 0.600 0 0 0
|
||||||
|
At SMD N 00888000
|
||||||
|
Ne 0 ""
|
||||||
|
$EndPAD
|
||||||
|
$PAD
|
||||||
|
Po 0.500 0.9
|
||||||
|
Sh "7" R 0.300 0.600 0 0 0
|
||||||
|
At SMD N 00888000
|
||||||
|
Ne 0 ""
|
||||||
|
$EndPAD
|
||||||
|
$PAD
|
||||||
|
Po 1.150 0.75
|
||||||
|
Sh "8" R 0.300 0.600 0 0 900
|
||||||
|
At SMD N 00888000
|
||||||
|
Ne 0 ""
|
||||||
|
$EndPAD
|
||||||
|
$PAD
|
||||||
|
Po 1.150 0.25
|
||||||
|
Sh "9" R 0.300 0.600 0 0 900
|
||||||
|
At SMD N 00888000
|
||||||
|
Ne 0 ""
|
||||||
|
$EndPAD
|
||||||
|
$PAD
|
||||||
|
Po 1.150 -0.25
|
||||||
|
Sh "10" R 0.300 0.600 0 0 900
|
||||||
|
At SMD N 00888000
|
||||||
|
Ne 0 ""
|
||||||
|
$EndPAD
|
||||||
|
$PAD
|
||||||
|
Po 1.150 -0.75
|
||||||
|
Sh "11" R 0.300 0.600 0 0 900
|
||||||
|
At SMD N 00888000
|
||||||
|
Ne 0 ""
|
||||||
|
$EndPAD
|
||||||
|
$PAD
|
||||||
|
Po 0.500 -0.9
|
||||||
|
Sh "12" R 0.300 0.600 0 0 0
|
||||||
|
At SMD N 00888000
|
||||||
|
Ne 0 ""
|
||||||
|
$EndPAD
|
||||||
|
$PAD
|
||||||
|
Po 0.000 -0.9
|
||||||
|
Sh "13" R 0.300 0.600 0 0 0
|
||||||
|
At SMD N 00888000
|
||||||
|
Ne 0 ""
|
||||||
|
$EndPAD
|
||||||
|
$PAD
|
||||||
|
Po -0.500 -0.9
|
||||||
|
Sh "14" R 0.300 0.600 0 0 0
|
||||||
|
At SMD N 00888000
|
||||||
|
Ne 0 ""
|
||||||
|
$EndPAD
|
||||||
|
$EndMODULE LSM6DSV16BXTR
|
||||||
|
$EndLIBRARY
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
EESchema-DOCLIB Version 2.0
|
||||||
|
#
|
||||||
|
$CMP W3213
|
||||||
|
D Antennas GPS patch antenna 13x13 mm
|
||||||
|
K
|
||||||
|
F https://www.arrow.com/en/products/w3213/pulse-electronics-corporation
|
||||||
|
$ENDCMP
|
||||||
|
#
|
||||||
|
#End Doc Library
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
(module "W3213" (layer F.Cu)
|
||||||
|
(descr "W3213-1")
|
||||||
|
(tags "Antenna")
|
||||||
|
(fp_text reference ANT** (at 0.000 -0.7) (layer F.SilkS)
|
||||||
|
(effects (font (size 1.27 1.27) (thickness 0.254)))
|
||||||
|
)
|
||||||
|
(fp_text user %R (at 0.000 -0.7) (layer F.Fab)
|
||||||
|
(effects (font (size 1.27 1.27) (thickness 0.254)))
|
||||||
|
)
|
||||||
|
(fp_text value "W3213" (at 0.000 -0.7) (layer F.SilkS) hide
|
||||||
|
(effects (font (size 1.27 1.27) (thickness 0.254)))
|
||||||
|
)
|
||||||
|
(fp_line (start -6.5 -7.2) (end 6.5 -7.2) (layer F.Fab) (width 0.2))
|
||||||
|
(fp_line (start 6.5 -7.2) (end 6.5 5.8) (layer F.Fab) (width 0.2))
|
||||||
|
(fp_line (start 6.5 5.8) (end -6.5 5.8) (layer F.Fab) (width 0.2))
|
||||||
|
(fp_line (start -6.5 5.8) (end -6.5 -7.2) (layer F.Fab) (width 0.2))
|
||||||
|
(fp_line (start -6.5 -7.2) (end 6.5 -7.2) (layer F.SilkS) (width 0.1))
|
||||||
|
(fp_line (start 6.5 -7.2) (end 6.5 5.8) (layer F.SilkS) (width 0.1))
|
||||||
|
(fp_line (start 6.5 5.8) (end -6.5 5.8) (layer F.SilkS) (width 0.1))
|
||||||
|
(fp_line (start -6.5 5.8) (end -6.5 -7.2) (layer F.SilkS) (width 0.1))
|
||||||
|
(fp_line (start -7.5 -8.2) (end 7.5 -8.2) (layer F.CrtYd) (width 0.1))
|
||||||
|
(fp_line (start 7.5 -8.2) (end 7.5 6.8) (layer F.CrtYd) (width 0.1))
|
||||||
|
(fp_line (start 7.5 6.8) (end -7.5 6.8) (layer F.CrtYd) (width 0.1))
|
||||||
|
(fp_line (start -7.5 6.8) (end -7.5 -8.2) (layer F.CrtYd) (width 0.1))
|
||||||
|
(pad 1 thru_hole circle (at 0.000 -0) (size 1.575 1.575) (drill 1.05) (layers *.Cu *.Mask))
|
||||||
|
(model W3213.stp
|
||||||
|
(at (xyz 0 0 0))
|
||||||
|
(scale (xyz 1 1 1))
|
||||||
|
(rotate (xyz 0 0 0))
|
||||||
|
)
|
||||||
|
)
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
(kicad_symbol_lib (version 20211014) (generator SamacSys_ECAD_Model)
|
||||||
|
(symbol "W3213" (in_bom yes) (on_board yes)
|
||||||
|
(property "Reference" "ANT" (at 16.51 7.62 0)
|
||||||
|
(effects (font (size 1.27 1.27)) (justify left top))
|
||||||
|
)
|
||||||
|
(property "Value" "W3213" (at 16.51 5.08 0)
|
||||||
|
(effects (font (size 1.27 1.27)) (justify left top))
|
||||||
|
)
|
||||||
|
(property "Footprint" "W3213" (at 16.51 -94.92 0)
|
||||||
|
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||||
|
)
|
||||||
|
(property "Datasheet" "https://www.arrow.com/en/products/w3213/pulse-electronics-corporation" (at 16.51 -194.92 0)
|
||||||
|
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||||
|
)
|
||||||
|
(property "ki_description" "Antennas GPS patch antenna 13x13 mm" (at 16.51 -294.92 0)
|
||||||
|
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||||
|
)
|
||||||
|
(property "Height" "4.83" (at 16.51 -394.92 0)
|
||||||
|
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||||
|
)
|
||||||
|
(property "Manufacturer_Name" "Yageo Group" (at 16.51 -494.92 0)
|
||||||
|
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||||
|
)
|
||||||
|
(property "Manufacturer_Part_Number" "W3213" (at 16.51 -594.92 0)
|
||||||
|
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||||
|
)
|
||||||
|
(property "Mouser Part Number" "673-W3213" (at 16.51 -694.92 0)
|
||||||
|
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||||
|
)
|
||||||
|
(property "Mouser Price/Stock" "https://www.mouser.co.uk/ProductDetail/Pulse-Electronics/W3213?qs=tNd8r9m29y7JOJNw2gSjhQ%3D%3D" (at 16.51 -794.92 0)
|
||||||
|
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||||
|
)
|
||||||
|
(property "Arrow Part Number" "W3213" (at 16.51 -894.92 0)
|
||||||
|
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||||
|
)
|
||||||
|
(property "Arrow Price/Stock" "https://www.arrow.com/en/products/w3213/pulse-electronics-corporation" (at 16.51 -994.92 0)
|
||||||
|
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||||
|
)
|
||||||
|
(rectangle
|
||||||
|
(start 5.08 2.54)
|
||||||
|
(end 15.24 -2.54)
|
||||||
|
(stroke (width 0.254) (type default))
|
||||||
|
(fill (type background))
|
||||||
|
)
|
||||||
|
(pin passive line (at 0 0 0) (length 5.08)
|
||||||
|
(name "1" (effects (font (size 1.27 1.27))))
|
||||||
|
(number "1" (effects (font (size 1.27 1.27))))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
EESchema-LIBRARY Version 2.3
|
||||||
|
#encoding utf-8
|
||||||
|
#SamacSys ECAD Model W3213
|
||||||
|
#/1938663/1957227/2.50/1/0/Antenna
|
||||||
|
DEF W3213 ANT 0 30 Y Y 1 F N
|
||||||
|
F0 "ANT" 650 300 50 H V L CNN
|
||||||
|
F1 "W3213" 650 200 50 H V L CNN
|
||||||
|
F2 "W3213" 650 100 50 H I L CNN
|
||||||
|
F3 "https://www.arrow.com/en/products/w3213/pulse-electronics-corporation" 650 0 50 H I L CNN
|
||||||
|
F4 "Antennas GPS patch antenna 13x13 mm" 650 -100 50 H I L CNN "Description"
|
||||||
|
F5 "4.83" 650 -200 50 H I L CNN "Height"
|
||||||
|
F6 "Yageo Group" 650 -300 50 H I L CNN "Manufacturer_Name"
|
||||||
|
F7 "W3213" 650 -400 50 H I L CNN "Manufacturer_Part_Number"
|
||||||
|
F8 "673-W3213" 650 -500 50 H I L CNN "Mouser Part Number"
|
||||||
|
F9 "https://www.mouser.co.uk/ProductDetail/Pulse-Electronics/W3213?qs=tNd8r9m29y7JOJNw2gSjhQ%3D%3D" 650 -600 50 H I L CNN "Mouser Price/Stock"
|
||||||
|
F10 "W3213" 650 -700 50 H I L CNN "Arrow Part Number"
|
||||||
|
F11 "https://www.arrow.com/en/products/w3213/pulse-electronics-corporation" 650 -800 50 H I L CNN "Arrow Price/Stock"
|
||||||
|
DRAW
|
||||||
|
X 1 1 0 0 200 R 50 50 0 0 P
|
||||||
|
P 5 0 1 6 200 100 600 100 600 -100 200 -100 200 100 N
|
||||||
|
ENDDRAW
|
||||||
|
ENDDEF
|
||||||
|
#
|
||||||
|
#End Library
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
PCBNEW-LibModule-V1 2026-03-07 19:18:26
|
||||||
|
# encoding utf-8
|
||||||
|
Units mm
|
||||||
|
$INDEX
|
||||||
|
W3213
|
||||||
|
$EndINDEX
|
||||||
|
$MODULE W3213
|
||||||
|
Po 0 0 0 15 69ac7a02 00000000 ~~
|
||||||
|
Li W3213
|
||||||
|
Cd W3213-1
|
||||||
|
Kw Antenna
|
||||||
|
Sc 0
|
||||||
|
At STD
|
||||||
|
AR
|
||||||
|
Op 0 0 0
|
||||||
|
T0 0.000 -0.7 1.27 1.27 0 0.254 N V 21 N "ANT**"
|
||||||
|
T1 0.000 -0.7 1.27 1.27 0 0.254 N I 21 N "W3213"
|
||||||
|
DS -6.5 -7.2 6.5 -7.2 0.2 24
|
||||||
|
DS 6.5 -7.2 6.5 5.8 0.2 24
|
||||||
|
DS 6.5 5.8 -6.5 5.8 0.2 24
|
||||||
|
DS -6.5 5.8 -6.5 -7.2 0.2 24
|
||||||
|
DS -6.5 -7.2 6.5 -7.2 0.1 21
|
||||||
|
DS 6.5 -7.2 6.5 5.8 0.1 21
|
||||||
|
DS 6.5 5.8 -6.5 5.8 0.1 21
|
||||||
|
DS -6.5 5.8 -6.5 -7.2 0.1 21
|
||||||
|
DS -7.5 -8.2 7.5 -8.2 0.1 24
|
||||||
|
DS 7.5 -8.2 7.5 6.8 0.1 24
|
||||||
|
DS 7.5 6.8 -7.5 6.8 0.1 24
|
||||||
|
DS -7.5 6.8 -7.5 -8.2 0.1 24
|
||||||
|
$PAD
|
||||||
|
Po 0.000 -0
|
||||||
|
Sh "1" C 1.575 1.575 0 0 900
|
||||||
|
Dr 1.05 0 0
|
||||||
|
At STD N 00E0FFFF
|
||||||
|
Ne 0 ""
|
||||||
|
$EndPAD
|
||||||
|
$EndMODULE W3213
|
||||||
|
$EndLIBRARY
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
[x] double-check resistors and capacitors - can I do with fewer unique values?
|
||||||
|
[x] double check sd card keep-out areas with datasheet
|
||||||
|
[x] swap sda/scl i2c test points
|
||||||
|
[x] Do we even need any pullup/down resistors?? can't the esp32 do this internally? (update: it can)
|
||||||
|
[x] i2c Pins need reassigned so they match what is on the dev boards, which use 17/18 for the OLED display. Or we somehow also squeeze an OLED into the dev boards. Or maybe replace the OLED entirely with just some LEDs to indicate sensor state?
|
||||||
|
|
||||||
|
R2:
|
||||||
|
[ ] some kind of power consumption measuring chip? INA233?
|
||||||
|
[ ] protection diodes on usb lines?
|
||||||
|
[ ] pull-down resistor before LED strip?
|
||||||
|
[ ] CS line on SD card
|
||||||
|
|
||||||
|
R3:
|
||||||
|
[ ] MP2662 lipoly charger chip on a 3.7v battery at 2500mAh
|
||||||
|
[ ] redesign power supply to use a buck booster from the 3.7v battery to 5v for the display, and a simple divider directly off the battery for 3.3v for system power
|
||||||
|
[ ] soldering ports to accept 3W at 6VAC to charge the battery from the bike dynamo
|
||||||
+13060
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,694 @@
|
|||||||
|
{
|
||||||
|
"board": {
|
||||||
|
"3dviewports": [],
|
||||||
|
"design_settings": {
|
||||||
|
"defaults": {
|
||||||
|
"apply_defaults_to_fp_fields": false,
|
||||||
|
"apply_defaults_to_fp_shapes": false,
|
||||||
|
"apply_defaults_to_fp_text": false,
|
||||||
|
"board_outline_line_width": 0.05,
|
||||||
|
"copper_line_width": 0.2,
|
||||||
|
"copper_text_italic": false,
|
||||||
|
"copper_text_size_h": 1.5,
|
||||||
|
"copper_text_size_v": 1.5,
|
||||||
|
"copper_text_thickness": 0.3,
|
||||||
|
"copper_text_upright": false,
|
||||||
|
"courtyard_line_width": 0.05,
|
||||||
|
"dimension_precision": 4,
|
||||||
|
"dimension_units": 3,
|
||||||
|
"dimensions": {
|
||||||
|
"arrow_length": 1270000,
|
||||||
|
"extension_offset": 500000,
|
||||||
|
"keep_text_aligned": true,
|
||||||
|
"suppress_zeroes": true,
|
||||||
|
"text_position": 0,
|
||||||
|
"units_format": 0
|
||||||
|
},
|
||||||
|
"fab_line_width": 0.1,
|
||||||
|
"fab_text_italic": false,
|
||||||
|
"fab_text_size_h": 1.0,
|
||||||
|
"fab_text_size_v": 1.0,
|
||||||
|
"fab_text_thickness": 0.15,
|
||||||
|
"fab_text_upright": false,
|
||||||
|
"other_line_width": 0.1,
|
||||||
|
"other_text_italic": false,
|
||||||
|
"other_text_size_h": 1.0,
|
||||||
|
"other_text_size_v": 1.0,
|
||||||
|
"other_text_thickness": 0.15,
|
||||||
|
"other_text_upright": false,
|
||||||
|
"pads": {
|
||||||
|
"drill": 0.3,
|
||||||
|
"height": 0.6,
|
||||||
|
"width": 0.6
|
||||||
|
},
|
||||||
|
"silk_line_width": 0.1,
|
||||||
|
"silk_text_italic": false,
|
||||||
|
"silk_text_size_h": 1.0,
|
||||||
|
"silk_text_size_v": 1.0,
|
||||||
|
"silk_text_thickness": 0.1,
|
||||||
|
"silk_text_upright": false,
|
||||||
|
"zones": {
|
||||||
|
"min_clearance": 0.15
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"diff_pair_dimensions": [
|
||||||
|
{
|
||||||
|
"gap": 0.0,
|
||||||
|
"via_gap": 0.0,
|
||||||
|
"width": 0.0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"drc_exclusions": [],
|
||||||
|
"meta": {
|
||||||
|
"version": 2
|
||||||
|
},
|
||||||
|
"rule_severities": {
|
||||||
|
"annular_width": "error",
|
||||||
|
"clearance": "error",
|
||||||
|
"connection_width": "warning",
|
||||||
|
"copper_edge_clearance": "error",
|
||||||
|
"copper_sliver": "warning",
|
||||||
|
"courtyards_overlap": "error",
|
||||||
|
"creepage": "error",
|
||||||
|
"diff_pair_gap_out_of_range": "error",
|
||||||
|
"diff_pair_uncoupled_length_too_long": "error",
|
||||||
|
"drill_out_of_range": "error",
|
||||||
|
"duplicate_footprints": "warning",
|
||||||
|
"extra_footprint": "warning",
|
||||||
|
"footprint": "error",
|
||||||
|
"footprint_filters_mismatch": "ignore",
|
||||||
|
"footprint_symbol_mismatch": "warning",
|
||||||
|
"footprint_type_mismatch": "ignore",
|
||||||
|
"hole_clearance": "error",
|
||||||
|
"hole_to_hole": "warning",
|
||||||
|
"holes_co_located": "warning",
|
||||||
|
"invalid_outline": "error",
|
||||||
|
"isolated_copper": "warning",
|
||||||
|
"item_on_disabled_layer": "error",
|
||||||
|
"items_not_allowed": "error",
|
||||||
|
"length_out_of_range": "error",
|
||||||
|
"lib_footprint_issues": "warning",
|
||||||
|
"lib_footprint_mismatch": "warning",
|
||||||
|
"malformed_courtyard": "error",
|
||||||
|
"microvia_drill_out_of_range": "error",
|
||||||
|
"mirrored_text_on_front_layer": "warning",
|
||||||
|
"missing_courtyard": "ignore",
|
||||||
|
"missing_footprint": "warning",
|
||||||
|
"net_conflict": "warning",
|
||||||
|
"nonmirrored_text_on_back_layer": "warning",
|
||||||
|
"npth_inside_courtyard": "ignore",
|
||||||
|
"padstack": "warning",
|
||||||
|
"pth_inside_courtyard": "ignore",
|
||||||
|
"shorting_items": "error",
|
||||||
|
"silk_edge_clearance": "warning",
|
||||||
|
"silk_over_copper": "warning",
|
||||||
|
"silk_overlap": "warning",
|
||||||
|
"skew_out_of_range": "error",
|
||||||
|
"solder_mask_bridge": "error",
|
||||||
|
"starved_thermal": "error",
|
||||||
|
"text_height": "warning",
|
||||||
|
"text_on_edge_cuts": "error",
|
||||||
|
"text_thickness": "warning",
|
||||||
|
"through_hole_pad_without_hole": "error",
|
||||||
|
"too_many_vias": "error",
|
||||||
|
"track_angle": "error",
|
||||||
|
"track_dangling": "warning",
|
||||||
|
"track_segment_length": "error",
|
||||||
|
"track_width": "error",
|
||||||
|
"tracks_crossing": "error",
|
||||||
|
"unconnected_items": "error",
|
||||||
|
"unresolved_variable": "error",
|
||||||
|
"via_dangling": "warning",
|
||||||
|
"zones_intersect": "error"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"max_error": 0.005,
|
||||||
|
"min_clearance": 0.0,
|
||||||
|
"min_connection": 0.0,
|
||||||
|
"min_copper_edge_clearance": 0.5,
|
||||||
|
"min_groove_width": 0.0,
|
||||||
|
"min_hole_clearance": 0.25,
|
||||||
|
"min_hole_to_hole": 0.25,
|
||||||
|
"min_microvia_diameter": 0.2,
|
||||||
|
"min_microvia_drill": 0.1,
|
||||||
|
"min_resolved_spokes": 2,
|
||||||
|
"min_silk_clearance": 0.0,
|
||||||
|
"min_text_height": 0.8,
|
||||||
|
"min_text_thickness": 0.08,
|
||||||
|
"min_through_hole_diameter": 0.3,
|
||||||
|
"min_track_width": 0.0,
|
||||||
|
"min_via_annular_width": 0.1,
|
||||||
|
"min_via_diameter": 0.5,
|
||||||
|
"solder_mask_to_copper_clearance": 0.0,
|
||||||
|
"use_height_for_length_calcs": true
|
||||||
|
},
|
||||||
|
"teardrop_options": [
|
||||||
|
{
|
||||||
|
"td_onpthpad": true,
|
||||||
|
"td_onroundshapesonly": false,
|
||||||
|
"td_onsmdpad": true,
|
||||||
|
"td_ontrackend": false,
|
||||||
|
"td_onvia": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"teardrop_parameters": [
|
||||||
|
{
|
||||||
|
"td_allow_use_two_tracks": true,
|
||||||
|
"td_curve_segcount": 0,
|
||||||
|
"td_height_ratio": 1.0,
|
||||||
|
"td_length_ratio": 0.5,
|
||||||
|
"td_maxheight": 2.0,
|
||||||
|
"td_maxlen": 1.0,
|
||||||
|
"td_on_pad_in_zone": false,
|
||||||
|
"td_target_name": "td_round_shape",
|
||||||
|
"td_width_to_size_filter_ratio": 0.9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"td_allow_use_two_tracks": true,
|
||||||
|
"td_curve_segcount": 0,
|
||||||
|
"td_height_ratio": 1.0,
|
||||||
|
"td_length_ratio": 0.5,
|
||||||
|
"td_maxheight": 2.0,
|
||||||
|
"td_maxlen": 1.0,
|
||||||
|
"td_on_pad_in_zone": false,
|
||||||
|
"td_target_name": "td_rect_shape",
|
||||||
|
"td_width_to_size_filter_ratio": 0.9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"td_allow_use_two_tracks": true,
|
||||||
|
"td_curve_segcount": 0,
|
||||||
|
"td_height_ratio": 1.0,
|
||||||
|
"td_length_ratio": 0.5,
|
||||||
|
"td_maxheight": 2.0,
|
||||||
|
"td_maxlen": 1.0,
|
||||||
|
"td_on_pad_in_zone": false,
|
||||||
|
"td_target_name": "td_track_end",
|
||||||
|
"td_width_to_size_filter_ratio": 0.9
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"track_widths": [
|
||||||
|
0.0
|
||||||
|
],
|
||||||
|
"tuning_pattern_settings": {
|
||||||
|
"diff_pair_defaults": {
|
||||||
|
"corner_radius_percentage": 80,
|
||||||
|
"corner_style": 1,
|
||||||
|
"max_amplitude": 1.0,
|
||||||
|
"min_amplitude": 0.2,
|
||||||
|
"single_sided": false,
|
||||||
|
"spacing": 1.0
|
||||||
|
},
|
||||||
|
"diff_pair_skew_defaults": {
|
||||||
|
"corner_radius_percentage": 80,
|
||||||
|
"corner_style": 1,
|
||||||
|
"max_amplitude": 1.0,
|
||||||
|
"min_amplitude": 0.2,
|
||||||
|
"single_sided": false,
|
||||||
|
"spacing": 0.6
|
||||||
|
},
|
||||||
|
"single_track_defaults": {
|
||||||
|
"corner_radius_percentage": 80,
|
||||||
|
"corner_style": 1,
|
||||||
|
"max_amplitude": 1.0,
|
||||||
|
"min_amplitude": 0.2,
|
||||||
|
"single_sided": false,
|
||||||
|
"spacing": 0.6
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"via_dimensions": [
|
||||||
|
{
|
||||||
|
"diameter": 0.0,
|
||||||
|
"drill": 0.0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"zones_allow_external_fillets": false
|
||||||
|
},
|
||||||
|
"ipc2581": {
|
||||||
|
"dist": "",
|
||||||
|
"distpn": "",
|
||||||
|
"internal_id": "",
|
||||||
|
"mfg": "",
|
||||||
|
"mpn": ""
|
||||||
|
},
|
||||||
|
"layer_pairs": [],
|
||||||
|
"layer_presets": [],
|
||||||
|
"viewports": []
|
||||||
|
},
|
||||||
|
"boards": [],
|
||||||
|
"cvpcb": {
|
||||||
|
"equivalence_files": []
|
||||||
|
},
|
||||||
|
"erc": {
|
||||||
|
"erc_exclusions": [],
|
||||||
|
"meta": {
|
||||||
|
"version": 0
|
||||||
|
},
|
||||||
|
"pin_map": [
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
2
|
||||||
|
],
|
||||||
|
[
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
2
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"rule_severities": {
|
||||||
|
"bus_definition_conflict": "error",
|
||||||
|
"bus_entry_needed": "error",
|
||||||
|
"bus_to_bus_conflict": "error",
|
||||||
|
"bus_to_net_conflict": "error",
|
||||||
|
"different_unit_footprint": "error",
|
||||||
|
"different_unit_net": "error",
|
||||||
|
"duplicate_reference": "error",
|
||||||
|
"duplicate_sheet_names": "error",
|
||||||
|
"endpoint_off_grid": "warning",
|
||||||
|
"extra_units": "error",
|
||||||
|
"footprint_filter": "ignore",
|
||||||
|
"footprint_link_issues": "warning",
|
||||||
|
"four_way_junction": "ignore",
|
||||||
|
"global_label_dangling": "warning",
|
||||||
|
"hier_label_mismatch": "error",
|
||||||
|
"label_dangling": "error",
|
||||||
|
"label_multiple_wires": "warning",
|
||||||
|
"lib_symbol_issues": "warning",
|
||||||
|
"lib_symbol_mismatch": "warning",
|
||||||
|
"missing_bidi_pin": "warning",
|
||||||
|
"missing_input_pin": "warning",
|
||||||
|
"missing_power_pin": "error",
|
||||||
|
"missing_unit": "warning",
|
||||||
|
"multiple_net_names": "warning",
|
||||||
|
"net_not_bus_member": "warning",
|
||||||
|
"no_connect_connected": "warning",
|
||||||
|
"no_connect_dangling": "warning",
|
||||||
|
"pin_not_connected": "error",
|
||||||
|
"pin_not_driven": "error",
|
||||||
|
"pin_to_pin": "warning",
|
||||||
|
"power_pin_not_driven": "error",
|
||||||
|
"same_local_global_label": "warning",
|
||||||
|
"similar_label_and_power": "warning",
|
||||||
|
"similar_labels": "warning",
|
||||||
|
"similar_power": "warning",
|
||||||
|
"simulation_model_issue": "ignore",
|
||||||
|
"single_global_label": "ignore",
|
||||||
|
"unannotated": "error",
|
||||||
|
"unconnected_wire_endpoint": "warning",
|
||||||
|
"undefined_netclass": "error",
|
||||||
|
"unit_value_mismatch": "error",
|
||||||
|
"unresolved_variable": "error",
|
||||||
|
"wire_dangling": "error"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"libraries": {
|
||||||
|
"pinned_footprint_libs": [],
|
||||||
|
"pinned_symbol_libs": []
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"filename": "board.kicad_pro",
|
||||||
|
"version": 3
|
||||||
|
},
|
||||||
|
"net_settings": {
|
||||||
|
"classes": [
|
||||||
|
{
|
||||||
|
"bus_width": 12,
|
||||||
|
"clearance": 0.2,
|
||||||
|
"diff_pair_gap": 0.25,
|
||||||
|
"diff_pair_via_gap": 0.25,
|
||||||
|
"diff_pair_width": 0.2,
|
||||||
|
"line_style": 0,
|
||||||
|
"microvia_diameter": 0.3,
|
||||||
|
"microvia_drill": 0.1,
|
||||||
|
"name": "Default",
|
||||||
|
"pcb_color": "rgba(0, 0, 0, 0.000)",
|
||||||
|
"priority": 2147483647,
|
||||||
|
"schematic_color": "rgba(0, 0, 0, 0.000)",
|
||||||
|
"track_width": 0.2,
|
||||||
|
"via_diameter": 0.6,
|
||||||
|
"via_drill": 0.3,
|
||||||
|
"wire_width": 6
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"meta": {
|
||||||
|
"version": 4
|
||||||
|
},
|
||||||
|
"net_colors": null,
|
||||||
|
"netclass_assignments": null,
|
||||||
|
"netclass_patterns": []
|
||||||
|
},
|
||||||
|
"pcbnew": {
|
||||||
|
"last_paths": {
|
||||||
|
"gencad": "",
|
||||||
|
"idf": "",
|
||||||
|
"netlist": "",
|
||||||
|
"plot": "/tmp/",
|
||||||
|
"pos_files": "",
|
||||||
|
"specctra_dsn": "",
|
||||||
|
"step": "board.stl",
|
||||||
|
"svg": "",
|
||||||
|
"vrml": ""
|
||||||
|
},
|
||||||
|
"page_layout_descr_file": ""
|
||||||
|
},
|
||||||
|
"schematic": {
|
||||||
|
"annotate_start_num": 0,
|
||||||
|
"bom_export_filename": "${PROJECTNAME}.csv",
|
||||||
|
"bom_fmt_presets": [],
|
||||||
|
"bom_fmt_settings": {
|
||||||
|
"field_delimiter": ",",
|
||||||
|
"keep_line_breaks": false,
|
||||||
|
"keep_tabs": false,
|
||||||
|
"name": "CSV",
|
||||||
|
"ref_delimiter": ",",
|
||||||
|
"ref_range_delimiter": "",
|
||||||
|
"string_delimiter": "\""
|
||||||
|
},
|
||||||
|
"bom_presets": [],
|
||||||
|
"bom_settings": {
|
||||||
|
"exclude_dnp": false,
|
||||||
|
"fields_ordered": [
|
||||||
|
{
|
||||||
|
"group_by": false,
|
||||||
|
"label": "Reference",
|
||||||
|
"name": "Reference",
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group_by": false,
|
||||||
|
"label": "Qty",
|
||||||
|
"name": "${QUANTITY}",
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group_by": true,
|
||||||
|
"label": "Value",
|
||||||
|
"name": "Value",
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group_by": true,
|
||||||
|
"label": "DNP",
|
||||||
|
"name": "${DNP}",
|
||||||
|
"show": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group_by": true,
|
||||||
|
"label": "Exclude from BOM",
|
||||||
|
"name": "${EXCLUDE_FROM_BOM}",
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group_by": true,
|
||||||
|
"label": "Exclude from Board",
|
||||||
|
"name": "${EXCLUDE_FROM_BOARD}",
|
||||||
|
"show": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group_by": true,
|
||||||
|
"label": "Footprint",
|
||||||
|
"name": "Footprint",
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group_by": false,
|
||||||
|
"label": "Datasheet",
|
||||||
|
"name": "Datasheet",
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group_by": false,
|
||||||
|
"label": "Arrow Price/Stock",
|
||||||
|
"name": "Arrow Price/Stock",
|
||||||
|
"show": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group_by": false,
|
||||||
|
"label": "Height",
|
||||||
|
"name": "Height",
|
||||||
|
"show": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group_by": false,
|
||||||
|
"label": "Manufacturer_Name",
|
||||||
|
"name": "Manufacturer_Name",
|
||||||
|
"show": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group_by": false,
|
||||||
|
"label": "Manufacturer_Part_Number",
|
||||||
|
"name": "Manufacturer_Part_Number",
|
||||||
|
"show": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group_by": false,
|
||||||
|
"label": "Mouser Part Number",
|
||||||
|
"name": "Mouser Part Number",
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group_by": false,
|
||||||
|
"label": "Mouser Price/Stock",
|
||||||
|
"name": "Mouser Price/Stock",
|
||||||
|
"show": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group_by": false,
|
||||||
|
"label": "Arrow Part Number",
|
||||||
|
"name": "Arrow Part Number",
|
||||||
|
"show": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group_by": false,
|
||||||
|
"label": "Description",
|
||||||
|
"name": "Description",
|
||||||
|
"show": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"group_by": false,
|
||||||
|
"label": "#",
|
||||||
|
"name": "${ITEM_NUMBER}",
|
||||||
|
"show": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"filter_string": "",
|
||||||
|
"group_symbols": true,
|
||||||
|
"include_excluded_from_bom": true,
|
||||||
|
"name": "",
|
||||||
|
"sort_asc": true,
|
||||||
|
"sort_field": "Reference"
|
||||||
|
},
|
||||||
|
"connection_grid_size": 50.0,
|
||||||
|
"drawing": {
|
||||||
|
"dashed_lines_dash_length_ratio": 12.0,
|
||||||
|
"dashed_lines_gap_length_ratio": 3.0,
|
||||||
|
"default_line_thickness": 6.0,
|
||||||
|
"default_text_size": 50.0,
|
||||||
|
"field_names": [],
|
||||||
|
"intersheets_ref_own_page": false,
|
||||||
|
"intersheets_ref_prefix": "",
|
||||||
|
"intersheets_ref_short": false,
|
||||||
|
"intersheets_ref_show": false,
|
||||||
|
"intersheets_ref_suffix": "",
|
||||||
|
"junction_size_choice": 3,
|
||||||
|
"label_size_ratio": 0.375,
|
||||||
|
"operating_point_overlay_i_precision": 3,
|
||||||
|
"operating_point_overlay_i_range": "~A",
|
||||||
|
"operating_point_overlay_v_precision": 3,
|
||||||
|
"operating_point_overlay_v_range": "~V",
|
||||||
|
"overbar_offset_ratio": 1.23,
|
||||||
|
"pin_symbol_size": 25.0,
|
||||||
|
"text_offset_ratio": 0.15
|
||||||
|
},
|
||||||
|
"legacy_lib_dir": "",
|
||||||
|
"legacy_lib_list": [],
|
||||||
|
"meta": {
|
||||||
|
"version": 1
|
||||||
|
},
|
||||||
|
"net_format_name": "",
|
||||||
|
"ngspice": {
|
||||||
|
"fix_include_paths": true,
|
||||||
|
"meta": {
|
||||||
|
"version": 0
|
||||||
|
},
|
||||||
|
"model_mode": 4,
|
||||||
|
"workbook_filename": ""
|
||||||
|
},
|
||||||
|
"page_layout_descr_file": "",
|
||||||
|
"plot_directory": "",
|
||||||
|
"space_save_all_events": true,
|
||||||
|
"spice_current_sheet_as_root": false,
|
||||||
|
"spice_external_command": "spice \"%I\"",
|
||||||
|
"spice_model_current_sheet_as_root": true,
|
||||||
|
"spice_save_all_currents": false,
|
||||||
|
"spice_save_all_dissipations": false,
|
||||||
|
"spice_save_all_voltages": false,
|
||||||
|
"subpart_first_id": 65,
|
||||||
|
"subpart_id_separator": 0
|
||||||
|
},
|
||||||
|
"sheets": [
|
||||||
|
[
|
||||||
|
"51d2b720-4c26-4e02-a385-8df43b74d31d",
|
||||||
|
"Root"
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"text_variables": {}
|
||||||
|
}
|
||||||
+12093
File diff suppressed because it is too large
Load Diff
+4709
File diff suppressed because one or more lines are too long
@@ -0,0 +1,720 @@
|
|||||||
|
(kicad_symbol_lib
|
||||||
|
(version 20241209)
|
||||||
|
(generator "kicad_symbol_editor")
|
||||||
|
(generator_version "9.0")
|
||||||
|
(symbol "HUSB238"
|
||||||
|
(exclude_from_sim no)
|
||||||
|
(in_bom yes)
|
||||||
|
(on_board yes)
|
||||||
|
(property "Reference" "U"
|
||||||
|
(at 2.54 -6.35 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "Value" ""
|
||||||
|
(at 0 0 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "Footprint" ""
|
||||||
|
(at 0 0 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
(hide yes)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "Datasheet" ""
|
||||||
|
(at 0 0 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
(hide yes)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "Description" ""
|
||||||
|
(at 0 0 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
(hide yes)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(symbol "HUSB238_0_1"
|
||||||
|
(rectangle
|
||||||
|
(start -5.08 2.54)
|
||||||
|
(end 10.16 -15.24)
|
||||||
|
(stroke
|
||||||
|
(width 0)
|
||||||
|
(type default)
|
||||||
|
)
|
||||||
|
(fill
|
||||||
|
(type none)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(symbol "HUSB238_1_1"
|
||||||
|
(pin input line
|
||||||
|
(at -7.62 0 0)
|
||||||
|
(length 2.54)
|
||||||
|
(name "VIN"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number ""
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin input line
|
||||||
|
(at -7.62 -5.08 0)
|
||||||
|
(length 2.54)
|
||||||
|
(name "D+"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number ""
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin input line
|
||||||
|
(at -7.62 -7.62 0)
|
||||||
|
(length 2.54)
|
||||||
|
(name "D-"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number ""
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin input line
|
||||||
|
(at -7.62 -11.43 0)
|
||||||
|
(length 2.54)
|
||||||
|
(name "CC1"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number ""
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin input line
|
||||||
|
(at -7.62 -13.97 0)
|
||||||
|
(length 2.54)
|
||||||
|
(name "CC2"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number ""
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin input line
|
||||||
|
(at 2.54 -17.78 90)
|
||||||
|
(length 2.54)
|
||||||
|
(name "GND"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number ""
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin input line
|
||||||
|
(at 12.7 0 180)
|
||||||
|
(length 2.54)
|
||||||
|
(name "GATE"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number ""
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin input line
|
||||||
|
(at 12.7 -5.08 180)
|
||||||
|
(length 2.54)
|
||||||
|
(name "SCL"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number ""
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin input line
|
||||||
|
(at 12.7 -7.62 180)
|
||||||
|
(length 2.54)
|
||||||
|
(name "SDA"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number ""
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin input line
|
||||||
|
(at 12.7 -11.43 180)
|
||||||
|
(length 2.54)
|
||||||
|
(name "VSET"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number ""
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin input line
|
||||||
|
(at 12.7 -13.97 180)
|
||||||
|
(length 2.54)
|
||||||
|
(name "ISET"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number ""
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(embedded_fonts no)
|
||||||
|
)
|
||||||
|
(symbol "LED_STRIP"
|
||||||
|
(exclude_from_sim no)
|
||||||
|
(in_bom yes)
|
||||||
|
(on_board yes)
|
||||||
|
(property "Reference" "SOCKET"
|
||||||
|
(at -10.414 -10.414 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
(hide yes)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "Value" ""
|
||||||
|
(at 0 0 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "Footprint" ""
|
||||||
|
(at 0 0 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
(hide yes)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "Datasheet" ""
|
||||||
|
(at 0 0 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
(hide yes)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "Description" ""
|
||||||
|
(at 0 0 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
(hide yes)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(symbol "LED_STRIP_0_1"
|
||||||
|
(rectangle
|
||||||
|
(start -1.27 -1.27)
|
||||||
|
(end -13.97 -8.89)
|
||||||
|
(stroke
|
||||||
|
(width 0)
|
||||||
|
(type default)
|
||||||
|
)
|
||||||
|
(fill
|
||||||
|
(type none)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(symbol "LED_STRIP_1_1"
|
||||||
|
(pin power_in line
|
||||||
|
(at 0 -2.54 180)
|
||||||
|
(length 2.54)
|
||||||
|
(name "LED_5V"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number "1"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin input line
|
||||||
|
(at 0 -5.08 180)
|
||||||
|
(length 2.54)
|
||||||
|
(name "LED_DATA"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number "2"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin passive line
|
||||||
|
(at 0 -7.62 180)
|
||||||
|
(length 2.54)
|
||||||
|
(name "LED_GND"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number "3"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(embedded_fonts no)
|
||||||
|
)
|
||||||
|
(symbol "PA1010D"
|
||||||
|
(exclude_from_sim no)
|
||||||
|
(in_bom yes)
|
||||||
|
(on_board yes)
|
||||||
|
(property "Reference" "U"
|
||||||
|
(at 10.16 -3.048 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "Value" ""
|
||||||
|
(at 0 0 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "Footprint" ""
|
||||||
|
(at 0 0 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
(hide yes)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "Datasheet" ""
|
||||||
|
(at 0 0 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
(hide yes)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "Description" ""
|
||||||
|
(at 0 0 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
(hide yes)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(symbol "PA1010D_0_1"
|
||||||
|
(rectangle
|
||||||
|
(start -1.27 8.89)
|
||||||
|
(end 21.59 -10.16)
|
||||||
|
(stroke
|
||||||
|
(width 0)
|
||||||
|
(type default)
|
||||||
|
)
|
||||||
|
(fill
|
||||||
|
(type none)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(symbol "PA1010D_1_1"
|
||||||
|
(text "PA1010D"
|
||||||
|
(at 10.16 0 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin bidirectional line
|
||||||
|
(at -3.81 6.35 0)
|
||||||
|
(length 2.54)
|
||||||
|
(name "I2C_SDA"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number "1"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin input line
|
||||||
|
(at -3.81 3.81 0)
|
||||||
|
(length 2.54)
|
||||||
|
(name "I2C_SCK"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number "2"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin output line
|
||||||
|
(at -3.81 -2.54 0)
|
||||||
|
(length 2.54)
|
||||||
|
(name "1PPS"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number "3"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin output line
|
||||||
|
(at -3.81 -5.08 0)
|
||||||
|
(length 2.54)
|
||||||
|
(name "TX"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number "4"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin input line
|
||||||
|
(at -3.81 -7.62 0)
|
||||||
|
(length 2.54)
|
||||||
|
(name "RX"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number "5"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin power_in line
|
||||||
|
(at 24.13 6.35 180)
|
||||||
|
(length 2.54)
|
||||||
|
(name "VCC"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number "10"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin power_in line
|
||||||
|
(at 24.13 3.81 180)
|
||||||
|
(length 2.54)
|
||||||
|
(name "VBACKUP"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number "9"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin input line
|
||||||
|
(at 24.13 -2.54 180)
|
||||||
|
(length 2.54)
|
||||||
|
(name "WAKE-UP"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number "8"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin passive line
|
||||||
|
(at 24.13 -5.08 180)
|
||||||
|
(length 2.54)
|
||||||
|
(name "GND"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number "7"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pin input line
|
||||||
|
(at 24.13 -7.62 180)
|
||||||
|
(length 2.54)
|
||||||
|
(name "NRESET"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number "6"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(embedded_fonts no)
|
||||||
|
)
|
||||||
|
(symbol "W3213"
|
||||||
|
(exclude_from_sim no)
|
||||||
|
(in_bom yes)
|
||||||
|
(on_board yes)
|
||||||
|
(property "Reference" "U"
|
||||||
|
(at 3.048 0.762 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "Value" ""
|
||||||
|
(at 0 0 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "Footprint" ""
|
||||||
|
(at 0 0 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
(hide yes)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "Datasheet" ""
|
||||||
|
(at 0 0 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
(hide yes)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "Description" ""
|
||||||
|
(at 0 0 0)
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
(hide yes)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(symbol "W3213_0_1"
|
||||||
|
(rectangle
|
||||||
|
(start 0 0)
|
||||||
|
(end 6.35 -2.54)
|
||||||
|
(stroke
|
||||||
|
(width 0)
|
||||||
|
(type default)
|
||||||
|
)
|
||||||
|
(fill
|
||||||
|
(type none)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(symbol "W3213_1_1"
|
||||||
|
(pin bidirectional line
|
||||||
|
(at -2.54 -1.27 0)
|
||||||
|
(length 2.54)
|
||||||
|
(name "FEED"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(number "1"
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1.27 1.27)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(embedded_fonts no)
|
||||||
|
)
|
||||||
|
)
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
(footprint "3_PIN_WIRE"
|
||||||
|
(version 20241229)
|
||||||
|
(generator "pcbnew")
|
||||||
|
(generator_version "9.0")
|
||||||
|
(layer "F.Cu")
|
||||||
|
(property "Reference" "REF**"
|
||||||
|
(at 2.5 -4 0)
|
||||||
|
(unlocked yes)
|
||||||
|
(layer "F.SilkS")
|
||||||
|
(uuid "ed9aed21-178b-495d-b9e1-929f192a36e4")
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1 1)
|
||||||
|
(thickness 0.1)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "Value" "3_PIN_WIRE"
|
||||||
|
(at 2.5 -2.5 0)
|
||||||
|
(unlocked yes)
|
||||||
|
(layer "F.Fab")
|
||||||
|
(uuid "a81b8f00-4fb7-411f-ad2b-943cd5e16966")
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1 1)
|
||||||
|
(thickness 0.15)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "Datasheet" ""
|
||||||
|
(at 0 0 0)
|
||||||
|
(unlocked yes)
|
||||||
|
(layer "F.Fab")
|
||||||
|
(hide yes)
|
||||||
|
(uuid "874c5579-03b1-4f97-8e19-7e491326f2e9")
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1 1)
|
||||||
|
(thickness 0.15)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "Description" ""
|
||||||
|
(at 0 0 0)
|
||||||
|
(unlocked yes)
|
||||||
|
(layer "F.Fab")
|
||||||
|
(hide yes)
|
||||||
|
(uuid "ac906119-cf33-42dd-9c1a-5d2fb67ab974")
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1 1)
|
||||||
|
(thickness 0.15)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(attr smd)
|
||||||
|
(fp_rect
|
||||||
|
(start -1.5 -1.5)
|
||||||
|
(end 6.5 1.5)
|
||||||
|
(stroke
|
||||||
|
(width 0.1)
|
||||||
|
(type solid)
|
||||||
|
)
|
||||||
|
(fill no)
|
||||||
|
(layer "F.SilkS")
|
||||||
|
(uuid "3c534895-8b86-4446-afd7-676a7a56e959")
|
||||||
|
)
|
||||||
|
(fp_text user "${REFERENCE}"
|
||||||
|
(at 2.5 3 0)
|
||||||
|
(unlocked yes)
|
||||||
|
(layer "F.Fab")
|
||||||
|
(uuid "0f18bd78-61ca-451a-9eeb-2263cb5f43f2")
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1 1)
|
||||||
|
(thickness 0.15)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pad "1" thru_hole circle
|
||||||
|
(at 0 0)
|
||||||
|
(size 1.75 1.75)
|
||||||
|
(drill 1)
|
||||||
|
(layers "*.Cu" "*.Mask")
|
||||||
|
(remove_unused_layers no)
|
||||||
|
(solder_mask_margin 0)
|
||||||
|
(uuid "ebc9015d-9cc2-4111-aef3-b60d76b89373")
|
||||||
|
)
|
||||||
|
(pad "2" thru_hole circle
|
||||||
|
(at 2.5 0)
|
||||||
|
(size 1.75 1.75)
|
||||||
|
(drill 1)
|
||||||
|
(layers "*.Cu" "*.Mask")
|
||||||
|
(remove_unused_layers no)
|
||||||
|
(solder_mask_margin 0)
|
||||||
|
(uuid "173044b5-a2c0-4c4f-bb3f-f9fd466a356c")
|
||||||
|
)
|
||||||
|
(pad "3" thru_hole circle
|
||||||
|
(at 5 0)
|
||||||
|
(size 1.75 1.75)
|
||||||
|
(drill 1)
|
||||||
|
(layers "*.Cu" "*.Mask")
|
||||||
|
(remove_unused_layers no)
|
||||||
|
(solder_mask_margin 0)
|
||||||
|
(uuid "f499520f-f554-4d82-83ea-fa2da2309ab8")
|
||||||
|
)
|
||||||
|
(embedded_fonts no)
|
||||||
|
)
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
(footprint "PA1010D"
|
||||||
|
(version 20241229)
|
||||||
|
(generator "pcbnew")
|
||||||
|
(generator_version "9.0")
|
||||||
|
(layer "F.Cu")
|
||||||
|
(property "Reference" "REF**"
|
||||||
|
(at 5.5 -1 0)
|
||||||
|
(unlocked yes)
|
||||||
|
(layer "F.SilkS")
|
||||||
|
(uuid "130f3771-d8e2-4352-9988-833559a1de18")
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1 1)
|
||||||
|
(thickness 0.1)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "Value" "PA1010D"
|
||||||
|
(at 5 8.5 0)
|
||||||
|
(unlocked yes)
|
||||||
|
(layer "F.Fab")
|
||||||
|
(uuid "41b10efd-006f-40fa-bbac-f8ded5a8fb94")
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1 1)
|
||||||
|
(thickness 0.15)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "Datasheet" ""
|
||||||
|
(at 0 0 0)
|
||||||
|
(unlocked yes)
|
||||||
|
(layer "F.Fab")
|
||||||
|
(hide yes)
|
||||||
|
(uuid "b21a5dda-2f72-4e25-aec7-67c44f8948ee")
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1 1)
|
||||||
|
(thickness 0.15)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "Description" ""
|
||||||
|
(at 0 0 0)
|
||||||
|
(unlocked yes)
|
||||||
|
(layer "F.Fab")
|
||||||
|
(hide yes)
|
||||||
|
(uuid "cc6957af-7fef-407d-948e-2e72ed2fa122")
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1 1)
|
||||||
|
(thickness 0.15)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(attr smd)
|
||||||
|
(fp_rect
|
||||||
|
(start 0 0)
|
||||||
|
(end 10 10)
|
||||||
|
(stroke
|
||||||
|
(width 0.1)
|
||||||
|
(type default)
|
||||||
|
)
|
||||||
|
(fill no)
|
||||||
|
(layer "F.SilkS")
|
||||||
|
(uuid "d57ec54c-ee0b-47c8-a7f8-dc75c8a9b37a")
|
||||||
|
)
|
||||||
|
(fp_circle
|
||||||
|
(center 1.5 1)
|
||||||
|
(end 1.5 1.5)
|
||||||
|
(stroke
|
||||||
|
(width 0.1)
|
||||||
|
(type solid)
|
||||||
|
)
|
||||||
|
(fill yes)
|
||||||
|
(layer "F.SilkS")
|
||||||
|
(uuid "edc44674-6cfd-4316-bad3-efc6a576b3ca")
|
||||||
|
)
|
||||||
|
(fp_circle
|
||||||
|
(center 5 5)
|
||||||
|
(end 3.5 5)
|
||||||
|
(stroke
|
||||||
|
(width 0.1)
|
||||||
|
(type solid)
|
||||||
|
)
|
||||||
|
(fill yes)
|
||||||
|
(layer "Edge.Cuts")
|
||||||
|
(uuid "c34f8951-a402-414f-8c4a-f636b56f109c")
|
||||||
|
)
|
||||||
|
(fp_text user "${REFERENCE}"
|
||||||
|
(at 5 2 0)
|
||||||
|
(unlocked yes)
|
||||||
|
(layer "F.Fab")
|
||||||
|
(uuid "722d4a1d-7d11-4a0d-8121-59126244e839")
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1 1)
|
||||||
|
(thickness 0.15)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pad "1" smd roundrect
|
||||||
|
(at 0 1.15 90)
|
||||||
|
(size 0.8 1.5)
|
||||||
|
(layers "F.Cu" "F.Mask" "F.Paste")
|
||||||
|
(roundrect_rratio 0.15)
|
||||||
|
(uuid "dd90346c-26e8-425a-9a22-9aa95ea9966c")
|
||||||
|
)
|
||||||
|
(pad "2" smd roundrect
|
||||||
|
(at 0 2.75 90)
|
||||||
|
(size 0.8 1.5)
|
||||||
|
(layers "F.Cu" "F.Mask" "F.Paste")
|
||||||
|
(roundrect_rratio 0.15)
|
||||||
|
(uuid "3ed9afe3-fc3d-4c35-8d7a-cc9f3faca37b")
|
||||||
|
)
|
||||||
|
(pad "3" smd roundrect
|
||||||
|
(at 0 5.65 90)
|
||||||
|
(size 0.8 1.5)
|
||||||
|
(layers "F.Cu" "F.Mask" "F.Paste")
|
||||||
|
(roundrect_rratio 0.15)
|
||||||
|
(uuid "baf6929d-7350-437e-b05a-f2d9bce91360")
|
||||||
|
)
|
||||||
|
(pad "4" smd roundrect
|
||||||
|
(at 0 7.25 90)
|
||||||
|
(size 0.8 1.5)
|
||||||
|
(layers "F.Cu" "F.Mask" "F.Paste")
|
||||||
|
(roundrect_rratio 0.15)
|
||||||
|
(uuid "6ec8d1d5-e58f-4b90-9380-ff6ca42c02dd")
|
||||||
|
)
|
||||||
|
(pad "5" smd roundrect
|
||||||
|
(at 0 8.85 90)
|
||||||
|
(size 0.8 1.5)
|
||||||
|
(layers "F.Cu" "F.Mask" "F.Paste")
|
||||||
|
(roundrect_rratio 0.15)
|
||||||
|
(uuid "6f769a41-6d17-4d4a-8d0a-1c53033bec90")
|
||||||
|
)
|
||||||
|
(pad "6" smd roundrect
|
||||||
|
(at 10 8.85 90)
|
||||||
|
(size 0.8 1.5)
|
||||||
|
(layers "F.Cu" "F.Mask" "F.Paste")
|
||||||
|
(roundrect_rratio 0.15)
|
||||||
|
(uuid "8377c02f-ce7c-4edb-af17-6113f49103f3")
|
||||||
|
)
|
||||||
|
(pad "7" smd roundrect
|
||||||
|
(at 10 7.25 90)
|
||||||
|
(size 0.8 1.5)
|
||||||
|
(layers "F.Cu" "F.Mask" "F.Paste")
|
||||||
|
(roundrect_rratio 0.15)
|
||||||
|
(uuid "1b5dcfd8-2351-4f21-91e5-80eb16f68d64")
|
||||||
|
)
|
||||||
|
(pad "8" smd roundrect
|
||||||
|
(at 10 5.65 90)
|
||||||
|
(size 0.8 1.5)
|
||||||
|
(layers "F.Cu" "F.Mask" "F.Paste")
|
||||||
|
(roundrect_rratio 0.15)
|
||||||
|
(uuid "af994bfc-2691-4dd3-a5a6-1b7939aef85f")
|
||||||
|
)
|
||||||
|
(pad "9" smd roundrect
|
||||||
|
(at 10 2.75 90)
|
||||||
|
(size 0.8 1.5)
|
||||||
|
(layers "F.Cu" "F.Mask" "F.Paste")
|
||||||
|
(roundrect_rratio 0.15)
|
||||||
|
(uuid "de6b9b35-3982-456a-8dad-8c11afd9d2a6")
|
||||||
|
)
|
||||||
|
(pad "10" smd roundrect
|
||||||
|
(at 10 1.15 90)
|
||||||
|
(size 0.8 1.5)
|
||||||
|
(layers "F.Cu" "F.Mask" "F.Paste")
|
||||||
|
(roundrect_rratio 0.15)
|
||||||
|
(uuid "ed9d7d62-f653-4f37-8d08-2969d1579eea")
|
||||||
|
)
|
||||||
|
(embedded_fonts no)
|
||||||
|
)
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
(module "SOIC127P600X175-8N" (layer F.Cu)
|
||||||
|
(descr "SO 8L")
|
||||||
|
(tags "Integrated Circuit")
|
||||||
|
(attr smd)
|
||||||
|
(fp_text reference IC** (at 0 0) (layer F.SilkS)
|
||||||
|
(effects (font (size 1.27 1.27) (thickness 0.254)))
|
||||||
|
)
|
||||||
|
(fp_text user %R (at 0 0) (layer F.Fab)
|
||||||
|
(effects (font (size 1.27 1.27) (thickness 0.254)))
|
||||||
|
)
|
||||||
|
(fp_text value "SOIC127P600X175-8N" (at 0 0) (layer F.SilkS) hide
|
||||||
|
(effects (font (size 1.27 1.27) (thickness 0.254)))
|
||||||
|
)
|
||||||
|
(fp_line (start -3.725 -2.75) (end 3.725 -2.75) (layer F.CrtYd) (width 0.05))
|
||||||
|
(fp_line (start 3.725 -2.75) (end 3.725 2.75) (layer F.CrtYd) (width 0.05))
|
||||||
|
(fp_line (start 3.725 2.75) (end -3.725 2.75) (layer F.CrtYd) (width 0.05))
|
||||||
|
(fp_line (start -3.725 2.75) (end -3.725 -2.75) (layer F.CrtYd) (width 0.05))
|
||||||
|
(fp_line (start -1.95 -2.45) (end 1.95 -2.45) (layer F.Fab) (width 0.1))
|
||||||
|
(fp_line (start 1.95 -2.45) (end 1.95 2.45) (layer F.Fab) (width 0.1))
|
||||||
|
(fp_line (start 1.95 2.45) (end -1.95 2.45) (layer F.Fab) (width 0.1))
|
||||||
|
(fp_line (start -1.95 2.45) (end -1.95 -2.45) (layer F.Fab) (width 0.1))
|
||||||
|
(fp_line (start -1.95 -1.18) (end -0.68 -2.45) (layer F.Fab) (width 0.1))
|
||||||
|
(fp_line (start -1.6 -2.45) (end 1.6 -2.45) (layer F.SilkS) (width 0.2))
|
||||||
|
(fp_line (start 1.6 -2.45) (end 1.6 2.45) (layer F.SilkS) (width 0.2))
|
||||||
|
(fp_line (start 1.6 2.45) (end -1.6 2.45) (layer F.SilkS) (width 0.2))
|
||||||
|
(fp_line (start -1.6 2.45) (end -1.6 -2.45) (layer F.SilkS) (width 0.2))
|
||||||
|
(fp_line (start -3.475 -2.605) (end -1.95 -2.605) (layer F.SilkS) (width 0.2))
|
||||||
|
(pad 1 smd rect (at -2.712 -1.905 90) (size 0.7 1.525) (layers F.Cu F.Paste F.Mask))
|
||||||
|
(pad 2 smd rect (at -2.712 -0.635 90) (size 0.7 1.525) (layers F.Cu F.Paste F.Mask))
|
||||||
|
(pad 3 smd rect (at -2.712 0.635 90) (size 0.7 1.525) (layers F.Cu F.Paste F.Mask))
|
||||||
|
(pad 4 smd rect (at -2.712 1.905 90) (size 0.7 1.525) (layers F.Cu F.Paste F.Mask))
|
||||||
|
(pad 5 smd rect (at 2.712 1.905 90) (size 0.7 1.525) (layers F.Cu F.Paste F.Mask))
|
||||||
|
(pad 6 smd rect (at 2.712 0.635 90) (size 0.7 1.525) (layers F.Cu F.Paste F.Mask))
|
||||||
|
(pad 7 smd rect (at 2.712 -0.635 90) (size 0.7 1.525) (layers F.Cu F.Paste F.Mask))
|
||||||
|
(pad 8 smd rect (at 2.712 -1.905 90) (size 0.7 1.525) (layers F.Cu F.Paste F.Mask))
|
||||||
|
(model L6982N50DR.stp
|
||||||
|
(at (xyz 0 0 0))
|
||||||
|
(scale (xyz 1 1 1))
|
||||||
|
(rotate (xyz 0 0 0))
|
||||||
|
)
|
||||||
|
)
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
(footprint "W3213"
|
||||||
|
(version 20241229)
|
||||||
|
(generator "pcbnew")
|
||||||
|
(generator_version "9.0")
|
||||||
|
(layer "F.Cu")
|
||||||
|
(property "Reference" "REF**"
|
||||||
|
(at 6.5 -10 0)
|
||||||
|
(unlocked yes)
|
||||||
|
(layer "F.SilkS")
|
||||||
|
(uuid "f24b340a-fab5-4a19-89d5-142c860a7fc0")
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1 1)
|
||||||
|
(thickness 0.1)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "Value" "W3213"
|
||||||
|
(at 6.5 -2.5 0)
|
||||||
|
(unlocked yes)
|
||||||
|
(layer "F.Fab")
|
||||||
|
(uuid "04dd6c12-8251-4781-a9f5-1b11f920ff3a")
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1 1)
|
||||||
|
(thickness 0.15)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "Datasheet" ""
|
||||||
|
(at 0 0 0)
|
||||||
|
(unlocked yes)
|
||||||
|
(layer "F.Fab")
|
||||||
|
(hide yes)
|
||||||
|
(uuid "b99134c4-fb24-4473-86ca-b34478a5cd2d")
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1 1)
|
||||||
|
(thickness 0.15)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(property "Description" ""
|
||||||
|
(at 0 0 0)
|
||||||
|
(unlocked yes)
|
||||||
|
(layer "F.Fab")
|
||||||
|
(hide yes)
|
||||||
|
(uuid "cd3e3cb1-5955-400e-9a61-613d452727a7")
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1 1)
|
||||||
|
(thickness 0.15)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(attr smd)
|
||||||
|
(fp_rect
|
||||||
|
(start 0 -13)
|
||||||
|
(end 13 0)
|
||||||
|
(stroke
|
||||||
|
(width 0.1)
|
||||||
|
(type default)
|
||||||
|
)
|
||||||
|
(fill no)
|
||||||
|
(layer "F.SilkS")
|
||||||
|
(uuid "c78c6db4-7e41-4ecc-83d0-30df1c12f03d")
|
||||||
|
)
|
||||||
|
(fp_rect
|
||||||
|
(start -1 -14)
|
||||||
|
(end 14 1)
|
||||||
|
(stroke
|
||||||
|
(width 0.1)
|
||||||
|
(type solid)
|
||||||
|
)
|
||||||
|
(fill no)
|
||||||
|
(layer "F.CrtYd")
|
||||||
|
(uuid "4539cb55-422e-48bf-a735-c6db2ea37043")
|
||||||
|
)
|
||||||
|
(fp_text user "${REFERENCE}"
|
||||||
|
(at 6.5 -1 0)
|
||||||
|
(unlocked yes)
|
||||||
|
(layer "F.Fab")
|
||||||
|
(uuid "4e027ed7-f943-4ac4-b64e-958cf24df4e4")
|
||||||
|
(effects
|
||||||
|
(font
|
||||||
|
(size 1 1)
|
||||||
|
(thickness 0.15)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(pad "1" thru_hole circle
|
||||||
|
(at 6.5 -5.8)
|
||||||
|
(size 3.6 3.6)
|
||||||
|
(drill 2.5)
|
||||||
|
(layers "*.Cu" "*.Mask")
|
||||||
|
(remove_unused_layers no)
|
||||||
|
(uuid "e16d3e96-37cd-42db-8a30-66cca71459e4")
|
||||||
|
)
|
||||||
|
(zone
|
||||||
|
(net 0)
|
||||||
|
(net_name "")
|
||||||
|
(layers "F.Cu" "B.Cu" "In1.Cu" "In2.Cu" "In3.Cu" "In4.Cu" "In5.Cu" "In6.Cu"
|
||||||
|
"In7.Cu" "In8.Cu" "In9.Cu" "In10.Cu" "In11.Cu" "In12.Cu" "In13.Cu" "In14.Cu"
|
||||||
|
"In15.Cu" "In16.Cu" "In17.Cu" "In18.Cu" "In19.Cu" "In20.Cu" "In21.Cu"
|
||||||
|
"In22.Cu" "In23.Cu" "In24.Cu" "In25.Cu" "In26.Cu" "In27.Cu" "In28.Cu"
|
||||||
|
"In29.Cu" "In30.Cu"
|
||||||
|
)
|
||||||
|
(uuid "4534681b-5cec-434f-b51a-fe95ff081411")
|
||||||
|
(name "KEEP-OUT")
|
||||||
|
(hatch edge 0.5)
|
||||||
|
(connect_pads
|
||||||
|
(clearance 0)
|
||||||
|
)
|
||||||
|
(min_thickness 0.25)
|
||||||
|
(filled_areas_thickness no)
|
||||||
|
(keepout
|
||||||
|
(tracks allowed)
|
||||||
|
(vias not_allowed)
|
||||||
|
(pads not_allowed)
|
||||||
|
(copperpour not_allowed)
|
||||||
|
(footprints not_allowed)
|
||||||
|
)
|
||||||
|
(placement
|
||||||
|
(enabled no)
|
||||||
|
(sheetname "")
|
||||||
|
)
|
||||||
|
(fill
|
||||||
|
(thermal_gap 0.5)
|
||||||
|
(thermal_bridge_width 0.5)
|
||||||
|
)
|
||||||
|
(polygon
|
||||||
|
(pts
|
||||||
|
(xy 0 0.141405) (xy 0 -12.858595) (xy 13 -12.858595) (xy 13 0.141405)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(embedded_fonts no)
|
||||||
|
)
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
(fp_lib_table
|
||||||
|
(version 7)
|
||||||
|
(lib (name "chips")(type "KiCad")(uri "${KIPRJMOD}/chips.pretty")(options "")(descr ""))
|
||||||
|
(lib (name "KiCad")(type "KiCad")(uri "${KIPRJMOD}/LIB_W3213/W3213/KiCad")(options "")(descr ""))
|
||||||
|
(lib (name "LSM6DSV80XTR")(type "KiCad")(uri "${KIPRJMOD}/LIB_LSM6DSV80XTR/LSM6DSV80XTR/KiCad")(options "")(descr ""))
|
||||||
|
)
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
(sym_lib_table
|
||||||
|
(version 7)
|
||||||
|
(lib (name "chips")(type "KiCad")(uri "${KIPRJMOD}/chips.kicad_sym")(options "")(descr ""))
|
||||||
|
(lib (name "L6982N50DR")(type "KiCad")(uri "${KIPRJMOD}/LIB_L6982N50DR/L6982N50DR/KiCad/L6982N50DR.kicad_sym")(options "")(descr ""))
|
||||||
|
(lib (name "W3213")(type "KiCad")(uri "${KIPRJMOD}/LIB_W3213/W3213/KiCad/W3213.kicad_sym")(options "")(descr ""))
|
||||||
|
(lib (name "LSM6DSV80XTR")(type "KiCad")(uri "${KIPRJMOD}/LIB_LSM6DSV80XTR/LSM6DSV80XTR/KiCad/LSM6DSV80XTR.kicad_sym")(options "")(descr ""))
|
||||||
|
)
|
||||||
@@ -205,12 +205,13 @@ fn write_sim_data() {
|
|||||||
let segments = [(StreamType::IMU, motion_output), (StreamType::GPS, gps_output), (StreamType::Annotations, annotation_output)];
|
let segments = [(StreamType::IMU, motion_output), (StreamType::GPS, gps_output), (StreamType::Annotations, annotation_output)];
|
||||||
|
|
||||||
// Write out the stream index header
|
// Write out the stream index header
|
||||||
rmp::encode::write_array_len(&mut unified_fd, segments.len() as u32).unwrap();
|
StreamIndex { count: segments.len() }.write_rmp(&mut unified_fd).unwrap();
|
||||||
|
|
||||||
// Then the streams
|
// Then the streams
|
||||||
for (stream_type, stream_path) in segments {
|
for (stream_type, stream_path) in segments {
|
||||||
let mut fd = File::open(stream_path).unwrap();
|
let mut fd = File::open(stream_path).unwrap();
|
||||||
rmp::encode::write_ext_meta(&mut unified_fd, fd.metadata().unwrap().len() as u32, stream_type.into()).unwrap();
|
// FIXME: Replace this with the actual rmp types in simdata
|
||||||
|
StreamHeader { id: stream_type, size: fd.metadata().unwrap().len() as usize }.write_rmp(&mut unified_fd).unwrap();
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
fd.read_to_end(&mut buf).unwrap();
|
fd.read_to_end(&mut buf).unwrap();
|
||||||
unified_fd.write_all(buf.as_slice()).unwrap();
|
unified_fd.write_all(buf.as_slice()).unwrap();
|
||||||
|
|||||||
+104
-61
@@ -1,8 +1,7 @@
|
|||||||
use embassy_time::{Duration, Instant, Timer};
|
use embassy_time::{Duration, Instant, Timer};
|
||||||
use esp_rtos::CurrentThreadHandle;
|
|
||||||
use figments::{liber8tion::interpolate::Fract8, surface::Surface};
|
use figments::{liber8tion::interpolate::Fract8, surface::Surface};
|
||||||
use figments_render::output::Brightness;
|
use figments_render::output::Brightness;
|
||||||
use core::{fmt::Debug, mem::MaybeUninit, ops::{Deref, DerefMut}};
|
use core::{fmt::Debug, ops::{Deref, DerefMut}};
|
||||||
use log::*;
|
use log::*;
|
||||||
|
|
||||||
use crate::graphics::display::DisplayControls;
|
use crate::graphics::display::DisplayControls;
|
||||||
@@ -30,9 +29,9 @@ impl<S: Surface> AnimationActor<Fract8> for S {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct AnimDisplay<'a>(pub &'a mut DisplayControls);
|
pub struct AnimDisplay<'a, 'b>(pub &'a mut DisplayControls<'b>);
|
||||||
|
|
||||||
impl<'a> AnimationActor<Fract8> for AnimDisplay<'a> {
|
impl<'a> AnimationActor<Fract8> for AnimDisplay<'a, '_> {
|
||||||
fn get_value(&self) -> Fract8 {
|
fn get_value(&self) -> Fract8 {
|
||||||
self.0.brightness()
|
self.0.brightness()
|
||||||
}
|
}
|
||||||
@@ -53,6 +52,10 @@ impl<S: Surface> AnimationActor<Fract8> for AnimatedSurface<S> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trait Tickable {
|
||||||
|
fn tick(&mut self) -> TickResult;
|
||||||
|
}
|
||||||
|
|
||||||
struct Slot<'a, T> {
|
struct Slot<'a, T> {
|
||||||
from: T,
|
from: T,
|
||||||
to: T,
|
to: T,
|
||||||
@@ -62,6 +65,38 @@ struct Slot<'a, T> {
|
|||||||
target: &'a mut dyn AnimationActor<T>
|
target: &'a mut dyn AnimationActor<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum TickResult {
|
||||||
|
Finished,
|
||||||
|
Continue
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> Slot<'a, T> {
|
||||||
|
fn is_valid(&self) -> bool {
|
||||||
|
self.step_time.as_ticks() != 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Tickable for Slot<'a, Fract8> {
|
||||||
|
/// Advances the animation by one tick, and then returns whether or not the animation should continue or not
|
||||||
|
fn tick(&mut self) -> TickResult {
|
||||||
|
self.next_update += self.step_time;
|
||||||
|
self.cur_step = if self.to > self.from {
|
||||||
|
self.cur_step + Fract8::from_raw(1)
|
||||||
|
} else {
|
||||||
|
self.cur_step - Fract8::from_raw(1)
|
||||||
|
};
|
||||||
|
|
||||||
|
self.target.set_value(self.cur_step);
|
||||||
|
|
||||||
|
if (self.to > self.from && self.cur_step >= self.to) ||
|
||||||
|
(self.to <= self.from && self.cur_step <= self.to) {
|
||||||
|
TickResult::Finished
|
||||||
|
} else {
|
||||||
|
TickResult::Continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a, T: Debug> core::fmt::Debug for Slot<'a, T> {
|
impl<'a, T: Debug> core::fmt::Debug for Slot<'a, T> {
|
||||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
f.debug_struct("Slot")
|
f.debug_struct("Slot")
|
||||||
@@ -73,7 +108,14 @@ impl<'a, T: Debug> core::fmt::Debug for Slot<'a, T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Animation<Fract8> {
|
struct Animator<'a, T, const ACTOR_COUNT: usize> {
|
||||||
|
animators: [Slot<'a, T>; ACTOR_COUNT]
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T, const ACTOR_COUNT: usize> Animator<'a, T, ACTOR_COUNT> {
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: core::fmt::Debug> Animation<T> {
|
||||||
pub const fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
from: None,
|
from: None,
|
||||||
@@ -82,6 +124,53 @@ impl Animation<Fract8> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn execute<'a, const ACTOR_COUNT: usize>(mut animators: [Slot<'a, T>; ACTOR_COUNT]) where Slot<'a, T>: Tickable {
|
||||||
|
let mut now: Instant = Instant::now();
|
||||||
|
loop {
|
||||||
|
// Find the next shortest delay
|
||||||
|
let mut next_keyframe_time = Instant::MAX;
|
||||||
|
let mut finished = false;
|
||||||
|
let mut has_valid = false;
|
||||||
|
for animator in &mut animators {
|
||||||
|
if !animator.is_valid() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
has_valid = true;
|
||||||
|
if animator.next_update <= now {
|
||||||
|
finished = match animator.tick() {
|
||||||
|
TickResult::Finished => true,
|
||||||
|
TickResult::Continue => finished
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if next_keyframe_time <= now || animator.next_update < next_keyframe_time {
|
||||||
|
next_keyframe_time = animator.next_update;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there are no valid animators, or if all animators are finished, then we're done
|
||||||
|
finished |= !has_valid;
|
||||||
|
if finished {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(next_keyframe_time > now, "Weird times: {next_keyframe_time:?} is earlier than {now:?} animators={animators:?}");
|
||||||
|
let keyframe_delay = next_keyframe_time - now;
|
||||||
|
trace!("delay {:?}", keyframe_delay.as_millis());
|
||||||
|
Timer::after(keyframe_delay).await;
|
||||||
|
now += keyframe_delay;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Animation<Fract8> {
|
||||||
|
pub const fn duration(self, duration: Duration) -> Self {
|
||||||
|
Self {
|
||||||
|
duration,
|
||||||
|
..self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub const fn from(self, from: Fract8) -> Self {
|
pub const fn from(self, from: Fract8) -> Self {
|
||||||
Self {
|
Self {
|
||||||
from: Some(from),
|
from: Some(from),
|
||||||
@@ -95,23 +184,18 @@ impl Animation<Fract8> {
|
|||||||
..self
|
..self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub const fn duration(self, duration: Duration) -> Self {
|
const MIN_ANIMATION_RATE: Duration = Duration::from_millis(5);
|
||||||
Self {
|
|
||||||
duration,
|
|
||||||
..self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Animation<Fract8> {
|
impl Animation<Fract8> {
|
||||||
pub async fn apply<const ACTOR_COUNT: usize>(&self, actors: [&mut dyn AnimationActor<Fract8>; ACTOR_COUNT]) {
|
pub async fn apply<const ACTOR_COUNT: usize>(&self, actors: [&mut dyn AnimationActor<Fract8>; ACTOR_COUNT]) {
|
||||||
|
|
||||||
let mut now = Instant::now();
|
let now = Instant::now();
|
||||||
trace!("start now={now:?} ACTOR_COUNT={ACTOR_COUNT}");
|
trace!("start now={now:?} ACTOR_COUNT={ACTOR_COUNT}");
|
||||||
|
|
||||||
let mut actors = actors.into_iter();
|
let mut actors = actors.into_iter();
|
||||||
let mut animators: [Slot<Fract8>; ACTOR_COUNT] = core::array::from_fn(|_| {
|
let animators: [Slot<Fract8>; ACTOR_COUNT] = core::array::from_fn(|_| {
|
||||||
let target = actors.next().unwrap();
|
let target = actors.next().unwrap();
|
||||||
let from = if let Some(val) = self.from {
|
let from = if let Some(val) = self.from {
|
||||||
val
|
val
|
||||||
@@ -123,12 +207,14 @@ impl Animation<Fract8> {
|
|||||||
} else {
|
} else {
|
||||||
target.get_value()
|
target.get_value()
|
||||||
};
|
};
|
||||||
let steps = to.abs_diff(from);
|
|
||||||
|
|
||||||
let step_time = if steps == Fract8::MIN {
|
let step_time = if to == from {
|
||||||
|
// Zero ticks is an 'invalid' animator that shouldn't get processed because start == end already
|
||||||
Duration::from_ticks(0)
|
Duration::from_ticks(0)
|
||||||
} else {
|
} else {
|
||||||
(self.duration / steps.to_raw().into()).max(Duration::from_millis(1))
|
let steps = to.abs_diff(from);
|
||||||
|
// FIXME: if the resulting duration is less than the animation rate, we also need to re-scale the number added to animator.cur_step further down below. Otherwise a 0-255 animation with a 100ms duration actually ends up running for 255ms
|
||||||
|
(self.duration / steps.to_raw().into()).max(MIN_ANIMATION_RATE)
|
||||||
};
|
};
|
||||||
Slot {
|
Slot {
|
||||||
from,
|
from,
|
||||||
@@ -142,50 +228,7 @@ impl Animation<Fract8> {
|
|||||||
|
|
||||||
trace!("animators={animators:?}");
|
trace!("animators={animators:?}");
|
||||||
|
|
||||||
loop {
|
Self::execute(animators).await;
|
||||||
// Find the next shortest delay
|
|
||||||
let mut next_keyframe_time = animators[0].next_update;
|
|
||||||
let mut finished = false;
|
|
||||||
for animator in &mut animators {
|
|
||||||
if animator.step_time.as_ticks() == 0 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if animator.next_update <= now {
|
|
||||||
animator.next_update += animator.step_time;
|
|
||||||
animator.cur_step = if animator.to > animator.from {
|
|
||||||
animator.cur_step + Fract8::from_raw(1)
|
|
||||||
} else {
|
|
||||||
animator.cur_step - Fract8::from_raw(1)
|
|
||||||
};
|
|
||||||
|
|
||||||
if (animator.to > animator.from && animator.cur_step >= animator.to) ||
|
|
||||||
(animator.to <= animator.from && animator.cur_step <= animator.to) {
|
|
||||||
finished = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*if animator.cur_step == animator.from || animator.cur_step == animator.to {
|
|
||||||
finished = true;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
animator.target.set_value(animator.cur_step);
|
|
||||||
}
|
|
||||||
|
|
||||||
if next_keyframe_time <= now || animator.next_update < next_keyframe_time {
|
|
||||||
next_keyframe_time = animator.next_update;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if finished {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
let keyframe_delay = next_keyframe_time - now;
|
|
||||||
trace!("delay {keyframe_delay:?}");
|
|
||||||
Timer::after(keyframe_delay).await;
|
|
||||||
now += keyframe_delay;
|
|
||||||
}
|
|
||||||
|
|
||||||
trace!("finished animators={animators:?}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+245
-79
@@ -7,34 +7,29 @@
|
|||||||
)]
|
)]
|
||||||
|
|
||||||
|
|
||||||
use core::{num::{self, Wrapping}, ptr::addr_of_mut};
|
|
||||||
|
|
||||||
use alloc::{string::String, sync::Arc};
|
use alloc::{string::String, sync::Arc};
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
use embassy_time::{Instant, Timer};
|
use embassy_time::{Duration, Instant, Timer, WithTimeout};
|
||||||
|
|
||||||
use esp_hal::{gpio::{Output, OutputConfig, Pin}, time::Rate, xtensa_lx::debug_break};
|
use enum_map::EnumMap;
|
||||||
|
use esp_hal::{gpio::{Event, Input, InputConfig, Output, OutputConfig, Pin}, handler, time::Rate};
|
||||||
use esp_hal::{
|
use esp_hal::{
|
||||||
clock::CpuClock, system::{AppCoreGuard, CpuControl, Stack}, timer::{systimer::SystemTimer, timg::{TimerGroup, Wdt}}
|
clock::CpuClock, timer::{systimer::SystemTimer, timg::{TimerGroup, Wdt}}
|
||||||
};
|
};
|
||||||
|
|
||||||
use embassy_sync::{
|
use embassy_sync::{
|
||||||
pubsub::PubSubChannel,
|
blocking_mutex::raw::NoopRawMutex, channel::DynamicReceiver, once_lock::OnceLock, pubsub::PubSubChannel
|
||||||
blocking_mutex::raw::NoopRawMutex
|
|
||||||
};
|
};
|
||||||
use static_cell::ConstStaticCell;
|
use esp_storage::FlashStorage;
|
||||||
use log::*;
|
use log::*;
|
||||||
use renderbug_embassy::{events::Prediction, graphics::display::DisplayControls, logging::RenderbugLogger, tasks::{oled::{OledUI, OledUiSurfacePool, oled_ui}, safetyui::{SafetyUi, safety_ui_main}, ui::UiSurfacePool}};
|
use renderbug_bike::{events::{Prediction, SensorSource, SensorState}, gpio_interrupt::{InterruptDispatch, PinInterrupt}, graphics::display::DisplayControls, logging::RenderbugLogger, simdata::IMUReading, storage::{SharedFlash, SimDataRecorder, StorageRange}, tasks::{oled::{OledUI, OledUiSurfacePool, oled_ui}, safetyui::{SafetyUi, safety_ui_main}, ui::UiSurfacePool}, tracing::Tracer};
|
||||||
use renderbug_embassy::events::Measurement;
|
use renderbug_bike::events::Measurement;
|
||||||
use static_cell::StaticCell;
|
use static_cell::StaticCell;
|
||||||
use esp_backtrace as _;
|
use esp_backtrace as _;
|
||||||
use esp_rtos::embassy::{Executor, InterruptExecutor};
|
use esp_hal::spi::master::any::Degrade;
|
||||||
use esp_hal::interrupt::software::SoftwareInterruptControl;
|
|
||||||
|
|
||||||
use renderbug_embassy::tasks::{
|
use renderbug_bike::tasks::ui::{Ui, ui_main};
|
||||||
motion::motion_task,
|
use esp_hal::dma::DmaChannelConvert;
|
||||||
ui::{Ui, ui_main}
|
|
||||||
};
|
|
||||||
|
|
||||||
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, pubsub::DynSubscriber};
|
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, pubsub::DynSubscriber};
|
||||||
use embassy_sync::channel::Channel;
|
use embassy_sync::channel::Channel;
|
||||||
@@ -48,27 +43,50 @@ esp_bootloader_esp_idf::esp_app_desc!();
|
|||||||
#[cfg(feature="radio")]
|
#[cfg(feature="radio")]
|
||||||
static WIFI_INIT: StaticCell<esp_radio::Controller<'static>> = StaticCell::new();
|
static WIFI_INIT: StaticCell<esp_radio::Controller<'static>> = StaticCell::new();
|
||||||
|
|
||||||
|
rtos_trace::global_trace!(Tracer);
|
||||||
|
|
||||||
|
static INTERRUPTS: OnceLock<InterruptDispatch<'static, 4>> = OnceLock::new();
|
||||||
|
#[handler]
|
||||||
|
fn gpio_interrupt_handler() {
|
||||||
|
INTERRUPTS.try_get().unwrap().process_interrupts();
|
||||||
|
}
|
||||||
|
|
||||||
|
static MOTION_BUS: ConstStaticCell<Channel<CriticalSectionRawMutex,Measurement,5> > = ConstStaticCell::new(Channel::new());
|
||||||
|
static RECORDING_BUS: ConstStaticCell<PubSubChannel<CriticalSectionRawMutex,Measurement,10, 4, 1> > = ConstStaticCell::new(PubSubChannel::new());
|
||||||
|
static PREDICTIONS: ConstStaticCell<PubSubChannel<NoopRawMutex, Prediction, 30, 7, 1>> = ConstStaticCell::new(PubSubChannel::new());
|
||||||
|
|
||||||
#[esp_rtos::main]
|
#[esp_rtos::main]
|
||||||
async fn main(spawner: Spawner) {
|
async fn main(spawner: Spawner) {
|
||||||
esp_alloc::heap_allocator!(size: 128 * 1024);
|
// If we aren't using the second CPU, we can use the bootloader space for the heap instead
|
||||||
|
if cfg!(not(feature="dual-core")) {
|
||||||
|
esp_alloc::heap_allocator!(#[esp_hal::ram(reclaimed)] size: 73744);
|
||||||
|
esp_alloc::heap_allocator!(size: 32 * 1024);
|
||||||
|
} else {
|
||||||
|
esp_alloc::heap_allocator!(size: 100000);
|
||||||
|
}
|
||||||
|
|
||||||
RenderbugLogger::init_logger();
|
RenderbugLogger::init_logger();
|
||||||
|
|
||||||
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
|
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
|
||||||
let peripherals = esp_hal::init(config);
|
let peripherals = esp_hal::init(config);
|
||||||
|
|
||||||
|
info!("Boot memory stats: {}", esp_alloc::HEAP.stats());
|
||||||
|
|
||||||
let sys_timer = SystemTimer::new(peripherals.SYSTIMER);
|
let sys_timer = SystemTimer::new(peripherals.SYSTIMER);
|
||||||
esp_rtos::start(sys_timer.alarm0);
|
esp_rtos::start(sys_timer.alarm0);
|
||||||
info!("Embassy initialized!");
|
info!("Embassy initialized!");
|
||||||
|
|
||||||
static MOTION_BUS: StaticCell<Channel<CriticalSectionRawMutex,Measurement,5> > = StaticCell::new();
|
let motion_bus = MOTION_BUS.take();
|
||||||
let motion_bus = MOTION_BUS.init_with(|| { Channel::new() });
|
let recording_bus = RECORDING_BUS.take();
|
||||||
|
|
||||||
info!("Setting up rendering pipeline");
|
info!("Setting up rendering pipeline");
|
||||||
let mut surfaces = UiSurfacePool::default();
|
let mut surfaces = UiSurfacePool::default();
|
||||||
let ui = Ui::new(&mut surfaces);
|
let ui = Ui::new(&mut surfaces);
|
||||||
|
|
||||||
let display_controls = DisplayControls::default();
|
static MAIN_DISPLAY_SWITCH: ConstStaticCell<DisplayControlResources> = ConstStaticCell::new(DisplayControlResources::new());
|
||||||
let oled_controls = DisplayControls::default();
|
static OLED_DISPLAY_SWITCH: ConstStaticCell<DisplayControlResources> = ConstStaticCell::new(DisplayControlResources::new());
|
||||||
|
let display_controls = DisplayControls::new(MAIN_DISPLAY_SWITCH.take());
|
||||||
|
let oled_controls = DisplayControls::new(OLED_DISPLAY_SWITCH.take());
|
||||||
|
|
||||||
let mut oled_surfaces = OledUiSurfacePool::default();
|
let mut oled_surfaces = OledUiSurfacePool::default();
|
||||||
let oled_uniforms = Default::default();
|
let oled_uniforms = Default::default();
|
||||||
@@ -83,12 +101,33 @@ async fn main(spawner: Spawner) {
|
|||||||
wdt.enable();
|
wdt.enable();
|
||||||
|
|
||||||
// Spawn the rendering task as soon as possible so it can start pushing pixels
|
// Spawn the rendering task as soon as possible so it can start pushing pixels
|
||||||
spawner.must_spawn(renderbug_embassy::tasks::render::render(peripherals.RMT, peripherals.GPIO5.degrade(), surfaces, safety_surfaces, display_controls, wdt));
|
spawner.must_spawn(renderbug_bike::tasks::render::render(peripherals.SPI2.degrade(), peripherals.DMA_CH2.degrade(), peripherals.GPIO5.degrade(), surfaces, safety_surfaces, display_controls.clone(), wdt));
|
||||||
|
|
||||||
// Wait one scheduler tick for the rendering task to get initialized
|
let imu_interrupt = PinInterrupt::new(Input::new(peripherals.GPIO36.degrade(), InputConfig::default()), Event::RisingEdge);
|
||||||
Timer::after_ticks(1).await;
|
let pd_interrupt = PinInterrupt::new(Input::new(peripherals.GPIO16.degrade(), InputConfig::default()), Event::RisingEdge);
|
||||||
|
let sd_detect_interrupt = PinInterrupt::new(Input::new(peripherals.GPIO42.degrade(), InputConfig::default()), Event::RisingEdge);
|
||||||
|
let gps_pulse_interrupt = PinInterrupt::new(Input::new(peripherals.GPIO45.degrade(), InputConfig::default()), Event::RisingEdge);
|
||||||
|
|
||||||
#[cfg(feature="motion")]
|
INTERRUPTS.init(InterruptDispatch::new([
|
||||||
|
imu_interrupt.clone(),
|
||||||
|
pd_interrupt.clone(),
|
||||||
|
sd_detect_interrupt.clone(),
|
||||||
|
gps_pulse_interrupt.clone()
|
||||||
|
])).ok();
|
||||||
|
let mut io = esp_hal::gpio::Io::new(peripherals.IO_MUX);
|
||||||
|
io.set_interrupt_handler(gpio_interrupt_handler);
|
||||||
|
|
||||||
|
spawner.must_spawn(renderbug_bike::tasks::sd_card::sdcard_task(
|
||||||
|
peripherals.SPI3.degrade(),
|
||||||
|
peripherals.GPIO41.degrade(),
|
||||||
|
peripherals.GPIO2.degrade(),
|
||||||
|
peripherals.GPIO1.degrade(),
|
||||||
|
Output::new(peripherals.GPIO40.degrade(), esp_hal::gpio::Level::High, OutputConfig::default()),
|
||||||
|
sd_detect_interrupt,
|
||||||
|
recording_bus.dyn_subscriber().unwrap()
|
||||||
|
));
|
||||||
|
|
||||||
|
#[cfg(feature="i2c")]
|
||||||
{
|
{
|
||||||
use embassy_embedded_hal::shared_bus::asynch::i2c::I2cDevice;
|
use embassy_embedded_hal::shared_bus::asynch::i2c::I2cDevice;
|
||||||
use esp_hal::{i2c::master::{Config, I2c}, Async};
|
use esp_hal::{i2c::master::{Config, I2c}, Async};
|
||||||
@@ -101,16 +140,17 @@ async fn main(spawner: Spawner) {
|
|||||||
let scl = peripherals.GPIO3;
|
let scl = peripherals.GPIO3;
|
||||||
let i2c = I2c::new(peripherals.I2C1, Config::default()).unwrap().with_scl(scl).with_sda(sda).into_async();
|
let i2c = I2c::new(peripherals.I2C1, Config::default()).unwrap().with_scl(scl).with_sda(sda).into_async();
|
||||||
let i2c_bus = I2C_BUS.init(Mutex::new(i2c));
|
let i2c_bus = I2C_BUS.init(Mutex::new(i2c));
|
||||||
#[cfg(feature="mpu")]
|
#[cfg(feature="mpu6050")]
|
||||||
spawner.must_spawn(renderbug_embassy::tasks::mpu::mpu_task(motion_bus.dyn_sender(), I2cDevice::new(i2c_bus)));
|
spawner.must_spawn(renderbug_bike::tasks::mpu::mpu_task(motion_bus.dyn_sender(), I2cDevice::new(i2c_bus), imu_interrupt));
|
||||||
#[cfg(feature="gps")]
|
|
||||||
spawner.must_spawn(renderbug_embassy::tasks::gps::gps_task(motion_bus.dyn_sender(), I2cDevice::new(i2c_bus)));
|
spawner.must_spawn(renderbug_bike::tasks::gps::gps_task(motion_bus.dyn_sender(), I2cDevice::new(i2c_bus)));
|
||||||
|
spawner.must_spawn(renderbug_bike::tasks::usb_power::usb_task(motion_bus.dyn_sender(), I2cDevice::new(i2c_bus), pd_interrupt, display_controls.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature="oled")]
|
#[cfg(feature="oled")]
|
||||||
{
|
{
|
||||||
use esp_hal::i2c::master::{Config, I2c};
|
use esp_hal::i2c::master::{Config, I2c};
|
||||||
use renderbug_embassy::graphics::ssd1306::SsdOutput;
|
use renderbug_bike::graphics::ssd1306::SsdOutput;
|
||||||
|
|
||||||
let rst = Output::new(peripherals.GPIO21, esp_hal::gpio::Level::Low, OutputConfig::default());
|
let rst = Output::new(peripherals.GPIO21, esp_hal::gpio::Level::Low, OutputConfig::default());
|
||||||
let i2c = I2c::new(
|
let i2c = I2c::new(
|
||||||
@@ -118,23 +158,7 @@ async fn main(spawner: Spawner) {
|
|||||||
Config::default().with_frequency(Rate::from_khz(400))
|
Config::default().with_frequency(Rate::from_khz(400))
|
||||||
).unwrap().with_scl(peripherals.GPIO18).with_sda(peripherals.GPIO17).into_async();
|
).unwrap().with_scl(peripherals.GPIO18).with_sda(peripherals.GPIO17).into_async();
|
||||||
let output = SsdOutput::new(i2c, rst, oled_controls).await;
|
let output = SsdOutput::new(i2c, rst, oled_controls).await;
|
||||||
spawner.must_spawn(renderbug_embassy::tasks::oled_render::oled_render(output, oled_surfaces, oled_uniforms));
|
spawner.must_spawn(renderbug_bike::tasks::oled_render::oled_render(output, oled_surfaces, oled_uniforms));
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature="simulation")]
|
|
||||||
{
|
|
||||||
use esp_storage::FlashStorage;
|
|
||||||
use renderbug_embassy::tasks::simulation::{SharedFlash, SimDataTable};
|
|
||||||
let mut storage = SharedFlash::new(FlashStorage::new());
|
|
||||||
let mut buf = [8; 1024];
|
|
||||||
let partitions = esp_bootloader_esp_idf::partitions::read_partition_table(&mut storage, &mut buf).unwrap();
|
|
||||||
for sim_data in SimDataTable::open(storage, partitions).expect("Could not find sim data!") {
|
|
||||||
let srcid = sim_data.srcid();
|
|
||||||
info!("Found simulation data for {srcid:?}");
|
|
||||||
if spawner.spawn(renderbug_embassy::tasks::simulation::simulation_task(sim_data, motion_bus.dyn_sender())).is_err() {
|
|
||||||
error!("Unable to spawn simulation task for {srcid:?}! Increase the task pool size.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature="radio")]
|
#[cfg(feature="radio")]
|
||||||
@@ -145,7 +169,16 @@ async fn main(spawner: Spawner) {
|
|||||||
|
|
||||||
let ble = esp_radio::ble::controller::BleConnector::new(wifi_init, peripherals.BT, esp_radio::ble::Config::default()).unwrap();
|
let ble = esp_radio::ble::controller::BleConnector::new(wifi_init, peripherals.BT, esp_radio::ble::Config::default()).unwrap();
|
||||||
|
|
||||||
let (wifi, interfaces) = esp_radio::wifi::new(wifi_init, peripherals.WIFI, esp_radio::wifi::Config::default())
|
let wifi_config = esp_radio::wifi::Config::default()
|
||||||
|
.with_rx_queue_size(64)
|
||||||
|
.with_tx_queue_size(256)
|
||||||
|
.with_static_rx_buf_num(32)
|
||||||
|
.with_static_tx_buf_num(32)
|
||||||
|
.with_dynamic_rx_buf_num(128)
|
||||||
|
.with_dynamic_tx_buf_num(128)
|
||||||
|
.with_rx_ba_win(7)
|
||||||
|
.with_power_save_mode(esp_radio::wifi::PowerSaveMode::Minimum);
|
||||||
|
let (wifi, interfaces) = esp_radio::wifi::new(wifi_init, peripherals.WIFI, wifi_config)
|
||||||
.expect("Failed to initialize WIFI!");
|
.expect("Failed to initialize WIFI!");
|
||||||
|
|
||||||
(wifi, interfaces.sta, ble)
|
(wifi, interfaces.sta, ble)
|
||||||
@@ -153,20 +186,18 @@ async fn main(spawner: Spawner) {
|
|||||||
|
|
||||||
let core2_main = |spawner: Spawner| {
|
let core2_main = |spawner: Spawner| {
|
||||||
info!("Starting application tasks");
|
info!("Starting application tasks");
|
||||||
|
let predictions = PREDICTIONS.take();
|
||||||
static PREDICTIONS: StaticCell<PubSubChannel<NoopRawMutex, Prediction, 15, 6, 1>> = StaticCell::new();
|
|
||||||
let predictions = PREDICTIONS.init(PubSubChannel::new());
|
|
||||||
|
|
||||||
#[cfg(not(feature="demo"))]
|
#[cfg(not(feature="demo"))]
|
||||||
{
|
{
|
||||||
info!("Launching motion engine");
|
info!("Launching motion engine");
|
||||||
spawner.must_spawn(motion_task(motion_bus.dyn_receiver(), predictions.dyn_publisher().unwrap()));
|
spawner.must_spawn(renderbug_bike::tasks::motion::motion_task(motion_bus.dyn_receiver(), predictions.dyn_publisher().unwrap(), recording_bus.dyn_publisher().unwrap()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature="demo")]
|
#[cfg(feature="demo")]
|
||||||
{
|
{
|
||||||
warn!("Launching with demo sequencer");
|
warn!("Launching with demo sequencer");
|
||||||
spawner.must_spawn(renderbug_embassy::tasks::demo::demo_task(predictions.dyn_publisher().unwrap()));
|
spawner.must_spawn(renderbug_bike::tasks::demo::demo_task(predictions.dyn_publisher().unwrap()));
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("Launching Safety UI");
|
info!("Launching Safety UI");
|
||||||
@@ -188,39 +219,96 @@ async fn main(spawner: Spawner) {
|
|||||||
let seed = Rng::new().random() as i32;
|
let seed = Rng::new().random() as i32;
|
||||||
let (stack, runner) = embassy_net::new(network_device, config, RESOURCES.take(), seed as u64);
|
let (stack, runner) = embassy_net::new(network_device, config, RESOURCES.take(), seed as u64);
|
||||||
info!("Launching network services");
|
info!("Launching network services");
|
||||||
//spawner.must_spawn(renderbug_embassy::tasks::wifi::net_task(runner));
|
spawner.must_spawn(renderbug_bike::tasks::wifi::net_task(runner));
|
||||||
|
|
||||||
info!("Starting connectivity task");
|
info!("Starting connectivity task");
|
||||||
//spawner.must_spawn(renderbug_embassy::tasks::wifi::wifi_connect_task(wifi, stack, motion_bus.dyn_sender()));
|
spawner.must_spawn(renderbug_bike::tasks::wifi::wifi_connect_task(wifi, motion_bus.dyn_sender()));
|
||||||
|
|
||||||
|
info!("Starting location sampler");
|
||||||
|
static SAMPLER: ConstStaticCell<Watch<NoopRawMutex, Prediction, 1>> = ConstStaticCell::new(Watch::new());
|
||||||
|
let sampler = SAMPLER.take();
|
||||||
|
spawner.must_spawn(renderbug_bike::tasks::wifi::location_sampler(predictions.dyn_subscriber().unwrap(), sampler.dyn_sender()));
|
||||||
|
|
||||||
info!("Launching HTTP telemetry");
|
info!("Launching HTTP telemetry");
|
||||||
//spawner.must_spawn(renderbug_embassy::tasks::wifi::http_telemetry_task(predictions.dyn_subscriber().unwrap(), stack));
|
spawner.must_spawn(renderbug_bike::tasks::wifi::http_telemetry_task(sampler.dyn_receiver().unwrap(), stack, motion_bus.dyn_sender()));
|
||||||
|
|
||||||
info!("Starting BLE services");
|
info!("Starting BLE services");
|
||||||
spawner.must_spawn(renderbug_embassy::tasks::ble::ble_task(ble, predictions.dyn_subscriber().unwrap(), spawner));
|
spawner.must_spawn(renderbug_bike::tasks::ble::ble_task(ble, predictions.dyn_subscriber().unwrap(), spawner));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature="dual-core")]
|
spawner.must_spawn(print_sensor_status(predictions.dyn_subscriber().unwrap()));
|
||||||
|
|
||||||
|
let mut storage = SharedFlash::new(esp_storage::FlashStorage::new(peripherals.FLASH).multicore_auto_park());
|
||||||
|
let mut partition_buf = [8; 1024];
|
||||||
|
let partitions = esp_bootloader_esp_idf::partitions::read_partition_table(&mut storage, &mut partition_buf).unwrap();
|
||||||
|
|
||||||
|
#[cfg(any(feature="flash-recording", feature="simulation"))]
|
||||||
{
|
{
|
||||||
info!("Launching core 2 watchdog");
|
use renderbug_bike::tasks::simulation::{SimDataStream, SimDataTable};
|
||||||
let timer1 = TimerGroup::new(peripherals.TIMG1);
|
let sim_partition = SimDataTable::find_partition(storage, partitions).expect("Could not find sim data partition!");
|
||||||
let mut ui_wdt = timer1.wdt;
|
info!("Got partition: {sim_partition:?}");
|
||||||
ui_wdt.set_timeout(esp_hal::timer::timg::MwdtStage::Stage0, esp_hal::time::Duration::from_secs(10));
|
let mut sim_table = match SimDataTable::open(sim_partition.clone()) {
|
||||||
#[cfg(feature="dual-core")]
|
Ok(table) => table,
|
||||||
ui_wdt.enable();
|
Err(SimDataError::StreamIndexMissing) => {
|
||||||
spawner.must_spawn(wdt_task(ui_wdt));
|
info!("Sim data partition not formatted, creating new stream table with {sim_partition:?}");
|
||||||
}
|
SimDataTable::create(sim_partition).expect("Unable to create sim data stream table in partition!")
|
||||||
|
},
|
||||||
spawner.must_spawn(print_telemetry(predictions.dyn_subscriber().unwrap()));
|
Err(e) => panic!("Error opening sim data stream table: {e:?}")
|
||||||
|
|
||||||
info!("System is ready in {}ms", Instant::now().as_millis());
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match sim_table.next() {
|
||||||
|
Some((header, reader)) => {
|
||||||
|
let srcid = header.id;
|
||||||
|
info!("Found simulation data for {srcid:?} at {:#x}", reader.abs_start());
|
||||||
|
|
||||||
|
let stream = SimDataStream::open(reader, srcid);
|
||||||
|
|
||||||
|
if cfg!(feature="simulation") {
|
||||||
|
if spawner.spawn(renderbug_bike::tasks::simulation::simulation_task(stream, motion_bus.dyn_sender())).is_err() {
|
||||||
|
error!("Unable to spawn simulation task for {srcid:?}! Increase the task pool size.");
|
||||||
|
}
|
||||||
|
} else if cfg!(feature="flash-recording") && srcid == StreamType::Bundle {
|
||||||
|
info!("Continuing recording stream");
|
||||||
|
spawner.spawn(record_telemetry(recording_bus.dyn_subscriber().unwrap(), stream, motion_bus.dyn_sender())).unwrap();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None if cfg!(feature="flash-recording") => {
|
||||||
|
// If we already found a recording stream, we break; out of the loop above
|
||||||
|
let reader = sim_table.append_new_stream(StreamType::Bundle).expect("Unable to create a new recording stream in the sim data partition! Is there enough free space?");
|
||||||
|
warn!("Starting new recording stream at {:#x}", reader.abs_start());
|
||||||
|
let recorder = SimDataStream::create(reader, StreamType::Bundle);
|
||||||
|
spawner.spawn(record_telemetry(recording_bus.dyn_subscriber().unwrap(), recorder, motion_bus.dyn_sender())).unwrap();
|
||||||
|
break;
|
||||||
|
},
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
spawner.must_spawn(wdt_task(rtc, predictions.dyn_subscriber().unwrap(), oled_controls, display_controls));
|
||||||
|
|
||||||
|
//info!("Final memory stats: {}", esp_alloc::HEAP.stats());
|
||||||
|
|
||||||
|
info!("Ready to rock and roll in {}ms", Instant::now().as_millis());
|
||||||
|
};
|
||||||
|
|
||||||
|
#[allow(static_mut_refs)]
|
||||||
#[cfg(feature="dual-core")]
|
#[cfg(feature="dual-core")]
|
||||||
{
|
{
|
||||||
static mut CORE2_STACK: Stack<16384> = Stack::new();
|
use core::mem::MaybeUninit;
|
||||||
|
|
||||||
|
use esp_hal::interrupt::software::SoftwareInterruptControl;
|
||||||
|
use esp_hal::system::Stack;
|
||||||
|
use esp_rtos::embassy::Executor;
|
||||||
|
|
||||||
|
// We can be sneaky and stick the stack for the second core into the bootloader ram
|
||||||
|
#[esp_hal::ram(reclaimed)]
|
||||||
|
static mut CORE2_STACK: MaybeUninit<Stack<73744>> = MaybeUninit::uninit();
|
||||||
let swi = SoftwareInterruptControl::new(peripherals.SW_INTERRUPT);
|
let swi = SoftwareInterruptControl::new(peripherals.SW_INTERRUPT);
|
||||||
esp_rtos::start_second_core(peripherals.CPU_CTRL, swi.software_interrupt0, swi.software_interrupt1, unsafe { &mut *addr_of_mut!(CORE2_STACK) }, || {
|
// SAFETY: The internal implementation of Stack is itself MaybeUninit
|
||||||
|
esp_rtos::start_second_core(peripherals.CPU_CTRL, swi.software_interrupt0, swi.software_interrupt1, unsafe { CORE2_STACK.assume_init_mut() }, || {
|
||||||
info!("Second CPU core started");
|
info!("Second CPU core started");
|
||||||
static CORE2_EXEC: StaticCell<Executor> = StaticCell::new();
|
static CORE2_EXEC: StaticCell<Executor> = StaticCell::new();
|
||||||
let exec = CORE2_EXEC.init_with(|| { Executor::new() });
|
let exec = CORE2_EXEC.init_with(|| { Executor::new() });
|
||||||
@@ -231,7 +319,10 @@ async fn main(spawner: Spawner) {
|
|||||||
#[cfg(not(feature="dual-core"))]
|
#[cfg(not(feature="dual-core"))]
|
||||||
core2_main(spawner);
|
core2_main(spawner);
|
||||||
|
|
||||||
info!("Ready to rock and roll");
|
loop {
|
||||||
|
//info!("Memory stats: {}", esp_alloc::HEAP.stats());
|
||||||
|
Timer::after_secs(1).await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[embassy_executor::task]
|
#[embassy_executor::task]
|
||||||
@@ -246,12 +337,87 @@ async fn wdt_task(mut wdt: Wdt<esp_hal::peripherals::TIMG1<'static>>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[embassy_executor::task]
|
#[embassy_executor::task]
|
||||||
async fn print_telemetry(mut events: DynSubscriber<'static, Prediction>) {
|
async fn record_telemetry(mut firehose: DynSubscriber<'static, Measurement>, mut storage: SimDataStream<StorageRange<SharedFlash<FlashStorage<'static>>>>, motion: DynamicSender<'static, Measurement>) {
|
||||||
info!("telemetry ready");
|
let mut skipped_events = 0;
|
||||||
let mut num_events = Wrapping(0usize);
|
while let Ok(Some((_, evt))) = storage.read_next() {
|
||||||
|
trace!("Skipping event {evt:?}");
|
||||||
|
skipped_events += 1;
|
||||||
|
}
|
||||||
|
info!("Skipped {} events to catch up to the end of the recording stream", skipped_events);
|
||||||
|
motion.send(Measurement::SensorHardwareStatus(SensorSource::FlashRecording, SensorState::Online)).await;
|
||||||
|
storage.write_next(AnnotationReading { buf: *b"Telemetry recording started " }).unwrap();
|
||||||
loop {
|
loop {
|
||||||
let next = events.next_message_pure().await;
|
match firehose.next_message_pure().await {
|
||||||
trace!("idx={} predict={next:?}", num_events.0);
|
Measurement::IMU { accel, gyro } => {
|
||||||
num_events += 1;
|
let reading = IMUReading {
|
||||||
|
accel_x: accel.x as f64,
|
||||||
|
accel_y: accel.y as f64,
|
||||||
|
accel_z: accel.z as f64,
|
||||||
|
gyro_x: gyro.x as f64,
|
||||||
|
gyro_y: gyro.y as f64,
|
||||||
|
gyro_z: gyro.z as f64
|
||||||
|
};
|
||||||
|
//storage.write_next(reading).unwrap();
|
||||||
|
trace!("Wrote IMU to flash");
|
||||||
|
},
|
||||||
|
Measurement::GPS(Some(pos)) => {
|
||||||
|
storage.write_next(GPSReading {
|
||||||
|
lat: pos.x,
|
||||||
|
lon: pos.y,
|
||||||
|
}).unwrap();
|
||||||
|
trace!("Wrote GPS to flash");
|
||||||
|
},
|
||||||
|
Measurement::SensorHardwareStatus(sensor, status) => {
|
||||||
|
let annotation = alloc::format!("{:?}={:?}", sensor, status);
|
||||||
|
let mut buf = [0; 32];
|
||||||
|
let bytes = annotation.as_bytes();
|
||||||
|
let copy_len = bytes.len().min(buf.len());
|
||||||
|
buf[..copy_len].copy_from_slice(&bytes[..copy_len]);
|
||||||
|
if copy_len < buf.len() {
|
||||||
|
buf[copy_len..].fill(b' ');
|
||||||
|
}
|
||||||
|
storage.write_next(AnnotationReading { buf }).unwrap();
|
||||||
|
trace!("Wrote sensor status update to flash");
|
||||||
|
}
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[embassy_executor::task]
|
||||||
|
async fn print_sensor_status(mut events: DynSubscriber<'static, Prediction>) {
|
||||||
|
let mut sensor_states: EnumMap<SensorSource, SensorState> = EnumMap::default();
|
||||||
|
loop {
|
||||||
|
let next = events.next_message_pure().with_timeout(Duration::from_secs(5)).await;
|
||||||
|
match next {
|
||||||
|
Ok(Prediction::SensorStatus(sensor, status)) => {
|
||||||
|
sensor_states[sensor] = status;
|
||||||
|
let mut report_str = String::new();
|
||||||
|
for (sensor, state) in &sensor_states {
|
||||||
|
let state_icon = match state {
|
||||||
|
SensorState::AcquiringFix => "⏱",
|
||||||
|
SensorState::Degraded => "!",
|
||||||
|
SensorState::Offline => "✗",
|
||||||
|
SensorState::Online => "✔"
|
||||||
|
};
|
||||||
|
report_str += alloc::format!("{sensor:?}={state_icon} ").as_str();
|
||||||
|
}
|
||||||
|
info!("{report_str}");
|
||||||
|
},
|
||||||
|
Err(_) => {
|
||||||
|
let mut report_str = String::new();
|
||||||
|
for (sensor, state) in &sensor_states {
|
||||||
|
let state_icon = match state {
|
||||||
|
SensorState::AcquiringFix => "⏱",
|
||||||
|
SensorState::Degraded => "!",
|
||||||
|
SensorState::Offline => "✗",
|
||||||
|
SensorState::Online => "✔"
|
||||||
|
};
|
||||||
|
report_str += alloc::format!("{sensor:?}={state_icon} ").as_str();
|
||||||
|
}
|
||||||
|
info!("{report_str}");
|
||||||
|
},
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
#![no_std]
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
use embassy_executor::Spawner;
|
||||||
|
use esp_hal::clock::CpuClock;
|
||||||
|
use esp_hal::timer::systimer::SystemTimer;
|
||||||
|
use log::*;
|
||||||
|
use esp_backtrace as _;
|
||||||
|
|
||||||
|
use renderbug_bike::logging::RenderbugLogger;
|
||||||
|
use renderbug_bike::tasks::simulation::{SimDataStream, SimDataTable};
|
||||||
|
use renderbug_bike::simdata::*;
|
||||||
|
|
||||||
|
esp_bootloader_esp_idf::esp_app_desc!();
|
||||||
|
|
||||||
|
#[esp_rtos::main]
|
||||||
|
async fn main(spawner: Spawner) {
|
||||||
|
|
||||||
|
esp_alloc::heap_allocator!(size: 100000);
|
||||||
|
|
||||||
|
RenderbugLogger::init_logger();
|
||||||
|
|
||||||
|
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
|
||||||
|
let peripherals = esp_hal::init(config);
|
||||||
|
let sys_timer = SystemTimer::new(peripherals.SYSTIMER);
|
||||||
|
esp_rtos::start(sys_timer.alarm0);
|
||||||
|
|
||||||
|
let mut storage = renderbug_bike::storage::SharedFlash::new(esp_storage::FlashStorage::new(peripherals.FLASH).multicore_auto_park());
|
||||||
|
let mut partition_buf = [8; 1024];
|
||||||
|
let partitions = esp_bootloader_esp_idf::partitions::read_partition_table(&mut storage, &mut partition_buf).unwrap();
|
||||||
|
|
||||||
|
let sim_partition = SimDataTable::find_partition(storage, partitions).expect("Could not find sim data partition!");
|
||||||
|
info!("Got partition: {sim_partition:?}");
|
||||||
|
let sim_table = SimDataTable::open(sim_partition).expect("Sim data partition not found");
|
||||||
|
|
||||||
|
for (header, reader) in sim_table {
|
||||||
|
info!("Found stream {header:?}");
|
||||||
|
if header.id == StreamType::Bundle {
|
||||||
|
let mut stream = SimDataStream::open(reader, header.id);
|
||||||
|
let mut timestamp = 0;
|
||||||
|
loop {
|
||||||
|
match stream.read_next() {
|
||||||
|
Ok(Some((timecode, next_evt))) => {
|
||||||
|
timestamp += timecode.as_millis();
|
||||||
|
esp_println::println!("{timestamp} {next_evt:?}");
|
||||||
|
},
|
||||||
|
Ok(None) => {
|
||||||
|
warn!("End of simulation data stream");
|
||||||
|
break
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
warn!("Error during sensor stream: {err:?}");
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
#![no_std]
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
use embassy_executor::Spawner;
|
||||||
|
use esp_hal::clock::CpuClock;
|
||||||
|
use esp_hal::timer::systimer::SystemTimer;
|
||||||
|
use log::*;
|
||||||
|
use esp_backtrace as _;
|
||||||
|
|
||||||
|
use renderbug_bike::logging::RenderbugLogger;
|
||||||
|
use renderbug_bike::tasks::simulation::{SimDataStream, SimDataTable};
|
||||||
|
use renderbug_bike::simdata::*;
|
||||||
|
|
||||||
|
esp_bootloader_esp_idf::esp_app_desc!();
|
||||||
|
|
||||||
|
#[esp_rtos::main]
|
||||||
|
async fn main(spawner: Spawner) {
|
||||||
|
|
||||||
|
esp_alloc::heap_allocator!(size: 100000);
|
||||||
|
|
||||||
|
RenderbugLogger::init_logger();
|
||||||
|
|
||||||
|
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
|
||||||
|
let peripherals = esp_hal::init(config);
|
||||||
|
let sys_timer = SystemTimer::new(peripherals.SYSTIMER);
|
||||||
|
esp_rtos::start(sys_timer.alarm0);
|
||||||
|
|
||||||
|
let mut storage = renderbug_bike::storage::SharedFlash::new(esp_storage::FlashStorage::new(peripherals.FLASH));
|
||||||
|
let mut partition_buf = [8; 1024];
|
||||||
|
let partitions = esp_bootloader_esp_idf::partitions::read_partition_table(&mut storage, &mut partition_buf).unwrap();
|
||||||
|
|
||||||
|
let sim_partition = SimDataTable::find_partition(storage, partitions).expect("Could not find sim data partition!");
|
||||||
|
info!("Got partition: {sim_partition:?}");
|
||||||
|
SimDataTable::create(sim_partition).expect("Could not write empty sim partition");
|
||||||
|
error!("Overwrote sim data partition with a blank stream table");
|
||||||
|
}
|
||||||
+58
-44
@@ -1,5 +1,5 @@
|
|||||||
use embassy_sync::pubsub::DynPublisher;
|
use embassy_sync::pubsub::DynPublisher;
|
||||||
use embassy_time::{Duration, Instant};
|
use embassy_time::{Duration, Instant, WithTimeout};
|
||||||
use nalgebra::{Rotation3, Vector2, Vector3, ComplexField, RealField};
|
use nalgebra::{Rotation3, Vector2, Vector3, ComplexField, RealField};
|
||||||
use log::*;
|
use log::*;
|
||||||
|
|
||||||
@@ -28,8 +28,7 @@ pub struct BikeStates {
|
|||||||
reference_fix: Option<Vector2<f64>>, // The first GPS value, which is used to make sense out of the EKF output which is in meters offset from this point
|
reference_fix: Option<Vector2<f64>>, // The first GPS value, which is used to make sense out of the EKF output which is in meters offset from this point
|
||||||
|
|
||||||
// State switches
|
// State switches
|
||||||
has_down: Breaker<bool>,
|
has_motion_frame: Breaker<bool>,
|
||||||
has_forwards: Breaker<bool>,
|
|
||||||
motion_state: Breaker<MotionState>,
|
motion_state: Breaker<MotionState>,
|
||||||
acquiring_data: Breaker<bool>,
|
acquiring_data: Breaker<bool>,
|
||||||
// FIXME: pub
|
// FIXME: pub
|
||||||
@@ -43,10 +42,35 @@ pub struct BikeStates {
|
|||||||
sleep_timer: IdleClock,
|
sleep_timer: IdleClock,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Above this speed, the system should consider waking up from sleep mode
|
||||||
|
const BUMP_SPEED_NOISE_GATE: f32 = 0.1;
|
||||||
|
|
||||||
|
/// Above this speed, the system should be fully awake and ready to start moving around with purpose
|
||||||
|
const WAKEUP_SPEED_NOISE_GATE: f32 = 0.5;
|
||||||
|
|
||||||
|
/// Above this average speed, we are considered to be moving around with purpose
|
||||||
|
const MOVING_SPEED_NOISE_GATE: f32 = 1.0;
|
||||||
|
|
||||||
|
/// IMU readings of at least this value are considered valid motion. Below this value, the signal is considered noise while stationary
|
||||||
|
const MOTION_NOISE_GATE: f32 = 0.8;
|
||||||
|
|
||||||
|
/// If the system's calculated speed is increasing or decreasing by this value or more, we are accelerating/decelerating
|
||||||
|
const ACCELERATION_NOISE_GATE: f32 = 1.0;
|
||||||
|
|
||||||
|
/// When we get a heading via GPS, this determines how much weight that value has when blended into the system.
|
||||||
|
const GPS_HEADING_ALPHA_CORRECTION: f32 = 0.9;
|
||||||
|
|
||||||
|
const TIMEOUT: Duration = Duration::from_millis(3);
|
||||||
|
|
||||||
|
macro_rules! publish {
|
||||||
|
($predictions:expr, $prediction:expr) => {
|
||||||
|
$predictions.publish($prediction).with_timeout(TIMEOUT).await.expect("Could not commit IMU data in time. Is the prediction bus stalled?")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Debug for BikeStates {
|
impl Debug for BikeStates {
|
||||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
f.debug_struct("BikeStates")
|
f.debug_struct("BikeStates")
|
||||||
.field("has_down", &self.orientation.has_down())
|
|
||||||
.field("has_orientation", &self.orientation.is_ready())
|
.field("has_orientation", &self.orientation.is_ready())
|
||||||
.field("heading", &self.heading.heading())
|
.field("heading", &self.heading.heading())
|
||||||
.field("motion_state", &self.motion_state)
|
.field("motion_state", &self.motion_state)
|
||||||
@@ -71,7 +95,7 @@ impl BikeStates {
|
|||||||
Some(coords) => {
|
Some(coords) => {
|
||||||
if self.last_fix != coords {
|
if self.last_fix != coords {
|
||||||
let gps_heading = self.last_fix.angle(&coords);
|
let gps_heading = self.last_fix.angle(&coords);
|
||||||
self.heading.correct(gps_heading as f32, 0.9);
|
self.heading.correct(gps_heading as f32, GPS_HEADING_ALPHA_CORRECTION);
|
||||||
}
|
}
|
||||||
|
|
||||||
let delta = gps_to_local_meters_haversine(&coords, &gps_pos);
|
let delta = gps_to_local_meters_haversine(&coords, &gps_pos);
|
||||||
@@ -99,17 +123,15 @@ impl BikeStates {
|
|||||||
let heading_rotation = Rotation3::from_axis_angle(&Vector3::z_axis(), self.heading.heading());
|
let heading_rotation = Rotation3::from_axis_angle(&Vector3::z_axis(), self.heading.heading());
|
||||||
|
|
||||||
let enu_rotated = heading_rotation * body_accel;
|
let enu_rotated = heading_rotation * body_accel;
|
||||||
if body_accel.xy().magnitude() >= 0.8 {
|
if body_accel.xy().magnitude() >= MOTION_NOISE_GATE {
|
||||||
self.kf.predict(enu_rotated.xy(), body_gyro.z, dt);
|
self.kf.predict(enu_rotated.xy(), body_gyro.z, dt);
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, we are standing stationary and should insert accel=0 data into the model
|
// Otherwise, we are standing stationary and should insert accel=0 data into the model
|
||||||
self.kf.update_zupt();
|
self.kf.update_zupt();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.has_down.set(true);
|
|
||||||
|
|
||||||
if self.orientation.is_ready() {
|
if self.orientation.is_ready() {
|
||||||
self.has_forwards.set(true);
|
self.has_motion_frame.set(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -118,23 +140,18 @@ impl BikeStates {
|
|||||||
let last_motion = self.motion_state.value;
|
let last_motion = self.motion_state.value;
|
||||||
|
|
||||||
if let Some(true) = self.acquiring_data.read_tripped() {
|
if let Some(true) = self.acquiring_data.read_tripped() {
|
||||||
predictions.publish(Prediction::SensorStatus(SensorSource::ForwardsReference, SensorState::AcquiringFix)).await;
|
publish!(predictions, Prediction::SensorStatus(SensorSource::MotionFrame, SensorState::AcquiringFix));
|
||||||
predictions.publish(Prediction::SensorStatus(SensorSource::GravityReference, SensorState::AcquiringFix)).await;
|
publish!(predictions, Prediction::SensorStatus(SensorSource::Location, SensorState::AcquiringFix));
|
||||||
predictions.publish(Prediction::SensorStatus(SensorSource::Location, SensorState::AcquiringFix)).await;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(true) = self.has_down.read_tripped() {
|
if let Some(true) = self.has_motion_frame.read_tripped() {
|
||||||
predictions.publish(Prediction::SensorStatus(SensorSource::GravityReference, SensorState::Online)).await
|
publish!(predictions, Prediction::SensorStatus(SensorSource::MotionFrame, SensorState::Online));
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(true) = self.has_forwards.read_tripped() {
|
|
||||||
predictions.publish(Prediction::SensorStatus(SensorSource::ForwardsReference, SensorState::Online)).await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.has_gps_fix.read_tripped() {
|
match self.has_gps_fix.read_tripped() {
|
||||||
None => (),
|
None => (),
|
||||||
Some(true) => predictions.publish(Prediction::SensorStatus(SensorSource::Location, SensorState::Online)).await,
|
Some(true) => publish!(predictions, Prediction::SensorStatus(SensorSource::Location, SensorState::Online)),
|
||||||
Some(false) => predictions.publish(Prediction::SensorStatus(SensorSource::Location, SensorState::Degraded)).await,
|
Some(false) => publish!(predictions, Prediction::SensorStatus(SensorSource::Location, SensorState::Degraded)),
|
||||||
}
|
}
|
||||||
|
|
||||||
let est = self.kf.x;
|
let est = self.kf.x;
|
||||||
@@ -146,7 +163,7 @@ impl BikeStates {
|
|||||||
if let Some(pos) = position {
|
if let Some(pos) = position {
|
||||||
self.predicted_location.set(pos);
|
self.predicted_location.set(pos);
|
||||||
if let Some(pos) = self.predicted_location.read_tripped() {
|
if let Some(pos) = self.predicted_location.read_tripped() {
|
||||||
predictions.publish(Prediction::Location(pos)).await;
|
publish!(predictions, Prediction::Location(pos));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,45 +174,43 @@ impl BikeStates {
|
|||||||
self.speedo.insert(current_prediction);
|
self.speedo.insert(current_prediction);
|
||||||
// If the model has enough samples to report useful data, we can start analyzing the motion trends
|
// If the model has enough samples to report useful data, we can start analyzing the motion trends
|
||||||
if self.speedo.is_filled() {
|
if self.speedo.is_filled() {
|
||||||
let threshold = 1.0;
|
|
||||||
// Calculate if the velocity is increasing, decreasing, or mostly the same
|
// Calculate if the velocity is increasing, decreasing, or mostly the same
|
||||||
let trend = self.speedo.data().windows(2).map(|n| {
|
let trend = self.speedo.data().windows(2).map(|n| {
|
||||||
n[1] - n[0]
|
n[1] - n[0]
|
||||||
}).sum::<f32>();
|
}).sum::<f32>();
|
||||||
// Also grab the average velocity of the last few sample periods
|
// Also grab the average velocity of the last few sample periods
|
||||||
let mean = self.speedo.mean();
|
let average_speed = self.speedo.mean();
|
||||||
|
trace!("prediction={current_prediction:?} mean={average_speed}");
|
||||||
|
|
||||||
// Reported velocity is kept only to the first decimal, so we aren't spamming the system with floating point noise
|
// Reported velocity is kept only to the first decimal, so we aren't spamming the system with floating point noise
|
||||||
self.reported_velocity.set((mean * 10.0).trunc() / 10.0);
|
self.reported_velocity.set((average_speed * 10.0).trunc() / 10.0);
|
||||||
if let Some(reported) = self.reported_velocity.read_tripped() {
|
if let Some(reported) = self.reported_velocity.read_tripped() {
|
||||||
predictions.publish(Prediction::Velocity(reported)).await;
|
publish!(predictions, Prediction::Velocity(reported));
|
||||||
}
|
}
|
||||||
|
|
||||||
// We only want to wake up from sleep if our current velocity is obviously intentional, eg not a quick bump
|
// We only want to wake up from sleep if our current velocity is something like a bump
|
||||||
if mean > 0.5 {
|
if average_speed > BUMP_SPEED_NOISE_GATE && self.sleep_timer.wake() {
|
||||||
if self.sleep_timer.wake() {
|
|
||||||
warn!("Waking from sleep into idle mode");
|
warn!("Waking from sleep into idle mode");
|
||||||
predictions.publish(Prediction::SetPersonality(Personality::Waking)).await;
|
publish!(predictions, Prediction::SetPersonality(Personality::Waking));
|
||||||
predictions.publish(Prediction::SetPersonality(Personality::Parked)).await;
|
publish!(predictions, Prediction::SetPersonality(Personality::Parked));
|
||||||
}
|
}
|
||||||
// Here, we additionally release the parking brake if we are currently parked and reading some kind of significant movement
|
// Here, we additionally release the parking brake if we are currently parked and reading substantial movement
|
||||||
if self.parking_timer.wake() {
|
if average_speed > WAKEUP_SPEED_NOISE_GATE && self.parking_timer.wake() {
|
||||||
warn!("Disengaging parking brake");
|
warn!("Disengaging parking brake");
|
||||||
predictions.publish(Prediction::SetPersonality(Personality::Active)).await;
|
publish!(predictions, Prediction::SetPersonality(Personality::Active));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the total slope is more upwards than not, we are accelerating.
|
// If the total slope is more upwards than not, we are accelerating.
|
||||||
if trend >= threshold {
|
if trend >= ACCELERATION_NOISE_GATE {
|
||||||
self.motion_state.set(MotionState::Accelerating);
|
self.motion_state.set(MotionState::Accelerating);
|
||||||
self.steady_timer.wake();
|
self.steady_timer.wake();
|
||||||
} else if trend <= -threshold {
|
} else if trend <= -ACCELERATION_NOISE_GATE {
|
||||||
self.steady_timer.wake();
|
self.steady_timer.wake();
|
||||||
self.motion_state.set(MotionState::Decelerating);
|
self.motion_state.set(MotionState::Decelerating);
|
||||||
} else if self.steady_timer.check() && mean > 1.0 {
|
} else if self.steady_timer.check() && average_speed > MOVING_SPEED_NOISE_GATE {
|
||||||
// If we haven't changed our acceleration for a while, and we still have speed, we are moving at a steady pace
|
// If we haven't changed our acceleration for a while, and we still have speed, we are moving at a steady pace
|
||||||
self.motion_state.set(MotionState::Steady);
|
self.motion_state.set(MotionState::Steady);
|
||||||
} else if current_prediction <= 1.0 && mean <= 1.0 {
|
} else if current_prediction <= 1.0 && average_speed <= MOVING_SPEED_NOISE_GATE {
|
||||||
// If the average and instantaneous speed is rather low, we are probably stationary!
|
// If the average and instantaneous speed is rather low, we are probably stationary!
|
||||||
self.motion_state.set(MotionState::Stationary);
|
self.motion_state.set(MotionState::Stationary);
|
||||||
}
|
}
|
||||||
@@ -206,19 +221,19 @@ impl BikeStates {
|
|||||||
//debug!("state={state:?} trend={trend} mean={mean} v={v}");
|
//debug!("state={state:?} trend={trend} mean={mean} v={v}");
|
||||||
//if state != MotionState::Stationary {
|
//if state != MotionState::Stationary {
|
||||||
// warn!("Active due to motion");
|
// warn!("Active due to motion");
|
||||||
// predictions.publish(Prediction::SetPersonality(Personality::Active)).await;
|
// publish!(predictions, Prediction::SetPersonality(Personality::Active));
|
||||||
//}
|
//}
|
||||||
predictions.publish(Prediction::Motion { prev: last_motion, next: state }).await;
|
publish!(predictions, Prediction::Motion { prev: last_motion, next: state });
|
||||||
} else if self.motion_state.value == MotionState::Stationary {
|
} else if self.motion_state.value == MotionState::Stationary {
|
||||||
// Finally, if we are stationary, check our parking and sleep timers
|
// Finally, if we are stationary, check our parking and sleep timers
|
||||||
if self.parking_timer.check() {
|
if self.parking_timer.check() {
|
||||||
warn!("Engaging parking brake");
|
warn!("Engaging parking brake");
|
||||||
predictions.publish(Prediction::SetPersonality(Personality::Parked)).await;
|
publish!(predictions, Prediction::SetPersonality(Personality::Parked));
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.sleep_timer.check() {
|
if self.sleep_timer.check() {
|
||||||
warn!("Sleeping!");
|
warn!("Sleeping!");
|
||||||
predictions.publish(Prediction::SetPersonality(Personality::Sleeping)).await;
|
publish!(predictions, Prediction::SetPersonality(Personality::Sleeping));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -232,8 +247,7 @@ impl Default for BikeStates {
|
|||||||
last_stamp: Instant::now(),
|
last_stamp: Instant::now(),
|
||||||
speedo: Default::default(),
|
speedo: Default::default(),
|
||||||
heading: Default::default(),
|
heading: Default::default(),
|
||||||
has_down: Default::default(),
|
has_motion_frame: Default::default(),
|
||||||
has_forwards: Default::default(),
|
|
||||||
kf: Default::default(),
|
kf: Default::default(),
|
||||||
steady_timer: IdleClock::new(Duration::from_secs(3)),
|
steady_timer: IdleClock::new(Duration::from_secs(3)),
|
||||||
last_fix: Default::default(),
|
last_fix: Default::default(),
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ impl OrientationEstimator {
|
|||||||
self.forward = Some(Unit::new_unchecked(-unit_forward.into_inner()));
|
self.forward = Some(Unit::new_unchecked(-unit_forward.into_inner()));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
info!("Found forwards: {unit_forward:?} avg.norm={}", horiz.norm());
|
info!("Motion frame established with forwards={unit_forward:?} avg.norm={} down={:?} bias={:?}", horiz.norm(), self.down, self.sensor_bias);
|
||||||
self.forward = Some(unit_forward);
|
self.forward = Some(unit_forward);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+11
-4
@@ -3,6 +3,7 @@ use embassy_time::Duration;
|
|||||||
use enum_map::Enum;
|
use enum_map::Enum;
|
||||||
use enumset::EnumSetType;
|
use enumset::EnumSetType;
|
||||||
use figments::liber8tion::interpolate::Fract8;
|
use figments::liber8tion::interpolate::Fract8;
|
||||||
|
use figments_render::power::Milliwatts;
|
||||||
use nalgebra::{Vector2, Vector3};
|
use nalgebra::{Vector2, Vector3};
|
||||||
|
|
||||||
use crate::ego::engine::MotionState;
|
use crate::ego::engine::MotionState;
|
||||||
@@ -38,13 +39,15 @@ pub enum Measurement {
|
|||||||
GPS(Option<Vector2<f64>>),
|
GPS(Option<Vector2<f64>>),
|
||||||
// Accelerometer values in body frame where x=forwards
|
// Accelerometer values in body frame where x=forwards
|
||||||
IMU { accel: Vector3<f32>, gyro: Vector3<f32> },
|
IMU { accel: Vector3<f32>, gyro: Vector3<f32> },
|
||||||
|
// Power status
|
||||||
|
ExternalPower(Milliwatts),
|
||||||
|
|
||||||
// Hardware status updates
|
// Hardware status updates
|
||||||
SensorHardwareStatus(SensorSource, SensorState),
|
SensorHardwareStatus(SensorSource, SensorState),
|
||||||
|
|
||||||
// Simulation metadata updates
|
// Simulation metadata updates
|
||||||
SimulationProgress(SensorSource, Duration, Fract8),
|
SimulationProgress(SensorSource, Duration, Fract8),
|
||||||
Annotation
|
Annotation([u8; 32])
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
@@ -73,13 +76,16 @@ pub enum SensorSource {
|
|||||||
// Real hardware
|
// Real hardware
|
||||||
IMU,
|
IMU,
|
||||||
GPS,
|
GPS,
|
||||||
|
ExternalPower,
|
||||||
|
|
||||||
// Connectivity related
|
// Connectivity related
|
||||||
Wifi,
|
Wifi,
|
||||||
|
|
||||||
|
// Data processing/logging
|
||||||
|
FlashRecording,
|
||||||
|
|
||||||
// Fusion outputs
|
// Fusion outputs
|
||||||
GravityReference,
|
MotionFrame,
|
||||||
ForwardsReference,
|
|
||||||
Location,
|
Location,
|
||||||
Cloud,
|
Cloud,
|
||||||
|
|
||||||
@@ -97,7 +103,8 @@ impl From<StreamType> for SensorSource {
|
|||||||
match value {
|
match value {
|
||||||
StreamType::Annotations => Self::Annotations,
|
StreamType::Annotations => Self::Annotations,
|
||||||
StreamType::GPS => Self::GPS,
|
StreamType::GPS => Self::GPS,
|
||||||
StreamType::IMU => Self::IMU
|
StreamType::IMU => Self::IMU,
|
||||||
|
StreamType::Bundle => unimplemented!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
use core::cell::RefCell;
|
||||||
|
|
||||||
|
use alloc::sync::Arc;
|
||||||
|
use critical_section::Mutex;
|
||||||
|
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, signal::Signal};
|
||||||
|
use esp_hal::gpio::Input;
|
||||||
|
|
||||||
|
pub struct InterruptDispatch<'a, const COUNT: usize> {
|
||||||
|
interrupts: [PinInterrupt<'a>; COUNT]
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, const COUNT: usize> InterruptDispatch<'a, COUNT> {
|
||||||
|
pub fn new(interrupts: [PinInterrupt<'a>; COUNT]) -> Self {
|
||||||
|
Self { interrupts }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process_interrupts(&self) {
|
||||||
|
for interrupt in &self.interrupts {
|
||||||
|
interrupt.handle_interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct PinInterrupt<'a> {
|
||||||
|
pin: Arc<Mutex<RefCell<Input<'a>>>>,
|
||||||
|
signal: Arc<Signal<CriticalSectionRawMutex, esp_hal::gpio::Level>>,
|
||||||
|
event: esp_hal::gpio::Event
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> PinInterrupt<'a> {
|
||||||
|
pub fn new(pin: Input<'a>, event: esp_hal::gpio::Event) -> Self {
|
||||||
|
Self {
|
||||||
|
pin: Arc::new(Mutex::new(RefCell::new(pin))),
|
||||||
|
signal: Arc::new(Signal::new()),
|
||||||
|
event
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_interrupt(&self) {
|
||||||
|
critical_section::with(|cs| {
|
||||||
|
let locked = self.pin.borrow(cs);
|
||||||
|
let mut pin = locked.borrow_mut();
|
||||||
|
if pin.is_interrupt_set() {
|
||||||
|
pin.clear_interrupt();
|
||||||
|
self.signal.signal(pin.level());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn listen(&self) {
|
||||||
|
critical_section::with(|cs| {
|
||||||
|
let locked = self.pin.borrow(cs);
|
||||||
|
let mut pin = locked.borrow_mut();
|
||||||
|
pin.listen(self.event);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn wait_for_interrupt(&self) -> esp_hal::gpio::Level {
|
||||||
|
self.signal.wait().await
|
||||||
|
}
|
||||||
|
}
|
||||||
+101
-57
@@ -1,11 +1,11 @@
|
|||||||
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, signal::Signal, watch::{Receiver, Watch}};
|
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, signal::Signal, watch::{Receiver, Watch}};
|
||||||
use figments::prelude::*;
|
use figments::{liber8tion::interpolate::Fract8, prelude::*};
|
||||||
use core::{fmt::Debug, sync::atomic::{AtomicBool, AtomicU8}};
|
use portable_atomic::AtomicU32;
|
||||||
|
use core::{cell::RefCell, fmt::Debug, ops::Mul, sync::atomic::{AtomicBool, AtomicU8}};
|
||||||
use alloc::sync::Arc;
|
use alloc::sync::Arc;
|
||||||
|
|
||||||
//use super::{Output};
|
|
||||||
use figments_render::{
|
use figments_render::{
|
||||||
gamma::{GammaCurve, WithGamma}, output::{Brightness, GammaCorrected, Output, OutputAsync}, power::AsMilliwatts, smart_leds::PowerManagedWriter
|
gamma::{GammaCurve, WithGamma}, output::{Brightness, GammaCorrected, Output, OutputAsync}, power::{AsMilliwatts, Milliwatts}, smart_leds::PowerManagedWriter
|
||||||
};
|
};
|
||||||
use smart_leds::{SmartLedsWrite, SmartLedsWriteAsync};
|
use smart_leds::{SmartLedsWrite, SmartLedsWriteAsync};
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ pub const NUM_PIXELS: usize = 178;
|
|||||||
pub struct BikeOutput<T, Color> {
|
pub struct BikeOutput<T, Color> {
|
||||||
pixbuf: [Color; NUM_PIXELS],
|
pixbuf: [Color; NUM_PIXELS],
|
||||||
writer: PowerManagedWriter<T>,
|
writer: PowerManagedWriter<T>,
|
||||||
controls: DisplayControls
|
controls: DisplayControls<'static>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, Color> GammaCorrected for BikeOutput<T, Color> {
|
impl<T, Color> GammaCorrected for BikeOutput<T, Color> {
|
||||||
@@ -24,10 +24,10 @@ impl<T, Color> GammaCorrected for BikeOutput<T, Color> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<T, Color: Default + Copy> BikeOutput<T, Color> {
|
impl<T, Color: Default + Copy> BikeOutput<T, Color> {
|
||||||
pub fn new(target: T, max_mw: u32, controls: DisplayControls) -> Self {
|
pub fn new(target: T, controls: DisplayControls<'static>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
pixbuf: [Default::default(); NUM_PIXELS],
|
pixbuf: [Default::default(); NUM_PIXELS],
|
||||||
writer: PowerManagedWriter::new(target, max_mw),
|
writer: PowerManagedWriter::new(target, controls.max_power()),
|
||||||
controls
|
controls
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -37,38 +37,39 @@ impl<T, Color: Default + Copy> BikeOutput<T, Color> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T: SmartLedsWrite + 'a> Output<'a, SegmentSpace> for BikeOutput<T, T::Color> where T::Color: AsMilliwatts + Copy + Fract8Ops + WithGamma + Default + Debug + 'a + 'static {
|
impl<'a, T: SmartLedsWrite + 'a> Output<'a, SegmentSpace> for BikeOutput<T, T::Color> where T::Color: AsMilliwatts + Copy + Mul<Fract8, Output = T::Color> + WithGamma + Default + Debug + 'a + 'static {
|
||||||
type Error = T::Error;
|
type Error = T::Error;
|
||||||
|
|
||||||
type Controls = DisplayControls;
|
type Controls = DisplayControls<'static>;
|
||||||
|
|
||||||
fn commit(&mut self) -> Result<(), Self::Error> {
|
fn commit(&mut self) -> Result<(), Self::Error> {
|
||||||
self.writer.controls().set_brightness(self.controls.brightness());
|
self.writer.controls().set_brightness(self.controls.brightness());
|
||||||
self.writer.controls().set_on(self.controls.is_on());
|
self.writer.controls().set_on(self.controls.is_on());
|
||||||
|
self.writer.controls().set_max_power(self.controls.max_power());
|
||||||
critical_section::with(|_| {
|
critical_section::with(|_| {
|
||||||
self.writer.write(&self.pixbuf)
|
self.writer.write(&self.pixbuf)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn controls(&self) -> Option<&Self::Controls> {
|
fn controls(&mut self) -> Option<&mut Self::Controls> {
|
||||||
Some(&self.controls)
|
Some(&mut self.controls)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T: SmartLedsWriteAsync + 'a> OutputAsync<'a, SegmentSpace> for BikeOutput<T, T::Color> where T::Color: 'static + Copy + Fract8Ops + WithGamma + Default + Debug + AsMilliwatts + 'a {
|
impl<'a, T: SmartLedsWriteAsync + 'a> OutputAsync<'a, SegmentSpace> for BikeOutput<T, T::Color> where T::Color: 'static + Copy + Mul<Fract8, Output = T::Color> + WithGamma + Default + Debug + AsMilliwatts + 'a {
|
||||||
async fn commit_async(&mut self) -> Result<(), T::Error> where T: SmartLedsWriteAsync {
|
async fn commit_async(&mut self) -> Result<(), T::Error> where T: SmartLedsWriteAsync {
|
||||||
self.writer.controls().set_brightness(self.controls.brightness());
|
self.writer.controls().set_brightness(self.controls.brightness());
|
||||||
self.writer.controls().set_on(self.controls.is_on());
|
self.writer.controls().set_on(self.controls.is_on());
|
||||||
|
self.writer.controls().set_max_power(self.controls.max_power());
|
||||||
// TODO: We should grab the power used here and somehow feed it back into the telemetry layer, probably just via another atomic u32
|
// TODO: We should grab the power used here and somehow feed it back into the telemetry layer, probably just via another atomic u32
|
||||||
self.writer.write_async(&self.pixbuf).await
|
self.writer.write_async(&self.pixbuf).await
|
||||||
}
|
}
|
||||||
|
|
||||||
//type HardwarePixel = T::Color;
|
|
||||||
type Error = T::Error;
|
type Error = T::Error;
|
||||||
type Controls = DisplayControls;
|
type Controls = DisplayControls<'static>;
|
||||||
|
|
||||||
fn controls(&self) -> Option<&Self::Controls> {
|
fn controls(&mut self) -> Option<&mut Self::Controls> {
|
||||||
Some(&self.controls)
|
Some(&mut self.controls)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,7 +110,6 @@ impl<'a, T> Sample<'a, SegmentSpace> for [T; NUM_PIXELS] where T: 'static {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Default, Debug)]
|
#[derive(Clone, Copy, Default, Debug)]
|
||||||
pub struct SegmentSpace {}
|
pub struct SegmentSpace {}
|
||||||
impl CoordinateSpace for SegmentSpace {
|
impl CoordinateSpace for SegmentSpace {
|
||||||
@@ -162,99 +162,143 @@ pub struct Uniforms {
|
|||||||
pub primary_color: Hsv
|
pub primary_color: Hsv
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const DEFAULT_FPS: u8 = 60;
|
||||||
|
pub const LOW_POWER_FPS: u8 = 16;
|
||||||
|
|
||||||
struct ControlData {
|
struct ControlData {
|
||||||
on: AtomicBool,
|
on: AtomicBool,
|
||||||
brightness: AtomicU8
|
brightness: AtomicU8,
|
||||||
|
fps: AtomicU8,
|
||||||
|
max_power_mw: AtomicU32
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ControlData {
|
impl Default for ControlData {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
on: AtomicBool::new(true),
|
on: AtomicBool::new(true),
|
||||||
brightness: AtomicU8::new(255)
|
brightness: AtomicU8::new(255),
|
||||||
|
fps: AtomicU8::new(DEFAULT_FPS),
|
||||||
|
// We default to 500mA, because that's what we get over USB by default
|
||||||
|
max_power_mw: AtomicU32::new(500)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A watch that indicates whether or not the rendering engine is running. If the display is off or sleeping, this will be false.
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
static RENDER_IS_RUNNING: Watch<CriticalSectionRawMutex, bool, 7> = Watch::new();
|
pub enum RenderState {
|
||||||
|
On,
|
||||||
|
Off
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DisplayControlResources {
|
||||||
|
state_rx: Watch<CriticalSectionRawMutex, RenderState, 7>,
|
||||||
|
state_ack: Watch<CriticalSectionRawMutex, RenderState, 7>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DisplayControlResources {
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
state_rx: Watch::new_with(RenderState::On),
|
||||||
|
state_ack: Watch::new_with(RenderState::On)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Implement something similar for a system-wide sleep mechanism
|
// TODO: Implement something similar for a system-wide sleep mechanism
|
||||||
pub struct DisplayControls {
|
pub struct DisplayControls<'a> {
|
||||||
data: Arc<ControlData>,
|
data: Arc<ControlData>,
|
||||||
display_is_on: Arc<Signal<CriticalSectionRawMutex, bool>>,
|
render_run_receiver: Receiver<'a, CriticalSectionRawMutex, RenderState, 7>,
|
||||||
render_run_receiver: Receiver<'static, CriticalSectionRawMutex, bool, 7>
|
render_ack_receiver: Receiver<'a, CriticalSectionRawMutex, RenderState, 7>,
|
||||||
|
resources: &'a DisplayControlResources,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for DisplayControls {
|
impl Clone for DisplayControls<'_> {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
data: Arc::clone(&self.data),
|
data: Arc::clone(&self.data),
|
||||||
display_is_on: Arc::clone(&self.display_is_on),
|
render_run_receiver: self.resources.state_rx.receiver().expect("Could not create enough render running receivers"),
|
||||||
render_run_receiver: RENDER_IS_RUNNING.receiver().expect("Could not create enough render running receivers")
|
resources: self.resources,
|
||||||
|
render_ack_receiver: self.resources.state_ack.receiver().expect("Could not create enough render ack receivers"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DisplayControls {
|
|
||||||
|
impl<'a> DisplayControls<'a> {
|
||||||
|
pub fn new(resources: &'a DisplayControlResources) -> Self {
|
||||||
|
Self {
|
||||||
|
data: Default::default(),
|
||||||
|
render_run_receiver: resources.state_rx.receiver().expect("Could not create enough render running receivers"),
|
||||||
|
render_ack_receiver: resources.state_ack.receiver().expect("Could not create enough render ack receivers"),
|
||||||
|
resources
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_on(&self) -> bool {
|
pub fn is_on(&self) -> bool {
|
||||||
self.data.on.load(core::sync::atomic::Ordering::Relaxed)
|
self.data.on.load(core::sync::atomic::Ordering::Relaxed)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn brightness(&self) -> u8 {
|
pub fn brightness(&self) -> Fract8 {
|
||||||
self.data.brightness.load(core::sync::atomic::Ordering::Relaxed)
|
Fract8::from_raw(self.data.brightness.load(core::sync::atomic::Ordering::Relaxed))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn wait_until_display_is_on(&self) {
|
pub fn fps(&self) -> u8 {
|
||||||
while !self.display_is_on.wait().await { log::info!("wait for display") }
|
self.data.fps.load(core::sync::atomic::Ordering::Relaxed)
|
||||||
log::trace!("display says on!");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn notify_render_is_running(&mut self, value: bool) {
|
pub fn set_fps(&self, value: u8) {
|
||||||
log::trace!("render is running!");
|
self.data.fps.store(value, core::sync::atomic::Ordering::Relaxed);
|
||||||
RENDER_IS_RUNNING.sender().send(value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn wait_until_render_is_running(&mut self) {
|
pub fn set_max_power(&mut self, mw: Milliwatts) {
|
||||||
while !self.render_run_receiver.changed().await { log::info!("wait for render run") }
|
self.data.max_power_mw.store(mw.0, core::sync::atomic::Ordering::Relaxed);
|
||||||
log::trace!("render says run!");
|
}
|
||||||
|
|
||||||
|
pub fn max_power(&self) -> Milliwatts {
|
||||||
|
Milliwatts(self.data.max_power_mw.load(core::sync::atomic::Ordering::Relaxed))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn wait_for_state(&mut self, state: RenderState) {
|
||||||
|
self.render_run_receiver.get_and(|cur| { *cur == state }).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn wait_for_ack(&mut self, state: RenderState) {
|
||||||
|
self.render_ack_receiver.get_and(|cur| { *cur == state }).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Indicates the current state has been applied to the hardware, which can wake up tasks waiting for the power to turn on/off.
|
||||||
|
pub async fn ack(&mut self) {
|
||||||
|
let next_state = self.render_run_receiver.get().await;
|
||||||
|
let is_on = next_state == RenderState::On;
|
||||||
|
self.data.on.store(is_on, core::sync::atomic::Ordering::Relaxed);
|
||||||
|
self.resources.state_ack.sender().send(if self.is_on() { RenderState::On } else { RenderState::Off });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GammaCorrected for DisplayControls {
|
impl GammaCorrected for DisplayControls<'_> {
|
||||||
fn set_gamma(&mut self, _gamma: GammaCurve) {
|
fn set_gamma(&mut self, _gamma: GammaCurve) {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Brightness for DisplayControls {
|
impl Brightness for DisplayControls<'_> {
|
||||||
fn set_brightness(&mut self, brightness: u8) {
|
fn set_brightness(&mut self, brightness: Fract8) {
|
||||||
self.data.brightness.store(brightness, core::sync::atomic::Ordering::Relaxed);
|
self.data.brightness.store(brightness.to_raw(), core::sync::atomic::Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_on(&mut self, is_on: bool) {
|
fn set_on(&mut self, is_on: bool) {
|
||||||
self.data.on.store(is_on, core::sync::atomic::Ordering::Relaxed);
|
self.resources.state_rx.sender().send(if is_on { RenderState::On } else { RenderState::Off });
|
||||||
log::trace!("display is on {is_on}");
|
log::trace!("display is on {is_on}");
|
||||||
self.display_is_on.signal(is_on);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl core::fmt::Debug for DisplayControls {
|
impl core::fmt::Debug for DisplayControls<'_> {
|
||||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
|
||||||
f.debug_struct("DisplayControls")
|
f.debug_struct("DisplayControls")
|
||||||
.field("on", &self.data.on)
|
.field("on", &self.data.on)
|
||||||
.field("brightness", &self.data.brightness)
|
.field("brightness", &self.data.brightness)
|
||||||
.field("render_pause_signaled", &self.display_is_on.signaled())
|
.field("fps", &self.data.fps)
|
||||||
|
.field("max_power_mw", &self.data.max_power_mw)
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for DisplayControls {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
data: Default::default(),
|
|
||||||
display_is_on: Default::default(),
|
|
||||||
render_run_receiver: RENDER_IS_RUNNING.receiver().unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,6 +4,8 @@ pub mod shaders;
|
|||||||
pub mod oled_ui;
|
pub mod oled_ui;
|
||||||
|
|
||||||
mod images {
|
mod images {
|
||||||
|
#![allow(unused)]
|
||||||
|
|
||||||
use embedded_graphics::{
|
use embedded_graphics::{
|
||||||
image::ImageRaw,
|
image::ImageRaw,
|
||||||
pixelcolor::BinaryColor
|
pixelcolor::BinaryColor
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ use embedded_graphics::primitives::{Line, PrimitiveStyle, PrimitiveStyleBuilder,
|
|||||||
use embedded_graphics::text::{Alignment, Text};
|
use embedded_graphics::text::{Alignment, Text};
|
||||||
use embedded_graphics::{image::Image, prelude::Point, Drawable};
|
use embedded_graphics::{image::Image, prelude::Point, Drawable};
|
||||||
use enum_map::EnumMap;
|
use enum_map::EnumMap;
|
||||||
use figments::liber8tion::trig::sin8;
|
use figments::liber8tion::trig::Trig8;
|
||||||
use figments::mappings::embedded_graphics::Matrix2DSpace;
|
use figments::mappings::embedded_graphics::Matrix2DSpace;
|
||||||
use figments::{liber8tion::trig::cos8, mappings::embedded_graphics::EmbeddedGraphicsSampler};
|
use figments::{mappings::embedded_graphics::EmbeddedGraphicsSampler};
|
||||||
use figments::prelude::*;
|
use figments::prelude::*;
|
||||||
use nalgebra::Vector2;
|
use nalgebra::Vector2;
|
||||||
use micromath::F32Ext;
|
use micromath::F32Ext;
|
||||||
@@ -108,7 +108,7 @@ const SENSOR_IMAGES: &[SensorImage] = &[
|
|||||||
on: &images::IMU_ON, off: &images::IMU_OFF,
|
on: &images::IMU_ON, off: &images::IMU_OFF,
|
||||||
},
|
},
|
||||||
SensorImage {
|
SensorImage {
|
||||||
source: SensorSource::GravityReference,
|
source: SensorSource::MotionFrame,
|
||||||
on: &images::GRAVITY_LOCATED, off: &images::GRAVITY_MISSING,
|
on: &images::GRAVITY_LOCATED, off: &images::GRAVITY_MISSING,
|
||||||
},
|
},
|
||||||
SensorImage {
|
SensorImage {
|
||||||
@@ -145,9 +145,9 @@ impl Screen {
|
|||||||
Image::new(&images::BOOT_LOGO, Point::zero()).draw(sampler).unwrap();
|
Image::new(&images::BOOT_LOGO, Point::zero()).draw(sampler).unwrap();
|
||||||
const SPARKLE_COUNT: i32 = 8;
|
const SPARKLE_COUNT: i32 = 8;
|
||||||
for n in 0..SPARKLE_COUNT {
|
for n in 0..SPARKLE_COUNT {
|
||||||
let sparkle_center = Point::new(128u8.scale8(cos8(state.frame.wrapping_mul(n as usize))) as i32, 64u8.scale8(sin8(state.frame.wrapping_mul(n as usize) as u8)) as i32);
|
let sparkle_center = Point::new((128u8 * state.frame.wrapping_mul(n as usize).cos8()) as i32, (64u8 * state.frame.wrapping_mul(n as usize).sin8()) as i32);
|
||||||
let offset = (state.frame / 2 % 32) as i32 - 16;
|
let offset = (state.frame / 2 % 32) as i32 - 16;
|
||||||
let rotation = PI * 2.0 * (sin8(state.frame) as f32 / 255.0);
|
let rotation = PI * 2.0 * state.frame.sin8();
|
||||||
let normal = Point::new((rotation.cos() * offset as f32) as i32, (rotation.sin() * offset as f32) as i32);
|
let normal = Point::new((rotation.cos() * offset as f32) as i32, (rotation.sin() * offset as f32) as i32);
|
||||||
let cross_normal = Point::new((rotation.sin() * offset as f32) as i32, (rotation.cos() * offset as f32) as i32);
|
let cross_normal = Point::new((rotation.sin() * offset as f32) as i32, (rotation.cos() * offset as f32) as i32);
|
||||||
// Draw horizontal
|
// Draw horizontal
|
||||||
|
|||||||
+19
-18
@@ -1,6 +1,7 @@
|
|||||||
use core::cmp::max;
|
use core::cmp::max;
|
||||||
|
|
||||||
use figments::{liber8tion::{interpolate::{ease_in_out_quad}, noise::inoise8, trig::{cos8, sin8}}, prelude::*};
|
use figments::{liber8tion::{interpolate::{Fract8, ease_in_out_quad}, noise::inoise8, trig::Trig8}, prelude::*};
|
||||||
|
use num_traits::WrappingAdd;
|
||||||
use rgb::Rgba;
|
use rgb::Rgba;
|
||||||
|
|
||||||
use crate::graphics::display::{SegmentSpace, Uniforms};
|
use crate::graphics::display::{SegmentSpace, Uniforms};
|
||||||
@@ -25,8 +26,8 @@ impl Shader<Uniforms, SegmentSpace, Rgba<u8>> for Movement {
|
|||||||
} else {
|
} else {
|
||||||
uniforms.frame.wrapping_sub(surface_coords.x)
|
uniforms.frame.wrapping_sub(surface_coords.x)
|
||||||
};
|
};
|
||||||
let idx = sin8(offset).wrapping_add(uniforms.primary_color.hue);
|
let idx = offset.sin8().wrapping_add(&Fract8::from_raw(uniforms.primary_color.hue));
|
||||||
Rgba::new(idx, idx.wrapping_mul(2), idx.wrapping_div(2), 128)
|
Rgba::new(idx.to_raw(), idx.to_raw().wrapping_mul(2), idx.to_raw() / 2, 128)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,12 +46,12 @@ impl Background {
|
|||||||
|
|
||||||
impl Shader<Uniforms, SegmentSpace, Rgba<u8>> for Background {
|
impl Shader<Uniforms, SegmentSpace, Rgba<u8>> for Background {
|
||||||
fn draw(&self, coords: &Coordinates<SegmentSpace>, uniforms: &Uniforms) -> Rgba<u8> {
|
fn draw(&self, coords: &Coordinates<SegmentSpace>, uniforms: &Uniforms) -> Rgba<u8> {
|
||||||
let noise_x = sin8(uniforms.frame % 255) as i16;
|
let noise_x = (uniforms.frame % 255).sin8().to_raw() as i16;
|
||||||
let noise_y = cos8(uniforms.frame % 255) as i16;
|
let noise_y = (uniforms.frame % 255).cos8().to_raw() as i16;
|
||||||
let brightness = inoise8(noise_x.wrapping_add(coords.x as i16), noise_y.wrapping_add(coords.y as i16));
|
let brightness = inoise8(noise_x.wrapping_add(coords.x as i16), noise_y.wrapping_add(coords.y as i16));
|
||||||
let saturation = inoise8(noise_y.wrapping_add(coords.y as i16), noise_x.wrapping_add(coords.x as i16));
|
let saturation = inoise8(noise_y.wrapping_add(coords.y as i16), noise_x.wrapping_add(coords.x as i16));
|
||||||
let rgb: Rgb<u8> = match self.color {
|
let rgb: Rgb<u8> = match self.color {
|
||||||
None => Hsv::new(uniforms.primary_color.hue, max(128, saturation), brightness).into(),
|
None => Hsv::new(uniforms.primary_color.hue, max(128, saturation.to_raw()), brightness.to_raw()).into(),
|
||||||
Some(c) => c
|
Some(c) => c
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -62,9 +63,9 @@ impl Shader<Uniforms, SegmentSpace, Rgba<u8>> for Background {
|
|||||||
pub struct Tail {}
|
pub struct Tail {}
|
||||||
impl Shader<Uniforms, SegmentSpace, Rgba<u8>> for Tail {
|
impl Shader<Uniforms, SegmentSpace, Rgba<u8>> for Tail {
|
||||||
fn draw(&self, coords: &Coordinates<SegmentSpace>, uniforms: &Uniforms) -> Rgba<u8> {
|
fn draw(&self, coords: &Coordinates<SegmentSpace>, uniforms: &Uniforms) -> Rgba<u8> {
|
||||||
let hue_offset: u8 = 32.scale8(sin8(uniforms.frame.wrapping_sub(coords.x)));
|
let hue_offset: u8 = 32u8 * Fract8::from_raw(uniforms.frame.wrapping_sub(coords.x) as u8);
|
||||||
let value = max(30, inoise8(coords.x.wrapping_add(uniforms.frame) as i16, coords.y.wrapping_add(uniforms.frame) as i16));
|
let value = max(30, inoise8(coords.x.wrapping_add(uniforms.frame) as i16, coords.y.wrapping_add(uniforms.frame) as i16).to_raw());
|
||||||
Hsv::new(uniforms.primary_color.hue.wrapping_sub(hue_offset), max(210, sin8(uniforms.frame.wrapping_add(coords.x))), value).into()
|
Hsv::new(uniforms.primary_color.hue.wrapping_sub(hue_offset), max(210, uniforms.frame.wrapping_add(coords.x).sin8().to_raw()), value).into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,10 +97,10 @@ impl Shader<Uniforms, SegmentSpace, Rgba<u8>> for Brakelight {
|
|||||||
let distance_from_end = Self::end() - coords.x;
|
let distance_from_end = Self::end() - coords.x;
|
||||||
|
|
||||||
if distance_from_end < Self::safety_length() {
|
if distance_from_end < Self::safety_length() {
|
||||||
Rgba::new(max(128, sin8(uniforms.frame.wrapping_sub(coords.x))), 0, 0, 255)
|
Rgba::new(max(128, uniforms.frame.wrapping_sub(coords.x).sin8().to_raw()), 0, 0, 255)
|
||||||
} else {
|
} else {
|
||||||
let pct = (distance_from_end as f32 / Self::length() as f32) * 255f32;
|
let pct = (distance_from_end as f32 / Self::length() as f32) * 255f32;
|
||||||
Rgba::new(max(100, ease_in_out_quad((pct as u8).wrapping_add(uniforms.frame as u8))), 0, 0, ease_in_out_quad(255 - pct as u8))
|
Rgba::new(max(100, ease_in_out_quad(Fract8::from_raw(pct as u8).wrapping_add(&Fract8::from_raw(uniforms.frame as u8))).to_raw()), 0, 0, ease_in_out_quad(Fract8::from_raw(255 - pct as u8)).to_raw())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,7 +109,7 @@ impl Shader<Uniforms, SegmentSpace, Rgba<u8>> for Brakelight {
|
|||||||
pub struct Headlight {}
|
pub struct Headlight {}
|
||||||
impl Shader<Uniforms, SegmentSpace, Rgba<u8>> for Headlight {
|
impl Shader<Uniforms, SegmentSpace, Rgba<u8>> for Headlight {
|
||||||
fn draw(&self, coords: &Coordinates<SegmentSpace>, uniforms: &Uniforms) -> Rgba<u8> {
|
fn draw(&self, coords: &Coordinates<SegmentSpace>, uniforms: &Uniforms) -> Rgba<u8> {
|
||||||
Hsv::new(0, 0, max(130, ease_in_out_quad(sin8(uniforms.frame.wrapping_sub(coords.x))))).into()
|
Hsv::new(0, 0, max(130, ease_in_out_quad(uniforms.frame.wrapping_sub(coords.x).sin8()).to_raw())).into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,14 +117,14 @@ impl Shader<Uniforms, SegmentSpace, Rgba<u8>> for Headlight {
|
|||||||
pub struct Panel {}
|
pub struct Panel {}
|
||||||
impl Shader<Uniforms, SegmentSpace, Rgba<u8>> for Panel {
|
impl Shader<Uniforms, SegmentSpace, Rgba<u8>> for Panel {
|
||||||
fn draw(&self, coords: &Coordinates<SegmentSpace>, uniforms: &Uniforms) -> Rgba<u8> {
|
fn draw(&self, coords: &Coordinates<SegmentSpace>, uniforms: &Uniforms) -> Rgba<u8> {
|
||||||
let noise_offset = max(180, inoise8(coords.x.wrapping_add(uniforms.frame) as i16, coords.y.wrapping_add(uniforms.frame) as i16));
|
let noise_offset = max(180, inoise8(coords.x.wrapping_add(uniforms.frame) as i16, coords.y.wrapping_add(uniforms.frame) as i16).to_raw());
|
||||||
let pct = (coords.x as f32 / 18f32) * 255f32;
|
let pct = (coords.x as f32 / 18f32) * 255f32;
|
||||||
let shift = match coords.y {
|
let shift = match coords.y {
|
||||||
1..=2 => 106, // 150 degrees
|
1..=2 => 106, // 150 degrees
|
||||||
3..=4 => 148, // 210 degrees
|
3..=4 => 148, // 210 degrees
|
||||||
_ => 0
|
_ => 0
|
||||||
};
|
};
|
||||||
Hsv::new(uniforms.primary_color.hue.wrapping_add(shift), noise_offset, max(100, sin8(pct as u8).wrapping_add(uniforms.frame as u8))).into()
|
Hsv::new(uniforms.primary_color.hue.wrapping_add(shift), noise_offset, max(100, pct.sin8().wrapping_add(&Fract8::from_raw(uniforms.frame as u8)).to_raw())).into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,15 +135,15 @@ impl Shader<Uniforms, SegmentSpace, Rgba<u8>> for Thinking {
|
|||||||
fn draw(&self, coords: &Coordinates<SegmentSpace>, uniforms: &Uniforms) -> Rgba<u8> {
|
fn draw(&self, coords: &Coordinates<SegmentSpace>, uniforms: &Uniforms) -> Rgba<u8> {
|
||||||
//let noise_x = sin8(sin8((frame % 255) as u8).wrapping_add(coords.x));
|
//let noise_x = sin8(sin8((frame % 255) as u8).wrapping_add(coords.x));
|
||||||
//let noise_y = cos8(cos8((frame % 255) as u8).wrapping_add(coords.y));
|
//let noise_y = cos8(cos8((frame % 255) as u8).wrapping_add(coords.y));
|
||||||
let offset_x = sin8(uniforms.frame.wrapping_add(coords.x));
|
let offset_x = uniforms.frame.wrapping_add(coords.x).sin8();
|
||||||
let offset_y = cos8(uniforms.frame.wrapping_add(coords.y));
|
let offset_y = uniforms.frame.wrapping_add(coords.y).cos8();
|
||||||
let noise_x = offset_x / 2;
|
let noise_x = offset_x / 2;
|
||||||
let noise_y = offset_y / 2;
|
let noise_y = offset_y / 2;
|
||||||
//let noise_x = coords.x.wrapping_add(offset_x);
|
//let noise_x = coords.x.wrapping_add(offset_x);
|
||||||
//let noise_y = coords.y.wrapping_add(offset_y);
|
//let noise_y = coords.y.wrapping_add(offset_y);
|
||||||
Hsv::new(
|
Hsv::new(
|
||||||
inoise8(offset_x as i16, offset_y as i16),
|
inoise8(offset_x.to_raw() as i16, offset_y.to_raw() as i16).to_raw(),
|
||||||
128_u8.saturating_add(inoise8(noise_y.into(), noise_x.into())),
|
128_u8.saturating_add(inoise8(noise_y.to_raw().into(), noise_x.to_raw().into()).to_raw()),
|
||||||
255
|
255
|
||||||
).into()
|
).into()
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-12
@@ -4,7 +4,7 @@ use core::{cmp::min, ops::{BitAnd, Shr}};
|
|||||||
use display_interface::DisplayError;
|
use display_interface::DisplayError;
|
||||||
use embedded_graphics::prelude::*;
|
use embedded_graphics::prelude::*;
|
||||||
use esp_hal::{gpio::Output, i2c::master::I2c, Async};
|
use esp_hal::{gpio::Output, i2c::master::I2c, Async};
|
||||||
use figments::{liber8tion::{interpolate::Fract8, noise}, mappings::embedded_graphics::Matrix2DSpace, prelude::*};
|
use figments::{liber8tion::{interpolate::Fract8}, mappings::embedded_graphics::Matrix2DSpace, prelude::*};
|
||||||
use embedded_graphics::pixelcolor::BinaryColor;
|
use embedded_graphics::pixelcolor::BinaryColor;
|
||||||
use figments::pixels::AdditivePixelSink;
|
use figments::pixels::AdditivePixelSink;
|
||||||
use figments_render::output::OutputAsync;
|
use figments_render::output::OutputAsync;
|
||||||
@@ -59,10 +59,10 @@ const DITHER_MAP: [u16;15] = [
|
|||||||
impl AdditivePixelSink<BinaryColor> for SsdPixel {
|
impl AdditivePixelSink<BinaryColor> for SsdPixel {
|
||||||
fn add(&mut self, pixel: BinaryColor, opacity: Fract8) {
|
fn add(&mut self, pixel: BinaryColor, opacity: Fract8) {
|
||||||
match opacity {
|
match opacity {
|
||||||
0 => (),
|
Fract8::MIN => (),
|
||||||
255 => self.set_pixel(pixel),
|
Fract8::MAX => self.set_pixel(pixel),
|
||||||
_ => {
|
_ => {
|
||||||
let dither_value = DITHER_MAP[opacity as usize / 17];
|
let dither_value = DITHER_MAP[opacity.to_raw() as usize / 17];
|
||||||
let dither_x = self.coords.x % 4;
|
let dither_x = self.coords.x % 4;
|
||||||
let dither_y = self.coords.y % 4;
|
let dither_y = self.coords.y % 4;
|
||||||
if dither_value.shr(dither_x).shr(dither_y * 4).bitand(0x01) == 1 {
|
if dither_value.shr(dither_x).shr(dither_y * 4).bitand(0x01) == 1 {
|
||||||
@@ -106,9 +106,9 @@ impl<'a> Iterator for SsdSampler<'a> {
|
|||||||
pub struct SsdOutput {
|
pub struct SsdOutput {
|
||||||
pixbuf: [u8; 128 * 64 / 8],
|
pixbuf: [u8; 128 * 64 / 8],
|
||||||
target: ssd1306::Ssd1306Async<ssd1306::prelude::I2CInterface<esp_hal::i2c::master::I2c<'static, esp_hal::Async>>, ssd1306::prelude::DisplaySize128x64, ssd1306::mode::BasicMode>,
|
target: ssd1306::Ssd1306Async<ssd1306::prelude::I2CInterface<esp_hal::i2c::master::I2c<'static, esp_hal::Async>>, ssd1306::prelude::DisplaySize128x64, ssd1306::mode::BasicMode>,
|
||||||
controls: DisplayControls,
|
controls: DisplayControls<'static>,
|
||||||
is_on: bool,
|
is_on: bool,
|
||||||
last_brightness: u8,
|
last_brightness: Fract8,
|
||||||
reset_pin: Output<'static>
|
reset_pin: Output<'static>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,7 +124,7 @@ impl SsdOutput {
|
|||||||
Some(SsdPixel { byte: pixref, bit, coords })
|
Some(SsdPixel { byte: pixref, bit, coords })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn new(i2c: I2c<'static, Async>, reset_pin: Output<'static>, controls: DisplayControls) -> Self {
|
pub async fn new(i2c: I2c<'static, Async>, reset_pin: Output<'static>, controls: DisplayControls<'static>) -> Self {
|
||||||
let interface = I2CDisplayInterface::new(i2c);
|
let interface = I2CDisplayInterface::new(i2c);
|
||||||
let target = Ssd1306Async::new(interface, DisplaySize128x64, DisplayRotation::Rotate0);
|
let target = Ssd1306Async::new(interface, DisplaySize128x64, DisplayRotation::Rotate0);
|
||||||
|
|
||||||
@@ -132,7 +132,7 @@ impl SsdOutput {
|
|||||||
pixbuf: [0; 128 * 64 / 8],
|
pixbuf: [0; 128 * 64 / 8],
|
||||||
target,
|
target,
|
||||||
controls,
|
controls,
|
||||||
last_brightness: 255,
|
last_brightness: Fract8::MAX,
|
||||||
is_on: true,
|
is_on: true,
|
||||||
reset_pin
|
reset_pin
|
||||||
}
|
}
|
||||||
@@ -221,7 +221,7 @@ impl OriginDimensions for SsdOutput {
|
|||||||
impl<'a> OutputAsync<'a, Matrix2DSpace> for SsdOutput {
|
impl<'a> OutputAsync<'a, Matrix2DSpace> for SsdOutput {
|
||||||
type Error = DisplayError;
|
type Error = DisplayError;
|
||||||
|
|
||||||
type Controls = DisplayControls;
|
type Controls = DisplayControls<'static>;
|
||||||
|
|
||||||
async fn commit_async(&mut self) -> Result<(), Self::Error> {
|
async fn commit_async(&mut self) -> Result<(), Self::Error> {
|
||||||
let new_brightness = self.controls.brightness();
|
let new_brightness = self.controls.brightness();
|
||||||
@@ -231,14 +231,14 @@ impl<'a> OutputAsync<'a, Matrix2DSpace> for SsdOutput {
|
|||||||
self.is_on = new_power;
|
self.is_on = new_power;
|
||||||
}
|
}
|
||||||
if self.last_brightness != new_brightness {
|
if self.last_brightness != new_brightness {
|
||||||
self.target.set_brightness(ssd1306::prelude::Brightness::custom(1, new_brightness)).await.unwrap();
|
self.target.set_brightness(ssd1306::prelude::Brightness::custom(1, new_brightness.to_raw())).await.unwrap();
|
||||||
self.last_brightness = new_brightness;
|
self.last_brightness = new_brightness;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.target.draw(&self.pixbuf).await
|
self.target.draw(&self.pixbuf).await
|
||||||
}
|
}
|
||||||
|
|
||||||
fn controls(&self) -> Option<&Self::Controls> {
|
fn controls(&mut self) -> Option<&mut Self::Controls> {
|
||||||
Some(&self.controls)
|
Some(&mut self.controls)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+3
-4
@@ -8,12 +8,11 @@ pub mod animation;
|
|||||||
pub mod idle;
|
pub mod idle;
|
||||||
pub mod logging;
|
pub mod logging;
|
||||||
pub mod graphics;
|
pub mod graphics;
|
||||||
|
pub mod tracing;
|
||||||
#[cfg(feature="simulation")]
|
|
||||||
pub mod storage;
|
pub mod storage;
|
||||||
|
|
||||||
#[cfg(feature="simulation")]
|
|
||||||
pub mod simdata;
|
pub mod simdata;
|
||||||
|
pub mod gpio_interrupt;
|
||||||
|
pub mod tusb320;
|
||||||
|
|
||||||
extern crate alloc;
|
extern crate alloc;
|
||||||
|
|
||||||
|
|||||||
+29
-4
@@ -1,11 +1,14 @@
|
|||||||
|
#![allow(static_mut_refs)]
|
||||||
|
|
||||||
use embassy_sync::blocking_mutex::{raw::CriticalSectionRawMutex, Mutex};
|
use embassy_sync::blocking_mutex::{raw::CriticalSectionRawMutex, Mutex};
|
||||||
|
use esp_hal::time::Instant;
|
||||||
use esp_println::println;
|
use esp_println::println;
|
||||||
use log::{LevelFilter, Metadata, Record};
|
use log::{LevelFilter, Metadata, Record};
|
||||||
use static_cell::StaticCell;
|
|
||||||
|
|
||||||
// Provides a threadsafe serial logger
|
// Provides a threadsafe serial logger
|
||||||
pub struct RenderbugLogger {
|
pub struct RenderbugLogger {
|
||||||
lock: Mutex<CriticalSectionRawMutex, ()>,
|
lock: Mutex<CriticalSectionRawMutex, ()>,
|
||||||
|
current_level: LevelFilter,
|
||||||
default_level: LevelFilter
|
default_level: LevelFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -13,12 +16,13 @@ impl Default for RenderbugLogger {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
lock: Mutex::new(()),
|
lock: Mutex::new(()),
|
||||||
|
current_level: LevelFilter::Info,
|
||||||
default_level: LevelFilter::Info
|
default_level: LevelFilter::Info
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static LOGGER: StaticCell<RenderbugLogger> = StaticCell::new();
|
static mut LOGGER: Option<RenderbugLogger> = None;
|
||||||
|
|
||||||
impl RenderbugLogger {
|
impl RenderbugLogger {
|
||||||
pub fn init_logger() {
|
pub fn init_logger() {
|
||||||
@@ -33,7 +37,8 @@ impl RenderbugLogger {
|
|||||||
_ => LevelFilter::Info
|
_ => LevelFilter::Info
|
||||||
};
|
};
|
||||||
|
|
||||||
let logger = LOGGER.init(RenderbugLogger { default_level, ..Default::default() });
|
unsafe { LOGGER.replace(RenderbugLogger { default_level, current_level: default_level, ..Default::default() }) };
|
||||||
|
let logger = unsafe { LOGGER.as_mut().unwrap() };
|
||||||
unsafe {
|
unsafe {
|
||||||
critical_section::with(|_| {
|
critical_section::with(|_| {
|
||||||
log::set_logger_racy(logger).ok();
|
log::set_logger_racy(logger).ok();
|
||||||
@@ -41,6 +46,25 @@ impl RenderbugLogger {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_level(level: LevelFilter) {
|
||||||
|
unsafe {
|
||||||
|
critical_section::with(|_| {
|
||||||
|
log::set_max_level_racy(level);
|
||||||
|
LOGGER.as_mut().unwrap().current_level = level;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset_level() {
|
||||||
|
unsafe {
|
||||||
|
critical_section::with(|_| {
|
||||||
|
let logger = LOGGER.as_mut().unwrap();
|
||||||
|
log::set_max_level_racy(logger.default_level);
|
||||||
|
logger.current_level = logger.default_level;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl log::Log for RenderbugLogger {
|
impl log::Log for RenderbugLogger {
|
||||||
@@ -69,13 +93,14 @@ impl log::Log for RenderbugLogger {
|
|||||||
|
|
||||||
let filename = record.file().map_or("???", |f| {f});
|
let filename = record.file().map_or("???", |f| {f});
|
||||||
let crate_name = record.module_path_static().unwrap();
|
let crate_name = record.module_path_static().unwrap();
|
||||||
|
let timestamp = Instant::now().duration_since_epoch().as_micros();
|
||||||
for enabled in option_env!("LOG_CRATES").unwrap_or("").split(",") {
|
for enabled in option_env!("LOG_CRATES").unwrap_or("").split(",") {
|
||||||
if crate_name.starts_with(enabled) {
|
if crate_name.starts_with(enabled) {
|
||||||
self.lock.lock(|_| {
|
self.lock.lock(|_| {
|
||||||
#[cfg(feature = "rtt")]
|
#[cfg(feature = "rtt")]
|
||||||
rtt_target::rprintln!("{}{}\t{}{}:{}{}\t{}{}", color, record.level(), GREY, filename, record.line().map_or(0, |f| {f}), RESET, record.args(), RESET);
|
rtt_target::rprintln!("{}{}\t{}{}:{}{}\t{}{}", color, record.level(), GREY, filename, record.line().map_or(0, |f| {f}), RESET, record.args(), RESET);
|
||||||
|
|
||||||
println!("{color}{}\t{GREY}{} {filename}:{}{RESET}\t{}{RESET}", record.level(), record.module_path_static().unwrap(), record.line().map_or(0, |f| {f}), record.args());
|
println!("{color}{}\t{GREY}{timestamp}\t{} {filename}:{}{RESET}\t{}{RESET}", record.level(), record.module_path_static().unwrap(), record.line().map_or(0, |f| {f}), record.args());
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
+78
-24
@@ -1,38 +1,48 @@
|
|||||||
use rmp::{Marker, decode::{ExtMeta, RmpRead, ValueReadError}, encode::{RmpWrite, ValueWriteError}};
|
use rmp::{decode::{ExtMeta, RmpRead, RmpReadErr, ValueReadError}, encode::{RmpWrite, RmpWriteErr, ValueWriteError}};
|
||||||
|
|
||||||
pub trait RmpData: Sized {
|
pub trait RmpData: Sized {
|
||||||
fn from_rmp<Reader: RmpRead>(reader: &mut Reader) -> Result<Self, SimDataError<ValueReadError<Reader::Error>>>;
|
fn from_rmp<Reader: RmpRead<Error = E>, E: RmpReadErr + RmpWriteErr>(reader: &mut Reader) -> Result<Self, SimDataError<E>>;
|
||||||
fn write_rmp<Writer: RmpWrite>(&self, writer: &mut Writer) -> Result<(), ValueWriteError<Writer::Error>>;
|
fn write_rmp<Writer: RmpWrite>(&self, writer: &mut Writer) -> Result<(), ValueWriteError<Writer::Error>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait EventRecord: RmpData {
|
pub trait EventRecord: RmpData {
|
||||||
|
fn stream_id() -> StreamType;
|
||||||
fn field_count() -> usize;
|
fn field_count() -> usize;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum SimDataError<E> {
|
pub enum SimDataError<E: RmpReadErr + RmpWriteErr> {
|
||||||
StreamIndexMissing,
|
StreamIndexMissing,
|
||||||
InvalidChunkSize { expected: usize, found: usize },
|
InvalidChunkSize { expected: usize, found: usize },
|
||||||
MissingTimecode,
|
MissingTimecode,
|
||||||
BadString,
|
BadString,
|
||||||
DecodeError(E),
|
DecodeError(ValueReadError<E>),
|
||||||
|
EncodeError(ValueWriteError<E>),
|
||||||
EndOfStream,
|
EndOfStream,
|
||||||
UnsupportedStreamType(ExtMeta),
|
UnsupportedStreamType(ExtMeta),
|
||||||
EventHeaderMissing,
|
EventHeaderMissing,
|
||||||
PartitionNotFound
|
PartitionNotFound,
|
||||||
|
NotEnoughSpace
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E> From<E> for SimDataError<E> {
|
impl<E: RmpReadErr + RmpWriteErr> From<ValueReadError<E>> for SimDataError<E> {
|
||||||
fn from(value: E) -> Self {
|
fn from(value: ValueReadError<E>) -> Self {
|
||||||
SimDataError::DecodeError(value)
|
SimDataError::DecodeError(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
impl<E: RmpReadErr + RmpWriteErr> From<ValueWriteError<E>> for SimDataError<E> {
|
||||||
|
fn from(value: ValueWriteError<E>) -> Self {
|
||||||
|
SimDataError::EncodeError(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum StreamType {
|
pub enum StreamType {
|
||||||
IMU,
|
IMU,
|
||||||
GPS,
|
GPS,
|
||||||
Annotations
|
Annotations,
|
||||||
|
Bundle
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<i8> for StreamType {
|
impl TryFrom<i8> for StreamType {
|
||||||
@@ -43,6 +53,7 @@ impl TryFrom<i8> for StreamType {
|
|||||||
1 => Ok(StreamType::IMU),
|
1 => Ok(StreamType::IMU),
|
||||||
2 => Ok(StreamType::GPS),
|
2 => Ok(StreamType::GPS),
|
||||||
3 => Ok(StreamType::Annotations),
|
3 => Ok(StreamType::Annotations),
|
||||||
|
4 => Ok(StreamType::Bundle),
|
||||||
_ => Err(())
|
_ => Err(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -53,7 +64,8 @@ impl From<StreamType> for i8 {
|
|||||||
match value {
|
match value {
|
||||||
StreamType::IMU => 1,
|
StreamType::IMU => 1,
|
||||||
StreamType::GPS => 2,
|
StreamType::GPS => 2,
|
||||||
StreamType::Annotations => 3
|
StreamType::Annotations => 3,
|
||||||
|
StreamType::Bundle => 4
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -64,20 +76,49 @@ pub struct StreamIndex {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RmpData for StreamIndex {
|
impl RmpData for StreamIndex {
|
||||||
fn from_rmp<Reader: RmpRead>(reader: &mut Reader) -> Result<Self, SimDataError<ValueReadError<Reader::Error>>> {
|
fn from_rmp<Reader: RmpRead<Error = E>, E: RmpReadErr + RmpWriteErr>(reader: &mut Reader) -> Result<Self, SimDataError<E>> {
|
||||||
rmp::decode::read_array_len(reader).map(|count| {
|
if rmp::decode::read_u16(reader)? != 0xDA1A {
|
||||||
|
Err(SimDataError::StreamIndexMissing)
|
||||||
|
} else {
|
||||||
|
rmp::decode::read_u64(reader).map(|count| {
|
||||||
Self {
|
Self {
|
||||||
count: count as usize
|
count: count as usize
|
||||||
}
|
}
|
||||||
}).map_err(|_| { SimDataError::StreamIndexMissing })
|
}).map_err(|err| { SimDataError::DecodeError(err) })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_rmp<Writer: RmpWrite>(&self, writer: &mut Writer) -> Result<(), ValueWriteError<Writer::Error>> {
|
fn write_rmp<Writer: RmpWrite>(&self, writer: &mut Writer) -> Result<(), ValueWriteError<Writer::Error>> {
|
||||||
rmp::encode::write_array_len(writer, self.count as u32)?;
|
rmp::encode::write_u16(writer, 0xDA1A)?;
|
||||||
|
rmp::encode::write_u64(writer, self.count as u64)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct BundleEventHeader {
|
||||||
|
pub id: StreamType,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RmpData for BundleEventHeader {
|
||||||
|
fn from_rmp<Reader: RmpRead<Error = E>, E: RmpReadErr + RmpWriteErr>(reader: &mut Reader) -> Result<Self, SimDataError<E>> {
|
||||||
|
let meta = rmp::decode::read_i8(reader)?;
|
||||||
|
if let Ok(id) = meta.try_into() {
|
||||||
|
Ok(Self {
|
||||||
|
id
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(SimDataError::EventHeaderMissing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_rmp<Writer: RmpWrite>(&self, writer: &mut Writer) -> Result<(), ValueWriteError<Writer::Error>> {
|
||||||
|
rmp::encode::write_i8(writer, self.id.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct StreamHeader {
|
pub struct StreamHeader {
|
||||||
pub id: StreamType,
|
pub id: StreamType,
|
||||||
@@ -85,7 +126,7 @@ pub struct StreamHeader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RmpData for StreamHeader {
|
impl RmpData for StreamHeader {
|
||||||
fn from_rmp<Reader: RmpRead>(reader: &mut Reader) -> Result<Self, SimDataError<ValueReadError<Reader::Error>>> {
|
fn from_rmp<Reader: RmpRead<Error = E>, E: RmpReadErr + RmpWriteErr>(reader: &mut Reader) -> Result<Self, SimDataError<E>> {
|
||||||
let meta = rmp::decode::read_ext_meta(reader)?;
|
let meta = rmp::decode::read_ext_meta(reader)?;
|
||||||
if let Ok(id) = meta.typeid.try_into() {
|
if let Ok(id) = meta.typeid.try_into() {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
@@ -97,8 +138,9 @@ impl RmpData for StreamHeader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_rmp<Writer: RmpWrite>(&self, _writer: &mut Writer) -> Result<(), ValueWriteError<Writer::Error>> {
|
fn write_rmp<Writer: RmpWrite>(&self, writer: &mut Writer) -> Result<(), ValueWriteError<Writer::Error>> {
|
||||||
todo!()
|
rmp::encode::write_ext_meta(writer, self.size as u32, self.id.into())?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,14 +150,14 @@ pub struct EventStreamHeader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RmpData for EventStreamHeader {
|
impl RmpData for EventStreamHeader {
|
||||||
fn from_rmp<Reader: RmpRead>(reader: &mut Reader) -> Result<Self, SimDataError<ValueReadError<Reader::Error>>> {
|
fn from_rmp<Reader: RmpRead<Error = E>, E: RmpReadErr + RmpWriteErr>(reader: &mut Reader) -> Result<Self, SimDataError<E>> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
count: rmp::decode::read_array_len(reader)? as usize
|
count: rmp::decode::read_u32(reader)? as usize
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_rmp<Writer: RmpWrite>(&self, writer: &mut Writer) -> Result<(), ValueWriteError<Writer::Error>> {
|
fn write_rmp<Writer: RmpWrite>(&self, writer: &mut Writer) -> Result<(), ValueWriteError<Writer::Error>> {
|
||||||
rmp::encode::write_array_len(writer, self.count as u32)?;
|
rmp::encode::write_u32(writer, self.count as u32)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -127,7 +169,7 @@ pub struct StreamEvent<Event: EventRecord> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<Event: EventRecord> RmpData for StreamEvent<Event> {
|
impl<Event: EventRecord> RmpData for StreamEvent<Event> {
|
||||||
fn from_rmp<Reader: RmpRead>(reader: &mut Reader) -> Result<Self, SimDataError<ValueReadError<Reader::Error>>> {
|
fn from_rmp<Reader: RmpRead<Error = E>, E: RmpReadErr + RmpWriteErr>(reader: &mut Reader) -> Result<Self, SimDataError<E>> {
|
||||||
let chunk_len = rmp::decode::read_array_len(reader).map_err(|_| { SimDataError::EventHeaderMissing })? as usize;
|
let chunk_len = rmp::decode::read_array_len(reader).map_err(|_| { SimDataError::EventHeaderMissing })? as usize;
|
||||||
// Add 1 to the field count for the timestamp
|
// Add 1 to the field count for the timestamp
|
||||||
if chunk_len != Event::field_count() + 1 {
|
if chunk_len != Event::field_count() + 1 {
|
||||||
@@ -158,16 +200,24 @@ impl EventRecord for GPSReading {
|
|||||||
fn field_count() -> usize {
|
fn field_count() -> usize {
|
||||||
2
|
2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn stream_id() -> StreamType {
|
||||||
|
StreamType::GPS
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventRecord for IMUReading {
|
impl EventRecord for IMUReading {
|
||||||
fn field_count() -> usize {
|
fn field_count() -> usize {
|
||||||
6
|
6
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn stream_id() -> StreamType {
|
||||||
|
StreamType::IMU
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RmpData for GPSReading {
|
impl RmpData for GPSReading {
|
||||||
fn from_rmp<Reader: RmpRead>(reader: &mut Reader) -> Result<Self, SimDataError<ValueReadError<Reader::Error>>> {
|
fn from_rmp<Reader: RmpRead<Error = E>, E: RmpReadErr + RmpWriteErr>(reader: &mut Reader) -> Result<Self, SimDataError<E>> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
lat: rmp::decode::read_f64(reader)?,
|
lat: rmp::decode::read_f64(reader)?,
|
||||||
lon: rmp::decode::read_f64(reader)?
|
lon: rmp::decode::read_f64(reader)?
|
||||||
@@ -191,7 +241,7 @@ pub struct IMUReading {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RmpData for IMUReading {
|
impl RmpData for IMUReading {
|
||||||
fn from_rmp<Reader: RmpRead>(reader: &mut Reader) -> Result<Self, SimDataError<ValueReadError<Reader::Error>>> {
|
fn from_rmp<Reader: RmpRead<Error = E>, E: RmpReadErr + RmpWriteErr>(reader: &mut Reader) -> Result<Self, SimDataError<E>> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
accel_x: rmp::decode::read_f64(reader)?,
|
accel_x: rmp::decode::read_f64(reader)?,
|
||||||
accel_y: rmp::decode::read_f64(reader)?,
|
accel_y: rmp::decode::read_f64(reader)?,
|
||||||
@@ -221,7 +271,7 @@ pub struct AnnotationReading {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RmpData for AnnotationReading {
|
impl RmpData for AnnotationReading {
|
||||||
fn from_rmp<Reader: RmpRead>(reader: &mut Reader) -> Result<Self, SimDataError<ValueReadError<Reader::Error>>> {
|
fn from_rmp<Reader: RmpRead<Error = E>, E: RmpReadErr + RmpWriteErr>(reader: &mut Reader) -> Result<Self, SimDataError<E>> {
|
||||||
let mut buf = [0; 32];
|
let mut buf = [0; 32];
|
||||||
rmp::decode::read_str(reader, &mut buf).map_err(|_| { SimDataError::BadString })?;
|
rmp::decode::read_str(reader, &mut buf).map_err(|_| { SimDataError::BadString })?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
@@ -239,4 +289,8 @@ impl EventRecord for AnnotationReading {
|
|||||||
fn field_count() -> usize {
|
fn field_count() -> usize {
|
||||||
1
|
1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn stream_id() -> StreamType {
|
||||||
|
StreamType::Annotations
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+151
-20
@@ -1,9 +1,14 @@
|
|||||||
use core::{cell::RefCell, fmt::Formatter};
|
use core::{cell::RefCell, fmt::Formatter};
|
||||||
|
|
||||||
use alloc::rc::Rc;
|
use alloc::rc::Rc;
|
||||||
|
use embedded_io::{ErrorKind, ErrorType, Read, Write};
|
||||||
use embedded_storage::{ReadStorage, Storage};
|
use embedded_storage::{ReadStorage, Storage};
|
||||||
|
use esp_bootloader_esp_idf::partitions::PartitionTable;
|
||||||
|
use esp_hal::time::Instant;
|
||||||
use log::*;
|
use log::*;
|
||||||
use rmp::decode::{RmpRead, RmpReadErr};
|
use rmp::{decode::{RmpRead, RmpReadErr}, encode::{RmpWrite, RmpWriteErr, ValueWriteError}};
|
||||||
|
|
||||||
|
use crate::{simdata::{BundleEventHeader, EventRecord, EventStreamHeader, RmpData, SimDataError, StreamEvent, StreamHeader, StreamIndex, StreamType}, tasks::simulation::Checkpoint};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct SharedFlash<S> {
|
pub struct SharedFlash<S> {
|
||||||
@@ -45,69 +50,174 @@ impl<S: ReadStorage> ReadStorage for SharedFlash<S> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum RangeReadError<E> {
|
pub enum StorageRangeError<E> {
|
||||||
OutOfData,
|
OutOfData,
|
||||||
Storage(E)
|
Storage(E)
|
||||||
}
|
}
|
||||||
impl<E: core::fmt::Debug + 'static> RmpReadErr for RangeReadError<E> {}
|
impl<E: core::fmt::Debug + 'static> RmpReadErr for StorageRangeError<E> {}
|
||||||
|
impl<E: core::fmt::Debug + 'static> RmpWriteErr for StorageRangeError<E> {}
|
||||||
|
|
||||||
impl<E> core::fmt::Display for RangeReadError<E> {
|
impl<E> core::fmt::Display for StorageRangeError<E> {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
|
||||||
f.write_str("RmpErr")
|
f.write_str("RmpErr")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct RangeReader<S> {
|
pub struct StorageRange<S> {
|
||||||
storage: S,
|
storage: S,
|
||||||
start: usize,
|
start: usize,
|
||||||
end: usize,
|
end: usize,
|
||||||
offset: usize
|
offset: usize,
|
||||||
|
reset_pos: usize
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: ReadStorage> RangeReader<S> {
|
impl<S> Checkpoint for StorageRange<S> {
|
||||||
|
fn checkpoint(&mut self) {
|
||||||
|
self.reset_pos = self.offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rollback(&mut self) {
|
||||||
|
self.offset = self.reset_pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> StorageRange<S> {
|
||||||
|
pub fn inner(&self) -> &S {
|
||||||
|
&self.storage
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inner_mut(&mut self) -> &mut S {
|
||||||
|
&mut self.storage
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn abs_start(&self) -> usize {
|
||||||
|
self.start
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn abs_end(&self) -> usize {
|
||||||
|
self.end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: core::fmt::Debug> embedded_io::Error for StorageRangeError<E> {
|
||||||
|
fn kind(&self) -> embedded_io::ErrorKind {
|
||||||
|
ErrorKind::Other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: ReadStorage<Error = E>, E: core::fmt::Debug> ErrorType for StorageRange<S> {
|
||||||
|
type Error = StorageRangeError<E>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: Storage<Error = E> + ReadStorage<Error = E>, E: core::fmt::Debug> Write for StorageRange<S> {
|
||||||
|
fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
|
||||||
|
let pos = self.start + self.offset;
|
||||||
|
trace!("write {:#02x}:{:#02x} -> {:#02x}:{:#02x}", self.start, self.end, pos, pos + buf.len());
|
||||||
|
if pos > self.end {
|
||||||
|
Err(StorageRangeError::OutOfData)
|
||||||
|
} else {
|
||||||
|
assert!(pos + buf.len() <= self.end);
|
||||||
|
match self.storage.write(pos as u32, buf) {
|
||||||
|
Ok(_) => {
|
||||||
|
self.offset += buf.len();
|
||||||
|
Ok(buf.len())
|
||||||
|
},
|
||||||
|
Err(err) => Err(StorageRangeError::Storage(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> Result<(), Self::Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: ReadStorage<Error = E>, E: core::fmt::Debug> Read for StorageRange<S> {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
|
||||||
|
let pos = self.start + self.offset;
|
||||||
|
trace!("read_exact_buf {:#02x}:{:#02x} -> {:#02x}:{:#02x}", self.start, self.end, pos, pos + buf.len());
|
||||||
|
if pos > self.end {
|
||||||
|
Err(StorageRangeError::OutOfData)
|
||||||
|
} else {
|
||||||
|
let remaining = self.end - pos;
|
||||||
|
let max_read = buf.len().min(remaining);
|
||||||
|
if max_read == 0 {
|
||||||
|
return Err(StorageRangeError::OutOfData)
|
||||||
|
}
|
||||||
|
assert!(pos + buf.len() <= self.end);
|
||||||
|
match self.storage.read(pos as u32, &mut buf[..max_read]) {
|
||||||
|
Ok(_) => {
|
||||||
|
self.offset += max_read;
|
||||||
|
Ok(max_read)
|
||||||
|
},
|
||||||
|
Err(err) => Err(StorageRangeError::Storage(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: ReadStorage> StorageRange<S> {
|
||||||
pub const fn new(storage: S, start: usize, end: usize) -> Self {
|
pub const fn new(storage: S, start: usize, end: usize) -> Self {
|
||||||
assert!(start <= end);
|
assert!(start <= end);
|
||||||
// TODO: Should add bounds checking since we will know the size of the chunk already
|
|
||||||
Self {
|
Self {
|
||||||
storage,
|
storage,
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
offset: 0
|
offset: 0,
|
||||||
|
reset_pos: 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn seek(&mut self, offset: usize) -> Result<(), RangeReadError<S::Error>> {
|
pub const fn capacity(&self) -> usize {
|
||||||
|
self.end - self.start
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn pos(&self) -> usize {
|
||||||
|
self.offset
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn seek(&mut self, offset: usize) -> Result<(), StorageRangeError<S::Error>> {
|
||||||
self.offset += offset;
|
self.offset += offset;
|
||||||
if self.offset > self.end {
|
if self.offset > self.end {
|
||||||
Err(RangeReadError::OutOfData)
|
Err(StorageRangeError::OutOfData)
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn subset(&self, size: usize) -> Result<Self, RangeReadError<S::Error>> where S: Clone + core::fmt::Debug {
|
pub fn seek_abs(&mut self, pos: usize) -> Result<(), StorageRangeError<S::Error>> {
|
||||||
|
self.offset = pos;
|
||||||
|
if self.offset > self.end {
|
||||||
|
Err(StorageRangeError::OutOfData)
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn subset(&self, size: usize) -> Result<Self, StorageRangeError<S::Error>> where S: Clone + core::fmt::Debug {
|
||||||
trace!("subset {:#02x}:{:#02x} -> {:#02x}:{:#02x}", self.start, self.end, self.start + self.offset, self.start + self.offset + size);
|
trace!("subset {:#02x}:{:#02x} -> {:#02x}:{:#02x}", self.start, self.end, self.start + self.offset, self.start + self.offset + size);
|
||||||
if self.start + self.offset + size > self.end {
|
if self.start + self.offset + size > self.end {
|
||||||
Err(RangeReadError::OutOfData)
|
Err(StorageRangeError::OutOfData)
|
||||||
} else {
|
} else {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
storage: self.storage.clone(),
|
storage: self.storage.clone(),
|
||||||
start: self.offset + self.start,
|
start: self.offset + self.start,
|
||||||
end: self.start + self.offset + size,
|
end: self.start + self.offset + size,
|
||||||
offset: 0
|
offset: 0,
|
||||||
|
reset_pos: 0
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: ReadStorage> RmpRead for RangeReader<S> where S::Error: core::fmt::Debug + 'static {
|
impl<S: ReadStorage> RmpRead for StorageRange<S> where S::Error: core::fmt::Debug + 'static {
|
||||||
type Error = RangeReadError<S::Error>;
|
type Error = StorageRangeError<S::Error>;
|
||||||
|
|
||||||
fn read_exact_buf(&mut self, buf: &mut [u8]) -> Result<(), RangeReadError<S::Error>> {
|
fn read_exact_buf(&mut self, buf: &mut [u8]) -> Result<(), StorageRangeError<S::Error>> {
|
||||||
let pos = self.start + self.offset;
|
let pos = self.start + self.offset;
|
||||||
if pos > self.end {
|
if pos > self.end {
|
||||||
Err(RangeReadError::OutOfData)
|
Err(StorageRangeError::OutOfData)
|
||||||
} else {
|
} else {
|
||||||
assert!(pos + buf.len() <= self.end);
|
assert!(pos + buf.len() <= self.end);
|
||||||
match self.storage.read(pos as u32, buf) {
|
match self.storage.read(pos as u32, buf) {
|
||||||
@@ -115,7 +225,28 @@ impl<S: ReadStorage> RmpRead for RangeReader<S> where S::Error: core::fmt::Debug
|
|||||||
self.offset += buf.len();
|
self.offset += buf.len();
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
},
|
||||||
Err(err) => Err(RangeReadError::Storage(err))
|
Err(err) => Err(StorageRangeError::Storage(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl<S: Storage> RmpWrite for StorageRange<S> where S::Error: core::fmt::Debug + 'static {
|
||||||
|
type Error = StorageRangeError<S::Error>;
|
||||||
|
|
||||||
|
fn write_bytes(&mut self, buf: &[u8]) -> Result<(), Self::Error> {
|
||||||
|
let pos = self.start + self.offset;
|
||||||
|
if pos > self.end {
|
||||||
|
Err(StorageRangeError::OutOfData)
|
||||||
|
} else {
|
||||||
|
assert!(pos + buf.len() <= self.end);
|
||||||
|
match self.storage.write(pos as u32, buf) {
|
||||||
|
Ok(_) => {
|
||||||
|
self.offset += buf.len();
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
Err(err) => Err(StorageRangeError::Storage(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-14
@@ -1,15 +1,13 @@
|
|||||||
|
|
||||||
use core::ptr::slice_from_raw_parts;
|
|
||||||
|
|
||||||
use embassy_executor::Spawner;
|
use embassy_executor::Spawner;
|
||||||
use embassy_futures::select::Either;
|
use embassy_futures::select::Either;
|
||||||
use embassy_sync::{blocking_mutex::raw::NoopRawMutex, pubsub::{DynSubscriber, PubSubChannel, Publisher}};
|
use embassy_sync::{blocking_mutex::raw::NoopRawMutex, pubsub::{DynSubscriber, PubSubChannel, Publisher}};
|
||||||
use esp_radio::ble::controller::BleConnector;
|
use esp_radio::ble::controller::BleConnector;
|
||||||
use static_cell::{ConstStaticCell, StaticCell};
|
use static_cell::{ConstStaticCell, StaticCell};
|
||||||
use trouble_host::{attribute, prelude::*, types::gatt_traits::FromGattError};
|
use trouble_host::{prelude::*, types::gatt_traits::FromGattError};
|
||||||
use log::*;
|
use log::*;
|
||||||
|
|
||||||
use crate::{backoff::Backoff, events::Prediction};
|
use crate::events::Prediction;
|
||||||
|
|
||||||
#[gatt_server]
|
#[gatt_server]
|
||||||
struct SerialServer {
|
struct SerialServer {
|
||||||
@@ -55,7 +53,7 @@ struct LocationData {
|
|||||||
|
|
||||||
impl FixedGattValue for LocationData {
|
impl FixedGattValue for LocationData {
|
||||||
fn as_gatt(&self) -> &[u8] {
|
fn as_gatt(&self) -> &[u8] {
|
||||||
let databuf = [0;Self::SIZE];
|
//let databuf = [0;Self::SIZE];
|
||||||
unsafe { core::slice::from_raw_parts(self as *const Self as *const u8, Self::SIZE) }
|
unsafe { core::slice::from_raw_parts(self as *const Self as *const u8, Self::SIZE) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,24 +89,24 @@ struct SerialService {
|
|||||||
async fn client_prediction_task(mut src: DynSubscriber<'static, Prediction>, sink: Publisher<'static, NoopRawMutex, Prediction, 5, 1, 1>) {
|
async fn client_prediction_task(mut src: DynSubscriber<'static, Prediction>, sink: Publisher<'static, NoopRawMutex, Prediction, 5, 1, 1>) {
|
||||||
debug!("Started BLE client prediction stream");
|
debug!("Started BLE client prediction stream");
|
||||||
loop {
|
loop {
|
||||||
sink.publish(src.next_message_pure().await).await;
|
let _ = sink.try_publish(src.next_message_pure().await);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static STATIC_RESOURCES: StaticCell<HostResources<DefaultPacketPool, 1, 1>> = StaticCell::new();
|
static STATIC_RESOURCES: StaticCell<HostResources<DefaultPacketPool, 1, 1>> = StaticCell::new();
|
||||||
static STATIC_STACK: StaticCell<Stack<'static, ExternalController<BleConnector<'static>, 1>, DefaultPacketPool>> = StaticCell::new();
|
static STATIC_STACK: StaticCell<Stack<'static, ExternalController<BleConnector<'static>, 1>, DefaultPacketPool>> = StaticCell::new();
|
||||||
static STATIC_SERVER: StaticCell<SerialServer> = StaticCell::new();
|
static STATIC_SERVER: StaticCell<SerialServer> = StaticCell::new();
|
||||||
static STATIC_CLIENT_PREDICTIONS: StaticCell<PubSubChannel<NoopRawMutex, Prediction, 5, 1, 1>> = StaticCell::new();
|
static STATIC_CLIENT_PREDICTIONS: ConstStaticCell<PubSubChannel<NoopRawMutex, Prediction, 5, 1, 1>> = ConstStaticCell::new(PubSubChannel::new());
|
||||||
|
|
||||||
#[embassy_executor::task]
|
#[embassy_executor::task]
|
||||||
pub async fn ble_task(ble: BleConnector<'static>, predictions: DynSubscriber<'static, Prediction>, spawner: Spawner) {
|
pub async fn ble_task(ble: BleConnector<'static>, predictions: DynSubscriber<'static, Prediction>, spawner: Spawner) {
|
||||||
info!("Starting BLE stack");
|
info!("Starting BLE stack");
|
||||||
let server = STATIC_SERVER.init(SerialServer::new_with_config(GapConfig::Peripheral(PeripheralConfig { name: "Renderbug", appearance: &appearance::light_source::LED_ARRAY })).unwrap());
|
let server = STATIC_SERVER.init_with(|| { SerialServer::new_with_config(GapConfig::Peripheral(PeripheralConfig { name: "Renderbug", appearance: &appearance::light_source::LED_ARRAY })).unwrap() });
|
||||||
let control: ExternalController<esp_radio::ble::controller::BleConnector<'_>, 1> = ExternalController::new(ble);
|
let control: ExternalController<esp_radio::ble::controller::BleConnector<'_>, 1> = ExternalController::new(ble);
|
||||||
let stack = STATIC_STACK.init(trouble_host::new(control, STATIC_RESOURCES.init(HostResources::new())));
|
let stack = STATIC_STACK.init_with(|| { trouble_host::new(control, STATIC_RESOURCES.init_with(|| { HostResources::new() })) });
|
||||||
let Host { mut peripheral, mut runner, .. } = stack.build();
|
let Host { mut peripheral, mut runner, .. } = stack.build();
|
||||||
|
|
||||||
let client_predictions = STATIC_CLIENT_PREDICTIONS.init(PubSubChannel::new());
|
let client_predictions = STATIC_CLIENT_PREDICTIONS.take();
|
||||||
spawner.must_spawn(client_prediction_task(predictions, client_predictions.publisher().unwrap()));
|
spawner.must_spawn(client_prediction_task(predictions, client_predictions.publisher().unwrap()));
|
||||||
|
|
||||||
// The host task must be started and ticking beore we can start advertising, so we use a join() here instead of a separate task
|
// The host task must be started and ticking beore we can start advertising, so we use a join() here instead of a separate task
|
||||||
@@ -116,7 +114,6 @@ pub async fn ble_task(ble: BleConnector<'static>, predictions: DynSubscriber<'st
|
|||||||
runner.run(),
|
runner.run(),
|
||||||
async {
|
async {
|
||||||
loop {
|
loop {
|
||||||
let advertiser = Backoff::from_secs(5).forever().attempt(async || {
|
|
||||||
info!("Starting BLE advertising");
|
info!("Starting BLE advertising");
|
||||||
let mut adv_data = [0; 64];
|
let mut adv_data = [0; 64];
|
||||||
let len = AdStructure::encode_slice(
|
let len = AdStructure::encode_slice(
|
||||||
@@ -148,11 +145,10 @@ pub async fn ble_task(ble: BleConnector<'static>, predictions: DynSubscriber<'st
|
|||||||
],
|
],
|
||||||
&mut adv_data[..],
|
&mut adv_data[..],
|
||||||
).unwrap();
|
).unwrap();
|
||||||
peripheral.advertise(
|
let advertiser = peripheral.advertise(
|
||||||
&Default::default(),
|
&Default::default(),
|
||||||
Advertisement::ConnectableScannableUndirected { adv_data: &adv_data[..len], scan_data: &[] }
|
Advertisement::ConnectableScannableUndirected { adv_data: &adv_data[..len], scan_data: &[] }
|
||||||
).await
|
).await.unwrap();
|
||||||
}).await.unwrap();
|
|
||||||
info!("Waiting for connection");
|
info!("Waiting for connection");
|
||||||
match advertiser.accept().await.and_then(|raw| { raw.with_attribute_server(server) }) {
|
match advertiser.accept().await.and_then(|raw| { raw.with_attribute_server(server) }) {
|
||||||
Ok(conn) => {
|
Ok(conn) => {
|
||||||
|
|||||||
+1
-1
@@ -17,7 +17,7 @@ pub async fn demo_task(ui: DynPublisher<'static, Prediction>) {
|
|||||||
for state in [SensorState::Offline, SensorState::AcquiringFix, SensorState::Degraded, SensorState::Offline] {
|
for state in [SensorState::Offline, SensorState::AcquiringFix, SensorState::Degraded, SensorState::Offline] {
|
||||||
for motion in [MotionState::Accelerating, MotionState::Steady, MotionState::Decelerating, MotionState::Stationary] {
|
for motion in [MotionState::Accelerating, MotionState::Steady, MotionState::Decelerating, MotionState::Stationary] {
|
||||||
ui.publish(Prediction::Motion { prev: motion, next: motion }).await;
|
ui.publish(Prediction::Motion { prev: motion, next: motion }).await;
|
||||||
for sensor in [SensorSource::ForwardsReference, SensorSource::GPS, SensorSource::GravityReference, SensorSource::IMU, SensorSource::Location] {
|
for sensor in [SensorSource::MotionFrame, SensorSource::GPS, SensorSource::IMU, SensorSource::Location] {
|
||||||
ui.publish(Prediction::SensorStatus(sensor, state)).await;
|
ui.publish(Prediction::SensorStatus(sensor, state)).await;
|
||||||
}
|
}
|
||||||
Timer::after_secs(1).await;
|
Timer::after_secs(1).await;
|
||||||
|
|||||||
+34
-29
@@ -1,23 +1,17 @@
|
|||||||
use alloc::string::String;
|
use alloc::string::String;
|
||||||
use embassy_embedded_hal::shared_bus::asynch::i2c::I2cDevice;
|
use embassy_embedded_hal::shared_bus::{I2cDeviceError, asynch::i2c::I2cDevice};
|
||||||
use embassy_sync::{blocking_mutex::raw::NoopRawMutex, channel::DynamicSender};
|
use embassy_sync::{blocking_mutex::raw::NoopRawMutex, channel::DynamicSender};
|
||||||
use embassy_time::Timer;
|
use embassy_time::Timer;
|
||||||
use embedded_hal_async::i2c::I2c as _;
|
use embedded_hal_async::i2c::I2c as _;
|
||||||
use esp_hal::{i2c::master::I2c, Async};
|
use esp_hal::{i2c::master::I2c, Async};
|
||||||
use log::*;
|
use log::*;
|
||||||
use nalgebra::Vector2;
|
use nalgebra::Vector2;
|
||||||
use nmea::Nmea;
|
use nmea::{Nmea, sentences::FixType};
|
||||||
|
|
||||||
use crate::{backoff::Backoff, events::{Measurement, SensorSource, SensorState}};
|
use crate::{events::{Measurement, SensorSource, SensorState}};
|
||||||
|
|
||||||
// FIXME: We need a way to put the GPS to sleep when the system goes to sleep
|
async fn init_gps(i2c_bus: &mut I2cDevice<'static, NoopRawMutex, I2c<'static, Async>>) -> Result<(), I2cDeviceError<esp_hal::i2c::master::Error>> {
|
||||||
#[embassy_executor::task]
|
|
||||||
pub async fn gps_task(events: DynamicSender<'static, Measurement>, mut i2c_bus: I2cDevice<'static, NoopRawMutex, I2c<'static, Async>>) {
|
|
||||||
Backoff::from_secs(5).forever().attempt::<_, (), ()>(async || {
|
|
||||||
events.send(Measurement::SensorHardwareStatus(SensorSource::GPS, SensorState::Offline)).await;
|
|
||||||
Backoff::from_secs(5).forever().attempt(async || {
|
|
||||||
info!("Initializing GPS");
|
info!("Initializing GPS");
|
||||||
events.send(Measurement::SensorHardwareStatus(SensorSource::GPS, SensorState::AcquiringFix)).await;
|
|
||||||
// Enable a bunch of data? idk
|
// Enable a bunch of data? idk
|
||||||
let bytes = "$PMTK314,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0*28\r\n";
|
let bytes = "$PMTK314,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0*28\r\n";
|
||||||
i2c_bus.write(0x10, bytes.as_bytes()).await?;
|
i2c_bus.write(0x10, bytes.as_bytes()).await?;
|
||||||
@@ -33,48 +27,60 @@ pub async fn gps_task(events: DynamicSender<'static, Measurement>, mut i2c_bus:
|
|||||||
// Antenna updates
|
// Antenna updates
|
||||||
let bytes = "$PGCMD,33,1*6C\r\n";
|
let bytes = "$PGCMD,33,1*6C\r\n";
|
||||||
i2c_bus.write(0x10, bytes.as_bytes()).await
|
i2c_bus.write(0x10, bytes.as_bytes()).await
|
||||||
}).await.unwrap();
|
}
|
||||||
|
|
||||||
|
// FIXME: We need a way to put the GPS to sleep when the system goes to sleep
|
||||||
|
#[embassy_executor::task]
|
||||||
|
pub async fn gps_task(events: DynamicSender<'static, Measurement>, mut i2c_bus: I2cDevice<'static, NoopRawMutex, I2c<'static, Async>>) {
|
||||||
|
events.send(Measurement::SensorHardwareStatus(SensorSource::GPS, SensorState::Offline)).await;
|
||||||
|
if let Err(e) = init_gps(&mut i2c_bus).await {
|
||||||
|
error!("Failed to initialize GPS: {e:?}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let mut strbuf = String::new();
|
let mut strbuf = String::new();
|
||||||
|
|
||||||
let mut parser = Nmea::default();
|
let mut parser = Nmea::default();
|
||||||
let mut parsing = false;
|
let mut parsing = false;
|
||||||
let mut has_lock = false;
|
let mut has_lock = false;
|
||||||
events.send(Measurement::SensorHardwareStatus(SensorSource::GPS, SensorState::Online)).await;
|
events.send(Measurement::SensorHardwareStatus(SensorSource::GPS, SensorState::AcquiringFix)).await;
|
||||||
info!("GPS is ready!");
|
info!("GPS is ready!");
|
||||||
loop {
|
loop {
|
||||||
let mut buf = [0; 1];
|
let mut buf = [0; 1];
|
||||||
i2c_bus.read(0x10, &mut buf).await.map_err(|_| { Err::<(), ()>(()) }).ok();
|
i2c_bus.read(0x10, &mut buf).await.map_err(|_| { Err::<(), ()>(()) }).ok();
|
||||||
if (buf[0] as char == '\n' || buf[0] as char == '\r') && !strbuf.is_empty() {
|
if (buf[0] as char == '\n' || buf[0] as char == '\r') && !strbuf.is_empty() {
|
||||||
if let Ok(result) = parser.parse(&strbuf) {
|
match parser.parse_for_fix(&strbuf) {
|
||||||
match parser.fix_type {
|
Ok(FixType::Invalid) if has_lock => {
|
||||||
None if has_lock => {
|
|
||||||
// TODO: Send a Measurement::SensorOffline(SensorSource::GPS) here instead
|
// TODO: Send a Measurement::SensorOffline(SensorSource::GPS) here instead
|
||||||
events.send(Measurement::GPS(None)).await;
|
events.send(Measurement::GPS(None)).await;
|
||||||
|
events.send(Measurement::SensorHardwareStatus(SensorSource::GPS, SensorState::Degraded)).await;
|
||||||
has_lock = false
|
has_lock = false
|
||||||
},
|
},
|
||||||
None => (),
|
Ok(FixType::Invalid) => {
|
||||||
Some(_) => {
|
debug!("Waiting for fix {parser:?}");
|
||||||
|
},
|
||||||
|
Ok(fix_type) => {
|
||||||
if !has_lock {
|
if !has_lock {
|
||||||
has_lock = true;
|
has_lock = true;
|
||||||
|
info!("Got a fix of type {fix_type:?}");
|
||||||
|
events.send(Measurement::SensorHardwareStatus(SensorSource::GPS, SensorState::Online)).await;
|
||||||
}
|
}
|
||||||
// TODO: Send a Measurement::SensorOnline(SensorSource::GPS) here instead
|
|
||||||
|
|
||||||
//TODO: 4 satellites seems to be "Some" fix, 6 is a perfect fix
|
|
||||||
//TODO: Only send updates when we get the correct nmea sentence
|
|
||||||
if let (Some(lat), Some(lng)) = (parser.latitude, parser.longitude) {
|
if let (Some(lat), Some(lng)) = (parser.latitude, parser.longitude) {
|
||||||
events.send(Measurement::GPS(Some(Vector2::new(lat, lng)))).await;
|
events.send(Measurement::GPS(Some(Vector2::new(lat, lng)))).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let (Some(date), Some(time)) = (parser.fix_date, parser.fix_time) {
|
||||||
|
let now = date.and_time(time).and_utc();
|
||||||
|
info!("GPS time is {now}");
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
Err(nmea::Error::ParsingError(_)) => {
|
||||||
|
debug!("NMEA could not parse: {strbuf}");
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
error!("NMEA error on {strbuf} {err:?}");
|
||||||
}
|
}
|
||||||
log::trace!("nmea={result:?} raw={strbuf:?}");
|
|
||||||
log::trace!("nmea={parser:?}");
|
|
||||||
log::trace!("speed={:?} altitude={:?} lat={:?} lng={:?} fix={:?}", parser.speed_over_ground, parser.altitude, parser.latitude, parser.longitude, parser.fix_type);
|
|
||||||
for sat in parser.satellites() {
|
|
||||||
trace!("\t{} snr={:?} prn={:?}", sat.gnss_type(), sat.snr(), sat.prn())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log::warn!("Unhandled NMEA {strbuf:?}");
|
|
||||||
}
|
}
|
||||||
strbuf = String::new();
|
strbuf = String::new();
|
||||||
parsing = false;
|
parsing = false;
|
||||||
@@ -92,5 +98,4 @@ pub async fn gps_task(events: DynamicSender<'static, Measurement>, mut i2c_bus:
|
|||||||
Timer::after_millis(500).await;
|
Timer::after_millis(500).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).await.ok();
|
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-3
@@ -1,18 +1,20 @@
|
|||||||
#[cfg(feature="mpu")]
|
#[cfg(feature="mpu6050")]
|
||||||
pub mod mpu;
|
pub mod mpu;
|
||||||
#[cfg(feature="gps")]
|
|
||||||
pub mod gps;
|
pub mod gps;
|
||||||
#[cfg(feature="radio")]
|
#[cfg(feature="radio")]
|
||||||
pub mod wifi;
|
pub mod wifi;
|
||||||
#[cfg(feature="radio")]
|
#[cfg(feature="radio")]
|
||||||
pub mod ble;
|
pub mod ble;
|
||||||
#[cfg(feature="simulation")]
|
|
||||||
pub mod simulation;
|
pub mod simulation;
|
||||||
#[cfg(feature="demo")]
|
#[cfg(feature="demo")]
|
||||||
pub mod demo;
|
pub mod demo;
|
||||||
#[cfg(feature="oled")]
|
#[cfg(feature="oled")]
|
||||||
pub mod oled_render;
|
pub mod oled_render;
|
||||||
|
|
||||||
|
pub mod usb_power;
|
||||||
|
|
||||||
|
pub mod sd_card;
|
||||||
|
|
||||||
// Prediction engines
|
// Prediction engines
|
||||||
pub mod motion;
|
pub mod motion;
|
||||||
|
|
||||||
|
|||||||
+12
-3
@@ -1,10 +1,11 @@
|
|||||||
use embassy_sync::{channel::DynamicReceiver, pubsub::DynPublisher};
|
use embassy_sync::{channel::DynamicReceiver, pubsub::DynPublisher};
|
||||||
use log::*;
|
use log::*;
|
||||||
|
use embassy_time::{Duration, WithTimeout};
|
||||||
|
|
||||||
use crate::{ego::engine::BikeStates, events::{Measurement, Prediction}};
|
use crate::{ego::engine::BikeStates, events::{Measurement, Prediction}};
|
||||||
|
|
||||||
#[embassy_executor::task]
|
#[embassy_executor::task]
|
||||||
pub async fn motion_task(src: DynamicReceiver<'static, Measurement>, prediction_sink: DynPublisher<'static, Prediction>) {
|
pub async fn motion_task(src: DynamicReceiver<'static, Measurement>, prediction_sink: DynPublisher<'static, Prediction>, recording_sink: DynPublisher<'static, Measurement>) {
|
||||||
let mut states = BikeStates::default();
|
let mut states = BikeStates::default();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
@@ -25,11 +26,19 @@ pub async fn motion_task(src: DynamicReceiver<'static, Measurement>, prediction_
|
|||||||
},
|
},
|
||||||
// FIXME: This needs harmonized with the automatic data timeout from above, somehow?
|
// FIXME: This needs harmonized with the automatic data timeout from above, somehow?
|
||||||
Measurement::SensorHardwareStatus(source, state) => {
|
Measurement::SensorHardwareStatus(source, state) => {
|
||||||
warn!("Sensor {source:?} reports {state:?}!");
|
debug!("Sensor {source:?} reports {state:?}!");
|
||||||
prediction_sink.publish(Prediction::SensorStatus(source, state)).await;
|
prediction_sink.publish(Prediction::SensorStatus(source, state)).await;
|
||||||
},
|
},
|
||||||
|
Measurement::ExternalPower(mw) => {
|
||||||
|
info!("Got external power change to {mw:?}");
|
||||||
|
},
|
||||||
Measurement::SimulationProgress(source, duration, pct) => debug!("{source:?} simulation time: {} {} / 255", duration.as_secs(), pct),
|
Measurement::SimulationProgress(source, duration, pct) => debug!("{source:?} simulation time: {} {} / 255", duration.as_secs(), pct),
|
||||||
Measurement::Annotation => ()
|
Measurement::Annotation(msg) => {
|
||||||
|
info!("Annotation: {}", str::from_utf8(&msg).unwrap_or("<non-utf8-data>"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if recording_sink.try_publish(next_measurement).is_err() {
|
||||||
|
warn!("Could not publish measurement to recording bus. Is the recording bus stalled?");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+38
-33
@@ -1,5 +1,3 @@
|
|||||||
use core::cell::RefCell;
|
|
||||||
|
|
||||||
use embassy_embedded_hal::shared_bus::asynch::i2c::I2cDevice;
|
use embassy_embedded_hal::shared_bus::asynch::i2c::I2cDevice;
|
||||||
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
||||||
use embassy_sync::channel::DynamicSender;
|
use embassy_sync::channel::DynamicSender;
|
||||||
@@ -12,52 +10,53 @@ use mpu6050_dmp::{address::Address, error_async::Error, sensor_async::Mpu6050};
|
|||||||
use nalgebra::Vector3;
|
use nalgebra::Vector3;
|
||||||
use crate::events::SensorSource;
|
use crate::events::SensorSource;
|
||||||
|
|
||||||
use crate::{backoff::Backoff, events::Measurement};
|
use crate::gpio_interrupt::PinInterrupt;
|
||||||
|
use crate::events::Measurement;
|
||||||
|
|
||||||
const G: f32 = 9.80665;
|
const G: f32 = 9.80665;
|
||||||
const GYRO_SCALE: GyroFullScale = GyroFullScale::Deg2000;
|
const GYRO_SCALE: GyroFullScale = GyroFullScale::Deg2000;
|
||||||
const ACCEL_SCALE: AccelFullScale = AccelFullScale::G2;
|
const ACCEL_SCALE: AccelFullScale = AccelFullScale::G2;
|
||||||
|
|
||||||
#[embassy_executor::task]
|
#[embassy_executor::task]
|
||||||
pub async fn mpu_task(events: DynamicSender<'static, Measurement>, bus: I2cDevice<'static, NoopRawMutex, I2c<'static, Async>>) {
|
pub async fn mpu_task(events: DynamicSender<'static, Measurement>, bus: I2cDevice<'static, NoopRawMutex, I2c<'static, Async>>, _interrupt: PinInterrupt<'static>) {
|
||||||
let backoff = Backoff::from_millis(5);
|
|
||||||
let busref = RefCell::new(Some(bus));
|
|
||||||
|
|
||||||
backoff.forever().attempt::<_, (), ()>(async || {
|
|
||||||
events.send(Measurement::SensorHardwareStatus(SensorSource::IMU, crate::events::SensorState::Offline)).await;
|
|
||||||
let mut sensor = backoff.forever().attempt(async || {
|
|
||||||
warn!("Initializing connection to MPU");
|
warn!("Initializing connection to MPU");
|
||||||
match Mpu6050::new(busref.replace(None).unwrap(), Address::default()).await.map_err(|e| { e.i2c }) {
|
|
||||||
Err(i2c) => {
|
|
||||||
busref.replace(Some(i2c));
|
|
||||||
Err(())
|
|
||||||
},
|
|
||||||
Ok(mut sensor) => {
|
|
||||||
events.send(Measurement::SensorHardwareStatus(SensorSource::IMU, crate::events::SensorState::AcquiringFix)).await;
|
events.send(Measurement::SensorHardwareStatus(SensorSource::IMU, crate::events::SensorState::AcquiringFix)).await;
|
||||||
match backoff.attempt(async || { mpu_init(&mut sensor).await }).await {
|
|
||||||
Err(_) => {
|
|
||||||
busref.replace(Some(sensor.release()));
|
|
||||||
Err(())
|
|
||||||
},
|
|
||||||
Ok(_) => Ok(sensor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).await?;
|
|
||||||
|
|
||||||
let sensor_ref = &mut sensor;
|
let mut sensor = match Mpu6050::new(bus, Address::default()).await {
|
||||||
|
Err(err) => {
|
||||||
|
error!("Could not connect to MPU: {err:?}");
|
||||||
|
events.send(Measurement::SensorHardwareStatus(SensorSource::IMU, crate::events::SensorState::Offline)).await;
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
Ok(sensor) => sensor
|
||||||
|
};
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if let Err(err) = mpu_init(&mut sensor).await {
|
||||||
|
error!("Could not initialize MPU: {err:?}");
|
||||||
|
events.send(Measurement::SensorHardwareStatus(SensorSource::IMU, crate::events::SensorState::Degraded)).await;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
events.send(Measurement::SensorHardwareStatus(SensorSource::IMU, crate::events::SensorState::Online)).await;
|
events.send(Measurement::SensorHardwareStatus(SensorSource::IMU, crate::events::SensorState::Online)).await;
|
||||||
|
|
||||||
//TODO: Need to read in a constant buffer of accelerometer measurements, which we can then use to determine where "forward" points in the body frame when converting from the sensor frame.
|
//TODO: Need to read in a constant buffer of accelerometer measurements, which we can then use to determine where "forward" points in the body frame when converting from the sensor frame.
|
||||||
// From there, we can rotate the body frame into the world frame using gps headings to generate a compass north
|
// From there, we can rotate the body frame into the world frame using gps headings to generate a compass north
|
||||||
fn lowpass(prev: f32, current: f32, alpha: f32) -> f32 {
|
fn lowpass(prev: f32, current: f32, alpha: f32) -> f32 {
|
||||||
prev + alpha * (current - prev) // alpha in (0,1), small alpha = heavy smoothing
|
prev + alpha * (current - prev) // alpha in (0,1), small alpha = heavy smoothing
|
||||||
}
|
}
|
||||||
let mut prev_accel = Vector3::default();
|
let mut prev_accel = Vector3::default();
|
||||||
|
let mut degraded = false;
|
||||||
loop {
|
loop {
|
||||||
match backoff.attempt(async || { sensor_ref.motion6().await }).await {
|
match sensor.motion6().await {
|
||||||
Ok((accel_data, gyro_data)) => {
|
Ok((accel_data, gyro_data)) => {
|
||||||
|
|
||||||
|
if degraded {
|
||||||
|
warn!("Reconnected to MPU");
|
||||||
|
events.send(Measurement::SensorHardwareStatus(SensorSource::IMU, crate::events::SensorState::Online)).await;
|
||||||
|
degraded = false;
|
||||||
|
}
|
||||||
|
|
||||||
// Apply the initial alignment correction to put it into the body frame where X is forward
|
// Apply the initial alignment correction to put it into the body frame where X is forward
|
||||||
let scaled = accel_data.scaled(ACCEL_SCALE);
|
let scaled = accel_data.scaled(ACCEL_SCALE);
|
||||||
let adjusted = Vector3::new(
|
let adjusted = Vector3::new(
|
||||||
@@ -90,12 +89,19 @@ pub async fn mpu_task(events: DynamicSender<'static, Measurement>, bus: I2cDevic
|
|||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Failed to read MPU motion data! {e:?}");
|
error!("Failed to read MPU motion data! {e:?}");
|
||||||
busref.replace(Some(sensor.release()));
|
if !degraded {
|
||||||
return Err(());
|
events.send(Measurement::SensorHardwareStatus(SensorSource::IMU, crate::events::SensorState::Degraded)).await;
|
||||||
|
degraded = true;
|
||||||
|
} else {
|
||||||
|
error!("MPU connection is degraded, and failed to read data again. Attempting to re-initialize connection...");
|
||||||
|
events.send(Measurement::SensorHardwareStatus(SensorSource::IMU, crate::events::SensorState::AcquiringFix)).await;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Timer::after_millis(10).await;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}).await.ok();
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn mpu_init(sensor: &mut Mpu6050<I2cDevice<'static, NoopRawMutex, I2c<'static, Async>>>) -> Result<(), Error<I2cDevice<'static, NoopRawMutex, I2c<'static, Async>>>> {
|
async fn mpu_init(sensor: &mut Mpu6050<I2cDevice<'static, NoopRawMutex, I2c<'static, Async>>>) -> Result<(), Error<I2cDevice<'static, NoopRawMutex, I2c<'static, Async>>>> {
|
||||||
@@ -104,10 +110,9 @@ async fn mpu_init(sensor: &mut Mpu6050<I2cDevice<'static, NoopRawMutex, I2c<'sta
|
|||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
let mut delay = Delay;
|
let mut delay = Delay;
|
||||||
let backoff = Backoff::from_millis(10);
|
|
||||||
info!("Initializing DMP");
|
info!("Initializing DMP");
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
backoff.attempt(async || { sensor.initialize_dmp(&mut delay).await }).await?;
|
sensor.initialize_dmp(&mut delay).await?;
|
||||||
info!("DMP initialized in {}ms", start.as_millis());
|
info!("DMP initialized in {}ms", start.as_millis());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-13
@@ -2,25 +2,25 @@ use alloc::sync::Arc;
|
|||||||
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex, pubsub::DynSubscriber};
|
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex, pubsub::DynSubscriber};
|
||||||
use embassy_time::{Duration, Timer};
|
use embassy_time::{Duration, Timer};
|
||||||
use embedded_graphics::pixelcolor::BinaryColor;
|
use embedded_graphics::pixelcolor::BinaryColor;
|
||||||
use figments::{mappings::embedded_graphics::Matrix2DSpace, prelude::{Coordinates, Rectangle}, render::Shader, surface::{BufferedSurfacePool, NullBufferPool, Surface, SurfaceBuilder, Surfaces}};
|
use figments::{liber8tion::interpolate::Fract8, mappings::embedded_graphics::Matrix2DSpace, prelude::{Coordinates, Rectangle}, render::Shader, surface::{Surface, SurfaceBuilder, Surfaces}};
|
||||||
use figments_render::output::Brightness;
|
use figments_render::output::Brightness;
|
||||||
use log::*;
|
use log::*;
|
||||||
|
|
||||||
use crate::{animation::Animation, events::{Personality, Prediction}, graphics::{display::DisplayControls, oled_ui::{OledUniforms, Screen}}};
|
use crate::{animation::Animation, events::{Personality, Prediction}, graphics::{display::DisplayControls, oled_ui::{OledUniforms, Screen}}};
|
||||||
|
|
||||||
#[cfg(feature="oled")]
|
#[cfg(feature="oled")]
|
||||||
pub type OledUiSurfacePool = BufferedSurfacePool<OledUniforms, Matrix2DSpace, BinaryColor>;
|
pub type OledUiSurfacePool = figments::surfaces::buffered::BufferedSurfacePool<OledUniforms, Matrix2DSpace, BinaryColor>;
|
||||||
|
|
||||||
#[cfg(not(feature="oled"))]
|
#[cfg(not(feature="oled"))]
|
||||||
pub type OledUiSurfacePool = NullBufferPool<OledUniforms, Matrix2DSpace, BinaryColor>;
|
pub type OledUiSurfacePool = figments::surfaces::null::NullBufferPool<OledUniforms, Matrix2DSpace, BinaryColor>;
|
||||||
|
|
||||||
type OledSurface = <OledUiSurfacePool as Surfaces<Matrix2DSpace>>::Surface;
|
type OledSurface = <OledUiSurfacePool as Surfaces>::Surface;
|
||||||
|
|
||||||
pub type LockedUniforms = Arc<Mutex<CriticalSectionRawMutex, OledUniforms>>;
|
pub type LockedUniforms = Arc<Mutex<CriticalSectionRawMutex, OledUniforms>>;
|
||||||
|
|
||||||
pub struct OledUI<S: Surface + core::fmt::Debug> {
|
pub struct OledUI<S: Surface + core::fmt::Debug> {
|
||||||
overlay: S,
|
overlay: S,
|
||||||
controls: DisplayControls,
|
controls: DisplayControls<'static>,
|
||||||
uniforms: LockedUniforms
|
uniforms: LockedUniforms
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,12 +32,12 @@ impl Shader<OledUniforms, Matrix2DSpace, BinaryColor> for OverlayShader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<S: core::fmt::Debug + Surface<CoordinateSpace = Matrix2DSpace, Pixel = BinaryColor, Uniforms = OledUniforms>> OledUI<S> {
|
impl<S: core::fmt::Debug + Surface<CoordinateSpace = Matrix2DSpace, Pixel = BinaryColor, Uniforms = OledUniforms>> OledUI<S> {
|
||||||
pub fn new<SS: Surfaces<Matrix2DSpace, Surface = S>>(surfaces: &mut SS, controls: DisplayControls, uniforms: LockedUniforms) -> Self where SS::Error: core::fmt::Debug {
|
pub fn new<SS: Surfaces<Surface = S>>(surfaces: &mut SS, controls: DisplayControls<'static>, uniforms: LockedUniforms) -> Self where SS::Error: core::fmt::Debug {
|
||||||
Self {
|
Self {
|
||||||
overlay: SurfaceBuilder::build(surfaces)
|
overlay: SurfaceBuilder::build(surfaces)
|
||||||
.rect(Rectangle::everything())
|
.rect(Rectangle::everything())
|
||||||
.shader(OverlayShader{})
|
.shader(OverlayShader{})
|
||||||
.opacity(0)
|
.opacity(Fract8::MIN)
|
||||||
.finish().unwrap(),
|
.finish().unwrap(),
|
||||||
controls,
|
controls,
|
||||||
uniforms
|
uniforms
|
||||||
@@ -45,15 +45,15 @@ impl<S: core::fmt::Debug + Surface<CoordinateSpace = Matrix2DSpace, Pixel = Bina
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn screen_transition(&mut self, next_screen: Screen) {
|
pub async fn screen_transition(&mut self, next_screen: Screen) {
|
||||||
const FADE_IN: Animation = Animation::new().from(0).to(255).duration(Duration::from_millis(300));
|
const FADE_IN: Animation<Fract8> = Animation::new().from(Fract8::MIN).to(Fract8::MAX).duration(Duration::from_millis(300));
|
||||||
const FADE_OUT: Animation = Animation::new().from(255).to(0).duration(Duration::from_millis(300));
|
const FADE_OUT: Animation<Fract8> = Animation::new().from(Fract8::MAX).to(Fract8::MIN).duration(Duration::from_millis(300));
|
||||||
info!("Fading in to screen {next_screen:?}");
|
info!("Fading in to screen {next_screen:?}");
|
||||||
FADE_IN.apply(&mut self.overlay).await;
|
FADE_IN.apply([&mut self.overlay]).await;
|
||||||
{
|
{
|
||||||
let mut locked = self.uniforms.lock().await;
|
let mut locked = self.uniforms.lock().await;
|
||||||
locked.current_screen = next_screen;
|
locked.current_screen = next_screen;
|
||||||
}
|
}
|
||||||
FADE_OUT.apply(&mut self.overlay).await;
|
FADE_OUT.apply([&mut self.overlay]).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn on_event(&mut self, event: Prediction) {
|
pub async fn on_event(&mut self, event: Prediction) {
|
||||||
@@ -94,7 +94,6 @@ impl<S: core::fmt::Debug + Surface<CoordinateSpace = Matrix2DSpace, Pixel = Bina
|
|||||||
|
|
||||||
#[embassy_executor::task]
|
#[embassy_executor::task]
|
||||||
pub async fn oled_ui(mut events: DynSubscriber<'static, Prediction>, mut ui: OledUI<OledSurface>) {
|
pub async fn oled_ui(mut events: DynSubscriber<'static, Prediction>, mut ui: OledUI<OledSurface>) {
|
||||||
|
|
||||||
ui.screen_transition(Screen::Bootsplash).await;
|
ui.screen_transition(Screen::Bootsplash).await;
|
||||||
Timer::after_secs(3).await;
|
Timer::after_secs(3).await;
|
||||||
ui.screen_transition(Screen::Home).await;
|
ui.screen_transition(Screen::Home).await;
|
||||||
@@ -102,5 +101,4 @@ pub async fn oled_ui(mut events: DynSubscriber<'static, Prediction>, mut ui: Ole
|
|||||||
loop {
|
loop {
|
||||||
ui.on_event(events.next_message_pure().await).await;
|
ui.on_event(events.next_message_pure().await).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -12,7 +12,7 @@ use crate::{backoff::Backoff, graphics::ssd1306::SsdOutput, tasks::oled::{Locked
|
|||||||
pub async fn oled_render(mut output: SsdOutput, mut surfaces: OledUiSurfacePool, uniforms: LockedUniforms) {
|
pub async fn oled_render(mut output: SsdOutput, mut surfaces: OledUiSurfacePool, uniforms: LockedUniforms) {
|
||||||
warn!("Starting OLED rendering task");
|
warn!("Starting OLED rendering task");
|
||||||
Backoff::from_secs(1).forever().attempt::<_, (), DisplayError>(async || {
|
Backoff::from_secs(1).forever().attempt::<_, (), DisplayError>(async || {
|
||||||
const FPS: u64 = 30;
|
const FPS: u64 = 15;
|
||||||
const RENDER_BUDGET: Duration = Duration::from_millis(1000 / FPS);
|
const RENDER_BUDGET: Duration = Duration::from_millis(1000 / FPS);
|
||||||
|
|
||||||
const ANIMATION_TPS: u64 = 30;
|
const ANIMATION_TPS: u64 = 30;
|
||||||
@@ -35,7 +35,7 @@ pub async fn oled_render(mut output: SsdOutput, mut surfaces: OledUiSurfacePool,
|
|||||||
if frame_time < RENDER_BUDGET {
|
if frame_time < RENDER_BUDGET {
|
||||||
Timer::after(RENDER_BUDGET - frame_time).await;
|
Timer::after(RENDER_BUDGET - frame_time).await;
|
||||||
} else {
|
} else {
|
||||||
//warn!("OLED Frame took too long to render! {}ms", frame_time.as_millis());
|
warn!("OLED Frame took too long to render! {}ms", frame_time.as_millis());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).await.unwrap();
|
}).await.unwrap();
|
||||||
|
|||||||
+37
-32
@@ -1,34 +1,35 @@
|
|||||||
use embassy_time::{Duration, Instant, Timer};
|
use embassy_time::{Duration, Instant, Timer};
|
||||||
use esp_hal::{gpio::AnyPin, rmt::Rmt, time::Rate, timer::timg::Wdt};
|
use esp_hal::spi::Mode;
|
||||||
use esp_hal_smartled::{buffer_size_async, SmartLedsAdapterAsync};
|
use esp_hal::spi::master::{AnySpi, Config, Spi};
|
||||||
|
use esp_hal::{gpio::AnyPin, time::Rate, timer::timg::Wdt};
|
||||||
|
use esp_hal::dma::{AnyGdmaChannel, DmaTxBuf};
|
||||||
use figments::prelude::*;
|
use figments::prelude::*;
|
||||||
|
use figments_esp32_ws2812_dma::{DmaBuffers, Esp32Ws2812SpiDmaWriter};
|
||||||
use figments_render::gamma::GammaCurve;
|
use figments_render::gamma::GammaCurve;
|
||||||
use figments_render::output::{GammaCorrected, OutputAsync};
|
use figments_render::output::{GammaCorrected, OutputAsync};
|
||||||
use log::{info, warn};
|
use log::*;
|
||||||
|
|
||||||
use crate::graphics::display::NUM_PIXELS;
|
use crate::graphics::display::{NUM_PIXELS, RenderState};
|
||||||
use crate::{graphics::display::{BikeOutput, DisplayControls, Uniforms}, tasks::ui::UiSurfacePool};
|
use crate::{graphics::display::{BikeOutput, DisplayControls, Uniforms}, tasks::ui::UiSurfacePool};
|
||||||
|
|
||||||
|
static SPI_BUFFERS: static_cell::ConstStaticCell<DmaBuffers<u8, {NUM_PIXELS * 12 + 140}>> = static_cell::ConstStaticCell::new(DmaBuffers::new(0));
|
||||||
|
|
||||||
#[embassy_executor::task]
|
#[embassy_executor::task]
|
||||||
pub async fn render(rmt: esp_hal::peripherals::RMT<'static>, gpio: AnyPin<'static>, mut surfaces: UiSurfacePool, mut safety_surfaces: UiSurfacePool, mut controls: DisplayControls, mut wdt: Wdt<esp_hal::peripherals::TIMG0<'static>>) {
|
pub async fn render(spi: AnySpi<'static>, dma: AnyGdmaChannel<'static>, gpio: AnyPin<'static>, mut surfaces: UiSurfacePool, mut safety_surfaces: UiSurfacePool, mut controls: DisplayControls<'static>, mut wdt: Wdt<esp_hal::peripherals::TIMG0<'static>>) {
|
||||||
let frequency: Rate = Rate::from_mhz(80);
|
info!("Starting rendering task");
|
||||||
let rmt = Rmt::new(rmt, frequency)
|
|
||||||
.expect("Failed to initialize RMT").into_async();
|
|
||||||
let rmt_channel = rmt.channel0;
|
|
||||||
|
|
||||||
// FIXME: I don't know why we need to add an extra bunch of pixels, but otherwise the buffer is too small?
|
let buffers = SPI_BUFFERS.take();
|
||||||
let mut rmt_buffer = esp_hal_smartled::smart_led_buffer!(NUM_PIXELS + 25);
|
|
||||||
|
|
||||||
// FIXME: The strip is GRB, we need to fix the pixel swizzling deep within the traits
|
let tx_buf = DmaTxBuf::new(&mut buffers.tx_descriptors, &mut buffers.tx_buffer).unwrap();
|
||||||
let target = SmartLedsAdapterAsync::new_with_color(rmt_channel, gpio, &mut rmt_buffer);
|
let conf = Config::default()
|
||||||
|
.with_frequency(Rate::from_khz(3_800))
|
||||||
|
.with_mode(Mode::_0);
|
||||||
|
|
||||||
// Change this to adjust the power available; the USB spec says 500ma is the standard limit, but sometimes you can draw more from a power brick
|
let driver = Spi::new(spi, conf).unwrap()
|
||||||
const POWER_MA : u32 = 500;
|
.with_mosi(gpio)
|
||||||
|
.with_dma(dma).into_async();
|
||||||
|
|
||||||
// You probably don't need to change these values, unless your LED strip is somehow not 5 volts
|
let target = Esp32Ws2812SpiDmaWriter::new(driver, tx_buf);
|
||||||
const POWER_VOLTS : u32 = 5;
|
|
||||||
|
|
||||||
const MAX_POWER_MW : u32 = if cfg!(feature="max-usb-power") { u32::MAX } else { POWER_VOLTS * POWER_MA };
|
|
||||||
|
|
||||||
// This value is used as the 'seed' for rendering each frame, allowing us to do things like run the animation backwards, frames for double FPS, or even use system uptime for more human-paced animations
|
// This value is used as the 'seed' for rendering each frame, allowing us to do things like run the animation backwards, frames for double FPS, or even use system uptime for more human-paced animations
|
||||||
let mut uniforms = Uniforms {
|
let mut uniforms = Uniforms {
|
||||||
@@ -36,21 +37,18 @@ pub async fn render(rmt: esp_hal::peripherals::RMT<'static>, gpio: AnyPin<'stati
|
|||||||
..Uniforms::default()
|
..Uniforms::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut output = BikeOutput::new(target, MAX_POWER_MW, controls.clone());
|
let mut output = BikeOutput::new(target, controls.clone());
|
||||||
output.set_gamma(GammaCurve::new(1.3));
|
output.set_gamma(GammaCurve::new(1.3));
|
||||||
|
|
||||||
info!("Rendering started! {}ms since boot", Instant::now().as_millis());
|
info!("Rendering started! {}ms since boot", Instant::now().as_millis());
|
||||||
controls.notify_render_is_running(true);
|
|
||||||
|
|
||||||
// TODO: The prediction engine should be able to scale the display FPS down for power saving sometimes
|
let mut requested_fps= controls.fps() as u64;
|
||||||
const FPS: u64 = 80;
|
let mut render_budget = Duration::from_millis(1000 / requested_fps);
|
||||||
const RENDER_BUDGET: Duration = Duration::from_millis(1000 / FPS);
|
|
||||||
|
|
||||||
const ANIMATION_TPS: u64 = 120;
|
const ANIMATION_TPS: u64 = 120;
|
||||||
const ANIMATION_FRAME_TIME: Duration = Duration::from_millis(1000 / ANIMATION_TPS);
|
const ANIMATION_FRAME_TIME: Duration = Duration::from_millis(1000 / ANIMATION_TPS);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// FIXME: need to put the rendering loop into a deep sleep when the display is off
|
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
|
|
||||||
output.blank();
|
output.blank();
|
||||||
@@ -59,7 +57,6 @@ pub async fn render(rmt: esp_hal::peripherals::RMT<'static>, gpio: AnyPin<'stati
|
|||||||
surfaces.commit();
|
surfaces.commit();
|
||||||
safety_surfaces.commit();
|
safety_surfaces.commit();
|
||||||
surfaces.render_to(&mut output, &uniforms);
|
surfaces.render_to(&mut output, &uniforms);
|
||||||
// TODO: We should split up the safety layers so they always have full power
|
|
||||||
safety_surfaces.render_to(&mut output, &uniforms);
|
safety_surfaces.render_to(&mut output, &uniforms);
|
||||||
|
|
||||||
// Finally, write out the rendered frame
|
// Finally, write out the rendered frame
|
||||||
@@ -67,27 +64,35 @@ pub async fn render(rmt: esp_hal::peripherals::RMT<'static>, gpio: AnyPin<'stati
|
|||||||
|
|
||||||
let render_duration = Instant::now() - start;
|
let render_duration = Instant::now() - start;
|
||||||
|
|
||||||
|
let next_fps = controls.fps() as u64;
|
||||||
|
if next_fps != requested_fps {
|
||||||
|
requested_fps = next_fps;
|
||||||
|
render_budget = Duration::from_millis(1000 / requested_fps);
|
||||||
|
info!("FPS changed to {requested_fps}");
|
||||||
|
}
|
||||||
|
|
||||||
if !controls.is_on() {
|
if !controls.is_on() {
|
||||||
warn!("Renderer is sleeping zzzz");
|
warn!("Renderer is sleeping zzzz");
|
||||||
controls.notify_render_is_running(false);
|
|
||||||
output.blank();
|
output.blank();
|
||||||
output.commit_async().await.expect("Failed to commit low power frame");
|
output.commit_async().await.expect("Failed to commit low power frame");
|
||||||
wdt.disable();
|
wdt.disable();
|
||||||
controls.wait_until_display_is_on().await;
|
controls.ack().await;
|
||||||
|
controls.wait_for_state(RenderState::On).await;
|
||||||
|
controls.ack().await;
|
||||||
wdt.feed();
|
wdt.feed();
|
||||||
wdt.enable();
|
wdt.enable();
|
||||||
warn!("Renderer is awake !!!!");
|
warn!("Renderer is awake !!!!");
|
||||||
controls.notify_render_is_running(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply the FPS cap where we sleep if we are rendering fast enough
|
// Apply the FPS cap where we sleep if we are rendering fast enough
|
||||||
if render_duration < RENDER_BUDGET {
|
if render_duration < render_budget {
|
||||||
let remaining_budget = RENDER_BUDGET - render_duration;
|
let remaining_budget = render_budget - render_duration;
|
||||||
Timer::after(remaining_budget).await;
|
Timer::after(remaining_budget).await;
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, we have a problem
|
// Otherwise, we have a problem
|
||||||
// TODO: Automatically scale FPS back when this happens
|
|
||||||
warn!("Render stall! Frame took {}ms", render_duration.as_millis());
|
warn!("Render stall! Frame took {}ms", render_duration.as_millis());
|
||||||
|
// Kick the scheduler since it took so long
|
||||||
|
Timer::after_ticks(1).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Need a way to let the UI layers configure this
|
// TODO: Need a way to let the UI layers configure this
|
||||||
|
|||||||
+31
-39
@@ -1,12 +1,12 @@
|
|||||||
use embassy_sync::pubsub::DynSubscriber;
|
use embassy_sync::pubsub::DynSubscriber;
|
||||||
use embassy_time::Duration;
|
use embassy_time::Duration;
|
||||||
use figments::prelude::*;
|
use figments::{liber8tion::interpolate::Fract8, prelude::*};
|
||||||
use figments_render::output::Brightness;
|
use figments_render::output::Brightness;
|
||||||
use rgb::Rgba;
|
use rgb::Rgba;
|
||||||
use core::fmt::Debug;
|
use core::fmt::Debug;
|
||||||
use log::*;
|
use log::*;
|
||||||
|
|
||||||
use crate::{animation::{AnimDisplay, AnimatedSurface, Animation}, events::{Personality, Prediction}, graphics::{display::{DisplayControls, SegmentSpace, Uniforms}, shaders::*}, tasks::ui::UiSurfacePool};
|
use crate::{animation::{AnimDisplay, AnimatedSurface, Animation}, events::{Personality, Prediction}, graphics::{display::{DEFAULT_FPS, DisplayControls, LOW_POWER_FPS, SegmentSpace, Uniforms}, shaders::*}, tasks::ui::UiSurfacePool};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct SafetyUi<S: Surface> {
|
pub struct SafetyUi<S: Surface> {
|
||||||
@@ -16,11 +16,11 @@ pub struct SafetyUi<S: Surface> {
|
|||||||
|
|
||||||
// The overlay covers everything and is used to implement a power-on and power-off animation.
|
// The overlay covers everything and is used to implement a power-on and power-off animation.
|
||||||
overlay: AnimatedSurface<S>,
|
overlay: AnimatedSurface<S>,
|
||||||
display: DisplayControls
|
display: DisplayControls<'static>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pixel = Rgba<u8>>> SafetyUi<S> {
|
impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pixel = Rgba<u8>>> SafetyUi<S> {
|
||||||
pub fn new<SS: Surfaces<SegmentSpace, Surface = S>>(surfaces: &mut SS, display: DisplayControls) -> Self where SS::Error: Debug {
|
pub fn new<SS: Surfaces<Surface = S>>(surfaces: &mut SS, display: DisplayControls<'static>) -> Self where SS::Error: Debug {
|
||||||
let ret = Self {
|
let ret = Self {
|
||||||
overlay: SurfaceBuilder::build(surfaces)
|
overlay: SurfaceBuilder::build(surfaces)
|
||||||
.rect(Rectangle::everything())
|
.rect(Rectangle::everything())
|
||||||
@@ -31,13 +31,13 @@ impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pix
|
|||||||
.rect(Rectangle::new_from_coordinates(0, 0, 255, 0))
|
.rect(Rectangle::new_from_coordinates(0, 0, 255, 0))
|
||||||
.shader(Headlight::default())
|
.shader(Headlight::default())
|
||||||
.visible(false)
|
.visible(false)
|
||||||
.opacity(0)
|
.opacity(Fract8::MIN)
|
||||||
.finish().unwrap().into(),
|
.finish().unwrap().into(),
|
||||||
brakelight: SurfaceBuilder::build(surfaces)
|
brakelight: SurfaceBuilder::build(surfaces)
|
||||||
.rect(Brakelight::rectangle())
|
.rect(Brakelight::rectangle())
|
||||||
.shader(Brakelight::default())
|
.shader(Brakelight::default())
|
||||||
.visible(false)
|
.visible(false)
|
||||||
.opacity(0)
|
.opacity(Fract8::MIN)
|
||||||
.finish().unwrap().into(),
|
.finish().unwrap().into(),
|
||||||
display
|
display
|
||||||
};
|
};
|
||||||
@@ -48,7 +48,7 @@ impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pix
|
|||||||
pub async fn sleep(&mut self) {
|
pub async fn sleep(&mut self) {
|
||||||
info!("Running sleep sequence");
|
info!("Running sleep sequence");
|
||||||
let mut disp_anim = AnimDisplay(&mut self.display);
|
let mut disp_anim = AnimDisplay(&mut self.display);
|
||||||
TURN_OFF.apply(&mut disp_anim).await;
|
TURN_OFF_FAST.apply([&mut disp_anim]).await;
|
||||||
|
|
||||||
warn!("Resetting safety lights");
|
warn!("Resetting safety lights");
|
||||||
self.brakelight.set_visible(false);
|
self.brakelight.set_visible(false);
|
||||||
@@ -57,8 +57,7 @@ impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pix
|
|||||||
warn!("Turning off display");
|
warn!("Turning off display");
|
||||||
self.display.set_on(false);
|
self.display.set_on(false);
|
||||||
// Wait for the display hardware to actually turn off, before we return to process the next event, which could cause funky behaviors.
|
// Wait for the display hardware to actually turn off, before we return to process the next event, which could cause funky behaviors.
|
||||||
// FIXME: also deadlocks :(
|
self.display.wait_until_render_is_running().await;
|
||||||
//self.display.render_is_running.wait().await;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn wake(&mut self) {
|
pub async fn wake(&mut self) {
|
||||||
@@ -66,30 +65,25 @@ impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pix
|
|||||||
|
|
||||||
info!("Turning on display");
|
info!("Turning on display");
|
||||||
// Turn on the display hardware
|
// Turn on the display hardware
|
||||||
self.display.set_brightness(0);
|
self.display.set_brightness(Fract8::MIN);
|
||||||
self.display.set_on(true);
|
self.display.set_on(true);
|
||||||
// Wait for the renderer to start running again
|
// Wait for the renderer to start running again
|
||||||
// FIXME: This deadlocks :(
|
self.display.wait_for_ack(RenderState::On).await;
|
||||||
//self.display.render_is_running.wait().await;
|
|
||||||
|
|
||||||
let fade_in = Animation::default().duration(Duration::from_secs(3)).from(0).to(255);
|
|
||||||
trace!("Fading in brightness with overlay={:?}", self.overlay);
|
trace!("Fading in brightness with overlay={:?}", self.overlay);
|
||||||
self.overlay.set_opacity(255);
|
self.overlay.set_opacity(Fract8::MAX);
|
||||||
self.overlay.set_visible(true);
|
self.overlay.set_visible(true);
|
||||||
fade_in.apply(&mut AnimDisplay(&mut self.display)).await;
|
TURN_ON_SLOW.apply([&mut AnimDisplay(&mut self.display)]).await;
|
||||||
|
|
||||||
warn!("Turning on safety lights");
|
warn!("Turning on safety lights");
|
||||||
self.headlight.set_opacity(0);
|
self.headlight.set_opacity(Fract8::MIN);
|
||||||
self.headlight.set_visible(true);
|
self.headlight.set_visible(true);
|
||||||
self.brakelight.set_opacity(0);
|
self.brakelight.set_opacity(Fract8::MIN);
|
||||||
self.brakelight.set_visible(true);
|
self.brakelight.set_visible(true);
|
||||||
embassy_futures::join::join(
|
TURN_ON_SLOW.apply([&mut self.headlight, &mut self.brakelight]).await;
|
||||||
fade_in.apply(&mut self.headlight),
|
|
||||||
fade_in.apply(&mut self.brakelight)
|
|
||||||
).await;
|
|
||||||
|
|
||||||
info!("Fade out overlay");
|
info!("Fade out overlay");
|
||||||
TURN_OFF.apply(&mut self.overlay).await;
|
TURN_OFF_FAST.apply([&mut self.overlay]).await;
|
||||||
self.overlay.set_visible(false);
|
self.overlay.set_visible(false);
|
||||||
info!("Wakeup complete!");
|
info!("Wakeup complete!");
|
||||||
}
|
}
|
||||||
@@ -98,18 +92,20 @@ impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pix
|
|||||||
if let Prediction::SetPersonality(personality) = event { match personality {
|
if let Prediction::SetPersonality(personality) = event { match personality {
|
||||||
Personality::Active => {
|
Personality::Active => {
|
||||||
// FIXME: These should be a Off/Low/High enum, so the stopping brake looks different from the dayrunning brake.
|
// FIXME: These should be a Off/Low/High enum, so the stopping brake looks different from the dayrunning brake.
|
||||||
|
self.display.set_fps(DEFAULT_FPS);
|
||||||
warn!("Active personality: Turning on safety lights");
|
warn!("Active personality: Turning on safety lights");
|
||||||
embassy_futures::join::join(
|
TURN_ON_FAST.apply([
|
||||||
TURN_ON.apply(&mut self.brakelight),
|
&mut self.brakelight,
|
||||||
TURN_ON.apply(&mut self.headlight)
|
&mut self.headlight
|
||||||
).await;
|
]).await;
|
||||||
},
|
},
|
||||||
Personality::Parked => {
|
Personality::Parked => {
|
||||||
warn!("Idle personality: Turning off safety lights");
|
warn!("Idle personality: Turning off safety lights");
|
||||||
embassy_futures::join::join(
|
TURN_OFF_FAST.apply([
|
||||||
TURN_OFF.apply(&mut self.brakelight),
|
&mut self.brakelight,
|
||||||
TURN_OFF.apply(&mut self.headlight)
|
&mut self.headlight
|
||||||
).await;
|
]).await;
|
||||||
|
self.display.set_fps(LOW_POWER_FPS);
|
||||||
},
|
},
|
||||||
Personality::Sleeping => {
|
Personality::Sleeping => {
|
||||||
warn!("Sleeping personality: Safety UI is going to sleep");
|
warn!("Sleeping personality: Safety UI is going to sleep");
|
||||||
@@ -117,23 +113,19 @@ impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pix
|
|||||||
},
|
},
|
||||||
Personality::Waking => {
|
Personality::Waking => {
|
||||||
warn!("Waking personality: Waking up safety UI");
|
warn!("Waking personality: Waking up safety UI");
|
||||||
|
self.display.set_fps(DEFAULT_FPS);
|
||||||
self.wake().await;
|
self.wake().await;
|
||||||
},
|
},
|
||||||
} }
|
} }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const TURN_ON: Animation = Animation::new().duration(Duration::from_secs(1)).from(0).to(255);
|
const TURN_ON_FAST: Animation<Fract8> = Animation::new().duration(Duration::from_secs(1)).from(Fract8::MIN).to(Fract8::MAX);
|
||||||
const TURN_OFF: Animation = Animation::new().duration(Duration::from_secs(1)).from(255).to(0);
|
const TURN_OFF_FAST: Animation<Fract8> = Animation::new().duration(Duration::from_secs(1)).from(Fract8::MAX).to(Fract8::MIN);
|
||||||
|
const TURN_ON_SLOW: Animation<Fract8> = Animation::new().duration(Duration::from_secs(3)).from(Fract8::MIN).to(Fract8::MAX);
|
||||||
|
|
||||||
#[embassy_executor::task]
|
#[embassy_executor::task]
|
||||||
pub async fn safety_ui_main(mut events: DynSubscriber<'static, Prediction>, mut ui: SafetyUi<<UiSurfacePool as Surfaces<SegmentSpace>>::Surface>) {
|
pub async fn safety_ui_main(mut events: DynSubscriber<'static, Prediction>, mut ui: SafetyUi<<UiSurfacePool as Surfaces>::Surface>) {
|
||||||
// Wait for the renderer to start running
|
|
||||||
//ui.display.render_is_running.wait().await;
|
|
||||||
trace!("spooling until render starts ui={ui:?}");
|
|
||||||
ui.display.wait_until_render_is_running().await;
|
|
||||||
|
|
||||||
trace!("spooling wait task ui={ui:?}");
|
|
||||||
// Run the wake sequence, and turn on the lights
|
// Run the wake sequence, and turn on the lights
|
||||||
ui.wake().await;
|
ui.wake().await;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,154 @@
|
|||||||
|
use core::cell::RefCell;
|
||||||
|
|
||||||
|
use embassy_sync::pubsub::DynSubscriber;
|
||||||
|
use embedded_hal_bus::spi::RefCellDevice;
|
||||||
|
use embedded_sdmmc::{Mode, SdCard, TimeSource, VolumeIdx, VolumeManager};
|
||||||
|
use esp_hal::{delay::Delay, gpio::{AnyPin, Level, Output}, spi::{master::{AnySpi, Config, Spi}}, time::Rate};
|
||||||
|
use rmp::encode::{RmpWrite, RmpWriteErr};
|
||||||
|
|
||||||
|
use crate::{events::Measurement, gpio_interrupt::PinInterrupt, simdata::IMUReading, storage::SimDataRecorder};
|
||||||
|
use log::*;
|
||||||
|
|
||||||
|
struct SdTimeSource;
|
||||||
|
|
||||||
|
// TODSO: We need a whole time keeping stack that can sync from GPS or NTP
|
||||||
|
impl TimeSource for SdTimeSource {
|
||||||
|
fn get_timestamp(&self) -> embedded_sdmmc::Timestamp {
|
||||||
|
embedded_sdmmc::Timestamp::from_fat(0, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::single_match)]
|
||||||
|
#[embassy_executor::task]
|
||||||
|
pub async fn sdcard_task(
|
||||||
|
spi: AnySpi<'static>,
|
||||||
|
sck: AnyPin<'static>,
|
||||||
|
mosi: AnyPin<'static>,
|
||||||
|
miso: AnyPin<'static>,
|
||||||
|
cs: Output<'static>,
|
||||||
|
sd_detect_interrupt: PinInterrupt<'static>,
|
||||||
|
mut recording_stream: DynSubscriber<'static, Measurement>
|
||||||
|
) {
|
||||||
|
|
||||||
|
let sd_spi = Spi::new(spi, Config::default().with_frequency(Rate::from_khz(400)).with_mode(esp_hal::spi::Mode::_0))
|
||||||
|
.unwrap()
|
||||||
|
.with_sck(sck)
|
||||||
|
.with_mosi(mosi)
|
||||||
|
.with_miso(miso)
|
||||||
|
.into_async();
|
||||||
|
|
||||||
|
let sd_ref = RefCell::new(sd_spi);
|
||||||
|
let sd_device = RefCellDevice::new(&sd_ref, cs, Delay::new()).unwrap();
|
||||||
|
let mut sd_card = SdCard::new(sd_device, Delay::new());
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match sd_detect_interrupt.wait_for_interrupt().await {
|
||||||
|
Level::High => {
|
||||||
|
info!("SD card inserted!");
|
||||||
|
sd_detect_interrupt.listen();
|
||||||
|
},
|
||||||
|
Level::Low => {
|
||||||
|
info!("SD card removed!");
|
||||||
|
sd_detect_interrupt.listen();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sd_card.mark_card_uninit();
|
||||||
|
|
||||||
|
|
||||||
|
match sd_card.get_card_type() {
|
||||||
|
Some(card_type) => info!("SD card detected: {:?}", card_type),
|
||||||
|
None => {
|
||||||
|
error!("Failed to determine SD card type!");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let volumes = VolumeManager::new(sd_card, SdTimeSource);
|
||||||
|
let vol = volumes.open_volume(VolumeIdx(0)).unwrap();
|
||||||
|
let rootfs = vol.open_root_dir().unwrap();
|
||||||
|
|
||||||
|
let stream = rootfs.open_file_in_dir("renderbug-sensors.rmp",Mode::ReadWriteCreateOrTruncate).unwrap();
|
||||||
|
let rmp_stream = EmbeddedRmp(stream);
|
||||||
|
|
||||||
|
/*let mut recorder = SimDataStream::open(rmp_stream, StreamType::Bundle);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
// FIXME: THis chunk could really go into an impl From<Measurement> for EventRecord
|
||||||
|
match recording_stream.next_message_pure().await {
|
||||||
|
Measurement::IMU { accel, gyro } => {
|
||||||
|
let reading = IMUReading {
|
||||||
|
accel_x: accel.x as f64,
|
||||||
|
accel_y: accel.y as f64,
|
||||||
|
accel_z: accel.z as f64,
|
||||||
|
gyro_x: gyro.x as f64,
|
||||||
|
gyro_y: gyro.y as f64,
|
||||||
|
gyro_z: gyro.z as f64
|
||||||
|
};
|
||||||
|
/*if recorder.write_next(reading).is_err() {
|
||||||
|
error!("Failed to write IMU reading to SD card");
|
||||||
|
break;
|
||||||
|
}*/
|
||||||
|
info!("Wrote IMU to SD card");
|
||||||
|
},
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
drop(recorder);*/
|
||||||
|
drop(rmp_stream);
|
||||||
|
drop(rootfs);
|
||||||
|
drop(vol);
|
||||||
|
|
||||||
|
sd_card = volumes.free().0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct EmbeddedRmp<W>(W);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct EmbeddedRmpError<T>(T);
|
||||||
|
|
||||||
|
impl<T: core::fmt::Debug + 'static> RmpWriteErr for EmbeddedRmpError<T> {
|
||||||
|
}
|
||||||
|
impl<T: core::fmt::Debug + 'static> RmpReadErr for EmbeddedRmpError<T> {
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> core::fmt::Display for EmbeddedRmpError<T> where T: core::fmt::Debug {
|
||||||
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
|
write!(f, "{:?}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: embedded_io::Write> RmpWrite for EmbeddedRmp<W> where W::Error: core::fmt::Debug + 'static {
|
||||||
|
type Error = EmbeddedRmpError<W::Error>;
|
||||||
|
|
||||||
|
fn write_bytes(&mut self, buf: &[u8]) -> Result<(), Self::Error> {
|
||||||
|
self.0.write(buf).map_err(|e| { EmbeddedRmpError(e)})?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: embedded_io::Read> RmpRead for EmbeddedRmp<W> where W::Error: core::fmt::Debug + 'static {
|
||||||
|
type Error = EmbeddedRmpError<ReadExactError<W::Error>>;
|
||||||
|
|
||||||
|
fn read_exact_buf(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> {
|
||||||
|
self.0.read_exact(buf).map_err(|e| { EmbeddedRmpError(e) })?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: embedded_io::Write> embedded_io::ErrorType for EmbeddedRmp<W> {
|
||||||
|
type Error = W::Error;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: embedded_io::Write> embedded_io::Write for EmbeddedRmp<W> {
|
||||||
|
fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
|
||||||
|
self.0.write(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> Result<(), Self::Error> {
|
||||||
|
self.0.flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
+165
-55
@@ -1,23 +1,54 @@
|
|||||||
|
use core::usize;
|
||||||
|
|
||||||
use embassy_sync::channel::DynamicSender;
|
use embassy_sync::channel::DynamicSender;
|
||||||
use embassy_time::{Duration, Timer};
|
use embassy_time::{Duration, Timer};
|
||||||
use embedded_storage::ReadStorage;
|
use embedded_storage::{ReadStorage, Storage};
|
||||||
use esp_bootloader_esp_idf::partitions::PartitionTable;
|
use esp_bootloader_esp_idf::partitions::PartitionTable;
|
||||||
|
use esp_hal::time::Instant;
|
||||||
use esp_storage::FlashStorage;
|
use esp_storage::FlashStorage;
|
||||||
|
use figments::liber8tion::interpolate::Fract8;
|
||||||
use nalgebra::{Vector2, Vector3};
|
use nalgebra::{Vector2, Vector3};
|
||||||
use log::*;
|
use log::*;
|
||||||
use rmp::decode::ValueReadError;
|
use rmp::{decode::{RmpRead, RmpReadErr, ValueReadError}, encode::{RmpWrite, RmpWriteErr, ValueWriteError}};
|
||||||
|
|
||||||
use crate::{Breaker, events::{Measurement, SensorSource, SensorState}, simdata::{AnnotationReading, EventRecord, EventStreamHeader, GPSReading, IMUReading, RmpData, SimDataError, StreamEvent, StreamHeader, StreamIndex, StreamType}, storage::{RangeReadError, RangeReader, SharedFlash}};
|
use crate::{Breaker, events::{Measurement, SensorSource, SensorState}, simdata::{AnnotationReading, BundleEventHeader, EventRecord, EventStreamHeader, GPSReading, IMUReading, RmpData, SimDataError, StreamEvent, StreamHeader, StreamIndex, StreamType}, storage::{SharedFlash, StorageRange, StorageRangeError}};
|
||||||
|
|
||||||
pub struct SimDataTable<S> {
|
pub struct SimDataTable<S> {
|
||||||
reader: RangeReader<S>,
|
reader: S,
|
||||||
count: usize,
|
count: usize,
|
||||||
index: usize
|
index: usize
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: ReadStorage + Clone> SimDataTable<S> where S::Error: core::fmt::Debug + 'static {
|
impl<S: RmpRead<Error = E>, E: RmpReadErr + RmpWriteErr> SimDataTable<S> {
|
||||||
|
pub fn open(mut reader: S) -> Result<Self, SimDataError<E>> {
|
||||||
|
if let Ok(index) = StreamIndex::from_rmp(&mut reader) {
|
||||||
|
info!("Found stream index: {index:?}");
|
||||||
|
Ok(Self {
|
||||||
|
reader,
|
||||||
|
count: index.count,
|
||||||
|
index: 0
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(SimDataError::StreamIndexMissing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn open(storage: S, partitions: PartitionTable<'_>) -> Result<Self, SimDataError<S>> {
|
impl<S: RmpWrite<Error = E>, E: RmpReadErr + RmpWriteErr> SimDataTable<S> {
|
||||||
|
pub fn create(mut writer: S) -> Result<Self, SimDataError<E>> {
|
||||||
|
// FIXME: The stream header should use a fixed size integer for the count, instead of the dynamic array length type
|
||||||
|
info!("Writing new stream index");
|
||||||
|
StreamIndex { count: 0 }.write_rmp(&mut writer)?;
|
||||||
|
Ok(Self {
|
||||||
|
reader: writer,
|
||||||
|
count: 0,
|
||||||
|
index: 0
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: ReadStorage<Error = E>, E: core::fmt::Debug + 'static> SimDataTable<StorageRange<S>> {
|
||||||
|
pub fn find_partition(storage: S, partitions: PartitionTable<'_>) -> Result<StorageRange<S>, SimDataError<StorageRangeError<E>>> {
|
||||||
let partition_type = esp_bootloader_esp_idf::partitions::PartitionType::Data(
|
let partition_type = esp_bootloader_esp_idf::partitions::PartitionType::Data(
|
||||||
esp_bootloader_esp_idf::partitions::DataPartitionSubType::Undefined,
|
esp_bootloader_esp_idf::partitions::DataPartitionSubType::Undefined,
|
||||||
);
|
);
|
||||||
@@ -29,39 +60,53 @@ impl<S: ReadStorage + Clone> SimDataTable<S> where S::Error: core::fmt::Debug +
|
|||||||
let start = data_partition.offset() as usize;
|
let start = data_partition.offset() as usize;
|
||||||
let end = data_partition.len() as usize + start;
|
let end = data_partition.len() as usize + start;
|
||||||
info!("Opening simulation data at {start:#02x}:{end:#02x}");
|
info!("Opening simulation data at {start:#02x}:{end:#02x}");
|
||||||
let mut reader = RangeReader::new(storage.clone(), start, end);
|
Ok(StorageRange::new(storage, start, end))
|
||||||
if let Ok(index) = StreamIndex::from_rmp(&mut reader) {
|
|
||||||
info!("Found stream index: {index:?}");
|
|
||||||
Ok(Self {
|
|
||||||
reader,
|
|
||||||
count: index.count,
|
|
||||||
index: 0
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
error!("Stream index is missing! Have you flashed the partition yet?");
|
|
||||||
Err(SimDataError::StreamIndexMissing)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: ReadStorage + Clone + core::fmt::Debug> Iterator for SimDataTable<S> where S::Error: core::fmt::Debug + 'static {
|
impl<S: Storage<Error = E> + Clone + core::fmt::Debug, E: core::fmt::Debug + 'static> SimDataTable<StorageRange<S>> {
|
||||||
type Item = SimDataReader<S>;
|
pub fn append_new_stream(&mut self, streamid: StreamType) -> Result<StorageRange<S>, SimDataError<StorageRangeError<E>>> {
|
||||||
|
let stream_size = self.reader.capacity() - self.reader.pos() - 6;
|
||||||
|
if stream_size < 1024 {
|
||||||
|
error!("Not enough space left in the simulation data partition to create a new stream!");
|
||||||
|
return Err(SimDataError::NotEnoughSpace);
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamHeader { id: streamid, size: stream_size }.write_rmp(&mut self.reader)?;
|
||||||
|
self.count += 1;
|
||||||
|
self.index += 1;
|
||||||
|
let reset_pos = self.reader.pos();
|
||||||
|
self.reader.seek_abs(0).unwrap();
|
||||||
|
StreamIndex { count: self.count }.write_rmp(&mut self.reader)?;
|
||||||
|
self.reader.seek_abs(reset_pos).unwrap();
|
||||||
|
|
||||||
|
info!("Creating new stream id={streamid:?} capacity={stream_size:#02x} range={:#02x}:{:#02x}", self.reader.abs_start() + self.reader.pos(), self.reader.abs_start() + stream_size);
|
||||||
|
|
||||||
|
Ok(self.reader.subset(stream_size).unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: ReadStorage<Error = E> + Clone + core::fmt::Debug, E: core::fmt::Debug + 'static> Iterator for SimDataTable<StorageRange<S>> {
|
||||||
|
type Item = (StreamHeader, StorageRange<S>);
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
if self.index >= self.count {
|
if self.index >= self.count {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
loop {
|
loop {
|
||||||
|
let reset_pos = self.reader.pos();
|
||||||
match StreamHeader::from_rmp(&mut self.reader) {
|
match StreamHeader::from_rmp(&mut self.reader) {
|
||||||
Ok(header) => {
|
Ok(header) => {
|
||||||
match self.reader.subset(header.size) {
|
info!("Found stream header: {header:?}");
|
||||||
|
let stream_size = if header.size == 0 { self.reader.capacity() - self.reader.pos() } else { header.size };
|
||||||
|
match self.reader.subset(stream_size) {
|
||||||
Ok(sensor_reader) => {
|
Ok(sensor_reader) => {
|
||||||
self.reader.seek(header.size).unwrap_or_else(|err| {
|
self.reader.seek(stream_size).unwrap_or_else(|err| {
|
||||||
error!("Simulation data appears bigger than the storage capacity: {err:?}")
|
error!("Simulation data appears bigger than the storage capacity: {err:?}")
|
||||||
});
|
});
|
||||||
self.index += 1;
|
self.index += 1;
|
||||||
debug!("Found header={header:?}");
|
debug!("Found header={header:?}");
|
||||||
return Some(SimDataReader::open(sensor_reader, header.id));
|
return Some((header, sensor_reader));
|
||||||
},
|
},
|
||||||
err => {
|
err => {
|
||||||
error!("Could not open the next simulation data chunk: {err:?}");
|
error!("Could not open the next simulation data chunk: {err:?}");
|
||||||
@@ -78,6 +123,8 @@ impl<S: ReadStorage + Clone + core::fmt::Debug> Iterator for SimDataTable<S> whe
|
|||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!("Read error while reading next chunk in simulation stream table {err:?}");
|
error!("Read error while reading next chunk in simulation stream table {err:?}");
|
||||||
|
// Reset back to right before we tried reading the stream header, so that append_new_stream() places it correctly.
|
||||||
|
self.reader.seek_abs(reset_pos).unwrap();
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -86,10 +133,11 @@ impl<S: ReadStorage + Clone + core::fmt::Debug> Iterator for SimDataTable<S> whe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SimDataReader<S> {
|
pub struct SimDataStream<S> {
|
||||||
reader: RangeReader<S>,
|
reader: S,
|
||||||
srcid: SensorSource,
|
srcid: StreamType,
|
||||||
runtime: Duration,
|
last_stamp: Instant,
|
||||||
|
//runtime: Duration,
|
||||||
event_count: usize,
|
event_count: usize,
|
||||||
index: usize
|
index: usize
|
||||||
}
|
}
|
||||||
@@ -111,79 +159,141 @@ impl From<GPSReading> for Measurement {
|
|||||||
|
|
||||||
impl From<AnnotationReading> for Measurement {
|
impl From<AnnotationReading> for Measurement {
|
||||||
fn from(value: AnnotationReading) -> Self {
|
fn from(value: AnnotationReading) -> Self {
|
||||||
warn!("ANNOTATION: {}", core::str::from_utf8(&value.buf).unwrap());
|
Measurement::Annotation(value.buf)
|
||||||
Measurement::Annotation
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: ReadStorage> SimDataReader<S> where S::Error: core::fmt::Debug + 'static {
|
impl<S> SimDataStream<S> {
|
||||||
pub fn open(mut reader: RangeReader<S>, stream_type: StreamType) -> Self {
|
pub fn srcid(&self) -> StreamType {
|
||||||
debug!("Opening {stream_type:?} sim data chunk");
|
self.srcid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Checkpoint {
|
||||||
|
fn checkpoint(&mut self);
|
||||||
|
fn rollback(&mut self);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: Checkpoint + RmpRead<Error = E>, E: RmpWriteErr + RmpReadErr> SimDataStream<S> {
|
||||||
|
pub fn open(mut reader: S, stream_type: StreamType) -> Self {
|
||||||
|
info!("Opening {stream_type:?} sim data chunk");
|
||||||
let event_count = EventStreamHeader::from_rmp(&mut reader).unwrap().count;
|
let event_count = EventStreamHeader::from_rmp(&mut reader).unwrap().count;
|
||||||
debug!("Found {event_count} events!");
|
info!("Found {event_count} events!");
|
||||||
Self {
|
Self {
|
||||||
reader,
|
reader,
|
||||||
srcid: stream_type.into(),
|
srcid: stream_type,
|
||||||
runtime: Default::default(),
|
//runtime: Default::default(),
|
||||||
|
last_stamp: Instant::now(),
|
||||||
event_count,
|
event_count,
|
||||||
index: 0
|
index: 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn srcid(&self) -> SensorSource {
|
fn read_next_event<T: EventRecord + Into<Measurement>>(&mut self) -> Result<(Duration, Measurement), SimDataError<E>> {
|
||||||
self.srcid
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn read_next_event<T: EventRecord + Into<Measurement>>(&mut self) -> Result<Measurement, SimDataError<ValueReadError<RangeReadError<S::Error>>>> {
|
|
||||||
let event = StreamEvent::<T>::from_rmp(&mut self.reader)?;
|
let event = StreamEvent::<T>::from_rmp(&mut self.reader)?;
|
||||||
let delay = embassy_time::Duration::from_millis((event.timecode * 1000.0) as u64);
|
let delay = embassy_time::Duration::from_millis((event.timecode * 1000.0) as u64);
|
||||||
self.runtime += delay;
|
Ok((delay, event.data.into()))
|
||||||
Timer::after(delay).await;
|
|
||||||
Ok(event.data.into())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn read_next(&mut self) -> Result<Option<Measurement>, SimDataError<ValueReadError<RangeReadError<S::Error>>>> {
|
pub fn read_next(&mut self) -> Result<Option<(Duration, Measurement)>, SimDataError<E>> {
|
||||||
if self.index < self.event_count {
|
if self.index < self.event_count {
|
||||||
self.index += 1;
|
self.index += 1;
|
||||||
|
self.reader.checkpoint();
|
||||||
|
let next_id = match self.srcid {
|
||||||
|
StreamType::Bundle => BundleEventHeader::from_rmp(&mut self.reader).inspect_err(|_| { self.reader.rollback() })?.id,
|
||||||
|
_ => self.srcid
|
||||||
|
};
|
||||||
// The read_* functions can only ever return a valid result, or a data/reading error, so we map them into a Some()
|
// The read_* functions can only ever return a valid result, or a data/reading error, so we map them into a Some()
|
||||||
Ok(Some(match self.srcid {
|
let (delay, evt) = match next_id {
|
||||||
SensorSource::IMU => self.read_next_event::<IMUReading>().await?,
|
StreamType::IMU => self.read_next_event::<IMUReading>().inspect_err(|_| { self.reader.rollback() })?,
|
||||||
SensorSource::GPS => self.read_next_event::<GPSReading>().await?,
|
StreamType::GPS => self.read_next_event::<GPSReading>().inspect_err(|_| { self.reader.rollback() })?,
|
||||||
SensorSource::Annotations => self.read_next_event::<AnnotationReading>().await?,
|
StreamType::Annotations => self.read_next_event::<AnnotationReading>().inspect_err(|_| { self.reader.rollback() })?,
|
||||||
srcid => unimplemented!("{srcid:?} is not a simulatable sensor yet!")
|
srcid => unimplemented!("{srcid:?} is not a simulatable sensor yet!")
|
||||||
}))
|
};
|
||||||
|
Ok(Some((delay, evt)))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn read_next_and_wait(&mut self) -> Result<Option<Measurement>, SimDataError<E>> {
|
||||||
|
if let Some((delay, evt)) = self.read_next()? {
|
||||||
|
trace!("waiting {delay}");
|
||||||
|
Timer::after(delay).await;
|
||||||
|
self.last_stamp = Instant::now();
|
||||||
|
Ok(Some(evt))
|
||||||
} else {
|
} else {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<S: Storage<Error = SE>, SE: core::fmt::Debug + 'static> SimDataStream<StorageRange<S>> {
|
||||||
|
pub fn write_next<T: EventRecord>(&mut self, event: T) -> Result<(), SimDataError<StorageRangeError<SE>>> {
|
||||||
|
BundleEventHeader { id: T::stream_id() }.write_rmp(&mut self.reader)?;
|
||||||
|
let now = Instant::now();
|
||||||
|
let event = StreamEvent {
|
||||||
|
data: event,
|
||||||
|
timecode: (now - self.last_stamp).as_millis() as f64 / 1000.0
|
||||||
|
};
|
||||||
|
event.write_rmp(&mut self.reader)?;
|
||||||
|
self.last_stamp = now;
|
||||||
|
self.event_count = self.event_count.checked_add(1).ok_or(SimDataError::NotEnoughSpace)?;
|
||||||
|
|
||||||
|
self.write_header()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_header(&mut self) -> Result<(), SimDataError<StorageRangeError<SE>>> {
|
||||||
|
let reset_pos = self.reader.pos();
|
||||||
|
self.reader.seek_abs(0).unwrap();
|
||||||
|
trace!("Writing event stream header with count={} to {:#x}", self.event_count, self.reader.abs_start());
|
||||||
|
EventStreamHeader { count: self.event_count }.write_rmp(&mut self.reader)?;
|
||||||
|
self.reader.seek_abs(reset_pos).unwrap();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create(mut storage: StorageRange<S>, stream_type: StreamType) -> Self {
|
||||||
|
storage.seek_abs(0).unwrap();
|
||||||
|
EventStreamHeader { count: 0 }.write_rmp(&mut storage).unwrap();
|
||||||
|
Self {
|
||||||
|
reader: storage,
|
||||||
|
srcid: stream_type,
|
||||||
|
//runtime: Default::default(),
|
||||||
|
last_stamp: Instant::now(),
|
||||||
|
event_count: 0,
|
||||||
|
index: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[embassy_executor::task(pool_size = 3)]
|
#[embassy_executor::task(pool_size = 3)]
|
||||||
pub async fn simulation_task(mut reader: SimDataReader<SharedFlash<FlashStorage>>, events: DynamicSender<'static, Measurement>) {
|
pub async fn simulation_task(mut reader: SimDataStream<StorageRange<SharedFlash<FlashStorage<'static>>>>, events: DynamicSender<'static, Measurement>) {
|
||||||
warn!("Starting simulation for {:?}", reader.srcid());
|
warn!("Starting simulation for {:?}", reader.srcid());
|
||||||
|
|
||||||
events.send(Measurement::SensorHardwareStatus(SensorSource::Simulation, SensorState::AcquiringFix)).await;
|
events.send(Measurement::SensorHardwareStatus(SensorSource::Simulation, SensorState::AcquiringFix)).await;
|
||||||
events.send(Measurement::SensorHardwareStatus(reader.srcid(), SensorState::Online)).await;
|
//events.send(Measurement::SensorHardwareStatus(reader.srcid().into(), SensorState::Online)).await;
|
||||||
|
|
||||||
let mut idx = 0;
|
let mut idx = 0;
|
||||||
let mut idx_breaker = Breaker::default();
|
let mut idx_breaker = Breaker::default();
|
||||||
|
|
||||||
// TODO: SimulationProgress updates
|
// TODO: SimulationProgress updates
|
||||||
loop {
|
loop {
|
||||||
match reader.read_next().await {
|
match reader.read_next_and_wait().await {
|
||||||
Ok(Some(next_evt)) => {
|
Ok(Some(next_evt)) => {
|
||||||
events.send(next_evt).await;
|
events.send(next_evt).await;
|
||||||
let pct = (idx as f32) / (reader.event_count as f32);
|
let pct = (idx as f32) / (reader.event_count as f32);
|
||||||
idx_breaker.set((pct * 255.0) as u8);
|
idx_breaker.set((pct * 255.0) as u8);
|
||||||
if let Some(pct) = idx_breaker.read_tripped() {
|
if let Some(pct) = idx_breaker.read_tripped() {
|
||||||
events.send(Measurement::SimulationProgress(reader.srcid(), reader.runtime, pct)).await;
|
//events.send(Measurement::SimulationProgress(reader.srcid().into(), reader.runtime, Fract8::from_raw(pct))).await;
|
||||||
}
|
}
|
||||||
idx += 1;
|
idx += 1;
|
||||||
},
|
},
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
warn!("End of simulation data stream");
|
warn!("End of simulation data stream");
|
||||||
break
|
break
|
||||||
}
|
},
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
warn!("Error during sensor stream: {err:?}");
|
warn!("Error during sensor stream: {err:?}");
|
||||||
break
|
break
|
||||||
@@ -191,7 +301,7 @@ pub async fn simulation_task(mut reader: SimDataReader<SharedFlash<FlashStorage>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
events.send(Measurement::SensorHardwareStatus(reader.srcid(), SensorState::Offline)).await;
|
//events.send(Measurement::SensorHardwareStatus(reader.srcid().into(), SensorState::Offline)).await;
|
||||||
events.send(Measurement::SensorHardwareStatus(SensorSource::Simulation, SensorState::Degraded)).await;
|
events.send(Measurement::SensorHardwareStatus(SensorSource::Simulation, SensorState::Degraded)).await;
|
||||||
|
|
||||||
warn!("End of simulation for {:?}", reader.srcid());
|
warn!("End of simulation for {:?}", reader.srcid());
|
||||||
|
|||||||
+33
-42
@@ -1,13 +1,25 @@
|
|||||||
use embassy_sync::pubsub::DynSubscriber;
|
use embassy_sync::pubsub::DynSubscriber;
|
||||||
use embassy_time::{Duration, Timer};
|
use embassy_time::{Duration, Timer};
|
||||||
use figments::prelude::*;
|
use figments::{liber8tion::interpolate::Fract8, prelude::*};
|
||||||
use rgb::{Rgb, Rgba};
|
use rgb::{Rgb, Rgba};
|
||||||
use core::fmt::Debug;
|
use core::fmt::Debug;
|
||||||
use futures::join;
|
|
||||||
use log::*;
|
use log::*;
|
||||||
|
|
||||||
use crate::{animation::{AnimatedSurface, Animation}, ego::engine::MotionState, events::{Personality, Prediction, Scene, SensorSource, SensorState}, graphics::{display::{SegmentSpace, Uniforms}, shaders::*}};
|
use crate::{animation::{AnimatedSurface, Animation}, ego::engine::MotionState, events::{Personality, Prediction, Scene, SensorSource, SensorState}, graphics::{display::{SegmentSpace, Uniforms}, shaders::*}};
|
||||||
|
|
||||||
|
const NOTIFY_FADE_IN: Animation<Fract8> = Animation::new().duration(Duration::from_millis(30)) .from(Fract8::MIN).to(Fract8::MAX);
|
||||||
|
const NOTIFY_PULSE_OUT: Animation<Fract8> = Animation::new().duration(Duration::from_millis(10)).from(Fract8::MAX).to(Fract8::from_raw(60));
|
||||||
|
const NOTIFY_PULSE_IN: Animation<Fract8> = Animation::new().duration(Duration::from_millis(10)).from(Fract8::MIN).to(Fract8::MAX);
|
||||||
|
const NOTIFY_FADE_OUT: Animation<Fract8> = Animation::new().duration(Duration::from_millis(750)) .from(Fract8::MAX).to(Fract8::MIN);
|
||||||
|
|
||||||
|
const FG_FADE_OUT: Animation<Fract8> = Animation::new().duration(Duration::from_millis(300)).to(Fract8::MIN);
|
||||||
|
const BG_FADE_IN: Animation<Fract8> = Animation::new().duration(Duration::from_millis(300)).to(Fract8::from_raw(128));
|
||||||
|
|
||||||
|
const TAIL: Animation<Fract8> = Animation::new().duration(Duration::from_millis(300)).to(Fract8::from_raw(96));
|
||||||
|
const PANELS: Animation<Fract8> = Animation::new().duration(Duration::from_millis(300)).to(Fract8::from_raw(128));
|
||||||
|
const BG: Animation<Fract8> = Animation::new().duration(Duration::from_millis(300)).to(Fract8::from_raw(32));
|
||||||
|
const MOTION: Animation<Fract8> = Animation::new().duration(Duration::from_secs(1)) .to(Fract8::MIN);
|
||||||
|
|
||||||
pub struct Ui<S: Surface> {
|
pub struct Ui<S: Surface> {
|
||||||
// Background layer provides an always-running background for everything to draw on
|
// Background layer provides an always-running background for everything to draw on
|
||||||
background: AnimatedSurface<S>,
|
background: AnimatedSurface<S>,
|
||||||
@@ -23,7 +35,7 @@ pub struct Ui<S: Surface> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pixel = Rgba<u8>>> Ui<S> {
|
impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pixel = Rgba<u8>>> Ui<S> {
|
||||||
pub fn new<SS: Surfaces<SegmentSpace, Surface = S>>(surfaces: &mut SS) -> Self where SS::Error: Debug {
|
pub fn new<SS: Surfaces<Surface = S>>(surfaces: &mut SS) -> Self where SS::Error: Debug {
|
||||||
Self {
|
Self {
|
||||||
background: SurfaceBuilder::build(surfaces)
|
background: SurfaceBuilder::build(surfaces)
|
||||||
.rect(Rectangle::everything())
|
.rect(Rectangle::everything())
|
||||||
@@ -54,25 +66,21 @@ impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pix
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn flash_notification_color(&mut self, color: Rgb<u8>) {
|
pub async fn flash_notification_color(&mut self, color: Rgb<u8>) {
|
||||||
let fade_in = Animation::default().from(0).to(255).duration(Duration::from_millis(30));
|
|
||||||
let pulse_out = Animation::default().from(255).to(60).duration(Duration::from_millis(100));
|
|
||||||
let pulse_in = Animation::default().from(0).to(255).duration(Duration::from_millis(100));
|
|
||||||
let fade_out = Animation::default().from(255).to(0).duration(Duration::from_secs(2));
|
|
||||||
info!("Flashing notification {color}");
|
info!("Flashing notification {color}");
|
||||||
|
|
||||||
self.notification.set_visible(true);
|
self.notification.set_visible(true);
|
||||||
|
|
||||||
self.notification.set_shader(Background::from_color(color));
|
self.notification.set_shader(Background::from_color(color));
|
||||||
|
|
||||||
fade_in.apply(&mut self.notification).await;
|
NOTIFY_FADE_IN.apply([&mut self.notification]).await;
|
||||||
|
|
||||||
// Pulse quickly 5 times
|
// Pulse quickly 5 times
|
||||||
for _ in 0..5 {
|
for _ in 0..5 {
|
||||||
pulse_out.apply(&mut self.notification).await;
|
NOTIFY_PULSE_OUT.apply([&mut self.notification]).await;
|
||||||
pulse_in.apply(&mut self.notification).await;
|
NOTIFY_PULSE_IN.apply([&mut self.notification]).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
fade_out.apply(&mut self.notification).await;
|
NOTIFY_FADE_OUT.apply([&mut self.notification]).await;
|
||||||
self.notification.set_visible(false);
|
self.notification.set_visible(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,56 +98,42 @@ impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pix
|
|||||||
self.as_slice().set_visible(true);
|
self.as_slice().set_visible(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Brakelight should only be toggled when actually braking or stationary
|
|
||||||
pub async fn apply_scene(&mut self, next_scene: Scene) {
|
pub async fn apply_scene(&mut self, next_scene: Scene) {
|
||||||
info!("Activating scene {next_scene:?}");
|
info!("Activating scene {next_scene:?}");
|
||||||
match next_scene {
|
match next_scene {
|
||||||
Scene::Ready => {
|
Scene::Ready => {
|
||||||
let tail = Animation::default().duration(Duration::from_millis(300)).to(96);
|
|
||||||
let panels = Animation::default().duration(Duration::from_millis(300)).to(128);
|
|
||||||
let bg = Animation::default().duration(Duration::from_millis(300)).to(32);
|
|
||||||
let motion = Animation::default().duration(Duration::from_secs(1)).to(0);
|
|
||||||
embassy_futures::join::join4(
|
embassy_futures::join::join4(
|
||||||
tail.apply(&mut self.tail),
|
TAIL.apply([&mut self.tail]),
|
||||||
panels.apply(&mut self.panels),
|
PANELS.apply([&mut self.panels]),
|
||||||
bg.apply(&mut self.background),
|
BG.apply([&mut self.background]),
|
||||||
motion.apply(&mut self.motion)
|
MOTION.apply([&mut self.motion])
|
||||||
).await;
|
).await;
|
||||||
self.background.set_shader(Background::default());
|
self.background.set_shader(Background::default());
|
||||||
},
|
},
|
||||||
Scene::Idle => {
|
Scene::Idle => {
|
||||||
// FIXME: The safety UI task should handle setting the display brightness to 50% here
|
|
||||||
self.background.set_shader(Thinking::default());
|
self.background.set_shader(Thinking::default());
|
||||||
let fg_fade = Animation::default().duration(Duration::from_millis(300)).to(0);
|
|
||||||
let bg_fade = Animation::default().duration(Duration::from_millis(300)).to(128);
|
|
||||||
|
|
||||||
// FIXME: The scenes shouldn't be touching the brake/headlights at all here. In fact, they should be dealt with in a whole separate task from the main UI, maybe running on the motion prediction executor
|
embassy_futures::join::join(
|
||||||
embassy_futures::join::join4(
|
FG_FADE_OUT.apply([&mut self.tail, &mut self.panels, &mut self.motion]),
|
||||||
fg_fade.apply(&mut self.tail),
|
BG_FADE_IN.apply([&mut self.background])
|
||||||
fg_fade.apply(&mut self.panels),
|
|
||||||
bg_fade.apply(&mut self.background),
|
|
||||||
fg_fade.apply(&mut self.motion)
|
|
||||||
).await;
|
).await;
|
||||||
},
|
},
|
||||||
Scene::Accelerating => {
|
Scene::Accelerating => {
|
||||||
self.motion.set_shader(Movement::default());
|
self.motion.set_shader(Movement::default());
|
||||||
Animation::default().duration(Duration::from_secs(1)).to(255).apply(&mut self.motion).await;
|
Animation::default().duration(Duration::from_secs(1)).to(Fract8::MAX).apply([&mut self.motion]).await;
|
||||||
},
|
},
|
||||||
Scene::Decelerating => {
|
Scene::Decelerating => {
|
||||||
self.motion.set_shader(Movement::default().reversed());
|
self.motion.set_shader(Movement::default().reversed());
|
||||||
Animation::default().duration(Duration::from_secs(1)).to(255).apply(&mut self.motion).await;
|
Animation::default().duration(Duration::from_secs(1)).to(Fract8::MAX).apply([&mut self.motion]).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn on_event(&mut self, event: Prediction) {
|
pub async fn on_event(&mut self, event: Prediction) {
|
||||||
match event {
|
match event {
|
||||||
Prediction::SetPersonality(personality) => match personality {
|
Prediction::SetPersonality(Personality::Active) => self.apply_scene(Scene::Ready).await,
|
||||||
Personality::Active => self.apply_scene(Scene::Ready).await,
|
Prediction::SetPersonality(Personality::Parked) => self.apply_scene(Scene::Idle).await,
|
||||||
Personality::Parked => self.apply_scene(Scene::Idle).await,
|
Prediction::SetPersonality(Personality::Waking) => self.show().await,
|
||||||
Personality::Waking => self.show().await,
|
|
||||||
_ => ()
|
|
||||||
},
|
|
||||||
Prediction::Motion { prev: _, next: MotionState::Accelerating } => self.apply_scene(Scene::Accelerating).await,
|
Prediction::Motion { prev: _, next: MotionState::Accelerating } => self.apply_scene(Scene::Accelerating).await,
|
||||||
Prediction::Motion { prev: _, next: MotionState::Decelerating } => self.apply_scene(Scene::Decelerating).await,
|
Prediction::Motion { prev: _, next: MotionState::Decelerating } => self.apply_scene(Scene::Decelerating).await,
|
||||||
Prediction::Motion { prev: _, next: MotionState::Steady } => self.apply_scene(Scene::Ready).await,
|
Prediction::Motion { prev: _, next: MotionState::Steady } => self.apply_scene(Scene::Ready).await,
|
||||||
@@ -180,7 +174,7 @@ impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pix
|
|||||||
self.as_slice().set_rect(rect);
|
self.as_slice().set_rect(rect);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_opacity(&mut self, opacity: u8) {
|
fn set_opacity(&mut self, opacity: Fract8) {
|
||||||
self.as_slice().set_opacity(opacity);
|
self.as_slice().set_opacity(opacity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,13 +187,10 @@ impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pix
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature="real-output"))]
|
|
||||||
pub type UiSurfacePool = NullBufferPool<Uniforms, SegmentSpace, Rgba<u8>>;
|
|
||||||
#[cfg(feature="real-output")]
|
|
||||||
pub type UiSurfacePool = BufferedSurfacePool<Uniforms, SegmentSpace, Rgba<u8>>;
|
pub type UiSurfacePool = BufferedSurfacePool<Uniforms, SegmentSpace, Rgba<u8>>;
|
||||||
|
|
||||||
#[embassy_executor::task]
|
#[embassy_executor::task]
|
||||||
pub async fn ui_main(mut events: DynSubscriber<'static, Prediction>, mut ui: Ui<<UiSurfacePool as Surfaces<SegmentSpace>>::Surface>) {
|
pub async fn ui_main(mut events: DynSubscriber<'static, Prediction>, mut ui: Ui<<UiSurfacePool as Surfaces>::Surface>) {
|
||||||
// FIXME: This should instead wait on some kind of flag set by the safety UI, or else we risk painting before we even have a display up and running
|
// FIXME: This should instead wait on some kind of flag set by the safety UI, or else we risk painting before we even have a display up and running
|
||||||
Timer::after_secs(3).await;
|
Timer::after_secs(3).await;
|
||||||
ui.show().await;
|
ui.show().await;
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
use embassy_embedded_hal::shared_bus::asynch::i2c::I2cDevice;
|
||||||
|
use embassy_sync::{blocking_mutex::raw::NoopRawMutex, channel::DynamicSender};
|
||||||
|
use esp_hal::Async;
|
||||||
|
use figments_render::power::Milliwatts;
|
||||||
|
use log::*;
|
||||||
|
|
||||||
|
use crate::{events::{Measurement, SensorSource, SensorState}, gpio_interrupt::PinInterrupt, graphics::display::DisplayControls, tusb320::{ActiveCableConnected, DEVICE_ID, InterruptStatus, TUSB320}};
|
||||||
|
|
||||||
|
#[embassy_executor::task]
|
||||||
|
pub async fn usb_task(motion: DynamicSender<'static, Measurement>, bus: I2cDevice<'static, NoopRawMutex, esp_hal::i2c::master::I2c<'static, Async>>, interrupt_pin: PinInterrupt<'static>, mut display_control: DisplayControls<'static>) {
|
||||||
|
let mut tusb = TUSB320::new(bus);
|
||||||
|
|
||||||
|
motion.send(Measurement::SensorHardwareStatus(SensorSource::ExternalPower, SensorState::AcquiringFix)).await;
|
||||||
|
match tusb.get_device_id().await {
|
||||||
|
Ok(DEVICE_ID) => {
|
||||||
|
info!("TUSB320 connected");
|
||||||
|
motion.send(Measurement::SensorHardwareStatus(SensorSource::ExternalPower, SensorState::Degraded)).await;
|
||||||
|
},
|
||||||
|
Ok(val) => {
|
||||||
|
error!("Unexpected TUSB320 Device ID: {val:?}! Power management is disabled!!");
|
||||||
|
motion.send(Measurement::SensorHardwareStatus(SensorSource::ExternalPower, SensorState::Offline)).await;
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to read from TUSB320! Power management is disabled!! {e:?}");
|
||||||
|
motion.send(Measurement::SensorHardwareStatus(SensorSource::ExternalPower, SensorState::Offline)).await;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match tusb.active_cable_connected().read().await.unwrap() {
|
||||||
|
ActiveCableConnected::Connected => {
|
||||||
|
let current_mode = tusb.current_mode_detect().read().await.unwrap();
|
||||||
|
let cur_mw = Milliwatts(current_mode.milliwatts());
|
||||||
|
info!("USB reports current mode: {current_mode:?}, {cur_mw:?} mW");
|
||||||
|
display_control.set_max_power(cur_mw);
|
||||||
|
motion.send(Measurement::ExternalPower(cur_mw)).await;
|
||||||
|
motion.send(Measurement::SensorHardwareStatus(SensorSource::ExternalPower, SensorState::Online)).await;
|
||||||
|
},
|
||||||
|
ActiveCableConnected::Disconnected => {
|
||||||
|
warn!("USB power is disconnected! Setting display power to zero.");
|
||||||
|
// FIXME: We should also send an event through the system to report that the power is down, and the renderer should stop
|
||||||
|
display_control.set_max_power(Milliwatts(0));
|
||||||
|
motion.send(Measurement::ExternalPower(Milliwatts(0))).await;
|
||||||
|
motion.send(Measurement::SensorHardwareStatus(SensorSource::ExternalPower, SensorState::Degraded)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
interrupt_pin.wait_for_interrupt().await;
|
||||||
|
tusb.interrupt_status().write(InterruptStatus::Clear).await.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
+76
-39
@@ -1,7 +1,8 @@
|
|||||||
use alloc::string::ToString;
|
use alloc::string::ToString;
|
||||||
use embassy_sync::channel::DynamicSender;
|
use embassy_sync::channel::DynamicSender;
|
||||||
use embassy_sync::pubsub::DynSubscriber;
|
use embassy_sync::pubsub::DynSubscriber;
|
||||||
use embassy_time::{Duration, Instant, WithTimeout};
|
use embassy_sync::watch::{DynReceiver, DynSender};
|
||||||
|
use embassy_time::{Duration, Instant, Timer, WithTimeout};
|
||||||
use esp_hal::rng::Rng;
|
use esp_hal::rng::Rng;
|
||||||
use esp_radio::wifi::{ClientConfig, ScanConfig, WifiController, WifiDevice, WifiEvent};
|
use esp_radio::wifi::{ClientConfig, ScanConfig, WifiController, WifiDevice, WifiEvent};
|
||||||
use log::*;
|
use log::*;
|
||||||
@@ -23,58 +24,62 @@ pub async fn net_task(mut runner: embassy_net::Runner<'static, WifiDevice<'stati
|
|||||||
runner.run().await
|
runner.run().await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[embassy_executor::task]
|
pub fn config_for_network(name: &str) -> Option<ClientConfig> {
|
||||||
pub async fn wifi_connect_task(mut wifi: WifiController<'static>, stack: Stack<'static>, motion: DynamicSender<'static, Measurement>) {
|
Some(match name {
|
||||||
wifi.set_config(&esp_radio::wifi::ModeConfig::Client(
|
"The Frequency" => Some(ClientConfig::default().with_password("thepasswordkenneth".to_string())),
|
||||||
ClientConfig::default()
|
"The Frozen Throne" => Some(ClientConfig::default().with_password("IronEmpress420".to_string())),
|
||||||
.with_ssid("The Frequency".to_string())
|
_ => None
|
||||||
.with_auth_method(esp_radio::wifi::AuthMethod::Wpa2Personal)
|
}?.with_ssid(name.to_string()))
|
||||||
.with_password("thepasswordkenneth".to_string())
|
}
|
||||||
)).unwrap();
|
|
||||||
wifi.set_mode(esp_radio::wifi::WifiMode::Sta).unwrap();
|
|
||||||
wifi.set_power_saving(esp_radio::wifi::PowerSaveMode::Maximum).unwrap();
|
|
||||||
|
|
||||||
|
#[embassy_executor::task]
|
||||||
|
pub async fn wifi_connect_task(mut wifi: WifiController<'static>, motion: DynamicSender<'static, Measurement>) {
|
||||||
|
wifi.set_mode(esp_radio::wifi::WifiMode::Sta).unwrap();
|
||||||
|
wifi.start_async().with_timeout(Duration::from_secs(30)).await.unwrap().unwrap();
|
||||||
|
Timer::after_secs(2).await;
|
||||||
|
|
||||||
|
// TODO: need a way to stop wifi after, say, 30 seconds of being unable to connect, or the system goes to sleep
|
||||||
loop {
|
loop {
|
||||||
Backoff::from_secs(3).forever().attempt(async || {
|
|
||||||
info!("Connecting to wifi...");
|
info!("Connecting to wifi...");
|
||||||
wifi.start_async().await.unwrap();
|
|
||||||
motion.send(Measurement::SensorHardwareStatus(SensorSource::Wifi, crate::events::SensorState::AcquiringFix)).await;
|
motion.send(Measurement::SensorHardwareStatus(SensorSource::Wifi, crate::events::SensorState::AcquiringFix)).await;
|
||||||
let networks = wifi.scan_with_config_async(ScanConfig::default().with_show_hidden(true)).await.unwrap();
|
let networks = wifi.scan_with_config_async(ScanConfig::default()).await.unwrap();
|
||||||
for network in networks {
|
for network in networks {
|
||||||
info!("wifi: {} @ {}db", network.ssid, network.signal_strength);
|
info!("wifi: {} @ {}db", network.ssid, network.signal_strength);
|
||||||
|
if let Some(cfg) = config_for_network(&network.ssid) {
|
||||||
|
wifi.set_config(&esp_radio::wifi::ModeConfig::Client(cfg)).unwrap();
|
||||||
|
if wifi.connect_async().await.is_err() {
|
||||||
|
error!("Unable to connect to wifi {network:?}");
|
||||||
}
|
}
|
||||||
match wifi.connect_async().await {
|
break;
|
||||||
Ok(_) => Ok(()),
|
|
||||||
Err(e) => {
|
|
||||||
error!("Unable to connect to wifi: {e:?}");
|
|
||||||
wifi.stop_async().await.unwrap();
|
|
||||||
Err(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).await.unwrap();
|
|
||||||
|
|
||||||
info!("Waiting for DHCP...");
|
if wifi.is_connected().unwrap() {
|
||||||
motion.send(Measurement::SensorHardwareStatus(SensorSource::Wifi, SensorState::Degraded)).await;
|
info!("Wifi is online!");
|
||||||
if stack.wait_config_up().with_timeout(Duration::from_secs(5)).await.is_ok() {
|
motion.send(Measurement::SensorHardwareStatus(SensorSource::Wifi, crate::events::SensorState::Online)).await;
|
||||||
info!("Online!");
|
wifi.wait_for_event(WifiEvent::StaDisconnected).await;
|
||||||
motion.send(Measurement::SensorHardwareStatus(SensorSource::Wifi, SensorState::Online)).await;
|
error!("Wifi disconnected");
|
||||||
let ip_cfg = stack.config_v4().unwrap();
|
|
||||||
info!("ip={ip_cfg:?}");
|
|
||||||
wifi.wait_for_event(WifiEvent::ApStaDisconnected).await;
|
|
||||||
info!("Wifi disconnected!");
|
|
||||||
} else {
|
} else {
|
||||||
warn!("DHCP timed out after 5 seconds. Disconnecting wifi");
|
info!("Wifi is offline.");
|
||||||
wifi.disconnect_async().await.unwrap();
|
motion.send(Measurement::SensorHardwareStatus(SensorSource::Wifi, crate::events::SensorState::Offline)).await;
|
||||||
|
Timer::after_secs(30).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[embassy_executor::task]
|
||||||
|
pub async fn location_sampler(mut predictions: DynSubscriber<'static, Prediction>, tx: DynSender<'static, Prediction>) {
|
||||||
|
loop {
|
||||||
|
if let Prediction::Location(coords) = predictions.next_message_pure().await {
|
||||||
|
debug!("New location prediction: {coords}");
|
||||||
|
tx.send(Prediction::Location(coords));
|
||||||
}
|
}
|
||||||
warn!("Stopping wifi device");
|
|
||||||
wifi.stop_async().await.unwrap();
|
|
||||||
motion.send(Measurement::SensorHardwareStatus(SensorSource::Wifi, SensorState::Offline)).await;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Wifi task needs to know when there is data to upload, so it only connects when needed.
|
// TODO: Wifi task needs to know when there is data to upload, so it only connects when needed.
|
||||||
#[embassy_executor::task]
|
#[embassy_executor::task]
|
||||||
pub async fn http_telemetry_task(mut predictions: DynSubscriber<'static, Prediction>, stack: Stack<'static>) {
|
pub async fn http_telemetry_task(mut latest_prediction: DynReceiver<'static, Prediction>, stack: Stack<'static>, motion: DynamicSender<'static, Measurement>) {
|
||||||
// TODO: should wait for wifi disconnect event somehow and use that to restart sending the wifi around
|
// TODO: should wait for wifi disconnect event somehow and use that to restart sending the wifi around
|
||||||
|
|
||||||
let seed = Rng::new().random() as i32;
|
let seed = Rng::new().random() as i32;
|
||||||
@@ -97,9 +102,19 @@ pub async fn http_telemetry_task(mut predictions: DynSubscriber<'static, Predict
|
|||||||
let mut last_push = Instant::from_ticks(0);
|
let mut last_push = Instant::from_ticks(0);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if let Prediction::Location(coords) = predictions.next_message_pure().await {
|
motion.send(Measurement::SensorHardwareStatus(SensorSource::Cloud, SensorState::Offline)).await;
|
||||||
|
stack.wait_config_up().await;
|
||||||
|
info!("Network is up, connecting to nextcloud...");
|
||||||
|
motion.send(Measurement::SensorHardwareStatus(SensorSource::Cloud, SensorState::AcquiringFix)).await;
|
||||||
|
|
||||||
if stack.is_config_up() {
|
if let Err(err) = ping_nextcloud(&mut client).await {
|
||||||
|
error!("Could not ping nextcloud: {err:?}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
motion.send(Measurement::SensorHardwareStatus(SensorSource::Cloud, SensorState::Online)).await;
|
||||||
|
|
||||||
|
if let Prediction::Location(coords) = latest_prediction.get().await {
|
||||||
// Only push to HTTP if we have an ip config etc
|
// Only push to HTTP if we have an ip config etc
|
||||||
if last_push.elapsed().as_secs() >= 5 || gps_to_local_meters_haversine(&last_location, &coords).norm() >= 10.0 {
|
if last_push.elapsed().as_secs() >= 5 || gps_to_local_meters_haversine(&last_location, &coords).norm() >= 10.0 {
|
||||||
last_location = coords;
|
last_location = coords;
|
||||||
@@ -107,8 +122,10 @@ pub async fn http_telemetry_task(mut predictions: DynSubscriber<'static, Predict
|
|||||||
if let Err(e) = Backoff::from_secs(3).attempt(async || {
|
if let Err(e) = Backoff::from_secs(3).attempt(async || {
|
||||||
push_location(&mut client, coords, Instant::now().as_millis()).await
|
push_location(&mut client, coords, Instant::now().as_millis()).await
|
||||||
}).await {
|
}).await {
|
||||||
|
motion.send(Measurement::SensorHardwareStatus(SensorSource::Cloud, SensorState::Offline)).await;
|
||||||
warn!("Could not submit location! {e:?}");
|
warn!("Could not submit location! {e:?}");
|
||||||
}
|
} else {
|
||||||
|
info!("Location published");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,3 +151,23 @@ async fn push_location(client: &mut HttpClient<'_, TcpClient<'_, 1, 4096, 4096>,
|
|||||||
debug!("HTTP response: {content}");
|
debug!("HTTP response: {content}");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn ping_nextcloud(client: &mut HttpClient<'_, TcpClient<'_, 1, 4096, 4096>, DnsSocket<'_>>) -> Result<bool, reqwless::Error> {
|
||||||
|
let mut buffer = [0u8; 4096];
|
||||||
|
let url = "https://nextcloud.malloc.hackerbots.net/nextcloud/index.php/login";
|
||||||
|
info!("Pinging via {url}");
|
||||||
|
let mut http_req = client
|
||||||
|
.request(
|
||||||
|
reqwless::request::Method::HEAD,
|
||||||
|
url,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let response = http_req.send(&mut buffer).await?;
|
||||||
|
if !response.status.is_client_error() && !response.status.is_server_error() {
|
||||||
|
info!("Nextcloud is online!");
|
||||||
|
Ok(true)
|
||||||
|
} else {
|
||||||
|
warn!("Nextcloud ping returned error status: {:?}", response.status);
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
+177
@@ -0,0 +1,177 @@
|
|||||||
|
#![allow(static_mut_refs)]
|
||||||
|
|
||||||
|
use log::*;
|
||||||
|
|
||||||
|
use crate::logging::RenderbugLogger;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct TracedTask {
|
||||||
|
id: u32,
|
||||||
|
name: &'static str,
|
||||||
|
priority: u32,
|
||||||
|
running: bool,
|
||||||
|
ready: bool
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TracedTask {
|
||||||
|
fn new(id: u32) -> Self {
|
||||||
|
Self {
|
||||||
|
id,
|
||||||
|
name: "<unnamed task>",
|
||||||
|
priority: 0,
|
||||||
|
running: false,
|
||||||
|
ready: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Tracer {}
|
||||||
|
|
||||||
|
struct TracerState {
|
||||||
|
tasks: [core::mem::MaybeUninit<TracedTask>; 16],
|
||||||
|
num_tasks: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
static mut TRACER_STATE: TracerState = TracerState::new();
|
||||||
|
|
||||||
|
impl TracerState {
|
||||||
|
const fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
tasks: [const { core::mem::MaybeUninit::uninit() }; 16],
|
||||||
|
num_tasks: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_task(&mut self, id: u32) -> &mut TracedTask {
|
||||||
|
for i in 0..self.num_tasks {
|
||||||
|
let info = unsafe { &mut *self.tasks[i].as_mut_ptr() };
|
||||||
|
if info.id == id {
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, we register a new task
|
||||||
|
if self.num_tasks < self.tasks.len() {
|
||||||
|
self.tasks[self.num_tasks].write(TracedTask::new(id));
|
||||||
|
self.num_tasks += 1;
|
||||||
|
} else {
|
||||||
|
panic!("too many tasks registered");
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe { &mut *self.tasks[self.num_tasks - 1].as_mut_ptr() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl rtos_trace::RtosTrace for Tracer {
|
||||||
|
fn start() {
|
||||||
|
warn!("rtos start");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stop() {
|
||||||
|
warn!("rtos stop");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn task_new(id: u32) {
|
||||||
|
let task = unsafe { TRACER_STATE.get_task(id) };
|
||||||
|
warn!("rtos new task {task:?}");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn task_send_info(id: u32, info: rtos_trace::TaskInfo) {
|
||||||
|
let task = unsafe { TRACER_STATE.get_task(id) };
|
||||||
|
task.name = info.name;
|
||||||
|
task.priority = info.priority;
|
||||||
|
warn!("rtos task info for task {task:?}");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn task_new_stackless(id: u32, name: &'static str, priority: u32) {
|
||||||
|
let task = unsafe { TRACER_STATE.get_task(id) };
|
||||||
|
task.name = name;
|
||||||
|
task.priority = priority;
|
||||||
|
warn!("rtos new task {task:?} {name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn task_terminate(id: u32) {
|
||||||
|
let task = unsafe { TRACER_STATE.get_task(id) };
|
||||||
|
warn!("rtos terminate {task:?}")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn task_exec_begin(id: u32) {
|
||||||
|
let task = unsafe { TRACER_STATE.get_task(id) };
|
||||||
|
task.running = true;
|
||||||
|
warn!("rtos exec {task:?}")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn task_exec_end() {
|
||||||
|
warn!("rtos exec end");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn task_ready_begin(id: u32) {
|
||||||
|
let task = unsafe { TRACER_STATE.get_task(id) };
|
||||||
|
task.ready = true;
|
||||||
|
warn!("rtos ready {task:?}")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn task_ready_end(id: u32) {
|
||||||
|
let task = unsafe { TRACER_STATE.get_task(id) };
|
||||||
|
task.ready = false;
|
||||||
|
warn!("rtos suspend {task:?}")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn system_idle() {
|
||||||
|
warn!("rtos system idle");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn isr_enter() {
|
||||||
|
warn!("rtos isr enter");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn isr_exit() {
|
||||||
|
warn!("rtos isr exit");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn isr_exit_to_scheduler() {
|
||||||
|
warn!("rtos isr exit to scheduler");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name_marker(id: u32, name: &'static str) {
|
||||||
|
warn!("rtos name marker {id}: {name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn marker(id: u32) {
|
||||||
|
warn!("rtos marker {}", Self::name_for_marker(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn marker_begin(id: u32) {
|
||||||
|
match id {
|
||||||
|
//1..2 => trace!("rtos marker begin {}", Self::name_for_marker(id)),
|
||||||
|
_ => {
|
||||||
|
warn!("rtos marker begin {}", Self::name_for_marker(id));
|
||||||
|
RenderbugLogger::set_level(LevelFilter::Trace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fn marker_end(id: u32) {
|
||||||
|
match id {
|
||||||
|
//1..2 => trace!("rtos marker end {}", Self::name_for_marker(id)),
|
||||||
|
_ => {
|
||||||
|
warn!("rtos marker end {}", Self::name_for_marker(id));
|
||||||
|
RenderbugLogger::reset_level();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tracer {
|
||||||
|
const fn name_for_marker(id: u32) -> &'static str {
|
||||||
|
match id {
|
||||||
|
0 => "run scheduler",
|
||||||
|
1 => "yield task",
|
||||||
|
2 => "timer tick",
|
||||||
|
3 => "process timer queue",
|
||||||
|
4 => "process embassy timer queue",
|
||||||
|
_ => "unknown",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+330
@@ -0,0 +1,330 @@
|
|||||||
|
use core::marker::PhantomData;
|
||||||
|
|
||||||
|
use embedded_hal_async::i2c::I2c as AsyncI2c;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum RegisterAddress {
|
||||||
|
DeviceID = 0x00,
|
||||||
|
DeviceID1 = 0x01,
|
||||||
|
DeviceID2 = 0x02,
|
||||||
|
DeviceID3 = 0x03,
|
||||||
|
DeviceID4 = 0x04,
|
||||||
|
DeviceID5 = 0x05,
|
||||||
|
DeviceID6 = 0x06,
|
||||||
|
DeviceID7 = 0x07,
|
||||||
|
Status1 = 0x08,
|
||||||
|
Status2 = 0x09,
|
||||||
|
Status3 = 0x0a,
|
||||||
|
DisableRDRP = 0x45
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait RegisterField {
|
||||||
|
fn mask() -> u8;
|
||||||
|
fn shift() -> u8;
|
||||||
|
fn address() -> RegisterAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! define_register_field {
|
||||||
|
($name:tt address=$address:tt offset=$shift:tt mask=$bitmask:tt $($value:tt = $valuemask:tt),+) => {
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum $name {
|
||||||
|
$($value = $valuemask),+
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<u8> for $name {
|
||||||
|
fn from(value: u8) -> Self {
|
||||||
|
match value & 0b11 {
|
||||||
|
$($valuemask => $name::$value),+,
|
||||||
|
_ => unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<$name> for u8 {
|
||||||
|
fn from(value: $name) -> Self {
|
||||||
|
value as u8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RegisterField for $name {
|
||||||
|
fn mask() -> u8 {
|
||||||
|
$bitmask
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shift() -> u8 {
|
||||||
|
$shift
|
||||||
|
}
|
||||||
|
|
||||||
|
fn address() -> RegisterAddress {
|
||||||
|
RegisterAddress::$address
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
define_register_field!{
|
||||||
|
CurrentAdvertisement
|
||||||
|
address=Status1
|
||||||
|
offset=6
|
||||||
|
mask=0b11
|
||||||
|
Default = 0b00,
|
||||||
|
// 1.5A
|
||||||
|
Medium = 0b01,
|
||||||
|
// 3A
|
||||||
|
High = 0b10
|
||||||
|
}
|
||||||
|
|
||||||
|
define_register_field!{
|
||||||
|
CurrentModeDetect
|
||||||
|
address=Status1
|
||||||
|
offset=4
|
||||||
|
mask=0b11
|
||||||
|
// 500mA
|
||||||
|
Default = 0b00,
|
||||||
|
// 1.5A
|
||||||
|
Medium = 0b01,
|
||||||
|
// 500mA
|
||||||
|
ThroughAccessory = 0b10,
|
||||||
|
// 3A
|
||||||
|
High = 0b11
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CurrentModeDetect {
|
||||||
|
pub fn milliwatts(&self) -> u32 {
|
||||||
|
match self {
|
||||||
|
CurrentModeDetect::Default => 500,
|
||||||
|
CurrentModeDetect::Medium => 1500,
|
||||||
|
CurrentModeDetect::ThroughAccessory => 500,
|
||||||
|
CurrentModeDetect::High => 3000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
define_register_field!{
|
||||||
|
AccessoryConnected
|
||||||
|
address=Status1
|
||||||
|
offset=1
|
||||||
|
mask=0b111
|
||||||
|
None = 0b000,
|
||||||
|
Audio = 0b100,
|
||||||
|
ThroughAccessory = 0b101,
|
||||||
|
DebugAccessory = 0b110
|
||||||
|
// All other patterns are 'reserved' and should not be possible
|
||||||
|
}
|
||||||
|
|
||||||
|
define_register_field!{
|
||||||
|
ActiveCableConnected
|
||||||
|
address=Status1
|
||||||
|
offset=0
|
||||||
|
mask=0b1
|
||||||
|
Disconnected = 0,
|
||||||
|
Connected = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
define_register_field!{
|
||||||
|
AttachedState
|
||||||
|
address=Status2
|
||||||
|
offset=6
|
||||||
|
mask=0b11
|
||||||
|
Unattached = 0b00,
|
||||||
|
Source = 0b01,
|
||||||
|
Sink = 0b10,
|
||||||
|
Accessory = 0b11
|
||||||
|
}
|
||||||
|
|
||||||
|
define_register_field! {
|
||||||
|
CableOrientation
|
||||||
|
address=Status2
|
||||||
|
offset=5
|
||||||
|
mask=0b1
|
||||||
|
Normal = 0,
|
||||||
|
Flipped = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
define_register_field! {
|
||||||
|
InterruptStatus
|
||||||
|
address=Status2
|
||||||
|
offset=4
|
||||||
|
mask=0b1
|
||||||
|
Clear = 0,
|
||||||
|
Set = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
define_register_field! {
|
||||||
|
DrpDutyCycle
|
||||||
|
address=Status2
|
||||||
|
offset=1
|
||||||
|
mask=0b11
|
||||||
|
// 30%
|
||||||
|
Default = 0b00,
|
||||||
|
// 40%
|
||||||
|
Fast = 0b01,
|
||||||
|
// 50%
|
||||||
|
Faster = 0b10,
|
||||||
|
// 60%
|
||||||
|
Fastest = 0b11
|
||||||
|
}
|
||||||
|
|
||||||
|
define_register_field! {
|
||||||
|
Debounce
|
||||||
|
address=Status3
|
||||||
|
offset=6
|
||||||
|
mask=0b11
|
||||||
|
// 133ms (default)
|
||||||
|
D133 = 0b00,
|
||||||
|
// 116ms
|
||||||
|
D116 = 0b01,
|
||||||
|
// 151ms
|
||||||
|
D151 = 0b10,
|
||||||
|
// 168ms
|
||||||
|
D168 = 0b11
|
||||||
|
}
|
||||||
|
|
||||||
|
define_register_field! {
|
||||||
|
ModeSelect
|
||||||
|
address=Status3
|
||||||
|
offset=4
|
||||||
|
mask=0b11
|
||||||
|
UsePortPin = 0b00,
|
||||||
|
UFP = 0b01,
|
||||||
|
DFP = 0b10,
|
||||||
|
DRP = 0b11
|
||||||
|
}
|
||||||
|
|
||||||
|
define_register_field! {
|
||||||
|
DisableRDRP
|
||||||
|
address=DisableRDRP
|
||||||
|
offset=2
|
||||||
|
mask=0b1
|
||||||
|
Enabled = 0,
|
||||||
|
Disabled = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const DEVICE_ID: [u8; 8] = *b"TUSB320A";
|
||||||
|
|
||||||
|
const USB_ADDR_WRITE: u8 = 0b1100000;
|
||||||
|
const USB_ADDR_READ: u8 = 0b1100001;
|
||||||
|
pub struct TUSB320<T> {
|
||||||
|
bus: T
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: AsyncI2c> TUSB320<T> {
|
||||||
|
pub const fn new(bus: T) -> Self {
|
||||||
|
Self { bus }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn soft_reset(&mut self) -> Result<(), T::Error> {
|
||||||
|
let mut val = self.read_address(RegisterAddress::Status3).await?;
|
||||||
|
val |= 0b1 << 3;
|
||||||
|
self.write_address(RegisterAddress::Status3, val).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn register<'a, R: RegisterField>(&'a mut self) -> UpdateRegister<'a, T, R> {
|
||||||
|
UpdateRegister::new(R::address(), self)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn current_advertisement<'a>(&'a mut self) -> UpdateRegister<'a, T, CurrentAdvertisement> {
|
||||||
|
self.register()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn current_mode_detect<'a>(&'a mut self) -> UpdateRegister<'a, T, CurrentModeDetect> {
|
||||||
|
self.register()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn accessory_connected<'a>(&'a mut self) -> UpdateRegister<'a, T, AccessoryConnected> {
|
||||||
|
self.register()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn active_cable_connected<'a>(&'a mut self) -> UpdateRegister<'a, T, ActiveCableConnected> {
|
||||||
|
self.register()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cable_orientation<'a>(&'a mut self) -> UpdateRegister<'a, T, CableOrientation> {
|
||||||
|
self.register()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn interrupt_status<'a>(&'a mut self) -> UpdateRegister<'a, T, InterruptStatus> {
|
||||||
|
self.register()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn drp_duty_cycle<'a>(&'a mut self) -> UpdateRegister<'a, T, DrpDutyCycle> {
|
||||||
|
self.register()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn debounce<'a>(&'a mut self) -> UpdateRegister<'a, T, Debounce> {
|
||||||
|
self.register()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mode_select<'a>(&'a mut self) -> UpdateRegister<'a, T, ModeSelect> {
|
||||||
|
self.register()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn disable_rdrp<'a>(&'a mut self) -> UpdateRegister<'a, T, DisableRDRP> {
|
||||||
|
self.register()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_device_id(&mut self) -> Result<[u8;8], T::Error> {
|
||||||
|
Ok([
|
||||||
|
self.read_address(RegisterAddress::DeviceID).await?,
|
||||||
|
self.read_address(RegisterAddress::DeviceID1).await?,
|
||||||
|
self.read_address(RegisterAddress::DeviceID2).await?,
|
||||||
|
self.read_address(RegisterAddress::DeviceID3).await?,
|
||||||
|
self.read_address(RegisterAddress::DeviceID4).await?,
|
||||||
|
self.read_address(RegisterAddress::DeviceID5).await?,
|
||||||
|
self.read_address(RegisterAddress::DeviceID6).await?,
|
||||||
|
self.read_address(RegisterAddress::DeviceID7).await?
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read_address(&mut self, reg: RegisterAddress) -> Result<u8, T::Error> {
|
||||||
|
let mut response = [0; 1];
|
||||||
|
self.bus.write_read(USB_ADDR_READ, &[reg as u8], &mut response).await?;
|
||||||
|
Ok(response[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn write_address(&mut self, reg: RegisterAddress, value: u8) -> Result<(), T::Error> {
|
||||||
|
// FIXME: I think this needs to use self.bus.transaction()
|
||||||
|
self.bus.write(USB_ADDR_WRITE, &[reg as u8]).await?;
|
||||||
|
self.bus.write(USB_ADDR_WRITE, &[value]).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct UpdateRegister<'a, I2C: AsyncI2c, T> {
|
||||||
|
reg: RegisterAddress,
|
||||||
|
raw: u8,
|
||||||
|
dev: &'a mut TUSB320<I2C>,
|
||||||
|
_type: PhantomData<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, I2C: AsyncI2c, T> UpdateRegister<'a, I2C, T> {
|
||||||
|
const fn new(reg: RegisterAddress, dev: &'a mut TUSB320<I2C>) -> Self {
|
||||||
|
Self { reg, raw: 0, dev, _type: PhantomData }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn refresh(&mut self) -> Result<(), I2C::Error> {
|
||||||
|
self.raw = self.dev.read_address(self.reg).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, I2C: AsyncI2c, T: RegisterField + Into<u8>> UpdateRegister<'a, I2C, T> where u8: Into<T> {
|
||||||
|
pub async fn write(&mut self, value: T) -> Result<(), I2C::Error> {
|
||||||
|
let mask = T::mask();
|
||||||
|
let v: u8 = value.into();
|
||||||
|
self.refresh().await?;
|
||||||
|
self.raw &= !(mask | (v << T::shift()));
|
||||||
|
self.dev.write_address(self.reg, self.raw).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn read(&mut self) -> Result<T, I2C::Error> {
|
||||||
|
self.refresh().await?;
|
||||||
|
Ok(self.value())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn value(&mut self) -> T {
|
||||||
|
(self.raw >> T::shift()).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user