独立按键控制LED灯的亮灭
key.c
key.c文件有两个重要函数:KEY_Init和KEY_Scan
KEY_Init
//按键初始化函数
void KEY_Init(void) //IO初始化
{
GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOE,ENABLE);//使能PORTA,PORTE时钟
//KEY0-KEY1 上拉输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_12; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //设置成上拉入
GPIO_Init(GPIOE, &GPIO_InitStructure);//初始化GPIOE4,3
//初始化 WK_UP-->GPIOA.0 下拉输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;//PA0设置成输入,默认下拉
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.0
}
1、由于KEY0和KEY1按键按下,变为低电平,所以设置为上拉输入,默认为高电平。
2、由于WK_UP按键按下,变为高电平,所以设置为下拉输入,默认为低电平。
KEY_Scan
//按键处理函数
//返回按键值
//mode:0,不支持连续按;1,支持连续按;
//0,没有任何按键按下
//1,KEY0按下
//2,KEY1按下
//3,KEY3按下 WK_UP
//注意此函数有响应优先级,KEY0>KEY1>KEY_UP!!
u8 KEY_Scan(u8 mode)
{
static u8 key_up=1;//按键按松开标志 static再次调用,继承上一次的值
if(mode)key_up=1; //支持连按
if(key_up&&(KEY0==0||KEY1==0||WK_UP==1))
{
delay_ms(20);//去抖动
key_up=0;
if(KEY0==0)return KEY0_PRES;
else if(KEY1==0)return KEY1_PRES;
else if(WK_UP==1)return WKUP_PRES;
}
else if(KEY0==1&&KEY1==1&&WK_UP==0)key_up=1;
return 0;// 无按键按下
}
KEY_Scan函数的执行过程;
1、mode=1的时候存在的bug:因为每一次调用KEY_Scan都会返回一个键值,假如一直按着KEY1不松,则每次调用KEY_Scan都会返回一个KEY1_PRES (KEY1_PRES=2),则下面while语句中第4行的if判断一直成立,那么就会一直执行第13行的翻转程序。第13行的程序本想让LED1的状态取反,但是由于执行频率太高,LED1高速地进行取反,以至于人眼看不到翻转,只会看到这个灯变暗。并且松开KEY1的时候,LED1有可能变为0,有可能变为1,即LED灯有可能亮,有可能不亮,存在这样一个bug。
2、mode=0时,key_up作为子程序KEY_Scan的局部静态变量,当没有键按下时,KEY_Scan第13行的if条件不成立,key_up仍然为1,KEY_Scan直接return 0,KEY_Scan结束执行;当有按键按下时,KEY_Scan第13行的if成立,执行if语句(由于程序执行周期远远小于人按键松开时间,所以认为执行1个周期期间key_up一直为1),延迟消抖后,key_up变为0,同时执行嵌套的if语句来再次判断是哪个键被按下,并且return对应的键值。尽管main高速调用KEY_Scan,但由于key_up是static类型的变量,会继承上一次调用的值,也即是说key_up=0,不执行第13行if,KEY_Scan就会return为0。直到按键完全松开,执行KEY_Scan第20行程序,key_up重新为1。做到了按下按键只返回一次对应值,便于main的if条件只执行一次,即LED灯取反一次。
然后进入main函数
main.c
while
while(1)
{
key=KEY_Scan(0); //得到键值
if(key)
{
switch(key)
{
case WKUP_PRES:
LED0=!LED0;
LED1=!LED1;
break;
case KEY1_PRES: //控制LED1翻转
LED1=!LED1;
break;
case KEY0_PRES: //同时控制LED0,LED1翻转
LED0=!LED0;
break;
}
}
else delay_ms(10);
}
由程序可知,当WKUP键按下时,LED0和LED1的状态都发生改变,KEY1键按下时,LED1的状态发生改变,KEY0键按下时,LED0的状态发生改变。
编译仿真
打开仿真
设置断点
定义按键初始状态:
因为KEY0和KEY1默认为高电平,所以PE.6和PE.12初始值为1
因为WK_UP默认为低电平,PA.0初始值为0
先点击运行
1、按下PE12
2、松开PE12
程序停在了LED1=!LED1,说明case KEY1_PRES执行,独立按键程序成功。
组合键按键控制LED灯的亮灭
修改程序
key.c
主要是修改KEY_Scan
u8 KEY_Scan(u8 mode)
{
u8 key=0;
static u8 tem;//按键按松开标志 static再次调用,继承上一次的值
if((KEY0==0||KEY1==0||WK_UP==1))//任何一个按下
{
delay_ms(20);//去抖动
if(KEY0==0&&tem<4)//不是由组合按键松开而导致的KEY0=0
tem=1;
if(KEY1==0&&tem<4)//不是由组合按键松开而导致的KEY1=0
tem=2;
if(WK_UP==1)
tem=3;
if(KEY0==0&&KEY1==0)//组合键按下
tem=4;
}
if(KEY0==1&&KEY1==1&&WK_UP==0)
{
key=tem;
tem=0;
return key;
}
return 0;// 无按键按下或者按键没有被全部释放
}
组合键按下过程:
1、KEY0按下,还没松开,此时第9行的if判断成立,tem=1,但由于KEY0按下还没松开,导致KEY0一直等于0,第18行的if判断不成立,所以返回0。
2、此时KEY1接着按下,且KEY0和KEY1都还没有松开,此时第15行的if判断成立,tem=4。
3、其中一个松开,因为tem=4,导致第9行和第11行的if判断不成立,所以tem维持4。并且由于只有一个按键松开,还有一个按键没有松开,因此第18行if判断不成立,所以返回0。
4、之后另一个按键也随之松开,第18的行if判断最终成立,key=tem=4,返回4,tem清0。
key.h
#ifndef __KEY_H
#define __KEY_H
#include "sys.h"
#define KEY0 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_6)//读取按键0
#define KEY1 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_12)//读取按键1
#define WK_UP GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)//读取按键3(WK_UP) #define KEY0_PRES 1 //KEY0按下
#define KEY1_PRES 2 //KEY1按下
#define WKUP_PRES 3 //KEY_UP按下(即WK_UP/KEY_UP)
#define k0k1_PRES 4 //K0 K1按下
void KEY_Init(void);//IO初始化u8 KEY_Scan(u8); //按键扫描函数 #endif
宏定义组合键k0k1_PRES,表示KEY0和KEY1都被按下。
编译仿真
打开仿真
设置断点
定义按键初始状态:
对于GPIOE引脚:PE.6和PE.12初始值为0
组合键运行过程:
1、按下KEY1不松
2、接着按下KEY0不松
3、KEY0松开
4、KEY1松开
程序断在了LED2=!LED2;说明case k0k1_PRES执行,组合键程序成功。
总结
1、初始化按键GPIO的时候需要注意按键的硬件连接方式,如果默认高电平,就选择输入上拉GPIO_Mode_IPU,如果默认为低电平,就选择输入下拉GPIO_Mode_IPD。
2、 static修饰的变量只执行一次,一开始运行就开辟了内存,内存放在全局。当再次调用,会继承上一次的值。
3、 不断扫描按键对应引脚的电平状态,如果电平有变化,消抖处理来消除抖动的影响后,再次判断是哪个引脚的电平发生了变化,返回相应的键值。电平没有变化就返回0。
按键扫描流程