ARM 嵌入式系统中断向量表的动态配置
摘要:通常32 位ARM 嵌入式系统的中断向量表是在程序编译前设置好的,每次编写中断程序都要改C 程序的汇编启动代码,相当繁琐。本文给出一种配置ARM 中断向量表新方法。该方法比通常方法仅增加一条指令执行时间,简便高效,功能完备,向量表在运行时动态生成,C 程序可以使用固定向量表的启动代码,并可隐藏起来。
关键词:动态配置嵌入式系统 ARM 中断向量表一般32 位ARM 嵌入式系统的中断向量表是程序编译前设置好的。在编写32 位ARM 嵌入式系统的中断服务程序、设置和修改ARM 体系结构的中断向量表时,常感到相当麻烦,不得不修改汇编代码,对不喜欢使用汇编代码编程的程序员尤其如此。当需要在程序运行过程中动态修改中断向量的程序时
会感到更为不便,不得不增加很多分支处理指令才能实现。为此本文提出一种简便高效的配置方法,实现了ROM 固化程序在运行时动态配置ARM 嵌入式系统中断向量表的功能。1 ARM 中断向量两种设置方法在32 位ARM 系统中,一般都是在中断向量表中放置一条分支指令或PC 寄存器加载指令,实现程序
跳转到中断服务例程的功能。例如:IRQEntry B HandleIRQ 跳转范围较小B HandleFIQ 或IRQEntry LDR PC,=HandleIRQ 跳转的范围是任意32 位地址空间LDR PC,=HandleFIQ LDR 伪指令等效生成1 条存储读取指令和1 条32 位常数定义指令。32 位常数存储在LDR 指令附近
的存储单元中,相对偏移小于4KB。该32 位数据就是要跳转到的中断服务程序入口地址。之所以使用LDR 伪指令,是因为ARM 的RISC 指令为单字指令,不能装载32 位的立即数(常数),
无法直接把一个32 位常数数据或地址数据装载到寄存器中。下面一般程序与上述伪指令功能等效,但中断向量表描述得更为清晰。其中VectorTable为相对LDR 指令的偏移量:IRQEntry LDR PC,VectorTable+0 ;与LDR PC,=HandleIRQ 等效LDR PC,VectorTable+4 ;与LDR PC,=HandleFIQ 等效……
VectorTable DCD HandleTRQ
DCD HandleFIQ
……
HandleIRQ
……
HandleFIQ
一般ARM 嵌入式系统的程序都是固化在从00000000H 开始的低端ROM 空间中,中断向量表VectorTable也是固化在ROM 中,所以上述两种方法都无法在程序运行时动态随机修改中断向量表。不论对于初学ARM 处理器的程序员还是有经验的程序员,设置中断向量都相当繁琐,必须修改ARM 的C 程序的启动代码。一段晦涩的汇编代码很不方便,比较容易出错。
2 X86 与ARM 处理器中断向量表比较 实模式X86 程序员都熟悉,在X86 体系结构的PC 系统中,不论是用汇编还是用C 语言,都可以动态随机地设置、修改中断向量表—只需要简单地把中断程序例程的入口地址写入到中断向量表数据区,即可完成向量表的设置。
X86 向量表设置方便的原因有两个。其一是中断向量表与程序代码完全分离,中断向量表设置在RAM 数据空间,向量表存放的数据是纯粹地址数据;而在ARM 向量表中存放的是与中断服务例程入口有关的一条分支指令。另一个原因是,除BIOS 外,大多数PC 程序都是在运行时加载到RAM 中的,程序数据是不加区别的,所以可以很容易在程序运行的过程中从数据生成程序,并可以很容易把CPU 控制权转到新生成的程序中。
表面上看,在ARM 第二种中断向量设置方法的向量表VectorTable中也是纯地址数据,不含指令代码,似乎可以把VectorTable设置在RAM 数据段中。然而一般ARM 体系的ROM 代码段和RAM 数据段间的偏移远大于2 12,故超出了LDR 使用PC 为基址的相对寻址范围。
代码中的VectorTable是一个与当前PC 间的一个偏移,LDR 指令的相对地址是在编译时计算的,要求VectorTable<2 12,所以VectorTable不能随意安排在RAM 空间中。VectorTable一般只能安排在中断跳转指令附近的代码区内中。
3 ARM 结构中中断向量表的动态配置方法
要在ARM 结构中实现与X86 中一样方便的在中断向量的随机存取功能,向量表的地址数据必须可以安排在任意32 位地址的RAM 空间中。为此,中断处理必须增加一条指令,先跳转到向量表,然后执行向量表中动态生成的跳转指令,跳转到中断服务程序,参见下列初始化代码:
;******向量表******
ENTRY B ResetHandle 原向量偏移,中断号B ReseHandle 0x00 ,00 LDR PC,=NewVectorTable+0x08 0x04,未定义,01 LDR PC,=NeWVector Table+0x10 0x08,SWI,02 LDR PC,=NewVectorTable+0x18 0x0c,未定义,03 LDR PC,=NewVectorTable+0x20 0x10,未定义,04 LDR PC,=NewVectorTable+0x28;0x14,未定义 0,05 LDR PC,=NewVectorTable+0x30 0x18,IRQ 06 LDR PC,=NewVectorTable+0x38 0x1c,FIQ ,07
……
;******代码段****** ResetHandle
……; ***数据段,为NewVectorTable分配数据空间*** NewVectorTable # 128;大小根据需要定义,每向量2 个字(8 字节);程序运行时,中断服务的初始化程序必须设置好新的中断向量表,即在NewVectorTable表中动态生
成下列指令:NewVectorTable;表安排在RAM 顶端0x0c1fff00 处(由硬件设定)LDR PC,[PC,#4];指令代码为0xe51ff004,功能为PC〈-[PC+4] nVt00 DCD ISR_RESET_HANDLE LDR PC,[PC,#4];与LDR PC,nVt01 指令等效
nVt01 DCD ISR_UNDEF_HANDLE
LDR PC,[PC,#4]
nVt02 DCD ISR_SWI_HANDLE
LDR pC,[PC,#4]
nVt03 DCD ISR_UNDEF_HANDLE
LDR PC,[PC,#4]
nVt04 DCD ISR_UNDEF_HANDLE
LDR PC,[PC,#4]
nVt05 DCD ISR_UNDEF_HANDLE
LDR PC,[PC,#4]
NVt06 DCD ISR_IRQ_HANDLE
LDR PC,[PC,#4]
nVt07 DCD ISR_FIQ_HANDLE
……可用C 函数在NweVectorTable中生成含上述指令的向量表,具体实现如下:#define VECTOR_TABLE 0x0c1fff00
//向量表首地址,根据实际硬件来配置
#define INSTRUCTION_LDR_PC 0xe51ff004 //加载PC 寄存器的指令码//设置向量C 函数,ISR_Handle 中断服务程序地址void SetVector(unsigned char no,unsigned long int ISR_Handle){ unsigned long int * pVectorTable;
// 定义32 位无符号数指令,指向向量表pVectorTable=((unsigned long int *)(VECTOR_TABLE+(no<<3))); *pVectorTable++=INSTRUCTION_LDR_PC; //在向量表中放置LDR PC,[PC,#4]指令
*pVectorTable =ISR_Handle;//设置中断服务例程入口地址} //读取向量C 函数,no 代表中断号unsigned long int GetVector(unsigned char no){ unsigned long int *pVectorTable; pVectorTable=((unsigned long int *)(VECTOR_TABLE+(no<<3))); return *(++pVectorTable);//返回中断处理程序入口地址}
使用上述初始化代码和向量设置函数,除复位向量外,其它所有中断向量都可以指向了在RAM 数据区中的新向量表,并给定一个统一的中断编号。中断服务程序可以放在任何模块文件中编译连接,不需要修改原向量表代码,但在打开中断使用中断服务例程前必须使用C 函数SetVector()设置中断向量。
4 结论 本文提出的中断向量表配置策略和实现方法,简便高效,仅比标准处理方法增加一条指令的执行时间。当把ARM 的C 初始化汇编代码中所有中断源(包括扩展的内外部中断源)的向量都指向了新向量表,并统一编号,此后编写任何中断服务程序几乎不需要修改汇编代码,C 初始化代码完全可以对C 程序员隐藏起来,并可以像在X86 体系下一样动态地设置和修改中断向量。