接开箱贴,进行软件环境准备打开 Arduino IDE,选择开发板型号:Adafruit Feather ESP32‑S3 Reverse TFT。
通过库管理器安装 4 个必需库:Adafruit SGP30 Sensor Library、Adafruit ST77XX and ST7789 Library、Adafruit GFX Library、Adafruit MAX1704X Library。WiFi、time.h 属于 ESP32 系统自带库,不用额外安装。

先上传 I2C 扫描代码做硬件校验,正常可以扫描出 I2C 地址0x58(SGP30),同时会识别板载电量计。这里踩过坑:只做 I2C 扫描不写屏幕初始化代码会出现黑屏,必须开启TFT_I2C_POWER和TFT_BACKLITE引脚才能点亮背光。确认硬件通信没问题,再刷入完整工程代码。
功能实现说明:
SGP30 是室内空气质量 MOX 金属氧化物气体传感器,不能做实验室级精确检测,主要看变化趋势,不是绝对高精度仪器。上电刚启动 10‑20 个读数:eCO₂=400 ppm,TVOC=0 ppb,属于传感器预热,不是故障,需要通电几十秒后数据才会正常变化。1、eCO₂ (等效二氧化碳,单位:ppm)
全称:Equivalent CO₂,等效二氧化碳
单位:ppm(百万分之一)
范围:400 ~ 60000 ppm
重点:这不是真正二氧化碳浓度!SGP30 没有 CO₂检测元件,它通过空气中氢气、VOC 气体的含量估算出来等效 CO₂值。
400ppm:代表室外新鲜空气基准值;
室内密闭人多、挥发物多,数值上升;
仅作为空气质量参考,不能替代真实 CO₂传感器。
2、TVOC(总挥发性有机物,单位:ppb)
全称:Total Volatile Organic Compounds,总挥发性有机物
单位:ppb(十亿分之一)
范围:0 ~ 60000 ppb
含义:综合反映室内挥发性有害气体,例如油漆、酒精、香水、家具挥发、油烟等。
参考经验:
TVOC <220 ppb:空气质量良好;
220‑600 ppb:一般,注意通风;
>600 ppb:污染物偏高,建议开窗通风。
3、rawH2(原始氢气信号)
原始传感器读数,无单位,SGP 芯片内部检测到的氢气原始电阻信号。
氢气来自人呼吸、材料分解;
主要用于芯片内部算法计算 eCO₂,一般不用直接看这个数值;
数值变大代表环境里氢气成分变多。
4、rawEthanol(原始乙醇信号)
乙醇(酒精类挥发物)原始传感器读数,无单位。
喷酒精、消毒水、白酒靠近传感器,这个数值会瞬间飙升;
芯片内部用来计算 TVOC,属于底层原始数据。
5、电池参数(来自板载 MAX17048 电量计)
batPercent:电池剩余百分比(%)
代码中设置:低于 20% 屏幕字体变红告警,提醒充电;
batVolt:锂电池电压(V)
3.7V 锂电池:满电约 4.2V,放空约 3.0V;USB 供电时读出电压,用来判断电池状态。
6、NTP 时间(屏幕上 Time)
从阿里云 NTP 服务器获取东八区北京时间,格式HH:MM:SS;
WiFi 断开,屏幕显示Time: WiFi Disconnect。
SGP30 采集 eCO₂等效 CO₂、TVOC 总挥发性有机物、原始氢气、乙醇信号;
MAX17048 读取锂电池电压、剩余电量,电量低于 20% 屏幕红字告警;
WiFi 联网,通过 SNTP 获取阿里云 NTP 北京时间,屏幕实时显示时分秒;
屏幕彩色 UI 展示全部数据,串口同步输出调试信息;
WiFi 断开自动重连,断网时提示时间不可用。
重要提示:SGP30 上电初期十几秒固定输出eCO₂=400ppm,TVOC=0ppb,属于传感器预热,不是故障;需要连续通电 12 小时完成基线学习,读数才更加稳定,传感器适合看变化趋势,不作为实验室精密仪器。#include <Wire.h>
#include <WiFi.h>
#include <WiFiUdp.h>
#include <NTPClient.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7789.h>
#include <Fonts/FreeMono9pt7b.h>
#include <Adafruit_SGP30.h>
#include <Adafruit_MAX1704X.h>
//==================== 请修改为你的WiFi信息 ====================
const char* ssid = "xbz123";
const char* password = "87654321";
//=============================================================
#define I2C_SDA 3
#define I2C_SCL 4
Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST);
Adafruit_SGP30 sgp;
Adafruit_MAX17048 batMon;
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org", 8 * 3600, 60000); //东八区,60s更新一次NTP
unsigned long screenTimer = 0;
const unsigned long interval = 1000;
uint16_t eco2, tvoc;
uint16_t rawH2, rawEth;
float batVolt, batPercent;
void setup() {
Serial.begin(115200);
// 屏幕电源、背光
pinMode(TFT_I2C_POWER, OUTPUT);
digitalWrite(TFT_I2C_POWER, HIGH);
delay(20);
pinMode(TFT_BACKLITE, OUTPUT);
digitalWrite(TFT_BACKLITE, HIGH);
delay(20);
tft.init(135, 240);
tft.setRotation(3);
tft.fillScreen(ST77XX_BLACK);
tft.setFont(&FreeMono9pt7b);
tft.setTextColor(ST77XX_WHITE);
tft.setTextSize(1);
tft.setCursor(10, 25);
tft.println("Init Hardware...");
Wire.begin(I2C_SDA, I2C_SCL);
//电量计
if (!batMon.begin()) {
Serial.println("MAX17048 init fail");
tft.println("BAT ERROR");
}
//SGP30
if (!sgp.begin()) {
Serial.println("SGP30 NOT FOUND! Check wiring!");
tft.fillScreen(ST77XX_BLACK);
tft.setCursor(10,40);
tft.setTextColor(ST77XX_RED);
tft.println("SGP30 ERROR!");
while (1) delay(100);
}
sgp.IAQinit();
delay(1000);
//WiFi连接
tft.setCursor(10,50);
tft.print("WiFi:"); tft.print(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(300);
Serial.print(".");
}
Serial.println("\nWiFi connected!");
timeClient.begin();
tft.fillScreen(ST77XX_BLACK);
Serial.println("===== SGP30 Monitor Ready =====");
}
void loop() {
//WiFi断连自动重连
if(WiFi.status() != WL_CONNECTED){
WiFi.reconnect();
}
timeClient.update();
if (millis() - screenTimer >= interval) {
screenTimer = millis();
//读取空气质量
if(sgp.IAQmeasure()){
eco2 = sgp.eCO2;
tvoc = sgp.TVOC;
}
if(sgp.IAQmeasureRaw()){
rawH2 = sgp.rawH2;
rawEth = sgp.rawEthanol;
}
//读取电池
batVolt = batMon.cellVoltage();
batPercent = batMon.cellPercent();
//串口打印
Serial.print("Time:"); Serial.print(timeClient.getFormattedTime()); Serial.print(" | ");
Serial.print("eCO2:"); Serial.print(eco2); Serial.print(" ppm | ");
Serial.print("TVOC:"); Serial.print(tvoc); Serial.print(" ppb | ");
Serial.print("H2:"); Serial.print(rawH2); Serial.print(" ETH:"); Serial.print(rawEth);
Serial.print(" | BAT:"); Serial.print(batPercent,1); Serial.print("% "); Serial.print(batVolt,2); Serial.println("V");
//屏幕刷新
tft.fillScreen(ST77XX_BLACK);
//时间行 青色
tft.setTextColor(ST77XX_CYAN);
tft.setCursor(8,22);
if(WiFi.status() == WL_CONNECTED){
tft.print("Time:");
tft.print(timeClient.getFormattedTime());
}else{
tft.print("Time: WiFi Disconnect");
}
//空气数据绿色
tft.setTextColor(ST77XX_GREEN);
tft.setCursor(8,48);
tft.print("eCO2: "); tft.print(eco2); tft.print(" ppm");
tft.setCursor(8,72);
tft.print("TVOC: "); tft.print(tvoc); tft.print(" ppb");
tft.setCursor(8,96);
tft.print("H2:"); tft.print(rawH2); tft.print(" ETH:"); tft.print(rawEth);
//电池状态
if(batPercent < 20){
tft.setTextColor(ST77XX_RED);
}else{
tft.setTextColor(ST77XX_YELLOW);
}
tft.setCursor(8,120);
tft.print("BAT: "); tft.print(batPercent,1); tft.print("% "); tft.print(batVolt,2); tft.print("V");
}
}在Arduino IDE中运行代码编译上传后,ESP32‑S3 Reverse TFT Feather显示采集的数据。

为了测试运行是否正常,将传感器靠近一个电动喷香机,可以看到参数直线上升,系统工作正常。

开发中的注意事项
1、屏幕黑屏:忘记开启屏幕电源与背光引脚,补上引脚输出高电平解决;
2、编译报错 API 找不到:Arduino 版 SGP30 库和 CircuitPython 函数不一样,更换 Arduino 对应的接口函数;
3、第三方库头文件找不到:安装库之后必须完全关闭 IDE 再重启;
4、NTP 库安装失败:改用 ESP32 原生time.h,避开第三方 NTPClient 库。
后续还可以继续扩展,增加温湿度传感器和 3D 打印外壳。

我要赚赏金
