打印
[微控制器/MCU]

ARM系统中断向量表的动态配置

[复制链接]
2981|1
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
Massif123|  楼主 | 2010-12-12 14:31 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
通常情况下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体系下一样动态地设置和修改中断向量。初始化应用程序执行环境
   映像一开始总是存储在ROM/Flash里面的,其RO部分即可以在ROM/Flash里面执行,也可以转移到
速度更快的RAM中执行;而RW和ZI这两部分是必须转移到可写的RAM里去。所谓应用程序执行环境的初始化
,就是完成必要的从ROM到RAM的数据传输和内容清零。
下面是在ADS下,一种常用存储器模型的直接实现:
LDR  r0,=|Image$$RO$$Limit| 得到RW数据源的起始地址
LDR  r1,=|Image$$RW$$Base| RW区在RAM里的执行区起始地址
LDR  r2,=|Image$$ZI$$Base| ZI区在RAM里面的起始地址
CMP  r0,r1         比较它们是否相等
   BEQ  %F1
0   CMP  r1,r3
   LDRCC r2,[r0],#4STRCC r2,[r1],#4
   BCC  %B0
1   LDR  r1,=|Image$$ZI$$Limit|
   MOV  r2,#0
2   CMP  r3,r1
   STRCC r2,[r3],#4
   BCC  %B2
   程序实现了RW数据的拷贝和ZI区域的清零功能。其中引用到的4个符号是由链接器第一输出的。
|Image$$RO$$Limit|:表示RO区末地址后面的地址,即RW数据源的起始地址
|Image$$RW$$Base|:RW区在RAM里的执行区起始地址,也就是编译器选项RW_Base指定的地址
|Image$$ZI$$Base|:ZI区在RAM里面的起始地址
|Image$$ZI$$Limit|:ZI区在RAM里面的结束地址后面的一个地址
   程序先把ROM里|Image$$RO$$Limt|开始的RW初始数据拷贝到RAM里面|Image$$RW$$Base|开始的地
址,当RAM这边的目标地址到达|Image$$ZI$$Base|后就表示RW区的结束和ZI区的开始,接下去就对这片ZI
区进行清零操作,直到遇到结束地址|Image$$ZI$$Limit|
改变处理器模式
   因为在初始化过程中,许多操作需要在特权模式下才能进行(比如对CPSR的修改),所以要特别注
意不能过早的进入用户模式。
   内核级的中断使能也可以考虑在这一步进行。如果系统中另外存在一个专门的中断控制器,这么做
总是安全的。
呼叫主应用程序
   当所有的系统初始化工作完成之后,就需要把程序流程转入主应用程序。最简单的一种情况是:
IMPORT main
B   main
直接从启动代码跳转到应用程序的主函数入口,当然主函数名字可以由用户随便定义。
在ARM ADS环境中,还另外提供了一套系统级的呼叫机制。
IMPORT __mainB   __main
__main()是编译系统提供的一个函数,负责完成库函数的初始化和初始化应用程序执行环境,最后自动跳
转到main()函数

相关帖子

沙发
-自己人| | 2010-12-12 16:44 | 只看该作者

一步一步学习uCOS和ARM体系结构的心得

本帖最后由 -自己人 于 2010-12-12 16:45 编辑

学习必备条件:
1、一块开发板——现在淘宝上有很多开发板,建议初学者不要去购买那些ARM9体系结构的,因为作为初学者来说ARM9体系的东西是很复杂的,买块ARM7的就可以了(先入门,然后再提高);


2、学习必备书籍
    《Pointer on C》中文名字叫《C和指针》(美)里科|译者:徐波 前提你的了解C指针吧(这本书时非常经典的书籍,完全可以看中文版的,作者翻译的非常到位)


   《嵌入式实时操作系统μCOS-II(第二版)》--邵贝贝翻译的(不能不承认邵贝贝是一个非常好的翻译者)


   《μC/OS-Ⅱ标准教程》--杨宗德


   对于英文不好的朋友可以购买《ARM体系结构与编程》--杜春蕾(该书其实还是有很多翻译错误的,大家在看的时候要注意一下,但是不可否认的是这本书到目前为止算得上是我见过最好的讲解关于ARM体系结构的书籍了)


   对于英文过关的兄弟姐妹可以直接看《ARM Architecture Reference Manual(2nd Edition)》


3、有条件的兄弟姐妹可以选购一个仿真器 JLINK V8全功能版(这个的感谢中国的优秀工程师,是他们把原价1999多的JLINK变成了几十元钱的东西


4、开发环境--MDK350或者IARARM
   如果要熟悉这些开发环境,就需要好好读一些这些开发环境的手册资料,不好意思,这个就只有英文的了。

以上4个条件具备下来,可能也不会超过500元钱。



下面来讲讲我的学习经历:

    我刚开始学习的时候,自己走了不少弯路,这里就省略不说了。
第一步:在开发板上跑跑简单程序
因为你有开发板,所以你就先在网上找一下对应开发板上的ARM芯片的datasheet,你都不先搞懂芯片的手册,又谈何让自己实现很多功能呢。
熟读数据手册后,就尽可能的在开发板上实现芯片对应的每个功能,像GPIO实验,UART通讯,I2C,ADC,DAC,PWM,RTC,SPI等等。自己写自己的程序,然后在开发板上好好实践一下。
当每个功能模块都实现以后,建议你让你的开发板实验一下多个功能复合使用,比如用UART打印出ADC的值什么的。


第二步:学习uCOS
鉴于你已经购买了《嵌入式实时操作系统μCOS-II(第二版)》,除开以下三个文件:
1、OS_CPU_C.c
2、OS_CPU.H,
3、OS_CPU_A.asm(这个是MDK环境下的汇编文件)或OS_CPU_A.a(这个是IAR开发环境下的汇编)
这三个文件的处理机制暂时可以不用先了解怎么做的,怎么移植的,以后再说
第一次读这本书时,搞清楚什么是任务,什么是前后台,为什么要在系统中加入嵌入式操作系统。
第二次读这本书时,详细了解他的TCB(Task Control Block)和ECB(Event Control Block)的数据结构,搞清楚整个uCOS的实现机制(这里不要先就去看uCOS的移植,这样只会让你云里雾里的)
第三次读这本书是,详细了解信号量,互斥信号量、消息队列、邮箱和时间标志组
鉴于你又购买了《μC/OS-Ⅱ标准教程》,你 可以想在VC 6.0开发环境下实现对信号量,互斥信号量、消息队列、邮箱和事件标志组的上机实验代码。
如果还是不能了解uCOS怎么工作的,建议你多看几遍(我可是足足看了6、7遍了,书都翻得脱页了。汗说明我很笨,希望各位看官都比我聪明,呵呵)


第三步:建立对uCOS的初步认识
在网上下载对应你的开发板ARM芯片移植好了文件OS_CPU_C.c,OS_CPU.H,OS_CPU_A.asm(当然有的开发板已经提供了这样的基础实验)
在你的开发板上跑跑uCOS,建立起自己对于uCOS在ARM7开发板上的认识。
在你所购买的ARM7开发板上编写一个自己跑uCOS得任务(这里先不要去搞中断,初学者对于uCOS下的中断程序设计根本就搞不清楚)


第四步:熟悉ARM体系结构
因为是基于ARM7的移植,建议英文不好的去看看这本书《ARM体系结构与编程》(虽然这本书的作者在翻译时有很多操作,但是还是建议初学者去购买来好好看看),对于英文好的直接在网上下载《ARM Architecture Reference Manual(2nd Edition)》看就可以了
在看这个《ARM体系结构与编程》或《ARM Architecture Reference Manual(2nd Edition)》时,务必注意多看几次,了解以下几个方面的知识:
1、ARM模式下的指令集
2、Thumd模式下的指令集
3、异常模式及进入异常时ARM如何处理
4、一定能熟悉ARM中的汇编指令
5、熟悉CPSR,R0-R7,R8-R14,R15及在各个模式下,这些寄存器的映射。
熟悉以后,再来自己做uCOS的移植了。


第五步:uCOS移植
当然在自己编写代码做移植uCOS之前,有必要看看别人的移植代码。
其实对于uCOS的移植,我们只需要编写关键的三个文件,文件如下:
1、OS_CPU_C.c(堆栈初始化操作OSTaskStkInit函数)
2、OS_CPU.H:完成在该芯片下的数据定义,比如什么unsigned char 之类的,注意一下OS_STK和OS_CPU_SR的定义,定义下关中断和开中断的方式,有三个只能选择1个,ARM芯片一般是定义为第三种方式
3、OS_CPU_A.asm(这个是MDK环境下的汇编文件)或OS_CPU_A.a(这个是IAR开发环境下的汇编),这个文件的内容就多了,要做以下几个函数的重写:
1、OSStartHighRdy函数
2、任务级的任务切换函数OSCtxSw,
3、中断级的任务切换OSIntCtxSw,其实只要写好了OSCtxSw,OSIntCtxSw自然就copy一下就完了(copy的时候记者要把保存当前任务的寄存器去掉就可以了。


第六步:测试自己的uCOS移植代码


第七步:在自己写的uCOS移植代码中跑跑任务。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

177

主题

276

帖子

1

粉丝