中断时学习每一款芯片都必须去了解的机制,CPU采用中断可以节省掉轮训带来的性能损耗,关于对jz2440的外部中断的学习,在此简单进行总结
1、中断框架
在jz2440芯片中,所有的中断都接到了中断控制器上,中断控制器又连接在了CPU上,当发生中断的一瞬间,中断控制器首先获得了中断源发生的中断,然后根据配置选项是否上报CPU,CPU再对这个中断进行处理。
CPU获取到中断控制器上传的异常后会跳转到一个固定的地方去执行指令,这个动作由硬件自动响应,存放这些地址的地方我们称之为这就是中断向量表,如下图所示
从这个表上可以看出每个异常类型跳转的地址,第一个是复位引起的异常,CPU会跳转到0地址开始执行,也就是一上电CPU会从0地址开始执行,在这里我们关心的外部中断irq存放在地址0x18这里,所以在处理中断的时候需要在这个地址存放指令。
2、处理过程
ARM CPU的每种异常都在CPSR寄存器中对应一个模式值,发生异常时CPU会把这些模式位改变为发生异常对应的模式值,由该寄存器的M4-M0的值来确定
对中断的处理过程可以概括为三个步骤
1. 保存现场
2. 处理异常
3. 恢复现场
其中,保存现场这里有一部分内容是CPU帮助我们来完成的,其中有
1、没种异常的lr寄存器将保存被中断指令的下一条指令的地址
2、发生的异常模式的spsr寄存器将保存被中断模式的cpsr寄存器
3、cpsr中的M4-M0被设置成对应的值
4、跳转到每种异常固定的地方开始执行
上面的内容是在发生异常是CPU自动处理的,那么我们就需要完成剩下的部分,接下来实际编写程序对这一部分进行说明
3、中断程序
在中断程序的开始我们需要来构建中断向量表,这一部分程序写在了引导的汇编部分
_start:
b reset /* vector 0 : reset */
ldr pc, und_addr /* vector 4 : und */
ldr pc, swi_addr /* vector 8 : swi */
b halt /* vector c :Abort */
b halt /* 10 */
b halt /* 14 */
ldr pc,irq_addr /* irq 0x18 */
b halt /* fiq 0x1c */
......
mrs r0, cpsr /* 读出cpsr */
bic r0, r0, #(1 << 7) /* 打开外部中断的总开关 */
msr cpsr, r0
和上面表中的一样,在开始的地方放置中断向量表,跳转到对应的地址执行相应的中断处理程序,在这里只处理irq异常,但是其他异常的地址也要用指令来占起来,在0x18的地方放入irq异常的处理指令
irq_addr:
.word do_irq ;实际上跳转到do_irq这里去执行
do_irq:
ldr sp, =0x33d00000
sub lr,lr,#4
stmdb sp!, {r0-r12, lr}
bl handler_irq
ldmia sp!, {r0-r12, pc}^ /* ^会把spsr的值恢复到cpsr里 */
在这里首先设置irq异常模式下的栈,然后要保存程序的返回地址,通过下面的表可以知道发生异常时硬件保存的PC值实际上是PC+4得到的,所以在上面的程序中先将lr的值减去4再进行保存
关于现场的保存和恢复的汇编指令在另一篇文章中有做介绍,这里就不再赘述,需要注意的是在恢复的时候不要忘了将之前保存的spsr寄存器的值恢复到cpsr寄存器中(使用“^”),完成了汇编程序程序的处理流程之后需要编写真正处理中断的程序,接下来就要去完成中断部分的寄存器配置和handler_irq程序
先贴一张jz2440的中断结构图,之后要根据这张图进行配置
这个例程为按键中断的程序,首先需要把按键对应的引脚设置为中断引脚
这里是配置对应的寄存器,过程就不再多说
1、引脚配置
GPFCON &= ~((3 << 0) | (3 << 4));
GPFCON |= ((2 << 0) | (2 << 4));
GPGCON &= ~((3 << 6) | (3 << 22));
GPGCON |= ((2 << 6) | (2 << 22));
/* 2、配置触发方式 */
EXTINT0 |= (7 << 0) | (7 << 8);
EXTINT1 |= (7 << 12);
EXTINT2 |= (7 << 12);
/* 3、使能中断 */
EINTMASK &= ~((1 << 11) | (1 << 19));
/* 4、打开中断控制器的相应位 */
INTMSK &= ~((1 << 0) | (1 << 2) | (1 << 5));
在刚才的那张中断结构图中,外部中断属于下面的那条线路,相应的寄存器只有
SRCPND: 用来显示哪个中断产生了, 需要清除对应位
INTPND 用来显示当前优先级最高的、正在发生的中断, 需要清除对应位
编写中断处理
void handler_irq(void)
{
/* 显示当前发生的中断 */
int bit = INTOFFSET;
if (bit == 2 || bit == 0 || bit == 5) {
key_irq(bit);
}
SRCPND = (1 << bit); // 清中断,要先从源头开始清除
INTPND = (1 << bit);
}
void key_irq(int irq)
{
int val = EINTPEND;
unsigned int val1 = GPFDAT;
unsigned int val2 = GPGDAT;
if (irq == 0) {
/* 处理程序 */
} else if (irq == 2) {
/* 处理程序 */
} else if (irq == 5) {
if (val & (1 << 11)) // 由于中断11和19共用中断线5,所以这里要根据EINTPEND寄存器进一步确定是哪个中断源产生的中断
/* 处理程序 */
else if (val & (1 << 19))
/* 处理程序 */
}
4、程序改进
上面写的中断驱动程序虽然能够满足我们的需求,但是还存在一个问题,就是每次添加一个中断的话都需要重新去修改使用程序,还有要在handler_irq函数中添加该部分中断的控制程序,这样一来太过麻烦,而且不利于程序的模块性,现在对上面的程序结构进行调整修改
//处理中断的程序都是一样的参数,将这个函数进行重定义
typedef void (*irq_func)(int);
irq_func irq_array[32]; //总共有32个中断线
// 添加中断注册函数
void register_irq(int irq,irq_func func)
{
irq_array[irq] = func; //将此中断处理函数添加进数组
INTMSK &= ~((1 << irq);// 使能中断,这样一来就不用初始化函数里添加中断使能了
}
// 修改中断处理handler_irq函数
void handler_irq(void)
{
int bit = INTOFFSET;
irq_array[bit](bit); // 只需要调用正在发生的中断处理函数
SRCPND = (1 << bit); // 清中断
INTPND = (1 << bit);
}
/*
那么上面的按键中断的结构就可以改成
1、引脚等配置
2、按键key_irq函数的实现
3、注册按键中断
register_irq(0,key_irq);
register_irq(2,key_irq);
register_irq(5,key_irq);
在注册之后就是把我们自己实现的处理函数放入了数组中,然后获取数组的下标调用对应的函数即可,
另一方面在初始化的同时使用register_irq进行注册
*/
由于程序过多,上面只写出了部分关键性代码,本节的内容也比较简单,目的是了解jz2440的中断流程与处理流程,到这里,关于外部中断的关键性代码已经完成,编译程序下载,实验结果为按下按键,点亮或者熄灭对应的LED灯