设计按键 FIFO 主要有三个方面的好处:
1. 可以有效的记录按键事件的发生,特别是需要实现按键的按下,长按,弹起等事件,使用 FIFO的方式来实现是一种非常好的思路。
2. 系统是非阻塞的,这样系统在检测到按键按下的情况下,由于机械按键抖动的原因不需要在这里等待一段时间,然后再确定按键是否按下。
3. 按键 FIFO 程序在嘀嗒定时器中定期的执行检测,不需要在主程序中一直做检测,这样可以有效的降低系统资源消耗。
实现代码如下:
1. 可以有效的记录按键事件的发生,特别是需要实现按键的按下,长按,弹起等事件,使用 FIFO的方式来实现是一种非常好的思路。
2. 系统是非阻塞的,这样系统在检测到按键按下的情况下,由于机械按键抖动的原因不需要在这里等待一段时间,然后再确定按键是否按下。
3. 按键 FIFO 程序在嘀嗒定时器中定期的执行检测,不需要在主程序中一直做检测,这样可以有效的降低系统资源消耗。
关于按键是否该使用中断方式去实现的问题,很多初学者都比较模糊,我这里从两方面简单说一下,纯属个人见解,如果那位有更好的意见,欢迎提出来。
从裸机的角度分析
中断方式:中断方式可以有效的检测到按键按下的消息,并执行相应的程序,但是用中断方式实现按键 FIFO 相对就有点麻烦,如果每个按键都是独立的接一个 IO 引脚,需要我们给每个 IO都设置一个中断,程序中过多的中断会影响系统的稳定性。
查询方式:查询方式有一个最大的缺点就是需要程序定期的去执行查询,耗费一定的系统资源,实际上耗费不了多大的系统资源,因为这种查询方式也只是查询按键是否按下,按键事件的执行还是在主程序里面实现。
从 OS 的角度分析
中断方式:在 OS 中要尽可能少用中断方式,因为在 RTOS 中过多的使用中断会影响系统的稳定性和可预见性(抢占式调度的 OS 基本没有可预见性,基于时间触发调度的可预见性要好很多)。比较重要的事件处理需要用中断的方式。
查询方式:对于用户按键推荐使用这种查询方式来实现,现在的 OS 基本都带有 CPU 利用率的功能,这个按键 FIFO 占用的还是很小的,基本都在%1 以下。
这个按键 FIFO 程序主要用于扫描扫描独立按键,具有软件滤波机制,具有按键 FIFO。可以检测如下事件:
1. 按键按下
2. 按键弹起
3. 长按键
4. 长按时自动连发
声明代码如下:
/**
******************************************************************************
* @file : bsp_key.h
* @author : xiaofeng
* @version : V1.0
* @date : 2015.05.21
* @brief : STM32F4 KEY FIFO
******************************************************************************
* @attention:
*
******************************************************************************
*/
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __BSP_KEY_H__
#define __BSP_KEY_H__
#ifdef __cplusplus
extern "C" {
#endif
/* Includes ------------------------------------------------------------------*/
#include "stm32f4xx.h"
/* Exported types ------------------------------------------------------------*/
// 按键ID
typedef enum
{
KID_K1 = 0,
KID_K2,
KID_K3,
KID_K4
}KEY_ID_E;
/*
定义键值代码, 必须按如下次序定时每个键的按下、弹起和长按事件
推荐使用enum, 不用#define,原因:
(1) 便于新增键值,方便调整顺序,使代码看起来舒服点
(2) 编译器可帮我们避免键值重复。
*/
typedef enum
{
KEY_NONE = 0, /* 0 表示按键事件 */
KEY1_DOWN, /* 1键按下 */
KEY1_UP, /* 1键弹起 */
KEY1_LONG, /* 1键长按 */
KEY2_DOWN, /* 2键按下 */
KEY2_UP, /* 2键弹起 */
KEY2_LONG, /* 2键长按 */
}KEY_ENUM;
/* Exported constants --------------------------------------------------------*/
/* Exported macro ------------------------------------------------------------*/
#define KEY_COUNT 2 // 按键个数
#define KEY_FIFO_SIZE 10 // 按键FIFO大小
#define KEY_FILTER_TIME 5 // 按键滤波时间50ms, 只有连续检测到50ms状态不变才认为有效,包括弹起和按下两种事件
#define KEY_LONG_TIME 0 // 长按时间. 0,表示不检测长按键; 其他,检测长按键的时间
#define KEY_REPEAT_SPEED 0 // 长按键连发速度. 0,表示不支持连发,上报长按事件;其他,连发按下
// 按键口对应的RCC时钟及引脚
#define RCC_ALL_KEY (RCC_AHB1Periph_GPIOA )
#define GPIO_PORT_K1 GPIOA
#define GPIO_PIN_K1 GPIO_Pin_0
#define GPIO_PORT_K2 GPIOD
#define GPIO_PIN_K2 GPIO_Pin_1
/* Exported functions --------------------------------------------------------*/
void KEY_Init(void);
void KEY_Scan(void);
void KEY_FIFO_Clear(void);
uint8_t KEY_FIFO_Get(void);
uint8_t KEY_GetState(KEY_ID_E ucKeyID);
void KEY_SetParam(uint8_t ucKeyID, uint16_t LongTime, uint8_t RepeatSpeed);
#ifdef __cplusplus
}
#endif
#endif
/*****END OF FILE****/
实现代码如下:
/**
******************************************************************************
* @file : bsp_key.h
* @author : xiaofeng
* @version : V1.0
* @date : 2015.05.21
* @brief : STM32F4 KEY FIFO
******************************************************************************
* @attention:
*
******************************************************************************
*/
/* Includes ------------------------------------------------------------------*/
#include "bsp_key.h"
/* Private typedef -----------------------------------------------------------*/
// 每个按键对应1个全局的结构体变量。
typedef struct
{
/* 下面是一个函数指针,指向判断按键手否按下的函数 */
uint8_t (*IsKeyDownFunc)(void); /* 按键按下的判断函数,1表示按下 */
uint8_t Count; /* 滤波器计数器 */
uint8_t State; /* 按键当前状态(按下还是弹起) */
uint16_t LongCount; /* 长按计数器 */
uint16_t LongTime; /* 按键按下持续时间, 0表示不检测长按 */
uint8_t RepeatSpeed; /* 连续按键周期 */
uint8_t RepeatCount; /* 连续按键计数器 */
}KEY_T;
// 按键FIFO用到变量
typedef struct
{
uint8_t Buf[KEY_FIFO_SIZE]; /* 键值缓冲区 */
uint8_t Read; /* 缓冲区读指针 */
uint8_t Write; /* 缓冲区写指针 */
}KEY_FIFO_T;
/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
static KEY_T s_tBtn[KEY_COUNT];
static KEY_FIFO_T s_tKey; /* 按键FIFO变量,结构体 */
/* Private function prototypes -----------------------------------------------*/
static void KEY_FIFO_Init(void);
static void KEY_GPIO_Config(void);
static void KEY_FIFO_Put(uint8_t _KeyCode);
static void KEY_Detect(uint8_t i);
/* Private functions ---------------------------------------------------------*/
/**
* @brief : KEY初始化
* @note :
* @param :
* @retval :
*/
void KEY_Init(void)
{
KEY_GPIO_Config();
KEY_FIFO_Init();
}
/**
* @brief : 清空按键FIFO缓冲区
* @note : 无
* @param : 无
* @retval : 无
*/
void KEY_FIFO_Clear(void)
{
s_tKey.Read = s_tKey.Write;
}
/**
* @brief : 从按键FIFO缓冲区读取一个键值
* @note : 无
* @param :
* @retval : 按键代码
*/
uint8_t KEY_FIFO_Get(void)
{
uint8_t ret;
if (s_tKey.Read == s_tKey.Write)
{
return KEY_NONE;
}
else
{
ret = s_tKey.Buf[s_tKey.Read];
if (++s_tKey.Read >= KEY_FIFO_SIZE)
{
s_tKey.Read = 0;
}
return ret;
}
}
/**
* @brief : 读取按键的状态
* @note : 无
* @param : ucKeyID : 按键ID,从0开始
* @retval : 1 表示按下, 0 表示未按下
*/
uint8_t KEY_GetState(KEY_ID_E ucKeyID)
{
return s_tBtn[ucKeyID].State;
}
/**
* @brief : 设置按键参数
* @note : 无
* @param : ucKeyID : 按键ID,从0开始
* LongTime : 长按事件时间
* RepeatSpeed : 连发速度
* @retval : 无
*/
void KEY_SetParam(uint8_t ucKeyID, uint16_t LongTime, uint8_t RepeatSpeed)
{
s_tBtn[ucKeyID].LongTime = LongTime; /* 长按时间 0 表示不检测长按键事件 */
s_tBtn[ucKeyID].RepeatSpeed = RepeatSpeed; /* 长按键连发的速度,0表示不支持连发 */
s_tBtn[ucKeyID].RepeatCount = 0; /* 连发计数器 */
}
/**
* @brief : 扫描所有按键。非阻塞,被周期性的调用(如systick中断)
* @note : 无
* @param : 无
* @retval : 无
*/
void KEY_Scan(void)
{
uint8_t i;
for (i = 0; i < KEY_COUNT; i++)
{
KEY_Detect(i);
}
}
/***************************************************************************/
/**
* @brief : 配置按键对应的GPIO
* @note : 无
* @param : 无
* @retval : 无
*/
static void KEY_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* 第1步:打开GPIO时钟 */
RCC_AHB1PeriphClockCmd(RCC_ALL_KEY, ENABLE);
/* 第2步:配置所有的按键GPIO为浮动输入模式(实际上CPU复位后就是输入状态) */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN; /* 设为输入口 */
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; /* 设为推挽模式 */
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; /* 无需上下拉电阻 */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; /* IO口最大速度 */
GPIO_InitStructure.GPIO_Pin = GPIO_PIN_K1;
GPIO_Init(GPIO_PORT_K1, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_PIN_K2;
GPIO_Init(GPIO_PORT_K2, &GPIO_InitStructure);
}
/**
* @brief : 判断按键是否按下
* @note : 无
* @param : 无
* @retval : 1 表示按下,0表示未按下
*/
static uint8_t IsKey1Down(void) {if (GPIO_ReadInputDataBit(GPIO_PORT_K1, GPIO_PIN_K1) == 0) return 1;else return 0;}
static uint8_t IsKey2Down(void) {if (GPIO_ReadInputDataBit(GPIO_PORT_K2, GPIO_PIN_K2) == 0) return 1;else return 0;}
/**
* @brief : 初始化按键变量
* @note : 无
* @param : 无
* @retval : 无
*/
static void KEY_FIFO_Init(void)
{
uint8_t i;
/* 对按键FIFO读写指针清零 */
s_tKey.Read = 0;
s_tKey.Write = 0;
/* 给每个按键结构体成员变量赋一组缺省值 */
for (i = 0; i < KEY_COUNT; i++)
{
s_tBtn[i].LongTime = KEY_LONG_TIME; /* 长按时间 0 表示不检测长按键事件 */
s_tBtn[i].Count = KEY_FILTER_TIME / 2; /* 计数器设置为滤波时间的一半 */
s_tBtn[i].State = 0; /* 按键缺省状态,0为未按下 */
s_tBtn[i].RepeatSpeed = KEY_REPEAT_SPEED; /* 按键连发的速度,0表示不支持连发 */
s_tBtn[i].RepeatCount = 0; /* 连发计数器 */
}
/* 判断按键按下的函数 */
s_tBtn[0].IsKeyDownFunc = IsKey1Down;
s_tBtn[1].IsKeyDownFunc = IsKey2Down;
}
/**
* @brief : 将1个键值压入按键FIFO缓冲区
* @note : 无
* @param : KeyCode : 按键代码
* @retval : 无
*/
static void KEY_FIFO_Put(uint8_t _KeyCode)
{
s_tKey.Buf[s_tKey.Write] = _KeyCode;
if (++s_tKey.Write >= KEY_FIFO_SIZE)
{
s_tKey.Write = 0;
}
}
/**
* @brief : 检测一个按键。非阻塞状态,必须被周期性的调用
* @note : 无
* @param : 按键数
* @retval : 无
*/
static void KEY_Detect(uint8_t i)
{
KEY_T *pBtn;
pBtn = &s_tBtn[i];
if (pBtn->IsKeyDownFunc())
{// 按键按下
if (pBtn->Count < KEY_FILTER_TIME)
{
pBtn->Count = KEY_FILTER_TIME;
}
else if(pBtn->Count < 2 * KEY_FILTER_TIME)
{
pBtn->Count++;
}
else
{
if (pBtn->State == 0)
{
pBtn->State = 1;
/* 发送按钮按下的消息 */
KEY_FIFO_Put((uint8_t)(3 * i + 1));
}
if (pBtn->LongTime > 0)
{
if (pBtn->LongCount < pBtn->LongTime)
{
/* 发送按钮持续按下的消息 */
if (++pBtn->LongCount == pBtn->LongTime)
{
if (pBtn->RepeatSpeed > 0)
{
pBtn->LongCount = 0;
if (++pBtn->RepeatCount >= pBtn->RepeatSpeed)
{
pBtn->RepeatCount = 0;
/* 常按键后,每隔10ms发送1个按键 */
KEY_FIFO_Put((uint8_t)(3 * i + 1));
}
}
else
{
/* 键值放入按键FIFO */
KEY_FIFO_Put((uint8_t)(3 * i + 3));
}
}
}
}
}
}
else
{// 按键抬起
if(pBtn->Count > KEY_FILTER_TIME)
{
pBtn->Count = KEY_FILTER_TIME;
}
else if(pBtn->Count != 0)
{
pBtn->Count--;
}
else
{
if (pBtn->State == 1)
{
pBtn->State = 0;
/* 发送按钮弹起的消息 */
KEY_FIFO_Put((uint8_t)(3 * i + 2));
}
}
pBtn->LongCount = 0;
pBtn->RepeatCount = 0;
}
}
/*****END OF FILE****/