Allowing debugger access to nRF5340

After an nRF5340 executes a power-on, brown-out, watchdog timer, or pin reset it is not automatically accessible to debug probes. This is true even if the nRF5340's memory is completely blank --i.e. no settings have been written to the device to disable the debugger-- because Access Port Protection APPROTECT requires firmware on the nRF5340 to explicitly enable debugger access every time the device starts up. This action must be performed independently on both cores.

A simple procedure is used to allow debugger access to a core in case a reset is executed before firmware has been programmed to keep the device "unlocked":

  1. Perform an ERASEALL via the CTRL-AP
  2. Program the required firmware before a reset is executed

The nrfjprog --recover operation handles both of these steps automatically. If nrfjprog isn't an option --or isn't practical for some reason-- then the steps can be done manually.

Performing an ERASEALL

The nRF5340 DK has an onboard J-Link so Segger's JLink utility is a convenient way to demonstrate how to interact with the CTRL-AP over SWD. Two Debug Port (DP) registers need to be introduced first:

CTRL/STATE DP

Setting the CSYSPWRUPREQ and CDBGPWRUPREQ bits in the CTRL/STAT DP powers up the device's debugger interface.

The APSEL field in the SELECT DP selects between the four possible Access Ports. The APBANKSEL is used to specify the most-significant four bits of the register address in the AP that you are accessing.

Only three JLink commands are required and they are all named intuitively:

  1. SWDWriteDP [ADDRESS] [VALUE]
  2. SWDReadAP [ADDRESS]
  3. SWDWriteAP [ADDRESS] [VALUE]

But there are two tricky things to remember:

  1. The ADDRESS parameter is automatically shifted left by two bits; Entering "SWDReadAP 1" is interpreted as "SWDReadAP 100b" or "SWDReadAP 4".
  2. The first invocation of SWDReadAP returns stale data so it is typically called at least twice.

The JLink utility is installed along with the Segger J-Link drivers (on Linux it is named JLinkExe). First plug an nRF5340 DK into the PC and launch the JLink utility. Then select the SWD interface:

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

Next the debug interface must be powered up:

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

The Identification Register (IDR) of one or more APs can be read as a sanity check. The first two APs should identify as Arm's AHB-AP (0x84770001) and the next two as Nordic's CTRL-AP (0x12880000). The SELECT DP's address is 8 (2<<2) and the IDR register's address is 0xFC (0xF<<4 | 3<<2). This is the application core's IDR:

J-Link>SWDWriteDP 2 0x020000F0
	Write DP register 2 = 0x020000F0
J-Link>SWDReadAP 3
	Read AP register 3 = 0x00000000
J-Link>SWDReadAP 3
	Read AP register 3 = 0x12880000

Next, the application core's ERASEALL operation can be executed by writing 1 to register 4 (1<<2):

J-Link>SWDWriteDP 2 0x02000000
	Write DP register 2 = 0x02000000
J-Link>SWDWriteAP 1 1
	Write AP register 1 = 0x00000001

When ERASEALLSTATUS (2<<2) reads zero then the operation is complete:

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

The same procedure can be used on the network core by substituting the index of its AP for the application core's AP:

J-Link>SWDWriteDP 2 0x03000000
	Write DP register 2 = 0x03000000
J-Link>SWDWriteAP 1 1
	Write AP register 1 = 0x00000001
J-Link>SWDReadAP 2
	Read AP register 2 = 0xXXXXXXXX
J-Link>SWDReadAP 2
	Read AP register 2 = 0x00000001
J-Link>SWDReadAP 2
	Read AP register 2 = 0x00000000

At this point the nRF5340 will remain accessible to the debugger until it is reset. Programming firmware to the cores can be done immediately without closing the JLink utility:

if 1
device NRF5340_XXAA_APP
speed 4000
connect
loadfile app_core_fw.hex
device NRF5340_XXAA_NET
loadfile net_core_fw.hex
exit

The firmware

Applications that are built using NCS tag v1.4.99-dev1 or newer include a function that takes care of setting up the APPROTECT registers. Currently this is done in system_nrf53_approtect.h and the device remains unlocked by default unless either ENABLE_APPROTECT or ENABLE_APPROTECT_USER_HANDLING was defined as a preprocesor symbol during compilation. This means that a simple application that contains only an infinite loop in main() can be programmed to a core to keep it unlocked. Note that the unlock firmware must be compiled separately for each core, however. An example project is attached to this post.

approtect.zip