#蓝桥杯之按键扫描思想
这里引用的是博主ReCclay的代码和思想:原文(https://blog.csdn.net/ReCclay/article/details/79293182)
同事可以观看B站金沙滩宋老师的讲解,宋老师对单片机的讲解非常透彻,以后有机会会再系统性地看一次,正如博主所说“宋老师教给自己的绝对不是鱼,而是渔”
##按键自己学习经历过程
按键涉及到的两个主要方面:扫描和消抖
其中自己前面接触的有中断响应,实时性较好,以下博主有涉及:
(https://blog.csdn.net/Zach_z/article/details/80548423)
还有设置标志位,主函数中进行判断标志位,一下博文有涉及:
(https://blog.csdn.net/qq_34952376/article/details/81169122)
但是自己在后面测试大量代码的过程中发现以上两种按键处理方法状态不稳定,因此寻找之后发现下面这种按键处理方式。
##按键思想
用定时器进行定时查询,通过比较当前状态与上一状态进行判断按键是出于按下还是弹起状态。稳定性来源于:由实际工程中程序员推出,经历时间推敲
理解三个函数:
KeyDriver():驱动函数,放在主函数里进行重复运行,函数是用来判断按键按下,弹开,按住;
KeyScan():扫描函数,放在定时器中断中,进行消抖处理
KeyAction();按键操作指令函数,放在KeyDriver()中,进行相应按键状态后的操作。
##代码
#include"led.h"
#include"stm32f10x.h"
#include"key1.h"
#include"timer.h"
u16 LED_MODE=0XFFFF;
void KeyAction(u8 keycode);
int main()
{
Led_Init();
key_Init();
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
SysTick_Config(SystemCoreClock/1000);
TIM4_Init(2000,72);//定时器2ms的中断
while(1)
{
KeyDriver();
}
}
void KeyAction(u8 keycode)
{
if(keycode==1)//'1'代表字符,不对
{
LED_MODE^=(1<<8); //闪烁
GPIOC->ODR = LED_MODE;
GPIOD->ODR|=(1<<2);
GPIOD->ODR&=~(1<<2);
}
else if(keycode==2)
{
LED_MODE^=(1<<9); //闪烁
GPIOC->ODR = LED_MODE;
GPIOD->ODR|=(1<<2);
GPIOD->ODR&=~(1<<2);
}
else if(keycode==3)
{
}
else if(keycode==4)
{
}
}
#include"key1.h"
//全局变量
u8 KeySta[4]={1,1,1,1}; //代表按键消抖之后的状态;
u16 KeyDownTime[4]={0,0,0,0}; //保存每个按键按下的时间累加;注意这里是u16!!!!!!!
extern void KeyAction(u8 keycode);
void key_Init()
{
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_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_2;
GPIO_Init(GPIOB, &GPIO_InitStructure); //
}
void KeyDriver()
{
u8 i;
static u8 backup[4]={1,1,1,1};//上一次按键的状态
static u16 TimeThr[4]={4000,4000,4000,4000};//注意是static u16 !!!!!!
//这里TimeThr的初始值代表按键第一次按下之前等待的时间
for(i=0;i<4;i++)
{
if(KeySta[i]!=backup[i]) //说明按键状态发生改变
{
if(backup[i]==0)//代表按下状态
{
KeyAction(i+1);
}
backup[i]=KeySta[i];
}
//长按键的加入
if(KeyDownTime[i]>0)//说明按键按下
{
if(KeyDownTime[i]>TimeThr[i])
{
KeyAction(i+1);
TimeThr[i]=TimeThr[i]+500;
//这里的增加值代表按键按住不放,按键状态翻转需要等待的时间;
}
}
else//说明按键弹起
{
TimeThr[i]=4000;
}
}
}
void KeyScan() //抖动大约占10ms,利用定时器2ms扫描一次,8*2=16ms;
{
u8 i;
static u8 keybuf[4]={0xff,0xff,0xff,0xff};
keybuf[0]=(keybuf[0]<<1)|KEY1;
keybuf[1]=(keybuf[1]<<1)|KEY2;
keybuf[2]=(keybuf[2]<<1)|KEY3;
keybuf[3]=(keybuf[3]<<1)|KEY4;
for(i=0;i<4;i++)
{
if(keybuf[i]==0xff)//代表弹起
{
KeySta[i]=1;
KeyDownTime[i]=0;
}
else if(keybuf[i]==0x00) //代表按下
{
KeySta[i]=0;
KeyDownTime[i]=KeyDownTime[i]+4;
}
else //代表消抖期间
{
}
}
}
#include"led1.h"
#include"stm32f10x.h"
void Led_Init()
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOC, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOD, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_11|GPIO_Pin_12|GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15;
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIOD->ODR|=(1<<2);
GPIOC->ODR|=(0xFF00);
GPIOD->ODR&=~(1<<2);
}
#ifndef LED_H
#define LED_H
#include"stm32f10x.h"
void key_Init(void);
#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)
void KeyDriver(void);
void KeyScan(void);
void KeyAction(u8 keycode);
#endif
```c
void TIM4_IRQHandler(void)
{
if(TIM_GetITStatus(TIM4, TIM_IT_Update) == SET)
{
TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
KeyScan();
}
}
*【定时器初始化部分没有粘贴】
###代码中还需汲取的知识:LED新的一种书写方式
LED初始化,全熄灭:
GPIOD->ODR|=(1<<2);
GPIOC->ODR|=(0xFF00);
GPIOD->ODR&=~(1<<2);
LED闪烁:
u16 LED_MODE=0XFFFF;
LED_MODE^=(1<<8); //闪烁
GPIOC->ODR = LED_MODE;
GPIOD->ODR|=(1<<2);
GPIOD->ODR&=~(1<<2);