cubemx配置
- 打开串口
- 配置按键的 gpio口为 up input 模式
- 配置时钟树
代码
yxy_open_key.c
#include "yxy_open_key.h"
#include "stdlib.h"
#include "string.h"
#include "yxy_debug.h"
/* // 按键实现思路:
用户需要使用某个按键的时候,
把按键通过 key_add 添加到链表里面,
key_add函数会把按键名称绑定到对应的按键端口和按键引脚上面去,
当按键按下时,会在 key_scan 函数中跳转到对应的按键处理函数中去,
并且如果 KEY_UART_DEBUG 为1 的话,
则会把按键名称以及对应的按键方式打印到串口上面去。
概念:
时间基段:判断按键的击打状态的时间基准。
单击判断条件: 按下时间在第一个时间基段以内,
并且按下结束后在一个时间基段内没有按下的算单击。
长按判断条件: 按下时间超过 4 个时间基段的则可以认为是长按
双击判断条件: 第一次的按下时间在一个时间基段以内,
第一次和第二次按下之间的时间不能大于一个时间基段,
第二次无论按下多长,都算是双击,
第二次按下结束后在一个时间基段内没有按下,则是双击。
三击判断条件: 三击和双击的原理一样。
四击判断条件: 四击和双击的原理一样。
使用:
创建一个 User_Key userVar 全局变量
初始化:
openKey_init();
key_add(&userVar, (uint8_t *)"keyName", KEY_GPIO_Port, KEY_Pin);
main主循环:
while(1)
{
// 每1ms扫描一次 key_scan 函数(在while中用任意代码使他等待1ms)。
key_scan();
HAL_Delay(1);
}
重写回调函数,key_click、key_double、key_third、key_fourth、key_long
在回调函数中if(key_id == userVar.keyID),判断是哪个按键的回调函数,
并在对应的回调中写对应的用户代码。
*/
/*********************
* 宏定义区域
*********************/
/*********************
* 内部变量区域
*********************/
uint16_t keyNum = 0; // 按键数量
struct Key_Link * p_kl = NULL;
/*********************
* 回调弱函数
*********************/
/* key中调函数(调用回调函数之前先调用中调函数)
* 用来确认调用哪个回调函数
*/
__weak void key_click(uint16_t key_id)
{
USER_PASS;
}
#if KEY_SUPPORT >= 2
__weak void key_double(uint16_t key_id)
{
USER_PASS;
}
#endif
#if KEY_SUPPORT >= 3
__weak void key_third(uint16_t key_id)
{
USER_PASS;
}
#endif
#if KEY_SUPPORT >= 4
__weak void key_fourth(uint16_t key_id)
{
USER_PASS;
}
#endif
__weak void key_long(uint16_t key_id)
{
USER_PASS;
}
/*********************
* 内部函数
*********************/
static uint16_t Radical_2(uint16_t x);
/* 连续开 2 次方根,计算某个数是2的多少次方跟 */
static uint16_t Radical_2(uint16_t x)
{
uint16_t y = 0;
while(x)
{
x >>= 1;
y++;
}
y--;
return y;
}
/*********************
* 外部函数实现
*********************/
/**
* @brief 开源按键的初始化
* @param
* @retval 返回 HAL_OK 则初始化成功
*/
HAL_StatusTypeDef openKey_init(void)
{
p_kl = (struct Key_Link *)malloc(sizeof(struct Key_Link));
if(p_kl == NULL)
{
yxy_DEBUG("open key initialization failed... \r\n");
return HAL_ERROR;
}
p_kl->p_kN = NULL;
strcpy((char *)p_kl->kC.keyName, "keyLinkHand"); //按键名字为"keyLinkHand"
p_kl->kC.keyID = 0xffff; // 给头结点赋值一个到不了的值
p_kl->kC.keyDownCnt = 0; // 按键按下计数为0
p_kl->kC.keyUpCnt = 0; // 按键抬起计数为0
p_kl->kC.keyManyCliCnt = 0; // 按键多击计数为0
p_kl->kC.kDownLockState = KEY_NULL;// 当前无按下的解锁状态
// yxy_DEBUG("%s\r\n", p_kl->kC.keyName);
// 按键数量为零
keyNum = 0;
yxy_DEBUG("open key initialization success !\r\n");
return HAL_OK;
}
/**
* @brief 添加一个按键到按键链表里面
* @param 按键的名字
* @param 按键的端口
* @param 按键的引脚
* @retval 返回 HAL_OK 则添加按键成功
*/
HAL_StatusTypeDef key_add(User_Key * userKey, uint8_t * keyName, GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
{
struct Key_Link * p_keyLinkNode = p_kl; // 创建一个中间变量保存p_kl的值
uint8_t GPIOxName = (uint8_t)((((uint32_t)GPIOx - (uint32_t)GPIOA) / 0x00000400UL) + 'A');
uint16_t PinName = Radical_2(GPIO_Pin);
if(IS_GPIO_ALL_INSTANCE(GPIOx) != 1)
{
yxy_DEBUG("key add GPIOx error... \r\n");
return HAL_ERROR;
}
if((IS_GPIO_PIN(GPIO_Pin) != 1) && (GPIO_Pin != GPIO_PIN_All))
{
yxy_DEBUG("key add GPIO_Pin error... \r\n");
return HAL_ERROR;
}
// 找到链表的尾部分
while(p_keyLinkNode->p_kN != NULL)
{
p_keyLinkNode = p_keyLinkNode->p_kN;
}
// 给新的按键创建一个空间
p_keyLinkNode->p_kN = (struct Key_Link *)malloc(sizeof(struct Key_Link));
p_keyLinkNode = p_keyLinkNode->p_kN;
if(p_keyLinkNode == NULL)
{
yxy_DEBUG("key add failed... \r\n");
return HAL_ERROR;
}
p_keyLinkNode->p_kN = NULL;
// 按键名字赋值
strcpy((char *)p_keyLinkNode->kC.keyName, (const char *)keyName);
// 按键id随着按键的增加逐渐递加
p_keyLinkNode->kC.keyID = keyNum;
// 按键数量加1
keyNum++;
// 按键绑定硬件端口、引脚
p_keyLinkNode->kC.GPIOx = GPIOx;
p_keyLinkNode->kC.GPIO_Pin = GPIO_Pin;
// 状态归位
p_keyLinkNode->kC.keyDownCnt = 0;
p_keyLinkNode->kC.keyUpCnt = 0;
p_keyLinkNode->kC.keyManyCliCnt = 0;
p_keyLinkNode->kC.kDownLockState = KEY_NULL;
// 把初始化好的 key 给用户用
userKey->GPIOx = GPIOx;
userKey->GPIO_Pin = GPIO_Pin;
userKey->keyID = p_keyLinkNode->kC.keyID;
userKey->portname = GPIOxName;
userKey->pinName = PinName;
// 打印添加的按键信息
yxy_DEBUG("New key :\r\n name : \"%s\" \r\n", p_keyLinkNode->kC.keyName);
yxy_DEBUG(" port : GPIO%c\r\n", GPIOxName);
yxy_DEBUG(" Pin : PIN_%d\r\n", PinName);
yxy_DEBUG("\r\n");
return HAL_OK;
}
/**
* @brief 按键链表删除一个按键节点
* @param 按键的端口
* @param 按键的引脚
* @retval 返回 HAL_OK 则添加按键成功
*/
HAL_StatusTypeDef key_clear(User_Key * userKey)
{
struct Key_Link * p_keyLinkNode = p_kl; // 创建一个中间变量保存p_kl的值
struct Key_Link * p_keyNode_temp = NULL;
if(IS_GPIO_ALL_INSTANCE(userKey->GPIOx) != 1)
{
yxy_DEBUG("key clear GPIOx error... \r\n");
return HAL_ERROR;
}
if(IS_GPIO_PIN(userKey->GPIO_Pin) != 1)
{
yxy_DEBUG("key clear GPIO_Pin error... \r\n");
return HAL_ERROR;
}
// 在整个链表寻找按键
while(p_keyLinkNode->p_kN != NULL)
{
// 寻找对应的按键,一但下一个节点是对应的按键,则把当前的节点连接到后面的节点上,并删除按键节点
if((p_keyLinkNode->p_kN->kC.GPIOx == userKey->GPIOx) && (p_keyLinkNode->p_kN->kC.GPIO_Pin == userKey->GPIO_Pin))
{
p_keyNode_temp = p_keyLinkNode->p_kN;
p_keyLinkNode->p_kN = p_keyNode_temp->p_kN;
free(p_keyNode_temp);
p_keyNode_temp = NULL;
userKey->GPIOx = NULL;
userKey->GPIO_Pin = 0;
userKey->keyID = 0xff;
yxy_DEBUG("key clear ok!\r\n");
return HAL_OK;
}
p_keyLinkNode = p_keyLinkNode->p_kN;
}
return HAL_ERROR;
}
/**
* @brief 扫描按键的状态,并跳转到对应的按键处理函数中
* @param
* @retval
*/
void key_scan(void)
{
struct Key_Link * p_keyLinkNode = p_kl->p_kN; // 创建一个中间变量保存p_kl的值
// 扫描每一个节点看看,有没有按键的状态发生变化
while(p_keyLinkNode != NULL)
{
// 若是当前节点的按键按下了
if((p_keyLinkNode->kC.GPIOx->IDR & p_keyLinkNode->kC.GPIO_Pin) == (uint32_t)GPIO_PIN_RESET) // 按下不一定是低电平,后期可以适配
{
p_keyLinkNode->kC.keyUpCnt = 0;
p_keyLinkNode->kC.keyDownCnt++;
switch(p_keyLinkNode->kC.kDownLockState)
{
case KEY_NULL:
{
// 消抖处理
if(p_keyLinkNode->kC.keyDownCnt >= KEY_Jitters_Elimination)
{
p_keyLinkNode->kC.keyDownCnt = 0;
p_keyLinkNode->kC.keyManyCliCnt = 0;
// 中间状态晋升为单击
p_keyLinkNode->kC.kDownLockState = KEY_CLICK;
}
}
break;
case KEY_CLICK:
{
// 消抖处理
if((p_keyLinkNode->kC.keyDownCnt >= KEY_Jitters_Elimination) && (p_keyLinkNode->kC.keyManyCliCnt == 1))
{
p_keyLinkNode->kC.keyDownCnt = 0;
p_keyLinkNode->kC.keyManyCliCnt = 0;
#if KEY_SUPPORT >= 2
// 中间状态晋升为双击
p_keyLinkNode->kC.kDownLockState = KEY_DOUBLE;
#else
// 无法判断为双击,则执行单击
p_keyLinkNode->kC.kDownLockState = KEY_NULL;
key_click(p_keyLinkNode->kC.keyID);
#endif
}
// 当预判断为单击的时候,仍按下2秒就是长按
if((p_keyLinkNode->kC.keyDownCnt >= TIME_BASE_SLOT * 4) && (p_keyLinkNode->kC.keyManyCliCnt < 1))
{
p_keyLinkNode->kC.keyDownCnt = 0;
p_keyLinkNode->kC.kDownLockState = KEY_NULL;
key_long(p_keyLinkNode->kC.keyID);
// 等待长按结束
while((p_keyLinkNode->kC.GPIOx->IDR & p_keyLinkNode->kC.GPIO_Pin) == (uint32_t)GPIO_PIN_RESET);
}
}
break;
#if KEY_SUPPORT >= 2
case KEY_DOUBLE:
{
// 消抖处理
if((p_keyLinkNode->kC.keyDownCnt >= KEY_Jitters_Elimination) && (p_keyLinkNode->kC.keyManyCliCnt == 2))
{
p_keyLinkNode->kC.keyDownCnt = 0;
p_keyLinkNode->kC.keyManyCliCnt = 0;
#if KEY_SUPPORT >= 3
// 中间状态晋升为三击
p_keyLinkNode->kC.kDownLockState = KEY_THIRD;
#else
// 无法判断为三击,则执行双击
p_keyLinkNode->kC.kDownLockState = KEY_NULL;
key_double(p_keyLinkNode->kC.keyID);
#endif
}
}
break;
#endif
#if KEY_SUPPORT >= 3
case KEY_THIRD:
{
// 消抖处理
if((p_keyLinkNode->kC.keyDownCnt >= KEY_Jitters_Elimination) && (p_keyLinkNode->kC.keyManyCliCnt == 3))
{
p_keyLinkNode->kC.keyDownCnt = 0;
p_keyLinkNode->kC.keyManyCliCnt = 0;
#if KEY_SUPPORT >= 4
// 中间状态晋升为四击
p_keyLinkNode->kC.kDownLockState = KEY_FOURTH;
#else
// 无法判断为四击,则执行三击
p_keyLinkNode->kC.kDownLockState = KEY_NULL;
key_third(p_keyLinkNode->kC.keyID);
#endif
}
}
break;
#endif
#if KEY_SUPPORT >= 4
case KEY_FOURTH:
{
}
break;
#endif
default:
break;
}
}
else // 若是当前节点的按键没按下
{
if(p_keyLinkNode->kC.kDownLockState != NULL)
{
p_keyLinkNode->kC.keyDownCnt = 0;
p_keyLinkNode->kC.keyUpCnt++;
switch(p_keyLinkNode->kC.kDownLockState)
{
case KEY_CLICK:
{
// 击打次数为 1
p_keyLinkNode->kC.keyManyCliCnt = 1;
}
break;
#if KEY_SUPPORT >= 2
case KEY_DOUBLE:
{
// 击打次数为 2
p_keyLinkNode->kC.keyManyCliCnt = 2;
}
break;
#endif
#if KEY_SUPPORT >= 3
case KEY_THIRD:
{
// 击打次数为 3
p_keyLinkNode->kC.keyManyCliCnt = 3;
}
break;
#endif
#if KEY_SUPPORT >= 4
case KEY_FOURTH:
{
// 击打次数为 4
p_keyLinkNode->kC.keyManyCliCnt = 4;
}
break;
#endif
default:
break;
}
if(p_keyLinkNode->kC.keyUpCnt >= TIME_BASE_SLOT)
{
p_keyLinkNode->kC.keyUpCnt = 0;
p_keyLinkNode->kC.keyManyCliCnt = 0;
switch(p_keyLinkNode->kC.kDownLockState)
{
case KEY_CLICK:
key_click(p_keyLinkNode->kC.keyID);
break;
#if KEY_SUPPORT >= 2
case KEY_DOUBLE:
key_double(p_keyLinkNode->kC.keyID);
break;
#endif
#if KEY_SUPPORT >= 3
case KEY_THIRD:
key_third(p_keyLinkNode->kC.keyID);
break;
#endif
#if KEY_SUPPORT >= 4
case KEY_FOURTH:
key_fourth(p_keyLinkNode->kC.keyID);
break;
#endif
default:
break;
}
p_keyLinkNode->kC.kDownLockState = KEY_NULL;
}
}
}
p_keyLinkNode = p_keyLinkNode->p_kN;
}
}
/**
* @brief 获取已经添加的按键数量
* @retval 返回添加的按键数量
*/
uint16_t get_key_Num(void)
{
return keyNum;
}
/**
* @brief 获取按键绑定的名字
* @param 获取按键名字的buff(buff大小至少需要20个字节)
* @param 按键的端口
* @param 按键的引脚号
* @retval 无
*/
HAL_StatusTypeDef get_key_Name(User_Key * userKey, uint8_t * nameReadBuff)
{
struct Key_Link * p_keyLinkNode = p_kl->p_kN; // 创建一个中间变量保存p_kl的值
while(p_keyLinkNode != NULL)
{
// 寻找对应的按键
if((p_keyLinkNode->kC.GPIOx == userKey->GPIOx) && (p_keyLinkNode->kC.GPIO_Pin == userKey->GPIO_Pin))
{
strcpy((char *)nameReadBuff, (const char *)p_keyLinkNode->kC.keyName);
return HAL_OK;
}
p_keyLinkNode = p_keyLinkNode->p_kN;
}
return HAL_ERROR;
}
yxy_open_key.h
#ifndef __KEY_H_
#define __KEY_H_
/* 支持C++调用。
* 用extern"C"来告诉编译器:这是一个用C写成的库文件,请用C的方式来链接它们。
*/
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "gpio.h"
/*Error checking*/
#ifdef YXY
// #error "故意给你一个错误,啦啦啦!"
#warning "这个警告要害死强迫症!"
#endif
/*********************
* DEFINES
*********************/
/* 按键多击支持(默认支持单击和长按)
*
* >=2 则可支持2击
* >=3 则可支持2、3击
* >=4 则可支持2、3、4击
*/
#define KEY_SUPPORT 4
/* 最大支持的按键数量 */
#define KEY_MAX_NUM 10
/* 默认按键名字最大长度,默认值为20 */
#define KEY_NUME_LEN_MAX 20
/* 消抖时间 - 20ms */
#define KEY_Jitters_Elimination 20
/* 时间基段 - 100ms */
#define TIME_BASE_SLOT 100
/**********************
* TYPEDEFS
**********************/
/* 按键状态值KEY_枚举 */
typedef enum
{
KEY_NULL = 0 , // 按键无动作
KEY_CLICK = 1 , // 默认支持单击
#if KEY_SUPPORT >= 2
KEY_DOUBLE = 2 ,
#endif
#if KEY_SUPPORT >= 3
KEY_THIRD = 3 ,
#endif
#if KEY_SUPPORT >= 4
KEY_FOURTH = 4 ,
#endif
KEY_MAX_VAL = 0xffff, // 按键状态最大值(永远无法超越的值)
KEY_LONG_PRESS = KEY_MAX_VAL-1, // 长按
KEY_LAST_NULL_VAL = 0xffff // 按键最后的无效值
}KEY_STATE;
/* 按键类结构体 */
typedef struct
{
/* 按键的名称、代号 */
uint8_t keyName[KEY_NUME_LEN_MAX]; // 按键名字,长度可由 KEY_NUME_LEN 设置
uint16_t keyID; // 按键id,由代码自动分配
/* 按键硬件 */
GPIO_TypeDef *GPIOx; // 按键的端口
uint16_t GPIO_Pin; // 按键的引脚
/* 按键的状态 */
uint16_t keyDownCnt; // 按键按下的计数值
uint16_t keyUpCnt; // 按键抬起的计数值
uint16_t keyManyCliCnt; // 按键多击计数
KEY_STATE kDownLockState; // 被按下的按键当前已解锁的状态
}key_Class;
/* 按键链表结构体 */
struct Key_Link
{
key_Class kC; // 按键类
struct Key_Link * p_kN; // 指向下一个节点的指针
};
/* 用户按键结构体 */
typedef struct
{
/* 代号 */
uint16_t keyID; // 按键id,由代码自动分配
/* 按键硬件 */
GPIO_TypeDef *GPIOx; // 按键的端口
uint16_t GPIO_Pin; // 按键的引脚
/* 硬件引脚打印用值 */
uint8_t portname; // 端口的名称
uint16_t pinName; // 引脚的名称
}User_Key;
/**********************
* GLOBAL PROTOTYPES
**********************/
HAL_StatusTypeDef openKey_init(void);
HAL_StatusTypeDef key_add(User_Key * userKey, uint8_t * keyName, GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin);
HAL_StatusTypeDef key_clear(User_Key * userKey);
void key_scan(void);
uint16_t get_key_Num(void);
HAL_StatusTypeDef get_key_Name(User_Key * userKey, uint8_t * nameReadBuff);
/* 弱函数外部引用(可以重写,不用调用) */
void key_click(uint16_t key_id);
#if KEY_SUPPORT >= 2
void key_double(uint16_t key_id);
#endif
#if KEY_SUPPORT >= 3
void key_third(uint16_t key_id);
#endif
#if KEY_SUPPORT >= 4
void key_fourth(uint16_t key_id);
#endif
void key_long(uint16_t key_id);
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /* __KEY_H_ */
使用
初次使用
在 yxy_open_key.h 中更改对应的宏配置
KEY_SUPPORT
- 按键多击支持(默认支持单击和长按)
- >=2 则可支持2击
- >=3 则可支持2、3击
- >=4 则可支持2、3、4击
KEY_MAX_NUM
- 可添加按键的最大数量。
KEY_NUME_LEN_MAX
- 用户自定义的按键名字的最大长度。
KEY_Jitters_Elimination
- 用户可定义的按键消抖时间,单位ms。
TIME_BASE_SLOT
- 时间段落的长度,用于判断按键的不同击打模式的时间基准,单位ms。
main.c
main函数外需要添加的部分
头文件
#include "O_redirect.h"
#include "yxy_debug.h"
#include "yxy_open_key.h"
需要定义的全局变量
// 用户的按键对象,用于初始化按键,以及在按键回调中,匹配按键id,
// 或者用于获取按键的打印信息
User_Key key0;
重写按键回调函数
// 单击回调函数
void key_click(uint16_t key_id)
{
if(key_id == key0.keyID)
{
U_Printf("key0 click\r\n");
}
}
// 双击回调函数
void key_double(uint16_t key_id)
{
if(key_id == key0.keyID)
{
U_Printf("key0 double\r\n");
}
}
// 三击回调函数
void key_third(uint16_t key_id)
{
if(key_id == key0.keyID)
{
U_Printf("key0 third\r\n");
}
}
// 四击回调函数
void key_fourth(uint16_t key_id)
{
if(key_id == key0.keyID)
{
U_Printf("key0 fourth\r\n");
}
}
// 长按回调函数
void key_long(uint16_t key_id)
{
if(key_id == key0.keyID)
{
U_Printf("key0 long\r\n");
}
}
main函数非循环部分
// 开源按键初始化,这个是必须的
openKey_init();
// 根据自己的按键情况,添加按键进来
key_add(&Key0, (uint8_t *)"first key", KEY0_GPIO_Port, KEY0_Pin);
while循环中
// 对应的按键扫描函数,提供时间基准,时间基准最好在 1ms 左右
// 以及扫描按键链表上各个按键的状态,并调用对应的按键事件回调函数
key_scan();
HAL_Delay(1);