disabling nRF52 access port protection with J-Link

tl;dr

to disable nRF52 hardware/software access port protection using J-Link Commander:

$ JLinkExe -device NRF52840_xxAA -if SWD -speed 4000
J-Link>connect
Do you want to unsecure the device? [yes]
J-Link>W4 0x10001208 5A
J-Link>loadfile write_SwDisable_to_APPROTECT_DISABLE.elf
J-Link>reset
J-Link>exit

full explanation below..

Access Port Protection

The nRF52840 rev.3 and later (build code Fxx and later) has Access Port Protection enabled by default.

As a result, any attempt to connect to a newer nRF52840 with J-Link will result in the following pop-up:

CTRL-AP indicates that the device is secured.
For debugger connection the device needs to be unsecured.
Note: Unsecuring will trigger a mass erase of the internal flash.

Do you want to unsecure the device?

This means that debuggers will have to perform a full flash erase in order to gain access for flashing/debugging.

As part of this full flash erase, UICR and its REGOUT0 register are also erased, meaning if you’re using high voltage mode and relying on REG0 to regulate the input voltage to something like 3.3V, the regulator output of the nRF52840 will be reset to 1.8V every time you want to flash a new program or debug.

This could be problematic, for example, if your debugger doesn’t support a 1.8V target voltage. Any time you want to connect to the nRF, you are forced to unsecure the device with a full erase, potentially resulting in a lost connection when the target voltage is reset to 1.8V.

The Access Port Protection will also re-enable upon certain reset events, meaning we can’t just unsecure the device like this once and forget about it.

So, we need to figure out how to disable this feature and how to keep it disabled. The necessary steps are described in the nRF52840 Product Specification, Section 4.8.2: Access port protection.

Access port protection controlled by hardware and software 

This information refers to build codes Fxx and later. 

By default, access port protection is enabled. Access port protection is disabled by issuing an ERASEALL command via CTRL-AP. Read CTRL-AP.APPROTECTSTATUS to ensure that access port protection is disabled, and repeat the ERASEALL command if needed. This command will erase the flash, UICR, and RAM. CTRL-AP is described in more detail in CTRL-AP - Control access port on page 71. Access port protection will remain disabled until one of the following occurs:

    • Pin reset
    • Power or brownout reset
    • Watchdog reset if not in Debug Interface Mode, see Debug Interface mode on page 73
    • Wake from System OFF if not in Emulated System OFF

To keep access port protection disabled, the following actions must be performed:

    • Program UICR.APPROTECT to HwDisabled. This disables the hardware part of the access port protection scheme after the first reset of any type. The hardware part of the access port protection will stay disabled as long as UICR.APPROTECT is not overwritten.

    • Firmware must write APPROTECT.DISABLE to SwDisable. This disables the software part of the access port protection scheme. 
    
    Note: Register APPROTECT.DISABLE is reset after pin reset, power or brownout reset, watchdog reset, or wake from System OFF as mentioned above.

First it says we need to issue an ERASEALL command via CTRL-AP in order to open the access port, but looking at page 71 as directed, there’s no base address for these CTRL-AP registers..

This is because these CTRL-AP registers don’t exist as MMIO. Instead, they are only accessible via SWD. To access these registers we can use J-Link Commander (JLinkExe).

Once we’ve erased the flash and thus opened the access port, all we need to do is write HwDisabled to UICR.APPROTECT and flash any program which writes SwDisable to APPROTECT.DISABLE.

As long as whatever firmware is running on the nRF makes sure to write SwDisable, our nRF will remain unlocked and debuggable. The HwDisabled value in UICR will stay set thanks to it being in FLASH.

Yes, both HwDisabled and SwDisable need to be set for the access port to unlock. I guess it’s like 2-factor authentication for your MCU.

In practice:

Start J-Link Commander:

$ JLinkExe -device NRF52840_xxAA -if SWD -speed 4000

J-Link>

At this point, if we submit connect, the pop-up will ask if we want to unsecure the device via a mass flash erase. If you confirm, J-Link will issue ERASEALL via CTRL-AP itself, opening the access port and successfully connecting for debugging. Thanks to this, we can skip ahead and just write HwDisabled to the UICR, flash our program, and get out.

We could also issue an ERASEALL to unlock the AP ourselves before connecting, as shown here. It simply involves manually selecting SWD, enabling power for the debug interface, and writing 1 to CTRL-AP.ERASEALL.

We’ll go the second route in the spirit of doing things from scratch:

Select SWD

J-Link>SWDSelect
Select SWD by sending SWD switching sequence.
Found SWD-DP with ID 0x2BA01477

Enable debug interface power

J-Link>WriteDP 1 0x50000000
    Write DP register 1 = 0x50000000

Select CTRL-AP

J-Link>WriteDP 2 0x01000000
    Write DP register 2 = 0x01000000

Check what APPROTECTSTATUS says to start with. 0x0 = protection enabled

J-Link>ReadAP 3
Read AP register 3 = 0x00000000

Set ERASEALL to 1

J-Link>WriteAP 1 1
	Write AP register 1 = 0x00000001

Read ERASEALLSTATUS until 0x0

J-Link>ReadAP 2
Read AP register 2 = 0x00000001
J-Link>ReadAP 2
Read AP register 2 = 0x00000000

If you get Read AP register 2 = ERROR here, don’t worry, just start from the beginning and keep trying until J-Link>ReadAP 2 works. Sorry, I’m not sure what exactly changed when it finally worked for me.

Check that APPROTECTSTATUS now shows disabled protection: 0x1

J-Link>ReadAP 3
Read AP register 3 = 0x00000001

Now we have to connect so we can write to UICR. We shouldn’t get any pop-up here, except maybe a license terms pop-up.

J-Link>connect

Write HwDisabled (0x5A) to UICR.APPROTECT (0x10001208) as instructed.

J-Link>Mem32 0x10001208 1
10001208 = FFFFFFFF 

J-Link>W4 0x10001208 5A
Writing 0000005A -> 10001208

J-Link>Mem32 0x10001208 1
10001208 = 0000005A

Normally, the UICR must be written/erased in the same manner as the rest of flash, by first configuring the CONFIG register of the NVMC peripheral in order to enable flash write/erase. However, j-link recognizes and handles this for us, so it was omitted here.

We can also optionally write UICR.REGOUT0 to 3.3V while we’re here.

J-Link>Mem32 0x10001304 1
10001304 = FFFFFFFF

J-Link>W4 0x10001304 FFFFFFFD
Writing FFFFFFFD -> 10001304

J-Link>Mem32 0x10001304 1
10001304 = FFFFFFFD

Finally, load your program, and make sure it writes SwDisable to APPROTECT.DISABLE.

blink.c
/********************************************************************
** file         : blink.c
** description  : minimal blink program for the nRF52840 Dongle
**
**                blink LD1 (P0.06)
**
** compilation  : arm-none-eabi-gcc -std=gnu23 -mcpu=cortex-m4 -ffreestanding -nostdlib -Os -T link.ld blink.c -o blink.elf
**
********************************************************************/

#include <stdint.h>

#define APPROTECT_DISABLE   *((volatile uint32_t *) 0x40000558)

#define GPIOP0_DIRSET   *((volatile uint32_t *) 0x50000518)
#define GPIOP0_OUTSET   *((volatile uint32_t *) 0x50000508)
#define GPIOP0_OUTCLR   *((volatile uint32_t *) 0x5000050C)

static void delay(uint32_t n) {
    while (n--) {
        __asm__("nop");
    }
}

void reset_handler(void) {

    /* SwDisable */
    APPROTECT_DISABLE = 0x5A;

    /* set p0.06 to output mode */
    GPIOP0_DIRSET = (1 << 6);

    for (;;) {
        delay(5000000);
        GPIOP0_OUTSET = (1 << 6);
        delay(5000000);
        GPIOP0_OUTCLR = (1 << 6);
    }
}

void nmi_handler(void) {
    for (;;);
}

void hardfault_handler(void) {
    for(;;);
}

const uint32_t vector_table[]
__attribute__ ((section (".vectors"))) = {
    (uint32_t) 0x20040000,
    (uint32_t) reset_handler,
    (uint32_t) nmi_handler,
    (uint32_t) hardfault_handler
};
/********************************************************************
** file         : link.ld
** description  : minimal linker script for an nRF52840
**
********************************************************************/

SECTIONS 
{
    . = 0x00000000;
    .text : 
    {
        *(.vectors)
        *(.text)
    }
}
J-Link>reset
J-Link>loadfile blink.elf
J-Link>reset
J-Link>exit

Unplug the power and plug it back in for good measure.

Let’s see if our target voltage VTref is now 3.3V.

[samu@yoga blink]$ JLinkExe -device NRF52840_xxAA -if SWD -speed 4000
SEGGER J-Link Commander V8.16 (Compiled Feb 26 2025 12:16:17)
DLL version V8.16, compiled Feb 26 2025 12:15:13

Connecting to J-Link via USB...O.K.
Firmware: J-Link EDU Mini V1 compiled Feb 20 2025 16:27:02
Hardware version: V1.00
J-Link uptime (since boot): 0d 00h 03m 07s
S/N: 801052139
License(s): FlashBP, GDB
USB speed mode: Full speed (12 MBit/s)
VTref=3.299V


Type "connect" to establish a target connection, '?' for help
J-Link>connect

No more full flash erases wiping out our UICR!

voila.