This is the engineering reference for the model: what it implements, register by register, for people writing firmware against it. For using the board in the editor, start with The BBC micro:bit V2 and the board page.
BBC micro:bit V2 board contract (Phase 9)
The fifth microcontroller, and the second on the Cortex-M4 engine. One kernel
component (microbit in crates/parts) wraps an nRF52833 SoC model
(crates/nrf52833) built on the Cortex-M core (crates/cortexm-core,
Profile::V7M). Firmware is a Thumb ELF that talks to the peripherals below at
their real nRF52833 addresses, so a program built for Mokxi runs on a real
micro:bit V2.
The catalog type is microbit (BBC micro:bit V2, nRF52833). Everything in
docs/esp32c3.md about how a board part behaves (slices, host channel, reset,
crash line) applies here unless this file says otherwise, and everything in
docs/stm32f411.md about the Cortex-M4 core applies to this core.
Running MakeCode or MicroPython .hex images is not a goal: both are built
against the vendor stack and expect the soft device, the radio and a file
system, none of which are modeled. What is modeled is the set of peripherals
pinMode, digitalWrite, digitalRead, millis, micros, delay,
Serial, attachInterrupt, showLeds, showText and tone need, at their
real addresses, so the same source built against firmware/microbit/ produces
an image that also runs on the hardware.
The board is unusual among the boards here in two ways, and both come from the hardware. It has no header to push into a breadboard: the edge connector is a gold comb along the bottom edge, and only its five big rings take a crocodile clip or a jumper, so the board stands beside a breadboard rather than in it. And it has no single LED to blink: the display is 25 LEDs on ten pads, scanned by the firmware, so a picture is a thing the simulator has to reconstruct from the scan rather than read off a register.
1. Board pins (catalog order)
Five pins, the five big rings of the edge connector, left to right with the board held with its USB socket at the top:
0, 1, 2, 3V, GND.
The small strips between the rings are real GPIO on the part and reachable from firmware, but they are not catalog pins: nothing on a breadboard reaches a strip 0.06 inch wide, and a pin the user cannot wire is a pin that lies about what the circuit is.
Electrical:
3Vdrives 3.3 V atR_SUPPLYandGNDdrives 0 V atR_SUPPLY, always, because the board is powered over USB. The board is a supply for the rest of the circuit.0,1and2are inout GPIO onP0.02,P0.03andP0.04. Output (DIRbit set): push-pull at 3.3 V or 0 V throughR_STRONG. Input: high-Z, plus a 13 k pull-up to 3.3 V whenPIN_CNF[n].PULLisPullup, a 13 k pull-down when it isPulldown. 13 k is the figure in the product specification. Input reads the net voltage against 1.65 V, half the supply; Unknown and Floating both read 0 with no error, as on every other board here.PIN_CNF[n].INPUTset toDisconnectmakes the input read 0.
2. Memory map (real nRF52833 addresses)
| region | address | size | notes |
|---|---|---|---|
| flash | 0x0000_0000 |
512 KB | the part boots from zero; VTOR points here out of reset |
| RAM, code bus | 0x0080_0000 |
128 KB | the same bytes as below, through the code bus |
| RAM, data bus | 0x2000_0000 |
128 KB | .data, .bss and the stack |
| FICR | 0x1000_0000 |
4 KB | factory information, read only |
| UICR | 0x1000_1000 |
4 KB | erased, never programmed here |
| APB peripherals | 0x4000_0000 |
48 x 4 KB | peripheral id at 0x4000_0000 + id * 0x1000, raising IRQ id |
| GPIO P0 | 0x5000_0000 |
P0.00 to P0.31 |
|
| GPIO P1 | 0x5000_0300 |
P1.00 to P1.09, at the same offsets plus 0x300 |
|
| SCS | 0xE000_E000 |
SysTick, NVIC and the SCB, from cortexm-core |
The stack grows down from the top of RAM. firmware/microbit/link.ld fails the
link if the static data leaves it less than 4 KB.
3. Peripheral registers (real offsets, real bit positions)
Every offset and bit position here is the nRF52833 Product Specification's.
Nordic peripherals share one shape: tasks at 0x000, events at 0x100,
SHORTS at 0x200, INTEN / INTENSET / INTENCLR at 0x300, and the
configuration registers from 0x500. Writing 1 to a task register fires it;
an event register reads 1 until firmware writes 0 to it.
A peripheral the model does not implement is not in the map at all, so a sketch that reaches for the radio takes a bus fault and stops with a line on the serial monitor rather than running on quietly. Inside a peripheral that is implemented, a register this model has no use for reads 0 and is counted.
3.1 CLOCK and POWER (0x4000_0000, IRQ 0)
| offset | register |
|---|---|
0x000 |
TASKS_HFCLKSTART |
0x004 |
TASKS_HFCLKSTOP |
0x008 |
TASKS_LFCLKSTART |
0x00C |
TASKS_LFCLKSTOP |
0x100 |
EVENTS_HFCLKSTARTED |
0x104 |
EVENTS_LFCLKSTARTED |
0x304 / 0x308 |
INTENSET / INTENCLR |
0x408 / 0x40C |
HFCLKRUN / HFCLKSTAT |
0x414 / 0x418 |
LFCLKRUN / LFCLKSTAT |
0x518 |
LFCLKSRC |
0x400 |
POWER.RESETREAS |
0x500 |
POWER.SYSTEMOFF |
0x51C / 0x520 |
POWER.GPREGRET / GPREGRET2 |
HFCLKSTAT and LFCLKSTAT carry STATE in bit 16 and the source in bit 0.
The HFXO is modeled as already running, so a start task completes in the same
slice it was written in and the started event is raised at once.
3.2 GPIO (0x5000_0000 and 0x5000_0300)
| offset | register |
|---|---|
0x504 |
OUT |
0x508 / 0x50C |
OUTSET / OUTCLR |
0x510 |
IN |
0x514 |
DIR |
0x518 / 0x51C |
DIRSET / DIRCLR |
0x520 |
LATCH |
0x524 |
DETECTMODE |
0x700 + 4n |
PIN_CNF[n] |
PIN_CNF[n] fields: DIR bit 0, INPUT bit 1 (1 disconnects the buffer),
PULL bits 3:2 (0 disabled, 1 pull-down, 3 pull-up), DRIVE bits 10:8 and
SENSE bits 17:16. Reset value 0x0000_0002: input, buffer disconnected, no
pull. The drive strength field is decoded and kept, but the model has one output
impedance, so H0H1 and S0S1 drive alike; a pad configured D0S1 or S0D1
is open drain on the disconnected half and that half is high-Z.
3.3 GPIOTE (0x4000_6000, IRQ 6)
| offset | register |
|---|---|
0x000 + 4n |
TASKS_OUT[n] |
0x030 + 4n |
TASKS_SET[n] |
0x060 + 4n |
TASKS_CLR[n] |
0x100 + 4n |
EVENTS_IN[n] |
0x17C |
EVENTS_PORT |
0x300 / 0x304 / 0x308 |
INTEN / INTENSET / INTENCLR |
0x510 + 4n |
CONFIG[n] |
Eight channels. CONFIG[n]: MODE bits 1:0 (0 disabled, 1 event, 3 task),
PSEL bits 12:8, PORT bit 13, POLARITY bits 17:16 (1 low to high, 2 high
to low, 3 toggle) and OUTINIT bit 20. INTEN bit 31 is the PORT event,
which is what PIN_CNF[n].SENSE feeds. attachInterrupt in the runtime uses
event channels; the PORT event is there for firmware that wants to wake on a
group of pins at once.
3.4 TIMER0, TIMER1, TIMER2 (0x4000_8000, 0x4000_9000, 0x4000_A000; IRQ 8, 9, 10)
| offset | register |
|---|---|
0x000 / 0x004 |
TASKS_START / TASKS_STOP |
0x008 / 0x00C |
TASKS_COUNT / TASKS_CLEAR |
0x010 |
TASKS_SHUTDOWN |
0x040 + 4n |
TASKS_CAPTURE[n] |
0x140 + 4n |
EVENTS_COMPARE[n] |
0x200 |
SHORTS |
0x304 / 0x308 |
INTENSET / INTENCLR |
0x504 |
MODE |
0x508 |
BITMODE |
0x510 |
PRESCALER |
0x540 + 4n |
CC[n] |
Timer mode counts PCLK16M divided by 2^PRESCALER; the reset prescaler is 4,
so 1 MHz. Counter mode counts TASKS_COUNT instead. BITMODE is 16, 8, 24 or
32 bits (0, 1, 2, 3). SHORTS bit n is COMPARE[n]_CLEAR and bit 8 + n is
COMPARE[n]_STOP; INTEN bit 16 + n is COMPARE[n]. All three timers have
four compare channels.
3.5 RTC0 (0x4000_B000, IRQ 11)
| offset | register |
|---|---|
0x000 / 0x004 / 0x008 |
TASKS_START / TASKS_STOP / TASKS_CLEAR |
0x00C |
TASKS_TRIGOVRFLW |
0x100 / 0x104 |
EVENTS_TICK / EVENTS_OVRFLW |
0x140 + 4n |
EVENTS_COMPARE[n] |
0x304 / 0x308 |
INTENSET / INTENCLR |
0x340 / 0x344 / 0x348 |
EVTEN / EVTENSET / EVTENCLR |
0x504 |
COUNTER |
0x508 |
PRESCALER |
0x540 + 4n |
CC[n] |
The counter is 24 bits and runs from LFCLK at 32,768 Hz through a 12-bit
prescaler, with three compare channels. INTEN and EVTEN: TICK bit 0,
OVRFLW bit 1, COMPARE[n] bit 16 + n. PRESCALER may only be written while the RTC is stopped, as on the
part.
3.6 UARTE0 (0x4000_2000, IRQ 2)
| offset | register |
|---|---|
0x000 / 0x004 |
TASKS_STARTRX / TASKS_STOPRX |
0x008 / 0x00C |
TASKS_STARTTX / TASKS_STOPTX |
0x02C |
TASKS_FLUSHRX |
0x108 |
EVENTS_RXDRDY |
0x110 |
EVENTS_ENDRX |
0x11C |
EVENTS_TXDRDY |
0x120 |
EVENTS_ENDTX |
0x124 |
EVENTS_ERROR |
0x144 / 0x14C / 0x150 |
EVENTS_RXTO / EVENTS_RXSTARTED / EVENTS_TXSTARTED |
0x200 |
SHORTS |
0x300 / 0x304 / 0x308 |
INTEN / INTENSET / INTENCLR |
0x500 |
ENABLE (4 the legacy UART, 8 the UARTE) |
0x508 .. 0x514 |
PSEL.RTS, PSEL.TXD, PSEL.CTS, PSEL.RXD |
0x518 / 0x51C |
RXD / TXD (the legacy one-byte registers) |
0x524 |
BAUDRATE |
0x534 / 0x538 / 0x53C |
RXD.PTR / RXD.MAXCNT / RXD.AMOUNT |
0x544 / 0x548 / 0x54C |
TXD.PTR / TXD.MAXCNT / TXD.AMOUNT |
0x56C |
CONFIG |
Both faces of the peripheral are modeled: the EasyDMA UARTE that reads and
writes buffers in RAM, and the legacy one-byte RXD and TXD registers, which
is what a small runtime reaches for first. BAUDRATE takes the values the
product specification lists (0x01D7_E000 is 115,200), and each byte costs the
simulated time ten bits at that rate would cost, so a long line takes as long
here as it does on the board. The pads named by PSEL.TXD and PSEL.RXD stay
GPIO; the bytes go to the host channel (section 4), exactly as
docs/esp32c3.md does it.
3.7 NVMC (0x4001_E000) and FICR (0x1000_0000)
NVMC.READY at 0x400 reads 1 and NVMC.CONFIG at 0x504 takes the write
and erase modes; a page erase clears 4 KB of the flash image and a word write
ands into it, as flash does. FICR carries CODEPAGESIZE (0x010, 4096),
CODESIZE (0x014, 128 pages), DEVICEID0/1 (0x060, 0x064) and the
INFO block from 0x100: part 0x52833, the variant, the package, 128 KB of
RAM and 512 KB of flash.
3.8 The core and interrupts
Cortex-M4 (ARMv7-M) at 64 MHz, no FPU: CPUID reads 0x410F_C241 and every
VFP and DSP instruction raises UNDEFINSTR, which is why
firmware/microbit/build.sh builds soft float and means it. The NVIC
implements the top three of the eight priority bits, as the nRF52 family does.
SysTick has no separate reference clock, so SYST_CALIB reads 0xC000_0000.
Interrupt numbers are the peripheral IDs: POWER_CLOCK 0, UARTE0 2, GPIOTE 6, TIMER0 8, TIMER1 9, TIMER2 10, RTC0 11.
4. Host channel
host_write gives the board bytes typed into the serial monitor; they arrive
in the UARTE0 receive path and raise EVENTS_RXDRDY. host_read takes the
bytes the firmware has transmitted. The format string in the catalog is
utf-8 bytes on UARTE0. On a real board these pads (P0.06 TX, P1.08 RX)
run to the interface chip and out over USB, so the serial monitor is standing
in for the USB serial port and not for a second UART.
The program format is elf32 arm nrf52833. load takes a Thumb ELF for the
memory map of section 2 and reruns the whole power-on sequence.
5. Time model, the probe and the poke
The part runs the SoC in 10 microsecond slices, as the STM32F411 part does: 640
cycles at 64 MHz, exactly. Each slice returns the pad changes it made with the
offset inside the slice at which each happened, and the part turns those into
drive_after calls at their own offsets, so a pin that moved at cycle 12 of a
slice moves 187.5 nanoseconds into it rather than at the end. A core parked on
WFI costs nothing until whatever it is waiting for, or until the circuit or
the host moves it; on_pin_change and on_poke nudge it awake a nanosecond
later rather than in the same delta cycle, so a pin change can never start a
loop that feeds itself.
The matrix. Twenty-five LEDs on five row pads (P0.21, P0.22, P0.15,
P0.24, P0.19) and five column pads (P0.28, P0.11, P0.31, P1.05,
P0.30). An LED is lit while its row drives high and its column drives low,
which is what the runtime's scan does a row at a time. Each LED is remembered
for 40 milliseconds after it was last lit, so a scan that shows one row per
millisecond reads as the steady picture an eye sees, and a display that is
cleared still goes dark before the next blink. The ten matrix pads are not
brought out to a ring, so they never drive a net.
The buttons. A on P0.14 and B on P0.23, behind the board's own 10 k
pull-ups, so a held button takes the pad to ground. They arrive through poke,
because the canvas has no notion of a region of a part: 1 is A held, 2 is B
held, 3 is both, 0 is neither.
The speaker. On P0.00. The part times the pad's edges the way the piezo
part does and calls it sounding while they keep arriving between 20 Hz and
20 kHz, going quiet 60 milliseconds after the last one.
The probe is one f32, and the board has more to say than one integer in a
float carries exactly. So the probe is a 32-bit word in the float's bit
pattern, laid out so it is always a normal number and never a NaN:
| bit | meaning |
|---|---|
| 0..24 | the LED matrix, in reading order: left to right, top to bottom |
| 25 | running |
| 26 | button A held |
| 27 | button B held |
| 28 | serial activity in the last 20 ms |
| 29 | always set: the tag that says this is a packed word |
| 30 | always clear, so the float is never a NaN |
| 31 | the speaker sounding |
With no program, or after a crash, the probe is plain 0. The visual reads the
bits back out of the same float (web/src/parts/microbit.ts, probeWord).
6. What is not modeled
- The radio. No 2.4 GHz anything: no Bluetooth, no micro:bit to micro:bit messages, and no pretense of either.
- The sensors. The accelerometer and compass (an LSM303 on I2C on the real V2), the microphone and the touch logo. The pads exist; nothing is behind them.
- SAADC, PWM, PPI, I2S, SPIM, TWIM and the temperature sensor. Their
addresses are unmapped, so touching one is a fault rather than a silent zero.
tone()is a square wave toggled from TIMER1's compare interrupt;analogWriteandanalogReadhave no peripheral behind them. - The soft device, the DAPLink interface chip and the file system. A
MakeCode or MicroPython
.hexwill not run here. - RTC1 and RTC2, TIMER3 and TIMER4. Only RTC0 and TIMER0 to TIMER2 are modeled.
- The V1. That is an nRF51822, a different chip.
7. Firmware runtime (firmware/microbit/)
A freestanding C and C++ runtime with an Arduino-shaped API, built with clang
and ld.lld for thumbv7em-none-eabi with soft float. No newlib, no CMSIS, no
Nordic SDK, no libgcc and no compiler-rt.
./firmware/microbit/build.sh # every example into web/public/firmware/microbit/
A sketch names a pin the way the board does: 0 to 20 for the edge
connector, plus BUTTON_A, BUTTON_B, SPEAKER, MIC, LOGO and the ten
LED_ROW* and LED_COL* names for the matrix. firmware/microbit/include/pins.h
holds the map from those numbers to nRF52833 pads, and it is the micro:bit V2
schematic's.
Beyond the usual Arduino set, the runtime offers the display:
showLeds(picture) takes five rows of five, # for a lit LED and . for a
dark one; showChar(c) holds one character from the 5x5 font; showText(s)
scrolls a string right to left and comes back when the last letter has gone;
plot, unplot and point reach one LED. The scan runs from the SysTick
interrupt, a row at a time, so a sketch that calls showLeds and then sleeps
keeps its picture. buttonA() and buttonB() read the two on-board buttons,
and tone(SPEAKER, hz, ms) is a square wave on P0.00.
The four built examples are blink (a heart beating on the matrix), button
(A and B change the face, a pushbutton on ring 1 lights an LED on ring 0),
serial (says what the buttons do and echoes what you type) and scroll (a
word going across the matrix). build.sh writes
web/public/firmware/microbit/index.json, which carries each sketch's source
for the editor.
8. What this model is sure of, and what it is not
Three groups, the same sorting docs/esp32c6.md section 7 uses. It exists because a
board model that quietly guesses an address is worse than one that says it did not know.
Taken from the product specification, and run through by real firmware. Every base
address, register offset and bit position in sections 2 and 3 is from the nRF52833
Product Specification: CLOCK and POWER at 0x4000_0000, UARTE0 at 0x4000_2000, GPIOTE at
0x4000_6000, TIMER0 to TIMER2 at 0x4000_8000/9000/A000, RTC0 at 0x4000_B000, NVMC at
0x4001_E000, FICR at 0x1000_0000 and GPIO P0/P1 at 0x5000_0000/0300, with their IRQ
numbers. crates/nrf52833/tests/firmware.rs loads real ELFs and drives GPIO, GPIOTE,
the timers, RTC0 and UARTE0 through them. The micro:bit V2's own numbers (the 5x5
matrix as five rows on P0.21/22/15/24/19 and five columns, buttons A and B on P0.14 and
P0.23, the speaker on P0.00, and the three big rings on the edge connector) are the
board's schematic.
Modeled, but not held to the specification's numbers. Behaviors rather than addresses:
- The clock is 64 MHz and the clock tree is a stub. CLOCK's
HFCLKSTARTandLFCLKSTARTtasks set their events immediately and the core runs at 64 MHz whatever is asked for; there is no crystal start-up time, no RC calibration and no low-power clock domain. - The slice has two gears, and the first store after a quiet spell is early. The
core runs in coarse batches until a store lands on a GPIO, GPIOTE, TIMER or UARTE
register and then one instruction at a time for a while, the same shape
docs/stm32f411.mdsection 7 describes. - The LED matrix is a probe, not a scan you can watch. The model reports the
twenty-five pixels as a bitmask; the row-by-row multiplex a real micro:bit runs is
real in the register writes (and
crates/nrf52833/tests/firmware.rschecks one row is lit at a time) but the picture handed to the canvas is all twenty-five at once, with no per-pixel brightness and no persistence. - The speaker is a square wave the runtime times itself, reported as a frequency between 20 Hz and 20 kHz with a 40 ms hold and 60 ms of silence to stop, not a driven waveform, no volume, and no PWM peripheral behind it.
- The pads are three states and a threshold. Push-pull at 3.3 V, high-Z, or a 13 kΩ internal pull (the specification's typical), read back against half the supply, with no hysteresis, no drive-strength setting, no rise time and no current limit. Only rings 0, 1, 2, 3V and GND are pins here; the rest of the edge connector is not.
- The board is an ideal supply at 3.3 V; nothing browns out, and neither the battery connector nor USB power is modeled. Startup is a microsecond.
Deliberately absent, as section 6 lists: the radio, the accelerometer, compass,
microphone and touch logo, SAADC, PWM, PPI, I2S, SPIM, TWIM, PDM, NFC, USB, the
temperature sensor, the watchdog, RTC1 and RTC2, TIMER3 and TIMER4, the FPU, flash
programming, the soft device, the DAPLink interface chip and the file system. Their
addresses are unmapped, so touching one is a fault rather than a silent zero, which
means analogRead and analogWrite have no peripheral behind them, and a MakeCode or
MicroPython .hex will not run here.