系列文章目录
STM32之GPIO(General Purpose Input/Output,通用型输入输出)
GPIO输出控制之LED闪烁、LED流水灯以及蜂鸣器应用案例
文章目录
- 系列文章目录
- 前言
- 一、按键简介
- 二、传感器模块简介
- 2.1 AO模拟量输出模块
- 2.2 DO数字量输出模块
- 2.3 指示灯
- 三、按键硬件电路
- 3.1 下接按键方式
- 3.2 上接按键方式
- 四、传感器硬件电路
- 五、GPIO输入模式应用案例
- 5.1 按键控制LED
- 5.1.1 电路接线以及原理图
- 5.1.2 应用案例代码
- 5.1.3 应用案例分析
- 5.2 光敏传感器控制蜂鸣器
- 5.2.1 电路接线图
- 5.2.2 应用案例代码
- 5.2.3 应用案例分析
前言
提示:本文主要用作在学习江协科大STM32入门教程后做的归纳总结笔记,旨在学习记录,如有侵权请联系作者
本文主要探讨stm32的GPIO输入模式,最后通过两个简单的应用案例(按键控制LED以及光敏传感器控制蜂鸣器)实现了GPIO输入状态的监测。
一、按键简介
- 按键:常见的输入设备,按下导通,松手断开。
- 按键抖动:由于按键内部使用的是机械式弹簧片来进行通断的,所以在按下和松手的瞬间会伴随有一连串的抖动。
二、传感器模块简介
如下图所示,从左到右分别是光敏电阻传感器、热敏电阻传感器、对射式红外传感器以及反射式红外传感器。
这些传感器模块都是利用传感器元件比如光敏电阻、热敏电阻、红外接收管等,这些元件的电阻会随外界的模拟量变化而变化。比如光线越强,光敏电阻的阻值就会越小。温度越高,热敏电阻的阻值就越小。红外光线越强,红外接收管的阻值就越小。
但是电阻的变化不容易直接被观察,所以我们通常将传感器元件与定值电阻进行串联分压,这样就可以得到模拟电压的输出了,对电路来说,检测电压就非常容易了。另外,这个模块还可以通过电压比较器来对这个模拟电压进行二值化,这样就可以得到数字电压输出了。
上图所示为传感器模块的基本电路图,我们先来看一下下图这部分电路(AO口为模拟量输出,DO口为数字量输出)
2.1 AO模拟量输出模块
电路中的N1就是传感器元件所代表的可变电阻,它的阻值可以根据环境的光线、温度等模拟量进行变化。上面这个R1是和N1进行分压的定值电阻,R1和N1进行串联,一端接到VCC正极,另一端接到GND负极,这就构成了基本的分压电路。
左边这个C2是一个滤波电容,它是为了给中间的电压输出进行滤波的,用来滤除一些干扰,保证输出电压波形的平滑。一般我们在电路里遇到这种一端接在电路中,另一端接地的电容,都可以考虑一下这个是不是滤波电容的作用。如果是滤波电容的作用,那这个电容就是用来保证电路稳定的,并不是电路的主要框架。那这时我们在分析电路的时候就可以先把这个电容抹掉,这样就可以使我们的电路分析变得更加简单了。那我们把这个电容抹掉,整个电路的主要框架就是定值电阻和传感器电阻的分压电路了。
在这里可以用分压定理来分析一下传感器电阻的阻值变化对输出电压的影响,当然我们还可以用上下拉电阻的思维来分析。当这个N1阻值变小时,下拉作用就会增强,中间的AO端的电压就会拉低。极端情况下,N1阻值为0,AO输出被完全下拉,输出0V。当N1阻值变大,下拉作用就会减弱,中间的引脚由于R1的上拉作用,电压就会升高。极端情况下,N1阻值无穷大,相当于断路,输出电压被R1拉高至VCC。这就是用上下拉电阻的思维来分析的。
2.2 DO数字量输出模块
在这两个电阻的分压下,AO就是我们想要的模拟电压输出了。这个模块还支持数字输出,这个数字输出就是对AO进行二值化的输出。这里的二值化是通过LM393芯片来完成的,如下图所示就是数字输出模块电路。
这个LM393是一个电压比较器芯片,里面有两个独立的电压比较器电路,然后剩下的是VCC和GND供电,那我们VCC就接到电路的VCC,GND也接到了电路的GND,这里有个电容,是一个电源供电的滤波电容。这个电压比较器其实就是一个运算放大器,当同相输入端电压大于反相输入端电压时,输出就会瞬间升高为最大值,也就是输出接VCC。反之,当同相输入端电压小于反相输入端电压时,输出就会瞬间降低为最小值,也就是输出接GND。这样就可以对一个模拟电压进行二值化了。
我们来看一下实际的应用,如上图所示。这里的同相输入端IN+接到了模拟电压端AO这里的IN+,而反相输入端IN-这里接了一个电位器,这个电位器的接法也是分压电阻的原理。拧动电位器,IN-就会生成一个可调的阙值电压,两个电压进行比较,最终输出结果就是DO,也就是数字电压输出。DO最终就接到了引脚的输出端,这就是数字电压的由来。
2.3 指示灯
这里还有两个指示灯电路,左边这个是电源指示灯,通电就会亮。右边的是DO输出指示灯,它可以指示DO的输出电平,低电平点亮,高电平熄灭。那右边DO这里还多了一个R5上拉电阻,这个是为了保证默认输出为高电平的。然后就是P1的排针,分别是VCC、GND、DO以及AO。
对于光敏电阻传感器来说,这个N1就是光敏电阻。对于热敏电阻传感器来说,这个N1就是热敏电阻。对于这个红外传感器来说,这个N1就是一个红外接收管,当然对应还会有一个点亮红外发射管的电路。
三、按键硬件电路
按键有四种接法,上面两个是下接按键的方式,下面两个是上接按键的方式。一般来说我们的按键都是采用下接按键的方式, 也就是上面的两种接法。这个原因跟LED的接法类似,是电路设计的习惯和规范。
3.1 下接按键方式
我们先来看一下左上角第一个图,这种接法是按键最常用的接法了。在这里随便选取一个GPIO口,比如PA0,然后通过K1接到地。当按键按下时,PA0被直接下拉到GND,此时读取PA0口的电压就是低电平。当按键松手时,PA0被悬空,悬空会出现什么情况呢?就是引脚的电压不确定对吧。所以在这种接法下,必须要求PA0是上拉输入的模式,否则就会出现引脚电压不确定的错误现象。如果PA0是上拉输入的模式,当引脚悬空时,PA0就是高电平。所以这种方式下,按下按键,引脚为低电平。松手,引脚为高电平。
接着再来看一下第二个图,相比较第一个图,在这里外部接了一个上拉电阻。这个上拉电阻大家可以想象成一个弹簧把这个端口往屋顶拉。当按键松手时,引脚由于上拉作用,自然保持为高电平。当按键按下时,引脚直接接到GND,也就是一股无穷大的力把这个引脚往下拉,那弹簧肯定对抗不了无穷大的力啊,所以引脚就为低电平。这种状态下引脚不会出现悬空状态,所以此时PA0引脚可以配置为浮空输入或者上拉输入。如果是上拉输入,那就是内外两个上拉电阻共同作用了,这时高电平就会更强一些,对应高电平更加稳定。当然这样的话,当引脚被强行拉到低时,损耗也就会大一些。
3.2 上接按键方式
那接下来看第三个图,PA0通过按键接到3.3V,这样也是可以的,不过要求PA0必须配置成下拉输入模式。当按键按下时,引脚为高电平,松手时,引脚回到默认值低电平。 注意:这要求单片机的引脚可以配置为下拉输入的模式,一般单片机可能不一定有下拉输入的模式,所以最好还是用上面的两种下接按键的方式,下面两种作为扩展部分,大家了解一下即可。
那最后一种接法,就是在刚才的这种接法下面再外接一个下拉电阻,这个接法PA0需要配置为下拉输入模式或者浮空输入模式。至于分析大家应该已经清楚了,这里就不再细讲了。
总结: 上面这两种接法是,按键按下,引脚为低电平,松手,引脚为高电平。下面这两种接法是,按键按下,引脚为高电平,松手,引脚为低电平。左边两种接法必须要求引脚是上拉或者是下拉输入模式,右边两种接法可以允许引脚是浮空输入的模式,因为已经外置了上拉电阻和下拉电阻。一般我们都用上面两种接法,下面两种接法用得较少。
四、传感器硬件电路
这里VCC接3.3V,GND接地,用于供电。DO数字输出随便接一个端口比如PA0,用于读取数字量。AO模拟输出暂时不接,等到学ADC模数转换器时再说。
五、GPIO输入模式应用案例
5.1 按键控制LED
本案例实现了一个通过按键控制LED灯亮灭的功能,两个按键分别接到GPIOB的PB1和PB11端口,分别控制接到GPIOA的PA1和PA2的LED亮灭。按下按键,LED点亮,再按下按键,LED熄灭。
5.1.1 电路接线以及原理图
5.1.2 应用案例代码
延时头文件Delay.h:
#ifndef __DELAY_H
#define __DELAY_H
void Delay_us(uint32_t us);
void Delay_ms(uint32_t ms);
void Delay_s(uint32_t s);
#endif
延时实现文件Delay.c:
#include "stm32f10x.h"
/**
* @brief 微秒级延时
* @param xus 延时时长,范围:0~233015
* @retval 无
*/
void Delay_us(uint32_t xus)
{
SysTick->LOAD = 72 * xus; //设置定时器重装值
SysTick->VAL = 0x00; //清空当前计数值
SysTick->CTRL = 0x00000005; //设置时钟源为HCLK,启动定时器
while(!(SysTick->CTRL & 0x00010000)); //等待计数到0
SysTick->CTRL = 0x00000004; //关闭定时器
}
/**
* @brief 毫秒级延时
* @param xms 延时时长,范围:0~4294967295
* @retval 无
*/
void Delay_ms(uint32_t xms)
{
while(xms--)
{
Delay_us(1000);
}
}
/**
* @brief 秒级延时
* @param xs 延时时长,范围:0~4294967295
* @retval 无
*/
void Delay_s(uint32_t xs)
{
while(xs--)
{
Delay_ms(1000);
}
}
LED头文件LED.h:
#ifndef __LED_H
#define __LED_H
void LED_Init(void);
void LED1_ON(void);
void LED1_OFF(void);
void LED1_Turn(void);
void LED2_ON(void);
void LED2_OFF(void);
void LED2_Turn(void);
#endif
LED实现文件LED.c:
#include "stm32f10x.h" // Device header
void LED_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA, GPIO_Pin_1 | GPIO_Pin_2);
}
void LED1_ON(void)
{
GPIO_ResetBits(GPIOA, GPIO_Pin_1);
}
void LED1_OFF(void)
{
GPIO_SetBits(GPIOA, GPIO_Pin_1);
}
void LED1_Turn(void)
{
if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_1) == 0)
{
GPIO_SetBits(GPIOA, GPIO_Pin_1);
}
else
{
GPIO_ResetBits(GPIOA, GPIO_Pin_1);
}
}
void LED2_ON(void)
{
GPIO_ResetBits(GPIOA, GPIO_Pin_2);
}
void LED2_OFF(void)
{
GPIO_SetBits(GPIOA, GPIO_Pin_2);
}
void LED2_Turn(void)
{
if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_2) == 0)
{
GPIO_SetBits(GPIOA, GPIO_Pin_2);
}
else
{
GPIO_ResetBits(GPIOA, GPIO_Pin_2);
}
}
按键头文件Key.h:
#ifndef __KEY_H
#define __KEY_H
void Key_Init(void);
uint8_t Key_GetNum(void);
#endif
按键实现文件Key.c:
#include "stm32f10x.h" // Device header
#include "Delay.h"
void Key_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
uint8_t Key_GetNum(void)
{
uint8_t KeyNum = 0;
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
{
Delay_ms(20);
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0);
Delay_ms(20);
KeyNum = 1;
}
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0)
{
Delay_ms(20);
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0);
Delay_ms(20);
KeyNum = 2;
}
return KeyNum;
}
主程序文件main.c:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "LED.h"
#include "Key.h"
uint8_t KeyNum;
int main(void)
{
LED_Init();
Key_Init();
while (1)
{
KeyNum = Key_GetNum();
if (KeyNum == 1)
{
LED1_Turn();
}
if (KeyNum == 2)
{
LED2_Turn();
}
}
}
5.1.3 应用案例分析
先来看一下LED初始化函数:
void LED_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA, GPIO_Pin_1 | GPIO_Pin_2);
}
- 开启GPIOA的时钟。
- 配置PA1和PA2为推挽输出模式,速度为50MHz。
- 初始化PA1和PA2引脚,并设置为高电平(即关闭LED灯)。
LED状态切换函数:
void LED1_Turn(void)
{
if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_1) == 0)
{
GPIO_SetBits(GPIOA, GPIO_Pin_1);
}
else
{
GPIO_ResetBits(GPIOA, GPIO_Pin_1);
}
}
void LED2_Turn(void)
{
if (GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_2) == 0)
{
GPIO_SetBits(GPIOA, GPIO_Pin_2);
}
else
{
GPIO_ResetBits(GPIOA, GPIO_Pin_2);
}
}
- 读取PA1或PA2引脚的状态,如果为低电平(LED灯亮),则设置为高电平(LED灯灭),反之亦然。
再来看一下按键初始化函数:
void Key_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
- 开启GPIOB的时钟。
- 配置PB1和PB11为上拉输入模式,速度为50MHz。
注意:由于是按键下接方式,为了保证按键按下时为低电平,松开是高电平,因此必须要将按键的引脚配置为上拉输入模式以保证引脚在悬空时为高电平,在上面按键的接法原理分析时已经讲过这一点了。
获取按键编号的函数:
uint8_t Key_GetNum(void)
{
uint8_t KeyNum = 0;
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0)
{
Delay_ms(20);
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == 0);
Delay_ms(20);
KeyNum = 1;
}
if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0)
{
Delay_ms(20);
while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11) == 0);
Delay_ms(20);
KeyNum = 2;
}
return KeyNum;
}
- 检测PB1和PB11按键是否按下。
- 如果检测到按键按下,进行20ms的去抖动延迟,之后再次检测按键状态。
- 根据按键的状态,返回按键编号1或2。
最后看下主程序整体结构:
int main(void)
{
LED_Init();
Key_Init();
while (1)
{
KeyNum = Key_GetNum();
if (KeyNum == 1)
{
LED1_Turn();
}
if (KeyNum == 2)
{
LED2_Turn();
}
}
}
- 初始化LED和按键。
- 在主循环中,持续检测按键状态。
- 根据检测到的按键编号,调用相应的LED状态切换函数。
5.2 光敏传感器控制蜂鸣器
本案例实现了一个通过光敏电阻传感器控制蜂鸣器的功能。蜂鸣器I/O控制引脚接到GPIOB的PB12口,光敏电阻传感器DO输出引脚接到GPIOB的PB13口。当我们遮住光线时,输出指示灯灭,代表输出高电平。当我们松手时,输出指示灯亮,代表输出低电平。
5.2.1 电路接线图
5.2.2 应用案例代码
蜂鸣器头文件Buzzer.h:
#ifndef __BUZZER_H
#define __BUZZER_H
void Buzzer_Init(void);
void Buzzer_ON(void);
void Buzzer_OFF(void);
void Buzzer_Turn(void);
#endif
蜂鸣器实现文件Buzzer.c:
#include "stm32f10x.h" // Device header
void Buzzer_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB, GPIO_Pin_12);
}
void Buzzer_ON(void)
{
GPIO_ResetBits(GPIOB, GPIO_Pin_12);
}
void Buzzer_OFF(void)
{
GPIO_SetBits(GPIOB, GPIO_Pin_12);
}
void Buzzer_Turn(void)
{
if (GPIO_ReadOutputDataBit(GPIOB, GPIO_Pin_12) == 0)
{
GPIO_SetBits(GPIOB, GPIO_Pin_12);
}
else
{
GPIO_ResetBits(GPIOB, GPIO_Pin_12);
}
}
光敏传感器模块头文件LightSensor.h:
#ifndef __LIGHT_SENSOR_H
#define __LIGHT_SENSOR_H
void LightSensor_Init(void);
uint8_t LightSensor_Get(void);
#endif
光敏传感器模块实现文件LightSensor.c:
#include "stm32f10x.h" // Device header
#include "Delay.h"
void LightSensor_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
uint8_t LightSensor_Get(void)
{
uint8_t state = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_13);
Delay_ms(10); // 短暂延迟
if (state == GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_13)) {
return state;
} else {
return !state;
}
}
主程序文件main.c:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "Buzzer.h"
#include "LightSensor.h"
int main(void)
{
Buzzer_Init();
LightSensor_Init();
while (1)
{
if (LightSensor_Get() == 1)
{
Buzzer_ON();
}
else
{
Buzzer_OFF();
}
}
}
5.2.3 应用案例分析
先来看一下蜂鸣器初始化函数:
void Buzzer_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB, GPIO_Pin_12);
}
- 启用GPIOB的时钟。
- 将PB12配置为推挽输出模式,速度为50MHz。
- 初始化PB12,并设置为高电平,确保蜂鸣器初始状态为关闭(PNP型三极管驱动,低电平触发)。
蜂鸣器控制函数:
void Buzzer_ON(void)
{
GPIO_ResetBits(GPIOB, GPIO_Pin_12);
}
void Buzzer_OFF(void)
{
GPIO_SetBits(GPIOB, GPIO_Pin_12);
}
- Buzzer_ON: 将PB12设置为低电平,打开蜂鸣器。
- Buzzer_OFF: 将PB12设置为高电平,关闭蜂鸣器。
再来看一下光敏传感器初始化函数:
void LightSensor_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
- 启用GPIOB的时钟。
- 将PB13配置为上拉输入模式(IPU),速度为50MHz。
光敏传感器获取状态函数:
uint8_t LightSensor_Get(void)
{
uint8_t state = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_13);
Delay_ms(10); // 短暂延迟
if (state == GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_13)) {
return state;
} else {
return !state;
}
}
通过读取GPIOB的PB13引脚的电平状态,结合延迟和重复读取,返回光敏电阻传感器的稳定状态。
最后看一下主函数:
int main(void)
{
Buzzer_Init();
LightSensor_Init();
while (1)
{
if (LightSensor_Get() == 1)
{
Buzzer_ON();
}
else
{
Buzzer_OFF();
}
}
}
- 初始化蜂鸣器和光敏电阻传感器。
- 进入无限循环,不断检查光敏电阻传感器的输出。
- 如果传感器输出为高电平(光线被遮挡),打开蜂鸣器。
- 如果传感器输出为低电平(光线正常),关闭蜂鸣器。
整体代码实现了根据光敏电阻传感器状态对蜂鸣器进行控制的功能。