Compare commits

...

38 Commits

Author SHA1 Message Date
tdfischer 3f87e22585 cargo: replace the 'motion' features with an i2c on/off flag, add a flag to switch between the mpu6050 and the next iteration board with the LSM6DS chip 2026-03-11 14:37:16 +01:00
tdfischer e09649592b tasks: first implementation of protocol for the TUSB320 USB-PD chip. also untested!!! 2026-03-11 14:01:40 +01:00
tdfischer ccb2680954 main: first implementation of a way to handle interrupts from sensors. untested!! 2026-03-11 14:00:25 +01:00
tdfischer 94144ab4b3 board: update some pins to match current devkits 2026-03-11 13:40:25 +01:00
tdfischer 25a6122d53 main: lots changed, I lost track 2026-03-09 10:31:37 +01:00
tdfischer 4e5f053c18 animation: move a few more steps towards a real generic animation framework 2026-03-09 10:29:40 +01:00
tdfischer 49c201add7 cargo: config: re-enable SPI in ram, disable USB PHY 2026-03-09 10:28:31 +01:00
tdfischer 4eafbde5a9 build.rs: use the streamindex to write the correct data, instead of hand-crafted binary bits 2026-03-09 10:27:58 +01:00
tdfischer d942561900 cargo: bump 2026-03-09 10:27:34 +01:00
tdfischer 45d84a91dc tasks: safetyui: re-add waiting for the renderer before starting up 2026-03-09 10:25:09 +01:00
tdfischer 74d753e0b3 tasks: wifi: tweak the hardware config for more reliability where possible 2026-03-09 10:24:21 +01:00
tdfischer 9ecfd11982 tasks: motion: implement a sensor recording layer 2026-03-09 10:22:30 +01:00
tdfischer 96e128ac67 storage: implement storage writing 2026-03-09 10:20:00 +01:00
tdfischer caefdfe131 simdata: implement bundle streams 2026-03-09 10:17:44 +01:00
tdfischer f0d7968843 graphics: display: implement dynamic target fps 2026-03-09 10:16:02 +01:00
tdfischer c77ecc9a19 tasks: wifi: report cloud connectivity status in the http telemetry task 2026-03-09 10:14:20 +01:00
tdfischer 561fef0ed1 tasks: safetyui: move one more animator into a global const 2026-03-09 10:12:36 +01:00
tdfischer 6c43d9f2b7 tasks: motion: improve error messages 2026-03-09 10:11:24 +01:00
tdfischer 3479fd1bf8 tasks: display: docs++ 2026-03-09 10:10:30 +01:00
tdfischer c9518498f2 tasks: ble: warning-- 2026-03-09 10:09:52 +01:00
tdfischer f650cfa644 tasks: gps: refine the gps sensor state reporting, and start handling GPS time 2026-03-09 10:08:40 +01:00
tdfischer 18458bbf27 tracing: first implementation of rtos-trace glue 2026-03-09 10:07:13 +01:00
tdfischer 030575f395 tasks: oled: update to new figments crates 2026-03-09 10:05:45 +01:00
tdfischer e2a26dab5a logging: add timestamp 2026-03-09 10:03:59 +01:00
tdfischer 99a3c61f20 board: add first revision of the new board 2026-03-09 10:02:18 +01:00
tdfischer c23764c6ef tasks: ui: docs++ 2026-03-09 10:01:54 +01:00
tdfischer a62aefda77 tasks: motion: add hard timeouts to the motion engine, to catch deadlocks 2026-02-28 17:18:34 +01:00
tdfischer 0d8254501b tasks: ble: cleanup 2026-02-28 17:16:46 +01:00
tdfischer 3b763fcf99 graphics: display: figments++ 2026-02-28 17:16:13 +01:00
tdfischer 315fa633c1 ego: engine: move some of the system constants out into real const values 2026-02-28 17:15:32 +01:00
tdfischer 54ebbbaea7 tasks: demo: build++ 2026-02-28 17:00:30 +01:00
tdfischer 816cc20087 tasks: use new animations api 2026-02-28 16:59:29 +01:00
tdfischer e8a7a18539 graphics: more figments updates 2026-02-28 16:58:11 +01:00
tdfischer 22d1b4d077 tasks: more Fract8 update 2026-02-28 16:54:44 +01:00
tdfischer 935f30d968 events: combine gravity and forwards fusion sensors into a shared motionframe 2026-02-28 16:52:00 +01:00
tdfischer f2b2674158 tasks: render: switch to using the new figments DMA driver 2026-02-28 16:49:26 +01:00
tdfischer 319c812a61 graphics: update to use figments Fract8 type 2026-02-28 16:47:25 +01:00
tdfischer 95ebc7820a logging: implement a way to set the logging levels at runtime 2026-02-28 16:43:25 +01:00
59 changed files with 29613 additions and 916 deletions
+5 -5
View File
@@ -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
+419 -423
View File
File diff suppressed because it is too large Load Diff
+25 -25
View File
@@ -1,19 +1,19 @@
[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 = ["real-output", "radio", "motion", "oled"]
real-output = [] real-output = []
dual-core = [] dual-core = []
simulation = ["dep:rmp"] simulation = []
radio = [ radio = [
"dep:esp-radio", "dep:esp-radio",
"dep:reqwless", "dep:reqwless",
@@ -25,44 +25,40 @@ max-usb-power = []
wokwi = ["max-usb-power"] wokwi = ["max-usb-power"]
mpu = ["dep:mpu6050-dmp"] mpu = ["dep:mpu6050-dmp"]
gps = ["dep:nmea"] gps = ["dep:nmea"]
oled = ["dep:ssd1306"] oled = ["dep:ssd1306", "dep:display-interface"]
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 +70,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 +98,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 +108,10 @@ 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"] }
[profile.dev] [profile.dev]
# Rust debug is too slow. # Rust debug is too slow.
+40
View File
@@ -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&region=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&region=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&region=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&region=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
+9
View File
@@ -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))))
)
)
)
+24
View File
@@ -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
+38
View File
@@ -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
+10
View File
@@ -0,0 +1,10 @@
[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?
+13060
View File
File diff suppressed because it is too large Load Diff
+694
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+720
View File
@@ -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)
)
)
+108
View File
@@ -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)
)
+173
View File
@@ -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))
)
)
+138
View File
@@ -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)
)
+6
View File
@@ -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 ""))
)
+7
View File
@@ -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 ""))
)
+3 -2
View File
@@ -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();
+97 -59
View File
@@ -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;
@@ -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> Animation<T> {
pub const fn new() -> Self { pub const fn new() -> Self {
Self { Self {
from: None, from: None,
@@ -82,6 +124,48 @@ 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 = animators[0].next_update;
let mut finished = false;
for animator in &mut animators {
if !animator.is_valid() {
continue;
}
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 finished {
break;
}
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 +179,18 @@ impl Animation<Fract8> {
..self ..self
} }
} }
pub const fn duration(self, duration: Duration) -> Self {
Self {
duration,
..self
}
}
} }
const MIN_ANIMATION_RATE: Duration = Duration::from_millis(5);
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 +202,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 +223,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:?}");
} }
} }
+176 -53
View File
@@ -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, signal::Signal
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}, 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,14 +43,31 @@ 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();
}
#[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!");
@@ -63,6 +75,9 @@ async fn main(spawner: Spawner) {
static MOTION_BUS: StaticCell<Channel<CriticalSectionRawMutex,Measurement,5> > = StaticCell::new(); static MOTION_BUS: StaticCell<Channel<CriticalSectionRawMutex,Measurement,5> > = StaticCell::new();
let motion_bus = MOTION_BUS.init_with(|| { Channel::new() }); let motion_bus = MOTION_BUS.init_with(|| { Channel::new() });
static RECORDING_BUS: StaticCell<PubSubChannel<CriticalSectionRawMutex,Measurement,1, 1, 1> > = StaticCell::new();
let recording_bus = RECORDING_BUS.init_with(|| { PubSubChannel::new() });
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);
@@ -83,12 +98,23 @@ 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, 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);
#[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 +127,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(I2cDevice::new(i2c_bus), pd_interrupt));
} }
#[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,25 +145,35 @@ 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));
} }
let mut storage = renderbug_bike::storage::SharedFlash::new(esp_storage::FlashStorage::new());
let mut partition_buf = [8; 1024];
let partitions = esp_bootloader_esp_idf::partitions::read_partition_table(&mut storage, &mut partition_buf).unwrap();
#[cfg(feature="simulation")] #[cfg(feature="simulation")]
{ {
use esp_storage::FlashStorage; use renderbug_bike::tasks::simulation::SimDataTable;
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!") { for sim_data in SimDataTable::open(storage, partitions).expect("Could not find sim data!") {
let srcid = sim_data.srcid(); let srcid = sim_data.srcid();
info!("Found simulation data for {srcid:?}"); info!("Found simulation data for {srcid:?}");
if spawner.spawn(renderbug_embassy::tasks::simulation::simulation_task(sim_data, motion_bus.dyn_sender())).is_err() { if spawner.spawn(renderbug_bike::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."); error!("Unable to spawn simulation task for {srcid:?}! Increase the task pool size.");
} }
} }
} }
#[cfg(not(feature="simulation"))]
{
use renderbug_bike::storage::SimDataRecorder;
let recorder = SimDataRecorder::open(storage, partitions).expect("Unable to open sim data partition for writing");
//spawner.spawn(record_telemetry(recording_bus.dyn_receiver(), recorder)).unwrap();
}
spawner.spawn(print_sensor_readings(recording_bus.dyn_subscriber().unwrap())).unwrap();
#[cfg(feature="radio")] #[cfg(feature="radio")]
let (wifi, network_device, ble) = { let (wifi, network_device, ble) = {
info!("Configuring wifi"); info!("Configuring wifi");
@@ -145,7 +182,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)
@@ -160,13 +206,13 @@ async fn main(spawner: Spawner) {
#[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,16 +234,16 @@ 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!("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(predictions.dyn_subscriber().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")] #[cfg(feature="dual-core")]
@@ -205,22 +251,31 @@ async fn main(spawner: Spawner) {
info!("Launching core 2 watchdog"); info!("Launching core 2 watchdog");
let timer1 = TimerGroup::new(peripherals.TIMG1); let timer1 = TimerGroup::new(peripherals.TIMG1);
let mut ui_wdt = timer1.wdt; let mut ui_wdt = timer1.wdt;
ui_wdt.set_timeout(esp_hal::timer::timg::MwdtStage::Stage0, esp_hal::time::Duration::from_secs(10)); ui_wdt.set_timeout(esp_hal::timer::timg::MwdtStage::Stage0, esp_hal::time::Duration::from_secs(60));
#[cfg(feature="dual-core")]
ui_wdt.enable(); ui_wdt.enable();
spawner.must_spawn(wdt_task(ui_wdt)); spawner.must_spawn(wdt_task(ui_wdt));
} }
spawner.must_spawn(print_telemetry(predictions.dyn_subscriber().unwrap())); spawner.must_spawn(print_sensor_status(predictions.dyn_subscriber().unwrap()));
info!("System is ready in {}ms", Instant::now().as_millis()); 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 +286,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 +304,77 @@ 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(firehose: DynamicReceiver<'static, Measurement>, mut storage: SimDataRecorder<SharedFlash<FlashStorage>>) {
info!("telemetry ready");
let mut num_events = Wrapping(0usize);
loop { loop {
let next = events.next_message_pure().await; match firehose.receive().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();
info!("Wrote IMU to flash");
},
_ => ()
}
}
}
#[embassy_executor::task]
async fn print_sensor_readings(mut events: DynSubscriber<'static, Measurement>) {
loop {
match events.next_message_pure().await {
Measurement::IMU { accel, gyro } => {
esp_println::println!("accel=({},{},{}) gyro=({},{},{})", accel.x, accel.y, accel.z, gyro.x, gyro.y, gyro.z);
},
Measurement::GPS(gps) => {
esp_println::println!("gps={gps:?}");
},
_ => ()
}
}
}
#[embassy_executor::task]
async fn print_sensor_status(mut events: DynSubscriber<'static, Prediction>) {
info!("telemetry ready");
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 => "X",
SensorState::Online => "O"
};
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 => "X",
SensorState::Online => "O"
};
report_str += alloc::format!("{sensor:?}={state_icon} ").as_str();
}
info!("{report_str}");
},
_ => ()
}
} }
} }
+43 -37
View File
@@ -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,27 @@ 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;
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 +87,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 +115,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,17 +132,12 @@ 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; predictions.publish(Prediction::SensorStatus(SensorSource::MotionFrame, SensorState::AcquiringFix)).await;
predictions.publish(Prediction::SensorStatus(SensorSource::GravityReference, SensorState::AcquiringFix)).await;
predictions.publish(Prediction::SensorStatus(SensorSource::Location, SensorState::AcquiringFix)).await; 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 predictions.publish(Prediction::SensorStatus(SensorSource::MotionFrame, SensorState::Online)).await
}
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() {
@@ -157,45 +166,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();
info!("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; predictions.publish(Prediction::Velocity(reported)).await;
} }
// 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;
predictions.publish(Prediction::SetPersonality(Personality::Waking)).await; predictions.publish(Prediction::SetPersonality(Personality::Parked)).await;
predictions.publish(Prediction::SetPersonality(Personality::Parked)).await; }
} // Here, we additionally release the parking brake if we are currently parked and reading substantial movement
// Here, we additionally release the parking brake if we are currently parked and reading some kind of significant movement if average_speed > WAKEUP_SPEED_NOISE_GATE && self.parking_timer.wake() {
if self.parking_timer.wake() { warn!("Disengaging parking brake");
warn!("Disengaging parking brake"); predictions.publish(Prediction::SetPersonality(Personality::Active)).await;
predictions.publish(Prediction::SetPersonality(Personality::Active)).await;
}
} }
// 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);
} }
@@ -232,8 +239,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(),
+1 -1
View File
@@ -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);
} }
+3 -3
View File
@@ -78,8 +78,7 @@ pub enum SensorSource {
Wifi, Wifi,
// Fusion outputs // Fusion outputs
GravityReference, MotionFrame,
ForwardsReference,
Location, Location,
Cloud, Cloud,
@@ -97,7 +96,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!()
} }
} }
} }
+62
View File
@@ -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
}
}
+29 -18
View File
@@ -1,9 +1,8 @@
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 core::{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, smart_leds::PowerManagedWriter
}; };
@@ -37,7 +36,7 @@ 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;
@@ -50,12 +49,12 @@ impl<'a, T: SmartLedsWrite + 'a> Output<'a, SegmentSpace> for BikeOutput<T, T::C
}) })
} }
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());
@@ -63,12 +62,11 @@ impl<'a, T: SmartLedsWriteAsync + 'a> OutputAsync<'a, SegmentSpace> for BikeOutp
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;
fn controls(&self) -> Option<&Self::Controls> { fn controls(&mut self) -> Option<&mut Self::Controls> {
Some(&self.controls) Some(&mut self.controls)
} }
} }
@@ -109,7 +107,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,16 +159,21 @@ 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
} }
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)
} }
} }
} }
@@ -201,11 +203,19 @@ impl DisplayControls {
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 {
self.data.fps.load(core::sync::atomic::Ordering::Relaxed)
}
pub fn set_fps(&self, value: u8) {
self.data.fps.store(value, core::sync::atomic::Ordering::Relaxed);
}
pub async fn wait_until_display_is_turned_on(&self) {
while !self.display_is_on.wait().await { log::info!("wait for display") } while !self.display_is_on.wait().await { log::info!("wait for display") }
log::trace!("display says on!"); log::trace!("display says on!");
} }
@@ -228,8 +238,8 @@ impl GammaCorrected for DisplayControls {
} }
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) {
@@ -244,6 +254,7 @@ impl core::fmt::Debug for DisplayControls {
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("fps", &self.data.fps)
.field("render_pause_signaled", &self.display_is_on.signaled()) .field("render_pause_signaled", &self.display_is_on.signaled())
.finish() .finish()
} }
+5 -5
View File
@@ -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
View File
@@ -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()
} }
+9 -9
View File
@@ -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 {
@@ -108,7 +108,7 @@ pub struct SsdOutput {
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,
is_on: bool, is_on: bool,
last_brightness: u8, last_brightness: Fract8,
reset_pin: Output<'static> reset_pin: Output<'static>
} }
@@ -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
} }
@@ -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
View File
@@ -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;
+30 -4
View File
@@ -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,14 @@ 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 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 +38,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 +47,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 +94,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;
} }
+57 -11
View File
@@ -6,6 +6,7 @@ pub trait RmpData: Sized {
} }
pub trait EventRecord: RmpData { pub trait EventRecord: RmpData {
fn stream_id() -> StreamType;
fn field_count() -> usize; fn field_count() -> usize;
} }
@@ -28,14 +29,15 @@ impl<E> From<E> for SimDataError<E> {
} }
} }
#[derive(Debug)] #[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 {
type Error = (); type Error = ();
fn try_from(value: i8) -> Result<Self, Self::Error> { fn try_from(value: i8) -> Result<Self, Self::Error> {
@@ -43,6 +45,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 +56,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
} }
} }
} }
@@ -65,19 +69,48 @@ 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>(reader: &mut Reader) -> Result<Self, SimDataError<ValueReadError<Reader::Error>>> {
rmp::decode::read_array_len(reader).map(|count| { if rmp::decode::read_u16(reader)? != 0xDA1A {
Self { Err(SimDataError::StreamIndexMissing)
count: count as usize } else {
} rmp::decode::read_array_len(reader).map(|count| {
}).map_err(|_| { SimDataError::StreamIndexMissing }) Self {
count: count as usize
}
}).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_u16(writer, 0xDA1A)?;
rmp::encode::write_array_len(writer, self.count as u32)?; rmp::encode::write_array_len(writer, self.count as u32)?;
Ok(()) Ok(())
} }
} }
#[derive(Debug)]
pub struct BundleEventHeader {
pub id: StreamType,
}
impl RmpData for BundleEventHeader {
fn from_rmp<Reader: RmpRead>(reader: &mut Reader) -> Result<Self, SimDataError<ValueReadError<Reader::Error>>> {
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,
@@ -97,8 +130,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(())
} }
} }
@@ -158,12 +192,20 @@ 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 {
@@ -239,4 +281,8 @@ impl EventRecord for AnnotationReading {
fn field_count() -> usize { fn field_count() -> usize {
1 1
} }
fn stream_id() -> StreamType {
StreamType::Annotations
}
} }
+89 -16
View File
@@ -2,8 +2,12 @@ use core::{cell::RefCell, fmt::Formatter};
use alloc::rc::Rc; use alloc::rc::Rc;
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, RmpData, SimDataError, StreamEvent, StreamHeader, StreamIndex, StreamType};
#[derive(Debug)] #[derive(Debug)]
pub struct SharedFlash<S> { pub struct SharedFlash<S> {
@@ -45,30 +49,30 @@ 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)]
pub struct RangeReader<S> { pub struct StorageRange<S> {
storage: S, storage: S,
start: usize, start: usize,
end: usize, end: usize,
offset: usize offset: usize
} }
impl<S: ReadStorage> RangeReader<S> { 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,
@@ -77,19 +81,27 @@ impl<S: ReadStorage> RangeReader<S> {
} }
} }
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 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(),
@@ -101,13 +113,13 @@ impl<S: ReadStorage> RangeReader<S> {
} }
} }
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,8 +127,69 @@ 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))
}
}
}
}
pub struct SimDataRecorder<S> {
storage: StorageRange<S>,
last_stamp: Instant
}
impl<S: Storage + Clone> SimDataRecorder<S> where S::Error: core::fmt::Debug + 'static {
pub fn open(storage: S, partitions: PartitionTable<'_>) -> Result<Self, SimDataError<ValueWriteError<StorageRangeError<S::Error>>>> {
let partition_type = esp_bootloader_esp_idf::partitions::PartitionType::Data(
esp_bootloader_esp_idf::partitions::DataPartitionSubType::Undefined,
);
info!("Searching for sim data partition");
let data_partition = partitions.iter().find(|partition| {
partition.partition_type() == partition_type && partition.label_as_str() == "sim"
}).ok_or(SimDataError::PartitionNotFound)?;
let start = data_partition.offset() as usize;
let end = data_partition.len() as usize + start;
let mut writer = StorageRange::new(storage.clone(), start, end);
warn!("Writing new simulation data at {start:#02x}:{end:#02x}");
StreamIndex { count: 1 }.write_rmp(&mut writer)?;
StreamHeader { id: StreamType::Bundle, size: 0 }.write_rmp(&mut writer)?;
Ok(Self {
storage: writer,
last_stamp: Instant::now()
})
}
pub fn write_next<T: EventRecord>(&mut self, event: T) -> Result<(), SimDataError<ValueWriteError<StorageRangeError<S::Error>>>> {
BundleEventHeader { id: T::stream_id() }.write_rmp(&mut self.storage)?;
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.storage)?;
self.last_stamp = now;
Ok(())
}
}
+4 -6
View File
@@ -1,12 +1,10 @@
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::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::{backoff::Backoff, events::Prediction};
@@ -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,7 +89,7 @@ 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);
} }
} }
+1 -1
View File
@@ -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;
+32 -30
View File
@@ -6,7 +6,7 @@ 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::{backoff::Backoff, events::{Measurement, SensorSource, SensorState}};
@@ -17,7 +17,6 @@ pub async fn gps_task(events: DynamicSender<'static, Measurement>, mut i2c_bus:
events.send(Measurement::SensorHardwareStatus(SensorSource::GPS, SensorState::Offline)).await; events.send(Measurement::SensorHardwareStatus(SensorSource::GPS, SensorState::Offline)).await;
Backoff::from_secs(5).forever().attempt(async || { 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?;
@@ -40,41 +39,44 @@ pub async fn gps_task(events: DynamicSender<'static, Measurement>, mut i2c_bus:
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:?}");
if !has_lock { },
has_lock = true; Ok(fix_type) => {
} if !has_lock {
// TODO: Send a Measurement::SensorOnline(SensorSource::GPS) here instead has_lock = true;
info!("Got a fix of type {fix_type:?}");
//TODO: 4 satellites seems to be "Some" fix, 6 is a perfect fix events.send(Measurement::SensorHardwareStatus(SensorSource::GPS, SensorState::Online)).await;
//TODO: Only send updates when we get the correct nmea sentence
if let (Some(lat), Some(lng)) = (parser.latitude, parser.longitude) {
events.send(Measurement::GPS(Some(Vector2::new(lat, lng)))).await;
}
} }
if let (Some(lat), Some(lng)) = (parser.latitude, parser.longitude) {
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;
+3 -2
View File
@@ -1,6 +1,5 @@
#[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;
@@ -13,6 +12,8 @@ pub mod demo;
#[cfg(feature="oled")] #[cfg(feature="oled")]
pub mod oled_render; pub mod oled_render;
pub mod usb_power;
// Prediction engines // Prediction engines
pub mod motion; pub mod motion;
+8 -4
View File
@@ -1,10 +1,13 @@
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}};
const TIMEOUT: Duration = Duration::from_millis(3);
#[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 {
@@ -14,11 +17,11 @@ pub async fn motion_task(src: DynamicReceiver<'static, Measurement>, prediction_
match next_measurement { match next_measurement {
Measurement::IMU { accel, gyro } => { Measurement::IMU { accel, gyro } => {
states.insert_imu(accel, gyro); states.insert_imu(accel, gyro);
states.commit(&prediction_sink).await; states.commit(&prediction_sink).with_timeout(TIMEOUT).await.expect("Could not commit IMU data in time. Is the prediction bus stalled?");
}, },
Measurement::GPS(Some(gps_pos)) => { Measurement::GPS(Some(gps_pos)) => {
states.insert_gps(gps_pos); states.insert_gps(gps_pos);
states.commit(&prediction_sink).await; states.commit(&prediction_sink).with_timeout(TIMEOUT).await.expect("Could not commit GPS data in time. Is the prediction bus stalled?");
}, },
Measurement::GPS(None) => { Measurement::GPS(None) => {
states.has_gps_fix.set(false); states.has_gps_fix.set(false);
@@ -26,10 +29,11 @@ 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:?}!"); warn!("Sensor {source:?} reports {state:?}!");
prediction_sink.publish(Prediction::SensorStatus(source, state)).await; prediction_sink.publish(Prediction::SensorStatus(source, state)).with_timeout(TIMEOUT).await.expect("Could not update sensor status in time. Is the prediction bus stalled?");
}, },
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 => ()
} }
let _ = recording_sink.try_publish(next_measurement);
} }
} }
+2 -1
View File
@@ -12,6 +12,7 @@ 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::gpio_interrupt::PinInterrupt;
use crate::{backoff::Backoff, events::Measurement}; use crate::{backoff::Backoff, events::Measurement};
const G: f32 = 9.80665; const G: f32 = 9.80665;
@@ -19,7 +20,7 @@ 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 backoff = Backoff::from_millis(5);
let busref = RefCell::new(Some(bus)); let busref = RefCell::new(Some(bus));
+10 -10
View File
@@ -2,19 +2,19 @@ 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>>;
@@ -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, 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) {
+36 -27
View File
@@ -1,34 +1,38 @@
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;
use crate::{graphics::display::{BikeOutput, DisplayControls, Uniforms}, tasks::ui::UiSurfacePool}; use crate::{graphics::display::{BikeOutput, DisplayControls, Uniforms}, tasks::ui::UiSurfacePool};
const POWER_MA : u32 = 300;
const POWER_VOLTS : u32 = 5;
const MAX_POWER_MW : u32 = POWER_VOLTS * POWER_MA;
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, 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
const POWER_VOLTS : u32 = 5;
const MAX_POWER_MW : u32 = if cfg!(feature="max-usb-power") { u32::MAX } else { POWER_VOLTS * POWER_MA }; let target = Esp32Ws2812SpiDmaWriter::new(driver, tx_buf);
// 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 {
@@ -42,15 +46,13 @@ pub async fn render(rmt: esp_hal::peripherals::RMT<'static>, gpio: AnyPin<'stati
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); 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 +61,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,13 +68,20 @@ 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); 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.wait_until_display_is_turned_on().await;
wdt.feed(); wdt.feed();
wdt.enable(); wdt.enable();
warn!("Renderer is awake !!!!"); warn!("Renderer is awake !!!!");
@@ -81,13 +89,14 @@ pub async fn render(rmt: esp_hal::peripherals::RMT<'static>, gpio: AnyPin<'stati
} }
// 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
+30 -38
View File
@@ -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> {
@@ -20,7 +20,7 @@ pub struct SafetyUi<S: Surface> {
} }
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) -> 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_until_render_is_running().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;
+35 -25
View File
@@ -1,22 +1,25 @@
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::ValueReadError, encode::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: StorageRange<S>,
count: usize, count: usize,
index: usize index: usize
} }
impl<S: ReadStorage + Clone> SimDataTable<S> where S::Error: core::fmt::Debug + 'static { impl<S: ReadStorage> SimDataTable<S> where S::Error: core::fmt::Debug + 'static {
pub fn open(storage: S, partitions: PartitionTable<'_>) -> Result<Self, SimDataError<S>> { pub fn open(storage: S, partitions: PartitionTable<'_>) -> Result<Self, SimDataError<S>> {
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,7 +32,7 @@ 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); let mut reader = StorageRange::new(storage, start, end);
if let Ok(index) = StreamIndex::from_rmp(&mut reader) { if let Ok(index) = StreamIndex::from_rmp(&mut reader) {
info!("Found stream index: {index:?}"); info!("Found stream index: {index:?}");
Ok(Self { Ok(Self {
@@ -54,9 +57,11 @@ impl<S: ReadStorage + Clone + core::fmt::Debug> Iterator for SimDataTable<S> whe
loop { loop {
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;
@@ -87,8 +92,8 @@ impl<S: ReadStorage + Clone + core::fmt::Debug> Iterator for SimDataTable<S> whe
} }
pub struct SimDataReader<S> { pub struct SimDataReader<S> {
reader: RangeReader<S>, reader: StorageRange<S>,
srcid: SensorSource, srcid: StreamType,
runtime: Duration, runtime: Duration,
event_count: usize, event_count: usize,
index: usize index: usize
@@ -117,39 +122,44 @@ impl From<AnnotationReading> for Measurement {
} }
impl<S: ReadStorage> SimDataReader<S> where S::Error: core::fmt::Debug + 'static { impl<S: ReadStorage> SimDataReader<S> where S::Error: core::fmt::Debug + 'static {
pub fn open(mut reader: RangeReader<S>, stream_type: StreamType) -> Self { pub fn open(mut reader: StorageRange<S>, stream_type: StreamType) -> Self {
debug!("Opening {stream_type:?} sim data chunk"); debug!("Opening {stream_type:?} sim data chunk");
let event_count = EventStreamHeader::from_rmp(&mut reader).unwrap().count; let event_count = if stream_type != StreamType::Bundle { EventStreamHeader::from_rmp(&mut reader).unwrap().count } else { usize::MAX };
debug!("Found {event_count} events!"); debug!("Found {event_count} events!");
Self { Self {
reader, reader,
srcid: stream_type.into(), srcid: stream_type,
runtime: Default::default(), runtime: Default::default(),
event_count, event_count,
index: 0 index: 0
} }
} }
pub fn srcid(&self) -> SensorSource { pub fn srcid(&self) -> StreamType {
self.srcid self.srcid
} }
async fn read_next_event<T: EventRecord + Into<Measurement>>(&mut self) -> Result<Measurement, SimDataError<ValueReadError<RangeReadError<S::Error>>>> { async fn read_next_event<T: EventRecord + Into<Measurement>>(&mut self) -> Result<Measurement, SimDataError<ValueReadError<StorageRangeError<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; self.runtime += delay;
info!("waiting {delay}");
Timer::after(delay).await; Timer::after(delay).await;
Ok(event.data.into()) Ok(event.data.into())
} }
pub async fn read_next(&mut self) -> Result<Option<Measurement>, SimDataError<ValueReadError<RangeReadError<S::Error>>>> { pub async fn read_next(&mut self) -> Result<Option<Measurement>, SimDataError<ValueReadError<StorageRangeError<S::Error>>>> {
if self.index < self.event_count { if self.index < self.event_count {
self.index += 1; self.index += 1;
let next_id = match self.srcid {
StreamType::Bundle => BundleEventHeader::from_rmp(&mut self.reader)?.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 { Ok(Some(match next_id {
SensorSource::IMU => self.read_next_event::<IMUReading>().await?, StreamType::IMU => self.read_next_event::<IMUReading>().await?,
SensorSource::GPS => self.read_next_event::<GPSReading>().await?, StreamType::GPS => self.read_next_event::<GPSReading>().await?,
SensorSource::Annotations => self.read_next_event::<AnnotationReading>().await?, StreamType::Annotations => self.read_next_event::<AnnotationReading>().await?,
srcid => unimplemented!("{srcid:?} is not a simulatable sensor yet!") srcid => unimplemented!("{srcid:?} is not a simulatable sensor yet!")
})) }))
} else { } else {
@@ -163,7 +173,7 @@ pub async fn simulation_task(mut reader: SimDataReader<SharedFlash<FlashStorage>
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();
@@ -176,14 +186,14 @@ pub async fn simulation_task(mut reader: SimDataReader<SharedFlash<FlashStorage>
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 +201,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());
+27 -36
View File
@@ -1,9 +1,8 @@
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::*}};
@@ -23,7 +22,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 +53,25 @@ 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 fade_in = Animation::default().from(Fract8::MIN).to(Fract8::MAX).duration(Duration::from_millis(30));
let pulse_out = Animation::default().from(255).to(60).duration(Duration::from_millis(100)); let pulse_out = Animation::default().from(Fract8::MAX).to(Fract8::from_raw(60)).duration(Duration::from_millis(100));
let pulse_in = Animation::default().from(0).to(255).duration(Duration::from_millis(100)); let pulse_in = Animation::default().from(Fract8::MIN).to(Fract8::MAX).duration(Duration::from_millis(100));
let fade_out = Animation::default().from(255).to(0).duration(Duration::from_secs(2)); let fade_out = Animation::default().from(Fract8::MAX).to(Fract8::MIN).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; 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; pulse_out.apply([&mut self.notification]).await;
pulse_in.apply(&mut self.notification).await; pulse_in.apply([&mut self.notification]).await;
} }
fade_out.apply(&mut self.notification).await; fade_out.apply([&mut self.notification]).await;
self.notification.set_visible(false); self.notification.set_visible(false);
} }
@@ -90,44 +89,39 @@ 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 tail = Animation::default().duration(Duration::from_millis(300)).to(Fract8::from_raw(96));
let panels = Animation::default().duration(Duration::from_millis(300)).to(128); let panels = Animation::default().duration(Duration::from_millis(300)).to(Fract8::from_raw(128));
let bg = Animation::default().duration(Duration::from_millis(300)).to(32); let bg = Animation::default().duration(Duration::from_millis(300)).to(Fract8::from_raw(32));
let motion = Animation::default().duration(Duration::from_secs(1)).to(0); let motion = Animation::default().duration(Duration::from_secs(1)).to(Fract8::MIN);
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 fg_fade = Animation::default().duration(Duration::from_millis(300)).to(Fract8::MIN);
let bg_fade = Animation::default().duration(Duration::from_millis(300)).to(128); let bg_fade = Animation::default().duration(Duration::from_millis(300)).to(Fract8::from_raw(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.apply([&mut self.tail, &mut self.panels, &mut self.motion]),
fg_fade.apply(&mut self.tail), bg_fade.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;
} }
} }
} }
@@ -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;
+25
View File
@@ -0,0 +1,25 @@
use embassy_embedded_hal::shared_bus::asynch::i2c::I2cDevice;
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
use esp_hal::Async;
use log::*;
use crate::{gpio_interrupt::PinInterrupt, tusb320::TUSB320};
#[embassy_executor::task]
pub async fn usb_task(bus: I2cDevice<'static, NoopRawMutex, esp_hal::i2c::master::I2c<'static, Async>>, interrupt_pin: PinInterrupt<'static>) {
let mut tusb = TUSB320::new(bus);
match tusb.get_device_id().await {
Ok(val) => {
info!("TUSB320 Device ID: {val:?}");
},
Err(_) => {
error!("Failed to read from TUSB320");
}
}
loop {
log::info!("Waiting for USB power interrupt...");
interrupt_pin.wait_for_interrupt().await;
}
}
+43 -43
View File
@@ -1,7 +1,7 @@
use alloc::string::ToString; use alloc::string::{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_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 +23,52 @@ pub async fn net_task(mut runner: embassy_net::Runner<'static, WifiDevice<'stati
runner.run().await runner.run().await
} }
pub fn config_for_network(name: &str) -> Option<ClientConfig> {
Some(match name {
"The Frequency" => Some(ClientConfig::default().with_password("thepasswordkenneth".to_string())),
"The Frozen Throne" => Some(ClientConfig::default().with_password("IronEmpress420".to_string())),
_ => None
}?.with_ssid(name.to_string()))
}
#[embassy_executor::task] #[embassy_executor::task]
pub async fn wifi_connect_task(mut wifi: WifiController<'static>, stack: Stack<'static>, motion: DynamicSender<'static, Measurement>) { pub async fn wifi_connect_task(mut wifi: WifiController<'static>, motion: DynamicSender<'static, Measurement>) {
wifi.set_config(&esp_radio::wifi::ModeConfig::Client(
ClientConfig::default()
.with_ssid("The Frequency".to_string())
.with_auth_method(esp_radio::wifi::AuthMethod::Wpa2Personal)
.with_password("thepasswordkenneth".to_string())
)).unwrap();
wifi.set_mode(esp_radio::wifi::WifiMode::Sta).unwrap(); wifi.set_mode(esp_radio::wifi::WifiMode::Sta).unwrap();
wifi.set_power_saving(esp_radio::wifi::PowerSaveMode::Maximum).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..."); motion.send(Measurement::SensorHardwareStatus(SensorSource::Wifi, crate::events::SensorState::AcquiringFix)).await;
wifi.start_async().await.unwrap(); let networks = wifi.scan_with_config_async(ScanConfig::default()).await.unwrap();
motion.send(Measurement::SensorHardwareStatus(SensorSource::Wifi, crate::events::SensorState::AcquiringFix)).await; for network in networks {
let networks = wifi.scan_with_config_async(ScanConfig::default().with_show_hidden(true)).await.unwrap(); info!("wifi: {} @ {}db", network.ssid, network.signal_strength);
for network in networks { if let Some(cfg) = config_for_network(&network.ssid) {
info!("wifi: {} @ {}db", network.ssid, network.signal_strength); wifi.set_config(&esp_radio::wifi::ModeConfig::Client(cfg)).unwrap();
} if wifi.connect_async().await.is_err() {
match wifi.connect_async().await { error!("Unable to connect to wifi {network:?}");
Ok(_) => Ok(()),
Err(e) => {
error!("Unable to connect to wifi: {e:?}");
wifi.stop_async().await.unwrap();
Err(())
} }
break;
} }
}).await.unwrap();
info!("Waiting for DHCP...");
motion.send(Measurement::SensorHardwareStatus(SensorSource::Wifi, SensorState::Degraded)).await;
if stack.wait_config_up().with_timeout(Duration::from_secs(5)).await.is_ok() {
info!("Online!");
motion.send(Measurement::SensorHardwareStatus(SensorSource::Wifi, SensorState::Online)).await;
let ip_cfg = stack.config_v4().unwrap();
info!("ip={ip_cfg:?}");
wifi.wait_for_event(WifiEvent::ApStaDisconnected).await;
info!("Wifi disconnected!");
} else {
warn!("DHCP timed out after 5 seconds. Disconnecting wifi");
wifi.disconnect_async().await.unwrap();
} }
warn!("Stopping wifi device");
wifi.stop_async().await.unwrap(); if wifi.is_connected().unwrap() {
motion.send(Measurement::SensorHardwareStatus(SensorSource::Wifi, SensorState::Offline)).await; info!("Wifi is online!");
motion.send(Measurement::SensorHardwareStatus(SensorSource::Wifi, crate::events::SensorState::Online)).await;
wifi.wait_for_event(WifiEvent::StaDisconnected).await;
error!("Wifi disconnected");
} else {
info!("Wifi is offline.");
motion.send(Measurement::SensorHardwareStatus(SensorSource::Wifi, crate::events::SensorState::Offline)).await;
Timer::after_secs(30).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 predictions: DynSubscriber<'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;
@@ -98,18 +92,24 @@ pub async fn http_telemetry_task(mut predictions: DynSubscriber<'static, Predict
loop { loop {
if let Prediction::Location(coords) = predictions.next_message_pure().await { if let Prediction::Location(coords) = predictions.next_message_pure().await {
if stack.is_config_up() { if stack.is_config_up() {
// 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;
last_push = Instant::now(); last_push = Instant::now();
if let Err(e) = Backoff::from_secs(3).attempt(async || { if let Err(e) = Backoff::from_secs(3).attempt(async || {
motion.send(Measurement::SensorHardwareStatus(SensorSource::Cloud, SensorState::AcquiringFix)).await;
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");
motion.send(Measurement::SensorHardwareStatus(SensorSource::Cloud, SensorState::Online)).await;
} }
} }
} else {
motion.send(Measurement::SensorHardwareStatus(SensorSource::Cloud, SensorState::Offline)).await;
} }
} }
} }
+177
View File
@@ -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",
}
}
}
+356
View File
@@ -0,0 +1,356 @@
use embassy_embedded_hal::shared_bus::{I2cDeviceError, asynch::i2c::I2cDevice};
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
use esp_hal::Async;
use embedded_hal_async::i2c::I2c;
// TODO: rewrite this to only use embedded_hal_async traits, then publish as a crate
const USB_ADDR: u8 = 0b1100000;
pub struct TUSB320 {
bus: I2cDevice<'static, NoopRawMutex, esp_hal::i2c::master::I2c<'static, Async>>
}
#[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
}
#[derive(Clone, Copy, Debug)]
pub enum Register {
DeviceID,
DeviceID1,
DeviceID2,
DeviceID3,
DeviceID4,
DeviceID5,
DeviceID6,
DeviceID7,
CurrentAdvertisement,
CurrentDetect,
AccessoryConnected,
ActiveCableConnected,
AttachedState,
CableOrientation,
InterruptStatus,
DrpDutyCycle,
Debounce,
ModeSelect,
SoftReset,
DisableRDRP
}
impl From<Register> for RegisterAddress {
fn from(reg: Register) -> Self {
match reg {
Register::DeviceID => RegisterAddress::DeviceID,
Register::DeviceID1 => RegisterAddress::DeviceID1,
Register::DeviceID2 => RegisterAddress::DeviceID2,
Register::DeviceID3 => RegisterAddress::DeviceID3,
Register::DeviceID4 => RegisterAddress::DeviceID4,
Register::DeviceID5 => RegisterAddress::DeviceID5,
Register::DeviceID6 => RegisterAddress::DeviceID6,
Register::DeviceID7 => RegisterAddress::DeviceID7,
Register::CurrentAdvertisement => RegisterAddress::Status1,
Register::CurrentDetect => RegisterAddress::Status1,
Register::AccessoryConnected => RegisterAddress::Status1,
Register::ActiveCableConnected => RegisterAddress::Status1
,
Register::AttachedState => RegisterAddress::Status2,
Register::CableOrientation => RegisterAddress::Status2,
Register::InterruptStatus => RegisterAddress::Status2,
Register::DrpDutyCycle => RegisterAddress::Status2,
Register::Debounce => RegisterAddress::Status3,
Register::ModeSelect => RegisterAddress::Status3,
Register::SoftReset => RegisterAddress::Status3,
Register::DisableRDRP => RegisterAddress::DisableRDRP
}
}
}
pub enum CurrentAdvertisement {
/// 500mA / 900mA
Default = 0b00,
/// 1.5A
Medium = 0b01,
/// 3A
High = 0b10,
/// Reserved, do not use
Reserved = 0b11
}
pub enum CurrentModeDetect {
/// Default value at startup. TODO: Does this mean 500ma, or just 'no connection'?
Default = 0b00,
/// 1.5A
Medium = 0b01,
/// 500ma
ThroughAccessory = 0b10,
/// 3A
High = 0b11,
}
pub enum AccessoryConnectionState {
None = 0b000,
Audio = 0b100,
ThroughAccessory = 0b101,
DebugAccessory = 0b110,
// All other binary patterns are 'reserved'
Reserved
}
pub enum AttachedState {
Unattached = 0b00,
Source = 0b01,
Sink = 0b10,
Accessory = 0b11
}
pub enum CableOrientation {
Normal = 0,
Flipped = 1
}
pub enum DrpDutyCycle {
/// 30%
Default = 0b00,
/// 40%
Fast = 0b01,
/// 50%
Faster = 0b10,
/// 60%
Fastest = 0b11
}
pub enum Debounce {
/// 133ms (default)
D133 = 0b00,
/// 116ms
D116 = 0b01,
/// 151ms
D151 = 0b10,
/// 168ms
D168 = 0b11
}
pub enum Mode {
UsePortPin = 0b00,
UFP = 0b01,
DFP = 0b10,
DRP = 0b11
}
impl TUSB320 {
pub const fn new(bus: I2cDevice<'static, NoopRawMutex, esp_hal::i2c::master::I2c<'static, Async>>) -> Self {
Self { bus }
}
pub async fn get_current_advertisement(&mut self) -> Result<CurrentAdvertisement, I2cDeviceError<esp_hal::i2c::master::Error>> {
let reg: RegisterAddress = Register::CurrentAdvertisement.into();
let val = self.read_address(reg).await? >> 6;
match val & 0b11 {
0b00 => Ok(CurrentAdvertisement::Default),
0b01 => Ok(CurrentAdvertisement::Medium),
0b10 => Ok(CurrentAdvertisement::High),
0b11 => Ok(CurrentAdvertisement::Reserved),
_ => unreachable!()
}
}
pub async fn set_current_advertisement(&mut self, adv: CurrentAdvertisement) -> Result<(), I2cDeviceError<esp_hal::i2c::master::Error>> {
let reg: RegisterAddress = Register::CurrentAdvertisement.into();
let mut val = self.read_address(reg).await?;
let mask = !(0b11 << 6);
val &= mask | ((adv as u8) << 6);
self.write_address(reg, val).await?;
Ok(())
}
pub async fn get_current_mode_detect(&mut self) -> Result<CurrentModeDetect, I2cDeviceError<esp_hal::i2c::master::Error>> {
let reg: RegisterAddress = Register::CurrentDetect.into();
let val = self.read_address(reg).await? >> 4;
match val & 0b11 {
0b00 => Ok(CurrentModeDetect::Default),
0b01 => Ok(CurrentModeDetect::Medium),
0b10 => Ok(CurrentModeDetect::ThroughAccessory),
0b11 => Ok(CurrentModeDetect::High),
_ => unreachable!()
}
}
pub async fn get_accessory_connection_state(&mut self) -> Result<AccessoryConnectionState, I2cDeviceError<esp_hal::i2c::master::Error>> {
let reg: RegisterAddress = Register::AccessoryConnected.into();
let val = self.read_address(reg).await? >> 1;
match val & 0b111 {
0b000 => Ok(AccessoryConnectionState::None),
0b100 => Ok(AccessoryConnectionState::Audio),
0b101 => Ok(AccessoryConnectionState::ThroughAccessory),
0b110 => Ok(AccessoryConnectionState::DebugAccessory),
_ => Ok(AccessoryConnectionState::Reserved)
}
}
pub async fn get_cable_detected(&mut self) -> Result<bool, I2cDeviceError<esp_hal::i2c::master::Error>> {
let reg: RegisterAddress = Register::ActiveCableConnected.into();
let val = self.read_address(reg).await?;
Ok((val & 0b1) == 1)
}
pub async fn get_attached_state(&mut self) -> Result<AttachedState, I2cDeviceError<esp_hal::i2c::master::Error>> {
let reg: RegisterAddress = Register::AttachedState.into();
let val = self.read_address(reg).await? >> 6;
match val & 0b11 {
0b00 => Ok(AttachedState::Unattached),
0b01 => Ok(AttachedState::Source),
0b10 => Ok(AttachedState::Sink),
0b11 => Ok(AttachedState::Accessory),
_ => unreachable!()
}
}
pub async fn get_cable_orientation(&mut self) -> Result<CableOrientation, I2cDeviceError<esp_hal::i2c::master::Error>> {
let reg: RegisterAddress = Register::CableOrientation.into();
let val = self.read_address(reg).await? >> 5;
match val & 0b1 {
0 => Ok(CableOrientation::Normal),
1 => Ok(CableOrientation::Flipped),
_ => unreachable!()
}
}
pub async fn get_interrupt_status(&mut self) -> Result<bool, I2cDeviceError<esp_hal::i2c::master::Error>> {
let reg: RegisterAddress = Register::InterruptStatus.into();
Ok((self.read_address(reg).await? >> 4) == 1)
}
pub async fn clear_interrupt(&mut self) -> Result<(), I2cDeviceError<esp_hal::i2c::master::Error>> {
let reg: RegisterAddress = Register::InterruptStatus.into();
let mut val = self.read_address(reg).await?;
val &= !(0b1 << 4);
self.write_address(reg, val).await
}
pub async fn set_drp_duty_cycle(&mut self, duty: DrpDutyCycle) -> Result<(), I2cDeviceError<esp_hal::i2c::master::Error>> {
let reg: RegisterAddress = Register::DrpDutyCycle.into();
let mut val = self.read_address(reg).await?;
let mask = !(0b11 << 1);
val &= mask | ((duty as u8) << 1);
self.write_address(reg, val).await
}
pub async fn get_drp_duty_cycle(&mut self) -> Result<DrpDutyCycle, I2cDeviceError<esp_hal::i2c::master::Error>> {
let reg: RegisterAddress = Register::DrpDutyCycle.into();
let val = self.read_address(reg).await? >> 1;
match val & 0b11 {
0b00 => Ok(DrpDutyCycle::Default),
0b01 => Ok(DrpDutyCycle::Fast),
0b10 => Ok(DrpDutyCycle::Faster),
0b11 => Ok(DrpDutyCycle::Fastest),
_ => unreachable!()
}
}
pub async fn get_debounce(&mut self) -> Result<Debounce, I2cDeviceError<esp_hal::i2c::master::Error>> {
let reg: RegisterAddress = Register::Debounce.into();
let val = self.read_address(reg).await? >> 6;
match val & 0b11 {
0b00 => Ok(Debounce::D133),
0b01 => Ok(Debounce::D116),
0b10 => Ok(Debounce::D151),
0b11 => Ok(Debounce::D168),
_ => unreachable!()
}
}
pub async fn set_debounce(&mut self, debounce: Debounce) -> Result<(), I2cDeviceError<esp_hal::i2c::master::Error>> {
let reg: RegisterAddress = Register::Debounce.into();
let mut val = self.read_address(reg).await?;
let mask = !(0b11 << 6);
val &= mask | ((debounce as u8) << 6);
self.write_address(reg, val).await
}
pub async fn get_mode(&mut self) -> Result<Mode, I2cDeviceError<esp_hal::i2c::master::Error>> {
let reg: RegisterAddress = Register::ModeSelect.into();
let val = self.read_address(reg).await? >> 4;
match val & 0b11 {
0b00 => Ok(Mode::UsePortPin),
0b01 => Ok(Mode::UFP),
0b10 => Ok(Mode::DFP),
0b11 => Ok(Mode::DRP),
_ => unreachable!()
}
}
pub async fn set_mode(&mut self, mode: Mode) -> Result<(), I2cDeviceError<esp_hal::i2c::master::Error>> {
let reg: RegisterAddress = Register::ModeSelect.into();
let mut val = self.read_address(reg).await?;
let mask = !(0b11 << 4);
val &= mask | ((mode as u8) << 4);
self.write_address(reg, val).await
}
pub async fn soft_reset(&mut self) -> Result<(), I2cDeviceError<esp_hal::i2c::master::Error>> {
let reg: RegisterAddress = Register::SoftReset.into();
let mut val = self.read_address(reg).await?;
val |= 0b1 << 3;
self.write_address(reg, val).await
}
pub async fn set_rdrp_disabled(&mut self, disable: bool) -> Result<(), I2cDeviceError<esp_hal::i2c::master::Error>> {
let reg: RegisterAddress = Register::DisableRDRP.into();
let mut val = self.read_address(reg).await?;
if disable {
val |= 0b1;
} else {
val &= !0b1;
}
self.write_address(reg, val).await
}
pub async fn get_rdrp_disabled(&mut self) -> Result<bool, I2cDeviceError<esp_hal::i2c::master::Error>> {
let reg: RegisterAddress = Register::DisableRDRP.into();
let val = self.read_address(reg).await? >> 2;
Ok((val & 0b1) == 1)
}
pub async fn get_device_id(&mut self) -> Result<[u8;8], I2cDeviceError<esp_hal::i2c::master::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?
])
}
pub async fn read_address(&mut self, reg: RegisterAddress) -> Result<u8, I2cDeviceError<esp_hal::i2c::master::Error>> {
let mut response = [0; 1];
// 1 means 'read' cycle
let i2c_addr = USB_ADDR | 1;
self.bus.write_read(i2c_addr, &[reg as u8], &mut response).await?;
Ok(response[0])
}
pub async fn write_address(&mut self, reg: RegisterAddress, value: u8) -> Result<(), I2cDeviceError<esp_hal::i2c::master::Error>> {
// 0 means 'write' cycle
let i2c_addr = USB_ADDR | 0;
self.bus.write(i2c_addr, &[reg as u8]).await?;
self.bus.write(i2c_addr, &[value]).await?;
Ok(())
}
}