Compare commits
38 Commits
08a3c65346
...
3f87e22585
| Author | SHA1 | Date | |
|---|---|---|---|
| 3f87e22585 | |||
| e09649592b | |||
| ccb2680954 | |||
| 94144ab4b3 | |||
| 25a6122d53 | |||
| 4e5f053c18 | |||
| 49c201add7 | |||
| 4eafbde5a9 | |||
| d942561900 | |||
| 45d84a91dc | |||
| 74d753e0b3 | |||
| 9ecfd11982 | |||
| 96e128ac67 | |||
| caefdfe131 | |||
| f0d7968843 | |||
| c77ecc9a19 | |||
| 561fef0ed1 | |||
| 6c43d9f2b7 | |||
| 3479fd1bf8 | |||
| c9518498f2 | |||
| f650cfa644 | |||
| 18458bbf27 | |||
| 030575f395 | |||
| e2a26dab5a | |||
| 99a3c61f20 | |||
| c23764c6ef | |||
| a62aefda77 | |||
| 0d8254501b | |||
| 3b763fcf99 | |||
| 315fa633c1 | |||
| 54ebbbaea7 | |||
| 816cc20087 | |||
| e8a7a18539 | |||
| 22d1b4d077 | |||
| 935f30d968 | |||
| f2b2674158 | |||
| 319c812a61 | |||
| 95ebc7820a |
+5
-5
@@ -2,11 +2,11 @@
|
||||
runner = "espflash flash --monitor --partition-table ./partitions.csv"
|
||||
|
||||
[env]
|
||||
ESP_LOG="info"
|
||||
ESP_HAL_CONFIG_PLACE_RMT_DRIVER_IN_RAM="true"
|
||||
ESP_HAL_EMBASSY_CONFIG_TIMER_QUEUE="multiple-integrated"
|
||||
ESP_WIFI_CONFIG_COUNTRY_CODE="DE"
|
||||
ESP_RTOS_CONFIG_TICK_RATE_HZ="50"
|
||||
ESP_HAL_CONFIG_PLACE_SPI_MASTER_DRIVER_IN_RAM="true"
|
||||
ESP_RTOS_CONFIG_SW_TASK_OVERFLOW_DETECTION="true"
|
||||
ESP_RTOS_CONFIG_HW_TASK_OVERFLOW_DETECTION="true"
|
||||
REASSEMBLY_BUFFER_COUNT="5"
|
||||
ESP_PHY_CONFIG_PHY_ENABLE_USB="false"
|
||||
|
||||
[build]
|
||||
rustflags = [
|
||||
|
||||
Generated
+419
-423
File diff suppressed because it is too large
Load Diff
+25
-25
@@ -1,19 +1,19 @@
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "renderbug-embassy"
|
||||
rust-version = "1.86"
|
||||
edition = "2024"
|
||||
name = "renderbug-bike"
|
||||
rust-version = "1.92"
|
||||
version = "0.1.0"
|
||||
default-run = "renderbug-embassy"
|
||||
default-run = "renderbug-bike"
|
||||
|
||||
[[bin]]
|
||||
name = "renderbug-embassy"
|
||||
name = "renderbug-bike"
|
||||
path = "./src/bin/main.rs"
|
||||
|
||||
[features]
|
||||
default = ["real-output"]
|
||||
default = ["real-output", "radio", "motion", "oled"]
|
||||
real-output = []
|
||||
dual-core = []
|
||||
simulation = ["dep:rmp"]
|
||||
simulation = []
|
||||
radio = [
|
||||
"dep:esp-radio",
|
||||
"dep:reqwless",
|
||||
@@ -25,44 +25,40 @@ max-usb-power = []
|
||||
wokwi = ["max-usb-power"]
|
||||
mpu = ["dep:mpu6050-dmp"]
|
||||
gps = ["dep:nmea"]
|
||||
oled = ["dep:ssd1306"]
|
||||
oled = ["dep:ssd1306", "dep:display-interface"]
|
||||
rtt = ["dep:rtt-target"]
|
||||
demo = []
|
||||
|
||||
[dependencies]
|
||||
# The basic requirements for all features
|
||||
figments = { path = "../figments/figments/", features = ["alloc", "embedded-graphics"] }
|
||||
figments-render = { path = "../figments/figments-render/", features = ["smart-leds", "micromath"], default-features = false }
|
||||
figments = { path = "../figments/figments/", features = ["alloc", "embedded-graphics", "log-04"] }
|
||||
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-hal = { version = "1.0.0", features = [
|
||||
"esp32s3",
|
||||
"log-04",
|
||||
"unstable",
|
||||
"log-04"
|
||||
] }
|
||||
esp-alloc = "0.9.0"
|
||||
esp-backtrace = { version = "0.18", features = [
|
||||
"esp32s3",
|
||||
"panic-handler",
|
||||
"println",
|
||||
] }
|
||||
esp-alloc = { version = "0.9.0", default-features = false, features = ["internal-heap-stats"] }
|
||||
esp-backtrace = { version = "0.18", features = ["esp32s3", "halt-cores", "panic-handler", "println"] }
|
||||
esp-println = { version = "0.16", features = ["esp32s3", "log-04"] }
|
||||
# for more networking protocol support see https://crates.io/crates/edge-net
|
||||
critical-section = "1.2.0"
|
||||
embassy-executor = { version = "0.9.0", features = [
|
||||
embassy-executor = { version = "0.9.0", default-features = false, features = [
|
||||
"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 = [
|
||||
"embassy",
|
||||
"esp-alloc",
|
||||
"esp32s3",
|
||||
# "rtos-trace",
|
||||
"log-04"
|
||||
] }
|
||||
log = "0.4"
|
||||
static_cell = "2.1.1"
|
||||
rgb = "0.8.52"
|
||||
esp-hal-smartled = { version = "0.17.0", features = ["esp32s3"] }
|
||||
smart-leds = "0.4.0"
|
||||
embassy-sync = "0.7.2"
|
||||
embassy-embedded-hal = "0.5.0"
|
||||
@@ -74,6 +70,9 @@ enumset = "1.1.10"
|
||||
enum-map = "2.7.3"
|
||||
portable-atomic = { version = "1.11", features = ["critical-section"] }
|
||||
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
|
||||
esp-radio = { version = "*", optional = true, features = [
|
||||
@@ -99,6 +98,7 @@ nmea = { version = "0.7.0", optional = true, default-features = false, features
|
||||
|
||||
"GLL",
|
||||
"GST",
|
||||
"all-sentences"
|
||||
] }
|
||||
mpu6050-dmp = { version = "0.6.1", features = ["async"], optional = true }
|
||||
|
||||
@@ -108,10 +108,10 @@ rtt-target = { version = "0.6.2", optional = true }
|
||||
# Simulation
|
||||
esp-storage = { version = "0.7.0", features = ["esp32s3", "critical-section"] }
|
||||
embedded-storage = "0.3.1"
|
||||
rmp = { path = "../msgpack-rust/rmp/", optional = true, default-features = false }
|
||||
display-interface = "0.5.0"
|
||||
embassy-net = { version = "0.7.1", features = ["alloc", "dns", "medium-ethernet", "proto-ipv4", "tcp", "udp", "dhcpv4"] }
|
||||
reqwless = { version = "0.13.0", optional = true }
|
||||
rmp = { path = "../msgpack-rust/rmp/", default-features = false }
|
||||
heapless = { version = "0.9.1", features = ["portable-atomic"] }
|
||||
num-traits = { version = "0.2.19", default-features = false }
|
||||
rtos-trace = { version = "0.2.1", default-features = false, features = ["trace_impl"] }
|
||||
|
||||
[profile.dev]
|
||||
# Rust debug is too slow.
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# For PCBs designed using KiCad: https://www.kicad.org/
|
||||
# Format documentation: https://kicad.org/help/file-formats/
|
||||
|
||||
# Temporary files
|
||||
*.000
|
||||
*.bak
|
||||
*.bck
|
||||
*.kicad_pcb-bak
|
||||
*.kicad_sch-bak
|
||||
*-backups
|
||||
*-cache*
|
||||
*-bak
|
||||
*-bak*
|
||||
*~
|
||||
~*
|
||||
_autosave-*
|
||||
\#auto_saved_files\#
|
||||
*.tmp
|
||||
*-save.pro
|
||||
*-save.kicad_pcb
|
||||
fp-info-cache
|
||||
~*.lck
|
||||
\#auto_saved_files#
|
||||
|
||||
# Netlist files (exported from Eeschema)
|
||||
*.net
|
||||
|
||||
# Autorouter files (exported from Pcbnew)
|
||||
*.dsn
|
||||
*.ses
|
||||
|
||||
# Exported BOM files
|
||||
*.xml
|
||||
*.csv
|
||||
|
||||
# Archived Backups (KiCad 6.0)
|
||||
**/*-backups/*.zip
|
||||
|
||||
# Local project settings
|
||||
*.kicad_prl
|
||||
@@ -0,0 +1,9 @@
|
||||
EESchema-DOCLIB Version 2.0
|
||||
#
|
||||
$CMP L6982N50DR
|
||||
D 38 V, 2 A synchronous step-down converter with 20 uA quiescent current
|
||||
K
|
||||
F https://www.st.com/resource/en/datasheet/l6982.pdf
|
||||
$ENDCMP
|
||||
#
|
||||
#End Doc Library
|
||||
@@ -0,0 +1,78 @@
|
||||
(kicad_symbol_lib (version 20211014) (generator SamacSys_ECAD_Model)
|
||||
(symbol "L6982N50DR" (in_bom yes) (on_board yes)
|
||||
(property "Reference" "IC" (at 34.29 7.62 0)
|
||||
(effects (font (size 1.27 1.27)) (justify left top))
|
||||
)
|
||||
(property "Value" "L6982N50DR" (at 34.29 5.08 0)
|
||||
(effects (font (size 1.27 1.27)) (justify left top))
|
||||
)
|
||||
(property "Footprint" "SOIC127P600X175-8N" (at 34.29 -94.92 0)
|
||||
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||
)
|
||||
(property "Datasheet" "https://www.st.com/resource/en/datasheet/l6982.pdf" (at 34.29 -194.92 0)
|
||||
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||
)
|
||||
(property "ki_description" "38 V, 2 A synchronous step-down converter with 20 uA quiescent current" (at 34.29 -294.92 0)
|
||||
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||
)
|
||||
(property "Height" "1.75" (at 34.29 -394.92 0)
|
||||
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||
)
|
||||
(property "Manufacturer_Name" "STMicroelectronics" (at 34.29 -494.92 0)
|
||||
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||
)
|
||||
(property "Manufacturer_Part_Number" "L6982N50DR" (at 34.29 -594.92 0)
|
||||
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||
)
|
||||
(property "Mouser Part Number" "511-L6982N50DR" (at 34.29 -694.92 0)
|
||||
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||
)
|
||||
(property "Mouser Price/Stock" "https://www.mouser.co.uk/ProductDetail/STMicroelectronics/L6982N50DR?qs=rQFj71Wb1eViugxt34b%2FVA%3D%3D" (at 34.29 -794.92 0)
|
||||
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||
)
|
||||
(property "Arrow Part Number" "L6982N50DR" (at 34.29 -894.92 0)
|
||||
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||
)
|
||||
(property "Arrow Price/Stock" "https://www.arrow.com/en/products/l6982n50dr/stmicroelectronics?utm_currency=USD®ion=nac" (at 34.29 -994.92 0)
|
||||
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||
)
|
||||
(rectangle
|
||||
(start 5.08 2.54)
|
||||
(end 33.02 -10.16)
|
||||
(stroke (width 0.254) (type default))
|
||||
(fill (type background))
|
||||
)
|
||||
(pin passive line (at 0 0 0) (length 5.08)
|
||||
(name "SW" (effects (font (size 1.27 1.27))))
|
||||
(number "1" (effects (font (size 1.27 1.27))))
|
||||
)
|
||||
(pin passive line (at 0 -2.54 0) (length 5.08)
|
||||
(name "BOOT" (effects (font (size 1.27 1.27))))
|
||||
(number "2" (effects (font (size 1.27 1.27))))
|
||||
)
|
||||
(pin passive line (at 0 -5.08 0) (length 5.08)
|
||||
(name "VCC" (effects (font (size 1.27 1.27))))
|
||||
(number "3" (effects (font (size 1.27 1.27))))
|
||||
)
|
||||
(pin passive line (at 0 -7.62 0) (length 5.08)
|
||||
(name "VOUT/FB" (effects (font (size 1.27 1.27))))
|
||||
(number "4" (effects (font (size 1.27 1.27))))
|
||||
)
|
||||
(pin passive line (at 38.1 0 180) (length 5.08)
|
||||
(name "PGND" (effects (font (size 1.27 1.27))))
|
||||
(number "8" (effects (font (size 1.27 1.27))))
|
||||
)
|
||||
(pin passive line (at 38.1 -2.54 180) (length 5.08)
|
||||
(name "VIN" (effects (font (size 1.27 1.27))))
|
||||
(number "7" (effects (font (size 1.27 1.27))))
|
||||
)
|
||||
(pin passive line (at 38.1 -5.08 180) (length 5.08)
|
||||
(name "AGND" (effects (font (size 1.27 1.27))))
|
||||
(number "6" (effects (font (size 1.27 1.27))))
|
||||
)
|
||||
(pin passive line (at 38.1 -7.62 180) (length 5.08)
|
||||
(name "EN/CLKIN" (effects (font (size 1.27 1.27))))
|
||||
(number "5" (effects (font (size 1.27 1.27))))
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,31 @@
|
||||
EESchema-LIBRARY Version 2.3
|
||||
#encoding utf-8
|
||||
#SamacSys ECAD Model L6982N50DR
|
||||
#/17168867/1957227/2.50/8/2/Integrated Circuit
|
||||
DEF L6982N50DR IC 0 30 Y Y 1 F N
|
||||
F0 "IC" 1350 300 50 H V L CNN
|
||||
F1 "L6982N50DR" 1350 200 50 H V L CNN
|
||||
F2 "SOIC127P600X175-8N" 1350 100 50 H I L CNN
|
||||
F3 "https://www.st.com/resource/en/datasheet/l6982.pdf" 1350 0 50 H I L CNN
|
||||
F4 "38 V, 2 A synchronous step-down converter with 20 uA quiescent current" 1350 -100 50 H I L CNN "Description"
|
||||
F5 "1.75" 1350 -200 50 H I L CNN "Height"
|
||||
F6 "STMicroelectronics" 1350 -300 50 H I L CNN "Manufacturer_Name"
|
||||
F7 "L6982N50DR" 1350 -400 50 H I L CNN "Manufacturer_Part_Number"
|
||||
F8 "511-L6982N50DR" 1350 -500 50 H I L CNN "Mouser Part Number"
|
||||
F9 "https://www.mouser.co.uk/ProductDetail/STMicroelectronics/L6982N50DR?qs=rQFj71Wb1eViugxt34b%2FVA%3D%3D" 1350 -600 50 H I L CNN "Mouser Price/Stock"
|
||||
F10 "L6982N50DR" 1350 -700 50 H I L CNN "Arrow Part Number"
|
||||
F11 "https://www.arrow.com/en/products/l6982n50dr/stmicroelectronics?utm_currency=USD®ion=nac" 1350 -800 50 H I L CNN "Arrow Price/Stock"
|
||||
DRAW
|
||||
X SW 1 0 0 200 R 50 50 0 0 P
|
||||
X BOOT 2 0 -100 200 R 50 50 0 0 P
|
||||
X VCC 3 0 -200 200 R 50 50 0 0 P
|
||||
X VOUT/FB 4 0 -300 200 R 50 50 0 0 P
|
||||
X PGND 8 1500 0 200 L 50 50 0 0 P
|
||||
X VIN 7 1500 -100 200 L 50 50 0 0 P
|
||||
X AGND 6 1500 -200 200 L 50 50 0 0 P
|
||||
X EN/CLKIN 5 1500 -300 200 L 50 50 0 0 P
|
||||
P 5 0 1 6 200 100 1300 100 1300 -400 200 -400 200 100 N
|
||||
ENDDRAW
|
||||
ENDDEF
|
||||
#
|
||||
#End Library
|
||||
@@ -0,0 +1,81 @@
|
||||
PCBNEW-LibModule-V1 2026-03-07 11:39:32
|
||||
# encoding utf-8
|
||||
Units mm
|
||||
$INDEX
|
||||
SOIC127P600X175-8N
|
||||
$EndINDEX
|
||||
$MODULE SOIC127P600X175-8N
|
||||
Po 0 0 0 15 69ac0e74 00000000 ~~
|
||||
Li SOIC127P600X175-8N
|
||||
Cd SO 8L
|
||||
Kw Integrated Circuit
|
||||
Sc 0
|
||||
At SMD
|
||||
AR
|
||||
Op 0 0 0
|
||||
T0 0 0 1.27 1.27 0 0.254 N V 21 N "IC**"
|
||||
T1 0 0 1.27 1.27 0 0.254 N I 21 N "SOIC127P600X175-8N"
|
||||
DS -3.725 -2.75 3.725 -2.75 0.05 24
|
||||
DS 3.725 -2.75 3.725 2.75 0.05 24
|
||||
DS 3.725 2.75 -3.725 2.75 0.05 24
|
||||
DS -3.725 2.75 -3.725 -2.75 0.05 24
|
||||
DS -1.95 -2.45 1.95 -2.45 0.1 24
|
||||
DS 1.95 -2.45 1.95 2.45 0.1 24
|
||||
DS 1.95 2.45 -1.95 2.45 0.1 24
|
||||
DS -1.95 2.45 -1.95 -2.45 0.1 24
|
||||
DS -1.95 -1.18 -0.68 -2.45 0.1 24
|
||||
DS -1.6 -2.45 1.6 -2.45 0.2 21
|
||||
DS 1.6 -2.45 1.6 2.45 0.2 21
|
||||
DS 1.6 2.45 -1.6 2.45 0.2 21
|
||||
DS -1.6 2.45 -1.6 -2.45 0.2 21
|
||||
DS -3.475 -2.605 -1.95 -2.605 0.2 21
|
||||
$PAD
|
||||
Po -2.712 -1.905
|
||||
Sh "1" R 0.7 1.525 0 0 900
|
||||
At SMD N 00888000
|
||||
Ne 0 ""
|
||||
$EndPAD
|
||||
$PAD
|
||||
Po -2.712 -0.635
|
||||
Sh "2" R 0.7 1.525 0 0 900
|
||||
At SMD N 00888000
|
||||
Ne 0 ""
|
||||
$EndPAD
|
||||
$PAD
|
||||
Po -2.712 0.635
|
||||
Sh "3" R 0.7 1.525 0 0 900
|
||||
At SMD N 00888000
|
||||
Ne 0 ""
|
||||
$EndPAD
|
||||
$PAD
|
||||
Po -2.712 1.905
|
||||
Sh "4" R 0.7 1.525 0 0 900
|
||||
At SMD N 00888000
|
||||
Ne 0 ""
|
||||
$EndPAD
|
||||
$PAD
|
||||
Po 2.712 1.905
|
||||
Sh "5" R 0.7 1.525 0 0 900
|
||||
At SMD N 00888000
|
||||
Ne 0 ""
|
||||
$EndPAD
|
||||
$PAD
|
||||
Po 2.712 0.635
|
||||
Sh "6" R 0.7 1.525 0 0 900
|
||||
At SMD N 00888000
|
||||
Ne 0 ""
|
||||
$EndPAD
|
||||
$PAD
|
||||
Po 2.712 -0.635
|
||||
Sh "7" R 0.7 1.525 0 0 900
|
||||
At SMD N 00888000
|
||||
Ne 0 ""
|
||||
$EndPAD
|
||||
$PAD
|
||||
Po 2.712 -1.905
|
||||
Sh "8" R 0.7 1.525 0 0 900
|
||||
At SMD N 00888000
|
||||
Ne 0 ""
|
||||
$EndPAD
|
||||
$EndMODULE SOIC127P600X175-8N
|
||||
$EndLIBRARY
|
||||
@@ -0,0 +1,45 @@
|
||||
(module "LSM6DSV16BXTR" (layer F.Cu)
|
||||
(descr "LGA-14L 2.5 x 3.0 x 0.74 mm XDM)")
|
||||
(tags "Integrated Circuit")
|
||||
(attr smd)
|
||||
(fp_text reference IC** (at 0.000 -0) (layer F.SilkS)
|
||||
(effects (font (size 1.27 1.27) (thickness 0.254)))
|
||||
)
|
||||
(fp_text user %R (at 0.000 -0) (layer F.Fab)
|
||||
(effects (font (size 1.27 1.27) (thickness 0.254)))
|
||||
)
|
||||
(fp_text value "LSM6DSV16BXTR" (at 0.000 -0) (layer F.SilkS) hide
|
||||
(effects (font (size 1.27 1.27) (thickness 0.254)))
|
||||
)
|
||||
(fp_line (start -1.5 -1.25) (end 1.5 -1.25) (layer F.Fab) (width 0.1))
|
||||
(fp_line (start 1.5 -1.25) (end 1.5 1.25) (layer F.Fab) (width 0.1))
|
||||
(fp_line (start 1.5 1.25) (end -1.5 1.25) (layer F.Fab) (width 0.1))
|
||||
(fp_line (start -1.5 1.25) (end -1.5 -1.25) (layer F.Fab) (width 0.1))
|
||||
(fp_line (start -2.3 -2.05) (end 2.3 -2.05) (layer F.CrtYd) (width 0.1))
|
||||
(fp_line (start 2.3 -2.05) (end 2.3 2.05) (layer F.CrtYd) (width 0.1))
|
||||
(fp_line (start 2.3 2.05) (end -2.3 2.05) (layer F.CrtYd) (width 0.1))
|
||||
(fp_line (start -2.3 2.05) (end -2.3 -2.05) (layer F.CrtYd) (width 0.1))
|
||||
(fp_line (start -1.9 -0.75) (end -1.9 -0.75) (layer F.SilkS) (width 0.1))
|
||||
(fp_line (start -2 -0.75) (end -2 -0.75) (layer F.SilkS) (width 0.1))
|
||||
(fp_arc (start -1.95 -0.75) (end -1.900 -0.75) (angle -180) (layer F.SilkS) (width 0.1))
|
||||
(fp_arc (start -1.95 -0.75) (end -2.000 -0.75) (angle -180) (layer F.SilkS) (width 0.1))
|
||||
(pad 1 smd rect (at -1.150 -0.75 90) (size 0.300 0.600) (layers F.Cu F.Paste F.Mask))
|
||||
(pad 2 smd rect (at -1.150 -0.25 90) (size 0.300 0.600) (layers F.Cu F.Paste F.Mask))
|
||||
(pad 3 smd rect (at -1.150 0.25 90) (size 0.300 0.600) (layers F.Cu F.Paste F.Mask))
|
||||
(pad 4 smd rect (at -1.150 0.75 90) (size 0.300 0.600) (layers F.Cu F.Paste F.Mask))
|
||||
(pad 5 smd rect (at -0.500 0.9 0) (size 0.300 0.600) (layers F.Cu F.Paste F.Mask))
|
||||
(pad 6 smd rect (at 0.000 0.9 0) (size 0.300 0.600) (layers F.Cu F.Paste F.Mask))
|
||||
(pad 7 smd rect (at 0.500 0.9 0) (size 0.300 0.600) (layers F.Cu F.Paste F.Mask))
|
||||
(pad 8 smd rect (at 1.150 0.75 90) (size 0.300 0.600) (layers F.Cu F.Paste F.Mask))
|
||||
(pad 9 smd rect (at 1.150 0.25 90) (size 0.300 0.600) (layers F.Cu F.Paste F.Mask))
|
||||
(pad 10 smd rect (at 1.150 -0.25 90) (size 0.300 0.600) (layers F.Cu F.Paste F.Mask))
|
||||
(pad 11 smd rect (at 1.150 -0.75 90) (size 0.300 0.600) (layers F.Cu F.Paste F.Mask))
|
||||
(pad 12 smd rect (at 0.500 -0.9 0) (size 0.300 0.600) (layers F.Cu F.Paste F.Mask))
|
||||
(pad 13 smd rect (at 0.000 -0.9 0) (size 0.300 0.600) (layers F.Cu F.Paste F.Mask))
|
||||
(pad 14 smd rect (at -0.500 -0.9 0) (size 0.300 0.600) (layers F.Cu F.Paste F.Mask))
|
||||
(model LSM6DSV80XTR.stp
|
||||
(at (xyz 0 0 0))
|
||||
(scale (xyz 1 1 1))
|
||||
(rotate (xyz 0 0 0))
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,9 @@
|
||||
EESchema-DOCLIB Version 2.0
|
||||
#
|
||||
$CMP LSM6DSV80XTR
|
||||
D 6-axis IMU (inertial measurement unit) with high-g accelerometer, embedded AI, and sensor fusion for wearables and sport trackers
|
||||
K
|
||||
F https://www.st.com/resource/en/datasheet/lsm6dsv80x.pdf
|
||||
$ENDCMP
|
||||
#
|
||||
#End Doc Library
|
||||
@@ -0,0 +1,102 @@
|
||||
(kicad_symbol_lib (version 20211014) (generator SamacSys_ECAD_Model)
|
||||
(symbol "LSM6DSV80XTR" (in_bom yes) (on_board yes)
|
||||
(property "Reference" "IC" (at 31.75 15.24 0)
|
||||
(effects (font (size 1.27 1.27)) (justify left top))
|
||||
)
|
||||
(property "Value" "LSM6DSV80XTR" (at 31.75 12.7 0)
|
||||
(effects (font (size 1.27 1.27)) (justify left top))
|
||||
)
|
||||
(property "Footprint" "LSM6DSV16BXTR" (at 31.75 -87.3 0)
|
||||
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||
)
|
||||
(property "Datasheet" "https://www.st.com/resource/en/datasheet/lsm6dsv80x.pdf" (at 31.75 -187.3 0)
|
||||
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||
)
|
||||
(property "ki_description" "6-axis IMU (inertial measurement unit) with high-g accelerometer, embedded AI, and sensor fusion for wearables and sport trackers" (at 31.75 -287.3 0)
|
||||
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||
)
|
||||
(property "Height" "0.74" (at 31.75 -387.3 0)
|
||||
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||
)
|
||||
(property "Manufacturer_Name" "STMicroelectronics" (at 31.75 -487.3 0)
|
||||
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||
)
|
||||
(property "Manufacturer_Part_Number" "LSM6DSV80XTR" (at 31.75 -587.3 0)
|
||||
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||
)
|
||||
(property "Mouser Part Number" "" (at 31.75 -687.3 0)
|
||||
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||
)
|
||||
(property "Mouser Price/Stock" "" (at 31.75 -787.3 0)
|
||||
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||
)
|
||||
(property "Arrow Part Number" "LSM6DSV80XTR" (at 31.75 -887.3 0)
|
||||
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||
)
|
||||
(property "Arrow Price/Stock" "https://www.arrow.com/en/products/lsm6dsv80xtr/stmicroelectronics?utm_currency=USD®ion=nac" (at 31.75 -987.3 0)
|
||||
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||
)
|
||||
(rectangle
|
||||
(start 5.08 10.16)
|
||||
(end 30.48 -20.32)
|
||||
(stroke (width 0.254) (type default))
|
||||
(fill (type background))
|
||||
)
|
||||
(pin passive line (at 0 0 0) (length 5.08)
|
||||
(name "SDO/SA0" (effects (font (size 1.27 1.27))))
|
||||
(number "1" (effects (font (size 1.27 1.27))))
|
||||
)
|
||||
(pin passive line (at 0 -2.54 0) (length 5.08)
|
||||
(name "SDX" (effects (font (size 1.27 1.27))))
|
||||
(number "2" (effects (font (size 1.27 1.27))))
|
||||
)
|
||||
(pin passive line (at 0 -5.08 0) (length 5.08)
|
||||
(name "SCX" (effects (font (size 1.27 1.27))))
|
||||
(number "3" (effects (font (size 1.27 1.27))))
|
||||
)
|
||||
(pin passive line (at 0 -7.62 0) (length 5.08)
|
||||
(name "INT1" (effects (font (size 1.27 1.27))))
|
||||
(number "4" (effects (font (size 1.27 1.27))))
|
||||
)
|
||||
(pin passive line (at 15.24 -25.4 90) (length 5.08)
|
||||
(name "VDDIO" (effects (font (size 1.27 1.27))))
|
||||
(number "5" (effects (font (size 1.27 1.27))))
|
||||
)
|
||||
(pin passive line (at 17.78 -25.4 90) (length 5.08)
|
||||
(name "GND_1" (effects (font (size 1.27 1.27))))
|
||||
(number "6" (effects (font (size 1.27 1.27))))
|
||||
)
|
||||
(pin passive line (at 20.32 -25.4 90) (length 5.08)
|
||||
(name "GND_2" (effects (font (size 1.27 1.27))))
|
||||
(number "7" (effects (font (size 1.27 1.27))))
|
||||
)
|
||||
(pin passive line (at 35.56 -7.62 180) (length 5.08)
|
||||
(name "VDD" (effects (font (size 1.27 1.27))))
|
||||
(number "8" (effects (font (size 1.27 1.27))))
|
||||
)
|
||||
(pin passive line (at 35.56 -5.08 180) (length 5.08)
|
||||
(name "INT2" (effects (font (size 1.27 1.27))))
|
||||
(number "9" (effects (font (size 1.27 1.27))))
|
||||
)
|
||||
(pin passive line (at 35.56 -2.54 180) (length 5.08)
|
||||
(name "NC_1" (effects (font (size 1.27 1.27))))
|
||||
(number "10" (effects (font (size 1.27 1.27))))
|
||||
)
|
||||
(pin passive line (at 35.56 0 180) (length 5.08)
|
||||
(name "NC_2" (effects (font (size 1.27 1.27))))
|
||||
(number "11" (effects (font (size 1.27 1.27))))
|
||||
)
|
||||
(pin passive line (at 20.32 15.24 270) (length 5.08)
|
||||
(name "CS" (effects (font (size 1.27 1.27))))
|
||||
(number "12" (effects (font (size 1.27 1.27))))
|
||||
)
|
||||
(pin passive line (at 17.78 15.24 270) (length 5.08)
|
||||
(name "SCL" (effects (font (size 1.27 1.27))))
|
||||
(number "13" (effects (font (size 1.27 1.27))))
|
||||
)
|
||||
(pin passive line (at 15.24 15.24 270) (length 5.08)
|
||||
(name "SDA" (effects (font (size 1.27 1.27))))
|
||||
(number "14" (effects (font (size 1.27 1.27))))
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,37 @@
|
||||
EESchema-LIBRARY Version 2.3
|
||||
#encoding utf-8
|
||||
#SamacSys ECAD Model LSM6DSV80XTR
|
||||
#/20519981/1957227/2.50/14/2/Integrated Circuit
|
||||
DEF LSM6DSV80XTR IC 0 30 Y Y 1 F N
|
||||
F0 "IC" 1250 600 50 H V L CNN
|
||||
F1 "LSM6DSV80XTR" 1250 500 50 H V L CNN
|
||||
F2 "LSM6DSV16BXTR" 1250 400 50 H I L CNN
|
||||
F3 "https://www.st.com/resource/en/datasheet/lsm6dsv80x.pdf" 1250 300 50 H I L CNN
|
||||
F4 "6-axis IMU (inertial measurement unit) with high-g accelerometer, embedded AI, and sensor fusion for wearables and sport trackers" 1250 200 50 H I L CNN "Description"
|
||||
F5 "0.74" 1250 100 50 H I L CNN "Height"
|
||||
F6 "STMicroelectronics" 1250 0 50 H I L CNN "Manufacturer_Name"
|
||||
F7 "LSM6DSV80XTR" 1250 -100 50 H I L CNN "Manufacturer_Part_Number"
|
||||
F8 "" 1250 -200 50 H I L CNN "Mouser Part Number"
|
||||
F9 "" 1250 -300 50 H I L CNN "Mouser Price/Stock"
|
||||
F10 "LSM6DSV80XTR" 1250 -400 50 H I L CNN "Arrow Part Number"
|
||||
F11 "https://www.arrow.com/en/products/lsm6dsv80xtr/stmicroelectronics?utm_currency=USD®ion=nac" 1250 -500 50 H I L CNN "Arrow Price/Stock"
|
||||
DRAW
|
||||
X SDO/SA0 1 0 0 200 R 50 50 0 0 P
|
||||
X SDX 2 0 -100 200 R 50 50 0 0 P
|
||||
X SCX 3 0 -200 200 R 50 50 0 0 P
|
||||
X INT1 4 0 -300 200 R 50 50 0 0 P
|
||||
X VDDIO 5 600 -1000 200 U 50 50 0 0 P
|
||||
X GND_1 6 700 -1000 200 U 50 50 0 0 P
|
||||
X GND_2 7 800 -1000 200 U 50 50 0 0 P
|
||||
X VDD 8 1400 -300 200 L 50 50 0 0 P
|
||||
X INT2 9 1400 -200 200 L 50 50 0 0 P
|
||||
X NC_1 10 1400 -100 200 L 50 50 0 0 P
|
||||
X NC_2 11 1400 0 200 L 50 50 0 0 P
|
||||
X CS 12 800 600 200 D 50 50 0 0 P
|
||||
X SCL 13 700 600 200 D 50 50 0 0 P
|
||||
X SDA 14 600 600 200 D 50 50 0 0 P
|
||||
P 5 0 1 6 200 400 1200 400 1200 -800 200 -800 200 400 N
|
||||
ENDDRAW
|
||||
ENDDEF
|
||||
#
|
||||
#End Library
|
||||
@@ -0,0 +1,115 @@
|
||||
PCBNEW-LibModule-V1 2026-03-08 11:22:49
|
||||
# encoding utf-8
|
||||
Units mm
|
||||
$INDEX
|
||||
LSM6DSV16BXTR
|
||||
$EndINDEX
|
||||
$MODULE LSM6DSV16BXTR
|
||||
Po 0 0 0 15 69ad5c09 00000000 ~~
|
||||
Li LSM6DSV16BXTR
|
||||
Cd LGA-14L 2.5 x 3.0 x 0.74 mm XDM)
|
||||
Kw Integrated Circuit
|
||||
Sc 0
|
||||
At SMD
|
||||
AR
|
||||
Op 0 0 0
|
||||
T0 0.000 -0 1.27 1.27 0 0.254 N V 21 N "IC**"
|
||||
T1 0.000 -0 1.27 1.27 0 0.254 N I 21 N "LSM6DSV16BXTR"
|
||||
DS -1.5 -1.25 1.5 -1.25 0.1 24
|
||||
DS 1.5 -1.25 1.5 1.25 0.1 24
|
||||
DS 1.5 1.25 -1.5 1.25 0.1 24
|
||||
DS -1.5 1.25 -1.5 -1.25 0.1 24
|
||||
DS -2.3 -2.05 2.3 -2.05 0.1 24
|
||||
DS 2.3 -2.05 2.3 2.05 0.1 24
|
||||
DS 2.3 2.05 -2.3 2.05 0.1 24
|
||||
DS -2.3 2.05 -2.3 -2.05 0.1 24
|
||||
DS -1.9 -0.75 -1.9 -0.75 0.1 21
|
||||
DS -2 -0.75 -2 -0.75 0.1 21
|
||||
DA -1.95 -0.75 -1.900 -0.75 -1800 0.1 21
|
||||
DA -1.95 -0.75 -2.000 -0.75 -1800 0.1 21
|
||||
$PAD
|
||||
Po -1.150 -0.75
|
||||
Sh "1" R 0.300 0.600 0 0 900
|
||||
At SMD N 00888000
|
||||
Ne 0 ""
|
||||
$EndPAD
|
||||
$PAD
|
||||
Po -1.150 -0.25
|
||||
Sh "2" R 0.300 0.600 0 0 900
|
||||
At SMD N 00888000
|
||||
Ne 0 ""
|
||||
$EndPAD
|
||||
$PAD
|
||||
Po -1.150 0.25
|
||||
Sh "3" R 0.300 0.600 0 0 900
|
||||
At SMD N 00888000
|
||||
Ne 0 ""
|
||||
$EndPAD
|
||||
$PAD
|
||||
Po -1.150 0.75
|
||||
Sh "4" R 0.300 0.600 0 0 900
|
||||
At SMD N 00888000
|
||||
Ne 0 ""
|
||||
$EndPAD
|
||||
$PAD
|
||||
Po -0.500 0.9
|
||||
Sh "5" R 0.300 0.600 0 0 0
|
||||
At SMD N 00888000
|
||||
Ne 0 ""
|
||||
$EndPAD
|
||||
$PAD
|
||||
Po 0.000 0.9
|
||||
Sh "6" R 0.300 0.600 0 0 0
|
||||
At SMD N 00888000
|
||||
Ne 0 ""
|
||||
$EndPAD
|
||||
$PAD
|
||||
Po 0.500 0.9
|
||||
Sh "7" R 0.300 0.600 0 0 0
|
||||
At SMD N 00888000
|
||||
Ne 0 ""
|
||||
$EndPAD
|
||||
$PAD
|
||||
Po 1.150 0.75
|
||||
Sh "8" R 0.300 0.600 0 0 900
|
||||
At SMD N 00888000
|
||||
Ne 0 ""
|
||||
$EndPAD
|
||||
$PAD
|
||||
Po 1.150 0.25
|
||||
Sh "9" R 0.300 0.600 0 0 900
|
||||
At SMD N 00888000
|
||||
Ne 0 ""
|
||||
$EndPAD
|
||||
$PAD
|
||||
Po 1.150 -0.25
|
||||
Sh "10" R 0.300 0.600 0 0 900
|
||||
At SMD N 00888000
|
||||
Ne 0 ""
|
||||
$EndPAD
|
||||
$PAD
|
||||
Po 1.150 -0.75
|
||||
Sh "11" R 0.300 0.600 0 0 900
|
||||
At SMD N 00888000
|
||||
Ne 0 ""
|
||||
$EndPAD
|
||||
$PAD
|
||||
Po 0.500 -0.9
|
||||
Sh "12" R 0.300 0.600 0 0 0
|
||||
At SMD N 00888000
|
||||
Ne 0 ""
|
||||
$EndPAD
|
||||
$PAD
|
||||
Po 0.000 -0.9
|
||||
Sh "13" R 0.300 0.600 0 0 0
|
||||
At SMD N 00888000
|
||||
Ne 0 ""
|
||||
$EndPAD
|
||||
$PAD
|
||||
Po -0.500 -0.9
|
||||
Sh "14" R 0.300 0.600 0 0 0
|
||||
At SMD N 00888000
|
||||
Ne 0 ""
|
||||
$EndPAD
|
||||
$EndMODULE LSM6DSV16BXTR
|
||||
$EndLIBRARY
|
||||
@@ -0,0 +1,9 @@
|
||||
EESchema-DOCLIB Version 2.0
|
||||
#
|
||||
$CMP W3213
|
||||
D Antennas GPS patch antenna 13x13 mm
|
||||
K
|
||||
F https://www.arrow.com/en/products/w3213/pulse-electronics-corporation
|
||||
$ENDCMP
|
||||
#
|
||||
#End Doc Library
|
||||
@@ -0,0 +1,31 @@
|
||||
(module "W3213" (layer F.Cu)
|
||||
(descr "W3213-1")
|
||||
(tags "Antenna")
|
||||
(fp_text reference ANT** (at 0.000 -0.7) (layer F.SilkS)
|
||||
(effects (font (size 1.27 1.27) (thickness 0.254)))
|
||||
)
|
||||
(fp_text user %R (at 0.000 -0.7) (layer F.Fab)
|
||||
(effects (font (size 1.27 1.27) (thickness 0.254)))
|
||||
)
|
||||
(fp_text value "W3213" (at 0.000 -0.7) (layer F.SilkS) hide
|
||||
(effects (font (size 1.27 1.27) (thickness 0.254)))
|
||||
)
|
||||
(fp_line (start -6.5 -7.2) (end 6.5 -7.2) (layer F.Fab) (width 0.2))
|
||||
(fp_line (start 6.5 -7.2) (end 6.5 5.8) (layer F.Fab) (width 0.2))
|
||||
(fp_line (start 6.5 5.8) (end -6.5 5.8) (layer F.Fab) (width 0.2))
|
||||
(fp_line (start -6.5 5.8) (end -6.5 -7.2) (layer F.Fab) (width 0.2))
|
||||
(fp_line (start -6.5 -7.2) (end 6.5 -7.2) (layer F.SilkS) (width 0.1))
|
||||
(fp_line (start 6.5 -7.2) (end 6.5 5.8) (layer F.SilkS) (width 0.1))
|
||||
(fp_line (start 6.5 5.8) (end -6.5 5.8) (layer F.SilkS) (width 0.1))
|
||||
(fp_line (start -6.5 5.8) (end -6.5 -7.2) (layer F.SilkS) (width 0.1))
|
||||
(fp_line (start -7.5 -8.2) (end 7.5 -8.2) (layer F.CrtYd) (width 0.1))
|
||||
(fp_line (start 7.5 -8.2) (end 7.5 6.8) (layer F.CrtYd) (width 0.1))
|
||||
(fp_line (start 7.5 6.8) (end -7.5 6.8) (layer F.CrtYd) (width 0.1))
|
||||
(fp_line (start -7.5 6.8) (end -7.5 -8.2) (layer F.CrtYd) (width 0.1))
|
||||
(pad 1 thru_hole circle (at 0.000 -0) (size 1.575 1.575) (drill 1.05) (layers *.Cu *.Mask))
|
||||
(model W3213.stp
|
||||
(at (xyz 0 0 0))
|
||||
(scale (xyz 1 1 1))
|
||||
(rotate (xyz 0 0 0))
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,50 @@
|
||||
(kicad_symbol_lib (version 20211014) (generator SamacSys_ECAD_Model)
|
||||
(symbol "W3213" (in_bom yes) (on_board yes)
|
||||
(property "Reference" "ANT" (at 16.51 7.62 0)
|
||||
(effects (font (size 1.27 1.27)) (justify left top))
|
||||
)
|
||||
(property "Value" "W3213" (at 16.51 5.08 0)
|
||||
(effects (font (size 1.27 1.27)) (justify left top))
|
||||
)
|
||||
(property "Footprint" "W3213" (at 16.51 -94.92 0)
|
||||
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||
)
|
||||
(property "Datasheet" "https://www.arrow.com/en/products/w3213/pulse-electronics-corporation" (at 16.51 -194.92 0)
|
||||
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||
)
|
||||
(property "ki_description" "Antennas GPS patch antenna 13x13 mm" (at 16.51 -294.92 0)
|
||||
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||
)
|
||||
(property "Height" "4.83" (at 16.51 -394.92 0)
|
||||
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||
)
|
||||
(property "Manufacturer_Name" "Yageo Group" (at 16.51 -494.92 0)
|
||||
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||
)
|
||||
(property "Manufacturer_Part_Number" "W3213" (at 16.51 -594.92 0)
|
||||
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||
)
|
||||
(property "Mouser Part Number" "673-W3213" (at 16.51 -694.92 0)
|
||||
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||
)
|
||||
(property "Mouser Price/Stock" "https://www.mouser.co.uk/ProductDetail/Pulse-Electronics/W3213?qs=tNd8r9m29y7JOJNw2gSjhQ%3D%3D" (at 16.51 -794.92 0)
|
||||
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||
)
|
||||
(property "Arrow Part Number" "W3213" (at 16.51 -894.92 0)
|
||||
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||
)
|
||||
(property "Arrow Price/Stock" "https://www.arrow.com/en/products/w3213/pulse-electronics-corporation" (at 16.51 -994.92 0)
|
||||
(effects (font (size 1.27 1.27)) (justify left top) hide)
|
||||
)
|
||||
(rectangle
|
||||
(start 5.08 2.54)
|
||||
(end 15.24 -2.54)
|
||||
(stroke (width 0.254) (type default))
|
||||
(fill (type background))
|
||||
)
|
||||
(pin passive line (at 0 0 0) (length 5.08)
|
||||
(name "1" (effects (font (size 1.27 1.27))))
|
||||
(number "1" (effects (font (size 1.27 1.27))))
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,24 @@
|
||||
EESchema-LIBRARY Version 2.3
|
||||
#encoding utf-8
|
||||
#SamacSys ECAD Model W3213
|
||||
#/1938663/1957227/2.50/1/0/Antenna
|
||||
DEF W3213 ANT 0 30 Y Y 1 F N
|
||||
F0 "ANT" 650 300 50 H V L CNN
|
||||
F1 "W3213" 650 200 50 H V L CNN
|
||||
F2 "W3213" 650 100 50 H I L CNN
|
||||
F3 "https://www.arrow.com/en/products/w3213/pulse-electronics-corporation" 650 0 50 H I L CNN
|
||||
F4 "Antennas GPS patch antenna 13x13 mm" 650 -100 50 H I L CNN "Description"
|
||||
F5 "4.83" 650 -200 50 H I L CNN "Height"
|
||||
F6 "Yageo Group" 650 -300 50 H I L CNN "Manufacturer_Name"
|
||||
F7 "W3213" 650 -400 50 H I L CNN "Manufacturer_Part_Number"
|
||||
F8 "673-W3213" 650 -500 50 H I L CNN "Mouser Part Number"
|
||||
F9 "https://www.mouser.co.uk/ProductDetail/Pulse-Electronics/W3213?qs=tNd8r9m29y7JOJNw2gSjhQ%3D%3D" 650 -600 50 H I L CNN "Mouser Price/Stock"
|
||||
F10 "W3213" 650 -700 50 H I L CNN "Arrow Part Number"
|
||||
F11 "https://www.arrow.com/en/products/w3213/pulse-electronics-corporation" 650 -800 50 H I L CNN "Arrow Price/Stock"
|
||||
DRAW
|
||||
X 1 1 0 0 200 R 50 50 0 0 P
|
||||
P 5 0 1 6 200 100 600 100 600 -100 200 -100 200 100 N
|
||||
ENDDRAW
|
||||
ENDDEF
|
||||
#
|
||||
#End Library
|
||||
@@ -0,0 +1,38 @@
|
||||
PCBNEW-LibModule-V1 2026-03-07 19:18:26
|
||||
# encoding utf-8
|
||||
Units mm
|
||||
$INDEX
|
||||
W3213
|
||||
$EndINDEX
|
||||
$MODULE W3213
|
||||
Po 0 0 0 15 69ac7a02 00000000 ~~
|
||||
Li W3213
|
||||
Cd W3213-1
|
||||
Kw Antenna
|
||||
Sc 0
|
||||
At STD
|
||||
AR
|
||||
Op 0 0 0
|
||||
T0 0.000 -0.7 1.27 1.27 0 0.254 N V 21 N "ANT**"
|
||||
T1 0.000 -0.7 1.27 1.27 0 0.254 N I 21 N "W3213"
|
||||
DS -6.5 -7.2 6.5 -7.2 0.2 24
|
||||
DS 6.5 -7.2 6.5 5.8 0.2 24
|
||||
DS 6.5 5.8 -6.5 5.8 0.2 24
|
||||
DS -6.5 5.8 -6.5 -7.2 0.2 24
|
||||
DS -6.5 -7.2 6.5 -7.2 0.1 21
|
||||
DS 6.5 -7.2 6.5 5.8 0.1 21
|
||||
DS 6.5 5.8 -6.5 5.8 0.1 21
|
||||
DS -6.5 5.8 -6.5 -7.2 0.1 21
|
||||
DS -7.5 -8.2 7.5 -8.2 0.1 24
|
||||
DS 7.5 -8.2 7.5 6.8 0.1 24
|
||||
DS 7.5 6.8 -7.5 6.8 0.1 24
|
||||
DS -7.5 6.8 -7.5 -8.2 0.1 24
|
||||
$PAD
|
||||
Po 0.000 -0
|
||||
Sh "1" C 1.575 1.575 0 0 900
|
||||
Dr 1.05 0 0
|
||||
At STD N 00E0FFFF
|
||||
Ne 0 ""
|
||||
$EndPAD
|
||||
$EndMODULE W3213
|
||||
$EndLIBRARY
|
||||
@@ -0,0 +1,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
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,694 @@
|
||||
{
|
||||
"board": {
|
||||
"3dviewports": [],
|
||||
"design_settings": {
|
||||
"defaults": {
|
||||
"apply_defaults_to_fp_fields": false,
|
||||
"apply_defaults_to_fp_shapes": false,
|
||||
"apply_defaults_to_fp_text": false,
|
||||
"board_outline_line_width": 0.05,
|
||||
"copper_line_width": 0.2,
|
||||
"copper_text_italic": false,
|
||||
"copper_text_size_h": 1.5,
|
||||
"copper_text_size_v": 1.5,
|
||||
"copper_text_thickness": 0.3,
|
||||
"copper_text_upright": false,
|
||||
"courtyard_line_width": 0.05,
|
||||
"dimension_precision": 4,
|
||||
"dimension_units": 3,
|
||||
"dimensions": {
|
||||
"arrow_length": 1270000,
|
||||
"extension_offset": 500000,
|
||||
"keep_text_aligned": true,
|
||||
"suppress_zeroes": true,
|
||||
"text_position": 0,
|
||||
"units_format": 0
|
||||
},
|
||||
"fab_line_width": 0.1,
|
||||
"fab_text_italic": false,
|
||||
"fab_text_size_h": 1.0,
|
||||
"fab_text_size_v": 1.0,
|
||||
"fab_text_thickness": 0.15,
|
||||
"fab_text_upright": false,
|
||||
"other_line_width": 0.1,
|
||||
"other_text_italic": false,
|
||||
"other_text_size_h": 1.0,
|
||||
"other_text_size_v": 1.0,
|
||||
"other_text_thickness": 0.15,
|
||||
"other_text_upright": false,
|
||||
"pads": {
|
||||
"drill": 0.3,
|
||||
"height": 0.6,
|
||||
"width": 0.6
|
||||
},
|
||||
"silk_line_width": 0.1,
|
||||
"silk_text_italic": false,
|
||||
"silk_text_size_h": 1.0,
|
||||
"silk_text_size_v": 1.0,
|
||||
"silk_text_thickness": 0.1,
|
||||
"silk_text_upright": false,
|
||||
"zones": {
|
||||
"min_clearance": 0.15
|
||||
}
|
||||
},
|
||||
"diff_pair_dimensions": [
|
||||
{
|
||||
"gap": 0.0,
|
||||
"via_gap": 0.0,
|
||||
"width": 0.0
|
||||
}
|
||||
],
|
||||
"drc_exclusions": [],
|
||||
"meta": {
|
||||
"version": 2
|
||||
},
|
||||
"rule_severities": {
|
||||
"annular_width": "error",
|
||||
"clearance": "error",
|
||||
"connection_width": "warning",
|
||||
"copper_edge_clearance": "error",
|
||||
"copper_sliver": "warning",
|
||||
"courtyards_overlap": "error",
|
||||
"creepage": "error",
|
||||
"diff_pair_gap_out_of_range": "error",
|
||||
"diff_pair_uncoupled_length_too_long": "error",
|
||||
"drill_out_of_range": "error",
|
||||
"duplicate_footprints": "warning",
|
||||
"extra_footprint": "warning",
|
||||
"footprint": "error",
|
||||
"footprint_filters_mismatch": "ignore",
|
||||
"footprint_symbol_mismatch": "warning",
|
||||
"footprint_type_mismatch": "ignore",
|
||||
"hole_clearance": "error",
|
||||
"hole_to_hole": "warning",
|
||||
"holes_co_located": "warning",
|
||||
"invalid_outline": "error",
|
||||
"isolated_copper": "warning",
|
||||
"item_on_disabled_layer": "error",
|
||||
"items_not_allowed": "error",
|
||||
"length_out_of_range": "error",
|
||||
"lib_footprint_issues": "warning",
|
||||
"lib_footprint_mismatch": "warning",
|
||||
"malformed_courtyard": "error",
|
||||
"microvia_drill_out_of_range": "error",
|
||||
"mirrored_text_on_front_layer": "warning",
|
||||
"missing_courtyard": "ignore",
|
||||
"missing_footprint": "warning",
|
||||
"net_conflict": "warning",
|
||||
"nonmirrored_text_on_back_layer": "warning",
|
||||
"npth_inside_courtyard": "ignore",
|
||||
"padstack": "warning",
|
||||
"pth_inside_courtyard": "ignore",
|
||||
"shorting_items": "error",
|
||||
"silk_edge_clearance": "warning",
|
||||
"silk_over_copper": "warning",
|
||||
"silk_overlap": "warning",
|
||||
"skew_out_of_range": "error",
|
||||
"solder_mask_bridge": "error",
|
||||
"starved_thermal": "error",
|
||||
"text_height": "warning",
|
||||
"text_on_edge_cuts": "error",
|
||||
"text_thickness": "warning",
|
||||
"through_hole_pad_without_hole": "error",
|
||||
"too_many_vias": "error",
|
||||
"track_angle": "error",
|
||||
"track_dangling": "warning",
|
||||
"track_segment_length": "error",
|
||||
"track_width": "error",
|
||||
"tracks_crossing": "error",
|
||||
"unconnected_items": "error",
|
||||
"unresolved_variable": "error",
|
||||
"via_dangling": "warning",
|
||||
"zones_intersect": "error"
|
||||
},
|
||||
"rules": {
|
||||
"max_error": 0.005,
|
||||
"min_clearance": 0.0,
|
||||
"min_connection": 0.0,
|
||||
"min_copper_edge_clearance": 0.5,
|
||||
"min_groove_width": 0.0,
|
||||
"min_hole_clearance": 0.25,
|
||||
"min_hole_to_hole": 0.25,
|
||||
"min_microvia_diameter": 0.2,
|
||||
"min_microvia_drill": 0.1,
|
||||
"min_resolved_spokes": 2,
|
||||
"min_silk_clearance": 0.0,
|
||||
"min_text_height": 0.8,
|
||||
"min_text_thickness": 0.08,
|
||||
"min_through_hole_diameter": 0.3,
|
||||
"min_track_width": 0.0,
|
||||
"min_via_annular_width": 0.1,
|
||||
"min_via_diameter": 0.5,
|
||||
"solder_mask_to_copper_clearance": 0.0,
|
||||
"use_height_for_length_calcs": true
|
||||
},
|
||||
"teardrop_options": [
|
||||
{
|
||||
"td_onpthpad": true,
|
||||
"td_onroundshapesonly": false,
|
||||
"td_onsmdpad": true,
|
||||
"td_ontrackend": false,
|
||||
"td_onvia": true
|
||||
}
|
||||
],
|
||||
"teardrop_parameters": [
|
||||
{
|
||||
"td_allow_use_two_tracks": true,
|
||||
"td_curve_segcount": 0,
|
||||
"td_height_ratio": 1.0,
|
||||
"td_length_ratio": 0.5,
|
||||
"td_maxheight": 2.0,
|
||||
"td_maxlen": 1.0,
|
||||
"td_on_pad_in_zone": false,
|
||||
"td_target_name": "td_round_shape",
|
||||
"td_width_to_size_filter_ratio": 0.9
|
||||
},
|
||||
{
|
||||
"td_allow_use_two_tracks": true,
|
||||
"td_curve_segcount": 0,
|
||||
"td_height_ratio": 1.0,
|
||||
"td_length_ratio": 0.5,
|
||||
"td_maxheight": 2.0,
|
||||
"td_maxlen": 1.0,
|
||||
"td_on_pad_in_zone": false,
|
||||
"td_target_name": "td_rect_shape",
|
||||
"td_width_to_size_filter_ratio": 0.9
|
||||
},
|
||||
{
|
||||
"td_allow_use_two_tracks": true,
|
||||
"td_curve_segcount": 0,
|
||||
"td_height_ratio": 1.0,
|
||||
"td_length_ratio": 0.5,
|
||||
"td_maxheight": 2.0,
|
||||
"td_maxlen": 1.0,
|
||||
"td_on_pad_in_zone": false,
|
||||
"td_target_name": "td_track_end",
|
||||
"td_width_to_size_filter_ratio": 0.9
|
||||
}
|
||||
],
|
||||
"track_widths": [
|
||||
0.0
|
||||
],
|
||||
"tuning_pattern_settings": {
|
||||
"diff_pair_defaults": {
|
||||
"corner_radius_percentage": 80,
|
||||
"corner_style": 1,
|
||||
"max_amplitude": 1.0,
|
||||
"min_amplitude": 0.2,
|
||||
"single_sided": false,
|
||||
"spacing": 1.0
|
||||
},
|
||||
"diff_pair_skew_defaults": {
|
||||
"corner_radius_percentage": 80,
|
||||
"corner_style": 1,
|
||||
"max_amplitude": 1.0,
|
||||
"min_amplitude": 0.2,
|
||||
"single_sided": false,
|
||||
"spacing": 0.6
|
||||
},
|
||||
"single_track_defaults": {
|
||||
"corner_radius_percentage": 80,
|
||||
"corner_style": 1,
|
||||
"max_amplitude": 1.0,
|
||||
"min_amplitude": 0.2,
|
||||
"single_sided": false,
|
||||
"spacing": 0.6
|
||||
}
|
||||
},
|
||||
"via_dimensions": [
|
||||
{
|
||||
"diameter": 0.0,
|
||||
"drill": 0.0
|
||||
}
|
||||
],
|
||||
"zones_allow_external_fillets": false
|
||||
},
|
||||
"ipc2581": {
|
||||
"dist": "",
|
||||
"distpn": "",
|
||||
"internal_id": "",
|
||||
"mfg": "",
|
||||
"mpn": ""
|
||||
},
|
||||
"layer_pairs": [],
|
||||
"layer_presets": [],
|
||||
"viewports": []
|
||||
},
|
||||
"boards": [],
|
||||
"cvpcb": {
|
||||
"equivalence_files": []
|
||||
},
|
||||
"erc": {
|
||||
"erc_exclusions": [],
|
||||
"meta": {
|
||||
"version": 0
|
||||
},
|
||||
"pin_map": [
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
2,
|
||||
1,
|
||||
1,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
2
|
||||
],
|
||||
[
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
2,
|
||||
1,
|
||||
2,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
0,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
2,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
0,
|
||||
2
|
||||
],
|
||||
[
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2
|
||||
]
|
||||
],
|
||||
"rule_severities": {
|
||||
"bus_definition_conflict": "error",
|
||||
"bus_entry_needed": "error",
|
||||
"bus_to_bus_conflict": "error",
|
||||
"bus_to_net_conflict": "error",
|
||||
"different_unit_footprint": "error",
|
||||
"different_unit_net": "error",
|
||||
"duplicate_reference": "error",
|
||||
"duplicate_sheet_names": "error",
|
||||
"endpoint_off_grid": "warning",
|
||||
"extra_units": "error",
|
||||
"footprint_filter": "ignore",
|
||||
"footprint_link_issues": "warning",
|
||||
"four_way_junction": "ignore",
|
||||
"global_label_dangling": "warning",
|
||||
"hier_label_mismatch": "error",
|
||||
"label_dangling": "error",
|
||||
"label_multiple_wires": "warning",
|
||||
"lib_symbol_issues": "warning",
|
||||
"lib_symbol_mismatch": "warning",
|
||||
"missing_bidi_pin": "warning",
|
||||
"missing_input_pin": "warning",
|
||||
"missing_power_pin": "error",
|
||||
"missing_unit": "warning",
|
||||
"multiple_net_names": "warning",
|
||||
"net_not_bus_member": "warning",
|
||||
"no_connect_connected": "warning",
|
||||
"no_connect_dangling": "warning",
|
||||
"pin_not_connected": "error",
|
||||
"pin_not_driven": "error",
|
||||
"pin_to_pin": "warning",
|
||||
"power_pin_not_driven": "error",
|
||||
"same_local_global_label": "warning",
|
||||
"similar_label_and_power": "warning",
|
||||
"similar_labels": "warning",
|
||||
"similar_power": "warning",
|
||||
"simulation_model_issue": "ignore",
|
||||
"single_global_label": "ignore",
|
||||
"unannotated": "error",
|
||||
"unconnected_wire_endpoint": "warning",
|
||||
"undefined_netclass": "error",
|
||||
"unit_value_mismatch": "error",
|
||||
"unresolved_variable": "error",
|
||||
"wire_dangling": "error"
|
||||
}
|
||||
},
|
||||
"libraries": {
|
||||
"pinned_footprint_libs": [],
|
||||
"pinned_symbol_libs": []
|
||||
},
|
||||
"meta": {
|
||||
"filename": "board.kicad_pro",
|
||||
"version": 3
|
||||
},
|
||||
"net_settings": {
|
||||
"classes": [
|
||||
{
|
||||
"bus_width": 12,
|
||||
"clearance": 0.2,
|
||||
"diff_pair_gap": 0.25,
|
||||
"diff_pair_via_gap": 0.25,
|
||||
"diff_pair_width": 0.2,
|
||||
"line_style": 0,
|
||||
"microvia_diameter": 0.3,
|
||||
"microvia_drill": 0.1,
|
||||
"name": "Default",
|
||||
"pcb_color": "rgba(0, 0, 0, 0.000)",
|
||||
"priority": 2147483647,
|
||||
"schematic_color": "rgba(0, 0, 0, 0.000)",
|
||||
"track_width": 0.2,
|
||||
"via_diameter": 0.6,
|
||||
"via_drill": 0.3,
|
||||
"wire_width": 6
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"version": 4
|
||||
},
|
||||
"net_colors": null,
|
||||
"netclass_assignments": null,
|
||||
"netclass_patterns": []
|
||||
},
|
||||
"pcbnew": {
|
||||
"last_paths": {
|
||||
"gencad": "",
|
||||
"idf": "",
|
||||
"netlist": "",
|
||||
"plot": "/tmp/",
|
||||
"pos_files": "",
|
||||
"specctra_dsn": "",
|
||||
"step": "board.stl",
|
||||
"svg": "",
|
||||
"vrml": ""
|
||||
},
|
||||
"page_layout_descr_file": ""
|
||||
},
|
||||
"schematic": {
|
||||
"annotate_start_num": 0,
|
||||
"bom_export_filename": "${PROJECTNAME}.csv",
|
||||
"bom_fmt_presets": [],
|
||||
"bom_fmt_settings": {
|
||||
"field_delimiter": ",",
|
||||
"keep_line_breaks": false,
|
||||
"keep_tabs": false,
|
||||
"name": "CSV",
|
||||
"ref_delimiter": ",",
|
||||
"ref_range_delimiter": "",
|
||||
"string_delimiter": "\""
|
||||
},
|
||||
"bom_presets": [],
|
||||
"bom_settings": {
|
||||
"exclude_dnp": false,
|
||||
"fields_ordered": [
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "Reference",
|
||||
"name": "Reference",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "Qty",
|
||||
"name": "${QUANTITY}",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": true,
|
||||
"label": "Value",
|
||||
"name": "Value",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": true,
|
||||
"label": "DNP",
|
||||
"name": "${DNP}",
|
||||
"show": false
|
||||
},
|
||||
{
|
||||
"group_by": true,
|
||||
"label": "Exclude from BOM",
|
||||
"name": "${EXCLUDE_FROM_BOM}",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": true,
|
||||
"label": "Exclude from Board",
|
||||
"name": "${EXCLUDE_FROM_BOARD}",
|
||||
"show": false
|
||||
},
|
||||
{
|
||||
"group_by": true,
|
||||
"label": "Footprint",
|
||||
"name": "Footprint",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "Datasheet",
|
||||
"name": "Datasheet",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "Arrow Price/Stock",
|
||||
"name": "Arrow Price/Stock",
|
||||
"show": false
|
||||
},
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "Height",
|
||||
"name": "Height",
|
||||
"show": false
|
||||
},
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "Manufacturer_Name",
|
||||
"name": "Manufacturer_Name",
|
||||
"show": false
|
||||
},
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "Manufacturer_Part_Number",
|
||||
"name": "Manufacturer_Part_Number",
|
||||
"show": false
|
||||
},
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "Mouser Part Number",
|
||||
"name": "Mouser Part Number",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "Mouser Price/Stock",
|
||||
"name": "Mouser Price/Stock",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "Arrow Part Number",
|
||||
"name": "Arrow Part Number",
|
||||
"show": false
|
||||
},
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "Description",
|
||||
"name": "Description",
|
||||
"show": false
|
||||
},
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "#",
|
||||
"name": "${ITEM_NUMBER}",
|
||||
"show": false
|
||||
}
|
||||
],
|
||||
"filter_string": "",
|
||||
"group_symbols": true,
|
||||
"include_excluded_from_bom": true,
|
||||
"name": "",
|
||||
"sort_asc": true,
|
||||
"sort_field": "Reference"
|
||||
},
|
||||
"connection_grid_size": 50.0,
|
||||
"drawing": {
|
||||
"dashed_lines_dash_length_ratio": 12.0,
|
||||
"dashed_lines_gap_length_ratio": 3.0,
|
||||
"default_line_thickness": 6.0,
|
||||
"default_text_size": 50.0,
|
||||
"field_names": [],
|
||||
"intersheets_ref_own_page": false,
|
||||
"intersheets_ref_prefix": "",
|
||||
"intersheets_ref_short": false,
|
||||
"intersheets_ref_show": false,
|
||||
"intersheets_ref_suffix": "",
|
||||
"junction_size_choice": 3,
|
||||
"label_size_ratio": 0.375,
|
||||
"operating_point_overlay_i_precision": 3,
|
||||
"operating_point_overlay_i_range": "~A",
|
||||
"operating_point_overlay_v_precision": 3,
|
||||
"operating_point_overlay_v_range": "~V",
|
||||
"overbar_offset_ratio": 1.23,
|
||||
"pin_symbol_size": 25.0,
|
||||
"text_offset_ratio": 0.15
|
||||
},
|
||||
"legacy_lib_dir": "",
|
||||
"legacy_lib_list": [],
|
||||
"meta": {
|
||||
"version": 1
|
||||
},
|
||||
"net_format_name": "",
|
||||
"ngspice": {
|
||||
"fix_include_paths": true,
|
||||
"meta": {
|
||||
"version": 0
|
||||
},
|
||||
"model_mode": 4,
|
||||
"workbook_filename": ""
|
||||
},
|
||||
"page_layout_descr_file": "",
|
||||
"plot_directory": "",
|
||||
"space_save_all_events": true,
|
||||
"spice_current_sheet_as_root": false,
|
||||
"spice_external_command": "spice \"%I\"",
|
||||
"spice_model_current_sheet_as_root": true,
|
||||
"spice_save_all_currents": false,
|
||||
"spice_save_all_dissipations": false,
|
||||
"spice_save_all_voltages": false,
|
||||
"subpart_first_id": 65,
|
||||
"subpart_id_separator": 0
|
||||
},
|
||||
"sheets": [
|
||||
[
|
||||
"51d2b720-4c26-4e02-a385-8df43b74d31d",
|
||||
"Root"
|
||||
]
|
||||
],
|
||||
"text_variables": {}
|
||||
}
|
||||
+12093
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,720 @@
|
||||
(kicad_symbol_lib
|
||||
(version 20241209)
|
||||
(generator "kicad_symbol_editor")
|
||||
(generator_version "9.0")
|
||||
(symbol "HUSB238"
|
||||
(exclude_from_sim no)
|
||||
(in_bom yes)
|
||||
(on_board yes)
|
||||
(property "Reference" "U"
|
||||
(at 2.54 -6.35 0)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Value" ""
|
||||
(at 0 0 0)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Footprint" ""
|
||||
(at 0 0 0)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
(hide yes)
|
||||
)
|
||||
)
|
||||
(property "Datasheet" ""
|
||||
(at 0 0 0)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
(hide yes)
|
||||
)
|
||||
)
|
||||
(property "Description" ""
|
||||
(at 0 0 0)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
(hide yes)
|
||||
)
|
||||
)
|
||||
(symbol "HUSB238_0_1"
|
||||
(rectangle
|
||||
(start -5.08 2.54)
|
||||
(end 10.16 -15.24)
|
||||
(stroke
|
||||
(width 0)
|
||||
(type default)
|
||||
)
|
||||
(fill
|
||||
(type none)
|
||||
)
|
||||
)
|
||||
)
|
||||
(symbol "HUSB238_1_1"
|
||||
(pin input line
|
||||
(at -7.62 0 0)
|
||||
(length 2.54)
|
||||
(name "VIN"
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(number ""
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(pin input line
|
||||
(at -7.62 -5.08 0)
|
||||
(length 2.54)
|
||||
(name "D+"
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(number ""
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(pin input line
|
||||
(at -7.62 -7.62 0)
|
||||
(length 2.54)
|
||||
(name "D-"
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(number ""
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(pin input line
|
||||
(at -7.62 -11.43 0)
|
||||
(length 2.54)
|
||||
(name "CC1"
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(number ""
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(pin input line
|
||||
(at -7.62 -13.97 0)
|
||||
(length 2.54)
|
||||
(name "CC2"
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(number ""
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(pin input line
|
||||
(at 2.54 -17.78 90)
|
||||
(length 2.54)
|
||||
(name "GND"
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(number ""
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(pin input line
|
||||
(at 12.7 0 180)
|
||||
(length 2.54)
|
||||
(name "GATE"
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(number ""
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(pin input line
|
||||
(at 12.7 -5.08 180)
|
||||
(length 2.54)
|
||||
(name "SCL"
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(number ""
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(pin input line
|
||||
(at 12.7 -7.62 180)
|
||||
(length 2.54)
|
||||
(name "SDA"
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(number ""
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(pin input line
|
||||
(at 12.7 -11.43 180)
|
||||
(length 2.54)
|
||||
(name "VSET"
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(number ""
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(pin input line
|
||||
(at 12.7 -13.97 180)
|
||||
(length 2.54)
|
||||
(name "ISET"
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(number ""
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(embedded_fonts no)
|
||||
)
|
||||
(symbol "LED_STRIP"
|
||||
(exclude_from_sim no)
|
||||
(in_bom yes)
|
||||
(on_board yes)
|
||||
(property "Reference" "SOCKET"
|
||||
(at -10.414 -10.414 0)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
(hide yes)
|
||||
)
|
||||
)
|
||||
(property "Value" ""
|
||||
(at 0 0 0)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Footprint" ""
|
||||
(at 0 0 0)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
(hide yes)
|
||||
)
|
||||
)
|
||||
(property "Datasheet" ""
|
||||
(at 0 0 0)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
(hide yes)
|
||||
)
|
||||
)
|
||||
(property "Description" ""
|
||||
(at 0 0 0)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
(hide yes)
|
||||
)
|
||||
)
|
||||
(symbol "LED_STRIP_0_1"
|
||||
(rectangle
|
||||
(start -1.27 -1.27)
|
||||
(end -13.97 -8.89)
|
||||
(stroke
|
||||
(width 0)
|
||||
(type default)
|
||||
)
|
||||
(fill
|
||||
(type none)
|
||||
)
|
||||
)
|
||||
)
|
||||
(symbol "LED_STRIP_1_1"
|
||||
(pin power_in line
|
||||
(at 0 -2.54 180)
|
||||
(length 2.54)
|
||||
(name "LED_5V"
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(number "1"
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(pin input line
|
||||
(at 0 -5.08 180)
|
||||
(length 2.54)
|
||||
(name "LED_DATA"
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(number "2"
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(pin passive line
|
||||
(at 0 -7.62 180)
|
||||
(length 2.54)
|
||||
(name "LED_GND"
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(number "3"
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(embedded_fonts no)
|
||||
)
|
||||
(symbol "PA1010D"
|
||||
(exclude_from_sim no)
|
||||
(in_bom yes)
|
||||
(on_board yes)
|
||||
(property "Reference" "U"
|
||||
(at 10.16 -3.048 0)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Value" ""
|
||||
(at 0 0 0)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Footprint" ""
|
||||
(at 0 0 0)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
(hide yes)
|
||||
)
|
||||
)
|
||||
(property "Datasheet" ""
|
||||
(at 0 0 0)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
(hide yes)
|
||||
)
|
||||
)
|
||||
(property "Description" ""
|
||||
(at 0 0 0)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
(hide yes)
|
||||
)
|
||||
)
|
||||
(symbol "PA1010D_0_1"
|
||||
(rectangle
|
||||
(start -1.27 8.89)
|
||||
(end 21.59 -10.16)
|
||||
(stroke
|
||||
(width 0)
|
||||
(type default)
|
||||
)
|
||||
(fill
|
||||
(type none)
|
||||
)
|
||||
)
|
||||
)
|
||||
(symbol "PA1010D_1_1"
|
||||
(text "PA1010D"
|
||||
(at 10.16 0 0)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(pin bidirectional line
|
||||
(at -3.81 6.35 0)
|
||||
(length 2.54)
|
||||
(name "I2C_SDA"
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(number "1"
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(pin input line
|
||||
(at -3.81 3.81 0)
|
||||
(length 2.54)
|
||||
(name "I2C_SCK"
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(number "2"
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(pin output line
|
||||
(at -3.81 -2.54 0)
|
||||
(length 2.54)
|
||||
(name "1PPS"
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(number "3"
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(pin output line
|
||||
(at -3.81 -5.08 0)
|
||||
(length 2.54)
|
||||
(name "TX"
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(number "4"
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(pin input line
|
||||
(at -3.81 -7.62 0)
|
||||
(length 2.54)
|
||||
(name "RX"
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(number "5"
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(pin power_in line
|
||||
(at 24.13 6.35 180)
|
||||
(length 2.54)
|
||||
(name "VCC"
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(number "10"
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(pin power_in line
|
||||
(at 24.13 3.81 180)
|
||||
(length 2.54)
|
||||
(name "VBACKUP"
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(number "9"
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(pin input line
|
||||
(at 24.13 -2.54 180)
|
||||
(length 2.54)
|
||||
(name "WAKE-UP"
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(number "8"
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(pin passive line
|
||||
(at 24.13 -5.08 180)
|
||||
(length 2.54)
|
||||
(name "GND"
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(number "7"
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(pin input line
|
||||
(at 24.13 -7.62 180)
|
||||
(length 2.54)
|
||||
(name "NRESET"
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(number "6"
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(embedded_fonts no)
|
||||
)
|
||||
(symbol "W3213"
|
||||
(exclude_from_sim no)
|
||||
(in_bom yes)
|
||||
(on_board yes)
|
||||
(property "Reference" "U"
|
||||
(at 3.048 0.762 0)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Value" ""
|
||||
(at 0 0 0)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Footprint" ""
|
||||
(at 0 0 0)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
(hide yes)
|
||||
)
|
||||
)
|
||||
(property "Datasheet" ""
|
||||
(at 0 0 0)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
(hide yes)
|
||||
)
|
||||
)
|
||||
(property "Description" ""
|
||||
(at 0 0 0)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
(hide yes)
|
||||
)
|
||||
)
|
||||
(symbol "W3213_0_1"
|
||||
(rectangle
|
||||
(start 0 0)
|
||||
(end 6.35 -2.54)
|
||||
(stroke
|
||||
(width 0)
|
||||
(type default)
|
||||
)
|
||||
(fill
|
||||
(type none)
|
||||
)
|
||||
)
|
||||
)
|
||||
(symbol "W3213_1_1"
|
||||
(pin bidirectional line
|
||||
(at -2.54 -1.27 0)
|
||||
(length 2.54)
|
||||
(name "FEED"
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
(number "1"
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(embedded_fonts no)
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,108 @@
|
||||
(footprint "3_PIN_WIRE"
|
||||
(version 20241229)
|
||||
(generator "pcbnew")
|
||||
(generator_version "9.0")
|
||||
(layer "F.Cu")
|
||||
(property "Reference" "REF**"
|
||||
(at 2.5 -4 0)
|
||||
(unlocked yes)
|
||||
(layer "F.SilkS")
|
||||
(uuid "ed9aed21-178b-495d-b9e1-929f192a36e4")
|
||||
(effects
|
||||
(font
|
||||
(size 1 1)
|
||||
(thickness 0.1)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Value" "3_PIN_WIRE"
|
||||
(at 2.5 -2.5 0)
|
||||
(unlocked yes)
|
||||
(layer "F.Fab")
|
||||
(uuid "a81b8f00-4fb7-411f-ad2b-943cd5e16966")
|
||||
(effects
|
||||
(font
|
||||
(size 1 1)
|
||||
(thickness 0.15)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Datasheet" ""
|
||||
(at 0 0 0)
|
||||
(unlocked yes)
|
||||
(layer "F.Fab")
|
||||
(hide yes)
|
||||
(uuid "874c5579-03b1-4f97-8e19-7e491326f2e9")
|
||||
(effects
|
||||
(font
|
||||
(size 1 1)
|
||||
(thickness 0.15)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Description" ""
|
||||
(at 0 0 0)
|
||||
(unlocked yes)
|
||||
(layer "F.Fab")
|
||||
(hide yes)
|
||||
(uuid "ac906119-cf33-42dd-9c1a-5d2fb67ab974")
|
||||
(effects
|
||||
(font
|
||||
(size 1 1)
|
||||
(thickness 0.15)
|
||||
)
|
||||
)
|
||||
)
|
||||
(attr smd)
|
||||
(fp_rect
|
||||
(start -1.5 -1.5)
|
||||
(end 6.5 1.5)
|
||||
(stroke
|
||||
(width 0.1)
|
||||
(type solid)
|
||||
)
|
||||
(fill no)
|
||||
(layer "F.SilkS")
|
||||
(uuid "3c534895-8b86-4446-afd7-676a7a56e959")
|
||||
)
|
||||
(fp_text user "${REFERENCE}"
|
||||
(at 2.5 3 0)
|
||||
(unlocked yes)
|
||||
(layer "F.Fab")
|
||||
(uuid "0f18bd78-61ca-451a-9eeb-2263cb5f43f2")
|
||||
(effects
|
||||
(font
|
||||
(size 1 1)
|
||||
(thickness 0.15)
|
||||
)
|
||||
)
|
||||
)
|
||||
(pad "1" thru_hole circle
|
||||
(at 0 0)
|
||||
(size 1.75 1.75)
|
||||
(drill 1)
|
||||
(layers "*.Cu" "*.Mask")
|
||||
(remove_unused_layers no)
|
||||
(solder_mask_margin 0)
|
||||
(uuid "ebc9015d-9cc2-4111-aef3-b60d76b89373")
|
||||
)
|
||||
(pad "2" thru_hole circle
|
||||
(at 2.5 0)
|
||||
(size 1.75 1.75)
|
||||
(drill 1)
|
||||
(layers "*.Cu" "*.Mask")
|
||||
(remove_unused_layers no)
|
||||
(solder_mask_margin 0)
|
||||
(uuid "173044b5-a2c0-4c4f-bb3f-f9fd466a356c")
|
||||
)
|
||||
(pad "3" thru_hole circle
|
||||
(at 5 0)
|
||||
(size 1.75 1.75)
|
||||
(drill 1)
|
||||
(layers "*.Cu" "*.Mask")
|
||||
(remove_unused_layers no)
|
||||
(solder_mask_margin 0)
|
||||
(uuid "f499520f-f554-4d82-83ea-fa2da2309ab8")
|
||||
)
|
||||
(embedded_fonts no)
|
||||
)
|
||||
@@ -0,0 +1,173 @@
|
||||
(footprint "PA1010D"
|
||||
(version 20241229)
|
||||
(generator "pcbnew")
|
||||
(generator_version "9.0")
|
||||
(layer "F.Cu")
|
||||
(property "Reference" "REF**"
|
||||
(at 5.5 -1 0)
|
||||
(unlocked yes)
|
||||
(layer "F.SilkS")
|
||||
(uuid "130f3771-d8e2-4352-9988-833559a1de18")
|
||||
(effects
|
||||
(font
|
||||
(size 1 1)
|
||||
(thickness 0.1)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Value" "PA1010D"
|
||||
(at 5 8.5 0)
|
||||
(unlocked yes)
|
||||
(layer "F.Fab")
|
||||
(uuid "41b10efd-006f-40fa-bbac-f8ded5a8fb94")
|
||||
(effects
|
||||
(font
|
||||
(size 1 1)
|
||||
(thickness 0.15)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Datasheet" ""
|
||||
(at 0 0 0)
|
||||
(unlocked yes)
|
||||
(layer "F.Fab")
|
||||
(hide yes)
|
||||
(uuid "b21a5dda-2f72-4e25-aec7-67c44f8948ee")
|
||||
(effects
|
||||
(font
|
||||
(size 1 1)
|
||||
(thickness 0.15)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Description" ""
|
||||
(at 0 0 0)
|
||||
(unlocked yes)
|
||||
(layer "F.Fab")
|
||||
(hide yes)
|
||||
(uuid "cc6957af-7fef-407d-948e-2e72ed2fa122")
|
||||
(effects
|
||||
(font
|
||||
(size 1 1)
|
||||
(thickness 0.15)
|
||||
)
|
||||
)
|
||||
)
|
||||
(attr smd)
|
||||
(fp_rect
|
||||
(start 0 0)
|
||||
(end 10 10)
|
||||
(stroke
|
||||
(width 0.1)
|
||||
(type default)
|
||||
)
|
||||
(fill no)
|
||||
(layer "F.SilkS")
|
||||
(uuid "d57ec54c-ee0b-47c8-a7f8-dc75c8a9b37a")
|
||||
)
|
||||
(fp_circle
|
||||
(center 1.5 1)
|
||||
(end 1.5 1.5)
|
||||
(stroke
|
||||
(width 0.1)
|
||||
(type solid)
|
||||
)
|
||||
(fill yes)
|
||||
(layer "F.SilkS")
|
||||
(uuid "edc44674-6cfd-4316-bad3-efc6a576b3ca")
|
||||
)
|
||||
(fp_circle
|
||||
(center 5 5)
|
||||
(end 3.5 5)
|
||||
(stroke
|
||||
(width 0.1)
|
||||
(type solid)
|
||||
)
|
||||
(fill yes)
|
||||
(layer "Edge.Cuts")
|
||||
(uuid "c34f8951-a402-414f-8c4a-f636b56f109c")
|
||||
)
|
||||
(fp_text user "${REFERENCE}"
|
||||
(at 5 2 0)
|
||||
(unlocked yes)
|
||||
(layer "F.Fab")
|
||||
(uuid "722d4a1d-7d11-4a0d-8121-59126244e839")
|
||||
(effects
|
||||
(font
|
||||
(size 1 1)
|
||||
(thickness 0.15)
|
||||
)
|
||||
)
|
||||
)
|
||||
(pad "1" smd roundrect
|
||||
(at 0 1.15 90)
|
||||
(size 0.8 1.5)
|
||||
(layers "F.Cu" "F.Mask" "F.Paste")
|
||||
(roundrect_rratio 0.15)
|
||||
(uuid "dd90346c-26e8-425a-9a22-9aa95ea9966c")
|
||||
)
|
||||
(pad "2" smd roundrect
|
||||
(at 0 2.75 90)
|
||||
(size 0.8 1.5)
|
||||
(layers "F.Cu" "F.Mask" "F.Paste")
|
||||
(roundrect_rratio 0.15)
|
||||
(uuid "3ed9afe3-fc3d-4c35-8d7a-cc9f3faca37b")
|
||||
)
|
||||
(pad "3" smd roundrect
|
||||
(at 0 5.65 90)
|
||||
(size 0.8 1.5)
|
||||
(layers "F.Cu" "F.Mask" "F.Paste")
|
||||
(roundrect_rratio 0.15)
|
||||
(uuid "baf6929d-7350-437e-b05a-f2d9bce91360")
|
||||
)
|
||||
(pad "4" smd roundrect
|
||||
(at 0 7.25 90)
|
||||
(size 0.8 1.5)
|
||||
(layers "F.Cu" "F.Mask" "F.Paste")
|
||||
(roundrect_rratio 0.15)
|
||||
(uuid "6ec8d1d5-e58f-4b90-9380-ff6ca42c02dd")
|
||||
)
|
||||
(pad "5" smd roundrect
|
||||
(at 0 8.85 90)
|
||||
(size 0.8 1.5)
|
||||
(layers "F.Cu" "F.Mask" "F.Paste")
|
||||
(roundrect_rratio 0.15)
|
||||
(uuid "6f769a41-6d17-4d4a-8d0a-1c53033bec90")
|
||||
)
|
||||
(pad "6" smd roundrect
|
||||
(at 10 8.85 90)
|
||||
(size 0.8 1.5)
|
||||
(layers "F.Cu" "F.Mask" "F.Paste")
|
||||
(roundrect_rratio 0.15)
|
||||
(uuid "8377c02f-ce7c-4edb-af17-6113f49103f3")
|
||||
)
|
||||
(pad "7" smd roundrect
|
||||
(at 10 7.25 90)
|
||||
(size 0.8 1.5)
|
||||
(layers "F.Cu" "F.Mask" "F.Paste")
|
||||
(roundrect_rratio 0.15)
|
||||
(uuid "1b5dcfd8-2351-4f21-91e5-80eb16f68d64")
|
||||
)
|
||||
(pad "8" smd roundrect
|
||||
(at 10 5.65 90)
|
||||
(size 0.8 1.5)
|
||||
(layers "F.Cu" "F.Mask" "F.Paste")
|
||||
(roundrect_rratio 0.15)
|
||||
(uuid "af994bfc-2691-4dd3-a5a6-1b7939aef85f")
|
||||
)
|
||||
(pad "9" smd roundrect
|
||||
(at 10 2.75 90)
|
||||
(size 0.8 1.5)
|
||||
(layers "F.Cu" "F.Mask" "F.Paste")
|
||||
(roundrect_rratio 0.15)
|
||||
(uuid "de6b9b35-3982-456a-8dad-8c11afd9d2a6")
|
||||
)
|
||||
(pad "10" smd roundrect
|
||||
(at 10 1.15 90)
|
||||
(size 0.8 1.5)
|
||||
(layers "F.Cu" "F.Mask" "F.Paste")
|
||||
(roundrect_rratio 0.15)
|
||||
(uuid "ed9d7d62-f653-4f37-8d08-2969d1579eea")
|
||||
)
|
||||
(embedded_fonts no)
|
||||
)
|
||||
@@ -0,0 +1,41 @@
|
||||
(module "SOIC127P600X175-8N" (layer F.Cu)
|
||||
(descr "SO 8L")
|
||||
(tags "Integrated Circuit")
|
||||
(attr smd)
|
||||
(fp_text reference IC** (at 0 0) (layer F.SilkS)
|
||||
(effects (font (size 1.27 1.27) (thickness 0.254)))
|
||||
)
|
||||
(fp_text user %R (at 0 0) (layer F.Fab)
|
||||
(effects (font (size 1.27 1.27) (thickness 0.254)))
|
||||
)
|
||||
(fp_text value "SOIC127P600X175-8N" (at 0 0) (layer F.SilkS) hide
|
||||
(effects (font (size 1.27 1.27) (thickness 0.254)))
|
||||
)
|
||||
(fp_line (start -3.725 -2.75) (end 3.725 -2.75) (layer F.CrtYd) (width 0.05))
|
||||
(fp_line (start 3.725 -2.75) (end 3.725 2.75) (layer F.CrtYd) (width 0.05))
|
||||
(fp_line (start 3.725 2.75) (end -3.725 2.75) (layer F.CrtYd) (width 0.05))
|
||||
(fp_line (start -3.725 2.75) (end -3.725 -2.75) (layer F.CrtYd) (width 0.05))
|
||||
(fp_line (start -1.95 -2.45) (end 1.95 -2.45) (layer F.Fab) (width 0.1))
|
||||
(fp_line (start 1.95 -2.45) (end 1.95 2.45) (layer F.Fab) (width 0.1))
|
||||
(fp_line (start 1.95 2.45) (end -1.95 2.45) (layer F.Fab) (width 0.1))
|
||||
(fp_line (start -1.95 2.45) (end -1.95 -2.45) (layer F.Fab) (width 0.1))
|
||||
(fp_line (start -1.95 -1.18) (end -0.68 -2.45) (layer F.Fab) (width 0.1))
|
||||
(fp_line (start -1.6 -2.45) (end 1.6 -2.45) (layer F.SilkS) (width 0.2))
|
||||
(fp_line (start 1.6 -2.45) (end 1.6 2.45) (layer F.SilkS) (width 0.2))
|
||||
(fp_line (start 1.6 2.45) (end -1.6 2.45) (layer F.SilkS) (width 0.2))
|
||||
(fp_line (start -1.6 2.45) (end -1.6 -2.45) (layer F.SilkS) (width 0.2))
|
||||
(fp_line (start -3.475 -2.605) (end -1.95 -2.605) (layer F.SilkS) (width 0.2))
|
||||
(pad 1 smd rect (at -2.712 -1.905 90) (size 0.7 1.525) (layers F.Cu F.Paste F.Mask))
|
||||
(pad 2 smd rect (at -2.712 -0.635 90) (size 0.7 1.525) (layers F.Cu F.Paste F.Mask))
|
||||
(pad 3 smd rect (at -2.712 0.635 90) (size 0.7 1.525) (layers F.Cu F.Paste F.Mask))
|
||||
(pad 4 smd rect (at -2.712 1.905 90) (size 0.7 1.525) (layers F.Cu F.Paste F.Mask))
|
||||
(pad 5 smd rect (at 2.712 1.905 90) (size 0.7 1.525) (layers F.Cu F.Paste F.Mask))
|
||||
(pad 6 smd rect (at 2.712 0.635 90) (size 0.7 1.525) (layers F.Cu F.Paste F.Mask))
|
||||
(pad 7 smd rect (at 2.712 -0.635 90) (size 0.7 1.525) (layers F.Cu F.Paste F.Mask))
|
||||
(pad 8 smd rect (at 2.712 -1.905 90) (size 0.7 1.525) (layers F.Cu F.Paste F.Mask))
|
||||
(model L6982N50DR.stp
|
||||
(at (xyz 0 0 0))
|
||||
(scale (xyz 1 1 1))
|
||||
(rotate (xyz 0 0 0))
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,138 @@
|
||||
(footprint "W3213"
|
||||
(version 20241229)
|
||||
(generator "pcbnew")
|
||||
(generator_version "9.0")
|
||||
(layer "F.Cu")
|
||||
(property "Reference" "REF**"
|
||||
(at 6.5 -10 0)
|
||||
(unlocked yes)
|
||||
(layer "F.SilkS")
|
||||
(uuid "f24b340a-fab5-4a19-89d5-142c860a7fc0")
|
||||
(effects
|
||||
(font
|
||||
(size 1 1)
|
||||
(thickness 0.1)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Value" "W3213"
|
||||
(at 6.5 -2.5 0)
|
||||
(unlocked yes)
|
||||
(layer "F.Fab")
|
||||
(uuid "04dd6c12-8251-4781-a9f5-1b11f920ff3a")
|
||||
(effects
|
||||
(font
|
||||
(size 1 1)
|
||||
(thickness 0.15)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Datasheet" ""
|
||||
(at 0 0 0)
|
||||
(unlocked yes)
|
||||
(layer "F.Fab")
|
||||
(hide yes)
|
||||
(uuid "b99134c4-fb24-4473-86ca-b34478a5cd2d")
|
||||
(effects
|
||||
(font
|
||||
(size 1 1)
|
||||
(thickness 0.15)
|
||||
)
|
||||
)
|
||||
)
|
||||
(property "Description" ""
|
||||
(at 0 0 0)
|
||||
(unlocked yes)
|
||||
(layer "F.Fab")
|
||||
(hide yes)
|
||||
(uuid "cd3e3cb1-5955-400e-9a61-613d452727a7")
|
||||
(effects
|
||||
(font
|
||||
(size 1 1)
|
||||
(thickness 0.15)
|
||||
)
|
||||
)
|
||||
)
|
||||
(attr smd)
|
||||
(fp_rect
|
||||
(start 0 -13)
|
||||
(end 13 0)
|
||||
(stroke
|
||||
(width 0.1)
|
||||
(type default)
|
||||
)
|
||||
(fill no)
|
||||
(layer "F.SilkS")
|
||||
(uuid "c78c6db4-7e41-4ecc-83d0-30df1c12f03d")
|
||||
)
|
||||
(fp_rect
|
||||
(start -1 -14)
|
||||
(end 14 1)
|
||||
(stroke
|
||||
(width 0.1)
|
||||
(type solid)
|
||||
)
|
||||
(fill no)
|
||||
(layer "F.CrtYd")
|
||||
(uuid "4539cb55-422e-48bf-a735-c6db2ea37043")
|
||||
)
|
||||
(fp_text user "${REFERENCE}"
|
||||
(at 6.5 -1 0)
|
||||
(unlocked yes)
|
||||
(layer "F.Fab")
|
||||
(uuid "4e027ed7-f943-4ac4-b64e-958cf24df4e4")
|
||||
(effects
|
||||
(font
|
||||
(size 1 1)
|
||||
(thickness 0.15)
|
||||
)
|
||||
)
|
||||
)
|
||||
(pad "1" thru_hole circle
|
||||
(at 6.5 -5.8)
|
||||
(size 3.6 3.6)
|
||||
(drill 2.5)
|
||||
(layers "*.Cu" "*.Mask")
|
||||
(remove_unused_layers no)
|
||||
(uuid "e16d3e96-37cd-42db-8a30-66cca71459e4")
|
||||
)
|
||||
(zone
|
||||
(net 0)
|
||||
(net_name "")
|
||||
(layers "F.Cu" "B.Cu" "In1.Cu" "In2.Cu" "In3.Cu" "In4.Cu" "In5.Cu" "In6.Cu"
|
||||
"In7.Cu" "In8.Cu" "In9.Cu" "In10.Cu" "In11.Cu" "In12.Cu" "In13.Cu" "In14.Cu"
|
||||
"In15.Cu" "In16.Cu" "In17.Cu" "In18.Cu" "In19.Cu" "In20.Cu" "In21.Cu"
|
||||
"In22.Cu" "In23.Cu" "In24.Cu" "In25.Cu" "In26.Cu" "In27.Cu" "In28.Cu"
|
||||
"In29.Cu" "In30.Cu"
|
||||
)
|
||||
(uuid "4534681b-5cec-434f-b51a-fe95ff081411")
|
||||
(name "KEEP-OUT")
|
||||
(hatch edge 0.5)
|
||||
(connect_pads
|
||||
(clearance 0)
|
||||
)
|
||||
(min_thickness 0.25)
|
||||
(filled_areas_thickness no)
|
||||
(keepout
|
||||
(tracks allowed)
|
||||
(vias not_allowed)
|
||||
(pads not_allowed)
|
||||
(copperpour not_allowed)
|
||||
(footprints not_allowed)
|
||||
)
|
||||
(placement
|
||||
(enabled no)
|
||||
(sheetname "")
|
||||
)
|
||||
(fill
|
||||
(thermal_gap 0.5)
|
||||
(thermal_bridge_width 0.5)
|
||||
)
|
||||
(polygon
|
||||
(pts
|
||||
(xy 0 0.141405) (xy 0 -12.858595) (xy 13 -12.858595) (xy 13 0.141405)
|
||||
)
|
||||
)
|
||||
)
|
||||
(embedded_fonts no)
|
||||
)
|
||||
@@ -0,0 +1,6 @@
|
||||
(fp_lib_table
|
||||
(version 7)
|
||||
(lib (name "chips")(type "KiCad")(uri "${KIPRJMOD}/chips.pretty")(options "")(descr ""))
|
||||
(lib (name "KiCad")(type "KiCad")(uri "${KIPRJMOD}/LIB_W3213/W3213/KiCad")(options "")(descr ""))
|
||||
(lib (name "LSM6DSV80XTR")(type "KiCad")(uri "${KIPRJMOD}/LIB_LSM6DSV80XTR/LSM6DSV80XTR/KiCad")(options "")(descr ""))
|
||||
)
|
||||
@@ -0,0 +1,7 @@
|
||||
(sym_lib_table
|
||||
(version 7)
|
||||
(lib (name "chips")(type "KiCad")(uri "${KIPRJMOD}/chips.kicad_sym")(options "")(descr ""))
|
||||
(lib (name "L6982N50DR")(type "KiCad")(uri "${KIPRJMOD}/LIB_L6982N50DR/L6982N50DR/KiCad/L6982N50DR.kicad_sym")(options "")(descr ""))
|
||||
(lib (name "W3213")(type "KiCad")(uri "${KIPRJMOD}/LIB_W3213/W3213/KiCad/W3213.kicad_sym")(options "")(descr ""))
|
||||
(lib (name "LSM6DSV80XTR")(type "KiCad")(uri "${KIPRJMOD}/LIB_LSM6DSV80XTR/LSM6DSV80XTR/KiCad/LSM6DSV80XTR.kicad_sym")(options "")(descr ""))
|
||||
)
|
||||
@@ -205,12 +205,13 @@ fn write_sim_data() {
|
||||
let segments = [(StreamType::IMU, motion_output), (StreamType::GPS, gps_output), (StreamType::Annotations, annotation_output)];
|
||||
|
||||
// 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
|
||||
for (stream_type, stream_path) in segments {
|
||||
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();
|
||||
fd.read_to_end(&mut buf).unwrap();
|
||||
unified_fd.write_all(buf.as_slice()).unwrap();
|
||||
|
||||
+97
-59
@@ -1,8 +1,7 @@
|
||||
use embassy_time::{Duration, Instant, Timer};
|
||||
use esp_rtos::CurrentThreadHandle;
|
||||
use figments::{liber8tion::interpolate::Fract8, surface::Surface};
|
||||
use figments_render::output::Brightness;
|
||||
use core::{fmt::Debug, mem::MaybeUninit, ops::{Deref, DerefMut}};
|
||||
use core::{fmt::Debug, ops::{Deref, DerefMut}};
|
||||
use log::*;
|
||||
|
||||
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> {
|
||||
from: T,
|
||||
to: T,
|
||||
@@ -62,6 +65,38 @@ struct Slot<'a, 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> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
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 {
|
||||
Self {
|
||||
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 {
|
||||
Self {
|
||||
from: Some(from),
|
||||
@@ -95,23 +179,18 @@ impl Animation<Fract8> {
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn duration(self, duration: Duration) -> Self {
|
||||
Self {
|
||||
duration,
|
||||
..self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const MIN_ANIMATION_RATE: Duration = Duration::from_millis(5);
|
||||
|
||||
impl Animation<Fract8> {
|
||||
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}");
|
||||
|
||||
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 from = if let Some(val) = self.from {
|
||||
val
|
||||
@@ -123,12 +202,14 @@ impl Animation<Fract8> {
|
||||
} else {
|
||||
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)
|
||||
} 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 {
|
||||
from,
|
||||
@@ -142,50 +223,7 @@ impl Animation<Fract8> {
|
||||
|
||||
trace!("animators={animators:?}");
|
||||
|
||||
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.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:?}");
|
||||
Self::execute(animators).await;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+176
-53
@@ -7,34 +7,29 @@
|
||||
)]
|
||||
|
||||
|
||||
use core::{num::{self, Wrapping}, ptr::addr_of_mut};
|
||||
|
||||
use alloc::{string::String, sync::Arc};
|
||||
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::{
|
||||
clock::CpuClock, system::{AppCoreGuard, CpuControl, Stack}, timer::{systimer::SystemTimer, timg::{TimerGroup, Wdt}}
|
||||
clock::CpuClock, timer::{systimer::SystemTimer, timg::{TimerGroup, Wdt}}
|
||||
};
|
||||
|
||||
use embassy_sync::{
|
||||
pubsub::PubSubChannel,
|
||||
blocking_mutex::raw::NoopRawMutex
|
||||
blocking_mutex::raw::NoopRawMutex, channel::DynamicReceiver, once_lock::OnceLock, pubsub::PubSubChannel, signal::Signal
|
||||
};
|
||||
use static_cell::ConstStaticCell;
|
||||
use esp_storage::FlashStorage;
|
||||
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_embassy::events::Measurement;
|
||||
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_bike::events::Measurement;
|
||||
use static_cell::StaticCell;
|
||||
use esp_backtrace as _;
|
||||
use esp_rtos::embassy::{Executor, InterruptExecutor};
|
||||
use esp_hal::interrupt::software::SoftwareInterruptControl;
|
||||
use esp_hal::spi::master::any::Degrade;
|
||||
|
||||
use renderbug_embassy::tasks::{
|
||||
motion::motion_task,
|
||||
ui::{Ui, ui_main}
|
||||
};
|
||||
use renderbug_bike::tasks::ui::{Ui, ui_main};
|
||||
use esp_hal::dma::DmaChannelConvert;
|
||||
|
||||
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, pubsub::DynSubscriber};
|
||||
use embassy_sync::channel::Channel;
|
||||
@@ -48,14 +43,31 @@ esp_bootloader_esp_idf::esp_app_desc!();
|
||||
#[cfg(feature="radio")]
|
||||
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]
|
||||
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();
|
||||
|
||||
let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
|
||||
let peripherals = esp_hal::init(config);
|
||||
|
||||
info!("Boot memory stats: {}", esp_alloc::HEAP.stats());
|
||||
|
||||
let sys_timer = SystemTimer::new(peripherals.SYSTIMER);
|
||||
esp_rtos::start(sys_timer.alarm0);
|
||||
info!("Embassy initialized!");
|
||||
@@ -63,6 +75,9 @@ async fn main(spawner: Spawner) {
|
||||
static MOTION_BUS: StaticCell<Channel<CriticalSectionRawMutex,Measurement,5> > = StaticCell::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");
|
||||
let mut surfaces = UiSurfacePool::default();
|
||||
let ui = Ui::new(&mut surfaces);
|
||||
@@ -83,12 +98,23 @@ async fn main(spawner: Spawner) {
|
||||
wdt.enable();
|
||||
|
||||
// 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
|
||||
Timer::after_ticks(1).await;
|
||||
let imu_interrupt = PinInterrupt::new(Input::new(peripherals.GPIO36.degrade(), InputConfig::default()), Event::RisingEdge);
|
||||
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 esp_hal::{i2c::master::{Config, I2c}, Async};
|
||||
@@ -101,16 +127,17 @@ async fn main(spawner: Spawner) {
|
||||
let scl = peripherals.GPIO3;
|
||||
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));
|
||||
#[cfg(feature="mpu")]
|
||||
spawner.must_spawn(renderbug_embassy::tasks::mpu::mpu_task(motion_bus.dyn_sender(), I2cDevice::new(i2c_bus)));
|
||||
#[cfg(feature="gps")]
|
||||
spawner.must_spawn(renderbug_embassy::tasks::gps::gps_task(motion_bus.dyn_sender(), I2cDevice::new(i2c_bus)));
|
||||
#[cfg(feature="mpu6050")]
|
||||
spawner.must_spawn(renderbug_bike::tasks::mpu::mpu_task(motion_bus.dyn_sender(), I2cDevice::new(i2c_bus), imu_interrupt));
|
||||
|
||||
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")]
|
||||
{
|
||||
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 i2c = I2c::new(
|
||||
@@ -118,25 +145,35 @@ async fn main(spawner: Spawner) {
|
||||
Config::default().with_frequency(Rate::from_khz(400))
|
||||
).unwrap().with_scl(peripherals.GPIO18).with_sda(peripherals.GPIO17).into_async();
|
||||
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")]
|
||||
{
|
||||
use esp_storage::FlashStorage;
|
||||
use renderbug_embassy::tasks::simulation::{SharedFlash, SimDataTable};
|
||||
let mut storage = SharedFlash::new(FlashStorage::new());
|
||||
let mut buf = [8; 1024];
|
||||
let partitions = esp_bootloader_esp_idf::partitions::read_partition_table(&mut storage, &mut buf).unwrap();
|
||||
use renderbug_bike::tasks::simulation::SimDataTable;
|
||||
for sim_data in SimDataTable::open(storage, partitions).expect("Could not find sim data!") {
|
||||
let srcid = sim_data.srcid();
|
||||
info!("Found simulation data for {srcid:?}");
|
||||
if spawner.spawn(renderbug_embassy::tasks::simulation::simulation_task(sim_data, motion_bus.dyn_sender())).is_err() {
|
||||
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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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")]
|
||||
let (wifi, network_device, ble) = {
|
||||
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 (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!");
|
||||
|
||||
(wifi, interfaces.sta, ble)
|
||||
@@ -160,13 +206,13 @@ async fn main(spawner: Spawner) {
|
||||
#[cfg(not(feature="demo"))]
|
||||
{
|
||||
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")]
|
||||
{
|
||||
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");
|
||||
@@ -188,16 +234,16 @@ async fn main(spawner: Spawner) {
|
||||
let seed = Rng::new().random() as i32;
|
||||
let (stack, runner) = embassy_net::new(network_device, config, RESOURCES.take(), seed as u64);
|
||||
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");
|
||||
//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");
|
||||
//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");
|
||||
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")]
|
||||
@@ -205,22 +251,31 @@ async fn main(spawner: Spawner) {
|
||||
info!("Launching core 2 watchdog");
|
||||
let timer1 = TimerGroup::new(peripherals.TIMG1);
|
||||
let mut ui_wdt = timer1.wdt;
|
||||
ui_wdt.set_timeout(esp_hal::timer::timg::MwdtStage::Stage0, esp_hal::time::Duration::from_secs(10));
|
||||
#[cfg(feature="dual-core")]
|
||||
ui_wdt.set_timeout(esp_hal::timer::timg::MwdtStage::Stage0, esp_hal::time::Duration::from_secs(60));
|
||||
ui_wdt.enable();
|
||||
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")]
|
||||
{
|
||||
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);
|
||||
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");
|
||||
static CORE2_EXEC: StaticCell<Executor> = StaticCell::new();
|
||||
let exec = CORE2_EXEC.init_with(|| { Executor::new() });
|
||||
@@ -231,7 +286,10 @@ async fn main(spawner: Spawner) {
|
||||
#[cfg(not(feature="dual-core"))]
|
||||
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]
|
||||
@@ -246,12 +304,77 @@ async fn wdt_task(mut wdt: Wdt<esp_hal::peripherals::TIMG1<'static>>) {
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
async fn print_telemetry(mut events: DynSubscriber<'static, Prediction>) {
|
||||
info!("telemetry ready");
|
||||
let mut num_events = Wrapping(0usize);
|
||||
async fn record_telemetry(firehose: DynamicReceiver<'static, Measurement>, mut storage: SimDataRecorder<SharedFlash<FlashStorage>>) {
|
||||
loop {
|
||||
let next = events.next_message_pure().await;
|
||||
trace!("idx={} predict={next:?}", num_events.0);
|
||||
num_events += 1;
|
||||
match firehose.receive().await {
|
||||
Measurement::IMU { accel, gyro } => {
|
||||
let reading = IMUReading {
|
||||
accel_x: accel.x as f64,
|
||||
accel_y: accel.y as f64,
|
||||
accel_z: accel.z as f64,
|
||||
gyro_x: gyro.x as f64,
|
||||
gyro_y: gyro.y as f64,
|
||||
gyro_z: gyro.z as f64
|
||||
};
|
||||
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}");
|
||||
},
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
}
|
||||
+37
-31
@@ -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
|
||||
|
||||
// State switches
|
||||
has_down: Breaker<bool>,
|
||||
has_forwards: Breaker<bool>,
|
||||
has_motion_frame: Breaker<bool>,
|
||||
motion_state: Breaker<MotionState>,
|
||||
acquiring_data: Breaker<bool>,
|
||||
// FIXME: pub
|
||||
@@ -43,10 +42,27 @@ pub struct BikeStates {
|
||||
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 {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("BikeStates")
|
||||
.field("has_down", &self.orientation.has_down())
|
||||
.field("has_orientation", &self.orientation.is_ready())
|
||||
.field("heading", &self.heading.heading())
|
||||
.field("motion_state", &self.motion_state)
|
||||
@@ -71,7 +87,7 @@ impl BikeStates {
|
||||
Some(coords) => {
|
||||
if self.last_fix != 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);
|
||||
@@ -99,17 +115,15 @@ impl BikeStates {
|
||||
let heading_rotation = Rotation3::from_axis_angle(&Vector3::z_axis(), self.heading.heading());
|
||||
|
||||
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);
|
||||
} else {
|
||||
// Otherwise, we are standing stationary and should insert accel=0 data into the model
|
||||
self.kf.update_zupt();
|
||||
}
|
||||
|
||||
self.has_down.set(true);
|
||||
|
||||
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;
|
||||
|
||||
if let Some(true) = self.acquiring_data.read_tripped() {
|
||||
predictions.publish(Prediction::SensorStatus(SensorSource::ForwardsReference, SensorState::AcquiringFix)).await;
|
||||
predictions.publish(Prediction::SensorStatus(SensorSource::GravityReference, SensorState::AcquiringFix)).await;
|
||||
predictions.publish(Prediction::SensorStatus(SensorSource::MotionFrame, SensorState::AcquiringFix)).await;
|
||||
predictions.publish(Prediction::SensorStatus(SensorSource::Location, SensorState::AcquiringFix)).await;
|
||||
}
|
||||
|
||||
if let Some(true) = self.has_down.read_tripped() {
|
||||
predictions.publish(Prediction::SensorStatus(SensorSource::GravityReference, SensorState::Online)).await
|
||||
}
|
||||
|
||||
if let Some(true) = self.has_forwards.read_tripped() {
|
||||
predictions.publish(Prediction::SensorStatus(SensorSource::ForwardsReference, SensorState::Online)).await
|
||||
if let Some(true) = self.has_motion_frame.read_tripped() {
|
||||
predictions.publish(Prediction::SensorStatus(SensorSource::MotionFrame, SensorState::Online)).await
|
||||
}
|
||||
|
||||
match self.has_gps_fix.read_tripped() {
|
||||
@@ -157,45 +166,43 @@ impl BikeStates {
|
||||
self.speedo.insert(current_prediction);
|
||||
// If the model has enough samples to report useful data, we can start analyzing the motion trends
|
||||
if self.speedo.is_filled() {
|
||||
let threshold = 1.0;
|
||||
// Calculate if the velocity is increasing, decreasing, or mostly the same
|
||||
let trend = self.speedo.data().windows(2).map(|n| {
|
||||
n[1] - n[0]
|
||||
}).sum::<f32>();
|
||||
// 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
|
||||
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() {
|
||||
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
|
||||
if mean > 0.5 {
|
||||
if self.sleep_timer.wake() {
|
||||
// We only want to wake up from sleep if our current velocity is something like a bump
|
||||
if average_speed > BUMP_SPEED_NOISE_GATE && self.sleep_timer.wake() {
|
||||
warn!("Waking from sleep into idle mode");
|
||||
predictions.publish(Prediction::SetPersonality(Personality::Waking)).await;
|
||||
predictions.publish(Prediction::SetPersonality(Personality::Parked)).await;
|
||||
}
|
||||
// Here, we additionally release the parking brake if we are currently parked and reading some kind of significant movement
|
||||
if self.parking_timer.wake() {
|
||||
// Here, we additionally release the parking brake if we are currently parked and reading substantial movement
|
||||
if average_speed > WAKEUP_SPEED_NOISE_GATE && self.parking_timer.wake() {
|
||||
warn!("Disengaging parking brake");
|
||||
predictions.publish(Prediction::SetPersonality(Personality::Active)).await;
|
||||
}
|
||||
}
|
||||
|
||||
// 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.steady_timer.wake();
|
||||
} else if trend <= -threshold {
|
||||
} else if trend <= -ACCELERATION_NOISE_GATE {
|
||||
self.steady_timer.wake();
|
||||
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
|
||||
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!
|
||||
self.motion_state.set(MotionState::Stationary);
|
||||
}
|
||||
@@ -232,8 +239,7 @@ impl Default for BikeStates {
|
||||
last_stamp: Instant::now(),
|
||||
speedo: Default::default(),
|
||||
heading: Default::default(),
|
||||
has_down: Default::default(),
|
||||
has_forwards: Default::default(),
|
||||
has_motion_frame: Default::default(),
|
||||
kf: Default::default(),
|
||||
steady_timer: IdleClock::new(Duration::from_secs(3)),
|
||||
last_fix: Default::default(),
|
||||
|
||||
@@ -66,7 +66,7 @@ impl OrientationEstimator {
|
||||
self.forward = Some(Unit::new_unchecked(-unit_forward.into_inner()));
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -78,8 +78,7 @@ pub enum SensorSource {
|
||||
Wifi,
|
||||
|
||||
// Fusion outputs
|
||||
GravityReference,
|
||||
ForwardsReference,
|
||||
MotionFrame,
|
||||
Location,
|
||||
Cloud,
|
||||
|
||||
@@ -97,7 +96,8 @@ impl From<StreamType> for SensorSource {
|
||||
match value {
|
||||
StreamType::Annotations => Self::Annotations,
|
||||
StreamType::GPS => Self::GPS,
|
||||
StreamType::IMU => Self::IMU
|
||||
StreamType::IMU => Self::IMU,
|
||||
StreamType::Bundle => unimplemented!()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
use core::cell::RefCell;
|
||||
|
||||
use alloc::sync::Arc;
|
||||
use critical_section::Mutex;
|
||||
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, signal::Signal};
|
||||
use esp_hal::gpio::Input;
|
||||
|
||||
pub struct InterruptDispatch<'a, const COUNT: usize> {
|
||||
interrupts: [PinInterrupt<'a>; COUNT]
|
||||
}
|
||||
|
||||
impl<'a, const COUNT: usize> InterruptDispatch<'a, COUNT> {
|
||||
pub fn new(interrupts: [PinInterrupt<'a>; COUNT]) -> Self {
|
||||
Self { interrupts }
|
||||
}
|
||||
|
||||
pub fn process_interrupts(&self) {
|
||||
for interrupt in &self.interrupts {
|
||||
interrupt.handle_interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PinInterrupt<'a> {
|
||||
pin: Arc<Mutex<RefCell<Input<'a>>>>,
|
||||
signal: Arc<Signal<CriticalSectionRawMutex, esp_hal::gpio::Level>>,
|
||||
event: esp_hal::gpio::Event
|
||||
}
|
||||
|
||||
impl<'a> PinInterrupt<'a> {
|
||||
pub fn new(pin: Input<'a>, event: esp_hal::gpio::Event) -> Self {
|
||||
Self {
|
||||
pin: Arc::new(Mutex::new(RefCell::new(pin))),
|
||||
signal: Arc::new(Signal::new()),
|
||||
event
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_interrupt(&self) {
|
||||
critical_section::with(|cs| {
|
||||
let locked = self.pin.borrow(cs);
|
||||
let mut pin = locked.borrow_mut();
|
||||
if pin.is_interrupt_set() {
|
||||
pin.clear_interrupt();
|
||||
self.signal.signal(pin.level());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn listen(&self) {
|
||||
critical_section::with(|cs| {
|
||||
let locked = self.pin.borrow(cs);
|
||||
let mut pin = locked.borrow_mut();
|
||||
pin.listen(self.event);
|
||||
});
|
||||
}
|
||||
|
||||
pub async fn wait_for_interrupt(&self) -> esp_hal::gpio::Level {
|
||||
self.signal.wait().await
|
||||
}
|
||||
}
|
||||
+29
-18
@@ -1,9 +1,8 @@
|
||||
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, signal::Signal, watch::{Receiver, Watch}};
|
||||
use figments::prelude::*;
|
||||
use core::{fmt::Debug, sync::atomic::{AtomicBool, AtomicU8}};
|
||||
use figments::{liber8tion::interpolate::Fract8, prelude::*};
|
||||
use core::{fmt::Debug, ops::Mul, sync::atomic::{AtomicBool, AtomicU8}};
|
||||
use alloc::sync::Arc;
|
||||
|
||||
//use super::{Output};
|
||||
use figments_render::{
|
||||
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 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> {
|
||||
Some(&self.controls)
|
||||
fn controls(&mut self) -> Option<&mut 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 {
|
||||
self.writer.controls().set_brightness(self.controls.brightness());
|
||||
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
|
||||
}
|
||||
|
||||
//type HardwarePixel = T::Color;
|
||||
type Error = T::Error;
|
||||
type Controls = DisplayControls;
|
||||
|
||||
fn controls(&self) -> Option<&Self::Controls> {
|
||||
Some(&self.controls)
|
||||
fn controls(&mut self) -> Option<&mut 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)]
|
||||
pub struct SegmentSpace {}
|
||||
impl CoordinateSpace for SegmentSpace {
|
||||
@@ -162,16 +159,21 @@ pub struct Uniforms {
|
||||
pub primary_color: Hsv
|
||||
}
|
||||
|
||||
pub const DEFAULT_FPS: u8 = 60;
|
||||
pub const LOW_POWER_FPS: u8 = 16;
|
||||
|
||||
struct ControlData {
|
||||
on: AtomicBool,
|
||||
brightness: AtomicU8
|
||||
brightness: AtomicU8,
|
||||
fps: AtomicU8
|
||||
}
|
||||
|
||||
impl Default for ControlData {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
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)
|
||||
}
|
||||
|
||||
pub fn brightness(&self) -> u8 {
|
||||
self.data.brightness.load(core::sync::atomic::Ordering::Relaxed)
|
||||
pub fn brightness(&self) -> Fract8 {
|
||||
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") }
|
||||
log::trace!("display says on!");
|
||||
}
|
||||
@@ -228,8 +238,8 @@ impl GammaCorrected for DisplayControls {
|
||||
}
|
||||
|
||||
impl Brightness for DisplayControls {
|
||||
fn set_brightness(&mut self, brightness: u8) {
|
||||
self.data.brightness.store(brightness, core::sync::atomic::Ordering::Relaxed);
|
||||
fn set_brightness(&mut self, brightness: Fract8) {
|
||||
self.data.brightness.store(brightness.to_raw(), core::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
|
||||
fn set_on(&mut self, is_on: bool) {
|
||||
@@ -244,6 +254,7 @@ impl core::fmt::Debug for DisplayControls {
|
||||
f.debug_struct("DisplayControls")
|
||||
.field("on", &self.data.on)
|
||||
.field("brightness", &self.data.brightness)
|
||||
.field("fps", &self.data.fps)
|
||||
.field("render_pause_signaled", &self.display_is_on.signaled())
|
||||
.finish()
|
||||
}
|
||||
|
||||
@@ -10,9 +10,9 @@ use embedded_graphics::primitives::{Line, PrimitiveStyle, PrimitiveStyleBuilder,
|
||||
use embedded_graphics::text::{Alignment, Text};
|
||||
use embedded_graphics::{image::Image, prelude::Point, Drawable};
|
||||
use enum_map::EnumMap;
|
||||
use figments::liber8tion::trig::sin8;
|
||||
use figments::liber8tion::trig::Trig8;
|
||||
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 nalgebra::Vector2;
|
||||
use micromath::F32Ext;
|
||||
@@ -108,7 +108,7 @@ const SENSOR_IMAGES: &[SensorImage] = &[
|
||||
on: &images::IMU_ON, off: &images::IMU_OFF,
|
||||
},
|
||||
SensorImage {
|
||||
source: SensorSource::GravityReference,
|
||||
source: SensorSource::MotionFrame,
|
||||
on: &images::GRAVITY_LOCATED, off: &images::GRAVITY_MISSING,
|
||||
},
|
||||
SensorImage {
|
||||
@@ -145,9 +145,9 @@ impl Screen {
|
||||
Image::new(&images::BOOT_LOGO, Point::zero()).draw(sampler).unwrap();
|
||||
const SPARKLE_COUNT: i32 = 8;
|
||||
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 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 cross_normal = Point::new((rotation.sin() * offset as f32) as i32, (rotation.cos() * offset as f32) as i32);
|
||||
// Draw horizontal
|
||||
|
||||
+19
-18
@@ -1,6 +1,7 @@
|
||||
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 crate::graphics::display::{SegmentSpace, Uniforms};
|
||||
@@ -25,8 +26,8 @@ impl Shader<Uniforms, SegmentSpace, Rgba<u8>> for Movement {
|
||||
} else {
|
||||
uniforms.frame.wrapping_sub(surface_coords.x)
|
||||
};
|
||||
let idx = sin8(offset).wrapping_add(uniforms.primary_color.hue);
|
||||
Rgba::new(idx, idx.wrapping_mul(2), idx.wrapping_div(2), 128)
|
||||
let idx = offset.sin8().wrapping_add(&Fract8::from_raw(uniforms.primary_color.hue));
|
||||
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 {
|
||||
fn draw(&self, coords: &Coordinates<SegmentSpace>, uniforms: &Uniforms) -> Rgba<u8> {
|
||||
let noise_x = sin8(uniforms.frame % 255) as i16;
|
||||
let noise_y = cos8(uniforms.frame % 255) as i16;
|
||||
let noise_x = (uniforms.frame % 255).sin8().to_raw() 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 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 {
|
||||
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
|
||||
};
|
||||
|
||||
@@ -62,9 +63,9 @@ impl Shader<Uniforms, SegmentSpace, Rgba<u8>> for Background {
|
||||
pub struct Tail {}
|
||||
impl Shader<Uniforms, SegmentSpace, Rgba<u8>> for Tail {
|
||||
fn draw(&self, coords: &Coordinates<SegmentSpace>, uniforms: &Uniforms) -> Rgba<u8> {
|
||||
let hue_offset: u8 = 32.scale8(sin8(uniforms.frame.wrapping_sub(coords.x)));
|
||||
let value = max(30, inoise8(coords.x.wrapping_add(uniforms.frame) as i16, coords.y.wrapping_add(uniforms.frame) as i16));
|
||||
Hsv::new(uniforms.primary_color.hue.wrapping_sub(hue_offset), max(210, sin8(uniforms.frame.wrapping_add(coords.x))), value).into()
|
||||
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).to_raw());
|
||||
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;
|
||||
|
||||
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 {
|
||||
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 {}
|
||||
impl Shader<Uniforms, SegmentSpace, Rgba<u8>> for Headlight {
|
||||
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 {}
|
||||
impl Shader<Uniforms, SegmentSpace, Rgba<u8>> for Panel {
|
||||
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 shift = match coords.y {
|
||||
1..=2 => 106, // 150 degrees
|
||||
3..=4 => 148, // 210 degrees
|
||||
_ => 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> {
|
||||
//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 offset_x = sin8(uniforms.frame.wrapping_add(coords.x));
|
||||
let offset_y = cos8(uniforms.frame.wrapping_add(coords.y));
|
||||
let offset_x = uniforms.frame.wrapping_add(coords.x).sin8();
|
||||
let offset_y = uniforms.frame.wrapping_add(coords.y).cos8();
|
||||
let noise_x = offset_x / 2;
|
||||
let noise_y = offset_y / 2;
|
||||
//let noise_x = coords.x.wrapping_add(offset_x);
|
||||
//let noise_y = coords.y.wrapping_add(offset_y);
|
||||
Hsv::new(
|
||||
inoise8(offset_x as i16, offset_y as i16),
|
||||
128_u8.saturating_add(inoise8(noise_y.into(), noise_x.into())),
|
||||
inoise8(offset_x.to_raw() as i16, offset_y.to_raw() as i16).to_raw(),
|
||||
128_u8.saturating_add(inoise8(noise_y.to_raw().into(), noise_x.to_raw().into()).to_raw()),
|
||||
255
|
||||
).into()
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use core::{cmp::min, ops::{BitAnd, Shr}};
|
||||
use display_interface::DisplayError;
|
||||
use embedded_graphics::prelude::*;
|
||||
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 figments::pixels::AdditivePixelSink;
|
||||
use figments_render::output::OutputAsync;
|
||||
@@ -59,10 +59,10 @@ const DITHER_MAP: [u16;15] = [
|
||||
impl AdditivePixelSink<BinaryColor> for SsdPixel {
|
||||
fn add(&mut self, pixel: BinaryColor, opacity: Fract8) {
|
||||
match opacity {
|
||||
0 => (),
|
||||
255 => self.set_pixel(pixel),
|
||||
Fract8::MIN => (),
|
||||
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_y = self.coords.y % 4;
|
||||
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>,
|
||||
controls: DisplayControls,
|
||||
is_on: bool,
|
||||
last_brightness: u8,
|
||||
last_brightness: Fract8,
|
||||
reset_pin: Output<'static>
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ impl SsdOutput {
|
||||
pixbuf: [0; 128 * 64 / 8],
|
||||
target,
|
||||
controls,
|
||||
last_brightness: 255,
|
||||
last_brightness: Fract8::MAX,
|
||||
is_on: true,
|
||||
reset_pin
|
||||
}
|
||||
@@ -231,14 +231,14 @@ impl<'a> OutputAsync<'a, Matrix2DSpace> for SsdOutput {
|
||||
self.is_on = new_power;
|
||||
}
|
||||
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.target.draw(&self.pixbuf).await
|
||||
}
|
||||
|
||||
fn controls(&self) -> Option<&Self::Controls> {
|
||||
Some(&self.controls)
|
||||
fn controls(&mut self) -> Option<&mut Self::Controls> {
|
||||
Some(&mut self.controls)
|
||||
}
|
||||
}
|
||||
+3
-4
@@ -8,12 +8,11 @@ pub mod animation;
|
||||
pub mod idle;
|
||||
pub mod logging;
|
||||
pub mod graphics;
|
||||
|
||||
#[cfg(feature="simulation")]
|
||||
pub mod tracing;
|
||||
pub mod storage;
|
||||
|
||||
#[cfg(feature="simulation")]
|
||||
pub mod simdata;
|
||||
pub mod gpio_interrupt;
|
||||
pub mod tusb320;
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
|
||||
+30
-4
@@ -1,11 +1,14 @@
|
||||
#![allow(static_mut_refs)]
|
||||
|
||||
use embassy_sync::blocking_mutex::{raw::CriticalSectionRawMutex, Mutex};
|
||||
use esp_hal::time::Instant;
|
||||
use esp_println::println;
|
||||
use log::{LevelFilter, Metadata, Record};
|
||||
use static_cell::StaticCell;
|
||||
|
||||
// Provides a threadsafe serial logger
|
||||
pub struct RenderbugLogger {
|
||||
lock: Mutex<CriticalSectionRawMutex, ()>,
|
||||
current_level: LevelFilter,
|
||||
default_level: LevelFilter
|
||||
}
|
||||
|
||||
@@ -13,12 +16,14 @@ impl Default for RenderbugLogger {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
lock: Mutex::new(()),
|
||||
current_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 {
|
||||
pub fn init_logger() {
|
||||
@@ -33,7 +38,8 @@ impl RenderbugLogger {
|
||||
_ => 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 {
|
||||
critical_section::with(|_| {
|
||||
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 {
|
||||
@@ -69,13 +94,14 @@ impl log::Log for RenderbugLogger {
|
||||
|
||||
let filename = record.file().map_or("???", |f| {f});
|
||||
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(",") {
|
||||
if crate_name.starts_with(enabled) {
|
||||
self.lock.lock(|_| {
|
||||
#[cfg(feature = "rtt")]
|
||||
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;
|
||||
}
|
||||
|
||||
+52
-6
@@ -6,6 +6,7 @@ pub trait RmpData: Sized {
|
||||
}
|
||||
|
||||
pub trait EventRecord: RmpData {
|
||||
fn stream_id() -> StreamType;
|
||||
fn field_count() -> usize;
|
||||
}
|
||||
|
||||
@@ -28,11 +29,12 @@ impl<E> From<E> for SimDataError<E> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum StreamType {
|
||||
IMU,
|
||||
GPS,
|
||||
Annotations
|
||||
Annotations,
|
||||
Bundle
|
||||
}
|
||||
|
||||
impl TryFrom<i8> for StreamType {
|
||||
@@ -43,6 +45,7 @@ impl TryFrom<i8> for StreamType {
|
||||
1 => Ok(StreamType::IMU),
|
||||
2 => Ok(StreamType::GPS),
|
||||
3 => Ok(StreamType::Annotations),
|
||||
4 => Ok(StreamType::Bundle),
|
||||
_ => Err(())
|
||||
}
|
||||
}
|
||||
@@ -53,7 +56,8 @@ impl From<StreamType> for i8 {
|
||||
match value {
|
||||
StreamType::IMU => 1,
|
||||
StreamType::GPS => 2,
|
||||
StreamType::Annotations => 3
|
||||
StreamType::Annotations => 3,
|
||||
StreamType::Bundle => 4
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,19 +69,48 @@ pub struct StreamIndex {
|
||||
|
||||
impl RmpData for StreamIndex {
|
||||
fn from_rmp<Reader: RmpRead>(reader: &mut Reader) -> Result<Self, SimDataError<ValueReadError<Reader::Error>>> {
|
||||
if rmp::decode::read_u16(reader)? != 0xDA1A {
|
||||
Err(SimDataError::StreamIndexMissing)
|
||||
} else {
|
||||
rmp::decode::read_array_len(reader).map(|count| {
|
||||
Self {
|
||||
count: count as usize
|
||||
}
|
||||
}).map_err(|_| { SimDataError::StreamIndexMissing })
|
||||
}).map_err(|err| { SimDataError::DecodeError(err) })
|
||||
}
|
||||
}
|
||||
|
||||
fn write_rmp<Writer: RmpWrite>(&self, writer: &mut Writer) -> Result<(), ValueWriteError<Writer::Error>> {
|
||||
rmp::encode::write_u16(writer, 0xDA1A)?;
|
||||
rmp::encode::write_array_len(writer, self.count as u32)?;
|
||||
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)]
|
||||
pub struct StreamHeader {
|
||||
pub id: StreamType,
|
||||
@@ -97,8 +130,9 @@ impl RmpData for StreamHeader {
|
||||
}
|
||||
}
|
||||
|
||||
fn write_rmp<Writer: RmpWrite>(&self, _writer: &mut Writer) -> Result<(), ValueWriteError<Writer::Error>> {
|
||||
todo!()
|
||||
fn write_rmp<Writer: RmpWrite>(&self, writer: &mut Writer) -> Result<(), ValueWriteError<Writer::Error>> {
|
||||
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 {
|
||||
2
|
||||
}
|
||||
|
||||
fn stream_id() -> StreamType {
|
||||
StreamType::GPS
|
||||
}
|
||||
}
|
||||
|
||||
impl EventRecord for IMUReading {
|
||||
fn field_count() -> usize {
|
||||
6
|
||||
}
|
||||
|
||||
fn stream_id() -> StreamType {
|
||||
StreamType::IMU
|
||||
}
|
||||
}
|
||||
|
||||
impl RmpData for GPSReading {
|
||||
@@ -239,4 +281,8 @@ impl EventRecord for AnnotationReading {
|
||||
fn field_count() -> usize {
|
||||
1
|
||||
}
|
||||
|
||||
fn stream_id() -> StreamType {
|
||||
StreamType::Annotations
|
||||
}
|
||||
}
|
||||
+89
-16
@@ -2,8 +2,12 @@ use core::{cell::RefCell, fmt::Formatter};
|
||||
|
||||
use alloc::rc::Rc;
|
||||
use embedded_storage::{ReadStorage, Storage};
|
||||
use esp_bootloader_esp_idf::partitions::PartitionTable;
|
||||
use esp_hal::time::Instant;
|
||||
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)]
|
||||
pub struct SharedFlash<S> {
|
||||
@@ -45,30 +49,30 @@ impl<S: ReadStorage> ReadStorage for SharedFlash<S> {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RangeReadError<E> {
|
||||
pub enum StorageRangeError<E> {
|
||||
OutOfData,
|
||||
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 {
|
||||
f.write_str("RmpErr")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RangeReader<S> {
|
||||
pub struct StorageRange<S> {
|
||||
storage: S,
|
||||
start: usize,
|
||||
end: usize,
|
||||
offset: usize
|
||||
}
|
||||
|
||||
impl<S: ReadStorage> RangeReader<S> {
|
||||
impl<S: ReadStorage> StorageRange<S> {
|
||||
pub const fn new(storage: S, start: usize, end: usize) -> Self {
|
||||
assert!(start <= end);
|
||||
// TODO: Should add bounds checking since we will know the size of the chunk already
|
||||
Self {
|
||||
storage,
|
||||
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;
|
||||
if self.offset > self.end {
|
||||
Err(RangeReadError::OutOfData)
|
||||
Err(StorageRangeError::OutOfData)
|
||||
} else {
|
||||
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);
|
||||
if self.start + self.offset + size > self.end {
|
||||
Err(RangeReadError::OutOfData)
|
||||
Err(StorageRangeError::OutOfData)
|
||||
} else {
|
||||
Ok(Self {
|
||||
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 {
|
||||
type Error = RangeReadError<S::Error>;
|
||||
impl<S: ReadStorage> RmpRead for StorageRange<S> where S::Error: core::fmt::Debug + 'static {
|
||||
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;
|
||||
if pos > self.end {
|
||||
Err(RangeReadError::OutOfData)
|
||||
Err(StorageRangeError::OutOfData)
|
||||
} else {
|
||||
assert!(pos + buf.len() <= self.end);
|
||||
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();
|
||||
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
@@ -1,12 +1,10 @@
|
||||
|
||||
use core::ptr::slice_from_raw_parts;
|
||||
|
||||
use embassy_executor::Spawner;
|
||||
use embassy_futures::select::Either;
|
||||
use embassy_sync::{blocking_mutex::raw::NoopRawMutex, pubsub::{DynSubscriber, PubSubChannel, Publisher}};
|
||||
use esp_radio::ble::controller::BleConnector;
|
||||
use static_cell::{ConstStaticCell, StaticCell};
|
||||
use trouble_host::{attribute, prelude::*, types::gatt_traits::FromGattError};
|
||||
use static_cell::StaticCell;
|
||||
use trouble_host::{prelude::*, types::gatt_traits::FromGattError};
|
||||
use log::*;
|
||||
|
||||
use crate::{backoff::Backoff, events::Prediction};
|
||||
@@ -55,7 +53,7 @@ struct LocationData {
|
||||
|
||||
impl FixedGattValue for LocationData {
|
||||
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) }
|
||||
}
|
||||
|
||||
@@ -91,7 +89,7 @@ struct SerialService {
|
||||
async fn client_prediction_task(mut src: DynSubscriber<'static, Prediction>, sink: Publisher<'static, NoopRawMutex, Prediction, 5, 1, 1>) {
|
||||
debug!("Started BLE client prediction stream");
|
||||
loop {
|
||||
sink.publish(src.next_message_pure().await).await;
|
||||
let _ = sink.try_publish(src.next_message_pure().await);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ pub async fn demo_task(ui: DynPublisher<'static, Prediction>) {
|
||||
for state in [SensorState::Offline, SensorState::AcquiringFix, SensorState::Degraded, SensorState::Offline] {
|
||||
for motion in [MotionState::Accelerating, MotionState::Steady, MotionState::Decelerating, MotionState::Stationary] {
|
||||
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;
|
||||
}
|
||||
Timer::after_secs(1).await;
|
||||
|
||||
+21
-19
@@ -6,7 +6,7 @@ use embedded_hal_async::i2c::I2c as _;
|
||||
use esp_hal::{i2c::master::I2c, Async};
|
||||
use log::*;
|
||||
use nalgebra::Vector2;
|
||||
use nmea::Nmea;
|
||||
use nmea::{Nmea, sentences::FixType};
|
||||
|
||||
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;
|
||||
Backoff::from_secs(5).forever().attempt(async || {
|
||||
info!("Initializing GPS");
|
||||
events.send(Measurement::SensorHardwareStatus(SensorSource::GPS, SensorState::AcquiringFix)).await;
|
||||
// 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";
|
||||
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 parsing = 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!");
|
||||
loop {
|
||||
let mut buf = [0; 1];
|
||||
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 let Ok(result) = parser.parse(&strbuf) {
|
||||
match parser.fix_type {
|
||||
None if has_lock => {
|
||||
match parser.parse_for_fix(&strbuf) {
|
||||
Ok(FixType::Invalid) if has_lock => {
|
||||
// TODO: Send a Measurement::SensorOffline(SensorSource::GPS) here instead
|
||||
events.send(Measurement::GPS(None)).await;
|
||||
events.send(Measurement::SensorHardwareStatus(SensorSource::GPS, SensorState::Degraded)).await;
|
||||
has_lock = false
|
||||
},
|
||||
None => (),
|
||||
Some(_) => {
|
||||
Ok(FixType::Invalid) => {
|
||||
debug!("Waiting for fix {parser:?}");
|
||||
},
|
||||
Ok(fix_type) => {
|
||||
if !has_lock {
|
||||
has_lock = true;
|
||||
info!("Got a fix of type {fix_type:?}");
|
||||
events.send(Measurement::SensorHardwareStatus(SensorSource::GPS, SensorState::Online)).await;
|
||||
}
|
||||
// TODO: Send a Measurement::SensorOnline(SensorSource::GPS) here instead
|
||||
|
||||
//TODO: 4 satellites seems to be "Some" fix, 6 is a perfect fix
|
||||
//TODO: Only send updates when we get the correct nmea sentence
|
||||
if let (Some(lat), Some(lng)) = (parser.latitude, parser.longitude) {
|
||||
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();
|
||||
parsing = false;
|
||||
|
||||
+3
-2
@@ -1,6 +1,5 @@
|
||||
#[cfg(feature="mpu")]
|
||||
#[cfg(feature="mpu6050")]
|
||||
pub mod mpu;
|
||||
#[cfg(feature="gps")]
|
||||
pub mod gps;
|
||||
#[cfg(feature="radio")]
|
||||
pub mod wifi;
|
||||
@@ -13,6 +12,8 @@ pub mod demo;
|
||||
#[cfg(feature="oled")]
|
||||
pub mod oled_render;
|
||||
|
||||
pub mod usb_power;
|
||||
|
||||
// Prediction engines
|
||||
pub mod motion;
|
||||
|
||||
|
||||
+8
-4
@@ -1,10 +1,13 @@
|
||||
use embassy_sync::{channel::DynamicReceiver, pubsub::DynPublisher};
|
||||
use log::*;
|
||||
use embassy_time::{Duration, WithTimeout};
|
||||
|
||||
use crate::{ego::engine::BikeStates, events::{Measurement, Prediction}};
|
||||
|
||||
const TIMEOUT: Duration = Duration::from_millis(3);
|
||||
|
||||
#[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();
|
||||
|
||||
loop {
|
||||
@@ -14,11 +17,11 @@ pub async fn motion_task(src: DynamicReceiver<'static, Measurement>, prediction_
|
||||
match next_measurement {
|
||||
Measurement::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)) => {
|
||||
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) => {
|
||||
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?
|
||||
Measurement::SensorHardwareStatus(source, 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::Annotation => ()
|
||||
}
|
||||
let _ = recording_sink.try_publish(next_measurement);
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -12,6 +12,7 @@ use mpu6050_dmp::{address::Address, error_async::Error, sensor_async::Mpu6050};
|
||||
use nalgebra::Vector3;
|
||||
use crate::events::SensorSource;
|
||||
|
||||
use crate::gpio_interrupt::PinInterrupt;
|
||||
use crate::{backoff::Backoff, events::Measurement};
|
||||
|
||||
const G: f32 = 9.80665;
|
||||
@@ -19,7 +20,7 @@ const GYRO_SCALE: GyroFullScale = GyroFullScale::Deg2000;
|
||||
const ACCEL_SCALE: AccelFullScale = AccelFullScale::G2;
|
||||
|
||||
#[embassy_executor::task]
|
||||
pub async fn mpu_task(events: DynamicSender<'static, Measurement>, bus: I2cDevice<'static, NoopRawMutex, I2c<'static, Async>>) {
|
||||
pub async fn mpu_task(events: DynamicSender<'static, Measurement>, bus: I2cDevice<'static, NoopRawMutex, I2c<'static, Async>>, _interrupt: PinInterrupt<'static>) {
|
||||
let backoff = Backoff::from_millis(5);
|
||||
let busref = RefCell::new(Some(bus));
|
||||
|
||||
|
||||
+10
-10
@@ -2,19 +2,19 @@ use alloc::sync::Arc;
|
||||
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex, pubsub::DynSubscriber};
|
||||
use embassy_time::{Duration, Timer};
|
||||
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 log::*;
|
||||
|
||||
use crate::{animation::Animation, events::{Personality, Prediction}, graphics::{display::DisplayControls, oled_ui::{OledUniforms, Screen}}};
|
||||
|
||||
#[cfg(feature="oled")]
|
||||
pub type OledUiSurfacePool = BufferedSurfacePool<OledUniforms, Matrix2DSpace, BinaryColor>;
|
||||
pub type OledUiSurfacePool = figments::surfaces::buffered::BufferedSurfacePool<OledUniforms, Matrix2DSpace, BinaryColor>;
|
||||
|
||||
#[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>>;
|
||||
|
||||
@@ -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> {
|
||||
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 {
|
||||
overlay: SurfaceBuilder::build(surfaces)
|
||||
.rect(Rectangle::everything())
|
||||
.shader(OverlayShader{})
|
||||
.opacity(0)
|
||||
.opacity(Fract8::MIN)
|
||||
.finish().unwrap(),
|
||||
controls,
|
||||
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) {
|
||||
const FADE_IN: Animation = Animation::new().from(0).to(255).duration(Duration::from_millis(300));
|
||||
const FADE_OUT: Animation = Animation::new().from(255).to(0).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<Fract8> = Animation::new().from(Fract8::MAX).to(Fract8::MIN).duration(Duration::from_millis(300));
|
||||
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;
|
||||
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) {
|
||||
|
||||
+36
-27
@@ -1,34 +1,38 @@
|
||||
use embassy_time::{Duration, Instant, Timer};
|
||||
use esp_hal::{gpio::AnyPin, rmt::Rmt, time::Rate, timer::timg::Wdt};
|
||||
use esp_hal_smartled::{buffer_size_async, SmartLedsAdapterAsync};
|
||||
use esp_hal::spi::Mode;
|
||||
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_esp32_ws2812_dma::{DmaBuffers, Esp32Ws2812SpiDmaWriter};
|
||||
use figments_render::gamma::GammaCurve;
|
||||
use figments_render::output::{GammaCorrected, OutputAsync};
|
||||
use log::{info, warn};
|
||||
use log::*;
|
||||
|
||||
use crate::graphics::display::NUM_PIXELS;
|
||||
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]
|
||||
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>>) {
|
||||
let frequency: Rate = Rate::from_mhz(80);
|
||||
let rmt = Rmt::new(rmt, frequency)
|
||||
.expect("Failed to initialize RMT").into_async();
|
||||
let rmt_channel = rmt.channel0;
|
||||
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>>) {
|
||||
info!("Starting rendering task");
|
||||
|
||||
// FIXME: I don't know why we need to add an extra bunch of pixels, but otherwise the buffer is too small?
|
||||
let mut rmt_buffer = esp_hal_smartled::smart_led_buffer!(NUM_PIXELS + 25);
|
||||
let buffers = SPI_BUFFERS.take();
|
||||
|
||||
// FIXME: The strip is GRB, we need to fix the pixel swizzling deep within the traits
|
||||
let target = SmartLedsAdapterAsync::new_with_color(rmt_channel, gpio, &mut rmt_buffer);
|
||||
let tx_buf = DmaTxBuf::new(&mut buffers.tx_descriptors, &mut buffers.tx_buffer).unwrap();
|
||||
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
|
||||
const POWER_MA : u32 = 500;
|
||||
let driver = Spi::new(spi, conf).unwrap()
|
||||
.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
|
||||
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());
|
||||
controls.notify_render_is_running(true);
|
||||
|
||||
// TODO: The prediction engine should be able to scale the display FPS down for power saving sometimes
|
||||
const FPS: u64 = 80;
|
||||
const RENDER_BUDGET: Duration = Duration::from_millis(1000 / FPS);
|
||||
let mut requested_fps= controls.fps() as u64;
|
||||
let mut render_budget = Duration::from_millis(1000 / requested_fps);
|
||||
|
||||
const ANIMATION_TPS: u64 = 120;
|
||||
const ANIMATION_FRAME_TIME: Duration = Duration::from_millis(1000 / ANIMATION_TPS);
|
||||
|
||||
loop {
|
||||
// FIXME: need to put the rendering loop into a deep sleep when the display is off
|
||||
let start = Instant::now();
|
||||
|
||||
output.blank();
|
||||
@@ -59,7 +61,6 @@ pub async fn render(rmt: esp_hal::peripherals::RMT<'static>, gpio: AnyPin<'stati
|
||||
surfaces.commit();
|
||||
safety_surfaces.commit();
|
||||
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);
|
||||
|
||||
// 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 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() {
|
||||
warn!("Renderer is sleeping zzzz");
|
||||
controls.notify_render_is_running(false);
|
||||
output.blank();
|
||||
output.commit_async().await.expect("Failed to commit low power frame");
|
||||
wdt.disable();
|
||||
controls.wait_until_display_is_on().await;
|
||||
controls.wait_until_display_is_turned_on().await;
|
||||
wdt.feed();
|
||||
wdt.enable();
|
||||
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
|
||||
if render_duration < RENDER_BUDGET {
|
||||
let remaining_budget = RENDER_BUDGET - render_duration;
|
||||
if render_duration < render_budget {
|
||||
let remaining_budget = render_budget - render_duration;
|
||||
Timer::after(remaining_budget).await;
|
||||
} else {
|
||||
// Otherwise, we have a problem
|
||||
// TODO: Automatically scale FPS back when this happens
|
||||
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
|
||||
|
||||
+30
-38
@@ -1,12 +1,12 @@
|
||||
use embassy_sync::pubsub::DynSubscriber;
|
||||
use embassy_time::Duration;
|
||||
use figments::prelude::*;
|
||||
use figments::{liber8tion::interpolate::Fract8, prelude::*};
|
||||
use figments_render::output::Brightness;
|
||||
use rgb::Rgba;
|
||||
use core::fmt::Debug;
|
||||
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)]
|
||||
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> {
|
||||
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 {
|
||||
overlay: SurfaceBuilder::build(surfaces)
|
||||
.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))
|
||||
.shader(Headlight::default())
|
||||
.visible(false)
|
||||
.opacity(0)
|
||||
.opacity(Fract8::MIN)
|
||||
.finish().unwrap().into(),
|
||||
brakelight: SurfaceBuilder::build(surfaces)
|
||||
.rect(Brakelight::rectangle())
|
||||
.shader(Brakelight::default())
|
||||
.visible(false)
|
||||
.opacity(0)
|
||||
.opacity(Fract8::MIN)
|
||||
.finish().unwrap().into(),
|
||||
display
|
||||
};
|
||||
@@ -48,7 +48,7 @@ impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pix
|
||||
pub async fn sleep(&mut self) {
|
||||
info!("Running sleep sequence");
|
||||
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");
|
||||
self.brakelight.set_visible(false);
|
||||
@@ -57,8 +57,7 @@ impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pix
|
||||
warn!("Turning off display");
|
||||
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.
|
||||
// FIXME: also deadlocks :(
|
||||
//self.display.render_is_running.wait().await;
|
||||
self.display.wait_until_render_is_running().await;
|
||||
}
|
||||
|
||||
pub async fn wake(&mut self) {
|
||||
@@ -66,30 +65,25 @@ impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pix
|
||||
|
||||
info!("Turning on display");
|
||||
// Turn on the display hardware
|
||||
self.display.set_brightness(0);
|
||||
self.display.set_brightness(Fract8::MIN);
|
||||
self.display.set_on(true);
|
||||
// Wait for the renderer to start running again
|
||||
// FIXME: This deadlocks :(
|
||||
//self.display.render_is_running.wait().await;
|
||||
self.display.wait_until_render_is_running().await;
|
||||
|
||||
let fade_in = Animation::default().duration(Duration::from_secs(3)).from(0).to(255);
|
||||
trace!("Fading in brightness with overlay={:?}", self.overlay);
|
||||
self.overlay.set_opacity(255);
|
||||
self.overlay.set_opacity(Fract8::MAX);
|
||||
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");
|
||||
self.headlight.set_opacity(0);
|
||||
self.headlight.set_opacity(Fract8::MIN);
|
||||
self.headlight.set_visible(true);
|
||||
self.brakelight.set_opacity(0);
|
||||
self.brakelight.set_opacity(Fract8::MIN);
|
||||
self.brakelight.set_visible(true);
|
||||
embassy_futures::join::join(
|
||||
fade_in.apply(&mut self.headlight),
|
||||
fade_in.apply(&mut self.brakelight)
|
||||
).await;
|
||||
TURN_ON_SLOW.apply([&mut self.headlight, &mut self.brakelight]).await;
|
||||
|
||||
info!("Fade out overlay");
|
||||
TURN_OFF.apply(&mut self.overlay).await;
|
||||
TURN_OFF_FAST.apply([&mut self.overlay]).await;
|
||||
self.overlay.set_visible(false);
|
||||
info!("Wakeup complete!");
|
||||
}
|
||||
@@ -98,18 +92,20 @@ impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pix
|
||||
if let Prediction::SetPersonality(personality) = event { match personality {
|
||||
Personality::Active => {
|
||||
// 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");
|
||||
embassy_futures::join::join(
|
||||
TURN_ON.apply(&mut self.brakelight),
|
||||
TURN_ON.apply(&mut self.headlight)
|
||||
).await;
|
||||
TURN_ON_FAST.apply([
|
||||
&mut self.brakelight,
|
||||
&mut self.headlight
|
||||
]).await;
|
||||
},
|
||||
Personality::Parked => {
|
||||
warn!("Idle personality: Turning off safety lights");
|
||||
embassy_futures::join::join(
|
||||
TURN_OFF.apply(&mut self.brakelight),
|
||||
TURN_OFF.apply(&mut self.headlight)
|
||||
).await;
|
||||
TURN_OFF_FAST.apply([
|
||||
&mut self.brakelight,
|
||||
&mut self.headlight
|
||||
]).await;
|
||||
self.display.set_fps(LOW_POWER_FPS);
|
||||
},
|
||||
Personality::Sleeping => {
|
||||
warn!("Sleeping personality: Safety UI is going to sleep");
|
||||
@@ -117,23 +113,19 @@ impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pix
|
||||
},
|
||||
Personality::Waking => {
|
||||
warn!("Waking personality: Waking up safety UI");
|
||||
self.display.set_fps(DEFAULT_FPS);
|
||||
self.wake().await;
|
||||
},
|
||||
} }
|
||||
}
|
||||
}
|
||||
|
||||
const TURN_ON: Animation = Animation::new().duration(Duration::from_secs(1)).from(0).to(255);
|
||||
const TURN_OFF: Animation = Animation::new().duration(Duration::from_secs(1)).from(255).to(0);
|
||||
const TURN_ON_FAST: Animation<Fract8> = Animation::new().duration(Duration::from_secs(1)).from(Fract8::MIN).to(Fract8::MAX);
|
||||
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]
|
||||
pub async fn safety_ui_main(mut events: DynSubscriber<'static, Prediction>, mut ui: SafetyUi<<UiSurfacePool as Surfaces<SegmentSpace>>::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:?}");
|
||||
pub async fn safety_ui_main(mut events: DynSubscriber<'static, Prediction>, mut ui: SafetyUi<<UiSurfacePool as Surfaces>::Surface>) {
|
||||
// Run the wake sequence, and turn on the lights
|
||||
ui.wake().await;
|
||||
|
||||
|
||||
+35
-25
@@ -1,22 +1,25 @@
|
||||
use core::usize;
|
||||
|
||||
use embassy_sync::channel::DynamicSender;
|
||||
use embassy_time::{Duration, Timer};
|
||||
use embedded_storage::ReadStorage;
|
||||
use embedded_storage::{ReadStorage, Storage};
|
||||
use esp_bootloader_esp_idf::partitions::PartitionTable;
|
||||
use esp_hal::time::Instant;
|
||||
use esp_storage::FlashStorage;
|
||||
use figments::liber8tion::interpolate::Fract8;
|
||||
use nalgebra::{Vector2, Vector3};
|
||||
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> {
|
||||
reader: RangeReader<S>,
|
||||
reader: StorageRange<S>,
|
||||
count: 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>> {
|
||||
let partition_type = esp_bootloader_esp_idf::partitions::PartitionType::Data(
|
||||
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 end = data_partition.len() as usize + start;
|
||||
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) {
|
||||
info!("Found stream index: {index:?}");
|
||||
Ok(Self {
|
||||
@@ -54,9 +57,11 @@ impl<S: ReadStorage + Clone + core::fmt::Debug> Iterator for SimDataTable<S> whe
|
||||
loop {
|
||||
match StreamHeader::from_rmp(&mut self.reader) {
|
||||
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) => {
|
||||
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:?}")
|
||||
});
|
||||
self.index += 1;
|
||||
@@ -87,8 +92,8 @@ impl<S: ReadStorage + Clone + core::fmt::Debug> Iterator for SimDataTable<S> whe
|
||||
}
|
||||
|
||||
pub struct SimDataReader<S> {
|
||||
reader: RangeReader<S>,
|
||||
srcid: SensorSource,
|
||||
reader: StorageRange<S>,
|
||||
srcid: StreamType,
|
||||
runtime: Duration,
|
||||
event_count: usize,
|
||||
index: usize
|
||||
@@ -117,39 +122,44 @@ impl From<AnnotationReading> for Measurement {
|
||||
}
|
||||
|
||||
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");
|
||||
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!");
|
||||
Self {
|
||||
reader,
|
||||
srcid: stream_type.into(),
|
||||
srcid: stream_type,
|
||||
runtime: Default::default(),
|
||||
event_count,
|
||||
index: 0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn srcid(&self) -> SensorSource {
|
||||
pub fn srcid(&self) -> StreamType {
|
||||
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 delay = embassy_time::Duration::from_millis((event.timecode * 1000.0) as u64);
|
||||
self.runtime += delay;
|
||||
info!("waiting {delay}");
|
||||
Timer::after(delay).await;
|
||||
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 {
|
||||
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()
|
||||
Ok(Some(match self.srcid {
|
||||
SensorSource::IMU => self.read_next_event::<IMUReading>().await?,
|
||||
SensorSource::GPS => self.read_next_event::<GPSReading>().await?,
|
||||
SensorSource::Annotations => self.read_next_event::<AnnotationReading>().await?,
|
||||
Ok(Some(match next_id {
|
||||
StreamType::IMU => self.read_next_event::<IMUReading>().await?,
|
||||
StreamType::GPS => self.read_next_event::<GPSReading>().await?,
|
||||
StreamType::Annotations => self.read_next_event::<AnnotationReading>().await?,
|
||||
srcid => unimplemented!("{srcid:?} is not a simulatable sensor yet!")
|
||||
}))
|
||||
} else {
|
||||
@@ -163,7 +173,7 @@ pub async fn simulation_task(mut reader: SimDataReader<SharedFlash<FlashStorage>
|
||||
warn!("Starting simulation for {:?}", reader.srcid());
|
||||
|
||||
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_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);
|
||||
idx_breaker.set((pct * 255.0) as u8);
|
||||
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;
|
||||
},
|
||||
Ok(None) => {
|
||||
warn!("End of simulation data stream");
|
||||
break
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
warn!("Error during sensor stream: {err:?}");
|
||||
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;
|
||||
|
||||
warn!("End of simulation for {:?}", reader.srcid());
|
||||
|
||||
+27
-36
@@ -1,9 +1,8 @@
|
||||
use embassy_sync::pubsub::DynSubscriber;
|
||||
use embassy_time::{Duration, Timer};
|
||||
use figments::prelude::*;
|
||||
use figments::{liber8tion::interpolate::Fract8, prelude::*};
|
||||
use rgb::{Rgb, Rgba};
|
||||
use core::fmt::Debug;
|
||||
use futures::join;
|
||||
use log::*;
|
||||
|
||||
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> {
|
||||
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 {
|
||||
background: SurfaceBuilder::build(surfaces)
|
||||
.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>) {
|
||||
let fade_in = Animation::default().from(0).to(255).duration(Duration::from_millis(30));
|
||||
let pulse_out = Animation::default().from(255).to(60).duration(Duration::from_millis(100));
|
||||
let pulse_in = Animation::default().from(0).to(255).duration(Duration::from_millis(100));
|
||||
let fade_out = Animation::default().from(255).to(0).duration(Duration::from_secs(2));
|
||||
let fade_in = Animation::default().from(Fract8::MIN).to(Fract8::MAX).duration(Duration::from_millis(30));
|
||||
let pulse_out = Animation::default().from(Fract8::MAX).to(Fract8::from_raw(60)).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(Fract8::MAX).to(Fract8::MIN).duration(Duration::from_secs(2));
|
||||
info!("Flashing notification {color}");
|
||||
|
||||
self.notification.set_visible(true);
|
||||
|
||||
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
|
||||
for _ in 0..5 {
|
||||
pulse_out.apply(&mut self.notification).await;
|
||||
pulse_in.apply(&mut self.notification).await;
|
||||
pulse_out.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);
|
||||
}
|
||||
|
||||
@@ -90,44 +89,39 @@ impl<S: Debug + Surface<Uniforms = Uniforms, CoordinateSpace = SegmentSpace, Pix
|
||||
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) {
|
||||
info!("Activating scene {next_scene:?}");
|
||||
match next_scene {
|
||||
Scene::Ready => {
|
||||
let tail = Animation::default().duration(Duration::from_millis(300)).to(96);
|
||||
let panels = Animation::default().duration(Duration::from_millis(300)).to(128);
|
||||
let bg = Animation::default().duration(Duration::from_millis(300)).to(32);
|
||||
let motion = Animation::default().duration(Duration::from_secs(1)).to(0);
|
||||
let tail = Animation::default().duration(Duration::from_millis(300)).to(Fract8::from_raw(96));
|
||||
let panels = Animation::default().duration(Duration::from_millis(300)).to(Fract8::from_raw(128));
|
||||
let bg = Animation::default().duration(Duration::from_millis(300)).to(Fract8::from_raw(32));
|
||||
let motion = Animation::default().duration(Duration::from_secs(1)).to(Fract8::MIN);
|
||||
embassy_futures::join::join4(
|
||||
tail.apply(&mut self.tail),
|
||||
panels.apply(&mut self.panels),
|
||||
bg.apply(&mut self.background),
|
||||
motion.apply(&mut self.motion)
|
||||
tail.apply([&mut self.tail]),
|
||||
panels.apply([&mut self.panels]),
|
||||
bg.apply([&mut self.background]),
|
||||
motion.apply([&mut self.motion])
|
||||
).await;
|
||||
self.background.set_shader(Background::default());
|
||||
},
|
||||
Scene::Idle => {
|
||||
// FIXME: The safety UI task should handle setting the display brightness to 50% here
|
||||
self.background.set_shader(Thinking::default());
|
||||
let fg_fade = Animation::default().duration(Duration::from_millis(300)).to(0);
|
||||
let bg_fade = Animation::default().duration(Duration::from_millis(300)).to(128);
|
||||
let fg_fade = Animation::default().duration(Duration::from_millis(300)).to(Fract8::MIN);
|
||||
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::join4(
|
||||
fg_fade.apply(&mut self.tail),
|
||||
fg_fade.apply(&mut self.panels),
|
||||
bg_fade.apply(&mut self.background),
|
||||
fg_fade.apply(&mut self.motion)
|
||||
embassy_futures::join::join(
|
||||
fg_fade.apply([&mut self.tail, &mut self.panels, &mut self.motion]),
|
||||
bg_fade.apply([&mut self.background])
|
||||
).await;
|
||||
},
|
||||
Scene::Accelerating => {
|
||||
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 => {
|
||||
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);
|
||||
}
|
||||
|
||||
fn set_opacity(&mut self, opacity: u8) {
|
||||
fn set_opacity(&mut self, opacity: Fract8) {
|
||||
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>>;
|
||||
|
||||
#[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
|
||||
Timer::after_secs(3).await;
|
||||
ui.show().await;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
+38
-38
@@ -1,7 +1,7 @@
|
||||
use alloc::string::ToString;
|
||||
use alloc::string::{String, ToString};
|
||||
use embassy_sync::channel::DynamicSender;
|
||||
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_radio::wifi::{ClientConfig, ScanConfig, WifiController, WifiDevice, WifiEvent};
|
||||
use log::*;
|
||||
@@ -23,58 +23,52 @@ pub async fn net_task(mut runner: embassy_net::Runner<'static, WifiDevice<'stati
|
||||
runner.run().await
|
||||
}
|
||||
|
||||
#[embassy_executor::task]
|
||||
pub async fn wifi_connect_task(mut wifi: WifiController<'static>, stack: Stack<'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_power_saving(esp_radio::wifi::PowerSaveMode::Maximum).unwrap();
|
||||
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]
|
||||
pub async fn wifi_connect_task(mut wifi: WifiController<'static>, motion: DynamicSender<'static, Measurement>) {
|
||||
wifi.set_mode(esp_radio::wifi::WifiMode::Sta).unwrap();
|
||||
wifi.start_async().with_timeout(Duration::from_secs(30)).await.unwrap().unwrap();
|
||||
Timer::after_secs(2).await;
|
||||
|
||||
// TODO: need a way to stop wifi after, say, 30 seconds of being unable to connect, or the system goes to sleep
|
||||
loop {
|
||||
Backoff::from_secs(3).forever().attempt(async || {
|
||||
info!("Connecting to wifi...");
|
||||
wifi.start_async().await.unwrap();
|
||||
motion.send(Measurement::SensorHardwareStatus(SensorSource::Wifi, crate::events::SensorState::AcquiringFix)).await;
|
||||
let networks = wifi.scan_with_config_async(ScanConfig::default().with_show_hidden(true)).await.unwrap();
|
||||
let networks = wifi.scan_with_config_async(ScanConfig::default()).await.unwrap();
|
||||
for network in networks {
|
||||
info!("wifi: {} @ {}db", network.ssid, network.signal_strength);
|
||||
if let Some(cfg) = config_for_network(&network.ssid) {
|
||||
wifi.set_config(&esp_radio::wifi::ModeConfig::Client(cfg)).unwrap();
|
||||
if wifi.connect_async().await.is_err() {
|
||||
error!("Unable to connect to wifi {network:?}");
|
||||
}
|
||||
match wifi.connect_async().await {
|
||||
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!");
|
||||
if wifi.is_connected().unwrap() {
|
||||
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 {
|
||||
warn!("DHCP timed out after 5 seconds. Disconnecting wifi");
|
||||
wifi.disconnect_async().await.unwrap();
|
||||
info!("Wifi is offline.");
|
||||
motion.send(Measurement::SensorHardwareStatus(SensorSource::Wifi, crate::events::SensorState::Offline)).await;
|
||||
Timer::after_secs(30).await;
|
||||
}
|
||||
warn!("Stopping wifi device");
|
||||
wifi.stop_async().await.unwrap();
|
||||
motion.send(Measurement::SensorHardwareStatus(SensorSource::Wifi, SensorState::Offline)).await;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Wifi task needs to know when there is data to upload, so it only connects when needed.
|
||||
#[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
|
||||
|
||||
let seed = Rng::new().random() as i32;
|
||||
@@ -98,18 +92,24 @@ pub async fn http_telemetry_task(mut predictions: DynSubscriber<'static, Predict
|
||||
|
||||
loop {
|
||||
if let Prediction::Location(coords) = predictions.next_message_pure().await {
|
||||
|
||||
if stack.is_config_up() {
|
||||
// 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 {
|
||||
last_location = coords;
|
||||
last_push = Instant::now();
|
||||
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
|
||||
}).await {
|
||||
motion.send(Measurement::SensorHardwareStatus(SensorSource::Cloud, SensorState::Offline)).await;
|
||||
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
@@ -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
@@ -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(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user