NRF52832学习笔记(6)——OTA DFU接口使用

一、简介

1.1 OTA DFU

DFU(Device Firmware Update) 设备固件升级,而 OTA(Over The Air) 空中升级技术是DFU其中一种类型。其他类型包括:UART、USB和SPI等有线方式升级。
NRF52832学习笔记(6)——OTA DFU接口使用

第一次烧录时需要将softdevice(协议栈)、bootloader(引导加载程序)、settings(引导程序配置页)、application(用户应用程序)合成一个hex文件进行烧录。

而之后的固件升级,只需通过将application(用户应用程序)合成一个zip的升级包,用手机APP进行上传更新。

以下将分别说明 bootloader、settings、application 、升级包在DFU中的作用及展示它们在Flash中的布局。

1.2 DFU Flash布局

DFU有两种模式,分别为:dual banksingle bank
简单来说 dual bank 模式在升级时系统先进入bootloader,然后把新系统(新固件)下载下来并校验成功,在擦除老系统(老固件),并运行新系统。缺点是耗费比较多的存储空间。

关于dual bank模式详细介绍查看 BLE DFU:Dual Bank

NRF52832学习笔记(6)——OTA DFU接口使用
single bank 模式在升级时系统先进入bootloader,然后立即擦除老系统(老固件),再把新系统(新固件)下载到原本老系统的存储区域,在运行起来。缺点是如果升级过程中出现问题或新固件有问题,就会一直停留在bootloader,相当于“变砖”了。
NRF52832学习笔记(6)——OTA DFU接口使用

注:Bootloader在接收数据前会检查Bank 1的大小,如果空间足够,就执行Dual Bank,否则当前应用程序,腾出空间执行Single Bank。SDK自动完成这个判断动作。

1.3 Bootloader

bootloader 是引导加载程序,DFU的基本流程就是在 bootloader中接收新固件数据并写入Flash,接收完毕后重启跳转并运行新固件 。其中SDK v11及更早版本中bootloader为开放透明性的Legacy DFU,而SDK v12之后有了加密性的Secure DFU。

关于Legacy DFU详细介绍查看 BLE DFU:Legacy DFU
关于Bootloader详细介绍查看 BLE DFU:Bootloader
NRF52832学习笔记(6)——OTA DFU接口使用

1.4 Settings

settings 全称bootloader settings,用于引导设备启动后由bootloader到application的自动跳转。bootloader启动时候,会检查Settings中的 bank0_bank_code、bank0_img_crc,如果二者都正确,则执行跳转进入application,否则驻留在bootloader中执行DFU。

关于settings详细介绍查看 解读Settings文件
NRF52832学习笔记(6)——OTA DFU接口使用

1.5 Application

在设备启动后bootloader到application进行正常工作,而当开发者后续需要对系统进行升级时,则需要由application重新跳转到bootloader中,这个过程可以采用按键方式(button)触发,也可以直接用BLE连接发送命令方式(buttonless),本文采取的切换DFU模式方式就是buttonless。

关于application详细介绍查看 BLE DFU:Application
NRF52832学习笔记(6)——OTA DFU接口使用
NRF52832学习笔记(6)——OTA DFU接口使用

1.6 升级包

从SDK 12开始,Nordic为DFU操作增加了签名校验机制,称为Secure DFU。执行Secure DFU,需要使用升级包(zip),而不能直接使用二进制文件(hex/bin)。
升级包 包括manifest.json(文件清单)、nrf52832_xxaa.bin(新固件)和nrf52832_xxaa.dat(init packet),其中init packet包含了meta信息:新固件的类型、大小、版本和签名信息。这里的签名将在执行DFU被校验。

关于升级包详细介绍查看 BLE DFU:生成升级包
关于init packet详细介绍查看 弄懂 Init Packet

二、安装软件

注意:由于Secure DFU需要micro-ecc库进行签名验证,需要micro_ecc_lib_nrf52.lib,所以需要使用GCC编译器生成。

所以我们只需将micro_ecc_lib_nrf52.lib加入我们的工程(在后面编译bootloader工程中说明),即可忽略以下2.1及2.2的安装

2.1 安装gcc-arm-none-eabi(可跳过)

作用:编译micro-ecc

官网下载:https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads
百度网盘:https://pan.baidu.com/s/1AQLMOVun3sMcvRd6WqL7AQ 提取码:jh4v

安装gcc-arm-none-eabi过程查看 Secure DFU环境搭建 中2.1,注意要安装在C盘

2.2 安装MinGW(可跳过)

作用:Windows的极简GNU编译器(GCC),执行make

官网下载:https://osdn.net/projects/mingw/releases/
百度网盘:https://pan.baidu.com/s/1QU-DRp8K2xFkUaMNflbs7Q 提取码:w5vy

安装MinGW过程及环境变量配置查看 Secure DFU环境搭建 中2.2和2.3,注意要安装在C盘

2.3 下载micro-ecc-master

作用:Nordic的DFU中采用了micro-ecc实现ECDSA算法,需要用到micro-ecc源码

官网下载:https://github.com/kmackay/micro-ecc
百度网盘:https://pan.baidu.com/s/1GYS5z-nbNgbyzrTk98NHog 提取码:tumw

2.4 安装Python

作用:pc-nrfutil需要用到python-2.7的环境

官网下载:https://www.python.org/downloads/
百度网盘:https://pan.baidu.com/s/1kphEO80KTAjGtVvmoaDWLw 提取码:du7i

安装Python过程及环境变量配置查看 Secure DFU环境搭建 中2.5,注意要安装在C盘

2.5 安装pc-nrfutil

作用:Nordic发布的PC端工具,支持无线DFU和加密功能

官网下载:https://github.com/NordicSemiconductor/pc-nrfutil/
百度网盘:https://pan.baidu.com/s/1L7-Rkv5P3nWG_8Lc-B6YhA 提取码:payb

以下两个选其一
1.把pc-nrfutil解压。打开有setup.py的文件夹,在此处打开dos命令(shift + 鼠标右键)
NRF52832学习笔记(6)——OTA DFU接口使用
输入python setup.py install,需要联网
NRF52832学习笔记(6)——OTA DFU接口使用
2.打开命令行,输入pip install nrfutil安装nrfutil,需要联网。
安装完成后,输入nrfutil version,如下则表示安装成功。
NRF52832学习笔记(6)——OTA DFU接口使用

三、修改Bootloader工程

3.1 添加工程并编译

找到SDK中 secure_bootloader 工程(nRF5_SDK_15.3.0_59ac345\examples\dfu\secure_bootloader\pca10040_ble\arm5_no_packs)
NRF52832学习笔记(6)——OTA DFU接口使用
然后进行编译,发现报出两类错误

  1. uecc.h 文件找不到

NRF52832学习笔记(6)——OTA DFU接口使用
\2. 需要生成私有的 key

NRF52832学习笔记(6)——OTA DFU接口使用

3.2 加入micro-ecc

把下载的 micro-ecc-master.zip 解压,解压后拷贝到nRF5_SDK_15.3.0_59ac345\external\micro-ecc文件中,重新命名为micro-ecc
NRF52832学习笔记(6)——OTA DFU接口使用

3.3 生成micro_ecc_lib_nrf52.lib并加入

生成 micro_ecc_lib_nrf52.lib 过程查看 Secure DFU环境搭建 中2.4

或者下载链接:https://pan.baidu.com/s/1zIUhCe7F4UCuQBQLCHRY8A [18qt]
解压后将 micro_ecc_lib_nrf52.lib 放到对应SDK\external\micro-ecc\nrf52hf_keil\armgcc文件夹下

3.4 生成dfu_public_key.c并加入

3.4.1 首先生成私钥

在D盘新建一个文件夹,命名为key,在cmd命令中输入以下内容:nrfutil.exe keys generate D:\key\private.key指令,生成私钥文件private.key
NRF52832学习笔记(6)——OTA DFU接口使用
注意:要保存好私钥private.key,以后每个新固件升级时,都要先通过这个私钥进行签名,一旦丢失,DFU将无法进行

3.4.2 然后生成公钥

输入生成公钥指令:nrfutil.exe keys display --key pk --format code D:\key\private.key --out_file public_key.c
NRF52832学习笔记(6)——OTA DFU接口使用

3.4.3 将公钥加入工程

将公钥public_key.c改名为 dfu_public_key.c,并将该文件替换掉目录\examples\dfu下的dfu_public_key.c
NRF52832学习笔记(6)——OTA DFU接口使用

3.5 根据情况屏蔽代码

如果LED灯IO口跟secure_bootloader工程默认引脚不同或原来LED引脚用作其他功能,需要将dfu_observer()函数中LED灯相关代码屏蔽,否则会一直运行在bootloader,不跳转到application

NRF52832学习笔记(6)——OTA DFU接口使用

3.6 配置sdk_config文件

点击 sdk_config.h 文件
NRF52832学习笔记(6)——OTA DFU接口使用
选择 Configuration Wizard
NRF52832学习笔记(6)——OTA DFU接口使用
将进入DFU方式改为无按键的BLE连接发送命令方式
NRF52832学习笔记(6)——OTA DFU接口使用

四、修改Application工程

4.1 配置sdk_config文件

点击 sdk_config.h 文件
NRF52832学习笔记(6)——OTA DFU接口使用
选择 Configuration Wizard
NRF52832学习笔记(6)——OTA DFU接口使用
勾选使能DFU服务
NRF52832学习笔记(6)——OTA DFU接口使用

修改UUID大小,在原有基础上+1

NRF52832学习笔记(6)——OTA DFU接口使用
修改RAM空间大小,每新增一个UUID增加0x10

NRF52832学习笔记(6)——OTA DFU接口使用

4.2 添加工程文件

4.2.1 添加源文件

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

  • \components\ble\ble_services\ble_dfu\ble_dfu.c
  • \components\ble\ble_services\ble_dfu\ble_dfu_bonded.c
  • \components\ble\ble_services\ble_dfu\ble_dfu_unbonded.c

NRF52832学习笔记(6)——OTA DFU接口使用

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

  • \components\libraries\bootloader\dfu\nrf_dfu_svci.c

NRF52832学习笔记(6)——OTA DFU接口使用

4.2.2 添加Include目录

添加以下路径:

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

4.2.3 添加宏

添加下列项:

  • NRF_DFU_TRANSPORT_BLE=1
  • BL_SETTINGS_ACCESS_ONLY
    NRF52832学习笔记(6)——OTA DFU接口使用

4.3 修改main.c

4.3.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"
123456

4.3.2 添加代码

在main.c中添加以下函数:

#if NRF_MODULE_ENABLED(BLE_DFU)
/**@brief Function for handling dfu events from the Buttonless Secure DFU service
 *
 * @param[in]   event   Event from the Buttonless Secure DFU service.
 */
static void ble_dfu_evt_handler(ble_dfu_buttonless_evt_type_t event)
{
    switch(event)
    {
        case BLE_DFU_EVT_BOOTLOADER_ENTER_PREPARE:
        {
            NRF_LOG_INFO("Device is preparing to enter bootloader mode.");

            // Prevent device from advertising on disconnect.
            ble_adv_modes_config_t config;
            advertising_config_get(&config);
            config.ble_adv_on_disconnect_disabled = true;
            ble_advertising_modes_config_set(&m_advertising, &config);

            // YOUR_JOB: Disconnect all other bonded devices that currently are connected.
            // 		   This is required to receive a service changed indication
            // 		   on bootup after a successful (or aborted) Device Firmware Update.
            uint32_t conn_count = ble_conn_state_for_each_connected(disconnect, NULL);
            NRF_LOG_INFO("Disconnected %d links.", conn_count);
            break;
        }

        case BLE_DFU_EVT_BOOTLOADER_ENTER:
            // YOUR_JOB: Write app-specific unwritten data to FLASH, control finalization of this
            //           by delaying reset by reporting false in app_shutdown_handler
            NRF_LOG_INFO("Device will enter bootloader mode.");
            break;

        case BLE_DFU_EVT_BOOTLOADER_ENTER_FAILED:
            NRF_LOG_ERROR("Request to enter bootloader mode failed asynchroneously.");
            // YOUR_JOB: Take corrective measures to resolve the issue
            //           like calling APP_ERROR_CHECK to reset the device.
            break;

        case BLE_DFU_EVT_RESPONSE_SEND_ERROR:
            NRF_LOG_ERROR("Request to send a response to client failed.");
            // YOUR_JOB: Take corrective measures to resolve the issue
            //           like calling APP_ERROR_CHECK to reset the device.
            APP_ERROR_CHECK(false);
            break;

        default:
            NRF_LOG_ERROR("Unknown event from ble_dfu_buttonless.");
            break;
    }
}

/**@brief Handler for shutdown preparation.
 *
 * @details During shutdown procedures, this function will be called at a 1 second interval
 *          untill the function returns true. When the function returns true, it means that the
 *          app is ready to reset to DFU mode.
 *
 * @param[in]   event   Power manager event.
 *
 * @retval  True if shutdown is allowed by this power manager handler, otherwise false.
 */
static bool app_shutdown_handler(nrf_pwr_mgmt_evt_t event)
{
    switch(event)
    {
        case NRF_PWR_MGMT_EVT_PREPARE_DFU:
            NRF_LOG_INFO("Power management wants to reset to DFU mode.");
            // YOUR_JOB: Get ready to reset into DFU mode
            //
            // If you aren't finished with any ongoing tasks, return "false" to
            // signal to the system that reset is impossible at this stage.
            //
            // Here is an example using a variable to delay resetting the device.
            //
            // if (!m_ready_for_reset)
            // {
            //      return false;
            // }
            // else
            //{
            //
            //    // Device ready to enter
            //    uint32_t err_code;
            //    err_code = sd_softdevice_disable();
            //    APP_ERROR_CHECK(err_code);
            //    err_code = app_timer_stop_all();
            //    APP_ERROR_CHECK(err_code);
            //}
            break;

        default:
            // YOUR_JOB: Implement any of the other events available from the power management module:
            //      -NRF_PWR_MGMT_EVT_PREPARE_SYSOFF
            //      -NRF_PWR_MGMT_EVT_PREPARE_WAKEUP
            //      -NRF_PWR_MGMT_EVT_PREPARE_RESET
            return true;
    }

    NRF_LOG_INFO("Power management allowed to reset to DFU mode.");
    return true;
}

//lint -esym(528, m_app_shutdown_handler)
/**@brief Register application shutdown handler with priority 0.
 */
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)
{
    if(state == NRF_SDH_EVT_STATE_DISABLED)
    {
        // Softdevice was disabled before going into reset. Inform bootloader to skip CRC on next boot.
        nrf_power_gpregret2_set(BOOTLOADER_DFU_SKIP_CRC);

        //Go to system off.
        nrf_pwr_mgmt_shutdown(NRF_PWR_MGMT_SHUTDOWN_GOTO_SYSOFF);
    }
}

/* nrf_sdh state observer. */
NRF_SDH_STATE_OBSERVER(m_buttonless_dfu_state_obs, 0) =
{
    .handler = buttonless_dfu_sdh_state_observer,
};


static void advertising_config_get(ble_adv_modes_config_t *p_config)
{
    memset(p_config, 0, sizeof(ble_adv_modes_config_t));

    p_config->ble_adv_fast_enabled  = true;
    p_config->ble_adv_fast_interval = APP_ADV_INTERVAL;
    p_config->ble_adv_fast_timeout  = APP_ADV_DURATION;
}


static void disconnect(uint16_t conn_handle, void *p_context)
{
    UNUSED_PARAMETER(p_context);

    ret_code_t err_code = sd_ble_gap_disconnect(conn_handle, BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
    if(err_code != NRF_SUCCESS)
    {
        NRF_LOG_WARNING("Failed to disconnect connection. Connection handle: %d Error: %d", conn_handle, err_code);
    }
    else
    {
        NRF_LOG_DEBUG("Disconnected connection handle %d", conn_handle);
    }
}
#endif
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152

4.3.3 添加DFU服务

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

/**@brief Function for initializing services that will be used by the application.
 */
static void services_init(void)
{
    ret_code_t err_code;
    nrf_ble_qwr_init_t qwr_init = {0};

    // Initialize Queued Write Module.
    qwr_init.error_handler = nrf_qwr_error_handler;

    err_code = nrf_ble_qwr_init(&m_qwr, &qwr_init);
    APP_ERROR_CHECK(err_code);
	
#if NRF_MODULE_ENABLED(BLE_DFU)		
	ble_dfu_buttonless_init_t dfus_init = {0};	
	// Initialize DFU.
    dfus_init.evt_handler = ble_dfu_evt_handler;
    err_code = ble_dfu_buttonless_init(&dfus_init);
    APP_ERROR_CHECK(err_code);
#endif
}
123456789101112131415161718192021

五、烧录固件

5.1 生成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 nrf52832_xxaa.hex --application-version 0 --bootloader-version 0 --bl-settings-version 2 settings.hex

其中SDK15.3之后 bl-settings-version值为2,之前的SDK版本值为1

NRF52832学习笔记(6)——OTA DFU接口使用

5.2 生成四合一hex文件

mergehex 一次最大合并3个文件,所以我们需要分两次合并,将softdevice(协议栈)、bootloader(引导加载程序)和application(应用程序)编译生成的hex文件存在单独文件夹,鼠标放在空白处,shift+右键执行“在此处打开命令窗口”,输入命令:

mergehex -m s132_nrf52_6.1.1_softdevice.hex bootloader.hex nrf52832_xxaa.hex -o output3tol.hex

此时完成一次文件合并,再次输入命令:
mergehex -m output3tol.hex settings.hex -o output4tol.hex

至此,直接用 nRFgo Studio 下载一个文件即可。

5.3 烧录

5.3.1 使用nRFgo Studio烧录

先擦除,在烧录output4tol.hex

NRF52832学习笔记(6)——OTA DFU接口使用

六、升级固件

6.1 生成升级包

private.key(私钥)nrf52832_xxaa.hex(应用程序) 放在同一文件夹,鼠标放在空白处,shift+右键执行“在此处打开命令窗口”,输入命令:
nrfutil pkg generate --hw-version 52 --application-version 1 --application nrf52832_xxaa.hex --sd-req 0xB7 --key-file private.key dfufile.zip
其中 –sd-req 0xB7 为协议栈的版本号
获取协议栈版本号的方法如下:

  1. 通过命令nrfutil pkg generate --help查看
  2. 在线文档论坛帖子中查看
  3. 烧录一个BLE程序,在nrfgo Studio中查看

NRF52832学习笔记(6)——OTA DFU接口使用

6.2 使用手机APP进行升级

利用nRF Connect或nRF Toolbox进行升级
NRF52832学习笔记(6)——OTA DFU接口使用
NRF52832学习笔记(6)——OTA DFU接口使用
NRF52832学习笔记(6)——OTA DFU接口使用
NRF52832学习笔记(6)——OTA DFU接口使用
NRF52832学习笔记(6)——OTA DFU接口使用
NRF52832学习笔记(6)——OTA DFU接口使用
随后升级完成

七、调试

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

我们可以在代码中禁止DFU服务,让它直接跳转进入Application。

打开应用工程main.c,注释掉main.c中的下面几行代码:

ble_dfu_buttonless_init_t dfus_init = {0};	
// Initialize DFU.
dfus_init.evt_handler = ble_dfu_evt_handler;
err_code = ble_dfu_buttonless_init(&dfus_init);
APP_ERROR_CHECK(err_code);
12345

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


上一篇:NRF52832空中升级DFU


下一篇:Windows中的原语与原子