19.1 初学者重要提示
学习本章节前,务必保证已经学习了第15,16和17章。
按键FIFO驱动扩展和移植更简单,组合键也更好用。支持按下、弹起、长按和组合键。
19.2 按键硬件设计
V6开发板有三个独立按键和一个五向摇杆,下面是三个独立按键的原理图:
注意,K1(S1)、K2(S2)和K3(S3)按键的上拉电阻是接在5V电压上,因为这三个按键被复用为PS/2键盘鼠标接口,而PS/2是需要5V供电的(注,V5和V6开发板做了PS/2复用)。实际测试,K1、K2、K3按键和PS/2键盘是可以同时工作的。
下面是五向摇杆的原理图:
通过这个硬件设计,有如下两个知识点为大家做介绍:
19.2.1 硬件设计
按键和CPU之间串联的电阻起保护作用。按键肯定是存在机械抖动的,开发板上面的硬件没有做硬件滤波处理,即使设计了硬件滤波电路,软件上还是需要进行滤波。
保护GPIO,避免软件错误将IO设置为输出,如果设置为低电平还好,如果设置输出的是高电平,按键按下会直接跟GND(低电平)连接,从而损坏MCU。
保护电阻也起到按键隔离作用,这些GPIO可以直接用于其它实验。
19.2.2 GPIO内部结构分析按键
详细的GPIO模式介绍,请参考第15章的15.3小节,本章仅介绍输入模式。下面我们通过一张图来简单介绍GPIO的结构。
红色的线条是GPIO输入通道的信号流向,作为按键检测IO,这些需要配置为浮空输入。按键已经做了5V上拉,因此GPIO内部的上下拉电阻都选择关闭状态。
19.3 按键FIFO的驱动设计
bsp_key按键驱动程序用于扫描独立按键,具有软件滤波机制,采用FIFO机制保存键值。可以检测如下事件:
按键按下。
按键弹起。
长按键。
长按时自动连发。
我们将按键驱动分为两个部分来介绍,一部分是FIFO的实现,一部分是按键检测的实现。
bsp_key.c 文件包含按键检测和按键FIFO的实现代码。
bsp.c 文件会调用bsp_InitKey()初始化函数。
bsp.c 文件会调用bsp_KeyScan按键扫描函数。
bsp_timer.c 中的Systick中断服务程序调用 bsp_RunPer10ms。
中断程序和主程序通过FIFO接口函数进行信息传递。
函数调用关系图:
19.3.1 按键FIFO的原理
FIFO是First Input First Output的缩写,先入先出队列。我们这里以5个字节的FIFO空间进行说明。Write变量表示写位置,Read变量表示读位置。初始状态时,Read = Write = 0。
我们依次按下按键K1,K2,那么FIFO中的数据变为:
如果Write!= Read,则我们认为有新的按键事件。
我们通过函数bsp_GetKey读取一个按键值进行处理后,Read变量变为1。Write变量不变。
我们继续通过函数bsp_GetKey读取3个按键值进行处理后,Read变量变为4。此时Read = Write = 4。两个变量已经相等,表示已经没有新的按键事件需要处理。
有一点要特别的注意,如果FIFO空间写满了,Write会被重新赋值为0,也就是重新从第一个字节空间填数据进去,如果这个地址空间的数据还没有被及时读取出来,那么会被后来的数据覆盖掉,这点要引起大家的注意。我们的驱动程序开辟了10个字节的FIFO缓冲区,对于一般的应用足够了。
设计按键FIFO主要有三个方面的好处:
可靠地记录每一个按键事件,避免遗漏按键事件。特别是需要实现按键的按下、长按、自动连发、弹起等事件时。
读取按键的函数可以设计为非阻塞的,不需要等待按键抖动滤波处理完毕。
按键FIFO程序在嘀嗒定时器中定期的执行检测,不需要在主程序中一直做检测,这样可以有效地降低系统资源消耗。
19.3.2 按键FIFO的实现
在bsp_key.h 中定了结构体类型KEY_FIFO_T。这只是类型声明,并没有分配变量空间。
#define KEY_FIFO_SIZE 10
typedef struct
{
uint8_t Buf[KEY_FIFO_SIZE]; /* 键值缓冲区 */
uint8_t Read; /* 缓冲区读指针1 */
uint8_t Write; /* 缓冲区写指针 */
uint8_t Read2; /* 缓冲区读指针2 */
}KEY_FIFO_T;
在bsp_key.c 中定义s_tKey结构变量, 此时编译器会分配一组变量空间。
static KEY_FIFO_T s_tKey; /* 按键FIFO变量,结构体 */
一般情况下,只需要一个写指针Write和一个读指针Read。在某些情况下,可能有两个任务都需要访问按键缓冲区,为了避免键值被其中一个任务取空,我们添加了第2个读指针Read2。出厂程序在bsp_Idle()函数中实现的按K1K2组合键截屏的功能就使用的第2个读指针。
当检测到按键事件发生后,可以调用 bsp_PutKey函数将键值压入FIFO。下面的代码是函数的实现:
/*
*********************************************************************************************************
* 函 数 名: bsp_PutKey
* 功能说明: 将1个键值压入按键FIFO缓冲区。可用于模拟一个按键。
* 形 参: _KeyCode : 按键代码
* 返 回 值: 无
*********************************************************************************************************
*/
void bsp_PutKey(uint8_t _KeyCode)
{
s_tKey.Buf[s_tKey.Write] = _KeyCode;
if (++s_tKey.Write >= KEY_FIFO_SIZE)
{
s_tKey.Write = 0;
}
}
这个bsp_PutKey函数除了被按键检测函数调用外,还可以被其他底层驱动调用。比如红外遥控器的按键检测,也共用了同一个按键FIFO。遥控器的按键代码和主板实体按键的键值统一编码,保持键值唯一即可实现两套按键同时控制程序的功能。
应用程序读取FIFO中的键值,是通过bsp_GetKey函数和bsp_GetKey2函数实现的。我们来看下这两个函数的实现:
/*
*********************************************************************************************************
* 函 数 名: bsp_GetKey
* 功能说明: 从按键FIFO缓冲区读取一个键值。
* 形 参: 无
* 返 回 值: 按键代码
*********************************************************************************************************
*/
uint8_t bsp_GetKey(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;
}
}
/*
*********************************************************************************************************
* 函 数 名: bsp_GetKey2
* 功能说明: 从按键FIFO缓冲区读取一个键值。独立的读指针。
* 形 参: 无
* 返 回 值: 按键代码
*********************************************************************************************************
*/
uint8_t bsp_GetKey2(void)
{
uint8_t ret;
if (s_tKey.Read2 == s_tKey.Write)
{
return KEY_NONE;
}
else
{
ret = s_tKey.Buf[s_tKey.Read2];
if (++s_tKey.Read2 >= KEY_FIFO_SIZE)
{
s_tKey.Read2 = 0;
}
return ret;
}
}
返回值KEY_NONE = 0, 表示按键缓冲区为空,所有的按键时间已经处理完毕。按键的键值定义在 bsp_key.h文件,下面是具体内容:
typedef enum
{
KEY_NONE = 0, /* 0 表示按键事件 */
KEY_1_DOWN, /* 1键按下 */
KEY_1_UP, /* 1键弹起 */
KEY_1_LONG, /* 1键长按 */
KEY_2_DOWN, /* 2键按下 */
KEY_2_UP, /* 2键弹起 */
KEY_2_LONG, /* 2键长按 */
KEY_3_DOWN, /* 3键按下 */
KEY_3_UP, /* 3键弹起 */
KEY_3_LONG, /* 3键长按 */
KEY_4_DOWN, /* 4键按下 */
KEY_4_UP, /* 4键弹起 */
KEY_4_LONG, /* 4键长按 */
KEY_5_DOWN, /* 5键按下 */
KEY_5_UP, /* 5键弹起 */
KEY_5_LONG, /* 5键长按 */
KEY_6_DOWN, /* 6键按下 */
KEY_6_UP, /* 6键弹起 */
KEY_6_LONG, /* 6键长按 */
KEY_7_DOWN, /* 7键按下 */
KEY_7_UP, /* 7键弹起 */
KEY_7_LONG, /* 7键长按 */
KEY_8_DOWN, /* 8键按下 */
KEY_8_UP, /* 8键弹起 */
KEY_8_LONG, /* 8键长按 */
/* 组合键 */
KEY_9_DOWN, /* 9键按下 */
KEY_9_UP, /* 9键弹起 */
KEY_9_LONG, /* 9键长按 */
KEY_10_DOWN, /* 10键按下 */
KEY_10_UP, /* 10键弹起 */
KEY_10_LONG, /* 10键长按 */
}KEY_ENUM;
必须按次序定义每个键的按下、弹起和长按事件,即每个按键对象(组合键也算1个)占用3个数值。我们推荐使用枚举enum, 不用#define的原因:
便于新增键值,方便调整顺序。
使用{ } 将一组相关的定义封装起来便于理解。
编译器可帮我们避免键值重复。
我们来看红外遥控器的键值定义,在bsp_ir_decode.h文件。因为遥控器按键和主板按键共用同一个FIFO,因此在这里我们先贴出这段定义代码,让大家有个初步印象。
/* 定义红外遥控器按键代码, 和bsp_key.h 的物理按键代码统一编码 */
typedef enum
{
IR_KEY_STRAT = 0x80,
IR_KEY_POWER = IR_KEY_STRAT + 0x45,
IR_KEY_MENU = IR_KEY_STRAT + 0x47,
IR_KEY_TEST = IR_KEY_STRAT + 0x44,
IR_KEY_UP = IR_KEY_STRAT + 0x40,
IR_KEY_RETURN = IR_KEY_STRAT + 0x43,
IR_KEY_LEFT = IR_KEY_STRAT + 0x07,
IR_KEY_OK = IR_KEY_STRAT + 0x15,
IR_KEY_RIGHT = IR_KEY_STRAT + 0x09,
IR_KEY_0 = IR_KEY_STRAT + 0x16,
IR_KEY_DOWN = IR_KEY_STRAT + 0x19,
IR_KEY_C = IR_KEY_STRAT + 0x0D,
IR_KEY_1 = IR_KEY_STRAT + 0x0C,
IR_KEY_2 = IR_KEY_STRAT + 0x18,
IR_KEY_3 = IR_KEY_STRAT + 0x5E,
IR_KEY_4 = IR_KEY_STRAT + 0x08,
IR_KEY_5 = IR_KEY_STRAT + 0x1C,
IR_KEY_6 = IR_KEY_STRAT + 0x5A,
IR_KEY_7 = IR_KEY_STRAT + 0x42,
IR_KEY_8 = IR_KEY_STRAT + 0x52,
IR_KEY_9 = IR_KEY_STRAT + 0x4A,
}IR_KEY_E;
我们下面来看一段简单的应用。这个应用的功能是:主板K1键控制LED1指示灯;遥控器的POWER键和MENU键控制LED2指示灯。
#include "bsp.h"
int main(void)
{
uint8_t ucKeyCode;
bsp_Init();
IRD_StartWork(); /* 启动红外解码 */
while(1)
{
bsp_Idle();
/* 处理按键事件 */
ucKeyCode = bsp_GetKey();
if (ucKeyCode > 0)
{
/* 有键按下 */
switch (ucKeyCode)
{
case KEY_DOWN_K1: /* K1键按下 */
上一篇:第20章 STM32F429的GPIO应用之无源蜂鸣器
下一篇:第18章 STM32F429的GPIO应用之跑马灯
推荐阅读最新更新时间:2024-10-12 17:59