Skip to content

v0.2.1

Latest

Choose a tag to compare

@scholzri scholzri released this 02 Aug 13:55
· 1 commit to main since this release
b690adc

Diagnostics and build-integrity release. No user-facing behaviour changes on a healthy board.

LED rail divergence trap

A dark-strip episode was observed once on v0.2.0: all 83 LEDs went dark while the keyboard typed normally and SMP answered, a host-mode fill was accepted with zero light, and it stayed dark for hours before self-healing at a USB resume without the board rebooting.

rail_on in the render loop is loop-local, so anything that drops the LED rail (PC2) behind the loop's back is invisible to it and never re-powered: every frame then renders into an unpowered strip, which is silent, logless and dark. That matches the symptoms exactly, but the cause has not been identified and the fault has not recurred, so this ships as instrumentation plus a defensive self-heal rather than a fix.

Every frame now records what the loop believes alongside what the pin actually reads into the USB diagnostic ring (RGB_STATE, code 32), on any change plus a 30 minute keepalive. The ring is .noinit, survives a replug on battery, is readable over SMP, and is persisted to NVS the moment a divergence is seen, before the self-heal removes the only symptom:

reverse/tools/usb_diag.py            # live ring
reverse/tools/usb_diag.py --saved    # the copy persisted at fault time

On divergence the loop re-asserts the rail, restoring light in about 20 ms instead of leaving a dark keyboard. Recovery replays the full drive configuration (GPIO mode, output enable, level), not just the level, so it covers every state the trap can prove rather than only one of the three.

Thanks to @ecliptik for the episode capture, the trap, and the NVS durability work.

Build integrity

build.sh decided a patch was already applied using git apply --reverse --check. That test is invalid for a stacked series: once a later patch rewrites the same lines, the earlier one no longer reverses cleanly. Four of the thirteen patches re-applied and failed noisily on every build.

The noise was the smaller half of the problem. It was indistinguishable from a patch that genuinely did not apply, which is how the USB bind-window grace went missing from every build unnoticed until it was regenerated by hand. Patches are now identified by commit subject, which git am records verbatim and which survives later edits, and a patch that fails to apply now stops the build. Building without one silently produces firmware that is not the tree anyone reviewed.

patches/apply-patches.sh is removed. Nothing called it, it had not been updated since the initial release, and it covered only patches/zephyr, so running it after a west update would have applied 9 of 13 patches and produced a tree silently missing every ZMK patch.

USB workqueue stack

CONFIG_USB_WORKQUEUE_STACK_SIZE=2048, up from Zephyr's 1024 default. Every USB transfer completion runs on that thread, as do the CDC and SMP transport callbacks, and an overflow there would present exactly like the CDC wedge fixed in v0.2.0. Costs 1 KB of RAM.

Upgrading

mcumgr image list reports 0.2.1. Firmware behaviour on a healthy board is unchanged from v0.2.0, so this is optional unless you want the rail trap or are building from source.