基于ESP32-C6与DHT11的物联网温湿度监测站开发实践
1. 项目概述一个极简的无线温湿度监测站最近在捣鼓一些物联网小玩意儿想着能不能用最小的硬件成本做一个可以实时查看温湿度、还能远程访问的数据记录仪。手头正好有一块DFRobot的Beetle ESP32-C6开发板这板子小巧但功能全自带Wi-Fi和蓝牙功耗控制得也不错。再翻翻零件盒找到了经典的DHT11温湿度传感器和一块0.96英寸的OLED屏幕驱动芯片是SSD1306。得齐活了一个“Beetle ESP32-C6温湿度记录仪”的雏形就有了。这个项目的核心目标很简单让ESP32-C6读取DHT11传感器的数据然后同时做两件事。第一把数据实时显示在手边这块小小的OLED屏幕上让你一眼就能看到当前的温度和湿度。第二通过板载的Wi-Fi把数据上传到网络这样你不在设备旁边时也能通过手机或电脑查看历史曲线甚至设置个温湿度超限报警。它非常适合放在书房、花房、小型仓库或者宠物箱里进行环境监控。整个方案硬件接线简单主要靠I2C和一根数据线代码逻辑清晰无论是物联网新手想入门还是老手想快速搭建一个原型都能从中找到乐趣和实用价值。下面我就把从硬件连接到软件编程再到实际部署中踩过的坑和总结的技巧详细拆解一遍。2. 硬件选型与连接思路解析为什么是这几样东西这得从需求倒推。我们需要一个计算核心、一个传感器、一个本地显示器和一个网络模块。Beetle ESP32-C6完美地集成了计算和网络功能它基于ESP32-C6芯片支持Wi-Fi 6和蓝牙5.0性能对于读取传感器和驱动小屏绰绰有余而且比一些老型号的ESP板子在功耗和连接稳定性上更有优势。DHT11是温湿度传感器的“入门常客”价格低廉虽然精度湿度±5%RH温度±2℃和响应速度不算顶尖但对于一般室内环境监测完全够用关键是单总线通信只需要一个GPIO口节省资源。0.96英寸的OLED屏我选择的是SSD1306驱动的I2C接口版本这种屏功耗低、对比度高在暗环境下显示效果尤其出色I2C接口也只需要两根线SDA SCL能进一步简化布线。2.1 核心控制器Beetle ESP32-C6的引脚考量Beetle ESP32-C6板子非常紧凑引脚复用功能强大。首先得规划好引脚用途。对于DHT11它只需要一根数据线我选择连接到GPIO4板子上可能有标注D4。选择这个引脚是因为它在Arduino核心中通常是一个通用的数字IO并且远离一些可能用于系统功能的特殊引脚如GPIO6-11通常连接内部Flash不建议使用。对于OLED屏幕SSD1306 I2C需要两个引脚SDA数据线和SCL时钟线。ESP32-C6有多个I2C接口硬件I2C0通常预留给内部传感器但我们可以使用软件I2C或者硬件I2C1。最通用的做法是使用Wire库它默认会映射到一组可用的硬件I2C引脚上。在Beetle ESP32-C6上常见的默认I2C引脚是GPIO8SDA和GPIO9SCL。但在动手前务必核对你所使用的开发板的具体引脚定义图不同厂商的板子可能映射不同。如果默认引脚被占用或者不方便布线我们完全可以在代码里重新定义SDA和SCL到任何其他数字引脚上这就是软件I2C的灵活性。我这次就打算用GPIO6和GPIO7因为它们位置集中方便接线。注意DHT11的数据引脚需要连接一个4.7kΩ - 10kΩ的上拉电阻到3.3V以确保信号稳定。很多DHT11模块已经内置了这个电阻如果你用的是模块就不需要额外添加如果用的是单独的传感器元件这个电阻必不可少。2.2 传感器与显示屏的接口协议简析DHT11的单总线协议它比I2C还要简单只有一根线进行双向通信。通信开始时微控制器MCU先发出一个起始信号拉低总线至少18ms后拉高然后切换到输入模式等待DHT11的响应。DHT11会先拉低80us作为应答信号再拉高80us随后开始发送40位数据16位湿度整数16位湿度小数16位温度整数16位温度小数对于DHT11小数部分通常为0。每一位数据都以一个50us的低电平起始位开始随后的高电平持续时间决定数据是026-28us还是170us。编程时关键就在于精确测量这个高电平的持续时间。很多库如DHT sensor library已经帮我们封装好了时序逻辑可靠性很高。SSD1306的I2C协议I2C是一个同步、多主从、半双工的串行总线。它由SCL时钟线和SDA数据线构成。通信开始时主设备ESP32-C6发送一个起始条件SDA在SCL高电平时由高变低然后发送7位从设备地址SSD1306的常见地址是0x3C或0x3D和1位读写位。从设备应答后就可以传输数据或命令了。SSD1306区分命令和数据发送一个“控制字节”Co0x00表示后续字节是命令0x40表示后续字节是显示数据GDDRAM。我们的驱动库如Adafruit SSD1306或U8g2会处理好这些底层细节我们只需要调用print()或draw函数即可。连线示意图Beetle ESP32-C6 DHT11模块 0.96 OLED (I2C) 3.3V --------------- VCC -------------- VCC GND --------------- GND -------------- GND GPIO4 -------------- DATA GPIO6 -------------------------------- SDA GPIO7 -------------------------------- SCL假设OLED和DHT11模块已内置上拉电阻3. 软件环境搭建与核心库选择硬件连好了接下来就是让它们“活”起来。我们选择在Arduino IDE中进行开发因为它对ESP32系列支持友好库生态丰富。3.1 开发环境与板卡支持包安装首先确保你安装了最新版的Arduino IDE。然后需要添加ESP32的开发板支持。在“文件”-“首选项”的“附加开发板管理器网址”中添加以下网址https://espressif.github.io/arduino-esp32/package_esp32_index.json接着打开“工具”-“开发板”-“开发板管理器”搜索“esp32”找到“ESP32 by Espressif Systems”并安装。安装完成后在开发板列表中选择“ESP32C6 Dev Module”。虽然可能没有直接的“Beetle ESP32-C6”选项但选择通用的C6开发模块通常兼容。关键是要在“工具”菜单中正确设置参数Flash Mode为“QIO”Flash Size根据你的板子选择通常是4MBCPU Frequency和Upload Speed使用默认值即可。最重要的是选择正确的端口。3.2 第三方库的安装与对比我们需要两个核心库一个用于驱动DHT11一个用于驱动OLED。DHT传感器库在Arduino IDE的库管理器工具-管理库中搜索“DHT sensor library”作者是Adafruit。安装时它通常会提示你一并安装“Adafruit Unified Sensor”库这个是必需的依赖库一定要安装。这个库支持DHT11、DHT22等多种传感器通用性强。OLED显示库这里有两个主流选择Adafruit SSD1306 Adafruit GFX这是Adafruit出品的一套经典组合。Adafruit_SSD1306负责底层驱动和硬件初始化Adafruit_GFX负责图形绘制点、线、圆、文字等。优点是文档丰富例子多。U8g2这是一个功能极其强大的单色显示器驱动库支持海量的控制器包括SSD1306和多种通信方式I2C, SPI等。它的API统一学习一次就能驱动很多屏而且对中文、图形和图标的支持非常完善。我这次选择U8g2库。原因有三第一它的字体和图形功能更强大方便未来扩展显示内容比如显示中文湿度“湿”字第二它的硬件抽象做得更好同一份代码稍作修改就能适配不同接口的屏幕第三其性能经过优化在某些场景下刷新效率更高。在库管理器中搜索“U8g2”并安装即可。4. 代码实现与分步详解有了库我们就可以开始编写代码了。整个程序的逻辑框架是初始化串口、OLED和DHT11 - 连接Wi-Fi - 进入主循环 - 读取传感器数据 - 刷新OLED显示 - 上传数据到网络服务可选- 延时等待下一轮。4.1 头文件引入与全局对象定义#include Arduino.h #include U8g2lib.h // OLED驱动库 #include DHT.h // DHT传感器库 #include WiFi.h // ESP32-C6的Wi-Fi库 // 1. 定义DHT11引脚和类型 #define DHTPIN 4 #define DHTTYPE DHT11 DHT dht(DHTPIN, DHTTYPE); // 2. 构建U8g2对象用于驱动I2C接口的SSD1306这里选择全缓冲模式显示效果好 // 参数说明U8G2_R0不旋转 SDA6, SCL7 地址0x3C U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset*/ U8X8_PIN_NONE, /* clock*/ 7, /* data*/ 6); // 3. Wi-Fi凭证请修改为你自己的 const char* ssid Your_WiFi_SSID; const char* password Your_WiFi_Password; // 4. 全局变量存储温湿度值 float temperature 0.0; float humidity 0.0;关键点解析构建U8g2对象时我选择了_HW_I2C硬件I2C和_F_全缓冲模式。全缓冲模式意味着会在内存中开辟一块和屏幕分辨率128x64对应的缓冲区所有绘图操作先在内存中进行完成后一次性发送到屏幕。这避免了屏幕闪烁但会消耗更多内存128*64/8 1024字节。对于ESP32-C6来说这完全可接受。U8X8_PIN_NONE表示我们不使用硬件复位引脚用软件复位。DHT对象的初始化很简单指定引脚和传感器类型即可。4.2 初始化设置setup函数void setup() { // 启动串口调试波特率115200 Serial.begin(115200); Serial.println(\n\nBeetle ESP32-C6 Temp Humidity Logger Starting...); // 初始化DHT传感器 dht.begin(); Serial.println(DHT11 Sensor Initialized.); // 初始化OLED显示屏 u8g2.begin(); u8g2.clearBuffer(); // 清空缓冲区 u8g2.setFont(u8g2_font_ncenB08_tr); // 设置初始字体 u8g2.drawStr(0, 15, System Booting...); u8g2.sendBuffer(); // 将缓冲区内容发送到屏幕显示 Serial.println(OLED Display Initialized.); // 连接Wi-Fi WiFi.begin(ssid, password); Serial.print(Connecting to WiFi); u8g2.clearBuffer(); u8g2.drawStr(0, 15, Connecting WiFi); u8g2.sendBuffer(); int wifiTimeout 20; // 超时计数约20秒 while (WiFi.status() ! WL_CONNECTED wifiTimeout 0) { delay(500); Serial.print(.); wifiTimeout--; } Serial.println(); if (WiFi.status() WL_CONNECTED) { Serial.print(Connected! IP Address: ); Serial.println(WiFi.localIP()); u8g2.clearBuffer(); u8g2.drawStr(0, 15, WiFi Connected); u8g2.setCursor(0, 35); u8g2.print(IP:); u8g2.print(WiFi.localIP().toString().c_str()); u8g2.sendBuffer(); delay(2000); // 显示IP地址2秒 } else { Serial.println(WiFi Connection FAILED!); u8g2.clearBuffer(); u8g2.drawStr(0, 15, WiFi FAILED); u8g2.drawStr(0, 35, Working Offline); u8g2.sendBuffer(); delay(2000); } // 显示就绪界面 u8g2.clearBuffer(); u8g2.drawStr(0, 15, Temp Humidity); u8g2.drawStr(0, 35, Logger Ready); u8g2.sendBuffer(); delay(1000); }setup函数要点顺序初始化先启动串口用于调试再初始化传感器和屏幕。这样如果传感器或屏幕初始化失败可以通过串口信息排查。OLED状态反馈在连接Wi-Fi的每个关键步骤都在OLED上给出提示。这在实际部署中非常有用你不需要连接串口监视器就能知道设备状态。Wi-Fi连接超时处理加入了wifiTimeout计数器避免网络异常时程序永远卡在while循环里。如果连接失败设备会进入离线模式依然可以正常采集和显示数据只是无法上传网络。显示IP地址连接成功后将IP地址显示在屏幕上。这样当你需要访问设备的Web服务器如果后续添加时就不需要去路由器后台查了。4.3 主循环逻辑与数据显示loop函数void loop() { // 读取温湿度数据读取间隔至少2秒DHT11传感器要求 delay(2000); // 读取湿度百分比 float h dht.readHumidity(); // 读取温度摄氏度 float t dht.readTemperature(); // 检查读取是否成功返回NaN表示失败 if (isnan(h) || isnan(t)) { Serial.println(Failed to read from DHT sensor!); u8g2.clearBuffer(); u8g2.setFont(u8g2_font_ncenB08_tr); u8g2.drawStr(0, 20, Sensor Error!); u8g2.sendBuffer(); return; // 跳过本次循环的后续操作 } // 更新全局变量 humidity h; temperature t; // 串口打印数据 Serial.print(Humidity: ); Serial.print(humidity); Serial.print( %\t); Serial.print(Temperature: ); Serial.print(temperature); Serial.println( °C); // OLED显示数据 u8g2.clearBuffer(); // 重要每次刷新前先清空缓冲区 // 设置大字体显示温度 u8g2.setFont(u8g2_font_logisoso24_tn); // 选择一款数字字体 u8g2.setCursor(0, 35); u8g2.print(temperature, 1); // 显示温度保留一位小数 u8g2.drawStr(90, 35, C); // 绘制摄氏度符号 // 设置较小字体显示湿度 u8g2.setFont(u8g2_font_ncenB10_tr); u8g2.setCursor(0, 55); u8g2.print(H: ); u8g2.print(humidity, 0); // 湿度取整显示 u8g2.print(%); // 绘制一个简单的分隔线和标题 u8g2.setFont(u8g2_font_ncenB08_tr); u8g2.drawStr(0, 12, Current Env); u8g2.drawHLine(0, 14, 128); // 在Y14位置画一条128像素长的横线 // 将缓冲区内容发送到屏幕 u8g2.sendBuffer(); // 此处可以添加网络上传数据的函数例如 sendDataToServer(); // uploadData(); }loop函数核心技巧遵守传感器时序delay(2000);是必须的因为DHT11两次读取之间需要至少2秒的间隔否则读取会失败。错误处理使用isnan()函数判断读取值是否为“非数字”NaN。这是检测DHT11通信失败最有效的方法。一旦失败在OLED上显示错误信息并直接return避免使用错误的数据。OLED显示优化clearBuffer()和sendBuffer()必须成对使用。clearBuffer()只清空内存缓冲区sendBuffer()才将内存数据刷到屏幕。字体混合使用用大字体突出显示主要数据温度用小字体显示次要信息湿度、标签。U8g2内置了大量字体u8g2_font_logisoso24_tn是一款很漂亮的等宽数字字体。布局与装饰使用drawHLine画一条分隔线让标题和内容区更清晰。简单的视觉分割能极大提升显示的专业感。数据上传预留在显示完成后我注释了网络上传的函数调用。这是为下一步功能扩展留的接口。你可以在这里调用函数将temperature和humidity变量通过HTTP或MQTT发送到你的服务器或云平台。5. 功能扩展数据上传与Web服务器一个本地记录仪很好但加上远程访问才是物联网的完全体。这里提供两种最常用的扩展思路。5.1 通过HTTP POST上传数据到云平台以国内常用的物联网平台“点灯科技Blinker”或通用的Web服务器为例。你需要先注册平台创建一个设备获取鉴权密钥。// 在文件开头添加必要的库和变量 #include HTTPClient.h const char* serverName http://api.your-server.com/data; // 替换为你的API地址 String apiKey YOUR_DEVICE_API_KEY; void uploadData() { if (WiFi.status() ! WL_CONNECTED) { Serial.println(WiFi not connected, skip upload.); return; } HTTPClient http; http.begin(serverName); http.addHeader(Content-Type, application/x-www-form-urlencoded); // 或者 application/json // 构建POST数据例如JSON格式 String httpRequestData {\api_key\:\ apiKey \,\temperature\: String(temperature) ,\humidity\: String(humidity) }; // 或者表单格式: api_keyYOUR_KEYtemp String(temperature) humi String(humidity) int httpResponseCode http.POST(httpRequestData); if (httpResponseCode 0) { Serial.print(HTTP POST code: ); Serial.println(httpResponseCode); String response http.getString(); Serial.println(response); } else { Serial.print(Error on HTTP POST: ); Serial.println(httpResponseCode); } http.end(); }然后在loop()函数的末尾取消对uploadData();的注释即可。注意频繁上传数据如每秒一次可能会被服务器限制也消耗更多流量和电量。通常环境数据每30秒或1分钟上传一次就足够了。你可以添加一个计时器控制上传频率。5.2 内置简易Web服务器显示实时数据让ESP32-C6自己作为一个Web服务器手机电脑在同一局域网内访问它的IP地址就能看到数据。这不需要外部云平台更私密。#include WebServer.h // ESP32内置WebServer库 WebServer server(80); // 在80端口创建服务器对象 // 在setup()函数中WiFi连接成功后添加路由处理 server.on(/, handleRoot); // 根目录请求 server.on(/data, handleData); // 获取JSON数据的API server.begin(); Serial.println(HTTP server started); // 在loop()函数末尾添加 server.handleClient(); // 定义处理函数 void handleRoot() { String html !DOCTYPE htmlhtmlheadmeta charsetUTF-8title温湿度监测/title; html meta http-equivrefresh content5; // 每5秒自动刷新页面 html stylebody {font-family: Arial; text-align: center; margin-top: 50px;}/style/headbody; html h1环境监测站/h1; html pstrong温度/strongspan idtemp String(temperature) /span °C/p; html pstrong湿度/strongspan idhumi String(humidity) /span %/p; html psmall数据更新时间: String(millis() / 1000) 秒前/small/p; html /body/html; server.send(200, text/html, html); } void handleData() { String json {\temperature\: String(temperature) ,\humidity\: String(humidity) }; server.send(200, application/json, json); }这样你访问http://[设备IP]/就能看到一个自动刷新的简单网页访问http://[设备IP]/data则会得到原始的JSON数据方便其他程序调用。6. 常见问题与深度排查实录在实际制作和调试过程中你几乎一定会遇到下面这些问题。我把我的排查经验和解决方案记录下来。6.1 DHT11读取失败或数据为0这是最常见的问题现象是串口打印Failed to read from DHT sensor!或者湿度温度始终为0。检查接线这是第一步。确保VCC、GND、DATA线连接正确且牢固。尤其注意DHT11的VCC要接3.3V不是5V虽然很多DHT11模块标称支持5V但为了与ESP32-C6的3.3V逻辑电平匹配强烈建议使用3.3V供电否则可能损坏ESP32的GPIO。检查上拉电阻如果你使用单独的DHT11元件必须在DATA引脚和3.3V之间连接一个4.7kΩ - 10kΩ的电阻。用万用表量一下。时序问题确保在loop()中每次读取前有至少2秒的delay。另外dht.begin()初始化后建议也等待1-2秒再开始第一次读取。电源干扰DHT11对电源纹波比较敏感。尝试在DHT11的VCC和GND之间并联一个100uF的电解电容可以稳定供电。代码排查在setup()里初始化后先简单读取一次并丢弃因为第一次读数可能不准。在主循环读取时使用readHumidity()和readTemperature()的返回值而不是dht.humidity和dht.temperature旧版库的用法。6.2 OLED屏幕不显示或白屏/乱码I2C地址错误SSD1306的I2C地址通常是0x3C或0x3D。你可以用一个简单的I2C扫描程序来确认。#include Wire.h void setup() { Wire.begin(6, 7); // 指定SDA, SCL引脚 Serial.begin(115200); while (!Serial); Serial.println(\nI2C Scanner); } void loop() { byte error, address; int nDevices 0; for(address 1; address 127; address ) { Wire.beginTransmission(address); error Wire.endTransmission(); if (error 0) { Serial.print(I2C device found at address 0x); if (address16) Serial.print(0); Serial.print(address,HEX); Serial.println( !); nDevices; } } if (nDevices 0) Serial.println(No I2C devices found\n); delay(5000); }将扫描到的地址替换到U8g2对象构造函数的参数中0x3C。接线和电源确认SDA、SCL、VCC、GND四根线接对。屏幕也需要3.3V供电。U8g2对象构造错误我代码中使用的构造函数U8G2_SSD1306_128X64_NONAME_F_HW_I2C适用于最常见的128x64分辨率、无复位引脚、硬件I2C的SSD1306屏。如果你的屏是128x32的需要选择U8G2_SSD1306_128X32_NONAME_F_HW_I2C。务必根据屏幕规格选择正确的构造函数可以在U8g2库的示例代码开头找到所有型号列表。缓冲区操作错误确保每个显示周期内clearBuffer()、绘图指令、sendBuffer()是完整且按顺序执行的。不要在sendBuffer()之后又去修改缓冲区内容而不再次sendBuffer()。6.3 I2C通信不稳定或无法识别上拉电阻I2C总线需要上拉电阻通常4.7kΩ到3.3V。很多OLED模块已经集成如果没有你需要在SDA和SCL线上各加一个。引脚冲突确保你定义的SDA和SCL引脚代码中的6和7没有被其他功能占用。避免使用那些标注为GPIO6-11的引脚通常连接内部Flash以及GPIO0可能影响 boot 模式。线长与干扰杜邦线过长超过20cm可能导致信号衰减和干扰。尽量缩短连接线或使用屏蔽线。如果必须用长线可以尝试降低I2C时钟频率在Wire.begin()后调用Wire.setClock(100000)设置为100kHz标准模式。逻辑电平确保所有I2C设备这里只有OLED与ESP32-C6使用相同的电压3.3V。6.4 Wi-Fi连接不稳定或经常断开信号强度ESP32-C6的Wi-Fi天线性能受环境影响大。确保设备离路由器不要太远中间障碍物少。可以通过WiFi.RSSI()读取信号强度进行判断。电源问题Wi-Fi发射时电流较大可能导致电压跌落。使用质量好的USB线或外部电源供电避免使用那些只能提供500mA的老旧充电头。在ESP32的电源引脚附近并联一个100-220uF的电解电容可以有效缓解瞬时压降。代码优化在主循环中避免在WiFi.status() ! WL_CONNECTED时进行长时间阻塞操作。可以加入自动重连机制void checkWiFi() { if (WiFi.status() ! WL_CONNECTED) { Serial.println(WiFi Lost. Reconnecting...); u8g2.clearBuffer(); u8g2.drawStr(0, 30, Reconnecting WiFi); u8g2.sendBuffer(); WiFi.disconnect(); WiFi.reconnect(); delay(2000); // 等待重连 } }然后在loop()中定期调用checkWiFi()。7. 功耗优化与部署建议如果你希望这个记录仪能靠电池运行更久就需要考虑功耗优化。深度睡眠模式ESP32-C6支持深度睡眠Deep Sleep此时大部分电路关闭功耗可低至10μA级别。我们可以让设备每5分钟唤醒一次读取传感器数据刷新OLED上传数据然后再次进入深度睡眠。#include esp_sleep.h void gotoSleep(int seconds) { Serial.println(Entering deep sleep for String(seconds) seconds); u8g2.clearBuffer(); u8g2.drawStr(0, 30, Sleeping...); u8g2.sendBuffer(); delay(100); // 让OLED完成显示 esp_deep_sleep(seconds * 1000000ULL); // 微秒单位 }在loop()函数执行完一次测量和上传后调用gotoSleep(300);即可睡眠5分钟。注意深度睡眠后GPIO状态会丢失唤醒后相当于重新执行setup()你需要考虑如何保存状态或快速恢复显示。关闭OLED在不需要显示时比如深度睡眠前可以调用u8g2.setPowerSave(1);来关闭OLED屏幕的供电进一步省电。部署建议外壳3D打印或找一个合适的小盒子既能保护电路也能让DHT11传感器与外界空气良好流通避免被内部元件发热影响。传感器位置DHT11不要放在阳光直射、靠近空调出风口或热源如ESP32本身的地方这会导致读数不准。放在空气流通的阴凉处。天线如果Wi-Fi信号弱可以尝试调整板载PCB天线的方向或者考虑使用外接天线如果板子支持。这个项目从硬件连接到软件编程再到问题排查和优化基本覆盖了一个典型物联网传感节点开发的全流程。它麻雀虽小五脏俱全。你可以在此基础上轻松地替换更精确的传感器如SHT30、BME280增加更多的显示信息或者集成到更复杂的智能家居系统中。动手做一遍你会发现很多知识不再是文档上的文字而是变成了你实实在在的经验。

相关新闻

最新新闻

日新闻

周新闻

月新闻