本笔记基于 B 站优质 STM32 教学自媒体 UP 主:
江协科技:【STM32入门教程-2023版 细致讲解 中文字幕】https://www.bilibili.com/video/BV1th411z7sn?vd_source=05dc4a341cab233beaacc8227fe5a7fe
铁头山羊:【铁头山羊stm32 入门教程【新版】】https://www.bilibili.com/video/BV11X4y1j7si?vd_source=05dc4a341cab233beaacc8227fe5a7fe
本文仅作为个人学习记录分享与交流,不涉及商业行为
实验代码见 Gitee 地址:https://gitee.com/LightHall/stm32-code
1. GPIO基础概念
GPIO 是 “General Purpose Input/Output” 的缩写,即“通用输入输出端口”
在 STM32 微控制器中,GPIO 是一种非常重要的功能模块,它允许用户:
- 通过编程来控制和读取微控制器引脚的状态,从而实现与外部设备的交互
GPIO 的工作主要是输入和输出:
-
输出:从单片机内部向引脚输出 0/1

-
输入:从引脚向单片机内部输入 0/1

1.1 GPIO基本结构
GPIO 基本结构由三大部分组成:输入结构、输出结构、引脚结构
① 输入结构

其中,TTL肖特基触发器用处在于:将引脚传入的并不算稳定的电压转化为纯净的 0/1 电平信号
不稳定指的是:由于按键抖动等原因造成电压值较低或较高,TTL肖特基触发器能消除这种噪声,为输入数据寄存器提供一个稳定的数字输入
输入结构+引脚结构的简化图可表示为:

TTL肖特基触发器是施密特触发器的一种具体实现方式
② 输出结构

输出结构+引脚结构的简化图可表示为:

方框 0/1 指的是数据输出寄存器
③ 引脚结构

将以上三部分组合,即 GPIO 完整的基本结构:

1.2 GPIO 的功能
① 输入功能:
GPIO可以被配置为输入模式,用于读取外部信号的状态
例如:可以连接一个按钮,当按钮按下时,GPIO 引脚会检测到低电平或高电平的变化
② 输出功能:
GPIO可以被配置为输出模式,用于控制外部设备
例如:可以连接一个 LED 灯,通过 GPIO 引脚输出高电平或低电平来点亮或熄灭 LED
③ 双向功能:
GPIO 还可以被配置为双向模式,即既可以作为输入,也可以作为输出,具体功能取决于当前的配置和需求
1.3 GPIO 控制引脚
引脚是微控制器或其他集成电路(IC)上用于连接外部电路的物理接口,是微控制器与外部世界进行通信和交互的通道
注意,IO 引脚并不全是 GPIO 的引脚
在微控制器(如STM32)中,IO引脚可以根据其功能和用途分为多种类型,其中只有部分引脚可以作为GPIO使用
如下图,除了红/黄高亮的引脚,其余引脚均可以作为 GPIO 的引脚来使用
其中 GPIO 可分为4 个模块:GPIOA、GPIOB、GPIOC、GPIOD,分别控制其对应的若干 IO 引脚

每一个 GPIO 的引脚,都对应地在内部结构拥有一个独立的 GPIO 基本结构
1.4 GPIO 的 8 种工作模式
输入和输出分别具有 4 种模式,即 GPIO 的每一根引脚都各自有 8 种工作模式
输出模式
- 通用输出推挽
- 通用输出开漏
- 复用输出推挽
- 复用输出开漏
输入模式
- 输入上拉
- 输入下拉
- 输入浮空
- 模拟模式
每一根 GPIO 的引脚都能独立地选择这 8 种工作模式的一种,互不干扰
2、四种输出模式
2.1 输出的基本结构

上图为输出结构的简化图,写着 0/1 的矩形为输出数据寄存器
2.2 推挽的概念
推:电流从单片机内部被推至引脚
挽:电流从引脚被拉进单片机内部
推挽是由两个 mos 管(P-MOS、N-MOS)控制,两个 mos 管交替工作实现推挽
① 推

当单片机内部的输出数据寄存器传入 1,输出结构会控制 P-MOS 管闭合,N-MOS 管断开
由于 P-MOS 管接 Vdd 电源,P-MOS 闭合而使得 Vdd 与引脚直接连接,所以引脚的电平被拉至高电压(≈3.3V),即引脚呈现高电平
② 挽

当单片机内部的输出数据寄存器传入0,输出结构会控制 P-MOS 管断开,N-MOS 管闭合
由于 N-MOS 管接 Vss(即接地),N-MOS 闭合而使得 Vss 与引脚直接连接,所以引脚的电平被拉至低电压(≈0V),即引脚呈现低电平
2.3 开漏的概念
开漏指的是 MOS 管的漏极断开

对于输出结构,当 P-MOS 管一直保持断开状态,对于 N-MOS 管来说就是持续呈现开漏状态
P-MOS 管的源极就是 N-MOS 管的漏极
开漏模式下的输出原理:
① 输出 0
当单片机内部的输出数据寄存器传入0,输出结构会控制 P-MOS 管断开,N-MOS 管闭合
由于 N-MOS 管接 Vss(即接地),N-MOS 闭合而使得 Vss 与引脚直接连接,所以引脚的电平被拉至低电压(≈0V),即引脚呈现低电平
此时与推挽模式下的原理一致
② 输出 1
当单片机内部的输出数据寄存器传入1,引脚悬空(即两个MOS管都断开了),外部引脚线电流恒为0
其实就是引脚与输出结构断开所有连接了
由于R=U/I,I为0,那么R无限大,引脚呈现高阻抗的状态,此时需要一个外部上拉电阻来让引脚在高阻态时能输出稳定的高电平
2.4 通用/复用的概念
- 当是 cpu 控制输出 0/1 时,为通用
- 当是片上外设控制输出 0/1 时,为复用
如下图所示:

并且,只要不是 cpu 直接控制输出就是复用,即使是 cpu 控制其他外设来向引脚输出 0/1 也是复用,如下图:

2.5 推挽和开漏并存的必要性
开漏输出模式是STM32 GPIO不可或缺的重要功能。
它解决了推挽输出模式无法解决的多设备共享总线冲突、不同电压系统间安全通信等关键问题,并在驱动高于VDD的负载和简化双向通信方面提供了独特的优势。在设计涉及总线通信、电平转换或特殊驱动需求时,开漏模式往往是必须的选择。
例如:

3、GPIO 的最大输出速度
3.1 GPIO 的最大输出速度:

3.2 上升时间、下降时间和保持时间的概念:

3.3 限制最大输出速度的因素
限制最大输出速度的是上升时间和下降时间:

3.4 STM32 的 GPIO 最大输出速度
最大输出速度应选满足要求的最小速度
因为过于陡峭的上升和下降(边沿)会增加耗电,并引入 EMI 问题
STM32 提供了三种速度:

4、四种输入模式
只了解输入上拉、输入下拉即可
4.1 输入的基本结构

4.2 输入上拉
由于施密特触发器可以看作一个巨大阻值的电阻,所以 3.3V 电压基本都分给了施密特触发器
所以默认输入的是 1
当引脚接入低电平,输入变为 0

4.3 输入下拉
由于施密特触发器可以看作一个巨大阻值的电阻,所以0V电压基本都分给了施密特触发器
所以默认输入的是0
当引脚接入高电平,输入变为1

4.4 输入浮空
当引脚悬空时为输入浮空

此时引脚会受到来自外界的干扰,可能导致引脚电压不稳定而导致的电平频繁切换
5、实验 1:板载 LED PC13 闪烁
5.1 两种常用的控制 LED 亮灭的方式
首先要知道 LED 的结构:

当 LED 两端有电压差时,LED 接通发出亮光
想让 LED 亮起,有两种控制方法:推挽输出、开漏输出
① 推挽输出

② 开漏输出

所以需要根据 LED 的电路结构来确定使用哪种输出模式来控制其亮灭
5.2 板载 LED PC13 的结构
板载 LED PC13 的位置如下图

下图为结构图

如上图,板载 LED 引脚为 PC13,外部接 VCC,所以当 PC13 输出 0 时,LED 才会亮起
我们既可以用推挽输出,也可以用开漏输出,我们这里以开漏输出为例
5.3 常用的 GPIO 模块编程接口
1)RCC_APB2PriphClockCmd(RCC_APB2Periph_GPIOx,ENABLE);
用于开启 GOIOx 的时钟的函数(让某组 GPIO 能够开始工作,称为使能)
parameter1:要开启的模块的名称
parameter2:选择是否使能
2)GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct 是结构体名,可以自定
GPIO 相关参数的结构体,包括
① GPIO_Mode:
GPIO_Mode_Out_PP :通用输出推挽模式
GPIO_Mode_Out_OD : 通用输出开漏模式
GPIO_Mode_AF_PP : 复用输出推挽模式
GPIO_Mode_AF_OD : 复用输出开漏模式
GPIO_Mode_IPU : 输入上拉模式
GPIO_Mode_IPD : 输入下拉模式
GPIO_Mode_IN_FLOATING : 输入浮空
GPIO_Mode_AIN : 模拟模式
② GPIO_Pin:
GPIO_Pin_0 … GPIO_Pin_15
③ GPIO_Speed:
GPIO_Speed_2MHz
GPIO_Speed_10MHz
GPIO_Speed_50MHz
3)GPIO_Init(GPIOx,&GPIOx_InitStructure);
GPIO 初始化函数(初始化 GPIO 某引脚的模式、最大速度)
parameter1:要开启的端口号,GPIOA、GPIOB、GPIOC、GPIOD
parameter2:初始化的参数(GPIO 结构体)
4)GPIO_WriteBit(GPIOx,GPIO_Pin,BitValue);
向输出数据寄存器写入 1 比特位的函数
parameter1:要开启的端口号,GPIOA、GPIOB、GPIOC、GPIOD
parameter1:要开启的引脚号,GPIO_Pin_0 … GPIO_Pin_15
parameter1:要写入的值,Bit_SET 表示高电平 、Bit_RESET 表示低电平
5)GPIO_ReadOutputDataBit(GPIOx,GPIO_Pin);
从输出数据寄存器读 1 个比特位的函数
parameter1:要开启的端口号,GPIOA、GPIOB、GPIOC、GPIOD
parameter1:要开启的引脚号,GPIO_Pin_0 … GPIO_Pin_15
6)GPIO_ReadInputDataBit(GPIOx,GPIO_Pin);
从输入数据寄存器读 1 个比特位的函数
parameter1:要开启的端口号,GPIOA、GPIOB、GPIOC、GPIOD
parameter1:要开启的引脚号,GPIO_Pin_0 … GPIO_Pin_15
5.4 步骤
思路
总思路:控制 PC13 引脚输出0/1控制板载 LED 的亮灭
首先,使能 GPIO,因为板载 LED 引脚位 PC13,输入 GPIOC 的引脚,所以使能 GPIOC
然后,定义 GPIOC 的 13 号引脚初始参数,并初始化
最后,通过向数据输出寄存器写比特函数 GPIO_WriteBit 控制板载 LED 的亮灭
① 开启 GPIOC 的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
因为板载 LED 对应的引脚为 PC13,由 GPIOC 控制,所以需要开启 GPIOC 的时钟
② 配置 GPIOC 参数,并初始化 GPIOC
GPIO_InitTypeDef GPIOC_InitStruct;
GPIOC_InitStruct.GPIO_Mode = GPIO_MODE_Out_OD; // 输出开漏模式
GPIOC_InitStruct.GPIO_Pin = GPIO_Pin_13; // 引脚13
GPIOC_InitStruct.GPIO_Speed = GPIO_Speed_2MHz; // 最大速度2MHZ
GPIO_Init(GPIOC,&GPIOC_InitStruct); // 初始化
③ 编写业务代码
Delay_Init();
while(1){
Delay(500);
// 给GPIOC的13号引脚对应的输出寄存器置0,由前述分析可知此操作会使板载LED亮起
GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_RESET);
Delay(500);
// 给GPIOC的13号引脚对应的输出寄存器置1,由前述分析可知此操作会使板载LED熄灭
GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET);
}
总代码
//--------------------------
// GPIO实验1:板载LED闪烁
//--------------------------
#include "stm32f10x.h"
#include "delay.h"
int main(void)
{
//开启GPIOx的时钟(此处以GPIOC为例)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
//配置GPIO初始化参数
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
//初始化GPIOx(此处以GPIOC为例)
GPIO_Init(GPIOC,&GPIO_InitStructure);
//初始化延时函数
Delay_Init();
while(1){
//延时500ms
Delay(500);
//给GPIOC的13号引脚对应的输出寄存器置0(Bit_RESET表示0)
GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_RESET);
//延时500ms
Delay(500);
//给GPIOC的13号引脚对应的输出寄存器置1(Bit_SET表示1)
GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET);
}
}
6、实验2:按钮控制板载 LED PC13 亮灭
此实验相较于实验1,我们还要编写GPIO输入控制代码
6.1 按钮结构

上图示例的按钮结构非常简单,一边接地,一边接引脚
- 当按钮按下,引脚接地,被拉至低电平,即向输入数据寄存器存入0
由此可知在按钮未按下时,内部应该持续为高电平
所以此结构下的按钮对应的 GPIO 的输入模式只能是上拉输入模式
6.2 步骤
接线图

思路
总思路:按钮按下与否作为输入信号,从数据输入寄存器中读取该输入信号,来控制 PC13 (板载 LED 的引脚)的输出,以实现控制板载 LED 亮灭
首先,根据接线图可知,需要分别开启 GPIOA 和 GPIOC 的时钟
然后,分别初始化 GPIOA 的 0 号引脚作为输入、GPIOC 的 13 号引脚作为输出
最后,根据 GPIOA 的 0 号引脚的输入信号,来控制 GPIOC 的 13 号引脚的输出
① 开启 GPIOA 和 GPIOC 的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
这里将按钮引脚一端连接到 PA0,PA0 由 GPIOA 控制,所以需要开启 GPIOA 的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
因为板载 LED 对应的引脚为 PC13,由 GPIOC 控制,所以需要开启 GPIOC 的时钟
② 配置 GPIOA、GPIOC 参数,并初始化
GPIOA:
GPIO_InitTypeDef GPIOA_InitStruct;
GPIOA_InitStruct.GPIO_Mode = GPIO_MODE_IPU;
GPIOA_InitStruct.GPIO_Pin = GPIO_Pin_0;
// 输入模式没有最大输出速度
GPIO_Init(GPIOA,&GPIOA_InitStruct);
GPIOC:
GPIO_InitTypeDef GPIOC_InitStruct;
GPIOC_InitStruct.GPIO_Mode = GPIO_MODE_Out_OD;
GPIOC_InitStruct.GPIO_Pin = GPIO_Pin_13;
GPIOC_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOC,&GPIOC_InitStruct);
③ 编写业务逻辑代码
while(1){
//当按钮按下时,输入寄存器会被置0,所以读取输入寄存器值为Bit_RESET时说明按钮按下
if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)==Bit_RESET)
//此时让板载LED PC13亮起
GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_RESET);
//当按钮未被按下时,输入寄存器会上拉电阻置1,所以读取输入寄存器值为Bit_SET时说明按钮未被按下
else
//此时让板载LED PC13熄灭
GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET);
}
总代码
//------------------------------
// GPIO实验2:按钮控制板载LED
//------------------------------
#include "stm32f10x.h"
int main(void)
{
//输出配置
//开启GPIOx的时钟(此处以GPIOC为例)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
//配置GPIOx的Pin_y初始化参数(此处以GPIOC的Pin_13为例)
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
//初始化GPIOx的Pin_y(此处以GPIOC的Pin_13为例)
GPIO_Init(GPIOC,&GPIO_InitStructure);
//输入配置
//开启GPIOx的时钟(此处以GPIOA为例)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//配置GPIOx的Pin_y初始化参数(此处以GPIOA的Pin_0为例)
GPIO_InitTypeDef GPIO_InitStructure2;
GPIO_InitStructure2.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure2.GPIO_Pin = GPIO_Pin_0;
//最大输出速度只对输入有用,输入不需要Speed
//初始化GPIOx的Pin_y(此处以GPIOA的Pin_0为例)
GPIO_Init(GPIOA,&GPIO_InitStructure2);
//逻辑代码
while(1){
//当按钮按下时,输入寄存器会被置0,所以读取输入寄存器值为Bit_RESET时说明按钮按下
if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)==Bit_RESET)
//此时让板载LED PC13亮起
GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_RESET);
//当按钮未被按下时,输入寄存器会上拉电阻置1,所以读取输入寄存器值为Bit_SET时说明按钮未被按下
else
//此时让板载LED PC13熄灭
GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET);
}
}
7、实验3:按钮控制外置 LED 亮灭
接线图

思路
总思路:实际就是将实验2的板载LED换为外置LED,只需要另外定义外置LED的输出配置即可
首先,由接线图可知,只需要开启 GPIOA 的时钟
然后,初始化 GPIOA 的 0号、1号引脚
最后, 根据 GPIOA 的 0 号引脚的输入信号,来控制 GPIOA 的 1 号引脚的输出
在本例中,外置 LED 非引脚端接地,我们选择输出推挽模式
① 开启 GPIOA 的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
因为例子中外置 LED 对应的引脚一端为 PA1,由 GPIOA 控制,所以需要开启 GPIOA 的时钟
由于按钮和外置 LED 都是接到由 GPIOA 控制的引脚,所以无需重复开启
② 配置 GPIOA 参数,并初始化 GPIOA
// 输出配置
GPIO_InitTypeDef GPIOA_InitStruct;
GPIOA_InitStruct.GPIO_Mode = GPIO_MODE_Out_PP;
GPIOA_InitStruct.GPIO_Pin = GPIO_Pin_1;
GPIOA_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOA,&GPIOA_InitStruct);
// 输入配置
GPIO_InitTypeDef GPIOA_InitStruct;
GPIOA_InitStruct.GPIO_Mode = GPIO_MODE_IPU;
GPIOA_InitStruct.GPIO_Pin = GPIO_Pin_0;
// 输入模式没有最大输出速度
GPIO_Init(GPIOA,&GPIOA_InitStruct);
本实验采取通用输出推挽模式控制外置 LED 亮灭
⑤ 编写业务代码
while(1){
//当按钮按下时,输入寄存器会被置0,所以读取输入寄存器值为Bit_RESET时说明按钮按下
if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)==Bit_RESET)
//此时让板载LED PC13亮起
GPIO_WriteBit(GPIOA,GPIO_Pin_1,Bit_SET);
//当按钮未被按下时,输入寄存器会上拉电阻置1,所以读取输入寄存器值为Bit_SET时说明按钮未被按下
else
//此时让板载LED PC13熄灭
GPIO_WriteBit(GPIOA,GPIO_Pin_1,Bit_RESET);
}
总代码
//------------------------------------
// GPIO实验3:按钮控制外置LED
//------------------------------------
#include "stm32f10x.h"
int main(void)
{
//由于本例都用GPIOA,所以开启一次GPIOA时钟、实例一次GPIO_InitTypeDef即可
//开启GPIOx的时钟(此处以GPIOA为例)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//配置GPIOx的Pin_y初始化参数
GPIO_InitTypeDef GPIO_InitStructure;
//输出配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
//输入配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_Init(GPIOA,&GPIO_InitStructure);
//逻辑代码
while(1){
//当按钮按下时,输入寄存器会被置0,所以读取输入寄存器值为Bit_RESET时说明按钮按下
if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)==Bit_RESET)
//此时让板载LED PC13亮起
GPIO_WriteBit(GPIOA,GPIO_Pin_1,Bit_SET);
//当按钮未被按下时,输入寄存器会上拉电阻置1,所以读取输入寄存器值为Bit_SET时说明按钮未被按下
else
//此时让板载LED PC13熄灭
GPIO_WriteBit(GPIOA,GPIO_Pin_1,Bit_RESET);
}
}

1357

被折叠的 条评论
为什么被折叠?



