01|从想法到硬件原型:用 ESP32-S3 做一个环境异常感知模块 刚开始做这个环境传感器原型时最容易犯的错误就是把目标写得太满想识别燃气想识别烟味想判断食物变质还想做成随身小配件最好再接一个手机提醒和家庭设备联动。听起来都合理但真正把开发板摆到桌面上以后会发现问题反而变得很简单第一块板子到底要先证明什么我最后把目标压回到一句话先做一个能采集环境变化、能建立日常基线、能把持续异常展示出来的小型硬件模块。这句话里没有急着承诺具体气味识别也没有把它写成成熟报警器。原因很现实传感器原始值、外壳结构、空气流动、佩戴位置、温湿度变化都会影响结果。一个还摆在桌面上的小东西最该先证明的是数据能不能从传感器一路跑到网页而不是先把外观和场景讲得很完整。这篇先记录最早的一段搭建过程用 ESP32-S3-LCD-1.47 开发板接入 BME690 环境传感器把采集、显示、联网和网页页面先跑起来。板子怎么选、传感器怎么接、ESP-IDF 工程怎么拆、LVGL 屏幕怎么做、WiFi 后台和异常评分怎么设计后面再分别展开。先把问题缩小不是“闻出气味”而是“发现异常变化”“电子鼻”这个词很容易让人误会好像接一个气体传感器就能知道空气里具体有什么味道。实际开发时不能这样理解。BME690 这类环境传感器可以提供温度、湿度、气压和气体阻值等信息。气体阻值会随着环境中的挥发性气体变化而变化但它不是一个直接输出“酒精浓度”“天然气浓度”“焦糊味等级”的设备。没有标定、没有多传感器阵列、没有长期样本训练就不能把原始阻值说成具体气味结论。所以早期版本只做一件更稳妥的事观察环境读数相对日常状态是否出现持续偏离。这个目标对早期硬件更友好。它不要求一次性解决所有气味识别问题只需要先把几件基础工作做扎实- 传感器能稳定读到数据- 数据能在开发板上被处理- 屏幕能显示当前状态- 设备能连上 WiFi- 网页能查看实时数据- 前端界面能区分正常波动、短时扰动和持续异常- 每一条数据都能看出来源避免把模拟数据和真实硬件数据混在一起。这些基础部分没跑通时外壳、佩戴形态和提醒策略都只能停留在概念上。反过来只要数据能稳定跑起来后面无论做成随身夹扣、桌面底座还是接入其他终端都有继续推进的基础。先把桌面上的几块东西连起来这个原型没有从定制 PCB 开始而是先用一块带屏幕的 ESP32-S3 开发板做验证。原因很直接开发阶段需要快速看到状态也需要频繁调整代码。板载屏幕能直接显示联网状态、传感器状态和关键读数调试效率比只盯着串口高很多。桌面上的硬件关系并不复杂可以先按四块理解1. BME690 负责采集环境数据2. ESP32-S3 负责读取传感器、计算状态、启动网络服务3. 板载屏幕负责直接显示状态4. 网页端负责展示趋势和更完整的数据。系统结构大致如下。这里面最关键的不是用了多少硬件而是每一块都能单独检查。传感器是否正常可以看串口日志屏幕是否正常可以看板载显示WiFi 是否正常可以看局域网后台数据是否真的进入前端可以看网页界面里的来源标记。这样拆开以后问题会更容易定位不会所有故障都混在一个黑盒里。外壳先不急数据稳定更重要很多硬件项目一开始容易被外观带着走。尤其是想做随身设备时很自然会想到胸针、挂坠、夹扣、手环这些形态。但对环境传感器来说外壳不是简单装饰它会直接影响传感器进气、散热和佩戴稳定性。如果外壳把进气孔挡住数据会滞后如果传感器贴近皮肤或衣物温湿度会受人体和衣料影响如果佩戴位置靠近香水、酒精、油烟或清洁剂短时间内的气体阻值变化也可能被误判。桌面底座也有类似问题靠近厨房、窗户、空调出风口和卫生间门口读数都会不同。所以这一版更适合先做开发台架而不是直接追求小型外观。台架至少要做到- 传感器接线清楚- 供电稳定- 串口可看- 屏幕可看- WiFi 可配- 网页后台可访问- 数据能被前端界面消费。这一步看起来不如成品外观漂亮但它决定了后面有没有继续缩小体积的意义。否则很容易做出一个看起来像产品的外壳里面的数据却不稳定。先把 ESP-IDF 环境跑起来这套原型使用 ESP-IDF 开发。对刚开始接触 ESP32 的人来说环境搭建不要一上来手动拼环境变量建议先使用官方安装器或者 VS Code 的 ESP-IDF 插件。手动方式当然也能做但 Windows 下 Python、Git、CMake、Ninja、交叉编译工具链之间的路径问题比较多新手很容易在真正写代码之前就卡住。我自己的环境路径类似这样textD:\esp\v6.0.1\esp-idf工程目录建议放在没有中文、没有空格的路径中例如textC:\esp\sensor_demo这不是迷信路径而是为了减少工具链、脚本和第三方组件在 Windows 下处理路径时的变量。后面接入板厂示例、LVGL 组件和本地传感器驱动时简单路径确实能省掉不少无意义问题。安装完成后先不要急着打开大工程先在 PowerShell 里确认idf.py能跑powershellpowershell -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。接下来可以先建一个空工程试一下工具链powershellcd C:\esppowershell -ExecutionPolicy Bypass -NoProfile -Command . D:\esp\v6.0.1\esp-idf\export.ps1; idf.py create-project sensor_democd C:\esp\sensor_demopowershell -ExecutionPolicy Bypass -NoProfile -Command . D:\esp\v6.0.1\esp-idf\export.ps1; idf.py set-target esp32s3powershell -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 示例跑通屏幕再在这个基础上删掉不需要的功能加入自己的传感器采集、网页后台和数据处理。当前工程结构可以理解成这样textC:\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更适合作为启动入口负责安排初始化顺序而不是承担所有业务判断。一个简化后的启动顺序可以写成这样cvoid 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 不是一个“气味识别芯片”。在这个原型里它更像一个环境变化观察点这个定位要先摆正。目前主要用到几类数据- 温度- 湿度- 气压- 气体阻值- 传感器状态。其中气体阻值是后续做环境变化判断时最关心的字段但它不能脱离温湿度和时间单独理解。传感器刚上电需要预热空气流动也会让短时读数波动。看到气体阻值突然变化时我不会马上把它解释成“识别到了某种气味”而是先看几个更基础的问题- 是否刚上电- 读数是否稳定- 温湿度是否同时变化- 变化持续了多久- 当前环境是否有明显干扰- 这个变化相对日常基线偏离了多少。网页后台里可以把这些数据放在一起看调试时比单看串口更直观。这一阶段不要急着给这些数据套复杂模型。更好的做法是先保留原始读数再做一层简单、可解释的平滑和基线。等采集到足够多的真实数据后再考虑更复杂的分类和识别。如果照着做建议一开始就把原始值、平滑值和基线值分开显示。只看最终分数很容易误判分数变了不知道是传感器真的变了还是平滑参数太敏感或者基线被带偏了。早期界面丑一点没关系字段越透明调试越省时间。读到数字以后还得把它变成状态只读到传感器数据还不够。这个小板子如果只输出一串数字看起来会很抽象调试起来也不方便。为了让读数变成状态我把处理过程拆得比较直白。传感器先按固定频率输出温湿度、气压和气体阻值。固件拿到数据后先判断是否还在预热再对读数做一层平滑避免单个尖峰直接影响状态。接着维护一个环境基线。这里的基线不是永远不变的常数而是当前环境在一段时间内的正常参考家庭环境、桌面环境、佩戴环境都会慢慢变化基线需要适度跟随但又不能被突发扰动马上带跑。最后才是异常评分。评分不是为了给出绝对安全结论而是把“偏离幅度”和“持续时间”合成一个更容易展示的状态。屏幕显示简化结果网页端展示更多细节。这个过程可以用一段伪代码表示ctypedef 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字段。它至少要能区分三类来源json{ 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才说明开发板正在产生实时数据。网页里也应该明确展示这个来源而不是只放一个漂亮的状态卡片。我是怎么确认它真的跑起来的这一版先不看外观有多像最终产品而是看数据有没有一路走通。实际检查时我会从最底层往上看。先确认开发板能正常编译和烧录。powershellcd C:\esp\sensor_demopowershell -ExecutionPolicy Bypass -NoProfile -Command . D:\esp\v6.0.1\esp-idf\export.ps1; idf.py buildpowershell -ExecutionPolicy Bypass -NoProfile -Command . D:\esp\v6.0.1\esp-idf\export.ps1; idf.py -p COM3 flash然后看串口里是否能识别到 BME690。textBME690 ready at 0x76, chip id 0x61T26.66C H42.35% P997.03hPa Gas26.64kOhm status0xb0这条日志至少说明三件事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 后台、前端页面和数据来源标记。它还不是成熟产品但已经足够支撑后续继续做开发板选型、传感器接入、界面显示和异常评分。对这类原型来说最重要的不是第一天就做出漂亮外壳而是每一步都能解释清楚数据从哪里来经过了什么处理页面上的状态依据是什么哪些结论现在还不能下。这个基础打稳了后面再谈随身夹扣、家庭底座和更多终端接入才不会变成空想。阅读顺序这一组文章建议按标题前面的序号阅读01 → 02 → 03 → 04 → 05 → 06 → 07 → 08 → 09 → 10。当前这篇是 01从想法到硬件原型用 ESP32-S3 做一个环境异常感知模块。下一篇下一篇 02开发板选型复盘为什么我先用 ESP32-S3-LCD-1.47 做原型