你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】STM32H7的GPIO应用之按键FIFO

[复制链接]
STMCU小助手 发布时间:2021-12-28 22:58
19.1 初学者重要提示
按键FIFO驱动扩展和移植更简单,组合键也更好用。支持按下、弹起、长按和组合键。

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

aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDE5MDUvMTM3OTEwNy0yMDE5.png


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

下面是五向摇杆的原理图:

aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDE5MDUvMTM3OTEwNy0yMDE5.png


通过这个硬件设计,有如下两个知识点为大家做介绍:

19.2.1 硬件设计

按键和CPU之间串联的电阻起保护作用。按键肯定是存在机械抖动的,开发板上面的硬件没有做硬件滤波处理,即使设计了硬件滤波电路,软件上还是需要进行滤波。

  保护GPIO,避免软件错误将IO设置为输出,如果设置为低电平还好,如果设置输出的是高电平,按键按下会直接跟GND(低电平)连接,从而损坏MCU。
  保护电阻也起到按键隔离作用,这些GPIO可以直接用于其它实验。
19.2.2 GPIO内部结构分析按键
详细的GPIO模式介绍,请参考第15章的15.3小节,本章仅介绍输入模式。下面我们通过一张图来简单介绍GPIO的结构。

aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDE5MDUvMTM3OTEwNy0yMDE5.png


红色的线条是GPIO输入通道的信号流向,作为按键检测IO,这些需要配置为浮空输入。按键已经做了5V上拉,因此GPIO内部的上下拉电阻都选择关闭状态。

19.3 按键FIFO的驱动设计
bsp_key按键驱动程序用于扫描独立按键,具有软件滤波机制,采用FIFO机制保存键值。可以检测如下事件:

  按键按下。
  按键弹起。
  长按键。
  长按时自动连发。
我们将按键驱动分为两个部分来介绍,一部分是FIFO的实现,一部分是按键检测的实现。

aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDE5MDUvMTM3OTEwNy0yMDE5.png


bsp_key.c 文件包含按键检测和按键FIFO的实现代码。

bsp.c 文件会调用bsp_InitKey()初始化函数。

bsp.c 文件会调用bsp_KeyScan按键扫描函数。

bsp_timer.c 中的Systick中断服务程序调用 bsp_RunPer10ms。

中断程序和主程序通过FIFO接口函数进行信息传递。

函数调用关系图:

aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDE5MDUvMTM3OTEwNy0yMDE5.png


19.3.1 按键FIFO的原理
FIFO是First Input First Output的缩写,先入先出队列。我们这里以5个字节的FIFO空间进行说明。Write变量表示写位置,Read变量表示读位置。初始状态时,Read = Write = 0。

aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDE5MDUvMTM3OTEwNy0yMDE5.png


我们依次按下按键K1,K2,那么FIFO中的数据变为:

aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDE5MDUvMTM3OTEwNy0yMDE5.png


如果Write!= Read,则我们认为有新的按键事件。

我们通过函数bsp_GetKey读取一个按键值进行处理后,Read变量变为1。Write变量不变。

aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDE5MDUvMTM3OTEwNy0yMDE5.png


我们继续通过函数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。这只是类型声明,并没有分配变量空间。

  1. #define KEY_FIFO_SIZE        10
  2. typedef struct
  3. {
  4.         uint8_t Buf[KEY_FIFO_SIZE];                /* 键值缓冲区 */
  5.         uint8_t Read;                                        /* 缓冲区读指针1 */
  6.         uint8_t Write;                             /* 缓冲区写指针 */
  7.         uint8_t Read2;                             /* 缓冲区读指针2 */
  8. }KEY_FIFO_T;
复制代码

在bsp_key.c 中定义s_tKey结构变量, 此时编译器会分配一组变量空间。

  1. static KEY_FIFO_T s_tKey;                /* 按键FIFO变量,结构体 */
复制代码

一般情况下,只需要一个写指针Write和一个读指针Read。在某些情况下,可能有两个任务都需要访问按键缓冲区,为了避免键值被其中一个任务取空,我们添加了第2个读指针Read2。出厂程序在bsp_Idle()函数中实现的按K1K2组合键截屏的功能就使用的第2个读指针。

当检测到按键事件发生后,可以调用 bsp_PutKey函数将键值压入FIFO。下面的代码是函数的实现:

  1. /*
  2. *********************************************************************************************************
  3. *        函 数 名: bsp_PutKey
  4. *        功能说明: 将1个键值压入按键FIFO缓冲区。可用于模拟一个按键。
  5. *        形    参: _KeyCode : 按键代码
  6. *        返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. void bsp_PutKey(uint8_t _KeyCode)
  10. {
  11.         s_tKey.Buf[s_tKey.Write] = _KeyCode;

  12.         if (++s_tKey.Write  >= KEY_FIFO_SIZE)
  13.         {
  14.                 s_tKey.Write = 0;
  15.         }
  16. }
复制代码

这个bsp_PutKey函数除了被按键检测函数调用外,还可以被其他底层驱动调用。比如红外遥控器的按键检测,也共用了同一个按键FIFO。遥控器的按键代码和主板实体按键的键值统一编码,保持键值唯一即可实现两套按键同时控制程序的功能。

应用程序读取FIFO中的键值,是通过bsp_GetKey函数和bsp_GetKey2函数实现的。我们来看下这两个函数的实现:

  1. /*
  2. *********************************************************************************************************
  3. *        函 数 名: bsp_GetKey
  4. *        功能说明: 从按键FIFO缓冲区读取一个键值。
  5. *        形    参:  无
  6. *        返 回 值: 按键代码
  7. *********************************************************************************************************
  8. */
  9. uint8_t bsp_GetKey(void)
  10. {
  11.         uint8_t ret;

  12.         if (s_tKey.Read == s_tKey.Write)
  13.         {
  14.                 return KEY_NONE;
  15.         }
  16.         else
  17.         {
  18.                 ret = s_tKey.Buf[s_tKey.Read];

  19.                 if (++s_tKey.Read >= KEY_FIFO_SIZE)
  20.                 {
  21.                         s_tKey.Read = 0;
  22.                 }
  23.                 return ret;
  24.         }
  25. }

  26. /*
  27. *********************************************************************************************************
  28. *        函 数 名: bsp_GetKey2
  29. *        功能说明: 从按键FIFO缓冲区读取一个键值。独立的读指针。
  30. *        形    参:  无
  31. *        返 回 值: 按键代码
  32. *********************************************************************************************************
  33. */
  34. uint8_t bsp_GetKey2(void)
  35. {
  36.         uint8_t ret;

  37.         if (s_tKey.Read2 == s_tKey.Write)
  38.         {
  39.                 return KEY_NONE;
  40.         }
  41.         else
  42.         {
  43.                 ret = s_tKey.Buf[s_tKey.Read2];

  44.                 if (++s_tKey.Read2 >= KEY_FIFO_SIZE)
  45.                 {
  46.                         s_tKey.Read2 = 0;
  47.                 }
  48.                 return ret;
  49.         }
  50. }
复制代码

返回值KEY_NONE = 0, 表示按键缓冲区为空,所有的按键时间已经处理完毕。按键的键值定义在 bsp_key.h文件,下面是具体内容:

  1. typedef enum
  2. {
  3.         KEY_NONE = 0,                        /* 0 表示按键事件 */

  4.         KEY_1_DOWN,                        /* 1键按下 */
  5.         KEY_1_UP,                                /* 1键弹起 */
  6.         KEY_1_LONG,                        /* 1键长按 */

  7.         KEY_2_DOWN,                        /* 2键按下 */
  8.         KEY_2_UP,                                /* 2键弹起 */
  9.         KEY_2_LONG,                        /* 2键长按 */

  10.         KEY_3_DOWN,                        /* 3键按下 */
  11.         KEY_3_UP,                                /* 3键弹起 */
  12.         KEY_3_LONG,                        /* 3键长按 */

  13.         KEY_4_DOWN,                        /* 4键按下 */
  14.         KEY_4_UP,                                /* 4键弹起 */
  15.         KEY_4_LONG,                        /* 4键长按 */

  16.         KEY_5_DOWN,                        /* 5键按下 */
  17.         KEY_5_UP,                                /* 5键弹起 */
  18.         KEY_5_LONG,                        /* 5键长按 */

  19.         KEY_6_DOWN,                        /* 6键按下 */
  20.         KEY_6_UP,                                /* 6键弹起 */
  21.         KEY_6_LONG,                        /* 6键长按 */

  22.         KEY_7_DOWN,                        /* 7键按下 */
  23.         KEY_7_UP,                                /* 7键弹起 */
  24.         KEY_7_LONG,                        /* 7键长按 */

  25.         KEY_8_DOWN,                        /* 8键按下 */
  26.         KEY_8_UP,                                /* 8键弹起 */
  27.         KEY_8_LONG,                        /* 8键长按 */

  28.         /* 组合键 */
  29.         KEY_9_DOWN,                        /* 9键按下 */
  30.         KEY_9_UP,                                /* 9键弹起 */
  31.         KEY_9_LONG,                        /* 9键长按 */

  32.         KEY_10_DOWN,                        /* 10键按下 */
  33.         KEY_10_UP,                        /* 10键弹起 */
  34.         KEY_10_LONG,                        /* 10键长按 */
  35. }KEY_ENUM;
复制代码

必须按次序定义每个键的按下、弹起和长按事件,即每个按键对象(组合键也算1个)占用3个数值。我们推荐使用枚举enum, 不用#define的原因:

  便于新增键值,方便调整顺序。
  使用{ } 将一组相关的定义封装起来便于理解。
  编译器可帮我们避免键值重复。
我们来看红外遥控器的键值定义,在bsp_ir_decode.h文件。因为遥控器按键和主板按键共用同一个FIFO,因此在这里我们先贴出这段定义代码,让大家有个初步印象。

  1. /* 定义红外遥控器按键代码, 和bsp_key.h 的物理按键代码统一编码 */
  2. typedef enum
  3. {
  4.         IR_KEY_STRAT         = 0x80,
  5.         IR_KEY_POWER         = IR_KEY_STRAT + 0x45,
  6.         IR_KEY_MENU         = IR_KEY_STRAT + 0x47,
  7.         IR_KEY_TEST         = IR_KEY_STRAT + 0x44,
  8.         IR_KEY_UP         = IR_KEY_STRAT + 0x40,
  9.         IR_KEY_RETURN        = IR_KEY_STRAT + 0x43,
  10.         IR_KEY_LEFT        = IR_KEY_STRAT + 0x07,
  11.         IR_KEY_OK                = IR_KEY_STRAT + 0x15,
  12.         IR_KEY_RIGHT        = IR_KEY_STRAT + 0x09,
  13.         IR_KEY_0                = IR_KEY_STRAT + 0x16,
  14.         IR_KEY_DOWN        = IR_KEY_STRAT + 0x19,
  15.         IR_KEY_C                = IR_KEY_STRAT + 0x0D,
  16.         IR_KEY_1                = IR_KEY_STRAT + 0x0C,
  17.         IR_KEY_2                = IR_KEY_STRAT + 0x18,
  18.         IR_KEY_3                = IR_KEY_STRAT + 0x5E,
  19.         IR_KEY_4                = IR_KEY_STRAT + 0x08,
  20.         IR_KEY_5                = IR_KEY_STRAT + 0x1C,
  21.         IR_KEY_6                = IR_KEY_STRAT + 0x5A,
  22.         IR_KEY_7                = IR_KEY_STRAT + 0x42,
  23.         IR_KEY_8                = IR_KEY_STRAT + 0x52,
  24.         IR_KEY_9                = IR_KEY_STRAT + 0x4A,        
  25. }IR_KEY_E;
复制代码

我们下面来看一段简单的应用。这个应用的功能是:主板K1键控制LED1指示灯;遥控器的POWER键和MENU键控制LED2指示灯。

  1. #include "bsp.h"

  2. int main(void)
  3. {
  4.         uint8_t ucKeyCode;
  5.                         
  6.         bsp_Init();

  7.         IRD_StartWork();        /* 启动红外解码 */

  8.         while(1)
  9.         {
  10.                 bsp_Idle();
  11.                
  12.                 /* 处理按键事件 */
  13.                 ucKeyCode = bsp_GetKey();
  14.                 if (ucKeyCode > 0)
  15.                 {
  16.                         /* 有键按下 */
  17.                         switch (ucKeyCode)
  18.                         {
  19.                                 case KEY_DOWN_K1:                /* K1键按下 */
  20.                                         bsp_LedOn(1);                /* 点亮LED1 */
  21.                                         break;

  22.                                 case KEY_UP_K1:                /* K1键弹起 */
  23.                                         bsp_LedOff(1);        /* 熄灭LED1 */
  24.                                         break;                                       

  25.                                 case IR_KEY_POWER:                /* 遥控器POWER键按下 */
  26.                                         bsp_LedOn(1);                /* 点亮LED2 */
  27.                                         break;

  28.                                 case IR_KEY_MENU:                /* 遥控器MENU键按下 */
  29.                                         bsp_LedOff(1);        /* 熄灭LED2 */
  30.                                         break;                                       

  31.                                 case MSG_485_RX:                /* 通信程序的发来的消息 */
  32.                                         /* 执行通信程序的指令 */
  33.                                         break;

  34.                                 default:
  35.                                         break;
  36.                         }
  37.                 }
  38.         }
  39. }
复制代码

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

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

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

19.3.3 按键检测程序分析

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

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

  2. typedef struct
  3. {
  4.         /* 下面是一个函数指针,指向判断按键手否按下的函数 */
  5.         uint8_t (*IsKeyDownFunc)(void); /* 按键按下的判断函数,1表示按下 */

  6.         uint8_t  Count;                /* 滤波器计数器 */
  7.         uint16_t LongCount;        /* 长按计数器 */
  8.         uint16_t LongTime;                /* 按键按下持续时间, 0表示不检测长按 */
  9.         uint8_t  State;                /* 按键当前状态(按下还是弹起) */
  10.         uint8_t  RepeatSpeed;        /* 连续按键周期 */
  11.         uint8_t  RepeatCount;        /* 连续按键计数器 */
  12. }KEY_T;
复制代码

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

  1. static KEY_T s_tBtn[KEY_COUNT];
  2. static KEY_FIFO_T s_tKey; /* 按键FIFO变量,结构体 */
复制代码

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

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

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

  1. /*
  2. *********************************************************************************************************
  3. *        函 数 名: bsp_InitKey
  4. *        功能说明: 初始化按键. 该函数被 bsp_Init() 调用。
  5. *        形    参: 无
  6. *        返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. void bsp_InitKey(void)
  10. {
  11.         bsp_InitKeyVar();                /* 初始化按键变量 */
  12.         bsp_InitKeyHard();                /* 初始化按键硬件 */
  13. }
复制代码

下面是bsp_InitKeyVar函数的定义:

  1. /*
  2. *********************************************************************************************************
  3. *        函 数 名: bsp_InitKeyVar
  4. *        功能说明: 初始化按键变量
  5. *        形    参:  无
  6. *        返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. static void bsp_InitKeyVar(void)
  10. {
  11.         uint8_t i;

  12.         /* 对按键FIFO读写指针清零 */
  13.         s_tKey.Read = 0;
  14.         s_tKey.Write = 0;
  15.         s_tKey.Read2 = 0;

  16.         /* 给每个按键结构体成员变量赋一组缺省值 */
  17.         for (i = 0; i < KEY_COUNT; i++)
  18.         {
  19.                 s_tBtn<i>.LongTime = KEY_LONG_TIME;                        /* 长按时间 0 表示不检测长按键事件 */</i>
  20. <span style="font-style: italic;">                s_tBtn.Count = KEY_FILTER_TIME / 2;                /* 计数器设置为滤波时间的一半 */
  21.                 s_tBtn<span style="font-style: italic;">.State = 0;                                                /* 按键缺省状态,0为未按下 */
  22.                 s_tBtn<span style="font-style: italic;">.RepeatSpeed = 0;                                        /* 按键连发的速度,0表示不支持连发 */
  23.                 s_tBtn<i style="font-style: italic;">.RepeatCount = 0;                                        /* 连发计数器 */
  24.      </i><span style="font-style: normal;">   }

  25.         /* 如果需要单独更改某个按键的参数,可以在此单独重新赋值 */
  26.         
  27.         /* 摇杆上下左右,支持长按1秒后,自动连发 */
  28.         bsp_SetKeyParam(KID_JOY_U, 100, 6);
  29.         bsp_SetKeyParam(KID_JOY_D, 100, 6);
  30.         bsp_SetKeyParam(KID_JOY_L, 100, 6);
  31.         bsp_SetKeyParam(KID_JOY_R, 100, 6);
  32. }</span></span></span></span>
复制代码

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

  1. /*
  2.         按键滤波时间50ms, 单位10ms。
  3.         只有连续检测到50ms状态不变才认为有效,包括弹起和按下两种事件
  4.         即使按键电路不做硬件滤波,该滤波机制也可以保证可靠地检测到按键事件
  5. */
  6. #define KEY_FILTER_TIME   5
  7. #define KEY_LONG_TIME     100                        /* 单位10ms, 持续1秒,认为长按事件 */
复制代码

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

  1. /*
  2. *********************************************************************************************************
  3. *        函 数 名: KeyPinActive
  4. *        功能说明: 判断按键是否按下
  5. *        形    参: 无
  6. *        返 回 值: 返回值1 表示按下(导通),0表示未按下(释放)
  7. *********************************************************************************************************
  8. */
  9. static uint8_t KeyPinActive(uint8_t _id)
  10. {
  11.         uint8_t level;
  12.         
  13.         if ((s_gpio_list[_id].gpio->IDR & s_gpio_list[_id].pin) == 0)
  14.         {
  15.                 level = 0;
  16.         }
  17.         else
  18.         {
  19.                 level = 1;
  20.         }

  21.         if (level == s_gpio_list[_id].ActiveLevel)
  22.         {
  23.                 return 1;
  24.         }
  25.         else
  26.         {
  27.                 return 0;
  28.         }
  29. }

  30. /*
  31. *********************************************************************************************************
  32. *        函 数 名: IsKeyDownFunc
  33. *        功能说明: 判断按键是否按下。单键和组合键区分。单键事件不允许有其他键按下。
  34. *        形    参: 无
  35. *        返 回 值: 返回值1 表示按下(导通),0表示未按下(释放)
  36. *********************************************************************************************************
  37. */
  38. static uint8_t IsKeyDownFunc(uint8_t _id)
  39. {
  40.         /* 实体单键 */
  41.         if (_id < HARD_KEY_NUM)
  42.         {
  43.                 uint8_t i;
  44.                 uint8_t count = 0;
  45.                 uint8_t save = 255;
  46.                
  47.                 /* 判断有几个键按下 */
  48.                 for (i = 0; i < HARD_KEY_NUM; i++)
  49.                 {
  50.                         if (KeyPinActive(i))
  51.                         {
  52.                                 count++;
  53.                                 save = i;
  54.                         }
  55.                 }
  56.                
  57.                 if (count == 1 && save == _id)
  58.                 {
  59.                         return 1;        /* 只有1个键按下时才有效 */
  60.                 }               

  61.                 return 0;
  62.         }
  63.         
  64.         /* 组合键 K1K2 */
  65.         if (_id == HARD_KEY_NUM + 0)
  66.         {
  67.                 if (KeyPinActive(KID_K1) && KeyPinActive(KID_K2))
  68.                 {
  69.                         return 1;
  70.                 }
  71.                 else
  72.                 {
  73.                         return 0;
  74.                 }
  75.         }

  76.         /* 组合键 K2K3 */
  77.         if (_id == HARD_KEY_NUM + 1)
  78.         {
  79.                 if (KeyPinActive(KID_K2) && KeyPinActive(KID_K3))
  80.                 {
  81.                         return 1;
  82.                 }
  83.                 else
  84.                 {
  85.                         return 0;
  86.                 }
  87.         }

  88.         return 0;
  89. }
复制代码

在使用GPIO之前,我们必须对GPIO进行配置,比如打开GPIO时钟,设置GPIO输入输出方向,设置上下拉电阻。下面是配置GPIO的代码,也就是bsp_InitKeyHard()函数:

  1. /*
  2. *********************************************************************************************************
  3. *        函 数 名: bsp_InitKeyHard
  4. *        功能说明: 配置按键对应的GPIO
  5. *        形    参:  无
  6. *        返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. static void bsp_InitKeyHard(void)
  10. {        
  11.         GPIO_InitTypeDef gpio_init;
  12.         uint8_t i;

  13.         /* 第1步:打开GPIO时钟 */
  14.         ALL_KEY_GPIO_CLK_ENABLE();
  15.         
  16.         /* 第2步:配置所有的按键GPIO为浮动输入模式(实际上CPU复位后就是输入状态) */
  17.         gpio_init.Mode = GPIO_MODE_INPUT;                           /* 设置输入 */
  18.         gpio_init.Pull = GPIO_NOPULL;                 /* 上下拉电阻不使能 */
  19.         gpio_init.Speed = GPIO_SPEED_FREQ_VERY_HIGH;  /* GPIO速度等级 */
  20.         
  21.         for (i = 0; i < HARD_KEY_NUM; i++)
  22.         {
  23.                 gpio_init.Pin = s_gpio_list<span style="font-style: italic;"><span style="font-style: normal;">.pin;
  24.                 HAL_GPIO_Init(s_gpio_list</span><span style="font-style: normal;">.gpio, &gpio_init);        
  25.         }
  26. }</span></span>
复制代码

我们再来看看按键是如何执行扫描检测的。

按键扫描函数bsp_KeyScan10ms ()每隔10ms被执行一次。bsp_RunPer10ms函数在systick中断服务程序中执行。

  1. void bsp_RunPer10ms(void)
  2. {
  3.         bsp_KeyScan10ms();                /* 扫描按键 */
  4. }
复制代码

bsp_KeyScan10ms ()函数的实现如下:

  1. /*
  2. *********************************************************************************************************
  3. *        函 数 名: bsp_KeyScan10ms
  4. *        功能说明: 扫描所有按键。非阻塞,被systick中断周期性的调用,10ms一次
  5. *        形    参: 无
  6. *        返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. void bsp_KeyScan10ms(void)
  10. {
  11.         uint8_t i;

  12.         for (i = 0; i < KEY_COUNT; i++)
  13.         {
  14.                 bsp_DetectKey(i);
  15.         }
  16. }
复制代码

每隔10ms所有的按键GPIO均会被扫描检测一次。bsp_DetectKey函数实现如下:

  1. /*
  2. *********************************************************************************************************
  3. *        函 数 名: bsp_DetectKey
  4. *        功能说明: 检测一个按键。非阻塞状态,必须被周期性的调用。
  5. *        形    参: IO的id, 从0开始编码
  6. *        返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. static void bsp_DetectKey(uint8_t i)
  10. {
  11.         KEY_T *pBtn;

  12.         pBtn = &s_tBtn<span style="font-style: italic;"><span style="font-style: normal;">;
  13.         if (IsKeyDownFunc(i))
  14.         {
  15.                 if (pBtn->Count < KEY_FILTER_TIME)
  16.                 {
  17.                         pBtn->Count = KEY_FILTER_TIME;
  18.                 }
  19.                 else if(pBtn->Count < 2 * KEY_FILTER_TIME)
  20.                 {
  21.                         pBtn->Count++;
  22.                 }
  23.                 else
  24.                 {
  25.                         if (pBtn->State == 0)
  26.                         {
  27.                                 pBtn->State = 1;

  28.                                 /* 发送按钮按下的消息 */
  29.                                 bsp_PutKey((uint8_t)(3 * i + 1));
  30.                         }

  31.                         if (pBtn->LongTime > 0)
  32.                         {
  33.                                 if (pBtn->LongCount < pBtn->LongTime)
  34.                                 {
  35.                                         /* 发送按钮持续按下的消息 */
  36.                                         if (++pBtn->LongCount == pBtn->LongTime)
  37.                                         {
  38.                                                 /* 键值放入按键FIFO */
  39.                                                 bsp_PutKey((uint8_t)(3 * i + 3));
  40.                                         }
  41.                                 }
  42.                                 else
  43.                                 {
  44.                                         if (pBtn->RepeatSpeed > 0)
  45.                                         {
  46.                                                 if (++pBtn->RepeatCount >= pBtn->RepeatSpeed)
  47.                                                 {
  48.                                                         pBtn->RepeatCount = 0;
  49.                                                         /* 常按键后,每隔RepeatSpeed * 10ms发送1个按键 */
  50.                                                         bsp_PutKey((uint8_t)(3 * i + 1));
  51.                                                 }
  52.                                         }
  53.                                 }
  54.                         }
  55.                 }
  56.         }
  57.         else
  58.         {
  59.                 if(pBtn->Count > KEY_FILTER_TIME)
  60.                 {
  61.                         pBtn->Count = KEY_FILTER_TIME;
  62.                 }
  63.                 else if(pBtn->Count != 0)
  64.                 {
  65.                         pBtn->Count--;
  66.                 }
  67.                 else
  68.                 {
  69.                         if (pBtn->State == 1)
  70.                         {
  71.                                 pBtn->State = 0;

  72.                                 /* 发送按钮弹起的消息 */
  73.                                 bsp_PutKey((uint8_t)(3 * i + 2));
  74.                         }
  75.                 }

  76.                 pBtn->LongCount = 0;
  77.                 pBtn->RepeatCount = 0;
  78.         }
  79. }</span></span>
复制代码

对于初学者,这个函数看起来比较吃力,我们拆分进行分析。

  1. pBtn = &s_tBtn<span style="font-style: italic;"><span style="font-style: normal;">;</span></span>
复制代码

读取相应按键的结构体地址,程序里面每个按键都有自己的结构体。

  1. static KEY_T s_tBtn[KEY_COUNT];
  2. if (IsKeyDownFunc(i))
  3. {
  4.         这个里面执行的是按键按下的处理
  5. }
  6. else
  7. {
  8.         这个里面执行的是按键松手的处理或者按键没有按下的处理
  9. }
复制代码

执行函数IsKeyDownFunc(i)做按键状态判断。

  1. /*
  2. **********************************************************************************
  3. 下面这个if语句主要是用于按键滤波前给Count设置一个初值,前面说按键初始化的时候
  4. 已经设置了Count = KEY_FILTER_TIME/2
  5. **********************************************************************************
  6. */
  7. if (pBtn->Count < KEY_FILTER_TIME)
  8. {
  9.     pBtn->Count = KEY_FILTER_TIME;
  10. }

  11. /*
  12. **********************************************************************************
  13. 这里实现KEY_FILTER_TIME时间长度的延迟
  14. **********************************************************************************
  15. */
  16. else if(pBtn->Count < 2 * KEY_FILTER_TIME)
  17. {
  18.     pBtn->Count++;
  19. }
  20. /*
  21. **********************************************************************************
  22. 这里实现KEY_FILTER_TIME时间长度的延迟
  23. **********************************************************************************
  24. */
  25. else
  26. {
  27. /*
  28. **********************************************************************************
  29. 这个State变量是有其实际意义的,如果按键按下了,这里就将其设置为1,如果没有按下这个
  30. 变量的值就会一直是0,这样设置的目的可以有效的防止一种情况的出现:比如按键K1在某个
  31. 时刻检测到了按键有按下,那么它就会做进一步的滤波处理,但是在滤波的过程中,这个按键
  32. 按下的状态消失了,这个时候就会进入到上面第二步else语句里面,然后再做按键松手检测滤波
  33. ,滤波结束后判断这个State变量,如果前面就没有检测到按下,这里就不会记录按键弹起。
  34. **********************************************************************************
  35. */
  36.     if (pBtn->State == 0)
  37.     {
  38.         pBtn->State = 1;

  39.         /* 发送按钮按下的消息 */
  40.         bsp_PutKey((uint8_t)(3 * i + 1));
  41.     }

  42.     if (pBtn->LongTime > 0)
  43.     {
  44.         if (pBtn->LongCount < pBtn->LongTime)
  45.         {
  46.             /* 发送按钮持续按下的消息 */
  47.             if (++pBtn->LongCount == pBtn->LongTime)
  48.             {
  49.                 /* 键值放入按键FIFO */
  50.                 bsp_PutKey((uint8_t)(3 * i + 3));
  51.             }
  52.         }
  53.         else
  54.         {
  55.             if (pBtn->RepeatSpeed > 0)
  56.             {
  57.                 if (++pBtn->RepeatCount >= pBtn->RepeatSpeed)
  58.                 {
  59.                     pBtn->RepeatCount = 0;
  60.                     /* 长按键后,每隔10ms发送1个按键 */
  61.                     bsp_PutKey((uint8_t)(3 * i + 1));
  62.                 }
  63.             }
  64.         }
  65.     }
  66. }
复制代码

19.3.4 按键检测采用中断方式还是查询方式
检测按键有中断方式和GPIO查询方式两种。我们推荐大家用GPIO查询方式。

从裸机的角度分析

中断方式:中断方式可以快速地检测到按键按下,并执行相应的按键程序,但实际情况是由于按键的机械抖动特性,在程序进入中断后必须进行滤波处理才能判定是否有效的按键事件。如果每个按键都是独立的接一个IO引脚,需要我们给每个IO都设置一个中断,程序中过多的中断会影响系统的稳定性。中断方式跨平台移植困难。

查询方式:查询方式有一个最大的缺点就是需要程序定期的去执行查询,耗费一定的系统资源。实际上耗费不了多大的系统资源,因为这种查询方式也只是查询按键是否按下,按键事件的执行还是在主程序里面实现。

从OS的角度分析

中断方式:在OS中要尽可能少用中断方式,因为在RTOS中过多的使用中断会影响系统的稳定性和可预见性(抢占式调度的OS基本没有可预见性)。只有比较重要的事件处理需要用中断的方式。

查询方式:对于用户按键推荐使用这种查询方式来实现,现在的OS基本都带有CPU利用率的功能,这个按键FIFO占用的还是很小的,基本都在1%以下。

19.4 按键板级支持包(bsp_key.c)

按键驱动文件bsp_key.c主要实现了如下几个API:

  KeyPinActive
  IsKeyDownFunc
  bsp_InitKey
  bsp_InitKeyHard
  bsp_InitKeyVar
  bsp_PutKey
  bsp_GetKey
  bsp_GetKey2
  bsp_GetKeyState
  bsp_SetKeyParam
  bsp_ClearKey
  bsp_DetectKey
  bsp_DetectFastIO
  bsp_KeyScan10ms
  bsp_KeyScan1ms

所有这些函数在本章的19.3小节都进行了详细讲解,本小节主要是把需要用户调用的三个函数做个说明。

19.4.1 函数bsp_InitKeyHard
函数原型:

  1. /*
  2. *********************************************************************************************************
  3. *        函 数 名: bsp_InitKey
  4. *        功能说明: 初始化按键. 该函数被 bsp_Init() 调用。
  5. *        形    参: 无
  6. *        返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. void bsp_InitKey(void)
  10. {
  11.         bsp_InitKeyVar();                /* 初始化按键变量 */
  12.         bsp_InitKeyHard();                /* 初始化按键硬件 */
  13. }
复制代码

函数描述:

此函数主要用于按键的初始化。

使用举例:

底层驱动初始化直接在bsp.c文件的函数bsp_Init里面调用即可。

19.4.2 函数bsp_GetKey
函数原型:

  1. /*
  2. *********************************************************************************************************
  3. *        函 数 名: bsp_GetKey
  4. *        功能说明: 从按键FIFO缓冲区读取一个键值。
  5. *        形    参: 无
  6. *        返 回 值: 按键代码
  7. *********************************************************************************************************
  8. */
  9. uint8_t bsp_GetKey(void)
  10. {
  11.         uint8_t ret;

  12.         if (s_tKey.Read == s_tKey.Write)
  13.         {
  14.                 return KEY_NONE;
  15.         }
  16.         else
  17.         {
  18.                 ret = s_tKey.Buf[s_tKey.Read];

  19.                 if (++s_tKey.Read >= KEY_FIFO_SIZE)
  20.                 {
  21.                         s_tKey.Read = 0;
  22.                 }
  23.                 return ret;
  24.         }
  25. }
复制代码

函数描述:

此函数用于从FIFO中读取键值。

使用举例:

调用此函数前,务必优先调用函数bsp_InitKey进行初始化。

  1. /*
  2. *********************************************************************************************************
  3. *        函 数 名: main
  4. *        功能说明: c程序入口
  5. *        形    参: 无
  6. *        返 回 值: 错误代码(无需处理)
  7. *********************************************************************************************************
  8. */
  9. int main(void)
  10. {
  11.         uint8_t ucKeyCode;                /* 按键代码 */
  12.         
  13.         bsp_Init();                /* 硬件初始化 */
  14.         
  15.         /* 进入主程序循环体 */
  16.         while (1)
  17.         {               
  18.                 /* 按键滤波和检测由后台systick中断服务程序实现,我们只需要调用bsp_GetKey读取键值即可。 */
  19.                 ucKeyCode = bsp_GetKey();        /* 读取键值, 无键按下时返回 KEY_NONE = 0 */
  20.                 if (ucKeyCode != KEY_NONE)
  21.                 {
  22.                         switch (ucKeyCode)
  23.                         {
  24.                                 case KEY_DOWN_K1:                        /* K1键按下 */
  25.                                         printf("K1键按下\r\n");
  26.                                         break;

  27.                                 case KEY_DOWN_K2:                        /* K2键按下 */
  28.                                         printf("K2键按下\r\n");
  29.                                         break;

  30.                                 default:
  31.                                         /* 其它的键值不处理 */
  32.                                         break;
  33.                         }
  34.                 }
  35.         }
  36. }
复制代码
19.4.3 函数bsp_KeyScan10ms
函数原型:

  1. /*
  2. *********************************************************************************************************
  3. *        函 数 名: bsp_KeyScan10ms
  4. *        功能说明: 扫描所有按键。非阻塞,被systick中断周期性的调用,10ms一次
  5. *        形    参: 无
  6. *        返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. void bsp_KeyScan10ms(void)
  10. {
  11.         uint8_t i;

  12.         for (i = 0; i < KEY_COUNT; i++)
  13.         {
  14.                 bsp_DetectKey(i);
  15.         }
  16. }
复制代码

函数描述:

此函数是按键的主处理函数,用于检测和存储按下、松手、长按等状态。

使用举例:

调用此函数前,务必优先调用函数bsp_InitKey进行初始化。

另外,此函数需要周期性调用,每10ms调用一次。

  如果是裸机使用,将此函数放在bsp.c文件的bsp_RunPer10ms函数里面即可,这个函数是由滴答定时器调用的,也就是说,大家要使用按键,定时器的初始化函数bsp_InitTimer一定要调用。
  如果是RTOS使用,需要开启一个10ms为周期的任务调用函数bsp_KeyScan10ms。


19.5 按键FIFO驱动移植和使用
按键移植步骤如下:

  第1步:复制bsp_key.c和bsp_key.c到自己的工程。
  第2步:根据自己使用的独立按键个数和组合键个数,修改几个地方。
  1. #define HARD_KEY_NUM        8                     /* 实体按键个数 */
  2. #define KEY_COUNT   (HARD_KEY_NUM + 2)   /* 8个独立建 + 2个组合按键 */
复制代码

  第3步:根据使用的引脚时钟,修改下面函数:
  1. /* 使能GPIO时钟 */
  2. #define ALL_KEY_GPIO_CLK_ENABLE() {        \
  3.                 __HAL_RCC_GPIOB_CLK_ENABLE();        \
  4.                 __HAL_RCC_GPIOC_CLK_ENABLE();        \
  5.                 __HAL_RCC_GPIOG_CLK_ENABLE();        \
  6.                 __HAL_RCC_GPIOH_CLK_ENABLE();        \
  7.                 __HAL_RCC_GPIOI_CLK_ENABLE();        \
  8.         };
复制代码

  第4步:根据使用的具体引脚,修改如下函数,第3列参数低电平表示按下或者高电平表示按下:
  1. /* GPIO和PIN定义 */
  2. static const X_GPIO_T s_gpio_list[HARD_KEY_NUM] = {
  3.         {GPIOI, GPIO_PIN_8, 0},                /* K1 */
  4.         {GPIOC, GPIO_PIN_13, 0},        /* K2 */
  5.         {GPIOH, GPIO_PIN_4, 0},                /* K3 */
  6.         {GPIOG, GPIO_PIN_2, 0},                /* JOY_U */        
  7.         {GPIOB, GPIO_PIN_0, 0},                /* JOY_D */
  8.         {GPIOG, GPIO_PIN_3, 0},                /* JOY_L */        
  9.         {GPIOG, GPIO_PIN_7, 0},                /* JOY_R */        
  10.         {GPIOI, GPIO_PIN_11, 0},        /* JOY_OK */
  11. };   
复制代码
     
  第5步:根据使用的组合键个数,在函数IsKeyDownFunc里面添加相应个数的函数:
  1.         /* 组合键 K1K2 */
  2.         if (_id == HARD_KEY_NUM + 0)
  3.         {
  4.                 if (KeyPinActive(KID_K1) && KeyPinActive(KID_K2))
  5.                 {
  6.                         return 1;
  7.                 }
  8.                 else
  9.                 {
  10.                         return 0;
  11.                 }
  12.         }
复制代码

第2行ID表示HARD_KEY_NUM + 0的组合键,HARD_KEY_NUM + 1表示下一个组合键,以此类推。

另外就是,函数KeyPinActive的参数是表示检测哪两个按键,设置0的时候表示第4步里面的第1组按键,设置为1表示第2组按键,以此类推。

  第6步:主要用到HAL库的GPIO驱动文件,简单省事些可以添加所有HAL库.C源文件进来。
  第7步:移植完整,应用方法看本章节配套例子即可。
特别注意,别忘了每10ms调用一次按键检测函数bsp_KeyScan10ms。

19.6 实验例程设计框架

通过程序设计框架,让大家先对配套例程有一个全面的认识,然后再理解细节,本次实验例程的设计框架如下:

aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDE5MDUvMTM3OTEwNy0yMDE5.png


1、 第1阶段,上电启动阶段:

这部分在第14章进行了详细说明。

2、 第2阶段,进入main函数:

  第1部分,硬件初始化,主要是MPU、Cache、HAL库、系统时钟、滴答定时器、按键等。
  第2部分,应用程序设计部分,实现了一个按键应用。
  第3部分,按键扫描程序每10ms在滴答定时中断执行一次。

19.7 实验例程说明(MDK)
配套例子:
V7-002_按键检测(软件滤波,FIFO机制)

实验目的:
学习按键的按下,弹起,长按和组合键的实现。

实验内容:
启动一个自动重装软件定时器,每100ms翻转一次LED2。

实验操作:
3个独立按键和5向摇杆按下时均有串口消息打印。
5向摇杆的左键和右键长按时,会有连发的串口消息。
独立按键K1和K2按键按下,串口打印消息。

上电后串口打印的信息:

波特率 115200,数据位 8,奇偶校验位无,停止位 1

aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDE5MDUvMTM3OTEwNy0yMDE5.png


程序设计:

  系统栈大小分配:

aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDE5MDUvMTM3OTEwNy0yMDE5.png


  RAM空间用的DTCM:

aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDE5MDUvMTM3OTEwNy0yMDE5.png


  硬件外设初始化

硬件外设的初始化是在 bsp.c 文件实现:

  1. /*
  2. *********************************************************************************************************
  3. *        函 数 名: bsp_Init
  4. *        功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
  5. *        形    参:无
  6. *        返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. void bsp_Init(void)
  10. {
  11.     /* 配置MPU */
  12.         MPU_Config();
  13.         
  14.         /* 使能L1 Cache */
  15.         CPU_CACHE_Enable();

  16.         /*
  17.        STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
  18.            - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
  19.            - 设置NVIV优先级分组为4。
  20.          */
  21.         HAL_Init();

  22.         /*
  23.        配置系统时钟到400MHz
  24.        - 切换使用HSE。
  25.        - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
  26.     */
  27.         SystemClock_Config();

  28.         /*
  29.            Event Recorder:
  30.            - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
  31.            - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章
  32.         */        
  33. #if Enable_EventRecorder == 1  
  34.         /* 初始化EventRecorder并开启 */
  35.         EventRecorderInitialize(EventRecordAll, 1U);
  36.         EventRecorderStart();
  37. #endif
  38.         
  39.         bsp_InitKey();            /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
  40.         bsp_InitTimer();          /* 初始化滴答定时器 */
  41.         bsp_InitUart();        /* 初始化串口 */
  42.         bsp_InitExtIO();        /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */        
  43.         bsp_InitLed();            /* 初始化LED */        
  44. }
复制代码

  MPU配置和Cache配置:

数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区。

  1. /*
  2. *********************************************************************************************************
  3. *        函 数 名: MPU_Config
  4. *        功能说明: 配置MPU
  5. *        形    参: 无
  6. *        返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. static void MPU_Config( void )
  10. {
  11.         MPU_Region_InitTypeDef MPU_InitStruct;

  12.         /* 禁止 MPU */
  13.         HAL_MPU_Disable();

  14.         /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
  15.         MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
  16.         MPU_InitStruct.BaseAddress      = 0x24000000;
  17.         MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
  18.         MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  19.         MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
  20.         MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
  21.         MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
  22.         MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
  23.         MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
  24.         MPU_InitStruct.SubRegionDisable = 0x00;
  25.         MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

  26.         HAL_MPU_ConfigRegion(&MPU_InitStruct);
  27.         
  28.         
  29.         /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
  30.         MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
  31.         MPU_InitStruct.BaseAddress      = 0x60000000;
  32.         MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;        
  33.         MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  34.         MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
  35.         MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;        
  36.         MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
  37.         MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
  38.         MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
  39.         MPU_InitStruct.SubRegionDisable = 0x00;
  40.         MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
  41.         
  42.         HAL_MPU_ConfigRegion(&MPU_InitStruct);

  43.         /*使能 MPU */
  44.         HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
  45. }

  46. /*
  47. *********************************************************************************************************
  48. *        函 数 名: CPU_CACHE_Enable
  49. *        功能说明: 使能L1 Cache
  50. *        形    参: 无
  51. *        返 回 值: 无
  52. *********************************************************************************************************
  53. */
  54. static void CPU_CACHE_Enable(void)
  55. {
  56.         /* 使能 I-Cache */
  57.         SCB_EnableICache();

  58.         /* 使能 D-Cache */
  59.         SCB_EnableDCache();
  60. }
复制代码

  每10ms调用一次按键检测:

按键检测是在滴答定时器中断里面实现,每10ms执行一次检测。

  1. /*
  2. *********************************************************************************************************
  3. *        函 数 名: bsp_RunPer10ms
  4. *        功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求
  5. *              不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。
  6. *        形    参: 无
  7. *        返 回 值: 无
  8. *********************************************************************************************************
  9. */
  10. void bsp_RunPer10ms(void)
  11. {
  12.         bsp_KeyScan10ms();
  13. }
复制代码

  主功能:

主功能的实现主要分为两部分:

启动一个自动重装软件定时器,每100ms翻转一次LED2
按键消息的读取,检测到按下后,做串口打印。
  1. /*
  2. *********************************************************************************************************
  3. *        函 数 名: main
  4. *        功能说明: c程序入口
  5. *        形    参: 无
  6. *        返 回 值: 错误代码(无需处理)
  7. *********************************************************************************************************
  8. */
  9. int main(void)
  10. {
  11.         uint8_t ucKeyCode;                /* 按键代码 */
  12.         
  13.         bsp_Init();                /* 硬件初始化 */
  14.         
  15.         PrintfLogo();        /* 打印例程名称和版本等信息 */
  16.         PrintfHelp();        /* 打印操作提示 */


  17.         bsp_StartAutoTimer(0, 100); /* 启动1个100ms的自动重装的定时器 */
  18.         
  19.         /* 进入主程序循环体 */
  20.         while (1)
  21.         {
  22.                 bsp_Idle();                /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */

  23.                 /* 判断定时器超时时间 */
  24.                 if (bsp_CheckTimer(0))        
  25.                 {
  26.                         /* 每隔100ms 进来一次 */  
  27.                         bsp_LedToggle(2);                        
  28.                 }
  29.                
  30.                 /* 按键滤波和检测由后台systick中断服务程序实现,我们只需要调用bsp_GetKey读取键值即可。 */
  31.                 ucKeyCode = bsp_GetKey();        /* 读取键值, 无键按下时返回 KEY_NONE = 0 */
  32.                 if (ucKeyCode != KEY_NONE)
  33.                 {
  34.                         switch (ucKeyCode)
  35.                         {
  36.                                 case KEY_DOWN_K1:                        /* K1键按下 */
  37.                                         printf("K1键按下\r\n");
  38.                                         break;

  39.                                 case KEY_UP_K1:                                /* K1键弹起 */
  40.                                         printf("K1键弹起\r\n");
  41.                                         break;

  42.                                 case KEY_DOWN_K2:                        /* K2键按下 */
  43.                                         printf("K2键按下\r\n");
  44.                                         break;

  45.                                 case KEY_UP_K2:                                /* K2键弹起 */
  46.                                         printf("K2键弹起\r\n");
  47.                                         break;

  48.                                 case KEY_DOWN_K3:                        /* K3键按下 */
  49.                                         printf("K3键按下\r\n");
  50.                                         break;

  51.                                 case KEY_UP_K3:                                /* K3键弹起 */
  52.                                         printf("K3键弹起\r\n");
  53.                                         break;

  54.                                 case JOY_DOWN_U:                        /* 摇杆UP键按下 */
  55.                                         printf("摇杆上键按下\r\n");
  56.                                         break;

  57.                                 case JOY_DOWN_D:                        /* 摇杆DOWN键按下 */
  58.                                         printf("摇杆下键按下\r\n");
  59.                                         break;

  60.                                 case JOY_DOWN_L:                        /* 摇杆LEFT键按下 */
  61.                                         printf("摇杆左键按下\r\n");
  62.                                         break;
  63.                                 
  64.                                 case JOY_LONG_L:            /* 摇杆LEFT键长按 */
  65.                                         printf("摇杆左键长按\r\n");
  66.                                         break;

  67.                                 case JOY_DOWN_R:                        /* 摇杆RIGHT键按下 */
  68.                                         printf("摇杆右键按下\r\n");
  69.                                         break;
  70.                                 
  71.                                 case JOY_LONG_R:            /* 摇杆RIGHT键长按 */
  72.                                         printf("摇杆右键长按\r\n");
  73.                                         break;

  74.                                 case JOY_DOWN_OK:                        /* 摇杆OK键按下 */
  75.                                         printf("摇杆OK键按下\r\n");
  76.                                         break;

  77.                                 case JOY_UP_OK:                        /* 摇杆OK键弹起 */
  78.                                         printf("摇杆OK键弹起\r\n");
  79.                                         break;

  80.                 case SYS_DOWN_K1K2:                        /* 摇杆OK键弹起 */
  81.                                         printf("K1和K2组合键按下\r\n");
  82.                                         break;

  83.                                 default:
  84.                                         /* 其它的键值不处理 */
  85.                                         break;
  86.                         }
  87.                
  88.                 }
  89.         }
  90. }
复制代码

19.8 实验例程说明(IAR)
配套例子:
V7-002_按键检测(软件滤波,FIFO机制)

实验目的:
学习按键的按下,弹起,长按和组合键的实现。

实验内容:
启动一个自动重装软件定时器,每100ms翻转一次LED2。

实验操作:
3个独立按键和5向摇杆按下时均有串口消息打印。
5向摇杆的左键和右键长按时,会有连发的串口消息。
独立按键K1和K2按键按下,串口打印消息。

上电后串口打印的信息:

波特率 115200,数据位 8,奇偶校验位无,停止位 1



程序设计:

  系统栈大小分配:

aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDE5MDUvMTM3OTEwNy0yMDE5.png


  RAM空间用的DTCM:

aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDE5MDUvMTM3OTEwNy0yMDE5.png


  硬件外设初始化

硬件外设的初始化是在 bsp.c 文件实现:

  1. /*
  2. *********************************************************************************************************
  3. *        函 数 名: bsp_Init
  4. *        功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
  5. *        形    参:无
  6. *        返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. void bsp_Init(void)
  10. {
  11.     /* 配置MPU */
  12.         MPU_Config();
  13.         
  14.         /* 使能L1 Cache */
  15.         CPU_CACHE_Enable();

  16.         /*
  17.        STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
  18.            - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
  19.            - 设置NVIV优先级分组为4。
  20.          */
  21.         HAL_Init();

  22.         /*
  23.        配置系统时钟到400MHz
  24.        - 切换使用HSE。
  25.        - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
  26.     */
  27.         SystemClock_Config();

  28.         /*
  29.            Event Recorder:
  30.            - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
  31.            - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章
  32.         */        
  33. #if Enable_EventRecorder == 1  
  34.         /* 初始化EventRecorder并开启 */
  35.         EventRecorderInitialize(EventRecordAll, 1U);
  36.         EventRecorderStart();
  37. #endif
  38.         
  39.         bsp_InitKey();            /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
  40.         bsp_InitTimer();          /* 初始化滴答定时器 */
  41.         bsp_InitUart();        /* 初始化串口 */
  42.         bsp_InitExtIO();        /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */        
  43.         bsp_InitLed();            /* 初始化LED */        
  44. }
复制代码

  MPU配置和Cache配置:

数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区。

  1. /*
  2. *********************************************************************************************************
  3. *        函 数 名: MPU_Config
  4. *        功能说明: 配置MPU
  5. *        形    参: 无
  6. *        返 回 值: 无
  7. *********************************************************************************************************
  8. */
  9. static void MPU_Config( void )
  10. {
  11.         MPU_Region_InitTypeDef MPU_InitStruct;

  12.         /* 禁止 MPU */
  13.         HAL_MPU_Disable();

  14.         /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
  15.         MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
  16.         MPU_InitStruct.BaseAddress      = 0x24000000;
  17.         MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
  18.         MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  19.         MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
  20.         MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
  21.         MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
  22.         MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
  23.         MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
  24.         MPU_InitStruct.SubRegionDisable = 0x00;
  25.         MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

  26.         HAL_MPU_ConfigRegion(&MPU_InitStruct);
  27.         
  28.         
  29.         /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
  30.         MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
  31.         MPU_InitStruct.BaseAddress      = 0x60000000;
  32.         MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;        
  33.         MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
  34.         MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
  35.         MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;        
  36.         MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
  37.         MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
  38.         MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
  39.         MPU_InitStruct.SubRegionDisable = 0x00;
  40.         MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
  41.         
  42.         HAL_MPU_ConfigRegion(&MPU_InitStruct);

  43.         /*使能 MPU */
  44.         HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
  45. }

  46. /*
  47. *********************************************************************************************************
  48. *        函 数 名: CPU_CACHE_Enable
  49. *        功能说明: 使能L1 Cache
  50. *        形    参: 无
  51. *        返 回 值: 无
  52. *********************************************************************************************************
  53. */
  54. static void CPU_CACHE_Enable(void)
  55. {
  56.         /* 使能 I-Cache */
  57.         SCB_EnableICache();

  58.         /* 使能 D-Cache */
  59.         SCB_EnableDCache();
  60. }
复制代码

  每10ms调用一次按键检测:

按键检测是在滴答定时器中断里面实现,每10ms执行一次检测。

  1. /*
  2. *********************************************************************************************************
  3. *        函 数 名: bsp_RunPer10ms
  4. *        功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求
  5. *              不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。
  6. *        形    参: 无
  7. *        返 回 值: 无
  8. *********************************************************************************************************
  9. */
  10. void bsp_RunPer10ms(void)
  11. {
  12.         bsp_KeyScan10ms();
  13. }
复制代码

主功能:

主功能的实现主要分为两部分:

启动一个自动重装软件定时器,每100ms翻转一次LED2
按键消息的读取,检测到按下后,做串口打印。
  1. /*
  2. *********************************************************************************************************
  3. *        函 数 名: main
  4. *        功能说明: c程序入口
  5. *        形    参: 无
  6. *        返 回 值: 错误代码(无需处理)
  7. *********************************************************************************************************
  8. */
  9. int main(void)
  10. {
  11.         uint8_t ucKeyCode;                /* 按键代码 */
  12.         
  13.         bsp_Init();                /* 硬件初始化 */
  14.         
  15.         PrintfLogo();        /* 打印例程名称和版本等信息 */
  16.         PrintfHelp();        /* 打印操作提示 */


  17.         bsp_StartAutoTimer(0, 100); /* 启动1个100ms的自动重装的定时器 */
  18.         
  19.         /* 进入主程序循环体 */
  20.         while (1)
  21.         {
  22.                 bsp_Idle();                /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */

  23.                 /* 判断定时器超时时间 */
  24.                 if (bsp_CheckTimer(0))        
  25.                 {
  26.                         /* 每隔100ms 进来一次 */  
  27.                         bsp_LedToggle(2);                        
  28.                 }
  29.                
  30.                 /* 按键滤波和检测由后台systick中断服务程序实现,我们只需要调用bsp_GetKey读取键值即可。 */
  31.                 ucKeyCode = bsp_GetKey();        /* 读取键值, 无键按下时返回 KEY_NONE = 0 */
  32.                 if (ucKeyCode != KEY_NONE)
  33.                 {
  34.                         switch (ucKeyCode)
  35.                         {
  36.                                 case KEY_DOWN_K1:                        /* K1键按下 */
  37.                                         printf("K1键按下\r\n");
  38.                                         break;

  39.                                 case KEY_UP_K1:                                /* K1键弹起 */
  40.                                         printf("K1键弹起\r\n");
  41.                                         break;

  42.                                 case KEY_DOWN_K2:                        /* K2键按下 */
  43.                                         printf("K2键按下\r\n");
  44.                                         break;

  45.                                 case KEY_UP_K2:                                /* K2键弹起 */
  46.                                         printf("K2键弹起\r\n");
  47.                                         break;

  48.                                 case KEY_DOWN_K3:                        /* K3键按下 */
  49.                                         printf("K3键按下\r\n");
  50.                                         break;

  51.                                 case KEY_UP_K3:                                /* K3键弹起 */
  52.                                         printf("K3键弹起\r\n");
  53.                                         break;

  54.                                 case JOY_DOWN_U:                        /* 摇杆UP键按下 */
  55.                                         printf("摇杆上键按下\r\n");
  56.                                         break;

  57.                                 case JOY_DOWN_D:                        /* 摇杆DOWN键按下 */
  58.                                         printf("摇杆下键按下\r\n");
  59.                                         break;

  60.                                 case JOY_DOWN_L:                        /* 摇杆LEFT键按下 */
  61.                                         printf("摇杆左键按下\r\n");
  62.                                         break;
  63.                                 
  64.                                 case JOY_LONG_L:            /* 摇杆LEFT键长按 */
  65.                                         printf("摇杆左键长按\r\n");
  66.                                         break;

  67.                                 case JOY_DOWN_R:                        /* 摇杆RIGHT键按下 */
  68.                                         printf("摇杆右键按下\r\n");
  69.                                         break;
  70.                                 
  71.                                 case JOY_LONG_R:            /* 摇杆RIGHT键长按 */
  72.                                         printf("摇杆右键长按\r\n");
  73.                                         break;

  74.                                 case JOY_DOWN_OK:                        /* 摇杆OK键按下 */
  75.                                         printf("摇杆OK键按下\r\n");
  76.                                         break;

  77.                                 case JOY_UP_OK:                        /* 摇杆OK键弹起 */
  78.                                         printf("摇杆OK键弹起\r\n");
  79.                                         break;

  80.                 case SYS_DOWN_K1K2:                        /* 摇杆OK键弹起 */
  81.                                         printf("K1和K2组合键按下\r\n");
  82.                                         break;

  83.                                 default:
  84.                                         /* 其它的键值不处理 */
  85.                                         break;
  86.                         }
  87.                
  88.                 }
  89.         }
  90. }
复制代码

19.9 总结
这个方案在实际项目中已经经过千锤百炼,大家可以放心使用。建议熟练掌握其用法。



aHR0cHM6Ly9pbWcyMDE4LmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDE5MDUvMTM3OTEwNy0yMDE5.png
收藏 评论0 发布时间:2021-12-28 22:58

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版