ARM中断基础知识

发布者:喜从中来最新更新时间:2021-10-19 来源: eefocus关键字:ARM  中断  基础知识 手机看文章 扫描二维码
随时随地手机看文章

一、ARM内核工作模式

因为中断会设计到ARM内核工作模式的切换,所以先简要介绍一下各个模式:

ARM模式的切换要设计到寄存器CPSR,下面是各个位表示的含义,CPSR[4:0]是工作模式切换控制位。

T=0时是ARM指令模式,T=1时是Thumb指令模式。

F=0时是允许FIQ,F=1是禁止FIQ

I=0时是允许IRQ,I=1是禁止IRQ

在开发板刚刚启动起来的时候首先得关闭所有中断,等把开发板的硬件初始化,各种参数设置好之后就可以打开中断了。


CPSR有4个8位区域:标志域(F)、状态域(S)、扩展域(X)、控制域(C)

C 控制域屏蔽字节( CPSR[7:0] )

X 扩展域屏蔽字节( CPSR[15:8] )

S 状态域屏蔽字节( CPSR[23:16] )

F 标志域屏蔽字节( CPSR[31:24] )

常用于MRS或MSR指令,用于cpsr中的值转移到寄存器或把寄存器的内容加载到cpsr中.

如: MSR CPSR_c,#0xd3

ARM 内核工作模式的切换是要自己手动切换的, 设置CPSR 寄存器低5位进行切换(第五位始终是1)

cpsr[4:0]

处理器模式

英文表示

1,0000

用户模式

usr

1,0001

快速中断模式

fiq

1,0010

中断模式

irq

1,0011

管理模式

svc

1,0111

中止模式

abt

1,1011

未定义

und

1,1111

系统模式

sys

CPU刚刚复位后的模式是管理模式(SVC),如果我们写纯裸机代码(不使用uboot),刚开始是在stepping stone中运行的,我们在这个里面得完成代码的一些硬件的初始化,比如时钟初始化,sdram初始化,nandflash初始化(从nandflash启动)等等,然后把程序从nandflash中拷贝到sdram中运行,在这段启动代码中我们免不了要初始化各种堆栈,而且是各个模式下的堆栈最好都提前设置好,这里就要考虑各种因素了。


比如我们是在OK6410平台上面测试,当复位或者开机后处于8K 大小的stepping stone中,此时我们设置堆栈是SVC下的堆栈,如果我们要设置其他模式下的堆栈的话我们就必须手动的设置CPSR下的模式位,来跳转到各个模式下设定好对应的堆栈。因为以后是要在sdram中运行了,所以这些堆栈应当设定为sdram中的地址。OK6410中的sdram是256M的,所以我们可以从顶部开始设置,比如说切换到irq模式,把堆栈设置为SP = 0x60000000 (因为S3C6410中内存起始地址是从0x50000000开始的,加上256M,即0x10000000,得到栈顶为0x60000000),然后切换到fiq模式下,把堆栈设置为SP = 0x5f000000,一次类推。但是要注意的是,我们还在stepping stone中运行的时候SVC的堆栈还是得保持在片内内存的顶部即SP = 8*1024,直到要跳转到sdram中运行的时候了才把SVC的堆栈设置到sdram中去。


有些人喜欢刚开始只设置SVC的堆栈(因为刚开启的时候就是这个模式,所以必须设置一下),而不设置别的模式的堆栈,等到中断发生了之后,在中断函数中设置堆栈的地址。我觉得这样有些不妥,提前都设置好,以后中断进来后就省去了每次设置堆栈的步骤,提高了效率。


中断向量表:

以下代码就是切换CPU工作模式的示例

;********** Begin init stact ***********/

;在6种模式下切换并设置堆栈指针

MRS R0,CPSR ;把CPSR读取到R0

BIC R0,#0x1f ;低5位清零

LDR R1,=MODE_Fiq ;设置R1 为0b10001,跳转到fiq模式

ORR R0,R0,R1 ;R0和R1相或,设置低5位

MSR CPSR_c,R0 ;把R0的值重新赋值到CPSR

LDR SP,=Stact_Fiq ;设置fiq的栈指针

BIC R0,#0x1f ;低5位清零

LDR R1,=MODE_Irq ;跳转Irq模式

ORR R0,R0,R1

MSR CPSR_c,R0

LDR SP,=Stact_Irq ;设置irq的栈指针

BIC R0,#0x1f

LDR R1,=MODE_Svc ;跳转到svc模式

ORR R0,R0,R1

MSR CPSR_c,R0

LDR SP,=Stact_Svc ;设置svc的栈指针

BIC R0,#0x1f

LDR R1,=MODE_Abort ;跳转到abort模式

ORR R0,R0,R1

MSR CPSR_c,R0

LDR SP,=Stact_Abort ;设置abort的栈指针

BIC R0,#0x1f

LDR R1,=MODE_Undef ;跳转到undef模式

ORR R0,R0,R1

MSR CPSR_c,R0

LDR SP,=Stact_Undef ;设置undef栈指针

BIC R0,#0x1f

LDR R1,=MODE_Sys ;跳转到sys模式

ORR R0,R0,R1

MSR CPSR_c,R0

LDR SP,=Stact_Sys ;设置sys栈指针

与S3C2440相比,S3C6410增加中断向量控制器,这样在S3C2440里需要用软件来跳转的中断处理机制,在S3C6410中可以完全由硬件来跳转。只要把ISR(中断处理函数)地址存在连续向量寄存器空间,而不必像在S3C2440自行分配空间进行管理。换句话说,在S3C2440下是由cpu触发IRQ/FIQ异常,由异常处理函数里再查找相关中断寄存器来跳到指定的ISR,而可以全部由S3C6410的VIC硬件来自动处理。这样就大大简化了中断处理编程。


另外一变化是S3C6410外部中断加入了滤波电路,这样原来需要软件去毛刺的地方均可以采用硬件来进行滤波了,这样大大简化了外部中断处理。 

 

S3C6410具有187个多功能IO端口,其中有127个用于外部中断。这127个引脚可以分为10组:

外部中断分组

对应GPIO

External interrupt Group 0

GPN0~GPN15 GPL8~GPL14 GPM0~GPM4

(16+7+5=28,所以EINT0PEND有28bit来识别这28个中断)

External interrupt Group 1

GPA0~GPA7 GPB0~GPB6

External interrupt Group 2

GPC0~GPC7

External interrupt Group 3

GPD0~GPD5

External interrupt Group 4

GPF0~GPF14

External interrupt Group 5

GPG0~GPG7

External interrupt Group 6

GPH0~GPH9

External interrupt Group 7

GPO0~GPO15

External interrupt Group 8

GPP0~GPP14

External interrupt Group 9

GPQ0~GPQ9

6410支持64种中断源,即有64个中断号。INT_EINT0仅仅对应0号中断,INT_EINT0又包含几个中断。


在VIC中,10组外部中断占用的中断源情况如下:

中断号中断源对应外部中断VIC组
0INT_EINT0External interrupt 0~3VIC0
1INT_EINT1External interrupt 4~11VIC0
32INT_EINT2External interrupt 12~19VIC1
33INT_EINT3External interrupt 20~27VIC1
53INT_EINT4External interrupt group1~group9VIC1

举个例子(OK6410的6个按键做外部中断):

按键对应引脚对应外部中断中断源
KEY1GPN0External interrupt 0INT_EINT0
KEY2GPN1External interrupt 1INT_EINT0
KEY3GPN2External interrupt 2INT_EINT0
KEY4GPN3External interrupt 3INT_EINT0
KEY5GPN4External interrupt 4INT_EINT1
KEY6GPN5External interrupt 5INT_EINT1

KEY1~KEY4都是属于INT_EINT0,即都是属于中断号为0的中断,这四个按键产生的外部中断所调用的中断处理函数的地址都是存在VIC0VECTADDR0寄存器中的,这四个随便哪个按下都会调用同一个中断处理函数,所以在处理函数里面必须判别是哪个按键所触发的中断。


中断相关寄存器的设计演变(寄存器以外部按键中断为例)


IC

ARM7(4510)

IC

ARM9(2440)

IC

ARM11(6410)

内核

(core)

CPSR I-bitCPSR I-bit

CPSR I-bit

VIC Port(Enable)

VIC interface(PC <--> A0~A31)

中断控制器

(IC)

INTMOD

INTPND

INTMSK

INTOFFSET

INTPRI

INTPND

INTMOD

INTMSK

SRCPND

VectADDRESS(32bit -> A0~A31,中断函数地址的注册 )

Vectors(handlers中断处理函数)

Priority(优先级的判别)

VIC0IRQSTATUS/VIC0FIQSTATUS(IRQ/FIQ的中断悬起位)

VIC0INTSELECT (选择是IRQ还是FIQ模式)

VIC0INTENABLE(MASK功能)

VIC0RAWINTR(显示FIQ中断是否置位,从EINT0PND上传输过来的)

中断源控制器

(GPIO)

EINTCON

(F/R/L)

GPXCON

(EINT)

EINTCON

(F/R/L)

GPXCON

(EXIT)

INTMASK

EINT0PND (需要手动清除)

EINT0CON0  (Low/High level Falling/Rising/Both edge)

GPxCON  (EINT)

硬件层key/UART/USB/Timerkey/UART/USB/Timerkey/UART/USB/Timer

我用的是OK6410,分析一下最后一列:

最后一列从下到上,是从最外层一直到内核各个寄存器的顺序,以外部key按键中断为例(GPN0~GPN5-->key1~key6)

中断源 GPIO Controller

GPNCON [1:0] --> key1 Set the pin mux function as Ext.Interrupt[0]

00 = Input

01 = Output

* 10 = Ext. Interrupt[0]

11 = Key pad ROW[0]

EINT0CON0[2:0] --> EINT1,0 Sets the signaling method of Ext.Interrupt[0]

000 = Low level

001 = High level

* 01x = Falling edge triggered

10x = Rising edge triggered

11x = Both edge triggered

EINT0PEND[0]

0 = Not occur

1 = Occur interrupt

EINT0MASK[0]

* 0 = Enables Interrupt

1 = Masked

我们这里的中断是使用中断向量控制器VIC

如何测试和中断相关的各个寄存器到底是怎么工作的,上下层的相互关系是怎么样的?

先看下面的关系图(从下到上越来越接近cpu内核): 

 

首先我们不采用中断方式来看各个寄存器的变化,我们用查询的方式查看,以按键中断为例,当有按键按下的时候,各个寄存器怎么变化,相互之间的关系如何:

准备一个能够在串口输出字符的程序。


OK6410的KEY1是接在 GPN0上面的,通过GPNCON把它设置成中断方式,且通过EINT0CON0设置成下降沿触发方式,代码如下:

 

01/* set GPNIO to EINT mode , 10 --> Eint */
02temp = GPNCON ;
03temp &= ~(0x3<<0);
04temp |= (0x2<<0);
05GPNCON = temp;
06
07/* set EINT triger mode to falling eage ,01x = Falling edge */
08temp = EINT0CON0 ;
09temp &= ~(0x7<<0);
10temp |= (0x3<<0);
11EINT0CON0 = temp;

当按下KEY的时候,会触发中断,并且把这个信号传送到最下层的中断悬起位寄存器EINT0PEND,这个寄存器不同的bit对应不同的中断。EINT0PEND的上层是中断屏蔽寄存器INTMASK,当这个寄存器设置为屏蔽的时候中断触发信号是无法通过它传送到上层去的。我们先来看看当中断发生的时候EINT0PEND是如何变化的,他的变化会带来怎么样的结果。 

 

01while (1)
02{
03    for(ch='a';ch<='z';ch++){
04        if( (EINT0PEND & 0x1)==0x1){ //轮询EINT0PEND有没有被置位
05              uart_putchar('+');
06        }
07        uart_putchar(ch);
08        delay();
09     }
10}

 

上面的程序是在不停的查询EINT0PEND[0],该bit对应的是外部中断0,如果按下KEY产生一个下降沿,对应位会置位,if语句满足,会打印出一个“+”,然后出来打印字母,按键松开后是否还会满足if呢?看现象:

发现按下一次之后“+”会不停的打印出来,也就是说if语句都是满足的,这就意味着EINT0PEND置位之后没有被清除,每次查询都还有,所以会不停的打印加号,所以这个寄存器我们必须手动清除,否则触发一次中断后就会不停的响应该中断。

清除的方式就是向对应位写1,我们这里是:

至于清除中断悬起位的方式:

3种清0的写法,只有最后一种是正确的清除。

1PEND |= 1<<0;      (not good)
2PEND = 0xFFFFFFFF; (not good)
3PEND = 1<<0;       (Good!)

如果PEND寄存器某个bit是0,你写一个1进去,那么会变成1。只有当这位是1的时候写个1进去才会变成0.

第一种:用或的方式,若PEND = 00011111(二进制),PEND | (1<<0) = 00011111,也就是将00011111写入到值为00011111的PEND中,那么PEND的值变成00000000,所有的位都被清0了,而不是我们原本的意思要清除第一位。


第二种:直接赋值,其实和第一种方式是一样的,只不过第一种是先求出或的值然后写进寄存器,如果原本是00011111的PEND,写进0xFFFFFFFF那么PEND中原本是0的位还是0,是1的位全部都清为0 ,还是不是我们想要的结果。


第三种:PEND = 1<< 0 ,加入PEND是00011111,把00000001赋值进来,那么得到PEND的值为00011110,刚好是我们想要的,清除我们想要清除的位。


下面我们看看EINT0MASK的屏蔽作用是怎样的效果:

首先看看EINT0MASK的描述信息:

 

可以看出哪位置1就是屏蔽对应的中断信号

1/* EINT0MASK[0] = 1 : Mask EINT0 */
2temp = EINT0MASK ;
3temp &= ~(0x1<<0);
4temp |= (0x1<<0);
5EINT0MASK = temp;

屏蔽外部中断0,看中断触发信号能不能通过它传达到上面的寄存器VIC0RAWINTR。

看看VIC0RAWINTR的描述:

   

先不屏蔽中断,看看能否在VIC0RAWINTR看到中断信号:

01/* EINT0MASK[0] = 0 : disMask EINT0 */
02temp = EINT0MASK ;
03temp &= ~(0x1<<0);
04EINT0MASK = temp;
05
06uart_init();
07while (1)
08{
09    for(ch='a';ch<='z';ch++){
10        if( (VIC0RAWINTR & 0x1)==0x1){
11            uart_putchar('+');
12        }
13        uart_putchar(ch);
14        delay();
15    }
16}

 

按下KEY,触发中断,发现现象如下(没有清理EINT0PEND):

   

说明在  VIC0RAWINTR 检测到中断触发信号了。

如果把EINT0MASK[0]设置为1,即屏蔽信号,看看结果:

 

怎么按KEY也检测不到中断触发信号,说明EINT0MASK是这里的一道关卡,中断触发信号能否传到上级要看这里有没有屏蔽掉,它相当于一个开关作用。


继续dismask中断触发信号,让它传递到VIC0RAWINTR ,然后清除中断悬起位,看现象是怎样的:

01/* EINT0MASK[0] = 0 : disMask EINT0 */
02temp = EINT0MASK ;
03temp &= ~(0x1<<0);
04EINT0MASK = temp;
05
06uart_init();
07while (1)
08{
09    for(ch='a';ch<='z';ch++){
10        if( (VIC0RAWINTR & 0x1)==0x1){
11            uart_putchar('+');
12            EINT0PEND = 0x1;
13        }
14        uart_putchar(ch);
15        delay();
16    }
17}

 

按一下KEY打印出一个“+”,然后查询VIC0RAWINTR 寄存器,发现中断触发信号没有了,说明EINT0PEND的值直接影响到VIC0RAWINTR 的值,VIC0RAWINTR 跟随EINT0PEND变化,EINT0PEND的变化实时反映到VIC0RAWINTR上来,当然得看MASK有没有屏蔽掉中断触发信号啦。


 

信号接着往上面传送,直到cpu内核检测到中断触发信号并产生对应的操作为止:

使用VIC(中断向量控制器)

VIC0INTENABLE的描述:

   

对应的清除寄存器VIC0INTENCLEAR的描述:

 

VIC0SELECT的描述:

 

我们这里选择IRQ方式,也可以是FIQ方式。

VIC0IRQSTATUS的描述:

   

VIC0FIQSTATUS描述:

 

我们选择IRQ方式,并且使能中断功能,看能不能到VIC0IRQSTATUS中查询到中断触发信号:

01void mymain(void){
02unsigned char ch;
03unsigned int temp=0;
04
05/* set GPNIO to EINT mode , 10 --> Eint */
06temp = GPNCON ;
07temp &= ~(0x3<<0);
08temp |= (0x2<<0);
09GPNCON = temp;
10
11/* set EINT triger mode to falling eage ,01x = Falling edge */
12temp = EINT0CON0 ;
13temp &= ~(0x7<<0);
14temp |= (0x3<<0);
15EINT0CON0 = temp;
16
17/* EINT0MASK[0] = 0 : disMask EINT0 */
18temp = EINT0MASK ;
19temp &= ~(0x1<<0);
20//temp |= (0x1<<0);
21EINT0MASK = temp;
22
23/* VIC0INTENABLE[0]=1 : enable interrupt */
24/* clear bit by VIC0INTENCLEAR */
25temp = VIC0INTENABLE ;
26temp &= ~(0x1<<0);
27temp |= (0x1<<0);
28VIC0INTENABLE = temp;
29
30/* VIC0INTSELECT[0] = 0 : set to IRQ */
31temp = VIC0INTSELECT ;
32temp &= ~(0x1<<0);
33VIC0INTSELECT = temp;
34
35uart_init();
36while (1)
37{
38    for(ch='a';ch<='z';ch++){
39        if( (VIC0IRQSTATUS & 0x1)==0x1){
40            uart_putchar('+');
41            EINT0PEND = 0x1;
42        }
43        uart_putchar(ch);
44        delay();
45    }
46}

按下KEY出现下面的现象:

[1] [2]
关键字:ARM  中断  基础知识 引用地址:ARM中断基础知识

上一篇:【JZ2440笔记】裸机实验使用中断
下一篇:S3c2440的LCD显示项目

小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved