上篇说了GPIO的裸机编程,实现了按键+亮灯的功能,但对按键是否按下的检测是通过轮询机制实现的,这种方法太过耗费cpu资源,今天使用中断方式来减轻对cpu的消耗。
实现原理
- 1.其实要实现功能的原理和按键轮询机制是一样的,不同的是使用中断不会让cpu一直去查询按键是否按下,而是在按键按下后通知cpu去处理。因此,在没有按键按下时cpu可以去处理其他事务而不是一直查询按键状态。
- 2.中断操作
1>.中断发生前:初始化中断引脚,使能中断。
2>.中断发生后:保存当前状态(处理完中断事件后cpu需要返回接着处理未完事务)。
3>.中断处理:分辨中断,中断处理,清除中断状态(否则cpu会认为中断一直在触发)。
4>.处理完成:恢复保存的断点,继续执行原先事务。 - 3.中断寄存器设置
1>.中断控制寄存器。
2>.使能/禁止寄存器。
3>.状态寄存器。
4>.设置高/低电平触发。
5>.引脚设置。
6>.中断优先级设置。
工作模式
- 1.在arm下,每种工作模式都有16个通用寄存器和1到2个程序状态寄存器,发生中断后就进入了中断模式。
- 2.各工作模式下的寄存器中R0~R15可以直接访问,他们中除了R15外都是通用寄存器,即既可以保存数据也可以保存地址。
- 3.R3~R15:
1>.R13:栈指针寄存器(sp),用于保存栈地址
2>.R14:程序连接寄存器 - 4.处于某种模式时,sp即为该模式私有sp,即R13寄存器保存的地址。
- 5.第17个寄存器CPSR:当前程序状态寄存器,其一些位用于表示状态,一些位用于表示当前处于什么模式。如下:
从第30位到第0位,依次为:
N Z C V ... I F T M4 M3 M2 M1 M0
(置位有效)负数标志,0标志,进位标志,溢出标志 ... 总中断IRQ禁止位,FIQ禁止位,状态位(T),M4~M0的组合表示当前模式
源码(省去寄存器地址配置)
- 1.head.S
该部分设置异常向量表,初始化各资源。
@初始化文件:初始化中断向量表,设置中断栈,设置中断处理函数
.extern main
.text
.global _start
_start:
@中断向量表
b Reset @复位(按下复位键或上电),0地址
HandleUndef:
b HandleUndef @0x04地址
HandleSWI:
b HandleSWI
HandlePrefetchAbort:
b HandlePrefetchAbort
HandleDataAbort:
b HandleDataAbort
HandleNotUsed:
b HandleNotUsed
b HandleIRQ @快中断
HandleFIQ:
b HandleFIQ
Reset:
ldr sp ,=4096 @设置栈指针(跳过前4k)
bl disable_watch_dog @关闭看门狗
msr cpsr_c, #0xd2 @设置当前程序状态寄存器,I:1(禁止IRQ),F:1(禁止FIQ),M4:1,M0:1(进入FIQ模式)
ldr sp,=3072 @设置当前模式栈指针
msr cpsr_c, #0xd3 @进入管理模式
ldr sp, =4096 @可省略(reset后cpu即处于管理模式,reset中已设置过)
bl init_led @初始化led
bl init_irq @初始化可中断
msr cpsr_c, #0x53 @开启IRQ,进入管理模式
ldr lr, =halt_loop @设置返回地址
ldr pc, =main
halt_loop:
b halt_loop
HandleIRQ:
sub lr, lr, #4 @计算返回地址(被中断出)
stmdb sp!, { r0-r12,lr } @保存现场(寄存器)
ldr lr, =int_return @设置调用ISR(中断处理服务)即EINT_Handle后的地址
ldr pc, =EINT_Handle @调用中断处理函数
int_return:
ldmia sp!, { r0-r12,pc }^ @中断返回,^表示将spsr(程序状态保存寄存器)的值复制到cpsr
- 2.init.c
该部分主要实现led及中断的初始化代码。
#define GPF4OUT (1<<8)
#define GPF5OUT (1<<10)
#define GPF6OUT (1<<12)
#define GPF4MASK (3<<8) //用作清除当前寄存器状态(置为Reserved状态)
#define GPF5MASK (3<<10)
#define GPF6MASK (3<<12)
#define GPF0EIN (0x2) //GPF0管脚配置为中断模式
#define GPF2EIN (0x2<<4) //GPF2管脚配置为中断模式
#define GPG3EIN (0x2<<6) //GPG3管脚配置为中断模式
#define GPF0MASK (0x3<<0)
#define GPF2MASK (0x3<<4)
#define GPG3MASK (0x3<<6)
void disable_watch_dog(void)
{
WTCON = 0x0;
}
void init_led(void)
{
GPFCON &= ~(GPF4MASK | GPF5MASK | GPF6MASK); //将寄存器对应位清0
GPFCON |= (GPF4OUT | GPF5OUT | GPF6OUT); //设置led对应寄存器为输出模式
}
void init_irq(void)
{
/**配置中断0,2,11引脚为中断模式**/
GPFCON &= ~(GPF0MASK | GPF2MASK);
GPFCON |= GPF0EIN | GPF2EIN;
GPGCON &= ~GPG3MASK;
GPGCON |= GPG3EIN;
EINTMASK &= ~(1<<11); //使能EINT11;对于EINT8-23需要设置EINTMASK和INTMSK,对于EINT0-EINT3只需设置INTMSK
PRIORITY = (PRIORITY & ((~0x01) | (0x3<<7))) | (0x0 << 7);
INTMSK &= (~(1<<0)) & (~(1<<2)) & (~(1<<5)); //EINT0,EINT2,EINT11(第5位为8~23共用使能位)
}
- 3.interrupt.c
该部分实现了中断处理函数。
void EINT_Handle(void)
{
unsigned long oft = INTOFFSET;
switch(INTOFFSET)
{
case 0:
GPFDAT |= (0x7<<4);
GPFDAT &= ~(1<<4);
break;
case 2:
GPFDAT |= (0x7<<4);
GPFDAT &= ~(1<<5);
break;
case 5:
GPFDAT |= (0x7<<4);
GPFDAT &= ~(1<<6);
break;
}
//清除中断(否则cpu会认为中断又一次发生了),对于EINT8-23需要清除EINTPEND和SRCPND,对于EINT0-EINT3只需清除SRCPND
if(oft == 5)
EINTPEND = 0x1<<11;
SRCPND = 1<<oft;
INTPND = 1<<oft;
}
- 3.main.c
main函数在此只需将程序阻塞,保证不会退出即可。可使用while(1)死循环。