传统的嵌入式远程更新方案普遍采用整包更新方式,这种方式更新数据量大,占用网络带宽时间长,同时也增加了设备的功耗。
针对这些问题,提出了以减少更新数据量为核心的两种远程更新方案。这两种方案分别使用LZ77压缩和BSDiff差分算法处理更新包,减少需要传输的数据量;同时,在数据传输方面增加断点续传功能,实现终端从断线的地方继续获取数据,以此达到节省设备流量和功耗的目的。另外,设计了一种 FLASH 分区方式,优化了本地自更新操作的流程,去除了多余的拷贝操作。将这两种方案在STM32F407ZGT6和 NB-IoT搭建的嵌入式平台中进行验证测试。实 验 结 果 显 示,这两种更新方案的平均更新效率与整包更新 的方式相比分别提升了26.44%和72.17%。
引言
随着NB-IoT、GPRS等无线通信技术的发展,越来越多的嵌入式无线终端设备应用到了日常生活中,这些设备功能的完善、bug的修复等都离不开系统固件的更新,为了更好地维护这些终端设备,远程在线更新固件的功能就显得格外重要。
但在传统的远程固件更新方案中,通常采用整包更新的方式,尽管新旧更新包之间的代码功能差异不大,但仍然需要通过无线的方式将整个更新包下载到硬件内存中,然后再启动更新,这种更新的方式占用网络带宽时间长,并且在网络拥堵的情况下容易出现丢包、断连的情况,进而影响设备的功耗和升级的效率。
LZ77压缩算法
LZ77是一种基于字典的无损压缩算法,首先分别定义一个不同长度的前向缓冲区和滑动窗口。
待压缩的数据会按顺序进入前向缓冲区中,假设前向缓冲区中有n个数据,它们分别为X1,X2,…,Xn,此时这些数据会组成字典Sj={(X1),(X1,X2),…,(X1,X2,…,Xn)}。
之后这些数据又会按顺序进入滑动窗口,假设滑动窗口有m个数据,它们分为X1,X2,…,Xm,此时这些数据组成字典Sw={(X1,X2,…,Xm),(X2,…,Xm),…,(Xm)}
随着前向缓冲区和滑动窗口的移动,字典Sf与Sw中的内容也会不断改变。
每次滑动一位便在字典Sf中寻找能与字典Sw匹配的最长字符串,若能匹配则标记成由偏移量、匹配数据长度、新字符三个参数构成的编码,否则输出未匹配字符。
LZ77算法用这些标记代替冗余的数据,进而达到压缩数据的目的。
BSDiff差分算法
在嵌入式系统中,尽管添加或删除少量的源代码,编译出来的二进制可执行文件仍然会产生很大的差异,这是由于修改过的源代码,经过编译之后,会使原来的代码块和数据块所指向的地址发生大量的变化,但这种变化是有规律可循的,可以由两条规律来总结:
- 没有被新代码所影响的区域,变化是很稀疏并且发生变化的字节之间差异较小,一般相隔几个字节。
- 其中被影响的区域虽然变化很大,但是大都属于代码块和数据块指针的整体偏移,并且这个偏移值为一个固定值。
图中列举了插入一行代码之后新旧可执行文件内容字节的差异。
许多字节的差异为0,因此差异文件是具有高度可压缩性。
步骤为:
- 将旧文件的数据进行后缀排序。
- 使用二分查找找到能匹配的最长字符串的长度,并计算出差异部分和新增部分。
- 将差异部分和新增部分进行压缩,生成差异Patch包。
远程更新系统方案优化
方案一,以LZ77压缩算法为核心,其整体框图如图3所示。
首先将新版本的可执行文件放至服务器,运行LZ77压缩算法,将可执行文件中的数据进行数据压缩生成压缩包。其次通过NB-IoT无线网络将将压缩包下发至终端设备,嵌入式终端设备收到数据之后,会允许LZ77解压程序,将数据还原成完整更新包,接下来由终端设备校验更新的数据;最终执行重启更新。
方案二以BSDiff差分算法为核心,首先服务器需要保存上一版本的升级包,每当有终端设备需要升级时,先将新版本升级包放至服务器,由服务器运行BSDiff程序,将新旧更新包里的数据进行比对运算,得出2个版本之间的Patch补丁包,再通过NB-IoT无线网络将补丁包的数据发送至终端设备。
设备终端收到补丁包之后,会允许BSPatch程序将旧二进制更新包的数据与补丁包的数据进行合并运算,生成完整的新更新包,由终端设备校验更新的数据;最后执行重启更新程序完成更新。
通信协议设计
系统采用移远BC26模组作为NB-IoT无线通信模块。
BC26模组具有丰富的外部接口(UART、SPI、ADC等)和多种网络协议栈(TCP、CoAP、MQTT等),主要采用AT指令的方式执行任务。
相关AT指令:
当STM32通过串口向BC26模组发送AT指令时,BC26模组会通过串口返回OK、NULL等信息告知AT指令是否执行成功。
STM32上电之后通过串口发送相应的AT指令让BC26与服务器建立TCP连接,并主动查询是否有新版本。发现有新版本后,继续向服务器请求更新包的数据。
本文设计了终端与服务器之间的通信协议,表2为终端向服务器查询升级协议包的组成结构,包括协议头、协议长度、操作码等。
其中协议长度为一帧数据包中不包含校验码的总长度,校验码为一帧数据包中不包含校验码本身的总数据的和。
在服务器返回的数据协议中,当升级包总大小这一位的值为0x0000时,表示服务器端没有新版本不需要升级。
当升级包总大小这一位的值不为0x0000时,表示需要升级并且它实际的值为升级包的总字节数。
这时终端会根据表3中请求升级数据的协议包格式继续向服务器请求升级包的数据。
由于BC26模组在每一帧的发送和接收数据量上有限制,因此在代码中设置终端每包向服务器请求的单包数据长度为0x0100(256 byte),即每次向服务器请求256byte的升级包数据,而最后一个升级包按实际长度传输。
在每传完一帧数据后,终端将这一帧数据进行字符串处理,取出升级包数据,直到所有的数据包传输完毕。
IAP流程优化
在应用编程(In application programming,IAP)支持用户使用引导程序对MCU的片内FLASH进行擦除烧写,从而更新本地应用程序。
传统更新步骤:
- 将所有更新包的数据保存至片外FLASH。
- 执行重启,进入Bootloader引导程序。
- 将保存在片外FLASH的更新包数据拷贝至片内User主程序区。
- 跳转至新程序入口。
本系统对IAP功能进行了优化,将STM32的片内FLASH划分为三个区域。
分为一个Bootloader引导区和两个User主程序区,并分别定义User1 与User2 FLASH的起始地址。
优化过后的IAP采用User1和User2主程序区交替更新的方式。
- 将所有更新包的数据直接保存在User2中,并设置标志位。
- 执行重启,进入BootLoader引导程序。
- 跳转至User2主程序入口。
- 下次更新将更新包的数据保存在User1中,设置标志位,执行步骤2,之后跳转至User1主程序区入口。
理由:在FLASH之间的数据交换中,大部分时间会花在数据的擦除和拷贝上,优化后的IAP不仅节约了片外FLASH空间的使用,而且去掉了更新包数据拷贝的这一个环节,进一步减少了本地自更新所需要的时间。
嵌入式平台远程更新整体实现步骤
首先通过LZ77压缩算法或者BSDiff差分算法减少更新包的大小。
其次在无线网络传输数据的环节增加了断点续传功能,在出现断电、网络断开等异常情况时,将前一个的升级包序号写入片外FLASH中,等再上电或者网络状态好的情况下,继续从记录的升级包序号开始请求升级包数据,无需从头开始请求升级包数据;最后通过优化本地执行IAP功能,节省FLASH之间数据拷贝时间。
- 系统在上电之后,对GPIO、时钟、串口等功能进行初始化,保证系统可以正常运行,进入BootLoader引导程序。
- 根据主程序的标志位,跳转至User1(User2)程序区启动。
- STM32通过串口向BC26模组发送相应的AT指令,让其创建TCP Socket并连接服务器。
- 连接完成之后,按照表2的通信协议将终端当前的软件版本号的信息发送给服务器,在服务器返回的应答包中,若升级包大小的值为0x0000时,不需要升级,随后退出升级;若值不为0x0000时,则表明需要升级并执行步骤5.
- 按照表3的通信协议和当前片外FLASH中记录的升级包序号,向服务器端请求升级包数据并将升级包序号加1,随他通过解析服务器端返回数据帧的升级包数据位的数据,并将解析出来的数据通过Flash_Write(wBuf,WriteAddr,NumByteToWrite)函数写入片外FLASH中,其中wBuf为要存储的升级包数据,WriteAddr位准备存入的起始地址,NumBByteToWrite为要存储的总字节数。
- 用升级包数据总量除以256byte计算一共需要请求多少个数据包,若所有升级包都已经请求完,则执行步骤8,否则执行步骤7。
- 没有请求完时会进一步判断,若是因为断电、信号不好等异常造成通信中断或超时,则会记录前一个升级包的序号,将其写入FLASH并断开TCP Sokcet连接,跳转步骤3;若传输没有中断超时,则跳转步骤5进行下一个升级包的请求。
- 当所有的升级包请求完毕之后,会根据对应的实验方案执行解压程序或者解差分还原出更新包。
- 将还原出的所有更新包数据使用Iap_WriteBin(BinAddr,BinBuf,BinSize)函数保存到User2主程序中。
- 保存完毕之后,设置主程序的标志位,并将升级包序号重置为1.
- 将系统重启,随后又进入Bootloader引导程序,根据刚刚设置的主程序标志位,执行Iap_LoadBin(BinAddr)函数初始化堆栈指针并跳转到值为BinAddr的用户程序区中,即完成本次升级并记录该软件版本号。