功能
本人移植平台为STM32F103C8+FreeRTOS实现按键短按按下 短按抬起 长按按下 长按抬起 长按连发 单键双击 组合按键 均为非阻塞方式,适合裸机和RTOS。
这篇笔记是为了在需要时方便个人拉取,因此只贴了相关代码,而没有太多的文字描述。移植参考的是安富莱的相关教程,相关链接已经贴在了帖子结尾处。
如果有朋友需要详细一点的笔记,或其它问题,可在评论区交流,有时间了在完善。
代码
- delay.c 中的 get_run_tim()、check_run_tim() 函数被 key_fifo.c里面的函数调用。
/**
****************************************************************************
* @file delay.c
* @author BJX
* @version V1.0
* @date 2023-10-xx
* @brief 延时函数,正点模板
****************************************************************************
* @attention
* 系统定时器(SysTick)是一个 24bit的向下递减的计数器,
* 计数器每计数一次的时间为 1/SYSCLK。
*
****************************************************************************
*/
#include "./delay/delay.h"
#include "./sys/sys.h"
static uint8_t fac_us = 0; /* us延时倍乘数 */
/*
全局运行时间,单位1ms,最长可以表示 24.85天
如果你的产品连续运行时间超过这个数,则必须考虑溢出问题
*/
__IO int32_t gil_run_tim = 0;
/**
* @brief systick中断服务获取CPU运行时间,单位1ms。
* 最长可以表示 24.85天,如果你的产品连续运行时间超过这个数,则必须考虑溢出问题
* @param 无
* @retval CPU运行时间,单位1ms
*/
int32_t get_run_tim(void)
{
int32_t run_time;
DISABLE_INT(); /* 关中断 */
run_time = gil_run_tim; /* 这个变量在Systick中断中被改写,因此需要关中断进行保护 */
ENABLE_INT(); /* 开中断 */
return run_time;
}
/**
* @brief 计算当前运行时间和给定时刻之间的差值。处理了计数器循环。
* @param _last_tim:上个时刻
* @retval 当前时间和过去时间的差值,单位1ms
*/
int32_t check_run_tim(int32_t _last_tim)
{
int32_t now_time;
int32_t time_diff;
DISABLE_INT(); /* 关中断 */
now_time = gil_run_tim; /* 这个变量在Systick中断中被改写,因此需要关中断进行保护 */
ENABLE_INT(); /* 开中断 */
if (now_time >= _last_tim)
{
time_diff = now_time - _last_tim;
}
else
{
time_diff = 0x7FFFFFFF - _last_tim + now_time;
}
return time_diff;
}
/* 判断是否使用OS */
#if SYSTEM_SUPPORT_OS
/* 添加公共头文件 FreeRTOS 使用 */
#include "FreeRTOS.h"
#include "task.h"
static uint16_t fac_ms = 0; /* ms延时倍乘数,在 os下,代表每个节拍的ms数 */
extern void xPortSysTickHandler(void);
/**
* @brief systick中断服务函数,使用OS时用到
* @param 无
* @retval 无
*/
void SysTick_Handler(void)
{
/* OS开始跑了,才执行正常的调度处理 */
if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)
{
xPortSysTickHandler();
/* 全局运行时间每1ms增1 */
gil_run_tim++;
/* 这个变量是 int32_t 类型,最大数为 0x7FFFFFFF */
if (gil_run_tim == 0x7FFFFFFF)
{
gil_run_tim = 0;
}
}
}
/**
* @brief 使用RTOS时的延迟函数初始化
* @param 无
* @retval 无
*/
void delay_init(void)
{
uint32_t reload;
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK); /* 选择外部时钟 HCLK */
fac_us = SystemCoreClock / 1000000; /* 不论是否使用 OS,fac_us 都需要使用 */
reload = SystemCoreClock / 1000000; /* 每秒钟的计数次数 单位为 M */
/* 根据 configTICK_RATE_HZ 设定溢出时间 reload 为 24 位寄存器, */
reload *= 1000000 / configTICK_RATE_HZ; /* 最大值:16777216,在 72M 下,约合 0.233s 左右 */
fac_ms = 1000 / configTICK_RATE_HZ; /* 代表 OS 可以延时的最少单位 */
SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk; /* 开启 SYSTICK 中断 */
SysTick->LOAD = reload; /* 每 1/configTICK_RATE_HZ 秒中断一次 */
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; /* 开启 SYSTICK */
}
/**
* @brief 微秒级延时
* @param nus:要延时的 us 数(0~204522252)
* @retval 无
*/
void delay_us(uint32_t nus)
{
uint32_t ticks;
uint32_t told, tnow, tcnt = 0;
uint32_t reload = SysTick->LOAD; /* LOAD 的值(LOAD:定时器重装值) */
ticks = nus * fac_us; /* 需要的节拍数 */
told = SysTick->VAL; /* 刚进入时的计数器值 */
while (1)
{
tnow = SysTick->VAL; /* VAL:当前计数值 */
if (tnow != told)
{
/* 这里注意一下 SYSTICK 是一个递减的计数器就可以了. */
if (tnow < told)
tcnt += told - tnow;
else
tcnt += reload - tnow + told;
told = tnow;
if (tcnt >= ticks)
break; /* 时间超过/等于要延迟的时间,则退出. */
}
}
}
/**
* @brief 毫秒级延时,会引起任务调度
* @param nms 延时时长,范围:0~65535
* @retval 无
*/
void delay_ms(uint32_t nms)
{
if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) /* 系统已经运行 */
{
if (nms >= fac_ms) /* 延时的时间大于 OS 的最少时间周期 */
{
vTaskDelay(nms / fac_ms); /* FreeRTOS 延时 */
}
nms %= fac_ms; /* OS 已经无法提供这么小的延时了 采用普通方式延时*/
}
delay_us((uint32_t)(nms * 1000)); /* 普通方式延时 */
}
/**
* @brief 毫秒级延时,不会引起任务调度
* @param nms 延时时长,范围:0~204522252
* @retval 无
*/
void delay_xms(uint32_t nms)
{
uint32_t i;
for (i = 0; i < nms; i++)
{
delay_us(1000);
}
}
#else
/**
* @brief 初始化延迟函数
* @param sysclk: 系统时钟频率(即CPU频率(HCLK)),单位为 MHz
* @retval 无
*/
void delay_init(uint16_t sysclk)
{
/* 清Systick状态,以便下一步重设,如果这里开了中断会关闭其中断 */
SysTick->CTRL = 0;
fac_us = sysclk; /* 作为1us的基础时基 */
}
/**
* @brief 微秒级延时
* @param xus 延时时长,范围:0~233015
* @retval 无
*/
void delay_us(uint32_t nus)
{
uint32_t temp;
SysTick->LOAD = nus * fac_us; /* 时间加载 */
SysTick->VAL = 0x00; /* 清空计数器 */
SysTick->CTRL |= 1 << 0 ; /* 开始倒数 */
do
{
temp = SysTick->CTRL;
} while ((temp & 0x01) && !(temp & (1 << 16))); /* CTRL.ENABLE位必须为1, 并等待时间到达 */
SysTick->CTRL &= ~(1 << 0) ; /* 关闭SYSTICK */
SysTick->VAL = 0X00; /* 清空计数器 */
}
/**
* @brief 延时nms
* @param nms: 要延时的ms数 (0< nms <= 65535)
* @retval 无
*/
void delay_ms(uint16_t nms)
{
/* 这里用1000,是考虑到可能有超频应用,比如128Mhz的时候,
delay_us最大只能延时1048576us左右了 */
uint32_t repeat = nms / 1000;
uint32_t remain = nms % 1000;
while (repeat)
{
delay_us(1000 * 1000); /* 利用delay_us 实现 1000ms 延时 */
repeat--;
}
if (remain)
{
delay_us(remain * 1000); /* 利用delay_us, 把尾数延时(remain ms)给做了 */
}
}
#endif
/****************************** END OF FILE ******************************/
delay.h
#ifndef __DELAY_H
#define __DELAY_H
#include "stm32f10x.h"
/* 定义系统文件夹是否支持 RTOS */
#define SYSTEM_SUPPORT_OS 1 /* 0,不支持。 1,支持 */
int32_t get_run_tim(void);
int32_t check_run_tim(int32_t _last_tim);
#if SYSTEM_SUPPORT_OS
void delay_init(void);
void delay_us(uint32_t nus);
void delay_ms(uint32_t nms); /* 会引起任务调度 */
void delay_xms(uint32_t nms); /* 不会引起任务调度 */
#else /* 不使用RTOS 的延迟函数 */
void delay_init(uint16_t sysclk);
void delay_us(uint32_t nus);
void delay_ms(uint16_t nms);
#endif
#endif
- sys.c
/**
****************************************************************************
* @file sys.c
* @author BJX
* @version V1.0
* @date 2023-10-28
* @brief 系统代码 暂时只有位带操作
****************************************************************************
* @attention
*
* 平台:STM32F103C8
*
****************************************************************************
*/
#include "./sys/sys.h"
/****************************** END OF FILE ******************************/
sys.h 该头文件里的两个开、关中断的宏要被delay.c里面的函数调用。
#ifndef __SYS_H
#define __SYS_H
#include "stm32f10x.h"
/* 开关全局中断的宏 */
#define ENABLE_INT() __set_PRIMASK(0) /* 使能全局中断 */
#define DISABLE_INT() __set_PRIMASK(1) /* 禁止全局中断 */
/* 位带操作公式:把“位带地址 + 位序号”转换成别名地址的宏 BINBAND:位带 */
#define BITBAND(addr, n) ((addr & 0xF0000000)+0x02000000+((addr &0x00FFFFFF)<<5)+(n<<2))
/* 先把地址强制转化为指针类型,再对指针进行操作 */
#define BIT_ADDR(addr,n) *(unsigned int*) BITBAND(addr, n)
#define GPIOA_ODR_Addr (GPIOA_BASE+0X0C) /* ODR寄存器 */
#define GPIOB_ODR_Addr (GPIOB_BASE+0X0C)
#define GPIOC_ODR_Addr (GPIOC_BASE+0X0C)
#define GPIOD_ODR_Addr (GPIOD_BASE+0X0C)
#define GPIOE_ODR_Addr (GPIOE_BASE+0X0C)
#define GPIOF_ODR_Addr (GPIOF_BASE+0X0C)
#define GPIOG_ODR_Addr (GPIOG_BASE+0X0C)
#define GPIOA_IDR_Addr (GPIOA_BASE+0X08) /* IDR寄存器 */
#define GPIOB_IDR_Addr (GPIOB_BASE+0X08)
#define GPIOC_IDR_Addr (GPIOC_BASE+0X08)
#define GPIOD_IDR_Addr (GPIOD_BASE+0X08)
#define GPIOE_IDR_Addr (GPIOE_BASE+0X08)
#define GPIOF_IDR_Addr (GPIOF_BASE+0X08)
#define GPIOG_IDR_Addr (GPIOG_BASE+0X08)
#define PAout(n) BIT_ADDR(GPIOA_ODR_Addr,n) /* 输出 */
#define PAin(n) BIT_ADDR(GPIOA_IDR_Addr,n) /* 输入 */
#define PBout(n) BIT_ADDR(GPIOB_ODR_Addr,n) /* 输出 */
#define PBin(n) BIT_ADDR(GPIOB_IDR_Addr,n) /* 输入 */
#define PCout(n) BIT_ADDR(GPIOC_ODR_Addr,n) /* 输出 */
#define PCin(n) BIT_ADDR(GPIOC_IDR_Addr,n) /* 输入 */
#define PDout(n) BIT_ADDR(GPIOD_ODR_Addr,n) /* 输出 */
#define PDin(n) BIT_ADDR(GPIOD_IDR_Addr,n) /* 输入 */
#define PEout(n) BIT_ADDR(GPIOE_ODR_Addr,n) /* 输出 */
#define PEin(n) BIT_ADDR(GPIOE_IDR_Addr,n) /* 输入 */
#endif
- key_fifo.c
/**
******************************************************************************
* @file key_fifo.c
* @author BJX
* @version V1.0
* @date 2024-01-27
* @brief 按键FIFO驱动
******************************************************************************
* @attention
*
* 平台: F103C8T6
*
******************************************************************************
*/
#include "./key/key_fifo.h"
#include "./delay/delay.h"
static key_t st_btn[KEY_COUNT] = {0};
static key_fifo st_key; /* 按键FIFO变量,结构体 */
/* GPIO和PIN定义 */
static const key_gpio st_gpio_list[KEY_HARD_NUM] = {
{GPIOA, GPIO_Pin_0, 1}, /* K1 */
{GPIOA, GPIO_Pin_1, 1}, /* K2 */
};
/**
* @brief 按键硬件初始化配置
* @param 无
* @retval 无
*/
void key_hard_init(void)
{
uint8_t i;
GPIO_InitTypeDef GPIO_InitStruct;
ALL_KEY_GPIO_CLK_ENABLE(); /* 打开所有按键GPIO时钟 */
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPD; /* 设置引脚模式为下拉输入 */
for (i = 0; i < KEY_HARD_NUM; i++)
{
GPIO_InitStruct.GPIO_Pin = st_gpio_list[i].pin;
GPIO_Init(st_gpio_list[i].gpio, &GPIO_InitStruct);
}
}
/**
* @brief 初始化按键变量
* @param 无
* @retval 无
*/
void key_var_init(void)
{
uint8_t i;
/* 对按键FIFO读写指针清零 */
st_key.read = 0;
st_key.write = 0;
st_key.read2 = 0;
/* 给每个按键结构体成员变量赋一组缺省值 */
for (i = 0; i < KEY_COUNT; i++)
{
st_btn[i].long_tim = KEY_LONG_TIME; /* 长按时间 0 表示不检测长按键事件 */
st_btn[i].cnt = KEY_FILTER_TIME / 2; /* 计数器设置为滤波时间的一半 */
st_btn[i].state = 0; /* 按键缺省状态,0为未按下 */
st_btn[i].repeat_speed = 6; /* 按键连发的速度,0表示不支持连发 */
st_btn[i].repeat_cnt = 0; /* 连发计数器 */
st_btn[i].delay_cnt = 0;
st_btn[i].click_cnt = 0;
st_btn[i].last_tim = 0;
}
/* 如果需要单独更改某个按键的参数,可以在此单独重新赋值 */
}
/**
* @brief 设置按键参数
* @param _key_id: 按键ID
* @param _long_tim: 长按事件时间
* @param _repeat_speed: 连发速度
* @retval 无
*/
void key_set_param(uint8_t _key_id, uint16_t _long_tim, uint8_t _repeat_speed)
{
st_btn[_key_id].long_tim = _long_tim; /* 长按时间 0 表示不检测长按键事件 */
st_btn[_key_id].repeat_speed = _repeat_speed; /* 按键连发的速度,0表示不支持连发 */
st_btn[_key_id].repeat_cnt = 0; /* 连发计数器 */
}
/**
* @brief 将1个键值压入按键FIFO缓冲区。可用于模拟一个按键
* @param _key_code: 按键代码
* @retval 无
*/
void key_put(uint8_t _key_code)
{
st_key.buf[st_key.write] = _key_code;
if(++st_key.write >= KEY_FIFO_SIZE)
{
st_key.write = 0;
}
}
/**
* @brief 从按键FIFO缓冲区读取一个键值
* @param 无
* @retval 按键代码
*/
uint8_t key_get(void)
{
uint8_t ret;
if (st_key.read == st_key.write)
{
return KEY_NONE;
}
else
{
ret = st_key.buf[st_key.read];
if (++st_key.read >= KEY_FIFO_SIZE)
{
st_key.read = 0;
}
return ret;
}
}
/**
* @brief 从按键FIFO缓冲区读取一个键值。独立的读指针
* @param 无
* @retval 按键代码
*/
uint8_t key_get2(void)
{
uint8_t ret;
if (st_key.read2 == st_key.write)
{
return KEY_NONE;
}
else
{
ret = st_key.buf[st_key.read2];
if (++st_key.read2 >= KEY_FIFO_SIZE)
{
st_key.read2 = 0;
}
return ret;
}
}
/**
* @brief 读取按键的状态
* @param _key_id: 按键ID
* @retval 1 表示按下, 0 表示未按下
*/
uint8_t key_get_state(key_id _key_id)
{
return st_btn[_key_id].state;
}
/**
* @brief 清空按键FIFO缓冲区
* @param 无
* @retval 无
*/
void key_clear(void)
{
st_key.read = st_key.write;
}
/**
* @brief 判断按键是否按下
* @param _id:按键id
* @retval 1 表示按下(导通),0表示未按下(释放)
*/
static uint8_t key_pin_active(uint8_t _id)
{
uint8_t level;
if ((st_gpio_list[_id].gpio->IDR & st_gpio_list[_id].pin) == 0)
{
level = 0;
}
else
{
level = 1;
}
if (level == st_gpio_list[_id].active_level)
{
return 1;
}
else
{
return 0;
}
}
/**
* @brief 判断按键是否按下。单键和组合键区分。单键事件不允许有其他键按下
* @param _id:按键id
* @retval 1 表示按下(导通),0表示未按下(释放)
*/
static uint8_t key_down_func(uint8_t _id)
{
/* 实体单键 */
if (_id < KEY_HARD_NUM)
{
uint8_t i;
uint8_t count = 0;
uint8_t save = 255;
/* 判断有几个键按下 */
for (i = 0; i < KEY_HARD_NUM; i++)
{
if (key_pin_active(i))
{
count++;
save = i;
}
}
if (count == 1 && save == _id)
{
return 1; /* 只有1个键按下时才有效 */
}
return 0;
}
/* 组合键 K1K2 */
if (_id == KEY_HARD_NUM + 0)
{
if (key_pin_active(KID_K1) && key_pin_active(KID_K2))
{
return 1;
}
else
{
return 0;
}
}
/* 组合键 K2K3 */
if (_id == KEY_HARD_NUM + 1)
{
if (key_pin_active(KID_K2) && key_pin_active(KID_K3))
{
return 1;
}
else
{
return 0;
}
}
return 0;
}
/**
* @brief 检测一个按键。非阻塞状态,必须被周期性的调用
* @param _id: 按键id
* @retval 无
*/
static void key_detect(uint8_t _id)
{
key_t *pbtn;
pbtn = &st_btn[_id];
if (key_down_func(_id))
{
if (pbtn->cnt < KEY_FILTER_TIME)
{
pbtn->cnt = KEY_FILTER_TIME;
}
else if (pbtn->cnt < 2 * KEY_FILTER_TIME)
{
pbtn->cnt++;
}
else
{
if (pbtn->state == 0)
{
pbtn->state = 1;
/* 发送按钮按下的消息 */
key_put((uint8_t)(KEY_MSG_STEP * _id + KEY_1_DOWN));
}
if (pbtn->long_tim > 0)
{
if (pbtn->long_cnt < pbtn->long_tim)
{
/* 发送长按消息 */
if (++pbtn->long_cnt == pbtn->long_tim)
{
pbtn->state = 2;
/* 键值放入按键FIFO */
key_put((uint8_t)(KEY_MSG_STEP * _id + KEY_1_LONG_DOWN));
}
}
else
{
if (pbtn->repeat_speed > 0)
{
if (++pbtn->repeat_cnt >= pbtn->repeat_speed)
{
pbtn->repeat_cnt = 0;
/* 长按键后,每隔连按周期发送1个按键弹起事件 */
key_put((uint8_t)(KEY_MSG_STEP * _id + KEY_1_AUTO_UP));
}
}
}
}
}
}
else
{
if (pbtn->cnt > KEY_FILTER_TIME)
{
pbtn->cnt = KEY_FILTER_TIME;
}
else if (pbtn->cnt != 0)
{
pbtn->cnt--;
}
else
{
if (pbtn->state != 0)
{
/* 长按后的弹起 */
if (pbtn->long_tim == 0)
{
/* 发送短按弹起的消息 */
key_put((uint8_t)(KEY_MSG_STEP * _id + KEY_1_UP));
}
else
{
if (pbtn->state == 2)
{
/* 发送长按弹起的消息 */
key_put((uint8_t)(KEY_MSG_STEP * _id + KEY_1_LONG_UP));
#if DOUBLE_CLICK_ENABLE == 1
st_btn[_id].last_tim = get_run_tim(); /* 记录按键弹起时刻 */
#endif
}
else
{
#if DOUBLE_CLICK_ENABLE == 1
/* 发送短按弹起的消息 */
if (check_run_tim(st_btn[_id].last_tim) < 500)
{
if (pbtn->click_cnt == 1)
{
key_put((uint8_t)(KEY_MSG_STEP * _id + KEY_1_DB_UP)); /* 双击事件 */
pbtn->click_cnt = 0;
}
else
{
key_put((uint8_t)(KEY_MSG_STEP * _id + KEY_1_UP)); /* 单击弹起事件 */
pbtn->click_cnt = 0;
}
pbtn->delay_cnt = 80;
}
else
{
pbtn->click_cnt++;
pbtn->delay_cnt = KEY_DB_CLICK_TIME;
}
st_btn[_id].last_tim = get_run_tim(); /* 记录按键弹起时刻 */
#else
key_put((uint8_t)(KEY_MSG_STEP * _id + KEY_1_UP)); /* 单击弹起事件 */
#endif
}
}
pbtn->state = 0;
}
}
pbtn->long_cnt = 0;
pbtn->repeat_cnt = 0;
}
}
/**
* @brief 扫描所有按键。非阻塞,10ms一次
* @param 无
* @retval 无
*/
void key_scan10ms(void)
{
uint8_t i;
for (i = 0; i < KEY_COUNT; i++)
{
#if DOUBLE_CLICK_ENABLE == 1
/* 超时判断 */
if (st_btn[i].delay_cnt > 0)
{
if (--st_btn[i].delay_cnt == 0)
{
if (st_btn[i].click_cnt == 1)
{
key_put((uint8_t)(KEY_MSG_STEP * i + KEY_1_UP)); /* 单击弹起 */
}
st_btn[i].click_cnt = 0;
}
}
#endif
/* 检测按键 */
key_detect(i);
}
}
/****************************** END OF FILE ******************************/
key_fifo.h
#ifndef __KEY_FIFO_H
#define __KEY_FIFO_H
#include "stm32f10x.h"
/* 使能GPIO时钟 */
#define ALL_KEY_GPIO_CLK_ENABLE() do{ \
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); \
}while(0);
#define DOUBLE_CLICK_ENABLE 0 /* 1表示启用双击检测, 会影响单击体验变慢 */
#define KEY_HARD_NUM 2 /* 实体按键个数 */
#define KEY_COUNT (KEY_HARD_NUM + 2) /* 8个独立建 + 2个组合按键 */
/*
按键滤波时间50ms, 单位10ms。
只有连续检测到50ms状态不变才认为有效,包括弹起和按下两种事件
即使按键电路不做硬件滤波,该滤波机制也可以保证可靠地检测到按键事件
*/
#define KEY_FILTER_TIME 5
#define KEY_LONG_TIME 100 /* 单位10ms, 持续1秒,认为长按事件 */
#define KEY_DB_CLICK_TIME 40 /* 双击检测时长,单位ms */
#define KEY_FIFO_SIZE 10 /* 按键FIFO最大的存储量 */
typedef struct{
uint8_t buf[KEY_FIFO_SIZE]; /* 键值缓冲区 */
uint8_t read; /* 缓冲区读指针1 */
uint8_t write; /* 缓冲区写指针 */
uint8_t read2; /* 缓冲区读指针2 */
}key_fifo;
typedef struct{ /* 依次定义按键所用的GPIO */
GPIO_TypeDef* gpio;
uint16_t pin;
uint8_t active_level; /* 触发电平 */
}key_gpio;
/* 按键ID, 主要用于bsp_KeyState()函数的入口参数 */
typedef enum{
KID_K1 = 0,
KID_K2,
KID_K3,
}key_id;
/* 每个按键对应1个全局的结构体变量 */
typedef struct{
/* 下面是一个函数指针,指向判断按键是否按下的函数 */
uint8_t (*key_down_func)(void); /* 按键按下的判断函数,1表示按下 */
uint8_t cnt; /* 滤波器计数器 */
uint16_t long_cnt; /* 长按计数器 */
uint16_t long_tim; /* 按键按下持续时间, 0表示不检测长按 */
uint8_t state; /* 按键当前状态(按下还是弹起) */
uint8_t repeat_speed; /* 连续按键周期 */
uint8_t repeat_cnt; /* 连续按键计数器 */
uint16_t delay_cnt; /* 延迟计数器,用于双击检测 */
uint8_t click_cnt; /* 单击次数 */
int32_t last_tim; /* 上次按键时刻 */
}key_t;
/* 根据应用程序的功能重命名按键宏 */
#define KEY_DOWN_K1 KEY_1_DOWN
#define KEY_UP_K1 KEY_1_UP
#define KEY_LONG_DOWN_K1 KEY_1_LONG_DOWN
#define KEY_LONG_UP_K1 KEY_1_LONG_UP
#define KEY_AUTO_K1 KEY_1_AUTO_UP
#define KEY_DB_K1 KEY_1_DB_UP
#define KEY_DOWN_K2 KEY_2_DOWN
#define KEY_UP_K2 KEY_2_UP
#define KEY_LONG_DOWN_K2 KEY_2_LONG_DOWN
#define KEY_LONG_UP_K2 KEY_2_LONG_UP
#define KEY_AUTO_K2 KEY_2_AUTO_UP
#define KEY_DB_K2 KEY_2_DB_UP
#define KEY_DOWN_K3 KEY_3_DOWN
#define KEY_UP_K3 KEY_3_UP
#define KEY_LONG_DOWN_K3 KEY_3_LONG_DOWN
#define KEY_LONG_UP_K3 KEY_3_LONG_UP
#define KEY_AUTO_K3 KEY_3_AUTO_UP
#define KEY_DB_K3 KEY_3_DB_UP
#define SYS_DOWN_K1K2 KEY_9_DOWN /* K1 K2 组合键 */
#define SYS_UP_K1K2 KEY_9_UP
#define SYS_LONG_DOWN_K1K2 KEY_9_LONG_DOWN
#define SYS_LONG_UP_K1K2 KEY_9_LONG_UP
#define SYS_AUTO_K1K2 KEY_9_AUTO_UP
#define SYS_DB_K1K2 KEY_9_DB_UP
#define SYS_DOWN_K2K3 KEY_10_DOWN /* K2 K3 组合键 */
#define SYS_UP_K2K3 KEY_10_UP
#define SYS_LONG_DOWN_K2K3 KEY_10_LONG_DOWN
#define SYS_LONG_UP_K2K3 KEY_10_LONG_UP
#define SYS_AUTO_K2K3 KEY_10_AUTO_UP
#define SYS_DB_K2K3 KEY_10_DB_UP
/*
定义键值代码, 必须按如下次序定时每个键的按下、弹起和长按事件
推荐使用enum, 不用#define,原因:
(1) 便于新增键值,方便调整顺序,使代码看起来舒服点
(2) 编译器可帮我们避免键值重复。
*/
#define KEY_MSG_STEP 6 /* 按键消息代码步长(每个键有几个事件代码,顺序排列) */
typedef enum{
KEY_NONE = 0, /* 0 表示按键事件 */
KEY_1_DOWN, /* 1键按下 */
KEY_1_UP, /* 1键弹起 */
KEY_1_LONG_DOWN, /* 1键长按 */
KEY_1_LONG_UP, /* 1键长按后的弹起 */
KEY_1_AUTO_UP, /* 1键长按后自动发码 */
KEY_1_DB_UP, /* 1键双击 */
KEY_2_DOWN, /* 2键按下 */
KEY_2_UP, /* 2键弹起 */
KEY_2_LONG_DOWN, /* 2键长按 */
KEY_2_LONG_UP, /* 2键长按后的弹起 */
KEY_2_AUTO_UP, /* 2键长按后自动发码 */
KEY_2_DB_UP, /* 2键双击 */
KEY_3_DOWN, /* 3键按下 */
KEY_3_UP, /* 3键弹起 */
KEY_3_LONG_DOWN, /* 3键长按 */
KEY_3_LONG_UP, /* 3键长按后的弹起 */
KEY_3_AUTO_UP, /* 3键长按后自动发码 */
KEY_3_DB_UP, /* 3键双击 */
/* 组合键 */
KEY_9_DOWN, /* 9键按下 */
KEY_9_UP, /* 9键弹起 */
KEY_9_LONG_DOWN, /* 9键长按 */
KEY_9_LONG_UP, /* 9键长按后的弹起 */
KEY_9_AUTO_UP, /* 9键长按后自动发码 */
KEY_9_DB_UP, /* 9键双击 */
KEY_10_DOWN, /* 10键按下 */
KEY_10_UP, /* 10键弹起 */
KEY_10_LONG_DOWN, /* 1键长按 */
KEY_10_LONG_UP, /* 1键长按后的弹起 */
KEY_10_AUTO_UP, /* 1键长按后自动发码 */
KEY_10_DB_UP, /* 1键双击 */
}key_num;
void key_hard_init(void);
void key_var_init(void);
void key_put(uint8_t _key_code);
void key_set_param(uint8_t _key_id, uint16_t _long_tim, uint8_t _repeat_speed);
uint8_t key_get(void);
uint8_t key_get2(void);
uint8_t key_get_state(key_id _key_id);
void key_clear(void);
void key_scan10ms(void);
static uint8_t key_pin_active(uint8_t _id);
static uint8_t key_down_func(uint8_t _id);
static void key_detect(uint8_t _id);
#endif /* __KEY_FIFO_H */
- key.c 要确保 key_task() 每10ms被调用一次。
/**
***************************************************************************
* @file key.c
* @author BJX
* @version V1.0
* @date 2023-xx-xx
* @brief 按键驱动
***************************************************************************
* @attention
*
* 平台: F103C8T6
*
***************************************************************************
*/
#include "./key/key.h"
#include "./key/key_fifo.h"
#include "./usart/usart.h"
/**
* @brief 按键初始化
* @param 无
* @retval 无
*/
void key_init(void)
{
key_hard_init(); /* 初始化按键变量 */
key_var_init(); /* 初始化按键硬件 */
}
/**
* @brief 按键任务处理
* @param 无
* @retval 无
*/
void key_task(void)
{
uint8_t key_code; /* 按键代码 */
key_scan10ms();
/* 按键滤波和检测由后台systick中断服务程序实现,我们只需要调用key_get读取键值即可。 */
key_code = key_get(); /* 读取键值, 无键按下时返回 KEY_NONE = 0 */
if (key_code != KEY_NONE)
{
switch (key_code)
{
case KEY_DOWN_K1:
printf("K1键按下\r\n");
break;
case KEY_UP_K1:
printf("K1键单击---\r\n");
break;
case KEY_LONG_DOWN_K1:
printf("K1键长按按下\r\n");
break;
case KEY_LONG_UP_K1:
printf("K1键长按弹起\r\n");
break;
case KEY_AUTO_K1:
printf("K1键长按连发\r\n");
break;
case KEY_DB_K1:
printf("K1键双击***\r\n");
break;
default:
/* 其它的键值不处理 */
break;
}
}
}
/****************************** END OF FILE ******************************/
key.h
#ifndef __KEY_H
#define __KEY_H
#include "stm32f10x.h"
void key_init(void);
void key_task(void);
#endif /* __KEY_H */
此移植参考 安富莱按键FIFO教程例子 、安富莱按键FIFO教程视频。