基于IAP和网口的ARM Cortex-M3固件升级技术

引言

现有的一台嵌入式设备基于ARM CortexM3处理器,具有以太网通信功能。为了降低设备维护成本,需要设计网口升级固件功能。本文描述了基于IAP和网口升级该嵌入式系统的方法,其中处理器为NXP公司的ARM CortexM3,开发环境为IAR Embedded Workbench for ARM。

IAP(In Application Programming,在应用编程),一般指MCU可以通过通信端口(UART口、网口等)从外部接收应用程序镜像并烧录到FLASH中实现固件升级,如图1所示。

图1 网口升级固件

1 原理介绍

先温习下小知识:一个典型的嵌入式软件地址空间如图2所示,程序代码(RO段)和初始化数据(RW段)都是存储在ROM(常见为FLASH)中,当系统上电运行时,BootLoader程序会把RW段数据从ROM中拷贝到RAM中(.data),同时它会清零未初始化数据段(.bss)、设置栈(.stack)和堆(.heap),之后系统就可以正常运行了[1]。

这里会有两个疑问:为什么要拷贝RW段呢?BootLoader程序是什么?第一个问题很简单,既然是RW(Read&Write)数据,就只能保存在RAM中,因为ROM是无法执行“写数据”操作的。第二个问题很容易被程序员忽视,它的实现有两种,一种是由芯片的Boot代码(固化在ROM中)来执行拷贝,另一种是编译器自带的代码(如IAR就有__iar_program_start函数)。

图2 软件地址空间

基于IAP固件升级一般会把软件设计成两部分:BOOT和应用程序。其中BOOT相当于PC机的BIOS,负责升级固件和引导应用程序,它对于用户是不可见的;应用程序就是常见的嵌入式软件。这两个软件的存储地址如图3所示。

图3 存储地址视图

再来看看BOOT和APP启动时序,当系统上电时,它首先从0地址找到中断向量表,取出Reset_Handler中断服务代码,该代码先初始化芯片(如PLL和中断寄存器),然后调用BootLoader代码执行搬运工作,把存储空间布置成图2(b)所示的“运行地址视图”,之后跳转到用户代码的main()函数,此时BOOT软件启动完成。BOOT代码开始检测是否需要升级固件,如果需要,就从外部取APP镜像文件并烧录到FLASH中,最后一步是启动APP软件。

那么,BOOT代码如何启动APP代码呢?其实很简单,因为APP代码自身包含中断向量表和BootLoader代码,BOOT代码只需要告诉MCU新的中断向量表地址,然后跳转到APP代码区。之后,APP的BootLoader会把自己的RW数据搬运到RAM中,同样也会布置存储空间(如图2(b)所示),最终跳转到APP代码的用户main()函数,这样APP代码就完成了启动。

2 IAP关键技术

首先,APP代码需要将程序地址重定向,在本例中,需要把代码地址重定向到0x0001 0000。为什么要如此操作呢?看一个实例,假设代码中有调用f1(),如果没有执行重定向,那么f1()可能被链接器分配在0x 1234。当PC寄存器导入该地址并解析指令执行时,致命的错误发生了——该地址根本没有f1()代码,因为APP代码是从0x0001 0000开始存储的。重定向就是告诉链接器,请从0x0001 0000开始定位程序代码,这样在本例中f1()分配在0x0001 1234,才能被正确调用。

在IAR环境下程序空间重定向操作如下:打开“Options”→“Linker”→“Config”,点击“Edit”,在弹出的窗口中设置如图4所示的地址空间[2]。

图4 链接器重定向代码地址

然后,BOOT代码在使用外设后,一定要DeInitialize该外设后再启动APP代码,即要让APP代码认为MCU只是刚上电运行,而不是跑完一个系统再调用自己。如果BOOT代码没有执行该动作,当APP代码运行时,MCU的外设处于不确定状态(尤其是中断未关闭),可能会带来一些预料不到的错误。

再次,BOOT代码操作FLASH是通过调用IAP函数来实现的(NXP公司的IAP库函数提供Erase()/Write()/Compare()等),其中写FLASH函数一次操作只能接收256/512/1 024/4 096字节,如果不足就需要填充[3]。

千万要注意的是,调用IAP函数期间,中断需要特别处理,一起看看NXP官方的说明文档:在IAP函数调用期间,芯片的BootLoader会暂时禁止访问用户ROM空间数据,用户ROM空间被映射到一些配置数据区以便于IAP调用。因为原来的中断向量地址没有包含正确的中断向量,所以当IAP调用正在处理时发生一个中断,该中断将不能被正确对待,同时MCU的行为也是不确定的。在一些情况下,当中断不能被正确处理时,MCU将会复位 [4]。

解决该问题有两种方法:如果中断是至关重要的(任何时候都不能禁止),那么需要把中断向量表和ISR重定向到SRAM中;另外一个简单的办法是调用IAP函数之前先禁止中断,调用完成之后再使能中断。在本固件升级中,采用第二种方法,因为短暂地关闭中断对于本设计来说是可以接受的。

最后,当APP代码被引导运行时,中断向量表不再位于0地址了,在本例中位于0x0001 0000,因此需要将这个新地址告诉MCU,有一个专门寄存器VTOR(Vector Table Offset Register)用来存储该地址。该工作必须由BOOT代码来完成,因为一旦跳转到APP代码,MCU的第一件事情就是访问中断向量表。

千万要注意的一个问题是:APP代码不要再对VTOR寄存器进行任何操作,否则MCU将因为无法访问中断向量表而紊乱。(笔者就遇到这个问题,APP代码中的汇编文件startup_LPC17xx.s启动时“静悄悄地”调用了NXP库函数SystemInit,该函数会重置VTOR导致APP的中断不能使用)。

3 网口下载镜像

这部分内容是偏向策略进行的设计,因此很多地方值得商榷,在这里坚持的原则是——简单就是美。

首先,通信帧直接建立在802.3以太网协议上,这样可以保证简单化;其次,不管镜像文件的实际长度是多少,一律向1 KB取整,不足则填充0;然后,因为以太网MTU为1 518字节,通信帧每次传输1 KB镜像文件;再次,嵌入式系统与PC机通信采用“停等+ACK”机制,即PC机只有接收到第i帧确认后才能传输第i+1帧;最后,为确保镜像文件在传输中不受损,每帧都包含CRC校验码。

升级固件时,PC与嵌入式设备的通信逻辑如下:当设备发出握手帧连续10次无应答后,BOOT代码将直接引导原APP程序启动,即无需升级固件,如图5所示。

图5 无需升级固件通信时序

当正常升级固件时,首先有3次握手,接下来是分片传输镜像文件,最后嵌入式设备会回应“升级成功”帧,如图6所示。

图6 正常升级固件通信时序

如果某分片通信时发生错误,嵌入式设备会回应“错误原因”帧,当重传达到5次仍出错时,PC机需要提醒用户,最好还能说明错误原因,如图7所示。

图7 升级固件出错通信时序

嵌入式设备回应PC的数据帧直接封装字符串,这样做的好处是可以通过截取数据包查明通信内容;而PC机传输给设备的数据帧采用二进制,主要是考虑到嵌入式系统较弱的计算和存储能力,该二进制通信帧格式如图8所示。

图8 二进制通信帧格式

FLASH一般是由Sector组成的,并且在写操作之前需要擦除该Sector。本系统中使用的FLASH共30个Sector,前16个均为4 KB,后14个为32 KB。为了简化设计,将BOOT放置在前16个Sector中,共计64 KB;APP镜像放置在后14个32 KB的Sector中,共448 KB。

这样安排程序依赖如下设定:镜像文件起始位置为0x0001 0000,每次接收与写入FLASH的字节数为1 024,当写入FLASH地址为Sector之首时需要擦除该分区,逻辑流程如图9所示。

图9 分片写镜像文件流程图

结语

本文重点研究了基于IAP和网口的固件升级技术,虽然基于NXP公司的ARM CortexM3处理器和IAR开发环境,但技术原理对于其他架构的嵌入式系统也是适用的。

论文中涉及的技术方法在IAR V6.0和NXP LPC1768芯片上测试成功,并研制成产品,接受过严格的现场升级考验。实践证明,论文中的方法稳定可行。

粽子糖果 发表于09-14 09:37 浏览65535次
分享到:

已有0条评论

暂时还没有回复哟,快来抢沙发吧

添加一条新评论

只有登录用户才能评论,请先登录注册哦!

话题作者

粽子糖果
粽子糖果(总统)
金币:41631个|学分:51991个
立即注册
畅学电子网,带你进入电子开发学习世界
专业电子工程技术学习交流社区,加入畅学一起充电加油吧!

x

畅学电子网订阅号