PlatformIO下ESP32与ESP8266自定义引脚I2C通信故障排查
ESP32与ESP8266 I2C通信故障排查
问题场景
使用板卡
ESP-WROOM-32 38PIN ESP8266MOD
接线方式
ESP32 G21 -> ESP8266 D2 (and 5 volts with 2k resistor from ESP8266) ESP32 G22 -> ESP8266 D1 (and 5 volts with 2k resistor from ESP8266) ESP32 GND -> ESP8266 G
开发环境配置
从站(ESP8266端)PlatformIO配置(platformio.ini)
[platformio] default_envs = esp8266dev data_dir = src/data [env:esp8266dev] platform = espressif8266 framework = arduino board = nodemcuv2 board_build.mcu = esp8266 board_build.partitions = huge_app.csv upload_protocol = esptool lib_deps = bblanchon/ArduinoJson @ ^6.19.4 upload_speed = 115200 monitor_speed = 9600 upload_port = COM14 monitor_port = COM14
主站(ESP32端)PlatformIO配置(platformio.ini)
[env:fm-devkit] platform = espressif32 board = fm-devkit framework = arduino ; upload_speed = 115200 ; monitor_speed = 115200 monitor_speed = 9600 upload_port = COM12 monitor_port = COM12
测试代码
ESP32主站测试代码
#include <Arduino.h> #include "Wire.h" // GPIO21 SDA #define SDA 33 // GPIO22 SLC #define SCL 36 int x = 0x1234; void setup() { Serial.begin(9600); // Wire.begin(SDA, SCL); Wire.begin(); Wire.setClock(100000); // Wire.beginTransmission(0x21); // roll calling for the Slave // byte busStatus = Wire.endTransmission(); // if (busStatus != 0x00) // { // Serial.print("I2C Bus communication problem...!" + String(busStatus)); // while (1) // ; // wait for ever // } // Serial.println("Slave found!"); } void loop() { Serial.print("."); // Wire.requestFrom(0x21, 1, true); // int a = Wire.read(); // Serial.println("Send " + String(a)); Wire.beginTransmission(0x21); // address byte in write mode=0100001+0=01000010= 0x42 (queued) // Wire.write(highByte(x)); // 0x12 is queued // Wire.write(lowByte(x)); // 0x34 is queued Wire.write("HOLA"); Wire.endTransmission(); // all queued data bytes are sent on ACK //------------------- delay(1000); // test interval }
ESP8266从站测试代码
#include <Arduino.h> #include <ArduinoJson.h> #include <Wire.h> #include "functions.hpp" void setup() { Serial.begin(SPEED); pinMode(RELAY1, OUTPUT); // Mode OUTPUT pinMode(RELAY2, OUTPUT); // Mode OUTPUT // pinMode(TX, FUNCTION_3); // Mode GPIO // pinMode(RX, FUNCTION_3); // Mode GPIO Wire.begin(SDA_PIN,SCL_PIN, I2C_SLAVE_ADDR); // Wire.begin(RX, TX, I2C_SLAVE_ADDR); Wire.setClock(CLOCK); Wire.onRequest(eventoSolicitud); // registrar evento de solicitud de datos Wire.onReceive(eventoRecepcion); // registrar evento de recepcion de datos digitalWrite(RELAY2, HIGH); } void loop() { delay((tBucle * 1000)); } void eventoRecepcion(int howMany) { Serial.print("datos i2c"); while (1 < Wire.available()) // loop through all but the last byte { char c = Wire.read(); // receive byte as a character Serial.print(c); // print the character } int x = Wire.read(); // receive byte as an integer Serial.println(x); // print the integer }
故障现象
- 使用默认D1/D2作为I2C引脚时,代码可运行但从站始终无法接收到主站发送的数据;
- 若取消代码中TX/RX引脚pinMode配置的注释、调用
Wire.begin(RX, TX, I2C_SLAVE_ADDR)配置自定义I2C引脚时,ESP8266从站完全无响应。
故障根因及修复方案
通用硬件问题
- I2C上拉电平错误:ESP32、ESP8266均为3.3V器件,引脚最高耐受电压不超过3.6V,当前接线将I2C引脚通过2k电阻上拉到5V,会导致电平阈值不匹配、从站无法正常应答ACK,长期通电还有烧坏引脚的风险。
- 修复:将两个上拉电阻(建议更换为4.7kΩ)的上拉端从5V改接3.3V,确保两个板卡GND可靠连接。
- 主站引脚配置错误:代码中注释的
SDA=33/SCL=36配置不可用,ESP32的GPIO36是仅输入引脚,没有输出驱动能力,完全无法满足I2C开漏输出的要求。- 修复:使用默认的GPIO21(SDA)/GPIO22(SCL)即可,若要更换引脚,避开GPIO34/35/36/39这类仅输入的引脚。
默认D1/D2引脚通信失败的代码问题
- 未做总线地址校验:主站代码将总线扫描逻辑注释,无法确认从站地址是否匹配、物理接线是否正常。I2C地址容易出现7位/8位混淆,比如代码注释里提到的0x42是8位写地址,对应的7位从站地址是0x21,要确认从站
I2C_SLAVE_ADDR宏定义确实为0x21。- 修复:放开主站总线扫描逻辑,上电先确认能扫描到地址为0x21的从站设备,再做数据收发测试。
- 从站接收回调逻辑错误:
Wire.onReceive注册的回调是在I2C中断上下文执行的,回调里直接调用Serial.print这类耗时操作(9600波特率下打印1个字符需要1ms以上),会打断I2C硬实时时序,直接导致丢数据、总线锁死。- 接收判断逻辑
while (1 < Wire.available())存在边界问题,当接收字节数小于2时会直接读空值,多字节接收时也容易出现残留数据。 - 修复:在回调里只做数据读取、标志位置位的操作,将收到的数据存入全局缓冲区,打印、数据解析这类耗时逻辑全部放到
loop()循环中执行;接收循环改为while(Wire.available() > 0)逐字节读完所有缓冲区数据。
切换RX/TX引脚后从站无响应的问题
- 引脚功能冲突:ESP8266的RX(GPIO3)/TX(GPIO1)上电默认是UART0的功能引脚,也就是
Serial绑定的硬件串口。如果仅配置引脚为GPIO模式、但没有关闭/重映射Serial,会出现I2C外设和UART外设抢占引脚控制权的问题,导致I2C完全无法工作。- 修复:如果必须用RX/TX做I2C引脚,需要将调试串口重映射到UART1(仅TX可用,接GPIO2做日志输出),或者临时关闭Serial打印,避免引脚冲突。
- 启动电平干扰:ESP8266上电时会检测GPIO0/GPIO2/GPIO15的电平确定启动模式,其中GPIO1(TX)如果在上电瞬间被拉低,会导致模块进入下载模式,无法正常运行用户代码。
- 修复:确保I2C接线不会在上电阶段拉低TX引脚电平,必要时可以在TX引脚加10k上拉电阻到3.3V,保证模块正常启动。
- 引脚复用配置顺序错误:必须在调用
Wire.begin()之前,先执行pinMode(TX, FUNCTION_3)、pinMode(RX, FUNCTION_3)将两个引脚从UART功能切换为GPIO功能,同时确认Wire.begin()传入的SDA/SCL引脚顺序和实际物理接线完全对应,不要接反SDA和SCL。
内容的提问来源于stack exchange,提问作者Enekoos Gaia
相关产品推荐
相关产品推荐

