前面文章分析了OTA的实现架构、协议和存储问题,提到了一个细节,如果使用双程序更新模式,将Flash空间分成上下两部分,如下图所示,那么最后生成的BIN文件为了放在不同的位置,需要生成两份地址不同文件,然后根据芯片的实际使用情况,选择合适的文件进行烧写。

BLE OTA memory layout

偶然看到一个信息,Nordic做双程升级时候,只需要一个二进制文件,不需要提供两个BIN文件进行选择。Nordic是怎么办到的呢?

内存布局

Nordic芯片的Flash布局图如下:

Nordic_DFU_Memory_Layout

Flash地址从下向上增长,最底层的MBR(Master Boot Record)区域存放中断向量表的位置。芯片中的Bootloader、SoftDevice和Application都具有自己的向量表,MBR的寄存器UICR.NRFFW[1]指示系统该选择哪一个中断向量表。(参考

SoftDevice区域存放协议栈。

Application区域存放用户固件程序。

APP Data区域存放用户希望永久保存的数据,比如设备地址等。这块区域在升级过程中也可以保持不被擦写。

DFU Bootloader区域存放Bootloader程序,用以执行升级过程。

更新方案

Nordic提供了三种更新方案,分别是:(参考

  1. 双程DFU
  2. 单程DFU
  3. 更新BootLoader和SoftDevice

1. 双程DFU

双程DFU方案的操作流程如下:

Dual-Bank DFU memory layout

从左侧图中可以看到Application区域空间分成了两部分:Blank 0和Blank 1。

假如当前的程序放在Blank 0区域,那么新的程序将被复制到Blank 1区域,如中间的图。

大多数芯片厂的DFU过程到这里就结束了,只要通过Bootloader引导MCU进入到新的程序地址即可。这样必然会产生本文开头提到的问题,在升级程序的时候,需要确定旧的程序放置位置。由于生成的BIN文件包含了起始地址信息,这意味着要新的程序要准备两份功能相同、地址不同的BIN文件,按照实际情况选择合适的文件烧写空白区域。

Nordic使用了一个小技巧,即将新的程序(Blank 1)复制到旧的程序空间(Blank 0),这样就是的程序基地址永远在Blank 0,从而解决了上述困难。

由于Flash赋值是一个耗时操作,DFU做起来一定会很慢。仔细观察,会发现双程DFU至少需要执行两次擦除和两次烧写,分别是:

  • 烧写Blank 1
  • 擦除Blank 0
  • 烧写Blank 0
  • 擦除Blank 1

疑问:如果在执行右侧图的Flash赋值过程中途,发生了断电,该怎么办?

我猜测应该先将Blank 1的内容复制到Blank 0,当复制完毕校验通过,再擦除Blank 1,结束DFU过程。这样即使在复制中途断电,Blank 1的内容仍然有效,重启后可以重新进行复制操作。读源码应该可以确认这一点。

2. 单程DFU

单程DFU的操作流程如下:

Single-Blank DFU memory layout

单程DFU就是现将Application区域清空,再将新的程序写入到Application空间的基地址。

这种做法好处是充分利用Application的空间,而且只需要擦除一次,烧写一次,操作时间相对于双程DFU一定会有大幅度降低。

单程DFU的缺点是不能在线更新,因为用户程序需要被擦除,所以一定需要断开连接和重启。

3. BootLoader和SoftDevice

升级BootLoader和SoftDevice的操作流程如下:

BootLoader and SoftDevice DFU memory layout

这个图乍一看比较复杂。

(1)擦除旧用户程序

(2)将新的Bootloader和SoftDevice写入Application区域

(3)将新的Bootloader和SoftDevice复制到Bootloader和SoftDevice区域

So easy? ——不是的!

并不是每个厂商的芯片都能实现这个功能,因为第(2)步到第(3)步之间,MCU要擦除Bootloader区域,有些BLE厂商的MCU,Bootloader程序是唯一的启动器,Bootloader程序自身是不可以被擦除的,否则MCU无法正常启动。Nordic的芯片存在MBR,利用MBR引导MCU跳转到Bootloader或其他地方。

程序在第(2)步结束时设置MBR的寄存器,让MCU下次启动时候不直接跳转到Bootloader区域,而是跳转到Application区域,执行一个复制操作,将新Bootloader程序和SoftDevice程序复制到各自区域,然后再设置MBR寄存器,让MCU下次重启时候直接跳转到Bootloader区域。

虽然仅仅增加了几行Flash来存放MBR信息,但是对MCU的更新提供了极大的灵活性。

(完)

BLE的OTA可以用来更新固件程序。本文以BlueNRG-1芯片为例,分析OTA实现方案细节。

1. 架构

一个OTA方案至少包括三个部分:

程序类型 描述
当前用户程序 当前正在运行的固件程序
新版用户程序 接收的更新后的固件程序
引导程序 芯片上电先进入引导程序,再跳转到新版程序运行

BlueNRG-1对160kB的内部Flash进行了逻辑分区,分别存放这三个部分程序:

BLE_OTA_Dual_Image_Architecture

这颗芯片的Flash地址区间为0x10040000 – 0x10068000,共160kB。上图中,最下面的Bootloader为引导程序,随后是两个程序空间,NVM中存放一些关键信息,最后一部分为预留空间,不使用。

APP_A和APP_B地位平等,如果当前程序在APP_A处,则将新版程序写入APP_B,反之亦然。

图中标注了各部分空间的大小,APP_A和APP_B的空间数值是根据总空间160kB减去其他几部分空间再除以2得到的。由此可知,用户更新的程序Flash大小应小于76kB。

芯片复位时直接进入引导程序,引导程序检测当前程序的位置,然后跳转至相应的程序空间。

2. 协议

在对BLE设备执行OTA升级过程,需要约定双方的行为规范。

2.1 从机GATT服务

OTA行为需要在一个专有的GATT服务上进行。该服务的GATT结构如下:

  • OTA Service
    • Current Image Char [Read]
    • New Image Info Char [Write | Read]
    • New Image Data Char [Write_Without_Response]
    • Expected Data Block Sequence Number Char [Notify]

该服务共四个特征项。

第一个特征项,用于主机读取从机的有效Flash空间地址和边界。

第二个特征项,用于主机向从机写入新版程序二进制文件的基地址和大小。

第三个特征项,用于主机向从机分块写入新版程序二进制文件的内容,每块共19个字节,包括16个字节文件数据,1字节校验信息,2字节块序号(Block Sequence)。

第四个特征项,用于从机向主机发送期望的块序号,配合第三个特征项,实现一个简单的流程控制功能,以保证二进制文件传输的完整性。

2.2 从机广播数据

可以在从机的广播包中加入特殊字段,以方便主机快速识别从机。

在从机的广播包中,加入Device Name内容。

在从机的扫描响应包中,加入OTA Service UUID内容。

2.3 时序

基本时序如下所示:

BLE_OTA_Protocol_Timing

3. 引导程序

引导程序完成两个任务:

  • 检测当前程序的位置
  • 跳转到当前程序

在程序的Flash空间基地址,存放的是中断向量表,向量表的第一个元素为_INITIAL_SP,其内容为栈顶的地址,即MSP指针;第二个元素为复位处理函数(Reset_Handler),执行该句柄将执行程序初始化操作并跳转进入C代码的main函数。

所以,引导程序需要做的是检测到当前程序的中断向量表,并执行它的复位处理函数。

中断向量表在程序空间的位置如下图所示:

BLE_OTA_Vector_Table

中断向量表的第五个元素OTA_APP_TAG是自定义的中断向量,它并不响应任何中断,仅仅记录当前程序空间的有效性。

OTA更新成功时,会将新版程序的OTA_APP_TAG设为0xAA5555AA,旧版程序的OTA_APP_TAG设为0x00000000。

引导程序检测两块Flash空间的OTA_APP_TAG值,如果检测到有效值,则设置该空间的程序为有效程序,并擦除旧程序。检测程序伪代码如下:

#define TAG_VALUE(addr)     (* ((volatile uint32_t*) ((addr) + OTA_TAG_VECTOR_TABLE_ENTRY_OFFSET)))

if ((TAG_VALUE(APP_A_ADDRESS) == 0xAA5555AA) && (TAG_VALUE(APP_B_ADDRESS) == 0x00000000)) {
    // 1. Set APP_A Availability
    // 2. Erase APP_B Flash
}
else {
    // 1. Set APP_B Availability
    // 2. Erase APP_A Flash  
}

执行复位处理函数通常在芯片复位时自动执行,然后再进入main函数。由于现在是手动引导用户程序,所以也需要手动执行复位处理函数。

基本思路是:(1)将中断向量表的第一个元素所指向的内容赋给MSP。(2)将中断向量表的第二个元素所指向的内容转换成函数并执行。

其代码如下:

/* 设置MSP */
__set_MSP(*(__IO uint32_t*) appAddress);
/* 执行Reset_Handler */
uint32_t JumpAddress = *(__IO uint32_t*) (appAddress + 4);
pFunction Jump_To_Application = (pFunction) JumpAddress;
Jump_To_Application();

代码中的appAddress表示检测到的当前程序空间的Flash基地址,所以appAddress指向_INITIAL_SP, (appAddress + 4)指向OTA_APP_TAG元素。

4. 用户程序存储位置

用户程序准备好以后,需要先烧录引导程序,再烧录用户程序。用户程序可能放在APP_A空间,也可能放在APP_B空间,而且不能覆盖引导程序。

需要利用链接文件(*.icf)控制程序在Flash上的存放位置。

打开程序的链接文件(BlueNRG1.icf),发现里面的内容十分复杂,一眼望不到头。下面挑出重要的代码,挖掘程序保存在Flash指定位置的思路。以APP_A程序为例:

define symbol _MEMORY_FLASH_BEGIN_    = 0x10040000;

define symbol RESET_MANAGER_SIZE      = 0x800;    /* 2KB  */
define symbol MEMORY_FLASH_APP_SIZE   = 0x13000;  /* 76KB */
define symbol _MEMORY_FLASH_OFFSET_   = RESET_MANAGER_SIZE;
  
define region REGION_FLASH = mem:[from _MEMORY_FLASH_BEGIN_ + _MEMORY_FLASH_OFFSET_
                                  to  _MEMORY_FLASH_BEGIN_ + _MEMORY_FLASH_OFFSET_ + MEMORY_FLASH_APP_SIZE - 1];

place at start of REGION_FLASH { readonly section .intvec };

第一行,定义了当前内存的开始地址,这个地址是芯片规定的Flash起始地址。

第二行,定义了引导程序的大小,代码中引导程序用“Reset Manager”表示。

第三行,定义了用户程序的大小,为76KB。

第四行,定义了用户程序相对于Flash起始地址的偏移量。对于APP_A而言,该偏移量为引导程序的大小(RESET_MANAGER_SIZE),对于APP_B而言,该偏移量为引导程序的大小加上APP_A的大小(RESET_MANAGER_SIZE + MEMORY_FLASH_APP_SIZE)。

第五行,定义了REGION_FLASH区域,这块区域就是当前用户程序的Flash区域。

最后一行,表示所有的readonly数据、.intvec段都放在REGION_FLASH区域的开头,翻译过来就是中断向量表放在REGION_FLASH的起始位置。这一行就决定了烧录工程二进制文件时,从REGION_FLASH的开头处开始烧写。

对于APP_B程序,只需要将第四行做如下更改即可:

define symbol _MEMORY_FLASH_OFFSET_   = RESET_MANAGER_SIZE + MEMORY_FLASH_APP_SIZE

5. 实现协议

系统提供了OTA_btl.c/OTA_btl.h两个文件,将所有的操作都打包成了API,这里分析其实现思路。

5.1 添加GATT服务

OTA Service的UUID、特征项、特性都已经定义好,所以可以封装成无参函数,在初始化BLE的GATT时候多添加这个服务项即可。

5.2 特征项

Current Image Char特征项需要准备当前设备空闲的程序空间供主机读取,由于这些数据是静态数据,所以在构建GATT服务时即可完成。

有三个特征项需要接收主机的Write数据,在回调函数中需要分别处理。

对于New Image Info Char特征项,接收的数据中包括新程序文件的大小和基地址。

对于New Image Data Char特征项,接收的数据位新程序文件的数据块,还附带校验信息和块序号。如果校验和块序号均有效,则立即写入Flash。同时统计总的数据长度,如果等于前面接收的程序文件的大小,说明传输完毕,则设置两个程序空间的标志位。

对于Expected Data Block Sequence Number Char特征项,写入一个数据块后会发送一个Notification给主机,告知下一个期望的块序号。

5.3 设置标志位

设置标志位时候只需要利用用户程序空间基地址,加上合适的偏移量,用标准库函数FLASH_ProgramWord写入即可。

6. APP_A与APP_B区别

Flash层面区别,二者是独立的。在链接文件中,需要用代码将应用程序的基地址指向APP_A或APP_B的基地址。

软件层面,主机需要知道从机的空闲Flash的地址和边界,在Current Image Char中需要将APP_A或APP_B的地址信息以硬代码形式写入程序。设置标志位时候,也需要知道当前程序是APP_A还是APP_B。

实际使用时,主机端需要准备APP_A和APP_B两套bin文件,先与从机沟通确定旧程序所在位置,然后选出合适的新程序传给从机。

疑问——能不能实现用户编程时候不管APP_A或APP_B,在设备端或主机端自动判断位置并将二进制文件写入到合适的Flash地址?

7. 单程OTA

前面介绍的OTA方案,新旧程序独立存放,因此在OTA升级过程中如果断电,重启后仍然可以运行旧程序。

但是该方案用户程序只能使用不足一半的Flash空间,浪费较多。

单程OTA方案不分割应用程序空间,将OTA Service 放在引导程序,更新时候在引导程序中接收新程序数据,并直接覆盖在程序空间。

这样好处是能够有效利用Flash空间,但如果OTA中途失败,将破坏旧程序,重启后会(且只能)进入等待升级状态。

单程OTA方案架构图如下:

BLE_OTA_Single_Image_Arch

实现思路与需要考虑的问题,与双程OTA方案大同小异。

(完)

协议发布时间 协议版本
2016/12 Bluetooth 5
2014/12 Bluetooth 4.2
2013/12 Bluetooth 4.1
2010/6 Bluetooth 4.0

Bluetooth 5 新功能

新增功能 协议层 描述
Slot Availability Mask (SAM) 物理层 标记设备的收发时间块。其他蓝牙设备获取该信息即可避免在该设备忙碌时争抢信道资源,这有利于在信道资源有限的情况下维持多设备同时工作,这个特性仅适用于经典蓝牙。
2 Mbit/s PHY for LE 物理层 2Mbit/s比特率的物理层。以前的物理层都是1Mbit/s比特率,这个特性从物理层提升了传输速率。
LE Long Range 物理层 该特性由多个方面共同实现。最高20dBm的发射功率(以前是10dBm),编码型物理层最低-82dBm接收灵敏度(以前是-70dBm),8位前向纠错编码FEC(以前没有),这些新特性共同实现了更远的通信距离,官方宣称能比过去提高4倍传输距离,实际测试高达750米。
High Duty Cycle Non-Connectable Advertising 链路层 在协议文档中未找到针对该特性的描述,从网络上也未获取有用信息。根据一个已有的类似概念“High Duty Cycle Connectable Advertising”,可以推测这个广播事件类型就是广播间隔更短,并且限制广播总时间,超时后将停止广播。
LE Advertising Extensions 链路层 扩展广播。以前广播仅仅使用37/38/39三个广播信道,现在可以使用扩展广播包,在0-36数据信道上传输,以前广播数据最大为31字节,现在扩展广播的数据长度最大可达255字节,所以官方宣称广播数据容量扩展8倍。
LE Channel Selection Algorithm #2 物理层 一种新的跳频算法。以前的算法仅用于连接数据的跳频,现在数据信道支持传输广播数据,以前的跳频算会产生性能问题,新的跳频算法可以用于连接数据通信的跳频和周期广播数据的跳频。

Bluetooth 4.2 新功能

新增功能 协议层 描述
LE Data Packet Length Extension 链路层 扩展链路层PDU长度。以前LL PDU的Payload最大长度是27字节,对应着L2CAP的MTU最大值23,该功能将LL Payload长度扩展到251字节,最大L2CAP MTU可达512,此时一个512资字节的L2CAP数据包需要分段传给链路层。这个功能显著的提升了传输速率,理论上最大可提升3倍(270kbps -> 800kbps)。
LE Secure Connections 安全管理器层 采用椭圆曲线加密算法加密配对过程中生成的密钥,确保密钥在配对过程中不被窃听,这个特性产生一种新的配对方法:LE安全连接配对。这个特性扩展了传统的配对方法和模式,配对过程能够防护窃听和MITM威胁。
Link Layer Privacy 链路层 采用可解析的私有地址来解决隐私问题。这种地址是一种随机地址,并且定时自动更新。
Link Layer Extended Scanner Filter Policies 链路层 在协议文档中未找到针对该特性的描述。

Bluetooth 4.1 新功能

这个版本有以下新功能:

  • Mobile Wireless Service Coexistence Signaling
  • Train Nudging and Generalized Interlaced Scanning
  • Low Duty Cycle Directed Advertising
  • L2CAP Connection Oriented and Dedicated Channels with Credit Based Flow Control
  • Dual Mode and Topology
  • LE Link Layer Topology
  • 802.11n PAL
  • Audio Architecture Updates for Wide Band Speech
  • Fast Data Advertising Interval
  • Limited Discovery Time

这些更新与BLE关系不大。

(完)