Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46822c182c
|
||
|
|
2b7aacdb99
|
||
|
|
cabc8c1cc0
|
||
|
|
0cbf59765c
|
||
|
|
de38278d7b
|
No files matched your search
@@ -11,3 +11,10 @@ rustflags = [
|
||||
"-C", "link-arg=-Tdefmt.x",
|
||||
"-C", "opt-level=z",
|
||||
]
|
||||
|
||||
[target.thumbv6m-none-eabi]
|
||||
rustflags = [
|
||||
"-C", "link-arg=-Tlink.x",
|
||||
"-C", "link-arg=-Tdefmt.x",
|
||||
"-C", "opt-level=z",
|
||||
]
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/).
|
||||
|
||||
## [0.3.0] - 2026-04-10
|
||||
|
||||
### Added
|
||||
|
||||
- Waveshare RP2040-LoRa board support (RP2040 + SX1262, no OLED, LED on GPIO25)
|
||||
- First RP2040 board — third MCU family after ESP32-S3 and nRF52840
|
||||
- New `hal/rp2040.rs` module with MCU primitives (SPI, USB, flash unique ID)
|
||||
- USB-C via FPC ribbon cable to adapter board
|
||||
- PE4259 antenna switch on GPIO17, 850-930 MHz (HF)
|
||||
|
||||
### Changed
|
||||
|
||||
- `cortex-m-rt/set-vtor` moved from global dependency to per-board feature
|
||||
(Cortex-M0+ has no VTOR register)
|
||||
- `build.rs` memory.x selection is now target-aware (thumbv6m vs thumbv7em)
|
||||
|
||||
## [0.2.1] - 2026-04-07
|
||||
|
||||
### Fixed
|
||||
|
||||
- Display stuck on status screen after USB disconnect (stale radio status
|
||||
updates re-rendered dashboard over splash because `disconnected` flag was
|
||||
not set in the `Reset` handler)
|
||||
- Radio left running after host disconnect, now sends `StopRx` when DTR drops
|
||||
- System deadlock when host app closes serial port while radio is receiving
|
||||
(unsolicited responses in radio task now use `try_send` to avoid blocking
|
||||
on a full response channel)
|
||||
- LED orphaned if OLED display initialization fails (display task now falls
|
||||
back to a LED-only event loop instead of returning early)
|
||||
|
||||
## [0.2.0] - 2026-04-07
|
||||
|
||||
### Added
|
||||
|
||||
- Wio Tracker L1 board support (nRF52840 + SX1262, SH1106 OLED)
|
||||
- LED blink on packet activity: brief flash on RX and TX when display is on
|
||||
- RX brightness scales with SNR (stronger signal = brighter)
|
||||
- Works on all boards with a user LED (Heltec V3/V4, Wio Tracker)
|
||||
- Native USB CDC-ACM support for Heltec V3 (requires hardware mod: solder R29/R3)
|
||||
- `heltec_v3_uart` feature for stock (unmodified) V3 boards via CP2102 bridge
|
||||
- `RgbLed` trait and `SimpleLed` driver for GPIO-based LEDs
|
||||
- `LoRaBoard` trait with full associated types (`RadioParts`, `CommParts`,
|
||||
`DisplayParts`, `DisplayDriver`, `LedDriver`) and `BoardParts<R, C, D, L>` struct
|
||||
|
||||
### Changed
|
||||
|
||||
- Major firmware architecture reorganization:
|
||||
- `hal/` module for MCU-specific primitives (ESP32-S3, nRF52840)
|
||||
- `driver/` module for hardware peripheral drivers (SH1106, SimpleLed)
|
||||
- `host/` module for unified USB/UART communication with internal cfg dispatch
|
||||
- `board/` contains only board definitions — no MCU helpers or drivers mixed in
|
||||
- Board files are declarative wiring diagrams calling hal primitives
|
||||
- `run()` function has zero board-specific conditional compilation
|
||||
- Display init moved to board layer (`create_display()`) — display task has no cfg
|
||||
- Flattened `radio/` and `display/` directories (no single-file directories)
|
||||
- `build.rs` auto-discovers boards by content (implements `LoRaBoard`) and
|
||||
helper modules by `use super::` imports — no exclusion lists
|
||||
- `check` and `clippy` commands now use `--release` profile
|
||||
- Deduplicated USB/UART protocol framing (shared `CobsDecoder` + `route_command`)
|
||||
- Removed `ErrorCode::CrcError` (SX1262 silently drops bad-CRC packets)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Display stuck on splash screen for UART boards (disconnected flag started true)
|
||||
|
||||
## [0.1.0] - 2026-03-15
|
||||
|
||||
### Added
|
||||
|
||||
- Transparent LoRa radio over USB CDC-ACM (VID `1209`, PID `5741`)
|
||||
- COBS-framed fixed-size LE binary protocol (9 commands, 7 responses)
|
||||
- Radio configuration: frequency, bandwidth, spreading factor, coding rate, TX power
|
||||
- Continuous RX mode with RSSI/SNR per packet
|
||||
- Single-shot TX with optional per-packet config override
|
||||
- SSD1306 OLED display support with status dashboard
|
||||
- Radio state, frequency, modulation parameters
|
||||
- Packet counters (RX/TX)
|
||||
- RSSI sparkline (1-minute history, TX shown as dotted bars)
|
||||
- Combined splash/waiting screen on disconnect
|
||||
- Auto-detect USB host connect/disconnect via DTR
|
||||
- Config validation against per-board hardware limits
|
||||
- `TX_POWER_MAX` sentinel (`-128`): auto-use the board's maximum TX power
|
||||
- Board support: Heltec V3, Heltec V4, RAK WisBlock 4631
|
||||
- Python host library (`donglora` on PyPI) with `pip install` and `donglora-mux` CLI
|
||||
- USB multiplexer for sharing one dongle with multiple applications
|
||||
- Two-way LoRa bridge over TCP (`examples/lora_bridge.py`)
|
||||
|
||||
### Technical
|
||||
|
||||
- Embassy async runtime (no threads, CPU sleeps when idle)
|
||||
- Zero-config: boots and waits for host commands
|
||||
- Protocol-agnostic: firmware has no opinion about what you transmit
|
||||
- Tasks never panic: all errors reported to host or logged
|
||||
- Board abstraction: one `.rs` file + one Cargo feature per board
|
||||
Generated
+523
-1
@@ -14,6 +14,30 @@ dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||
|
||||
[[package]]
|
||||
name = "ascii-canvas"
|
||||
version = "4.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891"
|
||||
dependencies = [
|
||||
"term",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.0"
|
||||
@@ -35,6 +59,21 @@ dependencies = [
|
||||
"rustc_version",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
|
||||
dependencies = [
|
||||
"bit-vec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-vec"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
|
||||
|
||||
[[package]]
|
||||
name = "bitfield"
|
||||
version = "0.13.2"
|
||||
@@ -79,6 +118,15 @@ version = "2.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byte-slice-cast"
|
||||
version = "1.2.3"
|
||||
@@ -103,6 +151,16 @@ version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "codespan-reporting"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
|
||||
dependencies = [
|
||||
"termcolor",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cortex-m"
|
||||
version = "0.7.7"
|
||||
@@ -136,6 +194,24 @@ dependencies = [
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc-any"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a62ec9ff5f7965e4d7280bd5482acd20aadb50d632cf6c1d74493856b011fa73"
|
||||
dependencies = [
|
||||
"debug-helper",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "critical-section"
|
||||
version = "1.2.0"
|
||||
@@ -261,6 +337,12 @@ dependencies = [
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "debug-helper"
|
||||
version = "0.3.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f578e8e2c440e7297e008bb5486a3a8a194775224bbc23729b0dbdfaeebf162e"
|
||||
|
||||
[[package]]
|
||||
name = "defmt"
|
||||
version = "0.3.100"
|
||||
@@ -329,6 +411,7 @@ version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
@@ -372,7 +455,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "donglora"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cortex-m",
|
||||
@@ -385,6 +468,7 @@ dependencies = [
|
||||
"embassy-executor",
|
||||
"embassy-futures",
|
||||
"embassy-nrf",
|
||||
"embassy-rp",
|
||||
"embassy-sync 0.8.0",
|
||||
"embassy-time",
|
||||
"embassy-usb",
|
||||
@@ -402,11 +486,18 @@ dependencies = [
|
||||
"minijinja",
|
||||
"owo-colors",
|
||||
"panic-probe",
|
||||
"portable-atomic",
|
||||
"ssd1306",
|
||||
"static_cell",
|
||||
"ucobs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
|
||||
[[package]]
|
||||
name = "embassy-embedded-hal"
|
||||
version = "0.5.0"
|
||||
@@ -486,6 +577,9 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95285007a91b619dc9f26ea8f55452aa6c60f7115a4edc05085cd2bd3127cd7a"
|
||||
dependencies = [
|
||||
"cortex-m",
|
||||
"critical-section",
|
||||
"defmt 1.0.1",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
@@ -560,6 +654,45 @@ dependencies = [
|
||||
"rand_core 0.9.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "embassy-rp"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af8d5ac11a8bc209d359ad98bb10a10f786471dd474790f3a4f991c77ae94f6f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cortex-m",
|
||||
"cortex-m-rt",
|
||||
"critical-section",
|
||||
"defmt 1.0.1",
|
||||
"document-features",
|
||||
"embassy-embedded-hal 0.5.0",
|
||||
"embassy-futures",
|
||||
"embassy-hal-internal 0.3.0",
|
||||
"embassy-sync 0.7.2",
|
||||
"embassy-time",
|
||||
"embassy-time-driver",
|
||||
"embassy-time-queue-utils",
|
||||
"embassy-usb-driver",
|
||||
"embedded-hal 0.2.7",
|
||||
"embedded-hal 1.0.0",
|
||||
"embedded-hal-async",
|
||||
"embedded-hal-nb",
|
||||
"embedded-io 0.6.1",
|
||||
"embedded-io-async 0.6.1",
|
||||
"embedded-storage",
|
||||
"embedded-storage-async",
|
||||
"fixed",
|
||||
"nb 1.1.0",
|
||||
"pio",
|
||||
"rand_core 0.6.4",
|
||||
"rand_core 0.9.5",
|
||||
"rp-pac",
|
||||
"rp2040-boot2",
|
||||
"sha2-const-stable",
|
||||
"smart-leds",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "embassy-sync"
|
||||
version = "0.6.2"
|
||||
@@ -735,6 +868,16 @@ dependencies = [
|
||||
"embedded-hal 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "embedded-hal-nb"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fba4268c14288c828995299e59b12babdbe170f6c6d73731af1b4648142e8605"
|
||||
dependencies = [
|
||||
"embedded-hal 1.0.0",
|
||||
"nb 1.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "embedded-io"
|
||||
version = "0.6.1"
|
||||
@@ -780,6 +923,15 @@ dependencies = [
|
||||
"embedded-storage",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ena"
|
||||
version = "0.14.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eabffdaee24bd1bf95c5ef7cec31260444317e72ea56c4c91750e8b7ee58d5f1"
|
||||
dependencies = [
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "encode_unicode"
|
||||
version = "0.3.6"
|
||||
@@ -1086,6 +1238,12 @@ dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fixedbitset"
|
||||
version = "0.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
|
||||
|
||||
[[package]]
|
||||
name = "float-cmp"
|
||||
version = "0.9.0"
|
||||
@@ -1254,18 +1412,83 @@ dependencies = [
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "keccak"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653"
|
||||
dependencies = [
|
||||
"cpufeatures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lalrpop"
|
||||
version = "0.22.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba4ebbd48ce411c1d10fb35185f5a51a7bfa3d8b24b4e330d30c9e3a34129501"
|
||||
dependencies = [
|
||||
"ascii-canvas",
|
||||
"bit-set",
|
||||
"ena",
|
||||
"itertools",
|
||||
"lalrpop-util",
|
||||
"petgraph",
|
||||
"pico-args",
|
||||
"regex",
|
||||
"regex-syntax",
|
||||
"sha3",
|
||||
"string_cache",
|
||||
"term",
|
||||
"unicode-xid",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lalrpop-util"
|
||||
version = "0.22.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5baa5e9ff84f1aefd264e6869907646538a52147a755d494517a8007fb48733"
|
||||
dependencies = [
|
||||
"regex-automata",
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.184"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af"
|
||||
|
||||
[[package]]
|
||||
name = "litrs"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
|
||||
dependencies = [
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.29"
|
||||
@@ -1347,6 +1570,12 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d"
|
||||
|
||||
[[package]]
|
||||
name = "new_debug_unreachable"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
|
||||
|
||||
[[package]]
|
||||
name = "nrf-pac"
|
||||
version = "0.3.0"
|
||||
@@ -1366,6 +1595,27 @@ dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26"
|
||||
dependencies = [
|
||||
"num_enum_derive",
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum_derive"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.37.3"
|
||||
@@ -1397,23 +1647,128 @@ dependencies = [
|
||||
"defmt 1.0.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "petgraph"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772"
|
||||
dependencies = [
|
||||
"fixedbitset",
|
||||
"indexmap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
|
||||
dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pico-args"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||
|
||||
[[package]]
|
||||
name = "pio"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0ba4153cee9585abc451271aa437d9e8defdea8b468d48ba6b8f098cbe03d7f"
|
||||
dependencies = [
|
||||
"pio-core",
|
||||
"pio-proc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pio-core"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61d90fddc3d67f21bbf93683bc461b05d6a29c708caf3ffb79947d7ff7095406"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"num_enum",
|
||||
"paste",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pio-parser"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "825266c1eaddf54f636d06eefa4bf3c99d774c14ec46a4a6c6e5128a0f10d205"
|
||||
dependencies = [
|
||||
"lalrpop",
|
||||
"lalrpop-util",
|
||||
"pio-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pio-proc"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed4a76571f5fe51af43cc80ac870fe0c79cc0cdd686b9002a6c4c84bfdd0176b"
|
||||
dependencies = [
|
||||
"codespan-reporting",
|
||||
"lalrpop-util",
|
||||
"pio-core",
|
||||
"pio-parser",
|
||||
"proc-macro-error2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.117",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
|
||||
dependencies = [
|
||||
"critical-section",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "precomputed-hash"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
@@ -1517,6 +1872,50 @@ version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
||||
|
||||
[[package]]
|
||||
name = "rgb"
|
||||
version = "0.8.53"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4"
|
||||
|
||||
[[package]]
|
||||
name = "riscv"
|
||||
version = "0.15.0"
|
||||
@@ -1576,6 +1975,25 @@ version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1376b15f3ff160e9b1e8ea564ce427f2f6fcf77528cc0a8bf405cb476f9cea7"
|
||||
|
||||
[[package]]
|
||||
name = "rp-pac"
|
||||
version = "7.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8af65855c40b2c35079514c5489abffc0429347fef25d8467ff98ad84b4322d3"
|
||||
dependencies = [
|
||||
"cortex-m",
|
||||
"cortex-m-rt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rp2040-boot2"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c92f344f63f950ee36cf4080050e4dce850839b9175da38f9d2ffb69b4dbb21"
|
||||
dependencies = [
|
||||
"crc-any",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.2.3"
|
||||
@@ -1597,6 +2015,21 @@ version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "semihosting"
|
||||
version = "0.1.25"
|
||||
@@ -1661,6 +2094,52 @@ dependencies = [
|
||||
"unsafe-libyaml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2-const-stable"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f179d4e11094a893b82fff208f74d448a7512f99f5a0acbd5c679b705f83ed9"
|
||||
|
||||
[[package]]
|
||||
name = "sha3"
|
||||
version = "0.10.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60"
|
||||
dependencies = [
|
||||
"digest",
|
||||
"keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "smart-leds"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66df34e571fa9993fa6f99131a374d58ca3d694b75f9baac93458fe0d6057bf0"
|
||||
dependencies = [
|
||||
"smart-leds-trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smart-leds-trait"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7f4441a131924d58da6b83a7ad765c460e64630cce504376c3a87a2558c487f"
|
||||
dependencies = [
|
||||
"rgb",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "somni-expr"
|
||||
version = "0.2.0"
|
||||
@@ -1716,6 +2195,18 @@ dependencies = [
|
||||
"portable-atomic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "string_cache"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f"
|
||||
dependencies = [
|
||||
"new_debug_unreachable",
|
||||
"parking_lot",
|
||||
"phf_shared",
|
||||
"precomputed-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
@@ -1765,6 +2256,15 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "term"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8c27177b12a6399ffc08b98f76f7c9a1f4fe9fc967c784c5a071fa8d93cf7e1"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.4.1"
|
||||
@@ -1854,6 +2354,18 @@ version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||
|
||||
[[package]]
|
||||
name = "unsafe-libyaml"
|
||||
version = "0.2.11"
|
||||
@@ -1932,6 +2444,16 @@ dependencies = [
|
||||
"vcell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.11"
|
||||
|
||||
+26
-4
@@ -3,7 +3,7 @@ members = ["."]
|
||||
|
||||
[package]
|
||||
name = "donglora"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
edition = "2021"
|
||||
build = "build.rs"
|
||||
authors = ["Stephen Waits <steve@waits.net>"]
|
||||
@@ -32,6 +32,7 @@ embassy-usb-driver = "0.2"
|
||||
ucobs = { version = "0.3", features = ["legacy-msrv"] }
|
||||
heapless = "0.9"
|
||||
lora-phy = "3.0"
|
||||
portable-atomic = { version = "1", optional = true, default-features = false, features = ["critical-section"] }
|
||||
static_cell = "2.1"
|
||||
|
||||
# Display rendering
|
||||
@@ -63,8 +64,10 @@ esp-rtos = { version = "0.2", optional = true, features = [
|
||||
] }
|
||||
|
||||
# nRF52840 (RAK 4631)
|
||||
cortex-m = { version = "0.7", optional = true, features = ["critical-section-single-core"] }
|
||||
cortex-m-rt = { version = "0.7", optional = true, features = ["set-vtor"] }
|
||||
cortex-m = { version = "0.7", optional = true, features = [
|
||||
"critical-section-single-core",
|
||||
] }
|
||||
cortex-m-rt = { version = "0.7", optional = true }
|
||||
defmt-rtt = { version = "1.0", optional = true }
|
||||
embassy-nrf = { version = "0.10", optional = true, features = [
|
||||
"nrf52840",
|
||||
@@ -74,6 +77,13 @@ embassy-nrf = { version = "0.10", optional = true, features = [
|
||||
] }
|
||||
panic-probe = { version = "1.0", optional = true, features = ["print-defmt"] }
|
||||
|
||||
# RP2040 (Waveshare RP2040-LoRa)
|
||||
embassy-rp = { version = "0.9", optional = true, features = [
|
||||
"rp2040",
|
||||
"time-driver",
|
||||
"defmt",
|
||||
] }
|
||||
|
||||
[build-dependencies]
|
||||
minijinja = "2.18"
|
||||
owo-colors = "4.3"
|
||||
@@ -100,6 +110,7 @@ heltec_v4 = [
|
||||
rak_wisblock_4631 = [
|
||||
"dep:cortex-m",
|
||||
"dep:cortex-m-rt",
|
||||
"cortex-m-rt/set-vtor",
|
||||
"dep:defmt-rtt",
|
||||
"dep:embassy-nrf",
|
||||
"dep:panic-probe",
|
||||
@@ -109,12 +120,23 @@ rak_wisblock_4631 = [
|
||||
wio_tracker_l1 = [
|
||||
"dep:cortex-m",
|
||||
"dep:cortex-m-rt",
|
||||
"cortex-m-rt/set-vtor",
|
||||
"dep:defmt-rtt",
|
||||
"dep:embassy-nrf",
|
||||
"dep:panic-probe",
|
||||
"embassy-executor/arch-cortex-m",
|
||||
"embassy-executor/executor-thread",
|
||||
]
|
||||
waveshare_rp2040_lora = [
|
||||
"dep:cortex-m",
|
||||
"dep:cortex-m-rt",
|
||||
"dep:defmt-rtt",
|
||||
"dep:embassy-rp",
|
||||
"dep:panic-probe",
|
||||
"dep:portable-atomic",
|
||||
"embassy-executor/arch-cortex-m",
|
||||
"embassy-executor/executor-thread",
|
||||
]
|
||||
|
||||
[lints.clippy]
|
||||
undocumented_unsafe_blocks = "deny"
|
||||
@@ -122,6 +144,6 @@ undocumented_unsafe_blocks = "deny"
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
lto = "fat"
|
||||
opt-level = 3 # "s"/"z" break Xtensa (l32r literal range exceeded)
|
||||
opt-level = 3 # "s"/"z" break Xtensa (l32r literal range exceeded)
|
||||
panic = "abort"
|
||||
strip = true
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Stephen Waits <steve@waits.net>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -24,6 +24,7 @@ All commands are run from the `firmware/` directory.
|
||||
| Heltec V3 (UART) | `heltec_v3_uart` | ESP32-S3 | SX1262 | CP2102 bridge | xtensa-esp32s3-none-elf |
|
||||
| Heltec V4 | `heltec_v4` | ESP32-S3 | SX1262 | Native CDC-ACM | xtensa-esp32s3-none-elf |
|
||||
| RAK WisBlock 4631 | `rak_wisblock_4631` | nRF52840 | SX1262 | Native CDC-ACM | thumbv7em-none-eabihf |
|
||||
| Waveshare RP2040-LoRa | `waveshare_rp2040_lora` | RP2040 | SX1262 | Native CDC-ACM | thumbv6m-none-eabi |
|
||||
| Wio Tracker L1 | `wio_tracker_l1` | nRF52840 | SX1262 | Native CDC-ACM | thumbv7em-none-eabihf |
|
||||
|
||||
### Heltec V3 USB Note
|
||||
|
||||
@@ -108,14 +108,20 @@ fn main() {
|
||||
println!("cargo:rerun-if-changed=src/board/mod.rs.j2");
|
||||
println!("cargo:rerun-if-changed={}", board_dir);
|
||||
|
||||
// cortex-m-rt's link.x does `INCLUDE memory.x`. Copy our nRF layout into
|
||||
// OUT_DIR so it's only visible to ARM builds.
|
||||
// cortex-m-rt's link.x does `INCLUDE memory.x`. Copy the correct layout
|
||||
// into OUT_DIR based on the target MCU family.
|
||||
let target = std::env::var("TARGET").unwrap_or_default();
|
||||
if !target.starts_with("xtensa") {
|
||||
let memory_x = match target.as_str() {
|
||||
"thumbv7em-none-eabihf" => Some("ld/nrf52840-memory.x"),
|
||||
"thumbv6m-none-eabi" => Some("ld/rp2040-memory.x"),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(src) = memory_x {
|
||||
let out_dir = std::env::var("OUT_DIR").unwrap();
|
||||
fs::copy("ld/nrf52840-memory.x", format!("{out_dir}/memory.x"))
|
||||
.expect("failed to copy ld/nrf52840-memory.x");
|
||||
fs::copy(src, format!("{out_dir}/memory.x"))
|
||||
.expect("failed to copy memory.x");
|
||||
println!("cargo:rustc-link-search={out_dir}");
|
||||
}
|
||||
println!("cargo:rerun-if-changed=ld/nrf52840-memory.x");
|
||||
println!("cargo:rerun-if-changed=ld/rp2040-memory.x");
|
||||
}
|
||||
@@ -8,12 +8,13 @@ heltec_v3_uart := "heltec_v3_uart xtensa-esp32s3-none-elf esp32s3"
|
||||
heltec_v4 := "heltec_v4 xtensa-esp32s3-none-elf esp32s3"
|
||||
rak_wisblock_4631 := "rak_wisblock_4631 thumbv7em-none-eabihf nRF52840_xxAA"
|
||||
wio_tracker_l1 := "wio_tracker_l1 thumbv7em-none-eabihf nRF52840_xxAA"
|
||||
waveshare_rp2040_lora := "waveshare_rp2040_lora thumbv6m-none-eabi rp2040"
|
||||
|
||||
builds_dir := "builds"
|
||||
version := `sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml`
|
||||
|
||||
# All known boards
|
||||
boards := "heltec_v3 heltec_v3_uart heltec_v4 rak_wisblock_4631 wio_tracker_l1"
|
||||
boards := "heltec_v3 heltec_v3_uart heltec_v4 rak_wisblock_4631 waveshare_rp2040_lora wio_tracker_l1"
|
||||
|
||||
# Install all required tools and toolchains
|
||||
setup:
|
||||
@@ -28,7 +29,8 @@ setup:
|
||||
@if ! rustup component list --toolchain nightly --installed 2>/dev/null | grep -q "^rust-src"; then \
|
||||
rustup component add rust-src --toolchain nightly; \
|
||||
fi
|
||||
@# ARM target
|
||||
@# ARM targets
|
||||
@rustup target list --installed | grep -q "^thumbv6m-none-eabi$" || rustup target add thumbv6m-none-eabi
|
||||
@rustup target list --installed | grep -q "^thumbv7em-none-eabihf$" || rustup target add thumbv7em-none-eabihf
|
||||
|
||||
# Build release firmware for all boards, installing toolchains as needed
|
||||
@@ -176,6 +178,16 @@ _copy_firmware board profile:
|
||||
if [ -f "$src" ]; then \
|
||||
cp "$src" "$dst.elf"; echo "→ $dst.elf"; \
|
||||
case "$target" in \
|
||||
xtensa-*) \
|
||||
mise exec -- espflash save-image --chip "$chip" --merge --skip-padding "$src" "$dst.bin"; \
|
||||
echo "→ $dst.bin"; \
|
||||
;; \
|
||||
thumbv6m-*) \
|
||||
mise exec -- rust-objcopy -O ihex "$src" "$dst.hex"; \
|
||||
mise exec -- cargo-hex-to-uf2 hex-to-uf2 --input-path "$dst.hex" --output-path "$dst.uf2" --family rp2040; \
|
||||
rm "$dst.hex"; \
|
||||
echo "→ $dst.uf2"; \
|
||||
;; \
|
||||
thumbv7em-*) \
|
||||
mise exec -- rust-objcopy -O ihex "$src" "$dst.hex"; \
|
||||
mise exec -- cargo-hex-to-uf2 hex-to-uf2 --input-path "$dst.hex" --output-path "$dst.uf2" --family nrf52840; \
|
||||
@@ -207,5 +219,6 @@ _info name:
|
||||
elif [ "{{name}}" == "heltec_v3_uart" ]; then echo "{{heltec_v3_uart}}"; \
|
||||
elif [ "{{name}}" == "heltec_v4" ]; then echo "{{heltec_v4}}"; \
|
||||
elif [ "{{name}}" == "rak_wisblock_4631" ]; then echo "{{rak_wisblock_4631}}"; \
|
||||
elif [ "{{name}}" == "waveshare_rp2040_lora" ]; then echo "{{waveshare_rp2040_lora}}"; \
|
||||
elif [ "{{name}}" == "wio_tracker_l1" ]; then echo "{{wio_tracker_l1}}"; \
|
||||
else echo "Unknown board: {{name}}" >&2; exit 1; fi
|
||||
@@ -0,0 +1,24 @@
|
||||
/* RP2040 memory layout — UF2 bootloader (ROM boot2)
|
||||
*
|
||||
* 0x10000000 – 0x10000100 boot2 (second-stage bootloader, 256 bytes)
|
||||
* 0x10000100 – 0x10200000 Application <- we live here
|
||||
*
|
||||
* Flash: 2MB (W25Q16JV on Waveshare RP2040-LoRa)
|
||||
* RAM: 264KB SRAM (two 256KB banks + 2x 4KB scratch)
|
||||
*/
|
||||
|
||||
MEMORY
|
||||
{
|
||||
BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100
|
||||
FLASH : ORIGIN = 0x10000100, LENGTH = 2048K - 0x100
|
||||
RAM : ORIGIN = 0x20000000, LENGTH = 264K
|
||||
}
|
||||
|
||||
EXTERN(BOOT2_FIRMWARE)
|
||||
|
||||
SECTIONS {
|
||||
.boot2 ORIGIN(BOOT2) :
|
||||
{
|
||||
KEEP(*(.boot2));
|
||||
} > BOOT2
|
||||
} INSERT BEFORE .text;
|
||||
@@ -7,6 +7,7 @@
|
||||
Create `src/board/your_board.rs`. Use an existing board as a template:
|
||||
- ESP32-S3 boards: copy `heltec_v4.rs` (uses shared helpers from `board/esp32s3.rs` and `hal/esp32s3.rs`)
|
||||
- nRF52840 boards: copy `rak_wisblock_4631.rs` (uses shared helpers from `hal/nrf52840.rs`)
|
||||
- RP2040 boards: copy `waveshare_rp2040_lora.rs` (uses shared helpers from `hal/rp2040.rs`)
|
||||
- New MCU family: create a new `hal/<mcu>.rs` with MCU primitives first
|
||||
|
||||
`build.rs` auto-discovers your board file — any `.rs` file in `src/board/` that
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
use embassy_embedded_hal::shared_bus::asynch::spi::SpiDevice;
|
||||
use embassy_rp::gpio::{Input, Level, Output, Pull};
|
||||
use embassy_rp::spi;
|
||||
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
||||
use embassy_time::Delay;
|
||||
|
||||
use lora_phy::iv::GenericSx126xInterfaceVariant;
|
||||
use lora_phy::sx126x::{self, Sx1262, Sx126x};
|
||||
|
||||
use super::traits::{BoardParts, LoRaBoard};
|
||||
use crate::hal::rp2040 as mcu;
|
||||
|
||||
// ── Concrete peripheral types ──────────────────────────────────────
|
||||
|
||||
type Nss = Output<'static>;
|
||||
type Iv = GenericSx126xInterfaceVariant<Output<'static>, Input<'static>>;
|
||||
type RadioSpiDevice = SpiDevice<'static, NoopRawMutex, mcu::SpiBus, Nss>;
|
||||
pub type RadioDriver = Sx126x<RadioSpiDevice, Iv, Sx1262>;
|
||||
pub type UsbDriver = mcu::UsbRpDriver;
|
||||
pub type LedDriver = crate::driver::simple_led::SimpleLed<Output<'static>>;
|
||||
|
||||
// Display types — never instantiated (no OLED on this board), but the
|
||||
// trait requires concrete associated types that satisfy the bounds.
|
||||
pub type DisplayI2c = embassy_rp::i2c::I2c<'static, embassy_rp::peripherals::I2C0, embassy_rp::i2c::Async>;
|
||||
pub type DisplayDriver = ssd1306::Ssd1306Async<
|
||||
ssd1306::prelude::I2CInterface<DisplayI2c>,
|
||||
ssd1306::size::DisplaySize128x64,
|
||||
ssd1306::mode::BufferedGraphicsModeAsync<ssd1306::size::DisplaySize128x64>,
|
||||
>;
|
||||
|
||||
// ── Peripheral bundles ─────────────────────────────────────────────
|
||||
|
||||
pub struct RadioParts {
|
||||
pub driver: RadioDriver,
|
||||
pub delay: Delay,
|
||||
}
|
||||
|
||||
pub struct UsbParts {
|
||||
pub driver: UsbDriver,
|
||||
}
|
||||
|
||||
pub struct DisplayParts {
|
||||
pub i2c: DisplayI2c,
|
||||
}
|
||||
|
||||
// ── Display init ───────────────────────────────────────────────────
|
||||
|
||||
pub async fn create_display(_i2c: DisplayI2c) -> Option<DisplayDriver> {
|
||||
None
|
||||
}
|
||||
|
||||
// ── Board init ─────────────────────────────────────────────────────
|
||||
|
||||
pub struct Board {
|
||||
p: embassy_rp::Peripherals,
|
||||
}
|
||||
|
||||
impl LoRaBoard for Board {
|
||||
const NAME: &'static str = "Waveshare RP2040-LoRa";
|
||||
const TX_POWER_RANGE: (i8, i8) = (-9, 22);
|
||||
|
||||
type RadioParts = RadioParts;
|
||||
type CommParts = UsbParts;
|
||||
type DisplayParts = DisplayParts;
|
||||
type DisplayDriver = DisplayDriver;
|
||||
type LedDriver = LedDriver;
|
||||
|
||||
fn init() -> Self {
|
||||
let p = embassy_rp::init(Default::default());
|
||||
Self { p }
|
||||
}
|
||||
|
||||
fn mac_address() -> [u8; 6] {
|
||||
mcu::mac_address()
|
||||
}
|
||||
|
||||
fn into_parts(self) -> BoardParts<RadioParts, UsbParts, DisplayParts, LedDriver> {
|
||||
let p = self.p;
|
||||
|
||||
// ── MAC address from flash unique ID ───────────────────
|
||||
let mut flash = embassy_rp::flash::Flash::<_, _, { 2 * 1024 * 1024 }>::new_blocking(p.FLASH);
|
||||
mcu::init_mac(&mut flash);
|
||||
|
||||
// ── SPI1 bus for SX1262 ────────────────────────────────
|
||||
//
|
||||
// Pin mapping from Meshtastic & MeshCore firmware (confirmed working):
|
||||
// SCK = GPIO14, MOSI = GPIO15, MISO = GPIO24, CS = GPIO13
|
||||
//
|
||||
// The Waveshare wiki (https://www.waveshare.com/wiki/RP2040-LoRa) lists
|
||||
// different pins (MOSI=11, MISO=8, CS=9) but those don't work in practice.
|
||||
// Both sets are valid SPI1 alternate functions on RP2040.
|
||||
let mut spi_cfg = spi::Config::default();
|
||||
spi_cfg.frequency = 1_000_000;
|
||||
let spi = spi::Spi::new(
|
||||
p.SPI1, p.PIN_14, p.PIN_15, p.PIN_24, p.DMA_CH0, p.DMA_CH1, spi_cfg,
|
||||
);
|
||||
let spi_bus = mcu::share_spi_bus(spi);
|
||||
|
||||
let nss = Output::new(p.PIN_13, Level::High);
|
||||
let spi_device = SpiDevice::new(spi_bus, nss);
|
||||
|
||||
let reset = Output::new(p.PIN_23, Level::High);
|
||||
let dio1 = Input::new(p.PIN_16, Pull::Down);
|
||||
let busy = Input::new(p.PIN_18, Pull::Down);
|
||||
|
||||
// PE4259 antenna switch: GPIO17 = !CTRL (rx_enable).
|
||||
// DIO2 is internally wired as CTRL (tx_enable), managed by the SX1262.
|
||||
let rx_enable = Output::new(p.PIN_17, Level::Low);
|
||||
let iv = GenericSx126xInterfaceVariant::new(reset, dio1, busy, Some(rx_enable), None)
|
||||
.expect("SX1262 interface init");
|
||||
|
||||
let sx_config = sx126x::Config {
|
||||
chip: Sx1262,
|
||||
tcxo_ctrl: None, // No TCXO on this board
|
||||
use_dcdc: true,
|
||||
rx_boost: false,
|
||||
};
|
||||
|
||||
let radio = RadioParts {
|
||||
driver: Sx126x::new(spi_device, iv, sx_config),
|
||||
delay: Delay,
|
||||
};
|
||||
|
||||
// ── USB ────────────────────────────────────────────────
|
||||
let host = UsbParts {
|
||||
driver: embassy_rp::usb::Driver::new(p.USB, mcu::Irqs),
|
||||
};
|
||||
|
||||
// ── LED on GPIO25 ──────────────────────────────────────
|
||||
let led_pin = Output::new(p.PIN_25, Level::Low);
|
||||
let led = crate::driver::simple_led::SimpleLed(led_pin);
|
||||
|
||||
BoardParts {
|
||||
radio,
|
||||
host,
|
||||
display: None,
|
||||
led,
|
||||
mac: Self::mac_address(),
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
-2
@@ -131,8 +131,15 @@ pub async fn display_task(
|
||||
};
|
||||
|
||||
let Some(mut display) = crate::board::create_display(parts.i2c).await else {
|
||||
defmt::error!("display init failed, giving up");
|
||||
return;
|
||||
defmt::error!("display init failed, running LED-only loop");
|
||||
loop {
|
||||
match display_commands.receive().await {
|
||||
DisplayCommand::Reset | DisplayCommand::Off => {
|
||||
led.set_rgb(0, 0, 0).await;
|
||||
}
|
||||
DisplayCommand::On => {}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut state = DisplayState::new();
|
||||
@@ -207,6 +214,7 @@ pub async fn display_task(
|
||||
}
|
||||
DisplayCommand::Reset => {
|
||||
state = DisplayState::new();
|
||||
state.disconnected = true;
|
||||
render::splash(&mut display, &board_info);
|
||||
let _ = display.flush().await;
|
||||
led.set_rgb(0, 0, 0).await;
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
#[cfg(feature = "wio_tracker_l1")]
|
||||
pub mod sh1106;
|
||||
|
||||
#[cfg(any(feature = "heltec_v3", feature = "heltec_v3_uart", feature = "heltec_v4", feature = "wio_tracker_l1"))]
|
||||
#[cfg(any(feature = "heltec_v3", feature = "heltec_v3_uart", feature = "heltec_v4", feature = "wio_tracker_l1", feature = "waveshare_rp2040_lora"))]
|
||||
pub mod simple_led;
|
||||
@@ -8,3 +8,6 @@ pub mod esp32s3;
|
||||
|
||||
#[cfg(any(feature = "rak_wisblock_4631", feature = "wio_tracker_l1"))]
|
||||
pub mod nrf52840;
|
||||
|
||||
#[cfg(feature = "waveshare_rp2040_lora")]
|
||||
pub mod rp2040;
|
||||
@@ -0,0 +1,76 @@
|
||||
//! RP2040 MCU initialization primitives.
|
||||
//!
|
||||
//! Provides shared boilerplate for RP2040 boards: interrupt bindings,
|
||||
//! type aliases, StaticCell bus wrapping, USB init, and MAC address reading.
|
||||
|
||||
use embassy_rp::bind_interrupts;
|
||||
use embassy_sync::blocking_mutex::raw::NoopRawMutex;
|
||||
use static_cell::StaticCell;
|
||||
|
||||
// ── Interrupts (shared by all RP2040 boards) ──────────────────────
|
||||
|
||||
bind_interrupts!(pub struct Irqs {
|
||||
USBCTRL_IRQ => embassy_rp::usb::InterruptHandler<embassy_rp::peripherals::USB>;
|
||||
});
|
||||
|
||||
// ── MCU-level types ────────────────────────────────────────────────
|
||||
|
||||
pub type SpiBus = embassy_rp::spi::Spi<'static, embassy_rp::peripherals::SPI1, embassy_rp::spi::Async>;
|
||||
pub type UsbRpDriver = embassy_rp::usb::Driver<'static, embassy_rp::peripherals::USB>;
|
||||
|
||||
// ── SPI bus sharing ────────────────────────────────────────────────
|
||||
|
||||
/// Wrap an initialized SPI peripheral in a shared bus (StaticCell + Mutex).
|
||||
pub fn share_spi_bus(
|
||||
spi: SpiBus,
|
||||
) -> &'static embassy_sync::mutex::Mutex<NoopRawMutex, SpiBus> {
|
||||
static SPI_BUS: StaticCell<embassy_sync::mutex::Mutex<NoopRawMutex, SpiBus>> =
|
||||
StaticCell::new();
|
||||
SPI_BUS.init(embassy_sync::mutex::Mutex::new(spi))
|
||||
}
|
||||
|
||||
// ── MAC address ────────────────────────────────────────────────────
|
||||
|
||||
/// Store for the MAC address derived from flash unique ID.
|
||||
/// Populated once during `init_mac()`, read by `mac_address()`.
|
||||
static MAC: StaticCell<[u8; 6]> = StaticCell::new();
|
||||
static mut MAC_REF: Option<&'static [u8; 6]> = None;
|
||||
|
||||
/// Read the flash chip's unique ID and derive a 6-byte MAC address.
|
||||
///
|
||||
/// Must be called once during board init before `mac_address()`.
|
||||
pub fn init_mac<const FLASH_SIZE: usize>(
|
||||
flash: &mut embassy_rp::flash::Flash<'_, embassy_rp::peripherals::FLASH, embassy_rp::flash::Blocking, FLASH_SIZE>,
|
||||
) {
|
||||
let mut uid = [0u8; 8];
|
||||
flash.blocking_unique_id(&mut uid).expect("flash unique ID");
|
||||
|
||||
// XOR the first 2 bytes into bytes 2..8 to fold 8 bytes into 6,
|
||||
// then set the locally-administered bit (byte 0, bit 1).
|
||||
let mac = [
|
||||
uid[2] ^ uid[0] | 0x02,
|
||||
uid[3] ^ uid[1],
|
||||
uid[4],
|
||||
uid[5],
|
||||
uid[6],
|
||||
uid[7],
|
||||
];
|
||||
|
||||
let stored = MAC.init(mac);
|
||||
// SAFETY: Written once during single-threaded init, before the executor
|
||||
// starts. All subsequent reads are from &'static reference.
|
||||
unsafe {
|
||||
MAC_REF = Some(stored);
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the MAC address derived from the flash unique ID.
|
||||
///
|
||||
/// Panics if `init_mac()` has not been called.
|
||||
pub fn mac_address() -> [u8; 6] {
|
||||
// SAFETY: MAC_REF is set once during init before the executor starts,
|
||||
// and only read (never written) after that.
|
||||
unsafe {
|
||||
*MAC_REF.expect("init_mac() must be called before mac_address()")
|
||||
}
|
||||
}
|
||||
+6
-2
@@ -8,6 +8,7 @@ use embassy_usb::Builder;
|
||||
use static_cell::StaticCell;
|
||||
|
||||
use crate::channel::{CommandChannel, DisplayCommand, DisplayCommandChannel, ResponseChannel};
|
||||
use crate::protocol::Command;
|
||||
use super::framing::{self, CobsDecoder, MAX_FRAME};
|
||||
|
||||
const MAX_PACKET: usize = 64;
|
||||
@@ -126,8 +127,11 @@ async fn protocol_loop<'d, D: embassy_usb_driver::Driver<'d>>(
|
||||
}
|
||||
|
||||
let connected = receiver.dtr();
|
||||
if was_connected && !connected && has_display {
|
||||
display_commands.send(DisplayCommand::Reset).await;
|
||||
if was_connected && !connected {
|
||||
let _ = commands.try_send(Command::StopRx);
|
||||
if has_display {
|
||||
display_commands.send(DisplayCommand::Reset).await;
|
||||
}
|
||||
}
|
||||
if !was_connected && connected && has_display {
|
||||
display_commands.send(DisplayCommand::On).await;
|
||||
|
||||
+2
-2
@@ -41,7 +41,7 @@ use crate::channel::{CommandChannel, DisplayCommandChannel, ResponseChannel, Sta
|
||||
|
||||
#[cfg(not(test))]
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(any(feature = "rak_wisblock_4631", feature = "wio_tracker_l1"))] {
|
||||
if #[cfg(any(feature = "rak_wisblock_4631", feature = "wio_tracker_l1", feature = "waveshare_rp2040_lora"))] {
|
||||
use defmt_rtt as _;
|
||||
use panic_probe as _;
|
||||
} else if #[cfg(any(feature = "heltec_v3", feature = "heltec_v3_uart", feature = "heltec_v4"))] {
|
||||
@@ -61,7 +61,7 @@ static DISPLAY_COMMANDS: DisplayCommandChannel = DisplayCommandChannel::new();
|
||||
|
||||
#[cfg(not(test))]
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(any(feature = "rak_wisblock_4631", feature = "wio_tracker_l1"))] {
|
||||
if #[cfg(any(feature = "rak_wisblock_4631", feature = "wio_tracker_l1", feature = "waveshare_rp2040_lora"))] {
|
||||
#[embassy_executor::main]
|
||||
async fn main(spawner: Spawner) {
|
||||
run(spawner).await;
|
||||
|
||||
+11
-6
@@ -113,19 +113,22 @@ pub async fn radio_task(
|
||||
// copy_len <= MAX_PAYLOAD == Vec capacity, so this cannot fail.
|
||||
let _ = payload.extend_from_slice(&rx_buf[..copy_len]);
|
||||
|
||||
responses
|
||||
.send(Response::RxPacket {
|
||||
if responses
|
||||
.try_send(Response::RxPacket {
|
||||
rssi: pkt_status.rssi,
|
||||
snr: pkt_status.snr,
|
||||
payload,
|
||||
})
|
||||
.await;
|
||||
.is_err()
|
||||
{
|
||||
warn!("RX packet dropped: response channel full");
|
||||
}
|
||||
|
||||
if let Err(e) = start_rx(&mut lora, &cfg).await {
|
||||
warn!("restart RX failed: {}", e);
|
||||
state.state = RadioState::Idle;
|
||||
status.sender().send(state.clone());
|
||||
responses.send(Response::Error(ErrorCode::RadioBusy)).await;
|
||||
let _ = responses.try_send(Response::Error(ErrorCode::RadioBusy));
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
@@ -133,7 +136,7 @@ pub async fn radio_task(
|
||||
if start_rx(&mut lora, &cfg).await.is_err() {
|
||||
state.state = RadioState::Idle;
|
||||
status.sender().send(state.clone());
|
||||
responses.send(Response::Error(ErrorCode::RadioBusy)).await;
|
||||
let _ = responses.try_send(Response::Error(ErrorCode::RadioBusy));
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -210,7 +213,9 @@ async fn handle_cmd(
|
||||
let _ = lora.enter_standby().await;
|
||||
state.state = RadioState::Idle;
|
||||
status.sender().send(state.clone());
|
||||
responses.send(Response::Ok).await;
|
||||
// try_send: StopRx may be sent internally on USB disconnect
|
||||
// when the host isn't draining responses.
|
||||
let _ = responses.try_send(Response::Ok);
|
||||
}
|
||||
Command::DisplayOn | Command::DisplayOff | Command::GetMac => {}
|
||||
Command::Transmit { config, payload } => {
|
||||
|
||||
Reference in new issue
Block a user