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

如何实现ble ota?什么叫dfu?如何通过uart实现固件升级?又如何通过usb实现固件升级?怎么保证升级的安全性?什么叫双区(dual bank)dfu?什么叫单区(single bank)dfu?什么叫后台式(background)dfu?本文将对上述问题进行探讨。
dfu过程中涉及的所有操作步骤所对应的脚本都放在百度云盘上。
脚本是按照sdk版本进行分类的,建议大家把自己sdk版本对应的脚本下载下来,然后跟着第3章的操作步骤一步一步去实现自己的dfu。 
1.概述
所谓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包括后台式(background)和非后台式两种模式。后台式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中,然后等待二次或者多次升级尝试,此时设备的正常功能已无法使用,从用户使用这个角度来说,你的确可以说此时设备已经“变砖”了。所以说,虽然双区模式牺牲了很多存储空间,但是换来了更好的升级体验。
可参考下面三个图来理解上述过程。
2. 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的启动校验信息(版本2才有)
版本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和签名的生成工作。
3. dfu升级步骤详解
3.1 安全式蓝牙空中升级步骤
如前所述,nordic sdk已经提供了dfu例子,下面我们一步一步给大家讲解如何通过nordic sdk来实现无按键式蓝牙空中升级。欲实现空中升级,设备需要同时下载softdevice,应用程序,bootloader程序,以及bootloader settings page。其中bootloader代码位于目录:sdk根目录examplesdfusecure_bootloader,然后在该目录下选择你对应的板子和工程。application对应的目录:sdk根目录examplesle_peripheralle_app_buttonless_dfu,而softdevice所在目录:sdk根目录componentssoftdevice。
下面我们以nrf52832/pca10040和s132/sdk16为例阐述无按键式蓝牙空中升级实现步骤,其他芯片/softdevice/sdk原理与之类似,这里就不再赘述。当然,不同芯片不同softdevice不同sdk,他们的实现脚本还是会有一些细微差别,所以强烈建议大家去百度网盘下载跟大家相匹配的脚本,百度网盘里面各个脚本的命名规则请参考3.2节。
1)      安装pc版nrfutil。nrfutil安装有两种方式,一种是直接下载exe文件,一种是以python的方式进行安装。nrfutil.exe直接下载链接为:https://github.com/nordicsemiconductor/pc-nrfutil/releases,记得把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运行需要几个特殊的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 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),这两个压缩包只是目录不一样,里面的算法库内容其实是一样的,这两个压缩包大家都可以在前面的百度云盘中找到,以供大家评估使用。大家下载下来后,直接覆盖同名目录即可。注意:百度云盘里面的micro-ecc库仅供大家评估使用,如要商用,请大家按照上面步骤去生成。
5)      编译bootloader代码。将刚才的dfu_public_key.c取代sdk根目录examplesdfu下的同名文件,然后使用keil编译如下目录中的工程:
sdk根目录examplesdfusecure_bootloaderpca10040_blearm5_no_packs,或者nrf5sdk160098a08e2examplesdfusecure_bootloaderpca10040_s132_blearm5_no_packs
,将生成的hex文件改名为:bootloader.hex(注:本文所有项目都会采用keil工程来讲解,如果你使用其他ide,请选择其对应的工程文件进行编译,不管是keil还是其他ide,除了编译时候选择的工程文件不一样,其余都大同小异,大家可以举一反三完成其他ide的相应工作)
6)      编译application代码。请编译工程:
sdk根目录 examplesle_peripheralle_app_buttonless_dfupca10040s132arm5_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包:new_app.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”获得,如下为当前所有softdevice id列表:
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包(如果打开失败,那么大家就要去下载一些第三方的文件浏览器了,比如es explorer),相关操作界面如下所示:
一旦zip包打开成功,升级过程开始,界面如下所示:
升级成功后,设备将运行新固件,即广播名字将变成:nordic_new,如下所示:
如上以nrf52832/s132为例阐述了nordic sdk实现无按键式签名式蓝牙空中升级的详细步骤,nordic sdk有多个版本,从sdk13.0.0到现在sdk16.0.0,他们的升级步骤基本上一模一样,大家完全可以参考上述步骤来做。sdk12升级步骤也与上述步骤基本一样,唯一如下地方需要注意一下:
编译application代码的时候,把如下语句注掉,否则会造成bootloader和application两者的hex文件相冲突
3.2节会按照上述步骤,对一些经典的安全式ble ota例子进行测试,并生成可直接运行的脚本,以供大家参考。
3.2 各种安全式蓝牙空中升级例子
3.1节是以升级nrf52832 application为例,详细阐述了安全式ble ota步骤。除了升级application,有的人还需要升级softdevice和bootloader;除了52832,有的人还会用52840/52833/52811/52810/51822等;除了sdk16.0.0,有的人还会用sdk15.3/15.2/14.2/12.3等。为此我选了一些经典组合,将他们dfu用得的所有脚本都做好了,并进行了实际测试,有需要的可以去百度网盘下载。这些脚本在百度网盘的命名规则为:安全模式_固件传输接口_升级哪一部分固件_sdk版本号_芯片型号.rar,比如secure_ble_s132_app_sdk160_nrf52832.rar表示采用安全签名,固件通过ble传输,ble使用s132协议栈,升级的时候只升级application而不升级bootloader和softdevice,基于sdk16.0.0和nrf52832。
目前百度网盘上传了如下安全式ble ota示例脚本(注:这些脚本都经过我的测试,全都可以直接运行):
secure_ble_s132_app_sdk160_nrf52832.rar
secure_ble_s140_app_sd_bl_sdk160_nrf52840.rar
secure_ble_s132_app_sdk153_nrf52832.rar
secure_ble_s132_app_sdk152_nrf52832.rar
secure_ble_s132_app_sdk150_nrf52832.rar
secure_ble_s140_app_sdk150_nrf52840.rar
secure_ble_s132_app_sdk142_nrf52832.rar
secure_ble_s132_app_sdk123_nrf52832.rar
secure_ble_s130_app_sdk123_nrf51.rar
3.3 通过uart口进行安全式固件升级示例脚本
我们以nrf52810为例来阐述如何通过uart进行安全式固件升级步骤:
1)      请参考3.1节第1)到第4)步,完成nrfutil安装,mico-ecc算法库生成,以及公私钥生成
2)      编译bootloader代码。将刚才的dfu_public_key.c取代sdk根目录examplesdfu下的同名文件,确保sdk_config.h中的nrf_bl_dfu_enter_method_button为1,然后使用keil编译如下目录中的工程:
sdk根目录 examplesdfusecure_bootloaderpca10040e_uartarm5_no_packs
,将生成的hex文件改名为:bootloader.hex
3)      编译application代码。3.1节讲述ota的时候,我们选择的例子是ble_app_buttonless_dfu,因为我们是通过蓝牙给设备发送一条命令,从而让设备进入dfu模式。通过串口升级固件,如何进入dfu模式,取决于你的应用设计,你可以采用通过发送蓝牙命令让其进入dfu模式,也可以通过上电检测按键是否按下以决定是否进入dfu模式。如果想采用ble_app_buttonless_dfu作为application,那么你需要把该工程中的main函数如下语句删掉(这些语句是为蓝牙版bootloader设计的,我们现在是uart版bootloader,不支持这些语句):
err_code = ble_dfu_buttonless_async_svci_init();app_error_check(err_code);
这里我们选择以上电检测按键的方式来决定是否进入dfu模式,并以ble_app_blinky作为应用例子,请直接编译如下工程:
sdk根目录examplesle_peripheralle_app_blinkypca10040es112arm5_no_packs,将生成的hex文件改名为:app.hex
4)      生成bootloader settings page并同时烧写老固件,双击“program.bat”即可完成,这个脚本是使用nrfjprog来完成固件烧写的。
5)      生成新固件zip包并进行uart dfu,双击“dfu.bat”即可完成,这个脚本是使用nrfutil作为uart主机,并将新固件通过电脑com口传给设备的。请记得一定要修改脚本中的uart对应的电脑com口,否则升级无法完成。
注:所有bat脚本都可通过右键选择notepad++打开,然后查看里面包含的具体命令,并按照自己的需求进行修改。如需进一步理解脚本中的命令,请参考3.1节的说明。
上述所有操作步骤已打包并上传到百度网盘,请去网盘下载文件:secure_uart_app_sdk160_nrf52810.rar,这个文件已经过我的测试,大家可以直接使用。
3.4 通过usb口进行安全式固件升级示例脚本
我们以nrf52840为例来讲述如何通过usb进行安全式固件升级,其实通过usb口升级固件步骤与3.3节的操作几乎一模一样,唯一不同的是,选择如下目录的bootloader工程进行编译:
sdk根目录 examplesdfusecure_bootloaderpca10056_usbarm5_no_packs
通过usb口进行安全式固件升级示例脚本已打包并上传到百度网盘,请去网盘下载文件:
secure_usb_app_sdk160_nrf52840.rar,这个文件已经过我的测试,大家可以直接使用。
3.5 通过usb口进行开放式固件升级示例脚本
我们还是以nrf52840为例来讲述如何通过usb进行开放式固件升级,其升级步骤与3.3节的操作几乎一模一样,唯一不同的是,选择如下目录的bootloader工程进行编译:
sdk根目录 examplesdfuopen_bootloaderpca10056_usbarm5_no_packs
相关脚本已上传百度网盘,请下载:
open_usb_app_sdk160_nrf52840.rar,这个文件已经过我的测试,大家可以直接使用。
3.6 开放式蓝牙空中升级(legacy dfu)步骤
所谓开放式ota,是指ota过程中,不需要检验新固件的签名,也就是说bootloader代码里面不包含公钥及相关密码算法库,升级的时候,只校验版本信息,版本校验通过,就可以开始升级流程。nordic sdk目前支持两套开放式ota方案,一套是sdk15和sdk16提供的,一套是sdk9/sdk10/sdk11提供的。sdk15/16提供的开放式ota工作原理和流程,与安全式ota基本上一样,只不过删掉了签名验签部分。sdk9/sdk10/sdk11提供的开放式ota也叫legacy ota dfu,它的工作流程与sdk15/16略有不同,下面将以nrf52832/s132为例,阐述如何在sdk11中实现无按键式开放式蓝牙空中升级,详细步骤如下所示:
1)         编译bootloader代码,请使用keil编译目录“nrf5_sdk_11.0.0_89a8197examplesdfuootloaderpca10040dual_bank_ble_s132arm5_no_packs”中的工程,将生成的hex文件改名为bootloader.hex
2)         编译application代码,请编译目录“nrf5_sdk_11.0.0_89a8197examplesle_peripheralble_app_hrspca10040s132_with_dfuarm5_no_packs”中的工程,将生成的hex文件改名为app.hex
3)         将softdevice,bootloader和app三个hex文件合成一个文件,命令如下所示:
mergehex --merge s132_nrf52_2.0.1_softdevice.hex app.hex bootloader.hex –output whole.hex  
4)         烧写固件到设备中,大家可以用nrf connect桌面版烧写,也可以通过nrfjprog烧写,nrfjprog烧写命令如下所示:
nrfjprog.exe --eraseall -f nrf52nrfjprog --program whole.hex --verify -f nrf52  
5)         另外我们还需要在flash中写一个application有效标志位,从而上电后程序直接跑到application中去执行,而不是停留在bootloader中不出来,其对应的命令如下所示:
nrfjprog --memwr 0x0007f000 --val 0x01 --verify -f nrf52  
6)         用老版本的nrfutil生成新固件对应的zip包。该zip包除了包含新固件image,还包含一些配置信息。升级时,zip包会通过云端下发到手机端app,手机端app再把zip包传给蓝牙设备以进行固件升级。请使用老版本nrfutil(版本号0.3.0)来生成该zip包,老版本nrfutil跟随nrfgo studio一起安装的,只要你安装了nrfgo studio,老版本nrfutil就会自动安装好,并放在目录“c:program files (x86)nordic semiconductor rfgo studio”中。生成zip包对应的命令如下所示:
nrfutil dfu genpkg --application app_new.hex --application-version 1 sdk110_app_s132.zip
7)         把上述的‘sdk110_app_s132.zip’拷到手机中,安卓和苹果手机都可以通过微信的‘文件传输助手’拷过去,非常方便。注:手机nrf connect和nrf toolbox都支持dfu功能,苹果手机拷贝的时候可以随便选择其中一个app。
8)         使用nrf connect或者nrf toolbox来完成dfu过程。这里以nrf connect为例来阐述整个升级过程。
成功执行完第5)步后,如果开发板运行正常,那么它将进行广播,广播名字为:nordic_hrm
连接该设备,并使能cccd,然后选择“dfu”
选择“dfu”后,将跳出一个对话框,让你选择新固件对应的zip包。由于zip包放在了微信下面的download目录下,我们需要通过文件浏览器找到这个zip包,大家可以先用系统自带的文件浏览器打开这个zip包(如果打开失败,那么大家就要去下载一些第三方的文件浏览器了,比如es explorer),相关操作界面如下所示:
一旦zip包打开成功,升级过程开始,界面如下所示:
升级成功,设备将自动启动,此时你会看到新固件已经在运行,广播名字也变成了:nordic_hrm_new,如下所示:
目前百度网盘上传了如下开放式ble ota示例脚本(注:这些脚本都经过我的测试,全都可以直接运行):
open_ble_s132_app_sdk110_nrf52832.rar
open_ble_s130_app_sdk110_nrf51.rar
如果你的应用是基于sdk11开发的,并且需要集成dfu功能,请参考上述例子ble_app_hrs来移植dfu功能,主要工作包括两部分:一把ble_dfu_app_support这个宏包括的所有代码拷到你的工程中,二如果你的设备支持bonding的话,还需把device manager相关代码也拷到你的工程中,如此即可完成dfu功能的移植。
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根目录examplesle_peripheralle_app_uartpca10040s132arm5_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)      (这一步可选)之前的ble_app_uart是没有bootloader的,所以启动起来非常快。现在加了bootloader代码,为了加快system off唤醒的速度,可以定义如下语句,
nrf_power_gpregret2_set(bootloader_dfu_skip_crc);  
然后先disable softdevice,然后再进入system off模式。这一步本身跟dfu没有什么关系,主要是为了加快程序启动速度而另加的。
7)      编译工程,并将生成的hex文件改名为“app.hex”
8)      然后按照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)      添加如下文件:
3)      修改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信息与设备进行加密连接。
4)      在main.c文件开头,包含如下头文件:
#include peer_manager.h  
5)      在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);  
6)      在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;  
7)      修改advertising_start定义,增加删除bonding信息功能(如果你不需要这个功能,也可以不改)
8)      (此步可选)一般来说,如果用户在手机端把配对信息删掉了,为了安全起见,设备端也需要把相关配对信息清掉,然后才可以允许手机和设备再次进行配对和bonding。如何触发设备端bonding信息的删除操作?可以通过按键检测的方式来做,比如目前我们这个例子的做法。但是有很多设备没有按键,而且很多人希望这种二次配对的操作对用户来说无感,即哪怕用户删掉了手机端配对信息,如果用户想发起第二次配对请求,设备也能接受,而且操作过程跟用户第一次发起配对请求的过程一模一样。nordic sdk其实是兼容这种操作的,用户只需在pm_evt_handler()中添加如下代码即可:
if (p_evt->evt_id == pm_evt_conn_sec_config_req) { pm_conn_sec_config_t cfg; cfg.allow_repairing = true; pm_conn_sec_config_reply(p_evt->conn_handle, &cfg); }  
9)      上述所有代码都包括在“bonding_support”宏中。
10)   编译工程,将生成的hex文件改名为app.hex
11)   然后按照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根目录examplesle_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上,链接如下所示:
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 
nrf toolbox软件界面如下所示:


2020年哪款车型将会成为最有潜力的车型
西门子变频器的制动和散热问题的考虑
巴巴腾智能机器人S3评测 整体性价比还是比较高的
如何避免千兆以太网交换机能量数据控制丢包,有什么好的方法预防
示波器的使用方法步骤 示波器的主要功能和作用
详解蓝牙空中升级(BLE OTA)原理与步骤
诺基亚8什么时候上市?诺基亚8、诺基亚9最新消息:诺基亚8、诺基亚9全新安卓手机曝光:双摄像头来了!
因拖欠157万元,ST越博被债权人申请破产
嵌入式软硬件开发设计是否是坑?
微动开关的用途与注意事项
电站负荷与电力变压器容量、台数选择的效益分析研究
Mouser为Littelfuse TCMOV和SMOV压敏电阻备货
同茂线性马达谈知名电器企业的新布局
恒温培养箱使用注意事项及技术参数
AI发展迎来第三次浪潮,为您起底百万倍算力挑战
新ipad批评之声日盛:苹果现战略盲点?
光纤封装UV胶固化的特点以及应用优势的介绍
三星Note9国行评测 一款过于求稳的全球旗舰
苹果HTC专利战:败诉将影响Android市场
区块链技术的应用有望解决供应链金融的发展痛点