历史上的今天
返回首页

历史上的今天

今天是:2024年08月27日(星期二)

正在发生

2019年08月27日 | 【STM32H7教程】第19章 STM32H7的GPIO应用之按键FIFO

2019-08-27 来源:eefocus

19.1 初学者重要提示


学习本章节前,务必保证已经学习了第15,16和17章。


按键FIFO驱动扩展和移植更简单,组合键也更好用。支持按下、弹起、长按和组合键。


19.2 按键硬件设计

V7开发板有三个独立按键和一个五向摇杆,下面是三个独立按键的原理图:


注意,K1(S1)、K2(S2)和K3(S3)按键的上拉电阻是接在5V电压上,因为这三个按键被复用为PS/2键盘鼠标接口,而PS/2是需要5V供电的(注,V5和V6开发板做了PS/2复用,而V7没有使用,这里只是为了兼容之前的板子)。实际测试,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键按下 */

bsp_LedOn(1); /* 点亮LED1 */

break;

 

case KEY_UP_K1: /* K1键弹起 */

bsp_LedOff(1); /* 熄灭LED1 */

break;

 

case IR_KEY_POWER: /* 遥控器POWER键按下 */

bsp_LedOn(1); /* 点亮LED2 */

break;

 

case IR_KEY_MENU: /* 遥控器MENU键按下 */

bsp_LedOff(1); /* 熄灭LED2 */

break;

 

case MSG_485_RX: /* 通信程序的发来的消息 */

/* 执行通信程序的指令 */

break;

 

default:

break;

}

}

}

}

看到这里,想必你已经意识到bsp_PutKey函数的强大之处了,可以将不相关的硬件输入设备统一为一个相同的接口函数。


在上面的应用程序中,我们特意添加了一段红色的代码来解说更高级的用法。485通信程序收到有效的命令后通过 bsp_PutKey(MSG_485_RX)函数可以通知APP应用程序进行进一步加工处理(比如显示接收成功)。这是一种非常好的任务间信息传递方式,它不会破坏程序结构。不必新增全局变量来做这种事情,你只需要添加一个键值代码。


对于简单的程序,可以借用按键FIFO来进行少量的信息传递。对于复杂的应用,我们推荐使用bsp_msg专门来做这种任务间的通信。因为bsp_msg除了传递消息代码外,还可以传递参数结构。


19.3.3 按键检测程序分析

在bsp_key.h 中定了结构体类型KEY_T。


#define KEY_COUNT    10     /* 按键个数, 8个独立建 + 2个组合键 */

 

typedef struct

{

/* 下面是一个函数指针,指向判断按键手否按下的函数 */

uint8_t (*IsKeyDownFunc)(void); /* 按键按下的判断函数,1表示按下 */

 

uint8_t  Count; /* 滤波器计数器 */

uint16_t LongCount; /* 长按计数器 */

uint16_t LongTime; /* 按键按下持续时间, 0表示不检测长按 */

uint8_t  State; /* 按键当前状态(按下还是弹起) */

uint8_t  RepeatSpeed; /* 连续按键周期 */

uint8_t  RepeatCount; /* 连续按键计数器 */

}KEY_T;

在bsp_key.c 中定义s_tBtn结构体数组变量。


static KEY_T s_tBtn[KEY_COUNT]; 

static KEY_FIFO_T s_tKey; /* 按键FIFO变量,结构体 */

每个按键对象都分配一个结构体变量,这些结构体变量以数组的形式存在将便于我们简化程序代码行数。


使用函数指针IsKeyDownFunc可以将每个按键的检测以及组合键的检测代码进行统一管理。


因为函数指针必须先赋值,才能被作为函数执行。因此在定时扫描按键之前,必须先执行一段初始化函数来设置每个按键的函数指针和参数。这个函数是 void bsp_InitKey(void)。它由bsp_Init()调用。


/*

*********************************************************************************************************

* 函 数 名: bsp_InitKey

* 功能说明: 初始化按键. 该函数被 bsp_Init() 调用。

* 形    参: 无

* 返 回 值: 无

*********************************************************************************************************

*/

void bsp_InitKey(void)

{

bsp_InitKeyVar(); /* 初始化按键变量 */

bsp_InitKeyHard(); /* 初始化按键硬件 */

}

下面是bsp_InitKeyVar函数的定义:


/*

*********************************************************************************************************

* 函 数 名: bsp_InitKeyVar

* 功能说明: 初始化按键变量

* 形    参:  无

* 返 回 值: 无

*********************************************************************************************************

*/

static void bsp_InitKeyVar(void)

{

uint8_t i;

 

/* 对按键FIFO读写指针清零 */

s_tKey.Read = 0;

s_tKey.Write = 0;

s_tKey.Read2 = 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 = 0; /* 按键连发的速度,0表示不支持连发 */

s_tBtn[i].RepeatCount = 0; /* 连发计数器 */

}

 

/* 如果需要单独更改某个按键的参数,可以在此单独重新赋值 */

/* 摇杆上下左右,支持长按1秒后,自动连发 */

bsp_SetKeyParam(KID_JOY_U, 100, 6);

bsp_SetKeyParam(KID_JOY_D, 100, 6);

bsp_SetKeyParam(KID_JOY_L, 100, 6);

bsp_SetKeyParam(KID_JOY_R, 100, 6);

}

注意一下 Count 这个成员变量,没有设置为0。为了避免主板上电的瞬间,检测到一个无效的按键按下或弹起事件。我们将这个滤波计数器的初值设置为正常值的1/2。bsp_key.h中定义了滤波时间和长按时间。


/*

按键滤波时间50ms, 单位10ms。

只有连续检测到50ms状态不变才认为有效,包括弹起和按下两种事件

即使按键电路不做硬件滤波,该滤波机制也可以保证可靠地检测到按键事件

*/

#define KEY_FILTER_TIME   5

#define KEY_LONG_TIME     100 /* 单位10ms, 持续1秒,认为长按事件 */

uint8_t KeyPinActive(uint8_t _id)(会调用函数KeyPinActive判断状态)函数就是最底层的GPIO输入状态判断函数。


/*

*********************************************************************************************************

* 函 数 名: KeyPinActive

* 功能说明: 判断按键是否按下

* 形    参: 无

* 返 回 值: 返回值1 表示按下(导通),0表示未按下(释放)

*********************************************************************************************************

*/

static uint8_t KeyPinActive(uint8_t _id)

{

uint8_t level;

if ((s_gpio_list[_id].gpio->IDR & s_gpio_list[_id].pin) == 0)

{

level = 0;

}

else

{

level = 1;

}

 

if (level == s_gpio_list[_id].ActiveLevel)

{

return 1;

}

else

{

return 0;

}

}

 

/*

*********************************************************************************************************

推荐阅读

史海拾趣

Blue Giga公司的发展小趣事

随着技术的不断进步,Blue Giga在无线网络连接领域取得了重要突破。其研发的超低功耗Bluetooth Smart和Bluetooth Classic模块,以及Wi-Fi模块,受到了市场的广泛欢迎。同时,公司还推出了配套的软件栈、开发工具和SDK,进一步丰富了产品线,满足了不同领域的需求。

HARTING公司的发展小趣事

为了扩大市场份额和提升品牌影响力,Blue Giga积极寻求与各行业领导者的合作。它与微软、谷歌、英特尔等知名企业建立了战略合作伙伴关系,共同推动物联网和无线连接技术的发展。这些合作不仅为Blue Giga带来了更多的商业机会,也提升了其在行业中的地位。

振华(CEC)公司的发展小趣事

为了进一步提升企业的竞争力和市场份额,振华积极实施国际化战略。公司加强与国外企业的合作与交流,积极参与国际市场竞争,通过引进外资、设立海外研发机构等方式,不断拓展海外市场。同时,振华还注重提升产品的国际竞争力,加强与国际标准的对接和认证工作,确保产品能够满足不同国家和地区的市场需求。

Central Semiconductor公司的发展小趣事

在20世纪70年代末,当许多半导体制造商纷纷转向硅器件的生产时,Central Semiconductor却洞察到了后缘半导体市场的空白。公司决定坚守锗器件的生产,并凭借这一决策填补了市场空白。这种对旧技术的坚持不仅让Central在一段时间内保持了竞争优势,也为公司在后续发展中积累了宝贵的经验。

随着技术的进步,Central并没有固步自封,而是逐渐将产品系列扩展到包括表面安装设备等更先进的产品。这种转型不仅满足了市场对小型化产品的需求,也展示了Central对市场变化的敏锐洞察和适应能力。

福声科技(FUET)公司的发展小趣事

在竞争日益激烈的电子行业中,福声科技始终坚持客户需求导向,提供定制化服务。公司深入了解客户的实际需求和应用场景,为客户提供从产品设计、生产到售后服务的全方位解决方案。这种以客户为中心的服务理念不仅增强了客户的满意度和忠诚度,还为公司赢得了更多的业务机会和市场份额。同时,福声科技还不断优化生产流程和服务流程,提高响应速度和交付能力,以满足客户日益增长的需求。

以上五个故事展示了福声科技(FUET)在电子行业中逐渐发展起来的历程,体现了公司在技术创新、质量管理、市场拓展和客户服务等方面的努力和成就。

Frequency Sources公司的发展小趣事

在20世纪90年代初,Frequency Sources公司(或类似名称的公司)凭借其在频率源技术领域的深厚积累,成功研发出了一种新型高精度晶体振荡器。这种振荡器在稳定性、相位噪声和温度特性等方面均达到了当时业界的顶尖水平,为无线通信、卫星导航等领域提供了关键的技术支持。这一技术创新不仅巩固了公司在频率源技术领域的领先地位,还为公司赢得了广泛的市场认可和大量订单。

问答坊 | AI 解惑

FPGA器件的在线配置方法

摘要: 介绍基于SRAM LUT结构的FPGA器件的上电配置方式;着重介绍采用计算机串口下载配置数据的方法和AT89C2051单片机、串行EEPROM组成的串行配置系统的设计方法及实现多任务电路结构中配置的方法,并从系统的复杂度、可靠性和经济性等方面进行比较 ...…

查看全部问答>

丰田自动织机展出新型转换器

丰田自动织机在“人与车科技展2007”(太平洋横滨会展中心)上,展示了“雷克萨斯LS460/600h”采用的电动助力转向系统中的2款DC/DC转换器。 雷克萨斯LS460采用的是可将12V电源电压升至45V的型号,主要用于电动助力转向系统(EPS)。最大输出功率 ...…

查看全部问答>

哥们根据实物反画的视频一拖四分配器原理图PROTEL99SE格式

哥们根据实物反画的视频一拖四分配器原理图PROTEL99SE格式,有兴趣的朋友拿去吧.…

查看全部问答>

想转让两个单片机试验箱---51的和MSP430的

手头有两个单片机学习试验箱,一个是周立功的51试验箱, 另一个是利尔达的MSP430F449试验箱, 这两个试验箱基本完整,都配有书 先来看看利尔达的MSP430试验箱,跟现在利尔达网站的一样,现在价格便宜了不少,当初是1750快大洋买的,有感兴趣的可 ...…

查看全部问答>

车载信息中心电路保护措施

新型客车、卡车、公共汽车甚至摩托车都已成为移动的网络,将众多特征和功能连接在一起。如内置控制、移动媒体和无线网络。信息娱乐系统、远程信息处理、安全控制等的应用均需使用几种现有的网络标准。如LIN、CAN、MOST、IDB-1394、FlexRay、Bytefli ...…

查看全部问答>

蓝精灵第三讲:STM32 GPIO的使用

本期主要讲述一下stm32GPIO的使用,做了一个GPIO的例子(流水灯)。需要的可以在附件中下载。该程序在飞嵌电子的蓝精灵stm32上测试运行通过。…

查看全部问答>

Windows CE显示问题!

E:\\FLT\\毕业设计\\测试程序\\WinCE5\\新建文件夹\\moto_0228.jpg 请问各位高手,显示重影可能的问题在哪?…

查看全部问答>

问下各位大侠,关于那个板载仿真器哈

TI开发板板载仿真器十分方便,我想问的是那个CPLD能否去掉,因为我还得CPLD编程,麻烦的很。请回答谢了…

查看全部问答>

msp430f2619仿真时遇到大麻烦

msp430f2619单片机的USB的仿真器仿真报错 “FATAL ERROR Failed to initialize device session aborted!”…

查看全部问答>