ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

用PCF8574扩展Arduino数字I/O:24路接口板设计与实现

用PCF8574扩展Arduino数字I/O:24路接口板设计与实现 做这个项目的起因其实特别朴素我手头有一块Arduino Uno要做一台小型分拣控制台12路继电器控制气动推杆再加12个光电传感器检测物料到位算下来正好需要24路数字I/O。Uno板子本身只有D0到D13、A0到A5这些可用的数字引脚满打满算20个其中D0和D1还要留给串口。算来算去就是差那么几路总不能为这多出来的4个引脚就换一块Mega。于是就有了这个方案用I²C总线上挂3片PCF8574把数字I/O扩展到24通道Arduino和兼容板ESP32、STM32、ATmega系列通吃。这篇文章就把整个设计过程、电路细节、代码封装和调试中踩过的坑完整记录下来给同样面临引脚紧张、又被“更多IO”需求卡住的朋友做个参考。项目需求解析为什么偏偏是24路而不是16路或者32路很多第一次做扩展的开发者会问既然要扩怎么不定成32路或者更多这里有个很实在的工程权衡。24路这个数字直接对应3片PCF8574。每片PCF8574提供8位数字I/O3片正好24位。PCF8574的I²C地址由A0、A1、A2三个硬件引脚决定每个引脚接高电平或低电平组合出来8个可选地址。也就是说一条I²C总线上最多能挂8片PCF8574对应64路I/O。从地址资源和代码管理来看3片是一个“够用又不浪费”的数量。从项目实际需求出发24路能覆盖大量场景。一个典型的工业原型验证台12路继电器输出控制电磁阀或指示灯12路开关量输入读取限位开关或按钮这是非常常规的配置。如果做LED点阵或跑马灯实验24路也能直接驱动24个独立LED。再比如一些简单的自动化设备验证8路输入、8路输出、8路备用通道同样落在24路范围之内。还有人会问干脆用MCP23017一片就是16路两片就是32路不是更划算吗这个说法有道理后面我会专门做对比。但在很多场景下PCF8574有几个不可替代的优势一是价格便宜零售价经常不到两块钱一片二是几乎所有Arduino库都原生支持写起来毫无门槛三是它自带强大的开漏输出结构同一片芯片的引脚既能做输入也能做输出切换灵活。对于一个“接口板”性质的项目24路采用3片PCF8574的组合在成本、易用性、供货稳定性上都是很均衡的选择。还有一个容易忽略的好处3片PCF8574意味着3个独立8位端口代码里可以按“组”管理。组0、组1、组2分别对应3个不同的功能区域比如组0管输出、组1管输入、组2做备用或扩展。这种分组逻辑在编写控制代码时非常直观不用维护一个庞大的位数组出问题也好定位。扩展方案选型I²C芯片与SPI芯片的取舍数字I/O扩展不是只有PCF8574这一条路市面上成熟的方案大概分四类。这里我用一张表把它们的特性和适用场景列清楚再详细说为什么这个项目选择I²C架构。芯片/方案位宽接口地址数量输出类型驱动电流中断输出典型应用PCF85748位I²C8A0/A1/A2开漏准双向约20mA灌电流有INT通用扩展、按键、LED、继电器信号控制PCF857516位I²C8A0/A1/A2开漏准双向约20mA有两片拼32路扩展MCP2301716位I²C8A0/A1/A2推挽/开漏可配约25mA有两路需要更强驱动、中断扩展、输出状态明确MCP23S1716位SPI最多8硬件地址推挽/开漏可配约25mA有高速刷新、对速率有要求的场景74HC5958位输出SPI-like级联无限制推挽约6mA无纯LED流水灯、低成本输出扩展从应用场景来看如果只需要“扩展输出控制LED”用74HC595串转并是最省钱的方案8片级联就是64路输出价格可能比两片PCF8574还便宜。但74HC595是纯输出芯片想读回按键状态就麻烦了要么加并转串的74HC165要么另想其他办法。对于一个同时需要输入和输出的24路接口板这种方案复杂度反而上去了。MCP23017是PCF8574的强力对手。它的输出是推挽结构这意味着输出高电平时可以主动提供电流而不是靠外部上拉电阻。点亮LED、驱动小负载时MCP23017的表现更稳定。它还提供了真正的方向寄存器可以独立配置每一位是输入还是输出编程思路更接近单片机原生的GPIO。但价格也明显高出一截而且MCP23017的使用需要稍微仔细一点IOCON寄存器配置不对会出各种奇怪问题。再往高速方向走如果应用场景需要快速翻转大量I/O比如LED点阵刷新那SPI接口的MCP23S17就是更好的选择。SPI的时钟可以跑到10MHz以上比标准I²C的100kHz快两个数量级。不过本项目面对的典型场景是继电器控制、按钮读取、光电传感器信号采集这些信号的刷新频率基本在几十毫秒级别I²C完全能应付没必要为此牺牲软件复杂度。我在这个项目里最终选择3片PCF8574核心原因就是它的“准双向”结构非常灵活。PCF8574的每一个引脚都可以独立作为输入或输出方向控制靠输出寄存器的高低电平实现写1则引脚呈现高电平外部不强拉时等效输入写0则引脚对外输出低电平。这种设计虽然不符合传统pinMode的习惯但在实际使用中非常方便。如果需要把一个输出引脚临时改成输入读取按钮写一个1就行完全不用重走初始化流程。提示PCF8574的输出是开漏结构引脚写1时靠内部上拉电流源提供高电平。如果驱动的负载比较大单纯靠芯片内部上拉可能不够要额外加外部上拉电阻或者干脆用低电平驱动方式比如继电器模块的IN端多数是低电平有效。如果让我给刚入门的朋友一个建议你的项目如果以控制继电器、读取开关为主预算敏感直接照抄本文的3片PCF8574方案就行。如果后续可能要加中断驱动的传感器、需要在睡眠状态下保持输出或者希望代码更接近原生GPIO逻辑那就选MCP23017。两种方案我都实测过闭着眼都不会走错路只是每款芯片有自己的脾气。硬件设计细节从电路原理图到PCB布局确定方案之后硬件设计就是整个项目的重头戏。这一块的细节直接决定接口板稳不稳定、好不好用。首先是I²C总线的连接。把3片PCF8574并联在同一条I²C总线上SDA、SCL分别连接Arduino的A4/A5Uno或对应引脚其他板卡。每片PCF8574的A0、A1、A2引脚通过跳线或焊盘配置为不同电平得到3个不同地址。我们来看一下地址配置方式的细节。把A0、A1、A2全部接地时7位地址是0100000左移一位加上读写位后就是0x20写、0x21读。分别将三片芯片配置为0x20、0x21、0x22这是最常用的配置。芯片A2A1A0I²C地址7位写地址读地址U10000x200x400x41U20010x210x420x43U30100x220x440x45这个表格里的写地址和读地址实际上用Arduino的Wire库时不需要手动计算程序里直接写0x20、0x21、0x22Wire库会自动处理读写位。但理解这个地址结构有助于排查问题时识别总线上挂载的设备。电路原理图里有几个关键点必须注意。第一个关键点是I²C总线的上拉电阻。标准I²C规范要求在SDA和SCL上各接一个上拉电阻到VCC阻值通常在4.7kΩ到10kΩ之间。PCF8574的数据手册也明确要求外部上拉电阻。如果总线上只有这一块接口板用两个4.7kΩ电阻接到5V或3.3V就非常稳。如果总线上还挂了传感器、显示屏等设备需要计算并联等效电阻避免上拉太强导致信号边沿过冲。第二个关键点是电源去耦。每片PCF8574的VCC和GND之间要就近放置一个0.1μF陶瓷电容这是所有数字芯片的标准配置。如果全部3片芯片共用一颗电容电源线上的纹波会导致信号抖动严重时可能出现I²C通信随机失败。我在原型验证阶段犯过这个错误三片芯片只放了一颗10μF电解电容结果运行一段时间后偶尔出现读不到某个地址的情况后来在每片芯片旁边补上0.1μF电容后问题消失。第三个关键点是输出驱动的电流折算。PCF8574的每个引脚最大灌电流sink current大约20mA但这个是绝对最大值长期稳定运行建议每脚不超过15mA。直接驱动LED时按5V供电计算串联一个330Ω到470Ω的电阻就能把电流限制在10mA左右。如果是驱动继电器线圈绝对不能直接用引脚驱动必须经过三极管或光耦。线圈的反电动势会瞬间击穿芯片内部结构。我在实际项目中接线方式是PCF8574引脚 → 1kΩ电阻 → 8050三极管基极继电器线圈接在三极管集电极和电源之间线圈两端并联一个1N4007二极管反向吸收。第四个关键点是输入端的上拉状态。PCF8574上电时所有引脚默认输出高电平寄存器全1这个特性对输入信号很友好。把按钮一端接引脚、另一端接地读取时按下就是0、松开就是1不需要额外上拉电阻。但如果按钮一端接VCC按下时引脚为高、松开时靠内部微弱上拉维持高读取逻辑会颠倒而且引脚悬空时电平不确定。我建议按钮全部采用“引脚接地”的接法省事又稳定。接下来是PCB布局。如果只是做实验在面包板上飞线连接就能跑起来。但要是做正式项目或者打算长期使用画一块小板子会更可靠。我在设计排针引脚排列时采用这样的顺序一侧排母放电源和I²C总线GND、VCC、SDA、SCL另一侧排母引出24路I/O每组8路用一个2×4或1×8排针。这种布局让接口板可以像扩展盾板一样叠加在Arduino上方也可以用杜邦线引出。PCB布线时有几个经验电源线尽量加宽至少1mm减少压降。SDA和SCL尽量平行短走线不要跨过继电器这类大电流回路。每片PCF8574的去耦电容尽量靠近芯片的VCC引脚中间不要隔过孔。地址配置焊盘做成跳线形式方便以后改地址。用0欧电阻或焊桥选择高低电平都可以。最后说兼容性问题。“Arduino Compatibles”这个描述意味着接口板不光能在Uno上用也得能在ESP32、STM32、树莓派Pico等兼容开发板上用。这里要特别注意电平匹配PCF8574的数据手册给出的工作电压范围是2.5V到6V可以直接用5V供电也可以用3.3V供电。但Arduino Uno的I²C引脚是5V逻辑而ESP32的I²C引脚是3.3V逻辑混用时需要保证上拉电阻接的电源和主控逻辑电平一致。最简单的做法是工作电压统一用3.3V上拉电阻也接到3.3V这样不管主控是3.3V还是5V都能兼容。如果坚持用5V供电接3.3V主控时要在SDA和SCL上串330Ω电阻做电平保护。驱动代码架构从Wire库到24通道统一接口硬件搭好之后驱动代码是让24路I/O“活起来”的关键。这一部分我讲两层底层怎么操作PCF8574寄存器上层怎么把24路封装成像原生GPIO一样易用的接口。先看最底层的寄存器操作。PCF8574的I²C操作其实非常简单。往某个地址写一个字节这个字节的每一位就对应P0到P7的电平。读一个字节返回的每一位就是P0到P7当前的电平状态。写1表示输出高或输入模式写0表示输出低。举个例子向地址0x20的芯片写入0xF0意思是P7、P6、P5、P4输出高电平P3、P2、P1、P0输出低电平。如果这个时候把P0引脚接一个按钮到地并按下读回0x20地址时读到的最低一位就是0其他位保持写入状态。这套机制看起来简单实际写代码时却有一个常见陷阱读操作返回的“当前状态”包含了你之前写入的输出值。比如你往某一位写0作为输出再接一个外部信号去读这个引脚读回的总是0因为芯片内部输出驱动把电平拉住了。要读取外部信号必须先把对应的位写成1释放为高阻输入状态再来读。这就是PCF8574“准双向”结构的本质。理解了这一点写代码就顺理成章了。下面是我实际项目中用到的一个基础驱动库封装了三个关键函数writePin、readPin、pinMode。你不需要把底层寄存器细节暴露给上层调用者而是让用的人像操作Arduino原生引脚一样操作P0到P23。#include Wire.h #define BASE_ADDR 0x20 #define CHIP_COUNT 3 static uint8_t portCache[CHIP_COUNT] {0xFF, 0xFF, 0xFF}; void ioInit() { Wire.begin(); for (int i 0; i CHIP_COUNT; i) { ioWritePort(i, 0xFF); } } void ioWritePort(uint8_t chipIndex, uint8_t value) { portCache[chipIndex] value; Wire.beginTransmission(BASE_ADDR chipIndex); Wire.write(value); Wire.endTransmission(); } uint8_t ioReadPort(uint8_t chipIndex) { Wire.requestFrom((uint8_t)(BASE_ADDR chipIndex), (uint8_t)1); if (Wire.available()) { return Wire.read(); } return 0xFF; } void ioPinMode(uint8_t pin, uint8_t mode) { uint8_t chipIndex pin / 8; uint8_t bitIndex pin % 8; if (mode INPUT) { portCache[chipIndex] | (1 bitIndex); } else { // 这里仅示意PCF8574没有硬件方向寄存器输出方向其实靠写值实现 portCache[chipIndex] ~(1 bitIndex); } ioWritePort(chipIndex, portCache[chipIndex]); } void ioDigitalWrite(uint8_t pin, uint8_t value) { uint8_t chipIndex pin / 8; uint8_t bitIndex pin % 8; if (value HIGH) { portCache[chipIndex] | (1 bitIndex); } else { portCache[chipIndex] ~(1 bitIndex); } ioWritePort(chipIndex, portCache[chipIndex]); } uint8_t ioDigitalRead(uint8_t pin) { uint8_t chipIndex pin / 8; uint8_t bitIndex pin % 8; uint8_t portValue ioReadPort(chipIndex); return (portValue (1 bitIndex)) ? HIGH : LOW; }这段代码的关键在于portCache数组。由于PCF8574的读回值包含了输出状态如果每次操作都直接读芯片再去改某一位很可能会读到异常值。用portCache维护一份软件镜像写操作直接改镜像再整体写出去既能避免读改写冲突也能处理“输入读取”的pinMode转换问题。要注意的是ioPinMode函数中我把INPUT模式对应的操作设计成将该位置1OUTPUT模式置0。实际上PCF8574没有硬件方向寄存器这个函数只是帮助我们统一访问逻辑。如果某位配置为INPUT程序应该保证它始终为1配置为OUTPUT后读写由ioDigitalWrite控制。这个设计对上层调用者来说是友好的因为它模拟了Arduino原生的pinMode写法。再来看一个实际问题怎么把24路通道映射到具体的物理设备上。我的习惯是在代码开头定义一组枚举或宏把通道号、芯片编号、位编号之间的关系固定下来。enum { CH_RELAY_0 0, // 对应 U1 P0 CH_RELAY_1 1, // 对应 U1 P1 // ... CH_RELAY_11 11, // 对应 U2 P3 CH_SENSOR_0 12, // 对应 U2 P4 // ... CH_SENSOR_11 23 // 对应 U3 P7 };这个映射方式的好处是硬件接线改动时只需要改枚举定义不需要动业务逻辑代码。我在项目里把24路通道分成两个逻辑区段0到11用于输出控制继电器12到23用于读取传感器输入。这套接口统一之后上层业务代码可以这样写void setup() { ioInit(); for (int i 0; i 12; i) { ioPinMode(i, OUTPUT); ioDigitalWrite(i, LOW); } for (int i 12; i 24; i) { ioPinMode(i, INPUT); } } void loop() { for (int i 0; i 12; i) { if (ioDigitalRead(12 i) HIGH) { ioDigitalWrite(i, HIGH); } else { ioDigitalWrite(i, LOW); } } }这个简单的逻辑模拟了“传感器到位后对应继电器吸合”的分拣场景24路IO在这里被完全压榨出来使用。实测下来单次写操作耗时大约在微秒级别对继电器这种毫秒级响应的设备完全够用。哪怕24路全部轮询一遍也就是几十微秒的时间完全不影响主循环其他任务。输入读取与按键去抖别让机械抖动骗了你的IO24路数字I/O接口板不光用来输出很多时候要读取按钮、限位开关、光电传感器的电平信号。在输入读取这件事上有一个绕不开的问题机械抖动。机械开关在闭合和断开的瞬间触点会因为弹性碰撞产生持续几毫秒到几十毫秒的高频抖动反映到引脚电平上就是连续的高低跳变。如果直接把这串跳变信号当成有效输入程序会执行多次误动作。一个继电器被误触发一次可能只是小事如果控制的是伺服电机或者加热设备误动作的代价就大了。去抖的做法很多硬件上用RC滤波电路、施密特触发器软件上最常用的就是延时确认法。核心思路检测到电平变化后等待一段时间再次读取如果电平与第一次一致才认为是有效信号。我在这个项目中使用的软件去抖逻辑如下uint8_t readDebouncedInput(uint8_t pin, uint8_t waitMs) { uint8_t state ioDigitalRead(pin); delay(waitMs); uint8_t state2 ioDigitalRead(pin); if (state state2) { return state; } // 两次不一致返回上一次的稳定值或者继续重试 return readDebouncedInput(pin, waitMs); }这个函数看起来简单但在实际项目中要小心延时时间不能太长否则会阻塞主循环。对于继电器控制等慢速场景10到20毫秒的延时完全可接受。如果在传感器读取任务中调用多次整体响应速度会下降。更好的方案是把去抖放到状态机里每个输入引脚维护一个历史状态和一个计时器主循环每次轮询时更新状态。这种方式不阻塞适合同时读取多路输入的复杂场景。我做过一个24路输入加24路输出的原型验证板如果直接用轮询加delay方式主循环里光去抖就去掉几十毫秒。后来改成非阻塞状态机去抖用millis()做超时判断整个24路输入扫描一次只耗时不到100微秒状态稳定性和响应速度都提升了一个量级。还要提一点PCF8574读输入时必须先把对应引脚置1。如果某引脚被程序配置为输入代码里应该保持portCache中的该位始终为1。我在库设计里已经把ioPinMode(INPUT)处理成将位写1但如果业务代码不小心对这个引脚调用ioDigitalWrite(0)输入读取就会失效。遇到这种“明明接了按钮却一直读到0”的问题优先怀疑是不是哪位代码把输入引脚拉低了。接线与供电最容易出问题的地方往往是电源很多新手的24路扩展板做出来之后I²C通信不稳定排查半天发现是电源接线问题。PCF8574本身功耗不高几片芯片加在一起电流也就几毫安但挂在扩展板上的外部设备不这么想。继电器线圈、LED灯条、舵机这些负载的电流全部从电源线走如果和数字芯片共用一根细长的杜邦线电阻效应会导致芯片供电电压跌落进而引发I²C误码。我在调试一个8路继电器控制板时发现继电器一吸合其他通道的传感器读数会瞬间跳变示波器抓下来是VCC上有将近0.7V的毛刺。解决方式很简单主控和接口板用一路电源继电器等功率负载单独用一路电源两路电源的GND要共地。信号线上的电流很小从5V到PCF8574引脚直接走细线没问题。功率负载电源线和接口板电源线物理分开这是我画PCB和接线时一直坚持的规则。另外24路接口板上的每个GND引脚不能用一根线串联所有负载最好采用星型接地或至少在接口板上有完整的GND平面。PCB设计至少两层板专门留一层铺地能大幅减少信号干扰。提示如果Arduino通过USB供电USB口的电流输出能力通常只有500mA左右。24路继电器加上去一旦同时吸合可能直接触发USB保护断电。这种情况下必须外接电源或者至少在继电器电源输入端加大电容缓冲。调试与排查从I²C扫描到总线挂死的完整经验这一节是干货中的干货。24路IO接口板做完之后调试阶段遇到的问题是整套项目里最值得记录的。先说第一道工序I²C地址扫描。每次焊接完新板子我第一步都是烧一个地址扫描程序确认总线上的3个地址都能被正确识别。这里推荐一个非常经典的工具函数#include Wire.h void scanI2C() { byte error, address; int nDevices 0; Serial.println(Scanning I2C bus...); for (address 1; address 127; address) { Wire.beginTransmission(address); error Wire.endTransmission(); if (error 0) { Serial.print(I2C device found at address 0x); if (address 16) Serial.print(0); Serial.print(address, HEX); Serial.println( !); nDevices; } else if (error 4) { Serial.print(Unknown error at address 0x); if (address 16) Serial.print(0); Serial.println(address, HEX); } } if (nDevices 0) { Serial.println(No I2C devices found); } }如果扫描出来不是3个地址先别急着怀疑芯片逐项排查SDA/SCL是否接反两根线交换一下再试。上拉电阻有没有焊VCC和GND有没有接对芯片的A0、A1、A2引脚有没有虚焊导致地址漂移我遇到过A2引脚接触不良导致地址从0x20跳到0x28的怪事重新补焊后恢复。扫描通过之后下一步是逐片读写测试。往某一片写入0x55再读回来确认每一位都能正确翻转。这个步骤能揪出虚焊和冷焊点。如果某一位读回来一直是1或一直是0基本就是这个引脚对应的焊盘有问题。调试过程中另一个常见问题是I²C总线挂死。现象是程序卡在Wire.endTransmission()或Wire.requestFrom()那里主循环不再执行看门狗超时后复位然后再次卡死。这种问题多半是SDA被某个设备拉低总线进入锁死状态。造成总线挂死的常见原因有几个。第一个是地址错误芯片没有应答导致通信协议状态机乱掉。第二个是供电不稳芯片突然掉电输出口线拉低总线。第三个是信号干扰太强数据帧损坏后总线卡在某一状态。处理办法在每次通信前加一个总线恢复逻辑强制切换SDA和SCL引脚为输出发9个时钟脉冲让从机复位状态机。实际项目里这个恢复函数救了我好几次void recoverI2C() { pinMode(SCL, OUTPUT); pinMode(SDA, OUTPUT); for (int i 0; i 9; i) { digitalWrite(SCL, HIGH); delayMicroseconds(5); digitalWrite(SCL, LOW); delayMicroseconds(5); } digitalWrite(SDA, HIGH); digitalWrite(SCL, HIGH); delayMicroseconds(5); Wire.begin(); }还有一个非常隐蔽的坑PCF8574的INT引脚。这块芯片有中断输出功能当某一位输入引脚状态变化时INT引脚会拉低可以用来唤醒主控CPU避免轮询。但在不适用的场景下INT引脚悬空会导致什么问题不会INT引脚是开漏输出悬空不影响通信。但如果板上把INT引脚引出且接了上拉电阻又恰好和别的信号线短接就可能出现干扰。我的做法是不需要中断就干脆不接INT让引脚悬空或者接地。常见故障对照表我整理了一张故障对照表可以快速定位板子出问题时的排查方向。这张表是我在几轮调试过程中总结出来的不是芯片手册里的标准说明但非常实用。故障现象可能原因排查与解决扫描不到任何I²C设备电源没接对、SDA/SCL接反、上拉电阻缺失测VCC引脚电压交换SDA/SCL补上拉电阻到VCC扫描到地址错误A0/A1/A2配置引脚虚焊或跳线错误检查焊接用万用表量A0/A1/A2到GND或VCC的电平某一位输出一直是高该引脚外部被拉高、芯片引脚损坏、端口寄存器未更新检查外部电路有没有短路到VCC换一片芯片测试某一位读输入一直是1该位没有写成输入高阻态、按钮接线错误程序里先ioPinMode(pin, INPUT)确保该位为1按钮接法改为引脚到GND继电器吸合时其他通道误动作供电电压跌落、信号线与功率线靠太近功率负载单独供电并共地PCB铺地隔离增加去耦电容运行十几分钟后通信失败芯片过热、电源噪声、接触不良检查散热给电源加滤波电容重新插拔连接排线第一次上电所有输出都为高PCF8574上电复位后寄存器默认全1程序初始化时主动将所有输出位写0后级设备设计成高电平无效上面提到的“第一次上电所有输出都为高”是很多继电器应用翻车的重灾区。PCF8574上电瞬间所有引脚都是高电平如果继电器模块的IN端是高电平触发那么板子上电的一瞬间所有继电器都会吸合一下然后程序初始化后才能复位。这个“吸合一下”在生产设备里会造成严重安全隐患。解决办法有两个思路一是把继电器模块换成低电平触发型号这样上电默认状态是断开二是在继电器控制端和PCF8574引脚之间加一个非门或反相驱动电路把高电平变成断开状态。考虑到24路通道数量单纯换低电平触发模块是最省事的方案。24通道接口的进阶玩法从并行控制到中断唤醒基础读写跑通之后这个24通道接口板能做的远不止简单的开关控制。我后来在项目里扩展了两种玩法效果都很好这里一并分享。第一种玩法是用I²C中断唤醒主控实现真正的“事件驱动”。PCF8574的INT引脚在检测到输入引脚电平变化时拉低把这个信号接到Arduino的外部中断引脚比如D2。主控在睡眠模式下被INT唤醒然后扫描I²C总线确认是哪个引脚发生了变化。这种方式在电池供电的物联网设备里特别有用不用每秒轮询几十次设备大部分时间处在低功耗状态。实测下来待机电流从轮询模式的30多毫安降到1毫安以下整个设备的续航提升了近十倍。但要注意的是PCF8574的INT输出在I²C读取后才恢复高电平也就是说不读数据INT就一直是低。做事件驱动时唤醒后必须立刻读一遍对应芯片的端口值清除中断标志。否则主控会被反复唤醒无法进入睡眠。第二种玩法是端口分组后的多设备并行控制。24路IO正好可以分成3个8位组利用Wire库的连续写功能一次事务把一组8位数据同时写到某一片芯片。这样控制一组LED或一组继电器时输出延迟一致性比逐位写要好得多。我在控制8个同步电机启停的实验里用一次I²C事务同时切换8路输出切换时间差在微秒级别肉眼完全看不出先后。写在最后从需求分析到方案对比从电路设计到代码封装从调试排错到进阶应用24通道数字I/O接口板的整个落地过程就是这样。这套方案的核心思路其实很简单不要被开发板自带的引脚数量限制住I²C总线上挂扩展芯片用3根线SDA、SCL、GND换回24路数字I/O投入低、见效快、扩展灵活。如果你正在做一个引脚紧张的项目或者想把手头的Uno用出更大价值这套方案完全可以照着做一个。按我的经验硬件焊接加代码调试一个下午基本就能跑通等你能熟练地把24路通道按组使用后再回过头看那些被引脚数量憋住的项目会发现很多问题都有了新的解法。
返回列表