GPIO
目录
一、GPIO的四种输出模式
1.1.GPIO的概念

**GPIO:**通用输入输出端口
**注:**G:general P:purpose I:input O:output
STM32芯片内置的片上外设模块
**功能:**类似于人类的手,控制单片机上的IO引脚
1.2.GPIO的编组
STM32芯片包含四组GPIO模块:
GPIOA,GPIOB,GPIOC,GPIOD
每组GPIO模块包含特定的引脚:
- GPIOA:PA0 PA1 PA2 ... PA15(16个)
- GPIOB:PB0 PB1 PB2 ... PB15(16个)
- GPIOC:PC13 PC14 PC15(3个)
- GPIOD:PD0 PD1(2个)
每组GPIO模块像人的一只手
每个GPIO引脚就是人的手指
1.3.GPIO的工作模式
GPIO的工作模式配置了IO引脚的功能
单个IO引脚需要实现多种功能:
- 输出高/低电平
- 输入外部信号
- 其他特殊功能
因此每个IO引脚都需要配置多种功能
所以GPIO定义了8种不同的工作模式
可分为4种输出 和4种输入两大类模式
1.3.1.输出模式
信号从【芯片内部】通过IO引脚流向【芯片外部】
**使用示例:**用输出模式控制Led亮灭:

在IO引脚上接一颗Led(发光二极管)
- 如果输出高电平:Led点亮
- 如果输出低电平:Led熄灭
通过编程向芯片内部的输出数据寄存器写入数据
- 写入1:引脚输出高电平,Led点亮
- 写入0:引脚输出低电平,Led熄灭
1.3.2.输入模式
信号从【芯片外部】通过IO引脚流向【芯片内部】
**使用示例:**通过输入模式判断开关闭合

在IO引脚上接一个开关
- 如果开关闭合:向引脚输入高电平,输入数据寄存器值为1
- 如果开关断开:向引脚输入低电平,输入数据寄存器值为0
通过编程从芯片内部的输入数据寄存器读取数据
- 读取1:当前开关闭合
- 读取0:当前开关断开
1.4.四种输出模式
1.4.1.四种输出模式命名
通用输出推挽 通用输出开漏
复用输出推挽 复用输出开漏
1.4.2.IO端口位电路图

因为在STM32中一共有32个IO引脚,像这样的电路在芯片中一共有32个
上方为输入模式电路,下方为输出模式电路
1.4.3.输出模式的电路图

1.4.4.推挽模式
**推状态(PUSH):**从里向外推电流
- 由P-MOS管实现:
- P-MOS管接Vdd(3.3V正电压)
- 导通P-MOS管,断开N-MOS管
- IO引脚与Vdd导通,输出高电平(3.3V)
- 电流从内部向外推送

**挽状态(PULL):**从外向里拉电流
- 挽与N-MOS管有关:
- N-MOS管接Vss(0V负电压)
- 导通N-MOS管,断开P-MOS管
- IO引脚与Vss短路,输出低电平(0V)
- 电流从外部向内拉取

注:
P-MOS管和N-MOS管只能交替导通,不能同时导通,
高电压(Vdd)与低电压(Vss)直接相连会发生短路
1.4.5.开漏模式
**开漏:**MOS管的漏极保持断开状态

在输出模式下,开漏表示P-MOS管一直处于断开状态
**写入0时:**N-MOS管导通,P-MOS管断开,输出低电平

**写入1时:**N-MOS管断开,P-MOS管断开,呈悬空状态
**注:**此时引脚电流趋于0,根据欧姆定律,电阻为无穷大,所以又称高阻态

1.4.6.通用模式
CPU直接向输出数据寄存器写入数据来控制GPIO引脚

**使用示例:**CPU直接控制PA9引脚实现Led亮灭

CPU直接向输出数据寄存器写入数据
**写1:**PA9输出高电平
**写0:**PA9输出低电平
1.4.7.复用模式
片上外设通过GPIO模块控制IO引脚的电平输出

**使用示例:**串口模块控制PA9引脚发送"Hello"数据

CPU向串口模块发送"Hello"数据
串口模块控制PA9引脚输出高低电平变换的波形,从而输出数据
通用 VS 复用:

二、IO的最大输出速度
2.1.IO最大输出速度
**定义:**向IO交替写0和1且输出不失真的最快速度
示例: 假定引脚的模式为推挽模式:

- 输出1时:P-MOS管导通,引脚为高电平
- 输出0时:N-MOS管导通,引脚为低电平
两个MOS管快速交替导通时,就会形成下面的波形:

每秒钟写10次,每秒中写100次时这个波形都为正常的
当速度加快到每秒钟写100000次后,波形就开始失真
此时能维持正常输出的最高频率即为IO最大输出速度
2.2.上升、下降、保持时间
理想情况:

**向IO引脚写1:**IO引脚电压立即从低电平(0V)跃升至高电平(3.3V)
**向IO引脚写0:**IO引脚电压立即从高电平(3.3V)下降至低电平(0V)
实际情况:

**上升时间:**0V~3.3V的过渡时间
**保持时间:**稳定在3.3V持续时间(输出有效电平的时间)
**下降时间:**3.3V~0V的过渡时间
2.3.限制最大输出速度的原因
随着IO输出速度的提升,上升沿与下降沿之间的间隔缩短
导致有效电平的持续时间减少,即保持时间变短
若数据写入数据速度超过IO引脚的最大输出速度,将无法正确输出电平

通过缩短上升时间 与下降时间,就可以提高IO最大输出速度
2.4.最大输出速度的选取
STM32芯片提供了三种最大速度:
低速:
- 上升时间:125ns
- 保持时间:250ns
- 下降时间:125ns
- 总时间:500ns
- 最大输出速度:2MHz

中速:
- 上升时间:25ns
- 保持时间:50ns
- 下降时间:25ns
- 总时间:100ns
- 最大输出速度:10MHz

高速:
- 上升时间:5ns
- 保持时间:10ns
- 下降时间:5ns
- 总时间:20ns
- 最大输出速度:50MHz

**选取原则:**选取满足要求的最小值
**注:**如果选择过高的输出速率会增加耗电,还会对其他的电子元器件产生干扰(EMI)
**示例1:**单片机驱动Led

Led的闪烁频率不超过100Hz,所以选择低速(2MHz)来驱动
注:
人眼的临界闪烁频率是45.8Hz,交流电的频率是50Hz
所以当使用50Hz的交流电点亮一盏灯时感受不到闪烁
**示例2:**单片机驱动TLE5012BD芯片(巨磁阻效应编码器)

TLE5012BD芯片使用SPI总线与单片机通信
SPI总线通信的波特率为8Mbit/s(MHz),所以选择中速(10MHz)来驱动
**示例3:**USB2.0全速接口

USB2.0 Fs通信的最大波特率为12Mbps(MHz),所以选择高速(50MHz)来驱动
三、LED闪灯实验
**实验内容:**通过编程控制PC13引脚连接的Led灯实现闪烁功能
3.1.如何点亮一盏Led
原理:
在二极管的阳极供正电压,阴极供负电压,这样就会有电流流过二极管
当流过二极管的电流足够大时,二极管就会被点亮,电流范围为2~10mA
用以下电路实现Led的点亮:

阳极接Vdd(3.3V), 阴极接Vss(0V),Vdd与Vss间串联一个510Ω的限流电阻
二极管导通时压降为0.7V,所以电流为(3.3-0.7)/510 ≈ 5mA,二极管可以正常发光
推挽接法:

如果把Vdd改为地,阳极为0V,阴极也为0V,就不会有电流流过,二极管就会熄灭
所以我们在Vdd与Led之前设置一个开关,上拨接Vdd,下拨接地,实现二极管闪烁
这种拨动开关控制接Vdd或接Vss的方式与推挽输出类似
所以可以将限流电左边的电路替换成单片机上的IO引脚
并且把IO引脚的输出模式设置为推挽模式:

开漏接法:

如果将开关设置在二极管与Vss之间
开关接上为通路Led点亮,开关断开为开路Led熄灭
这种拨动开关控制接Vss或开路的方式与开漏输出类似
所以可以将Led右边的电路替换成单片机上的IO引脚
并且把IO引脚的输出模式设置为开漏模式:

3.2.最小系统板的Led接法
最小系统板上分别设置了两个Led
一个连接GND:


一个连接PC13引脚:


因为Led的阳极只接上了3.3V的高电平
所以最小系统板的Led接法为开漏接法:

3.3.编码部分
3.3.1.开启GPIOx的时钟
cpp
/*开启GPIOC的时钟*/
RCC_APB2PriphClockCmd(RCC_APB2Periph_GPIOx,ENABLE);
解析:
- 参数1:设置的GPIO组别
- 参数2:使能
**注:**要控制板载LED,需通过GPIOC组的PC13引脚进行操作,所以在使用GPIOC之前,要为它开启时钟,时钟可以类比为人的心跳,开启时钟相当于给人身体里的器官供血

3.3.2.GPIO模块编程接口
3.3.2.1.编程接口1
cpp
void GPIO_Init(GPIOTypeDef * GPIOx, GPIO_InitTypeDef * GPIO_InistStruct);
解析:
- 参数1:端口号,取A,B,C,D
- 参数2:初始化的参数结构体地址
**注:**init(Initialize):初始化
作用: 初始化IO引脚,配置IO引脚的各种参数
- 引脚的工作模式
- 引脚的最大输出速度
**补充:**GPIO_InitTypeDef结构(IO参数菜单)
cpp
typedef struct GPIO_InitTypeDef
{
GPIO_Pin;
GPIO_Speed;
GPIO_Mode;
}GPIO_InitTypeDef;
分析:
**1.GPIO_Pin:**引脚编
- GPIO_Pin_0 ...... GPIO_Pin_15
**2.GPIO_Speed:**最大输出速度
- GPIO_Speed_2MHz
- GPIO_Speed_10MHz
- GPIO_Speed_50MHz
**3.GPIO_Mode:**模式
- GPIO_Mode_Out_PP:通用输出推挽 PP = Push Pull
- GPIO_Mode_Out_OD:通用输出开漏 OD = Open Drain
- GPIO_Mode_AF_PP:复用输出推挽 AF = Alternate Function
- GPIO_Mode_AF_OD:复用输出开漏
- GPIO_Mode_IPU:输入上拉 IPU = Input Pull-up
- GPIO_Mode_IPD:输入下拉 IPD = Input Pull-down
- GPIO_Mode_IN_FLOATING:输入浮空
- GPIO_Mode_AIN:模拟模式 AIN = Analog Input
**注:**此结构的每个成员变量都为枚举变量
3.3.2.2.编程接口2
cpp
void GPIO_WriteBit(GPIOTypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitValue);
解析:
- 参数1:端口号,取A,B,C,D
- 参数2:引脚编号
- 参数3:要写入的值 Bit_RESET(写0) Bit_SET(写1)
**作用:**向输出数据寄存器写1个比特位
**示例:**假设IO引脚为通用输出推挽模式,通过调用GPIO_WriteBit向输出数据寄存器写0或1

- 写入0时:N-MOS管闭合,P-MOS管断开,输出低电平
- 写入1时:P-MOS管闭合,N-MOS管断开,输出高电平
3.3.2.3.编程接口3
cpp
uint8_t GPIO_ReadInputDataBit(GPIOTypeDef * GPIOx,uint16_t GPIO_Pin);
解析:
- 参数1:端口号,取A,B,C,D
- 参数2:引脚编号
- 返回值:Bit_SET(表示1)Bit_RESET(表示0)
**作用:**从输入数据寄存器读1个比特位
3.3.2.4.编程接口4
cpp
uint8_t GPIO_ReadOutputDataBit(GPIOTypeDef* GPIOx, uint16_t GPIO_Pin);
解析:
- 参数1:端口号,取A,B,C,D
- 参数2:引脚编号
- 返回值:Bit_SET(表示1)Bit_RESET(表示0)
**作用:**从输出数据寄存器读1个比特位
**注:**读的是最近一次使用GPIO_WriteBit向输出数据寄存器写入的值
3.3.3.初始化IO引脚
cpp
/*初始化GPIO_InitTypeDef结构体*/
GPIO_InitTypeDef GPIO_InitStruct = {0};
/*给GPIO_InitTypeDef结构体成员赋值*/
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
/*调用GPIO_Init函数*/
GPIO_Init(GPIOC,&GPIO_InitStruct);
解析:
1.初始化GPIO_InitTypeDef结构体
2.给GPIO_InitTypeDef结构体成员赋值:
- IO引脚编号设置为13
- IO引脚最大输出速度为2MHz
- IO引脚模式设置为通用输出开漏模式
3.调用GPIO_Init函数:
- 参数1:端口号GPIOC
- 参数2:GPIO_InitTypeDef结构体的地址
3.3.4.写入数据,点亮Led
cpp
/*向输入数据寄存器中写1*/
GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET);
/*向输入数据寄存器中写0*/
GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_RESET);
解析:
调用GPIO_WriteBit函数:
- 参数1:端口号CPIOC
- 参数2:引脚编号设置为13
- 参数3:写入1,或写入0
**注:**写入1后Led熄灭,写入0后Led点亮
3.3.5.设计Led闪烁

cpp
#include <delay.h>
while(1)
{
/*写1点亮Led*/
GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET);
/*延迟100ms*/
Delay(100);
/*写0熄灭Led*/
GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_RESET);
/*延迟100ms*/
Delay(100);
}
解析:
使用delay延迟函数,使用while循环
- 写入1时,点亮Led,延迟100ms
- 写入0时,熄灭Led,延迟100ms
**注:**调用delay函数需包含头文件<delay.h>
四、GPIO的4种输入模式
4.1.四种输入模式命名
输入上拉 输入下拉
输入浮空 模拟模式
4.2.输入模式的电路图

4.2.1.保护二极管
作用: 由于人体含有静电,静电的电压一般高达几千伏,如果用手直接触碰IO引脚,高电压进入单片机内部,会把芯片烧坏,所以要设计保护电路来防静电
示例:
假设手指产生+10000V静电,Vdd电压为3.3V,此时上方保护二极管导通,电流从Vdd流出
假设手指产生-10000V静电,Vss电压为0V,此时下方保护二极管导通,电流从引脚口流出
4.2.2.上拉电阻
闭合上拉电阻开关,将输入模式设置为输入上拉
**作用:**给IO引脚提供一个默认的高电压,起到稳定作用
4.2.3.下拉电阻
闭合下拉电阻开关,将输入模式设置为输入下拉
**作用:**给IO引脚提供一个默认的低电压,起到稳定作用
注:
当给IO引脚输入3.3V的高电压时,输入数据寄存器当前数据为1
当给IO引脚输入0V的低电压时,输入数据寄存器当前数据为0
当IO引脚没有电压输入时,处于悬空状态,相当于一根天线,接收空间高低变换的电磁波
导致转换结果不能确定,若不闭合上拉电阻与下拉电阻,输入模式就为输入浮空
但闭合上拉电阻或闭合下拉电阻就可以解决这个问题
**补充:**上拉电阻的工作原理

只闭合上拉电阻开关,不处于悬空状态时,上拉电阻对电路没有影响
处于悬空状态时,施密特触发器的电阻值为无穷大,而上拉电阻值是
常数,根据分压原理,施密特触发器分得的电压为3.3V,转换为数字1
输入数据寄存器可以稳定地读取到1
4.2.4.施密特触发器
**作用:**用于将IO引脚输入的高低电平转换为数字信号0或1
**注:**由于输入电流始终为0,根据欧姆定律,可以等效为一个无穷大的电阻
五、按钮实验
**实验内容:**按下按钮,Led点亮;松开按钮,Led熄灭
5.1.Led的接线方法
5.1.1.接线设计
采用通用推挽模式,接在芯片的PA0引脚上
- 写入1时:Led点亮
- 写入0时:Led熄灭

5.1.2.接线安装

Led阳极接限流电阻,然后接在PA0引脚上
Led阴极直接接在最小系统板的GND引脚
**注:**Led较长的一根针为阳极,较短的一根针为阴极
5.1.3.测试代码
cpp
/*开启GPIOA的时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
/*初始化IO引脚,PA0 通用输出推挽模式 2MHz*/
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
/*向输入数据寄存器写1*/
GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_SET);
5.2.按钮的接线方法
5.2.1.接线设计
采用输入上拉模式,接在芯片的PA1引脚上
- 按钮松开:PA1处于悬空状态,上拉电阻将电压拉高为3.3V,输入值为1
- 按钮按下:PA1接GND,施密特触发器前的电压为0V,所以输入值为0


5.2.2.接线安装

将按钮安装在PA1引脚与GND之间
5.2.3.测试代码
cpp
/*开启GPIOA的时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
/*初始化IO引脚,PA1 输入上拉模式*/
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOA,&GPIO_InitStruct);
5.3.编程接口和代码
cpp
#include "stm32f10x.h"
int main(void)
{
/*开启GPIOA的时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
/*初始化IO引脚,PA0 通用输出推挽模式 2MHz*/
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
/*初始化IO引脚,PA1 输入上拉模式*/
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(GPIOA,&GPIO_InitStruct);
while(1)
{
if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_1) == Bit_RESET)
{
GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_SET);
}
else
{
GPIO_WriteBit(GPIOA,GPIO_Pin_0,Bit_RESET);
}
}
}
解析:
- 按钮按下,输入寄存器数据为0,编程接口4返回Bit_RESET,向输出寄存器写1,点亮Led
- 按钮松开,输入寄存器数据为1,编程接口4返回Bit_SET,向输出寄存器写0,熄灭Led