多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

ESP32+GC9A01打造智能雪茄柜控制器:温湿度闭环与圆形UI实战

ESP32+GC9A01打造智能雪茄柜控制器:温湿度闭环与圆形UI实战 1. 从雪茄柜到智能控制器这个项目到底在解决什么问题雪茄保存这件事说简单也简单说讲究也是真讲究。核心就两个指标温度和湿度。温度高了容易长虫湿度低了茄衣开裂湿度高了又会发霉。传统做法是扔一个机械湿度计进去再配一盒保湿包隔三差五打开柜子看一眼。问题是机械表精度感人保湿包吸放湿完全被动你根本不知道柜内真实状态等发现不对劲的时候一柜子雪茄可能已经废了。HC-01 这个项目要干的事情就是把这套被动保存逻辑换成主动闭环控制。用ESP32做主控配GC9A01 圆形 LCD做本地显示加上温湿度传感器、继电器驱动加湿/除湿模块做成一个能实时监测、自动调节、本地可视化的雪茄柜控制器。说白了就是给你的雪茄柜装一个大脑。为什么选 ESP32 而不是 Arduino UNO 或者 STM32几个很实际的理由。第一ESP32 自带 WiFi 和蓝牙后面想加手机端查看数据、OTA 升级、接入 Home Assistant 都是顺手的事不用额外挂模块。第二双核 240MHz 的主频跑 LVGL 图形界面绰绰有余GC9A01 这种 240x240 的圆屏刷新起来毫无压力。第三GPIO 数量够用I2C 接传感器、SPI 接屏幕、几路继电器输出引脚分配很从容。第四社区生态成熟Arduino 框架和 ESP-IDF 都能用出问题搜得到答案。GC9A01 这块圆屏是这个项目的视觉核心。1.28 寸、240x240 分辨率、SPI 接口驱动芯片就是 GC9A01。它最大的特点是圆形装在雪茄柜面板上比方形屏好看太多而且圆形界面天然适合做仪表盘式的温湿度显示——指针转一圈、环形进度条视觉上很直观。这块屏在 Arduino 环境下用 TFT_eSPI 或者 LVGL 都能驱动成本也就十几块钱性价比很高。这个项目适合谁如果你手上有雪茄柜或者打算做一个恒温恒湿箱又有一点 Arduino 基础那这个项目非常适合练手。它涉及传感器读取、PID 控制逻辑、SPI 屏幕驱动、UI 设计、继电器控制这几个典型嵌入式模块做完一遍对 ESP32 的综合运用能力会有明显提升。就算你不玩雪茄这套逻辑换成养爬宠的温湿度箱、蘑菇培养箱、酒柜代码改改参数就能直接用。2. 硬件选型与接线每一根线都有它的道理2.1 主控与屏幕的搭配逻辑ESP32 的型号很多这个项目我推荐用ESP32-WROOM-32或者ESP32-S3都行。WROOM-32 是经典款资料最多价格便宜S3 性能更强USB 原生支持烧录更方便。如果你手上是 ESP32-S3 核心板比如 1-N16R8 那种 16MB Flash 8MB PSRAM 的配置在 PlatformIO 里选板子的时候要选esp32-s3-devkitc-1然后手动在platformio.ini里指定 Flash 和 PSRAM 大小不然编译出来可能跑不起来。GC9A01 屏幕和 ESP32 之间走 SPI 通信。SPI 的好处是速度快刷屏不卡。接线的时候有几个关键引脚必须注意GC9A01 引脚ESP32 引脚说明VCC3.3V千万别接 5V会烧GNDGND共地SCLGPIO 18SPI 时钟SDAGPIO 23SPI 数据RESGPIO 4复位DCGPIO 2数据/命令切换CSGPIO 15片选BLKGPIO 32背光控制可接 PWM 调亮度这里有个坑要提前说GC9A01 的背光引脚BLK如果直接接 3.3V屏幕会一直最亮晚上刺眼。建议接到一个支持 PWM 的 GPIO 上用ledcWrite调亮度这样可以根据环境光或者时间段自动调节。另外 SPI 的 MOSI 和 SCLK 引脚在 ESP32 上是可以重映射的但为了省事直接用默认的 VSPI 引脚18/23最稳。2.2 温湿度传感器的选择温湿度传感器我用的是SHT31I2C 接口精度 ±0.3°C / ±2%RH响应快长期稳定性好。为什么不用 DHT22DHT22 便宜是便宜但精度 ±0.5°C / ±2%RH 其实也够用问题在于它的采样率太低最快 0.5Hz而且长时间在高湿环境下容易漂移。雪茄柜湿度常年 65%-75%DHT22 用几个月就可能不准了。SHT31 的漂移率低得多而且 I2C 接线简单两根线搞定。SHT31 接 ESP32 的 I2C 引脚SDA 接 GPIO 21SCL 接 GPIO 22。地址默认 0x44如果模块上有 ADDR 引脚拉高就是 0x45。I2C 总线上拉电阻一般模块自带了不用额外加。如果你预算紧张也可以用AHT20价格比 SHT31 便宜一半精度 ±0.3°C / ±2%RHI2C 接口Arduino 库也成熟。实测下来 AHT20 在湿度 70% 左右的环境里表现稳定性价比很高。2.3 执行机构加湿与除湿怎么驱动雪茄柜的加湿和除湿不能直接靠 ESP32 的 GPIO 驱动电流不够。必须通过继电器或者 MOSFET 模块来控制。加湿方案有两种一是用超声波雾化片通过继电器控制通断二是用半导体制冷片做冷凝除湿。加湿我用的是一个小型超声波雾化模块工作电流 300mA 左右5V 供电继电器控制 220V 或者直接控制 5V 通断都行。除湿我用的是一个 5V 的小风扇加速空气流通配合干燥剂盒继电器控制风扇启停。继电器模块选光耦隔离的那种避免继电器线圈反向电动势干扰 ESP32。接线的时候继电器 IN 引脚接 ESP32 的 GPIO比如 GPIO 25 和 GPIO 26VCC 接 5VGND 共地。注意继电器的触发电平有的是高电平触发有的是低电平触发买的时候看清楚代码里对应设置。注意如果你要控制 220V 的加湿器强电部分一定要做好绝缘继电器模块和 ESP32 之间保持足够距离走线分开。安全第一不懂强电就别碰直接用 5V 的雾化模块最省心。3. 软件环境搭建从零到点亮的完整路径3.1 Arduino IDE 还是 PlatformIO这个问题我被问过无数次。如果你只是快速验证Arduino IDE 上手最快装个 ESP32 开发板包就能跑。但如果你要做稍微复杂一点的项目我强烈建议用PlatformIO VSCode。原因很简单库管理更清晰、编译速度更快、代码补全和调试体验好太多。Arduino IDE 装 ESP32 支持的方法文件 - 首选项 - 附加开发板管理器网址填入https://espressif.github.io/arduino-esp32/package_esp32_index.json然后在开发板管理器里搜索 esp32 安装。国内网络下载可能很慢可以找离线包手动安装。装完之后选板子 ESP32 Dev Module 就行。PlatformIO 的platformio.ini配置大概长这样[env:esp32dev] platform espressif32 board esp32dev framework arduino monitor_speed 115200 lib_deps adafruit/Adafruit SHT31 Library^2.2.2 bodmer/TFT_eSPI^2.5.43 lvgl/lvgl^8.3.11 build_flags -D USER_SETUP_LOADED1 -D ILI9341_DRIVER0 -D GC9A01_DRIVER1 -D TFT_WIDTH240 -D TFT_HEIGHT240 -D TFT_MOSI23 -D TFT_SCLK18 -D TFT_CS15 -D TFT_DC2 -D TFT_RST4 -D SPI_FREQUENCY40000000这里有个细节TFT_eSPI 库默认不支持 GC9A01需要在User_Setup.h里手动开启GC9A01_DRIVER。用 PlatformIO 的build_flags方式可以避免直接改库文件升级库的时候不会丢配置。SPI 频率设 40MHz 实测很稳再高可能会花屏。3.2 屏幕驱动验证先点亮再谈其他拿到屏幕第一件事不是写业务逻辑而是先跑一个最简单的测试程序确认屏幕能亮、颜色正确、方向对。TFT_eSPI 库自带例子但 GC9A01 需要改配置。最简单的验证代码#include TFT_eSPI.h TFT_eSPI tft TFT_eSPI(); void setup() { tft.init(); tft.setRotation(0); tft.fillScreen(TFT_BLACK); tft.setTextColor(TFT_WHITE, TFT_BLACK); tft.setTextSize(2); tft.setCursor(60, 100); tft.println(HC-01); tft.setCursor(40, 130); tft.println(Humidor Ctrl); } void loop() {}如果屏幕显示正常说明 SPI 接线和驱动配置都没问题。如果花屏或者白屏先检查TFT_RST引脚有没有接对再检查 SPI 频率是不是太高降到 20MHz 试试。GC9A01 对时序比较敏感杜邦线太长也会导致花屏尽量用短一点的线。3.3 LVGL 图形界面的引入时机TFT_eSPI 直接画图够用但要做漂亮的圆形仪表盘LVGL 会省很多事。LVGL 有现成的圆弧控件lv_arc、标签、按钮做出来的界面比手动画圆润得多。不过 LVGL 有一定学习成本建议先把 TFT_eSPI 跑通再逐步迁移到 LVGL。LVGL 和 TFT_eSPI 配合需要配置lv_conf.h把LV_TICK_CUSTOM打开用millis()做时基。显示缓冲区大小设成屏幕的 1/10 就行240x240 的话 240*24 字节ESP32 内存完全够。4. 核心控制逻辑温湿度闭环是怎么跑起来的4.1 传感器读取与数据滤波SHT31 读取很简单Adafruit 库几行代码搞定#include Adafruit_SHT31.h Adafruit_SHT31 sht31 Adafruit_SHT31(); void setup() { Wire.begin(21, 22); if (!sht31.begin(0x44)) { Serial.println(SHT31 not found); } } float readTemp() { return sht31.readTemperature(); } float readHum() { return sht31.readHumidity(); }但直接读回来的数据会有波动尤其是加湿器启停的瞬间湿度值可能跳变好几个百分点。如果直接用原始值做控制判断继电器会频繁开关既吵又伤设备。所以必须做滤波。我用的是滑动平均滤波维护一个长度为 10 的环形缓冲区每次读新值替换最旧的值然后取平均。这样数据平滑很多继电器动作也稳定。代码大概这样#define FILTER_SIZE 10 float humBuffer[FILTER_SIZE]; int humIndex 0; float filteredHumidity(float newVal) { humBuffer[humIndex] newVal; humIndex (humIndex 1) % FILTER_SIZE; float sum 0; for (int i 0; i FILTER_SIZE; i) sum humBuffer[i]; return sum / FILTER_SIZE; }温度变化慢滤波窗口可以小一点5 个就够了。湿度响应快窗口大一点更稳。4.2 回差控制与 PID 的取舍温湿度控制最简单的做法是回差控制也叫滞环控制设定目标湿度 70%当湿度低于 68% 时开加湿高于 72% 时停。中间 68%-72% 是死区不动作。这样继电器不会频繁启停逻辑简单可靠。那要不要上 PID我的经验是雪茄柜这种大惯性、慢响应的系统PID 调参非常痛苦而且收益不大。加湿器开一下湿度从 65% 升到 70% 可能要十几分钟PID 的微分项根本捕捉不到有效变化积分项又容易饱和。回差控制配合合理的死区宽度实际效果完全够用。死区宽度怎么定看你的加湿/除湿能力。如果加湿器功率大死区可以宽一点比如 ±3%如果加湿很慢死区窄一点±1.5%。我实测下来 ±2% 是个比较平衡的值湿度基本能稳定在 68%-72% 之间雪茄保存完全没问题。4.3 继电器动作的安全逻辑继电器控制有几个必须注意的点。第一上电瞬间所有继电器必须处于关闭状态避免突然加湿或者除湿。在setup()里先把控制引脚设为 OUTPUT 并写 HIGH假设低电平触发或者 LOW高电平触发确保初始关闭。第二加湿和除湿不能同时开启。虽然逻辑上不太可能同时触发但代码里要加互锁如果加湿继电器开着除湿继电器强制关闭反之亦然。第三增加最小动作间隔。即使回差控制已经减少了动作频率还是建议加一个最小间隔保护比如两次加湿动作之间至少间隔 60 秒。防止传感器异常导致继电器疯狂开关。unsigned long lastHumAction 0; #define MIN_ACTION_INTERVAL 60000 void controlHumidity(float hum) { if (millis() - lastHumAction MIN_ACTION_INTERVAL) return; if (hum TARGET_HUM - HYSTERESIS) { digitalWrite(RELAY_HUMIDIFY, LOW); // 开启加湿 digitalWrite(RELAY_DEHUMIDIFY, HIGH); // 确保除湿关闭 lastHumAction millis(); } else if (hum TARGET_HUM HYSTERESIS) { digitalWrite(RELAY_DEHUMIDIFY, LOW); // 开启除湿 digitalWrite(RELAY_HUMIDIFY, HIGH); // 确保加湿关闭 lastHumAction millis(); } else { digitalWrite(RELAY_HUMIDIFY, HIGH); digitalWrite(RELAY_DEHUMIDIFY, HIGH); } }5. 圆形屏幕的 UI 设计让数据一眼就能看懂5.1 圆形布局的视觉逻辑GC9A01 是圆屏240x240 的有效区域是个直径 240 的圆。方形 UI 放上去四个角会被切掉所以布局必须按圆形来设计。我的方案是中心显示湿度大数字外圈用圆弧表示湿度范围顶部和底部显示温度和状态图标。中心区域半径 60 像素以内放主数据比如湿度值 70.2%字号用 4 号白色。外圈画一个圆弧从 135 度到 405 度也就是顺时针从左上到右上弧的填充比例对应湿度百分比。圆弧颜色根据状态变化低于目标值显示蓝色加湿中高于目标值显示橙色除湿中在范围内显示绿色正常。温度显示在圆弧上方字号小一点比如 22.5°C。底部显示 WiFi 连接状态和继电器状态图标。这样一眼看过去最关键的湿度信息最醒目其他信息辅助参考。5.2 用 LVGL 画圆弧仪表盘LVGL 的lv_arc控件做这个很合适。创建一个 arc 对象设置背景弧和前景弧前景弧的 value 绑定湿度值。代码框架lv_obj_t *arc lv_arc_create(lv_scr_act()); lv_obj_set_size(arc, 200, 200); lv_obj_center(arc); lv_arc_set_bg_angles(arc, 135, 405); lv_arc_set_range(arc, 0, 100); lv_arc_set_value(arc, (int)humidity); lv_obj_remove_style(arc, NULL, LV_PART_KNOB); // 隐藏旋钮 lv_obj_clear_flag(arc, LV_OBJ_FLAG_CLICKABLE);然后在中心放一个 label 显示湿度数值用lv_obj_align居中对齐。温度 label 放在 arc 上方用lv_obj_align_to相对 arc 对齐。LVGL 的刷新需要定期调用lv_timer_handler()放在loop()里配合delay(5)就行。注意不要用delay()阻塞太久否则界面会卡。5.3 刷新频率与性能平衡屏幕不需要每帧都刷新。温湿度数据每秒读一次但 UI 可以每 500ms 更新一次这样既流畅又不浪费 CPU。LVGL 的 label 设置文本时如果文本没变它不会重绘所以直接每秒lv_label_set_text也没问题。背光亮度调节用ledcSetup和ledcWrite频率设 5000Hz分辨率 8 位。晚上 10 点到早上 7 点自动降到 30% 亮度其他时间 80%。这个逻辑用millis()判断当前时间就行不需要 RTC 模块。6. 实测中的坑与排查记录6.1 屏幕花屏与 SPI 冲突第一次点亮 GC9A01 的时候屏幕显示正常但偶尔会闪一下白线。排查了半天发现是 SPI 频率设太高80MHz降到 40MHz 就稳了。另外ESP32 的 VSPI 默认引脚是 18/23/19/5我用了 15 做 CS2 做 DC4 做 RST这些都没问题但如果你同时用了 SD 卡或者其他 SPI 设备要确保 CS 引脚不冲突。还有一个坑TFT_eSPI 库的User_Setup.h里如果同时定义了多个驱动编译会报错。用 PlatformIO 的build_flags方式指定GC9A01_DRIVER1最干净不要手动改库文件。6.2 传感器读数跳变SHT31 在加湿器刚启动的时候湿度读数会突然升高 5%-8%这是因为雾化片附近的水汽还没扩散均匀。如果直接用这个值做控制加湿器会立刻停然后湿度扩散后又降下来又开反复震荡。解决办法有两个一是把传感器放在远离加湿口的位置让水汽先混合均匀二是加大滤波窗口我用 15 个样本的滑动平均基本消除了这个跳变。另外加湿器启动后加一个 30 秒的忽略期这段时间内不根据湿度值做判断等水汽稳定了再说。6.3 继电器干扰导致 ESP32 复位这个问题很隐蔽。继电器动作的瞬间ESP32 偶尔会重启。用示波器看电源纹波发现继电器线圈断电时产生了一个负向尖峰通过共地路径干扰了 ESP32 的供电。解决方案第一继电器模块单独供电不要和 ESP32 共用同一个 5V 电源第二在继电器线圈两端加一个续流二极管1N4148 就行第三ESP32 的电源引脚加一个 100uF 电解电容和 0.1uF 陶瓷电容滤波。做完这三步再没出现过复位。6.4 WiFi 连接与 OTA 的注意事项ESP32 连 WiFi 会消耗不少电流如果电源供电不足连 WiFi 的时候可能会重启。建议用 5V/2A 的电源适配器别用电脑 USB 口供电。OTA 升级很方便但要注意分区表。默认的分区表可能不够大如果代码超过 1MB需要选 Minimal SPIFFS 或者自定义分区表。PlatformIO 里在platformio.ini加board_build.partitions min_spiffs.csv就行。7. 后续可以扩展的方向这个项目做完基础版本之后有几个很自然的扩展方向。第一个是接入 Home Assistant通过 MQTT 把温湿度数据发出去手机随时看曲线还能远程改目标值。ESP32 跑 MQTT 很轻松PubSubClient 库几行代码就能搞定。第二个是加一个蜂鸣器做报警。湿度超过 75% 或者低于 60% 持续 10 分钟蜂鸣器响几声提醒。这个逻辑简单但很实用尤其是出门几天的时候。第三个是数据记录。加一个 microSD 卡模块每分钟记录一次温湿度存成 CSV 文件。回头可以分析雪茄柜的湿度波动规律优化控制参数。SD 卡和屏幕共用 SPI 总线注意 CS 引脚分开就行。第四个是多柜联动。如果你有多个雪茄柜可以用 ESP-NOW 或者 MQTT 把数据汇总到一个主控上统一显示和控制。ESP-NOW 不需要路由器点对点通信延迟很低适合这种场景。我在实际使用中最大的体会是控制逻辑越简单越可靠。一开始我也想过用模糊控制、自适应 PID 这些花哨的方案但实测下来回差控制配合合理的死区和滤波稳定性反而最好。嵌入式项目稳定运行比算法先进重要得多。
返回列表