9 中断体系结构
9.1 ARM中断体系
ARM CPU工作模式和状态
- 工作模式,7种,1种用户模式,其他6选中特权模式
- usr 用户模式,ARM处理器正常的工作模式
- fiq 快速中断模式,高速数据传输或者通道处理
- irq 中断模式,通用中断处理
- svc 管理模式,操作系统使用的保护模式
- abt 数据访问终止模式,数据或指令预取终止时进入该模式,用于虚拟存储及存储保护
- sys 系统模式,运行具有特权的操作系统任务
- und 未定义指令终止模式,未定义的指令得到执行的时候进入该模式,可用于支持硬件协处理器的软件仿真
大多数程序运行于用户模式,进入特权模式是为了处理中断、异常,或者访问被保护的系统资源。
- 工作状态,2种
- ARM状态,处理器执行32位的ARM指令
- Thumb状态,处理器执行16位的Thumb指令
CPU一上电就处于ARM状态,无需关心CPU状态。
- 寄存器
- 同一时刻可见的寄存器有17个
- r0 - r12
- r13 sp
- r14 lr
- r15 PC
- 不同模式下并不会复用所有的寄存器
- fiq r8 - r14 是独立的
- svc,abt,irq,und r13, r14 是独立的
- fiq,svc,abt,irq,und SPSR是独立的
- CPSR各位含义,详见CPSR
- N 结果是否为负数
- Z 运算结果是否为0
- C 进位/错位/移位溢出
- V 溢出
- I,F IRQ中断禁止,FIQ中断禁止
- T Thumb状态
- mode CPU当前工作模式
- 同一时刻可见的寄存器有17个
异常进入和返回流程
- 异常进入
- 异常工作模式下的lr保存进入前的下一个指令地址
- CPSR复制到SPSR
- 设置CPSR到当前异常的工作模式
- 设置PC到这个异常的异常向量表入口地址
- 异常返回
- 异常模式下的LR,减去适当的值,设置到PC
- SPSR的值恢复到CPSR
中断处理过程
- 中断控制器汇集各类外设发出的中断信号,通知CPU
- CPU保存当前程序运行环境,调用中断服务程序ISR来处理这些中断
- ISR中通过读取中断控制器、外设的相关寄存器识别中断源,进行相应处理
- 清除中断,读写中断控制器和外设寄存器
- 恢复被打断的程序
2440中断处理流程
- 中断源
- 含有自子中断的中断源,上图可以看到,子中断先过一遍mask,再设置SRCPND
- 不含有子中断的中断源,直接设置SRCPND
- 中断屏蔽
- 子中断先经过INTSUBMASK判断
- 不含有子中断和经过判断的子中断,再过一遍INTMASK
- 优先级
- 如果是FIQ,INTMOD是1,不用经过优先级选择,直接执行。FIQ只能分配一个。
- 如果是IRQ,需要经过优先级选择,最高优先级的中断,被设置到INTPND
- 中断执行
- ISR(中断处理程序)读INTPND或者INTOFFSET确定中断源
9.2 2440中断控制寄存器
SUBSRCPND
- 2440有15个子中断源,能看到分为5组,分别是这5个中断的子中断
- INT_WDT_AC97 看门狗
- INT_CAM 摄像头
- INT_ADC 触摸板
- INT_UART0, UART1, UART2 串口
- 发生中断的时候相应的位被自动设置成1
- 想要清除的话,需要再写入对应位置的1,请注意不是清零,是写1
INTSUBPND
某位被置1的时候,对应中断被屏蔽
SRCPND
处于pending状态的中断源。
来源有两类,参考上文,带有子中断的,和不带有子中断的。
清除某一位,需要对某一位写1而不是清0.
INTMSK
屏蔽SRCPND中的中断源。
某位写1即屏蔽该中断,只能屏蔽IRQ,不能屏蔽FIQ。
INTMOD
某位写1,即设定该中断位FIQ,见上文,同时只能有一个中断设置位FIQ。
一般设置最紧急的中断位FIQ。
PRIORITY
- 优先级寄存器控制优先级仲裁器的行为
- 2440的优先级仲裁,分为2个阶段
- 1级仲裁,即仲裁器0到仲裁器5,小组赛,选择出优胜者
- 2级仲裁,即仲裁器6,决赛,各 小组选出的中断再经过决赛选择总冠军
- 每组的优先级顺序可以配置,通过ARB_SLE
- 每组的优先级模式可以配置,是固定不变的,还是轮转的
- 固定不变的,按照ARB_SLE的配置顺序
- 轮转的,已经被服务的中断,其优先级降到倒数第二,仅高于REQ5
- 即使轮转,最高和最低,REQ0和REQ5都不会变化
INTPND
经过优先级筛选之后,从SRCPND中选出的中断,在INTPND中设置相应的位为1.
同一时间,只能有一位是1.
要清除这个中断,需要将相应的位置1,而不是清零,通过INTPND = INTPND 操作即可。
INTOFFSET
跟INTPND是联动的,INTPND对应位置1表示有中断等待处理,INTOFFSET这时设置成一个数字,跟INTPND表示的是同一个意思,编号为多少的中断正在等待处理。
清除SRCPND,INTPND寄存器的时候,INTOFFSET寄存器被自动清除。
9.3 实例
jz2440开发板上有4个按键,分别对应外部中断EINT0,EINT2,EINT11,EINT19,通过这4个按键操作LED灯。
- 程序结构
- 启动文件 head.s
- C程序
- 中断初始化 init.c
- 中断例程 interrrupt.c
- 主程序 main.c
- 中断向量
- 发生异常的时候,ARM核按照一个向量表决定如何跳转到响应的例程
- 当前的CPU处理的方式是在向量表的对应入口存放跳转指令
- 较新的CPU,或者第三方定制的CPU,也有可能存放的是例程地址,而不是指令
- 如果是指令,可以接受的指令如下
- b [address]
- ldr pc,[pc,#offset]
- ldr pc,[pc,#-ff0]
- mov pc,#immediate
- 向量位置
- reset svc 0x00
- undef und 0x04
- SWI svc 0x08
- I-abt abt 0x0C
- D-abt abt 0x10
- 未定义 未定义 0x14
- IRQ IRQ 0x18
- FIQ FIQ 0x1C
head.s
[head.s]
.extern main
.text
.global _start
_start: @ 这个例子里边,因为使用了IRQ,所以开始的部分按照IRQ的要求设置中断向量,从地址0开始
b Reset @ 0x00, reset异常,上电首先执行
HandleUndef:
b HandleUndef @ 0x04, 未定义异常,一下发生的异常,如果没有处理例程,就直接在对应位置死循环
HandleSWI:
b HandleSWI @ 0x08, SWI软中断异常
HandlePreFetchAbort:
b HandlePreFetchAbort @ 0x0C, 预取指异常
HandleDataAbort:
b HandleDataAbort @ 0x10, 数据访问异常
HandleReserved:
b HandleReserved @ 0x14,未定义
b HandleIRQ @ 0x18, 中断处理
HandleFIQ:
b HandleFIQ @ 0x1c,快速中断
Reset:
ldr r0,=0x53000000 @ 关闭看门狗
mov r1,#0
str r1,[r0]
ldr sp,=4096 @ 设置栈指针,因为下面都是C函数,所以需要设置栈指针
msr cpsr_c,#0xd2 @ 进入中断模式,CPSR 11010010
@ 为什么不是cpsr,而是cpsr_c,因为cpsr_c表示cpsr的低8位
@ IF都设置上了,IRQ和FIQ屏蔽,模式10010,IRQ模式
ldr sp,3072 @ 设置IRQ模式的栈指针,见上文,寄存器的拷贝
msr cpsr_c,#0xdf @ 进入系统模式,模式11111
ldr dp,=4096 @ 设置系统模式的栈指针,CPU复位之后,处于系统模式
bl init_led
bl init_irq
msr cpsr_c,#5f @ 打开I,IRQ去掉屏蔽
ldr lr,=halt_loop
ldr pc,=main
halt_loop:
b halt_loop
HandleIRQ:
sub lr,lr,#4 @ 返回地址
stmdb sp!,{r0-r12,lr} @ 保存使用到的寄存器,!的含义是sp随着数据传送的改变而改变
@ 现在sp,已经是IRQ模式的sp
@ IRQ模式的sp,比系统模式少1000字节,啥意思,是怕覆盖了系统模式下的栈数据?
ldr lr,=int_return
ldr pc,=EINT_Handle @ ISR例程,在interrupt.c中实现
int_return:
ldmia sp!,{r0-r12,lr}^ @ 中断返回,^的意思是将SPSR恢复到CPSR
init.c
init.c程序中设置中断寄存器的状态。
用到的定义,放在头文件int_key_led.h中。
[int_key_led.h]
#ifndef _INT_KEY_LED_H
#define _INT_KEY_LED_H
/*
* LED1,LED2,LED4对应GPF4、GPF5、GPF6
*/
#define GPF_OUT(x) (1 << ((x) * 2))
#define GPF_MSK(x) (3 << ((x) * 2))
#define GPG_MSK(x) GPF_MSK(x)
/*
* S2,S3,S4对应GPF0、GPF2、GPG3
*/
#define GPF_EINT(x) (2 << ((x) * 2))
#define GPG_EINT(x) GPF_EINT(x)
#define GPFCON (*(volatile unsigned long *)0x56000050)
#define GPFDAT (*(volatile unsigned long *)0x56000054)
#define GPGCON (*(volatile unsigned long *)0x56000060)
#define INTOFFSET (*(volatile unsigned long *)0x4a000014)
#define EINTMASK (*(volatile unsigned long *)0x560000a4)
#define PRIORITY (*(volatile unsigned long *)0x4a00000c)
#define INTMSK (*(volatile unsigned long *)0x4a000008)
#define EINTPEND (*(volatile unsigned long *)0x560000a8)
#define SRCPND (*(volatile unsigned long *)0x4a000000)
#define INTPND (*(volatile unsigned long *)0x4a000010)
/* LED灯对应的二进制数字,可以表示0到7 */
#define LED_NUM(x) (~(((x) & ~((~0) << 3)) << 4))
#define LED_MSK(x) (1 << ((x) + 4))
#endif
/*
* init.c: 进行一些初始化
*/
#include "int_key_led.h"
void init_led(void)
{
// LED1,LED2,LED4对应的3根引脚设为输出
GPFCON &= ~(GPF_MSK(4) | GPF_MSK(5) | GPF_MSK(6));
GPFCON |= GPF_OUT(4) | GPF_OUT(5) | GPF_OUT(6);
}
/*
* 初始化GPIO引脚为外部中断
* GPIO引脚用作外部中断时,默认为低电平触发、IRQ方式(不用设置INTMOD)
*/
void init_irq( )
{
// S2,S3对应的2根引脚设为中断引脚 EINT0,ENT2
GPFCON &= ~(GPF_MSK(0) | GPF_MSK(2));
GPFCON |= GPF_EINT(0) | GPF_EINT(2);
// S4对应的引脚设为中断引脚EINT11
GPGCON &= ~GPF_MSK(3);
GPGCON |= GPF_EINT(3);
// 对于EINT11,需要在EINTMASK寄存器中使能它
EINTMASK &= ~(1 << 11);
/*
* 设定优先级:
* ARB_SEL0 = 00b, ARB_MODE0 = 0: REQ1 > REQ3,即EINT0 > EINT2
* 仲裁器1、6无需设置
* 最终:
* EINT0 > EINT2 > EINT11即K2 > K3 > K4
*/
PRIORITY = (PRIORITY & ((~1) | (3 << 7))) | (0 << 7) ;
// EINT0、EINT2、EINT8_23使能
INTMSK &= (~(1 << 0)) & (~(1 << 2)) & (~(1 << 5));
}
interrupt.c
函数EINT_Handle的实现。
#include "int_key_led.h"
void EINT_Handle()
{
unsigned long oft = INTOFFSET;
unsigned long val;
GPFDAT &= ~(LED_MSK(0) | LED_MSK(1) | LED_MSK(2));
GPFDAT |= LED_NUM(oft);
//清中断
if( oft == 5 )
EINTPEND = (1<<11); // EINT8_23合用IRQ5
SRCPND = 1 << oft;
INTPND = 1 << oft;
}
main.c
死循环。
int main()
{
while(1);
return 0;
}
Makefile
src := $(shell ls *.c *.s)
obj := $(patsubst %.s, %.o, $(src))
obj := $(patsubst %.c, %.o, $(obj))
int_key_led.bin : $(obj)
arm-linux-ld -Ttext 0x00000000 $^ -o int_key_led_elf
arm-linux-objcopy -O binary -S int_key_led_elf int_key_led.bin
%.o : %.c
arm-linux-gcc -c -o $@ $<
%.o : %.s
arm-linux-gcc -c -o $@ $<
clean :
rm *.o *_elf *.bin