按键扫描是嵌入式开发中常用的功能,有按键 的地方就有它。一套优雅的按键扫描代码每个项目都可以用。本着这个思想我写了一套按键处理的函数,可以当作模块使用。
按键处理的需求,主要有识别单击、双击、多击、长按等需求。当有按键触发我们来处理一些逻辑,本套代码使用函数指针的方法实现按键回调,每个按键可编写一个回调函数来执行相关操作。
下面来看代码:
key.h内容
//按键数量与名称
typedef enum
{
KEY1,
KEY2,
KEY3,
KEY4,
KEY5,
KEYNUM //放在最后统计按键数量
} V_KEY_NAME;
//按键回调函数类型
typedef void(*v_key_fun)(int param);
void key_config(void);//初始化
void key_run(void); //按键扫描
int set_key_callback(V_KEY_NAME keyname, v_key_fun param);//回调函数绑定
这里说一下枚举类型操作,前几个成员是我们给我们的按键的名字,最后是利用枚举类型如果不指定值其成员值从0开始增加最后一个值正好记录共有多少个按键。
key.c
#include "key.h"
/*KEY_DOWN 在单片机中换成相应io口读取函数*/
#define KEY_DOWN(vKey) ((GetAsyncKeyState(vKey) & 0x8000) ? 1:0) //获取键盘对应按键状态
#define DEB_TIME 3 //按键消抖参数
//操作结束判定时间 例如按键扫描10ms执行一次 则30 即是300ms,在300ms内的点击会被算为连击
#define OP_ED_TIME 30
#define LTICK_TIME 150 //长按判定时间
typedef enum
{
IO_SET, //按键触发
IO_RESET //按键未触发
}IO_STATE;
/*按键结构体*/
typedef struct
{
v_key_fun key_callback; //按键回调函数
//按键硬件接口(在例如tm32单片机中可以再定义一个port GPIO端口号)
unsigned int key_pin;
IO_STATE io_state; //按键状态
unsigned int press_counter; //按键按下滤波
unsigned int lift_counter; //按键抬起滤波
unsigned int press_time; //按下计时
unsigned int lift_time; //抬起计时
unsigned int key_level;//多击判断
}keyStruct;
/*按键结构体数组*/
static keyStruct key_array[KEYNUM];
/*按键初始化配置*/
void key_config(void)
{
key_array[KEY1].key_pin = 'A'; //这里由于我使用的电脑按键,移植到单片机中绑定对应的引脚
key_array[KEY2].key_pin = 'S';
key_array[KEY3].key_pin = 'D';
key_array[KEY4].key_pin = 'W';
key_array[KEY5].key_pin = 'F';
for (int i = 0; i < KEYNUM; i++)
{
key_array[i].key_callback = NULL;
key_array[i].lift_counter = 0;
key_array[i].press_counter = 0;
key_array[i].io_state = IO_RESET;
key_array[i].press_time = 0;
key_array[i].lift_time = 0;
key_array[i].key_level = 0;
}
}
/*key_run 是按键执行的主函数 定时调用推荐5-15ms调用一次*/
void key_run(void)
{
static uint32_t key_run_times = 0;
key_run_times++;
for (int i = 0; i < KEYNUM; i++)
{
/*滤波*/
if (KEY_DOWN(key_array[i].key_pin))
{
key_array[i].press_counter++;
key_array[i].lift_counter = 0;
if (key_array[i].press_counter == DEB_TIME)
{
if (key_array[i].io_state == IO_RESET)
{
key_array[i].io_state = IO_SET;
key_array[i].press_time = key_run_times;
}
}
}
else
{
key_array[i].press_counter = 0;
key_array[i].lift_counter++;
if (key_array[i].lift_counter == DEB_TIME)
{
if (key_array[i].io_state == IO_SET)
{
key_array[i].io_state = IO_RESET;
key_array[i].lift_time = key_run_times;
}
}
}
/*N次击判定*/
if (key_array[i].press_time == key_run_times)
{
key_array[i].key_level++;
}
/*操作结束执行*/
else if ((unsigned int)(key_run_times - key_array[i].lift_time) == OP_ED_TIME && key_array[i].io_state != IO_SET && key_array[i].key_level != 0)
{
if (key_array[i].key_callback != NULL )
{
key_array[i].key_callback(key_array[i].key_level);//执行按键回调函数
}
key_array[i].key_level = 0;
}
/*长按操作*/
if (key_array[i].io_state == IO_SET && (unsigned int)(key_run_times - key_array[i].press_time) == LTICK_TIME)
{
if (key_array[i].key_callback != NULL)
{
key_array[i].key_callback(-1);
}
key_array[i].key_level = 0;
}
}
}
/*
* 绑定回调函数 成功返回 1 错误返回 0
*/
int set_key_callback(V_KEY_NAME keyname,v_key_fun param)
{
if (param != NULL)
{
key_array[keyname].key_callback = param;
return 1;
}
return 0;
}
/*
* 取消对应的回调函数
*/
void remove_key_callback(V_KEY_NAME keyname)
{
key_array[keyname].key_callback = NULL;
}
C文件主要写了按键多击判定逻辑,由于进行了多连击的判定,当在一定的时间的多级按键操作被判定为连击,所有当我们操作结束后并不会立即结束,程序会等带一定时间判定无新的操作,在这里key_level数值代码按了几次。
按键模块的使用
main.c
#include <stdio.h>
#include <stdint.h>
#include <Windows.h>
#include "sys_tick.h"
#include "key.h"
/*供按键绑定的回调函数*/
void key1callback(int param);
int main()
{
sys_tick_init();
key_config(); //按键配置函数
set_key_callback(KEY1, key1callback);//设置按键对用的回调函数
while (1)
{
/*每10ms扫描一次*/
key_run();
delay_ms(10);
}
return 0;
}
void key1callback(int param)
{
switch (param)
{
case 1:
printf("单击操作\n"); break;
case 2:
printf("双击操作\n"); break;
case 3:
printf("三连击操作\n"); break;
case 4:
printf("四连击操作\n"); break;
case -1:
printf("长按操作\n"); break;
default:
break;
}
}
通过回调函数来执行按键操作可以方便的启用或关闭相关按键。后续可以配合状态机使用,达到不同状态不同执行不同的按键功能
至此结束,内容原创禁止转载