如何使用蓝牙实现OTA固件升级

作为一种低成本的近距离无线连接协议,蓝牙在现实生活中的应用非常广泛,各种嵌入式、物联网设备随处可见。基于这一特性,我们今天讲一下如何使用蓝牙实现OTA固件升级。

一、概述

所谓DFU(Device Firmware Update的缩写),就是设备固件升级的意思,而OTA(Over The Air)是实现DFU的一种方式。准确地说,OTA的全称应该是OTA DFU,即通过空中无线方式实现设备固件升级。只不过大家为了方便起见,直接使用OTA来指代固件空中升级(有时候也将OTA称为FOTA,即Firmware OTA)。只要是通过无线通信方式实现DFU的,都可以叫OTA,比如2G/3G/4G/WiFi/蓝牙/NFC/Zigbee,他们都支持OTA。DFU除了可以通过无线方式(OTA)进行升级,也可以通过有线方式进行升级,比如通过UART,USB或者SPI通信接口来升级设备固件。

不管采用OTA方式还是有线通信方式,DFU都提供后台式非后台式两种模式。

其中,后台式DFU,又称静默式DFU(Silent DFU),在升级的时候,新固件在后台悄悄下载,即新固件下载属于应用程序功能的一部分,在新固件下载过程中,应用可以正常使用,也就是说整个下载过程对用户来说是无感的,下载完成后,系统再跳到BootLoader模式,由BootLoader完成新固件覆盖老固件的操作,至此整个升级过程结束。比如智能手机升级Android或者iOS系统都是采用后台式DFU方式,新系统下载过程中,手机是可以正常使用的。

非后台式DFU,在升级的时候,系统需要先从应用模式跳入到BootLoader模式,由BootLoader进行新固件下载工作,下载完成后BootLoader继续完成新固件覆盖老固件的操作,至此升级结束。早先的功能机就是采用非后台式 DFU来升级操作系统的,即用户需要先长按某些按键进入Bootloader模式,然后再进行升级,整个升级过程中手机的功能是无法使用的。

接下来,我们再讲讲双区DFU(dual bank)和单区DFU(single bank),双区或者单区DFU是新固件和老固件覆盖的两种方式。后台式DFU必须采用双区模式进行升级,即老系统(老固件)和新系统(新固件)各占一块bank(存储区),假设老固件放在bank0中,新固件放在bank1中,升级的时候,应用程序先把新固件下载到bank1中,只有当新固件下载完成并校验成功后,系统才会跳入BootLoader模式,然后擦除老固件所在的bank0区,并把新固件拷贝到bank0中。非后台式DFU可以采用双区也可以采用单区模式,与后台式DFU相似,双区模式下新老固件各占一块bank(老固件为bank0,新固件为bank1),升级时,系统先跳入BootLoader模式,然后BootLoader程序把新固件下载到bank1中,只有新固件下载完成并校验成功后,才会去擦除老固件所在的bank0区,并把新固件拷贝到bank0区。

单区模式的非后台式DFU只有一个bank0,老固件和新固件分享这一个bank0,升级的时候,进入bootloader模式后立马擦除老固件,然后直接把新固件下载到同一个bank中,下载完成后校验新固件的有效性,新固件有效升级完成,否则要求重来。跟非后台式DFU双区模式相比,单区模式节省了一个bank的Flash空间,在系统资源比较紧张的时候,单区模式是一个不错的选择。不管是双区模式还是单区模式,升级过程出现问题后,都可以进行二次升级,都不会出现“变砖”情况。

不过双区模式有一个好处,如果升级过程中出现问题或者新固件有问题,它还可以选择之前的老固件老系统继续执行而不受其影响。而单区模式碰到这种情况就只能一直待在bootloader中,然后等待二次或者多次升级尝试,此时设备的正常功能已无法使用,从用户使用这个角度来说,此时的设备已经“变砖”了。所以说,虽然双区模式牺牲了很多存储空间,但是换来了更好的升级体验。

下图展示了双区模式和单区模式的区别:
在这里插入图片描述
下面就讲讲基于Nordic nRF5 SDK的固件升级方案。如果你还是一名初学者,那么建议您先看下下面的两片文章:

二、Nordic官方

如果您是一位从事嵌入式开发的开发者,那么对Nordic肯定不会陌生。Nordic是一家无晶圆半导体公司,专长是开发短距离无线技术和低功耗蜂窝物联网应用。该公司率先推出超低功耗无线技术,并帮助开发广泛采用的低功耗蓝牙(Bluetooth Low Energy)无线技术。

2.1 如何使用官网

首先,我们打开Nordic官网,如下所示。
在这里插入图片描述

可以看出,Nordic官网有四个主入口:搜索,Products,software and tools,以及Documentation。我们可以通过Documentation来了解Nordic产品的所有信息。

2.1.1 搜索

首先,我们来看一下搜索。比如,搜索“nrf52832”就会出现如下结果,该结果与直接通过菜单导航到nrf52832产品目录所得到的结果基本一致的。
在这里插入图片描述

2.1.2 产品

Products就是芯片资料及其介绍,选择你感兴趣的芯片,比如nRF52832,将呈现与之有关的所有资料。同时,Products菜单项下的内容会同时包含Software and tools,以及Doclib内容,同样Software and tools菜单下内容也会同时包含Products和Doclib内容。
在这里插入图片描述

2.1.3 开发软件与工具

Software and tools包含了芯片开发有关的SDK、开发板、工具以及第三方模块等,所以SDK和工具都是通过这个页面进行下载的,下载时需要先找对对应的芯片开发的产品,然后再进行下载。
在这里插入图片描述

2.1.4 Infocenter

Infocenter是Nordic的文档中心,Nordic所有文档都可以在这里找到。
在这里插入图片描述
比如,选择“nRF52 series/nRF52840”,界面将如下所示。

在这里插入图片描述

2.2 Nordic Devzone

大家在开发Nordic产品过程中,不可避免的会碰到问题,这个时候大家可以到Nordic官方讨论社区Devzone去搜索问题答案,如果Devzone上没有找到自己想要的答案,那么你可以直接在Devzone上提问。

在这里插入图片描述

不过需要说明的是,只有登录的用户才能进行提问,如果还没有注册可以先注册一个账号后再进行提问。同时,Devzone还有一个tutorial区(教程区),可以帮助初学者快速入门某一个特定领域,比如BLE广播,我们可以打开广播的tutorial。
在这里插入图片描述

并且,Devzone还提供了一个在线的计算BLE理论功耗工具,大家可以通过这个工具计算出BLE的理论功耗,然后对比自己的实测功耗。
在这里插入图片描述

2.3 Nordic GitHub

作为全球最大的软件项目托管平台,Nordic也有很多软件代码放在GitHub上。比如,一些Nordic工具的Python脚本,一些教程例子,以及一些SDK没有的例子代码,都可以在Nordic GitHub上找到。Nordic有2个GitHub站点,一个是官方资源正式release的站点,Nordic将保证其产品质量;一个是playground的,是一些实验性质的例子或者驱动等,供大家参考或者修改。
在这里插入图片描述

三、Nordic nRF5 SDK DFU工作流程

Nordic nRF5 SDK软件架构跟其他家有点不一样,程序存储区最开始部分放得不是Bootloader,而是蓝牙协议栈Softdevice,应用程序则紧挨着Softdevice,Bootloader则被nRF5 SDK放在程序存储区的最上面,整个存储区结构图如下所示,如果用户还有Flash数据需要存放,那么这些数据紧挨着BootLoader下面。

在这里插入图片描述

目前,Nordic SDK默认只提供非后台式DFU开箱即用的例子(SDK16.0开始也支持后台式DFU框架),即系统必须先跳到BootLoader中,然后才能通过BLE/UART/USB去接收新的固件。如上所示,如果采用双区模式DFU,那么Bank0放的是应用程序,即老固件,Bank1放的是新固件。平时,Bank1为空或者忽略,系统只跑Bank0里面的应用程序;升级的时候,先跳到BootLoader,然后接收新固件并把它放在Bank1中,最后把Bank1里面的固件拷贝到Bank0中。如果采用单区模式,则没有Bank1这个区。平时,系统只跑Bank0里面的代码;升级的时候,跳到BootLoader,先擦除Bank0里面的老程序,并把新固件直接放在Bank0中。

根据升级时跳转Bootloader的时机,Nordic SDK又将DFU分为按键式DFU和非按键式(Buttonless)DFU,所谓按键式DFU,就是上电时长按某个按键以进入bootloader模式,而非按键式DFU,就是整个DFU过程中设备端无任何人工干预,通过BLE/UART/USB接口给应用程序发送一条指令,应用程序收到指令后再自动跳入bootloader模式。不管是按键式DFU还是非按键式DFU,两者只是进入BootLoader的方式不一样,其余基本一样,尤其是BootLoader工作过程基本上是一模一样的。后面只会阐述非按键式DFU的过程,按键式DFU以此类似,就不再赘述。

程序跳到BootLoader后,根据BootLoader需不需要对新固件进行验签,Nordic SDK又把DFU分为开放式DFU和安全式DFU(又称签名DFU)。开放式DFU,BootLoader不做任何验证,直接把新固件接收下来。安全式DFU,BootLoader存有一把公钥,BootLoader会先用这把公钥验证新固件的签名,只有验签通过,才允许后续的工作:比如把新固件接收下来;如果验签失败,BootLoader将拒绝升级,重新跳回应用程序。

BootLoader可以通过不同的通信接口来接收新的固件,目前Nordic SDK支持BLE,UART和USB三种接口,所以大家可以在Nordic SDK中看到如下三种工程目录:

在这里插入图片描述

其中pca0056表示nRF52840对应的开发板编号,S140对应Softdevice的型号,然后ble有两个目录:无debug和有debug,uart和usb也包含同样的两个目录。有debug和无debug两者功能是一样的,两者的区别是:debug版本BootLoader支持日志打印(大家可以通过打印出的日志去理解BootLoader的工作过程),并可以忽略各种校验,debug版本占据的代码空间要大很多;无debug版本 BootLoader不支持日志打印功能并且版本和有效性校验是强制的。正式量产的时候推荐使用无debug版本以节省代码空间。这里要强调一下,不管是debug版本还是无debug版本,两者都可以用Keil进行单步和断点调试。

BLE,UART和USB只是通信方式不一样,他们遵守的DFU流程是一模一样的,这里会以BLE通信接口为例,详细阐述DFU过程,UART和USB与之类似,就不再赘述。

在进行DFU升级之前,我们需要先了解一下nRF52的启动流程。在通电后,系统会先执行softdevice,softdevice通过读取UICR一个寄存器的值,来判断目前系统是否有BootLoader,如果没有BootLoader,系统直接跳到application;如果有BootLoader,系统先跳到BootLoader,BootLoader再根据目前的情况来决定是进入升级模式还是跳往application,BootLoader主要判断如下几种情况:

  • 按键是否按下
  • 保持寄存器GPREGRET1是否为0xB1
  • 上次DFU过程是否还在进行中
  • 应用程序校验是否通过

如果按键没有按下,GPREGRET1不为0xB1,本次复位不是上次DFU的继续,并且应用程序校验通过,那么BootLoader就会直接跳到application,去执行application应用程序。那怎么去校验应用程序的有效性呢?为此BootLoader引入了一个放在Flash的结构体参数:m_dfu_settings_buffer(数据类型:nrf_dfu_settings_t),这个结构体参数虽然只有896字节,但由于Flash只能按页擦除,所以这个参数实际占用了一个Flash page,这个page称为settings page,settings page放在Flash的最后一个页面,settings page目前有2个版本:版本1(SDK15.2及以前版本)和版本2(SDK15.3及以后版本),版本2可以兼容版本1,前面所述的896字节是指settings page版本2的大小。Settings page包含的信息比较多,大家用得比较多的是:

  • 各种版本信息
  • DFU升级过程信息
  • Application image的CRC值和大小
  • 应用程序的bonding信息
  • Init command内容
  • application/softdevice的启动校验信息

版本1的settings page只校验application image的CRC值,如果CRC匹配,则认为application有效。版本2的settings page不仅可以校验application image的CRC值,还可以校验application/softdevice的CRC值或者hash值或者签名,你可以选择你自己想要的校验方式,只有CRC值或者hash值或者签名校验通过(三选其一),应用程序才算有效,这时BootLoader才会跳到application去执行。为了保证settings page在发生意外时,比如写settings page过程中发生了复位或者掉电,系统也能正确恢复,SDK15及以后版本引入了一个backup page,backup page也占用一个Flash page,内容和settings page一模一样。

上面是没有触发升级的情况下nRF52的正常启动流程,那如果要执行DFU升级,流程又是怎么样的呢?下面看一下无按键式BLE OTA的工作流程。

  1. 正常启动后,系统运行在应用程序中,此时手机通过app发送一条开始DFU的指令给设备,设备收到指令后,将GPREGRET1赋值0xB1,并触发软复位。

  2. 复位后,系统再次进入BootLoader,因为GPREGRET1等于0xB1,BootLoader进入DFU模式,等待新固件接收。

  3. 手机先将init packet发送给设备,设备先做前期检验prevalidation,主要是各种版本校验以及签名验签,校验通过后,更新settings page并准备开始数据接收。

  4. 接收新固件。每接收4kB数据,回复一次CRC校验值,直至整个新固件image接收完毕,如果新固件校验通过(版本1校验CRC值,版本2校验hash值),就会去invalidate(无效化) bank0里面的老固件,更新settings page,并再次触发软复位。

  5. BootLoader启动后发现有新固件需要activate(激活),此时会去擦掉bank0里面的固件,并把bank1里面的固件拷贝到bank0,然后更新settings page,并再次触发软复位。注:上面讲的是dual bank的流程,single bank与之相似,只不过在第3)步的时候就会去擦除老固件。

  6. BootLoader再次启动后,检查新image的有效性,校验通过后,跳到新的application去执行代码。

从上面流程可以看出,DFU过程中,系统需要跑两段完全独立的代码:Application和BootLoader,Application和BootLoader都支持蓝牙功能,也就是说,两者都有自己的蓝牙广播和蓝牙连接。

不过,这里面有一个问题:当系统从Application跳到BootLoader后,手机怎么辨别两者为同一个设备?很多人会说,可以让BootLoader和Application两者的广播名字一样,然后根据广播名字的一致性来判断二者是否来自同一个设备。

这种方法存在两个问题:一大部分手机都支持GATT cache(缓存)功能,当application跟手机相连后,手机会把application的GATT数据缓存下来以加快下次连接的速度(这个现象在苹果手机最明显),之后如果系统跳到BootLoader,然后再跟手机相连,如果两者的蓝牙设备地址一样,手机会认为是同一个设备,从而跳过服务发现的过程而直接使用之前缓存下来的GATT数据,这样会导致BootLoader的服务无法被手机发现,从而出现升级失败。二如果多个设备同时在升级,而我们仅仅依靠广播名字来决定两者属不属于同一个设备,这会导致设备A application有可能跟设备B的BootLoader进行错配。为了解决这个问题,Nordic提出了两套方案。

方案一

假设application的蓝牙设备地址为x,跳到BootLoader后蓝牙设备地址会变成x+1,这样手机就可以通过这种地址+1的方式来辨别两者属不属于同一个设备,由于application和BootLoader使用不同的蓝牙设备地址,前面的GATT缓存问题也就不存在。关于方案一,有一个问题需要特别注意:如果你想修改例子默认的蓝牙设备地址(比如使用IEEE的public蓝牙MAC地址),此时一定要记得同时更改application和BootLoader的蓝牙设备地址,使他们满足+1的条件,否则Nordic手机DFU库无法辨别两者是否属于同一个设备,以致于无法完成OTA过程。

方案二

application和BootLoader的蓝牙设备地址一模一样,但设备跟手机执行配对和bonding操作,设备跟手机bonding后,就可以支持service changed indicate操作,这样跳到BootLoader后可以让手机主动再执行一次服务发现过程,从而解决GATT缓存问题。

可能许多人对签名验签不是很理解,下面来简单的看一下:

首先,你需要一对公私钥,其中私钥用来生成新固件的签名,公钥用来验证签名的有效性,大家可以用nrfutil来生成自己需要的公私钥对,公私钥制作成功后,私钥一定要妥善保管(一般放在云端),千万不能丢,否则你自己也无法升级自己的设备;也不能被第三方知道,否则升级的安全性就不能保证了。公钥可以变成一个.c文件,并覆盖DFU工程下的同名文件:dfu_public_key.c 。

其次,BootLoader要支持签名验签密码算法,这个DFU代码已经有了,并且有四种后端可选:micro-ecc,cc310_bl,Oberon和mbedtls,四选其一即可(这4种后端,只有cc310是硬件实现,其余都是软件实现),nRF52840推荐选择cc310作为算法后端,其他nRF52芯片推荐选择micro-ecc作为算法后端。micro-ecc效率高,占用的代码空间最小,但它的版权是CPOL,只要你能接受CPOL,那么推荐使用micro-ecc;反之,如果接受不了CPOL版权,而且硬件又不支持cc310,那么推荐使用Oberon,不过Oberon占用的代码空间比micro-ecc要大一些,这个大家注意一下。再次,手机端要生成新固件的签名,并把新固件的签名传给设备端。

大家还是可以用nrfutil去生成新固件的签名。最后,BootLoader接收到新固件hash值和签名,并使用自己的公钥对该签名进行验签。这里说一下,由于nrfutil是PC端应用程序,所以它可以集成各种加密算法库,并完成上面提及的公私钥对,hash和签名的生成工作。

四、DFU升级步骤

4.1 安全式蓝牙空中升级步骤

Nordic SDK已经提供了DFU例子,下面我们一步一步给大家讲解如何通过Nordic SDK来实现无按键式蓝牙空中升级。欲实现空中升级,设备需要同时下载softdevice,应用程序,BootLoader程序,以及BootLoader settings page。其中BootLoader代码位于目录:SDK根目录\examples\dfu\secure_bootloader,然后在该目录下选择你对应的板子和工程。Application对应的目录:SDK根目录\examples\ble_peripheral\ble_app_buttonless_dfu,而softdevice所在目录:SDK根目录\components\softdevice。

下面我们以nRF52832/PCA10040和S132/SDK16为例阐述无按键式蓝牙空中升级实现步骤,其他芯片/softdevice/SDK原理与之类似,这里就不再赘述。

1,安装PC版nrfutil
nrfutil安装有两种方式,一种是直接下载exe文件,一种是以Python的方式进行安装。如果是使用exe文件方式安装,可以先下载nrfutil.exe文件。,记得把nrfutil.exe所在目录放在Windows环境变量中。Python方式安装nrfutil步骤如下所示:

  • 安装Python2.7或者Python3.7,下载地址:https://www.python.org/downloads/,安装成功后请确保Windows环境变量包含Python目录。
  • 通过pip安装最新版的nrfutil,即打开Windows命令行工具CMD(管理员权限),输入如下命令:pip install nrfutil,即可以完成nrfutil的安装。
  • 安装完成后,在Windows命令行工具输入:nrfutil version,如果可以正确显示版本信息,说明安装已经成功。
  • 安装完成后,在Windows命令行工具输入:nrfutil version,如果可以正确显示版本信息,说明安装已经成功。

对于Windows用户,nrfutil运行需要几个特殊的DLL库,而这几个库有些Windows机器是没有的,如此,可往:https://www.microsoft.com/en-us/download/details.aspx?id=40784下载。

2,通过nrfutil生成公私钥对

  • 私钥生成命令:nrfutil keys generate priv.pem (priv.pem就是私钥)
  • 公钥生成命令:nrfutil keys display --key pk --format code priv.pem --out_file dfu_public_key.c (dfu_public_key.c就是公钥)
  • 务必保存好私钥priv.pem,以后每次升级新固件时,都会通过这个私钥对它进行签名,一旦priv.pem丢失或者被暴露,DFU将无法进行或者变得不安全。

3,确保已按照“Nordic nRF51/nRF52开发环境搭建”把Nordic nRF5 SDK开发环境搭建成功
4,生成micro-ecc算法库。
由于micro-ecc是第三方算法库,需要用户自己去安装(这个是版权的要求,没办法直接编译放在SDK中)。请先确保电脑已安装了git和GCC编译器,然后直接点击SDK如下目录的build_all脚本,就可以自动完成micro-ecc算法库的安装。

为了方便一些开发者评估,我这里在自己电脑上生成了micro-ecc算法库,micro-ecc目录编排结构有两种:SDK14及以后版本是一种目录结构(百度云盘压缩包名称:micro_ecc_new.rar),SDK13和SDK12又是一种目录结构(百度云盘压缩包名称:micro_ecc_old.rar),这两个压缩包只是目录不一样,里面的算法库内容其实是一样的,这两个压缩包大家都可以在前面的百度云盘中找到,以供大家评估使用。大家下载下来后,直接覆盖同名目录即可。
在这里插入图片描述
5,编译bootloader代码
将刚才的dfu_public_key.c取代SDK根目录\examples\dfu下的同名文件,然后使用Keil编译如下目录中的工程:SDK根目录\examples\dfu\secure_bootloader\pca10040_ble\arm5_no_packs,或者nRF5SDK160098a08e2\examples\dfu\secure_bootloader\pca10040_s132_ble\arm5_no_packs,将生成的hex文件改名为:bootloader.hex。

需要注意的是:本文所有项目都会采用Keil工程来讲解,如果你使用其他IDE,请选择其对应的工程文件进行编译,不管是Keil还是其他IDE,除了编译时候选择的工程文件不一样,其余都大同小异,大家可以举一反三完成其他IDE的相应工作。

6,编译application代码
编译工程,目录为:SDK根目录 \examples\ble_peripheral\ble_app_buttonless_dfu\pca10040\s132\arm5_no_packs,将生成的hex文件改名为:app.hex。

7,生成BootLoader settings page

Bootloader settings page存储在Flash最后一个page,如前所述,BootLoader settings page有2个版本,他们的生成脚本命令如下所示。

  • 版本2生成命令:nrfutil settings generate --family NRF52 --application app.hex --application-version 1 --bootloader-version 1 --bl-settings-version 2 settings.hex
  • 版本1生成命令:nrfutil settings generate --family NRF52 --application app.hex --application-version 1 --bootloader-version 1 --bl-settings-version 1 settings.hex

8,烧写固件
将上文生成的3个hex文件和softdevice hex文件merge成一个文件,然后通过nrfjprog或者nRF Connect桌面版进行烧写,相关命令如下所示。

  • 合并hex文件命令:
mergehex --merge bootloader.hex settings.hex --output bl_temp.hex
mergehex --merge bl_temp.hex app.hex s132_nrf52_7.0.1_softdevice.hex --output whole.hex
  • 烧写hex文件命令(以nrfjprog为例):
nrfjprog --eraseall -f NRF52
nrfjprog --program whole.hex --verify -f NRF52
nrfjprog --reset -f NRF52

9,通过nrfutil生成新固件对应的zip包
zip包包含新固件(新固件广播名改为:Nordic_New,其余跟老固件一模一样)和init包,zip包一般通过云端下发到手机app,手机app再通过蓝牙下载到设备中。生成zip包的命令如下所示。

nrfutil pkg generate --application app_new.hex --application-version 2 --hw-version 52 --sd-req 0xCB --key-file priv.pem SDK160_app_s132.zip

其中,命令的说明如下:

  • –application表示新固件hex文件。
  • –hw-version表示板子版本,只要BootLoader里面的hw version和这里的hw version对应起来即可。
  • –key-file 表示签名用的私钥文件。
  • –sd-req表示老固件运行在哪个版本softdevice上,这个值一定要跟自己的softdevice相匹配,否则无法升级,各个softdevice版本ID信息可以通过命令“nrfutil pkg generate --help”获得。

在这里插入图片描述
10,将“new_app.zip”拷贝到手机上
安卓和苹果手机都可以通过微信的‘文件传输助手’拷过去,非常方便。请注意,手机nRF Connect和nRF Toolbox都支持DFU功能,苹果手机拷贝的时候可以随便选择其中一个app。

11,通过手机版nRF Connect或者nRF Toolbox进行蓝牙空中升级
这里以nRF Connect为例阐述升级详细步骤,nRF Toolbox与此类似。第8步完成后,开发板就可以正常跑起来,并广播为Nordic_Buttonless。
在这里插入图片描述
连接该设备,使能CCCD(这一步可选),然后选择“DFU”。
在这里插入图片描述
选择“DFU”后,将跳出一个对话框,让你选择新固件对应的zip包。由于zip包放在了微信下面的download目录下,我们需要通过文件浏览器找到这个zip包,大家可以先用系统自带的文件浏览器打开这个zip包。
在这里插入图片描述
在这里插入图片描述
一旦zip包打开成功,升级过程开始,界面如下所示。
在这里插入图片描述
升级成功后,设备将运行新固件,即广播名字将变成Nordic_New,如下图所示。

在这里插入图片描述
当然,除了上面的方式外,升级的手段还有安全式蓝牙空中升级通过UART口进行安全式固件升级通过USB口进行安全式固件升级通过USB口进行开放式固件升级等。

五、如何移植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的蓝牙连接,下面也将分这两种情况详细阐述移植过程。

5.1 明文正常连接OTA

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,就必须要添加。

在这里插入图片描述
3,修改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起始地址,如下:
在这里插入图片描述
4,修改main.c文件。首先,添加如下头文件:

#include "ble_dfu.h"
#include "nrf_bootloader_info.h"
#include "nrf_power.h"

然后,在main函数的开始处,添加修改BootLoader广播名字的代码,由于iOS DFU的时候默认就会去改广播名字,为了兼容iOS,这一行代码是必须的。

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做法。

5,在跳转到bootloader之前,如果你想做一些专门的代码处理,比如完成pending的Flash操作,比如关闭某些模块,那么你可以注册一个app_shutdown_handler来做这些工作。

NRF_PWR_MGMT_HANDLER_REGISTER(app_shutdown_handler, 0);

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

5.2 bonding连接OTA

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

1,首先,打开工程,添加如下文件。
在这里插入图片描述
2,修改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信息与设备进行加密连接。

3,在main.c文件开头,包含如下头文件:

#include "peer_manager.h"

4,在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);
}

尤其要检查如下代码有没有添加,由于iOS DFU的时候默认就会去改广播名字,为了兼容iOS,这一行代码是必须的。

err_code = ble_dfu_buttonless_async_svci_init();
APP_ERROR_CHECK(err_code);

5,在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;

6,修改advertising_start定义,增加删除bonding信息功能。
7,编译工程,将生成的hex文件改名为app.hex。

接下来,只需要按照4.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”,以获得设备最新服务列表。
在这里插入图片描述
参考:

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

  • Android版DFU库:https://github.com/NordicSemiconductor/Android-DFU-Library
  • iOS版DFU库:https://github.com/NordicSemiconductor/IOS-Pods-DFU-Library

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

  • Android版nRF Toolbox源代码:https://github.com/NordicSemiconductor/Android-nRF-Toolbox
  • iOS版nRF Toolbox源代码:https://github.com/NordicSemiconductor/IOS-nRF-Toolbox

猜你喜欢

转载自blog.csdn.net/xiangzhihong8/article/details/127401266