目录
1.思路
步骤:
1.设置中断源,发出中断信号
2.设置中断控制器,能向CPU发出中断
3.设置CPU,CPSR的I位,打开IRQ中断总开关。
4.硬件进入中断处理函数之前会自动做一系列的工作
1).把下一条指令的地址 保存在Link Rigsiter(LR)寄存器(R14)里,注意此处的LR寄存器是异常模式下专属的LR寄存器,简称为LR_异常,其保存被中断模式下一条指令的地址(可能为PC+4,也有可能为PC+8,取决于不同的情况)。
2).把被中断模式下CPSR寄存器保存至当前模式下的SPSR_异常寄存器。
3).修改CPSR的模式位,进入异常模式(M4-M0),其位状态如下说明:
4).跳到中断向量表。
5.处理这个中断--->自己想做的工作
注意:在处理中断时,需要分辨中断源是哪一个,因为2440有很多中断源,看数据手册。
6.离开中断,CPU返回之前的模式需要做的工作
1).LR寄存器减去某个值,复制给PC(R15)=> PC = LR_异常 - offset,如下表
假如发生了swi异常,反返回的时候,可以把LR寄存器(R14_svc)的值赋值给PC,如果发生了IRQ中断,返回时可以把LR寄存器(R14_irq)的值减去4再赋值给PC、
2).恢复CPSR的值,从SPSR寄存器。CPSR = SPSR_异常
3).手动清除中断标识位。
2.例子
例子:
1).设置cpsr寄存器的 I 位为(第七位)为0,打开IRQ总中断。
2).编写1个c程序,两个函数,一个是初始化中断控制器,另一个是外部按键的初始化
流程:
1.初始化中断:①中断源初始化:按键设置为中断引脚 ②初始化中断控制器 ③设置CPU的CPSR寄存器使能总中断
2.在没有中断发生时,主函数打印字符串AaBbCc.....,中断按钮按下时,产生中断,执行中断处理函数,其处理任务就是点亮led灯,执行完中断之后,返回主函数继续执行打印字符串任务。
编写第一个函数,初始化外部中断:
分几步:
①设置寄存器GPFCON,GPGCON,配置GPF0/2,GPG3/11为外部中断引脚,对应原理图的:S2,S3,S4,S5
②设置寄存器GPFCON,配置GPF4/5/6引脚为输出,用于控制LED灯,对应原理图的,D10,D11,D12
③设置EXTIN0,EXTIN1,EXTIN2,寄存器,设置中断引脚的触发方式为双边沿触发。
④设置EINTMASK寄存器,配置外部中断11和外部中断19中断使能,而外部中断0和2是默认使能的。
⑤可以通过查询EINTPEND寄存器查询某个中断是否发生,写相应位为1清除中断(可查询的位为:EINT4-EINT23)
代码如下:
/*初始化按键中断*/
void key_eint_init(void)
{
/*配置GPF0/2,GPG3/11,为中断引脚*/
GPFCON &= ~((3<<0)| (3<<4)); //对位先清零
GPFCON |= ((2<<0)| (2<<4)); //设置[1:0]=0b10,[5:4]=0b10
GPGCON &= ~((3<<6) | (3<<22));
GPGCON |= ((2<<6) | (2<<22)); //设置[7:6]=0b10,[23:22]=0b10
/*配置GPF4/5/6 为输出引脚--->LED,对应D10,D11,D12*/
GPFCON &= ~((3<<8) | (3<<10)
| (3<<12));
GPFCON |= ((1<<8) | (1<<10)
| (1<<12));
/*设置中断的触发方式:双边沿触发*/
EXTINT0 &= ~((7<<0) | (7<<8));
EXTINT0 |= ((7<<0) | (7<<8)); //设置[2:0]=0b111,[10:8]=0b111
EXTINT1 &= ~(7<<12);
EXTINT1 |= (7<<12); //设置[14:12]=0b111
EXTINT2 &= ~(7<<12);
EXTINT2 |= (7<<12); //设置[14:12]=0b111
/*设置EINTMASK使能外部中断11和19,外部中断0和2不用设置*/
EINTMASK &= ~((1<<11) | (1<<19));
}
具体配置如下:
- 首先初始化按键设置为中断引脚:
查看原理图,配置外部中断(EINT0/2/11/19)为中断引脚,设置三个LED引脚为输出模式
其中中断引脚和LED的外部连接如下图所示:
外部中断0和2连接在:GPF0/2 ,外部中断11和19连接在:GPG3/11
LED的D10/11/12连接在:GPG4/5/6
- 首先找到GPFCON(GPF配置寄存器)设置GPF0/2为外部中断引脚:
- 设置GPGCON,配置GPG3/11为中断引脚:
- 配置EXTINT0寄存器,设置中断的触发方式为:双边沿触发
- 配置EXTINT1寄存器,设置中断的触发方式为:双边沿触发
- 配置EXTINT2寄存器,设置中断的触发方式为:双边沿触发
- 配置EINTMASK寄存器,使能外部中断11和19
可以通过读取EINTPEND寄存器来读取某个中断是否发生,写相应的位为0
编写第二个函数,设置中断控制器
找到数据手册中断控制器的部分,有一张简图如下:
这里有几个重要寄存器和步骤:
①SRPEND用来提示哪个中断产生了,处理完中断之后,需要清除相应位。
②INTMOD默认设置,模式寄存器默认为IRQ中断。
③设置INTMASK寄存器,不屏蔽外部中断0,外部中断2,以及外部中断8-23.
④暂不设置优先级.
⑤INTPEND用于显示当前优先级最高,正在发生的中断,需要清除相应位。
⑥INTOFFSET寄存器用于显示INTPEND中哪一位被设置为1,即通过判断其值,可以知道当前发生的中断。
代码如下:
/*设置中断控制器*/
void Interrupt_Init(void)
{
/*设置中断屏蔽寄存器,不屏蔽EINT0/2/11/19*/
INTMSK & =~((1<<0) | (1<<2) | (1<<5));
}
具体寄存器描述如下:
- 读取SRPEND,用于显示某一个中断是否发生,读取完需要清除相应位。
需要关心的位为:0--->外部中断0 . 外部中断2 , 5 ,外部中断8-23
- INTMOD寄存器,使用默认值
- 设置INTMASK寄存器,不屏蔽我们的中断源
- INTPEND 用于显示优先级最高,且正在发生的中断
- INTOFFSET,用来显示INTPEND中哪个中断正在等待处理,显示INPEND寄存器哪一个位设置为1
比如,读这个寄存器的值是0,代表中断0发生了,值为2,代表中断2发生了
3).在主函数中调用这两个函数,初始化中断
4).如果此时没有发生中断,就一直主函数执行打印函数,当发生中断时,硬件自动做一系列的动作(详情看上面),最终跳到IRQ异常向量地址的位置开始执行,即为:0x18的地方,所以需要在启动文件0x18的地方放一条跳转指令,接下来处理步骤就和前边的一样了,设置栈,保存现场,处理IRQ异常,恢复现场,回到中断之前的模式继续执行函数。
详情查看:点我查看
这里需要注意一点:
退出中断返回时,需要把R14_irq的值减去4,再赋值给PC
启动文件start.S中重要部分代码如下:
.text
.global _start
_start:
b Reset /*vector 0 : Reset*/
ldr pc, und_handle_addr /*vector 4 : undfined execption*/
ldr pc, swi_handle_addr /*vector 8 : software interrupt execption*/
b halt /*vector 0x0c*/
b halt /*vector 0x10*/
b halt /*vector 0x14*/
ldr pc,irq_handle_addr /*vector 0x18:IRQ execption*/
b halt /*vector 0x1c*/
und_handle_addr:
.word und_handle
swi_handle_addr:
.word swi_handle
irq_handle_addr:
.word irq_handle
irq_handle:
/*执行到这之前硬件做的工作
*1.lr_irq保存有被中断模式中下一条即将执行指令的地址
*2.SPSR_irq保存有被中断模式的CPSR
*3.CPSR中的M4-M0被设置为10010,进入IRQ模式
*4.跳到0x18地方开始执行
*/
/*设置IRQ异常模式下的栈*/
ldr sp, =0x30b00000
/*保存现场*/
/*1.保存r0-r12,因为这几个寄存器是和swi异常处于管理模式共用的
*在这个异常模式有可能会修改寄存器的值,先保存
*起来,等退出这个异常的时候再恢复回去
*2.在手册中可知,在swi异常返回时,直接把
*lr寄存器赋值减去4赋值给pc,有偏移,
*/
sub lr, l r, #4 /*lr 的值减去4*/
stmdb sp!, {r0-r12, lr}
/*处理IRQ异常*/
mrs r0, cpsr
ldr r1, =irq_string
bl Execption_print
bl IRQ_handle
/*恢复现场*/
/*1.恢复r0-r12的值,然后把lr寄存器赋值给pc,使程序
*返回到上一次异常的位置接着往下执行。
*2. ‘^’会把spsr的值恢复到cpsr寄存器
*/
ldmia sp!, {r0-r12,pc}^
irq_string:
.string "IRQ exception!"
/*此处注意:因为上面的字符串有可能不是四字节的倍数
*所以此处需要加上一个四字节对齐,让Reset从四字节
*对齐的地方开始运行。
*/
.align 4
备注:
为了方便查看结果,当发生中断时还会打印出当前的cpsr寄存器的值,以及打印出字符串IRQ exception
5)在interrupt.c中编写中断服务函数:IRQ_handle
/*中断服务函数*/
void IRQ_handle(void)
{
int eint_bit = INTOFFSET; //读取INTOFFSET寄存器的值
/*分辨中断源:使用INTOFFSET寄存器判断发生哪个中断*/
/*外部中断0,或者外部中断2,或者外部中断8-23发生*/
if(eint_bit == 0 || eint_bit==2 || eint_bit==5)
{
/*调用按键中断处理函数,清除中断*/
key_eint_irq(eint_bit);
}
/*清除中断标志位,
*1.首先清除具体哪一个中断,在上面执行了
*2.清除SRCPEND
*3.清除INTPEND
*/
SRCPND = (1<<eint_bit); //写1清除
INTPND = (1<<eint_bit); //写1清除
}
解析:
在start.S的启动文件中编写,当发生IRQ异常的时候先保存现场,接着调用IRQ_handle()函数,也就是如上的函数,读取INTOFFSET寄存器,通过判断这个寄存器的值可以知道当前发生了哪个中断,如果有我们所关注的中断就调用按键中断处理函数key_eint_irq(eint_bit),这个函数在下面,处理完了当然是清除中断标志位了,这是一个硬性的规定。
这个函数的原型如下,总共有四个按钮,全部设置为外部中断,eint0控制D12,eint2控制D11,eint11控制D10,eint19控制全部的灯,当有按键按下时灯就亮,否则等灭。
void key_eint_irq(int irq)
{
unsigned int val = EINTPEND;
unsigned int val1 = GPFDAT;
unsigned int val2 = GPGDAT;
if(irq == 0) //外部中断0发生了,控制D12
{
if(val1 &(1<<0)) /*对应GPF0*/
{
/*松开*/
GPFDAT |= (1<<6);
}
else
{
/*按下*/
GPFDAT &= ~(1<<6);
}
}
else if(irq == 2)//外部中断2发生了,控制D11
{
if(val1 &(1<<2)) /*对应GPF0*/
{
/*松开*/
GPFDAT |= (1<<5);
}
else
{
/*按下*/
GPFDAT &= ~(1<<5);
}
}
else if(irq == 5)//外部中断8 -23其中一个发生,eint11-控制D10,eint19控制所有的灯
{
/*表明发生了外部中断8-23的其中一个,需要区分*/
/*读取EINTPEND寄存器
*EINTPEND的位11为1,说明产生的是外部中断11
*EINTPEND的位19为1,说明产生的是外部中断19
*/
if(val & (1<<11)) /*外部中断11*/
{
if(val2 &(1<<3)) /*对应GPG3*/
{
/*松开*/
GPFDAT |= (1<<4);
}
else
{
/*按下*/
GPFDAT &= ~(1<<4);
}
}
else if(val & (1<<19)) /*外部中断19*/
{
if(val2 & (1<<11)) /*对应GPG11*/
{
/*松开*/
GPFDAT |= ((1<<4) | (1<<5) | (1<<6));
}
else
{
/*按下*/
GPFDAT &= ~((1<<4) | (1<<5) | (1<<6));
}
}
}
/*清除中断标志位*/
EINTPEND = val; //写相应位清除
}
解析:
通过上一个函数传进来的参数,也就是INTOFFSET寄存器的值就可以知道发生的是哪一个中断,根据不同的中断源做不同的处理,中断引脚连接的是按键,一个中断源控制一个灯,按下灯亮,松开熄灭,在最后还需要清除中断标志位。
- 在主函数中调用中断初始化初始化函数:
6).修改Makefile,增加interrupt.c
all: start.o led.o uart.o sdram_init.o main.o execption.o interrupt.o
arm-linux-ld -T sdram.lds $^ -o sdram.elf
arm-linux-objcopy -O binary -S sdram.elf sdram.bin
arm-linux-objdump -D sdram.elf > sdram.dis
%.o : %.c
arm-linux-gcc -c -o $@ $<
%.o : %.S
arm-linux-gcc -c -o $@ $<
clean:
rm *.bin *.o *.elf *.dis
6).编译,下载运行。
打开串口,刚开始程序正常运行,打印出AaBbCc,此时按下按键,s2,打印信息发生变化。
开发版的灯,也同样被开发版控制着。
3.中断服务函数的改进
步骤:
1.新建一个interrupt.h的文件:
根据INTMSK急寄存器的位做如下定义:
2.在interrupt.c中新建一个函数数组,用于保存注册的中断,他所保存的是已经注册的中断服务函数
3.提供一个中断服务注册函数:
备注:如果调用该函数去注册某一个中断的话,该中断就会保存在irq_array这个数组中。
4.修改总中断处理函数:
4.在初始化按键中断中注册相应的中断类型
即:在irq_func irq_array[32];数组中,的第0、2、5的位置保存的是key_eint_irq函数的地址。
注意:此处无论是发生了0、2或5号中断都会执行key_eint_irq函数。
验证可行性:
在主函数中调用初始化按键中断函数:key_eint_init();
注意:现在可以不需要调用Interrupt_Init函数了
因为在注册中断时已经同时设置了。
下载之后,运行代码,结果和上面一样。
源代码
IRQ外部中断:https://download.csdn.net/download/qq_36243942/10930909
IRQ代码改进:https://download.csdn.net/download/qq_36243942/11122869