一、基本介绍

GPIO是通用输入输出端口。

特点:①不同型号,IO口数量可能不同。

           ②快速翻转,每次翻转最快只需要两个时钟周期。

           ③每个IO口均可作中断。

           ④支持8种工作模式。

电气特性:①STM32工作电压范围:2V≤VDD≤3.6V

                  ②GPIO识别电压范围:(COMS端口)-0.3V≤Vil≤1.164V(逻辑0)1.833V≤Vih≤3.6V(逻辑1)

                  注:查看数据手册,标FT为TTL端口,没标为COMS,电压范围不能处于(1.164V,1.833V)之间,这样状态不确定。

                  ③GPIO输出电流:单个IO,最大25mA

二、基本结构

① 保护二极管

保护二极管共有两个,用于保护引脚外部过高或过低的电压输入。当引脚输入电压高于 VDD 时,上面的二极管导通,当引脚输入电压低于VSS时,下面的二极管导通,从而使输入芯片内部的电压处于比较稳定的值。虽然有二极管的保护,但这样的保护却很有限,大电压大电流的接入很容易烧坏芯片。所以在实际的设计中要考虑设计引脚的保护电路。

② 上拉、下拉电阻

它们阻值大概在30~50K 欧之间,可以通过上、下两个对应的开关控制,这两个开关由寄存器控制。当引脚外部的器件没有干扰引脚的电压时,即没有外部的上、下拉电压,引脚的电平由引脚内部上、下拉决定,开启内部上拉电阻工作,引脚电平为高,开启内部下拉电阻工作,则引脚电平为低。同样,如果内部上、下拉电阻都不开启,这种情况就是我们所说的浮空模式。 浮空模式下,引脚的电平是不可确定的。引脚的电平可以由外部的上、下拉电平决定。需要注 意的是,STM32的内部上拉是一种“弱上拉”,这样的上拉电流很弱,如果有要求大电流还是 得外部上拉。

③ 施密特触发器

对于标准施密特触发器,具有双阈值(正向阈值电压和负向阈值电压),当输入电压高于正向阈值电压,输出为高;当输入电压低于负向阈值电压,输出为低;输入电压在两者之间时输出保持不变,这种特性称为滞回现象,因此施密特触发器有记忆性。从本质上来说,施密特触发器是一种双稳态多谐振荡器。它可作为波形整形电路,将模拟信号整形为方波,并且由于滞回特性,可用于抗干扰

④ P-MOS管和N-MOS管

这个结构控制GPIO的开漏输出和推挽输出两种模式。开漏输出:输出端相当于三极管的集电极,要得到高电平状态需要上拉电阻才行。推挽输出:这两只对称的MOS管每次只有一只导通,所以导通损耗小、效率高。输出既可以向负载灌电流,也可以从负载拉电流。推挽输出既能提高电路的负载能力,又能提高开关速度。

注:关于开漏输出和推挽输出可看这个链接https://zhuanlan.zhihu.com/p/1913227702481158423

三、八种工作模式

1. 输入浮空

  • 特点:输入用,电平状态完全由外部决定。由于内部既不上拉也不下拉,引脚悬空时电平不确定(容易受到干扰)。

2. 输入上拉

  • 特点:输入用,内部上拉电阻接通。默认状态下(外部无驱动时),读取为高电平

3. 输入下拉

  • 特点:输入用,内部下拉电阻接通。默认状态下(外部无驱动时),读取为低电平

4. 模拟功能

  • 特点用于 ADC 采集电压或 DAC 输出电压。在此模式下,GPIO 的施密特触发器被关闭(禁止输入),上下拉电阻也被断开,引脚模拟信号直接进入片上外设(如 ADC)。

5. 开漏输出

  • 特点只能输出低电平,不能主动输出高电平。要获得高电平,必须依靠外部上拉电阻。

  • 内部状态

    • 写 1:N-MOS 管截止,引脚状态由外部上拉电阻决定(通常为高电平)。

    • 写 0:N-MOS 管导通,引脚输出低电平。

6. 推挽输出

  • 特点可以输出高电平(VDD)和低电平(VSS),驱动能力强(灌电流和拉电流能力较好)。

7. 开漏式复用功能

  • 特点:与开漏输出类似,不能主动输出高电平,需要外接上拉电阻。但区别在于,输出数据的来源不是 CPU 写入 ODR 寄存器,而是片上外设(如 USART 的 TX 引脚、I2C 的 SDA/SCL 引脚等)。

8. 推挽式复用功能

  • 特点:与推挽输出类似,可输出高低电平,驱动能力强。但输出数据由片上外设控制

四、GPIO寄存器介绍

1. 基本介绍

(F4/F7/H7系列)GPIO通用寄存器GPIOX_yyy

MODER

OTYPER

OSPEEDR

PUPDR

IDR

ODR

BSRR

LCKR

设置模式

设置输出类型

设置输出速度

设置上下拉电阻

输入数据

输出数据

设置ODR寄存器值

配置锁定,用的不多

2.8种工作模式对应的配置

GPIO工作模式

模式寄存器

MODER[0:1]

输出类型寄存器

OTYPER

输出速度寄存器

OSPEEDR[0:1]

上拉/下拉寄存器

PUPDR[0:1]

输入浮空

00-输入模式

无效

无效

00-无上拉或下拉

输入上拉

01-上拉

输入下拉

10-下拉

模拟功能

11-模拟模式

00-无上拉或下拉

开漏输出

01-通用输出

1-开漏输出

00-低速

01-中速

10-高速

11-超高速

00-无上拉或下拉

01-上拉

10-下拉

11-保留

推挽输出

0-推挽输出

开漏式复用功能

10-复用功能

1-开漏输出

推挽式复用功能

0-推挽输出

3.端口输入数据寄存器(IDR)

4.端口输出数据寄存器(ODR)

5.端口置位/复位寄存器(BSRR)

ODR和BSRR寄存器控制输出有什么区别?

ST官方给的答案:使用ODR,在读和修改访问之间产生中断时,可能会发生风险;BSRR则无风险。

BSRR VS ODR

GPIOB->ODR |= 1<<3; /*PB3=1*/  ODR |= 1<<3 等同于 ODR = ODR | (1<<3)

GPIOB->BSRR = 0x00000008;/*PB3=1*/

ODR修改:读>改>写

BSRR修改:      写

综上,建议使用BSRR寄存器控制输出!

五、通用外设驱动模型(四步法)

六、GPIO配置步骤

1. HAL_GPIO_Init 函数

函数原型
void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init);

功能
初始化 GPIO 引脚,配置工作模式、上下拉、输出速度、复用功能,并可设置 EXTI 中断。

关键参数

  • GPIOx:端口基址,如 GPIOA、GPIOB 等(STM32F407支持 GPIOA~GPIOG)。

  • GPIO_Init:指向初始化结构体的指针,包含以下成员:

    • Pin:引脚号(0~15),可单引脚或组合(如 GPIO_PIN_All)。

    • Mode:模式选择,包括输入、输出(推挽/开漏)、复用(推挽/开漏)、模拟、外部中断/事件(上升沿/下降沿/双边沿)。

    • Pull:上下拉设置(无、上拉、下拉)。

    • Speed:输出速度(低、中、高)。

    • Alternate:复用功能编号,需查阅数据手册确定。

返回值

注意事项
EXTI 外部中断的配置也在此函数中完成,不再单独设置。

2. HAL_GPIO_WritePin 函数

函数原型
void HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState);

功能

用于设置引脚输出高电平或者低电平,通过BSRR寄存器复位或者置位操作。

关键参数

  • GPIOx:端口号。

  • GPIO_Pin:引脚号。

  • PinState:输出状态,可选 GPIO_PIN_SET(高电平)或 GPIO_PIN_RESET(低电平)。

返回值


3. HAL_GPIO_TogglePin 函数

函数原型
void HAL_GPIO_TogglePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin);

功能

用于设置引脚的电平翻转,也是通过BSRR寄存器复位或者置位操作。

关键参数

  • GPIOx:端口号。

  • GPIO_Pin:引脚号。

返回值

七、编程实战:跑马灯

1.0805贴片发光二极管

2.LED原理图
3.GPIO输出配置步骤

1)使能对应GPIO时钟

STM32 在使用任何外设之前,都要先使能其时钟。本实验用到PF9和PF10两个IO口,因此需要先使能GPIOF的时钟,代码如下: __HAL_RCC_GPIOF_CLK_ENABLE();

2)设置对应GPIO工作模式 

本实验GPIO使用推挽输出模式,控制LED亮灭,通过函数HAL_GPIO_Init设置实现。

3)控制GPIO引脚输出高低电平

在配置好GPIO工作模式后,可以通过HAL_GPIO_WritePin函数控制GPIO引脚输出高低电平,从而控制LED的亮灭了。                 

4.代码详解(led.h)
#ifndef __LED_H           // 防止头文件被重复包含:如果未定义 __LED_H 宏,则编译下面的代码
#define __LED_H           // 定义 __LED_H 宏,标记本文件已被包含

#include "./SYSTEM/sys/sys.h"   // 包含系统级头文件,提供 HAL 库依赖、数据类型等基础定义

/******************************************************************************************/
/* 引脚 定义 */

// LED0 连接在 GPIOF 端口的第 9 号引脚上
#define LED0_GPIO_PORT                  GPIOF          // LED0 使用的端口
#define LED0_GPIO_PIN                    GPIO_PIN_9    // LED0 使用的引脚编号

// LED1 连接在 GPIOF 端口的第 10 号引脚上
#define LED1_GPIO_PORT                  GPIOF          // LED1 使用的端口
#define LED1_GPIO_PIN                    GPIO_PIN_10   // LED1 使用的引脚编号

// 使能 GPIOF 端口时钟的宏(必须调用后才能操作该端口的寄存器)
// do{ ... }while(0) 是一种安全封装,确保宏在使用时行为正确(加分号也不会出错)
#define LED0_GPIO_CLK_ENABLE()          do{ __HAL_RCC_GPIOF_CLK_ENABLE(); }while(0)
#define LED1_GPIO_CLK_ENABLE()          do{ __HAL_RCC_GPIOF_CLK_ENABLE(); }while(0)

/******************************************************************************************/

/* LED 控制宏(设置高低电平) */
// 如果参数 x 为真(非零),则调用 HAL_GPIO_WritePin 设置引脚为高电平(LED灭)
// 如果 x 为假(0),则设置引脚为低电平(LED亮)
// 注意:具体亮灭取决于硬件连接方式,此处低电平点亮
#define LED0(x)   do{ x ? \
                      HAL_GPIO_WritePin(LED0_GPIO_PORT, LED0_GPIO_PIN, GPIO_PIN_SET) : \
                      HAL_GPIO_WritePin(LED0_GPIO_PORT, LED0_GPIO_PIN, GPIO_PIN_RESET); \
                  }while(0)       /* LED0 控制,RED */

#define LED1(x)   do{ x ? \
                      HAL_GPIO_WritePin(LED1_GPIO_PORT, LED1_GPIO_PIN, GPIO_PIN_SET) : \
                      HAL_GPIO_WritePin(LED1_GPIO_PORT, LED1_GPIO_PIN, GPIO_PIN_RESET); \
                  }while(0)       /* LED1 控制,GREEN */

/* LED 电平翻转宏(每次调用改变一次状态) */
#define LED0_TOGGLE()    do{ HAL_GPIO_TogglePin(LED0_GPIO_PORT, LED0_GPIO_PIN); }while(0)  // 翻转 LED0
#define LED1_TOGGLE()    do{ HAL_GPIO_TogglePin(LED1_GPIO_PORT, LED1_GPIO_PIN); }while(0)  // 翻转 LED1

/******************************************************************************************/
/* 外部接口函数声明 */
void led_init(void);        // LED 初始化函数,需在源文件(led.c)中实现

#endif                      // 结束头文件保护

补充说明:

  • 头文件保护#ifndef __LED_H ... #endif 确保编译器只处理一次本文件,避免重复定义错误。

  • 宏定义:使用大写命名,增加可读性。宏在预处理阶段直接替换文本,无函数调用开销。

  • do{ ... }while(0) 的作用:让宏在使用时可以像普通函数一样加上分号,且不会引发语法错误(例如在 if 语句中不加花括号时也能正确执行)。

  • 时钟使能:STM32 外设使用前必须开启对应时钟,否则寄存器操作无效。

  • 模块化设计:将硬件相关的引脚定义和操作封装在头文件中,上层代码(如 main.c)只需调用 led_init() 和宏即可控制 LED,提高了代码的可移植性。

5.代码详解(led.c)
#include "./BSP/LED/led.h"      // 包含 LED 相关的头文件,里面定义了引脚、宏等

/**
 * @brief       初始化LED相关IO口, 并使能时钟
 * @param       无
 * @retval      无
 */
void led_init(void)
{
    GPIO_InitTypeDef gpio_init_struct;  // 定义一个 GPIO 初始化结构体变量,用于配置引脚参数
    
    // 使能 LED0 和 LED1 所在 GPIO 端口的时钟
    // 在 STM32 中,任何外设(包括 GPIO)在使用前都必须先开启其时钟,否则无法工作
    LED0_GPIO_CLK_ENABLE();             // 使能 GPIOF 时钟(根据宏定义,LED0 在 GPIOF)
    LED1_GPIO_CLK_ENABLE();             // 同样使能 GPIOF 时钟(两个 LED 共用同一端口,只使能一次也可以)

    // 配置 LED0 引脚
    gpio_init_struct.Pin = LED0_GPIO_PIN;               // 指定要配置的引脚:LED0 的引脚号(GPIO_PIN_9)
    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;        // 设置为推挽输出模式:可以输出高/低电平,驱动能力强
    gpio_init_struct.Pull = GPIO_PULLUP;                // 设置内部上拉电阻:引脚默认被拉高(配合低电平点亮 LED 的设计,关灯时输出高)
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;      // 设置输出速度为高速:对于 LED 这样简单的负载,低速也可以,但这里设为高速更通用
    HAL_GPIO_Init(LED0_GPIO_PORT, &gpio_init_struct);   // 调用 HAL 库函数,将配置写入寄存器,完成 LED0 引脚的初始化

    // 配置 LED1 引脚(与 LED0 使用相同的参数,只是引脚号不同)
    gpio_init_struct.Pin = LED1_GPIO_PIN;               // 修改引脚号为 LED1 的引脚(GPIO_PIN_10)
    HAL_GPIO_Init(LED1_GPIO_PORT, &gpio_init_struct);   // 再次调用初始化函数,配置 LED1 引脚
    
    // 初始化完成后,默认关闭两个 LED
    // 根据硬件设计,LED 低电平点亮,高电平熄灭
    // LED0(1) 宏定义为:参数为 1 时输出高电平,即关闭 LED
    LED0(1);    // 关闭 LED0
    LED1(1);    // 关闭 LED1
}

补充说明:

  • 为什么要使能时钟?
    STM32 的每个外设都有一个时钟开关,关闭时钟时外设完全不工作,这样可以省电。使用前必须通过 __HAL_RCC_GPIOx_CLK_ENABLE() 开启时钟。

  • 什么是推挽输出?
    推挽输出模式下,引脚可以主动输出高电平(连接 VDD)或低电平(连接 VSS),驱动能力较强,适合驱动 LED。

  • 为什么设置上拉?
    设置上拉电阻可以使引脚在无外部信号时保持高电平。这里配合 LED 低电平点亮的设计,上拉确保初始状态为高电平,LED 熄灭。

  • 速度设置有什么影响?
    速度指的是 I/O 口的响应速度,对于 LED 这种低速设备,低速即可,但高速设置也不影响功能,所以这里用高速保证兼容性。

  • 为什么初始化后要关灯?
    防止上电瞬间引脚电平不确定导致 LED 误亮,初始化后明确设置输出状态,保证系统启动时 LED 是熄灭的。

6.代码解析(main.c)
// 包含必要的头文件
#include "./SYSTEM/sys/sys.h"      // 系统核心文件,包含时钟配置、中断分组等基础函数
#include "./SYSTEM/usart/usart.h"   // 串口通信相关函数(本示例未使用,但保留以便后续扩展)
#include "./SYSTEM/delay/delay.h"   // 延时函数头文件,提供毫秒级和微秒级延时
#include "./BSP/LED/led.h"          // LED 控制头文件,包含 LED 初始化及控制宏

/**
 * @brief       主函数:程序入口
 * @param       无
 * @retval      无
 */
int main(void)
{
    // 1. 初始化 HAL 库
    // HAL_Init() 是 STM32 HAL 库的初始化函数,它负责:
    //   - 设置 SysTick 定时器(用于提供 HAL 库内部的时基,如 HAL_Delay)
    //   - 初始化全局中断优先级分组(默认设置为 NVIC_PRIORITYGROUP_4,即 4 位抢占优先级)
    //   - 初始化底层硬件(如 Flash 接口)
    HAL_Init();

    // 2. 配置系统时钟
    // sys_stm32_clock_init 是自定义的时钟初始化函数(来自 sys.c)
    // 参数说明:336, 8, 2, 7 分别对应 PLL 相关分频系数,最终得到系统时钟 168MHz
    // 具体计算方式:外部晶振一般为 8MHz,经过一系列倍频/分频后得到 168MHz
    // 这里不深入计算细节,只需知道系统时钟被设置为 168MHz 即可
    sys_stm32_clock_init(336, 8, 2, 7);   // 设置系统时钟为 168MHz

    // 3. 初始化延时函数
    // delay_init() 需要传入系统时钟频率(单位 MHz),用于精确计算延时
    // 参数 168 表示系统时钟为 168MHz,这样后续 delay_ms() 和 delay_us() 才能正常工作
    delay_init(168);

    // 4. 初始化 LED 相关的 GPIO 引脚
    // led_init() 在 led.c 中实现,会配置 LED0 和 LED1 的引脚为推挽输出模式
    // 并使能对应 GPIO 端口的时钟,最后默认关闭两个 LED(输出高电平)
    led_init();

    // 5. 主循环:程序会一直在这里循环执行
    while(1)
    {
        // LED0(0) 是一个宏定义,参数为 0 表示输出低电平(点亮 LED0)
        // 根据硬件设计,LED 低电平点亮,高电平熄灭
        LED0(0);        // 点亮 LED0(红色)
        LED1(1);        // 熄灭 LED1(绿色),参数 1 表示输出高电平
        delay_ms(500);  // 延时 500 毫秒,保持当前状态

        LED0(1);        // 熄灭 LED0
        LED1(0);        // 点亮 LED1
        delay_ms(500);  // 延时 500 毫秒,切换状态
    }
}

补充说明:

  • 头文件包含顺序:通常先包含系统文件(如 sys.h),再包含外设驱动文件,最后包含板级支持包(led.h)。这样能保证依赖关系正确。

  • 时钟配置:STM32 的时钟系统比较复杂,新手可以先使用现成的配置函数(如本例中的 sys_stm32_clock_init),后续再深入学习时钟树。

  • 延时函数delay_ms 是基于 SysTick 定时器实现的,必须在使用前调用 delay_init 进行初始化,否则延时不准。

  • 主循环while(1) 是嵌入式程序的标准写法,保证程序持续运行,不会退出。

  • LED 亮灭逻辑:代码中 LED 交替亮灭,间隔 0.5 秒,形成一个简单的流水灯效果。可以通过修改延时时间改变闪烁频率。

7.下载验证

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐