> 技术文档 > 【ESP32-IDF】基础外设开发1:IO_MUX和GPIO交换矩阵

【ESP32-IDF】基础外设开发1:IO_MUX和GPIO交换矩阵


ESP32-IDF系列文章

持续更新…


文章目录

  • ESP32-IDF系列文章
  • 前言
  • 一、IO_MUX 介绍
    • 1.IO_MUX 的作用
    • 2.IO_MUX 配置方式
    • 3.与 RTC IO_MUX 的区别
  • 二、通过 GPIO 交换矩阵的输入/输出
    • 1.GPIO 交换矩阵的作用
    • 2.输入/输出的切换
  • 三、GPIO 类型定义
  • 四、GPIO相关 API
  • 五、GPIO示例程序
    • 方式一:逐函数配置 + 独立任务
    • 方式二:结构体批量配置 + 主循环实现
  • 总结

前言

ESP32 是一款强大的 32 位微控制器,广泛应用于物联网(IoT)设备和嵌入式系统中。它拥有丰富的外设接口、强大的无线通信能力以及低功耗特点,非常适合开发各种智能硬件。ESP32 使用的开发框架是 ESP32-IDF (Espressif IoT Development Framework),这个框架提供了丰富的 API 来帮助开发者快速实现各种功能

本篇博文将详细介绍 ESP32 中的 IO_MUX 和 GPIO 交换矩阵,并结合 ESP32-IDF 开发方式,讲解如何通过 GPIO 配置输入/输出操作,帮助开发者更好地管理和配置外设。

参考文档:ESP32 GPIO 官方文档


一、IO_MUX 介绍

IO_MUX 是 “功能选择器”:决定 GPIO 能做什么(UART?SPI?)
在 ESP32 中,IO_MUX 是 ESP32 引脚功能复用的 “调度中心”,它是一种输入输出多路复用器。它允许开发者将各种外设功能映射到不同的 GPIO 引脚。通过配置 IO_MUX,开发者可以在 ESP32 的硬件资源中灵活地选择适合自己的引脚。

1.IO_MUX 的作用

ESP32 提供了多达 34 个 GPIO 引脚,但是并不是每个引脚都能直接用于所有外设。通过 IO_MUX,开发者可以将不同的外设功能映射到这些引脚上,灵活地分配资源。例如,你可以将 UART 功能映射到 GPIO 1 和 GPIO 3,将 SPI 功能映射到其他 GPIO 引脚。

2.IO_MUX 配置方式

在 ESP32 中,IO_MUX 是通过硬件映射实现的,开发者通过编程来选择 GPIO 引脚并映射外设功能。开发者可以通过 ESP32-IDF 提供的相关 API 来配置和操作 IO_MUX。

3.与 RTC IO_MUX 的区别

ESP32 中,编号为 0、2、4、12 ~ 15、25 ~ 27、32 ~ 39 的 18 个 GPIO 引脚,除常规 IO_MUX 功能外,还支持 RTC IO_MUX 模式。该模式由芯片的 RTC 子系统直接管控,即便主系统因深度睡眠断电,RTC 子系统仍能持续运行——这构成了低功耗场景的核心支撑。在输出特性上,深度睡眠期间这些引脚可保持电平稳定,实现对外部设备的供电或状态维持;输入侧则拓展了唤醒能力,既支持触摸传感器触发的唤醒机制,也能通过检测引脚电平变化(如外部按键信号)唤醒芯片。与普通 IO_MUX 不同,RTC IO_MUX 需通过 RTC_GPIO_Pinx 系列寄存器配置,还可复用实现模拟功能(如触摸传感应用),进一步挖掘低功耗场景下的引脚潜力。
在这里插入图片描述

二、通过 GPIO 交换矩阵的输入/输出

交换矩阵 是 “信号路由器”:决定 GPIO 的功能连接到哪个外设(UART0?UART1?)

ESP32 提供了一个 GPIO 交换矩阵,用于管理输入和输出信号的转换。这使得在开发中,GPIO 引脚不仅可以作为输入或输出,还可以用于处理外设信号。

1.GPIO 交换矩阵的作用

GPIO 交换矩阵用于将 GPIO 引脚与内部外设(如 UART、SPI、I2C 等)进行连接。它确保外设信号可以正确地传输到目标 GPIO 引脚,或者从 GPIO 引脚传输到外设。通过配置交换矩阵,开发者可以动态切换引脚的功能。

2.输入/输出的切换

使用 GPIO 交换矩阵,开发者可以通过编程在输入和输出模式之间切换。例如,将某个 GPIO 引脚配置为输出并控制 LED,然后再将其配置为输入,用于读取按钮的状态。
【ESP32-IDF】基础外设开发1:IO_MUX和GPIO交换矩阵

三、GPIO 类型定义

ESP32开发环境定义了自身的常量、变量和函数,这些构成了ESP32开发的基础。下面简单介绍ESP32的 GPIO 编程基础知识。头文件esp_enrh、gpio_types.h和gpio.h中,有一些基于C语言预定义。

//=============================================================================// ESP32 GPIO 相关类型定义// 本文件按大类分块,收录 esp_err_t 及 GPIO 驱动中用到的各类枚举/结构体//=============================================================================#include //=============================================================================// esp_err_t 类型// 统一的错误码返回类型//=============================================================================typedef int32_t esp_err_t;#define ESP_OK  0 // 成功#define ESP_FAIL -1 // 失败#define ESP_ERR_NO_MEM 0x101 // 内存错误#define ESP_ERR_INVALID_ARG 0x102 // 无效参数#define ESP_ERR_INVALID_STATE 0x103 // 无效状态#define ESP_ERR_INVALID_SIZE 0x104 // 无效大小#define ESP_ERR_NOT_FOUND 0x105 // 未发现请求资源#define ESP_ERR_NOT_SUPPORTED 0x106 // 不支持操作或功能#define ESP_ERR_TIMEOUT 0x107 // 操作超时#define ESP_ERR_INVALID_RESPONSE 0x108 // 收到的回应无效#define ESP_ERR_INVALID_CRC 0x109 // CRC 校验失败#define ESP_ERR_INVALID_VERSION 0x10A // 无效版本#define ESP_ERR_INVALID_MAC 0x10B // 无效的 MAC 地址#define ESP_ERR_WIFI_BASE 0x3000 // Wi-Fi 错误代码基址#define ESP_ERR_MESH_BASE 0x4000 // Mesh 错误代码基址#define ESP_ERR_FLASH_BASE 0x6000 // Flash 错误代码基址//=============================================================================// gpio_num_t 类型// ESP32 上可选的 GPIO 引脚编号//=============================================================================typedef enum { GPIO_NUM_NC = -1, // 不连接引脚,用于信号占位 GPIO_NUM_0 = 0, // GPIO0, 输入/输出 GPIO_NUM_1 = 1, // GPIO1, 输入/输出 GPIO_NUM_2 = 2, // GPIO2, 输入/输出 GPIO_NUM_3 = 3, // GPIO3, 输入/输出 GPIO_NUM_4 = 4, // GPIO4, 输入/输出 GPIO_NUM_5 = 5, // GPIO5, 输入/输出 GPIO_NUM_6 = 6, // GPIO6, 输入/输出 (闪存占用) GPIO_NUM_7 = 7, // GPIO7, 输入/输出 (闪存占用) GPIO_NUM_8 = 8, // GPIO8, 输入/输出 (闪存占用) GPIO_NUM_9 = 9, // GPIO9, 输入/输出 (闪存占用) GPIO_NUM_10 = 10, // GPIO10, 输入/输出 (闪存占用) GPIO_NUM_11 = 11, // GPIO11, 输入/输出 (闪存占用) GPIO_NUM_12 = 12, // GPIO12, 输入/输出 GPIO_NUM_13 = 13, // GPIO13, 输入/输出 GPIO_NUM_14 = 14, // GPIO14, 输入/输出 GPIO_NUM_15 = 15, // GPIO15, 输入/输出 GPIO_NUM_16 = 16, // GPIO16, 输入/输出 GPIO_NUM_17 = 17, // GPIO17, 输入/输出 GPIO_NUM_18 = 18, // GPIO18, 输入/输出 GPIO_NUM_19 = 19, // GPIO19, 输入/输出 GPIO_NUM_21 = 21, // GPIO21, 输入/输出 GPIO_NUM_22 = 22, // GPIO22, 输入/输出 GPIO_NUM_23 = 23, // GPIO23, 输入/输出 GPIO_NUM_25 = 25, // GPIO25, 输入/输出 GPIO_NUM_26 = 26, // GPIO26, 输入/输出 GPIO_NUM_27 = 27, // GPIO27, 输入/输出 GPIO_NUM_32 = 32, // GPIO32, 输入 (ESP32-S2 亦可输出) GPIO_NUM_33 = 33, // GPIO33, 输入 (ESP32-S2 亦可输出) GPIO_NUM_34 = 34, // GPIO34, 输入专用 GPIO_NUM_35 = 35, // GPIO35, 输入专用 GPIO_NUM_36 = 36, // GPIO36, 输入专用 GPIO_NUM_37 = 37, // GPIO37, 输入专用 GPIO_NUM_38 = 38, // GPIO38, 输入专用 GPIO_NUM_39 = 39, // GPIO39, 输入专用 GPIO_NUM_MAX} gpio_num_t;//=============================================================================// gpio_int_type_t 类型// GPIO 中断触发方式//=============================================================================typedef enum { GPIO_INTR_DISABLE = 0, // 禁用 GPIO 中断 GPIO_INTR_POSEDGE = 1, // 上升沿触发 GPIO_INTR_NEGEDGE = 2, // 下降沿触发 GPIO_INTR_ANYEDGE = 3, // 任意边沿触发 GPIO_INTR_LOW_LEVEL = 4, // 低电平触发 GPIO_INTR_HIGH_LEVEL = 5, // 高电平触发 GPIO_INTR_MAX} gpio_int_type_t;//=============================================================================// gpio_mode_t 类型// GPIO 输入输出模式//=============================================================================typedef enum { GPIO_MODE_DISABLE = 0,  // 禁用输入输出 GPIO_MODE_INPUT = 1,  // 仅输入 GPIO_MODE_OUTPUT = 2,  // 仅输出 GPIO_MODE_OUTPUT_OD = 3,  // 开漏输出 GPIO_MODE_INPUT_OUTPUT = 4,  // 输入输出 GPIO_MODE_INPUT_OUTPUT_OD= 5  // 输入 + 开漏输出} gpio_mode_t;//=============================================================================// gpio_pullup_t 与 gpio_pulldown_t 类型// GPIO 上拉 / 下拉 使能//=============================================================================typedef enum { GPIO_PULLUP_DISABLE = 0x0, // 禁用上拉 GPIO_PULLUP_ENABLE = 0x1 // 使能上拉} gpio_pullup_t;typedef enum { GPIO_PULLDOWN_DISABLE = 0x0, // 禁用下拉 GPIO_PULLDOWN_ENABLE = 0x1 // 使能下拉} gpio_pulldown_t;//=============================================================================// gpio_pull_mode_t 类型// GPIO 综合上下拉模式//=============================================================================typedef enum { GPIO_FLOATING, // 引脚浮空 GPIO_PULLUP_ONLY, // 仅上拉 GPIO_PULLDOWN_ONLY, // 仅下拉 GPIO_PULLUP_PULLDOWN // 同时上拉 + 下拉(一般无效)} gpio_pull_mode_t;//=============================================================================// gpio_drive_cap_t 类型// GPIO 驱动能力档位(输出电流能力)//=============================================================================typedef enum { GPIO_DRIVE_CAP_0 = 0, // 驱动能力:弱 GPIO_DRIVE_CAP_1 = 1, // 驱动能力:较弱 GPIO_DRIVE_CAP_2 = 2, // 驱动能力:中(默认) GPIO_DRIVE_CAP_3 = 3, // 驱动能力:较强 GPIO_DRIVE_CAP_MAX} gpio_drive_cap_t;//=============================================================================// gpio_config_t 类型// GPIO 引脚配置结构体//=============================================================================typedef struct { uint64_t  pin_bit_mask; // 位掩码:每一位对应一个 GPIO_NUM_x gpio_mode_t mode; // 引脚模式:输入/输出/开漏... gpio_pullup_t pull_up_en; // 是否启用上拉 gpio_pulldown_t pull_down_en; // 是否启用下拉 gpio_int_type_t intr_type; // 中断触发类型} gpio_config_t;

四、GPIO相关 API

在 ESP32-IDF 中,开发者可以使用一系列的 GPIO API 函数来配置、控制和管理 GPIO 引脚。以下是一些常用的 GPIO 函数和它们的作用

//=============================================================================// GPIO 常用 API 列表//=============================================================================// 配置一组 GPIO 引脚属性(方向、上下拉、中断类型等)esp_err_t gpio_config(const gpio_config_t *pGPIOConfig);// 恢复指定 GPIO 到复位前的默认状态esp_err_t gpio_reset_pin(gpio_num_t gpio_num);// 设置指定 GPIO 的输出电平(0 = 低,1 = 高)esp_err_t gpio_set_level(gpio_num_t gpio_num, uint32_t level);// 读取指定 GPIO 的输入电平(返回 0 或 1)int gpio_get_level(gpio_num_t gpio_num);// 设置指定 GPIO 的工作模式(输入/输出/开漏等)esp_err_t gpio_set_direction(gpio_num_t gpio_num, gpio_mode_t mode);// 设置指定 GPIO 的综合上下拉模式(浮空/仅上拉/仅下拉/上下拉)esp_err_t gpio_set_pull_mode(gpio_num_t gpio_num, gpio_pull_mode_t pull);// 使能指定 GPIO 作为深度睡眠唤醒源,并设置唤醒触发类型esp_err_t gpio_wakeup_enable(gpio_num_t gpio_num, gpio_int_type_t intr_type);// 禁用指定 GPIO 的深度睡眠唤醒功能esp_err_t gpio_wakeup_disable(gpio_num_t gpio_num);// 单独使能指定 GPIO 的内部上拉esp_err_t gpio_pullup_en(gpio_num_t gpio_num);// 单独禁用指定 GPIO 的内部上拉esp_err_t gpio_pullup_dis(gpio_num_t gpio_num);// 单独使能指定 GPIO 的内部下拉esp_err_t gpio_pulldown_en(gpio_num_t gpio_num);// 单独禁用指定 GPIO 的内部下拉esp_err_t gpio_pulldown_dis(gpio_num_t gpio_num);// 设置指定 GPIO 的驱动能力档位(输出电流强度)esp_err_t gpio_set_drive_capability(gpio_num_t gpio_num, gpio_drive_cap_t strength);// 获取指定 GPIO 的当前驱动能力档位esp_err_t gpio_get_drive_capability(gpio_num_t gpio_num, gpio_drive_cap_t *strength);// 使能指定 GPIO 的保持功能(在重启/睡眠中保持电平)esp_err_t gpio_hold_en(gpio_num_t gpio_num);// 禁用指定 GPIO 的保持功能esp_err_t gpio_hold_dis(gpio_num_t gpio_num);// 在深度睡眠中保持所有可保持的 GPIO 引脚状态void gpio_deep_sleep_hold_en(void);// 在深度睡眠中取消保持所有 GPIO 引脚状态void gpio_deep_sleep_hold_dis(void);// IO_MUX 层:将外设信号(signal_idx)路由到指定 GPIO 输入void gpio_iomux_in(uint32_t signal_idx, gpio_num_t gpio_num);// IO_MUX 层:将指定 GPIO 输出映射到外设信号(signal_idx),支持输出使能反向void gpio_iomux_out(uint32_t signal_idx, gpio_num_t gpio_num, bool oen_inv);// 强制所有支持保持功能的 GPIO 引脚进入保持状态esp_err_t gpio_force_hold_all(void);// 强制解除所有 GPIO 引脚的保持状态esp_err_t gpio_force_unhold_all(void);// 将指定 PAD 切换到通用 GPIO 功能模式void gpio_pad_select_gpio(uint32_t gpio_num);

五、GPIO示例程序

示例为经典的 LED 每隔 1s 闪烁实验
【ESP32-IDF】基础外设开发1:IO_MUX和GPIO交换矩阵

实验硬件连接方式:在 ESP32开发板的引脚 GPIO18上连接LED正极,LED负极连接一个阻值为 1kΩ的电阻器,开发板的 GND 引脚连接电阻器

方式一:逐函数配置 + 独立任务

#include #include \"freertos/FreeRTOS.h\" // FreeRTOS 内核头文件#include \"freertos/task.h\" // 任务管理头文件#include \"driver/gpio.h\" // GPIO 驱动头文件#include \"sdkconfig.h\"  // 工程配置头文件#define LED 18 // 定义 LED 连接的 GPIO 引脚(GPIO18)// LED 闪烁任务函数void LED_Task(void *pvParameters) { // 步骤1:选择 GPIO 引脚 gpio_pad_select_gpio(LED); // 步骤2:设置引脚方向为输出模式 gpio_set_direction(LED, GPIO_MODE_OUTPUT); while (1) { gpio_set_level(LED, 0); // 拉低电平(LED 灭) // 延迟 1 秒(portTICK_PERIOD_MS 是 FreeRTOS 时间基准宏) vTaskDelay(1000 / portTICK_PERIOD_MS); gpio_set_level(LED, 1); // 拉高电平(LED 亮) vTaskDelay(1000 / portTICK_PERIOD_MS); }}void app_main() { // 创建 FreeRTOS 任务: // - 任务函数:LED_Task // - 任务名称:\"LED_Task\" // - 栈大小:configMINIMAL_STACK_SIZE(默认最小栈) // - 参数:NULL(无参数) // - 优先级:5(优先级中等) // - 任务句柄:NULL(不保存句柄) xTaskCreate(LED_Task, \"LED_Task\", configMINIMAL_STACK_SIZE, NULL, 5, NULL);}

方式二:结构体批量配置 + 主循环实现

#include #include #include \"stdlib.h\"#include \"freertos/FreeRTOS.h\" // FreeRTOS 内核头文件#include \"freertos/task.h\" // 任务管理头文件#include \"freertos/queue.h\" // 队列头文件#include \"driver/gpio.h\" // GPIO 驱动头文件#define GPIO_OUTPUT_IO_0 18  // 定义输出引脚(GPIO18)// 引脚掩码(位运算标记目标引脚,方便批量配置)#define GPIO_OUTPUT_PIN_SEL (1ULL << GPIO_OUTPUT_IO_0) void app_main(void) { // 使用结构体初始化 GPIO 配置 gpio_config_t io_conf = { .pin_bit_mask = GPIO_OUTPUT_PIN_SEL, // 引脚掩码:标记目标引脚 .mode = GPIO_MODE_OUTPUT, // 设置为输出模式 .pull_down_en = 0,  // 禁用下拉电阻 .pull_up_en = 0,  // 禁用上拉电阻 .intr_type = GPIO_PIN_INTR_DISABLE // 禁用中断 }; gpio_config(&io_conf); // 应用配置到硬件(一次性生效) int cnt = 0; // 计数器(演示用) while (1) { printf(\"cnt: %d\\n\", cnt++); // 串口打印计数 // 延迟 1 秒 vTaskDelay(1000 / portTICK_RATE_MS); // 取余控制电平:偶数灭,奇数亮 gpio_set_level(GPIO_OUTPUT_IO_0, cnt % 2); }}

总结

本文探讨了 ESP32 的 GPIO 配置方式,重点介绍了如何通过 ESP32-IDF 进行外设管理和配置。GPIO 作为嵌入式系统中最常用的硬件接口之一,掌握其配置和操作对于开发高效、稳定的硬件至关重要。希望能为大家在 ESP32 开发过程中提供帮助,水平有限,供大家参考学习与讨论,欢迎提出宝贵意见。