从想法到硬件原型:用 ESP32-S3 做一个环境异常感知模块
刚开始做这个环境传感器原型时,最容易犯的错误就是把目标写得太满:想识别燃气,想识别烟味,想判断食物变质,还想做成随身小配件,最好再接一个手机提醒和家庭设备联动。听起来都合理,但真正把开发板摆到桌面上以后,会发现问题反而变得很简单:第一块板子到底要先证明什么?
我最后把目标压回到一句话:先做一个能采集环境变化、能建立日常基线、能把持续异常展示出来的小型硬件模块。
这句话里没有急着承诺具体气味识别,也没有把它写成成熟报警器。原因很现实:传感器原始值、外壳结构、空气流动、佩戴位置、温湿度变化,都会影响结果。一个还摆在桌面上的小东西,最该先证明的是数据能不能从传感器一路跑到网页,而不是先把外观和场景讲得很完整。
这篇先记录最早的一段搭建过程:用 ESP32-S3-LCD-1.47 开发板接入 BME690 环境传感器,把采集、显示、联网和网页页面先跑起来。板子怎么选、传感器怎么接、ESP-IDF 工程怎么拆、LVGL 屏幕怎么做、WiFi 后台和异常评分怎么设计,后面再分别展开。

先把问题缩小:不是“闻出气味”,而是“发现异常变化”
“电子鼻”这个词很容易让人误会,好像接一个气体传感器就能知道空气里具体有什么味道。实际开发时不能这样理解。
BME690 这类环境传感器可以提供温度、湿度、气压和气体阻值等信息。气体阻值会随着环境中的挥发性气体变化而变化,但它不是一个直接输出“酒精浓度”“天然气浓度”“焦糊味等级”的设备。没有标定、没有多传感器阵列、没有长期样本训练,就不能把原始阻值说成具体气味结论。
所以早期版本只做一件更稳妥的事:观察环境读数相对日常状态是否出现持续偏离。
这个目标对早期硬件更友好。它不要求一次性解决所有气味识别问题,只需要先把几件基础工作做扎实:
- 传感器能稳定读到数据;
- 数据能在开发板上被处理;
- 屏幕能显示当前状态;
- 设备能连上 WiFi;
- 网页能查看实时数据;
- 前端界面能区分正常波动、短时扰动和持续异常;
- 每一条数据都能看出来源,避免把模拟数据和真实硬件数据混在一起。
这些基础部分没跑通时,外壳、佩戴形态和提醒策略都只能停留在概念上。反过来,只要数据能稳定跑起来,后面无论做成随身夹扣、桌面底座,还是接入其他终端,都有继续推进的基础。
先把桌面上的几块东西连起来
这个原型没有从定制 PCB 开始,而是先用一块带屏幕的 ESP32-S3 开发板做验证。原因很直接:开发阶段需要快速看到状态,也需要频繁调整代码。板载屏幕能直接显示联网状态、传感器状态和关键读数,调试效率比只盯着串口高很多。
桌面上的硬件关系并不复杂,可以先按四块理解:
- BME690 负责采集环境数据;
- ESP32-S3 负责读取传感器、计算状态、启动网络服务;
- 板载屏幕负责直接显示状态;
- 网页端负责展示趋势和更完整的数据。
系统结构大致如下。

这里面最关键的不是用了多少硬件,而是每一块都能单独检查。
传感器是否正常,可以看串口日志;屏幕是否正常,可以看板载显示;WiFi 是否正常,可以看局域网后台;数据是否真的进入前端,可以看网页界面里的来源标记。这样拆开以后,问题会更容易定位,不会所有故障都混在一个黑盒里。

外壳先不急,数据稳定更重要
很多硬件项目一开始容易被外观带着走。尤其是想做随身设备时,很自然会想到胸针、挂坠、夹扣、手环这些形态。但对环境传感器来说,外壳不是简单装饰,它会直接影响传感器进气、散热和佩戴稳定性。
如果外壳把进气孔挡住,数据会滞后;如果传感器贴近皮肤或衣物,温湿度会受人体和衣料影响;如果佩戴位置靠近香水、酒精、油烟或清洁剂,短时间内的气体阻值变化也可能被误判。桌面底座也有类似问题,靠近厨房、窗户、空调出风口和卫生间门口,读数都会不同。
所以这一版更适合先做开发台架,而不是直接追求小型外观。台架至少要做到:
- 传感器接线清楚;
- 供电稳定;
- 串口可看;
- 屏幕可看;
- WiFi 可配;
- 网页后台可访问;
- 数据能被前端界面消费。
这一步看起来不如成品外观漂亮,但它决定了后面有没有继续缩小体积的意义。否则很容易做出一个看起来像产品的外壳,里面的数据却不稳定。
先把 ESP-IDF 环境跑起来
这套原型使用 ESP-IDF 开发。对刚开始接触 ESP32 的人来说,环境搭建不要一上来手动拼环境变量,建议先使用官方安装器或者 VS Code 的 ESP-IDF 插件。手动方式当然也能做,但 Windows 下 Python、Git、CMake、Ninja、交叉编译工具链之间的路径问题比较多,新手很容易在真正写代码之前就卡住。
我自己的环境路径类似这样:
D:\esp\v6.0.1\esp-idf
工程目录建议放在没有中文、没有空格的路径中,例如:
C:\esp\sensor_demo
这不是迷信路径,而是为了减少工具链、脚本和第三方组件在 Windows 下处理路径时的变量。后面接入板厂示例、LVGL 组件和本地传感器驱动时,简单路径确实能省掉不少无意义问题。
安装完成后,先不要急着打开大工程,先在 PowerShell 里确认 idf.py 能跑:
powershell -ExecutionPolicy Bypass -NoProfile -Command ". 'D:\esp\v6.0.1\esp-idf\export.ps1'; idf.py --version"
能看到版本号,基础命令行环境就算过了第一关。这里有个很常见的小坑:VS Code 命令面板里的 “Build your project” 是插件命令,不是 PowerShell 命令。终端里真正执行的是 idf.py build。
接下来可以先建一个空工程试一下工具链:
cd C:\esp
powershell -ExecutionPolicy Bypass -NoProfile -Command ". 'D:\esp\v6.0.1\esp-idf\export.ps1'; idf.py create-project sensor_demo"
cd C:\esp\sensor_demo
powershell -ExecutionPolicy Bypass -NoProfile -Command ". 'D:\esp\v6.0.1\esp-idf\export.ps1'; idf.py set-target esp32s3"
powershell -ExecutionPolicy Bypass -NoProfile -Command ". 'D:\esp\v6.0.1\esp-idf\export.ps1'; idf.py build"
空工程能成功编译以后,再去改带屏幕、WiFi 和传感器的项目。这个顺序值得坚持。空工程都过不了时,先别急着怀疑业务代码,工具链本身往往就是问题来源。
这里有个坑很常见:一边改板厂示例,一边怀疑 ESP-IDF 没装好,最后排查方向会很乱。更省时间的做法是先用空工程确认 idf.py build 能过,再用板厂原始 Demo 确认屏幕能亮,最后才把自己的传感器和网络代码加进去。这样一层一层往上叠,哪一层出问题就比较清楚。
不要一上来就从零写屏幕驱动
ESP32-S3-LCD-1.47 这类带屏幕的开发板,不只是一个裸 ESP32-S3。屏幕初始化、背光控制、SPI LCD、RGB 灯、LVGL 刷新、触发时序都需要板级适配。纯粹从零写当然可以,但早期目标是验证环境感知链路,不是先花大量时间重写显示驱动。
更稳妥的做法是:先拿板厂提供的 ESP-IDF 示例跑通屏幕,再在这个基础上删掉不需要的功能,加入自己的传感器采集、网页后台和数据处理。
当前工程结构可以理解成这样:
C:\esp\sensor_demo
├─ CMakeLists.txt
├─ sdkconfig.defaults
├─ partitions.csv
├─ main
│ ├─ main.c
│ ├─ app_ui.c # 板载屏幕界面
│ ├─ app_wifi.c # WiFi、网页后台、配网、重启
│ ├─ app_upload.c # 数据上传接口
│ ├─ sensor_bme690.c # BME690 采集、平滑、基线计算
│ ├─ sensor_bme690.h
│ ├─ LCD_Driver
│ ├─ LVGL_Driver
│ ├─ RGB
│ └─ Wireless
└─ components
├─ bosch_bme690
├─ lvgl__lvgl
└─ espressif__led_strip
这个结构的思路很朴素:板级代码、传感器代码、网络代码和界面代码分开放。不要把所有逻辑都塞进 main.c。main.c 更适合作为启动入口,负责安排初始化顺序,而不是承担所有业务判断。
一个简化后的启动顺序可以写成这样:
void app_main(void)
{
init_nvs_storage();
RGB_Init();
LCD_Init();
BK_Light(70);
LVGL_Init();
sensor_bme690_start();
app_wifi_start();
app_upload_start();
app_ui_start();
}
这段代码本身不复杂,但它体现了一个很实用的习惯:先初始化基础设施,再启动传感器、网络和界面。后面调试时,如果屏幕没亮,就先看 LCD 和背光;如果传感器没数据,就看 sensor_bme690_start();如果网页打不开,就看 WiFi 和 HTTP 服务,不要所有问题一起查。
BME690 先当作环境变化观察点
BME690 不是一个“气味识别芯片”。在这个原型里,它更像一个环境变化观察点,这个定位要先摆正。
目前主要用到几类数据:
- 温度;
- 湿度;
- 气压;
- 气体阻值;
- 传感器状态。
其中气体阻值是后续做环境变化判断时最关心的字段,但它不能脱离温湿度和时间单独理解。传感器刚上电需要预热,空气流动也会让短时读数波动。看到气体阻值突然变化时,我不会马上把它解释成“识别到了某种气味”,而是先看几个更基础的问题:
- 是否刚上电;
- 读数是否稳定;
- 温湿度是否同时变化;
- 变化持续了多久;
- 当前环境是否有明显干扰;
- 这个变化相对日常基线偏离了多少。
网页后台里可以把这些数据放在一起看,调试时比单看串口更直观。

这一阶段不要急着给这些数据套复杂模型。更好的做法是先保留原始读数,再做一层简单、可解释的平滑和基线。等采集到足够多的真实数据后,再考虑更复杂的分类和识别。
如果照着做,建议一开始就把原始值、平滑值和基线值分开显示。只看最终分数很容易误判:分数变了,不知道是传感器真的变了,还是平滑参数太敏感,或者基线被带偏了。早期界面丑一点没关系,字段越透明,调试越省时间。
读到数字以后,还得把它变成状态
只读到传感器数据还不够。这个小板子如果只输出一串数字,看起来会很抽象,调试起来也不方便。为了让读数变成状态,我把处理过程拆得比较直白。
传感器先按固定频率输出温湿度、气压和气体阻值。固件拿到数据后,先判断是否还在预热,再对读数做一层平滑,避免单个尖峰直接影响状态。接着维护一个环境基线。这里的基线不是永远不变的常数,而是当前环境在一段时间内的正常参考:家庭环境、桌面环境、佩戴环境都会慢慢变化,基线需要适度跟随,但又不能被突发扰动马上带跑。
最后才是异常评分。评分不是为了给出绝对安全结论,而是把“偏离幅度”和“持续时间”合成一个更容易展示的状态。屏幕显示简化结果,网页端展示更多细节。

这个过程可以用一段伪代码表示:
typedef struct {
float gas_raw;
float gas_smooth;
float gas_baseline;
float deviation;
float score;
int stable_seconds;
bool warmup;
} env_state_t;
void update_env_state(env_state_t *s, float gas_raw, bool warmup)
{
s->gas_raw = gas_raw;
s->warmup = warmup;
if (warmup) {
s->score = 0.0f;
s->stable_seconds = 0;
return;
}
s->gas_smooth = smooth_value(s->gas_smooth, gas_raw);
s->gas_baseline = update_baseline(s->gas_baseline, s->gas_smooth);
s->deviation = calc_deviation(s->gas_smooth, s->gas_baseline);
if (s->deviation > 0.25f) {
s->stable_seconds += 1;
} else {
s->stable_seconds = 0;
}
s->score = calc_score(s->deviation, s->stable_seconds);
}
这段伪代码里有几个关键点。
预热阶段不参与异常判断。传感器刚启动时的读数不稳定,拿它做基线容易污染后续结果。
原始值和平滑值要分开保留。原始值用于追溯,平滑值用于状态判断。
基线不能更新得太快。如果一次扰动马上改变基线,系统就会把异常当成新的正常状态。
异常评分也不能只看瞬时偏离。一个短暂波动和一个持续十几秒的偏离,处理方式应该不同。
这不是最终算法,只是第一版能解释、能调试、能看懂的计算过程。早期版本最怕的是算法看起来复杂,但自己都说不清为什么触发或为什么没有触发。
屏幕看状态,网页看细节
开发板上有屏幕,这一点在调试时很有价值。很多问题不需要接串口就能先判断方向:有没有联网、传感器有没有数据、当前状态是不是异常、IP 地址是什么。
但屏幕面积有限,不能承载太多信息。它适合显示“设备状态”和“关键读数”,不适合展示完整趋势、历史记录和详细 JSON。
网页后台解决的是另一类问题。它不需要一直盯着开发板,可以把更多字段摊开看:
- 用电脑或手机查看更多字段;
- 手动刷新当前数据;
- 查看联网状态;
- 清除 WiFi 配置;
- 重启设备;
- 查看上传状态;
- 和前端展示页面联动。
这也是我没有把调试完全放在串口里的原因。串口适合底层开发,但如果这个原型后面要拿给别人看,网页界面更直观。

从开发角度看,板载屏幕、串口日志和网页后台最好同时存在。三者各管一块:
- 串口:看底层日志;
- 屏幕:看设备当前状态;
- 网页:看数据结构和交互结果。
只依赖其中一个,都会让调试变慢。
模拟数据和真数据一定要分清
做网页页面时,很多人会先用模拟数据把界面跑起来,这很正常。问题在于,模拟数据一旦和真实硬件数据混在一起,就很难判断页面效果到底来自哪里。
所以我在数据结构里加入了 source 字段。它至少要能区分三类来源:
{
"timestamp": 1782201600000,
"gas_raw": 126.4,
"temperature": 25.3,
"humidity": 48.2,
"warmup": false,
"mode": "wearable",
"source": "live_hardware"
}
这里的 source 可以是:
simulation:界面模拟数据;recorded_hardware:真实硬件采集后的回放数据;live_hardware:实时硬件数据。
这个字段看起来很小,但能避免后面很多解释不清的问题。比如网页上显示了持续异常,如果来源是 simulation,那它只能说明界面逻辑正常;如果来源是 recorded_hardware,说明真实数据可以被回放验证;如果来源是 live_hardware,才说明开发板正在产生实时数据。
网页里也应该明确展示这个来源,而不是只放一个漂亮的状态卡片。

我是怎么确认它真的跑起来的
这一版先不看外观有多像最终产品,而是看数据有没有一路走通。实际检查时,我会从最底层往上看。
先确认开发板能正常编译和烧录。
cd C:\esp\sensor_demo
powershell -ExecutionPolicy Bypass -NoProfile -Command ". 'D:\esp\v6.0.1\esp-idf\export.ps1'; idf.py build"
powershell -ExecutionPolicy Bypass -NoProfile -Command ". 'D:\esp\v6.0.1\esp-idf\export.ps1'; idf.py -p COM3 flash"
然后看串口里是否能识别到 BME690。
BME690 ready at 0x76, chip id 0x61
T=26.66C H=42.35% P=997.03hPa Gas=26.64kOhm status=0xb0
这条日志至少说明三件事:I2C 接线方向大致正确,芯片地址正确,驱动能读到有效数据。它还不能说明异常判断可靠,更不能说明已经能识别具体气味。
再看屏幕。上电后至少要能看到联网状态、IP 地址或配网提示、传感器状态。
网页后台也要能打开。如果设备已经连上路由器,就在同一局域网里访问屏幕显示的 IP;如果还没连上,就进入设备热点完成配网。
最后看前端界面能不能读到硬件接口返回的数据,并且 source 显示为 live_hardware。如果需要观察扰动,就用安全方式做短时变化,不使用危险气体,也不做无人看管的燃烧实验。这里先只看“变化能否被采集”,不要把现象解释成具体气体识别。
跑通以后,我最关心这几件事
跑到这一步以后,我不会只看页面是不是漂亮,而是会反复确认几件比较硬的事情。
ESP32-S3-LCD-1.47 可以作为环境感知原型的开发底座。它提供了屏幕、WiFi、足够的外设和较方便的调试入口。
BME690 也已经能接入工程并持续输出数据。温度、湿度、气压和气体阻值都能进入固件侧处理。
板载屏幕和网页后台可以同时存在。屏幕解决现场可见性,网页解决详细数据查看。
更关键的是,数据可以从真实硬件进入前端界面。这个环节很重要,因为它把开发板从“能读传感器的玩具”变成了“能被软件界面消费的数据源”。
至于随身夹扣、家庭底座这些形态,现在还不是最终外观,但核心数据已经能支撑后续讨论。
有些结论现在还不能急着下
同样重要的是,它还有很多不能证明的地方。
它不能证明已经能识别所有气味。当前读取的是环境变化参考值,不是具体气味分类结果。
它不能替代专业安全设备。燃气、烟雾和医疗相关判断都有明确的专业设备和认证路径,不能用一个开发原型替代。
它也不能说明佩戴形态已经成熟。随身设备需要继续处理进气孔、外壳、功耗、震动提醒、充电和佩戴位置等问题。
长期稳定性也还没有被证明。传感器漂移、环境变化、网络重连、掉电恢复和数据记录,都需要单独测试。
把不能证明的部分写清楚,不是削弱这个东西,而是避免后续开发方向跑偏。早期版本只要把自己该证明的数据路径跑清楚,就已经足够有价值。
后面继续拆几个具体问题
后面可以继续把这个东西拆成几个更具体的问题。
下一篇先看开发板选型。ESP32-S3-LCD-1.47 为什么适合作为第一块验证板?它的屏幕、WiFi、尺寸、接口和板级示例分别解决了什么问题?它又有哪些不适合作为最终产品的地方?
再往后会展开 BME690 接线、ESP-IDF 工程搭建、LVGL 屏幕、WiFi 配网、网页后台、云端上传和异常评分算法。
这个顺序比较慢,但适合硬件项目。硬件开发最怕一开始讲得很完整,真正接线、烧录、联网、读数时才发现基础部分没有打通。先把桌面上的版本跑稳,再谈小型化和产品形态,风险会低很多。
写到这里,先留几个判断
这篇先把环境异常感知原型的早期搭建过程讲清楚:目标不是直接识别具体气味,而是先通过 ESP32-S3 和 BME690 建立一条能跑通的数据路径。
这条路径包括传感器采集、固件处理、板载屏幕、WiFi 后台、前端页面和数据来源标记。它还不是成熟产品,但已经足够支撑后续继续做开发板选型、传感器接入、界面显示和异常评分。
对这类原型来说,最重要的不是第一天就做出漂亮外壳,而是每一步都能解释清楚:数据从哪里来,经过了什么处理,页面上的状态依据是什么,哪些结论现在还不能下。这个基础打稳了,后面再谈随身夹扣、家庭底座和更多终端接入,才不会变成空想。
更多推荐

所有评论(0)