? ? ? ?最近看了很多按键扫描的文章,发现各有长处,后来自己花了一点时间做了一个单片机独立按键扫描的模块,此模块优点颇多,支持短按,长按,连发功能,只要配置相关结构体就可以实现这些功能,唯一的缺点是不支持多按键同时按下功能(后续有时间加上)
? ? ? ?先简单介绍下整体思路,首先在Key_Scan.h里面定义一个按键结构体KeyInitStruct,里面包含按键的一些设置
#define Key_Num 5 //定义按键的个数
//按键连按结构体
typedef struct
{
uint8_t const Key_Continuous_flg; //按键是否支持连按功能,0-无连按,1-连按
uint16_t const Key_down_TimeSet; //连按时间设置
uint16_t const Key_down_frequency; //连按频率设置
}Key_DownTypeStruct;
//按键长按结构体
typedef struct
{
uint8_t const Key_Longdown_flg; //按键是否支持长按功能,0-无长按,1-长按
uint16_t const Key_L_down_TimeSet; //长按时间设置
void(*Key_longdown_Dut)(void); //长按函数指针
}Key_LongDownStruct;
//按键设置结构体
typedef struct
{
GPIO_TypeDef* GPIO_X; //GPIO口
uint16_t GPIO_PIN; //GPIO引脚
uint8_t Key_Value; //按键不按时引脚电平
uint8_t Key_Down; //按键防抖处理后按下标志 0-没按下,1-按下
Key_LongDownStruct Key_LongDown; //按键长按结构体
Key_DownTypeStruct Key_DownType; //按键连按结构体
void(*Key_shortdown_Dut)(void); //短按函数指针
}KeyInitStruct ;
然后在Key_Scan.c定义一个Key的结构体数组
KeyInitStruct Key[Key_Num]={
{GPIOC,GPIO_Pin_7,1,0,{1,100,KeySet_Longdown_Dut},{1,75,10},KeySet_shortdown_Dut}, //SET按键
{GPIOC,GPIO_Pin_9,1,0,{0,500},{1,75,5},KeyOPEN_shortdown_Dut}, //OPEN按键
{GPIOC,GPIO_Pin_8,1,0,{0,500},{0,50,5},KeySTOP_shortdown_Dut}, //STOP按键
{GPIOC,GPIO_Pin_6,1,0,{0,500},{1,75,5},KeyCLOSE_shortdown_Dut}, //CLOSE按键
{GPIOC,GPIO_Pin_14,1,0,{1,400,KeyLearn_Longdown_Dut},{0,50,5},KeyLearn_shortdown_Dut}, //LEARN按键
};
在Key_Scan.c放入按键功能函数,将此函数放在20ms定时器里面,该函数一般不用更改(假如用户要在按键松开实现功能,可把对应函数移动到程序松开位置)
/******************************************************************************
Function :void Key_Scan(void)
------------------------------------------------------------------------------
Description :按键扫描函数
-----------------------------------------------------------------------------
Returnvalue :
-----------------------------------------------------------------------------
Parameters :
-----------------------------------------------------------------------------
Date :
-----------------------------------------------------------------------------
Remarks :
******************************************************************************/
void Key_Scan(void)
{
uint8_t i;
for(i=0;i<Key_Num;i++)
{
if(Key[i].Key_Value ==1) //硬件电路是否上拉
Key[i].Key_Down = 1-GPIO_ReadInputDataBit(Key[i].GPIO_X,Key[i].GPIO_PIN); //硬件电路上拉,按键按下则为0
else
Key[i].Key_Down = GPIO_ReadInputDataBit(Key[i].GPIO_X,Key[i].GPIO_PIN);
}
}
/******************************************************************************
Function :void Key_Function(void)
------------------------------------------------------------------------------
Description :按键功能函数,放在20ms定时器
-----------------------------------------------------------------------------
Returnvalue :
-----------------------------------------------------------------------------
Parameters :
-----------------------------------------------------------------------------
Date :
-----------------------------------------------------------------------------
Remarks :
******************************************************************************/
void Key_Function(void)
{
uint8_t i;
static uint8_t Pre_Key_St[Key_Num] = {0}; //定义一个按键上次状态的变量
static uint16_t Key_St_cnt[Key_Num]={0}; //按键按下计数变量
Key_Scan();
for(i=0;i<Key_Num;i++)
{
if( Key[i].Key_LongDown.Key_Longdown_flg==0 ) //无长按功能
{
if( Key[i].Key_DownType.Key_Continuous_flg==0 )//无连按功能
{
if( Pre_Key_St[i] ^ Key[i].Key_Down ) //按键上一次与现在状态不一致,说明按键要么按下,要么松开
{
Pre_Key_St[i] = Key[i].Key_Down; //这条语句很关键,自己体会,无法言语
if(Key[i].Key_Down) //按键按下
{
Key[i].Key_shortdown_Dut();
}
else //按键松开
{
//可以在此添加按键松开的函数
}
}
else
{
Pre_Key_St[i] = Key[i].Key_Down;;
}
}
else //连按功能
{
if( Pre_Key_St[i] ^ Key[i].Key_Down ) //按键上一次与现在状态不一致,说明按键要么按下,要么松开
{
Pre_Key_St[i] = Key[i].Key_Down; //这条语句很关键,自己体会,无法言语
if(Key[i].Key_Down) //按键按下
{
Key[i].Key_shortdown_Dut();
}
else //按键松开
{
//可以在此添加按键松开的函数
}
}
else
{
Pre_Key_St[i] = Key[i].Key_Down;
}
if(Key[i].Key_Down)
{
Key_St_cnt[i]++; //按键按下开始计数
}
else
{
Key_St_cnt[i] = 0; //松开计数清零
}
if(Key_St_cnt[i]>Key[i].Key_DownType.Key_down_TimeSet) //按键按下时间大于连按时间设置
{
if((Key_St_cnt[i]-Key[i].Key_DownType.Key_down_TimeSet)%Key[i].Key_DownType.Key_down_frequency==0) //进入函数的频率
{
Key[i].Key_shortdown_Dut();
}
}
}
}
else //有长按功能
{
if(Key[i].Key_Down)
{
Key_St_cnt[i]++;
}
if(Key_St_cnt[i]>0 && Key_St_cnt[i]<Key[i].Key_LongDown.Key_L_down_TimeSet) //短按
{
if(Key[i].Key_Down == 0) //短按松开
{
Key[i].Key_shortdown_Dut();
Key_St_cnt[i] = 0;
}
}
else if(Key_St_cnt[i]>=Key[i].Key_LongDown.Key_L_down_TimeSet)
{
if( Pre_Key_St[i] ^ Key[i].Key_Down ) //按键上一次与现在状态不一致,说明按键要么按下,要么松开
{
Pre_Key_St[i] = Key[i].Key_Down; //这条语句很关键,自己体会,无法言语
if(Key[i].Key_Down) //按键按下
{
Key[i].Key_LongDown.Key_longdown_Dut();
}
else //按键长按松开
{
Key_St_cnt[i] = 0; //按键长按松开计数清零
}
}
else
{
Pre_Key_St[i] = Key[i].Key_Down;
}
}
}
}
}
最后是按键对应的功能函数,用户只要在此更改自己所要实现的功能就行
void KeySet_shortdown_Dut(void)
{
//在此添加按键所对应的功能
}
void KeyOPEN_shortdown_Dut(void)
{
//在此添加按键所对应的功能
}
void KeySTOP_shortdown_Dut(void)
{
//在此添加按键所对应的功能
}
void KeyCLOSE_shortdown_Dut(void)
{
//在此添加按键所对应的功能
}
void KeyLearn_shortdown_Dut(void)
{
//在此添加按键所对应的功能
}
void KeySet_Longdown_Dut(void)
{
//在此添加按键所对应的功能
}
void KeyLearn_Longdown_Dut(void)
{
//在此添加按键所对应的功能
}
总结:用户只要配置按键结构体函数,定义按键个数Key_Num ,以及更改对应函数的功能。就可以实现所想要的功能,非常灵活,移植度高。下次有时间在做个按键与数码管二三级菜单设置模块。??
|