Turns a BBC micro:bit v2 (nRF52833) into a Bluetooth Low Energy keyboard:
- Advertises as
microbit kbd(HID-over-GATT / HOGP). - Button A → types
Hello, Button B → typesWorld. - Streams
hello worldonce per second over a Nordic UART Service (NUS) notification (a simple link-alive channel). - 5×5 LED status display: blinking ring = advertising, steady heart = connected, X flash = re-pairing.
- A + B together = re-match: wipes all bonds and re-advertises so a new host can pair.
- Auto re-pair when a host reconnects with a stale key, and auto re-advertise after a disconnect.
Built on the Nordic nRF5 SDK 17.1.0 ble_app_hids_keyboard example + S140 7.2.0 SoftDevice. Only the modified files live here (see overlay/); setup.sh fetches the SDK and drops them in.
./setup.sh # downloads nRF5 SDK 17.1.0 + applies overlay + sets toolchain
# build:
cd sdk/nRF5_SDK_17.1.0_ddde560/examples/ble_peripheral/ble_app_hids_keyboard/pca10100/s140/armgcc
make
# flash (back in the repo root):
./flash.shRequirements:
arm-none-eabi-gcc(tested with 13.2) onPATH, or passGNU_INSTALL_ROOT=/path/to/bin/ ./setup.sh.pyocdfor flashing (pip install pyocd); it has built-innrf52833support.curl,unzip.
overlay/ # the only real source — modified SDK files, mirrored at SDK-relative paths
components/boards/pca10100.h # micro:bit v2 button pins + LED-pin conflict fix
examples/ble_peripheral/ble_app_hids_keyboard/
main.c # HID typing + NUS + LED matrix + re-match + auto re-pair
pca10100/s140/config/sdk_config.h # RC LFCLK, NUS, attr table, VS UUID
pca10100/s140/armgcc/Makefile # adds ble_nus.c, relaxes GCC13 -Werror
pca10100/s140/armgcc/ble_app_hids_keyboard_gcc_nrf52.ld # RAM origin bumped for NUS
setup.sh # fetch SDK + apply overlay + configure toolchain
flash.sh # mass-erase recover + flash SoftDevice + app + reset
test/ble_kbd_test.py # bleak BLE-central test client (scan / NUS / HID)
The SDK is intentionally not committed (large, third-party, has its own license). setup.sh reconstructs the buildable tree under sdk/.
| Function | Pin(s) |
|---|---|
| Button A | P0.14 |
| Button B | P0.23 |
| LED matrix rows (anode, drive HIGH) | P0.21, P0.22, P0.15, P0.24, P0.19 |
| LED matrix cols (cathode, drive LOW) | P0.28, P0.11, P0.31, P1.05, P0.30 |
UART log TX (→ DAPLink CDC /dev/ttyACM0, 115200) |
P0.06 |
A pixel (row, col) is lit by driving its row HIGH and column LOW; TIMER1 multiplexes one row every 3 ms.
- Flash, then on a phone/computer pair with
microbit kbd(Just-Works, no PIN). - Open a text field, press A →
Hello, B →World. - Press A + B together to forget all bonds and pair a different host.
python test/ble_kbd_test.py scan # find 'microbit kbd'
python test/ble_kbd_test.py nus # connect + verify 1 Hz 'hello world'
python test/ble_kbd_test.py type # also try to read A/B HID reportsNeeds pip install bleak. See the HID observability note below for type.
- No 32 kHz crystal on micro:bit v2. The SDK/DK default
NRF_SDH_CLOCK_LF_SRC = 1(external XTAL) makes the SoftDevice log "Fast advertising" while the radio never actually transmits — the device is invisible to scanners. Fix = internal RC:NRF_SDH_CLOCK_LF_SRC = 0,LF_RC_CTIV = 16,LF_RC_TEMP_CTIV = 2,LF_ACCURACY = 1(insdk_config.h). - Hardened APPROTECT. pyOCD's normal flash erase/program fails at address
0x0withresult code 0x67(UICR0x10001208reads0xFFFFFFFF). You mustpyocd erase -t nrf52833 --mass(CTRL-AP ERASEALL recover) first, then flash in the same session.flash.shdoes this. - DK LED pin vs button A. On the
pca10100board header,BSP_LED_1 == P0.14— which is micro:bit v2's button A. Initializing LEDs (BSP_INIT_LEDS) drives P0.14 as output → button A never reads. Fix: don't init the DK LEDs (bsp_init(BSP_INIT_BUTTONS, ...)). - Whitelist advertising. The stock HID example advertises with a whitelist; with an empty bond list it's undiscoverable to new centrals. Set
ble_adv_whitelist_enabled = false. - USB flashing wedges over WSL/usbip.
pyocdmay hang or drop mid-program (Error during board uninit). Unplug/replug the board to re-enumerate, then retry.flash.shwraps calls intimeout.
- Observing the HID keystrokes from a Linux/WSL BLE central is blocked when the kernel lacks
uhid(no/dev/inputkeyboard) and BlueZ's input plugin hides the HoG characteristics from a GATT client. Verify A/B typing on a phone/Mac, or runbluetoothd --noplugin=inputsotest/ble_kbd_test.py typecan read the raw report characteristics. - Stale pairing after a re-match. A BLE peripheral cannot remotely delete a host's pairing record — the "paired" entry persists on the host until it forgets the device. The firmware's auto re-pair lets such a host reconnect without a manual "forget" on macOS/Linux/phones; Windows often still needs a manual remove.
Project glue (overlay diffs, scripts, test client) — do as you like. The nRF5 SDK (fetched by setup.sh) is under Nordic Semiconductor's own license; not redistributed here.