uboot step 12 按键玩中断-S3C6410中断介绍
- 向量中断控制器相关寄存器
- 中断处理流程
- 中断源-外部中断配置
- 代码实现-使用按键中断
- 相关参考
http://comm.chinaaet.com/adi/blogdetail/40071.html
http://blog.csdn.net/kokodudu/article/details/18094117
http://blog.csdn.net/mirkerson/article/details/8646690
S3C6410 包含两个VIC(Vectored Interrupt Controller.ARM Prime Cell PL192)向量中断控制器,相比S3C2440来说,大大简化了中断的编程工作,中断体系框图如下图,其总共支持64个中断源,其中有5个为外部中断源,VIC0负责0~31号中断源,VIC1负责32-63号中断源,使用中断,关键的就是如何正确配置VIC相关的寄存器,首先来了解下中断相关寄存器,有个大概印象。
中断VIC相关寄存器
-
IRQ 状态寄存器
-
FIQ 状态寄存器
-
中断类型选择
-
中断使能
-
中断使能清零
-
保护使能寄存器
-
中断服务程序地址寄存器
-
中断优先级寄存器
-
当前中断服务程序地址寄存器
中断处理流程
简要的说下中断处理流程,对于S3C2440与S3C6410这两种处理器中断处理流程是不相同的,首先来看下S3C2440的中断处理流程:
- 产生中断
- 中断信号经过层层困难向CPU传递,SubSrcPend>IntSubMask>SrcPend>IntMsk>CPU
- 当中断屏蔽寄存器都没有屏蔽,并且CPU的模式寄存器中的中断位设为允许中断时,CPU就会去处理这个中断
- CPU处理这个中断会首先产生异常,跳转到中断异常处执行,在这里要做保护现场的动作并跳转到我们所写的中断处理代码处
- 在中断处理代码中首先要查询EINTPEND寄存器来知道是哪一个中断产生了,再去调用对应的中断处理程序
- 执行完中断处理程序后在返回到中断异常处恢复环境,继续执行
由上来看,S3C2440产生的所有中断如果没有被屏蔽的话都会跳转到中断异常处进行处理,然后我们还需要在中断处理代码中进行查询到底是哪一个中断源所引发的中断,最后再去调用对应的中断服务程序,这样的流程效率比较低,而且对于中断编程也比较繁琐。S3C6410为了兼容以前的版本,其实有两种中断服务处理的流程,一种与S3C2440处理流程相似,这里主要说下另一种处理方式:
- 产生中断
- 中断信号会进入VIC中断向量控制器中进行处理
- 如果中断已经使能并且没有被屏蔽,VIC会知道到底是哪一个中断源所产生的中断信号
- 当确定哪一个中断源之后,VIC会将这个中断源的中断服务程序所在的地址赋值给当前中断服务程序地址寄存器通知到CPU
- CPU如果没有屏蔽中断的话,便会直接跳转到对应的中断服务程序中去执行,而不是跳转到中断异常地址处,在经过查询等步骤去跳转到对应的中断服务程序中去
这样的方式很明显提高了中断处理的效率,同时,对于中断处理程序的编写也会相对以前更加简单和清晰,因为一般不用再去做查询中断源这个动作了,使用这种方式的话首先要使能这个VIC功能,通过设置协处理器CP15的相关寄存器来设置,如下图:
如上,我们在中断初始化的时候使能了CP15协处理器的C1寄存器的24位VE才能使用这种处理流程。
中断源-外部中断配置
S3C6410的64个中断源如下图所示:
其中有5个中断源被连接到了外部中断源,总共有127个外部中断源,可以从芯片手册上得到
如上图所示:外部中断源中共有127个,分为了10组,其中外部中断组0比较特殊,它包含了27个外部中断引脚,占用了INT_EINT0 ~ INT_EINT3共4个中断源,剩下1~9组共用最后一个外部中断源INT_EINT4
当发生外部中断的时候,我们首先会进入对应的外部中断源的中断服务程序,因为不可能所有的引脚都对应一个中断源,所以我们无法知道到底是哪一个中断引脚产生的,这时候我们需要去查询外部中断挂起寄存器来判断属于哪一个中断引脚,与外部中断相关的寄存器描述如下:
-
外部中断控制寄存器 :设置外部中断触发方式
-
外部中断硬件滤波配置 : 是否使能硬件滤波,滤波方式 延时,数字滤波 ,数字滤波宽度设置
-
外部中断屏蔽寄存器
-
外部中断挂起寄存器
-
GPIO配置寄存器: 使用外部中断,需要首先配置GPIO功能为外部中断
代码实现
首先来看下原理图:
如图,我们使用K1,K2 ,K8 来测试中断,分别连到了GPION0,GPION1,GPIOL12这三个引脚,因此我们需要先将这三个引脚配置为中断模式。
如图,K1,K2对应的中断为外部中断0组的第0,1号引脚,K8对应的是外部中断0组得第20号引脚,虽说是第0组,其实芯片手册上并没有指出是第0组,而是直接称为外部中断,而第1 ~ 9组在芯片手册中则称为外部中断组1 ~ 9,由前文可以知道,K1,K2外部中断0组的0,1号引脚所对应的中断源为INT_EINT0,对应的中断源号为0,使用VIC0控制,K8外部中断0组的20号引脚所对应的中断源为INT_EINT3,对应的中断源号为33,使用VIC1控制。
下面是一些注意点:
-
清零外部中断挂起寄存器
当执行完中断服务程序后,我们需要手动清除中断,对于外部中断挂起寄存器,清除需要靠向对应位写1来清零
- 关于ARM流水线结构地址
ARM采取流水线结构运行,取码译码执行,所以PC指针一直指向当前执行地址+8,加入当前执行地址为0- 当前代码执行地址: 0
- 即将要执行的代码地址: 0+4
- 此时PC指针值: 0+8
如果执行当前代码时发生了中断,我们需要保存即将要执行的代码地址到LR寄存器,而此时LR指针的值其实为PC指针值,因此需要将LR寄存器的值减去4之后再保存到LR中,才是保存的的即将要执行的代码地址。
- 关于中断初始化
由于此前我们将CPSR寄存器中的中断使能位已将关闭了,因此这里我们需要将其打开。
为了能够使用VIC自动将中断服务地址通知到CPU,并自动跳转到中断服务程序执行,需要打开协处理器的C1寄存器的24位 - 中断模式堆栈指针
此前堆栈指针初始化的时候,我们只是设置了SVC模式下的堆栈指针,为了使用IRQ模式,因此我们需要也去设置下IRQ模式下堆栈指针
下面上代码:
//按键初始化,设置GPIO方式为中断方式 button.c
#define GPNCON (volatile unsigned long*)0x7f008830
#define GPL1CON (volatile unsigned long*)0x7f008814
void button_init()
{
*(GPNCON) = 0b10 | (0b10<<2); //K1,K2
*(GPL1CON) = 0b0011<<16; //K8
}
/*interrupt registes 中断初始化,和中断函数处理函数 */
#define EXT_INT_0_CON0 *((volatile unsigned int *)0x7f008900)
#define EXT_INT_0_CON1 *((volatile unsigned int *)0x7f008904)
#define EXT_INT_0_MASK *((volatile unsigned int *)0x7f008920)
#define EXT_INT_0_PEND *((volatile unsigned int *)0x7f008924)
#define VIC0INTENABLE *((volatile unsigned int *)0x71200010)
#define VIC1INTENABLE *((volatile unsigned int *)0x71300010)
#define EINT0_VECTADDR *((volatile unsigned int *)0x71200100)
#define EINT20_VECTADDR *((volatile unsigned int *)0x71300104)
#define VIC0ADDRESS *((volatile unsigned int *)0x71200f00)
#define VIC1ADDRESS *((volatile unsigned int *)0x71300f00)
void key1_handle()
{
__asm__(
"sub lr, lr, #4\n" //由于ARM处理器采用流水线结构,PC指针的值为当前执行代码地址+8,LR的值也为PC的值,这里将LR值减4,正好将下一条所需要执行的代码地址保存起来
"stmfd sp!, {r0-r12, lr}\n" //保存环境
:
:
);
led_on();
/* 清除中断 */
EXT_INT_0_PEND = ~0x0; //清零外部中断挂起寄存器,相对应位写1清零中断
VIC0ADDRESS = 0; //清零当前中断服务程序地址寄存器
VIC1ADDRESS = 0;
__asm__(
"ldmfd sp!, {r0-r12, pc}^ \n" //恢复环境
:
:
);
}
void key8_handle()
{
__asm__(
"sub lr, lr, #4\n"
"stmfd sp!, {r0-r12, lr}\n"
:
:
);
led_off();
/* 清除中断 */
EXT_INT_0_PEND = ~0x0;
VIC0ADDRESS = 0;
VIC1ADDRESS = 0;
__asm__(
"ldmfd sp!, {r0-r12, pc}^ \n"
:
:
);
}
void init_irq()
{
EXT_INT_0_CON0 = 0b010; /* 配置为下降沿触发 K1,K2 */
EXT_INT_1_CON1 = 0x010<<8; /* 配置为下降沿触发 K8*/
EXT_INT_0_MASK = 0; /* 取消屏蔽外部中断 */
VIC0INTENABLE |= 0x1; /* 使能外部中断*/
VIC1INTENABLE |= 0x0f;
EINT0_VECTADDR = (int)key1_handle; /* 用户按下key(K1,K2)时,CPU就会自动的将VIC0VECTADDR0的值赋给VIC0ADDRESS并跳转到这个地址去执 */
EINT20_VECTADDR = (int)key8_handle;
__asm__(
"mrc p15,0,r0,c1,c0,0\n"
"orr r0,r0,#(1<<24)\n"
"mcr p15,0,r0,c1,c0,0\n" //设置CP15协处理器的C1寄存器的VE位
"mrs r0,cpsr\n"
"bic r0, r0, #0x80\n" //使能IRQ中断
"msr cpsr_c, r0\n"
:
:
);
}
//重写init_stack start.s
init_stack:
msr cpsr_c, #0xd2
ldr sp, =0x53000000 //初始化r13_irq IRQ模式的sp指针
msr cpsr_c, #0xd3
ldr sp, =0x54000000 //初始化R13_svc
mov pc ,lr
此去经年
[email protected]
August 11, 2016