The board¶
oxinode targets the muzi.works Base Duo with the Super IO expansion board attached, as shipped: factory UF2 bootloader, S140 SoftDevice in flash, Meshtastic replaced.
UF2 Bootloader 0.9.2-37-gf7fad14
Model: muzi Base
Board-ID: muzi-Base-Board
SoftDevice: S140 6.1.1
One module, not two chips¶
The nRF52840 and the LR1121 are not two chips on the PCB. U1 is an Elecrow nRFLR1121, an 80-pin 20 × 20 × 3.5 mm system-on-module containing both dies, with the SPI link between them routed inside the module. Three consequences run through the whole firmware:
- The nRF52840 GPIO numbers are fixed facts, not board choices. They terminate inside the module rather than on board copper.
- There is nothing to probe. No oscilloscope or logic analyser can see the SPI bus, NRESET or BUSY. Every radio failure has to be diagnosed from software, which is why the bring-up image reports evidence for every step and why every wait on the chip is bounded.
- Exactly three LR1121 pins leave the module:
LR_DIO9,LR_DIO8andLR_DIO7. The datasheet requiresLR_DIO9to be jumpered to an MCU GPIO, and the Base Duo jumpers it to P1.08 over a net namedIRQ_JUMPER. That is the interrupt line.
The module has a footprint-compatible sibling, the nRFLR1262 (nRF52840 +
SX1262), which is why Meshtastic's variant defines both USE_SX1262 and
USE_LR1121. On that module the SX1262's IRQ surfaces on P1.06 instead. The
firmware reads the chip's identity at boot and expects an LR1121, which is
what this board's schematic populates.
Three separate antenna ports leave the module: ANT_BLE, ANT_LORA_2.4G and
ANT_LoRa. The sub-GHz LoRa path has an SMA connector; the 2.4 GHz path a
separate u.FL; Bluetooth has its own path and never contends with the LoRa
switch.
flowchart TB
subgraph module["Elecrow nRFLR1121 module (U1)"]
direction LR
mcu["nRF52840"]
lr["LR1121"]
mcu -- "SPI (P1.12-P1.15)<br/>NRESET P1.10 · BUSY P1.11<br/>module-internal" --> lr
lr -- "DIO3 3.0 V" --> tcxo["32 MHz reference"]
lr -- "DIO5 / DIO6" --> sw["RF switch"]
end
lr -- "DIO9 → IRQ_JUMPER → P1.08" --> mcu
sw --- sma["sub-GHz SMA"]
lr --- ufl["2.4 GHz u.FL"]
mcu --- ble["BLE antenna"]
subgraph base["Base Duo"]
usb["USB-C · CDC-ACM"]
qspi["W25Q128 QSPI flash 16 MB"]
chg["BQ25185 charger"]
leds["LEDs: green P1.03 · blue P1.04 · red (charger)"]
lfxo["32.768 kHz crystal"]
end
subgraph superio["Super IO (25 castellations JC1-JC25)"]
oled["SH1107 OLED 128×128<br/>I²C P0.24/P0.25 · 12 V boost P0.23"]
pad["Navigation pad<br/>6 switches"]
gps["GNSS module<br/>UART P0.20/P0.19 · load switch P1.01"]
mode["Power OFF / ON / GPS ON switch<br/>P1.09 · P0.12"]
buzz["Buzzer rail (load switch P0.22)"]
end
mcu --- base
mcu --- superio
Pin map¶
Read from the board's Meshtastic variant definition, cross-checked against the
Rev 01 schematic (Base Duo [MH212A]) and the module datasheet. Where the
schematic and the variant disagree, the schematic wins.
| Function | Pin(s) | Notes |
|---|---|---|
| LED green | P1.03 | active low |
| LED blue | P1.04 | active low |
| LR1121 IRQ | P1.08 | from the module's LR_DIO9, over IRQ_JUMPER |
| LR1121 NRESET | P1.10 | module-internal |
| LR1121 BUSY | P1.11 | module-internal |
| LR1121 SPI NSS / SCK / MOSI / MISO | P1.12 / P1.13 / P1.14 / P1.15 | module-internal; SPIM2 |
| LR1121 TCXO | — | 3.0 V via DIO3, which therefore cannot serve as an IRQ |
| LR1121 RF switch | — | the chip's own DIO5/DIO6, set by an on-chip command, not MCU GPIO |
| OLED I²C SDA / SCL | P0.24 / P0.25 | SH1107 at 0x3C; 128 × 128, 1.12"; 5.1 kΩ pull-ups on board |
| OLED 12 V boost enable | P0.23 | must be driven high or the panel is dark |
| QSPI SCK / CS / IO0–3 | P0.03 / P0.26 / P0.30, P0.29, P0.28, P0.02 | W25Q128, 16 MB; not driven by oxinode |
| LF clock | — | external 32.768 kHz crystal (LFXO) |
| Navigation pad up / down / left / right | P0.21 / P0.17 / P1.05 / P0.16 | active low, internal pull-ups |
| Navigation pad OK / back | P0.10 / P0.15 | active low; P0.10 is an NFC pin |
| Power OFF / Power ON / GPS ON switch | P1.09 / P0.12 | P1.09 high in Power ON, P0.12 high in GPS ON, no pull; Power OFF cuts the board's power |
GPS load switch (GPS_EN) |
P1.01 | active high; gates the switched 3V3 rail the module lives on, 500 mA |
| GPS UART | P0.20 / P0.19 | the module transmits on P0.20 (nRF52840 RXD) and receives on P0.19; 9600 baud, 8N1 |
| Battery sense | P0.31 (AIN7) |
the cell through 806 kΩ / 1.5 MΩ, ratio 0.65048; SAADC at gain 1/6, 12-bit |
| Charger status | P1.02 | BQ25185 STAT2, open drain, low while charging; 100 kΩ pull-up on the board plus the internal one; also drives the red LED |
| Charger fault | P0.27 | BQ25185 STAT1, open drain, low on a fault; internal pull-up only |
| SWDIO / SWDCLK | — | test pads TP1 / TP2, no header |
Out of scope, recorded so nobody has to re-derive it: a second I²C bus on
P0.04/P0.06 carrying the IMU, an RX8130CE RTC at 0x32 and the Qwiic/STEMMA
QT connector (5.1 kΩ pull-ups on board).
Everything above the Base Duo itself (pad, buzzer, GPS, OLED, mode switch) lives on the Super IO and reaches it through 25 castellations carrying VBAT+, a solar input, the two switched rails, the second I²C bus, the OLED bus and eight general-purpose IOs.
Things to be careful about¶
Two pins are load-switch enables, not peripheral pins. P1.01 (GPS_EN)
and P0.22 (PIN_BUZZER) each drive an NMOS that gates a high-side PMOS
feeding a switched 3V3 rail out to the expansion connector. Both are active
high, and P0.22 powers the buzzer rather than sounding it. 500 mA per switch,
600 mA total on the 3.3 V rail.
The GPS UART is named from opposite ends in the two sources. The
schematic labels P0.20 UART_GPS_TX (the net, from the module's side); the
variant declares GPS_RX_PIN P0.20 (the pin, from the MCU's side). They
agree: the module's TX arrives on P0.20. The firmware still probes both orders
rather than trusting either name. See GPS.
It is a navigation pad, not a trackball. Meshtastic declares
HAS_TRACKBALL and names the lines TB_*, but the Super IO has six discrete
switches. The pin numbers are the same either way; the driver is not. A
trackball emits a burst of edges and is read by counting them; a pad emits one
edge and then a level, and wants debouncing and auto-repeat. See
Pad and mode switch.
P0.10 is an NFC pin. It carries the pad's OK switch and the user button
(SW1, active low, 100 kΩ pull-up). NFC pins only work as GPIO once the
PROTECT bit of UICR.NFCPINS is cleared, a non-volatile write. The firmware
builds embassy-nrf with nfc-pins-as-gpio, which clears the bit itself if
it is set (a single 1→0 word write, no page erase, REGOUT0 untouched) and
resets once so it takes. On a board that shipped running Meshtastic the bit is
already clear and the write is a no-op. The product image logs both words at
every boot:
board: nav pad usable=true (UICR.NFCPINS), regulator=3.3 V
The module is rated below the chip. 20 dBm max sub-GHz and 11.5 dBm at 2.4 GHz, against the LR1121's headline 22/13 dBm. The firmware enforces the module's numbers and refuses rather than clamping: a host that asks for 21 dBm is told no, because a silent clamp is a lie it cannot detect.
The QSPI flash is 16 MB. The variant declares a W25Q32JVSS (4 MB) and
the product page says 8 MB, but the Rev 01 schematic populates a
W25Q128JVPIQ. oxinode does not drive it.
There is no DFU button. The bootloader's BUTTON_1 and BUTTON_2 are
both P0.05, which is unconnected. Double-tap reset and the software path
(GPREGRET) are the only two ways into the bootloader.
UICR.REGOUT0 is already 3.3 V. The bootloader programs it, so an
oxinode image inherits 3.3 V rather than the 1.8 V reset default. The panel's
boost converter and the QSPI flash expect 3.3 V, which is why nothing in the
firmware ever writes that word.
The nRF52's TWIM locks up after a NACK, and lies rather than failing. A
transaction that ends in an address NACK can leave the I²C peripheral in a
state it does not come out of, and embassy-nrf implements no workaround. It
produces plausible answers rather than errors: the same address answers a read
one moment and not the next. The display driver cycles the peripheral's
ENABLE register after any failure, and the bus scan does so after every
transaction. See The display.
The 1200-baud touch is state on the host. macOS caches terminal settings
per device path and re-applies them, so a failed touch leaves the port at
1200 baud, and then anything that opens it, including cat reading the log,
performs another touch and sends the board back to its bootloader. The flash
script resets the cached rate after every flash, and the firmware ignores the
touch for the first two seconds after boot. Always open the ports at an
explicit baud rate.
There are SWD pads. No image assumes a debug probe and none is needed, but SWDIO and SWDCLK come out to test pads TP1/TP2. If the no-probe constraint ever becomes expensive enough, it is solderable rather than impossible.
The red LED is the charger's. It hangs off the BQ25185's STAT2 output,
the same net the MCU reads on P1.02, not off a GPIO. A red glow means
charging, not a fault.
Battery¶
P0.31 reaches the cell through 806 kΩ over 1.5 MΩ, so the pin sees 0.65048 of
it: 4.2 V arrives as 2.73 V, inside the 3.6 V the SAADC measures at gain 1/6
against its 0.6 V reference. The arithmetic is in oxinode_core::battery,
pinned to those ADC settings, with the divider ratio checked against
Meshtastic's ADC_MULTIPLIER of 1.537 in a test. The percentage comes from an
eleven-point open-circuit table interpolated, because a straight line from
3.0 V to 4.2 V is twenty points wrong through the middle of a lithium cell's
curve. It is an estimate (a cell under load reads low, one on the charger
reads high), which is why the voltage is shown beside it. Below 2.5 V at the
cell the reading is reported as unknown rather than as a flat battery: with no
cell fitted the pin reads near zero.
One conversion is 1.35 mV at the cell and wanders by a few counts, which near the top of the table is a percentage point, so the ADC averages sixteen conversions per sample and the core averages samples over a few seconds and holds the shown value until the average has moved ten millivolts: the screen shows one number, not two.
The charger's two status lines are read together with the chip's pull-ups,
because STAT2 alone cannot tell charging from a latched-off fault. From the
BQ25185's table: STAT2 low is charging; STAT1 low is a recoverable fault
(input over-voltage, cell too hot or cold, chip too hot); both low is a fault
the chip stays off on until the input is cycled (safety timer, cell
over-current); both high is done, sleeping or disabled, which is full. That
table is defined with an input present. With the cable out the chip is in
battery-only mode, which the table does not cover, and on this board it holds
STAT2 low there, the "charging" row, so the pins alone say a board running
on its cell is charging. The nRF52840's own USB regulator status decides
first: no input is unplugged whatever the pins say. Every change is logged
with the raw lines, so what the screen says can be checked against the cable
and the red LED.
Provenance¶
The hardware facts here were cross-checked against three sources, in descending order of authority:
- The Base Duo Rev 01 schematic (
Base Duo [MH212A], KiCad, from muzi works) together with Elecrow's nRFLR1121 and nRFLR1262 module datasheets. - muzi works' fork of the Adafruit nRF52 bootloader, MIT-licensed, whose
src/boards/muzi_base/is the build the board reports running. This is where the UF2 family ids, the absent DFU button, theREGOUT0setting and the 40 KB reservation come from. - The Meshtastic variant (
variants/nrf52840/muzi_base/variant.h), useful and mostly right, but wrong about the flash part and misleading about the load-switch pins and the trackball. Pin numbers and hardware facts are not copyrightable expression; none of Meshtastic's driver logic or comments are reproduced here.