开始前适用开发板:ESP32 系列 · 开发框架:ESP-IDF
文档类型:ESP-IDF 通用 API 教程,不绑定单一开发板。
板级例程见 热门例程详解。
目录
简介
TCA9554 是一款 8 位 I/O 扩展器,通过 I2C 总线与主控制器通信,可以扩展 ESP32 的 GPIO 数量。该芯片具有以下特点:
- 8 位 I/O 端口,每个端口可独立配置为输入或输出
- 支持中断输出功能
- 工作电压范围:1.65V 至 5.5V
- I2C 总线接口,最高支持 400kHz 速率
- 可配置 3 位硬件地址,最多可同时使用 8 个芯片
- 小尺寸封装:TSSOP-16、SOIC-16
硬件连接
引脚定义
| TCA9554 引脚 | 功能描述 |
|---|---|
| A0, A1, A2 | 地址选择引脚 |
| SDA | I2C 数据线 |
| SCL | I2C 时钟线 |
| INT | 中断输出(低电平有效) |
| P0 - P7 | 8 位 I/O 端口 |
| VCC | 电源 (1.65V-5.5V) |
| GND | 地 |
连接示意图
ESP32 TCA9554
┌──────────┐ ┌─────────┐
│ │ │ │
│ GPIO21├─────────────┤SDA │
│ GPIO22├─────────────┤SCL │
│ GPIO4 ├─────────────┤INT │
│ │ │ │
│ │ │A0 │
│ │ │A1 │
│ │ │A2 │
│ │ │ │
│ │ │P0 │
│ │ │P1 │
│ │ │... │
│ │ │P7 │
│ │ │ │
│ 3.3V├─────────────┤VCC │
│ GND├─────────────┤GND │
└──────────┘ └─────────┘
地址配置
TCA9554 的 I2C 地址由 A0、A1、A2 引脚决定:
- 基础地址:0x20(当 A0、A1、A2 都接地时)
- 地址范围:0x20 - 0x27
| A2 | A1 | A0 | 设备地址 |
|---|---|---|---|
| 0 | 0 | 0 | 0x20 |
| 0 | 0 | 1 | 0x21 |
| 0 | 1 | 0 | 0x22 |
| 0 | 1 | 1 | 0x23 |
| 1 | 0 | 0 | 0x24 |
| 1 | 0 | 1 | 0x25 |
| 1 | 1 | 0 | 0x26 |
| 1 | 1 | 1 | 0x27 |
组件安装
1. 添加组件依赖
使用 ESP-IDF 的组件管理器添加 TCA9554 组件:
idf.py add-dependency espressif/esp_io_expander_tca9554
2. 在 CMakeLists.txt 中添加依赖
idf_component_register(
SRCS "main.c"
INCLUDE_DIRS "."
REQUIRES esp_io_expander_tca9554
)
基本使用
1. 包含头文件
#include "esp_io_expander_tca9554.h"
2. 创建 I2C 总线
使用 ESP-IDF 的新版 I2C 驱动:
#include "driver/i2c_master.h"
#define I2C_MASTER_SCL_IO 22 // GPIO 用于 I2C SCL
#define I2C_MASTER_SDA_IO 21 // GPIO 用于 I2C SDA
#define I2C_MASTER_FREQ_HZ 400000 // I2C 主频率
#define I2C_MASTER_TIMEOUT_MS 1000 // I2C 超时时间
static i2c_master_bus_handle_t i2c_bus = NULL;
// 初始化 I2C 总线
esp_err_t init_i2c_bus(void)
{
i2c_master_bus_config_t i2c_bus_config = {
.clk_source = I2C_CLK_SRC_DEFAULT,
.i2c_port = I2C_NUM_0,
.scl_io_num = I2C_MASTER_SCL_IO,
.sda_io_num = I2C_MASTER_SDA_IO,
.glitch_ignore_cnt = 7,
.flags.enable_internal_pullup = true,
};
esp_err_t ret = i2c_new_master_bus(&i2c_bus_config, &i2c_bus);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "I2C 总线初始化失败");
}
return ret;
}
3. 初始化 TCA9554
#define TCA9554_I2C_ADDR 0x20 // TCA9554 I2C 地址
static esp_io_expander_handle_t io_expander = NULL;
// 初始化 TCA9554
esp_err_t init_tca9554(void)
{
// 创建 TCA9554 设备
esp_io_expander_tca9554_config_t config = {
.i2c_bus = i2c_bus,
.device_address = TCA9554_I2C_ADDR,
};
esp_err_t ret = esp_io_expander_new_tca9554(&config, &io_expander);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "TCA9554 初始化失败");
return ret;
}
// 初始化 IO 扩展器
ret = esp_io_expander_init(io_expander);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "IO 扩展器初始化失败");
return ret;
}
return ESP_OK;
}
4. 基本 I/O 操作
// 配置 IO 方向
esp_err_t configure_io_direction(void)
{
// 设置 P0-P3 为输出模式
for (int i = 0; i < 4; i++) {
esp_err_t ret = esp_io_expander_set_dir(io_expander, i, IO_EXPANDER_OUTPUT);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "设置 P%d 为输出模式失败", i);
return ret;
}
}
// 设置 P4-P7 为输入模式
for (int i = 4; i < 8; i++) {
esp_err_t ret = esp_io_expander_set_dir(io_expander, i, IO_EXPANDER_INPUT);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "设置 P%d 为输入模式失败", i);
return ret;
}
}
return ESP_OK;
}
// 设置输出电平
esp_err_t set_output_level(uint8_t pin, bool level)
{
return esp_io_expander_set_level(io_expander, pin, level);
}
// 读取输入电平
esp_err_t get_input_level(uint8_t pin, bool *level)
{
return esp_io_expander_get_level(io_expander, pin, level);
}
高级功能
1. 中断处理
TCA9554 支持输入引脚状态变化中断,当配置为输入的引脚状态发生变化时,INT 引脚会输出低电平。
#define INT_GPIO 4 // ESP32 上连接 TCA9554 INT 引脚的 GPIO
static QueueHandle_t gpio_evt_queue = NULL;
// GPIO 中断服务函数
static void IRAM_ATTR gpio_isr_handler(void *arg)
{
uint32_t gpio_num = (uint32_t)arg;
xQueueSendFromISR(gpio_evt_queue, &gpio_num, NULL);
}
// 中断处理任务
static void gpio_task(void *arg)
{
uint32_t gpio_num;
while (1) {
if (xQueueReceive(gpio_evt_queue, &gpio_num, portMAX_DELAY)) {
ESP_LOGI(TAG, "检测到 TCA9554 中断,GPIO: %lu", gpio_num);
// 读取所有输入引脚状态
uint8_t input_mask = 0;
esp_io_expander_get_input_reg(io_expander, &input_mask);
ESP_LOGI(TAG, "输入状态: 0x%02x", input_mask);
}
}
}
// 初始化中断
esp_err_t init_interrupt(void)
{
// 创建中断队列
gpio_evt_queue = xQueueCreate(10, sizeof(uint32_t));
// 配置 GPIO 中断
gpio_config_t io_conf = {
.intr_type = GPIO_INTR_NEGEDGE, // 下降沿触发
.pin_bit_mask = (1ULL << INT_GPIO), // 中断 GPIO
.mode = GPIO_MODE_INPUT, // 输入模式
.pull_up_en = GPIO_PULLUP_ENABLE, // 启用上拉
};
gpio_config(&io_conf);
// 安装 GPIO 中断服务
gpio_install_isr_service(0);
gpio_isr_handler_add(INT_GPIO, gpio_isr_handler, (void *)INT_GPIO);
// 创建中断处理任务
xTaskCreate(gpio_task, "gpio_task", 2048, NULL, 10, NULL);
return ESP_OK;
}
2. 批量操作
// 批量设置输出电平
esp_err_t set_multiple_outputs(uint8_t mask, uint8_t value)
{
// 先读取当前输出寄存器值
uint8_t output_reg = 0;
esp_err_t ret = esp_io_expander_get_output_reg(io_expander, &output_reg);
if (ret != ESP_OK) {
return ret;
}
// 更新指定位
output_reg = (output_reg & ~mask) | (value & mask);
// 写入输出寄存器
return esp_io_expander_set_output_reg(io_expander, output_reg);
}
// 批量读取输入电平
esp_err_t get_multiple_inputs(uint8_t mask, uint8_t *value)
{
uint8_t input_reg = 0;
esp_err_t ret = esp_io_expander_get_input_reg(io_expander, &input_reg);
if (ret != ESP_OK) {
return ret;
}
*value = input_reg & mask;
return ESP_OK;
}
3. 复位操作
// 复位 TCA9554
esp_err_t reset_tca9554(void)
{
// 复位 IO 扩展器
return esp_io_expander_reset(io_expander);
}
完整示例
下面是一个完整的示例,演示如何使用 TCA9554 控制 LED 并读取按钮状态:
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_log.h"
#include "driver/i2c_master.h"
#include "esp_io_expander_tca9554.h"
static const char *TAG = "TCA9554_EXAMPLE";
#define I2C_MASTER_SCL_IO 22
#define I2C_MASTER_SDA_IO 21
#define I2C_MASTER_FREQ_HZ 400000
#define I2C_MASTER_TIMEOUT_MS 1000
#define TCA9554_I2C_ADDR 0x20
#define INT_GPIO 4
// LED 连接到 TCA9554 的 P0-P3
#define LED1_PIN 0
#define LED2_PIN 1
#define LED3_PIN 2
#define LED4_PIN 3
// 按钮连接到 TCA9554 的 P4-P7
#define BTN1_PIN 4
#define BTN2_PIN 5
#define BTN3_PIN 6
#define BTN4_PIN 7
static i2c_master_bus_handle_t i2c_bus = NULL;
static esp_io_expander_handle_t io_expander = NULL;
static QueueHandle_t gpio_evt_queue = NULL;
// GPIO 中断服务函数
static void IRAM_ATTR gpio_isr_handler(void *arg)
{
uint32_t gpio_num = (uint32_t)arg;
xQueueSendFromISR(gpio_evt_queue, &gpio_num, NULL);
}
// 中断处理任务
static void gpio_task(void *arg)
{
uint32_t gpio_num;
while (1) {
if (xQueueReceive(gpio_evt_queue, &gpio_num, portMAX_DELAY)) {
ESP_LOGI(TAG, "检测到 TCA9554 中断,GPIO: %lu", gpio_num);
// 读取所有按钮状态
uint8_t input_mask = 0;
esp_io_expander_get_input_reg(io_expander, &input_mask);
ESP_LOGI(TAG, "按钮状态: 0x%02x", input_mask);
// 检查各个按钮
if (!(input_mask & (1 << BTN1_PIN))) {
ESP_LOGI(TAG, "按钮 1 按下");
esp_io_expander_set_level(io_expander, LED1_PIN, 1); // 点亮 LED1
} else {
esp_io_expander_set_level(io_expander, LED1_PIN, 0); // 熄灭 LED1
}
if (!(input_mask & (1 << BTN2_PIN))) {
ESP_LOGI(TAG, "按钮 2 按下");
esp_io_expander_set_level(io_expander, LED2_PIN, 1); // 点亮 LED2
} else {
esp_io_expander_set_level(io_expander, LED2_PIN, 0); // 熄灭 LED2
}
if (!(input_mask & (1 << BTN3_PIN))) {
ESP_LOGI(TAG, "按钮 3 按下");
esp_io_expander_set_level(io_expander, LED3_PIN, 1); // 点亮 LED3
} else {
esp_io_expander_set_level(io_expander, LED3_PIN, 0); // 熄灭 LED3
}
if (!(input_mask & (1 << BTN4_PIN))) {
ESP_LOGI(TAG, "按钮 4 按下");
esp_io_expander_set_level(io_expander, LED4_PIN, 1); // 点亮 LED4
} else {
esp_io_expander_set_level(io_expander, LED4_PIN, 0); // 熄灭 LED4
}
}
}
}
// 初始化 I2C 总线
static esp_err_t init_i2c_bus(void)
{
i2c_master_bus_config_t i2c_bus_config = {
.clk_source = I2C_CLK_SRC_DEFAULT,
.i2c_port = I2C_NUM_0,
.scl_io_num = I2C_MASTER_SCL_IO,
.sda_io_num = I2C_MASTER_SDA_IO,
.glitch_ignore_cnt = 7,
.flags.enable_internal_pullup = true,
};
esp_err_t ret = i2c_new_master_bus(&i2c_bus_config, &i2c_bus);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "I2C 总线初始化失败");
}
return ret;
}
// 初始化 TCA9554
static esp_err_t init_tca9554(void)
{
// 创建 TCA9554 设备
esp_io_expander_tca9554_config_t config = {
.i2c_bus = i2c_bus,
.device_address = TCA9554_I2C_ADDR,
};
esp_err_t ret = esp_io_expander_new_tca9554(&config, &io_expander);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "TCA9554 初始化失败");
return ret;
}
// 初始化 IO 扩展器
ret = esp_io_expander_init(io_expander);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "IO 扩展器初始化失败");
return ret;
}
return ESP_OK;
}
// 配置 IO 方向
static esp_err_t configure_io_direction(void)
{
// 设置 LED 引脚为输出模式
for (int i = LED1_PIN; i <= LED4_PIN; i++) {
esp_err_t ret = esp_io_expander_set_dir(io_expander, i, IO_EXPANDER_OUTPUT);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "设置 P%d 为输出模式失败", i);
return ret;
}
}
// 设置按钮引脚为输入模式,启用上拉
for (int i = BTN1_PIN; i <= BTN4_PIN; i++) {
esp_err_t ret = esp_io_expander_set_dir(io_expander, i, IO_EXPANDER_INPUT);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "设置 P%d 为输入模式失败", i);
return ret;
}
ret = esp_io_expander_set_pull(io_expander, i, IO_EXPANDER_PULL_UP);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "设置 P%d 上拉失败", i);
return ret;
}
}
return ESP_OK;
}
// 初始化中断
static esp_err_t init_interrupt(void)
{
// 创建中断队列
gpio_evt_queue = xQueueCreate(10, sizeof(uint32_t));
// 配置 GPIO 中断
gpio_config_t io_conf = {
.intr_type = GPIO_INTR_NEGEDGE, // 下降沿触发
.pin_bit_mask = (1ULL << INT_GPIO), // 中断 GPIO
.mode = GPIO_MODE_INPUT, // 输入模式
.pull_up_en = GPIO_PULLUP_ENABLE, // 启用上拉
};
gpio_config(&io_conf);
// 安装 GPIO 中断服务
gpio_install_isr_service(0);
gpio_isr_handler_add(INT_GPIO, gpio_isr_handler, (void *)INT_GPIO);
// 创建中断处理任务
xTaskCreate(gpio_task, "gpio_task", 2048, NULL, 10, NULL);
return ESP_OK;
}
// LED 闪烁任务
static void led_blink_task(void *arg)
{
bool led_state = false;
while (1) {
// 切换所有 LED 状态
for (int i = LED1_PIN; i <= LED4_PIN; i++) {
esp_io_expander_set_level(io_expander, i, led_state);
}
led_state = !led_state;
vTaskDelay(pdMS_TO_TICKS(500));
}
}
void app_main(void)
{
// 初始化 I2C 总线
ESP_ERROR_CHECK(init_i2c_bus());
// 初始化 TCA9554
ESP_ERROR_CHECK(init_tca9554());
// 配置 IO 方向
ESP_ERROR_CHECK(configure_io_direction());
// 初始化中断
ESP_ERROR_CHECK(init_interrupt());
// 创建 LED 闪烁任务
xTaskCreate(led_blink_task, "led_blink_task", 2048, NULL, 5, NULL);
ESP_LOGI(TAG, "TCA9554 示例已启动");
}
常见问题
1. 通信错误
如果遇到通信错误,请检查:
- I2C 总线连接是否正确
- TCA9554 地址是否配置正确
- I2C 时钟频率是否过高
- 上拉电阻是否合适
2. 中断不工作
如果中断功能不正常,请检查:
- INT 引脚连接是否正确
- 输入引脚是否正确配置为输入模式
- 中断处理逻辑是否正确
3. 输出不稳定
如果输出不稳定,可能原因:
- 电源电压不稳定
- 负载过大
- 地线连接不良
4. 多个 TCA9554 使用
如果需要使用多个 TCA9554,请确保:
- 每个芯片的地址配置不同
- 共用 I2C 总线和中断线
- 在中断处理中识别触发中断的设备
总结
本教程详细介绍了如何使用 ESP-IDF 的 esp_io_expander_tca9554 组件来操作 TCA9554 I/O 扩展器。通过 TCA9554,可以轻松扩展 ESP32 的 GPIO 数量,实现更复杂的应用。
主要内容包括:
- TCA9554 基本原理和特性
- 硬件连接和地址配置
- 组件安装和初始化
- 基本 I/O 操作
- 中断处理
- 批量操作和复位
- 完整示例应用
通过合理使用 TCA9554,可以大大扩展 ESP32 的 I/O 能力,适用于需要大量 GPIO 的应用场景,如键盘矩阵、LED 阵列、多传感器系统等。
当前模型请求量过大,请求排队约 2 位,请稍候或切换至其他模型问答体验更流畅。