按键IRQ实验
硬件介绍
这四个按键和将要控制的LED灯对应的电路原理图如下所示:
由上图可知,当按键没有按下时,EINT0、EINT2、EINT11和EIN19都处于高电平;当按键按下时,这四个引脚都处于低电平。我们使用S2按键触发IRQ中断来控制LED1,使用S3按键触发IRQ中断来控制LED2,使用S4按键触发IRQ中断来控制LED4。
对于S5按键,我们让其触发FIQ中断来控制三盏灯同时点亮或熄灭。
都让它们按下点亮LED灯,松开熄灭LED灯。
所以IRQ实验涉及S2、S3、S4三个按键和LED1、LED2、LED4三个灯。其对应的SoC GPIO管脚如下图所示:
我们可以看到,LED1对应的GPIO是GPF4,LED2对应的GPIO是GPF5,LED4对应的GPIO是GPF6。
按键S2对应的GPIO是GPF0,S3对应的GPIO是GPF2,S4对应的GPIO是GPG3。
LED灯操作代码
首先我们先进行LED灯操作的代码编写。我们先将GPF4、GPF5、GPF6初始化为输出引脚,并使其输出高电平,这样三盏LED灯均熄灭。之后编写点亮和熄灭函数,方便后续调用。具体代码如下所示:
led.c
#include "s3c2440_soc.h"
#include "led.h"
void leds_init(void) {
//清除[13:8]比特位
GPFCON &= (~(0x3F << 8));
//[13:12][11:10][9:8]分别设置为0b01
//使得GPF4、GPF5、GPF6管脚处于输出状态
GPFCON |= (0x15 << 8);
//设置上拉电阻,使其默认处于高电平
//GPFUP |= (0x7 << 4);
//使其输出高电平,熄灭LED灯
GPFDAT |= (0x7 << 4);
}
void led_on(int ledNum) {
int offset = 0;
switch(ledNum){
case LED1:
offset = 4;
break;
case LED2:
offset = 5;
break;
case LED4:
offset = 6;
break;
default:
return;
}
if(offset == 0) {
return;
}
GPFDAT &= (~(1 << offset));
}
void led_off(int ledNum) {
int offset = 0;
switch(ledNum){
case LED1:
offset = 4;
break;
case LED2:
offset = 5;
break;
case LED4:
offset = 6;
break;
default:
return;
}
if(offset == 0) {
return;
}
GPFDAT |= (1 << offset);
}
led.h
#ifndef __LED_H__
#define __LED_H__
#define LED1 1
#define LED2 2
#define LED4 4
void leds_init(void);
void led_on(int ledNum);
void led_off(int ledNum);
#endif
按键初始化代码
void keys_init_irq(void) {
//按键S2对应的GPIO是GPF0
//S3对应的GPIO是GPF2
//S4对应的GPIO是GPG3
//初始化GPF0 GPF2为中断功能,对应EINT2 EINT0
GPFCON &= (~(0x33));
GPFCON |= (0x2 << 4) | (0x2 << 0);
//初始化GPG3为中断功能,对应EINT11
GPGCON &= (~(0x3 << 6));
GPGCON |= (0x2 << 6);
//设置GPIO中断触发方式为双边沿触发
EXTINT0 |= (0x7 | (0x7 << 8));
EXTINT1 |= (0x7 << 12);
//打开GPIO中断屏蔽开关
//对于EINT0~EINT3来说,GPIO控制器这里是始终没有屏蔽中断的。
//我们只需打开EINT11中断屏蔽即可。
EINTMASK &= (~(0x1 << 11));
//打开中断控制器对应的中断开关
INTMSK &= (~((1 << 0) | (1 << 2) | (1 << 5)));
}
从中可以看出,这里涉及到的中断为EINT0、EINT2和EINT11。
GPIO 中断触发方式
下面开始设置中断触发方式。我们使用的是EINT0、EINT2和EINT11。为了检测按键按下和松开,我们采用双边沿触发。
EXTINT0 |= (0x7 | (0x7 << 8));
EXTINT1 |= (0x7 << 12);
GPIO中断屏蔽寄存器
从上图我们可以看到,对于EINT0~EINT3来说,GPIO控制器这里是始终没有屏蔽中断的。
我们只需打开EINT11中断屏蔽即可。
EINTMASK &= (~(0x1 << 11));
中断控制器设置
通过S3C2440用户手册可以查到它的中断源表如上图所示。可以看到,EINT0、EINT2和EINT11都属于without sub-register中断源。所以我们不用设置SUBMSK寄存器。
下面来设置INTMSK寄存器,如下图所示:
我们需要打开EINT0、EINT2和EINT8_23中断开关。
INTMSK &= (~((1 << 0) | (1 << 2) | (1 << 5)));
优先级我们使用默认的优先级即可。
中断处理函数
在中断处理函数中,我们根据硬件中断号去读取相应引脚的电平状态,以判断是按下还是松开按键,之后决定打开还是熄灭LED灯。处理完毕后,从源头开始清理中断标识。
我们查看GPIO控制器中的外部中断PENDING寄存器 EINTPEND可以看到,如果是EINT11中断发生,那么我们首先需要清除这里的置位。注意,这里也是写1清零,写0不变。
void irqExpHandler(void) {
//现在处于SVC模式
//我们先获取硬件中断号
unsigned int interruptNum = INTOFFSET;
printf("IRQ Interrupt Number: %d\n\r", interruptNum);
//按键 按下后为低电平
unsigned int isKeyUp = 0;
int ledNum = -1;
switch(interruptNum){
case 0:
//EINT0中断 s2按键
isKeyUp = (GPFDAT & 0x1);
ledNum = LED1;
break;
case 2:
//EINT2 s3按键
isKeyUp = (GPFDAT & (0x1 << 2));
ledNum = LED2;
break;
case 5:
//EINT8_23 s4按键
isKeyUp = (GPGDAT & (0x1 << 3));
ledNum = LED4;
break;
default:
break;
}
if(ledNum > 0) {
if(isKeyUp) {
//熄灭LED1
led_off(ledNum);
printf("Key is Up.LED%d is turn off.\n\r", ledNum);
}else {
led_on(ledNum);
printf("Key is Down.LED%d is turn on.\n\r", ledNum);
}
}
//处理完毕,从源头开始清理中断标志
//注意是写1清零
if(interruptNum == 5) {
EINTPEND |= (0x1 << 11);
}
SRCPND |= (0x1 << interruptNum);
INTPND |= (0x1 << interruptNum);
printf("IRQ Interrupt is Handled\n\r");
}
main函数
主函数中只做一件事,就是无限死循环打印字符变量gCh,之后自增一继续打印。
char gCh = 'A';
int main(void) {
leds_init();
keys_init_irq();
printf("%s\n\r", "IRQ Test.");
dump_norFlash(80);
while(1) {
printf("%c(0x%02x) ", gCh, gCh);
gCh++;
wait(888888);
}
return 0;
}