STM32F4 按键FIFO设计

设计按键 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****/




  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值