这是什么
这是一个跑在 ESP32-2432S028(俗称 CYD,Cheap Yellow Display,2.8寸黄屏)上的通用 I2C
测试工具:CN1 排针插上任意一个 I2C 模块,固件自动扫描 1~126 全部地址,找到就报告地址、
判断在不在线;再按一下屏幕上的 FULL 按钮,对已知协议的芯片(目前是 Sensirion
SGP4x 系列和 VL53L5CX)跑真实的自检/测距,对其它芯片做通用的读数稳定性检测。
写这个工具的目的不是替代每个模块专属的测试代码,而是在写那份代码之前先有一个"总线通不通、 模块在不在线"的快速判断——不然排查的时候经常分不清到底是自己代码写错了,还是模块根本没接对 或者没通电。
硬件与CN1接口说明
主控板关键参数
| 项目 | 说明 |
|---|---|
| 主控 | ESP32-2432S028R (CYD),ESP32-D0WD-V3 双核 |
| 屏幕 | 2.8寸 ST7789 驱动 TFT,240×320,本项目位带(bit-bang)直接操作GPIO寄存器手写驱动,不依赖 TFT_eSPI 库 |
| 触摸 | XPT2046 电阻触摸,SPI软件位带读取 |
| Flash / 分区 | 4MB Flash,huge_app 分区方案(3MB APP,无OTA) |
CN1 扩展口 —— I2C 接口
- GND地
- IO22本项目当
SCL(I2C时钟)用 - IO27本项目当
SDA(I2C数据)用 - 3V33.3V 供电
IO22/IO27 是通用 GPIO 复用出来的 I2C,不是独立走线——同一时间 CN1 上只能接一个 I2C 模块,换模块前先拔掉旧的,两个模块的地址一样也无法同时挂在总线上分辨。
LCD / 触摸引脚定义(源码 ESP32_2432S028_Universal_I2C_Tester.ino 第10-27行)
// ESP32-2432S028 / CYD 2.8 inch ST7789 internal LCD pins.
#define PIN_SCK 14
#define PIN_SDA 13
#define PIN_CS 15
#define PIN_RST -1
#define PIN_DC 2
#define PIN_BLK 21
// CN1 sensor header: GND - IO22 - IO27 - 3V3. Any I2C device can go here.
#define I2C_SDA 27
#define I2C_SCL 22
// ESP32-2432S028R resistive touch controller, usually XPT2046.
#define TOUCH_CS 33
#define TOUCH_IRQ 36
#define TOUCH_MOSI 32
#define TOUCH_MISO 39
#define TOUCH_SCLK 25
已验证测试过的模块
CN1 先后接过下面三个模块验证,固件都能正常识别地址、QUICK/FULL 两个模式都测过:
OK / ADDR 0x28,
跟BNO055真实I2C地址(0x28,ADR脚接地时)完全对上。固件目前只对两类芯片写了专属协议支持:0x59 地址的 Sensirion
SGP4x 系列(自检+序列号+VOC/NOx)和 VL53L5CX(真实 ToF 测距)。SCD41、BNO055、DPS310
都不在这个专属列表里,FULL 模式对它们走的是通用分支——只读4字节原始数据看
稳不稳定、抖不抖,不解析成 CO2 浓度 / 姿态角 / 气压这些真实物理量。这个工具
验证的是"总线和接线没问题",真实读数请看各自模块的专属资料页。
QUICK / FULL 两种测试模式
| 模式 | 行为 | 适用场景 |
|---|---|---|
| QUICK | 只发 Wire.beginTransmission/endTransmission 探测有没有 ACK,秒级出结果,屏幕显示地址和累计连接次数 | 插上模块先看一眼有没有反应,最快的第一道检查 |
| FULL | SGP4x:真实自检(0x280E)+读序列号(0x3682)+持续测VOC/NOx;VL53L5CX:begin()上传固件+8x8测距,报告中心/角落两点距离;其它设备:连续读4字节原始数据,记录最大抖动(delta),抖动小于阈值且采样数够才判 PASS | 确认模块不只是"有反应",而是能持续稳定工作 |
FULL 模式的判定阈值由源码里的 TEST_MODE 宏切换,当前编译用的是宽松档
MODE_QUICK(8秒预热、连续4次采样、抖动阈值1000才算稳定),源码里还留了更严格的
MODE_LABEL 档(30秒预热、15次采样、阈值500),改一行宏定义重新编译即可切换成
更严苛的产线级判定。
触屏操作说明
屏幕底部固定4个触摸按钮,从左到右:
- IIC切到 QUICK 模式
- FULL切到 FULL 模式
- CLR清零连接计数;长按(≥1秒)在任意界面都能清零,不用先切到这个按钮所在的界面
- RFSH强制刷新一次(重新走一遍总线恢复+地址扫描)
触摸屏的原始X坐标是反的(数值越大,物理位置越靠屏幕左边),
所以四个按钮的判断阈值是从右往左递减的,源码 handleTouch() 里专门写了注释说明这一点,
照抄这段逻辑时注意别把方向搞反。
固件源码关键段
下面三段是源码里带真实排查经验的关键逻辑,摘自 ESP32_2432S028_Universal_I2C_Tester.ino。
① I2C 总线卡死自动恢复(第296-326行)
某个模块半途掉线可能把 SDA 拉死在低电平,堵死总线上所有其它设备。标准做法是手动打 最多9个SCL时钟把卡住的从机"放行",再补一个手动STOP让总线回到idle。
// Standard I2C bus recovery: a device that got interrupted mid-byte can
// hold SDA low forever and jam the whole bus for every other device on it.
// Clocking SCL up to 9 times lets a stuck slave finish/release SDA, then a
// manual STOP condition puts the bus back to idle. Cheap to call any time
// (returns almost instantly) since it exits immediately once SDA is high.
static void recoverBus() {
Wire.end();
pinMode(I2C_SDA, INPUT_PULLUP);
pinMode(I2C_SCL, OUTPUT);
digitalWrite(I2C_SCL, HIGH);
delayMicroseconds(5);
for (uint8_t i = 0; i < 9 && !digitalRead(I2C_SDA); i++) {
digitalWrite(I2C_SCL, LOW);
delayMicroseconds(5);
digitalWrite(I2C_SCL, HIGH);
delayMicroseconds(5);
}
pinMode(I2C_SDA, OUTPUT);
digitalWrite(I2C_SDA, LOW);
delayMicroseconds(5);
digitalWrite(I2C_SCL, LOW);
delayMicroseconds(5);
digitalWrite(I2C_SCL, HIGH);
delayMicroseconds(5);
digitalWrite(I2C_SDA, HIGH);
delayMicroseconds(5);
Wire.begin(I2C_SDA, I2C_SCL);
Wire.setClock(100000);
Wire.setTimeOut(8);
delay(5);
}
② 地址扫描去抖:先扫到,等30ms,再扫一次确认(第541-573行)
刚插上的连接器可能瞬间接触不良发生跳动,模块刚上电那几毫秒也可能误应答错误地址。 直接认第一次扫到的结果不可靠,所以扫到候选地址后先等待再复扫一次,两次一致才真正采信。
// A freshly-mated connector can bounce, and a module mid power-on can
// glitch-ACK the wrong address for a few ms. Don't latch on the first
// hit alone: let it settle, then re-scan and only commit if the same
// address confirms. This is what "first read shows the wrong address,
// retesting fixes it" was — nothing wrong with the module, we were just
// reading before the bus/power had settled.
delay(30);
for (uint8_t addr = 1; addr < 127; addr++) {
if (probeAddress(addr) == 0) {
if (addr == candidate) {
activeAddr = addr;
return true;
}
return false;
}
}
return false;
③ VL53L5CX 初始化让出看门狗(第759-769行)
VL53L5CX 的 begin() 要通过I2C往传感器里上传约80KB固件,正常情况下要跑好几秒,
比看门狗4秒的超时还长——如果不处理,这一步会被硬件看门狗误判成"卡死"直接强制重启。
drawTextBg(12, 66, "TOF INIT", YELLOW, BLACK, 3);
// begin() uploads ~80KB of firmware into the sensor over I2C and can
// legitimately take several seconds — pull the loop task off the
// watchdog for just this one call so that doesn't look like a hang.
esp_task_wdt_delete(NULL);
bool tofOk = vl53l5cx.begin(VL53L5CX_ADDR, &Wire, 400000) &&
vl53l5cx.setResolution(64) &&
vl53l5cx.setRangingFrequency(15) &&
vl53l5cx.startRanging();
esp_task_wdt_add(NULL);
固件下载与编译
⬇ 下载 .ino 源文件 ⬇ 下载源码包(.ino + zh_bitmap.h)
| 编译参数 | 取值 |
|---|---|
| 开发板 | esp32:esp32:esp32 |
| Partition Scheme | huge_app |
| 依赖库 | Adafruit VL53L5CX(会自动带上 Adafruit BusIO 依赖);其余用的 Wire.h/esp_task_wdt.h 是核心自带 |
| 编译结果 | 程序 427890 字节(13%),全局变量 25080 字节(7%),Arduino-CLI + ESP32 core 3.2.1 本地编译通过,无报错 |
网页烧录流程
不用装 Arduino IDE,浏览器直接烧录,用的是乐鑫官方真正的 ESP Launchpad
(espressif/esp-launchpad,
开源),不是自制的简易版工具——本站把编译好的固件放在自己服务器上,配一个官方支持的
launchpad.toml 配置文件指过去,打开链接就是官方页面的 Quick Start 一键烧录模式。
- 用数据线把 ESP32-2432S028 接上电脑(需要 Chrome / Edge 等支持 Web Serial 的浏览器)。
- 点上面的按钮,打开的页面会直接显示本项目固件卡片(Quick Start 模式)。
- 点
Connect,在弹出的串口列表里选中开发板对应的串口。 - 点
Flash开始烧录,进度条走完即完成,设备会自动重启。
如果 flashConfigURL 一键卡片加载不出来(比如网络问题),可以退到
手动模式:打开 ESP Launchpad 官方首页,
切到 DIY 标签,Flash Address 保持默认 0,选择文件下载
merged bin 固件
(已经是 bootloader+分区表+程序合并好的单文件,约4MB),其余步骤跟 Quick Start 一样。
常见问题与避坑
| 现象 | 说明 / 排查方向 |
|---|---|
屏幕显示 BUS LOW | SDA或SCL被拉死在低电平,固件会自动跑一次 recoverBus() 恢复;持续报错先查排线有没有短路 |
屏幕显示 NO RESPONSE | 没插好、没通电、芯片本身坏了,这三种情况I2C协议本身分辨不出来,工具只能告诉你"没应答",不能告诉你具体哪种 |
| FULL模式对我的模块显示的数字看不懂含义 | 如果不是SGP4x或VL53L5CX,FULL模式走的是通用4字节读数,不代表真实物理量(见第3节说明) |
| 换了个模块要不要重新烧录固件 | 不用,固件本身是通用的,拔掉旧模块插新模块,屏幕会自动重新扫描识别 |
| ESP Launchpad 一键卡片打不开/加载慢 | 退到 DIY 手动模式,见第8节备选方案 |