Skip to content

Replacing Nordic DFU bootloader

fanoush edited this page May 25, 2020 · 5 revisions

While it is typically possible to upgrade bootloader and softdevice via Nordic DFU procedure sometimes this may not work at all (by design) or may be too risky and fragile and you may end up with bricked device when trying. Some examples:

  • Relocating bootloader to different address. This is not possible via DFU. Also flashing DFU bootloader package with bootloader made for different address will brick your device. This is due to bootloader address being stored in UICR register at location 0x10001014 which cannot be changed via DFU procedure (there is no code for it in the bootloader). Also there is no information in DFU package about bootloader start address so new bootloader will be written to wrong address it was not compiled for and will crash when started.

  • Switching between dual bank and single bank bootloader seems to be not possible too. Most probably it is due to different structures used for storing update progress see also similar issue described here

  • Missing MBR settings stored at 0x10001018 UICR address, this still allows updating application but when trying softdevice or bootloader update the procedure hangs becasue MBR needs to overwrite bootloader or softdevice and cannot do this without MBR settings. This is the case with DaFit watches and also iBAND watches (F07,F10, DK08)

Fortunately with few tricks it is possible to safely erase/update/relocate bootloader over bluetooth while the application is running. Any application can do it, one example is DaFit watch bootloader installer, here I will describe how it can be done via Espruino.

Nordic boot sequence

Boot procedure is described in reply here and also in SDK documentation. Both Softdevice and Bootloader is optional. You can have just the application without softdevice or softdevice and application without bootloader. So one procedure to update bootloader in few steps is:

  • Disable bootloader via clearing 0x10001014 UICR address. After reboot application will continue to run without any bootloader being part of boot sequence.
  • Erase existing bootloader and store new bootloader to flash memory.
  • Update UICR to point to new bootloader.

These steps can be done in one step or with application reboot between each step.

Clearing UICR

UICR is stored in special flash page but same rules apply as erasing and writing ordinary flash pages - it is only possible to clear bits (change 1 to 0) and there are limits for number of writes to single page before erase is needed. So to reset bootloader start address whole UICR page must be cleared.

Another issue is that while SoftDevice is running some hardware registers are protected from writing for stability reasons and NVMC flash controller is one of them. This is mainly because CPU is completely halted while flash memory writing or erasing is done and this would interrupt bluetooth timing. So to allow writing to flash SoftDevice implements API which does the writing in small steps between radio traffic. And unfortunately there is no SoftDevice API to write or erase UICR registers!

So the only way to write and erase UICR registers is to temporarily stop Bluetooth and disable SoftDevice. With small one line patch this is possible to do with Espruino in relatively easy and safe way. Espruino already allows to restart SoftDevice because some changes in Bluetooth configuration cannot be done without it, so the patch implements executing custom javascript method between stopping and starting it. In such method direct access to NVMC controller is possible and writing and erasing UICR is possible via few poke32 calls.

Espruino examples

First before erasing it is good to backup UICR and/or see what is already stored there. Apart from bootloader related settings you might want to preserve contents of registers PSELRESET0,1 and NFCPINS = locations 0x10001000 + 0x200,0x204,0x20c. Typically both are not needed and can be kept erased and changed to 0xffffffff unless you want the reset pin to work or the device reuses NFC pins for generic GPIO access (like e.g. ID205L Smartwatch).

  • Print interesting UICR values:
peek32(0x10001014).toString(16);
peek32(0x10001018).toString(16);
peek32(0x10001204).toString(16); // RESET pin, all FFs or 0x15=21
peek32(0x1000120c).toString(16); // NFC pins, lowest bit is zero for NFC as GPIO
peek32(0x10001208).toString(16); // APROTECT -device is locked if lowest byte is 00
  • Backup UICR:
var f=require("Flash")
for (a=0x10001000;a<0x10001400;a+=256) console.log(btoa(f.read(256,a)));

then copy paste those lines to text file and use base64 decode on that, e.g. in linux run

base64 -d file.base64 >file.bin
  • Clear UICR (will disable bootloader, unlock device, disable reset pin, ..):
NRF.onRestart=function(){
poke32(0x4001e504,2);while(!peek32(0x4001e400)); // enable flash erase
poke32(0x4001e514,1);while(!peek32(0x4001e400)); // erase whole uicr
poke32(0x4001e504,0);while(!peek32(0x4001e400)); // disable flash writing
}
NRF.restart(); // will schedule SoftDevice restart after you disconnect

To run the code you must temporarily disconnect from the device to allow bluetooth and SoftDevice restart. After reconnect you may try to check via peek32 the UICR is cleared to all FFs.

  • Write UICR:

This will enable rest pin, turn off NFC functionality on NFC pins and enable bootloader at address 0x7a000. Do not run this unless you have valid bootloader stored there!

NRF.onRestart=function(){
poke32(0x4001e504,1);while(!peek32(0x4001e400)); // enable flash writing
poke32(0x10001014,0x7A000);while(!peek32(0x4001e400)); // set bootloader address 
poke32(0x10001018,0x7E000);while(!peek32(0x4001e400)); // set mbr settings
poke32(0x10001200,21);while(!peek32(0x4001e400)); // enable reset pin 21
poke32(0x10001204,21);while(!peek32(0x4001e400)); // confirm reset pin
poke32(0x1000120c,0xfffffffe);while(!peek32(0x4001e400)); // NFC pins as GPIO
poke32(0x4001e504, 0);while(!peek32(0x4001e400)); // disable flash writing
}
NRF.restart();

Flashing bootloader

For writing to flash memory in Espruino it is possible to use Flash module. To write to bootloader location some Espruino builds needs to enable unsafe flag. To load bootloader binary to flash one easy way is to paste it via clipboard. First paste methods for flash writing and verification like this:

E.setFlags({unsafeFlash:1});
var fl=require("Flash");
var ladd=0;var lpg=0;var nadd=0;// last address, last page addr, next addr
var flash=function(a,d){
  if (nadd>0 && nadd<a) console.log("Hole in data, got "+a.toString(16)+", expected "+nadd.toString(16));
  var p=fl.getPage(a).addr;
  if (p>lpg) {fl.erasePage(p);lpg=p;console.log("Erasing page 0x"+p.toString(16));}
  p=fl.getPage(a+d.length-1).addr;
  if (p>lpg) {fl.erasePage(p);lpg=p;console.log("Erasing page 0x"+p.toString(16));}
  if (a>ladd) {fl.write(d,a);ladd=a;nadd=a+d.length;}
  console.log(a.toString(16)+" F-OK")
}
var verify=function(a,d){
  if (typeof(d)=="string") d=E.toUint8Array(d);
  var m=fl.read(d.length,a)
  for (var i=0;i<d.length;i++,a++)
    if (m[i]!=d[i]) console.log(a.toString(16)+" V-FAIL");
  console.log(a.toString(16)+" V-OK");
}
var f=flash

Then you can convert bootloader binary on your PC to base64, here is linux bash script to convert file boot-7a000.bin to be flashed to 0x7a000 address.

a=$((0x7a000));for i in `base64 -w96  boot-7a000.bin` ; do echo "f(${a},atob('$i'));";a=$((a+72)) ; done

Output for working generic single bank SDK11 bootloader for address 0x7a000 compiled from https://github.com/fanoush/nrf52-legacy-bootloader is here. This one is suitable e.g. for iBAND smartwatches like F07, F10 or DK08.

After copying and pasting to Espruino IDE left side console via ctrl+v you should see lines of F-OK lines. To verify it is really flashed correctly you may set f method to verify instead of flashing via f=verify and then paste same bootloader code again. You should see lines of V-OK.

Here is also code to erase bootloader and MBR settings. Beware that if you still or already have DFU bootloader enabled and you will reboot, this will stay in DFU mode and you will need to reflash Espruino as bootloader settings page contains 'valid app' flag that this will clear. However if you are updating between single and dual bank bootloaders this may be good idea to do as last step to be sure there are no leftovers from previous bootloader.

E.setFlags({unsafeFlash:1});
var f=require("Flash");
f.erasePage(0x7f000);
f.erasePage(0x7e000);
E.reboot();