1. 为什么需要I/O口扩展很多刚开始玩STM32的朋友都会遇到一个尴尬的问题芯片的I/O口不够用了。比如做一个智能家居控制板要接8个LED灯、4个按键、2个数码管再加上蜂鸣器和温度传感器算下来至少需要20多个I/O口。但像STM32F103这类常用型号可用I/O口往往只有十几二十个。这时候就需要I/O口扩展芯片来救场了。PCF8574就是专门解决这个问题的神器。它通过I2C总线也叫IIC总线与STM32通信只需要两根线SCL和SDA就能扩展出8个可编程I/O口。更妙的是一个I2C总线上可以挂载多达8个PCF8574芯片理论上能扩展出64个额外I/O口。我在去年做智能灯光控制器时就用了3片PCF8574轻松实现了24路LED控制而且布线特别简洁。2. PCF8574芯片深度解析2.1 硬件特性揭秘PCF8574这颗芯片虽然只有16个引脚SO16或DIP16封装但功能相当强大。它的工作电压范围是2.5V-6V和大多数STM32的3.3V系统完美兼容。实测下来静态电流只有10μA左右特别适合电池供电的设备。最让我惊喜的是它的驱动能力——每个I/O口可以输出20mA电流能直接驱动LED不用加三极管。记得第一次用的时候我按老习惯加了驱动电路后来看手册才发现完全多余。不过要注意所有I/O口的总输出电流不要超过100mA否则会烧芯片。2.2 地址配置技巧PCF8574的I2C地址是0x20起步7位地址具体由A0-A2这三个引脚决定。比如A2A1A0000时地址是0x20写或0x21读A2A1A0111时地址是0x27写或0x27读实际项目中我建议用拨码开关来设置地址这样调试起来特别方便。有一次我同时用了4片PCF8574就是靠拨码开关快速切换地址测试的。3. 硬件连接实战3.1 电路设计要点连接STM32和PCF8574时有几点特别重要I2C总线要加上拉电阻一般用4.7kΩ就行。我曾经偷懒不加结果通信时好时坏。中断引脚(INT)要接STM32的外部中断口最好配置成下降沿触发。如果驱动感性负载比如继电器一定要在负载两端并联续流二极管。这是我在智能家居项目中的典型连接方案// STM32F103C8T6 连接示意图 // PB6 ---- SCL // PB7 ---- SDA // PB5 ---- INT (中断引脚) // VDD ---- 3.3V // GND ---- GND3.2 常见问题排查新手最容易遇到的三个坑地址错误I2C扫描不到设备时先用万用表量A0-A2电平上拉电阻SCL/SDA必须接上拉否则波形会畸变电源干扰数字地和模拟地之间建议加磁珠上周帮学弟调试时就遇到因为电源纹波太大导致通信失败的情况后来在VCC加了个100μF电容就解决了。4. 软件驱动开发4.1 HAL库驱动编写用STM32CubeMX配置I2C特别方便这里分享我的配置经验在CubeMX中启用I2C1模式选Standard Mode100kHzGPIO设置成Open-Drain模式记得开启I2C中断初始化代码长这样void MX_I2C1_Init(void) { hi2c1.Instance I2C1; hi2c1.Init.ClockSpeed 100000; hi2c1.Init.DutyCycle I2C_DUTYCYCLE_2; hi2c1.Init.OwnAddress1 0; hi2c1.Init.AddressingMode I2C_ADDRESSINGMODE_7BIT; hi2c1.Init.DualAddressMode I2C_DUALADDRESS_DISABLE; hi2c1.Init.OwnAddress2 0; hi2c1.Init.GeneralCallMode I2C_GENERALCALL_DISABLE; hi2c1.Init.NoStretchMode I2C_NOSTRETCH_DISABLE; if (HAL_I2C_Init(hi2c1) ! HAL_OK) { Error_Handler(); } }4.2 关键功能实现读写函数是使用PCF8574的核心这是我优化过的版本// 写入8位数据 void PCF8574_Write(uint8_t data) { HAL_I2C_Master_Transmit(hi2c1, PCF8574_ADDR, data, 1, 100); } // 读取8位数据 uint8_t PCF8574_Read(void) { uint8_t data; HAL_I2C_Master_Receive(hi2c1, PCF8574_ADDR|0x01, data, 1, 100); return data; } // 控制单个引脚 void PCF8574_SetPin(uint8_t pin, uint8_t state) { static uint8_t port_state 0xFF; if(state) port_state | (1pin); else port_state ~(1pin); PCF8574_Write(port_state); }中断处理有个小技巧读取操作会自动清除中断标志所以中断服务函数里一定要做一次读取void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if(GPIO_Pin GPIO_PIN_5) // INT引脚 { uint8_t input PCF8574_Read(); // 处理输入变化... } }5. 实战案例智能灯光控制器去年给朋友做的灯光控制系统就是用PCF8574扩展I/O口的典型应用。系统需要控制16路LED但STM32F103只剩5个I/O口可用。我的解决方案是使用2片PCF8574地址分别设成0x20和0x21每片控制8路LED利用中断功能实现按键检测关键代码如下// 初始化 void LightCtrl_Init(void) { MX_I2C1_Init(); PCF8574_Write(0xFF); // 所有LED初始化为灭 GPIO_Init_INT(); // 配置中断引脚 } // 控制指定LED void Set_LED(uint8_t chip, uint8_t led, uint8_t state) { static uint8_t led_state[2] {0xFF, 0xFF}; if(chip 1 || led 7) return; if(state) led_state[chip] | (1led); else led_state[chip] ~(1led); HAL_I2C_Master_Transmit(hi2c1, PCF8574_ADDRchip, led_state[chip], 1, 100); }这个项目让我深刻体会到PCF8574的两个优势一是布线简单16路LED只用了4根线SCL/SDA/INT/GND二是功耗低整个控制系统待机电流不到1mA。
STM32 I/O口扩展实战:基于PCF8574的I2C总线应用详解
1. 为什么需要I/O口扩展很多刚开始玩STM32的朋友都会遇到一个尴尬的问题芯片的I/O口不够用了。比如做一个智能家居控制板要接8个LED灯、4个按键、2个数码管再加上蜂鸣器和温度传感器算下来至少需要20多个I/O口。但像STM32F103这类常用型号可用I/O口往往只有十几二十个。这时候就需要I/O口扩展芯片来救场了。PCF8574就是专门解决这个问题的神器。它通过I2C总线也叫IIC总线与STM32通信只需要两根线SCL和SDA就能扩展出8个可编程I/O口。更妙的是一个I2C总线上可以挂载多达8个PCF8574芯片理论上能扩展出64个额外I/O口。我在去年做智能灯光控制器时就用了3片PCF8574轻松实现了24路LED控制而且布线特别简洁。2. PCF8574芯片深度解析2.1 硬件特性揭秘PCF8574这颗芯片虽然只有16个引脚SO16或DIP16封装但功能相当强大。它的工作电压范围是2.5V-6V和大多数STM32的3.3V系统完美兼容。实测下来静态电流只有10μA左右特别适合电池供电的设备。最让我惊喜的是它的驱动能力——每个I/O口可以输出20mA电流能直接驱动LED不用加三极管。记得第一次用的时候我按老习惯加了驱动电路后来看手册才发现完全多余。不过要注意所有I/O口的总输出电流不要超过100mA否则会烧芯片。2.2 地址配置技巧PCF8574的I2C地址是0x20起步7位地址具体由A0-A2这三个引脚决定。比如A2A1A0000时地址是0x20写或0x21读A2A1A0111时地址是0x27写或0x27读实际项目中我建议用拨码开关来设置地址这样调试起来特别方便。有一次我同时用了4片PCF8574就是靠拨码开关快速切换地址测试的。3. 硬件连接实战3.1 电路设计要点连接STM32和PCF8574时有几点特别重要I2C总线要加上拉电阻一般用4.7kΩ就行。我曾经偷懒不加结果通信时好时坏。中断引脚(INT)要接STM32的外部中断口最好配置成下降沿触发。如果驱动感性负载比如继电器一定要在负载两端并联续流二极管。这是我在智能家居项目中的典型连接方案// STM32F103C8T6 连接示意图 // PB6 ---- SCL // PB7 ---- SDA // PB5 ---- INT (中断引脚) // VDD ---- 3.3V // GND ---- GND3.2 常见问题排查新手最容易遇到的三个坑地址错误I2C扫描不到设备时先用万用表量A0-A2电平上拉电阻SCL/SDA必须接上拉否则波形会畸变电源干扰数字地和模拟地之间建议加磁珠上周帮学弟调试时就遇到因为电源纹波太大导致通信失败的情况后来在VCC加了个100μF电容就解决了。4. 软件驱动开发4.1 HAL库驱动编写用STM32CubeMX配置I2C特别方便这里分享我的配置经验在CubeMX中启用I2C1模式选Standard Mode100kHzGPIO设置成Open-Drain模式记得开启I2C中断初始化代码长这样void MX_I2C1_Init(void) { hi2c1.Instance I2C1; hi2c1.Init.ClockSpeed 100000; hi2c1.Init.DutyCycle I2C_DUTYCYCLE_2; hi2c1.Init.OwnAddress1 0; hi2c1.Init.AddressingMode I2C_ADDRESSINGMODE_7BIT; hi2c1.Init.DualAddressMode I2C_DUALADDRESS_DISABLE; hi2c1.Init.OwnAddress2 0; hi2c1.Init.GeneralCallMode I2C_GENERALCALL_DISABLE; hi2c1.Init.NoStretchMode I2C_NOSTRETCH_DISABLE; if (HAL_I2C_Init(hi2c1) ! HAL_OK) { Error_Handler(); } }4.2 关键功能实现读写函数是使用PCF8574的核心这是我优化过的版本// 写入8位数据 void PCF8574_Write(uint8_t data) { HAL_I2C_Master_Transmit(hi2c1, PCF8574_ADDR, data, 1, 100); } // 读取8位数据 uint8_t PCF8574_Read(void) { uint8_t data; HAL_I2C_Master_Receive(hi2c1, PCF8574_ADDR|0x01, data, 1, 100); return data; } // 控制单个引脚 void PCF8574_SetPin(uint8_t pin, uint8_t state) { static uint8_t port_state 0xFF; if(state) port_state | (1pin); else port_state ~(1pin); PCF8574_Write(port_state); }中断处理有个小技巧读取操作会自动清除中断标志所以中断服务函数里一定要做一次读取void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if(GPIO_Pin GPIO_PIN_5) // INT引脚 { uint8_t input PCF8574_Read(); // 处理输入变化... } }5. 实战案例智能灯光控制器去年给朋友做的灯光控制系统就是用PCF8574扩展I/O口的典型应用。系统需要控制16路LED但STM32F103只剩5个I/O口可用。我的解决方案是使用2片PCF8574地址分别设成0x20和0x21每片控制8路LED利用中断功能实现按键检测关键代码如下// 初始化 void LightCtrl_Init(void) { MX_I2C1_Init(); PCF8574_Write(0xFF); // 所有LED初始化为灭 GPIO_Init_INT(); // 配置中断引脚 } // 控制指定LED void Set_LED(uint8_t chip, uint8_t led, uint8_t state) { static uint8_t led_state[2] {0xFF, 0xFF}; if(chip 1 || led 7) return; if(state) led_state[chip] | (1led); else led_state[chip] ~(1led); HAL_I2C_Master_Transmit(hi2c1, PCF8574_ADDRchip, led_state[chip], 1, 100); }这个项目让我深刻体会到PCF8574的两个优势一是布线简单16路LED只用了4根线SCL/SDA/INT/GND二是功耗低整个控制系统待机电流不到1mA。