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:

  • 3V drives 3.3 V at R_SUPPLY and GND drives 0 V at R_SUPPLY, always, because the board is powered over USB. The board is a supply for the rest of the circuit.
  • 0, 1 and 2 are inout GPIO on P0.02, P0.03 and P0.04. Output (DIR bit set): push-pull at 3.3 V or 0 V through R_STRONG. Input: high-Z, plus a 13 k pull-up to 3.3 V when PIN_CNF[n].PULL is Pullup, a 13 k pull-down when it is Pulldown. 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].INPUT set to Disconnect makes 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; analogWrite and analogRead have no peripheral behind them.
  • The soft device, the DAPLink interface chip and the file system. A MakeCode or MicroPython .hex will 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 HFCLKSTART and LFCLKSTART tasks 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.md section 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.rs checks 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.