面向ARM Cortex-M系列MCU的嵌入式终端BIOS设计
2021-05-14程宏玉王宜怀姚望舒黄志贤
程宏玉 王宜怀 姚望舒 彭 涛 黄志贤
(苏州大学计算机科学与技术学院 江苏 苏州 215000)
0 引 言
ARM架构处理器在性能、功耗等方面的优势决定了其在芯片市场中不可取代的角色,其中Cortex-M系列微控制器(Microcontroller Unit,MCU)凭借出色的功耗和成本控制广泛应用于嵌入式系统领域。引导加载程序(Bootloader)是嵌入式系统在引导操作系统内核或用户程序之前运行的一段标准代码,其主要功能是建立内存空间映射,设置系统堆栈和系统启动参数区等,从而将系统的软硬件环境带到一个合适的状态[1],其功能类似于PC端基本输入输出系统(BIOS)。
当前Bootloader的研究更多地关注嵌入式设备中操作系统的引导以及Bootloader在嵌入式终端中的移植:Sha等[2]设计了基于S3C2440开发板的Bootloader,完成Linux操作系统的启动;Zhang等[3]成功将U-Boot移植到视频监控硬件平台DM6446上。而关于如何利用Bootloader为嵌入式开发提供支撑的论述相对较少。与之相对,面向MCU的嵌入式应用开发普遍存在难度大、周期长、移植困难等客观问题,Bootloader程序的低复用率及其单一功能已难以满足实际应用需求。
随着嵌入式系统的发展,软件构件化已成为嵌入式开发的发展趋势[4],相对于普通代码级编程,以构件为基本单位的嵌入式开发可有效提高编程颗粒度及应用可移植性。本文以ARM Cortex-M系列MCU为平台,在Bootloader中实现构件实体和对应索引的独立部署,优化Bootloader设计方案,在此基础上提出嵌入式终端BIOS的概念及设计方法。
1 嵌入式终端BIOS
PC机BIOS作为预先安装的固件,是上电启动后运行的第一个程序,可为操作系统和应用程序的运行提供通用底层软件接口。嵌入式终端BIOS以Bootloader为原型,遵循构件化软件设计思想,以尽可能小的软硬件资源为代价提高用户程序开发效率。
1.1 含 义
嵌入式终端BIOS是固化于目标设备指定非易失性存储区域(如Flash)的标准应用程序,与真正的用户程序在物理空间分离。BIOS在完成传统Bootloader启动和初始化功能的基础上,通过合理的存储空间划分实现底层驱动构件的驻留,为用户程序提供合理的调用接口;优化BIOS到用户程序的跳转流程,并提供返回BIOS机制;实现中断共享,扩展用户程序权限。
1.2 嵌入式终端软件架构
针对不同的MCU和应用程序类型,其对应的BIOS可能存在特殊功能或应用需求[5]。从中抽取出共性需求,设计对应的功能模块,同时考虑不同MCU间的移植以最大限度降低BIOS开发和移植的开销。结合实际应用和理论分析,将BIOS需求概括为3类:程序跳转,底层驱动构件驻留,共享中断服务例程。
以BIOS共性需求为出发点,为之封装易于移植复用的独立函数或文件,并借助ARM Cortex-M系列软件接口标准[6](Cortex Microcontroller Software Interface Standard,CMSIS),抽象出图1所示嵌入式终端软件架构。
图1 嵌入式终端软件架构
1.3 BIOS执行流程
MCU上电后BIOS从指定Flash区域加载,完成系统初始化和存储空间分配,在跳转主函数之前建立供用户程序调用的驱动函数接口表;随后进行跳转判断决定是否执行中断共享和运行状态切换。BIOS执行流程如图2所示。
图2 BIOS执行流程
2 整体设计
本文以NXP公司MKL36Z64VLH4[7](简称MKL36Z64)作为硬件平台,阐述嵌入式终端BIOS的设计方法。MKL36Z64基于ARM Cortex-M0+内核,最大运行速率48 MHz,Flash大小为64 KB,RAM空间为8 KB。
2.1 空间划分
MCU存储器空间的合理划分是保证系统稳定运行、充分发挥芯片性能的基础,划分原则是BIOS在实现必要功能的前提下占用尽可能少的存储空间,为用户保留更多的可用资源。MCU存储空间的划分可通过修改对应MCU工程中的链接文件实现。链接文件是一种规则性文件,它决定在链接步骤中MCU各存储介质的起始地址和空间大小,并指示各段数据的存储位置[8]。不同芯片厂商提供的链接文件格式及语法表示有所差异,但基本内涵一致。MKL36Z64中链接文件配置方式如图3所示。
图3 链接文件配置方式
通过链接文件的配置,将目标MCU的Flash区域一分为二,BIOS占据26 KB的空间,用户程序使用剩余空间,实现物理空间的分离,最终Flash划分情况如图4所示。
图4 MKL36Z64 Flash划分图
2.2 程序跳转
BIOS向用户程序跳转的判断一般需借助特定标识信息,这个标识信息可以是某个特定GPIO引脚状态[9]或是存储在带电可擦可编程只读存储器(Electrically Erasable Programmable Read-Only Memory,EEPROM)中某个标识位的值[10]。前者在实现上最为简单,但依赖单个GPIO引脚状态,可靠性相对较低;后者可靠性高但需要特定外设支持,通用性相对较差。本文选择在RAM区域中开辟一段独立的空间保存判断程序跳转的标识信息,该段区域处于data段和bss段之间,具有热复位变量不清除的特性,即程序跳转或正常软件复位不会干预定义在该段区域中的变量值。以定义在该段区域中的标识信息作为程序跳转依据摆脱硬件限制的同时具有更高的可靠性。
BIOS向用户程序的跳转参考MCU启动流程,在Cortex-M系列MCU中主要涉及主栈指针(Main Stack Pointer,MSP)指针和程序计数器(Program Counter,PC)的设置。其中:MSP默认指向栈顶位置;PC存放下一条待运行指令的地址。当系统由BIOS跳转至用户程序时,软件上主动从用户程序Flash区域取出栈顶指针重新赋值MSP,然后利用函数指针的方式跳转至用户程序复位向量,完成BIOS到用户程序的跳转。
用户程序一般由具体开发者编写并烧入设备,系统由BIOS跳转到用户程序执行后,程序运行中发生的错误或异常可能对整个系统造成不可逆的影响,提供必要的防错手段是嵌入式系统稳定运行的重要保障。本文利用非可屏蔽中断(Non Maskable Interrupt,NMI)使系统由用户程序强制跳转回BIOS,并持续运行在BIOS中直至程序错误修复。NMI优先级仅次于复位中断,可在系统停止响应或出现无法处理的异常时通过将芯片对应引脚拉低的方式触发,从而保证系统整体的可维护性与可靠性。程序跳转流程如图5所示。
(a) BIOS跳转用户程序 (b) 用户程序跳转BIOS图5 程序跳转流程
2.3 中断向量表重定向
中断向量表是按照中断源的中断向量号以固定顺序存放中断服务例程入口地址的一段存储区域,其默认存储位置为Flash起始地址。当触发中断事件时MCU从中断向量表中取得指定中断服务例程的入口地址并转而执行该中断服务例程。BIOS和用户程序是物理空间上独立的应用程序,默认情况下拥有各自的中断向量表及对应的中断服务例程。但在实际场景中用户程序需要使用BIOS中定义的某些系统服务,即两段应用程序需共享中断服务。
修改中断向量表表项,使其指向BIOS中断服务例程的物理地址,可实现中断服务例程的共享。该方法存在两个问题:(1) Flash以扇区为单位擦除的特点决定了修改某一表项内容的复杂性且过程难以向用户开放;(2) 用户必须提前获知BIOS中断服务例程的地址,无法实现动态修改。本文实现一种将中断向量表拷贝至RAM,动态修改中断向量表的方案。不同于Flash,RAM无须以扇区为最小操作单元。在跳转用户程序之前将BIOS的中断向量表拷贝至特定RAM段,跳转完成后用户程序可选择修改RAM区中断向量表任一表项内容。
在ARM Cortex-M处理器中以向量表偏移寄存器(Vector Table Offset Register,VTOR)定位中断向量表位置。在完成数据拷贝后,须将VTOR重新赋值为RAM区中断向量表的起始位置,真正完成中断向量表的重定向。
2.4 驱动构件函数接口
与Bootloader程序不同,BIOS内驻留了嵌入式常用驱动,如GPIO、UART、Flash、I2C、SPI、PWM等,并提供了函数原型级调用接口。用户程序不需要从“零”编起,而是在相应框架基础上,充分应用BIOS资源,实现快捷编程。
驱动函数的驻留一般采用在固定地址存放函数接口表的方式,但该方法需在BIOS特定存储区域开辟固定大小的存储空间,不利于程序移植和动态更新。本文利用请求管理调用(Supervisor Call,SVC)方式实现驻留驱动的调用,其思想借鉴DOS下的系统功能调用。在DOS操作系统中系统功能的调用是一个具有多种功能服务程序的软中断指令,DOS操作系统将特定功能编号,并向用户提供编号列表[11]。用户需要调用DOS系统功能时,将指定系统功能编号存入AH寄存器,并通过主动触发INT 21H中断实现系统服务调用。
SVC是ARM处理器中提供的一种能够使任务触发特定OS异常的软件中断机制,它既是一条指令,也是一种中断[12]。SVC指令包含一个8位立即数,理论上可利用SVC指令实现255种不同的服务,其中0号服务由操作系统占用。本文使用SVC的1号服务获取驱动函数接口表,并预留其他服务接口作为系统功能扩展。根据Cortex-M系列处理器的中断机制,进入SVC中断服务例程后,SVC指令编号可通过栈中PC寄存器值获取,堆栈状态如图6所示。
图6 SVC中断发生时堆栈状态
用户程序需要使用BIOS驻留驱动函数时主动触发SVC中断,在其中断服务例程中将函数接口表的地址作为返回参数传递至SVC中断调用处,用户根据驱动函数在表中的偏移量调用具体功能函数。获取驱动函数接口表流程如图7所示。
图7 获取驱动函数接口表流程
3 可移植性研究
可移植性是软件质量的重要属性,本文结合BIOS终端程序框架,阐述ARM Cortex-M系列MCU之间移植方法。
3.1 BIOS工程框架
结构清晰、组织合理的工程框架是软件可移植、可复用的基础[13]。针对嵌入式应用开发特点、兼顾BIOS在不同MCU间的移植,建立图8所示BIOS工程框架。
图8 BIOS工程框架
其中:Core存放内核文件,主要完成核内外设访问控制、外设寄存器访问权限设置、特设功能函数接口定义;MCU文件涉及芯片初始化、中断向量表定义、启动流程、外设驱动的具体实现等相关内容;UserBoard存放应用级硬件驱动;SoftComponent存放BIOS具体功能构件,projectJump实现程序跳转,svc提供驱动函数调用列表,cpy_nvic实现中断服务例程共享;NosPrg存放BIOS主程序文件、中断服务例程文件。
3.2 移植分析
3.1节所述程序框架已将相关文件按功能、软硬件结构合理部署,结合BIOS功能模块的构件化设计,可以较小的代价完成BIOS程序在不同芯片间移植。以MKL36Z64中BIOS为基准,表1给出BIOS在Cortex-M0+内核的MKW01Z128[14]以及Cortex-M4F内核的MSP432P401R[15]两款MCU上的移植比较。
表1 BIOS在不同MCU间移植比较
可以看出,BIOS在相同内核MCU间的移植只需修改MCU文件,不同内核间的移植因架构差异改动量稍有增加。但得益于构件化结构设计,BIOS功能构件移植时无须改动,这在最大程度上保证了方案可移植性。
4 结 语
嵌入式应用程序与目标设备之间的高度耦合决定了其开发和移植的复杂性,降低硬件差异影响、抽象共性开发技术是提高应用开发效率的关键。本文以ARM Cortex-M系列MCU为平台,提出嵌入式终端BIOS的概念和设计方法,结合构件化设计思想,提高终端程序可移植性;优化Bootloader实现关键技术,为用户提供构件级开发支撑,有效降低用户程序开发难度。