前面几篇都在介绍Secure DFU,而在SDK 12之前版本的DFU,没有签名,称为Legacy DFU。

Secure DFU是由Legacy DFU发展而来,前面介绍的大多数概念都适用于Legacy DFU,本文做一些补充介绍。

(1)类型

总结一下DFU的类型:

DFU类型 SDK版本 特点
Secure DFU SDK 12.x + 带签名校验,Flash占用大,工具支持完善
Legacy DFU SDK 11.x – 不带签名,Flash占用小,得找很老的工具
Open DFU SDK 15.x + 仅适用于USB接口,不适用于BLE

如果使用SDK 11.x,Legacy DFU是一个自然的选择。如果使用较新的SDK,只能选择Secure DFU。

(2)升级包

Legacy DFU生成升级包需要使用nrfutil v0.3版本,而不能使用新版本(新版本已经到达3.x)。

下载安装Master Control Panel(PC版)可以在其安装目录下找到nrfutil v0.3,下载地址为:链接

nrfutil v0.3的命令行参数与新版本不一样,生成升级包的命令为:

nrfutil dfu genpkg --application app.hex dfu_app.zip

有了升级包就可以利用新版的nrfutil或nRF connect APP进行DFU。

(3)Bootloader Settings

Legacy DFU的Bootloader Settings结构比较简单,仅有一个Flag指示应用程序的有效性。

nrfutil v0.3不能生成bl_settings.hex,新版本的nrfutil生成的bl_settings.hex与Legacy DFU不兼容。

Bootloader Settings中的Flag地址是确定的,因此可以利用nrfjprog向该Flash地址写1,手动修改Flag,命令为:

nrfjprog --family NRF51 --memwr 0x3FC00 --val 1

其中0x3FC00是Bootloader Settings的储存地址,而Flag位于该地址的首字节。

同样,如果通过DFU方式升级固件,Bootloader会自动生成和维护Bootloader Settings。

(4)对比

与Secure DFU相比,Legacy DFU有一些特别的地方:

  • Bootloader占用Flash为12 kB,Secure DFU占24 kB
  • Bootloader中MTU Size只支持23,无法利用BLE 4.2的特性
  • SDK提供了ble_app_hrs_with_dfu作为DFU示例,没有一个专门的DFU示例
  • DFU服务不需要检测Bootloader,可以脱离Bootloader运行,当然跳转时会出错
  • 待续……

 

(完)

从Application跳转到Bootloader中,可以采用按键(Button)触发,也可以直接用BLE命令,因此后者称为Buttonless。本文介绍Buttonless DFU服务的技术细节和使用方法。

(1)DFU服务

Buttonless DFU是一个自定义服务,它下面仅包含一个特征:

Attribute UUID Property
Secure DFU Service 0xFE59
Buttonless DFU Characteristic 0x8EC90003-F315-4F60-9FB8-838830DAEA50 Write, Indicate

Buttonless DFU特征主要职责是:从Application进入DFU Mode。

DFU Mode指芯片停驻在Bootloader中,准备或正在执行DFU相关动作。

执行这个过程,DFU Controller发送一个Enter DFU Mode的命令,该命令需要返回Response,然后从Application跳转进入Bootloader,进入DFU Mode。

在代码层面,Enter DFU Mode命令向GPREGRET寄存器写入一个标志位,然后重启。Bootloader启动时会检查该标志位。具体代码为:

uint32_t ble_dfu_buttonless_bootloader_start_finalize(void)
{
    err_code = sd_power_gpregret_clr(0, 0xffffffff);
    err_code = sd_power_gpregret_set(0, BOOTLOADER_DFU_START);
    ...
    NVIC_SystemReset();
}

(2)添加DFU服务

SDK 15提供了一个Buttonless的模板工程:ble_app_buttonless_dfu。用户可以基于该模板,开发自己的应用。

为了理解Buttonless服务,这里向ble_app_hrs工程手动添加Buttonless DFU服务。

打开<sdk>\examples\ble_peripheral\ble_app_hrs工程,确保该工程能够正常运行。

添加源文件

在工程中增加一个文件夹nRF_DFU,并添加以下文件:

  • <sdk>\components\ble\ble_services\ble_dfu\ble_dfu.c
  • <sdk>\components\ble\ble_services\ble_dfu\ble_dfu_bonded.c
  • <sdk>\components\ble\ble_services\ble_dfu\ble_dfu_unbonded.c
  • <sdk>\components\libraries\bootloader\dfu\nrf_dfu_svci.c

添加Include目录

在工程配置窗口中找到User Include Directories,添加以下路径:

  • ../../../../../../components/libraries/bootloader
  • ../../../../../../components/libraries/bootloader/ble_dfu
  • ../../../../../../components/libraries/bootloader/dfu
  • ../../../../../../components/libraries/svc

如果使用SEGGER Embedded Studio,需要额外注意这些路径末尾的空格,可能导致不被正确识别。

添加宏开关

在工程配置窗口中找到Preprocessor Definitions,添加下列项:

  • NRF_DFU_SVCI_ENABLED
  • NRF_DFU_TRANSPORT_BLE=1
  • BL_SETTINGS_ACCESS_ONLY

配置sdk_config

在sdk_config.h中,做如下更改:

  • BLE_DFU_ENABLED = 1
  • NRF_SDH_BLE_VS_UUID_COUNT += 1

添加头文件

在main.c中添加以下头文件:

  • #include “nrf_dfu_ble_svci_bond_sharing.h”
  • #include “nrf_svci_async_function.h”
  • #include “nrf_svci_async_handler.h”
  • #include “ble_dfu.h”
  • #include “nrf_power.h”
  • #include “nrf_bootloader_info.h”

添加代码

打开Buttonless示例工程的main.c,将下列函数复制到当前工程的main.c里:

static bool app_shutdown_handler(nrf_pwr_mgmt_evt_t event){};
NRF_PWR_MGMT_HANDLER_REGISTER(app_shutdown_handler, 0);
static void buttonless_dfu_sdh_state_observer(nrf_sdh_state_evt_t state, void * p_context){};
NRF_SDH_STATE_OBSERVER(m_buttonless_dfu_state_obs, 0) = {};
static void ble_dfu_evt_handler(ble_dfu_buttonless_evt_type_t event){};

添加Buttonless DFU服务

在main.c/services_init函数末尾添加DFU服务:

ble_dfu_buttonless_init_t dfus_init = {0};

// Initialize the async SVCI interface to bootloader.
err_code = ble_dfu_buttonless_async_svci_init();
APP_ERROR_CHECK(err_code);

dfus_init.evt_handler = ble_dfu_evt_handler;

err_code = ble_dfu_buttonless_init(&dfus_init);
APP_ERROR_CHECK(err_code);    

调整内存地址

按照上面步骤依次走下来,已经可以通过编译。烧录Softdevice和Application,打开串口工具,应该看到日志消息提示内存地址和内存大小需要调整,如下:

<warning> nrf_sdh_ble: Insufficient RAM allocated for the SoftDevice.
<warning> nrf_sdh_ble: Change the RAM start location from 0x20002B90 to 0x20002B98.
<warning> nrf_sdh_ble: Maximum RAM size for application is 0xD468.
<error> nrf_sdh_ble: sd_ble_enable() returned NRF_ERROR_NO_MEM.

在工程配置窗口中找到Section Placement Macro,参照日志调整RAM的起始地址和Size。

重新编译和下载工程。

此时串口工具不再提示内存问题,但是仍然提示Fatal error。这是因为Buttonless DFU服务会检测芯片中的Bootloader,如果我们没有预先烧录Bootloader,就会出现这个错误。

 

至此,为ble_app_hrs工程添加Buttonless DFU服务,代码层面工作全部结束。

(3)Bootloader Settings

直接烧录softdevice、bootloader和application,会发现application并未运行,芯片一直跑在Bootloader中。

芯片启动后先进入Bootloader,检测Bootloader Settings中的数据,如果这些数据指示Flash中有一个有效的Application,则跳转进入Application。Bootloader Settings是Flash中的一段区域,它包含了Application的大小、CRC等数据,执行DFU时也会在这里存储状态信息。

正常执行DFU时,Bootloader自动生成和维护Bootloader Settings信息。而烧录过程不同,需要手动写入。可以根据application.hex生成一个bl_settings.hex,以产生这些数据,然后烧录这个hex。

生成bl_settings.hex的命令为:

nrfutil settings generate 
    --family NRF52 
    --application app.hex 
    --application-version 2 
    --bootloader-version 2 
    --bl-settings-version 1 
    bl_settings.hex

注意–bl-settings-version只能是1,不可以是其他值。从源代码上看它应该是个预留位,没有作用。

通过命令nrfutil settings display bl_settings.hex可以查看Bootloader Settings的内容:

(4)烧录

每次我们修改应用程序代码,都会导致现有的Bootloader Settings信息失效,需要重新生成并烧录bl_settings.hex。可以利用批处理来完成生成、烧录的动作。

第一次烧录程序时,需要烧录softdevice和bootloader:

@echo off

set app=<your app.hex path>

nrfutil settings generate --family NRF52 --application %app% --application-version 2 --bootloader-version 2 --bl-settings-version 1 bl_settings.hex

nrfjprog -e
nrfjprog --program softdevice.hex
nrfjprog --program bootloader.hex
nrfjprog --program bl_settings.hex
nrfjprog --program %app%
nrfjprog --reset

pause

后续再次烧录,可以省略烧录Softdevice和Bootloader的步骤:

@echo off

set app=<your app.hex path>

nrfutil settings generate --family NRF52 --application %app% --application-version 2 --bootloader-version 2 --bl-settings-version 1 bl_settings.hex

nrfjprog --program bl_settings.hex --sectorerase
nrfjprog --program %app% --sectorerase
nrfjprog --reset

pause

nrfjprog命令中使用了--sectorerase参数,以保证只擦除并填写指定区域。

(5)调试

调试时候会频繁的修改代码,如果每次都要重新生成和下载一遍bl_settings.hex,会疯掉。

我们可以在代码中禁止Bootloader检测Bootloader Settings,让它直接跳转进入Application。

打开Bootloader工程main.c,注释掉main.c中的下面两行代码:

ret_val = nrf_bootloader_init(dfu_observer);
APP_ERROR_CHECK(ret_val);

芯片上电后,Bootloader完全不理会DFU Mode,直接进入Application。这样就如同一个没有Bootloader的工程,可以在Application中自由的调试,也无需生成bl_settings.hex。

 

(完)

从SDK 12开始,Nordic为DFU操作增加了签名校验机制,称为Secure DFU。执行Secure DFU,需要使用升级包(zip),而不能直接使用二进制文件(hex/bin),本文介绍如何生成和使用升级包。

(1)nrfutil

nrfutil是Nordic提供的命令行工具,它用于执行以下任务:

  • 生成公钥、私钥
  • 生成升级包
  • 生成bootloader_settings.hex
  • 执行BLE DFU或串口DFU

nrfutil是开源的python项目,托管在github上,可以下载预编译的nrfutil.exe,如果系统中已安装了python 2.7(不支持python 3.x),也可以通过pip命令进行安装:

pip install nrfutil

本文使用预编译的nrfutil.exe,放到C:\nordic\nrfutil中,并将该目录加入系统环境变量,方便在cmd中调用。

(2)公钥、私钥

Secure DFU采用签名(Sign)方式保证DFU的安全。直观上,它解决了一个安全问题:

对于传统的DFU,如果攻击者进入设备的Bootloader模式,那么他可以通过DFU将第三方的固件写入设备。这种非法升级的问题根源在于Bootloader无法识别新固件是否合法有效。

Secure DFU在Bootloader和升级包中分别加入签名,升级时通过校验签名保证升级包的安全和可靠。

在当前目录下生成一个唯一的私钥(private_key.pem):

nrfutil keys generate private_key.pem

可以用文本编辑器打开private_key.pem,其内容形式如下:

-----BEGIN EC PRIVATE KEY-----
MHcCAQEEICS144KRk5kbsB42yuQHFutQJV3nVfh7vUtj80nokAsboAoGCCqGSM49
AwEHoUQDQgAEKlnoPDH7wA00TtqUjc+5tuoI8gov4BxZR9KUITgKAmx/v4Jvpx2t
jRI6meLVMSme4itY78N+YtuK7s0wLbNzUw==
-----END EC PRIVATE KEY-----

利用私钥,可以生成一个公钥:

nrfutil keys display 
    --key pk 
    --format code private_key.pem 
    --out_file dfu_public_key.c

执行完毕,生成的公钥保存在dfu_public_key.c文件中,其文件内容形式如下:

/* This file was automatically generated by nrfutil on 2018-04-14 (YY-MM-DD) at 08:29:52 */

#include "stdint.h"
#include "compiler_abstraction.h"

/** @brief Public key used to verify DFU images */
__ALIGN(4) const uint8_t pk[64] =
{
    0x6c, 0x02, 0x0a, 0x38, 0x21, 0x94, 0xd2, 0x47, 0x59, 0x1c, 0xe0, 0x2f, 0x0a, 0xf2, 0x08, 0xea, 0xb6, 0xb9, 0xcf, 0x8d, 0x94, 0xda, 0x4e, 0x34, 0x0d, 0xc0, 0xfb, 0x31, 0x3c, 0xe8, 0x59, 0x2a, 
    0x53, 0x73, 0xb3, 0x2d, 0x30, 0xcd, 0xee, 0x8a, 0xdb, 0x62, 0x7e, 0xc3, 0xef, 0x58, 0x2b, 0xe2, 0x9e, 0x29, 0x31, 0xd5, 0xe2, 0x99, 0x3a, 0x12, 0x8d, 0xad, 0x1d, 0xa7, 0x6f, 0x82, 0xbf, 0x7f
};

Bootloader工程中会使用公钥dfu_public_key.c,这样Bootloader就可以利用公钥验证签名。

(3)升级包

升级包其实就是一个zip压缩包,一个典型的升级包内容如下:

各个文件的含义:

文件 含义
manifest.json 文件清单
nrf52832_xxaa.bin 新固件
nrf52832_xxaa.dat init packet

其中init packet包含了meta信息:新固件的类型、大小、版本和签名信息。这里的签名将在执行DFU被校验。

init packet是一个Protocol Buffer文件,它的内容很简单,但是不可读,可以通过命令nrfutil pkg display dfu_pkg.zip看到它的内容:

我们无需手动创建init packet,使用nrfutil生成升级包时,会自动完成这些工作。

生成升级包至少需要以下文件和信息:

  • 新固件文件
  • 私钥文件
  • 芯片型号
  • 支持的softdevice版本号
  • 应用程序版本号

假设新固件为app.hex,私钥文件为private_key.pem,芯片型号为nRF52,softdevice版本号为S132_nrf52_5.1.0,新固件版本号为0x02,最终生成的升级包文件为dfu_pkg.zip,那么对应的命令为:

nrfutil pkg generate 
    --application app.hex 
    --hw-version 52 
    --sd-req 0x5A 
    --application-version 0x02 
    --key-file private_key.pem 
    dfu_pkg.zip

命令中各参数含义很直观,值得一提的是--sd-req这个参数,它表示固件运行所需的softdevice版本,0x5A表示softdevice S132_nrf52_5.1.0。

获得sd-req的数值至少有以下几种途径:

  1. 通过命令nrfutil pkg generate --help查看
  2. 在线文档论坛帖子中查看
  3. 烧录一个BLE程序,在nrfgo Studio中查看
  4. 使用JFlash.exe打开softdevice.hex,找到地址0x300C的值(原理
  5. 利用Hex2Bin转换工具(开源版Keil版)将softdevice.hex转换成bin文件,然后利用任意十六进制查看器,查看地址0x300C的值
  6. 询问本文作者

如果在命令行中加入--debug-mode参数,就可以在DFU的校验阶段,忽略sd-req信息。

(4)执行DFU

现在有了升级包,可以利用nrfutil执行DFU,从而省去将升级包复制到手机,再利用APP做DFU的复杂流程。

假设DFU的目标设备是一个nRF52832开发板,先对其下载softdevice和bootloader,这时能够看到Bootloader正在广播,我这里BLE设备名称为DfuTest。

准备一个nRF51开发板(PCA10028)作为DFU的操作主机,芯片中不用预先下载任何程序,连上电脑后记录它的串口号COMx。

执行命令:

nrfutil dfu ble 
    -pkg dfu_pkg.zip 
    -ic NRF51 
    -p COM6 
    -n DfuTest 
    -f

最后的参数-f将自动给主机设备下载必要的固件程序。

这样就可以利用COM6对应的nRF51设备,向DfuTest设备做DFU升级,升级文件为dfu_pkg.zip,升级过程能看到一个进度条:

 

(完)