一 本章简介
TIP
【下面这些是我的备份下载链接,如果不能顺畅访问st官方可以从这里下载】
辅助资料(建议从 ST 官方获取最新版本,请结合下面的芯片手册进行学习):
- STM32F407x/E Datasheet(规格书,含绝对最大额定值与 I/O 电气特性)
- RM0090 Reference Manual(参考手册,GPIO 章节与寄存器)
1.1 学习目标
- 理解 GPIO 是什么、能干什么。
- 认清 STM32F4 GPIO 的内部结构、输入输出模式、上下拉、复用,中断模式等。
- 掌握 驱动能力、IO 速度(边沿速率)、5V 容忍、电平匹配 的工程边界。
- 明白 STM32F4 内部的GPIO寄存器的结构及这些寄存器都代表了什么,如何配置引脚。
1.2 重点提示
- 本章是单片机控制外设的基础,一定要理解GPIO的工作模式。
- GPIO就是我们常说的IO引脚,它直接从芯片上引出,可以被设置为输入或输出信号。没有专用复用功能的GPIO主要用来控制简单的设备,比如点亮LED灯或者读取按键的状态。
- GPIO的输出能力是有限的,一般建议在5~8mA以下,它无法直接驱动大电流设备,比如电机,蜂鸣器等。
- GPIO的耐受电压是有限的,STM32的大部分引脚都支持5V容忍,但是5V 容忍(FT)并非“任意情况皆可”。仅对【标注为 5V tolerant 的数字引脚】在【输入或开漏输出(OD)且内部上拉关闭】时有效;ADC/模拟功能不可 5V 容忍,一旦超压,极有可能导致芯片损毁。
- 下文中所说的IO驱动速度说的是【边沿速率/驱动强度】,也就是引脚从高电平到低电平的时间,驱动速度越高,EMI干扰就越大,如果你只是点灯等普通应用,直接用低速就好了。
1.3 基础概念与术语
复用(AF, Alternate Function):【本章暂不涉及具体的复用功能,后续章节会介绍】GPIO 引脚的控制权交给片上外设使用,比如我们的手机TYPE-C口,插充电头时可以用来充电,插电脑时可以用来传输数据,插耳机时又可以变身成音频输出口。复用功能说的就是我们可以通过软件配置(寄存器设置),将这个GPIO引脚给芯片内部特定的硬件外设(UART,SPI,I2C,定时器等)专用,不再受主控核心来控制。
推挽(PP, Push-Pull):有很高的驱动能力,信号完整性好,高低电平的切换速度很快。当输出高电平时,PMOS管(上管)闭合,直接连接到VDD,主动把输出脚“推”到高电平;当输出低电平时,NMOS管(下管)闭合,连接到GND,主动把输出脚“拉”到低电平。无论在哪种状态,线路上都有很强的驱动能力,电平明确。这个状态下,既能对外输出电流(输出高电平时),也能吸收外部的电流(输出为低电平时)。
开漏(OD, Open-Drain):只有“拉低”的功能(NMOS下管)。要输出低电平时,下管闭合,引脚被拉到GND。要输出高电平时,下管断开,引脚相当于悬空(高阻态),自身无法输出高电平,必须依靠外部电路(通常是上拉电阻)将电平“拉”上去。一般用于IIC通讯,以及需要线与的场景(多个开漏输出的设备可以直接连在一条线上,任何一个设备输出低电平,整条线就是低电平;只有所有设备都输出高阻态,线才会被上拉电阻变为高电平)。
通用(GP,General Purpose):指引脚或端口的基本功能是可编程的,既可以通过配置用作输入,也可以用作输出,它是上面这三种功能(AF,PP,OD)的基础。
上拉/下拉(PU/PD):引脚悬空时,极易受外界电磁干扰,电平状态不确定。我们就可以通过芯片内部电阻将输入脚默认拉到 1(高电平) 或 0(低电平),一般典型值在40kΩ左右。
上升沿/下降沿:信号从低到高/从高到低的边沿,这个说的不是电平状态,而是电平变化的过程,比如说中断,计数器计数一般都是靠这个“电平变化的瞬间”来触发的(边沿触发)。
灌电流/拉电流:灌(sink,电流流入 MCU 引脚),拉(source,电流流出 MCU 引脚),一般情况下,芯片的灌电流能力会略微大于拉电流能力。
5V 容忍(FT):在【数字(高低电平)输入模式】下引脚可承受 5V 电平(具体要看芯片的数据手册),STM32的工作电压是3.3V,IO口被输入3.3V时就会被认为当前为高电平(1)。但是有些设备高电平是5V的,输出5V表示为高电平(1),带有FT(5V容忍)标识的引脚即使接到5V设备上,也不会被烧毁,并且能将5V电平正常识别为高电平。
二 什么是GPIO?
名词定义:GPIO,全称通用输入/输出口(General-Purpose Input/Output),是微控制器(MCU)中最基本、最常用的外设接口(没有之一)。它就像微控制器的“手脚”,就是 MCU 上的“可编程引脚”,通过电平的高低(1或0)与外部设备进行交互。大部分引脚还能复用为片上外设的信号线(如UART/SPI/I2C/TIM等),来实现更复杂的功能。
一般我们会简称其为 IO 或者 引脚。
2.1 常见用途
- 输出功能(控制与驱动):控制 LED 灯、蜂鸣器(需要驱动电路支持)等简单外设;作为片选(CS)、使能(EN)信号控制其他芯片;通过驱动电路控制 MOSFET、继电器等,实现大功率设备的开关。
- 输入功能(感知与响应):读取按键、拨码开关等数字信号,GPIO可以读取到高低电平的变化来判断按键是否被按下;检测外部数字传感器输出的高低电平;配置为外部中断,用于快速响应外部事件,可以让MCU中断当前执行的任务,优先来处理这个外部事件,比如编码器计数,限位开关,休眠唤醒等)。
- 复用功能(更强大的功能):复用之后虽然也是输出高低电平,但此时GPIO的控制权就交给对应的外设了,可以作为串口(UART)的发送/接收引脚;作为 SPI 或 I2C 总线的数据线或时钟线;作为定时器(TIM)的 PWM 输出或输入捕获引脚,作为ADC(数模转换-模拟量读取),DAC(模数转换-模拟量输出)的功能承担脚。
当前这些功能可能大家还不了解,有个简单的认知就行,随着后续学习,渐渐都会理解的。
2.2 STM32系列的GPIO的命名方式
首先我们看张图,这个是从 [STM32F407的规格书] 上截取的,下面这个就是我们天空星核心板上的用到的芯片,STM32F407VET6(高配版上的芯片是STM32F407VGT6,和低配版天空星的区别就是FLASH存储从512K升级为了1024K,其他都一样),这个芯片所用的封装是LQFP100,也就是说总共有100个引脚,这些引脚中有一部分是专用引脚(电源,基准,地等),并不是所有IO都可以进行控制的。

我们看上面的第一个引脚(左上角),它是PE2。STM32引脚的命名方式是端口号+引脚号,端口一般用字母 A到I(STM32F407系列 常见有 GPIOA到GPIOI),每个端口最多 16 个引脚,编号 0~15。PE2就表示这个
命名格式是:P(PORT) + 端口号 + 引脚号,比如 PA0、PB7、PC12 等。类似这种命名格式的都是我们可以去控制IO电平的引脚。
除了类似上面这些命名方式的引脚,其他的都不能当作普通GPIO(可以由程序控制来输出高低电平)来使用的,比如说VDD/VSS/VCAP 等。
- 电源/地:VDD、VSS、VDDA、VSSA、VBAT
- 稳压/基准:VCAP_1、VCAP_2、VREF+、VSSA
- 复位/启动:NRST、BOOT0、PB2-BOOT1(系统成功启动后可作为IO使用,在天空星核心板上被下拉后接在LED灯上面)
- 时钟:OSC_IN/OSC_OUT、OSC32_IN/OSC32_OUT(PC14/PC15)
2.3 STM32F4的GPIO输入输出能力及特性
2.3.1 电流特性
继续查看我们主控芯片的数据手册,找到 Table 12. Current characteristics 这个表格。

可以得到下面这些信息:
- 全部芯片电源脚的总电流:I_VDD(流入 VDD)≤ 240 mA,I_VSS(流出 VSS)≤ 240 mA
- 单个引脚灌/拉电流 I_IO:最大 25 mA,单个GPIO长期电流建议要 ≤ 10–15 mA,做项目一定要留足余量,建议电流要在8mA以下。比如我们在驱动LED灯时一定要记得加电阻来限制电流。
- 注入电流(外部电源向内部电路输入电流) I_INJ(PIN):
- 对【5V 容忍】引脚:-5 mA 到 +0 mA(只允许负向注入(电流流入引脚),正向注入(电流流出引脚)为 0)
- 对其他引脚:±5 mA
- 全芯片注入电流绝对值总和 Σ|I_INJ(PIN)|:≤ ±25 mA
- 重点提示:
- 负向注入会干扰模拟外设(ADC,DAC等)。
- 以上为“绝对额定值” 范畴,不能拿来当“长期工作条件”,一定要留余量,否则极易导致芯片损毁。
除 PC13、PC14 和 PC15 引脚(最大灌电流/拉电流为±3mA)外,所有 GPIO引脚均可提供最高 ±8mA 的驱动电流,最大可达±20mA。当 PC13 至 PC15 引脚配置为输出模式时,驱动速度不应超过 2MHz,且最大负载电容为 30pF。在天空星核心板中,PC14和PC15被连接至低速晶振,而PC13被通过排针引出,一定要注意各位在使用PC13时最好不要将他设置为输出模式,输出能力有限,要尽量作为GPIO输入来使用。
2.3.2 电压特性
我们先看一下各个引脚的最大输入输出电压:

这里的主要说明主电源脚对地的绝对最大范围 −0.3~4.0V,当然我们实际使用的时候芯片供电就是3.3V。接下来的重点说的是Vin:
- 对 5V 容忍脚(FT):VIN 允许到 VDD+4,我们天空星的芯片脚供电是3.3V,也就是说VDD就是3.3V,5V 数字信号可直接接到 FT 脚当“输入”。但不能作为 ADC 输入;也不建议用作输出的5V系统。
- 对非 5V 容忍脚:VIN 允许到 4.0V(最大值)。实际使用中要把输入限制在 ≤3.6V,否则会导致芯片损毁。如果一定要接入5V的设备,一定要加电阻分压或者电平转换。
继续查看我们主控芯片的数据手册,找到 Table 6. Legend/abbreviations used in the pinout table 这个表格。

我们重点关注 FT, TTa,:
- FT:5V tolerant。数字脚可直接接5V逻辑输入(仅数字口,不能进 ADC)。
- TTa:3.3V tolerant,并直接连到 ADC。只能在0~3.3V(或VREF+)内,绝不允许5V。
一般的GPIO在复位期间与复位后,所有 I/Os 默认“浮空输入”。这意味着外设未初始化前,脚位的电平是不确定的,如果各位需要引脚有确定的电平,一定要外加上拉电阻或者下拉电阻。
上面说了这么多5V容忍引脚相关的知识,那么该如何确定哪些引脚是支持5V容忍的,哪些引脚是最高只支持3.3V的呢?
接下来继续在芯片数据手册中找到 Table 7. STM32F40xxx pin and ball definitions 这个表格,因为太长了,这里只截取部分:

左边的 Pin number 能看到有很多种芯片封装,这里我们只看LQFP100,因为天空星核心板使用的芯片封装就是这个。下面的数字就是具体的引脚号了,可以看到第一脚是PE2,第六脚是Vbat。而如果是其他芯片封装的话,引脚名和引脚号就不是这样对应的了,比如说UFBGA176的引脚号也变为了数字+字母的组合,比如引脚PE4在LQFP100上面对应的引脚号就是3,而在UFBGA176封装中就变为了 B1 ,如果你暂时搞不清,就只看我上面用绿线圈出来的那个封装对应引脚就可以了。
I / O structure 那一列中就标注了5V容忍特性,标了 FT 就是5V容忍的引脚,比如上图中的1,2,3,4,5号脚都是支持5V容忍的,STM32的大部分引脚都是支持5V容忍的。
接下来再看一下有哪些引脚不支持5V容忍:

标了TTa的就是无法直接接5V的脚,比如这里的29和30号两个引脚,也就是PA4和PA5是不支持5V容忍的。
Pin name 括号内的是“上电复位时”的默认功能;若没写括号,则默认功能与引脚名一致。比如说我们继续往下看,

PB2是芯片的BOOT1脚,它上电后默认功能是用来设定启动地址的,一旦上电完成后就可以用作普通GPIO来使用了,在天空星上面,它被一个下拉电阻拉到地同时也接到了一个绿色的LED灯上面,系统成功启动之后,就可以随意控制了。
2.3.3 复用特性
之前我们也提到了,GPIO除了作为普通IO来使用,也可以复用为其他功能,还是 Table 7. STM32F40xxx pin and ball definitions 这个表格,它后面的 Alternate functions就是说这个引脚还有什么复用功能。我们天空星核心板默认在2x5P的调试口那里引出了SWD调试口,一路串口,和3.3V以及5V的供电口,其中的串口脚就直接接在了PA9和PA10上面。

根据这个表格,可以知道PA9可以复用为串口1的TX,PA10可以复用为串口1的RX。一般每个引脚有不止一个复用功能,比如这里的PA9还可以复用为TIM1_CH2,定时器1的通道2。至于剩下的复用功能各位在后续学习中遇到了再了解吧。 如果需要具体引脚的复用功能如何映射以及如何进行复用功能的选择,需要参考下面这个复用表(Table 9),这里只截取了部分。

2.4 逻辑电平阈值(CMOS TTL电平)
2.4.1 STM32单片机的高低电平阈值
大家应该都大概了解高低电平的概念了,对于 STM32 来说,低电平就是0V,高电平就是3.3V,但是实际中绝对不会有这么精准稳定的电压,如果通向GPIO的是0.5V 或者 2.8V 呢?此时引脚检测到的是高电平还是低电平呢?这个电平检测又是如何实现的呢?
还是老规矩,先看数据手册,找到Table 48. I/O static characteristics 这个表,

表里的Conditions条件我们先看一下,我们的天空星核心板上的芯片供电是3.3V,也就是VDD是3.3V,满足这个条件。同时我们目前只看IO部分,BOOT0等部分先不看。
V(IL)是输入为低电平的阈值,可以看到表中Max那一列标注了两个值,被蓝色虚线框标出来的(右上角有(1)标记的)是设计中指定的,被绿色虚线框标出来的(右上角有(2)标记的)是已在生产中经过测试的。我们以绿色虚线框标出来的为准。
- 【低电平阈值】,只要小于0.3倍的VDD,就会被单片机认为是低电平,也就是 0.3 x 3.3 = 0.99V。
V(IH)是输入为高电平的阈值,可以看到这个表里面Min那一列也标注了两个值,被蓝色虚线框标出来的(右上角有(1)标记的)是设计中指定的,被绿色虚线框标出来的(右上角有(2)标记的)是已在生产中经过测试的。我们以绿色虚线框标出来的为准。
- 【高电平阈值】,只要大于0.7倍的VDD,就会被单片机认为是高电平,也就是 0.7x 3.3 = 2.31V。
现在就可以回答之前的问题了,如果通向GPIO的是 0.5V 或者 2.8V ,那么前者小于0.99V就会被判断为是低电平,后者大于2.31V就会被判断为是高电平。
那如果如果是1.5V呢?1.5V介于0.99V与2.31V之间,就进入“不确定区”,此时的电平状态是不确定的,实际使用中一定要尽量避免。
2.4.2 施密特触发器
继续延申上面的问题,假如你当前引脚的电平一直在高电平的阈值,也就是2.31V附近一直来回变动(比如有时候2.35V,有时候是2.28V,仅做说明,实际使用中应避免出现),那此时单片机检测到的是高电平还是低电平呢?
这时候就需要介绍以下TTL施密特触发器了,我们先来看个图片,这个图片是比较器的(A)和施密特触发器(B)作用比较(该图片来自 维基百科):

- 浅蓝色曲线(U):假定为真实世界的模拟信号,既会缓慢变化,也夹杂小的噪声尖峰。
- 红色水平虚线:普通比较器的单一阈值 Vth。输入电压一旦越过它,输出立即翻转;掉回去又再翻转。
- 绿色上下两条水平虚线:施密特触发器的两条阈值,分别叫上阈值 VTH+ 和下阈值 VTH-。它们之间的距离叫 滞回 ,Vhyst = VTH+ − VTH-。
- 竖向虚线:输入越过阈值的时间点,红色对应普通比较器单一阈值,绿色对应越过施密特触发器上下阈值的时间。
- 橙色波形(A):普通比较器(A)的输出。
- 绿色波形(B):施密特触发器(B)的输出。
普通比较器(A):一旦浅蓝色曲线第一次跨过红线 Vth,输出就跳到高电平。随后输入在阈值附近抖一抖(噪声、纹波、微小回落等),每次会再次穿越 Vth,输出都会被迫翻来覆去,产生很多窄脉冲(图中橙色后半段的 毛刺 )。
施密特触发器(B):第一次穿过上阈值 VTH+ 时,输出置高。只要输入仍然高于下阈值 VTH-,无论其间有多少噪声尖峰,都不会再翻转。直到蓝色曲线真正跌破 VTH-,输出才回到低电平。可以看到底部绿色只有干净的两个大方块,没有毛刺。
如下图所示的GPIO结构,MCU去读取实际的IO电平前,还需要经过这个TTL施密特触发器,其他部分大家可以先不管,后面会介绍。

施密特触发器把“判 0/1 的门槛”从一条线变成“两条线”(两个方向的阈值):
- 上升方向的判断阈值记作 VTH↑:只有当输入电压从低往高超过 VTH↑,才被“翻转”为高。
- 下降方向的判断阈值记作 VTH↓:只有当输入电压从高往低低于 VTH↓,才被“翻转”为低。
- 两者之间的差值称为滞回电压(Vhys = VTH↑ − VTH↓)。
这条“滞回带”能显著抑制阈值附近的小幅噪声导致的抖动:只要输入电压停留在 VTH↓ 与 VTH↑ 之间,输出状态就保持“上一次”的判定,不会来回翻转。
注意:滞回电压并不是第三条独立的电平标准,而是“高/低判决阈值之间的差值”,它通过把单一阈值拆分为“上行阈值”和“下行阈值”,来提升抗噪声与慢变化信号的稳定性。
说到施密特触发器就离不开 滞回宽度(电压)Vhys。参考数据手册中的 I/O 静态特性(如下所示的表项, Table 48. I/O static characteristics (continued) :

天空星核心板上的芯片供电VDD是3.3V,也就是他的迟滞宽度=10% x VDD=0.1 x 3.3= 0.33V,结合之前的高电平阈值(2.31V)和低电平阈值(0.99V),上行用高门槛,下行用低门槛;两条线之间的“距离”就是迟滞宽度,也就是 0.33 V。
- 电平从低往高:当电压上升并“至少达到 2.31 V”时,单片机此时“肯定认为当前为高电平”。
- 电平从高往低:若之前已被判为高,则在下降过程中,只有当电压低于 VTH↓ 才会变为低电平; 粗略估算,VTH↓ ≈ 2.31 V − 0.33 V = 1.98 V。也就是说,必须降到约 1.98 V 以下,才会从高翻回低电平状态。
现在就可以回答之前的问题了,总结来说,有了施密特触发器,“先到哪边门槛,就定哪边的状态;想反悔,必须跨过另一道门槛”。如果你的引脚输入曾经上升越过了 2.31 V,会被被判为高电平,那么只在 2.31 V 附近微小晃动(例如 2.28~2.35 V)并不会瞬间变回低。要想变回低,至少得降低到大约 1.98 V。
2.4.3 CMOS与TTL电平兼容
先看一张图片:

从上图可以看出来5V的TTL和3.3VTTL的高低电平阈值是一致的。可以得到两个要点:
- TTL 对 高电平 的要求仅为 ≥2.0 V,远低于 3.3V CMOS 输出,因而 3.3V 驱动 TTL 输入通常是没问题的 。
- 5V CMOS 输入的 高阈值 高达 3.5 V,3.3V 设备无法直驱(电平不够,需要做电平转换)。
然后再看STM32芯片数据手册中的TTL和CMOS兼容说明:

接下来我们考虑四种连接方式:
- 5V TTL 输出 → STM32F4 (天空星ST版本所用芯片)输入
- 条件:要使用使用 5V 容忍(FT)引脚。
- 论证:TTL 高电平最小 2.4 V;STM32 输入高阈值典型 ≈ 0.7·VDD≈2.31 V,2.4 V 满足。低电平 0.4 V 也远低于 0.3·VDD≈0.99 V。
- 结论:可直连,但务必确认是 FT 引脚;非 FT 引脚会被 5V 电压损坏。
- 5V CMOS 输出 → STM32F4 (天空星ST版本所用芯片)输入
- 条件:必须是 FT 引脚,或做降压(电阻分压)。
- 论证:逻辑上阈值没问题,但电压幅值是 5V CMOS(≈5 V),非 FT 引脚会过压。
- 结论:FT 直连可行;非 FT 需要电平转换(分压、专用电平芯片等)。
- STM32F4 (天空星ST版本所用芯片) 输出(3.3V)→ 5V TTL 输入
- 论证:TTL VIH ≥ 2.0 V;图2给出 F407 VOH(min) ≥ 2.4 V(TTL 口,8 mA 条件)或 ≥ VDD−0.4 ≈ 2.9 V(CMOS 口,8 mA 条件)。
- 结论:可直连。但是需要注意负载电流:假如你从 I/O 拉很大电流(比如 20 mA),VOH 可能降到 ~2.0 V,仅“勉强够 TTL”。实际工程使用建议加电平转换芯片。
- STM32F4 (天空星ST版本所用芯片) 输出(3.3V)→ 5V CMOS 输入
- 论证:5V CMOS VIH ≈ 3.5 V;F407 高电平上限只有 3.3 V。
- 结论:不能直连。需要加电平转换芯片。实际使用中一定要注意,即使偶尔能通讯也不会稳定的。
2.4.4 IO的5V容忍不是5V兼容
很多同学第一次看到 5V tolerant(5V 容忍) 会以为 这引脚能用 5V 供电/能输出 5V/和 5V 逻辑完全兼容。其实不是。5V 容忍是一个“输入耐压能力”的概念,最核心的含义只有一条:
在手册说明的限定条件下,这个引脚作为输入时,允许外部信号最高达到 5V(一般上限是 5.5V,STM32 IO输入的最大极限VIN 允许到 VDD+4),芯片不会被电压本身损坏。
这句话里有三个容易被忽略的限定:
只保证 输入不被5V电压打坏 ,不代表 电气特性 完全兼容,比如 3.3V 输出时 5V CMOS(VIH≈3.5V)仍然不满足要求。
只在 特定工作模式和电源状态 下成立
一般只在该引脚被配置为输入或开漏(open-drain)且外部上拉≤5V时有效。推挽输出时,输出高电平永远只会到 VDD(比如 3.3V),不会变成 5V。
不是所有引脚都 FT。带 ADC/模拟功能的脚位、特殊域(如 PC13~PC15)在很多单片机型号上不是 FT。务必以 数据手册 表中的 “FT” 标识为准。
只针对 幅值不超过规范 的正向电压
超过 5.5V 的过冲/尖峰、负向过压(如 -1V)都不在“容忍”范围。
现实连线中常有反射、串扰和 ESD,工程上仍要加限流/钳位和合理布线,不能仅凭“FT”就裸连长线,电压不稳定时。
为什么 FT 引脚能“容忍”5V?

- 普通(非 5V容忍引脚FT)I/O 的输入结构通常带有到 VDD 的 ESD 保护二极管,5V 一施加就会通过这颗二极管向 VDD 注入电流,导致芯片损坏。
- 而FT 引脚则移除了到 VDD 的上钳位二极管,采用专门的输入保护,其连接到了VDD_FT,使得在 VDD 正常供电时,脚上出现 5V 也不会把电流“倒灌”进 VDD 轨。
总结一下:
- 5V 容忍 ≠ 5V 输出。FT 引脚输出状态下最多也只能输出到 VDD( 3.3V)。
- 5V 容忍 ≠ 阈值兼容。3.3V 输出驱动 5V CMOS 仍旧不满足 VIH(高电平最低阈值),必须加装电平转换。
- 只在 该脚确认为 FT 时才可以直连 5V 信号。不要想当然的直接去接5V,当然STM32F4的大部分引脚都是支持5V容忍的,不确定的情况下一定要去查芯片的数据手册。
- 开漏上拉到 5V 只允许在 FT 脚;非 FT 脚这么做会导致芯片烧毁。
2.5 上下拉电阻与输出能力
2.5.1 什么是上拉下拉电阻?
【定义】
上拉(Pull-up):用电阻把引脚拉向逻辑高电平(比如3.3V)。
下拉(Pull-down):用电阻把引脚拉向逻辑低电平(通常是 GND)。
这个上下拉电阻还分为芯片的内部上拉和外部上拉:
[内部上拉/内部下拉]:
顾名思义,这里说的上下拉电阻存在于芯片内部,从下图可以看出来,阻值通常在30kΩ-50kΩ之间,典型值为40kΩ左右,属于是【弱上下拉】的设计(提供的电流较小,一般会小于100uA),内部的上拉电阻或下拉电阻配置后主要用于确定悬空状态下的默认电平(比如内部上拉电阻生效,默认电平就是高电平;内部下拉电阻生效,默认电平就是低电平),而不是提供大电流驱动。

这个图里面的Rpu(Pull-up)就是上拉电阻的阻值,Rpd(Pull-down)就是下拉电阻的阻值。
下图中框出来的两个部分就是芯片IO内部自带的上下拉电阻,通过寄存器配置后即可生效,每个IO都可以单独配置为无上下拉电阻,上拉电阻生效,下拉电阻生效三种模式。

【1】部分生效时该IO的电平被上拉到VDD(也就是3.3V),即内部上拉开启。
【2】部分生效时该IO的电平被下拉到VSS(也就是GND),即内部下拉开启。
【1】和【2】部分都不生效时,即为无内部上下拉电阻状态。
【外部上拉/外部下拉】:
顾名思义,这里说的上下拉电阻存在于芯片外面,一般是在电路板上的,通常来说外部的上拉或者下拉电阻都会小于内部的上下拉电阻。常见的阻值在2kΩ~10KΩ之间,比如天空星核心板的复位按键直接连接到芯片的NRST复位脚上:

这里面的R24,也就是这个10kΩ的电阻即为外部上拉电阻。NRST这个引脚被输入低电平时就会引发芯片的硬件复位(也可以理解为重启),这里将NRST通过上拉电阻拉到高电平后就可以避免各种干扰导致单片机意外复位了。
外部下拉电阻也同理,无非就是在芯片外部加了一个电阻连接到了GND上面,这里就不再举例说明了。
2.5.2上下拉的【强/弱】指的是什么?
在实际应用中,经常会存在强上拉、弱上拉、强下拉、弱下拉等概念。
- 弱上拉/下拉:阻值大(如 30–100 kΩ),电流小,边沿慢;抗干扰弱但省电。
- 强上拉/下拉:阻值小(如 1–10 kΩ),电流大,边沿快;抗干扰强但更耗电。
其实这里的强弱说的就是通过上下拉电阻来获取的电流的大小,电流大就强,电流小就弱。芯片内部的上下拉电阻属于弱上下拉,其阻值的典型值为40KΩ左右。
以我们天空星核心板上的STM32F407芯片做举例,如果把引脚配置为开漏输出模式(后面会讲解,现在可以把他简单理解为无法输出高电平的模式,只能靠上拉电阻来提供高电平),其电压是确定的3.3V,根据欧姆定律:I=U/R,也就是说这个上拉电阻的组织越小,它所能提供的电流就越大,比如说上拉电阻为10KΩ时,I=3.3V/10KΩ≈330uA;上拉电阻为1K时,I=3.3V/1KΩ≈3.3mA;
边沿的快慢说的就是 电平从高电平变到低电平时间 或者 低电平转换到高电平的时间,边沿变化越快就说明该引脚对外输出能力越强,我们可以把接在这条线上的设备都等效成一个个电容,甚至在电路板上走线太长也会导致较大的电容(平时可以忽略不记)。从低电平变成高电平毫无疑问是需要给这些电容充电的,在电压相同的情况下,电流越大充电越快,只有接近充满状态,电平才能完全变为高电平,也就是说,输出电流的能力越强,边沿就越快。
还有一个不可忽视的点就是这些上下拉电阻带来的功耗问题,比如 3.3V 上拉到一个被外部拉低的输入,10 kΩ 将产生约 0.33 mA 静态电流;100 kΩ 仅 33 µA。如果设备低功耗有要求的话,上拉拉电阻的选型也需要注意。
2.6 STM32F4版本天空星核心板可用的GPIO介绍
首先回顾一下天空星核心板的层次总框图:

可以看到左边的PA11,PA12用作USB的D+D-,直接连到TYPE-C内部;PA9,PA10,SWCLK(PA14),SWDIO(PA13),SWO(PB3)等直接连接到2*5P的调式排针口;PA0,BOOT0,PB2等被用作按钮和驱动LED灯,要注意PB2是BOOT1,系统启动前它被下拉电阻下拉到低电平,但是系统成功启动后就可以做为普通IO使用了;
接下来看右边,通过双路的2*40P排针引出了剩下的GPIO,同时把TF卡的SDIO和SPI FLASH用到的11个引脚也通过排针引出去了,如果不使用板载的TF卡座和SPI FLASH,也可以作为普通IO使用,需要注意的是部分引脚是有外部上拉的,如果会影响外部电路,需要把0欧排阻用风枪吹掉,在使用天空星学习底板时不需要去除,设计上就提前考虑了。具体哪些引脚做了上拉需要结合参考下方原理图进行查看:

可以看到除了PD2,PA5,PA6和PA7,其他的IO都是有上拉电阻的。
有哪些有冲突的引脚呢?根据上方这个原理图,如果要使用到TF卡和SPI FLASH相关的引脚,那么要注意上拉电阻的影响。本学习教程是以STM32F407VET6版本的天空星作为案例核心板学习的,也就是STM32版本的天空星青春版。高配版就是多贴了一个SPI FLASH,同时芯片内部的FLASH从512K变为了1024K,他们俩功能上是兼容的。
再看一下排针引出口具体有哪些引脚可以使用:

左边是实物图,右边是引脚标注图。这里所标注的复用功能不全,具体以对应芯片的数据手册为准。
2.7 GPIO应用的常见问题
- Q:既然STM32F4芯片的单GPIO引脚能最大输出 25 mA,那我能用一个 GPIO 直接带一个 20 mA LED 长期亮吗?
- A:不推荐。可以“亮”,但可靠性与温升。建议 5–10 mA 足矣,视觉亮度已足够;需要更亮请用三极管/MOSFET。比如,天空星核心板上的绿灯串了一个2K的电阻,已经很亮了,其通过的电流甚至没超过2mA。
- Q:5V 容忍脚是不是就能直接当 5V 输出?
- A:不是。它只是“输入容忍 5V”,输出仍是 3.3V,且输出到 5V 总线会造成电平不兼容有可能导致电流灌入造成芯片损坏。
- Q:内部上拉能否直接用于 I2C?
- A:一般不建议。内部上拉太弱(几十 kΩ),上升沿慢。I2C 通常用 4.7–10 kΩ 外部上拉,并根据总线电容与速率计算。如果iIC设备多的话,上拉电阻甚至要选2.2KΩ。
三 STM32F4的GPIO框图分析
3.1 STM32F4的GPIO主要特性
● 输出状态:推挽或开漏 + 内部上拉/下拉电阻
● 可以从输出数据寄存器 (GPIOx_ODR) 或外设(复用功能输出)输出数据
● 可以为每个 I/O 选择不同的速度,每个引脚可单独设置,不是运行频率,而是输出沿(高电平到低电平或者低电平到高电平)的快慢和驱动能力,主要影响功耗和电磁干扰
● 输入状态:浮空、上拉/下拉、模拟
● 将数据输入到输入数据寄存器 (GPIOx_IDR) 或外设(复用功能输入)
● 置位和复位寄存器 (GPIOx_BSRR),对 GPIOx_ODR 具有按位写权限
● 锁定机制 (GPIOx_LCKR),可冻结 I/O 配置
● 模拟功能(能输入(ADC,部分引脚可以),也能输出(DAC,少部分引脚可以))
● 复用功能输入/输出选择寄存器(一个 I/O 最多可具有 16 个复用功能,同一时间只能选择一个复用功能)
● 快速翻转,每次翻转最快只需要两个时钟周期,直接软件操作GPIO是达不到的,需要由外设驱动,比如定时器
● 引脚复用非常灵活,允许将 I/O 引脚用作 GPIO 或多种外设功能中的一种
3.2 STM32F4的GPIO基本结构
STM32F4 的 GPIO 可以配置为以下六种模式(后面的备注英文是HAL库代码中的宏定义,学习后续章节时大家就知道了):
- 推挽输出 【GPIO_MODE_OUTPUT_PP】
- 开漏输出 【GPIO_MODE_OUTPUT_OD】
- 复用推挽输出【GPIO_MODE_AF_PP】
- 复用开漏输出【GPIO_MODE_AF_OD】
- 输入模式 【GPIO_MODE_INPUT】
- 模拟模式 【GPIO_MODE_ANALOG】
- 芯片内部的上下拉电阻是可选的配置,模拟模式下只能配置为
No pull-up and no pull-down(浮空)模式。除了模拟模式无法配置上拉或者下拉电阻外,其他模式都可以设置。当然也可以不设置,既可以设置为内部上拉,也可以设置为内部下拉。- 复用 是把该引脚接入片上外设(比如 USART/SPI/I2C/TIM)的输入或输出矩阵。
- STM32F4 的很多引脚是 5V 容忍(FT),但是只在数字输入或开漏外部上拉场景有效;非 FT 引脚以及模拟路径不容忍 5V。务必以具体芯片的数据手册的“FT”标注为准。
先来看一下GPIO的的基本结构(下图为具有5V容忍的IO口):

PS:这个图是使用嘉立创EDA专业版-原理图部分的折线+文本绘制的。它和芯片参考手册部分里面的那个是基本一致的,调整了间距和部分文字。主要为了方便后续给各位展示各个模式的拆分,方便大家理解。
上图左边是更靠近芯片内部的,右边是连接到芯片封装的物理引脚的(就是你在天空星核心板上面看到的那个最大的黑色芯片的那些密密麻麻的引脚),在这里我们就从右往左看。
3.2.1 保护二极管
阅读本小节前先看一下 2.3.2 电压特性 章节中关于5V容忍和注入电流的部分。
在图里的最右边,可以看到有两个保护二极管,一个连接到了VDD_FT,一个连接到了VSS。这个是没有直接寄存器控制的,他是始终生效的,无需软件配置(且无法由软件关闭)来开启。
他的主要作用是对静电放电和轻微过压提供钳位保护,避免内部器件被瞬态冲击击穿。当IO引脚输入的电平高于VDD_FT时,上面的保护二极管生效,当电平低于VSS时,下面的保护二极管生效。普通的非FT引脚和上面的结构类似,只不过这里的不是VDD_FT,所以非FT引脚不能容忍5V。
保护二极管的核心原理是利用二极管的单向导通性和正向导通压降特性。当引脚电压超过VDD_FT加上二极管的正向压降(Vf典型值为0.7V)时,上方的二极管正向导通,将引脚电压钳位在VDD_FT + Vf的水平,并将过量电流疏导到VDD_FT电源轨。同理,当引脚电压低于VSS减去二极管正向压降(即低于-0.7V左右)时,下方的二极管导通,将电压钳位在VSS - Vf,并将电流导向地。
过压之后电流之所以会优先流向电源VDD_FT而非内部电路,是因为导通后的二极管呈现低阻抗(几欧姆到几十欧姆),而内部电路的输入阻抗极高(可达兆欧姆级别)。根据欧姆定律,电流会自然选择低阻抗路径流过,从而保护了高阻抗的内部电路。
有了这个保护二极管并不代表引脚在接任意设备时都是绝对安全的,这个保护主要针对的是瞬时的、低能量的过压事件,比如人体静电或者轻微噪声,它是无法承受持续的高压或者大电流的,如果强行把远高于VDD_FT的电压(比如12V)持续施加到引脚上,会导致上方的保护二极管持续导通,产生极大的电流。这股电流不仅可能烧毁保护二极管本身,使其永久失效,还可能通过电源路径损坏芯片内部的核心电路。
3.2.2 内部上拉/下拉电阻
在前面的 2.5 上下拉电阻与输出能力 章节中,我们已经详细介绍了什么是上下拉电阻 以及 STM32F4芯片内部的上拉/下拉电阻。
我们这里不再赘述,只是强调一下我们可以通过芯片内部的寄存器来控制这些上拉/下拉电阻,主要用于在 没有明确外部驱动电路 的情况下,将引脚稳定到 默认电平 。可以设置为无上下拉(浮空),上拉或者下拉模式。
控制这个内部上拉/下拉电阻功能的寄存器名称是 【PUPDR】,在下一节中会详细介绍。
3.2.3 模拟输出(DAC)
天空星核心板使用的STM32F4的芯片部分引脚是支持DAC输出的,就是可以从这个引脚直接输出连续的模拟电压,比如1.6V、2.3V、0.7V等。我们通过前面的学习知道,普通IO是只能输出高电平(3.3V)或者低电平(GND,也就是0V)的。支持DAC输出的引脚就能输出模拟量了。
DAC 直接接到引脚的 “模拟开关”,不需要通过 AF 复用表。使用 DAC 把管脚设为模拟模式就可以了(当然对应的DAC外设也得进行配置和初始化),从图里也能看出来,它不走复用功能那一路,几乎是最靠近物理IO引脚的路线。
只不过在天空星实际的引脚分配中,ADC管脚被我分配给了SPI FLASH 导致其被外接了电阻,所以在天空星筑基学习板上没有DAC的功能案例,确实要外扩使用的话,可以用外部的DAC芯片(比如MCP4726)来实现DAC功能。
在模拟模式下,芯片会自动关闭数字输入缓冲和施密特触发器,来降低噪声与功耗。
3.2.4 输出控制(P-MOS管和N-MOS管)
GPIO 的输出控制部分本质上是一个互补的 CMOS 推挽结构:上管为 P-MOS,负责拉高到电源,也就是输出高电平;下管为 N-MOS,负责拉低到地,也就是输出低电平。通过这种“推-拉”方式,管脚既能提供电流(Source)也能吸收电流(Sink),从而实现快速而有力的电平翻转。在这种输出模式下,无需额外的上下拉电阻其输出的电平就是稳定的。
在【推挽输出】模式下,上下两个MOS管协同工作。当需要输出高电平(逻辑 1)时,上方的P-MOS管导通,下方的N-MOS管关闭,引脚直接连接到VDD,输出3.3V高电平;当需要输出低电平(逻辑 0)时,N-MOS管导通,P-MOS管关闭,引脚连接到VSS(地),输出0V低电平。两个MOS管交替导通,分别负责拉电流(输出高电平时)和灌电流(输出低电平时)。
在【开漏输出】模式下,上方的P-MOS管完全不工作。若控制输出为低电平,则下方的N-MOS管导通;若控制输出为高电平,则N-MOS管也关闭。此时引脚既不输出高电平,也不输出低电平,呈现高阻态。因此,若要正常输出高电平,必须在外部连接一个上拉电阻至电源。开漏输出的一个重要特性是线与,即多个开漏输出的引脚可以直接连接在一起。只有当所有引脚都输出高阻态时,线路才由上拉电阻提供高电平;只要有一个引脚输出低电平,整条线路就为低电平,比如IIC通讯的两个引脚(SCL和SDA)就必须设置为开漏模式。
【TODO】再详细做一些等效电路原理,方便理解?
3.2.5 输出数据寄存器
会在后面的寄存器章节中详细介绍,可以理解为通过向这个寄存器写入对应的值,就可以改变引脚的输出状态。
在这个寄存器的对应位中写入0,那么对应的引脚就会输出低电平;写入1,则会输出高电平(推挽输出)或者高阻态(开漏模式)。
3.2.6 复用功能输出
会在后面的寄存器章节中详细介绍,可以理解为通过向这个寄存器写入对应的值,就可以改变引脚的复用功能。
GPIO引脚除了作为通用输入输出外,还可以作为片上外设(如USART、SPI、IIC、TIM等)的输入输出通道,这就是复用功能。
此时该GPIO的控制权就交给对应的片上外设了,单片机(也就是STM32主控的CPU)无法再去控制这个引脚的高低电平状态,对应GPIO的电平高低将由对应外设根据要发送的数据位自动控制。
3.2.7 置位/复位寄存器
会在后面的寄存器章节中详细介绍,可以理解为通过向这个寄存器对应的位写入0(而且只能写0,写1无效),他是一个32位寄存器,低16位用于置位(高电平)。
算是上面的【输出数据寄存器】的上级,且提供了一种原子方式(一句代码就可以改变引脚状态,无需读-改-写三个步骤)来设置对应引脚高低电平的方法。它最重要的特性是写零无效,这意味着可以非常高效的对特定引脚进行操作,完全不影响同组其他引脚的状态。
3.2.8 模拟输入(ADC)
上面说的DAC是把数字量转换位模拟量,这里的ADC则是反过来,把模拟量转换为数字量。比如说这个引脚接入1.8V,2.9V等就可以通过这个通道来读取。后面涉及到ADC部分时会再详细说明。
比如我们在做电池电流采集,传感器电压读取等场景就需要用到ADC。
3.2.9 TTL施密特触发器
在前面的 2.4.2 施密特触发器 章节中,我们已经详细介绍了施密特触发器,请先回到前面章节再仔细阅读一下。
简单来说,TTL施密特触发器一种具有迟滞特性的比较器,抑制慢变或有噪声信号在门限附近抖动,当输入电压上升超过上阈值 VIH 时输出被判为高;当下降低于下阈值 VIL 时输出被判为低;VIH 与 VIL 之间的差就是迟滞窗口。它提高了引脚的抗干扰能力,避免引脚IO的电压在阈值附近时进行的反复翻转。
3.2.10 复用功能输入
与复用功能输出类似,GPIO引脚也可以配置为 复用功能输入 模式 。在此模式下,引脚的电平状态不是由GPIO的 输入数据寄存器 捕获来供CPU读取,而是直接传输给指定的片上外设,比如复用功能中的 USART RX(串口接收脚)、SPI MISO(SPI的MASTER IN)、TIMx_CHx 输入捕获(捕获PWM)等。
3.2.11 输入数据寄存器
会在后面的寄存器详细分析中会详细介绍,可以理解为通过这个寄存器就能读到对应引脚的高低电平状态。
这个寄存器反应的是GPIO的实际的电平状态,与引脚当前模式无关(即使处于输出/复用模式,也可通过 IDR 观察到外部实际电平)。输出状态下也可以 回读 来验证。
3.3 STM32F4的GPIO的工作模式
从前面的 3.2 STM32F4的GPIO基本结构 章节,我们已经了解了GPIO框图,及其内部的框图功能。接下来我们看一下GPIO的六种工作模式在内部是怎样的走向,注意事项和应用场景主要有哪些。
这里罗嗦一下,再列举一遍这六种工作模式,:
- 推挽输出 【GPIO_MODE_OUTPUT_PP】
- 开漏输出 【GPIO_MODE_OUTPUT_OD】
- 复用推挽输出【GPIO_MODE_AF_PP】
- 复用开漏输出【GPIO_MODE_AF_OD】
- 输入模式 【GPIO_MODE_INPUT】
- 模拟模式 【GPIO_MODE_ANALOG】
上面这些功能并不是完全互斥的,比如说在复用功能和输出配置下,仍旧可以通过访问输入数据寄存器来获取IO的电平状态。
下方所有配图的红色强调部分就是这个工作模式的走向原理。
3.3.1 推挽输出

重点要参考 3.2.4 输出控制(P-MOS管和N-MOS管) 和部分的介绍。这两个MOS管同时生效时为推挽输出模式,上下 MOS 管交替导通,把引脚 导通 到电源或地,形成稳定的高/低电平,输出能力强、边沿快,适合各类数字信号驱动。
【典型应用】
- 驱动串接限流电阻的LED灯,MOS管,各类数字控制引脚(使能,片选,复位)等。
- 与3.3V逻辑兼容的数字总线。
- 普通的数字输出,需要明确的高低电平输出的场景。
【优缺点及注意事项】
- 【驱动能力强】:可以直接输出高低电平,无需外部电阻辅助。
- 【切换能力强】:高低电平切换速度快,适合高速信号传输。
- 【信号完整性】:输出电平稳定,抗干扰能力强。
- [不适合 多点线与 场景]:多个推挽IO如果连接在一起,当一个输出高电平而另一个输出低电平时,会形成短路,产生IO如何承受的电流,导致芯片损坏。
- [驱动能力也有限]:虽然该模式下驱动能力强,但还是不能直接驱动大电流负载(如电机、继电器的线圈,大电流LED等),需要驱动器/三极管/MOSFET 辅助。
3.3.2 开漏输出

开漏输出模式下,P-MOS管完全不工作,只有N-MOS管受控。当输出低电平(逻辑0)时,N-MOS管导通,将引脚拉低;当输出高电平(逻辑1)时,N-MOS管截止,引脚呈现高阻态,高电平由 外部上拉电阻 提供,多个开漏引脚可以并联实现“线与”(任意一方拉低则为低电平)。
【典型应用】
- I2C 总线(SCL/SDA): 线与 来避免冲突,且可通过外部上拉到更高电压来做电平匹配(比如 5V 总线)。;
- 1-Wire、共享中断线、故障指示线等 多设备共线 。
- 与不同电压域设备通信,如果要上拉到5V,一定要选择带5V容忍的引脚。
【优缺点及注意事项】
- 【线与/多点共享】天然支持多点并联,避免推挽相互“对冲”的风险。
- 【电压域灵活】上拉到目标电压即可做简单电平转换(前提:该引脚确为 5V 容忍IO)。
- 【抗冲突能力】两节点同时 拉低 不会短路;
- [驱动能力弱] 开漏只能 拉低/高阻态 ,不能主动拉高电平,不适合直接驱动大电流负载。
- [必须有上拉] 没有上拉电阻时,高电平无来源,线路会漂浮、抖动、误触发。
- [上拉阻值要选对] 阻值过大→上升沿过慢、易受噪声;阻值过小→功耗与下拉电流过大、器件难以拉低高电平。
- [通讯速率受 RC 常数限制] 线容越大、上拉越大,上升沿越慢。高速应用需减小上拉电阻的阻值。
【如果开漏模式下内部上拉电阻和外部上拉电阻同时导通会是什么效果?】
如果在连接了外部上拉电阻时,同时也使能了内部的上拉电阻,可以把这个场景等效为两个上拉电阻并联,根据并联电阻的计算公式,R总=(R1*R2)/(R1+R2)。如果此时我外接的电阻时4.7k欧姆(R1),引脚内部的上拉电阻可以等效为40K欧姆(R2),那么R总≈4.2KΩ。可以看到上拉能力更强了,并联后等效电阻略小于外部上拉值,线路对噪声的免疫略有提升。
但是不推荐大家这样做,内部的上拉电阻受工艺影响变化会比较大,做实际项目不推荐同时使用内部和外部的上拉电阻。
3.3.3 复用推挽输出

重点要参考 3.3.1 推挽输出 章节,复用推挽输出模式 的特征和 推挽输出 是一致的。 在复用推挽模式下,引脚作为片上外设的 输出口 ,由外设内部来主动驱动电平。把 控制权 交给对应外设,由外设按协议/时序自动输出。
【典型应用】
- 串口脚(UART_TX、UART_RX)。
- SPI 的 SCK、MOSI、片选(CS):点对点高速数字线。
- 定时器 PWM 输出引脚。
【优缺点及注意事项】
- 直接参考 3.3.1 推挽输出 章节部分的介绍,是一致的。
- 【高性能】:适合高速数据通信,比如SPI全双工通信。
- 【硬件精确】:时序由片上硬件外设来保证,比软件控制(模拟时序)更加精确。
3.3.4 复用开漏输出

重点要参考 3.3.2 开漏输出 章节,复用开漏输出模式 的特征和 开漏输出 是一致的。 此时引脚作为外设的 开漏输出口 ,由片上外设来决定何时拉低;需要外部上拉提供高电平。仍然具备 线与 的特性。
【典型应用】
- 直接参考 3.3.2 开漏输出 章节部分的介绍,是一致的。
【优缺点及注意事项】
- 直接参考 3.3.2 开漏输出 章节部分的介绍,是一致的。
- [上拉选择要科学] 结合总线电容与速率,平时大家最常用的IIC都是软件模拟的方式,如果使用硬件IIC来复用对应引脚,其速率会大于软件模拟的方式,此时就要选择更小的上拉电阻。
3.3.5 输入模式

该模式下,引脚作为数字输入通过施密特触发器后,再被系统判定为高/低电平,供程序来读取寄存器。输出部分的缓冲器完全被关闭。内部的上下拉电阻可以随意配置,也可以接外部的上下拉电阻,可以参考 开漏输出 部分的外部上拉电阻部分,这里就不再赘述。
可以配置为上拉,上拉或者浮空模式,在网上的其他资料中,是将输入模式分为三个模式的:【输入上拉】、【输入下拉】、【输入浮空】。笔者在这里认为上下拉电阻是可选的配置,完全没必要区分开来,不过还是再分开介绍一下吧。
浮空输入特点与应用:
- 高阻抗:内部既不上拉也不下拉,完全由外部电路决定电平。
- 应用场景:适合连接已经包含上拉/下拉电阻的外部电路,比如外部中断、通信接口接收端(UART RX)等。
上拉输入特点与应用:
- 默认为高电平:引脚悬空时,内部上拉电阻(约40kΩ)将电平拉高。
- 应用场景:按键检测(另一端接地)、开漏输出设备的信号接收,SPI设备的CS脚(一般使用外部上拉的)等。
下拉输入特点与应用:
- 默认为低电平:引脚悬空时,内部下拉电阻(约40kΩ)将电平拉低。
- 应用场景:高电平有效信号的检测(比如传感器输出信号)、片选信号、防止引脚悬空。
在输入模式下,GPIO还可以用作外部中断的引脚,可以把GPIO过来的信号作为 信号源 给到EXTI线路上,由 EXTI 的硬件 “边沿检测 + 挂起标志 + 屏蔽 + NVIC” 机制产生中断或事件,现在看不懂也没关系,后续在 外部中断章节 会详细介绍,这里只需要知道 GPIO 也可以作为中断引脚使用就可以。
配置了上下拉电阻,引脚就有了默认电平,可以避免悬空抖动。
【典型应用】
- 按键/拨码/限位开关输入(结合内/外部上拉下拉和软硬件消抖)。
- 外设状态脚读取(忙状态(Busy)/就绪状态(Ready)/中断(IRQ) 等),或作为外部中断的触发源(GPIO 的另一种输入检测,后面章节会介绍)。
- 与其他数字器件(3.3V 逻辑)的输出口直连读取,比如去读取其他传感器的高低电平状态。
【优缺点及注意事项】
- 【有了回读能力】即使引脚当前在输出/复用模式,也能通过输入数据路径观察 实际外部电平 ,方便分析负载影响,不过大部分情况下都用不到。
- 【抗抖动】施密特触发带迟滞,慢变/有噪声的输入更稳定。
- [按键消抖] 机械触点抖动常在毫秒级,除硬件 RC 外,还需软件定时确认/状态机处理,如果不了解,在后续的GPIO输入应用中会详细讲解。
3.3.6 模拟模式

在模拟模式下,数字输入缓冲/施密特触发器 与 输出控制部分 会关闭,引脚仅保留 纯模拟 通道,用于 ADC 采样或 DAC 输出;内部的上下拉电阻被关闭,对输入数据寄存器的读取始终为0。
未用到的引脚设为模拟模式还能降低功耗和干扰,直接悬空就行。
【典型应用】
- ADC (模拟输入模式下)采样各种模拟量(电源电压、模拟量传感器输出、波形检测)。
- DAC (模拟输出模式下)输出可变的模拟电压(电压基准偏移,波形生成,简单音频输出)。
- 未使用的引脚统一设为模拟,降低功耗与串扰。
【优缺点及注意事项】
- 【降低噪声/减少功耗】芯片上用不到的引脚可以配置为输入模式,从而切断数字路径,减少干扰与抖动。
- [无法配置内部的上下拉电阻] 模拟模式必须“浮空”,内部上/下拉会引入误差与多余功耗,同时也不能在引脚外部添加外部的上下拉电阻。
- [比较依赖布局布线] ADC/DAC 的基准和地布局对性能影响很大,需要注意模拟地回流;要远离大电流以及开关节点。
- [输出能力较弱] DAC 直驱能力有限,重负载需要后级缓冲电路。
- [不支持5V容忍] 模拟模式下不支持 5V 容忍,输入电压不得超范围(对于天空星核心板来说,ADC采集电压最高不得超过3.3V)。
四 STM32F4 的 GPIO 关键寄存器介绍
STM32F4 的GPIO 每个端口(GPIOA~GPIOI)都有一组功能定义完全相同的寄存器,端口基地址不同、但是寄存器偏移值一致。寄存器本质上是由 位(bit) 组成的状态控制器,多个位组合表示不同的工作模式与特性。
关于寄存器的具体详细介绍大概率只在本章涉及,STM32的寄存器实在是太多了,后续我们会直接使用HAL库,重点介绍其API,这里详细介绍主要是为了方便大家进一步理解寄存器的概念,大家在后续学习中也要多参考芯片的数据手册和应用手册,不要局限于本教程,要提升自己看手册的能力
每个 I/O 端口位均可自由编程,但 I/O 端口寄存器必须按 32 位字、半字或字节进行访问。强烈建议使用 32 位字写入,尤其是 BSRR、LCKR等。
寄存器就像是一个个的小开关,如果把这个 小开关 打开标记为1,关闭标记为0。1个小开关能表示两种状态(0或1),两个小开关就能表示四种状态(00,01,10,11)。我们上一章介绍到的GPIO工作模式就是靠好多个小开关(也就是寄存器里面的各种位)来控制的。
比如GPIO的模式可以分为四种模式:普通输入,通用输出,复用功能,模拟模式这四种,那么我们就可以用00表示普通输入,01表示通用输出,10表示复用功能,11表示复用功能。GPIO 的模式、上下拉、速度、输出类型等,都是通过组合不同的位来完成的。
接下来我们看一张总表,这张表可以表明如何配置这些位来让GPIO初始化为对应的功能。这里i的取值就是对应端口下面的0-15号引脚。
| MODER(i)[1:0] | GPIO模式 | OTYPER(i) | PUPDR(i)[1:0] | 内部上拉/下拉 | OSPEEDR(i)[1:0] | 最终 I/O 配置 |
|---|---|---|---|---|---|---|
| 00 | 输入 | x | 00 | 无 | — | 输入(浮空) |
| 00 | 输入 | x | 01 | 上拉 | — | 输入 + PU |
| 00 | 输入 | x | 10 | 下拉 | — | 输入 + PD |
| 00 | 输入 | x | 11 | 保留 | — | 【不支持,不要这样配置】 |
| 01 | 通用输出(GP) | 0(PP) | 00 | 无 | 可选 | GP 输出:推挽 PP |
| 01 | 通用输出(GP) | 0(PP) | 01 | 上拉 | 可选 | GP 输出:PP + PU |
| 01 | 通用输出(GP) | 0(PP) | 10 | 下拉 | 可选 | GP 输出:PP + PD |
| 01 | 通用输出(GP) | 0(PP) | 11 | 保留 | 可选 | 【不支持,不要这样配置】 |
| 01 | 通用输出(GP) | 1(OD) | 00 | 无 | 可选 | GP 输出:开漏 OD |
| 01 | 通用输出(GP) | 1(OD) | 01 | 上拉 | 可选 | GP 输出:OD + PU |
| 01 | 通用输出(GP) | 1(OD) | 10 | 下拉 | 可选 | GP 输出:OD + PD |
| 01 | 通用输出(GP) | 1(OD) | 11 | 保留 | 可选 | 【不支持,不要这样配置】 |
| 10 | 复用功能(AF) | 0(PP) | 00 | 无 | 可选 | AF:推挽 PP |
| 10 | 复用功能(AF) | 0(PP) | 01 | 上拉 | 可选 | AF:PP + PU |
| 10 | 复用功能(AF) | 0(PP) | 10 | 下拉 | 可选 | AF:PP + PD |
| 10 | 复用功能(AF) | 0(PP) | 11 | 保留 | 可选 | 【不支持,不要这样配置】 |
| 10 | 复用功能(AF) | 1(OD) | 00 | 无 | 可选 | AF:开漏 OD |
| 10 | 复用功能(AF) | 1(OD) | 01 | 上拉 | 可选 | AF:OD + PU |
| 10 | 复用功能(AF) | 1(OD) | 10 | 下拉 | 可选 | AF:OD + PD |
| 10 | 复用功能(AF) | 1(OD) | 11 | 保留 | 可选 | 【不支持,不要这样配置】 |
| 11 | 模拟 | x | 00 | 无 | — | 模拟输入/输出(ADC、DAC) |
| 11 | 模拟 | x | 01 | x | — | 【不支持,不要这样配置】 |
| 11 | 模拟 | x | 10 | x | — | 【不支持,不要这样配置】 |
| 11 | 模拟 | x | 11 | x | — | 【不支持,不要这样配置】 |
GP = 通用、PP = 推挽、PU = 上拉、PD = 下拉、OD = 开漏、AF = 复用功能。
OSPEEDR:仅在输出/复用功能模式下有效;输入/模拟模式下无效,用于控制上升/下降沿的边沿速度(本质上是驱动能力,不是外设频率)
4.1 端口、寄存器映射及访问原则
STM32F4的GPIO端口是微控制器与外部设备交互的桥梁。每个GPIO端口(如GPIOA、GPIOB等)都有一套完全相同的寄存器组,但位于不同的地址空间,也就是说你知道了A端口的16个IO如何使用,那么对于B端口来说,就是把地址偏移一下就可以。这些寄存器必须通过32位字、半字或字节方式访问,不当的访问方式会导致出现异常情况,强烈建议要使用32位来访问,要四字节对齐。有了内存映射,我们访问外设寄存器时就像读写内存一样。
在使用任何GPIO端口前,必须首先使能该端口的时钟。STM32F4采用外设时钟门控设计,未使能时钟时对寄存器的操作无效。例如,使用GPIOA前,必须设置
RCC_AHB1ENR寄存器的GPIOAEN位。时钟未使能,对该端口所有寄存器的读写操作都将无效。。
要去访问GPIO寄存器,要先知道这个寄存器的地址。根据前面对于寄存器的学习,我们可以把整个STM32F4的空间比作一栋二维世界的大楼(或者一张很长的列表)。Cortex-M的内存空间被划分为几大功能区。外设寄存器一般位于 0x4000_0000 起始的一片 外设区 ,STM32F4 把不同总线(APB1/APB2/AHB1/AHB2)的外设分层放置。
- APB1 外设基址:0x4000_0000
- APB2 外设基址:0x4001_0000
- AHB1 外设基址:0x4002_0000
- AHB2 外设基址:0x5000_0000
GPIO 属于 AHB1 外设,还比做大楼的话,那么楼层就是 AHB1 外设区 0x4002_0000。每个 GPIO 端口是一个 房间 ,每个房间之间等距排列:
- GPIOA 基地址:0x4002_0000
- GPIOB 基地址:0x4002_0400
- GPIOC 基地址:0x4002_0800
- 剩下的每个端口都是相隔 0x400 字节(1 KB)
可以同时参考下面这张表来理解:

那么每个端口号,比如GPIOA里面这0x4002 0000到0x4002 03FF这中间高达1KB的空间都放了些什么?难道控制个GPIO需要这么多寄存器参与嘛?
- 其实它大部分内容都是空的,多余的只是预留出来。实际去控制一个端口的16个GPIO只需要下面这些寄存器,满打满算16个IO也就用0x28(也就是40个字节,总共十个32位的寄存器)的空间。
从下面的表格中就能看出来,有灰色阴影的表示其上电后的复位值与其他GPIO端口号复位值(Reset value)不一样,比如说GPIOA_MODER的寄存器、GPIOB_MODER的寄存器和其他的GPIOx_MODER就不一样,至于为什么会这样,在后续介绍MODER寄存器的时候会详细说明。

从上表就能看出来,一个GPIOx端口((x = A..I)有十个32位寄存器:
- 【01】MODER 端口模式寄存器 偏移 0x00(每个引脚 2 位:00 输入、01 通用输出、10 复用、11 模拟)
- 【02】OTYPER 端口输出类型寄存器 偏移 0x04(每个引脚 1 位:0 推挽、1 开漏)
- 【03】OSPEEDR 端口输出速度寄存器 偏移 0x08(每个引脚 2 位:低/中/高/非常高)
- 【04】PUPDR 端口上拉/下拉寄存器 偏移 0x0C(每个引脚 2 位:浮空/上拉/下拉)
- 【05】IDR 端口输入数据寄存器 偏移 0x10(只读,每个引脚 1 位)
- 【06】ODR 端口输出数据寄存器 偏移 0x14(读/写,每个引脚 1 位)
- 【07】BSRR 端口置位/复位寄存器 偏移 0x18(只写,低 16 位置位,高 16 位复位,原子操作)
- 【08】LCKR 端口配置锁定寄存器 偏移 0x1C(锁定引脚配置,需要按照特定序列配置才能生效)
- 【09】AFRL 复用功能低位寄存器 偏移 0x20(引脚 0..7,每个引脚 4 位 AF 选择)
- 【10】AFRH 复用功能高位寄存器 偏移 0x24(引脚 8..15,每个引脚 4 位 AF 选择)
偏移地址的概念需要再强调一下,MODER寄存器的偏移地址是0,而GPIOA 基地址是0x4002_0000,那么GPIOA的MODER寄存器地址就是 0x4002_0000 + MODER寄存器偏移地址(0) = 0x4002_0000;同理GPIOB的MODER寄存器地址就是 0x4002_0400 + MODER寄存器偏移地址(0) = 0x4002_0400;
那么ODR寄存器的偏移地址是0x14,既GPIOA的ODR寄存器地址 = GPIOA基地址 + ODR偏移地址 = 0x4002 0000 + 0x14 = 0x4002 0014,GPIOB的ODR寄存器地址 = GPIOB基地址 + ODR偏移地址 = 0x4002 0400 + 0x14 = 0x4002 0414。
接下来详细介绍一下这些寄存器的功能。
4.2 初始化相关的GPIO寄存器
初始化的意思就是让GPIO通过设置寄存器来配置成你需要的工作模式(输入/输出/复用/模拟)并设置好上下拉,速度,输出类型等。在复位期间及复位刚刚完成后,复用功能尚未激活,基本上所有 I/O 端口被配置为输入浮空模式,除了部分调试引脚(比如PA14:SWCLK;PA13:SWDIO等)。
需要再强调一下,下面这些寄存器不是每个引脚一个,而是每个端口的寄存器去控制它下面的16个IO,然后这些寄存器里面有很多位,根据这些位来进行GPIO口的控制。
4.2.1 GPIO 端口模式寄存器 (GPIOx_MODER) (x = A..I)

看到这里,就先回头看一下在本章开头的那个总表,表中第一列的 MODER(i)[1:0] 说的就是这个寄存器,接下来我们以PA0,PA5和PA15举例辅助大家来理解,从上表就能看出来每个GPIO用两个位来进行IO模式的配置,这个32位寄存器正好能设置一个端口的全部16个引脚(0-15)。
对于GPIOA或者GPIOB,以及其他GPIOC,GPIOD等来说,他们只是地址不一样,寄存器定义是完全一致的,继续看上表,MODER0表示Px0引脚,MODER5表示Px5引脚,MODER13表示Px13引脚,这里面的x可选范围是 A..I。
下面以GPIOA端口的PA0,PA15和PA5这三个引脚来举例说明【后续的寄存器都参照这里来理解】:

假如我们需要把这三个IO(PA0,PA5,PA15)分别设置成,模拟输入(PA0),复用功能(PA5),通用输出(PA15)的话应该向这个地址写入什么数据呢?打开系统的计算器,调整为程序员模式,默认是64字节模式,修改为32字节模式,然后对照上面的表格进行选择:

那么我们就向这个地址(GPIOA_MODER)写入0x4000_0803,这三个引脚就会分别被设置成模拟输入(PA0),复用功能(PA5),通用输出(PA15)了。
❓【解惑】为何 GPIOA/GPIOB 的 MODER 复位值不是全 0?
有些比较细心的同学可能已经发现了,上面图片中,端口A的复位值是0xA800 0000,端口B的复位值是 0x0000 0280,其他端口的复位值却是 0x0000 0000。这是因为芯片复位后,部分引脚默认被配置为 JTAG/SWD 调试接口,以确保调试器能立即连接。
- GPIOA_MODER 复位值
0xA800_0000- 二进制为
1010 1000 ... MODER15[1:0]=10(复用 AF): 对应 PA15 (JTDI)MODER14[1:0]=10(复用 AF): 对应 PA14 (JTCK/SWCLK)MODER13[1:0]=10(复用 AF): 对应 PA13 (JTMS/SWDIO)
- 二进制为
- GPIOB_MODER 复位值
0x0000_0280- 二进制为
... 0010 1000 0000 MODER4[1:0]=10(复用 AF): 对应 PB4 (NJTRST)MODER3[1:0]=10(复用 AF): 对应 PB3 (JTDO/SWO)
- 二进制为
如果你不使用 JTAG(仅用 SWD),或者在程序中需要将这些引脚用作普通 GPIO,你必须在初始化代码中显式地将它们的 MODER 位改写为你需要的模式。例如,如果你想用 PA15 作普通输出,就需要将 MODER15[1:0] 改为 01。直接对整个 GPIOx_MODER 寄存器写 0 是一个危险的操作,因为它会禁用 SWD 调试接口,可能导致你的仿真器无法再次连接芯片!(一般遇到这个问题就只能先用其他下载方式把程序先擦除掉或者先进入芯片bootloader模式来解决了)。
在本篇学习教程中,我们只使用SWD模式给天空星开发板来下载和调试程序。
4.2.2 GPIO 端口输出类型寄存器 (GPIOx_OTYPER) (x = A..I)

这是一个 32 位寄存器,但只用到了低16位(高 16 位保留但是无效),每个 bit 控制一个引脚的输出类型,芯片复位后的默认状态为推挽输出。
假如当前这个寄存器的地址在GPIOA下面,那么上图中的OT0对应的就是PA0,OT5就对应着PA5,OT15就对应着PA15。
此寄存器仅在输出模式或复用输出模式下有效。也就是取决于对应IO口的GPIO 端口模式寄存器 (GPIOx_MODER)对应位,当端口模式寄存器为MODER(x)[1:0]是00或者11时,本寄存器不起作用。
4.2.3 GPIO 端口输出速度寄存器 (GPIOx_OSPEEDR) (x = A..I)

这是一个 32 位寄存器,和第一个寄存器一样,每 2 个 bit 控制一个引脚的输出速度。
在代码中,这四种速度的定义如下:
#define GPIO_SPEED_FREQ_LOW 0x00000000U /*!< IO works at 2 MHz, please refer to the product datasheet */
#define GPIO_SPEED_FREQ_MEDIUM 0x00000001U /*!< range 12,5 MHz to 50 MHz, please refer to the product datasheet */
#define GPIO_SPEED_FREQ_HIGH 0x00000002U /*!< range 25 MHz to 100 MHz, please refer to the product datasheet */
#define GPIO_SPEED_FREQ_VERY_HIGH 0x00000003U /*!< range 50 MHz to 200 MHz, please refer to the product datasheet */2
3
4
此寄存器仅在输出模式或复用输出模式下有效。
要明确一点:GPIO 的 输出速度 并不是指它能主动 创造 一个特定频率的信号。它定义的其实是 GPIO 引脚上电平翻转的“快慢”,即信号的上升/下降时间。
- 低速模式下:信号的边沿会比较 缓 。从 0V 上升到 3.3V(或从 3.3V 下降到 0V)需要的时间较长。
- 高速模式下:信号的边沿会非常 陡峭 。电平翻转所需的时间极短。
这个 速度 等级,本质上是配置了 GPIO 内部输出驱动电路(也就是是推挽输出的 PMOS 和 NMOS 管)的工作模式。高速模式下,驱动电路会用更强的电流、更快的开关速度来充电或放电,从而实现陡峭的信号边沿。
速度越高,驱动能力越强,但功耗和 EMI 也越大。实际工程中,根据信号的实际速率要求选择足够且不过高的速度等级。例如,对于低速的控制信号(如点亮 LED),选择低速即可;对于高速通信总线(如 SPI,SDIO),则需要选择高速或非常高速。
❓【解惑】为什么速度越高,驱动能力越强?
数字信号在实际的物理世界中并不是理想的方波。当引脚驱动一个负载(比如另一芯片的输入引脚、有长长的 PCB 走线)时,这个负载会表现出一定的电容特性(即 负载电容 )。要让引脚电平从低变高,驱动电路必须对这个负载电容进行充电;反之则要放电。
根据电容充电公式 I = C * (dV/dt),我们可以理解:
C是负载电容,I是充电电流,dV/dt是电压变化率(即信号的陡峭程度)。
- 高速模式下,驱动电路能提供更大的瞬时电流,可以极快地完成充/放电过程,信号的上升/下降时间就短,波形接近理想的方波。这对于高速通信至关重要,因为接收方需要清晰、快速的电平变化来正确采样数据。
- 低速模式下,驱动电流较小,充/放电过程缓慢,导致信号边沿倾斜。如果信号本身的频率很高(例如,一个 10MHz 的 SPI 时钟信号),而你却用了低速 GPIO 模式,那么可能在一个时钟周期内,电压还没来得及上升到高电平阈值,下一个时钟周期就开始了。这会导致接收方无法正确识别信号,导致通信失败。
❓【解惑】那具体这些IO的速度配置该如何选择呢?
- 静态或准静态信号:
- 场景:控制 LED 亮灭、使能配置引脚、电机启停控制(非PWM)等。这些信号要么长时间保持不变,要么变化频率极低(几 Hz 到几百 Hz)。
- 选择:
GPIO_SPEED_FREQ_LOW(低速)。使用更高速的模式纯属浪费功耗并增加不必要的 EMI 风险。
- 低速通信/协议:
- 场景:普通 UART (9600, 115200 bps)、标准模式 I2C (100kHz)、低速 SPI (< 2MHz)。
- 选择:
GPIO_SPEED_FREQ_LOW(低速) 通常足够。对应速率上限(如 1-2MHz),如果遇到问题,可以提升到GPIO_SPEED_FREQ_MEDIUM(中速),但一般时没有必要的。
- 中高速通信/协议:
- 场景:快速模式 I2C (400kHz)、SPI (2MHz - 25MHz)、I2S 音频、普通 FSMC/QSPI 访问。
- 选择:
GPIO_SPEED_FREQ_MEDIUM(中速) 或GPIO_SPEED_FREQ_HIGH(高速)。可以从MEDIUM开始尝试,如果信号波形不佳(假如用示波器观察到边沿过缓的话),再提升到HIGH。
- 非常高速的通信/协议:
- 场景:高速 SPI (>25MHz)、SDIO/SDMMC、高速 QSPI、以太网 (RMII/MII)、DCMI 摄像头接口、LCD-TFT 显示接口等。
- 选择:
GPIO_SPEED_FREQ_HIGH(高速) 或GPIO_SPEED_FREQ_VERY_HIGH(非常高速)。对于这些应用,通常需要直接选择最高或次高的速度等级,以保证信号质量。此时,EMI 的问题需要通过优良的 PCB 布局布线(如短走线、增加地平面、阻抗匹配等)来解决,而不是靠降低 GPIO 速度。
4.2.4 GPIO 端口上拉/下拉寄存器 (GPIOx_PUPDR) (x = A..I)

这是一个 32 位寄存器,每 2 个 bit 控制一个引脚的上下拉状态。
PUPDRy[1:0] | 设置 | 描述 |
|---|---|---|
| 00 | 无上拉、无下拉 | 引脚处于浮空状态(高阻态),电平由外部电路决定。 |
| 01 | 上拉 (Pull-up) | 内部一个弱上拉电阻连接到VDD。当引脚悬空时,其电平被拉高。 |
| 10 | 下拉 (Pull-down) | 内部一个弱下拉电阻连接到VSS(地)。当引脚悬空时,其电平被拉低。 |
| 11 | 保留 | 不得使用此设置。 |
此寄存器在除模拟模式外的所有模式下均有效。具体内部上下拉电阻特性及需要注意的地方请看前面部分关于上下拉电阻的介绍,这里不再赘述。
注意:内部上下拉不适用于需要较强上拉的场合,例如I2C总线(标准要求上拉电阻在几kΩ级别,而芯片内部的上拉电阻在40KΩ左右)或高速开漏信号。这些场合必须使用外部的上拉电阻。
4.3 输出相关的GPIO寄存器
STM32提供了两个主要寄存器来控制GPIO的输出状态:ODR和BSRR。理解它们之间的区别对于编写可靠、高效的代码至关重要。
4.3.1 GPIO 端口输出数据寄存器 (GPIOx_ODR) (x = A..I)

这是一个 32 位寄存器,但只用到了低16位(高 16 位保留但是无效),每个 bit 控制一个引脚的输出状态。
- 写操作:向
ODR的某一位写入1,会使对应引脚(如果配置为输出模式)输出高电平;写入0则输出低电平。 - 读操作:读取
ODR返回的是输出数据锁存器的当前值,即最后一次写入的值,而不是引脚上的实际物理电平。要读取引脚的实际电平,必须使用IDR寄存器(会在后面介绍)。
我们假定现在这个寄存器在GPIOA的地址范围内,想让PA0输出高电平,就把这个寄存器中的ODR0设置为1,想让PA3输出高电平,就把这个寄存器中的ODR3设置为1。对应到写入数据上,就是往这个地址(GPIOA_ODR = GPIOA基地址 + ODR寄存器偏移地址 = 0x4002_0000 + 0x14 = 0x4002_0014)写入0x0000_0009,如果对应引脚初始化正确的话,此时PA0和PA3就会对外输出高电平。
这个操作存在一个巨大的潜在风险,非原子操作问题,后面会进行解惑,这正是下面的BSRR寄存器要解决的问题。
4.3.2 GPIO 端口置位/复位寄存器 (GPIOx_BSRR) (x = A..I)

这是一个非常巧妙的32位寄存器,它将 置位 (Set,设为高电平)和 复位 (Reset,设为低电平)功能分开,以实现原子操作(即一次性写入)。
- 低16位 (BS[15:0]) - 置位:向
BSy位(y=0-15)写入1,会原子地将对应的ODRy位置1,使PINy输出高电平。写入0则无任何效果。 - 高16位 (BR[15:0]) - 复位:向
BRy位(y=0-15)写入1,会原子地将对应的ODRy位清0,使PINy输出低电平。写入0则无任何效果。
本寄存器(BSRR)的操作对象不是直接反应到GPIO引脚,而是(ODR)寄存器,从前面的介绍框图也能看出来,BSRR实际上类似于ODR的上级,它通过控制ODR来实现对引脚状态的改变。
也就是说:BSRR 这个寄存器并不是直接去 拉高/拉低 GPIO 引脚本身,而是去改写另一个寄存器 ODR(输出数据寄存器);真正决定引脚电平的是 ODR。BSRR 像一个 指令面板 ,你往它写数据,它帮你对 ODR 里相应的位做置 1(引脚变成高电平) 或清 0(引脚变成低电平),从而间接改变引脚电平状态。
本寄存器只有写入权限,无法读取,它最重要的特性就是写0无效,让我们在实际代码编写中可以实现对GPIO进行原子操作。例如,要设置 PA5 为高电平,可以执行 GPIOA->BSRR = (1 << 5);。要设置 PA5 为低电平,可以执行 GPIOA->BSRR = (1 << (5 + 16));。 这种操作在硬件层面保证是原子的,不会被中断打断,是多线程/中断环境下修改 GPIO 状态的最安全、最高效的方式(GPIO的位带操作也能做到,后面再介绍)。
【举例】假如我们现在要把PA0设为低电平,PA5设为高电平,PA15设为低电平,对于BSRR寄存器来说,我们该写入什么数据呢?
先看图:

对应到代码上,我们其实就是向A端口的BSRR地址(0x4002_0018)写入数据,如果要同时生效的话就是写入下面这个值(0x80010020):

这里就是一次性写入,写一次就能同时改变三个GPIO的引脚状态(后面章节会介绍,这里暂时看不懂也没关系):
- 下 16 位:bit5 = 1(置高,高电平)
- 上 16 位:bit0 = 1、bit15 = 1(复位,低电平)
- 组合:
(1<<5) | (1<<(0+16)) | (1<<(15+16)) = 0x8001_0020
GPIOA->BSRR = ( (1U << 5) /* set PA5 */
| (1U << (0 + 16)) /* reset PA0 */
| (1U << (15 + 16)) ); /* reset PA15 */2
3
如果想按顺序生效也可以像下面这样来写入数值,需要强调的是这个寄存器无法被读取,强行读取也只能读到 0,同时 写 0 没有作用,也就是说你向BSRR寄存器写0GPIOx->BSRR = 0,对于ODR寄存器来说什么都不会改变。所以我们在构造要写入的 32 位值时完全可以只关心需要置 1 的位,把别的位置成 0 也不会引入任何副作用;
比如说就是这样(后面章节会介绍,这里暂时看不懂也没关系):
GPIOA->BSRR = (1U << 5); // 先置高 PA5
GPIOA->BSRR = (1U << (0 + 16)); // 再拉低 PA0
GPIOA->BSRR = (1U << (15 + 16)); // 再拉低 PA152
3
❓【解惑】为什么有了ODR寄存器还要再来一个BSRR寄存器?(如果暂时看不懂,没有关系,后续学完中断章节后再返回来看就可以了)
为了实现“原子操作”,避免在中断或多线程环境下发生 竞态冲突 。
(可以学了中断章节再回来看这个)我们来看一个在中断服务程序和主循环中同时操作同一个GPIO端口的例子:
场景:主循环中,程序需要将PA5置高,它执行 读-改-写 ,因为ODR寄存器是同时控制一个GPIO端口16个引脚的状态的,你大概率是只想改变一个IO的状态的,如果你直接写入ODR寄存器,就可能会覆盖掉其他引脚的状态,所以通常的作法是先读出整个ODR寄存器的值,在读出的值上修改你想要改变的位,最后再把修改后的值写入ODR。这就是“读-改-写”操作。也就是这条语句, GPIOA->ODR = GPIOA->ODR | (1 << 5);
这个C语言操作在汇编层面至少包含三步:
LDR R0, [GPIOA_ODR_ADDR]; 读取ODR寄存器的值当前值到R0。ORR R0, R0, #0x20; 将R0的第5位置1。STR R0, [GPIOA_ODR_ADDR]; 将新值写回ODR。
问题:假设在第1步执行完毕后,一个中断发生了。在中断服务程序中,需要紧急将PA5拉低: GPIOA->ODR = GPIOA->ODR & ~(1 << 5);
当中断返回后,主循环继续执行第2步和第3步。此时,它写回ODR的值是基于中断发生前读取的旧值计算的。结果就是,中断中对PA6的清零操作被覆盖了,PA6会意外地恢复到中断前的状态,导致严重的逻辑错误!假如你这个GPIO是用来控制一个电机旋转的方向,那有可能就会造成机器的损坏。
BSRR如何解决这个问题? 使用BSRR,操作是原子的。硬件保证了对BSRR的一次写入是不可分割的。
- 主循环中置位
PA5:GPIOA->BSRR = (1 << 5); - 中断中复位
PA5:GPIOA->BSRR = (1 << (16 + 5));
这两个操作都只是一条独立的指令。它们直接作用于底层的输出锁存器,互不干扰。无论何时发生中断,都不会破坏彼此的操作。
结论:在任何可能存在并发访问(主循环与中断、多任务操作系统)的场景下,必须使用BSRR寄存器来修改GPIO输出状态。直接写入ODR只适用于初始化或能确保无并发的简单场景。STM32的HAL库函数HAL_GPIO_WritePin()内部就是通过操作BSRR寄存器来实现的。
4.4 输入相关的GPIO寄存器
4.4.1 GPIO 端口输入数据寄存器 (GPIOx_IDR) (x = A..I)

IDR是一个32位的只读寄存器,其低16位(IDR[15:0])反映了对应I/O引脚上的真实物理电平状态。
- 如果
IDRy位为1,表示PINy引脚上的电压高于输入施密特触发器的高电平阈值。 - 如果
IDRy位为0,表示PINy引脚上的电压低于输入施密特触发器的低电平阈值。
比如这个寄存器在 GPIOA 的地址范围内时,如果IDR0为1,就表示PA0引脚当前被接入了一个高电平,IDR8为1,就表示PA8引脚当前被接入了一个高电平。我们在代码中去访问这个寄存器就能获取对应引脚的电平了。
4.5 复用功能的GPIO寄存器
STM32F4的GPIO可以复用为很多种功能,这些功能具体是如何配置的呢,如何去看这些功能有哪些复用功能可选呢?可以看下表【这里没有列举全,请去STM32F4的规格书看全部的表格,搜Table 9. Alternate function mapping就能找到】:

上面可以看到每个引脚最多有16个复用功能可选(AF0-AF15),也就是每个IO需要16种配置,2^4=16,就是说需要4个bit来控制一个IO的复用功能,我们一个GPIOA端口有16个引脚,16*4= 64bit,就是说我们需要用到两个32位的寄存器, 所以这里和复用功能相关的配置是用到了两个寄存器的。
4.5.1 GPIO 复用功能低位寄存器 (GPIOx_AFRL) (x = A..I)

4.5.2 GPIO 复用功能高位寄存器 (GPIOx_AFRH) (x = A..I)

上面这两个32位寄存器共同完成了16个引脚的复用功能选择。
AFRL(复用功能低位寄存器) 用于配置 Pin 0 到 Pin 7。AFRH(复用功能高位寄存器) 用于配置 Pin 8 到 Pin 15。
例如,要将PA9用作USART1_TX,查表可知USART1_TX在PA9上对应的是AF7。那么就需要配置AFRH寄存器中对应PIN9的位域。在不管其他引脚的复用功能的情况下,可以直接向GPIOA_AFRH的地址,写入一个特定的值。
我们来计算一下这个值。PA9 对应的是 AFRH 寄存器中的 AFRH9[3:0] ,这个位域占据的是 AFRH 寄存器的 bit[7:4]。我们要将 PA9 配置为 AF7,也就是 0b0111。所以,我们需要将 0b0111 写入到 bit[7:4] 的位置,也就是0x70:

正确的做法是采用 读-改-写 三部曲,确保只修改我们关心的位,而保持其他位不变,上面这个操作是一个危险的操作,容易把其他IO的复用功能全部清除,导致出现难以排查的错误,这里只是降低复杂度,方便大家理解。
4.6 锁定寄存器
这是一个非常不常用的寄存器,绝大多数应用都不会用到。除非你的项目有明确的、苛刻的安全需求,否则可以忽略它。

这是一个用于锁定 GPIO 配置的特殊寄存器,防止在运行时意外修改。一旦某引脚的配置被锁定,在下次系统复位前,其 MODER, OTYPER, OSPEEDR, PUPDR, AFRL, AFRH 寄存器中对应的位都将变为只读。
锁定操作需要一个特定的 写序列 才能成功,主要用来防止误操作:
- 向
LCKR写入(1 << 16) | lock_bits(lock_bits 是要锁定的引脚位掩码)。- 向
LCKR写入lock_bits。- 向
LCKR写入(1 << 16) | lock_bits。- 读取
LCKR,检查LCKK位(第16位)是否为 1。
比如我们想让PA0和PA3的相关配置进行锁定,那么 lock_bits 的值就是 (1 << 0) | (1 << 3),即 0x0009。
整个锁定序列如下:
- 向
LCKR寄存器写入0x10009。 - 向
LCKR寄存器写入0x0009。 - 向
LCKR寄存器写入0x10009。 - 读取
LCKR寄存器,如果第 16 位(LCKK)为 1,则表示锁定成功。一旦锁定,涉及到的引脚的配置在下次复位前将无法更改。
4.7 GPIO的一般使用流程
无论是使用寄存器直接操作还是使用 HAL/LL 库,配置和使用一个 GPIO 引脚基本都是以下这些步骤:
- 使能 GPIO 端口时钟: 在
RCC_AHB1ENR寄存器中,使能目标 GPIO 端口(如 GPIOA)的时钟。 - 配置引脚模式: 设置
GPIOx_MODER寄存器,选择输入、输出、复用还是模拟模式。 - 配置引脚参数:
- 输出/复用模式: 配置
GPIOx_OTYPER(推挽/开漏) 和GPIOx_OSPEEDR(速度)。 - 输入/输出模式: 配置
GPIOx_PUPDR(上拉/下拉/浮空)。 - 复用模式: 配置
GPIOx_AFRL/AFRH选择具体的外设功能。
- 输出/复用模式: 配置
- 操作引脚:
- 输出: 通过写
GPIOx_ODR或GPIOx_BSRR来改变引脚电平。 - 输入: 通过读
GPIOx_IDR来获取引脚电平。 - 复用/模拟: 配置完成交由相应外设(如 USART, ADC)自动控制和使用,CPU 通常不再直接干预引脚电平。
- 输出: 通过写
后续章节我们去驱动GPIO时也会大致遵循上面这个步骤。