ARM处理器在嵌入式系统中应用广泛,共有7种运行模式:
处理器模式 |
描述 |
用户模式 |
正常程序执行的模式 |
快速中断模式(FIQ) |
用于告诉数据传输和通道处理 |
外部中断模式(IRQ) |
用于通常的中断处理 |
特权模式(Supervisor ,SVE) |
供操作系统使用的一种保护模式 |
数据访问中止模式(Abort) |
用于虚拟存储以及存储保护 |
未定义指令中止模式(Undefined) |
当未定义的指令执行时进入该模式,可用于支持硬件协处理器的软件仿真 |
系统模式 |
用于运行特权级的操作系统任务 |
各种不同异常模式下使用的寄存器组:
运行模式位M[4:0]的具体含义
M[4:0] |
处理器模式 |
可访问的寄存器 |
0b10000 |
用户模式 |
PC,CPSR,R0-R14 |
0b10001 |
FIQ模式 |
PC,CPSR, SPSR_fiq,R14_fiq-R8_fiq, R7~R0 |
0b10010 |
IRQ模式 |
PC,CPSR, SPSR_irq,R14_irq,R13_irq,R12~R0 |
0b10011 |
管理模式 |
PC,CPSR, SPSR_svc,R14_svc,R13_svc,,R12~R0, |
0b10111 |
中止模式 |
PC,CPSR, SPSR_abt,R14_abt,R13_abt, R12~R0, |
0b11011 |
未定义模式 |
PC,CPSR, SPSR_und,R14_und,R13_und, R12~R0, |
0b11111 |
系统模式 |
PC,CPSR(ARM v4及以上版本), R14~R0 |
在ARM指令执行的过程中,通常有三种执行的方式:
1.顺序执行,每执行一个指令,程序计数器寄存器(PC)增加;如果执行ARM指令,程序计数器寄存器(PC)的值加4,如果执行Thumb指令,PC值加两个字节;
2.执行跳转指令,如B、BX、BL、BLX指令,程序可以跳转到特定的地址标号处执
3.异常中断发生时,程序跳转到中断程序中执行,在处理完中断以后,程序返回到发生中断的指令的下一条指令处执行,在进出中断时要保存现场。
ARM体系中异常中断的种类
ARM体系结构所支持的异常及具体含义如表所示。
ARM体系结构所支持的异常
异常类型 |
具体含义 |
复位(Reset) |
当处理器的复位电平有效时,产生复位异常,程序跳转到复位异常处理程序处执行。复位异常中断通常用在下面两种情况:系统加电和系统复位时。 |
未定义指令 (undefined instruction) |
当ARM处理器或协处理器遇到不能处理的指令时,产生未定义指令异常。可使用该异常机制进行软件仿真。 |
软件中断 (softwart interrupt SWI) |
由用户定义,该异常由执行SWI指令产生,可用于用户模式下的程序调用特权操作指令。在实时操作系统中可使用该异常机制实现系统功能调用。 |
指令预取中止 (Prefech Abort) |
若处理器预取指令的地址不存在,或该地址不允许当前指令访问,存储器会向处理器发出中止信号,但当预取的指令被执行时,才会产生指令预取中止异常。 |
数据访问中止 (Data Abort) |
若处理器数据访问指令的地址不存在,或该地址不允许当前指令访问时,产生数据中止异常。 |
IRQ(外部中断请求) |
当处理器的外部中断请求引脚有效,且CPSR中的I位为0时,产生IRQ异常。系统的外设可通过该异常请求中断服务。 |
FIQ(快速中断请求) |
当处理器的快速中断请求引脚有效,且CPSR中的F位为0时,产生FIQ异常。 |
当系统进入异常时,转而执行不同向量地址的指令,如下表所示:
异常向量表
地址 |
异常 |
进入模式 |
0x0000,0000 |
复位 |
管理模式 |
0x0000,0004 |
未定义指令 |
未定义模式 |
0x0000,0008 |
软件中断 |
管理模式 |
0x0000,000C |
中止(预取指令) |
中止模式 |
0x0000,0010 |
中止(数据) |
中止模式 |
0x0000,0014 |
保留 |
保留 |
0x0000,0018 |
IRQ |
IRQ |
0x0000,001C |
FIQ |
FIQ |
当多个异常同时发生时,系统根据固定的优先级决定异常的处理次序。异常优先级由高到低的排列次序如表所示。
异常优先级
优先级 |
异 常 |
1(最高) |
复位 |
2 |
数据中止 |
3 |
FIQ |
4 |
IRQ |
5 |
预取指令中止 |
6(最低) |
未定义指令、SWI |
当一个异常出现以后,ARM微处理器会执行以下几步操作:
1、将下一条指令的地址存入相应连接寄存器LR,以便程序在处理异常返回时能从正确的位置重新开始执行。若异常是从ARM状态进入,LR寄存器中保存的是下一条指令的地址(当前PC+4或PC+8,与异常的类型有关);若异常是从Thumb状态进入,则在LR寄存器中保存当前PC的偏移量,这样,异常处理程序就不需要确定异常是从何种状态进入的。例如:在软件中断异常SWI,指令 MOV PC,R14_svc总是返回到下一条指令,不管SWI是在ARM状态执行,还是在Thumb状态执行。
2、将CPSR复制到相应的SPSR中。
3、根据异常类型,强制设置CPSR的运行模式位。
4、强制PC从相关的异常向量地址取下一条指令执行,从而跳转到相应的异常处理程序处。
还可以设置中断禁止位,以禁止中断发生。
如果异常发生时,处理器处于Thumb状态,则当异常向量地址加载入PC时,处理器自动切换到ARM状态。
ARM微处理器对异常的响应过程用伪码可以描述为:
R14_<Exception_Mode> = Return Link
SPSR_<Exception_Mode> = CPSR
CPSR[4:0] = Exception Mode Number
CPSR[5] = 0 ;当运行于ARM工作状态时
If <Exception_Mode> == Reset or FIQ then
;当响应FIQ异常时,禁止新的FIQ异常
CPSR[6] = 1
CPSR[7] = 1
PC = Exception Vector Address
从异常返回
异常处理完毕之后,ARM微处理器会执行以下几步操作从异常返回:
1、将连接寄存器LR的值减去相应的偏移量后送到PC中。
2、将SPSR复制回CPSR中。
3、若在进入异常处理时设置了中断禁止位,要在此清除。
可以认为应用程序总是从复位异常处理程序开始执行的,因此复位异常处理程序不需要返回。如下表所示,表中总结了进入异常处理时保存在相应R14中的PC值,及在退出异常处理时推荐使用的指令。
表2-4 异常进入/退出
|
返回指令 |
以前的状态 |
注意 |
|
ARM R14_x |
Thumb R14_x |
|||
BL |
MOV PC,R14 |
PC+4 |
PC+2 |
1 |
SWI |
MOVS PC,R14_svc |
PC+4 |
PC+2 |
1 |
UDEF |
MOVS PC,R14_und |
PC+4 |
PC+2 |
1 |
FIQ |
SUBS PC,R14_fiq,#4 |
PC+4 |
PC+4 |
2 |
IRQ |
SUBS PC,R14_irq,#4 |
PC+4 |
PC+4 |
2 |
PABT |
SUBS PC,R14_abt,#4 |
PC+4 |
PC+4 |
1 |
DABT |
SUBS PC,R14_abt,#8 |
PC+8 |
PC+8 |
3 |
RESET |
NA |
- |
- |
4 |
注意:
1、在此PC应是具有预取中止的BL/SWI/未定义指令所取的地址。
2、在此PC是从FIQ或IRQ取得不能执行的指令的地址。
3、在此PC是产生数据中止的加载或存储指令的地址。
4、系统复位时,保存在R14_svc中的值是不可预知的。
在应用程序中安装异常中断处理程序:
1)地址0x0处为ROM的情况,当地址0x0处为ROM时,在异常中断向量表中,可以使用数据读取指令LDR直接向程序计数器PC中赋值
INT_Vectors
LDR pc,INT_Table ;0x0000
LDR pc,(INT_Table + 4) ;0x0004
LDR pc,(INT_Table + 8) ;0x0008
LDR pc,(INT_Table + 12) ;0x000C
LDR pc,(INT_Table + 16) ;0x0010
LDR pc,(INT_Table + 20) ;0x0014
LDR pc,(INT_Table + 24) ;0x0018
LDR pc,(INT_Table + 28) ;0x001C
EXPORT INT_Table
INT_Table
INT_Initialize_Addr DCD INT_Initialize
Undef_Instr_Addr DCD Undef_Instr_ISR
SWI_Addr DCD SWI_ISR
Prefetch_Abort_Addr DCD Prefetch_Abort_ISR
Data_Abort_Addr DCD Data_Abort_ISR
Undefined_Addr DCD 0 ; NO LONGER USED
IRQ_Handler_Addr DCD INT_IRQ_Parse
FIQ_Handler_Addr DCD INT_FIQ_Parse
2)地址0x0处为RAM的情况,中断向量表还必须把中断向量表从ROM中复制到RAM的地址0x0开始的存储空间,如以下代码:
MOV v5,#0
ADR v6,INT_Vectors
LDMIA v6!,{a1-v4} ;复制中断向量表(8 words)
STMIA v5!,{a1-v4}
LDMIA v6!,{a1-v4} ;复制保存各中断处理函数地址。(8 words)
STMIA v5!,{a1-v4}
该ARM开发板支持多种功能,PCI接口、以太网口、音频设备、USB接口、LCD接口、外扩内存卡的功能,目前我们现在主要使用的功能是时钟(提供nucleus的系统时钟),UART串口(提供系统的输入与输出),PS/2键盘(提供系统的输入)。
ARM926EJ-S提供了两种中断信号:IRQ和FIQ,其实现的一般机制是,有一个向量中断控制器接收外部设备的中断信息,然后产生IRQ或者是FIQ中断信号,引导软件中断的执行。
开发板上的中断分为两组,主中断控制器和二级中断控制器,该两组没有主次之分,只是触发方式,即当中断发生后通知系统的顺序不一样而已。当有中断发生时,中断控制器状态位相应位置1,该位表示某个中断发生。
开发板上主要的寄存器如下表所示:
ADDRESS |
Name |
Access |
Description |
0x10140000 |
PICIRQStatus |
READ |
IRQ status register |
0x10140010 |
PICIntEnable |
READ/WRITE |
Interrupt enable register |
0x10140014 |
PICIntEnClear |
WRITE |
Interrupt enable clear register |
在PICIRQStatus中,常用的中断状态位如下所示(如相应为置1则表示对应中断发生,应该调用相应的中断处理函数):
15 |
14 |
13 |
12 |
11 |
10 |
9 |
8 |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
SCI0 |
UART2 |
UART1 |
UART0 |
SSP |
RTC |
GPIO3 |
GPIO2 |
GPIO1 |
GPIO0 |
TIMER23 |
TIMER01 |
CommTX |
CommRX |
SoftWare |
WDOG |
在当前的系统中,我们用到了时钟中断,串口中断,还有在二级中断中的键盘中断(上表中暂未列出)。
nucleus系统是实时嵌入式操作系统,具有占先、实时、多任务的内核,其中95%的代码是由C语言组成的,极易移植,广泛的应用在多种处理器当中;同时,nucleus开发包是开放源码的,方便开发工程师的配置,再加上nucleus的快速响应等其他特性,得到了广泛的应用。
1. 最先执行的函数是INT_INITIALIZE,在该函数中负责完成与目标开发板对应的所需要的硬件的初始化,如一些ARM上的控制寄存器,中断向量表,系统堆栈等;该函数一般在INT.S或者是INT_PID.S中;
2. 在INT_INITIALIZE执行完后,系统转入到nucleus上层的初始化中去,将控制权交给上层的函数INC_INITIALIZE,在该函数中完成nucleus各组件的初步的初始化工作,如线程、邮箱、管道、信号量、事件、定时器等,它的初始化只是给一些全局变量赋空值;
3. 在INC_INITIALIZE中,在各组件初始化完之后,会调用一个用户自定义函数Application_Initialize(),在该函数中系统会调用用户真正的应用代码,如内存池的创建、建立任务、建立消息队列等等。总之,Application_Initialize()中的函数是用户真正需要建立的代码;
4. 在执行完Application_Initialize()之后,系统进入调度函数TCT_Schedule(),然后整个nucleus系统就运转起来。
如图所示:
INT_INITIALIZE |
INC_INITIALIZE |
各组件初始化 |
Application_Initialize() |
TCT_Schedule() |
系统执行 |
针对我们的开发板,由于INT_INITIALIZE是系统运行的第一个函数,因此,在设置中,当系统reset后,此时执行地址为0x0000 0000中的代码,该地址中的代码跳转到函数INT_INITIALIZE()的地址,然后引导系统的运行。
INT_INITIALIZE() 函数主要执行的功能:
1) 将编译出来的已经初始化的数据从ROM中复制到RAM中,同时在RAM中建立ZI数据段,未初始化的数据;
2) 初始化异常中断向量表;
3) 设置ARM处理器对应的各种模式的nucleus的数据堆栈;
4) 设置对应的ARM开发板的硬件设置,在我们的需求中主要是一些中断的设置;
5) 将控制权转移到INC_INITIALIZE;
Nucleus在我们的ARM开发板上能够运行起来,首先要使nucleus的硬件相关部分能够与开发板相互对应,由于现有的nucleus版本已经在ADS1.2环境下编译通过,所以就不用费力的更改汇编函数,只需要修改涉及到具体ARM板的寄存器参数的设置,其中主要的是操作系统运行所需要的时钟中断的设置。
由第二部分的表格可知,时钟中断在PICIRQStatus中的第四个bit置位,当ARM开发板上的时钟中断发生时,产生IRQ中断,相应位置位。
在具体的设置当中,使用了TIMER0产生的时钟中断,其基地址是0x101e2000,其偏移地址0x08(TimerControl)是时钟控制寄存器,在其中设置相应的模式,如允许时钟运行模式,将时钟设
31:8 |
Bit 7 |
Bit 6 |
Bit 5 |
Bit 4 |
Bit 3:2 |
Bit 1 |
Bit 0 |
Reserved |
TimerEn |
TimerMode |
IntEnable |
reserved |
TimerPre |
TimerSize |
OneShot |
|
0- Disable 1- En |
0- freerun 1- periodic |
0- disa 1- IntEn |
|
分频 |
0- 16bit 1- 32bit |
0- wrap 1- oneshot |
置成周期性时钟模式,允许时钟中断模式等。完成设置后,运行,当有时钟中断发生时,ARM产生IRQ中断,通过状态位判断后,nucleus调用相应的时钟中断函数进行处理,维持nucleus系统的时钟相关性功能的运行。
在nucleus系统初始化时,可参看文件int.s或者是int_pid.s,首先完成中断向量的配置,在IRQ中断处理过程中,由于IRQ中断有多个中断源,需要处理各种子程序,通过判断PICIRQStatus中断标志位后,确定具体的中断源,在调用相应的中断处理函数。以上是一般的中断处理流程。
Nucleus提供了两种中断机制,管理的中断和非管理的中断。
管理的中断需要向操作系统注册该中断向量,它与下文提到的非管理中断的主要区别是,该种中断的上下文保护由nucleus系统来完成保护;中断产生后通过该中断向量注册的低级中断服务程序(LISR)激活高级的中断服务程序(HISR)。LISR主要完成硬件的中断的处理,及激活HISR。HISR的调度类似于任务,具有优先级,优先级比一般的任务要高,可以使用大多数的nucleus系统调用;
非管理的中断,该中断函数的地址直接的被放置到系统的中断向量表上,上下文的保存和恢复都需要用户自己来做,该种中断一般不能嵌套,否则可能会引起堆栈的出错,而且该种中断函数可以使用的nucleus系统调用比较少。
使用管理的中断的系统函数如下所示:
NU_Register_LISR(INT vector, //中断向量
VOID (*new_lisr)(INT), //低级中断处理函数
VOID (**old_lisr)(INT)); //保存的中断处理函数
NU_Create_HISR(NU_HISR *hisr, CHAR *name, //创建高级中断处理函数
VOID (*hisr_entry)(VOID), OPTION priority,
VOID *stack_address, UNSIGNED stack_size)
NU_Activate_HISR(NU_HISR *hisr) //由低级中断激活高级中断。
TCC_Dispatch_LISR(INT vector) //该函数应用在int.s或者int_pid.s中,负责根据中断向量号,索引到不同的中断函数执行。
举个例子:
VOID (*old_lisr)(INT);
void Lisr_uart0(INT vector_number);
void Hisr_uart0_proc(void);
NU_HISR Hisr_uart0;
NU_Create_HISR(&Hisr_uart0,"HISR_TEST",Hisr_uart0_proc,2,pointer,1000); //高级中断函数
NU_Register_LISR(21,Lisr_uart0,&old_lisr);//登记低级中断函数
void Lisr_uart0(INT vector_number) //登记的低级中断函数
{
unsigned int base;
//////////////////////////////////////////
base = UART0_BASE;
uart_data = HW_REG(base,0x00);
l++;
/////////////////////////////////////////
NU_Activate_HISR(&Hisr_uart0); //激活高级中断函数
}
void Hisr_uart0_proc(void) //高级中断函数
{
……
;
……
}
除了以上常用的函数外,用户还需要自己定义低级中断函数和高级中断函数。