详解蓝⽛空中升级(BLEOTA)原理与步骤
如何实现BLE OTA?什么叫DFU?如何通过UART实现固件升级?⼜如何通过USB实现固件升级?怎么保证升级的安全性?什么叫双区(dual bank)DFU?什么叫单区(single bank)DFU?什么叫后台式(background)DFU?本⽂将对上述问题进⾏探讨。
DFU过程中涉及的所有操作步骤所对应的脚本都放在百度云盘上,云盘链接如下所⽰:
链接:    密码: y8fb
脚本是按照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中,然后等待⼆次或者多次升级尝试,此时设备的正常功能已⽆法使⽤,从⽤户使⽤这个⾓度来说,你的确可以说此时设备已经“变砖”了。所以说,虽然双区模式牺牲了很多存储空间,但是换来了更好的升级体验。
可参考下⾯三个图来理解上述过程。
如果你是第⼀次接触Nordic nRF5 SDK,那么建议你先看⼀下这篇⽂章:,或者看⼀下这⼀篇⽂章:,以建⽴Nordic nRF5 SDK的⼀些基本知识,然后再往下看以下章节。
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根⽬录\examples\dfu\secure_bootloader,然后在该⽬录下选择你对应的板⼦和⼯程。Application对应的⽬录:SDK根⽬录
\examples\ble_peripheral\ble_app_buttonless_dfu,⽽softdevice所在⽬录:SDK根⽬录\components\softdevice。
下⾯我们以nRF52832/PCA10040和S132/SDK16为例阐述⽆按键式蓝⽛空中升级实现步骤,其他芯⽚/softdevice/SDK原理与之类似,这⾥就不再赘述。当然,不同芯⽚不同softdevice不同SDK,他们的实现脚本还是会有⼀些细微差别,所以强烈建议⼤家去百度⽹盘下载跟⼤家相匹配的脚本,百度⽹盘⾥⾯各个脚本的命名规则请参考3.2节。
安装Python2.7或者Python3.7,下载地址:,安装成功后请确保Windows环境变量包含Python⽬录
通过pip安装最新版的nrfutil,即打开Windows命令⾏⼯具CMD(管理员权限),输⼊如下命令:pip install nrfutil,即可以完成nrfutil的安装。
安装完成后,在Windows命令⾏⼯具输⼊:nrfutil version,如果可以正确显⽰版本信息,说明安装已经成功
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根⽬录\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包: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”,如下所⽰: