陈艺芃,林飞浪,郭大鸣,陈飞明
(1.武汉邮电科学研究院,湖北武汉 430074;2.烽火通信科技股份有限公司,湖北武汉 430073;3.武汉纺织大学,湖北 武汉 430073)
在嵌入式板级应用的开发过程中,系统设备的软件代码不是一次开发就能够完成的,需要进行多次的修改、更新和压缩操作来完善。针对测试人员在测试过程中发现的一些问题和客户在使用过程中提出的一些新的功能需求,软件开发人员需要提供一种合理的代码升级方式以便对设备进行在线升级。
嵌入式设备软件升级通常采用移植一个适用于该系统的独立BootLoader的方式,BootLoader 指启动引导程序,是系统加电后运行的第一段软件代码,通过这段代码,可以初始化硬件设备,建立内存空间的映射图,以便为最终调用操作系统内核提供正确的环境[1-2]。使用这种方式存在的问题是升级前需要将MCU 片内Flash 旧版本擦除,为新版本的写入释放空间,这势必导致设备停机[3-5]。
近年来常用的Bank Swap 方式可以解决上述问题。Bank Swap是ST公司对旗下的STM32F4、STM32F7系列芯片提供的MCU 片内Dual Bank Flash 进行交换的方式,它在执行程序的同时可对另一个Bank 进行擦除和编程操作。利用这个特性,可实现在线程序更新。在嵌入式设备软件初版代码的开发过程中,初始代码可能会较为庞大,对于比较复杂的设备,需要很大的运行内存容量,而Bank Swap 方式要将MCU 片内Flash 分为两部分运行,可用运存容量减半,直接采用Bank Swap 方式可能会影响开发过程中对软件的调试。
针对上述问题,文中设计了一种将独立BootLoader和Bank Swap 结合的方案。在开发初期先使用BootLoader+APP的方式过渡,并设计BootLoader+APP的版本,使其具备升级转换成Bank Swap 模式的功能。待开发后期压缩工程的目标文件小于1M(STM32F4 片内Flash 容量为2 M),通过修改程序中的标识位完成模式切换,即可以实现不停机升级操作,满足所有开发需求的同时将外部Flash 弃用,可以停止对片外BootLoader的维护,减少运维工作量。
对于STM32平台的启动引导和代码升级,常用的方式是采用BootLoader+APP的方式。在此可以移植一个适用于该系统的独立BootLoader,将MCU片内Flash划分为BootLoader 区和User Code 区[6]。BootLoader用于实现通过某种通信方式(如USB、USART)接收程序或数据,执行对APP 代码的更新,通常存储于Flash的起始地址(0x08000000)。User Code 区运行的是产品实现业务逻辑正常运行的APP 代码,该部分代码存储在片内Flash 中的BootLoader 区之后,需要进行相对的地址偏移;而缓存的升级数据可以存储在片外Flash 中,以节约内部存储空间。独立BootLoader 模式升级数据流向图如图1 所示。
图1 独立BootLoader模式升级数据流向图
图1 箭头所指为数据流向,在APP 中接收升级数据并缓存到外部Flash。升级时MCU 重启进入BootLoader,BootLoader 检查是否需要更新,若需要更新,则读取缓存在外部Flash的升级数据包,进行后续升级动作,引导代码进入应用区;若BootLoader没有检测到需要更新,开始检测应用区代码是否完整,如果完整则引导进入应用区,否则停留在Boot中,提示上位机等待BootLoader 引导程序进入应用区运行[7-9]。应用接收到升级命令,在Flash 做一个升级动作标记,记录升级的版本等信息。重启MCU 进入BootLoader 进行升级。
近年来利用Bank Swap 方式进行软件升级也越发普遍。对于STM32F4 系列的MCU,可以通过配置Dual Bank和Dual Boot 项,改变RWW 特性,从而实现Bank Swap 方式,以解决上述问题[10]。
配置成Dual Bank 模式后,片内Flash的地址将重新编排。如图2 所示,Bank1的地址为0x08000 000~0x080FFFFF,Bank2的地址为0x08100000~0x081FFFFF,扇区的数量从12 增加到了24,均匀分配于两个Bank 之中。每个Bank 中对应扇区的容量都为之前的一半,Dual Bank 模式时读写的最小单元为128 bit,也是Single Bank的一半。
图2 切换Dual Boot模式Flash分区示意图
将Single Bank 转变成Dual Bank 是通过纵向划分实现的。读取宽度由8 words 变成了4 words。如图3 所示,将Flash 控制器的地址线和数据线由一组分成两组,高位一组,低位一组,这样就可以实现在一个Bank 执行读操作时,对另一个Bank 进行写操作。
图3 Dual Boot模式Flash的划分方式图
另外需要注意的是模式转换前,存储数据地址的变动。假设Sector1 中存储着连续的256 bit 数据,由于采用纵向划分,在划分过程中高地址被重新编排,分别存储到Sector1和Sector13的对应位置中,跟原来的地址不再连续,导致Flash 保存的内容被完全打乱了。即使切换前只使用到MCU 片内Flash的前半部分,也需要在模式切换后将原有的数据擦除并重新烧录到设定的Bank 中。
当Flash配置成Dual Bank模式时,STM32F4 可以实现Dual Boot 模式,即双启动方式。程序可以选择从Bank1启动,也可从Bank2启动。这个操作是由保存在片内MCU的System Memory中的BootLoader 实现的,BootLoader 判断到当前是Dual Bank 模式且配置Dual Boot 选项,则根据用户配置的启动地址,选择从Bank1 启动或者从Bank2 启动。
RWW(Read-While-Write,RWW)是指在系统做运行读取操作的同时支持擦除写入操作的特性。由图4 可以看出,当使用Single Bank 模式时,对Flash的写操作会导致其他读操作被挂起,程序停止执行。而Dual Bank 模式中,可以在一个Bank的程序执行过程中对另一个Bank 执行写操作。
图4 RWW特性示意图
Bank Swap 方式利用Dual Bank 中的RWW 特性,在实现升级的同时其他任务可以继续运行,不影响系统正常功能的运转,并保证了升级期间片内另一个Bank 旧版本的完整性。
鉴于上述分析,结合工程实际需要,考虑到防止User Code 在开发初期超出单Bank 最大容量的可能(STM32F4 片内Flash 容量为2 M),最终决定选择独立BootLoader和Bank Swap 结合的方案。在开发初期先使用BootLoader+APP的方式过渡,并将此独立BootLoader的版本设计为具备升级转换成Bank Swap模式的功能。待开发后期压缩工程的目标文件小于1M,通过修改程序中的标识位完成模式切换,升级操作完全在MCU 片内进行,通过Bank 切换即可以实现不停机升级操作。更新为Bank Swap 稳定版后可以停止对片外BootLoader的维护,减少运维工作量。
将Flash 配置成Dual Bank 模式,片内Flash 地址进行重新编排。通过Flash_OPTCR 寄存器配置User Option Byte 中的nDBANK=0。Option Byte 不能直接写,需要通过Flash_OPTCR 寄存器来操作。
配置Dual Bank 模式完成后,即可开启Dual Boot模式。通过Flash_OPTCR 寄存器,配置Option Byte中的nDBOOT=0,即可配置成Dual Boot 模式。
Flash 配置成两个Bank后,可实现Bank之间直接进行地址交换的功能。配置SYSCFG_MEMRMP寄存器中的SWP_FB,来选择是否交换。当SWP_FB的值为0 时,Bank1的基地址为0x08000000,Bank2的基地址为0x08 100000;当SWP_FB的值为1 时,两个Bank的基地址对换。由此可以看出,无论从哪个Bank 启动,都能保证起始基地址为0x08000000。
独立BootLoader模式业务流程如图5所示。可以看出BootLoader和APP是互斥的,BootLoader运行时APP不运行,APP运行时BootLoader不运行。APP 接收完升级数据后,自身无法更新自身的代码,必须重启进入BootLoader才能完成APP代码的更新[11]。App 在Flash 中的某个位置记录升级动作,然后重启进入BootLoader,BootLoader检查Flash中相应位置的标志位,如需升级,则读取代码缓存区的新版本程序,对APP 进行更新,更新完成后清空该标志位。
图5 独立BootLoader模式业务流程图
将新程序写入User Code 区,发生断电等意外时,APP 可能不完整,下次启动BootLoader 后会对APP的完整性进行检查,如果发现APP 不完全(这种情况多数是上次升级刷机时,没有完成Flash的写操作便被中断导致的,那么升级包缓存区还保留着升级程序),则检查升级包缓存区的升级文件是否完整[12]。如果完整,则将其再次恢复到APP 代码区,否则停留在BootLoader,并主动请求升级。
为执行BootLoader 升级模式转换为Bank Swap模式的指令,在代码中增加了一个标识,每次升级的时候,BootLoader 识别该标识,如果没有转换成Swap方式的需求,则按原设计,将代码升级到User Code区;假如有转换成Bank Swap 模式的需求,则将代码更新到Bank2,同时设置boot address 启动地址为Bank2的起始地址,确认设置为Dual Boot 模式,然后重启进入Bank2 运行新程序。新程序应当具备Swap Bank 模式的升级能力。
升级为Swap 模式后,进入Bank2 运行,便切换到Bank Swap 模式了,此后,新的业务流程如图6 所示。在Bank Swap 模式中,程序也是永远运行于起始地址是0x8000000的片内Flash 中,但有可能是Bank1,也有可能是Bank2,因为执行Bank Swap 操作后,Bank2的起始地址也是0x8000000。因此,升级的过程中,新程序总是烧录在地址0x8100000 中,经过Swap,0x8100000 所属的Bank的地址便会变成0x8000000。需要在程序中通过读取Option Byte 中的某些位的值来判断当前程序运行在哪个Bank,如BOOT_ADD0 寄存器或者SWP_BF的值。
图6 Bank Swap模式业务流程图
Bank Swap 操作是System Memory 中的固件Boot Loader 完成的,这个BootLoader是STM32自带的[13]。只需要配置代码的启动地址和Flash的Dual Boot 模式,System Memory 中的BootLoader 会完成Bank Swap操作,并引导程序到指定的Bank 运行。
按照上述方案,采用以STM32F429IIT6 为核心设计的一个电源监控系统为例,通过网管对系统进行升级验证。
如图7 所示,准备好Tftp 服务器以及需要分别先后烧录的两个bin 文件[14]。为了更明显地感知升级完成的现象,文中使用同一个工程,改变osdelay 参数将LED 测试灯闪烁的频率设置成不一样的值,0100.bin 为LED 快闪烧录到地址0x8000000,闪烁延时设为300 ms;0101.bin 为LED 慢闪烧录到地址0x8100000,闪烁延时设为2 000 ms。
图7 Tftp传输工具和bin文件
打开Tftp 客户端设置界面会自动地读取本地的IP 地址,在下方添加主机信息和端口号以及想要传输的bin 文件所在的位置,如图8 所示[15-16]。
图8 Tftp传输参数填写
使用SecureCRT 接收设备侧串口上报的信息,如图9 所示。可以看到在升级前系统运行在Bank1 中,点击Tftp 中的Put 传输新文件,设备执行更新命令,检查CRC 校验后自动识别非运行Bank(Bank2)的Flash 地址,写入更新信息并完成检查。在升级过程中设备正常运行,LED的闪烁频率并未发生变化。重启后串口提示系统已经切换到Bank2中运行,LED的闪烁频率也明显降低,说明设备成功完成升级。
图9 SecureCRT接收的串口信息
重复上述操作将0100.bin 文件烧录进Bank1 中,升级过程中LED 持续慢闪,重启后闪烁变快,恢复为初始频率。说明此时系统可以在不影响运行的情况下,完成Read-While-Write 升级工作。
为了验证设备在Bank Swap 模式下升级过程中出现的异常停机情况不会影响设备更新前运行程序的完整性,在升级期间拔掉设备与主机间的通信线,此时升级中断。重新上电后发现监控单元上的软件为上一次的版本信息,与网管上待升级版本的信息不一致,说明升级失败后监控单元自动退回到升级前的版本运行,没有被升级所破坏。
该文以STM32F4 软件升级的现实案例为背景,为实现升级过程中不打断设备运行的RWW 特性,以及升级异常时具有自动退回到升级前版本的能力,同时兼顾开发初期代码量较大影响调试的现实问题,设计了一种独立BootLoader 与Bank Swap 相结合的方案。独立BootLoader 方案能提供更大的容量供User Code 使用,作为开发期间的过渡;而Bank Swap 方案使用System Memory 中自带的BootLoader启动,不需要开发和维护独立的BootLoader,可以最终实现RWW 特性以及升级失败时保护的功能。两种模式间通过标志位实现切换。最后通过Tftp 进行升级,通过LED 灯的运行状态以及串口上报的信息,验证了Bank的切换功能以及升级过程中系统可以正常运行的特性,实现了系统的设计要求。该设计对于嵌入式板级应用的开发升级具有较大的意义。