以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。
参考博客
s5pv210——按键 - biaohc - 博客园
一、按键的简介
1、按键的物理特性
按钮没有被按下时,内部是断开的。按钮被按下时,内部保持接通状态;放手后由于弹簧的作用,按钮又被弹开,内部断开。
一般按键有4个引脚,分成2对:一对是常开触点(像上面描述的不按则断开,按下则闭合);一对是常闭触点(平时不按时是闭合的,按下后是断开的)。
2、按键的电学原理
按键的原理图如下。
从原理图可知:
(1)按键SW5接在引脚GPH0_2,按键SW6接在GPH0_3,按键SW7~SW10分别接在GPH2_0~GPH2_3.
(2)按钮没有按下时,按钮内部断开,GPIO 引脚处电压为高电平。按钮被按下时,按钮的内部导通,外部VDD经过电阻和按钮连接到地,形成回路,此时GPIO引脚处的电压就变为低电平。也就是说,按键的按下与弹开,分别对应GPIO的两种电平状态(按下则GPIO为低电平,弹开则GPIO为高电平)。SoC内部通过检测这个GPIO的电平高低,来判断按键是否被按下,这个判断结果即可作为SoC的输入信号。
3、按键属于输入类设备
向SoC发送信息的设备,叫输入设备。
有些设备是单纯的输入设备,比如按键、触摸屏等;有些设备是单纯的输出设备,比如LCD;有些设备既能输入又能输出,就叫做输入输出设备(IO),比如串口。
按键一般用作输入设备,由人向SoC发送按键信号:按下信号和弹开信号。
4、SoC处理按键的2种方式
这里所说的处理,是指SoC如何得知按键被按下,以及按下之后SoC要做什么事情。在接下里的代码中,轮询方式得知按键被按下后,SoC让led闪烁;中断方式得知按键被按下后,也可以在中断服务子程序中让led闪烁,不过没有完成这部分内容。
(1)轮询方式
SoC每隔一段时间去读取按键所对应的GPIO的电平高低,以此获得按键信息。
这种处理方式,使得CPU要一直注意按键事件,影响CPU做其他事情。
(2)中断方式
SoC预先设定好GPIO触发的中断所对应的中断处理程序ISR,当按键按下或弹开时会触发GPIO对应的外部中断,导致ISR执行,从而自动处理按键信息。
5、按键消抖
按键这种物理器件本身会有抖动信号。抖动信号指的是在电平由高到低(按键按下时)或者电平由低到高(按键弹起时)的过程中,电平的变化不是立刻变化,而是要经过一段非稳定期才完成变化,在这个不稳定期,电平可能会时高时低反复变化。
抖动期内获取按键信息是不可靠的,要想办法消抖。消抖就是用硬件或者软件方法来尽量减小抖动期对按键获取的影响。有硬件消抖和软件消抖两种消抖方式,一般比较精密需要的时候,需要硬件消抖和软件消抖一起配合。
(1)硬件消抖
消抖思路就是尽量减小抖动时间,方法是通过硬件添加电容等元件来减小抖动。
(2)软件消抖
消抖思路是发现一次按键按下/弹起事件后,不立即处理按键,而是延时一段时间(一般10~20ms,这就是消抖时间)后再次获取按键键值,如果此次获取的和上次一样,则认为按键的确被按下或者弹起了。
二、轮询方式处理按键
1、X210开发板的按键接法
由上面的原理图可以看出,按键按下时,对应的GPIO是低电平,弹起时是高电平。
2、按键对应的GPIO模式设置
因为SoC要读取按键对应的GPIO的电平,所以要将按键对应的GPIO设置为输入模式。因为按键对应的引脚分别为GPH0_2、GPH0_3、GPH2_0~GPH2_3,涉及到GPH0和GPH2这两个IO端口,所以要在端口控制寄存器GPH0CON、GPH2CON中(这两个寄存器的每4bit控制一个引脚),将GPH0_2、GPH0_3、GPH2_0~GPH2_3设置为input模式。
又因为按键对应的GPIO的电平值,存储在IO端口对应的DAT寄存器相应的位中,因此需要读取GPH0DAT、GPH2DAT这两个寄存器中对应的位的值。
即与按键操作相关的寄存器有:
(1)GPH0CON(0xE0200C00),GPH2CON(0xE0200C40)
主要用来设置引脚的模式,比如输入模式、输出模式等等。
(2)GPH0DAT(0xE0200C04),GPH2DAT(0xE0200C44)
这两个寄存器每个bit的0或1,代表所对应的引脚的电平值是0或者1。
GPH0这个IO端口有关的寄存器
GPH2这个IO端口有关的寄存器
3、轮询方式监测按键是否按下
第一步,先初始化GPIO模式为输入模式。
第二步,循环读取按键对应的GPIO的电平值,判断有无按键按下。
代码如下:
/** 轮询方式处理按键是否按下 */#define _REG_GPH0CON (*(volatile unsigned int *)0xE0200C00) #define _REG_GPH0DAT (*(volatile unsigned int *)0xE0200C04) #define _REG_GPH2CON (*(volatile unsigned int *)0xE0200C40) #define _REG_GPH2DAT (*(volatile unsigned int *)0xE0200C44)void led_blink(void);//这个函数用来表示有按键按下void key_init(void) {//EINT2配置为输入模式_REG_GPH0CON &= ~(0xFF<<8); //GPH2_0~GPH2_3置为输入_REG_GPH2CON &= ~(0xFFFF<<0);}void key_polling(void) {key_init();while (1) {//通过_REG_GPH0CON设置GPH0_2这个引脚为输入模式,//则_REG_GPH0DAT这个寄存器对应的位就是对应引脚的值if (!(_REG_GPH0DAT & (0x1<<2))) {led_blink();}if (!(_REG_GPH0DAT & (0x1<<3))) {led_blink();} if (!(_REG_GPH2DAT & (0x1<<0))) {led_blink();}if (!(_REG_GPH2DAT & (0x1<<1))) {led_blink();}if (!(_REG_GPH2DAT & (0x1<<2))) {led_blink();}if (!(_REG_GPH2DAT & (0x1<<3))) {led_blink();}} }
三、中断方式处理按键
1、外部中断的概念
在轮询方式处理按键这部分内容中,讲到需要将按键对应的GPIO设置为输入模式。其实通过将按键对应的GPIO设置为中断模式,就可以利用中断的方式来处理按键。因为按键属于SoC外部的设备,它所触发的中断就叫做外部中断。相对的,如果中断源来自SoC内部外设,比如串口、定时器等部件产生的中断,则叫内部中断。
将按键对应的GPIO设置为中断模式后,如果通过按键改变按键对应的GPIO的电平,则会触发GPIO对应的外部中断。
2、外部中断的触发模式
外部中断的触发模式主要有2种:电平触发和边沿触发。
(1)电平触发
电平触发分为高电平触发、低电平触发。
电平触发的特点是,只要电平满足条件就会不停触发中断。
(2)边沿触发
边沿触发分为上升沿触发、下降沿触发、双边沿触发三种。
边沿触发不关心电平常规状态,只关心电平变化的瞬间,也就是说,边沿触发不关心电平本身是高还是低,只关心变化是从高到低还是从低到高的这个过程。如果关注的是按键按下和弹起这两个事件本身,那么应该用边沿触发来处理按键。
3、外部中断相关的寄存器
外部中断的设置,主要涉及以下4个方面的寄存器:
(1)设置GPIO引脚为外部中断模式:GPH0CON寄存器、GPH2CON寄存器
通过(与按键对应的GPIO所在的IO端口的)IO端口控制寄存器,将按键所对应的GPIO设置为外部中断模式。比如通过将GPH0CON寄存器的GPH0CON[2]、GPH0CON[3]设置为“1111”,从而将按键对应的GPIO引脚GPH0_2、GPH0_3设置为中断模式。
(2)设置中断触发的方式:EXT_INT_0_CON寄存器
触发方式是指外部电平怎么变化就能触发中断,也就是说这个外部中断产生的条件是什么。
(3)设置是否使能(或者说开启)中断:EXT_INT_0_MASK寄存器
这个寄存器的低8位,用来设置是否开启各个外部中断。
(4)设置中断挂起:EXT_INT_0_PEND寄存器
这个寄存器的每一位对应着一个外部中断,如果没有发生外部中断,这个寄存器对应的位的值就为0。如果发生中断,硬件会自动地将这个寄存器中对应的位设为1。如果发生了中断而暂时来不及处理时,这个位就一直是1(这就是挂起的含义)。当处理完这个中断后,我们应该手动地(写代码)将这个位设置为0,表示这个中断被处理了。
4、中断方式处理按键
代码如下:
#define EXT_INT_0_CON 0xE0200E00 #define EXT_INT_2_CON 0xE0200E08 #define EXT_INT_0_PEND 0xE0200F40 #define EXT_INT_2_PEND 0xE0200F48 #define EXT_INT_0_MASK 0xE0200F00 #define EXT_INT_2_MASK 0xE0200F08#define _REG_EXT_INT_0_CON (*(volatile unsigned int *)EXT_INT_0_CON) #define _REG_EXT_INT_2_CON (*(volatile unsigned int *)EXT_INT_2_CON) #define _REG_EXT_INT_0_PEND (*(volatile unsigned int *)EXT_INT_0_PEND) #define _REG_EXT_INT_2_PEND (*(volatile unsigned int *)EXT_INT_2_PEND) #define _REG_EXT_INT_0_MASK (*(volatile unsigned int *)EXT_INT_0_MASK) #define _REG_EXT_INT_2_MASK (*(volatile unsigned int *)EXT_INT_2_MASK)//中断方式初始化按键 void key_inter_init(void) {//ENIT2、ENIT3引脚设置为中断模式_REG_GPH0CON |= (0xFF<<8);//ENIT16、ENIT17、ENIT18、ENIT19引脚设置为中断模式_REG_GPH2CON |= (0xFFFF<<0);//设置ENIT2、ENIT3为下降沿触发_REG_EXT_INT_0_CON &= ~(0xFF<<8);_REG_EXT_INT_0_CON |= ((0x2<<8) | (0x2<<12));//设置ENIT16、ENIT17、ENIT18、ENIT19为下降沿触发_REG_EXT_INT_2_CON &= ~(0xFFFF<<0);_REG_EXT_INT_2_CON |= ((0x2<<0) | (0x2<<4) | (0x2<<8) | (0x2<<12));//使ENIT2、ENIT3能外部中断;_REG_EXT_INT_0_MASK &= ~(0x3<<2);//使ENIT16、ENIT17、ENIT18、ENIT19能外部中断;_REG_EXT_INT_2_MASK &= ~(0xF<<0);/* //清ENIT2、ENIT3中断挂起_REG_EXT_INT_0_PEND |= (0x3<<2);//清ENIT16、ENIT17、ENIT18、ENIT19中断挂起_REG_EXT_INT_2_PEND |= (0xF<<0);*/clean_int_pend(); }