【STM32】GPIO(超详细)
使用的单片机机型为STM32F103C8T6
文章目录
- GPIO基本结构
- GPIO模式(重点)
- 编程实例
- GPIO闪烁灯
- 流水灯
- 引脚输出控制蜂鸣器
- 按键控制LED
- 光敏电阻控制蜂鸣器
GPIO(General Purpose Input/Output)通用输入输出接口,是一种可以在微控制器(MCU)或其他数字电子设备上配置为输入或输出的接口。常用于与外部设备进行通信和控制。
GPIO的基本功能包括:
-
输入模式(Input):将GPIO引脚配置为输入模式时,设备可以读取外部设备(如传感器、开关等)的信号。例如,接收温度传感器的输出,或检测按键的状态。
-
输出模式(Output):将GPIO引脚配置为输出模式时,设备可以向外部设备发送信号。例如,控制LED灯的开关,或者驱动继电器、蜂鸣器等硬件。
STM32系统结构
- Cortex-M3:CPU核心
- DMA1/2:辅助CPU,做一些频繁的数据搬运的工作
- APB1/2:外设总线
- APB1:适用于低功耗、低速、低带宽的外设,适合一些控制任务和低速通信任务
- APB2:适用于对时钟频率要求较高的外设,适合需要更高带宽、更高精度的任务
GPIO 就处于 APB2 总线上
GPIO基本结构
GPIO 总共有 A ~ G,7个外设,每个 GPIO 有一个寄存器和驱动器,因为引脚为16根,所以寄存器为16位,每位控制一个一个引脚。STM32地址为32位,所以寄存器只用到了地址的低16位,高16位没有使用
输出
:当寄存器写入0,通过驱动器输出对应端口低电平;当寄存器写入1,通过驱动器输出对应端口高电平
输入
:驱动器将端口的高低电平转化为0/1,写入寄存器,再通过 APB2 总线读取
GPIO模式(重点)
GPIO内部,由输入输出寄存器,输入输出驱动器,外部引脚组成
I/O引脚及保护电路
首先讲解一些 I/O引脚及其保护电路
输出模式时,I/O引脚由输出驱动器控制,不会有问题。但为输入模式时,I/O引脚由外部控制。若无保护电路,当外部电压过高或过低时,都会对驱动器及寄存器造成损害
保护二极管作用如下:
VDD为电源正极,通常为3.3V;VSS为负极
当电压过高时,电流会导通到VDD
当电压过低时,电流会导通到外部
如此就不会损害内部电路
输入模式
输入有四个模式:浮空输入,上拉输入,下拉输入,模拟输入
数据从 I/O引脚进入输入驱动器,会先经过两个开关和电阻,一个接VDD(正极)为上拉电阻,一个接VSS(负极)位下拉电阻,可通过程序配置哪个开关导通
当上面开关导通,下面开关关闭,为上拉输入
模式
可读取引脚电平,若引脚悬空,默认输入高电平
当上面开关关闭,下面开关打开,为下拉输入
模式
可读取引脚电平,若引脚悬空,默认输入低电平
当上下开关都不导通,为浮空输入
模式
可读取引脚电平,当引脚悬空时,电平不确定,极易受外界影响。
使用浮空输入时,需要确保输出源是连续不断的,不能出现浮空的状态
上拉电阻和下拉电阻的阻值都较大,防止影响电路,为弱上/下拉
左侧为施密特触发器(肖特基为翻译错误),作用是将不稳定的电平转化为稳定的电平。因为电平转化为数字只有0/1,而不稳定的电平会造成不稳定的输入
原理:设定上下阈值,当电平高于上阈值,切换为高电平;当电平低于下阈值,切换为低电平
示例:
最往左将输入写入输入数据寄存器,可通过APB2总线读出数据
模拟输入
GPIO无效,引脚直接接入内部ADC,用于AD电模转换。因为读取电平值,所以在施密特触发器前
复用功能输入
直接接在一个需要输入的外设上,如串口的输入引脚,因为需要的是数据量,所以在施密特触发器后
输出模式
输出有四种模式:开漏输出,推挽输出,复用开漏输出,复用推挽输出
推挽输出
首先,通过寄存器写入数据0/1。
当写入数据1时,上面的晶体管导通,连接VDD输出高电平
当写入数据0时,下面的晶体管导通,连接VSS输出低电平
推挽输出的高低电平由正负极决定,有较强的电流和驱动能力
典型的应用包括LED驱动、电机控制、PWM信号输出、串行通信、信号发生器、继电器驱动以及高频信号传输等领域。
开漏输出
开漏输出不使用上面的晶体管,通常还需要外接一颗上拉电阻
当寄存器写入数据0,下面的晶体管导通,输出低电平
当寄存器写入数据1,下面的晶体管断开,此时为高阻态,不会有电流流过,由上拉电阻接的电源提供高电平
为什么要这样设计呢?
因为单片机有多机通信的场景,例如 I2C、SPI。
多个设备挂载在一条通信总线上
如果采用推挽输出,假设一个设备输出高电平,一个设备输出低电平,会出现以下场景
此时必有一个晶体管会被烧毁
而开漏输出搭配上拉电阻就可以规避这个场景
高电平由上拉电阻输出
对于每个设备,想输出低电平,就导通N-MOS晶体管;想输出高电平,就关闭N-MOS,由上拉电阻输出高电平
此时,可能场景如下:
-
两个都输出低电平,导通N-MOS,VDD会导向任意一个,此时输出低电平
-
两个都输出高电平,都关闭N-MOS,为高阻态,不会有电流流过,由上拉电阻输出高电平
-
一个输出高电平,一个输出低电平。一个关闭N-MOS,一个开启N-MOS。此时VDD被拉低,输出低电平
尽管无法满足部分设备的输出,但是避免了晶体管烧毁的情况
推挽输出 和 开漏输出由左侧的数据输出寄存器控制,而复用推挽输出 和 复用开漏输出由片上外设控制
编程实例
GPIO闪烁灯
ST封装了GPIO的库函数,步骤如下:
- 开启指定的GPIO外设时钟使能
//使能时钟
//接在A0端口,属于GPIOA
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
- 初始化/配置GPIOA
//初始化GPIO
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //A0引脚
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //50MHz速度
GPIO_Init(GPIOA, &GPIO_InitStructure);
- GPIO_Mode:GPIO的8种模式
- GPIO_Pin:要配置的引脚,共16个
GPIO_All 代表全部16个引脚,多个引脚可以同时配置,使用|
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2;
- GPIO_Speed:I/O口翻转的速度,配置 GPIO 引脚工作速度
-
低速(GPIO_Speed_Level_1):
适用于一些不需要快速响应的简单应用,比如低频信号的数字输入输出(例如按键扫描、开关控制等) -
中速(GPIO_Speed_Level_2):
适合一般用途的 GPIO 引脚,响应速度适中。例如常见的外设接口(如 UART),或者中等速度的信号控制 -
高速(GPIO_Speed_Level_3):
用于要求较高数据传输速率的应用,如 SPI、I2C 通信,或者需要频繁切换的数字输出
- 读/写数据
ST同样提供了数据读写的方法,在stm32f10x_gpio.h
最后可以看到定义
API | 参数 | 返回值 | 说明 |
---|---|---|---|
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); | GPIOX:指定GPIO外设,GPIOA ~ GPIOG GPIO_Pin:指定引脚,0 ~ 15 | 从指定引脚读到的数据 | 读取指定GPIO外设的输入寄存器数据 的某一位 |
uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx); | GPIOX:指定GPIO外设,GPIOA ~ GPIOG | 输入寄存器的16位数据 | 读取指定GPIO外设的16位输入寄存器数据 |
uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); | GPIOX:指定GPIO外设,GPIOA ~ GPIOG GPIO_Pin:指定引脚,0 ~ 15 | 从指定引脚读到的数据 | 读取指定GPIO外设的输出寄存器数据 的某一位 |
uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx); | GPIOX:指定GPIO外设,GPIOA ~ GPIOG | 输出寄存器的16位数据 | 读取指定GPIO外设的16位输出寄存器数据 |
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); | GPIOX:指定GPIO外设,GPIOA ~ GPIOG GPIO_Pin:指定引脚,0 ~ 15 | 无 | 将指定GPIO外设的指定引脚置1 |
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); | GPIOX:指定GPIO外设,GPIOA ~ GPIOG GPIO_Pin:指定引脚,0 ~ 15 | 无 | 将指定GPIO外设的指定引脚置0 |
void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal); | GPIOX:指定GPIO外设,GPIOA ~ GPIOG GPIO_Pin:指定引脚,0 ~ 15 BitVal:要写入的数据,0/1 | 无 | 将BitVal写入GPIO外设的指定引脚 |
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal); | GPIOX:指定GPIO外设,GPIOA ~ GPIOG PortVal:要写入的16位数据 | 无 | 将PortVal写入GPIO外设 |
我们通过操作GPIO输出寄存器输出高低电平,高低电平就可以控制LED的亮灭
LED灯的介绍可参看【51单片机】点亮LED
简单来说,引脚输出低电平——灯亮,输出高电平——灯灭
接线图
如下:
我们将LED接在A0端口
要实现LED闪烁,需要周期控制LED亮灭,此处直接提供 Delay(延迟)
方法
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亮灭就可以实现闪烁灯效果
代码如下:
/**
* @brief A0端口LED闪烁,以一定频率
* @parm 无
* @retval 无
*/
void FlashingLED()
{
//使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
//初始化GPIO
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //A0引脚
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //50MHz速度
GPIO_Init(GPIOA, &GPIO_InitStructure);
while(1)
{
GPIO_ResetBits(GPIOA, GPIO_Pin_0); //低电平,灯亮
Delay_ms(500);
GPIO_SetBits(GPIOA, GPIO_Pin_0); //高电平,灯灭
Delay_ms(500);
}
}
流水灯
接线图
如下:
流水灯是在闪烁灯的基础上,周期控制不同灯亮灭
代码如下:
/**
* @brief A0 ~ A7端口流水灯
* @parm 无
* @retval 无
*/
void WaterfallLED()
{
//使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
//初始化GPIO
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_All; //A0 ~ A15
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //50MHz速度
GPIO_Init(GPIOA, &GPIO_InitStructure);
while(1)
{
unsigned int LED = 0x0001;
for(int i = 0; i < 8; ++i)
{
GPIO_Write(GPIOA, ~LED);
LED <<= 1;
Delay_ms(300);
}
}
}
引脚输出控制蜂鸣器
蜂鸣器的介绍可参看【51单片机】蜂鸣器演奏天空之城
STM32的蜂鸣器是有源蜂鸣器,不需要我们控制振荡频率,所以发声频率一定。低电平发声,高电平不发声
接线图
如下:
因为蜂鸣器也是低电平导通,本质和LED相同,区别就是我们将I/O口接在了GPIOB的B12引脚
代码如下:
/**
* @brief B12控制蜂鸣器
* @parm 无
* @retval 无
*/
void Buzzer()
{
//使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
//初始化GPIO
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; //B12
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //50MHz速度
GPIO_Init(GPIOB, &GPIO_InitStructure);
while(1)
{
GPIO_ResetBits(GPIOB, GPIO_Pin_12); //低电平,蜂鸣器响
Delay_ms(100);
GPIO_SetBits(GPIOB, GPIO_Pin_12); //高电平,蜂鸣器不响
Delay_ms(100);
GPIO_ResetBits(GPIOB, GPIO_Pin_12); //低电平,蜂鸣器响
Delay_ms(100);
GPIO_SetBits(GPIOB, GPIO_Pin_12); //低电平,蜂鸣器响
Delay_ms(700);
}
}
按键控制LED
接线图
如下
按键
详细的介绍可参看【51单片机】独立按键
按键:是常见的输入设备,按下导通,松手断开
按键抖动:由于按键内部使用的是机械式弹簧片进行通断,所以在按下和松手的瞬间会伴随一连串的抖动
规避按键抖动的方法之一就是,检测到按键按下后,延迟10 ~ 20ms,跳过抖动。松开后同样要延迟
因为按键是输入设备,所以配置GPIO时,需要配置为上拉输入或下拉输入
Key.c
/**
* @brief 初始化GPIOB部分引脚服务按键
* @parm GPIO_Pin:要配置的引脚
* @retval 无
*/
void Key_Init(uint16_t GPIO_Pin)
{
//时钟使能
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
//配置GPIO
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin; //配置引脚
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //I/O口翻转速度
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
/**
* @brief 获取按键按下,若两个按键先后相近按下,会返回编号靠后的按键
* @parm 无
* @retval 按键按下,根据引脚编号 范围:1 ~ 16
*/
uint8_t Key(void)
{
uint8_t Key = 0;
if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0)
{
Delay_ms(20); //过滤抖动
while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == 0); //等待按键松开
Delay_ms(20);
Key = 1;
}
if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_10) == 0)
{
Delay_ms(20); //过滤抖动
while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_10) == 0); //等待按键松开
Delay_ms(20);
Key = 11;
}
return Key;
}
主程序逻辑就是循环检测按键按下,检测有按键按下后,根据按键控制LED
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "LED.h"
#include "Key.h"
int main()
{
LED_Init(GPIO_Pin_0 | GPIO_Pin_1);
Key_Init(GPIO_Pin_0 | GPIO_Pin_10);
while(1)
{
uint8_t KeyNum = Key();
if(KeyNum == 1)
LED_Tun(GPIO_Pin_0);
if(KeyNum == 11)
LED_Tun(GPIO_Pin_1);
}
}
完整项目链接:【STM32】按键控制LED
光敏电阻控制蜂鸣器
光敏电阻用于传感器模块
传感器元件(光敏电阻/热敏电阻/红外接收管等)的电阻会随着外界模拟量的变化而变化,通过与定值电阻分压
即可得到模拟电压输出,再通过电压比较器
进行二值化即可得到数字电压输出
硬件电路
如下:
我们逐步分析
首先,N1为光敏电阻/热敏电阻
,与R1进行串联分压。
一旁的C2电容,一端接电路,一端接GND,起到了滤波电容的作用,过滤一些不稳定的电压,使得电压更加平滑
如此,AO 和 IN+ 随着 N1 阻值的改变就会获得不同的电压。
利用上下拉电阻分析
当 N1 阻值变小,下拉能力增强
,AO 电压变小。当 N1 无限小时,相当于AO直接接在GND,输出低电平
当 N1 阻值变大,下拉能力减弱
,AO 被 R1 的上拉拉高,AO 电压变大。当 N1 无限大时,相当于断路,AO 与 VCC 相连,输出高电平
左侧的 IN+ 接在电压比较器,用于获得数字电压;AO 接在 外部引脚,用于输出模拟电压
电压比较器
右侧的 R2 为可调电阻,用于与 IN+ 输入的电压进行比较
左侧的两个电压比较器构成运算放大器,当 IN+ 电压高于 IN-,输出高电平;当 IN+ 电压低于 IN-,输出低电平。如此就实现了二值化,获得了数字电压
DO输入接在DO引脚
LED1 为电源指示灯,通电即亮
D0引脚用于输入,获取数字电压。AO用于模拟电压输出
LED2为数字电压指示灯,当DO输入为0点亮,输入为1熄灭
R5为上拉电阻,DO默认输入高电平
到此传感器模块就介绍完了
接线图
如下:
根据光敏电阻传感器控制蜂鸣器
当光亮减弱,光敏电阻阻值变大,输出高电平,反之输出低电平
我们设置光亮时蜂鸣器不响,昏暗时蜂鸣器响
Buzzer.c
#include "stm32f10x.h" // Device header
/**
* @brief 初始化蜂鸣器相关引脚
* @parm 无
* @retval 无
*/
void Buzzer_Init(void)
{
//使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
//初始化GPIOB相关引脚
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);
}
/**
* @brief 蜂鸣器发声
* @parm 无
* @retval 无
*/
void Buzzer_On(void)
{
GPIO_ResetBits(GPIOB, GPIO_Pin_12);
}
/**
* @brief 蜂鸣器不发声
* @parm 无
* @retval 无
*/
void Buzzer_Off(void)
{
GPIO_SetBits(GPIOB, GPIO_Pin_12);
}
/**
* @brief 蜂鸣器发声切换
* @parm 无
* @retval 无
*/
void Buzzer_Tun(void)
{
if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_12) == 0)
GPIO_SetBits(GPIOB, GPIO_Pin_12);
else
GPIO_ResetBits(GPIOB, GPIO_Pin_12);
}
LightSensor.c
#include "stm32f10x.h" // Device header
/**
* @brief 光敏电阻初始化,配置引脚为GPIOB_13
* @parm 无
* @retval 无
*/
void LightSensor_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
//配置GPIO
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; //I/O口翻转速度
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
/**
* @brief 获取当前光敏传感器输出的高低电平
* @parm 无
* @retval 光敏传感器输出的高低电平,范围:0/1
*/
uint8_t LightSensor_Get(void)
{
return GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_13);
}
main.c
#include "stm32f10x.h" // Device header
#include "Buzzer.h"
#include "LightSensor.h"
int main()
{
Buzzer_Init();
LightSensor_Init();
uint8_t Light;
while(1)
{
Light = LightSensor_Get();
if(Light == 1)
Buzzer_On();
else
Buzzer_Off();
}
}
完整项目链接:【STM32】光敏电阻控制蜂鸣器
以上就是本篇博客的所有内容,感谢你的阅读
如果觉得本篇文章对你有所帮助的话,不妨点个赞支持一下博主,拜托啦,这对我真的很重要。