【GPIO】按键控制小灯

学习如何使用GPIO的输入功能实现通过开发板上的按键来控制小灯

新建一个key的工程

本次学习要实现的效果是,当按住开发板上的按键KEY1不松开时,开发板上的绿色小灯亮起,松开KEY1时,绿色小灯熄灭,按一下开发板上的KEY2按键开发板上的红色小灯亮起,再按一下KEY2按键,红色小灯熄灭,既每按一次KEY2 红色小灯的亮灭状态都翻转一次。

开发板上按键原理图如图所示:

简单分析,首先与按键并联的电容是为了消除按键抖动,

所谓按键抖动就是指按键中的机械触点在接触或者断开的瞬间,在很小的时间片段上处于一种似连非连,似断非断的状态。

这个时间片段往往几毫秒,虽然按下按键的我们完全无法感知,但对于处理速度在微秒级的单片机而言,却是很长的一段时间,因而在这段时间中,与按键相连的GPIO口会收接收到一段非常抖动的电平信号,高高低低 起起伏伏,令我们的单片机程序无法正确分辨当前的电平。

而在开发板上按键并联上电容,可以比较有效地缓冲抖动的电平信号,从硬件上解决此问题的发生

忽略电容,电路得到进一步简化,

先来分析KEY1,PB12是我们用来获取KEY1按键状态的GPIO引脚,当按键处于松开状态时,此处电路也就处于断路状态。

我们将其隐去,此时PB12直接通过一个电阻连接到了3.3V电源上

接下来我们在工程中设置PB12时会将PB12设置为GPIO八大模式之一的浮空输入模式,而浮空输入模式下的GPIO口内部处于高阻态,就是说在芯片内部相当于有一个巨大的电阻,电阻越大分压越多,此处的10K电阻几乎吃不到多少电压,也就没有多少压降(压降几乎为0),PB12处也就几乎是3.3V,因而我们使用STM32程序读取到的PB12的电平值也就是高电平。

使用电源将GPIO口处的电平拉高的操作,我们将其称为上拉,由于上拉操作通常需要一个电阻来配合,因而我们有时也称这种操作为"加个上拉电阻"

当按键按下时,通路形成,相当于一根导线,PB12直接与GND连通,因而PB12读取到的就是GND的电平,也就是0V的低电平

有上拉操作便有下拉操作,将上拉电路中的3.3V与GND进行交换,便得到了下拉的按键电路,其对于GPIO口读取电平的影响与上拉正好相反

至此我们了解到了在开发板上,KEY1是上拉的,当按键处于松开的状态时,PB12读取的是高电平,而当我们按下按键时,PB12读取到的是低电平。

回到Cube IDE,首先根据原理图把需要用到的红绿小灯设置一下,都设置为GPIO_Output,别忘了右键给他们加上用户标签。然后找到我们的主角KEY1对应的GPIO口PB12,将其设置为GPIO_Input,代表其是一个用于输入的IO口,给其添加用户标签KEY1,然后我们Ctrl+s保存,代码生成完毕,接下来实现按下KEY1绿灯亮,松开KEY1绿灯灭的逻辑。

找到main函数的while无限循环,在USER CODE BEGIN WHILEUSER CODE END WHILE中,来写一个新的函数HAL_GPIO_ReadPin

复制代码
GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);

参数:
GPIOx:指向GPIO端口的指针,例如GPIOA、GPIOB等。
GPIO_Pin:指定要读取的引脚编号,例如GPIO_PIN_0、GPIO_PIN_1等。

返回值:
GPIO_PIN_SET:表示引脚状态为高电平(逻辑值1)。
GPIO_PIN_RESET:表示引脚状态为低电平(逻辑值0)。

别忘了使用Alt+/的代码提示功能,他前两个参数与HAL_GPIO_WritePin函数的前两个参数相同,第一个是GPIO的分组,我们使用用户标签来写KEY1_GPIO_Port,第二个参数是GPIO的编号,同样也使用用户标签KEY1_Pin,这句代码的含义是,读取KEY1所代表的GPIO口当前电平状态.

复制代码
HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin);

按住键盘的Ctrl键,同时点击函数,我们就来到了这个函数的实现页面,可以发现HAL_GPIO_ReadPin的返回值类型是GPIO_PinState,我们再按住Ctrl点击GPIO_PinState来到它的定义,会发现它是一个枚举类型,有两个取值,而这两个取值恰好就是我们之前提到的:高电平GPIO_PIN_SET和低电平GPIO_PIN_RESET,其中GPIO_PIN_RESET的值被定义为0.

回到我们自己的代码界面,现在我们知道了HAL_GPIO_ReadPin的返回值就是这个IO口当前是高电平还是低电平,高电平就返回GPIO_PIN_SET低电平就返回GPIO_PIN_RESET,所以我们可以写下这样的判断逻辑,当读取KEY1的电平为低电平时,设置绿色小灯的GPIO口为高电平,点亮小灯,反之,熄灭小灯.

复制代码
if(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin) == GPIO_PIN_RESET)
{
    HAL_GPIO_WritePin(LED_GREEN_GPIO_Port,LED_GREEN_Pin,GPIO_PIN_SET);
}
else
{
    HAL_GPIO_WritePin(LED_GREEN_GPIO_Port,LED_GREEN_Pin,GPIO_PIN_RESET);
}

将代码编译下载到开发板,就可以查看效果了.

回到原理图,通过之前的讲解我们知道PB12之所以能检测按键的按下与否,上拉电阻功不可没,而且上拉下拉的操作也非常常见,因而STM32为我们再芯片内部提前准备好了上拉与下拉电阻,如果我们采用内部的上拉电阻,就可以在电路上省略掉我们自己的上拉电阻,也就是KEY2的这种形式.

回到Cube IDE的芯片配置界面,将KEY2对应的GPIO口PB13也设置为GPIO_Input,并且设置用户标签

随后来到System Core下的GPIO,点击PB13查看详细设置,可以看到GPIO Pull-up/Pull-down,也就是GPIO口在芯片内部的上拉下拉,默认为no pull-up and no pull-down,不上拉也不下拉,也就是我们所说的浮空输入模式,也就是说刚刚KEY1对应的PB12一直处于浮空输入模式。

而对于没有外部上拉的KEY2对应的PB13来说,我们需要在此启用上拉输入模式Pull-upCtrl+S保存并生成代码

回到main函数中,我们来写每次按下KEY2红色小灯就翻转亮灭的代码,我们知道不按下按键时KEY2读取到的是高电平,按下按键时就会读到低电平,因而我们先写下每当KEY2读到低电平就让红灯亮起。

复制代码
if(HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin) == GPIO_PIN_RESET)
{
    HAL_GPIO_WritePin(LED_RED_GPIO_Port,RED_Pin,GPIO_PIN_SET);
}

可这并不是我们想要的,我们还想要再次读到低电平时就熄灭红色小灯,因此我们可以使用HAL_GPIO_TogglePin函数

复制代码
void HAL_GPIO_TogglePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
参数:
GPIOx:指向GPIO端口的指针,例如 GPIOA、GPIOB 等。
GPIO_Pin:指定要切换的引脚编号,例如 GPIO_PIN_0、GPIO_PIN_1 等。

其两个参数与HAL_GPIO_WritePin的前两个参数相同,其作用是翻转GPIO口的输出电平,例如当前某GPIO口若输出高电平,则运行此函数后就输出低电平,反之亦然。

复制代码
if(HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin) == GPIO_PIN_RESET)
{
    HAL_GPIO_TogglePin(LED_RED_GPIO_Port,RED_Pin);
}

删掉WritePin函数,这样我们想要的效果就实现了吗?若此时我们将程序下载到开发板中尝试一下,你会发现有一些奇怪,红色小灯好像不受控制,按下KEY2后的亮灭变化好像有些紊乱,这实际是因为我们在安歇KEY2后尚未松开的这段时间中,while循环中的代码不断地一遍又一遍地执行,每当运行到我们判断KEY2的代码时,都会读取到KEY2是低电平,再次将红色小灯翻转,为了解决这一问题,一个简单但有效的方案就是等!我们在读取到KEY2按下后,就开始一个while循环死等,循环条件为KEY2为低电平,直到按键被松开循环才会结束,代码才能继续执行。编译下载看看效果.

复制代码
if(HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin) == GPIO_PIN_RESET)
{
    HAL_GPIO_TogglePin(LED_RED_GPIO_Port,RED_Pin);
    while(HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin) == GPIO_PIN_RESET);
}

可以看到这次效果就非常明显了。

补充说明:

由于开发板采用的是比较稳定的硅胶按键,而且选用的消抖电容也比较合适,所以硬件的消抖效果非常的棒,我们很轻松就能实现我们的目标,但是对于某些稍微差一点的按键来说,可能硬件消抖并不能完全去除抖动,因而我们通常需要在软件中再次进行消抖,常见软件消抖的逻辑是这样的,当初次检测到低电平后,进行一个10ms的延时,由于抖动通常在10ms之内便会结束,因而10ms后再读取按键的电平便是稳定电平,若此时按键还是按下状态,则按照之前的逻辑翻转红色小灯并等待按键结束

复制代码
if(HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin) == GPIO_PIN_RESET)
{
    HAL_Delay(10);
    if(HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin) == GPIO_PIN_RESET)
    {
        HAL_GPIO_TogglePin(LED_RED_GPIO_Port,RED_Pin);
        while(HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin) == GPIO_PIN_RESET);
    }
}

再次编译下载,效果与刚刚相同。

完整代码:

复制代码
if(HAL_GPIO_ReadPin(KEY1_GPIO_Port,KEY1_Pin) == GPIO_PIN_RESET)
{
    HAL_GPIO_WritePin(LED_GREEN_GPIO_Port,LED_GREEN_Pin,GPIO_PIN_SET);
}
else
{
    HAL_GPIO_WritePin(LED_GREEN_GPIO_Port,LED_GREEN_Pin,GPIO_PIN_RESET);
}
if(HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin) == GPIO_PIN_RESET)
{
    HAL_Delay(10);
    if(HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin) == GPIO_PIN_RESET)
    {
        HAL_GPIO_TogglePin(LED_RED_GPIO_Port,RED_Pin);
        while(HAL_GPIO_ReadPin(KEY2_GPIO_Port,KEY2_Pin) == GPIO_PIN_RESET);
    }
}
相关推荐
水云桐程序员1 小时前
单片机:定时器/PWM 配置 - 呼吸灯效果
单片机·嵌入式硬件·mongodb
水云桐程序员1 小时前
单片机:新建第一个工程,点亮LED
单片机·嵌入式硬件
华芯微特SYNWIT1 小时前
SWM221 Cortex-M0系列MCU环境配置
单片机·嵌入式硬件
普中科技1 小时前
【普中 51-Ai8051 开发攻略】-- 第 12 章 LED 点阵实验-显示字符
单片机·嵌入式硬件·开发板·led点阵屏·普中科技·ai8051u·aicube
进击的小头2 小时前
第11篇:TI DSP芯片中断系统详解:PIE架构、配置实战与实时性优化
单片机·嵌入式硬件
2401_832635583 小时前
Spring Data MongoDB 最佳实践:如何构建高效数据访问层
java·mongodb·spring
Hello_Embed4 小时前
嵌入式上位机开发入门(二十四):Paho MQTT 嵌入式客户端源码分析
网络·单片机·网络协议·tcp/ip·嵌入式
yrx02030713 小时前
串口空闲中断+DMA接收+环形缓冲区 && 串口DMA发送+环形缓冲区
stm32·单片机
LCG元14 小时前
STM32实战:基于STM32F103的4G模块(EC20)HTTP通信
stm32·嵌入式硬件·http