PCA9555 I/O扩展芯片驱动详解

:您提到的"XL9555"可能是笔误,常见型号应为NXP的PCA9555(16位I²C I/O扩展器)。以下以PCA9555为准进行详解。

一、芯片特性与引脚说明

核心特性

  • 16位可编程I/O:分为端口0(P0.0-P0.7)和端口1(P1.0-P1.7)
  • I²C接口:支持100kHz(标准)/400kHz(快速)模式
  • 独立配置:每个I/O引脚可单独设为输入或输出
  • 中断功能:开漏INT引脚,输入状态变化时触发
  • 多器件支持:3个硬件地址引脚(A0-A2),单总线最多接8个设备
  • 宽电压范围:2.3V~5.5V(兼容3.3V/5V系统)

引脚定义

引脚 功能
SCL/SDA I²C时钟/数据线
A0-A2 地址选择引脚(决定I²C从地址)
INT 中断输出(开漏,需上拉)
RESET 低电平有效复位(可选)
P0.0-P0.7 端口0 I/O引脚
P1.0-P1.7 端口1 I/O引脚

二、寄存器映射(关键地址)

寄存器 地址 端口0 端口1 描述
输入寄存器 0x00 0x00 0x01 读取引脚电平(只读)
输出寄存器 0x02 0x02 0x03 设置输出电平(读写)
极性反转 0x04 0x04 0x05 1=反相输入(读写)
配置寄存器 0x06 0x06 0x07 0=输出,1=输入(读写)

I²C从地址0b0100_A2_A1_A0(A0-A2接地/接VCC决定实际地址)

三、驱动代码实现(C语言)

1. 基础配置

cpp 复制代码
#include "i2c.h" // 假设已实现底层I2C驱动

#define PCA9555_ADDR_BASE 0x20 // 基础地址(0b0100000)
#define PCA9555_ADDR(addr_pins) (PCA9555_ADDR_BASE | (addr_pins & 0x07))

// 寄存器地址枚举
typedef enum {
    REG_INPUT_0 = 0x00,
    REG_INPUT_1 = 0x01,
    REG_OUTPUT_0 = 0x02,
    REG_OUTPUT_1 = 0x03,
    REG_POLARITY_0 = 0x04,
    REG_POLARITY_1 = 0x05,
    REG_CONFIG_0 = 0x06,
    REG_CONFIG_1 = 0x07
} pca9555_reg_t;

2. 核心操作函数

cpp 复制代码
/**
 * @brief 写入16位数据到寄存器
 * @param addr: 芯片I2C地址
 * @param reg: 起始寄存器地址
 * @param value: 16位数据(低8位=端口0,高8位=端口1)
 * @return 0=成功,<0=错误
 */
int pca9555_write_reg(uint8_t addr, uint8_t reg, uint16_t value) {
    uint8_t data[3] = {reg, (uint8_t)(value & 0xFF), (uint8_t)(value >> 8)};
    return i2c_write(addr, data, 3); // 调用底层I2C写函数
}

/**
 * @brief 读取16位寄存器数据
 * @param addr: 芯片I2C地址
 * @param reg: 起始寄存器地址
 * @param value: 存储读取结果的指针
 * @return 0=成功,<0=错误
 */
int pca9555_read_reg(uint8_t addr, uint8_t reg, uint16_t *value) {
    uint8_t rx_data[2];
    if(i2c_write(addr, &reg, 1) != 0) return -1; // 先发送寄存器地址
    if(i2c_read(addr, rx_data, 2) != 0) return -2; // 读取2字节数据
    
    *value = ((uint16_t)rx_data[1] << 8) | rx_data[0]; // 端口1在高8位
    return 0;
}

3. 应用层接口

cpp 复制代码
/**
 * @brief 初始化PCA9555
 * @param addr: 芯片I2C地址
 * @return 0=成功
 */
int pca9555_init(uint8_t addr) {
    // 默认全部配置为输入(配置寄存器=0xFFFF)
    if(pca9555_write_reg(addr, REG_CONFIG_0, 0xFFFF) != 0) return -1;
    
    // 清除输出寄存器(避免上电时意外输出)
    if(pca9555_write_reg(addr, REG_OUTPUT_0, 0x0000) != 0) return -2;
    
    return 0;
}

/**
 * @brief 设置单个引脚方向
 * @param addr: 芯片地址
 * @param pin: 0-15(0-7=端口0,8-15=端口1)
 * @param is_input: 1=输入,0=输出
 * @return 0=成功
 */
int pca9555_set_pin_direction(uint8_t addr, uint8_t pin, uint8_t is_input) {
    uint16_t config;
    uint8_t reg = (pin < 8) ? REG_CONFIG_0 : REG_CONFIG_1;
    uint8_t bit = pin % 8;
    
    if(pca9555_read_reg(addr, reg, &config) != 0) return -1;
    
    if(is_input) {
        config |= (1 << bit);   // 设置为输入
    } else {
        config &= ~(1 << bit);  // 设置为输出
    }
    
    return pca9555_write_reg(addr, reg, config);
}

/**
 * @brief 读取单个引脚状态
 * @param addr: 芯片地址
 * @param pin: 0-15
 * @param state: 存储状态的指针(0/1)
 * @return 0=成功
 */
int pca9555_read_pin(uint8_t addr, uint8_t pin, uint8_t *state) {
    uint16_t input;
    uint8_t reg = (pin < 8) ? REG_INPUT_0 : REG_INPUT_1;
    uint8_t bit = pin % 8;
    
    if(pca9555_read_reg(addr, reg, &input) != 0) return -1;
    *state = (input >> bit) & 0x01;
    return 0;
}

/**
 * @brief 设置单个引脚输出
 * @param addr: 芯片地址
 * @param pin: 0-15
 * @param value: 0/1
 * @return 0=成功
 */
int pca9555_write_pin(uint8_t addr, uint8_t pin, uint8_t value) {
    uint16_t output;
    uint8_t reg = (pin < 8) ? REG_OUTPUT_0 : REG_OUTPUT_1;
    uint8_t bit = pin % 8;
    
    if(pca9555_read_reg(addr, reg, &output) != 0) return -1;
    
    if(value) {
        output |= (1 << bit);
    } else {
        output &= ~(1 << bit);
    }
    
    return pca9555_write_reg(addr, reg, output);
}

四、典型应用示例

1. 驱动LED矩阵

cpp 复制代码
#define PCA9555_ADDR0 PCA9555_ADDR(0x00) // A2=A1=A0=GND

void led_matrix_demo(void) {
    pca9555_init(PCA9555_ADDR0);
    
    // 配置P0.0-P0.7为输出(行驱动)
    for(int i=0; i<8; i++) {
        pca9555_set_pin_direction(PCA9555_ADDR0, i, 0); // 0=输出
    }
    
    // 配置P1.0-P1.3为输出(列驱动)
    for(int i=0; i<4; i++) {
        pca9555_set_pin_direction(PCA9555_ADDR0, 8+i, 0);
    }
    
    // 点亮(2,1)位置的LED
    pca9555_write_pin(PCA9555_ADDR0, 2, 1);  // 行2高电平
    pca9555_write_pin(PCA9555_ADDR0, 8+1, 0); // 列1低电平
}

2. 按键扫描(带中断)

cpp 复制代码
// 中断处理函数(需在MCU中配置)
void PCA9555_INT_IRQHandler(void) {
    uint8_t key_state;
    pca9555_read_pin(PCA9555_ADDR0, 10, &key_state); // 读取P1.2按键状态
    if(key_state) {
        // 处理按键按下事件
    }
    
    // 清除中断标志(读取输入寄存器会自动清除)
    uint16_t dummy;
    pca9555_read_reg(PCA9555_ADDR0, REG_INPUT_0, &dummy);
}

五、关键设计注意事项

1. 电气特性

  • 上拉电阻:I²C总线必须接4.7kΩ上拉电阻(3.3V系统)
  • 输出电流:单引脚最大25mA,总电流<100mA(避免直接驱动大功率LED)
  • 电平转换:5V系统连接3.3V MCU时需电平转换芯片

2. 时序优化

cpp 复制代码
// 批量操作提高效率(避免频繁I²C事务)
uint16_t output_state = 0x0000; // 初始状态

// 设置多个引脚
output_state |= (1 << 3);  // P0.3=1
output_state &= ~(1 << 10); // P1.2=0

// 一次性写入
pca9555_write_reg(PCA9555_ADDR0, REG_OUTPUT_0, output_state);

3. 常见问题解决

问题 可能原因 解决方案
无法通信 I²C地址错误 用逻辑分析仪检查实际地址
输出不稳定 未初始化输出寄存器 先写输出寄存器再配置方向
中断不触发 未配置输入引脚 确保中断引脚设为输入模式
读取值错误 未等待稳定时间 在读取前加10μs延时

六、替代型号对比

型号 位数 接口 特点 适用场景
PCA9555 16 I²C 标准功能,性价比高 通用I/O扩展
MCP23017 16 I²C 速度更快(1.7MHz) 高速应用
TCA9555 16 I²C 工业级温度范围 恶劣环境
PCF8574 8 I²C 低成本,简单 8位扩展需求

提示:在FreeRTOS系统中,建议将PCA9555驱动封装为线程安全模块,并添加错误重试机制:

复制代码
cpp 复制代码
#define MAX_RETRY 3
int pca9555_safe_write(uint8_t addr, uint8_t reg, uint16_t value) {
    for(int i=0; i<MAX_RETRY; i++) {
        if(pca9555_write_reg(addr, reg, value) == 0) return 0;
        vTaskDelay(pdMS_TO_TICKS(10)); // 重试前等待
    }
    return -1; // 最终失败
}

PCA9555是扩展微控制器I/O能力的经济高效方案,正确实现驱动后可稳定支持工业控制、智能家居、仪器仪表等多种应用场景。

相关推荐
zore_c1 小时前
【C语言】文件操作详解2(文件的顺序读写操作)
android·c语言·开发语言·数据结构·笔记·算法·缓存
乐科1 小时前
WPF定时器
stm32·单片机·wpf
逐步前行1 小时前
通讯协议--SPI同步串行协议
单片机
PPS柴油1 小时前
RK3568开发板gpio模拟LED呼吸灯
linux·驱动开发·嵌入式硬件
玩转单片机与嵌入式1 小时前
资料共享:可以显示波形的串口调试助手(附赠通信协议)
人工智能·stm32·单片机
Less is moree1 小时前
C语言文件操作中的读写模式
c语言
d111111111d1 小时前
STM32低功耗学习-待机模式-(学习笔记)
笔记·stm32·单片机·嵌入式硬件·学习
量子炒饭大师1 小时前
初探算法的魅力——【暴力枚举】
c语言·数据结构·c++·算法·动态规划
逐步前行1 小时前
通讯协议--UART异步串行通信
单片机