Zephyr RTOS firmware for the Seeed XIAO nRF52840 (Zephyr board: xiao_ble),
built against Zephyr v4.4.0 via west.
- Zephyr SDK 1.0.1+ installed (cross-compiler toolchain + host tools)
- Python venv with
westinstalled, at the workspace root (one level above this repo) - Zephyr source + modules fetched into the workspace (
west update)
See west.yml for the pinned Zephyr revision and required modules
(cmsis_6, hal_nordic).
The Python venv lives in the workspace root (one level above this repo), so
from inside this repo activate it with ../.venv:
cd bug-hunter-controller
source ../.venv/bin/activate
west build -b xiao_ble .The prompt should now start with (.venv). Activation only applies to the
current terminal, so repeat it in each new shell — otherwise west will not
be found.
This reuses the existing build/ directory and only rebuilds what changed.
For a full clean rebuild, add -p always:
west build -p always -b xiao_ble .A successful build produces build/zephyr/zephyr.uf2.
- Enter bootloader mode — double-tap the reset button quickly (both presses within ~0.5s). If it doesn't take, unplug the USB cable, press and hold reset, plug the cable back in while still holding it, then release after ~1-2s.
- The board mounts as a USB mass-storage drive (may be labeled
XIAO-SENSEeven on a plain, non-Sense board — this is a known bootloader labeling quirk, not an indication of which variant you have). - Copy the firmware onto the mounted drive:
cp build/zephyr/zephyr.uf2 /media/$USER/XIAO-SENSE/ - The drive disappearing right after the copy is success, not an error — the bootloader flashes the file and auto-resets into the new application.
The console is native USB CDC-ACM (/dev/ttyACM0), not a physical UART
bridge. Every board reset tears down and recreates this device, so a plain
screen/minicom session will die on each reset/reflash. To get a monitor
that survives resets without manual reattachment:
while true; do
[ -e /dev/ttyACM0 ] && cat /dev/ttyACM0
sleep 0.3
doneCtrl-C to stop. If /dev/ttyACM0 is not accessible, add yourself to the
dialout group (sudo usermod -aG dialout $USER) and start a new session.
The firmware drives two outputs, switched by commands on the same USB serial port as the console:
| Output | Header pin | nRF52840 pin |
|---|---|---|
0 |
D1 | P0.03 |
1 |
D2 | P0.28 |
Each command is a 3-byte frame: 0x11, a mask/value byte (high nibble =
which outputs to change, low nibble = their new values), then the bitwise
inverse of that byte as a check. Invalid frames are silently dropped; there is
no reply.
Requires Python 3 and pyserial (pip install pyserial). Works on Windows and
Linux.
python tools/set_outputs.py --list # list serial ports
python tools/set_outputs.py COM5 all=on # D1 and D2 on
python tools/set_outputs.py COM5 all=off # both off
python tools/set_outputs.py COM5 0=on # D1 on, D2 unchanged
python tools/set_outputs.py COM5 0=on 1=off # D1 on, D2 offReplace COM5 with the board's port (on Linux, /dev/ttyACM0). Outputs not
named keep their current state. On Windows only one program can open a COM
port at a time, so close any serial monitor on that port first, or the script
fails with "Access is denied".