优秀的编程知识分享平台

网站首页 > 技术文章 正文

【STM32F103ZET6开发板】第2-2讲:GPIO输入按键检测

nanyue 2024-09-10 16:17:47 技术文章 5 ℃

GPIO输入按键检测

实验目的

  • 掌握STM32 GPIO输入检测原理。
  • 掌握轻触按键检测电路的设计:分压式和高低电平式接法的原理。
  • 掌握对单个和多个按键检测的程序设计及算法。

实验内容

  • 编写程序实现单个按键的检测。
  • 编写程序实现多个按键的检测。

硬件电路设计

开发板用户按键硬件电路

轻触按键又称轻触开关(下文简称按键),是电路中常用的一种开关元器件,也是一种常用的人机接口。广泛用于家电、数码产品、便携仪产品、电脑产品等电子设备中。

IK-ZET6开发板上设计了4个用户按键S1、S2、S3、S4,分别连接到STM32F103ZET6的PE2~PE5引脚。通过读取这些按键对应的GPIO的状态可判断该按键是否按下。

图1:开发板按键检测电路

  • 4个用户按键占用的单片机的引脚如下表:

表1:用户按键引脚分配

KEY

颜色

引脚

说明

按键S1

黄色

PE2

独立GPIO

按键S2

黄色

PE3

独立GPIO

按键S3

黄色

PE4

独立GPIO

按键S4

黄色

PE5

独立GPIO

  • 注:独立GPIO表示开发板没有其他的电路使用这个GPIO。

轻触按键,顾名思义我们只需要施加很小的力量即可改变开关连接的状态。轻触按键在所需外力作用下(按下按键)触点导通,无外力作用时(释放按键)触点断开,如下图所示:

图2:轻触按键原理

按键检测接法

轻触按键常见检测的接法有两种:分压式接法和高低电平式接法。

分压式接法,使用的单片机引脚必须具有ADC功能,根据检测口测得的不同的电压值来识别是哪个按键被按下。如下图所示,是分压式接法的原理示意图。

图3:分压式接法原理

  • 注:分压式接法对电路各电阻的精度要求较高,不同按键按下会在GPIO口形成不同电压值(不同电阻分压)。本方法实际用到的是单片机的ADC功能,在此不做详细讲解。

高低电平式接法是最常见的按键检测的接法,顾名思义,该接法就是需要单片机引脚具有高低电平的检测能力,也就是常见的GPIO引脚即可。高低电平式接法又可分为两种:独立式接法和行列式接法。

行列式接法是利用单片机的 GPIO口组成行与列,在行与列的每一个交点处连接按键。 故也称为矩阵式按键,该接线方法最大的优势是可以使用较少的GPIO口实现较多按键的检测,这个在传感器实验部分矩阵按键实验中会详细介绍。

独立式接法的含义就是使用单片机的一个GPIO引脚检测一个按键的状态,有多少按键需检测就需要多少个GPIO引脚,对每个按键的检测相互独立。基于这种独立性,下面针对使用单个GPIO引脚检测单个按键的两种电路来说明独立式接法的原理,如下图。

图4:独立式接法原理

  • 从上图可知,按键独立式接法(无论加上拉电阻方式还是加下拉电阻方式)会用到两个电阻,两个电阻的作用如下所述。

1、电路中R1的作用一方面是保护单片机GPIO,若GPIO不小心被配置成了输出,按下按键可能会损坏GPIO,而串接电阻R1后,即使出现这种情况,也不会损坏GPIO。另一方面,电阻R1可降低按下按键时产生的抖动峰值电压,防止抖动电压对单片机产生影响。虽然很多单片机在GPIO上都有电压钳位设计,可以承受一定范围内的电压抖动,但是通过串接一个小阻值电阻既能让抖动峰值电压更低而又不影响按键检测电路的性能,而且花费的代价也很小(仅串接一个电阻)。

2、电路中R2是作为上拉电阻或下拉电阻使用,其作用是将GPIO输入端口不确定的信号通过该电阻钳位在高电平或低电平状态。我们知道数字电路有三种状态:高电平、低电平和高阻状态,有些应用场合并不希望出现高阻状态,这时加上拉电阻可钳位在高电平,加下拉电阻可钳位在低电平。

针对单片机GPIO口,有的已经将上拉电阻和下拉电阻集成到芯片内部,只需软件配置即可。

按键独立式接法 - 上拉电阻

下图是GPIO端口外接上拉电阻的按键独立式接法,该接法在按键断开时,GPIO输入检测是高电平,在按键闭合时,GPIO输入检测是低电平。某种意义上,按键按下若定义为有效操作,那么可以把这种接法叫做低电平有效的按键独立式接法。

图5:按键独立式接法原理 - 上拉电阻

当按键S1断开时,不会形成回路,没有电流流过电阻R1和R2,电阻是导体,所以电阻R2两端压降为0V,GPIO端口电压为3.3V,即GPIO输入端可检测到高电平。

当按键S1闭合时,形成回路,此时有电流流过电阻R1和R2。由上图可知在GPIO输入端被检测是高电平还是低电平取决于VF电压值,而VF电压值由R1和R2的阻值决定。R1一个很重要的作用是保护GPIO引脚,从这个角度考虑,R1阻值应该尽可能大一些,而VF电压值是由下面公式决定:

由这个公式可知R1阻值相对于R2阻值需尽可能小才会使得VF电压值越接近0V,这样GPIO输入检测才会为低电平。通常上拉或下拉电阻的选取在1KΩ~10KΩ范围内,在此不做具体计算,依据常规取值R2阻值为10K。根据STM32的手册知道GPIO引脚检测低电平是一个范围,而最大允许的低电平电压是0.8V。假使取值电阻R1为100Ω,这时可以计算出VF电压值约为0.033V,可以满足单片机检测为低电平的要求。同时,100Ω的阻值,也不是很小,可以保护GPIO被误配置成输出高电平的情况。

按键独立式接法 - 下拉电阻

下图是GPIO端口外接下拉电阻的按键独立式接法,该接法在按键断开时,GPIO输入检测是低电平,在按键闭合时,GPIO输入检测是高电平。某种意义上,按键按下若定义为有效操作,那么可以把这种接法叫做高电平有效的按键独立式接法。

图6:按键独立式接法原理 - 下拉电阻

当按键S1断开时,不会形成回路,没有电流流过电阻R1和R2,电阻是导体,所以电阻R2两端压降为0V,GPIO端口电压为0V,即GPIO输入端可检测到低电平。

当按键S1闭合时,形成回路,此时有电流流过电阻R1和R2。由上图可知在GPIO输入端被检测是高电平还是低电平取决于VF电压值,而VF电压值由R1和R2的阻值决定。VF电压值是由下面公式决定:

按照上一节分析,依然选择R1阻值为100Ω, R2阻值为10KΩ,可计算得VF电压值约为3.27V,根据STM32的手册知道GPIO引脚检测高电平是一个范围,而最小允许的高电平电压是2V,所以可以满足单片机检测为高电平的要求。

选择哪种方式来检测按键状态

绝大多数情况下,我们会选择使用上拉电阻的独立式接法来检测按键状态。

微处理器STM32F103ZET6的GPIO引脚在芯片内部有上拉和下拉电阻电路,只需软件配置就可使能选择相应电路。如下图所示。

图7:GPIO内部结构图

按键检测电路考虑因素

按键检测电路设计的时候,需要我们考虑两个方面:按键释放时GPIO口状态的确定和按键检测电路的保护以及按键消抖。

  1. 按键释放时GPIO口状态的确定

按键检测电路中,当按键释放后要能保证GPIO口电平是确定的,即按键释放时GPIO口固定为高电平或低电平。针对微处理器STM32F103ZET6,只需通过软件打开上拉/下拉电阻就可以了,而不需要在片外增加上拉/下拉电阻。

  1. 按键检测电路保护

关于上述关于独立式接法电路中R1的作用就是起到对检测电路的保护,还可以一定程度上消除按键抖动。

  1. 按键硬件消抖

对于按键硬件上的消抖,一般常用的方式是在按键上并接一个容值约0.1uF左右电容,利用电容两端的电压不能突变的特性,消除抖动时产生的毛刺电压。虽然电容可以起到消除抖动的作用,但是在考虑按键灵敏度的情况下,电容时无法完全消除抖动的,消除抖动还需要软件的配合。

实际上串接的电阻R1也起到了一些消抖的作用,再通过软件消抖,对于检测按键状态已经完全足够。

软件设计

GPIO寄存器汇集

STM32F103提供了10个用于操作GPIO的寄存器,如下表所示:

表2:GPIO相关寄存器

序号

寄存器名

读/写

功能描述

1

GPIOx_CRL

读/写

端口配置低寄存器。

2

GPIOx_CRH

读/写

端口配置高寄存器。

3

GPIOx_IDR

只读

端口输入数据寄存器。

4

GPIOx_ODR

读/写

端口输出数据寄存器。

5

GPIOx_BSRR

只写

端口位设置/清除寄存器。

6

GPIOx_BRR

只写

端口位清除寄存器。

7

GPIOx_LCKR

读/写

端口配置锁定寄存器。

8

AFIO_EVCR

读/写

事件控制寄存器。

9

AFIO _MAPR

读/写

复用重映射和调试 I/O 配置寄存器。

10

AFIO _EXTICRy

读/写

外部中断线路 0-15 配置寄存器。

  • 注:x取值:A、B、C、D、E、F、G , y取值:1、2、3、4。

每一种寄存器详细的描述在这里不做具体的介绍,大家可以参考目录:“第1部分:开发板硬件资料”--->“2 - 芯片资料”中“STM32英文参考手册_V15”或“STM32中文参考手册_V10”对应的GPIO章节的寄存器部分认真研读。

GPIO库函数版本编写程序

官方提供的最终库函数版本是V3.5版本,该版本库函数提供了17个与GPIO操作有关的库函数,如下表所示:

表3:GPIO相关库函数汇集

序号

函数名

功能描述

1

GPIO_DeInit

将外设 GPIOx 寄存器重设为缺省值。

2

GPIO_AFIODeInit

将复用功能(重映射事件控制和 EXTI设置)重设为缺省值。

3

GPIO_Init

将GPIO_InitStruct中指定参数初始化外设 GPIOx 寄存器。

4

GPIO_StructInit

把GPIO_InitStruct中的每一个参数按缺省值填入。

5

GPIO_ReadInputDataBit

读取指定端口管脚的输入。

6

GPIO_ReadInputData

读取指定的 GPIO 端口输入。

7

GPIO_ReadOutputDataBit

读取指定端口管脚的输出。

8

GPIO_ReadOutputData

读取指定的 GPIO 端口输出。

9

GPIO_SetBits

设置指定的数据端口位。

10

GPIO_ResetBits

清除指定的数据端口位。

11

GPIO_WriteBit

设置或者清除指定的数据端口位。

12

GPIO_Write

向指定 GPIO 数据端口写入数据。

13

GPIO_PinLockConfig

锁定 GPIO 管脚设置寄存器。

14

GPIO_EventOutputConfig

选择 GPIO 管脚用作事件输出。

15

GPIO_EventOutputCmd

使能或者失能事件输出。

16

GPIO_PinRemapConfig

改变指定管脚的映射。

17

GPIO_EXTILineConfig

选择 GPIO 管脚用作外部中断线路。

  • 注:x取值:A、B、C、D、E、F、G 。

每一种寄存器详细的描述在这里不做具体的介绍,大家可以参考目录:“第1部分:开发板硬件资料”--->“2 - 芯片资料”中“STM32固件库使用手册的中文翻译版”对应的GPIO章节的库函数部分认真研读。


单个按键的输入检测

  • 注:本节对应的实验源码是:“实验2-1:GPIO按键检测”。

工程需要用到的库文件

在使用库函数建“实验2-1:GPIO按键检测”工程时,需要用到的c文件如下表所示。

表4:实验需要用到的C文件

序号

文件名

后缀

功能描述

1

stm32f10x_rcc

.c

复位与时钟控制器。

2

stm32f10x_gpio

.c

通用输入输出。

  • 注:所建工程必须添加上面c文件。

可按下图所示在新建工程时添加需要的c文件。

图12:在新建工程中添加所需库函数c文件

表5:实验需要用到的H文件

序号

文件名

后缀

功能描述

1

stm32f10x_rcc

.h

复位与时钟控制器。

2

stm32f10x_gpio

.h

通用输入输出。

  • 注:所建工程必须添加上面h文件所在的目录:..\..\ Lib\ F10x_FWLIB\ inc。

在使用该实验工程时,需要用到的h文件如表5所示。需要在MDK中点击魔术棒,打开工程配置窗口,按照下图所示添加头文件包含路径。

图13:如何添加头文件包含路径

编写代码

首先是调用库函数完成用户按键S1的初始化配置,初始化代码如下。

代码清单:初始化用户按键S1的GPIO引脚

  1. /***************************************************************************
  2. * 描 述 : 初始化单片机检测按键S1的引脚PE2
  3. * 参 数 : 无
  4. * 返回值 : 无
  5. **************************************************************************/
  6. void key_init(void)
  7. {
  8. //定义IO初始化配置结构体
  9. GPIO_InitTypeDef GPIO_InitStructure;
  10. //打开PE端口时钟
  11. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE);

  12. //配置的IO是PE2
  13. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
  14. //配置为上拉输入
  15. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
  16. //调用库函数GPIO_Init()配置IO
  17. GPIO_Init(GPIOE, &GPIO_InitStructure);
  18. }

然后,在主函数中加入对按键GPIO口PE2检测的函数,一旦检测到PE2为低电平,先软件上进行按键消抖设计,再将用户指示灯D1点亮。若按键松开,则检测到PE2为高电平,则将用户指示灯D1熄灭。

代码清单:主函数

  1. int main(void)
  2. {
  3. //初始化用于驱动指示灯D1、D2、D3、D4的引脚PG6、PA8、PB8、PB9
  4. leds_init();
  5. //初始化用于检测按键S1的引脚PE2
  6. key_init();

  7. //主循环
  8. while(1)
  9. {
  10. //调用库函GPIO_ReadInputDataBit()检测按键S1对应引脚PE2的电平是否为低电平
  11. //(可参考开发板原理图易知当按键S1按下时,PE2与GND相连,即PE2为低电平)
  12. if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2) == 0 )
  13. {
  14. //软件延时10ms,如果延时后按键S1的电平依然没有变化,说明按键确实被有效操作,简称按键消抖
  15. sw_delay_ms(10);
  16. //检测按键S1对应引脚PE2的电平依然为低电平,即按键S1确实被按下
  17. if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2) == 0 )
  18. {
  19. //点亮用户指示灯D1
  20. led_on(LED_1);
  21. //等待按键S1释放,即如果PE2一直为低电平,会一直执行空命令
  22. while(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2) == 0 )
  23. {
  24. ; //条件GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2) == 0成立,会执行这个空命令
  25. }
  26. //按键S1松开,会执行下一行语句,即熄灭用户指示灯D1
  27. led_off(LED_1);
  28. }
  29. }
  30. }
  31. }

实验步骤

  1. 解压“…\第3部分:标准库教程和实验源码\ 1 - 基础实验程序\”目录下的压缩文件“实验2-1:GPIO按键检测”,将解压后得到的文件夹拷贝到合适的目录,如“D\STM32F103ZET6”。
  2. 启动MDK5.23。
  3. 在MDK5中执行“Project→Open Project”打开“…\ key \projec”目录下的工程“key.uvproj”。
  4. 点击编译按钮编译工程。注意查看编译输出栏,观察编译的结果,如果有错误,修改程序,直到编译成功为止。编译后生成的HEX文件“key.hex”位于工程目录下的“Objects”文件夹中。
  5. 点击下载按钮下载程序 。如果需要对程序进行仿真,点击Debug按钮,即可将程序下载到STM32F103ZET6中进行仿真。
  6. 程序运行后,可观察到操作用户按键S1对应用户指示灯D1状态会有变化。

多个按键的输入检测

  • 注:本节对应的实验源码是:“实验2-2:多个按键的检测”。

工程需要用到的库文件

在使用库函数建“实验2-2:多个按键的检测”工程时,需要用到的c文件以及添加头文件包含路径的方法与介绍“实验2-1:GPIO按键检测”完全一样,在此不再赘述。

编写代码

首先在建立“实验2-2:多个按键的检测”工程时,新建了key.c的文件,专门用于封装和4个用户KEY有关的所有函数。具体如下表所示。

表6:用户按键相关函数汇集(详见key.c)

序号

函数名

功能描述

1

keys_init

初始化开发板上4个用户按键的引脚。

2

keys_scan

检测开发板哪个用户按键按下。

  • 注:keys_scan函数有出参,出参值有:KEY1_ON、KEY2_ON、KEY3_ON、KEY4_ON、KEYS_OFF。

代码清单:初始化用户按键S1、S2、S3、S4的GPIO引脚

  1. /*******************************************************************************
  2. * 描 述 : 初始化单片机检测按键S1的引脚PE2、按键S2的引脚PE3、按键S3的引脚PE4、按键S4的引脚PE5
  3. * 参 数 : 无
  4. * 返回值 : 无
  5. ******************************************************************************/
  6. void keys_init(void)
  7. {
  8. //定义IO初始化配置结构体
  9. GPIO_InitTypeDef GPIO_InitStructure;
  10. //打开PE端口时钟
  11. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE);

  12. //配置的IO是PE2、PE3、PE4、PE5
  13. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5;
  14. //配置为上拉输入
  15. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
  16. //调用库函数GPIO_Init()配置IO
  17. GPIO_Init(GPIOE, &GPIO_InitStructure);
  18. }


代码清单:检测用户按键S1、S2、S3、S4哪一个被按下

  1. /***************************************************************************
  2. * 描 述 : 检测开发板上的4个按键(S1 S2 S3 S4)
  3. * 参 数 : 无
  4. * 返回值 : 按键编号
  5. ***************************************************************************/
  6. uint8_t keys_scan(void)
  7. {
  8. //调用库函GPIO_ReadInputDataBit()检测按键S1、S2、S3、S4对应引脚PE2、PE3、PE4、PE5的电平是否为低电平
  9. if((GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2) == KEY_ON ) || (GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3) == KEY_ON ) || (GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4) == KEY_ON ) || (GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_5) == KEY_ON ) )
  10. {
  11. //软件延时10ms,如果延时后按键S1的电平依然没有变化,说明按键确实被有效操作,简称按键消抖
  12. sw_delay_ms(10);
  13. if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2) == KEY_ON )
  14. {
  15. return KEY1_ON;
  16. }
  17. else if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3) == KEY_ON )
  18. {
  19. return KEY2_ON;
  20. }
  21. else if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4) == KEY_ON )
  22. {
  23. return KEY3_ON;
  24. }
  25. else if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_5) == KEY_ON )
  26. {
  27. return KEY4_ON;
  28. }
  29. }
  30. else if((GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2) == KEY_OFF )&&(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3) == KEY_OFF ) &&(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4) == KEY_OFF )&&(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_5) == KEY_OFF ) )
  31. {
  32. ;
  33. }

  34. return KEYS_OFF;
  35. }

在main.c中,主函数调用led.c和key.c中的函数,实现一个按键按下对应用户指示灯被点亮。具体代码如下。

代码清单:主函数

  1. int main(void)
  2. {
  3. uint8_t temp;
  4. //初始化用于驱动指示灯D1、D2、D3、D4的引脚PG6、PA8、PB8、PB9
  5. leds_init();
  6. //初始化用于检测按键S1、S2、S3、S4的引脚PE2、PE3、PE4、PE5
  7. keys_init();

  8. //主循环
  9. while(1)
  10. {
  11. temp=keys_scan(); //获取开发板按键检测值
  12. if(temp == KEY1_ON) //如果按键S1被操作
  13. {
  14. led_toggle(LED_1); //控制用户指示灯D1翻转
  15. }
  16. else if(temp == KEY2_ON) //如果按键S2被操作
  17. {
  18. led_toggle(LED_2); //控制用户指示灯D2翻转
  19. }
  20. else if(temp == KEY3_ON) //如果按键S3被操作
  21. {
  22. led_toggle(LED_3); //控制用户指示灯D3翻转
  23. }
  24. else if(temp == KEY4_ON) //如果按键S4被操作
  25. {
  26. led_toggle(LED_4); //控制用户指示灯D4翻转
  27. }

  28. }
  29. }

实验步骤

  1. 解压“…\第3部分:标准库教程和实验源码\ 1 - 基础实验程序\”目录下的压缩文件“实验2-2:多个按键的检测”,将解压后得到的文件夹拷贝到合适的目录,如“D\STM32F103ZET6”。
  2. 启动MDK5.23。
  3. 在MDK5中执行“Project→Open Project”打开“…\ key \projec”目录下的工程“key.uvproj”。
  4. 点击编译按钮编译工程。注意查看编译输出栏,观察编译的结果,如果有错误,修改程序,直到编译成功为止。编译后生成的HEX文件“key.hex”位于工程目录下的“Objects”文件夹中。
  5. 点击下载按钮下载程序 。如果需要对程序进行仿真,点击Debug按钮,即可将程序下载到STM32F103ZET6中进行仿真。
  6. 程序运行后,可观察到操作用户按键S1对应用户指示灯D1状态会有变化,操作用户按键S2对应用户指示灯D3状态会有变化,操作用户按键S3对应用户指示灯D3状态会有变化,操作用户按键S4对应用户指示灯D4状态会有变化。

Tags:

最近发表
标签列表