feat: defmt-over-RTT debug build + probe-rs flow
Add an opt-in `defmt` feature that swaps panic-reset for panic-probe, links defmt-rtt, and enables hsmc's `trace-defmt` so chart transitions print over RTT. Default builds are unchanged (still panic-reset, no defmt overhead). - Cargo.toml: `defmt` feature pulls defmt + defmt-rtt + panic-probe and forwards `trace-defmt` to hsmc; mirror-flag pattern matches the existing `embassy` feature. - build.rs: link `defmt.x` only when the feature is on. - src/main.rs: cfg-gate the panic handler swap, log boot, USB serial, kbd/mouse wake completion vs. deadline, and jiggle dx/dy. - main.rs: watchdog.pause_on_debug(true) so probe-rs halts don't trip the 8 s reset. - justfile: build-debug, debug, attach, probes recipes (release mode — debug builds overflow flash with defmt+panic-probe). - mise.toml: add cargo:probe-rs-tools.
This commit is contained in:
Generated
+93
-2
@@ -86,6 +86,12 @@ version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.11.1"
|
||||
@@ -262,6 +268,57 @@ version = "0.3.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f578e8e2c440e7297e008bb5486a3a8a194775224bbc23729b0dbdfaeebf162e"
|
||||
|
||||
[[package]]
|
||||
name = "defmt"
|
||||
version = "0.3.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad"
|
||||
dependencies = [
|
||||
"defmt 1.0.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "defmt"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "548d977b6da32fa1d1fda2876453da1e7df63ad0304c8b3dae4dbe7b96f39b78"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"defmt-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "defmt-macros"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d4fc12a85bcf441cfe44344c4b72d58493178ce635338a3f3b78943aceb258e"
|
||||
dependencies = [
|
||||
"defmt-parser",
|
||||
"proc-macro-error2",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "defmt-parser"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e"
|
||||
dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "defmt-rtt"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6eca0aae8aa2cf8333200ecbd236274697bc0a394765c858b3d9372eb1abcfa"
|
||||
dependencies = [
|
||||
"critical-section",
|
||||
"defmt 0.3.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
@@ -472,7 +529,7 @@ version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a25746d8b152b72fbf2a217f489a083dbbe243f281f09184a1f2cfbe9bbb245f"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.11.1",
|
||||
"embassy-futures",
|
||||
"embassy-net-driver-channel",
|
||||
"embassy-sync",
|
||||
@@ -722,6 +779,7 @@ version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "497ed9593e4baf6ef120b24aff80cb898f6832d32cdf01be9d90be0c06624e77"
|
||||
dependencies = [
|
||||
"defmt 0.3.100",
|
||||
"embassy-futures",
|
||||
"embassy-sync",
|
||||
"embassy-time",
|
||||
@@ -771,6 +829,8 @@ version = "0.2.0"
|
||||
dependencies = [
|
||||
"cortex-m",
|
||||
"cortex-m-rt",
|
||||
"defmt 0.3.100",
|
||||
"defmt-rtt",
|
||||
"embassy-executor",
|
||||
"embassy-futures",
|
||||
"embassy-rp",
|
||||
@@ -779,6 +839,7 @@ dependencies = [
|
||||
"embassy-usb",
|
||||
"hsmc",
|
||||
"libm",
|
||||
"panic-probe",
|
||||
"panic-reset",
|
||||
"portable-atomic",
|
||||
"smart-leds",
|
||||
@@ -960,6 +1021,16 @@ version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
|
||||
[[package]]
|
||||
name = "panic-probe"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4047d9235d1423d66cc97da7d07eddb54d4f154d6c13805c6d0793956f4f25b0"
|
||||
dependencies = [
|
||||
"cortex-m",
|
||||
"defmt 0.3.100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "panic-reset"
|
||||
version = "0.1.1"
|
||||
@@ -1150,7 +1221,7 @@ version = "0.5.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.11.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1420,6 +1491,26 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.9"
|
||||
|
||||
+27
-2
@@ -12,11 +12,31 @@ categories = ["embedded", "no-std"]
|
||||
publish = false # firmware binary; not a crates.io library
|
||||
|
||||
[features]
|
||||
default = ["embassy"]
|
||||
default = ["embassy", "panic-reset"]
|
||||
# `hsmc` emits `#[cfg(feature = "embassy")]` from inside `statechart!` and
|
||||
# the cfg is evaluated in this crate's context, so we mirror the flag here
|
||||
# and forward it to `hsmc/embassy`.
|
||||
embassy = ["hsmc/embassy"]
|
||||
# Default panic handler — silently resets the chip. Mutually exclusive with
|
||||
# `defmt` (which uses panic-probe). Build a defmt firmware with
|
||||
# `cargo build --no-default-features --features embassy,defmt`.
|
||||
panic-reset = ["dep:panic-reset"]
|
||||
# Same trick as `embassy` above: `hsmc` emits `#[cfg(feature = "trace-defmt")]`
|
||||
# from inside `__chart_observe!` (re-exported via `statechart!`), and that
|
||||
# cfg is evaluated in *this* crate's feature set, not hsmc's. So we mirror
|
||||
# the flag here and forward it to `hsmc/trace-defmt`. Without this mirror,
|
||||
# `--features defmt` would compile cleanly but emit zero state-transition
|
||||
# logs, because every defmt arm in __chart_observe! would be cfg'd out.
|
||||
trace-defmt = ["hsmc/trace-defmt"]
|
||||
# Debug build over RTT. Pulls in defmt + defmt-rtt, swaps the panic handler
|
||||
# for panic-probe (so panics get printed instead of silently resetting), and
|
||||
# turns on hsmc's chart/state/transition tracing.
|
||||
defmt = [
|
||||
"dep:defmt",
|
||||
"dep:defmt-rtt",
|
||||
"dep:panic-probe",
|
||||
"trace-defmt",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
embassy-executor = { version = "0.10.0", features = ["platform-cortex-m", "executor-thread"] }
|
||||
@@ -32,11 +52,16 @@ libm = "0.2"
|
||||
|
||||
cortex-m = { version = "0.7.6", features = ["inline-asm"] }
|
||||
cortex-m-rt = "0.7.5"
|
||||
panic-reset = "0.1"
|
||||
panic-reset = { version = "0.1", optional = true }
|
||||
static_cell = "2.1"
|
||||
# Cortex-M0+ has no hardware CAS; static_cell + heapless need this shim.
|
||||
portable-atomic = { version = "1.13", features = ["critical-section"] }
|
||||
|
||||
# Defmt stack — only linked when the `defmt` feature is enabled.
|
||||
defmt = { version = "0.3", optional = true }
|
||||
defmt-rtt = { version = "0.4", optional = true }
|
||||
panic-probe = { version = "0.3", features = ["print-defmt"], optional = true }
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(feature, values("tokio", "embassy"))'] }
|
||||
|
||||
|
||||
@@ -9,4 +9,11 @@ fn main() {
|
||||
println!("cargo:rustc-link-search={}", out.display());
|
||||
println!("cargo:rerun-if-changed=memory.x");
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
|
||||
// defmt ships its own linker script (`defmt.x`) that defines the
|
||||
// `_defmt_*` interner sections. Only link it when the feature is on —
|
||||
// otherwise the symbols don't exist and `ld` will refuse the build.
|
||||
if env::var_os("CARGO_FEATURE_DEFMT").is_some() {
|
||||
println!("cargo:rustc-link-arg=-Tdefmt.x");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ firmware := "jiggly"
|
||||
target := "thumbv6m-none-eabi"
|
||||
base_addr := "0x10000000"
|
||||
family_id := "0xE48BFF56"
|
||||
chip := "RP2040"
|
||||
|
||||
out_release := "target" / target / "release" / firmware
|
||||
out_bin := "target" / target / "release" / firmware + ".bin"
|
||||
@@ -99,6 +100,31 @@ flash: uf2
|
||||
echo "Timed out waiting for XIAO UF2 volume. Copy {{ out_uf2 }} manually." >&2
|
||||
exit 1
|
||||
|
||||
# Build the firmware with defmt-over-RTT enabled. Mutually exclusive with
|
||||
# the default `panic-reset` feature, so we drop default features.
|
||||
# DEFMT_LOG=trace overrides the `off` default in mise.toml so the macros
|
||||
# actually emit. Built in release mode because thumbv6m debug builds plus
|
||||
# defmt-rtt + panic-probe overflow flash quickly.
|
||||
build-debug:
|
||||
DEFMT_LOG=trace cargo build --release --no-default-features --features embassy,defmt
|
||||
|
||||
# Flash the defmt firmware via the attached SWD probe and tail RTT logs.
|
||||
# Plug the probe into the four SWD pads on the back of the Xiao
|
||||
# (SWCLK + SWDIO + GND — leave 3V3 disconnected so USB-C powers the board).
|
||||
# The Xiao's own USB-C can stay plugged into a separate host the whole time;
|
||||
# the SWD path and the USB path are independent.
|
||||
debug: build-debug
|
||||
probe-rs run --chip {{ chip }} {{ out_release }}
|
||||
|
||||
# Just tail RTT from a board that's already running the defmt firmware
|
||||
# (no flash). Useful for rejoining a session after Ctrl-C without a reset.
|
||||
attach:
|
||||
probe-rs attach --chip {{ chip }} {{ out_release }}
|
||||
|
||||
# List attached debug probes — sanity check that the DAPLink shows up.
|
||||
probes:
|
||||
probe-rs list
|
||||
|
||||
# Install the toolchain and cargo helpers declared in mise.toml.
|
||||
bootstrap:
|
||||
mise install
|
||||
|
||||
@@ -7,6 +7,7 @@ just = "latest"
|
||||
"cargo:cargo-watch" = "latest"
|
||||
"cargo:cargo-bloat" = "latest"
|
||||
"cargo:cargo-expand" = "latest"
|
||||
"cargo:probe-rs-tools" = "latest"
|
||||
|
||||
[env]
|
||||
CARGO_TARGET_DIR = "target"
|
||||
|
||||
+43
-3
@@ -24,10 +24,13 @@ use embassy_usb::{
|
||||
};
|
||||
use hsmc::{Duration, statechart};
|
||||
use libm::{cosf, powf, roundf, sinf};
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
use panic_reset as _;
|
||||
use smart_leds::RGB8;
|
||||
use static_cell::StaticCell;
|
||||
use usbd_hid::descriptor::{KeyboardReport, MouseReport, SerializedDescriptor};
|
||||
#[cfg(feature = "defmt")]
|
||||
use {defmt_rtt as _, panic_probe as _};
|
||||
|
||||
bind_interrupts!(struct Irqs {
|
||||
USBCTRL_IRQ => UsbInterruptHandler<USB>;
|
||||
@@ -291,11 +294,23 @@ impl JigglyActions for JigglyActionContext<'_> {
|
||||
}
|
||||
|
||||
async fn keyboard_wake(&mut self) {
|
||||
let _ = embassy_futures::select::select(
|
||||
let result = embassy_futures::select::select(
|
||||
wake_with_keyboard(&mut self.kbd),
|
||||
Timer::after(KBD_WAKE_DEADLINE),
|
||||
)
|
||||
.await;
|
||||
#[cfg(feature = "defmt")]
|
||||
match result {
|
||||
embassy_futures::select::Either::First(_) => defmt::info!("kbd wake: completed"),
|
||||
embassy_futures::select::Either::Second(_) => {
|
||||
defmt::warn!(
|
||||
"kbd wake: deadline hit ({} ms)",
|
||||
KBD_WAKE_DEADLINE.as_millis()
|
||||
)
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
let _ = result;
|
||||
// Belt-and-suspenders: always send an all-keys-released report,
|
||||
// even if the deadline preempted the loop *between* a key-down
|
||||
// and its key-up. Without this, a stuck modifier (Shift!) or key
|
||||
@@ -310,11 +325,21 @@ impl JigglyActions for JigglyActionContext<'_> {
|
||||
}
|
||||
|
||||
async fn mouse_wake(&mut self) {
|
||||
let _ = embassy_futures::select::select(
|
||||
let result = embassy_futures::select::select(
|
||||
wake_with_mouse(&mut self.mouse),
|
||||
Timer::after(MOUSE_WAKE_DEADLINE),
|
||||
)
|
||||
.await;
|
||||
#[cfg(feature = "defmt")]
|
||||
match result {
|
||||
embassy_futures::select::Either::First(_) => defmt::info!("mouse wake: completed"),
|
||||
embassy_futures::select::Either::Second(_) => defmt::warn!(
|
||||
"mouse wake: deadline hit ({} ms)",
|
||||
MOUSE_WAKE_DEADLINE.as_millis()
|
||||
),
|
||||
}
|
||||
#[cfg(not(feature = "defmt"))]
|
||||
let _ = result;
|
||||
}
|
||||
|
||||
async fn jiggle_pair(&mut self) {
|
||||
@@ -323,6 +348,8 @@ impl JigglyActions for JigglyActionContext<'_> {
|
||||
} else {
|
||||
(0, 1)
|
||||
};
|
||||
#[cfg(feature = "defmt")]
|
||||
defmt::info!("jiggle: dx={} dy={}", dx, dy);
|
||||
send_mouse(&mut self.mouse, dx, dy).await;
|
||||
Timer::after(PIXEL_DWELL).await;
|
||||
send_mouse(&mut self.mouse, -dx, -dy).await;
|
||||
@@ -649,7 +676,14 @@ async fn watchdog_task(mut wd: Watchdog) -> ! {
|
||||
async fn main(spawner: Spawner) {
|
||||
let p = embassy_rp::init(Default::default());
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
defmt::info!("jiggly v{} boot", env!("CARGO_PKG_VERSION"));
|
||||
|
||||
let mut watchdog = Watchdog::new(p.WATCHDOG);
|
||||
// Pause the countdown while a debugger has the core halted, so
|
||||
// breakpoints and single-stepping under `probe-rs` don't trip the
|
||||
// 8 s reset. Cheap and always-correct, so leave it on for release too.
|
||||
watchdog.pause_on_debug(true);
|
||||
watchdog.start(WATCHDOG_TIMEOUT);
|
||||
|
||||
// NeoPixel: GPIO11 powers it, GPIO12 is the WS2812 data line driven from
|
||||
@@ -679,7 +713,10 @@ async fn main(spawner: Spawner) {
|
||||
let mut config = UsbConfig::new(0x1209, 0xb0b0);
|
||||
config.manufacturer = Some("swaits.com");
|
||||
config.product = Some("jiggly");
|
||||
config.serial_number = Some(make_serial(p.FLASH));
|
||||
let serial = make_serial(p.FLASH);
|
||||
#[cfg(feature = "defmt")]
|
||||
defmt::info!("usb serial: {}", serial);
|
||||
config.serial_number = Some(serial);
|
||||
config.device_release = 0x0200; // matches firmware version 0.2.0
|
||||
config.max_power = 100;
|
||||
config.max_packet_size_0 = 64;
|
||||
@@ -731,6 +768,9 @@ async fn main(spawner: Spawner) {
|
||||
spawner.spawn(usb_task(usb).unwrap());
|
||||
spawner.spawn(watchdog_task(watchdog).unwrap());
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
defmt::info!("usb + watchdog tasks spawned, starting statechart");
|
||||
|
||||
static EVENT_CHAN: Channel<CriticalSectionRawMutex, Ev, 8> = Channel::new();
|
||||
|
||||
let ctx = Ctx {
|
||||
|
||||
Reference in New Issue
Block a user