OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 24|回复: 0

《STM32H7R7开发指南 V1.1 》第十四章 按键输入实验

[复制链接]

1296

主题

1310

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
5549
金钱
5549
注册时间
2019-5-8
在线时间
1468 小时
发表于 1 小时前 | 显示全部楼层 |阅读模式
第十四章 按键输入实验

1)实验平台:正点原子STM32H7R7开发板

2)章节摘自【正点原子】STM32H7R7开发指南 V1.1

3)购买链接: https://detail.tmall.com/item.htm?id=820823382459

4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/stm32/zdyz_stm32h7rx.html

5)正点原子官方B站:https://space.bilibili.com/394620890

6)正点原子STM32开发板技术交流群:756580169


2.jpg

3.png

上一章,我们介绍了STM32H7R7的IO口作为输出的使用。本章,我们将向大家介绍如何使用STM32H7R7的IO口作为输入。我们将利用板载的4个按键,来控制板载的两个LED灯亮灭。通过本章的学习,我们将了解到STM32H7R7的IO口作为输入的使用方法。
本章分为如下几个小节:
14.1 按键与输入数据寄存器
14.2 硬件设计
14.3 程序设计
14.4 下载验证


14.1 按键与输入数据寄存器简介

14.1.1 独立按键简介
几乎每个开发板都会板载有独立按键,因为按键用处很多。常态下,独立按键是断开的,按下的时候才闭合。每个独立按键会单独占用一个IO口,通过IO口的高低电平判断按键的状态。但是按键在闭合和断开的时候,都存在抖动现象,即按键在闭合时不会马上就稳定的连接,断开时也不会马上断开。这是机械触点,无法避免。独立按键抖动波形图如下:

第十四章 按键输入实验393.png
图14.1.1.1 独立按键抖动波形图

图中的按下抖动和释放抖动的时间一般为5~10ms如果在抖动阶段采样,其不稳定状态可能出现一次按键动作被认为是多次按下的情况。为了避免抖动可能带来的误操作,我们要做的措施就是给按键消抖(即采样稳定闭合阶段)。消抖方法分为硬件消抖和软件消抖,我们常用软件的方法消抖。
软件消抖:方法很多,我们例程中使用最简单的延时消抖。检测到按键按下后,一般进行10ms延时,用于跳过抖动的时间段,如果消抖效果不好可以调整这个10ms延时,因为不同类型的按键抖动时间可能有偏差。待延时过后再检测按键状态,如果没有按下,那我们就判断这是抖动或者干扰造成的;如果还是按下,那么我们就认为这是按键真的按下了。对按键释放的判断同理。
硬件消抖:利用RC电路的电容充放电特性来对抖动产生的电压毛刺进行平滑出来,从而实现消抖,但是成本会更高一点,本着能省则省的原则,我们推荐使用软件消抖即可。


14.1.2 GPIO相关寄存器
本实验我们将会用到GPIO端口输入数据寄存器,下面来介绍一下。
该寄存器用于存储GPIOx的输入状态,它连接到施密特触发器上,IO口外部的电平信号经过触发器后,模拟信号就被转化成0和1这样的数字信号,并存储到该寄存器中。寄存器描述如图14.1.2.1所示:


第十四章 按键输入实验942.png
图14.1.2.1 GPIOx IDR寄存器描述

该寄存器低16位有效,分别对应每一组GPIO的16个引脚。当CPU访问该寄存器,如果对应的某位为0(IDRy=0),则说明该IO口输入的是低电平,如果是1(IDRy=1),则表示输入的是高电平,y=0~15。

14.2 硬件设计

1. 例程功能
通过开发板上的四个独立按键控制LED灯:KEY0控制LED0翻转,KEY1控制LED1翻转,KEY2控制LED0、LED1状态翻转,KEY_UP控制LED0/LED1点亮。

2. 硬件资源
1)LED灯
                    LED0 – PD14
                LED1 – PC0
2)独立按键
                KEY0        – PE9
                KEY1        – PE8
                KEY2        – PE7
                KEY_UP        – PC13(程序中的宏名:WK_UP)

3. 原理图
独立按键硬件部分的原理图,如图14.2.1所示:


第十四章 按键输入实验1333.png
图14.2.1 独立按键与STM32H7R7连接原理图


这里需要注意的是:KEY0、KEY1和KEY2设计为采样到按键另一端的低电平为有效电平,而KEY_UP则需要采样到高电平才为按键有效,并且按键外部没有上下拉电阻,所以需要在STM32H7R7内部设置上下拉。

14.3 程序设计

14.3.1 HAL_GPIO_ReadPin函数
HAL_GPIO_ReadPin函数是GPIO口的读引脚函数。其声明如下:
  1.         GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin);
复制代码
函数描述:
用于读取GPIO引脚状态,通过IDR寄存器读取。
函数形参:
形参1是端口号,可以选择范围:GPIOA~GPIOG,GPIOM~GPIOP。
形参2是引脚号,可以选择范围:GPIO_PIN_0到 GPIO_PIN_15。
函数返回值:
引脚状态值0或者1。

GPIO输入配置步骤
1)使能对应GPIO时钟
本实验用到PC13和PE9/PE8/PE7这四个IO口,因此需要使能GPIOC和GPIOE的时钟,代码如下:

  1. __HAL_RCC_GPIOC_CLK_ENABLE();
  2. __HAL_RCC_GPIOE_CLK_ENABLE();
复制代码

2)设置对应GPIO工作模式(上拉/下拉输入)

本实验GPIO使用输入模式(带上拉/下拉),从而可以读取IO口的状态,实现按键检测,GPIO模式通过函数HAL_GPIO_Init设置实现。
3)读取GPIO引脚高低电平
在配置好GPIO工作模式后,我们就可以通过HAL_GPIO_ReadPin函数读取GPIO引脚的高低电平,从而实现按键检测了。


14.3.2 程序解析

1. 按键驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。按键(KEY)驱动源码包括两个文件:key.c和key.h。
下面我们先解析key.h的程序,我们把它分两部分功能进行讲解。
按键引脚定义
由硬件设计小节,我们知道KEY0、KEY1、KEY2和KEY_UP分别来连接到PE9、PE8、PE7和PC13上,我们做了下面的引脚定义:

  1. /* 引脚 定义 */
  2. #define WKUP_GPIO_PORT                  GPIOC
  3. #define WKUP_GPIO_PIN                   GPIO_PIN_13
  4. #define WKUP_GPIO_CLK_ENABLE()          do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0)   /* PC口时钟使能 */

  5. #define KEY0_GPIO_PORT                  GPIOE
  6. #define KEY0_GPIO_PIN                   GPIO_PIN_9
  7. #define KEY0_GPIO_CLK_ENABLE()          do{ __HAL_RCC_GPIOE_CLK_ENABLE(); }while(0)   /* PE口时钟使能 */

  8. #define KEY1_GPIO_PORT                  GPIOE
  9. #define KEY1_GPIO_PIN                   GPIO_PIN_8
  10. #define KEY1_GPIO_CLK_ENABLE()          do{ __HAL_RCC_GPIOE_CLK_ENABLE(); }while(0)   /* PE口时钟使能 */

  11. #define KEY2_GPIO_PORT                  GPIOE
  12. #define KEY2_GPIO_PIN                   GPIO_PIN_7
  13. #define KEY2_GPIO_CLK_ENABLE()          do{ __HAL_RCC_GPIOE_CLK_ENABLE(); }while(0)   /* PE口时钟使能 */
复制代码

按键操作函数定义
为了后续对按键进行便捷的操作,我们为按键操作函数做了下面的定义:

  1. #define WK_UP HAL_GPIO_ReadPin(WKUP_GPIO_PORT, WKUP_GPIO_PIN) /* 读取WKUP引脚 */
  2. #define KEY0  HAL_GPIO_ReadPin(KEY0_GPIO_PORT, KEY0_GPIO_PIN) /* 读取KEY0引脚 */
  3. #define KEY1  HAL_GPIO_ReadPin(KEY1_GPIO_PORT, KEY1_GPIO_PIN) /* 读取KEY1引脚 */
  4. #define KEY2  HAL_GPIO_ReadPin(KEY2_GPIO_PORT, KEY2_GPIO_PIN) /* 读取KEY2引脚 */

  5. #define NONE_PRES               0   /* 没有按键按下 */
  6. #define WKUP_PRES               1   /* WKUP按键按下 */
  7. #define KEY0_PRES               2   /* KEY0按键按下 */
  8. #define KEY1_PRES               3   /* KEY1按键按下 */
  9. #define KEY2_PRES               4   /* KEY2按键按下 */
复制代码
KEY0、KEY1、KEY2和WK_UP是读取对应按键状态的宏定义。用HAL_GPIO_ReadPin函数实现,该函数的返回值就是IO口的状态,返回值是枚举类型,取值0或者1。
KEY0_PRES、KEY1_PRES、KEY2_PRES和WKUP_PRES则是按键对应的四个键值宏定义标识符。
下面我们再解析key.c的程序,这里有两个函数,先看按键初始化函数,其定义如下:

  1. /**
  2. * @brief       按键初始化函数
  3. * [url=home.php?mod=space&uid=271674]@param[/url]       无
  4. * @retval      无
  5. */
  6. void key_init(void)
  7. {
  8.     GPIO_InitTypeDef gpio_init_struct = {0};
  9.    
  10.     /* 使能GPIO端口时钟 */
  11.     WKUP_GPIO_CLK_ENABLE();
  12.     KEY0_GPIO_CLK_ENABLE();
  13.     KEY1_GPIO_CLK_ENABLE();
  14.     KEY2_GPIO_CLK_ENABLE();
  15.    
  16.     /* 配置WKUP控制引脚 */
  17.     gpio_init_struct.Pin = WKUP_GPIO_PIN;
  18.     gpio_init_struct.Mode = GPIO_MODE_INPUT;
  19.     gpio_init_struct.Pull = GPIO_PULLDOWN;
  20.     gpio_init_struct.Speed = GPIO_SPEED_FREQ_LOW;
  21.     HAL_GPIO_Init(WKUP_GPIO_PORT, &gpio_init_struct);
  22.    
  23.     /* 配置KEY0控制引脚 */
  24.     gpio_init_struct.Pin = KEY0_GPIO_PIN;
  25.     gpio_init_struct.Mode = GPIO_MODE_INPUT;
  26.     gpio_init_struct.Pull = GPIO_PULLUP;
  27.     gpio_init_struct.Speed = GPIO_SPEED_FREQ_LOW;
  28.     HAL_GPIO_Init(KEY0_GPIO_PORT, &gpio_init_struct);
  29.    
  30.     /* 配置KEY1控制引脚 */
  31.     gpio_init_struct.Pin = KEY1_GPIO_PIN;
  32.     gpio_init_struct.Mode = GPIO_MODE_INPUT;
  33.     gpio_init_struct.Pull = GPIO_PULLUP;
  34.     gpio_init_struct.Speed = GPIO_SPEED_FREQ_LOW;
  35.     HAL_GPIO_Init(KEY1_GPIO_PORT, &gpio_init_struct);
  36.    
  37.     /* 配置KEY2控制引脚 */
  38.     gpio_init_struct.Pin = KEY2_GPIO_PIN;
  39.     gpio_init_struct.Mode = GPIO_MODE_INPUT;
  40.     gpio_init_struct.Pull = GPIO_PULLUP;
  41.     gpio_init_struct.Speed = GPIO_SPEED_FREQ_LOW;
  42.     HAL_GPIO_Init(KEY2_GPIO_PORT, &gpio_init_struct);
  43. }
复制代码
这里需要注意的是:KEY0、KEY1和KEY2是低电平有效的(即一端接地),所以我们要设置为内部上拉,而KEY_UP是高电平有效的(即一端接电源),所以我们要设置为内部下拉。
另一个函数是按键扫描函数,其定义如下:

  1. /**
  2. * @brief   扫描按键
  3. * [url=home.php?mod=space&uid=60778]@note[/url]    按键响应具有优先级:WKUP > KEY0 > KEY1 > KEY2
  4. * @param   mode: 扫描模式
  5. * @arg     0: 不支持连续按
  6. * @arg     1: 支持连续按
  7. * @retval  按键键值
  8. * @arg     NONE_PRES: 没有按键按下
  9. * @arg     WKUP_PRES: WKUP按键按下
  10. * @arg     KEY0_PRES: KEY0按键按下
  11. * @arg     KEY1_PRES: KEY1按键按下
  12. * @arg     KEY2_PRES: KEY2按键按下
  13. */
  14. uint8_t key_scan(uint8_t mode)
  15. {
  16.     static uint8_t key_release = 1;
  17.     uint8_t key_value = NONE_PRES;
  18.    
  19.     if (mode != 0)
  20.     {
  21.         key_release = 1;
  22.     }
  23.    
  24. if ((key_release == 1) && ((WKUP == 1) || (KEY0 == 0) ||
  25.                                           (KEY1 == 0) || (KEY2 == 0)))
  26.     {
  27.         delay_ms(10);
  28.         key_release = 0;
  29.         
  30.         if (KEY2 == 0)
  31.         {
  32.             key_value = KEY2_PRES;
  33.         }
  34.         
  35.         if (KEY1 == 0)
  36.         {
  37.             key_value = KEY1_PRES;
  38.         }
  39.         
  40.         if (KEY0 == 0)
  41.         {
  42.             key_value = KEY0_PRES;
  43.         }
  44.         
  45.         if (WKUP == 1)
  46.         {
  47.             key_value = WKUP_PRES;
  48.         }
  49.     }
  50.     else if ((WKUP == 0) && (KEY0 == 1) && (KEY1 == 1) && (KEY2 == 1))
  51.     {
  52.         key_release = 1;
  53.     }
  54.    
  55.     return key_value;
  56. }
复制代码
key_scan函数用于扫描这4个IO口是否有按键按下。key_scan函数,支持两种扫描方式,通过mode参数来设置。
当mode为0的时候,key_scan函数将不支持连续按,扫描某个按键,该按键按下之后必须要松开,才能第二次触发,否则不会再响应这个按键,这样的好处就是可以防止按一次多次触发,而坏处就是在需要长按的时候比较不合适。
当mode为1的时候,key_scan函数将支持连续按,如果某个按键一直按下,则会一直返回这个按键的键值,这样可以方便的实现长按检测。
有了mode这个参数,大家就可以根据自己的需要,选择不同的方式。这里要提醒大家,因为该函数里面有static变量,所以该函数不是一个可重入函数,在有OS的情况下,这个大家要留意下。可以看到该函数的消抖延时是10ms。同时还有一点要注意的是,该函数的按键扫描是有优先级的,最优先的是KEY_UP,第二优先的是KEY0,第三优先的是KEY1,最后是按键KEY2。该函数有返回值,如果有按键按下,则返回非0值,如果没有或者按键不正确,则返回0。


2. main.c代码
在main.c里面编写如下代码:

  1. int main(void)
  2. {
  3.     uint8_t key;
  4.    
  5.     sys_mpu_config();                   /* 配置MPU */
  6.     sys_cache_enable();                 /* 使能Cache */
  7.     HAL_Init();                         /* 初始化HAL库 */
  8.     sys_stm32_clock_init(300, 6, 2);    /* 配置时钟,600MHz */
  9.     delay_init(600);                    /* 初始化延时 */
  10.     usart_init(115200);                 /* 初始化串口 */
  11.     led_init();                         /* 初始化LED */
  12.     key_init();                         /* 初始化按键 */
  13.    
  14.     while (1)
  15.     {
  16.         key = key_scan(0);              /* 扫描按键 */
  17.         
  18.         switch (key)
  19.         {
  20.             case WKUP_PRES:             /* WKUP按键被按下 */
  21.             {
  22.                 LED0(0);                /* 开启LED0 */
  23.                 LED1(0);                /* 开启LED1 */
  24.                 break;
  25.             }
  26.             case KEY0_PRES:             /* KEY0按键被按下 */
  27.             {
  28.                 LED0_TOGGLE();          /* 翻转LED0状态 */
  29.                 break;
  30.             }
  31.             case KEY1_PRES:             /* KEY1按键被按下 */
  32.             {
  33.                 LED1_TOGGLE();          /* 翻转LED1状态 */
  34.                 break;
  35.             }
  36.             case KEY2_PRES:             /* KEY2按键被按下 */
  37.             {
  38.                 LED0_TOGGLE();          /* 翻转LED1状态 */
  39.                 LED1_TOGGLE();          /* 翻转LED1状态 */
  40.                 break;
  41.             }
  42.         }
  43.         
  44.         delay_ms(10);
  45.     }
  46. }
复制代码
首先是调用系统级别的初始化:初始化 HAL库、系统时钟和延时函数。接下来,调用led_init来初始化LED灯,调用key_init函数初始化按键。最后在无限循环里面扫描获取键值,接着用键值判断哪个按键按下,如果有按键按下则翻转相应的灯,如果没有按键按下则延时10ms。

14.4 下载验证
在下载好程序后,我们可以按KEY0、KEY1、KEY2和KEY_UP来看看LED灯的变化是否和我们预期的结果一致?
至此,我们的本章的学习就结束了。本章学习了STM32H7R7作为输入的使用方法,在前面的GPIO输出的基础上又学习了一种GPIO使用模式,大家可以回顾前面跑马灯实验介绍的GPIO的八种模式类型巩固GPIO的知识。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /1 下一条

正点原子公众号

如发现本坛存在违规或侵权内容, 请点击这里发送邮件举报 (或致电020-38271790)。请提供侵权说明和联系方式。我们将及时审核依法处理,感谢配合。

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2026-4-2 11:21

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表