| name | nrf52840-platformio |
| description | Build, flash, and debug nRF52840 projects with PlatformIO and the Adafruit BSP. Use when: setting up PlatformIO for nRF52840-DK, flashing via J-Link, restoring SoftDevice and Bootloader, understanding the flash memory layout, fixing 'pulsing LED' DFU mode, using firmware_signature.bin, selective app-only flash, checking UICR.BOOTLOADERADDR. |
| argument-hint | Optional: specific issue (e.g. 'flash layout', 'restore bootloader', 'upload') |
nRF52840 mit PlatformIO (Adafruit BSP)
Hardware & Software
- Board: nRF52840-DK (pca10056)
- Debugger: SEGGER J-Link OB (onboard), S/N: 1050252111
- Platform:
nordicnrf52 10.9.0
- Framework:
arduino
(Adafruit Bluefruit52Lib / framework-arduinoadafruitnrf52 1.10601.0)
- Board-ID:
nrf52840_dk_adafruit (Adafruit-Variante, nicht nrf52840_dk)
- Serielle Ports (Nummerierung kann je nach Enumeration variieren
â mit
ls /dev/ttyACM* prĂŒfen):
- J-Link VCOM (Serial2 UART, P0.06=TX / P0.08=RX) â aktuell
/dev/ttyACM0
- J-Link CDC Command-Kanal â weiterer ACM-Port (kein UART-Output)
- App-TinyUSB-CDC = separater Port (erscheint nur wenn zweites USB-Kabel
an J18 angesteckt)
platformio.ini (Minimal-Konfiguration)
[env:nrf52840dk]
platform = nordicnrf52
board = nrf52840_dk_adafruit
framework = arduino
lib_extra_dirs = ../../
monitor_port = /dev/ttyACM0
monitor_speed = 115200
upload_protocol = jlink
lib_deps = adafruit/Adafruit nRFCrypto@^0.1.2
build_flags =
-DBOARD_NRF52DK
-I.pio/libdeps/nrf52840dk/Adafruit\ nRFCrypto/src
debug_svd_path = /home/daniel/.platformio/platforms/nordicnrf52/misc/svd/nrf52840.svd
debug_tool = jlink
build_type = debug
debug_build_flags = -O0 -ggdb3
debug_server_parameters = -rtos GDBServer/RTOSPlugin_FreeRTOS
Wichtig: Immer nrf52840_dk_adafruit verwenden, nicht nrf52840_dk
(Nordic BSP)!
Der Nordic-BSP hat ein falsches Pin-Mapping: LED_BUILTIN â Arduino-Pin 6
â P0.17 (falsch, LEDs sind P0.13âP0.16).
Flash-Speicherlayout (nRF52840, Adafruit BSP)
| Region | Adresse | Inhalt |
|---|
| MBR | 0x00000000 | Nordic MBR (4 KB) |
| SoftDevice S140 | 0x00001000 | S140 BLE-Stack (~152 KB) |
| App | 0x00026000 | Adafruit Arduino App |
| Bootloader | 0x000F4000 | Adafruit DFU Bootloader |
| Bootloader Settings | 0x000FF000 | CRC + App-Metadaten (4 KB) |
| UICR | 0x10001000 | BOOTLOADERADDR @ 0x10001014 |
UICR prĂŒfen
JLinkExe -device nRF52840_xxAA -if SWD -speed 4000 -autoconnect 1 << 'EOF'
mem32 0x10001014 1
exit
EOF
Erwarteter Wert: 0x000F4000 (Bootloader-Adresse).
Wert 0xFFFFFFFF â Bootloader fehlt oder UICR gelöscht â Full-Restore nötig.
Upload (Normalfall)
cd examples/nRF52_Basic
pio run -e nrf52840dk -t upload
PlatformIO schreibt automatisch:
firmware.bin @ 0x26000
firmware_signature.bin @ 0xFF000 (Bootloader-Settings mit App-CRC)
Ohne firmware_signature.bin startet der Bootloader die App nicht
und bleibt im DFU-Modus (pulsierendes LED).
â ïž CPU bleibt nach jedem PIO-Upload gehalten
Ursache (hardcodiert in PIO):
~/.platformio/platforms/nordicnrf52/builder/main.py â _jlink_cmd_script() generiert:
h
loadbin firmware.bin, 0x26000
loadbin firmware_signature.bin, 0xFF000
r
q
r = Reset+Halt, q = quit â kein g (go). CPU friert nach jedem Upload ein.
Dauerlösung: extra_scripts = post:post_upload.py in platformio.ini
post_upload.py ĂŒbernimmt zwei Aufgaben:
-
Pre-Upload â stellt den kompletten BLE-Stack (MBR, SoftDevice S140,
Bootloader, UICR) aus der Framework-HEX wieder her, bevor PIO das
App-Binary flasht.
JLink vergleicht zuerst und flasht nur geĂ€nderte Sektoren â dauert
â 2â3Â s extra wenn der Stack intakt ist, spart den manuellen
Full-Restore wenn er gelöscht wurde.
-
Post-Upload â sendet g (go) damit die CPU nach dem Upload
tatsÀchlich startet.
import glob
import os
import subprocess
import tempfile
Import("env")
def _jlink_run(commands):
with tempfile.NamedTemporaryFile(
mode="w", suffix=".jlink", delete=False) as f:
f.write("\n".join(commands) + "\n")
tmpfile = f.name
try:
return subprocess.run(
["JLinkExe", "-device", "nRF52840_xxAA", "-if", "SWD",
"-speed", "4000", "-autoconnect", "1", "-CommandFile", tmpfile],
capture_output=True, text=True,
)
finally:
os.unlink(tmpfile)
def jlink_restore_stack(source, target, env):
if env.get("UPLOAD_PROTOCOL") != "jlink":
return
fw_dir = env.PioPlatform().get_package_dir("framework-arduinoadafruitnrf52")
board_variant = env.BoardConfig().get("build.variant", "pca10056")
bl_dir = os.path.join(fw_dir, "bootloader", board_variant)
matches = sorted(glob.glob(os.path.join(bl_dir, "*.hex")))
if not matches:
print(f">> pre_upload: No bootloader HEX found in {bl_dir} â skipping")
return
bl_hex = matches[-1]
print(">> pre_upload: Restoring MBR + SoftDevice + Bootloader + UICR")
print(f" HEX: {os.path.basename(bl_hex)}")
r = _jlink_run(["h", f"loadfile {bl_hex}", "exit"])
print(">> pre_upload: BLE stack restored OK" if r.returncode == 0
else ">> pre_upload: Restore FAILED\n" + r.stderr)
def jlink_go_after_upload(source, target, env):
if env.get("UPLOAD_PROTOCOL") != "jlink":
return
print(">> post_upload: sending 'g' to release CPU halt...")
r = _jlink_run(["g", "exit"])
print(">> post_upload: CPU released (go OK)" if r.returncode == 0
else ">> post_upload: FAILED\n" + r.stderr)
env.AddPreAction("upload", jlink_restore_stack)
env.AddPostAction("upload", jlink_go_after_upload)
Hinweis: /dev/stdin funktioniert nicht als -CommandFile
bei JLinkExe â immer Tempfile verwenden.
Manueller Einzel-Fix (ohne Post-Script):
printf 'g\nexit\n' > /tmp/jlink_go.jlink
JLinkExe -device nRF52840_xxAA -if SWD -speed 4000 -autoconnect 1 \
-CommandFile /tmp/jlink_go.jlink
Flash-Zustand prĂŒfen (vor g):
printf 'regs\nmem 0x00000000, 4\nexit\n' > /tmp/jlink_diag.jlink
JLinkExe -device nRF52840_xxAA -if SWD -speed 4000 -autoconnect 1 \
-CommandFile /tmp/jlink_diag.jlink
PC = gĂŒltige Adresse + kein Fault â nur CPU angehalten â g genĂŒgt
PC = FFFFFFFE + 0x00000000 = FF FF FF FF â SoftDevice gelöscht
â Full-Restore nötig
XPSR IPSR = 003 (HardFault) â CPU in HardFault
â g hilft nicht â Full-Restore nötig
Full-Restore (nach versehentlichem Chip-Erase)
Normalfall: post_upload.py stellt den Stack automatisch vor jedem
Upload wieder her. Der manuelle Restore unten ist nur nötig wenn auch
post_upload.py nicht mehr hilft
(z. B. JLink-Verbindung verloren, PIO gar nicht startbar).
BL=~/.platformio/packages/framework-arduinoadafruitnrf52/bootloader/pca10056/pca10056_bootloader-0.9.0_s140_6.1.1.hex
printf "erase\nloadfile %s\nr\ng\nexit\n" "$BL" > /tmp/restore.jlink
JLinkExe -device nRF52840_xxAA -if SWD -speed 4000 -autoconnect 1 -CommandFile /tmp/restore.jlink
cd examples/nRF52_Basic && pio run -e nrf52840dk -t upload
Hinweis: loadfile fĂŒr die .hex
(enthÀlt SoftDevice + Bootloader + UICR).
loadbin fĂŒr die App .bin
(ohne Header, immer mit Zieladresse 0x26000).
â ïž HĂ€ufige Falle: Partielles Erase löscht Bootloader
erase 0x26000 0x100000 löscht auch den Bootloader bei 0xF4000!
UICR bleibt erhalten â Boot-Loop / DFU-Pulsieren.
Sichere Alternative fĂŒr App-only Update:
erase 0x26000 0x51000
loadbin firmware.bin 0x26000
Besser: immer pio run -t upload verwenden.
LED-Pin-Mapping (pca10056 / Adafruit BSP)
LED_RED = PIN_LED1 = Arduino 13 â P0.13 (LED1 auf DK)
LED_BLUE = PIN_LED2 = Arduino 14 â P0.14 (LED2 auf DK)
PIN_LED3 = Arduino 15 â P0.15 (LED3 auf DK)
PIN_LED4 = Arduino 16 â P0.16 (LED4 auf DK)
LED_STATE_ON = 0 (active-LOW)
initVariant() im BSP setzt alle 4 LEDs automatisch auf OUTPUT + OFF beim Start.
Direktes GPIO ohne BSP (Busy-Wait-Blink fĂŒr Early-Boot-Test)
#define GPIO0_OUTSET (*(volatile uint32_t*)0x50000508UL)
#define GPIO0_OUTCLR (*(volatile uint32_t*)0x5000050CUL)
#define GPIO0_DIRSET (*(volatile uint32_t*)0x50000518UL)
#define LED1_BIT (1u << 13)
GPIO0_DIRSET = LED1_BIT;
GPIO0_OUTCLR = LED1_BIT;
for (volatile uint32_t d = 0; d < 400000; d++) {}
GPIO0_OUTSET = LED1_BIT;
NĂŒtzlich wenn delay() / FreeRTOS noch nicht initialisiert sind.
DFU-Modus erkennen
- Pulsierendes LED = Adafruit Bootloader im DFU-Modus
- Ursache:
firmware_signature.bin fehlt oder ist ungĂŒltig
- Fix:
pio run -t upload (schreibt signature automatisch)
Serial Monitor
J-Link VCOM (Serial2 / UART) â empfohlen fĂŒr nRF52840-DK
- Port: J-Link VCOM (Serial2 UART) â aktuell
/dev/ttyACM0, Nummer kann variieren
- Kein zweites USB-Kabel nötig
- Port-Nummer mit
ls /dev/ttyACM* oder pio device list ermitteln
pio device monitor --port /dev/ttyACM0 --baud 115200
Im Code mit DBG_SERIAL-Makro (statt -DSerial=Serial2 Build-Flag):
#if defined(BOARD_NRF52DK)
#define DBG_SERIAL Serial2
#else
#define DBG_SERIAL Serial
#endif
Warum nicht -DSerial=Serial2?
Der PrĂ€prozessor wĂŒrde Adafruit_USBD_CDC Serial zu
Adafruit_USBD_CDC Serial2 umbenennen â und kollidiert dann mit der
BSP-Deklaration Uart Serial2. Build-Fehler.
TinyUSB CDC (Serial) â fĂŒr XIAO / Feather
TinyUSB erzeugt einen eigenen CDC-Port (erscheint nach Serial.begin()).
monitor_port weglassen oder auf den USB-CDC-Port zeigen.
Warte-Schleife im Code:
while (!DBG_SERIAL && (millis() - startTime < 8000)) { }
!Serial2 (UART) ist immer false â Schleife verlĂ€sst sofort;
!Serial (USB CDC) wartet bis Port geöffnet.