Part14: 按键中断与定时器
Part14: 按键中断与定时器
0 明确硬件中断处理
对于s3c2440开发版,查手册可获得如下图
上图不方便注释寄存器的用途,在此从中断源–>中断寄存器–>CPU详细说明
1)对于中断源:s3c2440有60种中断源。中断源分为两类:带sub-register和不带sub-register.
带sub-register是指某一个中断源有多种中断,需借助sub-register寄存器(即SUBSRCPND和SUBMASK共同作用)辨别。
如对于UART0串口发生中断时,在SRCPND对应位置1,但无法区分是接收/发送数据或发送/接收数据异常引起的中断,
因此可设置SUBSRCPND明确UART0的具体中断。
反之,不带sub-register的中断源,可直接到达SRCPND寄存器,即不需借助其它寄存器加以区分
2)可同时发生多个中断,此时,SRCPND寄存器对应位自动置位,表示有中断发生
3)此外,还可设置MASK屏蔽寄存器(INTMSK和INTSUBMSK)屏蔽中断
注意:发生中断时,SRCPND对应位肯定会置1,并不会受屏蔽寄存器的影响。
即中断源能否发生 与 是否屏蔽 没关系,换句话说,任你中断发生,但半路我可以选择屏蔽/封杀
4)接着会发现有两种中断可选择,一个FIQ(快中断),一个IRQ(正常中断)(这两者区别我前面博客有讲)
可通过INTMOD寄存器(32bit,每位对应一个/一组中断源)设置。
注意:一旦置位INTMOD某位,即设置某个中断源为快中断, INTOFFSET 和INTPND不受影响
5)对于中断优先级,可通过仲裁寄存器(即PRIORITY)设置中断源的优先级
6)对于IRQ中断,INTPND寄存器(32bit),某位置1表示对应的中断将会被送往CPU,即响应中断
需注意的是:INTPND某一时刻只能某位置1
7)最容易忘的一步,清中断,查看手册会发现,有三个寄存器需要在ISR(中断服务例程)中手动清零
记住既可:从源头开始清分别是:
EINTPEND——清除发生的外部中断对应位,避免多次响应
SRCPND——中断的源头,以让同一中断源可再次发出中断
INTPND——服务完了,记得清除
OK,理清硬件的中断处理后,在编程前需明确套路
1.开总中断,即cpsr的I位(bit 7)清零
2.中断源、中断控制器的初始化
3.编写中断处理函数
下面的代码根据这三步编写…
1 按键中断处理程序
1.0 设置中断入口
_start:
b reset
ldr pc, halt
ldr pc, halt
ldr pc, halt
ldr pc, halt
ldr pc, halt
ldr pc, addr_itr // 中断入口
ldr pc, halt
addr_itr:
.word do_itr
.align 4
do_itr:
ldr sp, =0x34000000
/*保护现场*/
sub lr, lr, #4
stmdb sp!, {r0-r12, lr}
/*调用中断处理函数*/
bl handle_irq
/*恢复现场*/
ldmia sp!, {r0-r12, pc}^
.align 4
reset:
/*......*/
1.1 CPU使能总中断
mrs r0, cpsr // r0 = cpsr
bic r0, r0, #0xf // 设置为usr模式,cpsr低5bit:0b10000
bic r0, r0, #(1<<7) // 清零cpsr的I位(bit 7), 使能中断
msr cpsr, r0 // cpsr = r0
1.2 中断源初始化
interrupt.c
void key_eint_init(void)
{
// s2--eint0 s3--eint2 s4--eint11 s5--eint19
// 配置四个按键为中断引脚
GPFCON &= ~((3<<0) | (3<<4)); //s2
GPFCON |= ((2<<0)|(2<<4)); //s3
GPGCON &= ~((3<<6)|(3<<22)); //s4
GPGCON |= ((2<<6) |(2<<22)); //s5
// 设置中断触发方式:双边沿触发
EXTINT0 |= (7<<0) | (7<<8);
EXTINT1 |= (7<<12);
EXTINT2 |= (7<<12);
// 使能eint11, eint19
EINTMASK &= ~((1<<11) | (1<<19));
}
1.3 中断控制器初始化
interrupt.c
void interrupt_init(void)
{
INTMSK &= ~((1<<0) | (1<<2) | (1<<5));
}
1.4 中断处理函数
void handle_irq_c(void)
{
/* 分辨中断源 */
int bit = INTOFFSET;
/* 调用对应的处理函数 */
if (bit == 0 || bit == 2 || bit == 5) /* eint0,2,eint8_23 */
{
key_eint_irq(bit); /* 处理中断, 清中断源EINTPEND */
}
/* 清中断 : 从源头开始清 */
SRCPND = (1<<bit);
INTPND = (1<<bit);
}
void key_eint_irq(int irq)
{
unsigned int val = EINTPEND;
unsigned int val1 = GPFDAT;
unsigned int val2 = GPGDAT;
if (irq == 0) /* eint0 : s2 控制 D12 */
{
if (val1 & (1<<0)) /* s2 --> gpf6 */
{
/* 松开 */
GPFDAT |= (1<<6);
}
else
{
/* 按下 */
GPFDAT &= ~(1<<6);
}
}
else if (irq == 2) /* eint2 : s3 控制 D11 */
{
if (val1 & (1<<2)) /* s3 --> gpf5 */
{
/* 松开 */
GPFDAT |= (1<<5);
}
else
{
/* 按下 */
GPFDAT &= ~(1<<5);
}
}
else if (irq == 5) /* eint8_23, eint11--s4 控制 D10, eint19---s5 控制所有LED */
{
if (val & (1<<11)) /* eint11 */
{
if (val2 & (1<<3)) /* s4 --> gpf4 */
{
/* 松开 */
GPFDAT |= (1<<4);
}
else
{
/* 按下 */
GPFDAT &= ~(1<<4);
}
}
else if (val & (1<<19)) /* eint19 */
{
if (val2 & (1<<11))
{
/* 松开 */
/* 熄灭所有LED */
GPFDAT |= ((1<<4) | (1<<5) | (1<<6));
}
else
{
/* 按下: 点亮所有LED */
GPFDAT &= ~((1<<4) | (1<<5) | (1<<6));
}
}
}
EINTPEND = val;
}
备注以下:贴出全部代码太占篇幅,完整代码可在我的一篇博文:“代码集合”获取
总结:
硬件中断的编写套路非常明朗,重点理解硬件的中断处理。看似要
操作很多寄存器,但是心里明确套路后非常简单,即既然要把中断发给CPU,那就沿着从中断源->
中断控制器–>CPU 一路设置下去,把这条路打通了,就可以响应中断了。
2 定时器实现LED计数
代码如下
2.1 时钟初始化
// 在 timer.c 文件中
void timer_init(void)
{
/* 设置TIMER0的时钟 */
/* Timer clk = PCLK / {prescaler value+1} / {divider value}
= 50000000/(99+1)/16
= 31250
*/
TCFG0 = 99; /* Prescaler 0 = 99, 用于timer0,1 */
TCFG1 &= ~0xf;
TCFG1 |= 3; /* MUX0 : 1/16 */
/* 设置TIMER0的初值 */
TCNTB0 = 15625; /* 0.5s中断一次 */
/* 加载初值, 启动timer0 */
TCON |= (1<<1); /* Update from TCNTB0 & TCMPB0 */
/* 设置为自动加载并启动 */
TCON &= ~(1<<1);
TCON |= (1<<0) | (1<<3); /* bit0: start, bit3: auto reload */
/* 设置中断 */
}
// 在interrupt.c文件中
/* 初始化中断控制器 */
void interrupt_init(void)
{
INTMSK &= ~((1<<0) | (1<<2) | (1<<5));
INTMSK &= ~(1<<10); /* enable timer0 int */
}
void handle_irq_c(void)
{
/* 分辨中断源 */
int bit = INTOFFSET;
/* 调用对应的处理函数 */
if (bit == 0 || bit == 2 || bit == 5) /* eint0,2,eint8_23 */
{
key_eint_irq(bit); /* 处理中断, 清中断源EINTPEND */
}
else if (bit == 10) /* 记得,这里要修改 */
{
timer_irq();
}
/* 清中断 : 从源头开始清 */
SRCPND = (1<<bit);
INTPND = (1<<bit);
}
// 在main.c文件中,添加下面这句
timer_init();
2.2 时钟中断处理函数
void timer_irq(void)
{
/* 点灯计数 */
static int cnt = 0;
int tmp;
cnt++;
tmp = ~cnt;
tmp &= 7;
GPFDAT &= ~(7<<4);
GPFDAT |= (tmp<<4);
}
ok,以上代码经过测试,运行没问题