OpenEdv-开源电子网

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

《MiniPRO H750开发指南》第十五章 按键输入实验

[复制链接]

1140

主题

1152

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
4896
金钱
4896
注册时间
2019-5-8
在线时间
1248 小时
发表于 2022-12-15 11:14:00 | 显示全部楼层 |阅读模式
本帖最后由 正点原子运营 于 2022-12-26 15:29 编辑

第十五章 按键输入实验


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

2) 章节摘自【正点原子】MiniPro STM32H750 开发指南_V1.1


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

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

6)MiniPro STM32H750技术交流QQ群:170313895

155537c2odj87vz1z9vj6l.jpg

155537nfqovl2gg9faaol9.png

上一章,我们介绍了STM32H7的IO口作为输出的使用。本章,我们将向大家介绍如何使用STM32H7的IO口作为输入。我们将利用板载的3个按键,来控制板载的RGB的红绿蓝三个灯亮灭。通过本章的学习,我们将了解到STM32H7的IO口作为输入的使用方法。
本章分为如下几个小节:
15.1 按键与输入数据寄存器
15.2 硬件设计
15.3 程序设计
15.4 下载验证

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

图15.1.1.1 独立按键抖动波形图

图中的按下抖动和释放抖动的时间一般为5~10ms如果在抖动阶段采样,其不稳定状态可能出现一次按键动作被认为是多次按下的情况。为了避免抖动可能带来的误操作,我们要做的措施就是给按键消抖(即采样稳定闭合阶段)。消抖方法分为硬件消抖和软件消抖,我们常用软件的方法消抖。

软件消抖:方法很多,我们例程中使用最简单的延时消抖。检测到按键按下后,一般进行10ms延时,用于跳过抖动的时间段,如果消抖效果不好可以调整这个10ms延时,因为不同类型的按键抖动时间可能有偏差。待延时过后再检测按键状态,如果没有按下,那我们就判断这是抖动或者干扰造成的;如果还是按下,那么我们就认为这是按键真的按下了。对按键释放的判断同理。

硬件消抖:利用RC电路的电容充放电特性来对抖动产生的电压毛刺进行平滑出来,从而实现消抖,但是成本会更高一点,本着能省则省的原则,我们推荐使用软件消抖即可。

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

图15.1.2.1 GPIOx IDR寄存器描述

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

15.2 硬件设计
1. 例程功能
通过开发板上的三个独立按键控制RGB灯:KEY_UP控制LED0翻转,KEY1控制LED1翻转,KEY0控制LED2翻转。

2. 硬件资源
1)RGB灯
     RED   :LED0 - PB4
     GREEN :LED1 - PE6
     BLUE  :LED2 - PE5
2)独立按键
KEY0 - PA1,KEY1 - PA15,WK_UP - PA0

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

图15.2.1 独立按键与STM32H7连接原理图
这里需要注意的是:KEY0和KEY1是低电平有效的,而KEY_UP是高电平有效的,并且外部都没有上下拉电阻,所以,需要在STM32H750内部设置上下拉。

15.3 程序设计
15.3.1 HAL_GPIO_ReadPin函数
HAL_GPIO_ReadPin函数是GPIO口的读引脚函数。其声明如下:
  1. <font face="Tahoma" size="3">GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin);</font>
复制代码
l  函数描述:
用于读取GPIO引脚状态,通过IDR寄存器读取。

l  函数形参:
形参1是端口号,可以选择范围:GPIOA~GPIOK。
形参2是引脚号,可以选择范围:GPIO_PIN_0到 GPIO_PIN_15。

l  函数返回值:
引脚状态值0或者1

GPIO输入配置步骤
1)使能对应GPIO时钟
本实验用到PA0/1/15这三个IO口,因此需要先使能GPIOA的时钟,代码如下:
  1. <font face="Tahoma" size="3">__HAL_RCC_GPIOA_CLK_ENABLE();</font>
复制代码
2)设置对应GPIO工作模式(上拉/下拉输入)
本实验GPIO使用输入模式(带上拉/下拉),从而可以读取IO口的状态,实现按键检测,GPIO模式通过函数HAL_GPIO_Init设置实现。

3)读取GPIO引脚高低电平
在配置好GPIO工作模式后,我们就可以通过HAL_GPIO_ReadPin函数读取GPIO引脚的高低电平,从而实现按键检测了。

15.3.2 程序流程图
image008.png

图15.3.2.1 按键输入实验程序流程图

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

  2. /* 引脚 定义 */



  3. #define KEY0_GPIO_PORT                  GPIOA

  4. #define KEY0_GPIO_PIN                   GPIO_PIN_1

  5. /* PA口时钟使能 */

  6. #define KEY0_GPIO_CLK_ENABLE()        do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)



  7. #define KEY1_GPIO_PORT                  GPIOA

  8. #define KEY1_GPIO_PIN                   GPIO_PIN_15

  9. /* PA口时钟使能 */

  10. #define KEY1_GPIO_CLK_ENABLE()        do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)



  11. #define WKUP_GPIO_PORT                  GPIOA

  12. #define WKUP_GPIO_PIN                   GPIO_PIN_0

  13. /* PA口时钟使能 */

  14. #define WKUP_GPIO_CLK_ENABLE()        do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)

  15. /*****************************************************************************/</font>
复制代码
为了后续对按键进行便捷的操作,我们为按键操作函数做了下面的定义。
  1. <font face="Tahoma" size="3">#define KEY0   HAL_GPIO_ReadPin(KEY0_GPIO_PORT, KEY0_GPIO_PIN)/* 读取KEY0引脚 */

  2. #define KEY1   HAL_GPIO_ReadPin(KEY1_GPIO_PORT, KEY1_GPIO_PIN)/* 读取KEY1引脚 */

  3. #define WK_UP  HAL_GPIO_ReadPin(WKUP_GPIO_PORT, WKUP_GPIO_PIN)/* 读取WKUP引脚 */



  4. #define KEY0_PRES    1      /* KEY0按下 */

  5. #define KEY1_PRES    2      /* KEY1按下 */

  6. #define WKUP_PRES    3      /* KEY_UP按下(即WK_UP) */</font>
复制代码
KEY0、KEY1和WK_UP分别是读取对应按键状态的宏定义。用HAL_GPIO_ReadPin函数实现,该函数的返回值就是IO口的状态,返回值是枚举类型,取值0或者1。
KEY0_PRES、KEY1_PRES和WKUP_PRES则是按键对应的三个键值宏定义标识符。
下面我们再解析key.c的程序,这里有两个函数,先看按键初始化函数,其定义如下:
  1. <font face="Tahoma" size="3">/**

  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;



  9.     KEY0_GPIO_CLK_ENABLE(); /* KEY0时钟使能 */

  10.     KEY1_GPIO_CLK_ENABLE(); /* KEY1时钟使能 */

  11.     WKUP_GPIO_CLK_ENABLE(); /* KEY_UP时钟使能 */



  12.     gpio_init_struct.Pin = KEY0_GPIO_PIN;                     /* KEY0引脚 */

  13.     gpio_init_struct.Mode = GPIO_MODE_INPUT;                 /* 输入 */

  14.     gpio_init_struct.Pull = GPIO_PULLUP;                      /* 上拉 */

  15.     gpio_init_struct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;    /* 高速 */

  16.     HAL_GPIO_Init(KEY0_GPIO_PORT, &gpio_init_struct);      /* KEY0引脚初始化 */



  17.     gpio_init_struct.Pin = KEY1_GPIO_PIN;                     /* KEY1引脚 */

  18.     gpio_init_struct.Pull = GPIO_PULLUP;                      /* 上拉 */

  19.     HAL_GPIO_Init(KEY1_GPIO_PORT, &gpio_init_struct);      /* KEY1引脚初始化 */

  20.    

  21.     gpio_init_struct.Pin = WKUP_GPIO_PIN;                    /* KEY_UP引脚 */

  22.     gpio_init_struct.Pull = GPIO_PULLDOWN;                   /* 下拉 */

  23.     HAL_GPIO_Init(WKUP_GPIO_PORT, &gpio_init_struct);       /* KEY_UP引脚初始化 */

  24. }</font>
复制代码
这里需要注意的是:KEY0和KEY1是低电平有效的(即一端接地),所以我们要设置为内部上拉,而KEY_UP是高电平有效的(即一端接电源),所以我们要设置为内部下拉。
另一个函数是按键扫描函数,其定义如下:
  1. <font face="Tahoma" size="3">/**

  2. * @brief       按键扫描函数

  3. * [url=home.php?mod=space&uid=60778]@note[/url]        该函数有响应优先级(同时按下多个按键): WK_UP > KEY1 > KEY0!!

  4. * @param       mode:0 / 1, 具体含义如下:

  5. *   @arg       0,  不支持连续按(当按键按下不放时, 只有第一次调用会返回键值,

  6. *                   必须松开以后, 再次按下才会返回其他键值)

  7. *   @arg       1,  支持连续按(当按键按下不放时, 每次调用该函数都会返回键值)

  8. * @retval    键值, 定义如下:

  9. *              KEY0_PRES, 1, KEY0按下

  10. *              KEY1_PRES, 2, KEY1按下

  11. *              WKUP_PRES, 3, WKUP按下

  12. */

  13. uint8_t key_scan(uint8_t mode)

  14. {

  15.     static uint8_t key_up = 1;  /* 按键按松开标志 */

  16.     uint8_t keyval = 0;



  17.     if (mode) key_up = 1;       /* 支持连按 */

  18. /* 按键松开标志为1, 且有任意一个按键按下了 */

  19.     if (key_up && (KEY0 == 0 || KEY1 == 0 || WK_UP == 1))  

  20.     {

  21.         delay_ms(10);             /* 去抖动 */

  22.         key_up = 0;

  23.         if (KEY0 == 0)  keyval = KEY0_PRES;

  24.         if (KEY1 == 0)  keyval = KEY1_PRES;

  25.         if (WK_UP == 1) keyval = WKUP_PRES;

  26.     }

  27.     else if (KEY0 == 1 && KEY1 == 1 && WK_UP == 0)/* 没有任何按键按下, 标记按键松开*/

  28.     {

  29.         key_up = 1;

  30.     }

  31.     return keyval;  /* 返回键值 */

  32. }</font>
复制代码
key_scan函数用于扫描这4个IO口是否有按键按下。key_scan函数,支持两种扫描方式,通过mode参数来设置。

当mode为0的时候,key_scan函数将不支持连续按,扫描某个按键,该按键按下之后必须要松开,才能第二次触发,否则不会再响应这个按键,这样的好处就是可以防止按一次多次触发,而坏处就是在需要长按的时候比较不合适。

当mode为1的时候,key_scan函数将支持连续按,如果某个按键一直按下,则会一直返回这个按键的键值,这样可以方便的实现长按检测。

有了mode这个参数,大家就可以根据自己的需要,选择不同的方式。这里要提醒大家,因为该函数里面有static变量,所以该函数不是一个可重入函数,在有OS的情况下,这个大家要留意下。可以看到该函数的消抖延时是10ms。同时还有一点要注意的是,该函数的按键扫描是有优先级的,最优先的是KEY_UP,第二优先的是KEY1,最后是按键KEY0。该函数有返回值,如果有按键按下,则返回非0值,如果没有或者按键不正确,则返回0。

2. main.c代码
在main.c里面编写如下代码:
  1. <font face="Tahoma" size="3">#include "./SYSTEM/sys/sys.h"

  2. #include "./SYSTEM/usart/usart.h"

  3. #include "./SYSTEM/delay/delay.h"

  4. #include "./BSP/LED/led.h"

  5. #include "./BSP/KEY/key.h"



  6. int main(void)

  7. {

  8.     uint8_t key;

  9.    

  10.     sys_cache_enable();                        /* 打开L1-Cache */

  11.     HAL_Init();                                 /* 初始化HAL库 */

  12.     sys_stm32_clock_init(240, 2, 2, 4);     /* 设置时钟, 480Mhz */

  13.     delay_init(480);                           /* 延时初始化 */

  14.     led_init();                                 /* 初始化LED */

  15.     key_init();                                 /* 初始化按键 */

  16.     LED0(0);                                    /* 先点亮LED0 */

  17.    

  18.     while (1)

  19.     {

  20.         key = key_scan(0);                     /* 得到键值 */



  21.         if (key)

  22.         {

  23.             switch (key)

  24.             {

  25.                 case WKUP_PRES:                /* 控制LED0(RED)翻转 */

  26.                     LED0_TOGGLE();             /* LED0状态取反 */

  27.                     break;



  28.                 case KEY1_PRES:                /* 控制LED1(GREEN)翻转 */

  29.                     LED1_TOGGLE();             /* LED1状态取反 */

  30.                     break;



  31.                 case KEY0_PRES:                /* 控制LED2(BLUE)翻转 */

  32.                     LED2_TOGGLE();             /* LED2状态取反 */

  33.                     break;

  34.             }

  35.         }

  36.         else

  37.         {

  38.             delay_ms(10);

  39.         }

  40.     }

  41. }</font>
复制代码
首先是调用系统级别的初始化:sys_cache_enable函数使能I-Cache和D-Cache,然后初始化 HAL库、系统时钟和延时函数。接下来,调用led_init来初始化RGB灯,调用key_init函数初始化独立按键。最后在无限循环里面扫描获取键值,接着用键值判断哪个按键按下,如果有按键按下则翻转相应的灯,如果没有按键按下则延时10ms。

15.4 下载验证
在下载好程序后,我们可以按KEY0、KEY1和KEY_UP来看看RGB灯的变化,是否和我们预期的结果一致?
至此,我们的本章的学习就结束了。本章学习了STM32H750的IO作为输入的使用方法,同时巩固了前面的学习。
正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

8

主题

163

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
448
金钱
448
注册时间
2021-12-14
在线时间
70 小时
发表于 2022-12-15 16:07:03 | 显示全部楼层
回复 支持 反对

使用道具 举报

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

本版积分规则



关闭

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

正点原子公众号

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

GMT+8, 2025-2-24 23:36

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

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