尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

I2C IO扩展芯片选型实战:从GPIO不够到PCF8574与MCP23017的坑

I2C IO扩展芯片选型实战:从GPIO不够到PCF8574与MCP23017的坑 做硬件的兄弟应该都有这个经历板子画到一半功能加得差不多了主控的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扩展就是非常合适的选择。一根总线最多能挂好几片扩展芯片轻松扩展出几十路IOPCB走线也简单成本却很低。2. 为什么I2C是IO扩展的主流常见方案横向对比市面上IO扩展方案不少除了I2C还有SPI、移位寄存器、甚至用UART转并口之类的“野路子”。我自己把这些方案对比过一遍下面这张表基本能说明问题。方案占用主控引脚可扩展IO数双向支持主要优点主要缺点典型芯片I2C扩展2SDA/SCL可级联几十路支持引脚少、可级联、带中断速度慢、软件略绕PCF8574A、MCP23017、TCA9555SPI扩展3~4SCLK/MOSI/MISO/CS可级联部分支持速度快、时序简单占用CS、双向方案选择少MCP23S17移位寄存器2~3DATA/CLK/LATCH输出不限输出方便、输入需专用片便宜、抗干扰好输入扩展麻烦、无中断74HC595、74HC165直换大封装MCU0视芯片视芯片免额外芯片、性能强改板改代码、成本可能上升各种MCU2.1 I2C方案的核心优势I2C扩展最大优势是“省引脚”。一条总线上挂多个设备每个设备只占一个地址地址用A0/A1/A2引脚硬件配置。比如MCP23017有一组A0/A1/A28个地址可配每片是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个IO16位芯片一片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/A28个地址可选。选型时要注意芯片后缀PCF8574和PCF8574A地址范围完全不同混用可以扩展到16个不同地址但千万别搞混。3.3 驱动电流不是所有芯片都能推LED输出驱动电流是选型时最容易误判的地方。很多新手看到芯片手册写“IO可以输出25mA”兴冲冲直接接LED结果高电平点亮时灯不亮低电平点亮时又怕烧芯片。这里的门道在于输出高电平和灌入低电平的能力往往不一样。PCF8574这类准双向芯片灌电流能力还可以外部负载从IO口吸电流到GND时每脚能到20mA左右但输出高电平时几乎没有电流输出能力。所以用这类芯片驱动LED正确接法是让LED正极接电源、负极经过限流电阻接IOIO输出低电平时LED点亮。MCP23017的推挽输出特性好很多高低电平驱动能力都接近但也不能拿它直接驱动继电器、电磁阀那需要额外的功率器件。另外还要注意整颗芯片的总电流限制。虽然每个引脚可能允许20mA但所有引脚同时输出大电流时芯片总功耗会超限。工程上我习惯把每个LED的电流控制在2到10mA以内多个灯同时亮的场景再适当留余量。3.4 中断引脚与上电复位这两个细节很容易被忽视带中断的IO扩展芯片在主控需要低功耗休眠时非常有用。比如按键按下时扩展芯片检测到引脚电平变化主动把INT引脚拉低唤醒主控。选型时要注意INT引脚是开漏输出还是推挽输出多个扩展芯片的INT能否直接线与共用一个主控中断引脚。MCP23017的INT默认是推挽模式如果打算把多片INT接在一起就需要把IOCON寄存器里的ODR位设1改成开漏模式否则一片输出高一片输出低直接打架。上电复位行为同样重要。有些芯片复位后IO默认是输入高阻有些是内部上拉有些是输出高。如果IO接的是LED复位期间引脚默认输出低那LED就会在上电瞬间闪一下如果接的是继电器那就更麻烦了。选型时最好查清楚复位状态的默认电平或者在外部电路上做限制。3.5 常用芯片横向对比芯片位宽端口结构基地址范围中断工作电压特点PCF8574A8位准双向0x38~0x3F有2.5~5.5V经典、便宜、资料多高电平驱动弱MCP2301716位可配推挽/开漏0x20~0x27有可配置1.8~5.5V寄存器丰富软件能力强适合复杂需求TCA955516位推挽0x20~0x27部分后缀在另一段有1.65~5.5V低压低功耗封装多AW952316位开漏/推挽可配常见基址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Ω最小值由低电平灌电流决定假设VDD3.3VVOL0.4VIOL3mA则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扩展芯片是5VI2C上拉又接到5V那SDA/SCL的高电平就会到5V主控引脚如果不支持5V容忍轻则通信异常重则烧引脚。解决思路有三种。第一种最省事选一颗工作电压覆盖主控电压的扩展芯片比如主控3.3V就选工作在3.3V的PCF8574或MCP23017I2C上拉也统一接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 中断接法开漏、上拉、共INTIO扩展芯片的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 MCUI2C总线上已经挂了一颗温度传感器地址0x48低功耗要求比较严格主控大部分时间睡眠不能用轮询。合计需要14路IO其中6路输入5个按键1个报警都需要中断8路输出。5.2 芯片选型决策过程先看PCF8574A8位一片不够得两片。两片都有INT脚可以接两个外部中断软件上也还行。但它的高电平驱动弱8路LED得全部用低电平点亮这个没问题可是两片芯片占用两个I2C地址加上温度传感器总线上三个设备地址还好。成本很低这是个备选。再看MCP2301716位一片搞定。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.3VINT引脚接到主控一个支持外部中断的引脚并配置为上拉输入。因为只用一片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. 中断触发方式设为电平变化触发INTCONB0且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接反、上拉缺失先确认供电和上拉再核对地址有起始位但无ACK7位/8位地址混用、芯片复位期间访问、地址引脚配置错确认地址格式读芯片手册通信时好时坏上拉过小/过大、线太长、电源纹波大降速到100kHz调上拉到2.2k~4.7kLED接上不亮准双向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欧电阻做跳线出厂默认接地需要改地址时刮一刀换颗电阻就行。这些细节看起来不起眼真出了问题能帮你省一整天时间。
返回列表