s5pv210中断部分是我学习arm以来较为复杂的一部分,并且很难理解里面的关系,现在我写一篇博客来把思路挑清楚,使得这一部分不再杂乱无章。
按键只是中断系统的一部分,也就是说按了按键中断就产生了。比如按键按下了LED灯亮了,其实这就是一个中断。他按下了就把正在进行的事情停止了下来,去做LED灯亮的中断。
查S5PV210的硬件原理图
一共有7个按键,能编程按键的只有SW5~10,从图中可以看出左边VDD_IO是高电压的,当按键按下时会与右边的接地符号导通。从而使整个按键处于低电平。底版中的EINT2-3或则KP—COL3则是引了条引线到核心板中。
SW5:GPHO_2
SW6:GPH0_3
同理通过先查底板的原理图,再查核心板的原理图可以得到sw7-11对应的寄存器
SW7:GPH2_0
SW8:GPH2_1
SW9:GPH2_2
SW10:GPH2_3
-
SoC按键有2种思路:轮询方式和中断方式。
- 轮询方式,就是SoC主动的每隔一段时间去读取(按键所对应的)GPIO的电平高低,以此获得按键信息;
中断方式,就是SoC事先设定好GPIO触发的中断所对应的中断处理程序ISR,当外部按键按下或弹开时会自动触发GPIO对应的外部中断,导致ISR执行,从而自动处理bbbbbbbbbbbbbbbbbbb从 按键信息。
首先介绍一下轮询方式的中断
由上分析可知可设置是寄存器是GPIO(通用输入输出模式),按键不按下是高电平,按下是低电平,按键的对应的GPIO为GPH0 GPH2,可把这两个寄存器设置为输入模式。
轮询方式处理中断的流程:- 第一步,先初始化GPIO模式为input
- 第二歩,轮询读取GPIO的值,差看是否按键有被按下产生中断。
初始化按键:
//先创建key.c的文件,然后开始往里面写代码
//这些宏定义都是查看数据手册得知的。
#define GPH0CON 0xE0200C00
#define GPH0DAT 0XE0200C04
#define GPH2CON 0xE0200C40
#define GPH2DAT 0XE0200C44
//把这些宏定义对应的寄存器作为变量存储
#define rGPH0CON (*(volatile unsigned int*)GPH0CON)
#define rGPH0DAT (*(volatile unsigned int*)GPH0DAT)
#define rGPH2CON (*(volatile unsigned int*)GPH2CON)
#define rGPH2DAT (*(volatile unsigned int*)GPH2DAT)
//初始化按键
void key_init(void)
{
//先把各按键的中断寄存器控制位设置为输入模式,即设置GPHXCON寄存器
//查看数据手册可知GPH0CON的bit8~15全部设置为0,即把GPH0CON_2和3全部设置为输入模式
rGPH0CON &= ~(0xff<<8);
//查看数据手册可知GPH2CON的bit0~15全部设置为0,即把GPH2CON_0、1、2和3全部设置为输入模式
rGPH2CON &= ~(0xffff<<0);
}
void key_polling(void)
{
//依次,去读取每个GPIO的值,判断是否按键有按下而产生中断
while(1)
{
//这里只是做一个按键的独立,还可拓展到各个按键的独立中断
//中断函数功能可以写led亮或灭、
if(rGPH0DAT & (1<<2))
{
//为1,说明没有按键
}
else
{
//为0,说明有按键
}
.
.
.
.
.
.
}
}
-
真正的中断处理:
- 为什么需要中断?因为单核CPU实际无法并行的,但是通过中断机制,可以实现假并行(宏观上的并行,微观上实际还是串行的)。
异常向量表是很重要的,他可以提供给中断程序入口地址。使得COU知道在哪里中断。这是CPU在设计时就已经定义好的入口地址,不能通过人来改变的,(譬如定义0x00000000地址为复位异常(其中一种中断)向量地址,则发生复位异常时CPU会自动跳转到0x00000000地址去执行指令。)硬件已经设计好发生什么异常,CPU自动跳转PC到哪个地址去执行,软件需要做的就是把处理这个异常的代码的首地址填入异常向量地址。
-
异常向量表的编程处理
- S5PV210的异常向量表是可以改变的(在c15协处理器中),以适应操作系统的需求。但是目前系统刚启动时,此时DRAM尚未初始化,程序都在SRAM中运行。210在iRAM中设置了异常向量表,供暂时性使用。
- 查210的iROM application note 文档中iRam的地址分配,可知,iRAM中异常向量表其实地址为0xD0037400。知道了异常向量表的其实地址后,各和异常对应的入口就知道了。
中断处理程序最好用汇编语言去写,因为中断处理程序要注意保护现场,很多种中断模式,用的寄存器是相同的,我们执行完一个中断的时候需要保护好这个中断里面的数据,不能被下一个处理函数覆盖掉。保护现场包括:1.设置IRQ栈;2.保存LR;第三,保存R0~R12
_satrt:
// 第1歩:关看门狗(向WTCON的bit写入0即可)
ldr r0, =WTCON
ldr r1, =0x0
str r1, [r0]
// 第2歩:初始化时钟
bl clovk_init
// 第3歩:设置SVC栈
ldr sp, = SVC_STACK
// 第4歩:开/关icache
mrc p15,0,r0,c1,c0,0; //读出cp15的c1到r0中
//bic r0, r0 , #(1<<12) //bit12 置0 关icache
orr r0, r0, #(1<<12) //bit12 置1 开icache
mcr p15,0,r0,c1,c0,0;
bl main
b.
// 重点中断处理函数
// 在这个汇编函数中,用来做中断模式下的现场保护和恢复
IRQ_handle:
// 设置IRQ模式下的栈
ldr sp, =IRQ_STACK
// 保存lr,因为arm有流水线,所以PC的值会比真正执行的代码+8
sub lr,lr, #4
// 保存r0-r12和lr到irq模式下的栈上面
stmfd sp!, {r0-12,lr}
//在此调用真正的isr来处理中断
bl irq_handler
// 处理完成开始恢复现场,其实就是做中断返回,关键是将r0-r12,pc,cpsr一起恢复
ldmfd sp!, {r0-r12,pc}^
-凡是中断,都要设计两个中断处理过程:
- 如何找到具体是哪个中断:s5pv210中因为支持的中断源很多,所以直接设计了4个中断寄存器,每个32位,每位对应一个中断源。(理论上210最多支持128个中断,实际上不足128个,有些位是置空的);210没有子中断寄存器,每个中断源都是并列的。当中断发生时,在irq_handler中一次去查询4个中断源寄存器,看哪一个的哪移位被置1,则这个位对应的寄存器就发生了中断,即找到了中断编号。
- 怎么知道到对应的isr的问题:S5PV210对应的isr处理的优势在于他运用了软件和硬件结合的方式。当发硬相应中断时,硬件会自动的将相应的isr推入一定的寄存器中,我们软件只要去这个寄存器中执行函数就行了。
- 接下来:介绍几个重要的寄存器
- VICnINTENABLE :负责中断使能
- VICnINTENCLEAR:负责中断禁止
- VICnINSELECT:选择中断模式的(写0irq写1fiq)
- VICnIRQSTATUS:中断状态寄存器,当发生中断时,硬件会自动将寄存器对应的位置为1
- VICnFIQSTATUS:中断状态寄存器,当发生中断时,硬件会自动将寄存器对应的位置为1
- VICnVECTPRIORITY0~31:中断优先级使能
- VICnVECTADDR0~31:分别用来存放各个中断对应的isr的函数地址。程序在设置中断的时候,把这个中断的isr地址直接放入这个中断对应的VECTADDR寄存器即可
VICnADDR :这个寄存器是只读的,当发生相应中断时,硬件会自动识别中断编号,并且会自动找到这个中断的VECTADDR寄存器,然后将其读出复制到VICnADDR中,供我们使用。
介绍几个重要的函数
//主要功能:绑定异常向量表,禁止所有中断,把中断模式设置为IRQ模式,清VICxADDR寄存器的地址
void system_init_exception(void)
{
//绑定异常向量表
r_exception_reset = (unsigned int)reset_exception;
r_exception_undef = (unsigned int)undef_exception;
r_exception_sotf_init = (unsigned int)sotf_init_exception;
r_exception_prefetch = (unsigned int)prefetch_exception;
r_exception_data = (unsigned int)data_exception;
r_exception_irq = (unsigned int)irq_exception;
r_exception_fiq = (unsigned int)fiq_exception;
//初始化中断控制器的基本寄存器
intc_init();
}
//初始化中断控制器
vaoid intc_init(void)
{
//禁止所有中断,防止还未设置好中断时进入中断导致执行错误的代码
VIC0INTENCLEAR = 0xffffffff;
VIC1INTENCLEAR = 0xffffffff;
VIC2INTENCLEAR = 0xffffffff;
VIC3INTENCLEAR = 0xffffffff;
// 选择中断类型为IRQ
VIC0INSELECT = 0X0;
VIC1INSELECT = 0X0;
VIC2INSELECT = 0X0;
VIC3INSELECT = 0X0;
//清VICxADDR
intc_clearvectaddr();
}
//清楚需要处理的中断的中断处理函数的地址
void intc_clearvectaddr(void)
{
//VICxADDR;当前正在处理的中断的中断处理函数的地址
VIC0ADDR = 0;
VIC1ADDR = 0;
VIC2ADDR = 0;
VIC3ADDR = 0;
// 通过传参的intnum来使能某个具体的中断源,中断号在int.h中定义,是物理中断号
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
{
}
}
//绑定我们写的isr到VICnVECTADDR寄存器参数:intnum是中断号,handler是一个函数指针,就是我们写的isr
void intc_setvectaddr(unsigned long intnum,void (*handler)(void))
{
//VIC0
if(intnum <32)
{
*((volatile unsigned long *)(VIC0VECTADDR +4*(intnum-0)) ) = (unsigned)handler;
}
//VIC1
else if (intnum <64)
{
*((volatile unsigned long *)(VIC1VECTADDR +4*(intnum-32)) ) = (unsigned )handler;
}
.
.
.
.
.
.
}
//真正的中断处理程序。只考虑处理中断,不考了保护、恢复现场
void irq_handler(void)
{
//虽然硬件已经自动把isr(中断处理程序)从VICnVECTADDR中传入了VICnADDR中,但是还不知道到底是哪个VICnADDR,我们这时候还需要运用软件来检查一下,到底是在哪个n中
unsigned long vicaddr[4] = {VIC0ADDR, VIC1ADDR, VIC2ADDR, VIC3ADDR};
int i = 0;
void (*isr)(void) = NULL;
for(i=0; i<4; i++)
{
if(intc_getvicirqstatus(i) != 0)
{
isr = (void (*)(void))vicaddr[i];
break;
}
}
(*isr)();
}
参考资料《朱老师物联网大讲堂》