Flashing¶
oxinode runs on a Base Duo that still has its factory bootloader. Nothing here needs a debug probe, and nothing here can overwrite the bootloader, so a bad flash is always recoverable with a double-tap of the reset button.
Prebuilt images¶
Every tagged release carries
.uf2 files for each image, the .elf files they were built from, and a
SHA256SUMS. Every CI run on main also uploads the same set as a build
artifact, which is the easier way to try an unreleased change.
| Image | What it is |
|---|---|
oxinode-rnode-*.uf2 |
The product image: RNode over USB and Bluetooth, the panel, the pad, the GPS |
oxinode-radio-*.uf2 |
A radio bring-up diagnostic with a serial console |
oxinode-usb-cdc-*.uf2 |
USB serial echo, to prove the USB path |
oxinode-blink-*.uf2 |
Blinks the green LED, to prove flashing |
See Firmware images for what each does.
Over the UF2 drive¶
- Double-tap the reset button. A drive named
muzi-Baseappears. - Copy the
.uf2onto it. The board reboots into the new firmware.
Not on macOS 26
The bootloader's drive is not a real FAT volume. It is a synthetic
filesystem that exists only to catch 512-byte UF2 blocks and write them
to flash. macOS 26 serves FAT through FSKit in userspace, and against a
shim like this the writes never reach the chip: the copy succeeds, the
file appears in ls, and the board keeps running its old firmware with
no error anywhere. Use serial DFU instead.
The bootloader also silently discards any UF2 block whose family id it does
not recognise, and then never reboots. This bootloader accepts 0x239A0081
(formed from the board's USB VID and PID) and 0xADA52840 (the generic
Adafruit nRF52840 id). It rejects the 0x1B57745F that the microsoft/uf2
registry lists for the nRF52840. A third id, 0xD663823C, rewrites the
bootloader itself; never emit it. tools/uf2-flash.sh reads the right id from
the board's own CURRENT.UF2 rather than trusting a constant.
Over serial DFU¶
This is the path the repository's cargo run uses, and the one that works
everywhere. It goes through the bootloader's SLIP-framed serial DFU interface
using adafruit-nrfutil, which tools/dfu-flash.sh bootstraps into
tools/.venv/ on first use if it is not on your PATH.
From a checkout, with the toolchain installed:
cargo run --release --bin blink
The product image carries the Bluetooth stack and needs its feature set:
cargo run --release --no-default-features --features ble --bin rnode
cargo run builds the ELF, checks it against memory.x, packages it, and
sends it down /dev/cu.usbmodem*. With two boards attached the runner refuses
to guess; name the port:
OXINODE_DFU_PORT=/dev/cu.usbmodemXXXX cargo run --release --no-default-features --features ble --bin rnode
To flash a prebuilt or previously built ELF directly:
tools/dfu-flash.sh target/thumbv7em-none-eabihf/release/rnode
Getting into the bootloader¶
The first time, double-tap the reset button. Once an image with USB serial is on the board, no button is needed: the runner performs a 1200-baud touch (opens the first serial port at 1200 baud and closes it), which every oxinode image with a serial port answers by rebooting into the bootloader. The firmware ignores the touch for the first two seconds after boot, because macOS caches the 1200-baud line setting per device path and would otherwise send a freshly flashed board straight back to its bootloader.
A board already sitting in its bootloader on a host that cannot mount the UF2 drive needs to be told so:
OXINODE_DFU_IN_DFU=1 tools/dfu-flash.sh target/thumbv7em-none-eabihf/release/rnode
Disconnect a phone first
Taking the 1200-baud touch on the product image while a phone is connected over Bluetooth has been seen to leave the board in its bootloader with serial DFU not answering. Disconnect the phone or reset the board before reflashing. See Known limitations.
Checking what is on the chip¶
The bootloader publishes the chip's current flash contents as CURRENT.UF2,
so "did that flash take?" has an answer. Double-tap reset, then:
tools/verify_flash.py target/thumbv7em-none-eabihf/release/rnode
It prints the vector table found on the board next to the one you built and
says MATCH or MISMATCH.
Recovery¶
Double-tap reset. The bootloader lives above the application region and no oxinode image can reach it. If an image panics, it reboots into the bootloader on its own, so a board that went quiet after a flash is usually already sitting in DFU waiting to be reflashed.
There is no DFU button on this board and never was: the bootloader's
BUTTON_1 and BUTTON_2 are both an unconnected pin. Double-tap reset and
the software path are the only two ways in.