第一次接触PCF8575是在去年做一个智能家居控制板项目时,当时STC12C5A60S2的GPIO口全被占满了,但还需要控制16个LED指示灯。这个场景下,I/O扩展芯片就成了救命稻草。PCF8575作为德州仪器的经典产品,用起来确实省心。
这个芯片有三大核心优势让我最终选择了它:首先是电压兼容性好,2.5V-5.5V的工作范围完美匹配STC12的3.3V/5V系统;其次是硬件地址引脚支持,通过A0-A2可以设置8种不同地址,这意味着单条I2C总线上最多能挂8个PCF8575;最重要的是16位准双向I/O的设计,既能当输入也能当输出,灵活性很强。
实际选型时要注意几个关键参数:
淘宝上常见的模块大概10-15元,建议选择带排针焊接位的版本。有个坑要注意:有些廉价模块省略了上拉电阻,必须自己外接。我实测发现当输出模式时,10kΩ的上拉电阻效果最稳定,驱动LED亮度足够也不会过热。
硬件设计中最容易出错的就是地址配置。PCF8575的A0-A2引脚通过接地或接VCC来设置地址,这里有个反直觉的设计:地址对应的是引脚电平的反码。比如A0-A2全部接地时,器件地址是0x40(写)/0x41(读),而不是初学者常误以为的0x00。
我的布线经验是:
有个实际案例:曾经调试时发现某个PCF8575时好时坏,最后发现是A1引脚虚焊。建议用万用表测量每个地址引脚对地/对VCC的阻抗,确保接触可靠。
虽然PCF8575的功耗很低,但电源设计也不能马虎。推荐方案:
特别提醒:当用PCF8575驱动继电器时,建议用光耦隔离。我有次没加隔离,继电器动作导致整个I2C总线挂死,排查了半天才发现是反电动势惹的祸。
STC12的硬件I2C不太好用,模拟实现反而更稳定。下面是我优化过的驱动代码,关键点在于时序控制:
c复制// 精确的5μs延时函数
void I2C_Delay() {
_nop_(); _nop_(); _nop_();
}
// 启动信号时序
void I2C_Start() {
SDA = 1; I2C_Delay();
SCL = 1; I2C_Delay();
SDA = 0; I2C_Delay();
SCL = 0; I2C_Delay();
}
// 字节发送函数(带超时检测)
bit I2C_WriteByte(uchar dat) {
uchar i;
for(i=0; i<8; i++) {
SDA = (dat & 0x80) ? 1 : 0;
dat <<= 1;
SCL = 1; I2C_Delay();
SCL = 0; I2C_Delay();
}
SDA = 1; // 释放总线
SCL = 1;
I2C_Delay();
if(SDA) { // NACK
SCL = 0;
return 0;
}
SCL = 0;
return 1;
}
调试时遇到过最头疼的问题是ACK信号异常,后来发现是延时不够。不同主频下需要调整_nop_()数量,建议用逻辑分析仪抓取波形,确保SCL高电平期间SDA保持稳定。
结合项目经验,分享一个稳定版的读写函数:
c复制#define PCF8575_ADDR 0x40 // A0-A2接地时的地址
void PCF8575_Write(uint16_t data) {
I2C_Start();
I2C_WriteByte(PCF8575_ADDR);
I2C_WriteByte(data & 0xFF); // 低字节
I2C_WriteByte(data >> 8); // 高字节
I2C_Stop();
}
uint16_t PCF8575_Read() {
uint16_t val = 0;
I2C_Start();
I2C_WriteByte(PCF8575_ADDR | 0x01);
val = I2C_ReadByte();
I2C_Ack();
val |= (I2C_ReadByte() << 8);
I2C_NAck();
I2C_Stop();
return val;
}
特别注意:读取时需要先发送设备地址(读模式),连续读取两个字节后要发送NACK终止传输。曾经因为漏发NACK导致后续通信全部失败。
用PCF8575驱动8x8 LED矩阵是个经典应用。硬件连接要注意:
软件实现关键点:
c复制void LED_Refresh() {
static uchar row = 0;
PCF8575_Write(~(1 << row) | (pattern[row] << 8));
row = (row + 1) % 8;
}
这个方案比直接用MCU驱动省了8个IO口,而且亮度更均匀。实测最大支持16x16的点阵(需要两片PCF8575级联)。
当用于按键扫描时,推荐使用中断方式。将PCF8575的INT引脚接到MCU外部中断,有任何按键变化时都会触发中断:
c复制// 初始化设置
PCF8575_Write(0xFFFF); // 所有端口置高
EX0 = 1; // 使能INT0中断
// 中断服务程序
void Key_ISR() interrupt 0 {
uint16_t key_val = ~PCF8575_Read();
// 按键处理逻辑...
}
这种方案比轮询方式更省电,响应速度也更快。注意INT引脚需要接10kΩ上拉电阻。
根据多年调试经验,整理了几个典型故障现象及解决方法:
通信无响应
输出电平异常
随机误动作
最近帮朋友调试一个案例:设备在电机启动时PCF8575会误触发,最后发现是电源地线设计不合理。在MCU和PCF8575的GND之间加个0Ω电阻就解决了,这类问题往往需要结合具体场景分析。