摘自:《嵌入式Linux应用开发完全手册》——韦东山
一、S3C2410/
S3C2440中断体系结构
1)ARM体系CPU的7种工作模式
用户模式(usr):ARM处理器正常的程序执行状态
快速中断模式(fiq):用于高速数据传输或通道处理
中断模式(irq):用于通用的中断处理
管理模式(svc):操作系统使用的保护模式
数据访问终止模式(abt):当数据或指令预取终止时进入该模式,可用于虚拟存储及存储保护
系统模式(sys):运行具有特权的操作系统任务
未定义指令中止模式(und):当未定义的指令执行时进入该模式,可用于支持硬件协处理器的软件仿真
除用户模式外,其他6种工作模式都属于特权模式,大多数程序运行于用户模式,进入特权模式是为了处理中断、异常,或者访问被保护的系统资源。
ARM体系的CPU有以下两个工作状态
ARM状态:此时处理器执行32位的字对齐的ARM指令
Thumb状态:此时处理器执行16位的、半字对齐的Thumb指令
ARM920T有31个通用的32位寄存器和6个程序状态寄存器。这37个寄存器分为7组,如下图所示:
图中R0-R15可以直接访问,这些寄存器除了R15外都是通用寄存器,既可以保存地址也可以保存数据。
R13称为栈指针寄存器,通常用于保存栈指针
R14称为程序连接寄存器(LR),当执行BL子程序调用指令时,R14得到R15(程序计数器PC)的备份。
而当发生中断或异常时,对应的R14_svc,R14_irq,R14_abt或R14_und中保存R15的返回值
R15是程序计数器
快速中断模式有7个备份寄存器R8_fiq-R14_fiq,这使得进入快速中断模式执行很大部分程序时(不改变R0-R7),不需要保存任何寄存器。用户模式、管理模式、数据访问终止模式和未定义指令中止模式都含有两个独占的寄存器副本R13、R14,这样令每个模式拥有自己的栈指针寄存器和连接寄存器。
每种工作模式还有寄存器CPSR(当前程序状态寄存器),它被用于标识各种状态和当前处于哪种工作模式,如下图所示:
当一个异常发生时,将切换进入相应的工作模式,这是ARM920T CPU将自动完成如下工作:
①在异常工作模式的连接寄存器R14中保存前一个工作模式的下一条即将执行的指令地址。对于ARM状态,这个值是当前PC值加4或加8
②将CPSR的值复制到异常模式的SPSR
③将CPSR的工作模式位设为这个异常对应的工作模式
④令PC值等于这个异常模式在异常向量表中的地址,即跳转去执行异常向量表中的相应指令
从异常工作模式回到之前的工作模式时,需要通过软件完成如下事情:
①前面进入异常工作模式时,连接寄存器中保存了前一工作模式的一个指令地址,将它减去适当的值后赋值给PC寄存器
②将SPSR的复制回CPSR
异常模式
退出异常模式时PC的计算方法
进入异常模式时R14中保存的值
管理模式(SWI指令进入)
MOVS PC, R14
PC+4(1)
未定义指令终止模式
MOVS PC, R14
PC+4(1)
快速中断模式
SUBS PC, R14, #4
PC+4(2)
中断模式
SUBS PC, R14, #4
PC+4(2)
数据访问终止模式
异常原因:指令预取终止
SUBS PC, R14, #4
PC+4(1)
异常原因:数据访问终止
SUBS PC, R14, #8
PC+8(3)
注:
(1)PC为这些指令的地址:SWI、未定义的指令、在预取指时就失败的指令
(2)PC为这些指令的地址:进入快速中断模式、中断模式前,被打断而未执行的指令
(3)PC为这些指令的地址:导致数据访问终止的加载/存储指令(LDR、STR、LDM和STM)
二、S3C2440中断控制器
当某事件发生时,硬件会设置某个寄存器,CPU在执行完一个指令时,通过硬件查看这个寄存器,如果发现所关注的事件发生了,则中断当前程序流程,跳转到一个固定的地址处理这个事件,最后返回继续执行被中断的程序。
中断处理的过程:
①中断控制器汇集各类外设发出的中断信号,告诉CPU
②CPU保存当前程序的运行环境,调用中断服务程序(ISR)来处理这些中断
③在ISR中通过读取中断控制器、外设的相关寄存器来识别时哪个中断,并进行相应处理
④清除中断:通过读写中断控制器和外设的相关寄存器来实现
⑤最后恢复被中断程序的运行环境(恢复寄存器),继续执行
s3c2440的中断控制器结构如上图所示:
①request sources(without sub-register)中的中断源被触发后,SRCPND寄存器中相应位被置1,如果此中断没有被INTMSK寄存器屏蔽或者快速中断的话,它将被进一步处理。
② 对于request sources(with sub-register)中的中断源被触发后,SUBSRCPEND寄存器中的相应位被置1,如果此中断没有被INTSUBMSK寄存器屏蔽的话,它在 SRCPND寄存器中的相应位也被置1,以后的处理过程就和①步骤类似。
③如果被触发的中断中有快速中断,INTMOD寄存器中为1的位对应的中断是FIQ,则CPU进入快速中断模式进行处理
④ 对于一般的中断IRQ,可能同时有几个中断被触发,未被INTMSK寄存器屏蔽的中断经过比较后,选出优先级最高的中断,此中断在INTPND寄存器中的相应位被置1,然后CPU进入中断模式进行处理。中断服务程序通过读取INTPND或者INTOFFSET来确定中断源
三、中断控制寄存器
1)SUBSRCPND寄存器
它用来表示INT_RXD0、INT_TXD0等中断是否发生,每位对应一个中断,当这些中断发生并且没有被INTSUBMSK寄存器屏蔽,则它们中的若干位将汇集出现在SRCPND寄存器的某一位上。要清除中断,往此寄存器中某位写1
2)INTSUBMSK寄存器
它用来屏蔽SUBSRCPND寄存器所标识的中断,INTSUBMSK寄存器中某位设置1时,对应的中断被屏蔽
3)SRCPND寄存器
它每一位被用来表示一个或一类中断是否发生,要清除某一位,往此位写1,具体参考数据手册
4)INTMSK寄存器
用来屏蔽SRCPND寄存器所标识的中断。INTMSK寄存器中某位被设为1时,对应的中断被屏蔽,它只能屏蔽IRQ中断,不能屏蔽FIQ
5)INTMOD寄存器
它某位被设为1时,对应的中断被设为FIQ,同一时间,INTMOD只能有一位被设为1
6)PRIORITY寄存器
当有多个IRQ同时发生时,中断控制器选出最高优先级的中断,首先处理它。中断优先级通过7个仲裁器来完成,结构图如下所示:
每个仲裁器基于一个位仲裁器模式控制(ARB_MODE)和选择控制信号(ARB_SEL)的两位来处理 6个中断请求。
如果ARB_SEL位是 00b,优先级是REQ0,REQ1,REQ2,REQ3,REQ4,和REQ5.
如果ARB_SEL位是 01b,优先级是REQ0,REQ2,REQ3,REQ4,REQ1,和REQ5.
如果ARB_SEL位是 10b,优先级是REQ0,REQ3,REQ4,REQ1,REQ2,和REQ5.
如果ARB_SEL位是 11b,优先级是REQ0,REQ4,REQ1,REQ2,REQ3,和REQ5.
注意仲裁器的 REQ0 总是有最高优先级,REQ5 总是有最低优先级。此外通过改变ARB_SEL 位,我们可以翻转 REQ1 到 REQ4 的优先级。如果ARB_MODE位置0,ARB_SEL位不会自动改变,使得仲裁器在一个固定优先级的模式下操作(注意在此模式下,我们通过手工改变 ARB_SEL 位来配置优先级)。另外,如果 ARB_MODE 位是 1,ARB_SEL 位以翻转的方式改变。例如如果 REQ1 被服务,则ARB_SEL位自动的变为01b,把REQ1放到最低的优先级。ARB_SEL变化的详细规则如下:
如果REQ0 或REQ5 被服务,ARB_SEL位完全不会变化。
如果REQ1 被服务,ARB_SEL位变为 01b。
如果REQ2 被服务,ARB_SEL位变为 10b。
如果REQ3 被服务,ARB_SEL位变为 11b。
如果REQ4 被服务,ARB_SEL位变为 00b。
7)INTPND寄存器
经过中断优先级选出优先级最高的中断后,这个中断在INTPND寄存器中的相应位被置1,随后CPU进入中断模式处理它
同一时间,此寄存器只有一位被置1,在ISR中,可以根据这个位确定是哪个中断,清除中断时,往此位写入1
8)INTOFFSET寄存器
用来表示INTPND寄存器中哪位被置1了,即INTPND寄存器中位[x]为1时,INTOFFSET寄存器的值为x(x为0-31)
清除SRCPND、INTPND寄存器时,INTOFFSET寄存器被自动清除
四、中断控制器操作实例:外部中断
开发板上,K1-K4四个按键所接的CPU引脚可以设为外部中断,本程序的功能是,当按下某个按键时,CPU调用中断服务程序点亮对应的LED
@******************************************************************************
@ File:head.S
@ 功能:初始化,设置中断模式、系统模式的栈,设置好中断处理函数
@******************************************************************************
.extern main
.text
.global _start
_start:
@******************************************************************************
@ 中断向量,本程序中,除Reset和HandleIRQ外,其它异常都没有使用
@******************************************************************************
b Reset
@ 0x04: 未定义指令中止模式的向量地址
HandleUndef:
b HandleUndef
@ 0x08: 管理模式的向量地址,通过SWI指令进入此模式
HandleSWI:
b HandleSWI
@ 0x0c: 指令预取终止导致的异常的向量地址
HandlePrefetchAbort:
b HandlePrefetchAbort
@ 0x10: 数据访问终止导致的异常的向量地址
HandleDataAbort:
b HandleDataAbort
@ 0x14: 保留
HandleNotUsed:
b HandleNotUsed
@ 0x18: 中断模式的向量地址
b HandleIRQ
@ 0x1c: 快中断模式的向量地址
HandleFIQ:
b HandleFIQ
Reset:
ldr sp, =4096 @ 设置栈指针,以下都是C函数,调用前需要设好栈
bl disable_watch_dog @ 关闭WATCHDOG,否则CPU会不断重启
msr cpsr_c, #0xd2 @ 进入中断模式,cpsr_c表示cpsr[7:0],0xd2=0b1101 0010
ldr sp, =3072 @ 设置中断模式栈指针
msr cpsr_c, #0xdf @ 进入系统模式
ldr sp, =4096 @ 设置系统模式栈指针,
@ 其实复位之后,CPU就处于系统模式,
@ 前面的“ldr sp, =4096”完成同样的功能,此句可省略
bl init_led @ 初始化LED的GPIO管脚
bl init_irq @ 调用中断初始化函数,在init.c中
msr cpsr_c, #0x5f @ 设置I-bit=0,开IRQ中断
ldr lr, =halt_loop @ 设置返回地址
ldr pc, =main @ 调用main函数
halt_loop:
b halt_loop
HandleIRQ:
sub lr, lr, #4 @ 计算返回地址
stmdb sp!, { r0-r12,lr } @ 保存使用到的寄存器
@ 注意,此时的sp是中断模式的sp
@ 初始值是上面设置的3072
ldr lr, =int_return @ 设置调用ISR即EINT_Handle函数后的返回地址
ldr pc, =EINT_Handle @ 调用中断服务函数,在interrupt.c中
int_return:
ldmia sp!, { r0-r12,pc }^ @ 中断返回, ^表示将spsr的值复制到cpsr
/*
* init.c: 进行一些初始化
*/
#include "s3c24xx.h"
/*
* LED1-4对应GPB5、GPB6、GPB7、GPB8
*/
#define GPB5_out (1<<(5*2)) // LED1
#define GPB6_out (1<<(6*2)) // LED2
#define GPB7_out (1<<(7*2)) // LED3
#define GPB8_out (1<<(8*2)) // LED4
/*
* K1-K4对应GPG0,GPG3,GPG5,GPG6
*/
#define GPG0_eint (2<<(0*2)) // K1,EINT8
#define GPG3_eint (2<<(3*2)) // K2,EINT11
#define GPF5_eint (2<<(5*2)) // K3,EINT13
#define GPF6_eint (2<<(6*2)) // K4,EINT14
/*
* 关闭WATCHDOG,否则CPU会不断重启
*/
void disable_watch_dog(void)
{
WTCON = 0; // 关闭WATCHDOG很简单,往这个寄存器写0即可
}
void init_led(void)
{
GPBCON = GPB5_out | GPB6_out | GPB7_out | GPB8_out ;
}
/*
* 初始化GPIO引脚为外部中断
* GPIO引脚用作外部中断时,默认为低电平触发、IRQ方式(不用设置INTMOD)
*/
void init_irq( )
{
GPGCON = GPG0_eint | GPG3_eint | GPG5_eint | GPG6_eint;
// 对于EINT8、11、13、14,需要在EINTMASK寄存器中使能它们
EINTMASK &= (~(1<<8)) & (~(1<<11)) & (~(1<<13)) & (~(1<<14));
/*
* 设定优先级:
* ARB_SEL0 = 00b, ARB_MODE0 = 0: REQ1 > REQ3,即EINT0 > EINT2
* 仲裁器1、6无需设置
* 最终:
* EINT0 > EINT2 > EINT11,EINT19,即K4 > K3 > K1,K2
* EINT11和EINT19的优先级相同
*/
PRIORITY = (PRIORITY & ((~0x01) | (0x3<<7))) | (0x0 << 7) ;
//开启EINT8_23
INTMSK &= ~(1<<5);
}
interrupt.c
#include "s3c24xx.h"
void EINT_Handle()
{
unsigned long oft = INTOFFSET;
switch( oft )
{
//INTOFFSET为5时,代表INTPND的位[5]为1,则EINT8-23中断发生
case 5:
{
GPBDAT |= (0x0f<<5);
//LED全灭
if (EINTPEND & (1<<8))
//EINT8发生(EINT8对应K1)
GPBDAT &= ~(1<<5);
if (EINTPEND & (1<<11))
//EINT11发生(EINT8对应K2)
GPBDAT &= ~(1<<6);
if (EINTPEND & (1<<13))
//EINT13发生(EINT8对应K3)
GPBDAT &= ~(1<<7);
if (EINTPEND & (1<<14))
//EINT14发生(EINT8对应K4)
GPBDAT &= ~(1<<8);
break;
}
default:
break;
}
//清除中断
if(oft == 5)
EINTPEND = (1<<8) | (1<<11) | (1<<13) | (1<<14);
SRCPND = 1<
INTPND = 1<
}
main.c
int main()
{
while(1);
return 0;
}
Makefile
objs := head.o init.o interrupt.o main.o
int.bin: $(objs)
arm-linux-ld -Ttext 0x00000000 -o int_elf $^
arm-linux-objcopy -O binary -S int_elf $@
arm-linux-objdump -D -m arm int_elf > int.dis
%.o:%.c
arm-linux-gcc -Wall -O2 -c -o $@ $<
%.o:%.S
arm-linux-gcc -Wall -O2 -c -o $@ $<
clean:
rm -f int.bin int_elf int.dis *.o