STM32 GPIO输入输出实战教程:江协经验分享,避免常见误区
江协stm32学习:3-1~3-4 GPIO输出、GPIO输入
一、GPIO简介
二、GPIO的结构
1、基本结构

2、位结构


3、驱动器配置:输入、输出模式
1)输入模式
输入模式下,输出都是无效的。
上拉模式相当于默认高电平模式,下拉模式则是默认低电平模式,不接属于浮空输入,极易受到外界环境影响。由于浮空输入的电平是不确定的,所以使用浮空输入的时候,端口一定要接上一个连续的驱动源,不能出现悬空的状态。
而模拟输入的特征是GPIO无效,引脚直接接入内部ADC。是ADC模数转换器的专属配置。
2)输出模式
输出模式下,输入仍然有效。
推挽输出模式下,P-MOS和N-MOS均有效,数据寄存器为1时,上管导通,下管断开,输出直接接到VDD,就是输出高电平;为0时,反之。这种模式下,高低电平均有较强的驱动能力,所以也叫强推输出模式。在此模式下,STM32对IO口具有绝对的控制权
在开漏模式下,P-MOS无效,只有N-MOS在工作。数据寄存器为1时,下管断开,输出相当于断开,也就是高阻模式;数据寄存器为0时,下管导通,输出直接接到VSS,也就是输出低电平。这种模式下,低电平有驱动能力,高电平无。可以作为通信协议的驱动方式,在多机通信的情况下,可以避免多个设备的相互干扰;还可以用于输出5V的电平信号。
复用的输出引脚电平是由片上外设控制。
关闭状态,是当引脚配置为输入模式时,两MOS管均无效,也就是输出关闭,端口的电平由外部信号控制。

(想要详细了解每个模式对应的电路,可以从19:42开始看,到23:18 江协 3-1GPIO)
4、GPIO寄存器配置
1)端口配置寄存器
每个端口模式需要4位进行配置,16个端口就需要64位,所以这里的配置寄存器有两个。一个是端口配置低寄存器,一个是端口配置高寄存器。
GPIO的输出速度可以限制输出引脚的最大翻转速度,为了低功耗的稳定性的,一般设置为50MHz
2)端口输入寄存器和端口输出寄存器
低16位对应16个引脚,高16位没有使用
3)端口位设置/清除寄存器
高16位进行位清除,低16位进行位设置,写1:设置或者清除;0:不影响
4)端口位清除寄存器
低16位进行位清除。存在的意义是方便操作,如果只想单一的进行设置或者位清除,那么位设置时使用3),位清除时使用4)。对多个的话,用3),可以保证同步性
5)端口配置锁定寄存器
对端口的配置进行锁定,防止意外更改。
三、GPIO初始化
1、操作STM32的GPIO的三个步骤
1、使用RCC开启GPIO的时钟 (RCC使能时钟)
2、使用GPIO_Init函数初始化GPIO
3、使用输出或者输入的函数控制GPIO口
一共涉及RCC和GPIO两个外设
2、RCC库函数介绍(使能时钟)
library->rcc.h,在h文件的最下面时库函数所有函数的声明
常用的有三种:第一个参数是选择哪个外设。第二个参数是使能enabled或失能disable。
void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState NewState);
void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);
void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);
//第一个参数是选择哪个外设。第二个参数是使能enabled或失能disable。
void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState NewState);//RCC AHB外设时钟控制
void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);//RCC APB2外设时钟控制
void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);//RCC APB1外设时钟控制
3、GPIO库函数介绍——初始化函数
(输入输出函数在后续输入输出部分分开写)
void GPIO_DeInit(GPIO_TypeDef* GPIOx); //将指定的GPIO端口恢复到默认状态。
void GPIO_AFIODeInit(void); //将AFIO复位到默认状态。
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct); //非常重要,是使用结构体的参数来初始化GPIO口 //一般我们初始化外设都是使用init函数
void GPIO_StructInit(GPIO_InitTypeDef* GPIO_InitStruct); //给GPIO初始化结构体赋一个默认值
※结构体配置 GPIO_InitTypeDef
Mode: 指定GPIO的工作模式(如输入、输出、复用功能等)。
Speed: 指定输出速度(仅适用于输出模式)。
Pull: 指定是否启用内部上拉或下拉电阻。
Alternate: 指定复用功能编号(仅适用于复用功能模式)。
Pin: 指定要配置的引脚。
| 模式宏定义 | 描述 | 备注 |
|---|---|---|
GPIO_MODE_INPUT |
输入模式 | 用于接收来自外部的信号 |
GPIO_MODE_OUTPUT_PP |
推挽输出模式 | 用于输出信号,能够驱动较高或较低的电平 |
GPIO_MODE_OUTPUT_OD |
开漏输出模式 | 用于输出信号,需要外接上拉电阻 |
GPIO_MODE_AF_PP |
复用功能推挽输出模式 | 用于复用功能(如I2C、USART等)的推挽输出 |
GPIO_MODE_AF_OD |
复用功能开漏输出模式 | 用于复用功能(如I2C、USART等)的开漏输出 |
GPIO_MODE_ANALOG |
模拟模式 | 引脚配置为模拟输入,用于ADC等模拟外设 |
GPIO_MODE_IT_RISING |
上升沿触发中断输入模式 | 输入信号的上升沿触发中断 |
GPIO_MODE_IT_FALLING |
下降沿触发中断输入模式 | 输入信号的下降沿触发中断 |
GPIO_MODE_IT_BOTH_EDGE |
双边沿触发中断输入模式 | 输入信号的上升沿和下降沿都触发中断 |
#include "stm32f10x.h" // Device header
int main(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_0;
//GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;//可以同时初始化多个引脚
//GPIO_InitStructure.GPIO_Pin = GPIO_Pin_ALL;//初始化所有引脚
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure)
while (1)
{
}
}
四、GPIO输出
1、硬件电路(LED灯和蜂鸣器为例)

对于功率较低的,可以使用左侧的两种电路。又由于很多单片机或者芯片都使用了高电平弱驱动,低电平强驱动的规则,这样可以一定程度上避免高低电平打架。所以通常会使用。左上的电路。这样在输出低电平的时候电路可以工作,是强驱动。
对于功率稍微大一点的,直接用IO口驱动会导致stm32负担过重,这时可以使用一个三极管驱动电路(最简单的驱动电路)来完成驱动的任务。图中右侧上方是PNP电路,下方是NPN。
浅谈单片机的GPIO外部驱动电路(三极管驱动电路)这一篇文我觉得讲的挺好,简单的电路和驱动电路都有,而且讲解了设计思路,告诉你了为什么这样设计,我觉得初学者也可以看。
2、写入函数(GPIO输出)
//第一个参数是端口类型GPIOA、GPIOB等,第二个是要设置的引脚编号
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); //将指定的端口设置为高电平
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); //将指定的端口设置为低电平
//第一个参数是端口类型GPIOA、GPIOB等,第二个是要设置的引脚编号,第三个是引脚输出值,使用Bit_SET表示高电平,Bit_RESET表示低电平,如果想使用0、1来表示低电平和高电平,需要加上强制类型转换
void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal);
GPIO_WriteBit(GPIOA, GPIO_Pin_5, Bit_SET); // 将GPIOA的第5引脚设置为高电平
GPIO_WriteBit(GPIOA, GPIO_Pin_5, Bit_RESET); // 将GPIOA的第5引脚设置为低电平
//如果想使用0、1来表示低电平和高电平,需要加上强制类型转换
GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)0);
GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)1);
//第一个参数是端口类型GPIOA、GPIOB等,第二个是指定要写入的端口值,每个位对应一个引脚的输出值(0表示低电平,1表示高电平)。
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal); //同时对16个端口进行写入操作
GPIO_Write(GPIOA, 0x00FF); // 将GPIOA的低8位设置为高电平,高8位保持不变
五、GPIO输入
1、硬件电路(按键和传感器为例)
对于按键电路,一般使用上两种,也就是下接的方式,这是电路设计的习惯与规范。
左上和左下的都需要配置上拉/下拉模式,以免出现悬空状态;右边两个随意,其实就是把stm32内部的上拉、下拉电阻移在了外面。
上面两种接法,按键按下时引脚时低电平,松手是高电平;下面两种接法,按键按下时引脚是高电平,松手是低电平。
2、读取函数(GPIO输入)
1)读取输入寄存器
//第一个参数指定外设,第二个参数指定引脚
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); //用来读取输入数据寄存器某个端口的输入值
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx); //读取整个输入寄存器//返回一个16位的数据,每一位代表一个端口值
2)读取输出寄存器
uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); //用来读取输出数据寄存器某个端口的输入值,所以严格来说它不属于读取输入,而是在输出的时候看看自己在输出啥,方便检查的
uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx); //读取整个输出寄存器
六、模块化封装
一般来说,对于这种驱动代码而言,我们一般都会把它封装起来,方便管理和移植。.c用来存放驱动程序的主体代码,.h用来存放这个驱动程序可以对外提供的函数或变量的声明
1、h文件
h文件的格式很固定,indef+define+endif
#ifndef __LED_H:检查__LED_H这个宏是否未被定义。如果__LED_H未被定义,那么编译器会执行#ifndef和#endif之间的代码。
#define __LED_H:定义一个名为__LED_H的宏,这个宏定义的作用是标记这个头文件已经被包含过,防止后续代码再次包含这个头文件。
#endif:“结束条件编译”(End If)的指令,表示#ifndef块的结束。
#ifndef和#endif之间的代码:c文件中定义的函数的声明,表明它可以被外部引用。后加分号。
#ifndef __LED_H
#define __LED_H
void led_init(void);
void led_on(void);
void led_off(void);
#endif
2、c文件
首先引入stm32fx10x.h头文件
然后开始写驱动程序的函数,初始化可以全部放在一个函数里面,这样之后主程序只需要调用一个函数就可以完成全部初始化
#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);
}
void LED_On(void)
{
GPIO_SetBits(GPIOA, GPIO_Pin_1 | GPIO_Pin_2); // 将引脚1和2设置为高电平,点亮LED
}
void LED_Off(void)
{
GPIO_ResetBits(GPIOA, GPIO_Pin_1 | GPIO_Pin_2); // 将引脚1和2设置为低电平,熄灭LED
}
作者:白鱼不小白