Close

Flashing a custom nRF52832 board via J-Link - sounds easier than it is

A project log for LumiBand Gen3 - Wireless DMX LED wristband ESP32

A wearable LED fixture that receives wireless DMX512 over WiFi. Because why should the lighting rig stop at the stage edge.

markus-loefflerMarkus Loeffler 06/04/2026 at 20:000 Comments

We built the custom PCB around the RF-BM-ND04 nRF52832 BLE module and used an Adafruit nRF52832 Feather BSP to compile our firmware. Getting it to actually run via J-Link took us through every possible wrong turn. Here's the full list

Mistake 1: Flashing the app-only hex on a blank device

The Arduino IDE produces a firmware.ino.hex (app only). On a blank chip this does nothing - the nRF52832 needs the SoftDevice S132 in flash before any BLE app can run. Without it the chip just sits there silently.

Fix: use the firmware.ino.with_bootloader.hex which includes the SoftDevice.

Mistake 2: The Adafruit bootloader silently enters DFU mode

with_bootloader.hex includes the Adafruit DFU bootloader. Sounds great: until you learn that the Adafruit bootloader validates the app before launching it by checking for a "DFU settings page" in flash. That page is written by nrfutil settings generate during a normal OTA DFU workflow, but it is never written by a direct J-Link flash. Result: the bootloader silently enters DFU mode, no LEDs, no output, nothing.

Fix: skip the Adafruit bootloader entirely for factory programming. Extract just the SoftDevice (0x00000–0x25FFF) from the bootloader hex using Python intelhex, merge it with the app hex (starting at 0x26000), and flash that combined file. The SoftDevice's own MBR boots the app directly - no validation step, no DFU mode.

  from intelhex import IntelHex
  bl = IntelHex(); bl.loadhex("with_bootloader.hex")
  sd = IntelHex()
  for addr in range(0x00000, 0x26000):
      if bl[addr] != 0xFF: sd[addr] = bl[addr]
  app = IntelHex(); app.loadhex("app_only.hex")
  sd.merge(app, overlap='error')
  sd.write_hex_file("sd_only.hex")

Mistake 3: nrfutil is broken on Python 3

We tried the "proper" way first: nrfutil settings generate to create the DFU settings page. It crashes immediately:

  NameError: name 'xrange' is not defined

  nrfutil 5.2.0 is Python 2 code. It does not run on MacOS Python 3. Don't bother.

Mistake 4: Putting the hex file path in RTT Viewer's "Script file" field  

J-Link RTT Viewer has an optional Script file field in its connection dialog. Someone at the factory where they made & test the PCBs assumed this is where you load the firmware. It isn't - it's for .jlink script files. Loading a hex file there produces:

  :020000040000FA ^ Expected pragma identifier

Fix: leave the Script file field empty. RTT Viewer does not flash firmware.

Mistake 5: RTT Viewer and nRF Connect Programmer open at the same time  

The J-Link can only be claimed by one application. If RTT Viewer is connected and you try to use Programmer (or vice versa), one of them will fail silently or error out.

Fix: close one before opening the other.

Mistake 6: Not clicking "SELECT DEVICE" in nRF Connect Programmer

The Programmer UI shows a file loaded in the left panel, but all action buttons stay grayed out until you explicitly click SELECT DEVICE and choose your J-Link. Easy to miss, looks like everything is ready.

Bonus: UART TX was unconnected on our custom PCB

Our custom board doesn't connect P0.06 (UART TX). The Adafruit BSP's Serial.begin() happily shifts bytes to nowhere - costs ~500ms on startup and produces nothing. We replaced all Serial logging with SEGGER RTT (SEGGER_RTT_WriteString()), which works over the same SWD cable you're already using for programming. Zero extra wires.

The working factory flash procedure

  1. Build → produces app.hex
  2. Combine SoftDevice + app into sd_only.hex using intelhex (script above)
  3. Close RTT Viewer
  4. nRF Connect Programmer → Add file → sd_only.hex → SELECT DEVICE → Erase & write
  5. Close Programmer
  6. Open J-Link RTT Viewer → device NRF52832_XXAA, SWD, Auto Detection → connect
  7. See your firmware's log output in Terminal 0

  Hope this saves someone a day of head-scratching.

Discussions