搜索
bottom↓
回复: 0

《STM32MP1 M4裸机CubeIDE开发指南 V1.5.2》第十二章 按键输入实验

[复制链接]

出0入234汤圆

发表于 2022-7-29 12:14:17 | 显示全部楼层 |阅读模式
本帖最后由 正点原子 于 2022-7-29 12:16 编辑

1)实验平台:正点原子STM32MP157开发板
2) 章节摘自【正点原子】STM32MP1 M4裸机CubeIDE开发指南 V1.5.2
3)购买链接:https://detail.tmall.com/item.htm?id=631745687288
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/arm-linux/zdyzmp157.html1.0.0 文档
5)正点原子官方B站:https://space.bilibili.com/394620890
6)STM32MP157技术交流QQ群:703341432 lQLPJxaFi2zaB4UWWrDAMgIsFEW2pwLb3abnwDMA_90_22.png
lQDPJxaFi2nfFizMjM0CbLCPlxn_FVheIQLb3aGrwFQA_620_140.jpg

lQLPJxaFi2nfFhLMkM0BXrDNvOUyeU_FPgLb3aGvQNIA_350_144.png


第十二章 按键输入实验

        上一章,我们介绍了STM32MP157的IO口作为输出的使用。本章,我们向大家介绍IO口作为输入使用的操作方法,我们将利用板载的3个按键来控制LED灯亮和灭以及和蜂鸣器的开和关。
        本章将分为如下几个小节:
        12.1、按键输入简介;
        12.2、硬件设计;
        12.3、软件设计;
        12.4、章节小结;
12.1 按键输入简介

几乎每个开发板都会板载有独立按键,因为按键用处很多。常态下,独立按键是断开的,按下的时候才闭合。每个独立按键会单独占用一个IO口,通过查询IO口的高低电平来判断按键的状态。按键在闭合和断开的时候,都存在抖动现象,即按键在闭合时不会马上就稳定的连接,断开时也不会马上断开,这是机械触点,无法避免。独立按键抖动波形图如下:
第十二章 按键输入实验340.png
图12.1. 1独立按键抖动波形图

图中的按下抖动和释放抖动的时间大概都为10ms。为了避免抖动可能带来的误操作,我们要做的措施就是给按键消抖。消抖方法分为硬件消抖和软件消抖,我们常用软件的方法消抖。
软件消抖:检测到按键按下后,一般进行10ms延时,用于跳过抖动的时间段,如果消抖效果不好可以调整这个10ms延时,因为不同类型的按键抖动时间可能有偏差。按键按下以后,待延时过后再检测按键状态,如果测试到按键没有按下,那我们就判断这是抖动或者干扰造成的;如果测试到还是按下的状态,那么我们就认为这是按键真的按下了。对按键释放的判断同理。
硬件消抖:利用R-S触发器或者单稳态触发器构成消抖电路,或者利用RC积分电路吸收振荡脉冲的特点来达到消抖的效果。
12.2 硬件设计
1. 例程功能
通过开发板上的三个独立按键控制LED灯:WKUP控制LED0翻转,KEY1控制LED1翻转,KEY0控制蜂鸣器翻转。
2. 硬件资源
7B32F0E7-7AA7-4ab9-9646-DA69CE779E00.png
表12.2. 1硬件资源

3. 原理图
LED和BEEP的原理图我们前面已经涉及,独立按键硬件部分的原理图如下图所示:
第十二章 按键输入实验1136.png
图12.2. 1独立按键与STM32MP157连接原理图

这里需要注意的是:KEY0和KEY1是低电平有效的,而WK_UP是高电平有效的,并且KEY0和KEY2接了一个10k的上拉电阻,而WK_UP外部没有下拉电阻,所以WK_UP的配置中,一定要通过软件来开启STM32MP157内部下拉。
12.3 软件设计
        本实验配置好的实验工程已经放到了开发板光盘中,路径为:开发板光盘A-基础资料\1、程序源码\11、M4 CubeIDE裸机驱动例程\CubeIDE_project\ 5 KEY。
12.3.1 程序设计流程
        本章节我们通过HAL库的HAL_GPIO_ReadPin函数(实际上是操作IDR寄存器)来读取按键对应GPIO引脚电平状态,从而判断按键是否有按下。实验程序的设计流程如下:
        KEY0按下,LED0翻转;KEY1按下,LED1翻转,WKUP按下,蜂鸣器翻转。
第十二章 按键输入实验1556.png
图12.3.1. 1程序设计流程图

12.3.2 GPIO功能引脚配置
        新建一个工程KEY,或者可以直接在上一章节的BEEP工程的BEEP.ioc文件中配置。按照前面的配置方法配置好LED0、LED1、BEEP和按键,要注意以下3点:
        ①这里,除了按键WKUP配置为下拉以外,其它引脚均配置为上拉了;
        ②User Lable名字要和前面的实验的一致,因为我们后面会用到前面实验的代码;
        ③记得要配置引脚给M4内核使用,这点我们在前面的实验中都有提到,后面的实验就不再赘述。
        引脚配置结果如下图所示:
第十二章 按键输入实验1840.png
图12.3.2. 1 GPIO功能引脚配置

12.3.3 时钟和工程配置
        我们采用默认内部高速时钟HSI(64MHz)。同时在Project Manager窗口勾选此项,配置独立生成对应外设的初始化.h和.c 文件:
第十二章 按键输入实验1979.png
图12.3.2. 2配置生成外设独立的初始化文件

12.3.4 生成工程
        配置好之后,按下键盘的“Ctrl+S”组合键保存保存 KEY.ioc 文件,系统开始生成初始化代码。
第十二章 按键输入实验2097.png
图12.3.4. 1生成KEY工程

12.3.5 控制逻辑代码实现
1. 新建用户文件
        将上一章BEEP工程的BSP文件夹直接复制粘贴到本工程的CM4工程的Core文件夹下(这次直接拷贝到Core文件夹下,就不需要再设置源文件和工程关联了)。然后在BSP的Include目录下新建key.h头文件,在BSP的根目录下新建key.c源文件,如下图:
第十二章 按键输入实验2301.png
图12.3.5. 1新建文件

2. 添加用户驱动代码
        LED和蜂鸣器的代码不变,我们在key.h、key.c和main.c文件中添加如下代码。
        key.h文件代码如下:
key.h文件代码
1   #ifndef __KEY_H
2   #define __KEY_H
3
4   #include"gpio.h"
5   /* 读取KEY0引脚 */
6   #define KEY0  HAL_GPIO_ReadPin(KEY0_GPIO_Port, KEY0_Pin)
7   /* 读取KEY1引脚 */
8   #define KEY1  HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin)
9   /* 读取WKUP引脚 */
10  #define WKUP  HAL_GPIO_ReadPin(WKUP_GPIO_Port, WKUP_Pin)
11
12  #define KEY0_PRES    1      /* KEY0按下  */
13  #define KEY1_PRES    2                              /* KEY1按下  */
14  #define WKUP_PRES    3                              /* WKUP按下  */
15
16  uint8_t key_scan(uint8_t mode);                /* 按键扫描函数 */
17
18  #endif
        key.h头文件很简单:
        第6~10行,通过HAL_GPIO_ReadPin函数读取GPIO的电平值,也就是读取按键电平值。
        第12~14行,定义3个宏,分别对应3个按键按下。
        第16行,声明按键扫描函数key_scan。
        key.c文件代码如下:
key.c文件代码
1   #include "./Include/key.h"
2   #include "./Include/led.h"
3   #include "./Include/beep.h"
4
5   /**
6    * @brief  按键扫描函数
7    * @note   该函数有响应优先级(同时按下多个按键): WKUP > KEY1 > KEY0!
8    * @param  mode:0 / 1, 具体含义如下:
9    *   @arg  0, 不支持连续按(当按键按下不放时, 只有第一次调用会返回键值,
10   *         必须松开以后, 再次按下才会返回其他键值)
11   *   @arg  1, 支持连续按(当按键按下不放时, 每次调用该函数都会返回键值)
12   * @retval 键值, 定义如下:
13   *         KEY0_PRES, 1, KEY0按下
14   *         KEY1_PRES, 2, KEY1按下
15   *         WKUP_PRES, 3, WKUP按下
16   */
17  uint8_t key_scan(uint8_t mode)
18  {
19      static uint8_t key_up = 1;          /* 按键按松开标志 */
20      uint8_t keyval = 0;
21
22      if (mode) key_up = 1;               /* 支持连按 */
23      /* 按键松开标志为1, 且有任意一个按键按下了 */
24      if (key_up && (KEY0 == 0 || KEY1 == 0 || WKUP == 1))   
25      {
26          HAL_Delay(10);                          /* 去抖动,后面会换成高精度延时函数! */
27          key_up = 0;
28
29          if (KEY0 == 0)  keyval = KEY0_PRES;
30
31          if (KEY1 == 0)  keyval = KEY1_PRES;
32
33          if (WKUP == 1) keyval = WKUP_PRES;
34      }
35      /* 没有任何按键按下, 标记按键松开 */
36      else if (KEY0 == 1 && KEY1 == 1 && WKUP == 0)
37      {
38          key_up = 1;
39      }
40
41      return keyval;                                  /* 返回键值 */
42  }
        key.c文件主要是处理按键扫描函数,程序设计按键支持连续按和不支持连续按两种情况。我们来看按键处理的程序设计过程:
        第19行,key_up为按键松开标志,如果key_up等于1,表示按键已经松开。
        第20行,定义程序的返回值keyval。
        第22行,如果函数的参数mode等于1,则key_up等于1,表示支持按键可连续按模式,也就是当按键按下不放时,每次调用该函数都会返回键值。如果函数的参数mode等于0,表示不支持连续按,也就是当按键按下不放时,只有第一次调用会返回键值,必须松开按键以后, 再次按下才会返回其键值。
        第24行,如果标志位key_up等于1(表示按键是松开的,没有按下),当KEY0等于0或者KEY1等于0或者WKUP等于1的时候,表示有按键按下了,具体是哪一个按键按下了,需经过后面的程序进行判断。
        第26行,程序延时10ms,因为按键按下过程产生抖动,抖动周期大概10ms,所以我们先延时10ms以后再去读取按键对应的IO口的状态。
        第27行,将标志位key_up置0,方便程序用于判断下次按键是否有按下。
        第29~33行,通过读取到的按键对应的IO口电平来判断是哪一个按键按下了。
        我们前面通过原理图分析知道,当按键WKUP按下以后,对应的IO口为高电平,当KEY0或KEY1按键按下以后,对应的IO口为低电平。
        第36行,如果读取到KEY0和KEY1的电平为1,WKUP的电平为0,说明此时没有按键按下, key_up值等于1,表示按键是松开的。
        main.c文件函数如下:
main.c文件代码
1   #include "main.h"
2   #include "gpio.h"
3   #include "../BSP/Include/beep.h"
4   #include "../BSP/Include/led.h"
5   #include "../BSP/Include/key.h"
6   uint8_t key;
7  
8   void SystemClock_Config(void);
9  
10  int main(void)
11  {
12   
13    HAL_Init();
14
15    if(IS_ENGINEERING_BOOT_MODE())
16    {
17      /* 配置系统时钟 */
18      SystemClock_Config();
19    }
20      /* 初始化所有已经配置的外设 */
21      MX_GPIO_Init();
22      led_init(); /* 关闭 LED0,打开LED1 */
23      beep_init(); /* 关闭BEEP */
24
25    while (1)
26    {
27      key = key_scan(0);                                   /* 得到键值 */
28        if (key)
29        {
30            switch (key)
31            {
32                case WKUP_PRES:              /* 控制BEEP翻转 */
33                    BEEP_TOGGLE();           /* BEEP状态取反 */
34                    break;
35
36                case KEY1_PRES:              /* 控制LED1翻转 */
37                    LED1_TOGGLE();           /* LED1状态取反 */
38                    break;
39
40                case KEY0_PRES:              /* 控制LED0开关 */
41                    LED0_TOGGLE();           /* LED0状态取反 */
42                    break;
43            }
44        }
45        else
46        {
47            HAL_Delay(10);                            /* 延时10ms */
48        }
49    }
50  }
51  /* 系统时钟配置函数 */
52  void SystemClock_Config(void)
53  {
54   /*此处省略时钟初始化代码*/
55  }
56
57  void Error_Handler(void)
58  {
59   
60  }
        main.c文件的SystemClock_Config函数我们前面的第九章的实验中有分析过,因为我们前面没有去配置系统时钟,此时时钟源默认采用高速内部时钟HSI(64MHz)。MX_GPIO_Init
函数是GPIO初始化部分,主要打开用到的GPIO外设时钟,并初始化用到的GPIO的工作模式,我们在前面第10.5.5小节有分析过。
        第22和23行,在进入while循环前,LED0和蜂鸣器是关闭的,LED1是打开的,所以后面Debug运行的时候,会看到LED1是亮的,LED0不亮,蜂鸣器也不会响。
        第25~50行,while循环处理的过程。
        第27行,先获取按键的值,这里key_scan(0)函数的参数是0,表示不支持连续按模式,如果大家想测试连续按模式,只需要把函数的参数0改为1即可。
        第28行,key_scan(0)的返回值key可以是0、1、2或者3中的某一个,如果key的值是1、2和3的话,则表示有按键按下,通过第30~43行的case语句判断按键的按下情况:
        第一次WKUP按键按下,则蜂鸣器响,再次按下,蜂鸣器不响,再次按下蜂鸣器又响,如此循环,实现蜂鸣器翻转。
        第一次按下KEY1,LED1灭,再次按下KEY1,LED1又亮,如此循环。
        第一次按下KEY0,LED0亮,再次按下KEY0,LED0灭,如此循环。
        如果key的值是0,表示没有按键按下,程序执行HAL_Delay(10)函数延时10ms以后再返回到第27行往下执行,即在while中循环。
12.3.6 编译和调试
        按下“Ctrl+B”编译工程,按照我们前面的操作进入Debug模式,然后点击运行按钮来运行调试,可以看到开发板底板的LED1 灯第亮的,LED0不亮,蜂鸣器也不响。然后开始按下按键来测试实验结果,实现现象和我们上面分析的一致。
12.4 章节小结
        经过前面的实验学习,相信会有部分小伙伴对什么时候软件要配置上拉什么时候软件要配置下拉有疑惑,本小节我们就来总结这部分内容吧。
        随着技术的迅速发展,芯片的集成度和复杂度也越来越高,功能也越来越强大,现在的芯片内部一般都集成了电阻和电容,为PCB设计极大地节省了空间。如下图所示,STM32的GPIO口内部自带了上拉和下拉电阻,电阻上有一个开关,可通过软件配置来设置开或者关。
第十二章 按键输入实验7293.png
图12.4. 1 GPIO的基本结构图

        上拉就是将一个不确定的信号通过一个电阻嵌位在高电平,电阻同时起限流作用,下拉就是将一个不确定的信号通过一个电阻嵌位在低电平,电阻同时起限流作用。STM32的这个上下拉电阻阻值一般是30KΩ~50KΩ,上拉电阻,一般称为弱上拉,下拉电阻,一般称为弱下拉。这里的弱是指电阻阻值比较大,电流就比较小,充电就比较慢,从而上下拉的速度就比较慢。
        例如,芯片内部开启了弱上拉(例如阻值是50KΩ),外部再加一个强上拉电阻10KΩ,电路中这两个电阻相当于并联,IO口的上拉电阻就小于10KΩ,电流就变大了,也就变“强”了。此电流可以供开漏电路使用,开漏输出最主要的特性就是高电平没有驱动能力,需要借助外部上拉电阻才能真正输出高电平。
        芯片复位以后,端口上拉下拉寄存器(PUPDR)的复位值为0x0000 0000,即默认不开启内部上拉和下拉,IO口相当于浮空状态,电平状态是高还是低不确定。那么问题来了,如果此IO口接的是一个按键,如果外部不做上下拉会怎样?我们来分析前面的实验。
第十二章 按键输入实验7777.png
图12.4. 2按键原理图

        我们是通过读取按键对应的IO电平状态来判断按键是否有按下的,按键WK_UP一端接的高电平,另一端接的PA0,按键按下以后IO口是高电平,所以认为此按键是高电平有效。如果PA0内部不开启下拉,那么IO口电平是未知的,当按键程序去读取IO口电平时可能读到值为0也可能为1,如果此时恰好没有按下按键,而程序读取IO口电平为1,程序就认为按键是已经按下了,这就错了。在按键未按下前,我们当然希望PA0电平为0,所以对于按键WK_UP一定要开启弱下拉。对于按键KEY0和KEY1是低电平有效,IO口的一端实际上是已经接了10KΩ的上拉电阻,IO口电平已经默认为高电平了,所以内部上拉不开启也是不影响的。
        上下拉电阻的目的是给IO口设定一个确定的电平状态,如果按键按下是高电平有效,我们就做下拉,让该IO口在默认状态下处于低电平状态,即没有按键按下时,IO口检测到的总是低电平,只有按键按下的时候IO口才会检测到高电平;如果按键按下是低电平有效的话,我们就上拉,让该IO口在默认状态下处于高电平,即没有按键按下时,IO口检测到的总是高电平,只有按键按下的时候IO口才会检测到低电平。

阿莫论坛20周年了!感谢大家的支持与爱护!!

月入3000的是反美的。收入3万是亲美的。收入30万是移民美国的。收入300万是取得绿卡后回国,教唆那些3000来反美的!
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-27 08:27

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

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