基于ESP32与GC9A01的智能雪茄柜温湿度控制器DIY

发布时间:2026/10/9 4:36:27
基于ESP32与GC9A01的智能雪茄柜温湿度控制器DIY
1. 项目缘起与整体设计思路雪茄柜这个东西玩过的人都知道温湿度控制是核心痛点。市面上能买到的成品电子雪茄柜动辄大几千而便宜的柜子要么湿度控制粗糙要么根本没有精确调节能力。我自己抽雪茄有些年头了一直想做一个能精确控制温湿度、还能实时显示数据的控制器于是就有了这个 HC-01 Smart Humidor Controller 项目。这个项目的核心思路很直接用ESP32做主控搭配GC9A01 圆形 TFT 屏幕做本地显示界面通过温湿度传感器采集柜内环境数据再用MOSFET驱动半导体制冷片和加湿模块实现温湿度的闭环控制。外壳部分用3D 打印完成整体做成一个圆形的桌面控制器放在雪茄柜旁边或者直接嵌入柜体。为什么选 ESP32 而不是 Arduino Uno 或者 STM32原因有几个。第一ESP32 自带 WiFi 和蓝牙后续想加手机端监控或者接入智能家居平台非常方便不用额外挂模块。第二ESP32 的 ADC 和 PWM 资源足够用控制两路 MOSFET 输出绰绰有余。第三Arduino IDE 对 ESP32 的支持已经非常成熟开发门槛低社区资源丰富遇到问题基本都能搜到答案。第四ESP32 的价格便宜一块开发板二三十块钱就能搞定性价比极高。GC9A01 这块屏幕也值得说一下。它是一块 1.28 英寸的圆形 IPS 屏分辨率 240x240驱动芯片是 GC9A01通过 SPI 接口通信。选它的理由很简单圆形屏幕做仪表盘界面天然好看而且尺寸小巧适合嵌入到 3D 打印的圆形外壳里。市面上很多智能恒温器的界面风格都可以参考做出来的效果很精致。整体方案的设计逻辑是这样的传感器负责采集ESP32 负责运算和决策MOSFET 负责执行屏幕负责人机交互。这四个环节形成一个完整的闭环。温湿度传感器我选的是 SHT31I2C 接口精度比常见的 DHT22 高不少温度精度 ±0.3°C湿度精度 ±2%RH对于雪茄养护来说完全够用。如果你手头只有 DHT22 或者 SHT30也能用代码稍作修改就行。注意雪茄的理想养护环境是温度 18-21°C湿度 65-72%RH。温度过高会导致烟草虫孵化湿度过高会发霉湿度过低则雪茄会干裂。所以控制精度直接决定了雪茄的保存质量。外壳设计方面我用 Fusion 360 画了一个圆形壳体前面板开孔安装 GC9A01 屏幕背面留了传感器通风孔和电源接口。3D 打印材料用的 PETG比 PLA 更耐温和耐湿适合放在雪茄柜附近这种湿度较高的环境。打印参数后面会详细说。这个项目适合什么人参考如果你有一点 Arduino 基础玩过 ESP32想做一个实用的温湿度控制项目那这个非常适合你。如果你是完全的新手也没关系我会把每个环节拆开讲清楚包括接线、代码、调试和避坑经验。2. 核心硬件选型与电路设计细节2.1 主控与屏幕的搭配逻辑ESP32 的型号很多我手头用的是 ESP32-WROOM-32 开发板38 引脚的那种。选它是因为引脚数量够用而且市面上最常见的就是这款价格便宜资料多。如果你用的是 ESP32-S3 或者 ESP32-C3大部分代码是兼容的只需要注意引脚定义和外设差异。GC9A01 屏幕的接线方式采用 SPI 接口需要连接的引脚包括屏幕引脚ESP32 引脚说明VCC3.3V供电千万别接 5VGNDGND共地SCLGPIO 18SPI 时钟SDAGPIO 23SPI 数据RESGPIO 4复位DCGPIO 2数据/命令切换CSGPIO 15片选BLKGPIO 32背光控制可接 PWM这里要特别注意GC9A01 的供电电压是 3.3V绝对不能接 5V否则屏幕直接烧掉。我第一块屏幕就是因为这个原因报废的血的教训。另外背光引脚 BLK 可以接普通 GPIO 做开关控制也可以接 PWM 引脚做亮度调节。我接的是 GPIO 32用ledcWrite做亮度调节晚上看起来不刺眼。SPI 引脚的选择不是随便定的。ESP32 有两组硬件 SPI默认的 VSPI 引脚是 GPIO 18SCK、GPIO 23MOSI、GPIO 19MISO、GPIO 5CS。但 GC9A01 只需要单向通信MISO 用不上所以 CS 可以随便选一个 GPIO。我用 GPIO 15 做 CSGPIO 2 做 DCGPIO 4 做 RES这些引脚在 ESP32 上都是普通 GPIO没有特殊功能冲突。2.2 温湿度传感器的选择与对比温湿度传感器我最终选了 SHT31但在此之前我试过 DHT22 和 SHT30。这里做一个简单的对比传感器温度精度湿度精度接口价格适用场景DHT22±0.5°C±2-5%RH单总线约10元入门级精度一般SHT30±0.3°C±3%RHI2C约15元中端性价比高SHT31±0.3°C±2%RHI2C约25元高精度推荐SHT35±0.2°C±1.5%RHI2C约50元专业级DHT22 的问题在于响应速度慢而且湿度精度在低湿度区间偏差较大。SHT31 的 I2C 接口用起来很方便两根线就能搞定而且支持 CRC 校验数据可靠性高。接线方式是 VCC 接 3.3VGND 接 GNDSDA 接 GPIO 21SCL 接 GPIO 22。ESP32 的 I2C 默认引脚就是这两个直接用就行。实操心得SHT31 的 I2C 地址默认是 0x44如果你买的是带 ADDR 引脚的版本拉高 ADDR 可以改成 0x45。多个传感器共用 I2C 总线时要注意地址冲突。2.3 MOSFET 驱动电路的设计考量控制半导体制冷片和加湿模块需要大电流ESP32 的 GPIO 输出电流只有几十毫安根本驱动不了。所以必须用 MOSFET 做开关控制。我选的是 IRLZ44N这是一款逻辑电平 N 沟道 MOSFETVgs(th) 低至 1V用 3.3V 的 GPIO 就能完全导通不需要额外的电平转换电路。MOSFET 的接线方式如下栅极Gate接 ESP32 的 GPIO 25PWM 控制漏极Drain接负载负极制冷片或加湿模块源极Source接 GND负载正极接 12V 电源正极这里有一个关键点MOSFET 的栅极和源极之间需要接一个 10kΩ 的下拉电阻防止 ESP32 启动时 GPIO 浮空导致 MOSFET 误导通。另外负载两端要反向并联一个续流二极管比如 1N5819用来吸收电感性负载断电时产生的反向电动势。虽然制冷片和加湿模块不完全是感性负载但加湿模块里的风扇电机是感性的加一个二极管更保险。PWM 频率的选择也有讲究。半导体制冷片不适合高频 PWM因为频繁开关会影响制冷效率而且会产生噪音。我用的 PWM 频率是 1kHz占空比根据温度偏差动态调整。加湿模块的 PWM 频率可以高一些5kHz 左右这样雾化片工作更平稳。注意半导体制冷片工作时会产生大量热量必须配散热片和风扇。我一开始没加散热片制冷片热端温度直接飙到 80°C差点烧坏。后来加了一个 40mm 的散热风扇温度稳定在 45°C 左右。2.4 电源方案与功耗估算整个系统的电源需求如下模块电压电流功耗ESP323.3V240mA峰值约0.8WGC9A013.3V40mA约0.13WSHT313.3V1mA约0.003W半导体制冷片12V3A36W加湿模块5V500mA2.5W散热风扇12V200mA2.4W总功耗大约 42W所以电源至少要用 12V 5A 的适配器。ESP32 和屏幕的 3.3V 供电可以用一块 AMS1117-3.3 降压模块从 12V 转下来但 AMS1117 的压差较大12V 转 3.3V 时发热严重建议用 MP1584 或者 LM2596 这种开关降压模块效率高发热小。我在实际搭建时用的是 12V 5A 的电源适配器经过一个 LM2596 降压模块输出 5V 给加湿模块和 ESP32 开发板的 VIN 引脚ESP32 板载的 AMS1117 再转 3.3V 给核心和屏幕供电。这样分工明确发热分散稳定性好。3. 软件架构与核心代码实现3.1 开发环境搭建与库依赖开发环境我用的是 Arduino IDE 2.x 版本配合 ESP32 开发板支持包。安装步骤如下打开 Arduino IDE进入“文件”-“首选项”在“附加开发板管理器网址”中填入 ESP32 的板管理器地址。打开“工具”-“开发板”-“开发板管理器”搜索“esp32”安装最新版本我用的 2.0.14 版本比较稳定。安装完成后在开发板列表中选择“ESP32 Dev Module”。安装必要的库TFT_eSPI用于驱动 GC9A01、Adafruit_SHT31用于读取传感器、ArduinoJson后续扩展用。TFT_eSPI 库的配置是个坑。安装完库之后需要修改User_Setup.h文件选择 GC9A01 驱动并定义引脚。具体修改如下#define GC9A01_DRIVER #define TFT_WIDTH 240 #define TFT_HEIGHT 240 #define TFT_MOSI 23 #define TFT_SCLK 18 #define TFT_CS 15 #define TFT_DC 2 #define TFT_RST 4 #define TFT_BL 32 #define LOAD_GLCD #define LOAD_FONT2 #define LOAD_FONT4 #define SPI_FREQUENCY 40000000SPI 频率我设的是 40MHzGC9A01 最高支持 80MHz但 40MHz 已经足够流畅而且稳定性更好。如果你发现屏幕有花屏或者闪烁可以降到 27MHz 试试。实操心得TFT_eSPI 库的User_Setup.h文件在库文件夹里但如果你用的是 PlatformIO可以在项目目录下创建User_Setup.h覆盖默认配置这样升级库的时候不会丢失配置。3.2 温湿度采集与数据滤波SHT31 的读取代码比较简单用 Adafruit_SHT31 库几行就能搞定。但直接读出来的数据会有波动尤其是湿度值跳动可能达到 ±3%RH。所以我在代码里加了一个滑动平均滤波取最近 10 次采样的平均值这样显示出来的数据稳定很多。#include Adafruit_SHT31.h Adafruit_SHT31 sht31 Adafruit_SHT31(); const int FILTER_SIZE 10; float tempBuffer[FILTER_SIZE]; float humBuffer[FILTER_SIZE]; int filterIndex 0; float getFilteredTemp() { float sum 0; for (int i 0; i FILTER_SIZE; i) { sum tempBuffer[i]; } return sum / FILTER_SIZE; } void readSensor() { float t sht31.readTemperature(); float h sht31.readHumidity(); if (!isnan(t) !isnan(h)) { tempBuffer[filterIndex] t; humBuffer[filterIndex] h; filterIndex (filterIndex 1) % FILTER_SIZE; } }采样间隔我设的是 2 秒一次太快了没必要SHT31 的转换时间本身就要 15ms 左右。2 秒的间隔既能保证实时性又不会给 I2C 总线太大压力。3.3 PID 控制算法的实现与调参温湿度控制的核心是 PID 算法。我用的是位置式 PID输出值直接映射到 PWM 占空比。温度控制和湿度控制各用一套 PID 参数。struct PID { float Kp, Ki, Kd; float integral; float prevError; float outputMin, outputMax; }; float computePID(PID pid, float setpoint, float input) { float error setpoint - input; pid.integral error; float derivative error - pid.prevError; float output pid.Kp * error pid.Ki * pid.integral pid.Kd * derivative; pid.prevError error; if (output pid.outputMax) { output pid.outputMax; pid.integral - error; // 抗积分饱和 } else if (output pid.outputMin) { output pid.outputMin; pid.integral - error; } return output; }温度 PID 参数我调的是 Kp80, Ki2, Kd200。湿度 PID 参数是 Kp60, Ki1.5, Kd150。这些参数是根据实际调试得出的不同柜体体积和保温性能可能需要微调。调参的经验是这样的先把 Ki 和 Kd 设为 0只调 Kp从小到大增加直到系统开始振荡然后取振荡临界值的 60% 作为 Kp。接着加 Ki从很小开始慢慢增加消除稳态误差。最后加 Kd抑制超调。整个过程需要耐心我大概花了一个下午才调到自己满意的效果。注意PID 的积分项一定要做抗饱和处理否则系统启动时误差很大积分项会累积到很大的值导致输出长时间饱和温度会冲过头。3.4 GC9A01 界面设计与刷新策略GC9A01 的界面我用 TFT_eSPI 库的 Sprite 功能做的先在内存里绘制好一整帧画面再一次性推送到屏幕。这样做的好处是不会闪烁刷新率高。界面布局是这样的屏幕中央显示当前温度和湿度大字体一眼就能看清。外圈用圆弧进度条表示湿度百分比颜色从蓝色渐变到绿色。底部显示设定值和当前工作状态制冷/加湿/待机。TFT_eSPI tft TFT_eSPI(); TFT_eSprite spr TFT_eSprite(tft); void drawUI(float temp, float hum, float targetTemp, float targetHum) { spr.createSprite(240, 240); spr.fillSprite(TFT_BLACK); // 绘制外圈圆弧 int arcAngle map(hum, 0, 100, 0, 360); spr.drawSmoothArc(120, 120, 110, 100, 0, arcAngle, TFT_GREEN, TFT_BLACK, true); // 显示温度 spr.setTextColor(TFT_WHITE, TFT_BLACK); spr.setTextDatum(MC_DATUM); spr.drawFloat(temp, 1, 120, 100, 6); spr.drawString(C, 160, 100, 4); // 显示湿度 spr.drawFloat(hum, 1, 120, 140, 4); spr.drawString(%RH, 170, 140, 2); spr.pushSprite(0, 0); spr.deleteSprite(); }刷新策略是每 500ms 刷新一次界面但只在数据变化超过阈值时才重绘避免不必要的 SPI 传输。实测下来GC9A01 在 40MHz SPI 下刷新一帧 240x240 的画面大约需要 15ms完全不影响传感器采集和控制循环。3.5 外部中断与按键交互我加了一个物理按键用于切换显示页面和手动调节设定值。按键接在 GPIO 0 上用外部中断检测按下事件。ESP32 的外部中断配置如下volatile bool buttonPressed false; void IRAM_ATTR buttonISR() { buttonPressed true; } void setup() { pinMode(0, INPUT_PULLUP); attachInterrupt(digitalPinToInterrupt(0), buttonISR, FALLING); } void loop() { if (buttonPressed) { buttonPressed false; // 处理按键逻辑 currentPage (currentPage 1) % PAGE_COUNT; } }这里要注意中断服务函数必须用IRAM_ATTR修饰否则可能因为 Flash 访问导致中断响应延迟。另外按键消抖在中断里不做而是在主循环里用millis()做软件消抖这样中断响应快逻辑也清晰。4. 3D 打印外壳设计与装配实录4.1 外壳结构设计与打印参数外壳用 Fusion 360 设计分为前壳、后壳和屏幕压环三个部分。前壳开孔直径 33mm正好容纳 GC9A01 的显示区域。后壳留了传感器通风孔和 M3 螺丝孔。屏幕压环用来固定屏幕用 M2 螺丝拧在前壳上。打印参数如下参数值说明材料PETG耐温耐湿适合高湿度环境层高0.2mm兼顾精度和速度壁厚1.2mm3 层墙足够结实填充20%网格填充省材料打印温度240°CPETG 的标准温度热床温度80°C保证首层附着打印速度50mm/s慢一点质量更好PETG 的打印要注意回抽设置否则容易拉丝。我用的回抽距离是 6mm回抽速度 25mm/s效果不错。另外 PETG 不容易粘热床我涂了一层固体胶首层附着很稳。实操心得如果你只有 PLA也能用但 PLA 在湿度高的环境里会慢慢降解强度会下降。如果雪茄柜湿度长期在 70%RH 左右建议还是用 PETG 或者 ABS。4.2 装配流程与注意事项装配顺序很重要搞错了就得拆了重来。我的装配流程是这样的先把 SHT31 传感器用热熔胶固定在后壳的通风孔内侧注意传感器不要被胶堵住感应面。把 GC9A01 屏幕放入前壳的凹槽里注意屏幕排线朝下。用屏幕压环压住屏幕拧上 M2 螺丝不要拧太紧屏幕是玻璃的受力过大会裂。把 ESP32 开发板和 MOSFET 模块用 M3 铜柱固定在后壳内侧。焊接所有连接线注意线长要留够方便合壳。合上前壳和后壳拧上 M3 螺丝。最后插上电源测试功能。焊接的时候有个小技巧先用杜邦线测试好所有功能确认没问题再焊接。焊接完了再想改线就很麻烦。我一开始就是先焊了再测结果发现屏幕引脚接错了拆焊花了半小时。4.3 散热与通风设计半导体制冷片的散热是个大问题。我在后壳上开了两个 40mm 的风扇孔一个进风一个出风形成风道。散热片用 40x40x20mm 的铝挤散热片用导热硅脂贴在制冷片热端。通风孔的设计也有讲究。传感器所在的区域要和制冷片的气流隔开否则传感器读到的温度会被制冷片的热端影响。我用一块隔板把后壳内部分成两个区域一侧是传感器和电路板另一侧是制冷片和散热风扇。这样传感器读到的才是柜内真实温度。注意如果你把传感器和散热风扇放在同一个区域温度读数会偏高 3-5°C导致控制系统一直以为温度不够低制冷片会一直满功率工作既费电又伤设备。5. 常见问题排查与调试经验5.1 屏幕不亮或花屏的排查思路GC9A01 屏幕不亮是最常见的问题排查顺序如下现象可能原因解决方法完全黑屏供电不足或接错检查 VCC 是否接 3.3VGND 是否共地背光亮但无显示SPI 通信失败检查 MOSI、SCLK、CS、DC 接线花屏或闪烁SPI 频率过高降低 SPI_FREQUENCY 到 27MHz颜色异常颜色顺序错误修改 TFT_RGB_ORDER 为 TFT_BGR显示偏移分辨率设置错误确认 TFT_WIDTH 和 TFT_HEIGHT 为 240我遇到过一次花屏折腾了半天以为是接线问题最后发现是 SPI 频率设成了 80MHz降到 40MHz 就好了。所以遇到花屏先降频这是最快的排查方法。5.2 传感器读数异常的解决方法SHT31 读数异常通常有几种情况读数一直是 NaNI2C 通信失败。检查 SDA 和 SCL 是否接反或者传感器供电是否正常。用 I2C 扫描程序确认传感器地址是否能被识别。湿度读数偏高传感器表面有冷凝水。把传感器从高湿度环境取出在干燥环境放置 2 小时再试。温度读数偏高传感器靠近发热元件。检查传感器是否离 ESP32 或降压模块太近挪远一点。读数跳动大电源纹波大。在传感器 VCC 和 GND 之间并联一个 100nF 电容能明显改善。5.3 MOSFET 发热与开关异常MOSFET 发热严重通常是因为没有完全导通。IRLZ44N 在 Vgs3.3V 时 Rds(on) 大约是 0.05Ω通过 3A 电流时功耗是 0.45W会有点温热但不会烫手。如果你摸上去烫手说明 MOSFET 没有完全导通可能是 GPIO 电压不够或者 MOSFET 型号不对用了非逻辑电平的型号。开关异常的表现是负载时开时关或者不受控制。这通常是栅极下拉电阻没接或者 PWM 信号受到干扰。检查下拉电阻是否焊接PWM 线是否远离大电流线路。实操心得调试 MOSFET 电路时先用万用表测栅极电压。如果栅极电压在 3.3V 和 0V 之间正常切换说明控制信号没问题问题在负载侧。如果栅极电压一直是高电平或者浮空检查 GPIO 配置和下拉电阻。5.4 Arduino IDE 编译与烧录常见错误ESP32 在 Arduino IDE 里编译慢是出了名的尤其是第一次编译可能要几分钟。几个提速技巧关闭杀毒软件的实时扫描或者把 Arduino 的编译目录加入白名单。用 SSD 而不是机械硬盘编译速度差别很大。如果一直卡在“正在编译”检查是否有库冲突尤其是 TFT_eSPI 和其他的显示库同时安装时容易冲突。烧录失败的话检查以下几点开发板型号是否选对ESP32 Dev Module。端口是否选对Windows 下通常是 COMxMac 下是 /dev/cu.usbserial-xxx。如果一直显示“Connecting...”按住开发板上的 BOOT 键再点上传看到“Writing”再松开。烧录波特率降到 115200 试试有些板子对高波特率支持不好。6. 系统优化与功能扩展方向6.1 温湿度控制精度的进一步优化目前系统的控制精度可以做到温度 ±0.5°C湿度 ±3%RH。如果想进一步提高可以从几个方面入手第一把 SHT31 换成 SHT35湿度精度从 ±2%RH 提升到 ±1.5%RH温度精度从 ±0.3°C 提升到 ±0.2°C。第二在柜内不同位置放多个传感器取平均值避免局部环境偏差。第三优化 PID 参数加入前馈控制根据开门事件提前调整输出。我试过在柜门上加一个磁簧开关开门时自动暂停 PID 输出关门后延迟 30 秒再恢复。这样避免了开门时湿度骤降导致加湿模块猛喷关门后湿度又冲过头的问题。6.2 接入 WiFi 与手机端监控ESP32 的 WiFi 功能还没用上后续可以加一个 Web 服务器在手机浏览器里查看实时数据和历史曲线。用 ESPAsyncWebServer 库可以很方便地实现。数据存储可以用 SPIFFS 或者 LittleFS存最近 7 天的数据每小时一个采样点。如果想接入智能家居平台可以用 MQTT 协议把数据推送到 Home Assistant 或者类似平台。ESP32 上跑 MQTT 客户端很轻松PubSubClient 库几行代码就能搞定。6.3 低功耗与断电恢复策略如果想让控制器在断电后自动恢复需要在代码里保存设定值到 NVS非易失性存储。ESP32 的 Preferences 库可以很方便地读写 NVS。#include Preferences.h Preferences prefs; void saveSettings(float t, float h) { prefs.begin(humidor, false); prefs.putFloat(targetTemp, t); prefs.putFloat(targetHum, h); prefs.end(); } void loadSettings() { prefs.begin(humidor, true); targetTemp prefs.getFloat(targetTemp, 20.0); targetHum prefs.getFloat(targetHum, 68.0); prefs.end(); }低功耗方面如果控制器用电池供电可以启用 ESP32 的深度睡眠模式每隔几分钟唤醒一次采集数据。但这个项目通常是用 12V 适配器常供电所以低功耗不是重点。6.4 外壳的迭代改进第一版外壳用下来有几个问题屏幕压环的螺丝孔太浅拧两次就滑丝了后壳的通风孔太小散热风扇风量不够。第二版我把螺丝孔加深了 1mm通风孔从 3mm 扩大到 5mm散热效果明显改善。如果你要打印这个外壳建议先用 0.2mm 层高打一个测试件确认屏幕和开发板能装进去再打最终版。3D 打印的尺寸公差通常在 ±0.2mm 左右设计的时候留 0.3mm 的间隙比较保险。这个项目从构思到完成大概花了两周时间其中调试 PID 和 3D 打印外壳占了大半时间。实际用下来雪茄柜的湿度稳定性比之前用保湿包好了很多温度也能控制在 20°C 以下。如果你也在为雪茄养护发愁不妨试试这个方案成本不到两百块效果不比成品柜差。