目录
一、ARM的异常处理机制
异常
处理器在正常执行程序的过程中可能会遇到一些不正常的事件发生 这时处理器就要将当前的程序暂停下来转而去处理这个异常的事件 异常事件处理完成之后再返回到被异常打断的点继续执行程序
2.异常处理机制
不同的处理器对异常的处理的流程大体相似,但是不同的处理器在具体实现的机制上有所不同;比如处理器遇到哪些事件认为是异常事件遇到异常事件之后处理器有哪些动作、处理器如何跳转到异常处理程序如何处理异常、处理完异常之后又如何返回到被打断的程序继续执行等我们将这些细节的实现称为处理器的异常处理机制
3.异常源
导致异常产生的事件称为异常源
ARM异常源
- FIQ 快速中断请求引脚有效(外接设备向CPU请求服务使用)(相应速度更快)
- IRQ 外部中断请求引脚有效 (外接设备向CPU请求服务使用)
- Reset 复位电平有效
- Software Interrupt 执行swi指令
- Data Abort 数据终止
- Prefetch Abort 指令预取终止
- Undefined Instruction 遇到不能处理的指令
4.ARM异常模式
在ARM的基本工作模式中有5个属于异常模式,即ARM遇到异常后会切换成对应的异常模式
5.ARM异常响应
ARM产生异常后的动作(自动完成)
- 拷贝CPSR中的内容到对应异常模式下的SPSR_<mode>:备份处理前的模式和状态
- 修改CPSR的值
- 修改中断禁止位禁止相应的中断 :不希望被其他中断打断
- 修改模式位进入相应的异常模式 产生异常:
- 修改状态位进入ARM状态
- 保存返回地址到对应异常模式下的LR_<mode>
- 设置PC为相应的异常向量(异常向量表对应的地址)
6. 异常向量表
-
异常向量表的本质是内存中的一段代码
-
表中为每个异常源分配了四个字节的存储空间
-
遇到异常后处理器自动将PC修改为对应的地址
-
因为异常向量表空间有限一般我们不会再这里写异常处理程序,而是在对应的位置写一条跳转指令使其跳转到指定的异常处理程序的入口
-
注:ARM的异常向量表的基地址默认在0x00地址 ,但可以通过配置协处理器来修改其地址
二、工程模板代码结构分析
三、中断处理框架搭建
处理中断时:
1.LR保存返回地址的原因:
1)执行跳转指令
2.遇到异常 。这两种情况PC指向不同
所以需要人为修正PC的值
对usr模式的寄存器压栈保护,因为不同模式下有通用的寄存器,有一些是特有的,在学习混合编程 栈的时候有寄存器覆盖的问题,在中断处理中也有,所以我们需要在中断处理前做相应的操作
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}^
四、中断处理程序编程
#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(挂起)
寄存器 [7:0] 每一位对应一位引脚,产生中断信号后,EXT_INT41_PEND(挂起)
寄存器会自动置1,就会导致中断控制器一直给CPU0中断信号,需要清零(写1清零)
//IRQ异常处理
void do_irq(void)
{
printf("key2\n");
/*清除GPIO控制器中GPX1_1的中断挂起标志位*/
EXT_INT41_PEND = (1 << 1);
}
在实际使用中很多外部设备都能触发CPU的中断,诸多都是IRQ信号,CPU无法识别谁发的,需要向中断控制器读取是谁发的;所有通过中断控制器对此进行区分信号,外部中断就响应外部中断处理程序,串口中断就响应串口处理程序
读完ID后,CPU处理中断,处理完后但是中断控制器不知道CPU处理完了,所以CPU处理完中断需要通知中断控制器。
用此寄存器通知 ,将处理完的中断号写入[9:0],
最终中断处理程序
#include "exynos_4412.h"
void Delay(unsigned int Time)
{
while(Time--);
}
//异常处理程序
void do_irq(void)
{
unsigned int IrqNum = 0;
//从中断控制器中读取当前中断的中断号
IrqNum = CPU0.ICCIAR & 0x3FF;//将高22位全部清0
switch(IrqNum)
{
case 0:
//0号中断的处理程序
break;
case 1:
//1号中断的处理程序
break;
case 57:
printf("Key2 press\n");
//清楚GPIO控制器中的中断挂起位
EXT_INT41_PEND = (1 << 1);
//将当前中断的中断号写会中断控制器,以这种方式告知中断控制器
//当前中断已经处理完毕,可以发送其他的中断
CPU0.ICCEOIR = CPU0.ICCEOIR & (~(0x3FF << 10)) | (57);
break;
//..
case 159:
//159号中断的处理程序
default:
break;
}
}
int main()
{
/*1.外设层次,让外部的硬件控制器能产生一个中断信号病发送给中断控制器*
*将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));
/*2.中断控制器层次,将中断控制器接收外设发来的中断信号并对其进行和管理
*然后再转发给一个合适的CPU处理
* */
//全局使能中断控制器.使其能够接收外部设备产生的中断信号并转发给cpu接口
ICDDCR = ICDDCR | 1;
//延时
Delay(1000000);
//在中断控制器中使能57号中断,使中断控制器在接收到57号中断后能将其进一步转发给cpu接口
ICDISER.ICDISER1 = ICDISER.ICDISER1 | (1<<25);
//选择CPU处理57号中断
ICDIPTR.ICDIPTR14 = ICDIPTR.ICDIPTR14 & (~(0xFF << 8)) | (0x01 << 8);
//将中断控制器和cpu之间的接口使能,是中断控制器转发的信号能到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);
//熄灭led2
GPX2.DAT =GPX2.DAT & (~(1 << 7));
//延时
Delay(1000000);
}
return 0;
}