
Ciallo(∠・ω< )⌒☆
| 系列专栏 | 直达链接 |
|---|---|
| 🔤 C 语言 | 进入专栏 → |
| 🧱 数据结构 | 进入专栏 → |
| 🔧 C++ | 进入专栏 → |
| 🐍 Python | 进入专栏 → |
| 🔌 STM32 | 进入专栏 → |
🏠 博客主页:暖焰核心
文章目录
- 引言
-
- [一、GPIO 的定位](#一、GPIO 的定位)
-
- 输入输出的内容
- "通用"两个字的来处
- [MCU 与 CPU 的关系](#MCU 与 CPU 的关系)
- 二、引脚与端口
-
- 引脚、针脚、管脚
- [按键与 LED 两个例子](#按键与 LED 两个例子)
- [Port 与 Pin 的层级](#Port 与 Pin 的层级)
- 引脚在内部的一套电路
- 三、端口保护电路
- 四、八种工作模式
- 五、输出驱动器的构成
- 六、通用推挽输出
- 七、通用开漏输出
- 八、复用推挽与复用开漏
- 九、输入侧与施密特触发器
-
- 输入部分的边界
- 数字信息与模拟信息
- 不规整的输入
- [TTL 施密特触发器](#TTL 施密特触发器)
- 中文手册里的一处矛盾
- 十、上拉输入与下拉输入
- 十一、浮空输入
- 十二、模拟输入
- 十三、八种模式的价值
引言
数完 STM32F103ZET6 的引脚之后,我心里还留着一个问题。
144 个引脚,去掉电源、复位、晶振、启动模式这些功能固定的,剩下 112 个可以随便编程的,分成 PA 到 PG 七组,每组 16 个。名字怎么来的、编号怎么数,前面已经理清了。
但引脚本身到底是个什么东西?
同一个引脚,接上 LED 就能点亮它,接上按键就能读它按没按;改个配置,它又能挂到串口上收发数据。芯片里面拿什么在支撑这种"什么都能干"?为什么它既可以被程序写,又可以被程序读?
我原以为这大概是常识,翻了两页手册才发现里面有一整套电路:保护二极管、上拉下拉电阻、触发器、两个 MOS 管。看起来最"入门"的 GPIO,内部结构其实相当完整。
一、GPIO 的定位
GPIO 是 General Purpose Input/Output 的缩写,中文叫"通用输入输出"。这两个词分别对应两个问题:输入输出的是什么,以及为什么叫"通用"。
输入输出的内容
引脚上能有的东西就那么两种:高电平或者低电平。上层语言把它看成 0 和 1,下层硬件把它当成控制外设的手段。
输出高电平,LED 会理解成"点亮",电机会理解成"正转",串口对面的接收方会理解成"送来一个 1"。输出低电平则相反。
输入是另一回事。单片机的本职是"控制",要控制外设,前提是先知道外设的状态:开关合上没有、传感器有没有读到变化、设备是不是在休眠。这些信息从引脚上读回来,程序再决定下一步做什么。
所以高低电平只是手段,获取外设信息、控制外设才是目的。
"通用"两个字的来处
"通用"的意思是这东西造出来就是给"大家"用的,跟英语被叫做世界通用语言是一个道理:英国人说,美国人也说,中国人还说。
GPIO 也是。它自己能输入输出高低电平,可 MCU(Microcontroller Unit,微控制器)里面还集成了一堆其他外设控制器:ADC、DAC、USART、TIM、I2C、SPI 等等。这些缩写现在不用记全,知道它们各管一摊事就够了:ADC 和 DAC 管模拟量,USART 管串口通信,TIM 管定时,I2C 和 SPI 是两种芯片之间常用的通信总线。它们要控制外设,同样得输出高低电平。难道给每个控制器都单独焊一套输入输出电路?
那样做,芯片内部的复杂度、成本和功耗都会往上走。既然 GPIO 已经把输入输出这件事做了,其他外设控制器直接用就行。这就是"通用"的由来,也是后面会反复出现的"复用 GPIO"这个概念。
MCU 与 CPU 的关系
要把上面这层关系看明白,得先纠正一个容易混的地方:MCU 不等于 CPU。
CPU 只是 MCU 里面的一部分。以 Cortex-M3 为内核的 MCU,内部大致分三层:
text
+-----------------------------------------------------------+
| MCU 内部 |
| |
| +----------------+ +-------------------+ |
| | ARM-CPU | 总线连接 | 片上外设控制器 | |
| | Cortex-M3 |<------------>| ADC DAC USART | |
| +-------+--------+ | TIM I2C SPI | |
| | +---------+---------+ |
| | 直接控制 GPIO | 复用 GPIO |
| v v |
| +---------------------------------------------------+ |
| | GPIO | |
| +-------------------------+-------------------------+ |
+-----------------------------|-----------------------------+
| 输入 / 输出高低电平
v
+------------------+
| 板级外设 |
| LED 按键 传感器 |
+------------------+
这三层可以拿古代的朝廷打个比方。ARM-CPU 是坐在上面的决策者,负责下命令;片上外设控制器是各个衙门,把事情管起来、把命令转成动作;板级外设是地方上真正干活的,灯亮、电机转、按键弹起来,都发生在这一层。
顺带说清一个容易含糊的地方:严格讲 GPIO 自己也是一个片上外设,参考手册里它单独占一章。它的特殊之处在于不只给 CPU 用,还是别的片上外设对外进出的那条公共通道,所以这里把它单独画在下面一层。
这个比方里有个关键点:GPIO 被设计出来,不是只给 CPU 读写用的 。所有片上外设都要借它的手出去,所以芯片上一大半引脚都是 GPIO 引脚。这也是学单片机要从 GPIO 开始的原因,它是整个片上外设体系的出入口。
明白了这一层,前面那个问题就顺了一半:GPIO 之所以要有这么多工作模式,是因为用它的"人"实在太多。每个外设的场景都不一样,GPIO 只能把功能做得足够丰富,让大家都挑得着。就像键盘,只打游戏的话 WASD 几个键就够,但键盘不能只有 WASD,因为它还要被拿来写文档、写代码、聊天。
二、引脚与端口
引脚、针脚、管脚
对 GPIO 最直接的印象就是芯片外面那一圈金属针脚。这颗芯片用的是 LQFP144 封装(LQFP 是 Low-profile Quad Flat Package,薄型方形扁平封装,144 是引脚数)。下面这张是它的引脚分布图,标成红色的 PAx 到 PGx 就是 GPIO 引脚:

下面这张是这颗芯片的实拍照片:

两相对照,图上的位置和实物上的针脚是一一对应的。针脚、引脚、管脚是同一个概念,都指这些金属针脚。数一数就能看出来,芯片四周这一圈绝大多数都是 GPIO 引脚。
按键与 LED 两个例子
一根 GPIO 引脚在功能上只有两种工作方式,输入或者输出。用两个最简单的例子就能说明白。
按键是输入。按键一端接引脚,另一端接 GND(地);同时引脚还要通过一个电阻接到 VDD(电源正极),这个电阻的作用是把引脚平时拉到高电平。
按键没按下去的时候,引脚和 GND 之间是断开的,引脚被电阻拉在 VDD 上,读到的就是高电平;按下去,引脚直接接通 GND,读到的就是低电平。
text
VDD
|
[上拉电阻] <- 按键没按下时,引脚被拉到 VDD,读到高电平
|
+--------------------------- 引脚 PA0
|
[按键] <- 按下时,引脚接地,读到低电平
|
GND
LED 是输出。LED 阳极(长脚)接 VDD,阴极接到引脚上。引脚输出低电平,电流从 VDD 经 LED 流进引脚,灯亮;引脚输出高电平,LED 两端电压一样、没有压差,就没有电流,灯灭。
text
VDD
|
[ LED ] <- 阳极接 VDD,阴极接引脚
|
+--------------------------- 引脚 PA1
MCU 内部(输出电平)
LED这盏灯要的是低电平,所以"输出高"反倒让它灭了。引脚输出什么,跟外设要什么,是两件事。
Port 与 Pin 的层级
引脚在外面是一根一根的,到了芯片内部,MCU 得把它们管起来。管理方式是分组的:每 16 个引脚算一组,叫做一个 GPIO Port (端口)。STM32F103ZET6 一共 7 组,GPIOA 到 GPIOG。
以 GPIOA 为例,它管着 PA0 到 PA15 共 16 个引脚,每个引脚在组里有个序号:
| 组内序号 | 外部物理引脚名 |
|---|---|
| 0 | PA0 |
| 1 | PA1 |
| ... | ... |
| 15 | PA15 |
程序里要指定某一根引脚,用的就是这个组内序号,也就是"这一组的第几位"。外面叫 PA3,内部就是 GPIOA 的第 3 位。
层层往下看,是个多叉树的形状:
text
GPIO 控制器
|-- GPIOA ---+-- Pin 0 -> PA0
| +-- Pin 1 -> PA1
| +-- ...
| +-- Pin 15 -> PA15
|
|-- GPIOB ---+-- Pin 0 -> PB0
| +-- ...
|
|-- ...
|
|-- GPIOG ---+-- Pin 0 -> PG0
+-- ...
这里有个细节值得停一下:为什么不叫 GPIO Group,而叫 GPIO Port?
Group 只能表达"分组"。而 Port 这个词本义是"港口",在计算机领域引申成"数据交换的入口或出口"。一组 GPIO 引脚在一起,本质是芯片和外部世界交换数据的一道口子,用 Port 比用 Group 准确。
引脚在内部的一套电路
到这里,引脚"可见"的部分算是看完了。真正要研究的是:一根引脚在芯片内部,究竟靠什么完成输入输出。
下图是 GPIO 端口位的基本结构,所谓"端口位"就是端口里的单个引脚,比如 PA0、PB3:

这张图要慢慢看,它也是后面所有内容的落脚点。先记住两条:
第一,每一个引脚在内部都有一套这样的电路 。做 16 套就是一组,做 112 套就是全部的 GPIO。反过来说,只要搞清楚一个引脚怎么工作,其余的都通了。
第二,这张图逻辑上分成两部分:上面是输入路径,下面是输出路径。看的时候记住四个字------上 I 下 O,就不会在里面绕晕。
图右边那两个"保护二极管"是电路的最外层,紧贴着引脚。下一节细嗦。
三、端口保护电路
保护电路的位置
回看端口位结构图的右端:紧挨着 I/O 引脚的地方,上下各画了一个二极管,标注是"保护二极管"。这两个管子组成了 I/O 端口最外面的一道墙。
先把二极管本身说清楚。它是一种单向导电的元件,只允许电流从正极(阳极)流向负极(阴极),反方向阻断。电流的产生需要电压差,有了压差,二极管才会导通。导通的时候它自己也承压,这个压降通常在 0.6 V 到 0.7 V 之间。[1](#1)
双二极管钳位
两个二极管的接法是固定的:上面那个,阳极靠引脚一侧、阴极接 VDD;下面那个反过来,阳极接 VSS(电源负极,也就是前面说的 GND)、阴极靠引脚一侧。
- 上路二极管的阴极接
VDD,意思是它只有在一个条件下才会导通:引脚上的电压比VDD还高出 0.7 V 以上。一旦满足,它导通,多余的电压被导走,引脚电压被按住,停在VDD+ 0.7 V 附近。 - 下路二极管阳极接
VSS,它导通的条件是引脚电压比VSS还低 0.7 V 以上,导通后引脚电压被按住,停在VSS− 0.7 V 附近。 - 中间那一段谁都不导通,引脚爱是什么电压就是什么电压。
按 VDD = 3.3 V、VSS = 0 V、二极管压降 0.7 V 代入,三个区间是这样:
| 引脚电压 | 哪个二极管导通 | 结果 |
|---|---|---|
| 低于 −0.7 V | 下路 | 电压被钳到 −0.7 V 附近,不再往下掉 |
| −0.7 V ~ 4.0 V | 两个都不导通 | 正常输入,引脚电压是多少就是多少 |
| 高于 4.0 V | 上路 | 电压被钳到 4.0 V 附近,不再往上冲 |
这种接法的学名叫双二极管钳位电路 ,作用是防止不合理的电压进入芯片。如果没有它,一个远超 VDD 的电压接上引脚,会在芯片内部形成很大的电流,把里面的元器件击穿。
什么情况下会超范围
正常接线的时候引脚电压不会越界,越界往往来自一个想不到的地方:静电。
手上有静电的时候去碰芯片的针脚,瞬间电压可以到几千伏。具体的量级不用记,关键是从这个量级就能看出来,为什么手册里会专门写"不要用手去摸针脚"。
不过也不用把这件事看得太严重。偶尔一次超范围的电压进来,不一定就会击穿器件;真正危险的是持续的超范围电压。还有一点值得一提:电压的正负是人规定出来的,真正决定一切的始终是压差,别被符号绕进去。
四、八种工作模式
八种模式的全貌
GPIO 要负责输入和输出,而输入输出在具体场景里又各不相同,于是就有了下面八种模式:
| 方向 | 模式 | 说明 |
|---|---|---|
| 输出 | 通用推挽输出 | CPU 直接控制,能输出高电平也能输出低电平 |
| 输出 | 通用开漏输出 | CPU 直接控制,只能输出低电平和高阻抗 |
| 输出 | 复用推挽输出 | 片上外设控制,能输出高电平也能输出低电平 |
| 输出 | 复用开漏输出 | 片上外设控制,只能输出低电平和高阻抗 |
| 输入 | 上拉输入 | 内部接上拉电阻,悬空时读到 1 |
| 输入 | 下拉输入 | 内部接下拉电阻,悬空时读到 0 |
| 输入 | 浮空输入 | 内部两个电阻都断开,电平由外部决定 |
| 输入 | 模拟输入 | 直通,不做整形,交给 ADC、DAC 这类外设 |
四种输出模式是"通用 / 复用"和"推挽 / 开漏"两个维度的组合:
| 推挽 | 开漏 | |
|---|---|---|
| 通用 | 通用推挽输出 | 通用开漏输出 |
| 复用 | 复用推挽输出 | 复用开漏输出 |
通用与复用的分界
第一节说过这对词,这里给个准确的说法。
通用 就是通常使用,指的是由 ARM-CPU 直接控制 GPIO 输出。程序往数据寄存器里写什么,引脚上就出现什么。
复用 是重复使用的意思,指的是 MCU 内部的片上外设借 GPIO 来做输入输出。比如串口要发数据,它不会自己接一根线出来,而是把这个任务交给某个引脚,由串口控制器去驱动。
一句话区分:看谁在控制这根引脚。CPU 直接控制是通用,片上外设接管是复用。
至于为什么要这么多模式,第一节已经交代过了:GPIO 是给所有片上外设共用的,每个外设的场景都不一样,模式多才能覆盖得住。
五、输出驱动器的构成
输出部分的边界
研究输出模式之前,先把要研究的范围圈出来。下面是端口位结构图里属于输出的那一部分:
!\[STM32/attachments/GPIO输出部分结构框图.png\|从端口位结构图中框出的输出部分,右侧是驱动引脚的 P-MOS 与 N-MOS]
从左往右看这块电路,一共四样东西:左边是输出数据寄存器和"来自片上外设"的复用功能输出,中间是复用器和输出控制,右边是 P-MOS 和 N-MOS 两只管子,最后接到引脚上。
复用器与输出控制
复用器的名字来自"复用",它的工作是从多组输入信号里选一个作为有效输入。
为什么要有它?因为 GPIO 到底归 CPU 用还是归片上外设用,得有人说了算。复用器就是那个做决定的:选数据寄存器,这根引脚就归 CPU;选复用功能输出,就归片上外设。
输出控制 接在复用器后面,负责把选中的信号送到 MOS 管的栅极。它做的是"力气的转换":内部逻辑电平本身很弱,驱动不了外面的负载,输出控制把它变成有足够驱动能力的电平,去控制管子的通断。
P-MOS 与 N-MOS
要理解输出,核心在最后这两只管子上。
MOS 是 Metal-Oxide-Semiconductor Field-Effect Transistor(金属氧化物半导体场效应管)的缩写,也写作 MOSFET。前面带 P 的是 P 沟道,带 N 的是 N 沟道。P 取自 positive(正向的),N 取自 negative(反向的)。
这两种管子的符号长这样:

不管是 P 还是 N,都有三个极:
- S :
Source,源极。载流子的来源。 - D :
Drain,漏极。载流子从这里流出器件,它是一个电流路过的地方,不是终点。 - G :
Gate,栅极。Gate本意是"门",开关的开启和关闭由它控制。
记住一句就够用:S 和 D 之间是电流要走的路,G 是控制这条路通不通的开关。实际接法上,S 和 D 会分别接到电源或者地,G 接控制电路。
至于"P"和"N"怎么区分,从电流方向上有个直观的分法:被控制的电流从 S 流向 D,就是 P 型;从 D 流向 S,就是 N 型。这跟 P 的"顺势而为"、N 的"逆水行舟"是一套说法,先当口诀记着。
而它们最本质的一点是:这是一个开关。不是放大器,不是别的东西,就是一个可以用小电压去控制的开关,最终目的是用很小的能量去控制大电流。
两只管子的接法
在 GPIO 的输出电路里,P-MOS 和 N-MOS 的接法是固定的:
text
VDD (3.3V)
|
S
[P-MOS]
D
|
+------------------> I/O 引脚 <- 两只管子的 D 极在这里相接
|
D
[N-MOS]
S
GND
P-MOS在上面 ,S 极接VDD。N-MOS在下面 ,S 极接GND(也就是VSS,看成 0 V)。- 两只管子的 D 极接在一起,接出去就是引脚。
这个"上 P 下 N、D 极相接"的形状,是整个输出部分的骨架。接下来讲的推挽和开漏,都只是围着这两只管子做加减法。
简化之后的输出电路
为了后文说得清楚,把前面那些寄存器、复用器、输出控制先收起来,只留最要紧的部分:
text
VDD
|
[P-MOS] <- 闭合则引脚接 VDD
|
+------------------> I/O 引脚
|
[N-MOS] <- 闭合则引脚接 GND
|
GND
后面再看推挽和开漏,就只盯着"输出控制让哪只管子闭合"这一点。
六、通用推挽输出
两只管子交替工作
输出模式里最直观的是通用推挽输出(Push-Pull Output),它的核心思想就一句话:交替闭合和断开 P-MOS 与 N-MOS。
text
输出数据寄存器写 1:闭合 P-MOS,断开 N-MOS
VDD
|
[P-MOS] <- 闭合
|
+------------------> I/O 引脚 = 高电平(通过 P-MOS 接上 VDD)
|
[N-MOS] <- 断开
|
GND
输出数据寄存器写 0:断开 P-MOS,闭合 N-MOS
VDD
|
[P-MOS] <- 断开
|
+------------------> I/O 引脚 = 低电平(通过 N-MOS 接地)
|
[N-MOS] <- 闭合
|
GND
引脚要么接在 VDD 上,要么接在 GND 上。接 VDD 就是高电平,接 GND 就是低电平,中间没有第三种状态。
"推挽"的命名来处
名字来自这两只管子干活的方式。
输出高电平时,P-MOS 把引脚接到 VDD,电流从芯片里"推"出去(提供电流);输出低电平时,N-MOS 把引脚接到 GND,电流从外面"挽"进来(吸收电流)。一推一挽,所以叫推挽。
一个不能出现的情况
看到这里可能会冒出一个念头:要是两只管子同时导通呢?
那就是
VDD和GND直接连起来,中间只剩两只几乎不承压的管子,电流会大到烧掉电路。所以输出控制单元有一条硬要求:保证两只MOS管永远不同时导通。这条在电路里靠逻辑设计保证,程序不需要操心。
导通条件
MOS 管要导通,栅极得加上足够的电压。这个门槛叫导通阈值电压 Vth(Voltage Threshold),一般零点几伏。判断条件是看源极和栅极之间的电压差够不够大,跟漏极无关:
P-MOS:Vs−Vg超过Vth,就导通;否则断开。N-MOS:Vg−Vs超过Vth,就导通;否则断开。
具体数值不用记,记住方向就行:P-MOS 是源极电压高于栅极,N-MOS 是栅极电压高于源极。这也解释了前面那个口诀,P 管顺着 S 到 D 的方向走,N 管逆着来。
顺带记一个量级:MOS 管的开关速度可以到纳秒级。这也是它能用来做数字电路的原因。
所以程序往输出数据寄存器写 1 还是写 0,本质是让输出控制去挑 P-MOS 还是 N-MOS,最终决定引脚接 VDD 还是接 GND。
七、通用开漏输出
开漏输出的构成
通用开漏输出(Open-Drain Output)的思路跟推挽反着来:不要 P-MOS 了,让它永远关闭,只留 N-MOS。
只留一只管子,事情就简单了,也不用再考虑"两只同时导通"的问题。但代价是能力少了一半。
text
输出数据寄存器写 0:闭合 N-MOS,引脚接地
VDD
|
P-MOS 永远断开
|
+------------------> I/O 引脚 = 低电平
|
[N-MOS] <- 闭合
|
GND
输出数据寄存器写 1:断开 N-MOS,内部与引脚断开
VDD
|
P-MOS 永远断开
|
+------------------> I/O 引脚 = 高阻抗
|
[N-MOS] <- 断开
|
GND
先记住这个结论,后面用得到:
- 向输出数据寄存器写 0,表示
N-MOS闭合,引脚输出低电平。 - 向输出数据寄存器写 1,表示
N-MOS断开,引脚输出高阻抗。
换成一句话:开漏模式只能输出低电平和高阻抗,想要高电平得靠外部。
高阻抗的含义
"高阻抗"这个说法有点抽象,换个大白话的解释:MCU 内部和外部引脚彻底断开了,相当于开路 ,或者说 N-MOS 的漏极和外部引脚互相悬空了。
对引脚来说,既然内部已经断开了连接,那此刻的 MCU 就可以看成一个阻值无穷大的设备。阻值无穷大,我们就说它输出高阻抗。
"开漏"的命名来处
开漏的全称是漏极开路输出 。名字的来源很直白:P-MOS 被拿掉之后,N-MOS 的 D 漏极就相当于什么都没接,是"开路"的状态,所以叫漏极开路,简称开漏。
这里有个容易想岔的地方:不要把引脚当成一个设备,引脚就是 D 漏极那根线的延伸 。推挽输出里 P-MOS 和 N-MOS 互为对方漏极接的设备,所以不算开漏;开漏里没有这个"对方",管子外面是空的。
高电平的外部来源
现在问题来了:开漏模式只能输出低电平和高阻抗,那高电平怎么办?
办法是在引脚外面接一个上拉电阻,把引脚拉到另一个电源上。
这个电阻为什么不能做在芯片里?参考手册给了直接的答案:端口配置成输出之后,内部的弱上拉和弱下拉都被禁止,所以输出模式下的上拉只能在外部加。至于加多大、拉到哪个电源上,取决于外部设备需要什么电压,得由开发板的设计者决定。
text
VDD (3.3V)
|
[上拉电阻] <- 这个电阻在芯片外面,由开发板设计者决定
|
+-----------+ I/O 引脚
|
N-MOS 断开时:引脚被外部电阻拉到 VDD,输出高电平
N-MOS 闭合时:引脚直接接地,输出低电平
加上这个电阻之后,开漏模式也能输出高低电平了:N-MOS 闭合时接地,输出低电平;N-MOS 断开时引脚被外部电阻拉上去,输出高电平。
高阻抗之后的引脚电压
这一步很多人是"记住结论"跳过去的,但它其实可以用高中物理讲明白。
先说一个常识:在一个电路回路里,电压会被各个元器件承接,电阻值越高的元器件,承压越多。
为了方便算,把图上的几个电阻起个名字:外部上拉电阻记作 R_PULL;引脚本身记作 R1,它其实就是一根导线,阻值无限趋近 0;N-MOS 断开的位置记作 R2,阻值是无穷大。
然后请出欧姆定律 V = I × R。这里把 N-MOS 断开的位置当成一个阻值无穷大的电阻,这样从 VDD 到 GND 就成了一条串着三个电阻的通路:
- 整个回路的电流 I = 3.3 V ÷ (
R_PULL+R1+R2)。因为R2接近于无穷大,所以 I 约等于 0。 - 引脚处的电压 = 3.3 V 减去上拉电阻上承掉的那部分 = 3.3 V − I ×
R_PULL。 - I 约等于 0,所以 3.3 V − 0 ×
R_PULL= 3.3 V。
引脚处就是 3.3 V,输出高电平。
但这个推导里藏着一个坑。既然开路,就没有电流,I = 0,那按 V = I × R 算,整个电路里都不该有电压,引脚处也不该有。跟上面的结论对不上。
问题出在对欧姆定律的理解上。一块裸露的电池什么都不接,它自己就是开路的,正极有没有电压?当然有。水龙头不打开,管子里照样有水压。
电压的本质是两端的电势差,开路的状态下它依然存在 。欧姆定律算出来的那个电压,是某个元器件在整个回路里承担的电压差,也就是电流经过它之后电压下降了多少,不是某一处的绝对电压。
假设一个回路上串三个电阻,电源 10 V,电阻分别是 2 欧、3 欧、5 欧:
text
VDD ---- [ R1 ] ---- [ R2 ] ---- [ R3 ] ---- GND
10V 2欧 3欧 5欧
回路电流 I = 10V / (2 + 3 + 5) = 1A
R1 承压 = 1A x 2欧 = 2V
R2 承压 = 1A x 3欧 = 3V
R3 承压 = 1A x 5欧 = 5V
R1 承担了 2 V,意思是电流流过 R1 之后,电压从 10 V 掉到 8 V。所以要知道 R1 右边那一点的电压是多少,就是 10 V 减去 2 V,等于 8 V。导线本身不承压,所以 R1 右边的电压就等于 R2 左边的电压。
回到开漏加上拉电阻那张图,R2 无穷大、电流约为 0、上拉电阻上什么都不承,引脚处自然就是 3.3 V。
上拉电阻的另一半作用
上拉电阻不只是为了输出高电平,它还管着另一件事:N-MOS 闭合、引脚接地的时候。
如果外部没有任何电阻限流,引脚一接地,就会从外面灌进来一股不受控制的大电流。串上这个电阻之后,电流被限制在一个合理范围里。所以这个电阻同时承担了"拉高电平"和"限流"两个职责。
至于"上拉"这个名字,说的是它的工作方式:引脚的高电平是被外部电阻加上高电压拉起来的,所以叫上拉电阻。阻值一般取 4.7 kΩ 到 10 kΩ。
八、复用推挽与复用开漏
前面两节讲的都是"通用",控制权在 CPU 手里。剩下两种输出模式(复用推挽输出、复用开漏输出)只在一处不同:控制权交给了片上外设。
具体来说,输出数据寄存器的位置换成了片上外设的输出信号,信号经过复用器进入输出控制,后面接的还是同样的 P-MOS 和 N-MOS。所以推挽的能力、开漏的能力,两种版本完全一样:
- 复用推挽输出:片上外设控制,能输出高电平也能输出低电平。
- 复用开漏输出:片上外设控制,只能输出低电平和高阻抗,要输出高电平同样得靠外部上拉。
有个细节值得记一下:配置成复用输出之后,引脚和输出数据寄存器就断开了,转而接上片上外设的信号线。这时候程序再往数据寄存器里写值,引脚是不会理会的。
把四种输出模式放在一起对照:
| 模式 | 谁在控制 | 高电平 | 低电平 | 高阻抗 |
|---|---|---|---|---|
| 通用推挽输出 | ARM-CPU |
能 | 能 | 不能 |
| 通用开漏输出 | ARM-CPU |
不能(需外部上拉) | 能 | 能 |
| 复用推挽输出 | 片上外设 | 能 | 能 | 不能 |
| 复用开漏输出 | 片上外设 | 不能(需外部上拉) | 能 | 能 |
选择哪种模式,取决于这根引脚要接什么。
推挽驱动能力强,用来点灯、驱动电机这类负载;开漏能输出高阻抗,适合共享总线和电平转换,代价是少不了外部上拉电阻。至于通用还是复用,由这根引脚的用途决定:
CPU直连的用通用,交给片上外设的用复用。
九、输入侧与施密特触发器
输入部分的边界
输入端口的范围和输出是对称的。下面是端口位结构图里属于输入的那一部分:

从右往左看:引脚进来,先经过保护二极管(外层已经讲过),然后遇到两个带开关的电阻(上拉和下拉),接着是触发器 ,最后到输入数据寄存器,一路读给 CPU,一路送给片上外设。
现在就差中间那个触发器没交代了。它做的事说起来简单,但很关键。
数字信息与模拟信息
现实世界里的信息大致分两类。
一类是状态有限的:电梯门关好没有、安全带系上没有、共享单车锁上没有、门铃被按过没有。这类信息只有有限的几个状态,叫数字信息。
另一类是连续变化的:洗澡水的冷热、手机剩余电量、奶茶店背景音乐的音量、早高峰地铁的拥挤程度。这类信息在任意时刻都能取到无穷多个值,叫模拟信息。
计算机和单片机是为人类服务的,要提供服务就得先获取信息,两种信息都得能处理。为了处理它们,硬件工程师造出了一大堆电路,高校专门开了两门课来教:《数字电路》和《模拟电路》。
这一节只关心数字信息这一侧。
不规整的输入
理想情况下,引脚上收到的应该是一个方方正正的波形:要么干脆在高电平,要么干脆在低电平,切换的那一刻干净利落。
真实情况不是这样。引脚连着导线,导线连着外面的世界。电源线、人体、手机、电机、开关电源,甚至隔壁的 WiFi、蓝牙、对讲机,都在往外面辐射电磁场,这些干扰会实实在在地叠在输入信号上。于是本来该干净翻上去的波形,翻到一半就开始抖:

如果就这么交给后面的电路去判断高低电平,它会在 0 和 1 之间反复横跳,读到的值一会儿一个样。
TTL 施密特触发器
解决这个问题的单元叫 TTL 施密特触发器。
TTL 是 Transistor-Transistor Logic 的缩写,中文是"晶体管-晶体管逻辑"。施密特是人的姓。整个名字直译过来就是"晶体管-晶体管逻辑施密特触发器",名字不用记,记住它的作用就行:抗噪整形,把抖动的、带毛刺的、爬升缓慢的输入波形,整形成规则的方波。
原理在于它设有两个不同的阈值,而不是一个。翻上去的时候要越过正向阈值电压 VT+ 才算高电平,掉下来的时候要跌破负向阈值电压 VT− 才算低电平。两个阈值之间夹出来的那一段,叫迟滞区域:

迟滞区域的作用在于:只要输入还在这个区间里抖,输出就保持上一次的状态不变。哪怕噪声恰好把电压顶到阈值附近来回晃,也不会引起输出的频繁跳变。
举个具体的:电压在 VT+ 附近来回抖动。高于 VT+ 的那一瞬间,输出是高电平;落回迟滞区域里,输出维持上一次的状态。而"上一次"既然是从 VT+ 上方掉下来的,那自然是高电平。所以结论是维持高电平不变 。在 VT− 附近抖动同理,维持低电平不变。
噪声就这样被吃掉了。真正送进输入数据寄存器的是触发器输出的那个方波,前面那段抖动的波形到不了后面。
中文手册里的一处矛盾
讲这个触发器的时候,有一件事我得专门说一下,因为它很容易让人以为是自己看错了。
中文版参考手册里,同一个东西出现了两种叫法:
- 正文(8.1.7 到 8.1.10 四节)写的是"施密特触发输入被激活"。
- 图 13(
I/O端口位的基本结构)的图里,标注写的是"TTL肖特基触发器"。
"肖特基"和"施密特"是两种完全不同的器件,前者是
Schottky,后者是Schmitt,不能混。[2](#2)那到底哪个对?
对照英文原稿就清楚了:英文版参考手册里 Schmitt 出现了好几处,Schottky 一次都没有。所以正确译名是施密特,中文手册图里的那个"肖特基"是错的。
有意思的是,这不是"翻译翻错了",而是同一本手册里图和正文对不上。前面那张端口位基本电路结构图,就是从手册图 13 来的,图上印的也是"肖特基"。看书的时候如果只盯着图,很容易把错的那个名字记下来。
十、上拉输入与下拉输入
配置开关的真实含义
前面那张输入结构图上,上拉电阻和下拉电阻各串了一个开关。所谓"把这根引脚配置成上拉输入",落到电路上就是一件事:闭合上拉电阻的开关,断开下拉电阻的开关。反过来,配置成下拉输入就是闭合下拉、断开上拉。
两个开关都在芯片内部,程序通过配置寄存器去控制它们。
text
上拉输入:闭合上拉开关,断开下拉开关
VDD
|
[上拉电阻] <- 开关闭合
|
+--------------------- 引脚
|
[下拉电阻] <- 开关断开
|
VSS
下拉输入:闭合下拉开关,断开上拉开关
VDD
|
[上拉电阻] <- 开关断开
|
+--------------------- 引脚
|
[下拉电阻] <- 开关闭合
|
VSS
上拉输入
配置成上拉输入之后,会发生三种情况:
| 引脚状态 | 输入数据寄存器读到 |
|---|---|
| 引脚接高电平 | 1 |
| 引脚接低电平 | 0 |
| 引脚悬空,外部什么都没接 | 1 |
前两种不奇怪,引脚上是什么就读到什么。关键在第三种:引脚什么都没接的时候,它被内部的上拉电阻拉到了 VDD,于是读到 1。
这就是上拉输入存在的理由:给输入一个确定的缺省值。外部设备可能还没接上、可能处于断开状态,有了上拉电阻,程序读到的至少是个明确的值,而不是随机的东西。
下拉输入
下拉输入就是把上面那套反过来,三种情况也对称:
| 引脚状态 | 输入数据寄存器读到 |
|---|---|
| 引脚接高电平 | 1 |
| 引脚接低电平 | 0 |
| 引脚悬空,外部什么都没接 | 0 |
区别只在缺省值:上拉给的是 1,下拉给的是 0。选哪个取决于电路的需求,比如按键一端接地时,平时希望读到 1,那就配上拉。
"弱"字的意思
芯片内部的这两个电阻,阻值偏大,通常在 30 kΩ 到 50 kΩ 这个量级。
阻值大,意味着它能提供的电流很小,对引脚电压的控制力就比较"弱"。所以内部上下拉常被叫做弱上拉、弱下拉。这个"弱"字后面还会用到。
顺便说清一个位置上的区别:输入这边说的上下拉电阻,是 MCU 内部自带的 ;而第七节开漏输出讲的那个上拉电阻,是芯片外面由开发板设计者加的。两者的作用类似,位置和责任人都不一样。
两个根本问题
讲到这里,有个疑问一直悬着:上拉输入的时候,如果引脚接低电平,上拉电阻不会把电平拉高吗?下拉输入的时候,如果引脚接高电平,下拉电阻不会把它拉低吗?
这两个问题原理上是同一个问题,本质上是在问"引脚上的电平和上下拉电阻对抗的时候,谁说了算"。
答案是不会。理由有两层。
第一层是强弱关系:内部上下拉电阻的阻值在几十 kΩ 这个量级,它提供的电流很小;而外部驱动器给出的低电平或者高电平要"结实"得多。对抗起来,外部信号说是什么就是什么。
第二层是第七节留下的结论:一个回路里电压会被各个元器件承接,阻值高的承压多。用等效电路看会更清楚。
上拉输入、引脚接低电平的情况,可以等效成引脚直接接地:
text
VDD
|
[上拉电阻] <- 回路里唯一承压的元件,承压 = VDD
|
+--------------------- 引脚处电压 = 0V(导线不承压)
|
[外部的低电平] <- 相当于直接把引脚接地
|
GND
这个回路里唯一需要承压的元件就是上拉电阻,它承担的压差是整个 VDD。而引脚就是一根导线,不承压,所以引脚处的电压就是 0 V,是低电平,不受上拉电阻的影响。
下拉输入、引脚接高电平时对称:外部把引脚接到 VDD,回路里唯一承压的是下拉电阻,引脚处电压就是 VDD,不受下拉电阻的影响。
所以上下拉电阻的作用范围是有限定的:它只能在引脚悬空的时候决定电平,一旦外部有信号进来,决定权就交出去了。这也正是它被称为"弱"上下拉的原因。
十一、浮空输入
两个开关都断开
浮空输入就是内部的上拉和下拉都不配置,开关全部断开。等效出来是这样一个电路:
text
浮空输入:断开上拉开关,断开下拉开关
VDD
|
[上拉电阻] <- 开关断开
|
+--------------------- 引脚
|
[下拉电阻] <- 开关断开
|
VSS
引脚接高电平就读 1,接低电平就读 0,这两件事跟前面一样。真正的区别出现在引脚悬空的时候。
悬空的引脚像一根天线
引脚悬空、内部又没有上下拉把电平钉住,这根导线就变成了一根接收天线:

区别在于有没有人跟干扰较劲。上一节那些干扰源叠在信号上,至少还有一个外部驱动源在把电平往确定的方向拉;引脚悬空的时候连这个对手都没有,干扰直接决定了引脚上的电压。经过触发器之后,读到的就是随机的高低电平。
所以浮空输入在引脚悬空时,电平是不确定的。上一节强调的"缺省值",浮空输入给不出来。
适用场合
浮空输入不是不能用,条件是这个引脚一定会被外部驱动:要么接着一个明确的输出,要么接的器件在需要读的时候一定会给出确定电平。这种情况下不需要内部电阻来兜底,还能省掉上下拉带来的那点电流。
反过来说,如果这根引脚有可能处于悬空状态,又需要随时读它,那就别配成浮空。
十二、模拟输入
等效电路
输入模式里最后一种是模拟输入。回到输入结构图上看,模拟输入这条路径上,物理上等效于一根直通的导线:
text
模拟输入:直通,不做整形
引脚 ------------------------------ 到片上外设
(不经过触发器)
它和前面三种最大的不同在于:没有走触发器。
触发器的作用是整形,把抖动的输入整理成干净的 0 和 1。这对数字信号是好事,但对模拟信号是灾难,因为整形这个动作本身就是在把连续的量变成两个离散的状态,信息全没了。所以模拟输入干脆把这条路掐掉,外面是什么就原样交给片上外设。
用一句话概括:数字输入要的是"这是高还是低",模拟输入要的是"这到底是多少"。
模拟信号的去处
引脚把模拟信号原样送进来,总得有人处理。MCU 内部专门做这件事的片上外设是 ADC (Analog-to-Digital Converter,模数转换器)和 DAC (Digital-to-Analog Converter,数模转换器),前者把模拟量读成数字量,后者反过来。
模拟信号 是时间和幅度上都连续变化的物理量,物理世界本身是连续的。
数字信号在时间和幅度上都离散,是把连续的物理量量化成有限个取值之后的结果。
十三、八种模式的价值
回到开头那三个问题。
引脚是什么?是芯片内部一套完整电路的出口。这套电路里有保护二极管挡住过压,有上下拉电阻提供缺省值,有触发器把噪声整理干净,有两个 MOS 管负责往外输出。
为什么它既可以被程序读、又可以被程序写?因为输入路径和输出路径本来就是两套电路,只是共用了一个引脚。
为什么它能接 LED、能接按键、还能去做串口?因为有八种模式兜着。同样是这一根引脚,配上不同的模式,就是不同的用途:推挽能推能挽,适合驱动负载;开漏多了个高阻抗状态,适合共享总线;上拉下拉让输入有个确定的缺省值;浮空留给一定会被外部驱动的场合;模拟输入则把控制权整个让给 ADC 和 DAC。
这也是 GPIO 被称为"通用"的真正含义。它把自己做得很宽,是因为用它的不只是 CPU,而是整个片上外设体系。一个引脚不值钱,一百多个引脚加上八种模式,才让 MCU 有能力去感知外部世界发生了什么,再操控外部设备做出动作。
我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=e09fc9j7z34