声明:本文为本人学习朱有鹏老师讲解的《ARM裸机编程》中“按键与中断”章节的学习总结
如有任何侵权行为,请联系本人删除—-QQ:534116982
近期在学习按键与中断这个章节,在此做一个梳理与总结,便于以后的查阅
由于本人个人的能力水平有限,不保证所写内容的正确性,仅作为当前学习的笔记记录,也欢迎各位朋友一起讨论,衷心感谢!
正文:
cpu为了支持处理异常的功能,通常需要解决的几个问题:
1–CPU如何支持异常处理
目前大部分CPU采用异常向量表机制。异常向量表在CPU设计时被确定,当异常发生时,CPU会自动跳转到指定的位置去执行代码,来实现对异常的处理;异常向量表中各个向量的相对位置是固定的
举例:s5pv210的异常向量表
0x1c------fiq
0x18------irq
0x14------(Reserved)
0x10------data_abort
0x0c------prefetch_abort
0x08------software interrupt
0x04------undefined instruction
0x00------Reset
2–多种中断源可能会导致产生相同的异常,如何区分是哪种中断源
各个CPU对支持的中断源都有一个唯一的编号,得到这个编号就得知了产生中断的中断源
如何得到这个编号?多数的CPU通过特定的寄存器中的不同的位来表示每个中断源
举例:
s3c2440 采用的方法:
用一个32位寄存器和一个子中断寄存器;32位寄存器中的每一位对应一种或两种中断源
因为32个中断源不够用,所以有些只好共用一位,1就表示发生了该种中断,0表示没有
对于共用的中断源,再通过子中断寄存器去区分
s5pv210采用的方法:
用了4个32位的寄存器(VIC0----VIC3),这4个32位寄存器上的每一位对应了一种中断源
理论上S5PV210支持128个中断源,但中间有的位没有使用;这组寄存器中的某位被置1时,则表示发生了该种中断。
3–得知中断编号后,如何找到其对应的中断处理程序(isr)
s3c2440 采用的方法:
需要用软件来实现,将各中断处理子函数的地址,做成一张表(即函数指针数组)
然后通过软件查询寄存器得到中断源的编号,再去这个表中找到对应的元素(子函数地址),即找到了isr
s5pv210采用的方法:
通过硬件来实现,程序员在配置时,将中断处理子函数的地址写到对应的寄存器中
当中断发生时,由硬件来完成isr的查找,通过读VICnADDR寄存器就可以得到这个isr
s5pv210中断处理的几个主要寄存器概述
1.VICnINTENABLE
中断源使能寄存器: 写1有效 写0不影响
2.VICNINTENCLEAR
中断源禁止寄存器: 写1有效 写0不影响
3.VICnINTSELECT
设置中断源模式寄存器:1--FIQ模式 0--IRQ模式
4.VICnIRQSTATUS
中断状态寄存器:0--无 1--产生中断
查询这组寄存器可以得到中断源编号
5.VICnVECTPRIORITY0 ---- VICnVECTPRIORITY31
中断优先级配置寄存器
6.VICnVECTADDR0 --- VICnVECTADDR31
这4 * 32个寄存器 分别用来存放每个中断源对应的isr的函数地址
7.VICnADDR
这组寄存器只需要读;当发生了中断,硬件会根据中断编号自动找到其VECTADDR对应的地址,并复制到该寄存器中
s5pv210的中断配置步骤:
STEP1. 异常向量表的初始化
将异常发生后需要执行的程序入口,根据异常向量表的格式位置,填到对应的地址中
//结构体代表各个异常在异常向量表的相对位置
typedef struct
{
uint32 Reset;
uint32 Undef;
uint32 SWI;
uint32 Prefetch;
uint32 Data;
uint32 Reserved;
uint32 IRQ;
uint32 FIQ;
}VECTOR_Typedef;
//异常向量表的基地址
#define VECTOR_BASE 0xD0037400
//使用VECTOR即可以访问这个地址
#define VECTOR ((INT_Typedef *)VECTOR_BASE)
//在初始化程序中 填好指定的服务程序入口
void IRQ_HANDLE(void);
void interrupt_init(VECTOR_Typedef *vector)
{
vector->IRQ = (uint32)IRQ_HANDLE;//将IRQ_HANDLE函数的地址放入中断向量表的IRQ位置
vector->FIQ = (uint32)IRQ_HANDLE;
vector->Data = (uint32)exceptiondabort;
vector->Undef = (uint32)exceptionundef;
...
// 初始化中断控制器
intc_init(VIC0);
}
这个IRQ_HANDLE的具体实现放在start.S中
这是因为中断产生,进入中断服务程序后,要做的第一步是保存现场。
保存现场是为了中断返回后,程序能够回到中断时的位置继续执行下去
而保存现场及回复现场的代码需要用汇编语言实现,所以写在start.S中
而intc_init()函数是配置中断寄存器,这两个函数在下面会讲到。
STEP2. 中断源寄存器的初始化
// 初始化中断源控制器
void intc_init(void)
{
// 禁止所有中断源
VIC0INTENCLEAR = 0xffffffff;
VIC1INTENCLEAR = 0xffffffff;
VIC2INTENCLEAR = 0xffffffff;
VIC3INTENCLEAR = 0xffffffff;
// 选择中断类型为IRQ
VIC0INTSELECT = 0x0;
VIC1INTSELECT = 0x0;
VIC2INTSELECT = 0x0;
VIC3INTSELECT = 0x0;
// 清VICxADDR
intc_clearvectaddr();
}
// 清除需要处理的中断的中断处理函数的地址
void intc_clearvectaddr(void)
{
// VICxADDR:当前正在处理的中断的中断处理函数的地址
VIC0ADDR = 0;
VIC1ADDR = 0;
VIC2ADDR = 0;
VIC3ADDR = 0;
}
STEP3.完善做好现场保护工作
为了中断返回后程序能回到刚才中断的地方继续向下执行,需要在汇编中实现现场的保存/恢复工作
start.S文件中有如下定义
.global IRQ_HANDLE
IRQ_HANDLE:
//设置IRQ模式下的栈
ldr sp, =IRQ_STACK //#define IRQ_STACK 0xD0037F80 这个地址是手册规定的地址
//保护现场
//lr减4得到下条指令地址
sub lr, lr, #4; //因为流水线的关系,lr中的值是当前执行的指令地址的下两条指令的地址(+8 byte)
//保存r0-r12,lr
stmfd sp!,{r0-r12, lr}
//跳转至中断服务程序
bl irq_handler//这个irq_handler是真正的中断服务程序,用C语言实现
//现场恢复
ldmfd sp!,{r0-r12, pc}^
STEP4.完善irq_handler函数
目前假设中断产生了,并假设已经绑定好了中断处理函数的地址(实际上这两步还没有完成)
那么:产生异常–>通过异常向量表跳到irq入口–>进入汇编现场保护部分–>跳到irq_handler中之后,仍有个问题就是得到ISR地址并执行中断处理函数的问题。
因为有四个VICADDR寄存器,而正确的地址只在其中一个寄存器中;所以要找到正确的寄存器
在irq_handler中通过调用intc_getvivirqstatus()函数来读取正确的寄存器并执行中断处理函数
//查找中断源寄存器
unsigned long intc_getvicirqstatus(unsigned long ucontroller)
{
if(ucontroller == 0)
return VIC0IRQSTATUS;
else if(ucontroller == 1)
return VIC1IRQSTATUS;
else if(ucontroller == 2)
return VIC2IRQSTATUS;
else if(ucontroller == 3)
return VIC3IRQSTATUS;
else
{}
return 0;
}
// 通用中断处理函数
void irq_handler(void)
{
unsigned long vicaddr[4] = {VIC0ADDR,VIC1ADDR,VIC2ADDR,VIC3ADDR};
int i=0;
void (*isr)(void) = NULL;
for(; i<4; i++)
{
//通过查找中断源寄存器来确定该中断源使用的寄存器组(0-3)
if(intc_getvicirqstatus(i) != 0)
{
isr = (void (*)(void)) vicaddr[i];
break;
}
}
(*isr)();
}
解决了ISR查找问题后,就可以继续完成余下的工作–配置中断源和绑定ISR
STEP5.配置外部中断,使得外部中断可以产生中断源
// 配置成外部中断模式
GPH2CON |= 0xF;
// 010 = Falling edge triggered下降沿触发
EXT_INT_2_CON |= 1<<1;
// unmasked 外部中断使能
EXT_INT_2_MASK &= ~(1<<0);
STEP6.编写中断服务程序
注意:在中断服务程序中,要注意清对应的中断挂起寄存器的位,并且是写1清除; 同时还要把取出后的地址清理掉
void isr_key(void)
{
printf("we get company:EINT16_31\r\n");
//清地址
intc_clearvectaddr();
// clear pending bit
EXT_INT_2_PEND |= 1<<0;
}
STEP7.将上一步编写好的中断服务程序与使用的中断进行绑定
绑定之后,如果发生该中断,则硬件会将绑定好的isr 自动放入VICnADDR寄存器中
在main.c中
intc_setvectaddr(NUM_EINT16_31, isr_key);
来绑定中断源编号为NUM_EINT16_31的中断源与isr_key中断函数
这样,在发生中断编号为NUM_EINT16_31的时候,硬件会自动将绑定好的isr_key函数地址
复制到VICnADDR寄存器中,如果不绑定,发生该中断时,就不知道执行什么
绑定函数的实现在int.c中
//因为有4组绑定地址的寄存器,具体要将地址写入到哪个寄存器中,要通过编号来选择
void intc_setvectaddr(unsigned long intnum, void (*handler)(void))
{
//VIC0
if(intnum<32)
{
*( (volatile unsigned long *)(VIC0VECTADDR + 4*intnum) ) = (unsigned)handler;
}
//VIC1
else if(intnum<64)
{
*( (volatile unsigned long *)(VIC1VECTADDR + 4*(intnum-32)) ) = (unsigned)handler;
}
//VIC2
else if(intnum<96)
{
*( (volatile unsigned long *)(VIC2VECTADDR + 4*(intnum-64)) ) = (unsigned)handler;
}
//VIC3
else
{
*( (volatile unsigned long *)(VIC3VECTADDR + 4*(intnum-96)) ) = (unsigned)handler;
}
return;
}
STEP8.相应的中断源使能
在main.c中
intc_enable(NUM_EINT16_31);
调用intc_enable(NUM_EINT16_31);来使能NUM_EINT16_31号中断源
特别提醒:这里使能的是中断源,可以理解为打开了这个编号的中断源的接收开关。
在使用具体的中断的时候,还需要设置具体中断的寄存器来使能具体的中断
使能中断源的实现部分:
// 根据中断源编号使能中断源 如果数大于NUM_ALL 中断源会全部使能
void intc_enable(unsigned long intnum)
{
unsigned long temp;
if(intnum<32)
{
temp = VIC0INTENABLE;
temp |= (1<<intnum);
VIC0INTENABLE = temp;
}
else if(intnum<64)
{
temp = VIC1INTENABLE;
temp |= (1<<(intnum-32));
VIC1INTENABLE = temp;
}
else if(intnum<96)
{
temp = VIC2INTENABLE;
temp |= (1<<(intnum-64));
VIC2INTENABLE = temp;
}
else if(intnum<NUM_ALL)
{
temp = VIC3INTENABLE;
temp |= (1<<(intnum-96));
VIC3INTENABLE = temp;
}
// NUM_ALL : enable all interrupt
else
{
VIC0INTENABLE = 0xFFFFFFFF;
VIC1INTENABLE = 0xFFFFFFFF;
VIC2INTENABLE = 0xFFFFFFFF;
VIC3INTENABLE = 0xFFFFFFFF;
}
}
通过以上步骤,s5pv210的外部中断配置完成;
扩展:S5pv210是如何保证fiq比irq更快的?
1--fiq模式下的专用寄存器
s5pv210在fiq模式下,r8--r12是专用的,因此在 fiq的isr中,可以直接使用R8-R12而不用保存
2--异常向量表中的位置
fiq是异常向量表中最后一个向量,而其他向量由于它们之间只有4字节大小的空间,所以只能放一条跳转指令
而fiq就不必进行跳转,可以直接将中断处理函数写在这里,省去了执行跳转指令的时间
基于以上两种原因,fiq可以比irq更快