一、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组 |
0 | INT_EINT0 | External interrupt 0~3 | VIC0 |
1 | INT_EINT1 | External interrupt 4~11 | VIC0 |
32 | INT_EINT2 | External interrupt 12~19 | VIC1 |
33 | INT_EINT3 | External interrupt 20~27 | VIC1 |
53 | INT_EINT4 | External interrupt group1~group9 | VIC1 |
举个例子(OK6410的6个按键做外部中断):
按键 | 对应引脚 | 对应外部中断 | 中断源 |
KEY1 | GPN0 | External interrupt 0 | INT_EINT0 |
KEY2 | GPN1 | External interrupt 1 | INT_EINT0 |
KEY3 | GPN2 | External interrupt 2 | INT_EINT0 |
KEY4 | GPN3 | External interrupt 3 | INT_EINT0 |
KEY5 | GPN4 | External interrupt 4 | INT_EINT1 |
KEY6 | GPN5 | External interrupt 5 | INT_EINT1 |
KEY1~KEY4都是属于INT_EINT0,即都是属于中断号为0的中断,这四个按键产生的外部中断所调用的中断处理函数的地址都是存在VIC0VECTADDR0寄存器中的,这四个随便哪个按下都会调用同一个中断处理函数,所以在处理函数里面必须判别是哪个按键所触发的中断。
中断相关寄存器的设计演变(寄存器以外部按键中断为例)
IC ARM7(4510) | IC ARM9(2440) | IC ARM11(6410) | |
内核 (core) | CPSR I-bit | CPSR 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/Timer | key/UART/USB/Timer | key/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
如何测试和中断相关的各个寄存器到底是怎么工作的,上下层的相互关系是怎么样的?
首先我们不采用中断方式来看各个寄存器的变化,我们用查询的方式查看,以按键中断为例,当有按键按下的时候,各个寄存器怎么变化,相互之间的关系如何:
准备一个能够在串口输出字符的程序。
OK6410的KEY1是接在 GPN0上面的,通过GPNCON把它设置成中断方式,且通过EINT0CON0设置成下降沿触发方式,代码如下:
01 | /* set GPNIO to EINT mode , 10 --> Eint */ |
02 | temp = GPNCON ; |
03 | temp &= ~(0x3<<0); |
04 | temp |= (0x2<<0); |
05 | GPNCON = temp; |
06 |
07 | /* set EINT triger mode to falling eage ,01x = Falling edge */ |
08 | temp = EINT0CON0 ; |
09 | temp &= ~(0x7<<0); |
10 | temp |= (0x3<<0); |
11 | EINT0CON0 = temp; |
当按下KEY的时候,会触发中断,并且把这个信号传送到最下层的中断悬起位寄存器EINT0PEND,这个寄存器不同的bit对应不同的中断。EINT0PEND的上层是中断屏蔽寄存器INTMASK,当这个寄存器设置为屏蔽的时候中断触发信号是无法通过它传送到上层去的。我们先来看看当中断发生的时候EINT0PEND是如何变化的,他的变化会带来怎么样的结果。
01 | while (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的写法,只有最后一种是正确的清除。
1 | PEND |= 1<<0; (not good) |
2 | PEND = 0xFFFFFFFF; (not good) |
3 | PEND = 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 */ |
2 | temp = EINT0MASK ; |
3 | temp &= ~(0x1<<0); |
4 | temp |= (0x1<<0); |
5 | EINT0MASK = temp; |
屏蔽外部中断0,看中断触发信号能不能通过它传达到上面的寄存器VIC0RAWINTR。
看看VIC0RAWINTR的描述:
先不屏蔽中断,看看能否在VIC0RAWINTR看到中断信号:
01 | /* EINT0MASK[0] = 0 : disMask EINT0 */ |
02 | temp = EINT0MASK ; |
03 | temp &= ~(0x1<<0); |
04 | EINT0MASK = temp; |
05 |
06 | uart_init(); |
07 | while (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 */ |
02 | temp = EINT0MASK ; |
03 | temp &= ~(0x1<<0); |
04 | EINT0MASK = temp; |
05 |
06 | uart_init(); |
07 | while (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中查询到中断触发信号:
01 | void mymain(void){ |
02 | unsigned char ch; |
03 | unsigned int temp=0; |
04 |
05 | /* set GPNIO to EINT mode , 10 --> Eint */ |
06 | temp = GPNCON ; |
07 | temp &= ~(0x3<<0); |
08 | temp |= (0x2<<0); |
09 | GPNCON = temp; |
10 |
11 | /* set EINT triger mode to falling eage ,01x = Falling edge */ |
12 | temp = EINT0CON0 ; |
13 | temp &= ~(0x7<<0); |
14 | temp |= (0x3<<0); |
15 | EINT0CON0 = temp; |
16 |
17 | /* EINT0MASK[0] = 0 : disMask EINT0 */ |
18 | temp = EINT0MASK ; |
19 | temp &= ~(0x1<<0); |
20 | //temp |= (0x1<<0); |
21 | EINT0MASK = temp; |
22 |
23 | /* VIC0INTENABLE[0]=1 : enable interrupt */ |
24 | /* clear bit by VIC0INTENCLEAR */ |
25 | temp = VIC0INTENABLE ; |
26 | temp &= ~(0x1<<0); |
27 | temp |= (0x1<<0); |
28 | VIC0INTENABLE = temp; |
29 |
30 | /* VIC0INTSELECT[0] = 0 : set to IRQ */ |
31 | temp = VIC0INTSELECT ; |
32 | temp &= ~(0x1<<0); |
33 | VIC0INTSELECT = temp; |
34 |
35 | uart_init(); |
36 | while (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出现下面的现象:
上一篇:【JZ2440笔记】裸机实验使用中断
下一篇:S3c2440的LCD显示项目
设计资源 培训 开发板 精华推荐
- 小汐 -> 离线语音智能小球灯
- 陶瓷LED时钟(控制部分)
- 使用 ON Semiconductor 的 LA42051 的参考设计
- LC75843UGAGEVB,通用 LCD 显示驱动器评估板
- 使用 LTC4162EUFD-L40M 1-8 节、3.2A 降压型开关电池充电器和 PowerPath 的典型应用
- 适用于工业应用的 C8051F901 MCU 的 C8051F912-DK、8051 开发系统
- FEB157-001,使用 FAN7554D PWM 控制器的评估板,高亮度 LED 驱动器
- 使用 ROHM Semiconductor 的 BD48E34G-TR 的参考设计
- 适用于 EV/HEV 电池管理系统的高性能 MCU 参考设计
- RGB耳环 V1.0
- 了解Keithley 4200-SCS参数分析仪,下载技术文章,抽奖赢礼!
- 【分享成长,10月有奖】EEWORLD优秀主题/回复第15期活动开始啦!!!
- 调查:泰克创新实验室全面升级,功率测试痛点通通都抛来!参与赢好礼
- EEWORLD新年有奖竞猜,邀你来出题啦!
- VISHAY新能源主题月,幸运闯关赢大奖!
- 机智云Gokit3免费测评试用!用熟悉的方式,快速实现每一个想法!
- 了解 MPS 隔离解决方案,答题赢【华为蓝牙无线耳机、小米氮化镓充电器】!
- TI 嵌入式主题直播月——为高效、智能、低功耗系统设计助力
- 恩智浦LPC54100迅猛来袭,关注有礼
- 【EEWORLD第二十六届】2011年05月社区明星人物揭晓!