原创支持单击、双击、弹起、长按的按键驱动程序

头文件  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);  // 注册按键

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值