3、ESP32基础外设编程:GPIO、UART、I2C、SPI接口的使用与调试
各位同学,欢迎来到第三章。这一章我们直接上手干活。
ESP32 之所以能成为物联网网关的「心脏」,靠的就是它那一堆丰富的外设接口。GPIO、UART、I2C、SPI,这四个是基本功。你想想看,传感器要接吧?显示屏要接吧?跟其他模块通信要接吧?全得靠它们。
我个人习惯,拿到一个新板子,第一件事不是跑 Hello World,而是先把这几个外设点亮。心里有底了,后面做协议转换才不慌。
3.1 GPIO:最基础的数字「手脚」
GPIO,通用输入输出口。说白了,就是芯片伸出来的一个个引脚,你可以控制它输出高电平(3.3V)或低电平(0V),也可以读取它上面的电平状态。
ESP32 有 34 个 GPIO,但不是所有都能随便用。嗯,这里要注意,有些引脚有特殊功能,比如 GPIO6-11 默认连到了 Flash 芯片上,你拿来当普通 IO 用,程序直接跑飞。我在项目中遇到过这种坑,排查了半天,最后发现是用了保留引脚。
3.1.1 输出模式:点亮一盏灯
代码很简单,但背后逻辑要清楚。
// 配置 GPIO2 为输出模式
gpio_pad_select_gpio(GPIO_NUM_2);
gpio_set_direction(GPIO_NUM_2, GPIO_MODE_OUTPUT);
// 输出高电平,点亮 LED
gpio_set_level(GPIO_NUM_2, 1);
vTaskDelay(1000 / portTICK_PERIOD_MS);
gpio_set_level(GPIO_NUM_2, 0);
这里有个细节:gpio_pad_select_gpio() 是把引脚从「其他功能」切换到「GPIO 功能」。很多人忘了这步,结果怎么配置都没反应。我刚开始学的时候也犯过这错。
3.1.2 输入模式:读取按键状态
读取输入时,最怕的是引脚悬空,电平乱跳。所以一定要加上拉或下拉电阻。
gpio_set_direction(GPIO_NUM_4, GPIO_MODE_INPUT);
gpio_set_pull_mode(GPIO_NUM_4, GPIO_PULLUP_ONLY); // 内部上拉
int level = gpio_get_level(GPIO_NUM_4);
if (level == 0) {
printf("按键按下\n");
}
你想想看,如果不加上拉,引脚就像一根天线,周围电磁干扰都能让它电平变化。做工业网关时,这种干扰尤其明显。
3.2 UART:最经典的串行通信
UART,通用异步收发器。两根线(TX、RX)就能通信,简单可靠。ESP32 有 3 个 UART 控制器,每个都可以映射到任意 GPIO。
做网关时,UART 主要用来跟老旧设备通信,比如电表、PLC、GPS 模块。这些设备很多还是 RS232 或 RS485 电平,需要外加转换芯片。
3.2.1 基本配置与收发
// 配置 UART2,波特率 115200
uart_config_t uart_config = {
.baud_rate = 115200,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE
};
uart_param_config(UART_NUM_2, &uart_config);
uart_set_pin(UART_NUM_2, TXD_PIN, RXD_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
uart_driver_install(UART_NUM_2, BUF_SIZE * 2, 0, 0, NULL, 0);
// 发送数据
uart_write_bytes(UART_NUM_2, "AT\r\n", 4);
// 接收数据
uint8_t data[128];
int len = uart_read_bytes(UART_NUM_2, data, sizeof(data), 1000 / portTICK_PERIOD_MS);
这里我建议你注意波特率匹配。两边差一点点,偶尔能通,但数据全是乱码。我曾经调试一个 Modbus 设备,波特率设成了 9600,对方是 9600 但校验位不同,折腾了两小时才发现。
3.3 I2C:多设备共享两根线
I2C 只有 SDA(数据线)和 SCL(时钟线)两根线,却能挂载上百个设备。每个设备有唯一地址,通过地址寻址。
做物联网网关时,I2C 常用来接温湿度传感器(如 SHT30)、OLED 显示屏、EEPROM 存储芯片。我个人的经验是,I2C 适合短距离、低速率的板内通信,超过 20cm 就容易受干扰。
3.3.1 主机模式读写传感器
// 配置 I2C 主机
i2c_config_t conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = I2C_MASTER_SDA_IO,
.scl_io_num = I2C_MASTER_SCL_IO,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = 100000 // 100KHz 标准模式
};
i2c_param_config(I2C_NUM_0, &conf);
i2c_driver_install(I2C_NUM_0, conf.mode, 0, 0, 0);
// 向从机地址 0x3C 写数据
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (0x3C << 1) | I2C_MASTER_WRITE, true);
i2c_master_write_byte(cmd, 0x00, true); // 控制字节
i2c_master_stop(cmd);
i2c_master_cmd_begin(I2C_NUM_0, cmd, 1000 / portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);
为什么地址要左移一位?因为 I2C 协议里,7 位地址加上读写位,组成一个字节。左移一位是为了给读写位腾位置。很多新手在这里搞混,地址写错了,设备怎么都不应答。
3.4 SPI:高速数据通道
SPI 是四线制(MOSI、MISO、SCLK、CS),全双工通信,速度可以跑到几十 MHz。适合接显示屏、SD 卡、射频模块等需要高速传输的设备。
ESP32 有 4 个 SPI 控制器,其中 SPI2 和 SPI3 可以映射到任意 GPIO。做边缘计算时,SPI 常用来接摄像头或高速 ADC,采集数据后直接在本地处理。
3.4.1 主机模式读写
// 配置 SPI 主机
spi_bus_config_t buscfg = {
.miso_io_num = PIN_MISO,
.mosi_io_num = PIN_MOSI,
.sclk_io_num = PIN_SCLK,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = 4096
};
spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO);
// 添加设备
spi_device_interface_config_t devcfg = {
.clock_speed_hz = 10 * 1000 * 1000, // 10MHz
.mode = 0,
.spics_io_num = PIN_CS,
.queue_size = 7
};
spi_device_handle_t spi;
spi_bus_add_device(SPI2_HOST, &devcfg, &spi);
// 发送数据
spi_transaction_t t = {
.length = 8 * sizeof(tx_data),
.tx_buffer = tx_data,
.rx_buffer = rx_data
};
spi_device_transmit(spi, &t);
SPI 的模式(Mode 0-3)取决于时钟极性(CPOL)和相位(CPHA)。不匹配的话,数据全是错的。我建议你查一下从机设备的 datasheet,确认它工作在哪个模式。Mode 0 是最常见的,但不是绝对的。
3.5 调试技巧总结
做外设编程,调试比写代码更花时间。我总结几个实用技巧:
- 逻辑分析仪是神器:几十块钱的 USB 逻辑分析仪,能抓取 GPIO、UART、I2C、SPI 的波形。比靠猜靠谱一万倍。
- 打印调试信息:在关键节点加
printf,看数据对不对。但注意,UART 打印本身会占用时间,影响时序。 - 分步验证:不要一口气写完所有代码。先验证 GPIO 能亮灯,再验证 UART 能收发,最后才组合起来。这样出问题了,定位范围小。
- 看 errno:ESP-IDF 的函数很多返回 esp_err_t,用
ESP_ERROR_CHECK()捕获错误。很多问题其实函数已经告诉你原因了,只是你没看。
好了,这一章的内容就到这里。GPIO、UART、I2C、SPI 这四个接口,是 ESP32 物联网网关的「四肢」。你把这些练熟了,后面做协议转换、边缘计算,才能游刃有余。
下一章,我们开始玩点高级的——WiFi 和蓝牙通信。到时候见。