SoftwareInterrupt
LDR SP, StackSvc ; 重新设置堆栈指针
STMFD SP!, {R0-R3, R12, LR}
MRS R3, SPSR ; 保存状态
STMFD SP!, {R3}
TST R3, #T_bit ; 中断前是否是Thumb状态
LDRNEH R0, [LR,#-2] ; 是: 取得Thumb状态SWI号
BICNE R0, R0, #0xff00
LDREQ R0, [LR,#-4] ; 否: 取得arm状态SWI号
BICEQ R0, R0, #0xFF000000
; r0 = SWI号,R1指向参数存储位置
CMP R0, #1
LDRLO PC, =OSCtxSw
LDREQ PC, =__OSStartHighRdy ; SWI 0x01为第一次任务切换;
LDMFD SP!, {R3} ; MRS R3, SPSR huifu 状态
MSR SPSR_cxsf, R3
BL SWI_Exception
LDMFD SP!, {R0-R3, R12, PC}^
;**************************************************************************
OSCtxSw LDR R3, [SP, #24] ;获取PC
LDR R1, [SP, #20] ;获取R12
MRS R0, CPSR
MSR CPSR_c, #(NoInt | SYS32Mode)
MOV R2,LR
STMFD SP!,{R1-R3} ; R12, LR,PC
STMFD SP!,{R4-R11} ;R4-R11
MSR CPSR_c,R0
LDMFD SP!, {R3} ; SPSR huifu 状态
LDMFD SP!, {R4-R7} ;R0-R3
ADD SP,SP,#8
LDR R2, =OsEnterSum
LDR R2,[R2]
MSR CPSR_c, #(NoInt | SYS32Mode)
STMFD SP!,{R2-R7} ; OsEnterSum,SPSR, R0-R3
LDR R0, =OSTCBCur
LDR R0, [R0]
STR SP, [R0]
BL OSTaskSwHook ;调用钩子函数
;OSPrioCur <= OSPrioHighRdy
LDR R4, =OSPrioCur
LDR R5, =OSPrioHighRdy
LDRB R6, [R5]
STRB R6, [R4]
;OSTCBCur <= OSTCBHighRdy
LDR R6, =OSTCBHighRdy
LDR R6, [R6]
LDR R4, =OSTCBCur
STR R6, [R4]
OSIntCtxSw_1
;获取新任务堆栈指针
LDR R4, [R6]
ADD SP, R4, #68 ;17寄存器OsEnterSum,CPSR,R0-R12,LR,PC
LDR LR, [SP, #-8]
MSR CPSR_c, #(NoInt | SVC32Mode) ;进入管理模式
MOV SP, R4 ;设置堆栈指针
LDMFD SP!, {R4, R5} ;CPSR,OsEnterSum
;恢复新任务的OsEnterSum
LDR R3, =OsEnterSum
STR R4, [R3]
MSR SPSR_cxsf, R5 ;恢复CPSR
LDMFD SP!, {R0-R12,LR, PC }^ ;运行新任务
**************************************************************************
OSIntCtxSw
ADD SP,SP,#8 ;调整STACK
LDR R3, [SP, #24] ;获取PC
LDR R1, [SP, #20] ;获取R12
MRS R0, CPSR
MSR CPSR_c, #(NoInt | SYS32Mode)
MOV R2,LR
STMFD SP!,{R1-R3} ; R12, LR,PC
STMFD SP!,{R4-R11} ;R4-R11
MSR CPSR_c,R0
LDMFD SP!, {R3} ; SPSR huifu 状态
LDMFD SP!, {R4-R7} ;R0-R3
ADD SP,SP,#8
MSR CPSR_c, #(NoInt | SYS32Mode)
LDR R2, =OsEnterSum ;OsEnterSum=0, SAVE BACK
MOV R1,#0
STR R1,[R2]
LDR R2,[R2]
STMFD SP!,{R2-R7} ; OsEnterSum,SPSR, R0-R3
LDR R0, =OSTCBCur
LDR R0, [R0]
STR SP, [R0]
BL OSTaskSwHook ;调用钩子函数
;OSPrioCur <= OSPrioHighRdy
LDR R4, =OSPrioCur
LDR R5, =OSPrioHighRdy
LDRB R6, [R5]
STRB R6, [R4]
;OSTCBCur <= OSTCBHighRdy
LDR R6, =OSTCBHighRdy
LDR R6, [R6]
LDR R4, =OSTCBCur
STR R6, [R4]
;获取新任务堆栈指针
LDR R4, [R6]
ADD SP, R4, #68 ;17寄存器OsEnterSum,CPSR,R0-R12,LR,PC
LDR LR, [SP, #-8]
MSR CPSR_c, #(NoInt | SVC32Mode) ;进入管理模式 NO IRQ
MOV SP, R4 ;设置堆栈指针
LDMFD SP!, {R4, R5} ;CPSR,OsEnterSum
LDR R3, =OsEnterSum ;恢复新任务的OsEnterSum
STR R4, [R3]
MSR SPSR_cxsf, R5 ;恢复CPSR
LDMFD SP!, {R0-R12,LR, PC }^ ;运行新任务
;************************************************************************/
__OSStartHighRdy
MSR CPSR_c, #(NoInt | SYS32Mode)
;告诉uC/OS-II自身已经运行
LDR R4, =OSRunning
MOV R5, #1
STRB R5, [R4]
BL OSTaskSwHook ;调用钩子函数
LDR R6, =OSTCBHighRdy ;指针数组中的元素,指向一个 OS_TCB
LDR R6, [R6] ;OSTCBHighRdy 处的首地址 (OS_TCB 结构体中的第一个元素:堆栈指针, OSSTK *OSTCBStkPtr) dy
B OSIntCtxSw_1 ;R6 now is 堆栈首址
**************************************************************************
IRQHandler
SUB LR, LR, #4 ; 计算返回地址
STMFD SP!, {R0-R3, R12, LR} ; 保存任务环境
MRS R3, SPSR ; 保存状态
STMFD SP!, {R3}
LDR R3, =OSIntNesting
LDRB R1, [R3]
ADD R1, R1, #1
STRB R1, [R3] ;;SAVE BACK
BL interrupts ; 调用c语言的中断处理程序
BL OSIntExit
LDMFD SP!, {R3} ; MRS R3, SPSR huifu 状态
MSR SPSR_cxsf, R3
LDMFD SP!, {R0-R3, R12, PC}^ ; 不进行任务切换/b/b/b
.globl _start
;系统复位位置
_start: b reset
;各个异常向量对应的跳转代码
ldr pc, _undefined_instruction
;未定义的指令异常
ldr pc, _software_interrupt
;软件中断异常
ldr pc, _prefetch_abort
;内存操作异常
ldr pc, _data_abort
;数据异常
ldr pc, _not_used
;未使用
ldr pc, _irq
;慢速中断异常
ldr pc, _fiq
;快速中断异常
|
***************************************************************
程序状态寄存器
***************************************************************
CPSR(当前程序状态寄存器)在任何处理器模式下被访问。它包含了条件标志位、中断禁止位、当前处理器模式标志以及其他的一些控制和状态位。每一种处理器
模式下都有一个专用的物理状态寄存器,称为SPSR(备份程序状态寄存器)
。当特定的异常中断发生时,这个寄存器用于存放当前程序状态寄存器的内容。在异常中断退出时,可以用SPSR来恢复CPSR。由于用户模式和系统模式不是异常
中断模式,所以他没有SPSR。当用户在用户模式或系统模式访问SPSR,将产生不可预知的后果。
CPSR格式如下所示。SPSR和CPSR格式相同。
31 30 29 28 27 26 7 6 5 4 3 2 1 0
N Z C V Q DNM(RAZ) I F T M4 M3 M2 M1 M0
***条件标志位***
N——本位设置成当前指令运算结果的bit[31]的值。当两个表示的有符号整数运算时,n=1表示运算结果为负数,n=0表示结果为正书或零。
z——z=1表示运算的结果为零;z=0表示运算的结果不为零。对于CMP指令,Z=1表示进行比较的两个数大小相等。
C——下面分四种情况讨论C的设置方法:
在加法指令中(包括比较指令CMP),当结果产生了进位,则C=1,表示无符号运算发生上溢出;其他情况C=0。
在减法指令中(包括减法指令CMP),当运算中发生错位,则C=0,表示无符号运算数发生下溢出;其他情况下C=1。
对于包含移位操作的非加碱运算指令,C中包含最后一次溢出的的位的数值
对于其他非加减运算指令,C位的值通常不受影响
V——对于加减运算指令,当操作数和运算结果为二进制的补码表示的带符号数时,V=1表示符号为溢出;通常其他指令不影响V位。
***Q标识位***
在ARM V5的E系列处理器中,CPSR的bit[27]称为q标识位,主要用于指示增强的dsp指令是否发生了溢出。同样的spsr的bit[27]位也称为q标识位,用于在异常中
断发生时保存和恢复CPSR中的Q标识位。
在ARM V5以前的版本及ARM V5的非E系列的处理器中,Q标识位没有被定义。
***CPSR中的控制位***
CPSR的低八位I、F、T、M[4:0]统称为控制位。当异常中断发生时这些位发生变化。在特权级的处理器模式下,软件可以修改这些控制位。
**中断禁止位:当I=1时禁止IRQ中断,当F=1时禁止FIQ中断
**T控制位:T控制位用于控制指令执行的状态,即说明本指令是ARM指令还是Thumb指令。对于ARM V4以更高版本的T系列ARM处理器,T控制位含义如下:
T=0表示执行ARM指令
T=1表示执行Thumb指令
对于ARM V5以及更高版本的非T系列处理器,T控制位的含义如下
T=0表示执行ARM指令
T=1表示强制下一条执行的指令产生未定指令中断
***M控制位***
M控制位控制处理器模式,具体含义如下:
M[4:0] 处理器模式 可访问的寄存器
ob10000 user pc,r14~r0,CPSR
0b10001 FIQ PC,R14_FIQ-R8_FIQ,R7~R0,CPSR,SPSR_FIQ
0b10010 IRQ PC,R14_IRQ-R13_IRQ,R12~R0,CPSR,SPSR_IRQ
0B10011 SUPERVISOR PC,R14_SVC-R13_SVC,R12~R0,CPSR,SPSR_SVC
0b10111 ABORT PC,R14_ABT-R13_ABT,R12~R0,CPSR,SPSR_ABT
0b11011 UNDEFINEED PC,R14_UND-R8_UND,R12~R0,CPSR,SPSR_UND
0b11111 SYSTEM PC,R14-R0,CPSR(ARM V4以及更高版本)
***CPSR中的其他位***
这些位用于将来扩展。应用软件不要操作这些位。
在ARM体系中通常有以下3种方式控制程序的执行流程:
**在正常执行过程中,每执行一条ARM指令,程序计数器(PC)的值加4个字节;每执行一条Thumb指令,程序计数器寄存器(PC)加2个字节。整个过程是按顺序执行
。
**跳转指令,程序可以跳转到特定的地址标号处执行,或者跳转到特定的子程序处执行。其中,B指令用于执行跳转操作;BL指令在执行跳转操作同时,保存子程
序的返回地址;BX指令在执行跳转操作同时,根据目标地址为可以将程序切换到Thumb状态;BLX指令执行3个操作,跳转到目标地址处执行,保存子程序的返回
地址,根据目标地址为可以将程序切换到Thumb状态。
**当异常中断发生时,系统执行完当前指令后,将跳转到相应的异常中断处理程序处执行。当异常中断处理程序执行完成后,程序返回到发生中断指令的下条指
令处执行。在进入异常中断处理程序时,要保存被中断程序的执行现场
ARM中异常中断的种类
**复位(RESET)**
当处理器复位引脚有效时,系统产生复位异常中断,程序跳转到复位异常中断处理程序处执行。复位异常中断通常用在下面几种情况下:系统加电时;系统复位时;跳转到复位中断向量处执行成为软复位。
**未定义的指令**
当ARM处理器或者是系统中的协处理器认为当前指令未定义时,产生未定义的指令异常中断,可以通过改异常中断机制仿真浮点向量运算。
**软件中断**
这是一个由用户定义的中断指令。可用于用户模式下的程序调用特权操作指令。在实时操作系统中可以通过该机制西线系统功能调用。
**指令与取终止(PrefechAbort)**
如果处理器预取的指令的地址不存在,或者该地址不允许当前指令访问,当被预取的指令执行时,处理器产生指令预取终止异常中断。
**数据访问终止(DATAABORT)
如果数据访问指令的目标地址不存在,,或者该地址不允许当前指令访问,处理器产生数据访问终止异常中断
**外部中断请求(IRQ)**
当处理器的外部中断请求引脚有效,而且CPSR的寄存器的I控制位被清除时,处理器产生外部中断请求异常中断。系统中个外设通过该异常中断请求处理服务。
**快速中断请求(FIQ)**
当处理器的外部快速中断请求引脚有效,而且CPSR的F控制位被清除时,处理器产生外部中断请求异常中断
异常中断向量表及异常中断优先级
中断向量表指定了个异常中断及其处理程序的对应关系。他通常存放在存储地址的低端。在ARM体系中,异常中断向量表的大小为32字节,其中每个异常中断占据4个字节大小,保留了4个字节空间。
每个异常中断对应的中断向量表中的4个字节的空间中存放了一个跳转指令或者一个向PC寄存器中赋值的数据访问指令。通过这两种指令,程序将跳转到相应的异常中断处理程序处执行。当几个异常中断同时发生时,就必须按照一定的次序来处理这些异常中断。
各个异常中断的中断向量地址以及中断的处理优先级
中断向量地址 异常中断类型 异常中断模式 优先级(6最低)
0x00 复位 特权模式 1
0x04 未定义的指令 未定义指令终止模式 6
0x08 软件中断 特权模式 6
0x0C 指令预取终止 终止模式 5
0x10 数据访问终止 终止模式 2
0x14 保留 未使用 未使用
0x18 外部中断请求 IRQ模式 4
0x1C 快速中断请求 FIQ模式 3
**在系统复位时安装异常中断处理程序**
1.地址0x00处为ROM的情况
使用数据读取指令LDR示例如下所示:
Vector_Init_Block
LDR PC, Reset_Addr
LDR PC, Undefined_Addr
LDR PC, SW_Addr
LDR PC, Prefeth_Addr
LDR PC, Abort_Addr
NOP
LDR PC, IRQ_Addr
LDR PC, FIQ_Addr
Reset_Addr DCD Start_Boot
Undefined_Addr DCD Undefined_Handle
SW_Addr DCD SWI_Handle
Prefeth_Addr DCD Prefeth_Handle
Abort_Addr DCD Abort_Handle
DCD 0
IRQ_Addr DCD IRQ_Handle
FIQ_Addr DCD FIQ_Handle
使用跳转指令的示例如下所示:
Vector_Init_Block
BL Reset_Handle
BL DCD Undefined_Handle
BL SWI_Handle
BL Prefeth_Handle
BL Abort_Handle
NOP
BL IRQ_Handle
BL FIQ_Handle
2.地址0x00处为RAM的情况
地址0x00处为RAM时,中断向量表必须使用数据读取指令直接指向PC中赋值的形式。而且,必须使用下面的代码巴中断向量表从ROM中复制到RAM中地址0x00开始处的存储空间中:
MOV r8,#0
ADR r9,Vector_Init_Block
;复制中断向量表(8字)
LDMIA r9!,(r0-r7)
STMIA r8!,(r0-r7)
;复制保存各中断处理函数地址的表(8字words)
LDMIA r9!,(r0-r7)
STMIA r8!,(r0-r7)
/b
/b
/b
/b
*********************************************
ARM存储系统概述
*********************************************
ARM存储系统的体系结构适应不同的嵌入式应用系统的需要差别很大。最简单的存储系统使用平办事的地址映射机制,就像一些简单的弹片机系统中一样,地址空间的分配方式是固定的,系统各部分都使用物理地址。而一些复杂系统可能包括下面的一种或几种技术,从而提供更为强大的存储系统。
**系统中可能包含多种类型的存储器,如FLASH,ROM,RAM,EEPROM等,不同类型的存储器的速度和宽度等各不相同。
**通过使用CACHE及WRITE BUFFER技术缩小处理器和存储系统速度差别,从而提高系统的整体性能。
**内存管理部件通过内存映射技术实现虚拟空间到物理空间的映射。在系统加电时,将ROM/FLASH影射为地址0,这样可以进行一些初始化处理;当这些初始化完成后将RAM地址影射为0,并把系统程序加载到RAM中运行,这样很好地解决了嵌入式系统的需要。
**引入存储保护机制,增强系统的安全性。
**引入一些机制保证I/O操作应设成内存操作后,各种I/O操作能够得到正确的结果。
**与存储系统相关的程序设计指南**
本节从外部来看ARM存储系统,及ARM存储系统提供的对外接口。本节介绍用户通过这些接口来访问ARM存储系统时需要遵守的规则。
1.地址空间
ARM体系使用单一的和平板地址空间。该地址空间大小为2^32个8位字节,这些字节的单元地址是一个无符号的32位数值,其取值范围为0~2^32-1。ARM地址空间也可以看作是2^30个32位的字单元。这些字单元的地址可以被4整除,也就是说该地址低两位为0b00。地址为A的字数据包括地址为A、A+1、A+3、A+3 4个字节单元的内容。
各存储单元的地址作为32为无符号数,可以进行常规的整数运算。这些运算的结果进行2^32取模。
程序正常执行时,每执行一条ARM指令,当前指令计数器加4个字节;每执行一条Thumb指令,当前指令计数器加2个字节。但是,当地址上发生溢出时,执行结果将是不可预知的。
2.存储器格式
在ARM中,如果地址A是字对齐的,有下面几种:
**地址为A的字单元包括字节单元A,A+1,A+2,A+3。
**地址为A的班子单元包括字节单元A,A+1。
**地址为A+2的半字单元包括字节单元A+2,A=3.
**地址为A的字单元包括半字节单元A,A+2。
在big-endian格式中,对于地址为a的字单元其中字节单元由高位到低位字节顺序为A,A+1,A=2,A+3;这种存储器格式如下所示:
31 24 23 16 15 8 7 0
--------------------------------------------------------------------
字单元A |
--------------------------------------------------------------------
半字单元A | 半字单元A+2 |
--------------------------------------------------------------------
字节单元A | 字节单元A+1 | 字节单元A+2 | 字节单元A+3|
--------------------------------------------------------------------
在little-endian格式中,对于地址为A的字单元由高位到低位字节顺序为A+3,A+2,A+1,A,这种存储格式如下所示
31 24 23 16 15 8 7 0
--------------------------------------------------------------------
字单元A |
--------------------------------------------------------------------
半字单元A+2 | 半字单元A |
--------------------------------------------------------------------
字节单元A+3 |字节单元A+2 | 字节单元A+1 | 字节单元A |
--------------------------------------------------------------------
在ARM系统中没有提供指令来选择存储器格式。如果系统中包含标准的ARM控制协处理器CP15,则CP15的寄存器C1的位[7]决定系统中存储器的格式。当系统复位时,寄存器C1的[7]值为零,这时系统中存储器格式为little-endian格式。如果系统中采用的是big-endian格式,则复位异常中断处理程序中必须设置c1寄存器的[7]位。
3.非对齐的存储访问操作
非对齐:位于arm状态期间,低二位不为0b00;位于Thumb状态期间,最低位不为0b0。
3.1非对齐的指令预取操作
如果系统中指定当发生非对齐的指令预取操作时,忽略地址中相应的位,则由存储系统实现这种忽略。
3.2非对齐的数据访问操作
对于LOAD/STORE操作,系统定义了下面3中可能的结果:
***执行结果不可预知
***忽略字单元地址低两位的值,即访问地址为字单元;忽略半字单元最低位的值,即访问地址为半字单元。
***由存储系统忽略字单元地址中低两位的值,半字单元地址最低位的值。
4.指令预取和自修改代码
当用户读取PC计数器的值时,返回的是当前指令下面的第二条指令的地址。对于ARM指令来说,返回当前指令地址值加8个字节;对于Thumb指令来说,返回值为当前指令地址值加4个字节。
自修改代码指的是代码在执行过程中修改自身。应尽量避免使用。
5.存储器映射的I/O空间
在ARM中,I/O操作通常被影射为存储器操作。通常需要将存储器映射的I/O空间设置成非缓冲的。
/b
/b
/b
/b
*************************************************************
ARM编译器支持的数据类型
************************************************************
数据类型 长度(位) 对齐特性
Char 8 1(字节对齐)
short 16 2(百字对齐)
Int 32 4(字对齐)
Long 32 4(字对齐)
Longlong 64 4(字对齐)
Float 32 4(字对齐)
Double 64 4(字对齐)
Long double 64 4(字对齐)
All pointers 32 4(字对齐)
Bool(C++ only) 32 4(字对齐)
1.整数类型
在ARM体系中,整数类型是以2的补码形式存储的。对于long long类型来说,在little endian内存模式下,其低32位保存在低地址的字单元中,高32为保存在高地址的字单元中;在big endian模式下,其低32位保存在高地址的字单元中,高32为保存在低地址的字单元中。对于整型数据的操作遵守下面的规则:
**所有带符号的整型书的运算是按照二进制的补码进行的。
**带符号的整型数的运算不进行符号的扩展。
**带符号的整型数的右移操作是算数移位。
**制定的移位位数的数是8位的无符号数。
**进行移位操作的数被作为32位数。
**超过31位的逻辑左移的结果为0。
**对于无符号数和有符号的正数来说,超过32位的右移操作结果为0;对于有符号的负数来说,超过32位的右移操作结果为-1。
**整数除法运算的余数和除数有相同的符号。
**当把一个整数截断成位数更短的整数类型的数时,并不能保证所得到的结果的最高位的符号位的正确性。
**整型数据之间的类型转换不会产生异常中断。
**整型数据的溢出不会产生异常中断。
**整型数据除以0将会产生异常中断。
2.浮点数
在ARM体系中,浮点数是按照IEEE标准存储的。
**float类型的数是按照IEEE的单精度数表示的。
**double和long double 是用IEEE的双精度数表示的。
对于浮点数的操作遵守下面的规则:
**遵守正常的IEEE754规则。
**当默认情况下禁止浮点数运算异常中断。
**当发生卷绕时,用最接近的数据来表示。
3.指针类型的数据
下面的规则适用于处数据成员指针以外的其他指针:
**NULL被定义为0。
**相邻的两个存储单元地址相差一。
**在指向函数的指针和指向数据的指针进行数据转换时,编译器将会产生警告信息。
**类型size_t被定义为unsigned int.
**类型ptrdiff_t被定义为signed int。
**两个指针类型的数据相减时,结果可以按照下面的公式得到。
((int)a-(int)b)/(int)sizeof(type pointed to)
这时,只要指针所指的对象不是pack的,其对齐特性能够满足整除的要求。
/b
/b
/b
/b
***************************************************** ARM编译器中预定义的宏 ***************************************************** _arm _ 使用编译器armcc,tcc,armcpp,tcpp时 _ARMCC_VERSION Ver 代表编译器版本号,其格式为: _APCS_INTERWORK _ 使用编译选项-apcs/interwork时 _APCS_ROPI _ 使用编译选项apcs/ropi时 _RWPI _ 使用编译选项-apcs/rwpi时 _APCS_SWST _ 使用编译选项-apcs/swst时 _BIG_ENDIAN _ 编译器针对目标系统使用big-endian内存模式时 _cplusplus _ 编译器工作与C++模式时 _CC_ARM _ 返回编译器的名称 _DATE_ date 编译源文件的日期 _embedded_cplusplus 编译器工作于EC++模式时 _FEATURE_SINGED_CHAE 使用编译设置选项-zc时设置该预定义宏 _FILE_ name 包含全路径的当前被编译的源文件名称 _func_ name 当前被编译的函数名称 _LINE_ num 当前被编译的代码行号名称 _MOUDLE_ mod 预定义宏_FILE_的文件名称部分 _OPTIMISE_SPACE _ 使用编译选项-OSPACE时 _OPTIMISE_TIME _ 使用编译选项-Otime时 _pretty_func name unmangled的当前函数名称 _sizeof_int 4 sizeof(int),在预处理表达式中可以使用 _sizeof_long 4 sizeof(long),在预处理表达式中可以使用 _sizeof_ptr 4 sizeof(void*)在预处理表达式中可以使用 _SOFTFP _ 编译时使用浮点数 _ _ 在各种编译器模式下 _STDC_VERSION _ 标准的版本信息 _STRICT_ANSI_ _ 使用编译选项-STRICT时 _TARGET_ARCH_xx _ xx代表ARM体系编号 _TARGET_CPU_xx _ xx代表CPU编号 _TARGET_FEATURE_ 当ARM体系支持指令PLD,LDRD,STRD,MCRR,MRRC时 _TARGET_FEATURE_ 当系统中包含DSP乘法处理器时,设置该 _TARGET_FEATURE_ 如果目标ARM体系支持半字访问以及有符号的字节数据 _TARGET_FEATURE_ 如果目标ARM体系支持长乘法指令MULL和 _TARGET_FEATURE_ 如果目标ARM体系支持THUMB指令 _TARGET_FPU_xx _ 表示FPU选项,可能取值如下所示: _thumb _ 编译器为tcc或tcpp时,设置该预定义宏 _TIME 源文件编译时间 |
/b
/b
/b
*****************************************************
ARM映像文件
*****************************************************
1.ELF格式文件的结构
1.1映像文件组成部分
**一个映像文件有一个或多个域组成
**每个域包含一个或多个输出段
**每个输出段包含一个或多个输入段
**各输入段中包含了目标文件中的代码和数据
输入段中包含了四类内容:代码、已经初始化的数据、未经初始化的存储区域、内容初始化成0的存储区域。每个输入段有相应的属性,可以为只读的(RO)、可读写的(RW)以及初始化成0的(ZI)。ARM连接器根据个输入段的属性将这些输入段分组,再组成不同的输出段及域。
一个输出段中包含了一系列的具有相同的RO、RW和ZI属性的输入段。输出段的属性与其中包含的输入段的属性相同。在一个输出段的内部,各输入段是按照一定的规则排序的,这将在1.3节油详细地介绍。
一个域中包含1-3个输出段,其中个输出段的属性各不相同。各输出段的排列顺序是由其属性决定的。其中RO属性的输出段排在最前面,其次是RW属性的输出段,最后是ZI属性的输出段。一个域通常映射到一个物理存储器上,如ROM或RAM。
1.2ARM映像文件各组成部分的地址影射
ARM映像文件各组成部分在存储系统中的地址有两种:一种是映像文件位于存储器中时(也就是该映像文件运行之前)的地址,称之为加载地址;一种是映像文件运行时的地址,称之为运行时地址。之所以有这两种地址,是因为映像文件在运行时,其中的有些域是可以移动的新的存储区域。比如,已经初始化的RW属性的数据所在的段运行之前可能保存系统的ROM中,在运行时,他被移动至RAM中。
通常,一个映像文件包含若干个域,各域又包含若干的输出段。ARM连接器需要知道如下的信息,已决定如何生成相应的映像文件。
**分组信息 决定如何将个输入段组织成相应的输出段和域。
**定位信息 决定个域在存储空间地址中的起始地址。
根据映像文件中地址映射的复杂程度,有两种方法来告诉arm连接器这些相关信息。对于映像文件中地址映射关系比较简单的情况,可以使用命令行选项;对于映像文件中地址映射关系比较复杂的情况,可以使用一个配置文件。
2.arm映像文件的入口点
2.1arm映像文件的入口点有两种类型:一种是映像文件运行时的入口点,称为初始入口点(initial entry point),另一种是普通入口点(entry point).
初始入口点是映像文件运行时的入口点,每个映像文件只有一个唯一的初始入口点,它保存在ELF头文件中。如果映像文件是被操作系统加载的,操作系统是通过跳转到该初始入口点处来加载该映像文件。
普通的入口点是在汇编中用ENTRY伪操作定义。他通常用于标志该段代码是通过异常中断处理程序进入的。这样连接器删除无用的段时不会将该段代码删除。一个映像文件中可以定义多个普通入口点。
应该注意的是,初始入口点可以使普通入口点,但也可以不是普通入口点。
2.2定义初始入口点
初始入口点必须满足下面两个条件:
**初始入口点必须位于映像文件的运行时域内。
**饱含初始入口点的运行时域不能被覆盖,他的加载地址和运行地址必须是相同的。
可以使用连接选项-entry address来指定映像文件的初始入口点。这时,address指定了映像文件的初始入口点的地址值。
对于地址0x0处为rom的嵌入式应用系统,可以使用-entry 0x0来指定映像文件的初始入口点。这样当系统复位后,自动跳转到该入口开始执行。
如果映像文件是被一个加载器加载的,该映像文件该映像文件必须包含一个初始化入口点。这种映像文件通常还包含了其他普通入口点,这些普通入口点一般为异常中断处理程序的入口地址。
当用户没有指定-entry address时,连接器根据下面的规则决定映像文件的初始入口点。
**如果输入的目标文件中只有一个普通入口点,该普通入口点被连接器当成映像文件的初始入口点。
**如果输入的目标文件中没有一个普通入口点,或者其中的普通入口点多于一个,则连接器生成的映像文件中不包含初始入口点,并产生警告信息。
2.3普通入口点的用法
普通入口点是在汇编中用ENTRY 伪操作定义。在嵌入式应用中,各异常中断的处理程序入口使用普通入口点标示。这样连接器在删除无用段时不会将该段代码删除。
一个映像文件中可以定义多个普通入口点。没有指定连接选项-entry addres时,如果输入的目标文件中只有一个普通入口点,该入口点被连接器当成映像文件的初始入口点。
/b
/b
/b
*****************************************************
ARM映像文件——2
*****************************************************
3输入段的排序规则
连接器根据输入段的属性来组织这些输入段,具有相同属性的输入段被放到域中一段连续的空间中,组成一个输出段。在一个输出段中,各输入段的起始地址与
输出段的起始地址和该输出段中个输入段的排列顺序有关。
通常情况下,一个输出段中个输入段的排列顺序由下面几个因素决定的。用户可以通过连接选项-first和-last来改变这些规则。
**输入段的属性。
**输入段的名称
**各输入段在连接命令行的输入段列表中的排列顺序
按照输入段的属性,其排列顺序如下所示:
**只读的代码段
**只读的数据段
**可读写的代码段
**其他已经初始化的数据段
**未出世化的数据
对于具有相同属性的输入段,按照其名称来排序。这是输入段名称是区分大小写的,按照其ASCII码顺序进行排序。
对于具有相同属性和名城的输入段,按照其在输入段列表中的顺序进行排序。也就是说,几十个输入段的属性和名称保持不变,如果其在编译时,各输入段在输
入段列表中的排列顺序不同,生成的映像文件也将不同。
可以通过连接选项-first和-last来改变这些规则。如果连接时使用了配置文件,可以在配置文件中通过伪属性FIRST和LAST达到相同的效果。
连接选项-first和-last不能改变根据输入段进行排序的规则,它只能改变根据输入段名称和其在输入列表中的顺序的排序规则。也就是说,如果使用-first指定
一个输入段,只有该输入段所在的输出段位于运行时域的开始位置时,该输入段才能位于整个运行时域的开始位置。
各个输入段排好顺序后,在确定各个输入段的起始地址之前,何以通过填充补丁是个输入段满足地址对齐的要求。
这些是很多书中第一章节的内容。都比较基础,用的会比较多。
第四章 移植μC/OS-Ⅱ
§4.1 LPC2106的介绍
ARM7TDMI-S是通用的32位微处理器,它具有高性能和低功耗的特性。ARM结构是基于精简指令集计算机(RISC)原理而设计的,指令集和相关的译码机制比复杂指令集计算机要简单得多,这样使用一个小的、廉价的处理器核就可实现很高的指令吞吐量和实时的中断响应。
由于使用了流水线技术,处理和存储系统的所有部分都可连续工作。通常在执行一条指令的同时对下一条指令进行译码,并将第三条指令从存储器中取出。
ARM7TDMI-S处理器使用了一个被称为Thumb的独特结构化策略,它非常适用于那些对存储器有限制或者需要较高代码密度的大批量产品的应用。
在Thumb后面一个关键的概念是“超精简指令集”。基本上,ARM7TDMI-S处理器具有两个指令集:
·标准32位ARM指令集;
·16位Thumb指令集。
Thumb指令集的16位指令长度使其可以达到标准ARM代码两倍的密度,却仍然保持ARM的大多数性能上的优势,这些优势是使用16位寄存器的16位处理器所不具备的。因为Thumb代码和ARM代码一样,在相同的32位寄存器上进行操作。
Thumb代码仅为ARM代码规模的65%,但其性能却相当于连接到16位存储器系统的相同ARM处理器性能的160%。
LPC2106带有一个支持实时仿真和跟踪的 ARM7TDMI-S CPU,并嵌入了128KB高速Flash存储器。LPC2106将 ARM7TDMI-S配置为小端(little-endian)字节顺序。128位宽度的存储器接口和独特的加速结构使32位代码能够在最大时钟频率下运行。
LPC2106主要的特征如下:
·128 KB片内 Flash程序存储器,具有ISP和IAP功能;
· Flash编程时间: 1ms可编程512字节,扇区擦除或整片擦除只需400 ms;
·64KB静态 RAM;
·向量中断控制器;
·仿真跟踪模块,支持实时跟踪;
·RealMonitor模块支持实时调试;
·标准 ARM测试/调试接口,兼容现有工具;
·极小封装:TQFP48(7 mm x 7 mm);
·双UART,其中一个带有完全的调制解调器接口;
·I2 C串行接口;
·SPI串行接口;
·两个定时器,分别具有4路捕获/比较通道;
·多达6路输出的PWM单元;
·实时时钟;
·看门狗定时器;
·通用I/O口;
·CPU操作频率可达60 MHZ;
·双电源:
-CPU操作电压范围:1.65~1.95 V,即 1.8(1±8.3%)V;
-I/O电压范围:3.0~3.6 V,即 3.3(1±10%)V。
·两个低功耗模式:空闲和掉电;
·通过外部中断将处理器从掉电模式中唤醒;
·外设功能可单独使能/禁止,实现功耗最优化;
·片内晶振的操作频率范围:10~25 MHZ;
·片内PLL允许CPU以最大速度运行,可以在超过整个晶振操作频率范围的情况使用。
§4.2 LPC2106的启动代码
启动代码是芯片复位后进入C语言的main()函数前执行的一段代码,主要是为运行C语言程序提供基本运行环境,如初始化存储器系统等。ARM公司只设计内核,不自己生产芯片,只是把内核授权给其它厂商,其它厂商购买了授权且加入自己的外设后生产出各具特色的芯片。这样就促进了基于ARM处理器核的芯片多元化,但也使得每一种芯片的启动代码差别很大,不易编写出统一的启动代码。ADS(针对ARM处理器核的C语言编译器)的策略是不提供完整的启动代码,启动代码不足部分或者由厂商提供,或者自己编写。启动代码划分为4个文件:Vectors.c、Init.s、Target.c、 Target.h。Vectors.c包含异常向量表、堆栈初始化及中断服务程序与C程序的接口。Init.s包含统初始化代码,并跳转到ADS提供的初始化代码。Target.c和 Target.h包含目标板特殊的代码,包括异常处理程序和目标板初始化程序。这样做的目的是为了尽量减少汇编代码,同时把不需要修改的代码独立出来以减少错误。
§4.2.1 Vectors.c文件的编写
§4.2.1.1 中断向量表
Vectors
LDR PC, ResetAddr
LDR PC, UndefinedAddr
LDR PC, SWI_Addr
LDR PC, PrefetchAddr
LDR PC, DataAbortAddr
DCD 0xb9205f80
LDR PC, [PC, #-0xff0]
LDR PC, FIQ_Addr
ResetAddr DCD Reset
UndefinedAddr DCD Undefined
SWI_Addr DCD SoftwareInterrupt
PrefetchAddr DCD PrefetchAbort
DataAbortAddr DCD DataAbort
nouse DCD 0
IRQ_Addr DCD IRQ_Handler
FIQ_Addr DCD FIQ_Handler
异常是由内部或外部源产生的以引起处理器处理的一个事件。ARM处理器核支持7种类型的异常。异常出现后,CPU强制从异常类型对应的固定存储地址开始执行程序。这个固定的地址就是异常向量。向量从上到下依次为复位、未定义指令异常、软件中断、预取指令中止、预取数据中止、保留的异常、IRQ和 FIQ。IRQ向量“LDR PC, [PC, #-0xff0]” 使用的指令与其它向量不同。在正常情况下这条指令所在地址为0X00000018。当CPU执行这条指令但还没有跳转时,PC的值为0X00000020,0X00000020减去 0X00000FF0为 0XFFFFF030,这是向量中断控制器(VIC)的特殊寄存器VICVectAddr。这个寄存器保存当前将要服务的IRQ的中断服务程序的入口,用这一条指令就可以直接跳转到需要的中断服务程序中。至于在保留的异常向量“DCD 0xb9205f80”位置填数据0xb9205f8是为了使向量表中所有的数据32位累加和为0。
§4.2.1.2 初始化CPU堆栈
InitStack
MOV R0, LR
MSR CPSR_c, #0xd2 ;设置中断模式堆栈
LDR SP, StackIrq
MSR CPSR_c, #0xd1 ;设置快速中断模式堆栈
LDR SP, StackFiq
MSR CPSR_c, #0xd7 ;设置中止模式堆栈
LDR SP, StackAbt
MSR CPSR_c, #0xdb ;设置未定义模式堆栈
LDR SP, StackUnd
MSR CPSR_c, #0xdf ;设置系统模式堆栈
LDR SP, StackSys
MOV PC, R0
StackIrq DCD (IrqStackSpace + IRQ_STACK_LEGTH * 4 - 4)
StackFiq DCD (FiqStackSpace + FIQ_STACK_LEGTH * 4 - 4)
StackAbt DCD (AbtStackSpace + ABT_STACK_LEGTH * 4 - 4)
StackUnd DCD (UndtStackSpace + UND_STACK_LEGTH * 4 - 4)
StackSys DCD (SysStackSpace + SYS_STACK_LEGTH * 4 -4 )
;/* 分配堆栈空间 */
AREA MyStacks, DATA, NOINIT
IrqStackSpace SPACE IRQ_STACK_LEGTH * 4 ;中断模式堆栈FiqStackSpace SPACE FIQ_STACK_LEGTH * 4 ;快速中断模式堆栈
AbtStackSpace SPACE ABT_STACK_LEGTH * 4 ;中止义模式堆栈UndtStackSpace SPACE UND_STACK_LEGTH * 4 ;未定义模式堆栈
SysStackSpace SPACE SYS_STACK_LEGTH * 4 ; 系统模式堆栈
因为程序需要切换模式,而且程序退出时CPU的模式已经不再是管理模式而是系统模式LR已经不再保存返回程序地址,所以程序首先把返回地址保存到 R0中,同时使用R0返回。然后程序把处理器模式转化为IRQ模式,并设置IRQ模式的堆栈指针。其中变量Stacklrq保存着IRQ模式的堆栈指针的初始值,Irqstackspace是分配给 IRQ模式的堆栈空间的开始地址,IRQ_STACK_LEGTH是用户定义的常量,用于设置 IRQ模式的堆栈空间的大小。程序使用同样的方法设置FIQ模式堆栈指针、中止模式堆栈指针、未定义堆栈指针和系统模式堆栈指针。
程序使用编译器分配的空间作为堆栈,而不是按照通常的做法把堆栈分配到 RAM的顶端,之所以这样是因为这样做不必知道RAM顶端位置,移植更加方便;编译器给出的占用RAM空间的大小就是实际占用的大小,便于控制RAM的分配。对于 LPC2106来说,中止模式是不需要分配堆栈空间的,这是因为 LPC2106没有外部总线,也没有虚拟内存机制,如果出现取数据中止或取指令中止肯定是程序有问题。而一般情况下也不需要模拟协处理器指令或扩充指令,未定义中止也就意味着程序有错误,也不需要分配堆栈空间。
§4.2.1.3 异常处理代码与C语言的接口程序
μC/OS-Ⅱ中断服务子程序流程图如图4-1所示:
图4-1 中断服务子程序流程图
异常处理代码与C语言的接口程序如下:
MACRO
$IRQ_Label HANDLER $IRQ_Exception
EXPORT $IRQ_Label ;输出的标号
IMPORT $IRQ_Exception ;引用的外部标号
$IRQ_Label
SUB LR, LR, #4 ;计算返回地址
STMFD SP!, {R0-R3, R12, LR} ;保存任务环境
MRS R3, SPSR ;保存状态
STMFD SP!, {R3}
LDR R2, =OSIntNesting ;OSIntNesting++
LDRB R1, [R2]
ADD R1, R1, #1
STRB R1, [R2]
BL $IRQ_Exception ;调用c语言的中断处理程序
MSR CPSR_c, #0x92 ;关中断
BL OSIntExit
LDR R0, =OSTCBHighRdy
LDR R0, [R0]
LDR R1, =OSTCBCur
LDR R1, [R1]
CMP R0, R1
LDMFD SP!, {R3}
MSR SPSR_cxsf, R3
LDMEQFD SP!, {R0-R3, R12, PC}^ ;不进行任务切换
LDR PC, =OSIntCtxSw ;进行任务切换
MEND
Undefined ;未定义指令
b Undefined
PrefetchAbort ;取指令中止
b PrefetchAbort
DataAbort ;取数据中止
b DataAbort
IRQ_Handler HANDLER IRQ_Exception ;中断
FIQ_Handler ;快速中断
b FIQ_Handler
Timer0_Handler HANDLER Timer0 ;定时器0中断
未定义指令异常、取指令中止异常、取数据中止异常均是死循环,其中原因在上一小节已经说明。而快速中断在本应用中并未使用,所以也设置为死循环。LPC2106使用向量中断控制器,各个 IRQ中断的人口不一样,所以使用了一个宏来简化中断服务程序与C语言的接口编写。由ARM处理器核的文档可知,处理器进入IRQ中断服务程序时(LR-4)的值为中断返回地址,为了使任务无论在主动放弃CPU时还是中断时堆栈结构都一样,在这里先把LR减4。其它的部分与μC/OS-Ⅱ要求的基本一致。ARM处理核在进入中断服务程序时处理器模式变为IRQ模式,与任务的模式不同,它们的堆栈指针SP也不一样,而寄存器应当保存到用户的堆栈中,为了减少不必要的CPU时间和RAM空间的浪费,本移植仅在必要时将处理器的寄存器保存到用户的堆栈中,其它时候还是保存到IRQ模式的堆栈中。同时,从编译器的函数调用规范可知,C语言函数返回时,寄存器R4—R11、SP不会改变,所以只需要保存CPSR、R0—R3、R12和返回地址LR,在后面保存 CPSR是为了必要时将寄存器保存到用户堆栈比较方便。
在异常处理代码与C语言的接口程序中没有与中断服务子程序流程图中的判断语句对应的语句。判断语句是为了避免在函数OSIntCtxsw()调整堆栈指针,这个调整量是与编译器、编译器选项、μC/OS-Ⅱ配置选项都相关的变量。在这里进行这些处理相对其它处理器结构可能增加的处理器时间很少,但对于ARM来说,由于中断(IRQ)有独立的堆栈,在这里这样做就需要把所有寄存器从中断的堆栈拷贝到任务的堆栈,需要花费比较多的额外时间。而变量OSIntNesting为0时,并不一定会进行任务切换,所以本移植没有与之对应的程序,而在函数OSIntCtxsw()中做这一项工作。这样,仅在需要时才处理这些事物,程序效率得以提高。
在中断调用后,如果需要任务切换,则变量OSTCBHighRdy和变量OSTCBCur的值不同;如果不需要任务切换这两个变量则相同。本移植通过判断这两个变量来决定是进行任务切换,还是不进行任务切换。通过比较,如果需要任务切换则执行“LDR PC, =OSIntCtxSw”跳转到OSIntCtxSw处进行任务切换;如果不需要任务切换则执行“LDMEQFD SP!, {R0-R3, R12, PC}^”中断返回。
这里需要对“MSR CPSR_c, #0x92”说明下,这条指令的作用是关IRQ中断。因为中断(IRQ)模式的LR寄存器在处理器响应中断时用于保存中断返回地址,所以在处理器响应中断时中断(IRQ)模式的LR寄存器不能保存有效数据。而BL指令要用LR寄存器保存BL下一条指令的位置,所以在中断(IRQ)模式时,在BL指令之前必须关中断,在保存LR后才能开中断。
§4.2.2 Target.c文件的编写
为了使系统基本能够工作,必须在进人 main()函数前对系统进行一些基本的初始化工作,这些工作由函数TargetResetInit()完成。
void TargetResetInit(void)
{
uint32 i;
uint32 *cp1;
uint32 *cp2;
extern void Vectors(void) ;
/* 拷贝向量表,保证在flash和ram中程序均可正确运行 */
cp1 = (uint32 *)Vectors;
cp2 = (uint32 *)0x40000000;
for (i = 0; i < 2 * 8; i++)
{
*cp2++ = *cp1++;
}
MEMMAP = 0x2;
PINSEL0 = (PINSEL0 & 0xFFFF0000) | UART0_PCB_PINSEL_CFG | 0x50;
PLLCON = 1; /* 设置系统各部分时钟 */
VPBDIV = 0;
PLLCFG =0x23;
PLLFEED = 0xaa;
PLLFEED = 0x55;
while((PLLSTAT & (1 << 10)) = = 0) ;
PLLCON = 3;
PLLFEED = 0xaa;
PLLFEED = 0x55;
MAMCR = 2; /* 设置存储器加速模块 */
#if Fcclk < 20000000
MAMTIM = 1;
#else
#if Fcclk < 40000000
MAMTIM = 2;
#else
MAMTIM = 3;
#endif
#endif
首先向量表拷贝到RAM底部,加上这部分是为了代码无论从Flash基地址开始编译还是从RAM基地址开始编译程序均运行正确。而把RAM底部映射到向量表“MEMMAP = 0x2”也是为了同一个目的。至于复制16个字而不是8个字,是因为后8个字存储跳转的地址是通过 PC指针间接寻址的,它们与对应指令(在向量表中)相对位置是不能变化的。
因为在进入多任务环境前使用了一些外设,部分外设使用了芯片的引脚,而 LPC2106的所有引脚都是多功能的,所以需要设置引脚功能。同时串口也进行了设置。时钟是芯片各部分正常工作的基础,虽然时钟可以在任何时候设置,但为了避免混乱,最好在进入 main()函数前设置。程序首先使能PLL但不连接PLL,然后设置外设时钟(VPB时钟pclk)与系统时钟(cclk)的分频比。接着设置PLL的乘因子和除因子。设置完成后,使用“PLLFEED = 0xaa; PLLFEED = 0x55;”的访问序列把数据正确写人硬件,并等待PLL跟踪完成。最后,使能PLL,并使PLL联上系统。本应用外接的晶振频率(Fosc)为11.0592MHz,倍增器的值M=4,所以处理器时钟(Fcclk)为44.2368 MHz。为了使电流控制振荡器频率(Fcco)满足156-320MHz,所以分频器的值P=2,使得Fcco= Fcclk×2×P=176.9472 MHz。取VPB分频器的分频值为1/4,所以外设时钟(Fpclk)= Fcclk/4=11.0592 MHz,则记数周期为0.09042μs,定时0.2ms,则记数值为2212个,这些时钟的定义都在config.h文件中。
用户程序最终是要在Flash中运行的,而系统复位时Flash是以最低速度运行,这对发挥芯片的性能极其不利。虽然存储器加速模块可以在任何时候设置,但为了避免混乱,最好在进入 main()函数前设置。首先使存储器加速模块全速工作,然后根据系统主时钟利用条件编译将Flash的访问时钟设置到合适的值。
§4.2.3 Init.s文件的编写
由于LPC2106微控制器的存储系统比较简单,所以系统初始化代码也比较简单,代码如下:
Reset
BL InitStack ;初始化堆栈
BL TargetResetInit ;目标板基本初始化
B __main ;跳转到c语言入口
在芯片复位在芯片复位时程序会跳转到标号Reset处,程序首先调用Initstack初始化各种模式的堆栈,然后调用TargetResetlnit对系统进行基本初始化,最后跳转到ADS提供的启动代码__main。_main是 ADS提供的启动代码起始位置,它初始化库并最终引导CPU进入main函数。
§4.3 移植μC/OS-Ⅱ
图4-1 μC/OS-Ⅱ硬件/软件体系结构
图4-1说明了μC/OS-Ⅱ的结构以及它与硬件的关系,由此可知与处理器相关的代码在OS_CPU.H(包括用#define设置一些常量的值,声明的数据类型和用#define声明的宏),OS_CPU_C.C(用C语言编写的简单函数)和 OS_CPU_A.ASM(编写的汇编语言函数)。下面逐一介绍这3个文件中的关键部分程序段。
§4.3.1 OS_CPU.H文件的编写
§4.3.1.1 定义与编译器无关的数据类型
typedef unsigned char BOOLEAN; /* 布尔变量*/ typedef unsigned char INT8U; /* 无符号8位整型变量*/
typedef signed char INT8S; /* 有符号8位整型变量*/
typedef unsigned short INT16U; /* 无符号16位整型变量*/
typedef signed short INT16S; /* 有符号16位整型变量 */
typedef unsigned int INT32U; /* 无符号32位整型变量*/
typedef signed int INT32S; /* 有符号32位整型变量*/ typedef float FP32; /* 单精度浮点数(32位长度)*/
typedef double FP64; /* 双精度浮点数(64位长度)*/
typedef INT32U OS_STK; /* 堆栈是32位宽度*/
§4.3.1.2 与ARM7体系结构相关的一些定义
#define OS_CRITICAL_METHOD 3 /* 选择开、关中断的方式 */
实现OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()这2个宏的定义有3种方法,本移植采用的是第三种方法,具体参见§4.3.2.2小节。
ARM处理器核具有2个指令集,用户任务还可以有2种处理器模式:用户模式和系统模式,组合起来具有4种方式,各种方式对系统资源有不同的访问控制权限。为了使底层接口函数与处理器状态无关,同时使任务调用相应的函数不需要知道函数位置,本移植使用软中断指令SWI作为底层接口,使用不同的功能号区分不同的函数,同时预留挂接μC/OS-Ⅱ系统服务函数的接口。软中断功能号分配如表4-1所列,未列出的为保留。
功能号 |
接口函数 |
简述 |
0x00 |
void OS_TASK_SW(void) |
任务级任务切换函数 |
0x01 |
_OSStartHighRdy(void) |
运行优先级最高的函数 |
0x02 |
void OS_ENTER_CRITICAL(void) |
开终端 |
0x03 |
void OS_EXIT_CRITICAL(void) |
关中断 |
表4-1 软中断功能号分配
底层接口函数声明如下:
__swi(0x00) void OS_TASK_SW(void); /* 任务级任务切换函数*/
__swi(0x01) void _OSStartHighRdy(void); /* 运行优先级最高的任*/
__swi(0x02) void OS_ENTER_CRITICAL(void); /* 关中断*/
__swi(0x03) void OS_EXIT_CRITICAL(void); /* 开中断*/
用软件中断作为操作系统的底层接口就需要在C语言中使用SWI指令。在ADS中,有一个关键字__swi,用它声明一个不存在的函数,调用这个函数就在调用这个函数的地方插入一条SWI指令,并且可以指定功能号。
#define OS_STK_GROWTH 1 /* 堆栈是从上往下/
μC/OS-Ⅱ使用结构常量OS_STKG_ROWTH中指定堆栈的生长方式,置 OS_STKG_ROWTH为 0表示堆栈从下往上长;置OS_STKG_ROWTH为1表示堆栈从上往下长。虽然ARM处理器核对于两种方式均支持,但ADS的C语言编译器仅支持一种方式,即从上往下长,并且是必须是满递减堆栈,所以OS_STKG_ROWTH的值为 1。
#define USR32Mode 0x10 /* 用户模式*/
#define SYS32Mode 0x1f /* 系统模式*/
#define NoInt 0x80
#ifndef USER_USING_MODE
#define USER_USING_MODE USR32Mode /* 任务缺省*/
#endif
用户任务具有两种处理器模式(用户模式和系统模式),可以使用两种指令集(ARM指令集和THUMB指令集),这些状态均保存在程序状态寄存器CPSR中,而USER_USING_MODE就是定义任务开始执行时默认的处理器模式和使用的指令集。
§4.3.2 OS_CPU_C.C文件的编写
§4.3.2.1 OSTaskStkInt()的编写
OSTaskCreate()和OSTaskCreateExt()通过调用OSTaskStkInt()来初始
化任务的堆栈结构,因此,堆栈看起来就像刚发生过中断并将所有的寄存器保存到堆栈中的情形一样。
OS_STK *OSTaskStkInit (void (*task)(void *pd), void *pdata,
OS_STK *ptos, INT16U opt)
{
OS_STK *stk;
opt = opt;
stk = ptos;
*stk = (OS_STK) task; /* pc */
*--stk = (OS_STK) task; /* lr */
*--stk = 0; /* r12 */
*--stk = 0; /* r11 */
*--stk = 0; /* r10 */
*--stk = 0; /* r9 */
*--stk = 0; /* r8 */
*--stk = 0; /* r7 */
*--stk = 0; /* r6 */
*--stk = 0; /* r5 */
*--stk = 0; /* r4 */
*--stk = 0; /* r3 */
*--stk = 0; /* r2 */
*--stk = 0; /* r1 */
*--stk = (unsigned int) pdata; /* r0,第一个参数使用r0传递*/
*--stk = (USER_USING_MODE|0x00);/* spsr,允许IRQ, FIQ 中断 */
*--stk = 0; /* 关中断计数器OsEnterSum; */
return (stk);
}
图4-2所示为本移植堆栈结构,对照图4-2很容易理解OSTaskStkInt()的代码,这里就不做过多的说明了。
图4-2 任务堆栈结构
§4.3.2.2 SWI_ Exception的编写
由软中断的汇编接口程序SoftwareInterrupt(具体参见§4.3.3.1小节)可知,在发生软中断时,除了功能号0和1的软中断,其他软中断都要跳转到软件中断的C语言函数SWI_ Exception处,其结构是一个switch语句把各个功能分开,使各个功能相对独立,程序清单如下所示:
void SWI_Exception(int SWI_Num, int *Regs)
{
OS_TCB *ptcb;
switch(SWI_Num)
{
//case 0x00: /* 任务切换函数OS_TASK_SW,参考os_cpu_s.s文件 */
// break;
//case 0x01: /* 启动任务函数OSStartHighRdy,参考os_cpu_s.s文件 */
// break;
case 0x02: /* 关中断函数OS_ENTER_CRITICAL() */
__asm
{
MRS R0, SPSR
ORR R0, R0, #NoInt
MSR SPSR_c, R0
}
OsEnterSum++;
break;
case 0x03: /* 开中断函数OS_EXIT_CRITICAL()*/
if (--OsEnterSum == 0)
{
__asm
{
MRS R0, SPSR
BIC R0, R0, #NoInt
MSR SPSR_c, R0
}
}
break;
}
}
对于任务切换函数OS_TASK_SW和启动任务函数OSStartHighRdy在os_cpu_s.s文件中介绍。这里需要对关中断函数和开中断函数作些解释。与所有的实时内核一样,μC/OS-Ⅱ需要先禁止中断再访问代码的临界段,并且在访问完毕后重新允许中断。这就使得μC/OS-Ⅱ能够保护临界段代码免受多任务或中断服务破坏。实现OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()这2个宏的定义有3种方法,本移植采用的是第三种方法,即在OS_CPU.H文件中使OS_CRITICAL_MEHTOD等于3,这种方法是利用某些编译器提供的扩展功能,用户可以得到当前处理器状态字的值,并将其保存在C函数的局部变量之中,这个变量可以用于恢复PSW,而本ARM内核关中断和开中断时,是通过改变程序状态寄存器CPSR中的相应控制位实现。由于使用了软件中断,程序状态寄存器 CPSR保存到程序状态保存寄存器 SPSR中,软件中断退出时会将SPSR恢复到CPSR中。所以程序只要改变程序状态保存寄存器SPSR中相应的控制位就可以了。
§4.3.3 OS_CPU_A.ASM文件的编写
§4.3.3.1 SoftwareInterrupt的编写
当发生软件中断时,程序通过异常向量表跳转到软中断的汇编与C接口程序SoftwareInterrupt处,图4-3为SoftwareInterrupt的流程图。
图4-3 软件中断代码的汇编部分流程图
SoftwareInterrupt
LDR SP, StackSvc ; 重新设置堆栈指针
STMFD SP!, {R0-R3, R12, LR}
MOV R1, SP ; R1指向参数存储位置
MRS R3, SPSR
TST R3, #T_bit ; 中断前是否是Thumb状态
LDRNEH R0, [LR,#-2] ; 是: 取得Thumb状态SWI号
BICNE R0, R0, #0xff00
LDREQ R0, [LR,#-4] ; 否: 取得arm状态SWI号
BICEQ R0, R0, #0xFF000000
; r0 = SWI号,R1指向参数存储位置
CMP R0, #1
LDRLO PC, =OSIntCtxSw
LDREQ PC, =__OSStartHighRdy ; SWI 0x01为第一次任务切换
BL SWI_Exception
LDMFD SP!, {R0-R3, R12, PC}^
StackSvc DCD (SvcStackSpace+SVC_STACK_LENGTH* 4-4)
因为执行任务切换时堆栈指针会指向用户的堆栈,这样下一次进入管理模式就会破坏用户堆栈,从而导致程序执行不正确。所以程序在一开始设置堆栈指针。软中断指令使处理器进入管理模式,而用户程序处于系统/用户模式,其它异常也有自己的处理器模式,都有各自的堆栈指针,不会因为给堆栈指针赋值而破坏其它处理器模式的堆栈而影响其它程序的执行。返回的地址已经存储在连接寄存器LR中而不是存储在堆栈中。由于进人管理模式自动关中断,所以这段程序不会被其它程序同时调用,设置的堆栈指针指向的位置肯定是空闲位置,后一次调用不会影响前一次调用。这样就可以保证“LDR SP, StackSvc”进行正确的堆栈指针设置。
软中断的功能号是包含在SWI指令当中的。程序通过读取该条指令的相应位段获得。由于ARM处理器核具有两个指令集,两个指令集的指令长度不同,SWI指令的功能号的位段也不同,所以程序先判断在进入软中断前处理器是在什么指令集状态,根据指令集状态的不同取出相应的SWI指令的功能号。然后,程序用功能号与1比较,当功能号符号小于1即为 0时,就跳转到任务切换函数OSIntCtxSw处。当功能号等于1时,就跳转到第一次任务切换OSStartHighRdy处。其它部分是给软件中断的C语言处理函数处理。这里有两个参数,第一个就是功能号,存于R0中;第二个是保存参数和返回值的指针,也就是堆栈中存储用户函数R0—R4的位置,就是当前堆栈指针的值,它存于R1中。
这里对功能号0说明一下,功能号0跳转到OSIntCtxsw程序段,而这段程序实际是实现了OS_TASK_SW()函数的功能。OS_TASK_SW()是在μC/OS-Ⅱ从低优先级任务切换到最高优先级任务时被调用的,因为ARM处理器核具有两个指令集,在执行Thumb指令的状态时不是所有寄存器都可见(参考ARM的相关资料),而且任务又可能不在特权模式(不能改变CPSR)。为了兼容任意一种模式,本移植使用软中断指令SWI使处理器进入管理模式和ARM指令状态,并使用功能0实现OS_TASK_SW()的功能,即使用SWI 0X00代替 OSTASKSW(),具体实现方法见下一小节。
§4.3.3.2 OSIntCtxSw的编写
在μC/OS-Ⅱ中,任务切换只是简单的将处理器寄存器保存到将被挂起的任务的堆栈中,并且将更高优先级的任务从堆栈中恢复出来。处于就绪状态的任务的堆栈结构看起来就像刚发生过中断并将所有的寄存器保存到堆栈中的情形一样。换句话说,μC/OS-Ⅱ要运行处于就绪状态的任务必须要做的事就是将所有处理器寄存器从任务堆栈中恢复出来,并且执行中断的返回。
在μC/OS-Ⅱ中,用户级任务调度时会调用宏(或者函数)OS_TASK_SW(),它是在μC/OS-Ⅱ从低优先级任务切换到最高优先级任务时被调用的,μC/OS-Ⅱ建议OS_TASK_SW()通过某种途径最终调用函数OSCtxSw()。函数OSCtxSw()是与系统相关的,μC/OS-Ⅱ提供的OSCtxSw()函数原型如下:
OSCtxSw()原型的程序清单
void OSCtxSw(void)
{
保存处理器寄存器;
将当前任务的堆栈指针保存到当前任务的OS_TCB中;
OSTCBCur->OSTCBStkPtr = Stack pointer;
调用用户定义的OSTaskSwHook();
OSTCBCur = OSTCBHighRdy;
OSPrioCur = OSPrioHighRdy;
得到需要恢复的任务的堆栈指针;
Stack pointer = OSTCBHighRdy->OSTCBStkPtr;
将所有处理器寄存器从新任务的堆栈中恢复出来;
执行中断返回指令;
}
在μC/OS-Ⅱ中,函数OSIntExit( )被用来在ISR使得更高优先级任务处于就绪状态时,执行任务切换功能。中断退出函数通过调OSIntCtxSw()来从ISR中执行切换功能。函数OSIntCtxSw()是与系统相关的,μC/OS-Ⅱ提供的OSIntCtxSw()函数原型如下:
OSIntCtxSw( )原型的程序清单
void OSIntCtxSw(void)
{
调用用户定义的OSTaskSwHook( ) ;
OSTCBCur = OSTCBHighRdy ;
OSPrioCur = OSPrioHighRdy ;
得到需要恢复的任务的堆栈指针 ;
堆栈指针 = OSTCBHighRdy->OSTCBStkPtr ;
将所有处理器寄存器从新任务的堆栈中恢复出来;
执行中断返回指令;
}
对比两个函数原型,除OSCtxSw()原型的程序清单比OSIntCtxSw( )原型的程序清单多了两句外,其它都是一样的。由异常处理代码与C语言的接口程序可知,OS_TASK_SW()实质是软件中断的功能号0,在软件中断中已经把除变量OsEntersum外的所有寄存器保存到管理模式的堆栈中。而由程序可知,当程序执行函数 OSIntCtxsw()时,变量 OsEnterSum也没有保存到 IRQ模式的堆栈中。也就是说,两种情况需要做的工作一样,都可用同一段代码实现。
OSIntCtxSw
;下面为保存任务环境
LDR R2, [SP, #20] ;获取PC (1)
LDR R12, [SP, #16] ;获取R12 (2)
MRS R0, CPSR (3)
MSR CPSR_c, #(NoInt | SYS32Mode) (4)
MOV R1, LR (5)
STMFD SP!, {R1-R2} ;保存LR,PC (6)
STMFD SP!, {R4-R12} ;保存R4-R12 (7)
MSR CPSR_c, R0 (8)
LDMFD SP!, {R4-R7} ;获取R0-R3 (9)
ADD SP, SP, #8 ;出栈R12,PC (10)
MSR CPSR_c, #(NoInt | SYS32Mode) (11)
STMFD SP!, {R4-R7} ;保存R0-R3 (12)
LDR R1, =OsEnterSum ;获取OsEnterSum (13)
LDR R2, [R1] (14)
STMFD SP!, {R2, R3} ;保存CPSR,OsEnterSum (15)
;保存当前任务堆栈指针到当前任务的TCB
LDR R1, =OSTCBCur (16)
LDR R1, [R1] (17)
STR SP, [R1] (18)
BL OSTaskSwHook ;调用子函数 (19)
LDR R4, =OSPrioCur (20)
LDR R5, =OSPrioHighRdy (21)
LDRB R6, [R5] (22)
STRB R6, [R4] (23)
LDR R6, =OSTCBHighRdy (24)
LDR R6, [R6] (25)
LDR R4, =OSTCBCur (26)
STR R6, [R4] (27)
OSIntCtxSw_1
;获取新任务堆栈指针
LDR R4, [R6] (28)
ADD SP, R4, #68 (29)
LDR LR, [SP, #-8] (30)
MSR CPSR_c, #(NoInt | SVC32Mode) ;进入管理模式(31)
MOV SP, R4 ;设置堆栈指针(32)
LDMFD SP!, {R4, R5} ;CPSR,OsEnterSum (33)
;恢复新任务的OsEnterSum
LDR R3, =OsEnterSum (34)
STR R4, [R3] (35)
MSR SPSR_cxsf, R5 ;恢复CPSR (36)
LDMFD SP!, {R0-R12, LR, PC }^ ;运行新任务(37)
这部分代码基本按照μC/OS-Ⅱ提供的函数原型编写的,其中程序清单(1)—(18)部分与OSCtxSw()和OSIntCtxSw( )的原型是没有对应语句的,寄存器应当保存到任务的堆栈中,但为了节省CPU的时间和RAM的空间,仅在必要的时候才将寄存器保存到任务堆栈。OSTCBCur->OSTCBStkPtr=SP也是在必要的时候才执行的。这部分正是在处理这两件事情,其流程图见图4-4。
图4-4 OSIntCtxSw部分代码流程图
由软中断的汇编与C接口程序可知,在调用OS_TASK_SW( )(即软件中断的功能号0)时,寄存器R0—R3、R12、PC已经保存到当前模式的堆栈中,任务的R4—R11、SP、LR没有发生变化。也就是说寄存器已经保存,但不是保存到任务的堆栈中。同样由异常处理代码与C语言的接口程序可知,在调用OSIntCtxSw( )时,寄存器R0—R3、R12、PC已经保存到当前模式的堆栈中,任务的R4—R11、SP、LR没有发生变化。也就是说寄存器已经保存,但不是保存到任务的堆栈中。当前处理器模式的堆栈结构如图4-5所示。
图4-5 当前处理器模式堆栈结构图
在执行(1)—(18)这部分程序时的寄存器和存储器之间的具体关系如图4-6所示,图中的标号为对应的程序段,这里是以调用OS_TASK_SW( )为例子来说明,调用OSIntCtxSw( )的工作过程和调用OS_TASK_SW( ) 的工作过程是一样的,只不过调用OS_TASK_SW( )时处理器当前模式为管理模式,而调用OSIntCtxSw( )时处理器当前模式为IRQ模式。
图4-6-1
此时处理器处于管理模式,因为本移植是使用软中断指令SWI使处理器进入管理模式和ARM指令状态,并使用功能0来实现OS_TASK_SW( )的功能。具体参见软中断的汇编与C接口程序代码。
图4-6-2
此时是通过执行程序段(4),利用MSR指令直接设置状态寄存器CPSR的模式位,使处理器进入用户/系统模式此时需要注意的是处理器的可见寄存器与管理模式时是有区别的。在保存之后同样是通过执行程序段(8),利用MSR指令返回到管理模式。
图4-6-3
进入管理模式以后继续保存数据,当数据保存好以后,要注意调整当前模式堆栈指针因为当前模式的入栈比出栈的数据多,而在堆栈中剩余的数据(R12、PC)已经没有用处,所以要进行调整。完成指针调整的为程序段(10)。
图4-6-4
此时又使处理器进入用户/系统模式,继续把未保存的寄存器内容保存到任务堆栈,这里要注意的是R3保存着任务的CPSR(既当前模式的SPSR),这部分在异常处理代码与C语言的接口程序中完成的。至此,寄存器内容已经完全保存到任务堆栈里了。
从标号OSIntCtxSw_1处开始至程序的最后,是将所有处理器寄存器从新任务的堆栈中恢复出来。处理器执行这段代码时处于ARM状态,但用户任务可能处于Thumb状态。而由ARM的相关资料可知,程序不可以直接改变程序状态寄存器的CPSR的T位来改变处理器的状态。但“LDMFD SP!, {R0-R12, LR, PC }^ ” 异常返回指令是可以使用的(具体参见ARM的多寄存器加载指令),所以在恢复寄存器时,必须使处理器处于某种异常模式。由于FIQ模式和IRQ模式对应中断,SP指针不能随意改变,而未定义模式和中止模式以后可能会用到,所以本移植选择为管理模式。
在执行(28)—(37)这部分程序时的寄存器和存储器之间的具体关系如图4-7所示,图中的标号为对应的程序段。
图4-7-1
由于 OSTCBHirtRdy->OSTCBStkPtr保存的是任务堆栈位置,而寄存器恢复后堆栈指针并不指向这里,所以要调整新任务堆栈指针,这是由程序段(29)完成的。
图4-7-2
然后通过执行程序段(31),利用MSR指令直接设置状态寄存器CPSR的模式位,使处理器进入管理模式,先恢复新任务OsEntersum,这是由程序段(34)(35)完成的,然后把新任务的 CPSR恢复到 SPSR这是由程序段(36)实现的。
图4-7-3
最后通过中断返回指令恢复R0—R12,把SPSR拷贝到CPSR(恢复用户任务的处理器模式和指令集)和执行用户任务(恢复PC指针),这是由程序段(37)实现的。这里需要注意的是程序段(29)和(32)中的SP是不同的处理器寄存器分别为R13和R13_SVC。
§4.3.3.3 OSStartHighRdy的编写
μC/OS-Ⅱ启动多任务环境的函数叫做OSStart()。用户在调用OSStart()之前,必须已经建立了一个或更多任务。OSStart()最终调用OSStartHighRdy()函数运行多任务启动前优先级最高的任务。由软中断的汇编与C接口程序SoftwareInterrupt可知,这是调用软中断的1号功能。这是因为ARM处理器核具有两个指令集,在执行Thumb指令的状态时不是所有寄存器都可见(参考ARM的相关资料),而且任务可能不在特权模式。为了兼容任意一种模式,本移植使用软中断指令SWI使处理器进入管理模式和ARM指令状态,并使用功能1实现OSStartHighRdy的功能。μC/OS-Ⅱ中调用函数 OSStartHighRdy()之前,OSTCBHighRdy指向的是优先级最高的任务的任务控制块。μC/OS-Ⅱ要求处于就绪状态的任务的堆栈结构看起来就像刚发生过中断,并将所有寄存器保存到堆栈中的情形一样。要想运行最高优先级任务,用户要做的是将所有处理器寄存器按顺序从任务堆栈中恢复出来,并且执行中断的返回。μC/OS-Ⅱ提供的OSStartHighRdy函数原型如下:
OSStartHighRdy原型的程序清单
void OSStartHighRdy (void)
{
调用用户函数OSTaskSwHook();
OSRunning=True;
获取堆栈指针SP=OSTCBHighRdy->OSTCBStkPtr:
从新任务堆栈中恢复所有寄存器;
执行中断返回指令;
}
OSStartHighRdy的程序代码如下:
OSStartHighRdy
MSR CPSR_c, #(NoInt | SYS32Mode)
;告诉uC/OS-II自身已经运行
LDR R4, =OSRunning
MOV R5, #1
STRB R5, [R4]
BL OSTaskSwHook ;调用子函数
LDR R6, =OSTCBHighRdy
LDR R6, [R6]
B OSIntCtxSw_1
这部分代码是比较严格的按照μC/OS-Ⅱ提供的原型编写的,其中OSIntCtxSw_1的代码在上一小节已经详细介绍过。
这里需要对“MSR CPSR_c, #(NoInt | SYS32Mode)”作以说明,在μC/OS-Ⅱ中,需要用户提供周期性信号源,用于实现时间延时和确认超时。必须在多任务系统启动以后再开启时钟节拍器,也就是在调用OSStart()之后。换句话说,在调用OSStart()之后做的第一件事是初始化定时器中断。通常,容易犯的错误是将允许时钟节拍器中断放在系统初始化函数OSInit()之后,在调启动多任务系统启动函数OSStart()之前允许时钟节拍中断。μC/OS-Ⅱ的启动多任务函数OSStart()会在最后调用OSStartHighRdy,而SStartHighRdy的第一条语句就允许中断。这里是将CPSR的第8位置1,允许IRQ中断。
至此已经将μC/OS-Ⅱ移植到了LPC2106,下面可以进行瞬时电压有效值检测的程序的编写。
ARM处理器中主要有7个异常(2个中断异常):
1、复位异常;在以ARM为核的单片机中,常把下列事件作为引起复位的原因。
• 上电复位:在上电后,复位使内部达到预定的状态,特别是程序跳到初始入口;
• 复位引脚上的复位脉冲:这是由外部其他控制信号引起的;
• 对系统电源检测发现过压或欠压;
• 时钟异常复位。
ARM处理器复位后,处理器硬件将进行以下操作:
• 强制进入管理模式;
• 强制进入ARM状态;
• 跳转到绝对地址PC=0x00000000处执行;
• 禁止IRQ中断和FIQ中断。
复位后,程序状态寄存器如下:
......
|
I
|
F
|
T
|
M4
|
M3
|
M2
|
M1
|
M0
|
1
|
1
|
0
|
1
|
0
|
0
|
1
|
1
|
上电复位后,进入管理模式,执行操作系统程序,一般用做对系统初始化,例如开中断等;然后切换到用户模式,开始执行正常的用户程序。
切换到用户模式可使用下列程序:
MRS R0,CPSR ;读状态寄存器
BIC R0,R0,#03 ;把末两位清0
MSR CPRS_c,R0 ;把修改后的值加载给状态寄存器,切换结束
...... ;用户程序
2、未定义指令异常;由于ARM使用32位代码,包含的信息量很大,可达2的31方(4G)。ARM指令集不能用尽所有代码。当ARM处理器遇到无法译码的指令时就会发
生未定义指令异常。进入中断处理程序。
ARM的未定义指令异常有以下两种情况:
(1)遇到一条无法执行的指令,此指令没有定义;
(2)执行一条对协处理器的操作指令,在正常情况下,协处理器应该应答,但协处理器没有应答。
未定义异常中断时,状态寄存器中的F位不变。使用下列指令退出异常中断,返回原程序 。
• 把下一条指令的地址拷贝给LR;
• 把程序状态寄存器CPSR拷贝给SPSR_und;
• 强制进入未定义模式;
• 强制进入到ARM模式;
• 跳转到绝对地址PC=0x00000004处执行;
• 禁止IRQ中断。
进入中断后,程序状态寄存器如下:
......
|
I
|
F
|
T
|
M4
|
M3
|
M2
|
M1
|
M0
|
1
|
x
|
0
|
1
|
1
|
0
|
1
|
1
|
MOVS PC,R14.
3、软件中断异常;是由指令SWI引起的。程序在执行这一指令后,进入异常中断。处理器响应中断,硬件执行如下的操作。
• 把下一条指令的地址拷贝给LR;
• 把程序状态寄存器CPSR拷贝给SPSR_svc;
• 强制进入管理模式;
• 强制进入到ARM状态;
• 跳转到绝对地址PC=0x00000008处执行;
• 禁止IRQ中断。
进入中断后的程序状态寄存器如下:
......
|
I
|
F
|
T
|
M4
|
M3
|
M2
|
M1
|
M0
|
1
|
x
|
0
|
1
|
0
|
0
|
1
|
1
|
软件中断处理程序完成后,使用下列指令返回到原中断处:MOVS PC,R14.
4、预取指中止异常;由程序存储器引起的中止异常叫做预取指中止异常;由数据存储器引起的中止异常叫做数据中止异常。由于ARM的指令是3级流水线结构,读
取指令周期是提前进行的,因此把读取指令的过程一般称预取指。如果在取得指令的同时程序存储器发出中止信号,则ARM处理器把这一条指令标记位无效,然后等待执
行。有两种可能如下:
• 当执行这条指令前程序发生跳转,则这条无效指令不引起异常中断;
• 当执行到这条指令时,处理器会发生预取指中止异常,引起中断。
当记进入预取指异常中断时,处理器硬件响应中断,执行以下的操作:
• 把中断时PC的地址拷贝给LR;
• 把程序状态寄存器CPSR拷贝给SPSR_abt;
• 强制进入中止异常模式;
• 强制进入到ARM状态;
• 跳转到绝对地址PC=0x0000000C处执行;
• 禁止IRQ中断。
进入中断后,程序状态寄存器如下:
......
|
I
|
F
|
T
|
M4
|
M3
|
M2
|
M1
|
M0
|
1
|
x
|
0
|
1
|
0
|
1
|
1
|
1
|
预取指中止异常中断返回时,应该执行下列指令:SUBS PC,R14,#4.
5、数据中止异常;ARM处理器访问数据存储器时,在读取数据的同时数据存储器发出了中止信号,引起数据中止异常。
数据中止异常中断服务程序返回时,使用下列指令:
SUBS PC,R14,#8
上述指令是返回到中断时所执行的指令,目的是再一次从数据存储器中读取数据。如果不再一次读取数据,则执行下一条指令,此时使用下列指令返回:
SUBS PC,R14,#4
• 把中断时的PC的地址拷贝给LR;
• 把程序状态寄存器CPSR拷贝给SPSR_abt;
• 强制进入中止异常模式;
• 强制进入到ARM状态;
• 跳转到绝对地址PC=0x00000010处执行;
• 禁止IRQ中断。
进入中断后,程序状态寄存器如下:
......
|
I
|
F
|
T
|
M4
|
M3
|
M2
|
M1
|
M0
|
1
|
x
|
0
|
1
|
0
|
1
|
1
|
1
|
6、中断请求(IRQ)异常;例如:定时器中断、串行口通讯中断、外部信号中断和A/D处理中断等。IRQ中断是可屏蔽的。在状态寄存器中的I位就是IRQ的屏蔽位
。当I=1时。则屏蔽IRQ中断,当I=0时,则允许中断。处理器复位后置I为1,关闭中断。
当发生IRQ中断时,处理器硬件响应中断,执行下列操作:
• 把中断时的PC的地址值拷贝给LR;
• 把程序状态寄存器CPSR拷贝给SPSR_irq;
• 强制进入IRQ异常模式;
• 强制进入到ARM状态;
• 跳转到绝对地址PC=0x00000018处执行;
• 禁止IRQ中断。
进入中断后,程序状态寄存器如下:
......
|
I
|
F
|
T
|
M4
|
M3
|
M2
|
M1
|
M0
|
1
|
x
|
0
|
1
|
0
|
0
|
1
|
0
|
完成中断处理后,程序执行下列返回原中断处:SUBS PC.R14,#4.
7、快速中断(FIQ)请求异常;FIQ快速中断是可屏蔽的。在状态寄存器中的F位就是FIQ的屏蔽位。当F=1时。则屏蔽FIQ中断,当F=0时,则允许中断。处理器复
位后置F为1,关闭中断。
当发生IRQ中断时,处理器硬件响应中断,执行下列操作:
• 把中断时的PC的地址值拷贝给LR;
• 把程序状态寄存器CPSR拷贝给SPSR_fiq;
• 强制进入FIQ异常模式;
• 强制进入到ARM状态;
• 跳转到绝对地址PC=0x0000001C处执行;
• 禁止FIQ中断。
进入中断后,程序状态寄存器如下:
......
|
I
|
F
|
T
|
M4
|
M3
|
M2
|
M1
|
M0
|
1
|
x
|
0
|
1
|
0
|
0
|
0
|
1
|
完成中断处理后,程序执行下列返回原中断处:SUBS PC.R14,#4