跳转到内容
新建笔记

STM32 GPIO 配置:输出结构、寄存器、时钟与按键示例

GPIO(General Purpose Input/Output)配置的目标,是让一个实际引脚按照电路要求采样、驱动或交给外设控制。应先确定芯片型号、引脚连接与有效极性,再选择寄存器和初始化顺序。电源、复位及专用引脚并不都能改作普通 GPIO。

本页以 STM32F1 为配置主线,比较部分 F4、F7、H7 的差异。关于下拉输入停在 1.x V、内部电流路径和原图分压计算,见 STM32F1 GPIO 电平与排查。

推挽与开漏:名称和电流路径

跳转到“推挽与开漏:名称和电流路径”

典型 CMOS 推挽输出级可抽象为:

VDD ── P-MOS 的 S(源极)
D(漏极)──┐
├── 引脚
D(漏极)──┘
VSS ── N-MOS 的 S(源极)

两个栅极受内部驱动逻辑控制,使上管和下管按要求工作。上管向负载提供电流、拉高引脚,下管吸收电流、拉低引脚,这有助于理解“推挽”。这里的源漏位置限于这个输出级模型,不是“凡电源端都是源极”的通用 MOS 管辨识法;也不应把载流子方向和常规电流方向混为一谈。

开漏把下管的漏极作为输出端,未用内部上管主动拉高。漏极仍连接引脚,并非这个端子永远悬空;下管导通时吸收电流,关断时才释放节点。外部上拉、其他器件和负载共同决定释放后的电压。

项目推挽开漏
输出 0主动拉低主动拉低
输出 1主动拉高释放,由外部电路决定高电平
多个输出接同一节点不能把互相冲突的高、低输出直接相连电压、漏电、灌电流和协议条件满足时可共享
典型用途单源数字输出、外设时钟或数据、有限电流的指示灯共享低有效中断、常见 I²C、适当设计的线逻辑
边沿取决于驱动等级、负载与走线上升沿通常还受上拉与电容限制

多个开漏输出构成共享节点时,任何一个拉低都会使该节点为低。在正逻辑中,这称为“线与”;对于低有效请求,也可理解为任一请求都能使信号有效。它不意味着多个设备可以不遵守时序和仲裁就任意发送数据。

开漏也不是自动电平转换器。把上拉电源从 3.3 V 改到 5 V 之前,必须核对所有连接引脚的耐压、输入阈值、供电状态、注入电流和保护结构;不满足时应采用适当的电平转换电路。LED 要有限流措施,继电器、电机等负载需要合适的驱动级,不能用“推挽驱动强”替代电流与功耗设计。

第一步:确认所属总线并打开端口时钟

跳转到“第一步:确认所属总线并打开端口时钟”

GPIO 寄存器访问需要对应端口时钟。默认状态应查具体芯片,而不是概括成“所有外设时钟都关闭”。STM32 系列也不是共用同一条 GPIO 总线。

本页核对的器件范围GPIO 时钟使能寄存器
RM0008 对应 STM32F1RCC_APB2ENR,如 IOPAEN
RM0090 对应 STM32F4RCC_AHB1ENR,如 GPIOAEN
ST STM32F7 LL 驱动所列端口RCC_AHB1ENR
RM0433 对应 H742/H743/H753/H750RCC_AHB4ENR,如 GPIOAEN

原笔记把“F7/H7”统一列为 AHB1,不能用于 H743 等本页列出的 H7。依据分别为 RM0008、RM0090、F7 官方 LL 总线定义和 RM0433。

F1 打开 GPIOA 的三种表达属于不同代码层次,工程中选择所用库的方式即可:

/* CMSIS 设备寄存器:F1 */
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
(void)RCC->APB2ENR; /* 读回,随后才访问 GPIO */
/* F1 标准外设库(SPL) */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
/* F1 HAL */
__HAL_RCC_GPIOA_CLK_ENABLE();

F1 的 RCC_APB2ENR 位于 RCC 基址加 0x18,即 RM0008 映射中的 0x40021018;bit 0 为 AFIOEN,bit 2~6 对应 GPIOA~GPIOE 的使能。端口是否实际存在、哪些引脚引出,仍受型号和封装限制。

第二步:用正确寄存器表达模式

跳转到“第二步:用正确寄存器表达模式”

CRL 管理 pin 0~7,CRH 管理 pin 8~15。对引脚 nn,选中寄存器内的移位为 4(n mod 8)4(n\bmod8),其四位字段按 CNF[1:0] MODE[1:0] 排列。

MODE含义
00输入配置
01输出,10 MHz 速度档
10输出,2 MHz 速度档
11输出,50 MHz 速度档
CNFMODE=00 时MODE≠00 时
00模拟输入普通推挽输出
01浮空输入普通开漏输出
10上下拉输入,由 ODR 选择方向复用推挽输出
11保留复用开漏输出

这对应旧 SPL 的八类常用名称:AIN、IN_FLOATING、IPU、IPD、Out_PP、Out_OD、AF_PP、AF_OD。浮空输入是“不用内部偏置”,不表示外部已驱动的引脚电压必然不确定。模拟模式关闭相应数字输入缓冲与输出路径,不宜夸大为“整个引脚周边所有数字电路关闭”。

PA5 位于 CRL 的 bit 20~23。例如 50 MHz 普通推挽为 0x3,同档普通开漏为 0x7;上下拉输入为 0x8,还需设置 ODR 为 1(上拉)或 0(下拉)。F1 输出模式没有可独立启用的内部上下拉。

RM0090 的配置由多个寄存器共同表达:

寄存器每引脚位数编码要点
MODER200 输入、01 普通输出、10 复用、11 模拟
OTYPER10 推挽、1 开漏
OSPEEDR200 Low、01 Medium、10 High、11 Very high
PUPDR200 无上下拉、01 上拉、10 下拉、11 保留
AFRL/AFRH4选择复用功能编号,按实际引脚复用表查询

这不是“F4 以后所有系列都完全一样”的版本规则。这里仅核对上述器件范围,迁移到其他 MCU 时仍应重新检查模式、时钟、速度编码及复位值。

例如把一个两位字段从旧值改为新值,应先清除掩码再写入新值:

/* F4:PA5 设置为普通输出;其余配置应先完成 */
GPIOA->MODER = (GPIOA->MODER & ~(3u << 10u)) | (1u << 10u);

单独 |= 不能可靠地把任意旧值改成目标值。上述读改写也不具备跨线程或中断的配置事务原子性;初始化阶段应由一个明确的所有者配置该端口。

第三步:先准备初始值,再启用输出级

跳转到“第三步:先准备初始值,再启用输出级”

下面假设 F1 的 PA5 接一个低有效 LED:VDD → 限流电阻 → LED → PA5,高电平为不点亮。示例不适用于引脚已被其他外设占用的板子。

#include "stm32f1xx.h" /* 工程还需选择实际器件宏 */
void Led_Init(void)
{
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
(void)RCC->APB2ENR;
GPIOA->BSRR = 1u << 5u; /* 先预装 ODR=1 */
GPIOA->CRL = (GPIOA->CRL & ~(0xFu << 20u))
| (0x2u << 20u); /* 普通推挽,2 MHz 档 */
}
void Led_On(void)
{
GPIOA->BSRR = 1u << (5u + 16u);
}
void Led_Off(void)
{
GPIOA->BSRR = 1u << 5u;
}

BSRR 低 16 位用于置位,高 16 位用于清零;F1 的 BRR 也可清零相应位。直接写 BSRR 可避免 ODR |= ... / ODR &= ... 的读改写被中断打断、丢失其他位更新的问题,但不会自动解决多个执行者对同一输出含义的争用。

若沿用原来的 SPL 工程,应先 GPIO_SetBits() 预装不动作电平,再 GPIO_Init() 配置 GPIO_Mode_Out_PP 与所需速度。HAL 中对应先 HAL_GPIO_WritePin(),再 HAL_GPIO_Init()。这些顺序减少模式切换时的脉冲,不能保证软件运行之前的复位窗口;该阶段还需电路偏置和上电波形验证。

第四步:速度按电气时序选择

跳转到“第四步:速度按电气时序选择”

F1 的 2/10/50 MHz 是输出速度档,并不是写入后引脚就会按该频率振荡。CPU 访问、外设时钟、负载电容、供电及走线共同约束实际波形。F4 的速度名称也不能简单换算成所有电压、负载下都可达到“100 MHz”。

选择能满足接收端建立保持、上升下降时间要求的档位,再检查过冲、振铃及干扰。没有测量条件时,不应机械规定“所有 SPI 用 50 MHz、所有 UART 用 10 MHz”。按键等输入不使用输出速度字段;旧 SPL 结构体中填该成员不等于它改变输入采样速度。

原来的“速度越高,功耗一定越高”也过于简单。负载的动态功耗还与电容、电压和实际翻转频率相关,驱动配置不能替代完整功耗分析。低速指示灯通常不需要最快边沿;精确方波宜用定时器等外设,循环 HAL_Delay(1) 翻转只能得到受时基与调度影响的近似波形。

第五步:配置按键输入并处理抖动

跳转到“第五步:配置按键输入并处理抖动”

上拉输入配合按键接地,按下读低;下拉输入配合按键接 VDD,按下读高。这建立了电气极性,还不等于已得到一次可靠的“按下事件”。

F1 的 PA0 上拉输入示例:

void Key_Init(void)
{
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
(void)RCC->APB2ENR;
GPIOA->BSRR = 1u; /* ODR0=1,选择上拉 */
GPIOA->CRL = (GPIOA->CRL & ~0xFu) | 0x8u;
}

若采用下拉,预装 ODR0=0,CRL 字段仍是 0x8。外部已有明确偏置或主动驱动时,是否加内部偏置需要结合电路判断;未使用引脚也不是一律接下拉,应按器件低功耗建议和板上连接处理。

原文“延时 10 ms,再一直等待松开”的实现会阻塞主循环,按键卡住时还可能永远不返回。以下纯 C 状态机每次调用立即结束,在候选电平持续 20 ms 后接受变化,只在接受按下时返回一次 true:

#include <stdbool.h>
#include <stdint.h>
typedef struct {
bool stable_high;
bool candidate_high;
uint32_t since_ms;
} KeyDebounce;
bool Key_Update(KeyDebounce *key, bool raw_high, uint32_t now_ms)
{
if (raw_high != key->candidate_high) {
key->candidate_high = raw_high;
key->since_ms = now_ms;
}
if (key->candidate_high != key->stable_high &&
(uint32_t)(now_ms - key->since_ms) >= 20u) {
key->stable_high = key->candidate_high;
return !key->stable_high;
}
return false;
}

每隔约 1 ms 调用下面的轮询函数,传入同一递增时基的毫秒数。必须先取出 PA0 这一位,不能把整个 GPIOA->IDR 转成 bool:其他引脚为高时,整口数值仍非零。状态要跨轮询保留,不能每次调用都重新初始化。

bool Key_Poll(uint32_t now_ms)
{
static KeyDebounce key = {true, true, 0u};
bool raw_high = (GPIOA->IDR & (1u << 0u)) != 0u;
return Key_Update(&key, raw_high, now_ms);
}

20 ms 是示例消抖时间,不是所有机械开关的固有值;两次采样之间发生的抖动不可见。时间使用同一递增的 32 位毫秒计数,采用无符号差值可跨一次回绕,但不能让调用间隔长达整个计数周期。每个按键使用独立状态,调用方需保证单一所有者。

本例把上电时持续按住也视为一次按下;若产品要求先释放再接收,应另设上电抑制状态。需要区分单击、长按和连发时,在稳定状态之上增加事件逻辑。

PB6/PB7:分清软件 I²C 和硬件 I²C

跳转到“PB6/PB7:分清软件 I²C 和硬件 I²C”

原笔记将 GPIO_Mode_Out_OD 的初始化直接称为“I²C 配置”,实际上它只准备了普通开漏 GPIO,适合作为软件模拟 I²C 的引脚部分;并未提供 START、STOP、数据采样、ACK、超时等协议实现。

对于 F1 默认引脚映射的硬件 I2C1,PB6/PB7 通常对应 SCL/SDA,应按器件手册使用复用开漏,并配置外设时钟与 I²C 控制器;重映射或不同型号需另查。两种方式都应使用满足电气和时序要求的外部上拉。软件模拟时应预先释放两根线,并读取真实引脚状态,不能把 ODR=1 当作总线必定为高。

常见双向 I²C 使用开漏/开集电极以支持共享线、仲裁和时钟延展。规范也允许特定无时钟延展的单控制器系统把 SCL 设计为推挽;这不是将任意共享 SDA/SCL 改为推挽的许可,其他速度模式也有各自要求。这里的示例保持常见开漏接法。NXP UM10204,3.1.1

F1 常用寄存器速查与验证

跳转到“F1 常用寄存器速查与验证”
寄存器相对 GPIO 基址偏移用途
CRL0x00pin 0~7 配置
CRH0x04pin 8~15 配置
IDR0x08读取数字输入
ODR0x0C输出数据;上下拉输入时选择方向
BSRR0x10按位设置/清零输出数据
BRR0x14按位清零输出数据

完成配置后,读取寄存器确认没有被其他初始化覆盖,测量初始电平和输出波形,检查电流、电压和外设反馈是否满足要求。共享引脚配置应有单一所有者;输入速度字段、内部偏置、复用选择和输出命令不能相互替代。

本页合并了原来的名称解释和配置详解,保留寄存器位表、三类库接口、LED、按键及 I²C 场景。已核对列出的官方寄存器资料,消抖状态机已在主机 C 环境验证抖动、长按、释放、重复按下与计数回绕;寄存器片段尚未在具体 STM32 板上编译、下载或测量。