DAY18. 中断处理
如果出现图片无法查看可能是网络问题,我用的GitHub+图床保存的图片,可以参考我另外一篇文章GitHub的使用方法含网络问题解决
GitHub使用教程含网络问题_github加速器_肉丸子QAQ的博客-CSDN博客
相关作业和资料已上传,请在主页自行查看
1. ARM的异常处理机制
异常
处理器在正常执行程序的过程中可能会遇到一些不正常的事件发生 这时处理器就要将当前的程序暂停下来转而去处理这个异常的事件 异常事件处理完成之后再返回到被异常打断的点继续执行程序
异常处理机制
不同的处理器对异常的处理的流程大体相似,但是不同的处理器在具体实现的机制上有所不同;
比如处理器遇到哪些事件认为是异常事件遇到异常事件之后处理器有哪些动作、处理器如何跳转到异常处理程序如何处理异常、处理完异常之后又如何返回到被打断的程序继续执行等我们将这些细节的实现称为处理器的异常处理机制
ARM异常源
导致异常产生的事件称为异常源
ARM异常源
-
FIQ 快速中断请求引脚有效
-
IRQ 外部中断请求引脚有效
-
Reset 复位电平有效
-
Software Interrupt 执行swi指令
-
Data Abort 数据终止
-
Prefetch Abort 指令预取终止
-
Undefined Instruction 遇到不能处理的指令
ARM异常模式
在ARM的基本工作模式中有5个属于异常模式,即ARM遇到异常后会切换成对应的异常模式
ARM异常响应
ARM产生异常后的动作(自动完成)
-
拷贝
CPSR
中的内容到对应异常模式下的SPSR_<mode>
:备份处理前的模式和状态 -
修改
CPSR
的值-
修改中断禁止位禁止相应的中断 :不希望被其他中断打断
-
修改模式位进入相应的异常模式 产生异常:
-
修改状态位进入ARM状态
-
-
保存返回地址到对应异常模式下的
LR_<mode>
-
设置
PC
为相应的异常向量(异常向量表对应的地址)
异常向量表
-
异常向量表的本质是内存中的一段代码
-
表中为每个异常源分配了四个字节的存储空间
-
遇到异常后处理器自动将PC修改为对应的地址
-
因为异常向量表空间有限一般我们不会再这里写异常处理程序,而是在对应的位置写一条跳转指令使其跳转到指定的异常处理程序的入口
相当于跳转两次
注:ARM的异常向量表的基地址默认在0x00地址 ,但可以通过配置协处理器来修改其地址
2. 工程模板代码结构分析
复习DAY13章节
3. 中断处理框架搭建
在前面学习中可以看到我们的启动代码里面的异常向量表是空的,现在我们要使用中断,所以需要更新一下异常向量表
异常向量表
_start:
/*
* Vector table
*/
b reset
b .
b .
b .
b .
b .
/*
* 从异常向量表再跳转到IRQ的异常处理程序
*/
b irq_handler
b .
中断处理程序
在学习寄存器的时候可以知道不同模式下有不同的寄存器组,有一些是通用的,有一些是特有的,在学习栈的时候说过一个寄存器覆盖的问题,在中断处理中也会有这个问题,所以我们需要在中断处理前做相应的操作
irq_handler:
/*
* 因为产生IRQ异常后ARM自动保存到LR中的返回地址是被IRQ打断的指令
* 的下一条再下一条指令的地址,所以我们需要人为的去修正一下
*不理解的话看下面寄存器的介绍
*/
sub lr, lr, #4
/*
* 因为IRQ模式下使用的R0-R12寄存器和USER模式下使用的是同一组
* 所以在处理异常之前需要先将之前寄存器中的值压栈保护
*/
stmfd sp!, {r0-r12,lr}
/*
* 跳转到do_irq处理异常
*/
bl do_irq @跳转到C语言中
/*
* 异常返回
* 1.将R0-R12寄存器中的值出栈,使其恢复到被异常打断之前的值
* 2.将SPSR寄存器中的值恢复到CPSR,使CPU的状态恢复到被异常打断之前
* 3.将栈中保存的LR寄存器的值出栈给PC,使程序跳转回被异常打断的点继续执行
*/
ldmfd sp!,{r0-r12,pc}^
LR寄存器
- R14(LR,Link Register) 链接寄存器
一般有以下两种用途:
- 执行跳转指令(BL/BLX)时,LR会自动保存跳转指令下一条指令的地址 程序需要返回时将LR的值复制到PC即可实现
- 产生异常时,对应异常模式下的LR会自动保存被异常打断的指令的下 一条指令的地址,异常处理结束后将LR的值复制到PC可实现程序返回
- 原理 当执行跳转指令或产生异常时,LR寄存器中不会凭空产生一个返回地址 其原理是当执行跳转指令或产生异常时,处理器内部会将PC寄存器中的 值拷贝到LR寄存器中,然后再将LR寄存器中的值自减4
BL
-
当执行BL指令时,指令执行过程中处理器内部就会将PC寄存器的值拷贝到 LR寄存器**,然后再将LR寄存器中的值自减4**, 所以LR寄存器中保存的就是 BL指令下一条指令的地址
-
该时刻PC=N+8 LR=N+4
IRQ中断
IRQ中断 当执行一条指令时产生了一个IRQ中断,执行这条指令过程中处理器不会保 存返回地址,而是执行完成后才会保存,但执行完成后PC的值又会自动增4, 所以对于IRQ来说LR中保存的是被中断打断的指令的下下条指令的地址
注意和跳转的区别
该时刻PC=N+12 LR=N+8
4. 中断处理程序编程
#include "exynos_4412.h"
void Delay(unsigned int Time)
{
while(Time--);
}
//IRQ异常处理
void do_irq(void)
{
printf("key2\n");
/*清除GPIO控制器中GPX1_1的中断挂起标志位*/
EXT_INT41_PEND = (1 << 1);
}
int main()
{
/*外设层次 - 让外部的硬件控制器产生一个中断信号发送给中断控制器*/
/*将GPX1_1设置成中断功能*/
GPX1.CON = GPX1.CON | (0xF << 4);
/*设置GPX1_1的中断触发方式为下降沿触发*/
EXT_INT41_CON = EXT_INT41_CON & (~(0x7 << 4)) | (0x2 << 4);
/*使能GPX1_1的中断功能*/
EXT_INT41_MASK = EXT_INT41_MASK & (~(1 << 1));
/*中断控制器层次 - 让中断控制器接收外设产生的中断信号并对其进行管理然后再转发给CPU处理*/
/*全局使能中断控制器使其能接收外设产生的中断信号并转发到CPU接口*/
ICDDCR = ICDDCR | 1;
/*在中断控制器中使能57号中断,使中断控制器接收到57号中断后能将其转发到CPU接口*/
ICDISER.ICDISER1 = ICDISER.ICDISER1 | (1 << 25);
/*选择由CPU0来处理57号中断*/
ICDIPTR.ICDIPTR14 = ICDIPTR.ICDIPTR14 & (~(0xFF << 8)) | (0X01 << 8);
/*使能中断控制器和CPU0之间的接口,使中断控制器转发的中断信号能够到达CPU0*/
CPU0.ICCICR = CPU0.ICCICR | 1;
GPX2.CON = GPX2.CON & (~(0xF << 28)) | (0x1 << 28);
while(1)
{
/*点亮LED2*/
GPX2.DAT = GPX2.DAT | (1 << 7);
/*延时*/
Delay(1000000);c
/*熄灭LED2*/
GPX2.DAT = GPX2.DAT & (~(1 << 7));
/*延时*/
Delay(1000000);
}
return 0;
}
当按键按下的时候会打印东西,但是无法停止,会一直打印下去
原因:产生中断信号后,
EXT_INT41_PEND
寄存器会自动置1,就会导致中断控制器一直给CPU中断信号,需要清零(写1清零)//IRQ异常处理 void do_irq(void) { printf("key2\n"); /*清除GPIO控制器中GPX1_1的中断挂起标志位*/ EXT_INT41_PEND = (1 << 1); }
在实际使用中很多硬件都能触发cpu的中断,我们需要对此进行区分信号,外部中断就响应外部中断处理程序,串口中断就响应串口处理程序
//IRQ异常处理
void do_irq(void)
{
unsigned int IrqNum = 0;
/*从中断控制器中获取当前中断的中断号*/
IrqNum = CPU0.ICCIAR & 0x3FF;
/*根据中断号处理不同的中断*/
switch(IrqNum)
{
case 0:
//0号中断的处理程序
break;
case 1:
//1号中断的处理程序
break;
/*
* ... ...
*/
case 57:
printf("Key2 Pressed\n");
/*清除GPIO控制器中GPX1_1的中断挂起标志位*/
EXT_INT41_PEND = (1 << 1);
/*将当前中断的中断号写回到中断控制器中,以这种方式来告知中断控制器当前的中断已经处理完成,可以发送其它中断*/
CPU0.ICCEOIR = CPU0.ICCEOIR & (~(0x3FF)) | (57);
break;
/*
* ... ...
*/
case 159:
//159号中断的处理程序
break;
default:
break;
}
}
ICCIAR_CPUn寄存器
中断控制器知道是谁产生的中断,通过读取这个的[9:0]位就能知道是谁给的,读取的值是中断号
ICCEOIR_CPUn寄存器
通知中断控制器,cpu已经处理完中断了,通过写相关的中断号,代表已经处理完毕,可以接着发送中断信号,否则会一直挂起其他中断
5. 作业
1.使用中断的方式检测Key3按键的状态,实现按一次按键,LED2点亮,再次按下,LED2熄灭
没修改启动文件的需要修改一下
#include "exynos_4412.h"
void LED_INIT(void)
{
GPX2.CON = GPX2.CON & (~(0XF << 28)) | (0x1 << 28);
}
void LED_ON(void)
{
GPX2.DAT = GPX2.DAT | (0x1 << 7);
}
void LED_OFF(void)
{
GPX2.DAT = GPX2.DAT & (~(0x1 << 7));
}
void Delay(unsigned int Time)
{
while(Time--);
}
void IRQ_INIT(void)
{
/*外设层次 - 让外部的硬件控制器产生一个中断信号发送给中断控制器*/
/*将GPX1_1设置成中断功能*/
GPX1.CON = GPX1.CON | (0xF << 8);
/*设置GPX1_1的中断触发方式为下降沿触发*/
EXT_INT41_CON = EXT_INT41_CON & (~(0x7 << 8)) | (0x2 << 8);
/*使能GPX1_1的中断功能*/
EXT_INT41_MASK = EXT_INT41_MASK & (~(1 << 2));
/*中断控制器层次 - 让中断控制器接收外设产生的中断信号并对其进行管理然后再转发给CPU处理*/
/*全局使能中断控制器使其能接收外设产生的中断信号并转发到CPU接口*/
ICDDCR = ICDDCR | 1;
/*在中断控制器中使能57号中断,使中断控制器接收到57号中断后能将其转发到CPU接口*/
ICDISER.ICDISER1 = ICDISER.ICDISER1 | (1 << 26);
/*选择由CPU0来处理57号中断*/
ICDIPTR.ICDIPTR14 = ICDIPTR.ICDIPTR14 & (~(0xFF << 16)) | (0X01 << 16);
/*使能中断控制器和CPU0之间的接口,使中断控制器转发的中断信号能够到达CPU0*/
CPU0.ICCICR = CPU0.ICCICR | 1;
}
//IRQ异常处理
void do_irq(void)
{
static int a = 0;
unsigned int IrqNum = 0;
/*从中断控制器中获取当前中断的中断号*/
IrqNum = CPU0.ICCIAR & 0x3FF;// 0-9位为1
/*根据中断号处理不同的中断*/
switch(IrqNum)
{
case 0:
//0号中断的处理程序
break;
case 1:
//1号中断的处理程序
break;
/*
* ... ...
*/
case 58:
/*判断GPX1_2引脚的状态,即判断按键是否按下*/
if( a == 0) //除了自己要读的位保持不变,其他位全部清零,读取到低电平表示按下所以要取非才能做出判断
{
LED_OFF();
printf("ON\n");
a = 1;
}
else //除了自己要读的位保持不变,其他位全部清零,读取到低电平表示按下所以要取非才能做出判断
{
LED_ON();
printf("OFF\n");
a = 0;
}
printf("Key3 Pressed\n");
/*清除GPIO控制器中GPX1_2的中断挂起标志位*/
EXT_INT41_PEND = (1 << 2);
/*将当前中断的中断号写回到中断控制器中,以这种方式来告知中断控制器当前的中断已经处理完成,可以发送其它中断*/
CPU0.ICCEOIR = CPU0.ICCEOIR & (~(0x3FF)) | (58);
break;
/*
* ... ...
*/
case 159:
//159号中断的处理程序
break;
default:
break;
}
}
int main()
{
LED_INIT();
LED_ON();
IRQ_INIT();
while(1)
{
}
return 0;
}