用 ESP32-S3 打造一个会“说话”的温湿度屏 🌡️💧
你有没有过这样的经历:想看看家里温室的湿度是不是太低,结果发现手机App连不上设备;或者办公室空调开得太猛,却没人知道室温已经跌破警戒线?
这时候要是墙上挂着一块小小的彩色屏幕,直接告诉你“当前温度26.3°C,湿度48%”,那得多安心?
今天,我们就来动手做一个 能自己显示、自己刷新、还能未来扩展报警和联网功能的本地温湿度监视器 。主角是最近在嵌入式圈子里风头正劲的 ESP32-S3 ,搭配常见的 DHT22 传感器和一块便宜又好看的 ILI9341 TFT 屏。整个项目成本不到百元,但体验感拉满——它不像某些只能靠手机看数据的“伪智能”设备,这块小屏是真的能独立工作的“智能终端”。
为什么选 ESP32-S3?不只是因为性能强 💪
说实话,做温湿度监测,哪怕是用 ESP8266 都绰绰有余。那为啥还要上 ESP32-S3?
关键在于: 我们不只想读个数,还想让这个小设备“活”起来 。
ESP32-S3 是乐鑫为 AIoT 场景量身打造的一颗 SoC,双核 LX7 架构,主频飙到 240MHz,还带浮点运算单元(FPU),这意味着它可以轻松跑图形界面、处理触摸输入、甚至做点简单的边缘计算。
更重要的是:
- 支持外接 PSRAM(最大 2GB!),这对图形渲染至关重要;
- 内置 USB OTG 接口,调试时不用额外串口板,Type-C 插上就能烧录+打印日志;
- 原生支持 LVGL 这类重量级 GUI 框架,UI 动画丝滑流畅;
- 开发生态成熟,Arduino 和 ESP-IDF 全兼容,社区资源丰富到爆。
换句话说,它不是“能用就行”的芯片,而是让你敢往复杂交互方向去探索的平台。
比如,将来你想加个菜单系统,调个报警阈值,或者画个历史趋势图?没问题,S3 能扛得住。
DHT22:老当益壮的温湿度“老兵” 🧪
DHT22(也叫 AM2302)可能是最广为人知的数字温湿度传感器之一了。虽然它不算最快、也不是最准,但它胜在 稳定、便宜、易用 。
它是怎么把温湿度变成信号的?
DHT22 使用的是 单总线协议(One-Wire Protocol) ——没错,只用一根 GPIO 线就能通信。听起来很神奇?其实原理很简单:
- 主机(也就是我们的 ESP32-S3)先发一个至少 1ms 的低电平“启动信号”;
- DHT22 检测到后,回应一个 80μs 的低电平 + 80μs 的高电平作为“应答”;
- 然后开始发送 40 位数据:
- 湿度整数 | 湿度小数 | 温度整数 | 温度小数 | 校验和
(每部分都是 8bit)
每个 bit 的编码方式靠脉冲宽度区分:
- 高电平持续约 26–28μs → 表示 0
- 高电平持续约 70μs → 表示 1
整个过程对时序要求极高,稍有延迟就会读错。这也是为什么很多初学者抱怨“DHT22 经常读失败”。
🔧 小贴士:如果你发现读数频繁报
NaN,别急着换传感器,先检查三点:
1. 是否加了 4.7kΩ 上拉电阻?
2. 是否两次读取间隔 ≥2 秒?
3. 是否在 FreeRTOS 的高优先级任务中调用了读取函数?
实际表现如何?
根据 Aosong 官方手册:
- 温度范围:-40 ~ 80°C,精度 ±0.5°C
- 湿度范围:0~100% RH,精度 ±2% RH
- 响应时间:<5 秒(典型值)
对于家庭或普通办公环境来说,完全够用。而且它是数字输出,抗干扰能力强,走线十几厘米也没问题。
当然,如果你追求更高精度,也可以换成 SHT30 或 BME280,它们走 I²C 总线,通信更可靠,但成本也会高一些。而 DHT22 的优势就是“五块钱解决问题”。
代码怎么写?简单得像呼吸一样 😮💨
得益于 Arduino 社区的强大支持,读 DHT22 几乎成了“Hello World”级别的操作:
#include "DHT.h"
#define DHT_PIN 4
#define DHT_TYPE DHT22
DHT dht(DHT_PIN, DHT_TYPE);
void setup() {
Serial.begin(115200);
dht.begin();
}
void loop() {
float h = dht.readHumidity();
float t = dht.readTemperature();
if (isnan(h) || isnan(t)) {
Serial.println("❌ 读取失败,请检查接线!");
return;
}
Serial.printf("✅ 温度: %.2f°C, 湿度: %.2f%%\n", t, h);
delay(2000); // 必须等待至少 2 秒
}
这段代码跑起来后,串口每隔两秒就会输出一次数据。如果返回 NaN ,说明通信失败——最常见的原因是电源不稳或时序被打断。
💡 进阶建议 :为了提升稳定性,可以在软件层面加入重试机制和滑动平均滤波:
float readWithRetry() {
for (int i = 0; i < 3; i++) {
float val = dht.readHumidity();
if (!isnan(val)) return val;
delay(50);
}
return NAN;
}
// 滑动平均(假设 buffer 大小为 5)
float movingAverage(float new_val) {
static float buf[5] = {0};
static int idx = 0;
buf[idx] = new_val;
idx = (idx + 1) % 5;
float sum = 0;
for (int i = 0; i < 5; i++) sum += buf[i];
return sum / 5;
}
这样即使偶尔丢一次数据,整体显示依然平稳。
TFT 屏幕:让数据“看得见” 👀
再厉害的数据采集,看不见也是白搭。这时候就需要一块彩色屏幕来“可视化”结果。
我们选用的是市面上最常见的 2.4 英寸 ILI9341 驱动的 TFT 屏 ,分辨率 240×320,支持 16 位色深(RGB565),SPI 接口驱动,价格只要二三十块。
它是怎么被点亮的?
ILI9341 本质上是一个 LCD 控制器,它不直接“画画”,而是接收命令和像素流。大致流程如下:
- 上电后发送一系列初始化指令(如设置伽马曲线、显示方向、开启显示等);
- 设置“绘图窗口”(GRAM 区域),告诉控制器接下来要改哪块区域;
- 开始写入颜色数据,每一个 pixel 对应一个 16bit 的 RGB565 值;
- 控制器自动将这些数据刷到屏幕上。
由于 SPI 传输速度直接影响刷新率,所以我们必须榨干硬件性能——好在 ESP32-S3 支持 DMA 加速 SPI ,可以把图像数据交给 DMA 引擎自动搬运,CPU 只负责发号施令,效率极高。
实测在 40MHz SPI 频率下,全屏刷新能做到接近 30fps,足够支撑基础动画效果。
接线怎么接?记住这五个关键引脚 ✅
| 屏幕引脚 | 推荐连接 |
|---|---|
| VCC | 3.3V(严禁接 5V!) |
| GND | GND |
| SCK | GPIO12(SPI_SCK) |
| MOSI | GPIO11(SPI_MOSI) |
| CS | GPIO10(片选) |
| DC | GPIO9(数据/命令切换) |
| RST | GPIO8(复位,可选) |
| LED | 3.3V 或 PWM 控制背光亮度 |
⚠️ 注意:有些模块标称支持 5V 输入,但 IO 是 3.3V 兼容。为安全起见,建议全部使用 3.3V 供电。
如何编程控制?TFT_eSPI 库真香 🍜
有一个库叫 TFT_eSPI ,可以说是 ESP32 驱动 TFT 屏的“瑞士军刀”。它不仅封装了底层命令,还提供了字体渲染、图片解码、触摸支持等功能。
安装完库之后,第一步是配置 User_Setup.h 文件,指定使用的引脚和屏幕型号。比如:
#define TFT_MISO -1
#define TFT_MOSI 11
#define TFT_SCLK 12
#define TFT_CS 10
#define TFT_DC 9
#define TFT_RST 8
然后就可以愉快地写 UI 了!
#include <TFT_eSPI.h>
TFT_eSPI tft;
void setup() {
tft.init();
tft.setRotation(3); // 横屏显示
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextSize(2);
}
void loop() {
float temp = 25.6, humi = 60.3;
tft.setCursor(20, 50);
tft.printf("🌡️ 温度:\n %.2f °C", temp);
tft.setCursor(20, 150);
tft.printf("💧 湿度:\n %.2f %%", humi);
delay(1000);
// 下次更新前清除旧内容
tft.fillRect(20, 50, 220, 60, TFT_BLACK);
tft.fillRect(20, 150, 220, 60, TFT_BLACK);
}
运行效果?就像一个小气象站,清晰明了。
🎨 美化建议 :
- 使用 tft.drawRoundRect() 画圆角边框;
- 用不同颜色表示状态:绿色正常,黄色预警,红色超限;
- 添加图标(通过 XBM 或压缩 BMP 显示);
- 启用抗锯齿字体让文字更柔和。
把所有东西串起来:系统架构与工作流 🔄
现在我们有三个核心组件:
- DHT22:采集环境数据
- ESP32-S3:大脑,协调一切
- ILI9341:输出界面,展示成果
它们之间的连接关系其实非常清晰:
DHT22 ──GPIO4──→ ESP32-S3 ←──SPI─── ILI9341
│
↓
(可选)Wi-Fi → MQTT / HTTP
启动流程是怎样的?
- 上电,ESP32-S3 自动运行 Bootloader,加载程序;
- 初始化 GPIO、串口用于调试;
- 初始化 DHT22(延时 1 秒后再首次读取);
- 初始化 TFT 屏幕(设置旋转、清屏、加载默认样式);
- 进入主循环,周期性执行:
- 读取温湿度(带错误重试)
- 数据有效性判断
- 更新屏幕显示
- (可选)通过 Wi-Fi 发送到云端
多任务处理:别让屏幕卡住 ⏱️
如果你直接在一个 loop() 里顺序执行“读传感器 → 刷屏 → 延时2秒”,你会发现屏幕刷新明显滞后,用户体验很差。
更好的做法是引入 FreeRTOS 多任务机制 ,把不同功能拆成独立任务:
xTaskCreatePinnedToCore(
read_sensor_task, // 任务函数
"Read Sensor", // 名称
2048, // 栈大小
NULL,
2, // 优先级
NULL,
0 // 绑定到 CPU0
);
xTaskCreatePinnedToCore(
update_display_task,
"Update Display",
4096, // 图形任务需要更大栈
NULL,
1,
NULL,
1 // 绑定到 CPU1
);
这样两个任务并行运行,互不影响。你可以设定传感器每 2 秒采样一次,而屏幕每 500ms 刷新一次(实现动态过渡动画),逻辑更清晰,响应也更及时。
数据同步怎么办?用队列传递消息 📥
为了避免全局变量满天飞,推荐使用 FreeRTOS 提供的 消息队列(Queue) 来传递数据:
QueueHandle_t sensor_queue;
// 创建队列
sensor_queue = xQueueCreate(10, sizeof(SensorData));
// 在采集任务中发送
SensorData data = {temperature, humidity};
xQueueSend(sensor_queue, &data, 0);
// 在显示任务中接收
SensorData received;
if (xQueueReceive(sensor_queue, &received, portMAX_DELAY)) {
updateScreen(received.temp, received.humi);
}
这种方式解耦性强,后期加网络上传任务也方便,只需要再开一个任务监听同一个队列即可。
实战中踩过的坑 & 解决方案 💣➡️✨
任何项目都不会一帆风顺,这个也不例外。以下是我在实际搭建过程中遇到的真实问题及应对策略:
❌ 问题1:屏幕乱码、花屏、闪屏
现象 :刚上电时屏幕出现彩色条纹、字符错位、甚至黑屏。
原因分析 :
- 初始化序列未正确发送
- SPI 速率过高导致通信失败
- 电源不稳定,尤其是背光开启瞬间压降过大
解决方案 :
- 使用标准初始化代码(TFT_eSPI 已内置)
- 初始 SPI 频率设为 10MHz,稳定后再升到 40MHz
- 在 VCC 和 GND 之间并联一个 100μF 电解电容 + 0.1μF 陶瓷电容滤波
- 将背光引脚接到独立电源或通过 MOSFET 由 PWM 控制
❌ 问题2:DHT22 读数频繁失败
现象 :串口不停打印“Failed to read”,偶尔成功一次。
原因分析 :
- 单总线协议对时序极其敏感
- ESP32 正在处理 Wi-Fi 中断或其他高负载任务
- 没有上拉电阻或线路过长
解决方案 :
- 必须在 DHT22 数据线上加 4.7kΩ 上拉电阻到 3.3V
- 避免在 Wi-Fi 扫描期间读取传感器(可暂停扫描)
- 使用 vTaskSuspendAll() 临时关闭调度器以保证时序精确
- 增加重试机制和超时控制
❌ 问题3:屏幕刷新慢、卡顿明显
现象 :每次更新都要几百毫秒,看起来像幻灯片。
根本原因 :没有启用 DMA,SPI 全靠 CPU 轮询发送。
优化手段 :
- 确保使用支持 DMA 的库(如 TFT_eSPI 默认启用)
- 减少不必要的全屏刷新,改为局部擦除+重绘
- 使用双缓冲机制(需 PSRAM 支持)预渲染画面,再一次性切换
例如,绘制温度进度条时,不要每次都重画整个背景,而是只更新变化的部分。
还能怎么升级?让它变得更聪明 🤖
目前我们实现了基本的“采集+显示”功能,但这只是起点。ESP32-S3 的潜力远不止于此。
✅ 加个触摸屏,实现交互
市面上很多 ILI9341 模块自带 XPT2046 触摸控制器 ,通过 SPI 接口连接。我们可以用它来实现:
- 点击切换摄氏/华氏单位
- 长按进入设置模式
- 滑动查看过去 24 小时趋势图
配合 LVGL 库,甚至可以做出类似手机 App 的交互体验。
✅ 接入 Wi-Fi,上传数据到云端
加上几行代码,就能让设备联网:
WiFi.begin("your_ssid", "password");
while (WiFi.status() != WL_CONNECTED) delay(500);
HTTPClient http;
http.begin("http://your-server.com/data");
http.addHeader("Content-Type", "application/json");
String payload = "{\"temp\":" + String(t) + ",\"humi\":" + String(h) + "}";
http.POST(payload);
http.end();
或者更专业一点,用 MQTT 协议发布到 Home Assistant、Node-RED 或私有服务器。
✅ 加个蜂鸣器,异常自动报警 🔔
设定阈值,比如湿度低于 30% 或高于 80%,就触发蜂鸣器鸣叫,或者点亮红色 LED。
甚至可以用语音合成模块播报:“警告!当前湿度偏低,请及时加湿。”
✅ 记录历史数据,画趋势图 📈
借助 PSRAM 和内部 Flash,可以实现简单的数据记录功能:
struct LogEntry {
float temp;
float humi;
uint32_t timestamp;
} __attribute__((packed));
每隔 5 分钟存一条,保存最近 24 小时的数据,然后在屏幕上画出折线图,直观反映环境变化。
设计细节决定成败:那些容易被忽略的事 ⚙️
🔌 电源设计不能马虎
虽然 ESP32-S3 和 DHT22 都支持 3.3V,但实际供电要讲究:
- 使用 AMS1117 或 XC6206 等低压差稳压器;
- 输入端建议使用 5V USB 供电,经过 LDO 转为 3.3V;
- 电流需求:ESP32-S3 最大可达 250mA(Wi-Fi 工作时),TFT 屏背光也要 100mA 左右,总功耗可能超过 350mA;
- 如果用电池供电,建议选用 18650 锂电池 + TP4056 充电模块,并加入低电量检测。
🧱 PCB 布局也有讲究
如果是自己做 PCB,注意以下几点:
- DHT22 信号线尽量短,远离 Wi-Fi 天线和高频走线;
- 电源路径加宽,减少压降;
- 模拟地和数字地分开处理(如有 ADC 扩展);
- 屏幕 SPI 走线尽量等长,避免串扰。
🧰 软件架构建议分层设计
不要把所有代码塞进一个 .ino 文件里。推荐结构化组织:
/src
├── sensors/
│ ├── dht22.cpp
│ └── dht22.h
├── display/
│ ├── lcd.cpp
│ ├── lcd.h
│ └── ui_elements.cpp
├── network/
│ ├── wifi_manager.cpp
│ └── mqtt_client.cpp
├── tasks/
│ ├── sensor_task.cpp
│ ├── display_task.cpp
│ └── comm_task.cpp
└── main.cpp
模块化开发,后期维护和扩展都会轻松得多。
写在最后:做一个“有温度”的物联网设备 ❤️
这个项目看似简单,但它代表了一种理念: 物联网设备不该只是冷冰冰的数据上报机器,它应该有自己的表达方式,能与人产生互动 。
一块小小的 TFT 屏,不只是为了炫技,而是为了让信息触手可及。老人不用打开手机,孩子也能看懂,这才是真正的“智能普惠”。
而 ESP32-S3 的强大之处,就在于它让我们有能力去做这种“有温度的设计”——无论是细腻的动画、贴心的提示音,还是未来的语音交互、AI识别,它都准备好了舞台。
所以,别再满足于“LED 闪烁”和“串口打印”了。拿起你的开发板,焊上屏幕,点亮它,让你的作品真正“开口说话”。
🚀 下一步你想让它做什么?评论区聊聊吧~ 😄
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
995

被折叠的 条评论
为什么被折叠?



