做硬件的兄弟应该都有这个经历:板子画到一半,功能加得差不多了,主控的GPIO也快被薅秃了。显示器占掉一排,几个传感器又是一排,还要接按键、接LED,翻来覆去数引脚,怎么都不够用。这时候最省事的方案就是挂一颗I2C IO扩展芯片,两根线换回八个甚至十六个IO口,听起来非常划算。
不过这个方案“听起来香”,真正落地却是另一回事。我自己前阵子刚做一个控制板,主控是ESP32,屏、编码器、传感器把引脚占得差不多了,后面又追加了8个按键和十几颗LED,只能老老实实挂IO扩展。项目做完回头复盘,发现选型这个环节踩的坑比写驱动还多:地址算错、电平不匹配、输出能力不够、上拉电阻反复调……这篇就把我自己的选型思路和踩坑记录整理一下,给准备上IO扩展的朋友做个参考,避免你在一颗小芯片上浪费好几天。
1. 先冷静三分钟:你真的需要I2C IO扩展吗
很多人一看到GPIO紧张,第一反应就是找IO扩展芯片。但我的建议是先别急着下单,花几分钟把需求盘清楚,因为不少场景其实有更简单、更便宜的替代方案,直接上I2C扩展反而不划算。
1.1 先盘清楚需求:输入、输出、中断、速度
做选型第一步不是看芯片,而是回答这几个问题:
- 你需要扩展多少路?全是输出,还是输入输出都有?
- 输入信号需要中断唤醒吗?还是主控定时轮询就行?
- 输出要驱动什么东西?指示LED、继电器、还是直接推负载?
- 这些IO的翻转速度要求高不高?例如有没有需要模拟某种时序的场合?
- 系统里I2C总线是否已经很忙?还有多少余量?
这几个问题直接决定了你该用8位还是16位芯片、该选带中断的还是不带中断的、该选准双向还是推挽输出。不少翻车现场,根源就是这里的需求没盘清楚,拿着一颗PCF8574硬撑,结果发现输出电流不够,或者按键没有中断导致低功耗睡不下去。
1.2 有几种更“土但稳”的方案可以替代
如果你面临的情况比较单纯,先看看下面三种替代方案能不能解决问题:
- 纯扩展输出,可以用74HC595。串行移位寄存器,3根线就能扩展无数路输出,驱动LED、继电器逻辑电平完全够用,单颗芯片几毛钱,实在是便宜可靠。
- 纯扩展输入,可以用74HC165。同样移位寄存器原理,3根线读取多位输入,适合按键、拨码开关这类低速信号。
- 只是LED数量太多,不一定占用普通GPIO。比如用WS2812这类串行灯珠,单根数据线就能串几十颗,颜色亮度还能调,比IO扩展芯片加限流电阻的方案优雅得多。
- 换更大封装的主控。如果项目刚开始,PCB面积也允许,直接换一颗引脚更多的MCU往往比加扩展芯片更省心。扩展芯片毕竟还要写驱动、调时序,而换主控只需要把代码里引脚定义改一改。
以上几种方案的成本、可靠性和开发效率,在特定场景下都优于I2C扩展芯片。所以别一上来就选型,先把替代方案比一遍。
1.3 什么场景才真正适合I2C扩展芯片
那什么时候I2C扩展才是最优解?我总结下来,需要同时满足这几点:
- 输入和输出都要有,而且数量都不少,比如20路左右;
- 输入信号需要触发中断,最好能唤醒睡眠中的主控;
- 输出速度要求不高,主要是开关量、LED这类低速负载;
- 主控本身有硬件I2C,或者软件I2C的时序压力不大;
- I2C总线上挂的其他设备不多,地址和带宽都还够用。
满足这些条件时,I2C IO扩展就是非常合适的选择。一根总线最多能挂好几片扩展芯片,轻松扩展出几十路IO,PCB走线也简单,成本却很低。
2. 为什么I2C是IO扩展的主流:常见方案横向对比
市面上IO扩展方案不少,除了I2C,还有SPI、移位寄存器、甚至用UART转并口之类的“野路子”。我自己把这些方案对比过一遍,下面这张表基本能说明问题。
| 方案 | 占用主控引脚 | 可扩展IO数 | 双向支持 | 主要优点 | 主要缺点 | 典型芯片 |
|---|---|---|---|---|---|---|
| I2C扩展 | 2(SDA/SCL) | 可级联几十路 | 支持 | 引脚少、可级联、带中断 | 速度慢、软件略绕 | PCF8574A、MCP23017、TCA9555 |
| SPI扩展 | 3~4(SCLK/MOSI/MISO/CS) | 可级联 | 部分支持 | 速度快、时序简单 | 占用CS、双向方案选择少 | MCP23S17 |
| 移位寄存器 | 2~3(DATA/CLK/LATCH) | 输出不限 | 输出方便、输入需专用片 | 便宜、抗干扰好 | 输入扩展麻烦、无中断 | 74HC595、74HC165 |
| 直换大封装MCU | 0 | 视芯片 | 视芯片 | 免额外芯片、性能强 | 改板改代码、成本可能上升 | 各种MCU |
2.1 I2C方案的核心优势
I2C扩展最大优势是“省引脚”。一条总线上挂多个设备,每个设备只占一个地址,地址用A0/A1/A2引脚硬件配置。比如MCP23017有一组A0/A1/A2,8个地址可配,每片是16位IO,理论上一条I2C总线最多能挂8片,也就是128路IO,对绝大多数项目都绰绰有余。
I2C协议本身也很成熟,几乎所有MCU都有硬件外设支持,Linux下还有i2c-tools,调试起来非常方便。而且很多IO扩展芯片自带中断输出,按键类输入信号可以直接触发主控的外部中断,不用主控一直轮询I2C总线,对低功耗设计很关键。
2.2 I2C方案的固有局限,提前降低预期
I2C IO扩展并不是万能的,它的局限也很明显。首先是速度,标准模式100kHz,快速模式400kHz,一次写操作至少包含地址字节、数据字节和应答位,整体算下来更新8位IO也要几十微秒,实际翻转频率通常到不了几十kHz。你拿它驱动LED、按键、继电器这些低速信号没问题,但想模拟WS2812那种800kHz的时序,或者做并口数据输出,基本没戏。
其次是总线独占问题。I2C是共享总线,一旦某个从设备把SDA拉死,整条总线的传感器、存储芯片全部瘫痪。IO扩展芯片如果焊接不良,或者输入引脚接错电平导致钳位,排查起来会比其他方案痛苦得多。
2.3 扩展出来的IO是“低速IO”,别拿它干高速活
这里我特别想强调一个认知:I2C扩展出来的IO,在物理上依然是低速IO,它的时序和响应速度受限于总线协议和芯片内部逻辑,不可能拥有主控原生GPIO那种几十MHz的翻转能力。
所以选型时一定要把功能分成两堆:需要快速响应、精确时序的信号,必须留在主控原生GPIO上;真正对速度无所谓的开关量、指示灯、按键检测,才适合丢给扩展芯片处理。这样分完之后,你会发现主控的GPIO压力其实也没那么大了。
3. 选型核心参数拆解:这几个指标决定成败
确定要用I2C扩展之后,选型就会落到具体芯片上。市面上的I2C IO扩展芯片少说也有十几款,但真正决定项目成败的参数,其实就那么几个。
3.1 位宽与端口结构:8位还是16位,准双向还是推挽
位宽很好理解,8位芯片一片8个IO,16位芯片一片16个IO。但除了数字,更要关注端口结构。常见的有准双向(quasi-bidirectional)和推挽输出(push-pull)两种,差别巨大。
以经典的PCF8574为例,它是准双向结构,没有方向寄存器。想用作输入时,先往对应位写1,内部只是接了一个非常弱的电流源上拉,然后靠外部信号拉高拉低。想用作输出时,写0是强下拉,写1是高阻加弱上拉。这导致它输出高电平的能力极弱,实测能提供的电流常常不到1mA,拿它直接驱动LED正极基本不亮。而MCP23017这类推挽输出芯片,每个引脚可以主动输出高、低电平,驱动能力好很多。
选型时先看端口结构,再谈其他参数,这是第一个关键决策点。
3.2 I2C地址:7位还是8位,怎么规划
I2C地址这个坎,新手几乎必踩。芯片手册里写的是7位地址,比如PCF8574的基地址是0x20,但很多MCU驱动库函数要求填8位地址,也就是左移一位变成0x40。你在代码里填0x20还是0x40,取决于库的约定。更麻烦的是,Linux下i2cdetect扫描出来的是7位地址0x20,而STM32的HAL函数要的是8位地址0x40,两边一混,就等着查半天吧。
地址引脚方面,PCF8574有A0/A1/A2三根地址线,每根可接VDD或GND,组合出8个地址;PCF8574A则映射到另一段地址范围0x38到0x3F。MCP23017同样有A0/A1/A2,8个地址可选。选型时要注意芯片后缀,PCF8574和PCF8574A地址范围完全不同,混用可以扩展到16个不同地址,但千万别搞混。
3.3 驱动电流:不是所有芯片都能推LED
输出驱动电流是选型时最容易误判的地方。很多新手看到芯片手册写“IO可以输出25mA”,兴冲冲直接接LED,结果高电平点亮时灯不亮,低电平点亮时又怕烧芯片。这里的门道在于,输出高电平和灌入低电平的能力往往不一样。
PCF8574这类准双向芯片,灌电流能力还可以,外部负载从IO口吸电流到GND时,每脚能到20mA左右,但输出高电平时几乎没有电流输出能力。所以用这类芯片驱动LED,正确接法是让LED正极接电源、负极经过限流电阻接IO,IO输出低电平时LED点亮。MCP23017的推挽输出特性好很多,高低电平驱动能力都接近,但也不能拿它直接驱动继电器、电磁阀,那需要额外的功率器件。
另外还要注意整颗芯片的总电流限制。虽然每个引脚可能允许20mA,但所有引脚同时输出大电流时,芯片总功耗会超限。工程上我习惯把每个LED的电流控制在2到10mA以内,多个灯同时亮的场景再适当留余量。
3.4 中断引脚与上电复位:这两个细节很容易被忽视
带中断的IO扩展芯片,在主控需要低功耗休眠时非常有用。比如按键按下时,扩展芯片检测到引脚电平变化,主动把INT引脚拉低,唤醒主控。选型时要注意INT引脚是开漏输出还是推挽输出,多个扩展芯片的INT能否直接线与共用一个主控中断引脚。MCP23017的INT默认是推挽模式,如果打算把多片INT接在一起,就需要把IOCON寄存器里的ODR位设1,改成开漏模式,否则一片输出高一片输出低,直接打架。
上电复位行为同样重要。有些芯片复位后IO默认是输入高阻,有些是内部上拉,有些是输出高。如果IO接的是LED,复位期间引脚默认输出低,那LED就会在上电瞬间闪一下;如果接的是继电器,那就更麻烦了。选型时最好查清楚复位状态的默认电平,或者在外部电路上做限制。
3.5 常用芯片横向对比
| 芯片 | 位宽 | 端口结构 | 基地址范围 | 中断 | 工作电压 | 特点 |
|---|---|---|---|---|---|---|
| PCF8574A | 8位 | 准双向 | 0x38~0x3F | 有 | 2.5~5.5V | 经典、便宜、资料多,高电平驱动弱 |
| MCP23017 | 16位 | 可配推挽/开漏 | 0x20~0x27 | 有,可配置 | 1.8~5.5V | 寄存器丰富,软件能力强,适合复杂需求 |
| TCA9555 | 16位 | 推挽 | 0x20~0x27(部分后缀在另一段) | 有 | 1.65~5.5V | 低压低功耗,封装多 |
| AW9523 | 16位 | 开漏/推挽可配 | 常见基址0x58附近 | 有 | 2.5~5.5V | 自带LED PWM、恒流驱动,做灯效很香 |
具体参数以你买到的批次和官方数据手册为准,尤其“输出电流”这种指标,不同厂商、不同温度下的实测值可能有差异。但大致数量级关系是:PCF8574高电平驱动最弱,MCP23017/TCA9555这类推挽芯片强很多,AW9523则把LED驱动功能集成进去了。
4. 选型之后还要避的坑:上拉、电平、复位、寄存器
芯片选好了,硬件设计阶段还有一堆细节等着你。很多项目功能上没选错芯片,却在硬件和软件配置上翻车。下面这些坑,我基本都踩过。
4.1 上拉电阻怎么算怎么选
I2C总线必须接上拉电阻,这个大家都知道,但阻值选多少,经常有人随手放个10k就不管了。上拉电阻太小,灌电流太大,从设备可能拉不低总线;上拉电阻太大,上升沿太缓,高速模式下通信直接失败。
理论计算按照I2C规范来。快速模式400kHz下,上升沿最大允许300ns,假设总线电容约200pF,上拉电阻最大值约等于:
Rp_max = tr / (0.8473 × Cb) = 300ns / (0.8473 × 200pF) ≈ 1.77kΩ
最小值由低电平灌电流决定,假设VDD=3.3V,VOL=0.4V,IOL=3mA,则:
Rp_min = (VDD - VOL) / IOL ≈ (3.3 - 0.4) / 0.003 ≈ 966Ω
所以400kHz下理论范围大概在1k到1.8k之间。实际工程中总线电容不好精确估算,我通常的做法是:先用4.7k的电阻跑100kHz标准模式把所有功能调通,再拿示波器看上升沿,如果波形不好或者需要上400kHz,就把上拉电阻降到2.2k甚至1k,同时注意总线上所有设备加起来会不会把阻值压得太低。别一上来就极端取值,容易把问题复杂化。
4.2 电平匹配:芯片电压和主控电压不同怎么办
I2C总线的高电平由上拉电阻接的电源决定,而不是从设备芯片的VDD简单决定。如果主控是3.3V,扩展芯片是5V,I2C上拉又接到5V,那SDA/SCL的高电平就会到5V,主控引脚如果不支持5V容忍,轻则通信异常,重则烧引脚。
解决思路有三种。第一种最省事:选一颗工作电压覆盖主控电压的扩展芯片,比如主控3.3V,就选工作在3.3V的PCF8574或MCP23017,I2C上拉也统一接3.3V,大家电平一致。第二种:如果必须用5V扩展芯片,上拉电阻接3.3V,这样总线高电平是3.3V,只要扩展芯片能识别3.3V的高电平,也能工作,但这依赖芯片VIH参数。第三种:加I2C电平转换芯片,比如PCA9306,稳妥但增加成本。
我个人习惯是双电源场景直接上电平转换,或者干脆所有器件统一供电电压。
4.3 上电时序:LED闪一下和IO默认状态
IO扩展芯片刚上电那几十毫秒,引脚状态往往不受软件控制,这是硬件设计里一个隐形坑。比如MCP23017上电后IO方向默认是输入,引脚高阻;PCF8574上电后IO默认输出高(弱上拉)。如果你的LED接法是“IO输出低点亮”,那上电瞬间PCF8574输出高,LED不会亮,还算安全;但如果你用了PNP三极管,高电平反而让LED亮,上电瞬间就可能闪一下。
我在实际项目里的处理方法是:LED的电源加一个MOS管或使能脚,等主控初始化完所有IO扩展芯片之后,再打开LED电源;如果不想加电源开关,就在LED支路并联一个下拉电阻,让上电瞬间即使IO悬空,LED也不会导通。
4.4 地址规划:同一总线多片扩展怎么排
I2C总线上的设备不能地址冲突。规划地址时,先把所有I2C外设的基地址列个表,包括传感器、存储芯片、扩展芯片,再给每片扩展芯片分配A0/A1/A2的组合。PCB上最好每个地址引脚都留0欧电阻或跳线焊盘,方便调试时改地址。
如果要挂多片相同型号的扩展芯片,地址范围有限。PCF8574有8个地址,PCF8574A还有8个地址,加起来16个,普通项目够用。如果超过16路,可以考虑换16位扩展芯片,或者用TCA9548A这类I2C多路开关切通道,但软件复杂度会上升,一般用不到。
4.5 中断接法:开漏、上拉、共INT
IO扩展芯片的INT引脚是给你接主控外部中断用的。按键扫描、报警输入这些场景,离开中断确实会很痛苦,因为MCU深度睡眠时不可能一直轮询I2C。
接法上有几个细节。首先确认INT引脚是开漏还是推挽输出。如果是开漏,必须外部上拉;如果是推挽,多片INT直接并联会打架,需要把芯片配置成开漏模式。其次,中断触发后要在中断服务程序里读一次扩展芯片的输入端口或中断标志寄存器,把中断状态清掉,不然INT引脚会一直被拉低,主控不停进中断。最后,按键类输入建议配合内部上拉或外部上拉,避免悬空电平抖动导致误触发。
4.6 寄存器配置别想当然:MCP23017为例
推挽输出的芯片寄存器多,功能强,但也意味着配置不当会出怪问题。以MCP23017为例,它的寄存器有IODIR(方向)、GPPU(上拉)、GPINTEN(中断使能)、INTCON(中断触发条件)、DEFVAL(默认比较值)、IOCON(配置控制)等。很多人初始化时只改IODIR,忘了设置GPPU和GPINTEN,结果按键输入悬空或中断不触发。
特别提醒IOCON寄存器里的BANK位,如果被改成1,芯片内部寄存器地址会重新映射,之后所有寄存器读写地址全变,代码里如果还按默认地址操作,整个芯片就跟“坏了”一样。我调试时有过一次,查了半天才发现是初始化时往IOCON写错了值,把BANK位改成了1。所以操作这类芯片时,先读一遍IOCON,确认默认值,再决定要不要动它。
4.7 选型自检清单
把上面这些整理成一份清单,每次选型对着过一遍:
- 输入、输出各需要几路,是否需要中断;
- 是否需要PWM、LED呼吸这类特殊功能;
- 输出驱动电流是否符合负载要求,LED接法是否明确;
- 工作电压是否和主控一致,I2C上拉电平是否匹配;
- I2C地址引脚是否留了跳线,总线上所有设备地址是否列过表;
- 芯片复位状态下IO默认电平对负载是否安全;
- 封装和供货是否稳定,价格是否在预算内;
- 软件上有没有现成驱动,寄存器复杂度是否可控。
5. 实战案例:给控制板加8路LED、5路按键、1路报警输入
光讲理论和坑,不如走一遍完整案例。下面这个例子是我做控制板时的一个简化版,需求不复杂,但刚好涵盖了选型、硬件、软件的全过程。
5.1 需求盘点和引脚预算
需求是这样的:
- 8路LED指示灯,每路电流约10mA,不需要调光,不需要呼吸;
- 5路按键输入,用于菜单操作,按键时需要立即响应,最好中断唤醒;
- 1路报警信号输入,传感器输出的是开漏信号,平时高电平,报警时拉低;
- 主控是某款3.3V MCU,I2C总线上已经挂了一颗温度传感器,地址0x48;
- 低功耗要求比较严格,主控大部分时间睡眠,不能用轮询。
合计需要14路IO,其中6路输入(5个按键+1个报警)都需要中断,8路输出。
5.2 芯片选型决策过程
先看PCF8574A,8位,一片不够,得两片。两片都有INT脚,可以接两个外部中断,软件上也还行。但它的高电平驱动弱,8路LED得全部用低电平点亮,这个没问题,可是两片芯片占用两个I2C地址,加上温度传感器,总线上三个设备,地址还好。成本很低,这是个备选。
再看MCP23017,16位,一片搞定。IO端口强度好,推挽输出,驱动10mA LED很稳。INT功能可以配置,只让需要中断的输入引脚触发,其他引脚正常使用。寄存器多但资料也多,开发稍微花点时间但可控。价格比两片PCF8574A贵一点,还在接受范围。
AW9523也翻了翻,16位,带LED PWM,如果以后要做呼吸灯效果会很爽。但这项目不需要调光,AW9523很多引脚配置手册不够直观,软件调试成本可能更高,决定不选。
最终选了MCP23017。核心原因就是“一片覆盖所有需求”,地址占用少,中断配置灵活,推挽输出对LED很友好,社区代码也丰富。
5.3 硬件接线与地址设计
MCP23017的A0/A1/A2全部接地,I2C地址就是0x20,和温度传感器0x48不冲突。SDA和SCL各接2.2k上拉到3.3V,INT引脚接到主控一个支持外部中断的引脚,并配置为上拉输入。因为只用一片,INT输出保持默认推挽模式即可。
IO分配这样安排:
- GPIOA0~A7接8路LED。LED正极接3.3V,负极通过限流电阻接到IO脚,低电平点亮。
- GPIOB0~B4接5个按键。按键另一端接GND,内部上拉使能,平时读高,按下读低。
- GPIOB7接报警传感器开漏输出,内部上拉使能,报警时被拉低。
限流电阻取值计算:LED压降按2V算,3.3V电源,目标电流5mA,限流电阻约 (3.3-2)/0.005 = 260Ω,取330Ω标准值即可。IO输出低电平灌入约5mA电流,离芯片限制远得很。
5.4 代码初始化与读写
MCP23017的驱动不复杂,就是I2C写寄存器。初始化顺序很关键,先把方向寄存器配好,再配上拉,再使能中断,最后读一次GPIO清中断标志。
// 伪代码,I2C地址0x20 // 1. 配置方向:GPIOA全输出,GPIOB bit0-4和bit7为输入,其余输出 i2c_write_reg(0x20, 0x00, 0x00); // IODIRA = 0x00 i2c_write_reg(0x20, 0x01, 0b10001111); // IODIRB // 2. 配置内部上拉:GPIOB的输入引脚使能上拉 i2c_write_reg(0x20, 0x0C, 0x00); // GPPUA = 0x00 i2c_write_reg(0x20, 0x0D, 0b10001111); // GPPUB // 3. 配置中断:只使能GPIOB的bit0-4和bit7 i2c_write_reg(0x20, 0x04, 0x00); // GPINTENA = 0 i2c_write_reg(0x20, 0x05, 0b10001111); // GPINTENB // 4. 中断触发方式:设为电平变化触发(INTCONB=0,且DEFVALB不用管) i2c_write_reg(0x20, 0x08, 0x00); // INTCONA = 0 i2c_write_reg(0x20, 0x09, 0x00); // INTCONB = 0 // 5. 读一次GPIOB,清掉初始化过程可能产生的假中断 i2c_read_reg(0x20, 0x13);初始化完成后,主控进入睡眠前把要亮的LED状态写进GPIOA,等INT唤醒后去读GPIOB判断是哪个按键或报警。
这里有个小经验:MCP23017的中断标志是电平变化触发的,如果输入引脚是按键,按下和释放都会触发一次中断,需要在中断服务程序里加20ms左右的软件消抖,不然按键一次会被当成两次。报警信号如果本身有干扰,同样建议加软件滤波。
5.5 功能调试与心得
板子回来后调试,按顺序做了几轮验证。先写个简单的I2C扫描,确认0x20设备在线;再写8个LED全亮测试,发现低电平点亮效果正常,也没有亮度不均;然后逐个读按键,发现按键触发中断正常;最后测睡眠电流,主控睡眠后在按键上唤醒,总功耗符合预期。
这个案例整体顺利,但回想起来有一个差点翻车的细节:MCP23017的中断输出引脚,手册默认推挽而不是开漏。如果我当时把INT直接并联到其他开漏中断源上,就会出现一片输出高、另一片输出低互相拉死的情况。还好这项目只有一片扩展芯片,INT独占一个主控引脚,没踩到那个坑。
6. 上电I2C不通?从扫描到波形的排查流程
不管选型多仔细,硬件回来总有不工作的时候。I2C这块排查起来,只要思路清晰,其实比很多疑难杂症都好查。
6.1 第一步:地址扫描
拿到板子先不写应用代码,先确认设备有没有上线。Linux主控上直接跑i2c-tools,命令很简单:
i2cdetect -y 1如果你的扩展芯片地址是0x20,扫描结果应该出现这个地址。如果扫描不到,先确认总线上是否有上拉电阻,芯片供电是否正常,SDA和SCL是不是接反了,地址引脚A0/A1/A2的设置和预想是否一致。
如果是STM32这类裸机环境,可以先跑一个最简单的I2C扫描demo,网上现成代码很多,本质就是把总线上的每个地址都发一个起始条件加地址字节,看有没有ACK。
6.2 第二步:看波形和状态
扫描不到设备,或者通信时好时坏,就该上示波器或逻辑分析仪了。观察这几个点:
- 总线空闲时SDA和SCL是否都是高电平,这是I2C通信的前提;
- 起始条件是否标准,SCL高电平期间SDA由高跳低;
- 地址字节发送后,第9个时钟周期是否看到从设备把SDA拉低,那是ACK;
- 数据线上有没有毛刺,上升沿是否太缓,太缓大概率是上拉电阻偏大或总线电容偏大。
如果主控一直在发起始位但没有任何ACK,最常见的原因是地址写错。记得先确认你的驱动库用的是7位地址还是8位地址。
6.3 常见问题速查表
| 现象 | 可能原因 | 解决方法 |
|---|---|---|
| 扫描不到设备 | 地址写错、芯片没供电、SDA/SCL接反、上拉缺失 | 先确认供电和上拉,再核对地址 |
| 有起始位但无ACK | 7位/8位地址混用、芯片复位期间访问、地址引脚配置错 | 确认地址格式,读芯片手册 |
| 通信时好时坏 | 上拉过小/过大、线太长、电源纹波大 | 降速到100kHz,调上拉到2.2k~4.7k |
| LED接上不亮 | 准双向IO高电平输出能力太弱 | 改低电平点亮,或换推挽输出芯片 |
| 按键中断乱触发 | 输入悬空、未配置内部上拉、无消抖 | 使能内部上拉,软件加20ms消抖 |
| INT引脚一直低 | 中断标志没清除、输入信号抖动 | 读GPIO或INTCAP清标志 |
| 上电瞬间LED闪一下 | 复位期间IO默认电平/高阻 | 加外部下拉,或LED电源加使能控制 |
6.4 强制手段:软件I2C隔离问题
如果硬件I2C一直通不了,我还有一个屡试不爽的招数:用两个普通GPIO模拟软件I2C。主控引脚多的情况下,临时把两个GPIO分别当SDA和SCL,按I2C时序手动翻转,频率放到很低,比如10kHz,只要能通信,基本就能确认是硬件I2C外设配置问题还是芯片本身问题。
软件I2C调试时还可以顺便验证接线和地址。比如先手动发出一个地址字节,看ACK位有没有,比示波器还直观。很多传感器和扩展芯片的例程都喜欢用软件I2C,比如一些BH1750光照度传感器的驱动,其实就是GPIO模拟时序,原理一样。软件I2C速度慢,但排查问题真的是神器。
最后说点个人的习惯:我现在做任何带I2C总线的板子,SDA和SCL上都默认留测试点,而且上拉电阻放成0603封装,方便调试时改阻值。IO扩展芯片的地址引脚全部用0欧电阻做跳线,出厂默认接地,需要改地址时刮一刀换颗电阻就行。这些细节看起来不起眼,真出了问题能帮你省一整天时间。