本章目标:
了解ARM体系CPU的7种工作模式
了解S3C2410/S3C2440中断体系结构
掌握S3C2410/S3C2440的中断服务程序的编写方法
9.1 S3C2410/S3C2440 中断体系结构
9.1.1 ARM体系CPU 的7种工作模式
ARM体系的CPU有以下7种工作模式:
① 用户模式(usr):ARM处理器正常的程序执行状态;
② 快速中断模式(fiq):用于高速数据传输或通道处理;
③ 中断模式(irq):用于通用的中断处理;
④ 管理模式(svc):操作系统使用的保护模式;
⑤ 数据访问终止模式(abt):当数据或指令预取终止时进入该模式,可用于虚拟存储及
存储保护;
⑥ 系统模式(sys):运行具有特权的操作系统任务;
⑦ 未定义指令中止模式(und):当未定义的指令执行时进入该模式,可用于支持硬件协
处理器的软件仿真。
可以通过软件来进行模式切换,或者发生各类中断、异常时CPU自动进入相应的模式。
除了用户模式外,其他6种工作模式属于特权模式。大多数程序运行与用户模式,进入特权
模式是为了处理中断、异常,或者访问被保护的系统资源。
另外,ARM体系的CPU有以下两种工作状态。
① ARM状态:此时处理器执行32位的字对齐的ARM指令;
② Thumb状态:此时处理器执行16位的、半字对齐的Thumb指令。
实际上,本书所有程序都是在ARM状态下运行的,而CPU一上电就处于ARM状态,所
以无需关心CPU的状态。
ARM920T有31个通用的32位寄存器和6个程序状态寄存器。这37个寄存器分为7组,进
入某个工作模式时,就是用它那组的寄存器。有些寄存器,不同的工作模式下有自己的副
本,当切换到另一个模式时,那个模式的寄存器副本将被使用:这些寄存器被称为备份寄
存器(图9.1中使用灰色三角形标记的寄存器)。
图中R0~R15可以直接访问,这些寄存器中除R15外都是通用寄存器,即它们既可以
用于保存数据,也可以用于保存地址。另外,R13~R15稍有不同。
R13被称为“ 栈指针 ” 寄存器,通常被用来保存栈指针;
R14被称为“ 程序连接 ”寄存器或“ 连接 ”寄存器,当执行BL子程序调用指令时,R14中
得到R15(程序计数器PC)的备份。而发生中断或异常时,对应的R14_svr、R14_irq、
R14_fiq、R14_abt或R14_und中保存R15返回值。
R15是程序计数器。
快速中断模式有7个备份寄存器R8~R14(即R8_fiq~R14_fiq),这使得进入快速中断模式
执行很大部分程序时(只要它们不改变R0~R7),甚至不需要保存任何寄存器。用户模式、
管理模式、数据访问终止模式和未定义指令中止模式都含有两个独占的寄存器副本R13和
R14,这样可以令每个模式拥有自己的栈指针寄存器和连接寄存器。
每种工作模式除R0~R15共16个寄存器外,还有第17个寄存器——CPSR,即“当前程序
状态寄存器”。其中一些位被用来标识各种状态,一些位被用来标识当前处于什么工作模式。
CPSR中各位意义如下,如图9.2所示。
(1)T位:置位时,CPU处于Thumb状态:否则处于ARM状态。
(2)中断禁止位:I位和F位属于中断禁止位。它们被置位时,IRQ中断、FIQ中断分别
被禁止。
(3)工作模式位:表明CPU当前处于什么工作模式。可以编写这些位,使CPU进入指
定的工作模式。
除CPSR外,还有快速中断模式、中断模式、管理模式、数据访问终止模式和未定义指
令中止模式等5种工作模式和一个寄存器——SPSR(程序状态保存寄存器)。当切换进这些
模式时,在SPSR中保存前一个模式的CPSR值,返回前一个模式时,将SPSR的值恢复到
CPSR中。
综上所述,当一个异常发生时,将切换进入相应的工作模式(异常模式),这时ARM920T
CPU核将自动完成如下事情:
(1)在异常模式的连接寄存器R14中保存前一个模式的下一条即将执行的指令地址。
对于ARM状态,这个值是当前PC值加4或加8(参考表9.1);
(2)将CPSR的值复制到异常模式的SPSR;
(3)将CPSR的工作模式位设为这个异常对应的工作模式;
(4)令PC值等于这个异常模式在异常向量表中的地址,即跳转去执行异常向量表中的相应指令。
相反地,从异常模式返回之前的工作模式时,需要通过软件完成如下事情:
(1)将之前保存在连接寄存器中的前一工作模式的一个指令地址减去一个适当的值(参考
表9.1)后赋给PC寄存器;
(2)将SPSR的值复制回CPSR;
9.1.2 S3C2410、S3C2440中断控制器
对于不同的CPU,中断的处理只是细节不同。S3C2410/S3C2440的中断控制器结构
如图9.4所示,可以看出中断的处理细节。
SUBSRCPND和SRCPND寄存器表明有哪些中断被触发了,正在等待处理(pending);
SUBMASK(INTSUBMSK寄存器)和MASK(INTMSK寄存器)用于屏蔽某些中断。
图中“Request sources(with sub-register)”表示INT_RXD0、INT_TXD0等中断源(S3C2410
中这类中断有11个,而S3C2440中有15个)。它们不同于“Request sources(without sub-register)”。
(1)“ Request sources(without sub-register)”中的中断源被触发之后,SRCPND寄存器中
相应位被置1,如果此中断没有被INTMSK寄存器屏蔽掉或快速中断的话,它将被进一步处理。
(2)对于“Request sources(with sub-register)”中的中断源被触发之后,SUBSRCPND寄存器
中的相应位被置1,如果此中断没有被INTSUBMSK寄存器屏蔽的话,它在SRCPND寄存器中
的相应位也被置1,之后的处理过程就和“Request sources(without sub-register)”一样了。
继续沿着图9.4的箭头前进:在SRCPND寄存器中,被触发的中断的相应位被置1,等待处理。
(1)如果被触发的中断中有快速中断(FIQ)——MODE (INTMOD寄存器)中为1的位对应的
中断时FIQ,则CPU进入快速中断模式(FIQ Mode)进行处理。
注意:FIQ只能分配一个,即INTMOD中只能有一位设为1。
(2)对于一般中断IRQ,可能同时又几个中断触发,未被INTMSK寄存器屏蔽的中断经过比较
后,选出优先级最高的中断,此时中断在INTPND寄存器中的相应位被置1,然后CPU进入中断模
式(IRQ Mode)进行处理。中断处理服务程序可以通过读取INTPND寄存器或者INTOFFSET寄存器
来确定中断源。
图9.4中的“Priority”表示中断的优先级判断,通过PRIORITY寄存器进行设置,这在9.1.3小节介绍。
综上所述,使用中断的步骤如下:
(1)设置好中断模式和快速终端模式下的栈:
当发生 中断IRQ时,CPU进入 中断模式, 这时使用 中断模式下的栈;
当发生快速中断FIQ时,CPU进入快速中断模式,这时使用快速中断模式下的栈。
(2)准备好中断处理函数。
① 异常向量。
在异常向量表中设置好当进入中断模式或快速中断模式时的跳转函数,它们的异常向量
地址分别为:0x0000 0018、0x0000 001c。
② 中断服务程序(ISR)。
IRQ、FIQ的跳转函数,最终将调用具体中断的服务函数。
对于IRQ,读取INTPND寄存器或INTOFFSET寄存器的值来确定中断源,然后分别处理。
对于FIQ,因为只有一个中断可以设为FIQ,无须判断中断源。
③ 清除中断:如果不清除中断,则CPU会误认为有发生了一次这个中断。
可以调用ISR之前清除中断,也可以在调用ISR之后清除,这取决于在ISR执行过程中,这
个中断是否可能继续发生、是否能够丢弃。
如果可能发生并不能丢弃,则在调用ISR之前清除 中断,这样在ISR执行过程中发生的中断
能够被各寄存器再次记录并通知CPU;
如果不会发生 或者可以丢弃,则在调用ISR之后清除中断。
(3)进入、退出中断模式或快速中断模式时,需要保存、恢复中断程序的运行环境。
① 对于IRQ,进入和退出的代码如下:
1 sub lr, lr, #4 @计算返回地址 2 stmdb sp!, {r0-r12, lr} @保存使用的寄存器 3 ... ... @处理中断 4 ldmia sp!, {r0-r12, pc}^ @中断返回 5 @^表示将spsr的值赋给cpsr
② 对于FIQ,进入和退出的代码如下:
1 sub lr, lr #4 @计算计算返回地址 2 stmdb sp!, {r0-r7, lr} @保存使用到的寄存器 3 ... ... @快速处理中断 4 sdmia sp!, {r0-r7, pc}^ @快速中断返回 5 @^表示将spsr的值赋给cpsr
(4)根据具体中断,设置相关外设。比如对于GPIO中断,需要将相应引脚的功能设为
“外部中断”、设置中断触发条件(低电平触发、高电平触发、下降沿触发还是上升沿触发)
等。一些中断拥有自己的屏蔽寄存器,还要开启它。
(5)对于“Request sources(without sub-register)”中的中断,将INTSUBMSK寄存
器中相应位设为0。
(6)确定使用此中断的方式:FIQ还是IRQ。
① 如果是FIQ,则在INTMOD寄存器中设置相应位为1;
② 如果是IRQ,则在RIOPITY寄存器中设置优先级;
(7)如果是IRQ,将INTMSK寄存器中相应位设为0(FIQ不受INTMSK寄存器控制);
(8)设置CPSR寄存器中的I-bit(对于IRQ)或F-bit(对于FIQ)为0,使能IRQ或FIQ。
9.1.3 中断控制寄存器
SUBSRCPND、INTSUBMSK这两个寄存器中相同的位对应相同的中断;
SRCPND、INTMSK、INTMOD、INTPND这4个寄存器中相同的位对应相同的中断。
下面沿着图9.4的箭头方向讲解所涉及的寄存器。
1.SUBSRCPND寄存器(SUB SOURCE PENDING)
SUBSRCPND寄存器被用来标识INT_RXD0、INT_TXD0等中断(S3C2410中这类中断有11
个,而S3C2440中有15个)是否发生,每位对应一个中断。当这些中断发生并且被INTSUBMSK
寄存器(下面介绍)屏蔽,则它们中的若干位将“汇集”出现在SRCPND寄存器(下面介绍)的
一位上。比如SUBSRCPND寄存器中的3个中断INT_RXD0、INT_TXD0、INT_ERR0,只要有一个
发生了且没有被屏蔽,则SRCPND寄存器中的INT_UART0位被置1。
要清除中断时,向SUBSRCPND寄存器中某位写入1即可令此位为0;写入0无效,数据
不变。
SUBSRCPND寄存器中各位对应的中断、SUBSRCPND寄存器中哪几位“汇集”成SRCPND
寄存器中的哪一位,请参考数据手册。
2.INTSUBMSK寄存器
被用来屏蔽SUBSRCPND寄存器所标识的中断。其中的某位被设为1时,对应中断被屏蔽。
3.SRCPND寄
存器
其中的每一位被用来表示一个(或一类)中断是否已经发生,即图9.4中输入“SRCPND”
的两类中断:使用SUBSRCPND/INTSUBMSK控制的中断;不适用SUBSRCPND/INTSUBMSK控制的中
断。
SRCPND寄存器的操作与SUBSRCPND寄存器类似,想相除某一位,向该位写入1。
SRCPND中各位对应哪个(哪类)中断,请参考数据手册。
4.INTMSK寄存器
被用来屏蔽SRCPND寄存器所标识的中断。 其中某些位被设为1时,对应中断被屏蔽。
本寄存器只能屏蔽被设为IRQ的中断,不能屏蔽被设为FIQ的中断,请参考下面的
INTMOD寄存器。
5.INTMOD寄存器
本寄存器中某位被设为1时,对应中断被设为FIQ,即此中断发生时,CPU进入FIQ
模式,这通常用来处理特别紧急的中断。
注意:同一时间内,INTMOD中只能有一位被设为1。
6.PRIORITY寄存器
上面INTMOD寄存器中,将设为1的中断成为快速中断(FIQ),其余为0的中断成为普通
中断(IRQ)。
当有多个普通中断同时发生时,中断控制器将选出最高优先级的中断,首先处理它。
优先级判断通过7个仲裁器完成,包括6个一级和1个二级仲裁器,结构图如图9.5所示。
每个仲裁器含6个输入引脚REQ0~REQ5。对于每个仲裁器,PRIORITY寄存器使用3位来
控制其行为:一位被用于选择仲裁器的工作模式,被称为ARB_MODE;两位被用于控制各输入
信号的优先级,称为ARB_SEL。
SRB_SEL的取值和REQ0~REQ5的优先级如表9.2所示。
当某个仲裁器的ARB_MODE位被设为0时,它的ARB_SEL位是不会自动变化的,此时这个
仲裁器的6个输入引脚的优先级固定不变(当然,可以通过软件修改ARB_SEL来改变它们的优
先级)。当ARB_MODE位被设为1时,ARB_SEL会随着“已经被服务的REQx(x:1~4)”自动变化,
顺序如表9.3所示。
结合表9.2、表9.3可知:当ARB_MODE为1时,某个REQx(x:1~4)被服务之后,它的
优先级变为REQ0~REQ4中的最低。
PRIORITY寄存器中位[0:6]对应这7个仲裁器的ARB_MODE位(位[0]是ARB_MODE0,依
此类推),位[7:20]位对应这7个仲裁器的ARB_SEL位([7:8]是ARB_SEL0,依此类推)。
7.INTPND寄存器
经过中断优先级仲裁器选出优先级最高的中断后,这个中断在本寄存器中的相应
位被置1,随后,CPU进入中断模式处理它。
同一时间内,此寄存器只有一位被置1;在ISR中,可以根据这个位确定是哪个中断。
清除中断时,向这个位写入1。
8.INTOFFSET寄存器
这个寄存器被用来表示INTPND寄存器中哪位被置1了,即INTPND寄存器中位[x]为1时,
INTOFFSET寄存器的值为x(x为0~31)。
在清除SRCPND、INTPND寄存器时,本寄存器被自动清除。
9.2 中断控制器操作实例:外部中断
9.2.1 按键中断代码详解
开发板上,K1~K4四个按键所接的CPU引脚可以设成外部中断功能。本程序的main
函数是一个不做任何事的无线循环,程序的功能完全靠中断来驱动:当按下某个按键时,
CPU调用其中断服务程序来点亮对应的LED。
程序代码在/work/hardware/int目录下,有4个源文件:head.S、init.c、interrupt.c和
main.c,1个头文件s3c24xx.h。
1.head.S代码详解
先看head.S文件:
1 @************************************* 2 @File:head.S 3 @功能:初始化、设置中断模式、系统模式的栈,设置好中断处理函数 4 @************************************* 5 6 .extern main 7 .text 8 .global _start 9 _start: 10 @************************************* 11 @中断向量,本程序中,除了Reset和HandleIRQ外,其他异常都没有使用 12 @************************************* 13 b Reset 14 15 @ 0x04:未定义指令终止模式的向量地址 16 HandleUndef: 17 b HandleUndef 18 19 @ 0x08:管理模式的向量地址,通过SWI指令进入此模式 20 HandleSWI: 21 b HandleSWI 22 23 @ 0x0c:指令预取终止导致的异常向量的向量地址 24 HandlePrefetchAbort: 25 b HandlePrefetchAbort 26 27 @ 0x10:数据访问终止导致的异常向量地址 28 HandleDataAbort: 29 b HandleDataAbort 30 31 @ 0x14:保留 32 HandleNotUsed: 33 b HandleNotUsed 34 35 @ 0x18:中断模式的向量地址 36 b HandleIRQ 37 38 @ 0x1c:快速中断模式的向量地址 39 HandleFIQ: 40 b HandleFIQ 41
上面的第17、21、25、29、33、36和40行等共7条指令所在地址为0x00、0x04、......、
0x1c,这些地址上的指令被称为“异常向量”。当发生各类异常时,CPU进入对应的工作模
式,并跳转去执行它的“异常向量”。比如,当复位时,CPU进入系统模式,并跳转到0x00
地址开始执行;当发生中断时,CPU进入中断模式,并跳到0x18地址开始执行。
本程序中,只使用到“复位”和“中断”对应异常向量,其他异常向量没有实际作用。
0x00地址处的指令为“ b Reset ”,在系统复位后,这条指令将跳去执行标号“Reset”开始
的代码,它们完成一些初始化,代码如下:
1 接上面 2 42行 Reset: 3 43行 ldr sp, =4096 @设置栈指针,以下都是C函数,需要在调用前设置好栈 4 44行 bl disable_watch_dog @关闭看门狗 5 45行 6 46行 msr cpsr_c, #0xd2 @进入中断模式 7 47行 ldr sp, =3072 @设置中断模式栈指针 8 48行 9 49行 msr cpsr_c, #0xdf @进入系统模式 10 50行 ldr sp, =4096 @设置系统模式栈指针 11 51行 @起始复位之后,CPU就处于系统模式 12 52行 @前面的“ldr sp, =4096”完成同样的功能,此句可省略 13 53行
注意,这是还没有完成所有初始化,所以还不能开中断——第46、49行的代码中,CPSR寄存
器的I位、F位都被设为1.第47、50行中sp寄存器并不是同一个寄存器,前者为sp_irq,后者为sp_sys。
继续向下看代码:
接上面 54行 bl init_led @初始化LED的GPIO管脚 55行 bl init_irq @调用中断管脚初始化函数,在init.c中 56行 msr cpsr_c, #0x5f @设置I-bit=0,开启IRQ中断 57行 58行 ldr lr, =halt_loop @设置返回地址 59行 ldr pc, =main @调用main函数 60行 halt_loop: 61行 b halt_loop
main函数中不做任何事的无限循环,当按下按键时,这个循环被打断,CPU进入中断模式,执行
第36行的“ b HandleIRQ ”的指令。
标号“HandleIRQ”开始的代码用于处理中断,如下所示:
1 接上面 2 62行 HandleIRQ: 3 63行 sub lr, lr #4 @计算返回地址 4 64行 stmdb sp!, {r0-r12, lr} @保存使用到的寄存器 5 65行 @注意,此时的sp是中断模式的sp 6 66行 @初始值是上面设置的3072 7 67行 8 68行 ldr lr, =int_return @设置调用ISR即EINT_Handle函数后的返回地址,即为第71行指令的地址 9 69行 ldr pc, =EINT_Handle @调用中断服务函数,在interrupt.c中 10 70行 int_return: 11 71行 ldmia sp!, {r0-r12, pc}^ @中断返回,^表示将spsr的值复制到cpsr 12 72行
第63行请参考表9.1,lr寄存器的值等于被中断指令的地址加4,所以返回地址为lr-4。
第64行用于保存被中断程序的运行环境,即各个寄存器。其中的sp为中断模式的栈。
在上面的47行初始化。这样,r0~r12、lr这14个寄存器被保存在中断模式的栈中。
第69行调用中断服务函数EINT_Handle(代码在interrupt.c中,下面详述)。
当EINT_Handle函数处理完所有发生的中断后,返回71行的指令。它恢复前面第64行
保存的各个寄存器,即恢复被中断程序的运行环境 :从栈中恢复r0~r12、pc这14个寄存器
的值,同时,将SPSR寄存器的值复制到CPSR(在进入中断模式时,CPU自动将原来的
CPSR的值保存到SPSR中),这导致CPU切换到原来的工作模式。
2.init.c中与中断相关的代码详解
下面详细讲述前面略过的init_irq函数,它在init.c中
1 37行 /* 2 38行 * 初始化GPIO引脚为外部中断 3 39行 * GPIO引脚为外部中断时,默认低电平触发,IRQ方式(不用设置INTMOD) 4 40行 */ 5 41行 void init_irq() 6 42行 { 7 // S2,S3对应的2根引脚设为中断引脚 EINT0,ENT2 8 43行 GPFCON &= ~(GPF0_msk | GPF2_msk); 9 44行 GPFCON |= GPF0_eint | GPF2_eint; 10 // S4对应的引脚设为中断引脚EINT11 11 GPGCON &= ~GPG3_msk; 12 GPGCON |= GPG3_eint; 13 45行 14 46行 //对于EINT11,需要在EINTMASK寄存器中使能它们 15 47行 EINTMASK &= ~(1<<11); 16 48行 17 49行 /* 18 50行 *设定优先级 19 51行 *ARB_SEL0 = 00b,ARB_MODE0 = 0;REQ1 > REQ3,即EINT0 > EINT2 20 52行 *仲裁器1、6无须设置 21 53行 *最终: 22 54行 *EINT0 > EINT2 > EINT11, EINT19、即K4 > K3 > K1、K2 23 55行 *EINT11和EINT19的优先级相同 24 56行 */ 25 57行 PRIORITY = (PRIORITY & ((~0x01) | (0x3 << 7))) | (0x0 << 7); 26 58行 27 59行 //EINT0、EINT2、EINT8_23使能 28 60行 INTMSK &= (~(1 << 0)) & (~(1 << 2)) & (~(1 << 5)); 29 61行 }
使用GPIO的中断功能时,需要确定它们的中断触发方式,我们使用默认的低电平触发,
所以无须额外设置。
第47行在EINTMASK寄存器中开启EINT11和19中断,EINT0和EINT2中断不受
EINTMASK寄存器控制。这个寄存器可以屏蔽的中断请参考数据手册。
第57行用于设置中断优先级。请参考图9.5,EINT0和2被接到仲裁器0的REQ1、REQ3,
程序中设置ARB_SEL0为0(即0b00,参考表9.2),所以REQ1的优先级高于REQ3。
程序中 设置ARB_MODE0为0,所以仲裁器0中各优先级保持不变。
EINT8~EINT23公用仲裁器1的 REQ1。
仲裁器0、1的输出接到仲裁器6的REQ0、REQ1,而仲裁器的REQ0优先级总是高于REQ1,
所以这4个控制的优先级如下:EINT0 > EINT2 > EINT11。
本程序使用GPIO默认的中断模式——IRQ方式(不是FIQ),所以不用设置INTMOD寄存器。
最后,在第60行,将INTMSK寄存器中EINT0、2、8_23这3个(类)中断对应的位设为0,
使能中断(注意,即使在这里,中断仍未完全开启,head.S第56行才打开最后一个开关)。
3.interrupt.c中的中断处理函数
上面讲解了有关的初始化、中断的进入和退出,真正的处理函数为EINT_Handle,它被称
为中断服务程序(ISR),代码在interrupt.c中,如下所示:
1 #include "s3c24xx.h" 2 3 void EINT_Handle() 4 { 5 unsigned long oft = INTOFFSET; 6 unsigned long val; 7 8 switch( oft ) 9 { 10 // S2被按下 11 case 0: 12 { 13 GPFDAT |= (0x7<<4); // 所有LED熄灭 14 GPFDAT &= ~(1<<4); // LED1点亮 15 break; 16 } 17 18 // S3被按下 19 case 2: 20 { 21 GPFDAT |= (0x7<<4); // 所有LED熄灭 22 GPFDAT &= ~(1<<5); // LED2点亮 23 break; 24 } 25 26 // K4被按下 27 case 5: 28 { 29 GPFDAT |= (0x7<<4); // 所有LED熄灭 30 GPFDAT &= ~(1<<6); // LED4点亮 31 break; 32 } 33 34 default: 35 break; 36 } 37 38 //清中断 39 if( oft == 5 ) 40 EINTPEND = (1<<11); // EINT8_23合用IRQ5 41 SRCPND = 1<<oft; 42 INTPND = 1<<oft; 43 }
第5行用来读取INTOFFSET寄存器,用来标识INTPND寄存器中哪位被设为1。此值位0时
表示INTPND寄存器的位[0]为1,即EINT0中断发生了,这说明K4被按下,以此类推。
注意:清除中断时,顺序很重要,先EINTPEND,然后是SRCPND,最后是INTPND。
4.主函数
在main.c中什么也不做,如下所示:
1 int main() 2 { 3 while(1); 4 return 0; 5 }
为了更形象地了解本程序,下面用图9.6.来演示代码的执行过程,注意其中SP、PC寄存器的变化。
在源码目录INT下执行make命令生成int.bin,烧入NAND Flash后,按下复位键启动。轮流按下K1~K4,
LED1~LED4被轮流点亮;同时按下K3、K4时,只有LED4被点亮。
附:代码:
链接: https://pan.baidu.com/s/1kV24a9L 密码: tfab