详解蓝牙空中升级(BLE OTA)原理与步骤《三》

992 阅读7分钟

4. 详解如何移植DFU功能到ble_app_uart

为了让SDK14及以后版本的ble_app_uart具有DFU功能,有2种做法,一是把NUS服务移植到ble_app_buttonless_dfu中,这种方法相对来说更简单,大家可以自己去实践一下;二是把DFU服务移植到ble_app_uart中,这种移植方式挑战更大,但更有利于我们理解DFU的工作原理,我们现在就来阐述如何给ble_app_uart加上OTA功能。如前所述,OTA过程中,手机跟设备可以进行配对和bonding,也可以用明文进行蓝牙通信。配对bonding的时候,我们可以让BootLoader和application共享bonding信息,也可以只让application进行配对bonding,而BootLoader还是以明文方式进行蓝牙通信。

Nordic已经把DFU服务做成了一个模块,大家只要把这个模块加到自己的应用中,然后完成一些必须的配置,初始化以及回调函数的撰写,再加上把SVCI模块(SVCI模块主要用来修改BootLoader的一些配置参数)加入到应用中移植即可大功告成。在SDK中,DFU服务的名字是:BLE_DFU_SERVICE,这个服务放在文件ble_dfu.c中,而ble_dfu.c又有两个后端实现:ble_dfu_unbonded.c和ble_dfu_bonded.c,分别对应无bonding明文蓝牙连接和有bonding的蓝牙连接,下面也将分这两种情况详细阐述移植过程。

4.1 明文正常连接OTA(无bonding)

  1. 用Keil打开如下工程:SDK根目录\examples\ble_peripheral\ble_app_uart\pca10040\s132\arm5_no_packs

  2. 添加DFU服务有关的文件,目录和宏定义。首先添加如下DFU目录及相关文件:

在define中添加这些宏:DEBUG DFU_SUPPORT BL_SETTINGS_ACCESS_ONLY NRF_DFU_SVCI_ENABLED NRF_DFU_TRANSPORT_BLE=1,其中DEBUG宏只是为了调试方便而设置的,跟DFU本身无关。DFU_SUPPORT是我用来控制我添加的DFU代码的,删掉DFU_SUPPORT,将不编译所有DFU有关代码。其余的宏都是系统自带的,如果要支持DFU,就必须要添加。

然后包含如下目录:

  1. 修改sdk_config.h文件。首先我们需要使能BLE_DFU模块,及选择OTA蓝牙连接方式,如下为使用明文进行蓝牙通信的配置:
#define BLE_DFU_ENABLED 1
#define NRF_DFU_BLE_BUTTONLESS_SUPPORTS_BONDS 0

同时我们还需要修改softdevice配置。现在整个应用包括2个供应商自定义UUID:NUS和DFU(其实这两个UUID可以合成一个,但由于历史原因,DFU和NUS分别使用了两个不同的vs UUID),相应地ATT table size也要变大,然后应用程序RAM起始地址也需要跟着变,如下(注:这里的NRF_SDH_BLE_GATTS_ATTR_TAB_SIZE 设置得稍稍偏大):

#define NRF_SDH_BLE_GATTS_ATTR_TAB_SIZE 1600
#define NRF_SDH_BLE_VS_UUID_COUNT 2

修改应用程序RAM起始地址,如下:

  1. 修改main.c文件。首先添加如下头文件:
#include "ble_dfu.h"
#include "nrf_bootloader_info.h"
#include "nrf_power.h"

然后在main函数的开始处,添加修改BootLoader广播名字的代码,这一行代码是可选的,如果想改掉BootLoader默认的广播名字,就需要它;反之就不需要:

err_code = ble_dfu_buttonless_async_svci_init();
APP_ERROR_CHECK(err_code);

然后在services_init()中添加ble dfu服务

dfus_init.evt_handler = ble_dfu_evt_handler;
err_code = ble_dfu_buttonless_init(&dfus_init);
APP_ERROR_CHECK(err_code);

ble_dfu_evt_handler回调函数的撰写,大家只要按照要求来,就没问题,如果应用只支持一个连接,那么ble_dfu_evt_handler可以直接为空。如果应用支持多个连接,可以参考ble_app_buttonless_dfu做法,这里就不贴代码了。

  1. 在跳转到bootloader之前,如果你想做一些专门的代码处理,比如完成pending的Flash操作,比如关闭某些模块,那么你可以注册一个app_shutdown_handler来做这些工作。(注:这一步不是必须的,是可选的!)
NRF_PWR_MGMT_HANDLER_REGISTER(app_shutdown_handler, 0);
  1. (这一步可选)之前的ble_app_uart是没有BootLoader的,所以启动起来非常快。现在加了BootLoader代码,为了加快system off唤醒的速度,可以定义如下语句,
 nrf_power_gpregret2_set(BOOTLOADER_DFU_SKIP_CRC);

然后先disable softdevice,然后再进入system off模式。这一步本身跟DFU没有什么关系,主要是为了加快程序启动速度而另加的。

  1. 编译工程,并将生成的hex文件改名为“app.hex”

  2. 然后按照3.1****节的步骤一步一步完成后续的DFU过程。

4.2 bonding连接OTA

4.1节的工程已经移植了DFU功能,现在我们再把bonding功能移植到4.1节工程上,就可以让我们的应用同时支持DFU和bonding。Bonding功能是通过peer_manager模块来实现的,大家只要把peer_manager有关的文件添加进来,就可以实现bonding的目标。

  1. 打开4.1节的工程

  2. 添加如下文件:

  1. 修改sdk_config.h文件,需要修改多个地方,如下:
#define PEER_MANAGER_ENABLED 1
#define FDS_ENABLED 1
#define NRF_SDH_BLE_SERVICE_CHANGED 1
#define NRF_FSTORAGE_ENABLED 1
#define NRF_DFU_BLE_BUTTONLESS_SUPPORTS_BONDS 1

当NRF_DFU_BLE_BUTTONLESS_SUPPORTS_BONDS设为1时,表示application将与主机进行bonding,同时该bonding信息将共享给BootLoader,也就是说,进入bootloader模式后,主机将使用以前的bonding信息与设备进行加密连接。

  1. 在main.c文件开头,包含如下头文件:
#include "peer_manager.h"
  1. 在main函数中添加peer_manager_init(),其定义如下所示:
static void peer_manager_init()
{
    ble_gap_sec_params_t sec_param;
    ret_code_t           err_code;

    err_code = pm_init();
    APP_ERROR_CHECK(err_code);

    memset(&sec_param, 0, sizeof(ble_gap_sec_params_t));

    // Security parameters to be used for all security procedures.
    sec_param.bond           = SEC_PARAM_BOND;
    sec_param.mitm           = SEC_PARAM_MITM;
    sec_param.lesc           = SEC_PARAM_LESC;
    sec_param.keypress       = SEC_PARAM_KEYPRESS;
    sec_param.io_caps        = SEC_PARAM_IO_CAPABILITIES;
    sec_param.oob            = SEC_PARAM_OOB;
    sec_param.min_key_size   = SEC_PARAM_MIN_KEY_SIZE;
    sec_param.max_key_size   = SEC_PARAM_MAX_KEY_SIZE;
    sec_param.kdist_own.enc  = 1;
    sec_param.kdist_own.id   = 1;
    sec_param.kdist_peer.enc = 1;
    sec_param.kdist_peer.id  = 1;

    err_code = pm_sec_params_set(&sec_param);
    APP_ERROR_CHECK(err_code);

    err_code = pm_register(pm_evt_handler);
    APP_ERROR_CHECK(err_code);
} 

添加pm_evt_handler定义,代码如下所示:

static void pm_evt_handler(pm_evt_t const * p_evt)
{
    pm_handler_on_pm_evt(p_evt);
    pm_handler_flash_clean(p_evt);
}
  1. 在ble_evt_handler中删除BLE_GAP_EVT_SEC_PARAMS_REQUEST分支,因为这个分支在peer_manager模块中已经进行处理了,这里再处理一次,会产生异常:
//        case BLE_GAP_EVT_SEC_PARAMS_REQUEST:
//            // Pairing not supported
//            err_code = sd_ble_gap_sec_params_reply(m_conn_handle, BLE_GAP_SEC_STATUS_PAIRING_NOT_SUPP, NULL, NULL);
//            APP_ERROR_CHECK(err_code);
//            break;
  1. 修改advertising_start定义,增加删除bonding信息功能(如果你不需要这个功能,也可以不改)

  2. (此步可选)一般来说,如果用户在手机端把配对信息删掉了,为了安全起见,设备端也需要把相关配对信息清掉,然后才可以允许手机和设备再次进行配对和bonding。如何触发设备端bonding信息的删除操作?可以通过按键检测的方式来做,比如目前我们这个例子的做法。但是有很多设备没有按键,而且很多人希望这种二次配对的操作对用户来说无感,即哪怕用户删掉了手机端配对信息,如果用户想发起第二次配对请求,设备也能接受,而且操作过程跟用户第一次发起配对请求的过程一模一样。Nordic SDK其实是兼容这种操作的,用户只需在pm_evt_handler()中添加如下代码即可:

  1. 上述所有代码都包括在“BONDING_SUPPORT”宏中。

  2. 编译工程,将生成的hex文件改名为app.hex

  3. 然后按照3.1****节步骤来执行OTA过程,不过如下几点需要注意:

    • 如果你在应用中把NRF_DFU_BLE_BUTTONLESS_SUPPORTS_BONDS设为1,那么bootloader代码就不能采用默认配置,请修改bootloader工程中的sdk_config.h文件中的如下宏定义,然后重新编译生成新的bootloader.hex。
              #define NRF_DFU_BLE_REQUIRES_BONDS 1
              #define NRF_SDH_BLE_SERVICE_CHANGED 1
    • 在nRF Connect中勾选“keep bond information”选项,如下:

    • 手机连接设备成功后,请手动使能CCCD,以让手机自动发起bonding请求

    • DFU升级成功后,设备将会与手机自动重连,此时需点击“Refresh services”,以获得设备最新服务列表,如下:

上述代码工程我已打包成:ble_app_uart_ota_SDK16_0_0.rar,并上传到百度网盘,大家下载下来解压缩到:SDK根目录\examples\ble_peripheral这个目录下,就可以直接编译和运行。DFU过程中用到的所有脚本我也帮大家做好了,大家可以直接下载下来使用,其中secure_ble_S132_uart_SDK160_nRF52832_Nobonding.rar对应明文蓝牙传输,secure_ble_S132_uart_SDK160_nRF52832_bonding.rar对应bonding蓝牙传输。

5 手机端DFU参考代码

Nordic不仅提供DFU设备端的参考代码,同时提供手机端的参考代码。Nordic分别开发了Android版和iOS版的DFU库,大家可以直接拿过来使用,集成到自己的移动端app中,这两个库都放在github上,链接如下所示:

Nordic还提供了一个移动端app:nRF Toolbox,nRF Toolbox是代码开源的,里面也集成了上面提到的DFU库,大家可以参考nRF Toolbox来开发自己的移动端app。nRF Toolbox源码也可以在github上找到:

nRF Toolbox软件界面如下所示: