-
Notifications
You must be signed in to change notification settings - Fork 28
DFU update
Performing DFU update is risky and by flashing custom firmware you may make this $9.99 $7.99 $6.99 $5.99 $4.99 watch semi-bricked. With good luck it may be enough to drain the battery to perform reset but most probably you may need to open the watch and attach SWD debugger to restore it to working order. Do not attempt to flash anything if you are not willing to open the device in case something goes wrong. My suggestion is to get at least two devices and use one with front glass removed for testing and flash only verified code to unopened watch. It may also make sense to enable watchdog timer in your code so it reboots when your code hangs (see e.g. here).
DS-D6 firmware is developed with Nordic SDK 11 and uses SoftDevice S132 v2.0.0. Memory layout is described here and Nordic bootloader details are here. Since Nordic bootloader sources are freely available as part of SDK the Desay bootloader is derived from this code (including bugs, see below).
0x00000-0x1BFFF - MBR and SoftDevice
0x1c000 main application (1.0.0 version ends at 0x47bd4)
0x78000 Desay DFU bootloader (ends at 0x7d464), UICR.NRFFW[0] points here
0x7e000 bootloader settings, UICR.NRFFW[1] points here
If your app is developed with SDK11 and SoftDevice S132 v2.0.0 (=>starts at 0x1c000) you can package just your app without SoftDevice or bootloader and flash it via DFU update over serial or bluetooth and it should run. With newer Nordic SDKs (12 and up) this became more complicated since the bootloader can be secure and package must be signed via private key but we are lucky that with SDK 11 this is still easy and you can upload anything. The tool Nordic provides for creating packages and performing serial DFU update is called nrfutil. As described for legacy package format one needs 0.5.x version. Latest is 0.5.3 Either install from source or install via pip (I tried python2 version) sudo pip2 install nrfutil==0.5.3
Also if you have newer pip >=10 you may need to downgrade it first as described here if you see same error message sudo python2 -m pip install pip==9.0.3
To create zip package from D6-DS.hex file (memory locations 0x1c000-0x47bd4) use following command:
nrfutil dfu genpkg --application-version 65535 --application D6-DS.hex D6-DS.zip
Result also uploaded here.
To perform serial update reboot to DFU bootloader and run following command
nrfutil dfu serial -pkg D6-DS.zip -p /dev/ttyUSB0
where ttyUSB0 is TTL 3.3V serial port connected to GND and middle usb pins. DS-D6 connector pinout is:
color | pin |
---|---|
black | GND |
green | RX (connect TX here) |
white | TX (connect RX here) |
red | 5V |
See also this photo. Default parameters are speed 38400 with no flow control. The output looks like this:
$ nrfutil dfu serial -pkg D6-DS.zip -p /dev/ttyUSB0
Upgrading target on /dev/ttyUSB0 with DFU package /home/pi/DS-D6/D6-DS.zip. Flow control is disabled.
[####################################] 100%
Device programmed.
First it goes quickly to 100% (package is checked?), then it takes some time. If you have usb-serial adapter with RX/TX LEDs, you should see some traffic all the time. If your adapter has no traffic LEDs please be patient and wait for 'device programmed' message.
If you don't see 'device programmed' but some timeout error instead then just re-run it again. Sometimes this happens but it eventually succeeds in the end. Also the quality of your usb to serial adapter may be the reason. I first tried with blackmagic probe which also includes serial port but this was constantly failing at various stages of upload so I had to use real adapter. Also if it fails with error "Serial port could not be opened" then check permissions, on some systems normal users do not belong to group (e.g. dialout) with rights to access serial port. In such case fix permissions/add user to group or try to run nrfutil with sudo.
To put device to DFU mode just try to run the serial procedure and it will probably switch to bootloader automatically but will timeout so you need to retry. Other way is to use following AT commands over bluetooth:
BT+UPGB:1
BT+RESET
The UPGB command writes value 1 to GPREGRET register and this is a flag for bootloader to enter DFU mode after reset. This is similar to how Nordic bootloader works too. If you implement your own firmware you should provide way to set this too so your app could be updated or original firmware could be restored. E.g. in Espruino it is possible to trigger this with poke32(0x4000051c,1)
.
If you need both the latest nrfutil for newer Nordic SDKs and this old 0.5.3 you can also use adafruit fork of old 0.5.3 code ported to python3. You can use it together with latest nrfutil as long as you install original nrfutil in python2 and adafruit one in python3. It has different command name adafruit-nrfutil and different output when sending the update but it should work too with baud rate forced to 38400 (default is 115200 for this fork). https://github.com/adafruit/Adafruit_nRF52_nrfutil
There is now user friendly Android app for flashing custom firmware over bluetooth done by atc1441, see Google Play page here
https://github.com/fanoush/ds-d6/blob/master/fwdump/README.md#desay_dfupy-flasher
nrfutil can also make package with just soft device or combined soft device and bootloader. bootloader is dependent on soft device version so you can update only minor versions of soft device without updating also bootloader.
It is possible to upgrade 2.0.0 SoftDevice to 2.0.1 to get latest bugfixes. Upgrade package is here
Package data is loaded into application space first so after update one needs to send application package again.
I also tried softdevice 3.0 with newer bootloader however I have verified that Desay bootloader cannot successfully flash such combined bootloader+softdevice package. Looks like some part of soft device is not updated correctly and result does not boot. While the bootloader itself is updated correctly the SoftDevice is not. Later I found that this is in fact known Nordic bug described here https://devzone.nordicsemi.com/f/nordic-q-a/16774/updating-from-s132-v2-0-x-to-s132-v3-0-0-with-dual-bank-bootloader-from-sdk-v11-0-0-does-not-work
I have binary patched the bug in existing bootloader (for details see below) and working bootloader is here. You first need to update existing bootloader with the -fixed- DFU zip and only then you can safely use combined packages with newer soft device and bootloader.
Below is relevant code in desay bootloader binary (decompiled by arm-none-eabi-objdump -D -bihex -marm ds-d6-bootloader.hex -Mforce-thumb > ds-d6-bootloader.S
). Looks like wrong, unaligned block size is in register r4 so what we need is to replace this:
7a7e8: f5a2 5080 sub.w r0, r2, #4096 ; 0x1000 ; r0 =boot_settings.sd_image_start - sd_start
7a7ec: 0844 lsrs r4, r0, #1 ;r4 = block_size = (boot_settings.sd_image_start - sd_start) / 2;
with this
7a7e8: f44f 4450 mov.w r4, #53248 ; 0xd000
7a7ec: bf00 nop
I have uploaded DFU package for this fixed bootloader alone and also together with SoftDevice 2.0.1, check DS-D6-desay-bootloader-fix* zips in fwdump folder. I have also verified that this indeed successfully flashes combined package with SoftDevice 132v3.0.0 and SDK12 based Espruino bootloader.
Full listing of buggy dfu_sd_image_swap method:
<dfu_sd_image_swap>:
7a7c8: e92d 41f0 stmdb sp!, {r4, r5, r6, r7, r8, lr}
7a7cc: b088 sub sp, #32
7a7ce: 4668 mov r0, sp
bootloader_settings_get(&boot_settings);
7a7d0: f7ff fad8 bl 0x79d84
7a7d4: 9803 ldr r0, [sp, #12] ; r0=boot_settings.sd_image_size
if (boot_settings.sd_image_size == 0)
{
return NRF_SUCCESS;
}
7a7d6: 2800 cmp r0, #0
7a7d8: d02b beq.n 0x7a832
if ((SOFTDEVICE_REGION_START + boot_settings.sd_image_size) > boot_settings.sd_image_start)
7a7da: 9a06 ldr r2, [sp, #24] ;r2=boot_settings.sd_image_start
7a7dc: f500 5080 add.w r0, r0, #4096 ; 0x1000 ; +SOFTDEVICE_REGION_START
7a7e0: f44f 5180 mov.w r1, #4096 ; 0x1000 ; r1=SOFTDEVICE_REGION_START
7a7e4: 4290 cmp r0, r2
7a7e6: d927 bls.n 0x7a838
uint32_t sd_start = SOFTDEVICE_REGION_START;
uint32_t block_size = (boot_settings.sd_image_start - sd_start) / 2;
uint32_t image_end = boot_settings.sd_image_start + boot_settings.sd_image_size;
uint32_t img_block_start = boot_settings.sd_image_start + 2 * block_size;
uint32_t sd_block_start = sd_start + 2 * block_size;
7a7e8: f5a2 5080 sub.w r0, r2, #4096 ; 0x1000 ; r0 =boot_settings.sd_image_start - sd_start
7a7ec: 0844 lsrs r4, r0, #1 ;r4 = block_size = (boot_settings.sd_image_start - sd_start) / 2;
7a7ee: 4610 mov r0, r2 ; boot_settings.sd_image_start
7a7f0: 9a03 ldr r2, [sp, #12]; boot_settings.sd_image_size
7a7f2: eb00 0644 add.w r6, r0, r4, lsl #1 ;r6 = img_block_start = boot_settings.sd_image_start + 2 * block_size;
7a7f6: 1887 adds r7, r0, r2 ;r7= image_end = boot_settings.sd_image_start + boot_settings.sd_image_size;
if (SD_SIZE_GET(MBR_SIZE) < boot_settings.sd_image_size)
{
7a7f8: f44f 5040 mov.w r0, #12288 ; 0x3000
7a7fc: 460d mov r5, r1 ; r5=sd_start = SOFTDEVICE_REGION_START;
7a7fe: 6880 ldr r0, [r0, #8] ;r0=SD_SIZE_GET(MBR_SIZE)
7a800: eb01 0844 add.w r8, r1, r4, lsl #1 ;r8 = sd_block_start = sd_start + 2 * block_size;
7a804: 4290 cmp r0, r2
7a806: d20e bcs.n 0x7a826
err_code = dfu_copy_sd((uint32_t *)(sd_start + block_size),
(uint32_t *)(sd_start + block_size),
sizeof(uint32_t));
7a808: f504 5080 add.w r0, r4, #4096 ; 0x1000
7a80c: 2204 movs r2, #4
7a80e: 4601 mov r1, r0
7a810: f7ff fd73 bl 0x7a2fa
VERIFY_SUCCESS(err_code);
7a814: 2800 cmp r0, #0
7a816: d10c bne.n 0x7a832
err_code = dfu_copy_sd((uint32_t *)sd_start, (uint32_t *)sd_start, sizeof(uint32_t));
7a818: 4629 mov r1, r5
7a81a: 2204 movs r2, #4
7a81c: 4608 mov r0, r1
7a81e: f7ff fd6c bl 0x7a2fa
VERIFY_SUCCESS(err_code);
7a822: 2800 cmp r0, #0
7a824: d105 bne.n 0x7a832
return dfu_sd_img_block_swap((uint32_t *)img_block_start,
(uint32_t *)sd_block_start,
image_end - img_block_start,
block_size);
7a826: 1bba subs r2, r7, r6
7a828: 4623 mov r3, r4
7a82a: 4641 mov r1, r8
7a82c: 4630 mov r0, r6
7a82e: f000 f837 bl 0x7a8a0
7a832: b008 add sp, #32
7a834: e8bd 81f0 ldmia.w sp!, {r4, r5, r6, r7, r8, pc}
else
return dfu_copy_sd((uint32_t *)boot_settings.sd_image_start,
(uint32_t *)SOFTDEVICE_REGION_START,
boot_settings.sd_image_size);
7a838: 9a03 ldr r2, [sp, #12]
7a83a: 9806 ldr r0, [sp, #24]
7a83c: f7ff fd5d bl 0x7a2fa
7a840: e7f7 b.n 0x7a832
dfu_copy_sd:
7a2fa: b51f push {r0, r1, r2, r3, r4, lr}
7a2fc: 2301 movs r3, #1
7a2fe: 9300 str r3, [sp, #0]
7a300: e9cd 0101 strd r0, r1, [sp, #4]
7a304: 0890 lsrs r0, r2, #2
7a306: 9003 str r0, [sp, #12]
7a308: 4668 mov r0, sp
7a30a: df18 svc 24
7a30c: b004 add sp, #16
7a30e: bd10 pop {r4, pc}
Additionally, there is almost the same code in the method dfu_sd_image_validate() which is run when something fails and softdevice update is interrupted, this will continue and try again to finish the copy. Here needs to be replaced this:
0007a860 a2 f5 80 50 sub.w r0,r2,#0x1000
0007a864 43 08 lsr r3,r0,#0x1
with this
0007a860 4f f4 50 43 mov.w r3,#0xd000
0007a864 00 bf nop