头文件 KeyDriver.h
/*
* @Author: Neo Wang
* @Date: 2022-04-27 16:01:15
* @LastEditTime: 2022-05-16 11:53:50
*/
#ifndef __KEY_DRIVER_H_
#define __KEY_DRIVER_H_
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "driver/gpio.h"
typedef signed int s32_t;
typedef unsigned int u32_t;
typedef signed short s16_t;
typedef unsigned short u16_t;
typedef signed char s8_t;
typedef unsigned char u8_t;
#define MAX_KEY_SIZE (32) // 最多支持按键数量
#define DEFAULT_RND_TIM (30) // 默认循环调用时间 30ms
#define DEFAULT_KEY_INTERVAL (20) // 默认连击间隔 20x30=600ms
#define DEFAULT_CB_PRESS_CNT (100) // 默认长按回调计数 100x30=3000ms
#define GET_PIN_STATUS(k) gpio_get_level(k) // 读取单一按键函数, 不同平台读取函数不同,
// 返回值要求为bool, 按下返回0
typedef struct KeyStruct {
struct sta {
u8_t press : 1; // 按下标记
u8_t click : 1; // 单击标记
u8_t upClick : 1; // 弹起标记
u8_t pressExe : 1; // 长按执行标记
} sta;
u16_t keyNumber; // 按键号(和硬件相关)
u16_t clickCnt; // 按键次数
u16_t clickCntDown; // 按键间隔时间
u16_t pressCnt; // 长按时间(执行次数)
u16_t pressCBCnt; // 长按回调时间
void (*clickCallback)(u16_t, u16_t); // 单击回调 传入参数为键号和点击次数
void (*upClickCallback)(u16_t, u16_t); // 弹起回调 传入参数为键号和点击次数
void (*pressCallback)(u16_t, u16_t); // 长按回调 传入参数为键号和长按时间
} KeyStruct;
void ScanKeyboard(void) ;
void SetKeyInterval(u16_t tim) ;
void SetKeyboardRoundTime(u16_t tim) ;
s16_t GetKeyPressTime(u16_t num) ;
int KeyInsert(struct KeyStruct *k) ;
int KeyAdd(u16_t num, u16_t tim, void (*clickCB)(u16_t, u16_t), void (*uplickCB)(u16_t, u16_t), void (*lpressCB)(u16_t, u16_t)) ;
#endif
按键驱动程序源码
/*
按键驱动程序
实现按键的单击 双击 多击等操作
实现按键的弹起检测操作
实现按键长按操作
使用方式 :
把 void ScanKeyboard(void) 放在一个可以定时执行的任务或者系统嘀嗒中
在执行之前先配置定时执行的间隔时间 < void SetKeyboardRoundTime(u16_t tim) >,不配置的话默认采用30ms;
如果配置的时间和实际循环时间不同, 则会影响长按键响应时间和连击间隔时间的准确性
配置按键连击的间隔时间 < void SetKeyInterval(u16_t tim) > , 不配置的话,默认连击间隔时间是600ms
采用 < int KeyInsert(struct KeyStruct *k) > 或者 < int KeyAdd(u16_t, u16_t, void (*)(u16_t), void (*)(void), void (*)(u16_t)) >
添加按键,注意添加的顺序对应按键的键号
根据需要编写对应功能的回调函数,不需要的功能需要配置回调为 NULL
* 单击和弹起回调函数的传入值是点击次数
* 长按回调函数传入值是按下时间长度 单位ms
注意:
1. 根据不同的平台需要自己修改头文件中的 #define GET_PIN_STATUS(k)
用来读取单一按键的高低电平值,返回值要求为 bool,而且按下为0弹起为1
2. 配置按键间隔时间应该放在配置循环时间之后
*/
#include "KeyDriver.h"
struct KeyStruct *_gKeyBuffer[MAX_KEY_SIZE]; // 按键buffer,采用数组方式
u16_t _gRoundTime = DEFAULT_RND_TIM; // 循环时间
u16_t _gInterval = DEFAULT_KEY_INTERVAL; // 间隔时间
u16_t _gBtnSize = 0; // 按键数量
/**
* @brief 按键扫描函数
* 需要定时执行,执行时间需要根据实际时间使用SetKeyboardRoundTime来配置 *
*/
void ScanKeyboard(void) {
u8_t tssta = 0;
for(int i=0; i<_gBtnSize; i++) {
tssta = !GET_PIN_STATUS(_gKeyBuffer[i]->keyNumber);
_gKeyBuffer[i]->sta.click = tssta & (tssta ^ _gKeyBuffer[i]->sta.press);
_gKeyBuffer[i]->clickCntDown = _gKeyBuffer[i]->clickCntDown ? _gKeyBuffer[i]->clickCntDown-1 : 0;
if(_gKeyBuffer[i]->clickCntDown == 0)
_gKeyBuffer[i]->clickCnt = 0;
if(_gKeyBuffer[i]->sta.press != tssta) {
_gKeyBuffer[i]->sta.upClick = tssta ^ _gKeyBuffer[i]->sta.press ^ _gKeyBuffer[i]->sta.click;
if(_gKeyBuffer[i]->sta.click != 0) {
_gKeyBuffer[i]->clickCnt += 1;
_gKeyBuffer[i]->clickCntDown = _gInterval;
}
} else
_gKeyBuffer[i]->sta.upClick = 0;
_gKeyBuffer[i]->sta.press = tssta;
_gKeyBuffer[i]->pressCnt = (tssta ? _gKeyBuffer[i]->pressCnt+1 : 0);
if(_gKeyBuffer[i]->sta.click == 1 && _gKeyBuffer[i]->clickCallback != NULL) {
_gKeyBuffer[i]->clickCallback(i, _gKeyBuffer[i]->clickCnt);
} else if(_gKeyBuffer[i]->sta.upClick == 1 && _gKeyBuffer[i]->upClickCallback != NULL) {
_gKeyBuffer[i]->upClickCallback(i, _gKeyBuffer[i]->clickCnt);
}
if((_gKeyBuffer[i]->pressCallback != NULL) && (_gKeyBuffer[i]->pressCnt >= _gKeyBuffer[i]->pressCBCnt)) {
if(_gKeyBuffer[i]->sta.pressExe == 0) {
_gKeyBuffer[i]->sta.pressExe = 1;
_gKeyBuffer[i]->pressCallback(i, _gKeyBuffer[i]->pressCnt*_gRoundTime);
}
} else
_gKeyBuffer[i]->sta.pressExe = 0;
}
}
/**
* @brief 配置双击和多连击之间的按键间隔时间
*
* @param tim
*/
void SetKeyInterval(u16_t tim) {
_gInterval = tim/_gRoundTime;
}
/**
* @brief 配置循环时间
*
* @param tim
*/
void SetKeyboardRoundTime(u16_t tim) {
u16_t tsInterval = _gRoundTime * _gInterval;
_gRoundTime = tim;
_gInterval = tsInterval / tim; // 重新计算连击间隔时间
}
/**
* @brief 获取对应按键的按下时间,可以用来判断多个按键按下的时常
*
* @param num 按键号 执行KeyAdd或者KeyInsert的顺序就是按键号
* @return s16_t
*/
s16_t GetKeyPressTime(u16_t num) {
if(num < _gBtnSize) {
return _gKeyBuffer[num]->pressCnt * _gRoundTime;
}
return -1;
}
/**
* @brief 采用直接添加结构体的方式添加按键
* 添加失败返回 -1
* @param k
* @return int
*/
int KeyInsert(struct KeyStruct *k) {
if(_gBtnSize >= MAX_KEY_SIZE)
return -1;
if(k->pressCBCnt < _gInterval)
k->pressCBCnt = DEFAULT_CB_PRESS_CNT;
_gKeyBuffer[_gBtnSize] = k;
_gBtnSize += 1;
return 0;
}
/**
* @brief
*
* @param num 按键号,对应硬件管脚号
* @param tim 长按回调时间 毫秒
* @param clickCB 点击回调函数
* @param uplickCB 弹起回调函数
* @param lpressCB 长按回调函数
* @return int
*/
int KeyAdd(u16_t num, u16_t tim, void (*clickCB)(u16_t, u16_t), void (*uplickCB)(u16_t, u16_t), void (*lpressCB)(u16_t, u16_t)) {
if(_gBtnSize >= MAX_KEY_SIZE)
return -1;
struct KeyStruct *k = malloc(sizeof(struct KeyStruct));
if(k == NULL)
return -2;
memset(k,0,sizeof(struct KeyStruct));
k->keyNumber = num;
k->pressCBCnt = tim/_gRoundTime;
k->clickCallback = clickCB;
k->upClickCallback = uplickCB;
k->pressCallback = lpressCB;
if(k->pressCBCnt < _gInterval)
k->pressCBCnt = DEFAULT_CB_PRESS_CNT;
_gKeyBuffer[_gBtnSize] = k;
_gBtnSize += 1;
return 0;
}
使用说明看源代码顶端的说明部分
示例
#define GPIO_INPUT_SW1 11
void keyLpress(u16_t k, u16_t t) { // 长按1秒回调
printf("Key %d Long press %d", k,t);
}
void keyClick(u16_t k, u16_t c) { // 按键回调
printf("Click %d cnt=%d",k,c);
}
void keyUpClick(u16_t k, u16_t c) { // 按键回调
printf("UpClick %d cnt=%d",k,c);
}
SetKeyboardRoundTime(100);
KeyAdd(GPIO_INPUT_SW1,1000,keyClick,keyUpClick,keyLpress); // 注册按键
// 其中这部分是ESP32中的定义
#define GET_PIN_STATUS(k) gpio_get_level(k)
STM32使用的话需要修改一下单按键读取的部分
也就是 #define GET_PIN_STATUS(k)
这里的定义
我一般采用下面的这种定义方式,里面的地址是STM32F10X的地址
其他系列需要修改
//-另外一种定义方式
#define nPA(n) (0x20+n)
#define nPB(n) (0x30+n)
#define nPC(n) (0x40+n)
#define nPD(n) (0x50+n)
#define nPE(n) (0x60+n)
#define nPF(n) (0x70+n)
#define nPG(n) (0x80+n)
#define nRCC(PIN) (1<<(PIN>>4)) // 转换为端口时钟
#define nPIN(PIN) (1<<(PIN&0x0f)) // 转换为管脚号
#define nGIO(PIN) ((GPIO_TypeDef *)(((PIN&0xF0)<<6) + APB2PERIPH_BASE)) // 转换为端口号
#define GPIO_PinOutHL(pin,sta) (nGIO(pin)->BSRR = (nPIN(pin)<<(((sta)==0)<<4))) // GPIO按位输出指定电平
#define GPIO_PinOutHigh(pin) (nGIO(pin)->BSRR = nPIN(pin)) // GPIO按位输出高
#define GPIO_PinOutLow(pin) (nGIO(pin)->BRR = nPIN(pin)) // GPIO按位输出低
#define GPIO_PinOutRev(pin) (nGIO(pin)->ODR ^= nPIN(pin)) // GPIO按位输出反
#define GPIO_PortOutHigh(port,pin) (nGIO(port)->BSRR = pin) // GPIO端口输出高 暂时有问题
#define GPIO_PortOutLow(port,pin) (nGIO(port)->BRR = pin) // GPIO端口输出低 暂时有问题
#define GPIO_PortOutRev(port,pin) (nGIO(port)->ODR ^= pin) // GPIO端口输出反 暂时有问题
#define GPIO_PinIn(pin) ((nGIO(pin)->IDR & nPIN(pin)) != 0) // 读取单一端口状态
#define GPIO_PortIn(pin) (nGIO(pin)->IDR ) // 读取端口状态(输入该端口任何一个管脚即可)
#define GPIO_PinOutSta(pin) ((nGIO(pin)->ODR & nPIN(pin)) != 0) // 读取单一端口输出状态
#define GPIO_PortOutSta(port) (nGIO(pin)->ODR ) // 读取端口输出状态(输入该端口任何一个管脚即可)
使用方式如下
// 修改读取IO的接口
#define GET_PIN_STATUS(k) GPIO_PinIn(k)
#define GPIO_INPUT_SW1 nPA(1)
SetKeyboardRoundTime(100);
KeyAdd(GPIO_INPUT_SW1,800,keyClick,NULL,keyLpress); // 注册按键