- Authors

- Name
- Youngju Kim
- @fjvbn20031
- 引言 —— 我把 HC-SR04 直接插上了树莓派
- 什么是逻辑电平 —— VIH 与 VIL
- 为什么 3.3V 输出接到 5V 输入能读到,反过来却不行
- 5V 打进 3.3V 引脚,硅片内部发生了什么
- 各板卡的逻辑电平对照表
- 电阻分压 —— 便宜,但只能单向,而且在快信号下会崩掉
- MOSFET 双向电平转换与专用 IC
- I2C 为什么不一样 —— 开漏总线
- 结语 —— 电源引脚的电压和信号引脚的电压是两个不同的数字
引言 —— 我把 HC-SR04 直接插上了树莓派
超声波测距模块 HC-SR04 几乎是每个 Arduino 入门套件的标配。四个引脚:VCC、GND、TRIG、ECHO。示例代码十行就能搞定。
试试在树莓派上做同样的事。VCC 接 5V 引脚,GND 接 GND,TRIG 接 GPIO23,ECHO 接 GPIO24。逻辑上看不出任何问题。跑一下脚本,测出了距离,工作正常。
然后过了几天,或者几个小时,GPIO24 报废了。数值要么一直是 0,要么一直是 1。换个引脚又能正常工作。可那个引脚也过不了多久就报废了。
原因不在代码,而是一个简单的事实:HC-SR04 是 5V 器件,它的 ECHO 引脚输出的是 5V。树莓派的 GPIO 是 3.3V 器件,扛不住 5V。
这篇文章要讲清楚的是,这个「扛不住」精确来说到底是什么意思。以及有哪些办法能让它扛得住,各自的限制又是什么。
什么是逻辑电平 —— VIH 与 VIL
「数字」这个词容易让人误解。引脚内部来回传递的不是 0 和 1,而是电压,0 和 1 只是按某个基准去解读那个电压之后的结果。
解读的基准写在数据手册里,是两个数字。
VIH 是输入被确切判定为 HIGH 的最小电压。VIL 是被确切判定为 LOW 的最大电压。两者之间是一段什么都不保证的区间。
输出这边也有对应的两个数字。VOH 是输出为 HIGH 时保证的最小电压,VOL 是输出为 LOW 时保证的最大电压。
把两块芯片连起来,归根结底就是这样一场比较:发送方的 VOH 是否大于接收方的 VIH,发送方的 VOL 是否小于接收方的 VIL。两个条件都成立,通信才能成立。
来看看实际的数值。
| 器件 | 工作电压 | VIH(HIGH最小值) | VIL(LOW最大值) | 基准 |
|---|---|---|---|---|
| ATmega328P(Arduino Uno) | 5V | 3.0V | 1.5V | 0.6倍/0.3倍 |
| 74HC系列 CMOS | 5V | 3.5V | 1.5V | 0.7倍/0.3倍 |
| 74HCT系列 | 5V | 2.0V | 0.8V | 固定为TTL兼容值 |
| BCM2835(树莓派) | 3.3V | 2.31V | 0.99V | 0.7倍/0.3倍 |
| ESP32 | 3.3V | 2.475V | 0.825V | 0.75倍/0.25倍 |
同样是 5V 器件,ATmega328P 是 3.0V,74HC 却是 3.5V。这 0.5V 的差异,恰恰就是下一节要讲的关键分界。
为什么 3.3V 输出接到 5V 输入能读到,反过来却不行
3.3V 发往 5V —— 大体上没问题
3.3V 器件输出 HIGH 时,出来的大约是 3.2V 到 3.3V。这个信号如果被 5V 的 Arduino 接收,
接收到的电压3.3V 对比 ATmega328P的VIH 3.0V
3.3V大于3.0V,所以被读作HIGH。
能读到。裕量是 0.3V,不算宽裕,但能工作。所以把 ESP32 或树莓派的输出直接接到 Arduino 输入的电路,实际上到处都是,而且大多数时候工作得没问题。
不过我想强调一下「大体上」这个词。再看一遍上表的第二行。74HC 系列逻辑 IC 的 VIH 是 3.5V。3.3V 比 3.5V 小,所以规格上并不保证能被识别为 HIGH。可能在室温下运气好一直工作,一旦温度变化或者换了一批元件,就会突然失效。3.3V 板子接 74HC 移位寄存器或缓冲器时「偶尔不正常」这个症状,根源就在这里。
这种情况下正确的解法是换成 74HCT 系列。HCT 的输入阈值被固定在 TTL 标准的 2.0V,所以能相当宽裕地接受 3.3V 输入。串接一片 74AHCT125 之类的缓冲器是常见的解决方案,3.3V 板子驱动 WS2812 灯带信号不稳定时,通常靠这个元件就能解决。
5V 发往 3.3V —— 绝对不行
反方向不是能不能识别的问题,而是会不会被烧毁的问题。
3.3V 器件的数据手册上写着绝对最大额定值。以树莓派的 BCM 芯片为例,GPIO 引脚能承受的最大电压是:
绝对最大输入电压 = VDD + 0.5V = 3.3V + 0.5V = 3.8V
3.8V。5V 超出这个值 1.2V。而「绝对最大额定值」这句话的意思不是「超过就性能下降」,而是「超过就可能损坏,厂商不做任何保证」。
具体会损坏什么,下一节来看。
5V 打进 3.3V 引脚,硅片内部发生了什么
CMOS 输入引脚内部有一套静电保护电路,结构是这样的:
VDD (3.3V)
│
▼ 上钳位二极管(引脚→VDD方向导通)
│
引脚 ────┼──── 通向内部栅极
│
▼ 下钳位二极管(GND→引脚方向导通)
│
GND
一旦引脚电压比 VDD 高出二极管压降那么多,上方的二极管就会导通——也就是说,电流会从引脚流入 3.3V 电源轨。这是设计上本来就要的行为,专门用来把人手上带来的几千伏静电脉冲安全地泄放掉。
问题出在那个脉冲持续的时间。静电放电是纳秒级的。钳位二极管的设计目标,是扛住那一瞬间的大电流,而不是持续通过电流。
来算一下接上 HC-SR04 的 ECHO 输出会怎样。ECHO 为 HIGH 时输出 5V,那个输出晶体管的电阻大约是 50 欧姆。把二极管的正向压降取为 0.7V,
流过二极管的电流 = (5V - 3.3V - 0.7V) / 50欧姆
= 1.0V / 50欧姆
= 0.02A = 20mA
20mA 会持续不断地通过钳位二极管流入 3.3V 电源轨。这个二极管是个只有几微米宽的薄结构,扛不住持续的 20mA。反复多次之后就会发生电迁移——硅和金属走线接触面上的原子被电流拖着移动——最终要么断开,要么短路。
这只是第一条损坏路径,第二条更糟。
被注入的这 20mA 会进入 3.3V 电源轨。稳压器是被设计成往外输出电流的,不是用来吸收电流的。如果没有其他电路来消耗这部分电流,3.3V 电源轨的电压就会被抬高。挂在这条轨上的所有器件都会因此收到超出规格的电压。这就是为什么损坏不会止步于那一个 GPIO 引脚。
第三条是决定性的:闩锁效应。
CMOS 结构内部总是存在设计者并不想要的寄生晶体管对。p 型和 n 型区域交替排布,自然而然就形成了一个 pnpn 结构——这其实就是一个可控硅。平时是关闭的,但只要有足够的电流从引脚注入,它就会导通。而按可控硅的特性,一旦导通,除非切断电源,否则不会自己关闭。
导通的那一刻,电源和地之间就出现了一条低阻通路。几百毫安到几安培的电流会贯穿芯片内部。症状是这样的:树莓派突然发烫、卡死,必须断电重启才能恢复。就算恢复了,里面往往已经留下了熔化的走线。
总结一下,5V 打进 3.3V 引脚会发生三件事之一:钳位二极管慢慢磨损直至断裂,3.3V 电源轨被抬高并把其他元件一起拖出规格范围,或者闩锁效应当场烧毁芯片。而且这三种情况都不可逆。
还要补一句:这种损坏不会立刻显现出来,这一点反而更危险。一个好好运行了好几天的电路某天突然报废,人们不会去接线上找原因。「一开始就运行良好」这个事实,并不能证明接线是对的。
各板卡的逻辑电平对照表
先弄清楚自己的板子属于哪一边,是一切的起点。这里第三列是本文的核心。
| 板子 | 逻辑电压 | 能扛住5V输入吗 | 单引脚最大电流 | GPIO总和 | ADC |
|---|---|---|---|---|---|
| Arduino Uno R3/Nano/Mega | 5V | 不适用(本身就是5V) | 20mA(绝对最大40mA) | 200mA | 有 |
| Arduino Pro Mini 3.3V | 3.3V | 否 | 20mA | 200mA | 有 |
| Arduino Due | 3.3V | 否 | 3mA(推荐)、15mA(最大) | 130mA | 有 |
| 树莓派40针接头 | 3.3V | 否 | 16mA | 50mA | 无 |
| 树莓派 Pico | 3.3V | 否 | 12mA | 50mA | 有 |
| ESP32 DevKit v1 | 3.3V | 否 | 12mA | 120mA | 有 |
| ESP8266(NodeMCU) | 3.3V | 否 | 12mA | 无公开数据 | 有(1通道) |
| Teensy 4.x | 3.3V | 否 | 4mA(推荐) | 有限制 | 有 |
有三点要说明。
第一,这份列表里的 3.3V 板子,没有一个能扛住 5V。老款的 Teensy 3.x 和部分 STM32 系列曾经有过耐 5V 的引脚,但现在常用的板子,最好一律当作例外来看待,不要指望有这种保护。
第二,树莓派没有 ADC 这件事经常闯祸。要把模拟输出的传感器接到树莓派上,需要 MCP3008 这类外部 ADC;不知道这一点,把模拟传感器直接接到 GPIO 上,不但读不出数值,如果还是个 5V 传感器,连引脚也会一起报废。
第三,有 5V 电源引脚,不代表 GPIO 就是 5V 的。树莓派接头上的第 2、4 号引脚会输出 5V,但那是用来给元件供电的。一旦把使用这个 5V 的元件的信号线直接接到 GPIO 上,就又回到了本文开头那一幕。
如果已经画好了接线,想确认这个组合是否安全,可以把板子和元件放进本站的接线验证工具。像 HC-SR04 这种只能用 5V 供电、也只输出 5V 逻辑电平的元件,一旦接到 3.3V 板子上,就会报出逻辑电平风险的错误,同时也会一并算出单引脚电流和各电源轨的总和。
电阻分压 —— 便宜,但只能单向,而且在快信号下会崩掉
最便宜的解法是两个电阻——就是前一篇文章里那个分压器,原样搬过来。
5V信号 ──[ R1 ]──┬── 3.3V板子的输入引脚
│
[ R2 ]
│
GND
输出电压是这样算出来的:
输出 = 输入 × R2 / (R1 + R2)
取 R1 为 1 千欧姆,R2 为 2 千欧姆,
输出 = 5V × 2000 / (1000 + 2000) = 5V × 0.667 = 3.33V
3.33V,对 3.3V 引脚来说是安全的数值。另一组常用的组合是 1.8 千欧姆和 3.3 千欧姆:
输出 = 5V × 3300 / (1800 + 3300) = 5V × 0.647 = 3.24V
3.24V,比树莓派的 VIH(2.31V)高出不少,能可靠地读作 HIGH。
这个方案的优点很明显:只要两个电阻,元件几分钱一个,还能直接插在面包板上。对 HC-SR04 的 ECHO 引脚来说,这个方法几乎就是事实标准。
缺点有三个,每一个都值得精确地理解清楚。
缺点一 —— 只能单向工作
分压器只能把高电压降下来,没法把低电压抬上去。也就是说,它只能用来让 3.3V 板子接收 5V 元件的输出。如果需要把 3.3V 板子的输出送到 5V 元件那边,这个电路完全帮不上忙。
好在大多数情况下,反方向根本不需要转换就能通。HC-SR04 的 TRIG 直接接收树莓派的 3.3V 输出就能识别得很好。但就像前面 74HC 的例子那样,如果对方的 VIH 比较高,就通不过。
缺点二 —— 会消耗常态电流
只要信号是 HIGH,电流就会持续经过 R1 和 R2。
1千欧姆 + 2千欧姆 → 5V / 3000欧姆 = 1.67mA
1.67mA 在 USB 供电下可以忽略,但在电池供电设备上,如果有八个这样的电路,就是 13mA,已经不能忽略了。
想省电流就把电阻调大,可下一个缺点又会随之变大。这两者是正面冲突的关系。
缺点三 —— 在快信号下会崩掉
这是最重要、也最容易被忽视的一条。
从分压器输出端往回看整个电路的电阻,也就是输出阻抗,是两个电阻的并联值。
1千欧姆 || 2千欧姆 = (1000 × 2000) / 3000 = 667欧姆
再加上输入引脚和走线大约 30 皮法的电容,就构成了一个 RC 低通滤波器。
时间常数 = 667欧姆 × 0.00000000003F = 0.00000002秒 = 20纳秒
信号从 10% 升到 90% 所需的时间,是时间常数的 2.2 倍。
上升时间 = 20ns × 2.2 = 44纳秒
HC-SR04 的 ECHO 是毫秒级变化的信号,44 纳秒完全不算问题。这也是这个组合能好好工作的原因。
现在把电阻调大试试。如果为了省电流用了 10 千欧姆和 20 千欧姆,
输出阻抗 = 10000 || 20000 = 6667欧姆
时间常数 = 6667 × 30pF = 200纳秒
上升时间 = 440纳秒
440 纳秒。1MHz 的 SPI 信号一个比特的长度是 1000 纳秒,信号才爬到一半,下一个比特就已经开始了。波形被压成三角形,数据也就跟着损坏。这就是电阻分压器不能用在 SPI 或高速 UART 上的原因。
总结一下,电阻分压器的适用范围大致如下:
| 信号 | 大致频率 | 电阻分压器的适用性 |
|---|---|---|
| 按钮、继电器状态 | 数Hz | 非常适合 |
| HC-SR04 ECHO | 数百Hz | 适合 |
| UART 9600bps | 9.6kHz | 适合 |
| UART 115200bps | 115kHz | 用较小阻值时可行 |
| I2C 100kHz | 100kHz | 不适合(开漏结构不同) |
| SPI 1MHz以上 | 1MHz以上 | 不适合 |
| WS2812数据 | 800kHz,时序严格 | 不适合 |
MOSFET 双向电平转换与专用 IC
BSS138 双电阻电路
最常用的双向电平转换电路,由一颗 N 沟道 MOSFET 加两个上拉电阻搭成。市面上四通道电平转换模块的内部,绝大多数用的都是这个电路。
弄懂它的工作原理,也就顺带弄懂了它的局限。MOSFET 的栅极被固定在较低的那个电压——3.3V,源极接到 3.3V 一侧的线,漏极接到 5V 一侧的线。两侧的线各自按各自的电压接了上拉。
两条线都不受干扰时,3.3V 那侧升到 3.3V,5V 那侧各自升到 5V。因为栅极和源极电压相同,MOSFET 处于关断状态。
3.3V 一侧被拉到 LOW 时,源极变成 0V。栅极是 3.3V,于是栅源之间出现 3.3V 电压,MOSFET 导通,把 5V 那一侧也一起拽了下去。
反过来,5V 一侧被拉到 LOW 时,首先是 MOSFET 的寄生二极管导通,把 3.3V 一侧拉到大约 0.7V 附近,这样栅源之间又产生了电压,MOSFET 随之完全导通。不管哪种情况,最终两侧都变成 LOW。
不论从哪一侧开始,LOW 都能传过去,而 HIGH 则由各自的上拉电阻负责生成。不需要指定方向。
这里也就暴露出它的局限。这个电路本质上是开漏结构。升到 HIGH 的速度完全由上拉电阻和电容决定,MOSFET 在这个过程里帮不上任何忙。用 10 千欧姆上拉配 50 皮法,上升时间会超过 1 微秒。所以实用上限大约在 400kHz 到 1MHz 之间。对 I2C 来说完美,但没法用在高速 SPI 上。
专用电平转换 IC
需要速度或者通道数多的时候,就得上专用 IC。每个型号的性格差异很明显,得知道挑选的标准。
| 元件 | 方向 | 输出方式 | 大致速度 | 是否可用于I2C | 特点 |
|---|---|---|---|---|---|
| 电阻分压器 | 单向(降压) | 无源 | 100kHz以下 | 不可 | 最便宜,两个元件 |
| BSS138电路 | 双向 | 开漏 | 400kHz~1MHz | 适合 | 无需指定方向 |
| 74LVC245 | 单向(引脚指定) | 推挽 | 100MHz以上 | 不可 | 8通道,速度极快 |
| 74AHCT125 | 单向(升压) | 推挽 | 50MHz以上 | 不可 | 把3.3V推到5V时使用 |
| TXB0104 / TXB0108 | 自动方向检测 | 推挽 | 数十MHz | 不适合 | 与上拉电阻冲突 |
| TXS0102 / TXS0108 | 自动方向检测 | 兼容开漏 | 数MHz | 适合 | 几乎是I2C专用 |
| PCA9306 | 双向 | 开漏 | 400kHz | 适合 | I2C专用,2通道 |
这张表里必须牢记的一行是 TXB 系列。TXB0104 带有自动方向检测,看起来很方便,但绝对不能用在 I2C 上。这颗芯片内置了一个能检测信号变化瞬间、并短暂用力推一把的电路,而 I2C 总线的上拉电阻恰恰会干扰这个检测,把方向判断搅乱。数据手册上也明确写着不要用在开漏信号上。同一家厂商的 TXS 系列才是为这个用途而生的。名字只差一个字母,用途却截然相反,下单的时候要格外留意。
I2C 为什么不一样 —— 开漏总线
I2C 之所以要单独拿出来讲,是因为这条总线的前提和上面所有方法都不一样。
I2C 的 SDA 和 SCL 是开漏的。挂在总线上的任何器件都不会主动把线推向 HIGH,只会拉低或者松手,HIGH 完全靠上拉电阻来实现。
这一点立刻就会暴露出一个问题:上拉接在哪个电压上,决定了整条总线的电压。5V 传感器模块常常已经在板子上自带了 5V 上拉。把这种模块直接接到树莓派的 SDA 和 SCL 上,哪怕总线处于空闲状态,这两条线也都会是 5V。也就是说,树莓派的 GPIO2 和 GPIO3 会一直承受 5V——前面讲的钳位二极管那套故事,不只是在有信号时才发生,而是无时无刻不在发生。
所以 I2C 的电平转换不是可选项,而且方法也很有限。电阻分压器用不了——在开漏线上接分压器,那个电阻会和上拉相互作用,导致 LOW 电压根本降不到 0V。推挽式电平转换器也用不了——就是前面 TXB 系列的那个问题。剩下能用的只有 BSS138 电路、TXS 系列,或者 PCA9306。
计算上拉电阻值
处理 I2C 时还有一个值得掌握的计算:上拉电阻值的上限和下限。
下限由器件能把线拉多低的能力决定。I2C 规范要求,输出为 LOW 时要能在维持 0.4V 以下的同时吸收 3mA 电流。
最小上拉电阻 = (3.3V - 0.4V) / 0.003A = 967欧姆
上拉电阻如果小于 967 欧姆,器件就没法把线拉得够低。
上限由上升时间决定。标准模式 100kHz 下允许的最大上升时间是 1000 纳秒,假设总线电容为 100 皮法,
最大上拉电阻 = 1000ns / (0.8473 × 100pF) = 11803欧姆 ≈ 11.8千欧姆
400kHz 的高速模式下,允许的上升时间缩短到 300 纳秒,于是
最大上拉电阻 = 300ns / (0.8473 × 100pF) = 3541欧姆 ≈ 3.5千欧姆
这就是为什么标准模式常用 4.7 千欧姆、高速模式常用 2.2 千欧姆作为惯例——不是没有依据的习惯,而正是这个计算的中间值。
树莓派上常见的一个坑
树莓派的 GPIO2 和 GPIO3 板载了固定的 1.8 千欧姆上拉电阻。而市面上的 I2C 模块通常也自带上拉电阻。这些全都是并联关系。
接两个模块,各自带 10 千欧姆上拉,
合成电阻 = 1 / (1/1800 + 1/10000 + 1/10000) = 1 / 0.000756 = 1324欧姆
维持LOW所需的电流 = (3.3V - 0.4V) / 1324欧姆 = 0.00219A = 2.19mA
2.19mA,在 3mA 的规格之内,能正常工作。
现在接四个带 4.7 千欧姆上拉的模块,
合成电阻 = 1 / (1/1800 + 4/4700) = 1 / 0.001406 = 711欧姆
所需电流 = (3.3V - 0.4V) / 711欧姆 = 0.00408A = 4.08mA
4.08mA,超出规格。某个器件没法把线拉到 0.4V 以下,总线会间歇性地出问题。症状是「接三个设备还好好的,接到第四个就不对劲了」。解决办法是把模块上的上拉电阻物理拆掉。在大多数模块上,那个电阻就是板子上一个小元件,用烙铁焊下来就行。
实际代码里的样子
回到 HC-SR04。在 ECHO 上接好 1k 和 2k 的分压器之后,代码就变得平平无奇了。
# 把HC-SR04接到树莓派上。
#
# 接线:
# VCC -> 5V (2号引脚)
# GND -> GND (6号引脚)
# TRIG -> GPIO23 (直接接收树莓派的3.3V输出,不需要转换。)
# ECHO -> 经过1k电阻接到GPIO24,再从GPIO24经2k电阻接到GND
#
# 分压结果: 5V * 2000 / 3000 = 3.33V
# 如果把ECHO直接接到GPIO24,引脚会损坏。
from gpiozero import DistanceSensor
from time import sleep
sensor = DistanceSensor(echo=24, trigger=23, max_distance=2.0)
while True:
# distance的单位是米。
print(f"{sensor.distance * 100:.1f} cm")
sleep(0.2)
代码里完全看不出分压器的痕迹。因为从软件的角度看,什么都没发生。这也正是硬件问题最后总被拿到代码里去找的原因——接线错了,代码却看起来一切正常,甚至看起来运行得很好。
结语 —— 电源引脚的电压和信号引脚的电压是两个不同的数字
这篇文章的结论很简短。接一个元件之前,需要分开确认两件事:这个元件用多少伏工作,以及这个元件输出的信号是多少伏。
这两个值通常相同,但不总是相同。HC-SR04 用 5V 工作,输出的也是 5V。PIR 传感器 HC-SR501 用 5V 工作,但输出是 3.3V。同样是 5V 器件,一个会烧毁树莓派,一个却是安全的。数据手册里要确认的不是供电电压,而是输出的逻辑电平。
而且这项确认没法用「电路能不能工作」来代替。把 5V 的 ECHO 直接接到树莓派上,电路一开始运行得很好。硬件世界里存在这样一个区域:「能工作」并不能证明「是安全的」,而逻辑电平正是其中最典型的一处。
下一篇文章会转向由电流而非电压引发问题的情形:把电机接到 GPIO 上,板子为什么会重启。