引子
按键作为整块开发板几乎最重要的输入方式,个人感觉还是相当重要的。按键无法实现或者崩溃,很大程度会导致整个程序无法调试和演示效果。大多数年的赛题要求的只要能够正确检测按键触发而且不会出现抖动基本就足够了,但是第九届也是考到了长按和短按。这里提供几种方案供参考。
驱动编写
和上篇的LED驱动类似,只需注意修改GPIO引脚和模式即可
原理图上按键是上拉输入,所以IO模式相应修改即可(好像改成浮空也可以)
void KEY_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_8;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
按键的简单实现
这里参考的是正点原子的例程,语句少原理也简单。
基本就是通过标志位判断按键是否按下,加一个延时消抖。执行后更改标志位达到按下后只执行一次而不会重复触发的效果。
如果你要按下按键执行控制某个LED灯亮类似功能其实不加标志位也可以,因为语句执行一次是执行N次效果是一样的。但是如果要实现计数的话这就要求你按下只执行一次。
预定义
#define key1 GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)
#define key2 GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_8)
#define key3 GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_1)
#define key4 GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_2)
#define key1_flag 1
#define key2_flag 2
#define key3_flag 3
#define key4_flag 4
代码
u8 Key_Scan(void)
{
static u8 flag=1;
if(flag&&(key1==0||key2==0||key3==0||key4==0))
{
flag=0;
delay_ms(5);
if(key1==0) return key1_flag;
else if(key2==0) return key2_flag;
else if(key3==0) return key3_flag;
else if(key4==0) return key4_flag;
}
else if(key1&&key2&&key3&&key4) flag=1;
return 0;
}
长短按阻塞式实现
如果需要实现长短按的话这就要求记录按下的时间,而通过延时函数虽然简单其实存在一个比较大的弊端就是会占用资源使程序卡住。比如说你要实现一个LED闪烁同时要执行其他任务,你在while里使用(led亮->延时一会儿->led灭->演示一会儿)的方法,如果其他任务稍微复杂点可能就会受到影响执行出现异常。但是蓝桥杯资源不多其实用这种方法也可以。
基本思路是:参照上面的框架写一个扫描函数,判断哪个按键触发。然后再执行while循环一次延时十毫秒记录延时的次数,当超过某个上限(防止程序卡死)或者按键抬起强制跳出。记录的次数作为返回值传入控制函数可以折换成时间。然后判断延时长短执行相应操作。
标志位最好用静态变量!
代码
u8 Key_Scan(void)
{
static u8 flag=1;
u8 temp;
if(flag==1)
{
if(key1==0) // KEY1
{
flag=0;
while(key1==0&&temp<85)
{
temp++;
delay_ms(10);
}
key_flag=key1_flag;
return temp;
}
if(key2==0) //KEY2
{
flag=0;
while(key2==0&&temp<85)
{
temp++;
delay_ms(10);
}
key_flag=key2_flag;
return temp;
}
if(key3==0) //KEY3
{
flag=0;
while(key3==0&&temp<85)
{
temp++;
delay_ms(10);
}
key_flag=key3_flag;
return temp;
}
if(key4==0) //KEY4
{
flag=0;
while(key4==0&&temp<85)
{
temp++;
delay_ms(10);
}
key_flag=key4_flag;
return temp;
}
}
else if(key1&&key2&&key3&&key4) flag=1,key_flag=0;
return 0;
}
void Key_mode(void)
{
u8 key_t;
key_t=Key_Scan();
switch(key_flag)
{
case key1_flag: if(key_t>1&&key_t<80)
{
// LED_Show(led1,0);
}
else if(key_t>80)
{
while(key_flag==key1_flag)
{
led_n++;
delay_ms(50);
sprintf((char*)str," LED: %d ",led_n);
LCD_DisplayStringLine(Line5,str);
key_t=Key_Scan();
}
}break;
case key2_flag: if(key_t>1&&key_t<80)
{
// LED_Show(led3,0);
}
else if(key_t>80)
{
}break;
case key3_flag: if(key_t>1&&key_t<80)
{
}
else if(key_t>80)
{
}break;
case key4_flag: if(key_t>1&&key_t<80)
{
}
else if(key_t>80)
{
}break;
}
}
Tips:可以看出控制函数里面要求按键一长按(大于800ms)后计数值连续加,这个时候需要加一个短暂延时控制递增速度。而且while循环最后要加按键扫描函数作为退出条件。(按键扫描函数会检测按键触发位置,while循环成立条件正好是当前按键一直在触发状态)
长短按非阻塞式
原理以及代码框架和阻塞式相近,只不过把按键扫描的延时改成滴答定时器。虽然感觉有点鸡肋,因为其实长按实现技术递增其实还是用到了延时,但是大家可以学习这种用定时器中断定时的思路。比如执行其他代码同时实现led闪烁,就参考此办法可用滴答定时器。
全局变量
u8 Rec_key; //按键触发接收
u8 Key_delayFlag; //按键非阻塞延时标志位
u16 Key_Cnt=0; //按键触发计时
滴答定时器中断服务函数
void SysTick_Handler(void)
{
TimingDelay--;
if(Key_delayFlag==1)
{
Key_Cnt++;
}
else Key_Cnt=0;
}
代码
u16 Key_LongScan(void)
{
static u8 flag=1;
if(flag==1)
{
if(key1==0)
{
flag=0;
while(key1==0&&Key_Cnt<=Key_thre)
{
Key_delayFlag=1; //开始计时
}
Rec_key=key1_flag; //按键1触发
Key_delayFlag=0; //停止计时
return Key_Cnt; //返回计时时间
}
if(key2==0)
{
flag=0;
while(key2==0&&Key_Cnt<=Key_thre)
{
Key_delayFlag=1; //开始计时
}
Rec_key=key2_flag; //按键2触发
Key_delayFlag=0; //停止计时
return Key_Cnt; //返回计时时间
}
if(key3==0)
{
flag=0;
while(key3==0&&Key_Cnt<=Key_thre)
{
Key_delayFlag=1; //开始计时
}
Rec_key=key3_flag; //按键3触发
Key_delayFlag=0; //停止计时
return Key_Cnt; //返回计时时间
}
if(key4==0)
{
flag=0;
while(key4==0&&Key_Cnt<=Key_thre)
{
Key_delayFlag=1; //开始计时
}
Rec_key=key4_flag; //按键4触发
Key_delayFlag=0; //停止计时
return Key_Cnt; //返回计时时间
}
}
else if(key1&&key2&&key3&&key4) flag=1,Rec_key=0;
return 0;
}
void Key_Disp(void)
{
u16 temp;
u8 n;
temp = Key_LongScan();
switch(Rec_key)
{
case key1_flag: if(temp>1&&temp<800) LED_Show(led1,0);
else if(temp>800)
{
while(Rec_key==key1_flag)
{
LED_Show(led1,1);
LED_flow(n);
delay_ms(50);
n++;
if(n==9) n=1;
temp = Key_LongScan(); //退出条件
}
}break;
case key2_flag: break;
case key3_flag: break;
case key4_flag: break;
}
}