目录

摘要

绪论

系统总体设计方案

系统功能需求

总体框图

软件架构概述

硬件系统设计

核心元器件选型

单片机选型

温湿度传感器选型

显示屏选型

旋转编码器选型

加湿器的选型

单片机管脚介绍

ESP32-S3其余外设简介

元器件的连接

引脚的选择

器件连接协议

SPI接口协议(OLED)

单总线协议(DHT11)

正交编码器协议

GPIO与ADC

软件系统设计

主程序流程图

库的使用

Arduino 核心库(ESP32 框架自带)

第三方库

关键代码及说明(仅含部分关键代码)

主控调度(SmartHumidifier.ino)

三模式加湿器控制(control.cpp)

编码器按键(control.cpp)

OLED顶部状态栏(display.cpp)

电池检测(hardware.cpp)

Web API路由(wifiweb.cpp)

LED操作反馈+心跳(control.cpp)

串口命令(serialcmd.cpp)

系统调试与测试

调试过程

串口控制说明

OLED屏幕调试

DHT11温湿度传感器调试

EC11旋转编码器调试

继电器控制测试

电池检测校准

系统联调

总结与展望

设计成果总结

存在问题

改进方向

操作手册

摘要

针对家用及便携场景下环境湿度自动调节的需求,本文设计并实现一款基于ESP32-S3的便携式智能加湿器控制系统(v2.0)。系统以ESP32-S3为主控核心,采用DHT11数字温湿度传感器采集环境数据,通过SPI接口的0.95寸彩色OLED显示屏实现多模式人机交互,利用EC11旋转编码器完成参数设置与模式切换,通过继电器驱动加湿器雾化模块,并新增板载WS2812 RGB LED实现操作反馈与系统心跳指示。软件上实现自动恒湿、手动开关、定时关闭三种工作模式,集成WiFi热点、Web页面自动校时、OLED顶部WiFi状态指示、四阶段开机动画,同时保留电池电量检测、软件计时、串口对时以及低电/缺水多重保护功能。系统采用模块化设计,硬件连接简洁,控制逻辑清晰,3.7V锂电池供电稳定运行,具备低功耗、低成本、操作便捷、无线互联与可视化反馈强的特点,适用于宿舍、卧室等小型空间的湿度管理。

注意,该项目目前仅限于实验验证,具真正家用尚段距离

绪论

本项目旨在利用ESP32-S3高性能低功耗微控制器,配合常见的DHT11温湿度传感器和OLED显示屏,构建一个具备自动、手动、定时三种工作模式的智能加湿器控制系统。系统通过软件实现非对称回差控制(低于目标-3%开启,高于目标+2%关闭),避免加湿器频繁启停,延长设备寿命;同时配备旋转编码器作为唯一输入设备,实现简洁高效的单手操作逻辑,符合家用电器的使用习惯;利用WiFi热点、Web远程控制、浏览器自动校时、WS2812 LED视觉反馈、四阶段开机动画、OLED WiFi状态指示;以旋转编码器实现单手高效操作,完善便携化、智能化、交互友好性,为小型智能家居设备提供可复用的升级方案。该设计在满足基本加湿功能的同时,强调了便携化、智能化与安全保护,为小型智能家居设备的开发提供了参考方案。

系统总体设计方案

  1. 系统功能需求

本项目所设计的智能加湿器需实现以下核心功能:

1. 环境监测:通过DHT11传感器实时采集环境温度和湿度。

2. 信息显示:OLED三模式切换,顶部栏具有警告、WiFi、电量、运行状态等。

3. 智能控制:自动、手动、定时三模式,支持硬件、串口、web三端操控方式,迟滞回差控制(-3%/+2%)。

4. 交互输入:使用EC11旋转编码器编辑模式、调节参数、切换、控制开关等操作。

5. 安全保护:低电量/缺水强制停机,红色警告标识。

6. 时间与对时:软件时钟+串口校时+Web浏览器自动校时。

7. 视觉反馈:WS2812 LED操作反馈(1000ms)+系统心跳(100ms/秒)。

8. 无线连接:WiFi热点无线连接,支持AP+STA双模式。

9. 开机交互:四阶段水滴涟漪+打字机开机动画,全程LED同步规律闪烁。

10. 远程控制:Web API支持模式/湿度/定时/校时/开关远程设置。

总体框图

系统采用模块化硬件架构,以ESP32-S3开发板为中心,外接DHT11温湿度传感器、彩色OLED显示屏、EC11旋转编码器、继电器驱动模块及电池检测电路。

软件架构概述

系统软件遵循分层模块化设计。

分层模块化:主程序→显示模块→LED控制模块→硬件模块→串口命令模块→WiFi Web模块。

主循环:编码器事件→时钟更新→串口/Web指令→加湿器控制→倒计时更新→LED状态管理(ledLoop)→传感器读取→界面局部刷新。

为提升显示流畅度,程序采用了局部刷新策略:仅当数据变化超过阈值时才重绘对应显示区域。

硬件系统设计

核心元器件选型

单片机选型

选用ESP32-S3开发板作为主控核心。ESP32-S3具有双核Xtensa LX7处理器,主频最高240MHz,内置512KB SRAM和8MB PSRAM,集成Wi-Fi 4 (802.11 b/g/n)无线通信模块。丰富的GPIO(共45个可用)、ADC、SPI、I2C、UART等外设接口,支持USB OTG和JTAG在线调试。板载CH343P USB转串口芯片,方便程序下载和串口调试。WS2812可编程RGB LED接GPIO48,可实现彩色状态指示。

图 1管脚复用图

温湿度传感器选型

选用DHT11数字温湿度传感器模块。该模块采用单总线数字通信,连接仅需一个数据引脚,内置上拉电阻。虽然精度(湿度±5%RH,温度±2℃)略低于更高等级的传感器,但其成本极低、驱动简单,足以满足一般恒湿控制的需求。

图 2DHT11模块

显示屏选型

选用基于SSD1331驱动芯片的0.95寸彩色OLED显示屏模块,分辨率为96x64像素,采用SPI串行通信接口。与单色屏相比,彩色显示可以更直观地用不同颜色区分工作状态(如编辑模式红色、定时绿色等),提升人机交互体验。SPI接口保证了较高的数据传输速率,使界面刷新流畅。

图 3OLED显示屏模块

旋转编码器选型

选用EC11数字旋转编码器模块,集成了360度旋转及一个按键开关。旋转轴上带有A、B两相脉冲输出,可通过相位差判断旋转方向;按键为高电平有效。其机械寿命长、操作手感清晰,一个器件即可完成参数调整、模式切换等多种输入功能,符合便携式设备对简洁外观的要求。

加湿器的选型

选用超声雾化加湿模块(微孔雾化片 + 驱动电路) ,工作频率 108kHz,雾化量约 200-350mL/h。其原理是利用压电陶瓷的高频谐振将液态水打散成 1-5μm 的微细水雾颗粒,通过内置风扇或自然扩散排出。相较传统加热蒸发式加湿器,功耗低(典型 2-4W)、无热风险、即开即出雾、体积小巧,特别适合 USB 供电或电池供电的便携式场景。与 ESP32-S3 的 GPIO 经继电器模块控制启停,配合 DHT11 温湿度传感器实现自动恒湿调节,满足桌面/车载等小空间加湿需求。

图 5加湿片

单片机管脚介绍

本章仅介绍本项目中实际用到的ESP32-S3管脚及外设功能。

引脚

功能

GPIO1

ADC电池检测(ADC1_CH0),模拟输入

GPIO4

继电器控制,推挽输出(高电平开启,低电平关闭)

GPIO5

编码器CLK,输入上拉

GPIO6

编码器DT,输入上拉

GPIO7

编码器SW(按键),输入上拉

GPIO8

DHT11温湿度传感器,单总线数字输入/输出

GPIO15

OLED-SCL(SPI时钟),GPIO矩阵映射

GPIO16

OLED-SDA(SPI MOSI),GPIO矩阵映射

GPIO17

OLED-RES(硬件复位),推挽输出

GPIO18

OLED-DC(数据/命令选择),推挽输出

GPIO21

OLED-CS(片选),推挽输出

表 2本项目用到的管脚与外设

串口(UART):本项目仅使用UART0(TXD=GPIO43,RXD=GPIO44),板载CH343P芯片转USB-C与电脑通信,波特率115200,用于程序下载与串口调试。

板载外设:WS2812 RGB灯(GPIO48)用于操作反馈闪烁(红色,持续1秒)及系统心跳(每秒亮100ms)。另有TX/RX指示灯(由CH343P控制)和PWR电源指示灯。

ESP32-S3其余外设简介

除上述已使用的管脚外,ESP32-S3还具备以下本项目未涉及的外设功能,仅作简要介绍:

触摸传感器(Touch):14个通道(GPIO1~14),支持电容式触摸检测和Deep Sleep唤醒,本项目未使用。

RTC低功耗域:GPIO0~21属于RTC GPIO域,在深度睡眠模式下可保持电平或作为唤醒源,本项目未启用休眠功能故未涉及。

SPI控制器:共4个,SPI0/1供内部Flash使用,SPI2(FSPI)和SPI3(VSPI)可供用户使用。本项目采用软件SPI通过GPIO矩阵映射到任意引脚,未占用硬件SPI控制器。

JTAG调试接口:TMS=GPIO42、TDI=GPIO41、TDO=GPIO40、TCK=GPIO39,支持在线调试,本项目通过串口调试故未使用。

USB OTG:USB_D-=GPIO19、USB_D+=GPIO20,本项目通过板载CH343P串口通信,未使用USB OTG功能。

PSRAM占用:GPIO26~37通常被8MB PSRAM占用,不可用作普通GPIO。

元器件的连接

系统各模块与ESP32-S3的引脚连接如表3所示。

器件

型号/参数

引脚连接

OLED显示屏(SPI)

SSD1331(96x64)

SCL→GPIO15

SDA→GPIO16

RES→GPIO17

DC→GPIO18

CS→GPIO21

DHT11温湿度传感器

单总线数字输出

DATA→GPIO8

EC11旋转编码器

AB相+按键

CLK→GPIO5

DT→GPIO

SW→GPIO7

电池检测(分压电路)

2x10kΩ电阻分压

ADC→GPIO1

加湿器控制(继电器模块)

低电平触发

OUT→GPIO4

表 3元器件连接表

引脚的选择

原则说明:避开Strapping引脚GPIO0/3/45/46影响启动模式;避开JTAG引脚GPIO39~42用于USB-JTAG调试;避开PSRAM/Flash引脚GPIO26~37通常被PSRAM占用;功能复用匹配ADC/I2C/SPI等有固定引脚映射;避免上电电平冲突部分引脚上电瞬间有默认电平。

器件

引脚

原因

旋转编码器

CLK→GPIO5

DT→GPIO6

SW→GPIO7

-编码器只需要普通GPIO+内部上拉,任何可用GPIO都行

-选了连续的三个引脚(5/6/7),走线整齐,模块化接线方便

-GPIO5/6/7没有特殊功能冲突,安全可靠

-使用INPUT_PULLUP,省去外部上拉电阻

OLED显示屏

SCL→GPIO15

SDA→GPIO16

RES→GPIO17

DC→GPIO18

CS→GPIO21

-使用的不是ESP32的硬件SPI接口

-通过ESP32-S3的GPIO矩阵功能将信号映射到GPIO15~18、21,方便模块化接线

-GPIO15~18四个连续引脚+GPIO21单独引脚,分两组排线连接

-所有引脚未被编码器(5-7)、DHT11(8)、继电器(4)占用

-避开了ADC1_CH0(GPIO1)和Strapping引脚

-任何硬件外设的输出信号,可以被路由到任何一个GPIO引脚上

DHT11传感器

DATA→GPIO8

-DHT11是单总线协议,只需要一个普通GPIO

-GPIO8在编码器区块(5~7)旁边,传感器区集中布线

-没有特殊功能冲突

继电器

RELAY→GPIO4

-继电器只需要电平输出(HIGH/LOW),任何GPIO都行

-上电默认LOW(关闭加湿器)是安全设计——启动时不会误开

-GPIO4断开连接的用电器,和其他外设不冲突

电池检测

BAT→GPIO1

-必须用ADC引脚,ESP32-S3有两个12位SAR ADC

-ADC1_CH0对应GPIO1,是ADC1的第一个通道

-ADC1不受Wi-Fi干扰(ADC2在Wi-Fi开启时不可用)

-GPIO1恰好没有被其他外设占用

器件连接协议

SPI接口协议(OLED)

OLED显示屏使用4线SPI(Serial Peripheral Interface)协议与ESP32通信,ESP32作为主机。引脚映射及协议参数如下:

-CS:片选信号,低电平有效,用于选中从设备。

-DC:数据/命令选择线,高电平发送显示数据,低电平发送命令。

-SDA(MOSI):主机数据输出,从机数据输入,传输显示数据及命令。

-SCL(SCK):SPI时钟信号,由主机产生。

-RES:硬件复位线,由GPIO控制。

实际操作时,通过Adafruit_SSD1331库的构造函数传入引脚号,库内部自动完成SPI初始化,通信时钟频率及数据格式已在库中预设,用户无需干预底层时序。

单总线协议(DHT11)

DHT11采用特有的单总线(1-Wire)半双工通信。模块内部有一个约4.7kΩ的上拉电阻。一次完整的数据传输包含40位数据(湿度整数、湿度小数、温度整数、温度小数、校验和),由MCU发送开始信号后,DHT11拉低-拉高回应,随后输出40位数据。位数据"0"和"1"通过高电平持续时间区分("0"约为26-28μs,"1"约为70μs)。本项目使用DHTesp库已完整封装该时序,只需调用getTempAndHumidity()即可获取浮点数值。

正交编码器协议

EC11编码器输出A、B两路方波信号。旋转时,A、B信号产生相位差。顺时针旋转时A相位超前B相位90度;逆时针旋转时B相位超前A相位90度。通过读取当前A电平并在A电平变化瞬间检测B电平,即可判断旋转方向。本项目采用轮询方式读取CLK引脚状态,与上一次状态比较并配合消抖,实现旋转方向识别。引脚配置为INPUT_PULLUP,无外部上拉电阻。

GPIO与ADC

-继电器控制:GPIO4设为推挽输出,低电平关闭,高电平开启,驱动继电器模块接通加湿器(加湿器模块自带的控制方式为电平控制)。

-编码器按键(SW):GPIO7设为INPUT_PULLUP,按下为低,松开为高,通过电平变化和时长判断短按、长按与双击。

-电池检测(BAT_PIN):使用ESP32内部ADC1接口,配置为模拟输入。外部采用2个10kΩ电阻组成1/2分压电路,将电池电压(3.2V~4.2V)降至ADC可容忍范围(0~2.1V)。ADC采样值经线性换算得到实际电池电压,再通过映射计算电量百分比。

软件系统设计

主程序流程图

系统主程序流程图如图3所示。系统上电→硬件初始化→四阶段开机动画(涟漪效果→中心光晕→HUMIDIFIER逐字显示→READY闪烁)→进入主界面→主循环。

图 6智能加湿器系统总体架构

库的使用

Arduino 核心库(ESP32 框架自带)

库名

用途

使用的位置

<Arduino.h>

Arduino 核心框架( millis() , analogRead() , digitalWrite() 等)

control.cpp , hardware.cpp , serialcmd.cpp , wifiweb.cpp

<SPI.h>

SPI 通信协议(OLED 屏幕)

SmartHumidifier.ino

第三方库

库名

用途

说明

DHTesp ( "DHTesp.h" )

DHT11 温湿度传感器驱动

读取温度和湿度数据

Adafruit_SSD1331 ( <Adafruit_SSD1331.h> )

SSD1331 OLED 屏幕驱动屏幕)

96×64 彩色 OLED 显示核心

Adafruit_GFX ( <Adafruit_GFX.h> )

图形绘制库

Adafruit 通用图形基类(提供 drawCircle , fillRect , print 等)

Adafruit_BusIO

Adafruit 总线 I/O 抽象层

SSD1331 的底层依赖(SPI 设备抽象)

WiFi ( <WiFi.h> )

ESP32 Wi-Fi 功能

AP 热点 + STA 客户端模式,内嵌 Web 服务器

Preferences ( <Preferences.h> )

ESP32 非易失性存储 (NVS)

保存/读取 Wi-Fi 配网凭据

SPI

SPI 驱动库

OLED 屏幕的硬件 SPI 通信

关键代码及说明(仅含部分关键代码)

图 7vs code项目目录

主控调度(SmartHumidifier.ino)

// 软件SPI模式驱动OLED(通过GPIO矩阵路由到任意引脚)
Adafruit_SSD1331 display = Adafruit_SSD1331(OLED_CS, OLED_DC, OLED_RES);
void setup() {
  Serial.begin(115200);
  SPI.begin(OLED_SCL, -1, OLED_SDA, OLED_CS);  // 软件SPI,MISO=-1未使用
  display.begin();
  dht.setup(DHT_PIN, DHTesp::DHT11);
  pinMode(RELAY_PIN, OUTPUT); digitalWrite(RELAY_PIN, LOW);
  bootAnim();
  switchPage(PAGE_MONITOR);
  wifiInit();
}
void loop() {
  serialPoll(); wifiHandle(); ntpCheck();
  encKey(); encRot(); clickPoll(); updateTime();
  controlHumidifier(); timerTick();
  serialWatchPoll(); ledLoop();
  // 每2秒读取DHT11并刷新OLED
}

逻辑结构:setup()顺序初始化各模块:串口→SPI总线→OLED→传感器→继电器→开机动画→默认页面→WiFi。loop()采用平铺轮询结构,每个周期依次调用所有模块的tick函数,各模块内部通过millis()时间戳自行判断是否到了执行时机,模块之间无调用依赖。传感器读取和屏幕刷新由loop()尾部的2秒间隔判断分支统一触发。

三模式加湿器控制(control.cpp)

图 4三模式加湿器控制逻辑

void controlHumidifier() {
  bool want = false;
  switch (workMode) {
    case MODE_AUTO:  // 带迟滞:湿度<目标-3%开启,>目标+2%关闭
      if      (humi < targHumi - 3) want = true;
      else if (humi > targHumi + 2) want = false;
      else                           want = relayOn;
      break;
    case MODE_MANU:  want = manualOn;             break;
    case MODE_TIMER: want = timerRemainSec > 0;   break;
  }
  if (waterLack || lowBatWarn) want = false;  // 缺水/低电强制关闭
  if (want != relayOn) {
    relayOn = want;
    digitalWrite(RELAY_PIN, want ? HIGH : LOW);
    ledFlash();
  }
}

逻辑结构:两级决策。第一级:根据workMode分支计算加湿需求——AUTO走三段if-else迟滞窗(低于下限→开/高于上限→关/窗内→保持),MANU直接取manualOn,TIMER判断timerRemainSec>0。第二级:安全保护——waterLack或lowBatWarn为真时强制覆盖want=false。最后仅当want!=relayOn时才操作继电器,避免冗余翻转。

编码器按键(control.cpp)

void controlHumidifier() {
  bool want = false;
  switch (workMode) {
    case MODE_AUTO:  // 带迟滞:湿度<目标-3%开启,>目标+2%关闭
      if      (humi < targHumi - 3) want = true;
      else if (humi > targHumi + 2) want = false;
      else                           want = relayOn;
      break;
    case MODE_MANU:  want = manualOn;             break;
    case MODE_TIMER: want = timerRemainSec > 0;   break;
  }
  if (waterLack || lowBatWarn) want = false;  // 缺水/低电强制关闭
  if (want != relayOn) {
    relayOn = want;
    digitalWrite(RELAY_PIN, want ? HIGH : LOW);
    ledFlash();
  }
}

逻辑结构:encKey()在按键释放时执行,以按下时长>800ms为界分长按/短按。长按直接执行;短按不立即执行,转由clickPoll()在loop()中轮询——等待300ms,若期间无第二次按下(即非双击),才执行单击逻辑。双击由300ms窗口内第二次encKey()触发,在短按分支内通过pendingClick和clickCount联合判断。子页(HUMISET/TIMER)无单击/双击概念,长短按分别对应保存和放弃。

OLED顶部状态栏(display.cpp)

图 5OLED顶部状态栏逻辑图

void encKey() {
  // ...消抖后判断按键释放
  bool isLong = (now - keyDownAt > 800);  // >800ms长按
  if (currPage == PAGE_MONITOR) {
    if (isLong) {
      // 长按:AUTO→编辑目标湿度 TIMER→编辑定时 MANU→翻转电池监控
      switch (workMode) { /*...*/ }
    } else {
      // 短按:延迟300ms确认(区分单击/双击)
      pendingClick = true; pendingClickAt = now;
    }
  } else {
    switchPage(PAGE_MONITOR);  // 子页:长按保存/短按放弃
  }
}
void clickPoll() {
  if (!pendingClick || millis() - pendingClickAt < 300) return;
  pendingClick = false;
  workMode = (workMode + 1) % 3;  // 单击:MANU→AUTO→TIMER循环切换
  drawTopBar(); drawMonitorData(); drawBotTip();
}

逻辑结构:先清空96×10顶部区域,然后按固定x坐标分5个区域顺序绘制。区域1采用if-else优先级链:缺水/低电→红色"!"并短路跳过后续;WiFi开启→青色"W"。后续区域无分支依赖,按固定布局直接绘制模式字母、时间字符串、继电器指示方块、电池图标。

电池检测(hardware.cpp)

void readBattery() {
  long sum = 0;
  for (int i = 0; i < 4; i++) sum += analogRead(A0);
  int raw = (int)(sum / 4);
  long mv = (long)raw * 3300 / 4095 * 2;  // mV=raw×3.3V/4095×2分压
  battery = constrain(map(mv, 3300, 4200, 0, 100), 0L, 100L);
  lowBatWarn = (battery < 15);
}

逻辑结构:四步流水线:①4次ADC采样累加取算术平均,消除随机噪声;②将12位ADC值换算为实际电压(raw×3.3V/4095得到分压点电压,×2还原电池端电压);③将电压线性映射到0~100%并钳位边界;④与阈值15%比较,设置低电警告布尔标志。调用方由loop()中2秒定时器触发,且受batteryMonitorOn开关控制。

Web API路由(wifiweb.cpp)

图 6Web API路由逻辑图

// 页面内嵌HTML(PROGMEM存储,节省RAM)
static const char INDEX_HTML[] PROGMEM = R"raw(
<!DOCTYPE html>...完整仪表盘HTML/CSS/JS...
)raw";
// RESTful API路由
if (path == "/api/mode")    { /*设置工作模式*/ }
if (path == "/api/target")  { targHumi = parse(body); ledFlash(); }
if (path == "/api/manual")  { manualOn = parse(body); ledFlash(); }
if (path == "/api/timer")   { timerSetMin = parse(body); ledFlash(); }
if (path == "/api/time")    { currentHour/Minute = parse(body); ledFlash(); }
// AP模式启动
WiFi.mode(WIFI_AP_STA);
WiFi.softAP("SmartHumidifier", "12345678", 1);
// NTP校时(STA联网后每小时一次)
configTime(28800, 0, "ntp.aliyun.com", "pool.ntp.org");
if (getLocalTime(&ti, 1500)) { currentHour = ti.tm_hour; ... }

逻辑结构:wifiInit()启动AP+STA双模式——AP供手机直连,STA从NVS读取已保存凭据自动连接路由器。wifiHandle()在每个loop周期以非阻塞方式accept()客户端连接,解析首行HTTP请求获取方法和路径,按路径字符串匹配路由到对应处理分支。GET请求直接拼JSON返回;POST请求先读Content-Length取body,再解析参数写入全局变量。ntpCheck()仅在staConnected为真时执行,首次配置NTP服务器,之后每小时调用getLocalTime()同步一次。Web页面内嵌的autoSync()函数在浏览器加载时以new Date()获取手机时间直接POST到/api/time,无需NTP。

LED操作反馈+心跳(control.cpp)

unsigned long ledFlashUntil = 0;
void ledFlash() {
  LED_ON();  // WS2812红色亮度10/255
  ledFlashUntil = millis() + 1000;
}
void ledLoop() {
  if (ledFlashUntil > 0) {  // 优先级1:操作反馈(亮1秒)
    if (millis() >= ledFlashUntil) { LED_OFF(); ledFlashUntil = 0; }
    return;
  }
  // 优先级2:心跳(每秒亮100ms)
  if (!beatOn && now - lastBeat >= 1000UL) { LED_ON();  beatOn = true; }
  else if (beatOn && now - lastBeat >= 100UL) { LED_OFF(); beatOn = false; lastBeat = now; }
}

逻辑结构:ledFlashUntil是唯一的状态变量(>0表示操作反馈激活中)。ledFlash()仅做两件事:点亮LED并设定到期时间戳。ledLoop()内部是两级优先级状态机:第一级检查ledFlashUntil——到期则熄灯清零,未到期则return短路跳过心跳;第二级用心跳标志beatOn做二相切换——灭态等1000ms后亮,亮态等100ms后灭,形成每秒100ms脉冲。全系统25+个操作点统一调用ledFlash(),不需要各自管理LED状态。

串口命令(serialcmd.cpp)

void serialPoll() {

  // 自动识别"12:30"格式校时

  if (parseTime(line, h, m)) { currentHour = h; currentMinute = m; ledFlash(); return; }

  if (cmd == "STATUS") { printStatus(); }   // 查询全部状态

  if (cmd == "MODE")   { workMode = ... }   // AUTO/MANU/TIMER

  if (cmd == "TARGET") { targHumi = ... }   // 20~90

  if (cmd == "TIMER")  { timerSetMin = ... }  // 0~180

  if (cmd == "SIM")    { /*TEMP/HUMI/BAT/WATER/LOWBAT传感器模拟注入*/ }

}

逻辑结构:三步流水线。

第1步:检查输入是否纯HH:MM格式(无空格),是则直接校时返回。

第2步:转大写,以首个空格为界拆出cmd和arg两部分。

第3步:cmd字符串匹配分发——无参数命令(STATUS/HELP/INFO/TEMP/HUMI/BAT/WATCH)直接执行后返回;带参数命令(PAGE/MODE/TARGET/TIMER/MANUAL/RELAY/TIME)解析arg后写入全局变量;SIM命令再二次拆分arg为sub(TEMP/HUMI/BAT/WATER/LOWBAT/OFF)和val,对应注入模拟数据。所有控制类命令执行后均调用ledFlash()。

系统调试与测试

调试过程

串口控制说明

--- 查看 ---

  STATUS        打印全部系统状态

  TEMP          读取当前温度

  HUMI          读取当前湿度

  BAT           读取当前电量

  INFO          显示固件版本信息

--- 控制 ---

  PAGE 1        切换到温湿度监控页

  PAGE 2        切换到湿度设定页

  PAGE 3        切换到倒计时页

  MODE AUTO     切换为自动模式

  MODE MANU     切换为手动模式

  MODE TIMER    切换为定时模式

  TARGET 20~90  设置目标湿度(%)

  TIMER 0~180   设置定时分钟数

  START         启动定时器

  STOP          停止定时器

  MANUAL ON     手动开启加湿

  MANUAL OFF    手动关闭加湿

  RELAY ON      强制开启继电器(调试)

  RELAY OFF     强制关闭继电器(调试)

  TIME 12:30    设置系统时间(直接发12:30也可)

--- 模拟注入 ---

  SIM TEMP 25       模拟温度 25C

  SIM HUMI 60       模拟湿度 60%

  SIM BAT 80        模拟电量 80%

  SIM WATER ON      模拟缺水

  SIM WATER OFF     取消缺水模拟

  SIM LOWBAT ON     模拟低电量警告

  SIM LOWBAT OFF    取消低电警告

  SIM OFF           退出模拟,恢复真实传感器

--- 监控 ---

  WATCH          每2秒自动打印状态(再发关闭)

OLED屏幕调试

目的:验证SSD1331 OLED屏幕接线正确、显示正常,为后续所有界面开发奠定基础。屏幕是所有交互的视觉核心,务必在此步确认色彩、分辨率、SPI通信三者均无问题。

操作:

  1. 按引脚表连接OLED模块:
  2. Arduino IDE → 工具 → 管理库 → 搜索并安装"Adafruit SSD1331"及"Adafruit GFX"。
  3. 上传色彩测试程序:全屏流动显示浅蓝色界面,每个循环停留500ms,循环显示。

观察要点:

     颜色是否为渐变色

     是否全黑无漏光(在暗室中观察更明显)。

  1. 上传文字,图形测试程序:size=1字体显示"Hello",在(6,38)处显示"ESP32-S3"等。

观察:

  1. 圆是否为正圆(非椭圆)→ 判断分辨率比例是否正确(SSD1331为96×64,非正方形);
  2. 文字笔画是否完整无虚线 → 判断SPI数据传输无误码。

预期结果:

  1. 圆形无畸变,矩形边框完整,文字清晰无缺线;
  2. 96×64全区域均能正常显示(四角无暗角,边缘无亮线);
  3. 每次切换颜色时屏幕刷新流畅无闪烁。

遇到过的问题的:

  1. 屏幕完全不亮:用万用表测3V3→GND是否为3.3V(低于3.2V屏幕可能不工作);测CS(GPIO21)是否为LOW(CS=LOW才选中屏幕器件);
  2. 花屏/满屏彩点:重点查SDA(GPIO16)和SCL(GPIO15)是否接反——MOSI和SCK反接是花屏的头号原因;
  3. 颜色显示异常(红变绿/蓝变红等):检查SSD1331初始化中的颜色模式,库默认RGB565,如果屏幕实际是BGR565则会颜色通道颠倒,需在构造函数后手动调用setColorMode();显示有规律横纹/竖纹:RES(GPIO17)可能未正确复位,尝试在setup()中加digitalWrite(RES, LOW); delay(200); digitalWrite(RES, HIGH); delay(200);的硬件复位序列;
  4. SPI通信失败(屏幕一直黑屏无任何反应):用逻辑分析仪或示波器测SCL引脚,应能观察到规律的时钟方波(约8MHz),若无时钟→SPI.begin()未正确执行;屏幕工作时有时无:检查杜邦线接触,SPI四线中SCL/SDA对接触最敏感。

图 7图形文字测试

图 8颜色填充测试

DHT11温湿度传感器调试

目的:验证DHT11传感器通信正常、数据合理。DHT11是系统唯一的环境数据来源,其准确性直接影响加湿器自动控制的效果。

操作:

(1) 连接DHT11:DATA→GPIO8、VCC→3V3、GND→GND。

(2) Arduino IDE → 工具 → 管理库 → 搜索安装"DHT sensor library"(by Adafruit)。

(3) 上传测试程序,每2秒读取一次温湿度,串口打印格式如下。

   

(4) 人手测试升温:用拇指和食指捏住传感器白色塑料外壳中部(避开引脚,防止短路),

    保持30秒不松手。串口温度应从25.x℃逐步升至28~30℃(人体手指温度约32℃,传导到传感器内部有一定热阻)。

(5) 哈气测试升湿:嘴距离传感器约5cm,深哈气3~5次。湿度应从45%左右迅速升至75%以上(哈气的相对湿度接近100%),随后在30~60秒内逐渐回落到环境湿度。

(6) 长时间稳定性测试:保持程序运行10分钟以上,观察数据是否有突然跳变或周期性。

预期结果:

     常温下(25℃左右)温度读数20~30℃,湿度30~60%RH(视季节而定);

     捏住传感器后温度上升1~5℃(取决于捏合紧密程度、环境温度);

     哈气后湿度上升20%RH以上,最高可达85%~95%;

     两次正常读取之间数值波动不超过±0.5℃,±2%RH;

     10分钟内NaN出现次数不超过3次(DHT11单总线通信偶尔失败属正常现象)。

遇到的问题:

硬件参数未遇到问题,软件在调试WiFi时出现了单总线数据传送和WiFi传送并行,数据量过大卡死的情况,发生在后期。经查证前期的串口调式汇报数据会占用单行总线上传数据的时间,将两个数据传送的时间错开导致待后期整理优化代码才发现,后期通过延迟时间解决。

图7(DHT11传感器调试——串口数据输出截图,包含升温和哈气两段数据)

EC11旋转编码器调试

目的:验证编码器旋转方向识别、按键消抖、长按/双击识别。编码器是系统唯一的物理输入设备,识别准确性直接影响用户体验——按键误判会导致模式切换错乱,旋转丢步会导致参数调节不准。

操作:

(1) 连接编码器

(2) 上传测试程序,串口打印如下格式事件:

(3) 旋转测试(分三个速度段):

(4) 按键测试:

遇到的问题:

     旋转一格触发2~3次事件:消抖时间太短(DEBOUNCE=20ms),增大到30~40ms试试;或用示波器测CLK引脚波形——若一个定位点内出现多个脉冲,是编码器本身机械触点老化,更换编码器模块;

快速旋转丢步严重(10格只触发2~3次):消抖时间太长(>50ms),减小到10~15ms;或loop()循环中执行了其他耗时操作导致来不及轮询CLK状态变化;

按键无反应:用万用表蜂鸣档测量SW和GND——按下时应导通。

不导通则编码器开关损坏;

导通但程序无反应→检查INPUT_PULLUP是否启用(pinMode(ENC_SW, INPUT_PULLUP)),按下时digitalRead(ENC_SW)是否返回LOW;

     双击被误判为两次短按:DOUBLE_CLICK窗口(300ms)太短,可增大到350ms;

继电器控制测试

目的:验证继电器模块接线正确、吸合/断开控制正常。继电器直接控制加湿器雾化片的通断,

属于强电控制元件(虽然雾化片本身功耗不大),务必确认其在各种状态下行为可预测。

操作:

(1) 连接继电器模块:OUT→GPIO4、VCC→5V0、GND→GND。

(2) 上传测试程序:每2秒翻转GPIO4电平(高/低交替),串口打印"Relay: ON"和"Relay:

    OFF",WS2812 LED同时闪烁反馈。

(3) 万用表验证(关键步骤):

(5) 连续切换测试:让程序连续快速翻转GPIO4 50次(间隔200ms),确认每一次都稳定切换

电池检测校准

目的:验证分压电路和ADC采样正确,校准电压-电量映射关系。电池电量检测是低电保护功能

的基础,映射精度直接影响用户对电量的判断。

操作:

(1) 搭建分压电路(面包板):两个10kΩ电阻(色环:棕黑橙金)串联,上端接可调电源正极(模拟电池正极),中间抽头接GPIO1(A0),下端接可调电源负极/GND。

    原理:电池电压(假设4.0V)经过两个10kΩ电阻分压,中点电压 = 4.0V × 10/(10+10)= 2.0V,落入ADC 0~3.3V安全范围。

(2) 可调电源设置5个测试点依次输出:3.20V、3.50V、3.70V、4.00V、4.20V。

    这5个点覆盖了锂聚合物电池从过放保护临界电压(约3.2V)到满电终止电压(4.2V)的完整区间。3.7V为标称电压(即电池铭牌上标的"3.7V")。

(3) 上传测试程序:每2秒读取一次ADC,执行以下换算后串口打印。

    换算公式:mv = raw × 3300 / 4095 × 2(分压比1/2);

    battery = map(mv, 3300, 4200, 0, 100);lowBatWarn = battery < 15。

(4) 每个测试点稳定后记录至少3组RAW和BAT数据,计算均值和偏差。

校准说明:

     分压比理论值为1/2,但电阻实际阻值有±5%误差(10kΩ ±500Ω),可能导致系统偏差。若MV计算值在多个电压点都偏高或偏低约同一比率,说明分压比并非精确1/2,需要修正代码中"×2"的系数。校准方法:用万用表实测两个分压电阻的实际阻值(记为R1和R2),修正分压比 =(R1+R2)/R2。例如实测R1=10.2kΩ、R2=9.8kΩ,实际分压比=20000/9800≈2.0408,代码中将"×2"改为"×2.0408"即可消除系统误差。

     ADC本身有噪声(±5~10 LSB约±4~8mV),代码中已通过4次采样取均值来压制。

系统联调

目的:加载完整程序,验证全部功能模块协同工作。前6步已验证各模块独立正确,本步检验

模块间的相互影响(如WiFi开启时ADC2不可用、SPI与GPIO矩阵映射的时序冲突等)。

操作:

(1) 确认全部硬件已按引脚表正确连接:

     OLED(SCL=15/SDA=16/RES=17/DC=18/CS=21, VCC=3V3)

     DHT11(DATA=8, VCC=3V3)

     编码器(CLK=5/DT=6/SW=7, VCC=3V3)

     继电器(OUT=4, VCC=5V0)

     电池检测(ADC=GPIO1/A0, 通过面包板分压电路)

     板载WS2812 LED(GPIO48, 板载无需接线)

(2) 在Arduino IDE打开SmartHumidifier.ino,确认以下7个文件全部在工程标签页中:

(3) 上电观察开机流程——应完整经历以下四个阶段:

     涟漪阶段(约1.5秒):屏幕三个位置依次扩散同心圆涟漪,LED同步红色闪烁;

     中心光晕(约0.5秒):屏幕中心显示一个亮青色点,外围两层蓝色光晕圈;

     逐字标题(约1.5秒):字符"H-U-M-I-D-I-F-I-E-R"依次从左到右出现,类似打字机效果;

     READY闪烁(约1秒):屏幕显示"READY",LED同步三次闪烁;

     动画结束后进入MONITOR主界面。全程无花屏、无停顿、无重启。

(4) 逐项验证核心功能(以下操作按顺序执行,每项验证完后截图留档):

     [测试a] 模式切换(短按):

     从默认MANU开始,短按编码器 → 切换为AUTO(顶部栏显示绿色"A",底部栏显示"OFF")→ 再短按 → 切换为TIMER(显示青色"T")→ 再短按 → 切回MANU(显示黄色"M")。每次切换后底部栏文字同步变化。

     [测试b] AUTO模式——湿度设定(长按+旋转+长按):在AUTO模式下长按 → 进入HUMISET子页(当前湿度显示在"NOW:"行,目标湿度在"SET:"行红色显示)。旋转编码器,目标湿度从25逐步调至60。观察数值变化流畅无跳跃。长按保存 → 返回MONITOR主界面,底部栏目标湿度更新为60%。

     [测试c] TIMER模式——定时设定与启动停止(长按+旋转+双击):在TIMER模式下长按 → 进入定时设定页。旋转编码器设置定时为30分钟。长按保存。

     双击编码器启动倒计时 → 顶部栏模式字母位置显示绿色">"(表示定时运行中)底部栏显示"01:00"并开始每秒递减"00:59"→"00:58"→...。再次双击停止 → ">"消失,底部栏切换为当前模式的状态文字。倒计时归零→自动切回原模式。

     [测试d] MANU模式——手动开关(双击):

     在MANU模式下双击编码器 → 顶部栏继电器指示灯(绿色5×5方块)亮起、底部栏显示"ON"、同时能听到继电器"咔"的吸合声。再次双击 → 指示灯灭、显示"OFF"。

(5) 无线功能测试(WiFi AP + Web控制台):

     ① 手机上打开WiFi列表,应搜索到"SmartHumidifier"热点;

     ② 连接热点(密码12345678);

     ③ 手机浏览器打开 http://192.168.4.1 → 应出现仪表盘页面(深色背景、青色标

        题"SMART HUMIDIFIER");

     ④ 页面顶部温湿度卡片应与OLED显示一致(误差<2秒同步延迟);

     ⑤ 点击"AUTO"按钮 → OLED模式切换为AUTO,手机页面模式按钮同步点亮;

     ⑥ 拖动湿度滑块到80% → 点击保存 → OLED目标湿度同步变为80%;

     ⑦ 设置定时15分钟并点击"启动" → OLED切换到TIMER模式并开始倒计时;

     ⑧ 点击"校时"按钮 → OLED顶部时间应更新为手机当前时间。

遇到的问题:

     开机动画卡在某一帧不动 → 检查loop()中各模块轮询频率。如动画帧用delay()阻塞了主循环,会导致WiFi/编码器处理延迟积累。bootAnim()中已使用非阻塞delay;

     WiFi热点搜不到 → 检查WiFi.mode(WIFI_AP_STA)是否调用成功;串口查看WiFi AP启动日志(wifiInit()会打印IP地址)。若串口无WiFi日志→WiFi库初始化失败,排查PSRAM配置(Tools → PSRAM → "OPI PSRAM");

     Web页面打开但数据不更新 → 检查JS中的autoSync()和setInterval(X, 2000)是否正常执行;浏览器控制台(F12)查看是否有CORS跨域错误;

总结与展望

设计成果总结

本项目成功设计并实现了一款基于ESP32-S3的便携式智能加湿器控制系统。硬件方面,选用了高性价比的DHT11传感器、SSD1331彩色OLED和EC11编码器,并合理设计了SPI、单总线及ADC接口电路。软件方面,采用模块化设计,实现了自动恒湿、手动控制、定时关闭三种实用工作模式,并配备了低电/缺水多重安全保护。系统交互逻辑清晰,界面信息丰富,所有功能均通过一个编码器完成操作,用户体验良好。此外,系统集成了WiFi热点与Web控制台,支持手机浏览器远程操控,并通过WS2812 LED实现操作反馈与心跳显示,提升了交互体验。该设计方案硬件简单、控制精确,满足了便携式恒湿控制的应用需求。

存在问题

1.传感器精度:DHT11传感器的标称精度相对较低(湿度±5%RH,温度±2℃),在极端环境下可能与实际值存在偏差,影响恒湿控制的准确性。

2.缺水检测缺失:当前代码中缺水标志waterLack为静态变量,未接入实际缺水检测电路,安全保护功能不完整。

3.低功耗优化:当前系统持续全速运行且屏幕常亮,未实现休眠和省电模式,对电池续航不够友好。

改进方向

1.升级传感器:将DHT11更换为精度更高的DHT22(±2%RH)或AHT20(±2%RH,±0.3℃),提升环境数据采集的准确性。

2.完善缺水检测:增加一个水位探针接至GPIO的ADC或数字输入,实际检测水箱是否缺水,并与软件联动实现自动停机保护。

3.增加低功耗设计:利用ESP32-S3的深度睡眠模式,通过编码器按键唤醒,定时唤醒读取传感器并更新数据,延长电池使用时间。

4.增加BLE蓝牙控制(规划中):利用ESP32-S3内置BLE功能,设计GATT服务端,可通过手机蓝牙调节目标湿度、工作模式、手动开关等参数,实现无网环境下的近距离无线控制。

操作手册

整机只有一个EC11旋转编码器(含按键),所有功能通过旋转和按压组合完成。

=== 基本操作 ===

- 旋转编码器:在湿度设定页 / 定时设定页调节参数值。

- 短按按键(<0.8秒):

  · 在主界面 → 循环切换模式(MANU → AUTO → TIMER → MANU)

  · 在子界面 → 放弃修改,返回主界面

- 长按按键(>0.8秒):

  · AUTO模式下 → 进入湿度设定页,旋转调节目标湿度,长按保存

  · TIMER模式下 → 进入定时设定页,旋转调节分钟,长按保存

  · MANU模式下 → 翻转电池电量监控开关

- 双击按键(0.3秒内连按两次):

  · MANU模式下 → 翻转手动加湿器开关(ON/OFF)

  · TIMER模式下 → 启动 / 停止倒计时

=== 开机与默认状态 ===

- USB-C供电后自动开机,OLED显示开机动画(涟漪→光晕→HUMIDIFIER逐字显示→READY闪烁)。

- 默认进入手动模式(MANU),默认目标湿度25%RH,加湿器默认关闭。

=== 自动恒湿模式(AUTO)===

1. 短按编码器切换到AUTO模式(顶部栏显示绿色"A")。

2. 长按编码器进入湿度设定页,旋转调节目标湿度(范围20%~90%),再次长按保存并返回主界面。

3. 系统自动控制:当环境湿度低于目标值-3%时自动开启加湿,当湿度高于目标值+2%时自动停止加湿,中间区间内保持当前状态(迟滞控制,避免频繁启停)。

4. 底部栏显示继电器当前状态(ON/OFF)。

=== 手动模式(MANU)===

1. 短按编码器切换到MANU模式(顶部栏显示黄色"M")。

2. 双击编码器翻转加湿器开关(ON/OFF),底部栏同步显示当前状态。

3. MANU模式下长按编码器可翻转电池电量监控开关(关闭后不再检测低电)。

=== 定时模式(TIMER)===

1. 短按编码器切换到TIMER模式(顶部栏显示青色"T")。

2. 长按编码器进入定时设定页,旋转调节倒计时分钟(范围0~180分钟),再次长按保存。

3. 双击编码器启动倒计时,顶部栏显示绿色">"表示运行中,底部栏显示剩余时间(M:SS格式)。

4. 倒计时运行期间,再次双击可提前停止,恢复之前的工作模式。

5. 倒计时归零后自动切回之前的工作模式,屏幕即时切换。

=== WiFi无线控制 ===

1. 系统开机后自动创建WiFi热点"SmartHumidifier",密码"12345678"。

2. 用手机连接该热点,浏览器打开地址"192.168.4.1",即可进入仪表盘页面。

3. 在仪表盘上可查看实时温湿度、电量、工作状态,并远程切换模式、设置参数、控制开关。

4. 页面每2秒自动刷新状态,打开页面时自动同步手机时间。

5. 点击"配网设置"可让设备连接家中路由器,连接后自动通过NTP网络授时。

=== 串口命令 ===

通过USB连接电脑,使用串口工具(波特率115200)发送命令:

- STATUS:查看全部状态

- MODE AUTO/MANU/TIMER:切换工作模式

- TARGET 20~90:设置目标湿度

- TIMER 0~180:设置定时分钟

- MANUAL ON/OFF:手动开关控制

- TIME HH:MM:校时(也可直接输入如"12:30")

- HELP:查看完整命令列表

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐