☰
ESP32+GC9A01打造智能恒湿箱:从硬件选型到控制逻辑全解析
2026/10/7 14:47:46 网站建设 项目流程

1. 从"雪茄柜"到"智能恒湿箱":这个项目到底在做什么

HC-01 这个名字听起来像某个工业控制器型号,实际上它是一个围绕 ESP32 和 GC9A01 圆形屏搭建的智能恒湿控制器。核心场景很明确:雪茄、茶叶、相机镜头、乐器这类对湿度极度敏感的物件,需要一个能长期稳定维持在特定湿度区间的密闭空间。市面上的成品恒湿柜动辄几千块,而自己动手做一套,成本可以压到两三百元以内,并且所有逻辑都掌握在自己手里。

这个项目的本质是一个闭环控制系统。ESP32 负责采集温湿度传感器的数据,跟设定的目标值做比较,然后决定加湿模块和除湿模块的启停。GC9A01 是一块 1.28 英寸的圆形 IPS 屏,分辨率 240x240,通过 SPI 接口驱动,用来实时显示当前温湿度、目标值、设备状态和运行时长。整个系统跑起来之后,你把它塞进一个密封箱里,它就能自己维持环境稳定,不需要你天天盯着。

适合谁来参考这个项目?如果你手上有 ESP32 开发板,玩过 Arduino 或者 ESP-IDF,会基本的焊接和接线,那这个项目对你来说就是一次很好的综合练习。它涉及传感器驱动、屏幕驱动、PID 或阈值控制逻辑、外设电源管理这几个典型的嵌入式模块。如果你是完全零基础的小白,也不用慌,我会把每个环节的来龙去脉讲清楚,你照着做也能跑通,只是调试阶段可能需要多一点耐心。

我之所以选 GC9A01 而不是常见的 OLED 或者 LCD1602,原因很直接:圆形屏装在木质或亚克力面板上,视觉上更像一个成品仪表,而不是一堆裸露的电子模块。而且 240x240 的分辨率足够显示多行数据和简单的图标,SPI 接口占用的引脚也少,ESP32 完全带得动。下面我会从硬件选型、接线、驱动配置、控制逻辑、外壳装配这几个维度,把整个项目的实操细节拆开来讲。

2. 硬件清单与选型逻辑:为什么是这些模块

2.1 主控与屏幕的搭配考量

ESP32 的型号很多,我手头用的是 ESP32-WROOM-32 模组,双核 240MHz,自带 WiFi 和蓝牙,GPIO 数量足够。选它不是因为性能过剩,而是因为生态成熟,Arduino 核心和 ESP-IDF 的支持都很完善,遇到问题搜一下基本都有答案。如果你手上有 ESP32-S3 或者 C3,也能用,但要注意引脚定义不同,尤其是 SPI 的默认引脚。

GC9A01 这块屏在淘宝和立创商城都能买到,价格大概十几到二十几块。它有几个版本,有的带触摸,有的不带。我建议买不带触摸的版本,因为恒湿箱的场景下你不需要频繁交互,触摸反而增加驱动复杂度。屏幕的驱动芯片是 GC9A01,分辨率 240x240,SPI 接口,支持 3.3V 逻辑电平,跟 ESP32 直接对接没问题。

这里有个细节要注意:GC9A01 的 SPI 时钟频率可以跑到 80MHz,但实际使用中我建议设在 40MHz 左右。太高的时钟频率在杜邦线连接的情况下容易出现花屏或者闪屏,尤其是线长超过 10cm 的时候。如果你用 PCB 走线,可以适当提高。

2.2 温湿度传感器的选择

温湿度传感器我试过三种:DHT22、SHT31 和 AHT20。DHT22 便宜,但精度一般,湿度误差在 ±2% 到 ±5% 之间,而且响应速度慢,两秒才能读一次。SHT31 精度高,I2C 接口,湿度误差 ±2%,但价格贵一些。AHT20 是性价比之选,I2C 接口,湿度误差 ±2%,价格只要几块钱,而且体积小。

最终我选了 AHT20,原因是它在精度和价格之间取得了很好的平衡,而且 I2C 接口接线简单,只需要 SDA 和 SCL 两根线。需要注意的是,AHT20 的 I2C 地址是 0x38,跟某些 OLED 屏的地址冲突,但 GC9A01 是 SPI 接口,不存在这个问题。

2.3 加湿与除湿模块的驱动方案

加湿模块我用的是超声波雾化片,5V 供电,通过一个继电器或者 MOS 管来控制通断。除湿模块我用的是半导体制冷片,配合散热风扇,把冷端的水汽凝结排出去。这两个模块的功率都不小,雾化片大概 1-2W,制冷片加上风扇大概 5-10W,所以不能直接用 ESP32 的 GPIO 驱动,必须通过继电器或者 MOS 管。

我推荐用光耦隔离的继电器模块,或者 IRF540N 这类 MOS 管配合续流二极管。继电器模块的好处是接线简单,直接 GPIO 给高电平就吸合,缺点是体积大、有机械寿命限制。MOS 管方案体积小、寿命长,但需要自己搭电路。如果你不想折腾电路,直接买现成的继电器模块最省事。

2.4 电源方案与功耗估算

整个系统的功耗分三部分:ESP32 加屏幕大概 0.5W,传感器忽略不计,加湿和除湿模块加起来峰值大概 12W。所以电源至少需要 5V 3A 的输出能力。我用的是一个 5V 3A 的开关电源适配器,通过 DC 接口接入,然后分两路:一路 5V 直接给继电器和雾化片,另一路经过 AMS1117-3.3 降压给 ESP32 和屏幕。

这里有个坑要注意:AMS1117 的压差是 1.1V 左右,5V 降到 3.3V 的时候,如果电流超过 500mA,芯片会发热严重。ESP32 加屏幕的电流大概在 150-200mA,问题不大,但如果你还挂了其他外设,建议换成 MP1584 或者 LM2596 这类开关降压模块,效率高、发热小。

模块型号工作电压峰值电流备注
主控ESP32-WROOM-323.3V240mAWiFi 开启时
屏幕GC9A013.3V40mASPI 接口
传感器AHT203.3V1mAI2C 接口
加湿超声波雾化片5V400mA继电器控制
除湿半导体制冷片5V2AMOS 管控制
降压AMS1117-3.35V转3.3V800mA建议换开关降压

3. 接线与引脚分配:一次讲清楚避免返工

3.1 ESP32 与 GC9A01 的 SPI 连接

GC9A01 的引脚定义如下:VCC、GND、SCL、SDA、RES、DC、CS、BLK。其中 SCL 是 SPI 时钟,SDA 是 SPI 数据,RES 是复位,DC 是数据/命令选择,CS 是片选,BLK 是背光控制。

我用的引脚分配是:SCL 接 GPIO18,SDA 接 GPIO23,RES 接 GPIO4,DC 接 GPIO2,CS 接 GPIO15,BLK 接 GPIO32。这里要注意,GPIO2 在 ESP32 上电时会有一个启动电平的要求,如果被拉低可能会进入下载模式,所以 DC 接 GPIO2 的时候要确保屏幕模块内部没有下拉电阻。如果遇到无法启动的问题,把 DC 换到 GPIO27 或者 GPIO14 就行。

背光控制我接的是 GPIO32,通过 PWM 调节亮度。如果你不需要调光,直接接 3.3V 也行,但屏幕会一直全亮,功耗和发热会大一点。

3.2 AHT20 的 I2C 接线

AHT20 只有四个引脚:VCC、GND、SDA、SCL。SDA 接 GPIO21,SCL 接 GPIO22,这是 ESP32 默认的 I2C 引脚。如果你用其他引脚,需要在代码里重新定义。I2C 总线上建议加上 4.7k 的上拉电阻,虽然很多模块自带上拉,但长线连接的时候外加上拉更稳定。

3.3 继电器与 MOS 管的控制引脚

加湿继电器我接的是 GPIO25,除湿 MOS 管接的是 GPIO26。这两个引脚都是普通的 GPIO,没有特殊功能,用起来最省心。继电器模块的 VCC 接 5V,GND 接公共地,IN 接 GPIO25。MOS 管的栅极通过一个 100 欧姆的电阻接 GPIO26,源极接地,漏极接制冷片的负极,制冷片的正极接 5V。

这里要特别注意:制冷片和雾化片都是感性负载,关断的时候会产生反向电动势,必须在负载两端并联续流二极管,否则很容易烧掉 MOS 管或者干扰 ESP32 的正常运行。我用的是 1N5819 肖特基二极管,压降小、恢复快。

3.4 电源分配与共地处理

整个系统的地线必须共地,也就是 ESP32 的 GND、屏幕的 GND、传感器的 GND、继电器模块的 GND、电源的 GND 全部连在一起。如果地线没接好,会出现屏幕花屏、传感器读数跳变、继电器误动作等各种奇怪的问题。

电源分配我建议用一块洞洞板或者 PCB 来做,把 5V 和 3.3V 分别引出排针,这样接线整洁,也方便排查故障。杜邦线虽然方便,但长期运行可靠性差,尤其是经过多次插拔之后接触电阻会变大。

提示:接线完成后,先不要接负载,只给 ESP32 和屏幕通电,确认屏幕能正常点亮、传感器能读到数据,再逐步接入继电器和负载。这样可以避免因为接线错误导致烧毁模块。

4. 开发环境搭建与 GC9A01 驱动配置

4.1 Arduino IDE 的 ESP32 支持包安装

如果你用 Arduino IDE,首先要在首选项里添加 ESP32 的开发板管理器地址。然后打开开发板管理器,搜索 esp32,安装最新版本。这里有个现实问题:国内网络环境下,下载支持包的速度可能很慢,甚至失败。我的做法是找离线的支持包,或者用 PlatformIO 配合离线包。

Arduino IDE 的 ESP32 支持包安装完成后,选择开发板为 "ESP32 Dev Module",端口选择对应的串口。上传速度我一般设为 921600,但如果你用的 USB 转串口芯片质量一般,降到 115200 更稳定。

4.2 GC9A01 库的选择与配置

GC9A01 的驱动库我用的是 TFT_eSPI,这个库支持多种屏幕驱动芯片,性能好,功能全。安装完库之后,需要修改 User_Setup.h 文件,把 GC9A01 的驱动打开,并配置引脚。

具体操作是:找到 TFT_eSPI 库目录下的 User_Setup.h,注释掉其他驱动的定义,取消 GC9A01_DRIVER 的注释。然后设置屏幕的宽度和高度为 240,设置 SPI 频率为 40000000。接着配置引脚,把 TFT_CS、TFT_DC、TFT_RST、TFT_SCLK、TFT_MOSI 分别改成你实际使用的 GPIO 编号。

这里有个容易忽略的点:TFT_eSPI 默认使用 VSPI 接口,对应 GPIO18、GPIO23、GPIO19、GPIO5。如果你用的引脚不在这个范围内,需要在 User_Setup.h 里定义 USE_HSPI_PORT 或者自定义 SPI 引脚。我建议直接用 VSPI 的默认引脚,省去很多麻烦。

4.3 AHT20 库的安装与测试

AHT20 我用的是 Adafruit AHTX0 库,安装简单,API 清晰。初始化的时候调用 aht.begin(),然后读取温度和湿度用 aht.getEvent()。需要注意的是,AHT20 上电后需要一点时间稳定,建议在 setup 里延时 100ms 再初始化。

测试的时候,先单独跑一个 I2C 扫描程序,确认能扫到 0x38 地址。如果扫不到,检查接线和上拉电阻。扫到地址之后,再跑 AHT20 的示例程序,看温湿度读数是否正常。如果读数一直是 NaN 或者固定值,可能是传感器损坏或者 I2C 通信失败。

4.4 编译速度优化与离线包方案

Windows 下编译 ESP32 项目速度慢是出了名的,尤其是第一次编译,可能要几分钟。我的优化经验是:关闭杀毒软件的实时扫描,把项目目录加到白名单;使用 PlatformIO 代替 Arduino IDE,因为 PlatformIO 的增量编译做得更好;如果条件允许,把项目放在 SSD 上,不要放在机械硬盘或者网络驱动器上。

离线包方案我试过 welinklab 的 ESP32 PlatformIO 离线包,把常用的库和工具链都打包好了,安装之后不需要联网就能编译。对于网络不稳定的环境,这个方案很实用。

5. 控制逻辑设计:阈值控制还是 PID

5.1 为什么我最终选了带死区的阈值控制

一开始我想用 PID 来控制湿度,因为 PID 听起来更专业,控制更平滑。但实际调试下来发现,恒湿箱这个场景跟温度控制不一样。湿度变化的惯性很大,加湿器开几秒钟,湿度可能几十秒后才开始上升。PID 的微分项在这种大滞后系统里很容易震荡,参数很难调。

最后我用了带死区的阈值控制:设定目标湿度是 70%,死区是 ±3%。也就是说,湿度低于 67% 的时候开加湿,高于 73% 的时候开除湿,在 67% 到 73% 之间的时候两个都不开。这样控制简单可靠,湿度会在 67% 到 73% 之间缓慢波动,对于雪茄和茶叶来说完全够用。

死区的大小可以根据你的箱体密封性和负载功率来调整。密封性好、负载功率大的话,死区可以设小一点,比如 ±2%。密封性差、负载功率小的话,死区设大一点,避免频繁启停。

5.2 加湿与除湿的互锁逻辑

加湿和除湿绝对不能同时开启,否则就是一边加水一边抽水,白白浪费电,还会让湿度剧烈波动。我在代码里加了互锁:如果加湿继电器处于开启状态,除湿 MOS 管强制关闭,反之亦然。这个逻辑在 loop 函数里每次循环都检查一遍,确保不会出现同时开启的情况。

另外,加湿和除湿切换的时候,我加了一个 30 秒的延时。比如加湿刚关闭,不能立刻开除湿,要等 30 秒让箱体内的水汽扩散均匀。这个延时可以通过变量来调整,根据你的箱体大小来定。

5.3 传感器读数滤波与异常处理

AHT20 的读数偶尔会跳变,尤其是电源波动或者 I2C 总线干扰的时候。我在代码里加了滑动平均滤波:每次读取的新数据跟之前 9 次的数据一起做平均,这样单次跳变对结果的影响就很小了。

异常处理也很重要。如果传感器连续 5 次读取失败,我会关闭加湿和除湿,并在屏幕上显示 "Sensor Error"。这样可以避免因为传感器故障导致加湿器一直开着,把箱子变成水帘洞。

// 滑动平均滤波示例 const int FILTER_SIZE = 10; float humidityBuffer[FILTER_SIZE]; int bufferIndex = 0; float getFilteredHumidity() { float sum = 0; humidityBuffer[bufferIndex] = aht.getHumidity(); bufferIndex = (bufferIndex + 1) % FILTER_SIZE; for (int i = 0; i < FILTER_SIZE; i++) { sum += humidityBuffer[i]; } return sum / FILTER_SIZE; }

5.4 屏幕界面的信息布局

GC9A01 是圆形屏,240x240 的分辨率,四个角是圆的,所以界面元素要尽量放在中间区域。我的布局是:中间大字号显示当前湿度,下面小字号显示当前温度,再下面是目标湿度和设备状态图标。最底部显示运行时长。

颜色我用的是深色背景配白色和青色文字,这样在暗光环境下不刺眼,而且省电。屏幕刷新我设的是每 500ms 一次,太快了没必要,反而增加 SPI 占用。

6. 外壳装配与传感器放置的实战经验

6.1 箱体密封与传感器位置

恒湿箱的箱体我用的是一个 20L 的亚克力密封箱,带密封条。箱体密封性越好,湿度维持越容易,加湿和除湿的启停频率也越低。如果你用的箱子密封性一般,可以用硅胶条或者密封胶带加强。

传感器的位置很关键。不要放在加湿器出雾口的正上方,否则读数会偏高;也不要放在箱体角落,那里空气流通差,读数不能代表整体湿度。我把它放在箱体中间偏上的位置,离加湿口大概 15cm,这样读数比较有代表性。

6.2 加湿与除湿模块的安装位置

加湿雾化片我装在一个小水盒里,水盒放在箱体底部,雾化片朝上。除湿的制冷片我装在箱体侧面,冷端在箱内,热端在箱外,配合风扇把热量排出去。这里要注意,制冷片的热端必须散热良好,否则制冷效率会急剧下降,甚至烧毁。

风扇我用的是 5V 4010 涡轮风扇,风量够用,噪音也小。风扇跟制冷片并联,一起开关。如果你觉得噪音大,可以加一个温控开关,只在温度超过阈值的时候才开风扇。

6.3 屏幕面板的开孔与固定

GC9A01 是圆形的,开孔需要用 32mm 或者 33mm 的开孔器。我建议先用纸板试一下孔径,确认屏幕能刚好卡进去,再在面板上开孔。屏幕固定我用的是热熔胶,简单可靠,但要注意不要涂太多,以免溢到屏幕表面。

面板我用的是一块 3mm 厚的黑色亚克力板,用激光切割的。如果你没有激光切割机,可以用手工锯加锉刀,但边缘会比较粗糙。亚克力板的好处是绝缘、易加工、外观也不错。

6.4 长期运行的稳定性检查

装好之后,我连续运行了 72 小时,记录湿度的波动曲线。实测下来,湿度稳定在 68% 到 72% 之间,波动幅度 4%,完全满足雪茄的保存要求。期间出现过一次传感器读数跳变,滑动平均滤波之后恢复正常。

长期运行还要注意电源的稳定性。我用的是质量好一点的开关电源,纹波小,不会干扰传感器和屏幕。如果你用的电源纹波大,可以在 5V 输出端并联一个 1000uF 的电解电容和一个 0.1uF 的陶瓷电容,滤除高频和低频噪声。

7. 调试过程中踩过的坑与排查思路

7.1 屏幕花屏与 SPI 时钟的关系

第一次点亮 GC9A01 的时候,屏幕花屏,显示的内容错位。我一开始以为是驱动库配置错了,换了几个库都一样。后来用逻辑分析仪抓 SPI 波形,发现时钟频率跑到 80MHz 的时候,数据线跟不上,导致数据错位。把 SPI 频率降到 40MHz 之后,花屏问题消失。

这个坑的教训是:杜邦线连接的时候,SPI 频率不要设太高。线越长、质量越差,能稳定工作的频率就越低。如果你非要用高频率,建议用排线或者 PCB 走线。

7.2 继电器误动作与电源干扰

调试的时候发现,加湿继电器吸合的瞬间,屏幕会闪一下,有时候传感器读数也会跳变。这是典型的电源干扰问题。继电器线圈在吸合和释放的时候会产生反向电动势,通过电源线传导到 ESP32 和屏幕。

解决办法是在继电器线圈两端并联一个续流二极管,同时在电源输入端加一个 LC 滤波电路。我加了一个 100uH 的电感和一个 470uF 的电容,问题就解决了。如果你不想折腾,直接用光耦隔离的继电器模块,干扰会小很多。

7.3 I2C 总线锁死与恢复机制

AHT20 偶尔会出现 I2C 总线锁死的情况,表现为读数一直是 NaN,重启之后恢复正常。这是 I2C 协议的一个已知问题:如果主设备在从设备发送数据的时候复位,从设备可能会一直拉低 SDA 线,导致总线锁死。

我的解决办法是在代码里加一个看门狗:如果连续 10 次读取失败,就重新初始化 I2C 总线。具体操作是把 SCL 引脚切换为普通 GPIO,发送 9 个时钟脉冲,然后重新初始化 I2C。这个操作可以解锁大部分 I2C 锁死的情况。

7.4 烧录失败与自动下载电路

ESP32 烧录的时候,需要把 GPIO0 拉低,然后复位。很多开发板自带了自动下载电路,用 USB 转串口芯片的 DTR 和 RTS 信号来控制。但有些便宜的开发板没有这个电路,需要手动按按钮。

如果你遇到烧录失败,先检查 GPIO0 是否被其他外设拉低。我一开始把 DC 接在 GPIO2 上,而 GPIO2 在启动时如果被拉低,会进入下载模式,导致程序不运行。后来把 DC 换到 GPIO27,问题解决。

7.5 湿度控制震荡与死区调整

最初我把死区设成了 ±1%,结果加湿器和除湿器频繁启停,湿度反而波动更大。这是因为湿度变化的滞后性,加湿器刚关,湿度还在上升,除湿器就开了,然后湿度又下降,加湿器又开,形成震荡。

把死区扩大到 ±3% 之后,启停频率明显降低,湿度波动也变小了。如果你的箱体密封性好,死区可以设小一点;密封性差的话,死区设大一点,避免频繁启停。

8. 后续可以扩展的方向与个人体会

这个项目跑通之后,我陆续加了一些扩展功能。一个是 WiFi 联网,通过 MQTT 把温湿度数据上传到本地服务器,用 Grafana 做可视化。另一个是 OTA 升级,不用拆机就能更新固件。还有一个是蓝牙配网,用手机 App 设置目标湿度和死区参数。

如果你也想做扩展,我建议先从 OTA 开始,因为调试阶段需要频繁改代码,OTA 能省很多事。然后是数据记录,把温湿度数据存到 SD 卡或者上传到服务器,方便分析长期趋势。最后再考虑联网控制,因为联网会增加功耗和复杂度,而且涉及到网络配置,调试起来比较麻烦。

个人体会方面,我觉得这个项目最大的价值不是省了多少钱,而是让你完整地走一遍"传感器采集、数据处理、执行器控制、人机交互"的闭环。这个经验在工业控制、智能家居、环境监测等领域都是通用的。另外,GC9A01 这块屏的可玩性很高,你可以用它做各种仪表盘、时钟、天气显示器,驱动配置学会了之后,换个项目直接复用。

最后分享一个小技巧:调试阶段,在屏幕上显示 ESP32 的剩余内存和运行时间,这样能快速判断系统是否稳定。如果剩余内存持续下降,说明有内存泄漏;如果运行时间经常复位,说明电源或者看门狗有问题。这个习惯帮我省了很多排查时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询