基于Arduino的DIY温湿度时钟:DHT11、DS1307与5110液晶屏实战指南
1. 项目缘起为什么需要自己动手做一个温湿度计兼时钟几年前我还在用着那种超市里十几块钱买的电子温湿度计显示的数字时大时小湿度读数经常飘忽不定最要命的是里面的纽扣电池一年得换好几次每次换完还得重新对时。后来我接触了Arduino看着手头闲置的DHT11温湿度传感器、DS1307实时时钟模块和一块诺基亚5110液晶屏一个想法就冒了出来为什么不自己做一个呢一个能精确显示时间、实时监测环境温湿度并且可以自己编程控制、甚至未来扩展功能的桌面小设备。这个想法背后的需求其实很具体我需要一个稳定、可靠、可定制的环境监测终端。市售产品要么功能单一要么数据不准要么就是封闭系统你想加个数据记录或者超限报警功能根本无从下手。而用DHT11、DS1307和5110液晶这三样经典器件组合恰恰能完美解决这些问题。DHT11负责采集温湿度虽然精度不算顶级但对于家庭和办公室环境监测完全够用关键是它数字输出抗干扰能力比那些模拟传感器强得多。DS1307提供精准的计时自带电池备份断电也不用担心时间丢失。5110液晶屏则是经典中的经典84x48的分辨率显示这些信息绰绰有余功耗极低而且驱动简单。更重要的是这个项目是一个绝佳的嵌入式开发入门实践。它涵盖了传感器数据读取、I2C总线通信、SPI液晶驱动、时间管理等多个核心知识点代码量适中硬件连接清晰成功率高非常适合从点亮LED灯进阶到实际应用的爱好者。做完之后你得到的不仅仅是一个工具更是一套可复用的技能未来无论是想做更复杂的智能家居节点还是给其他项目添加时间或环境感知功能都能直接套用这里的经验。2. 核心器件选型与特性深度剖析选择这三样器件并非偶然它们都是在开源硬件领域久经考验、资料丰富、性价比极高的代表。理解它们的特性是项目成功和后续调试的基础。2.1 DHT11温湿度传感器够用且稳定的数字采集单元DHT11是一款复合型温湿度传感器内部集成了一个电阻式感湿元件和一个NTC测温元件并通过一个8位单片机进行校准和数字信号输出。它的核心优势在于单总线数字信号输出。这意味着你只需要一根数据线除了电源和地就能读取数据极大简化了布线也避免了模拟信号在长距离传输中容易受到的干扰。它的参数决定了其应用场景测量范围湿度20-90%RH温度0-50℃。这基本覆盖了室内环境的常态范围。精度湿度±5%RH温度±2℃。对于需要高精度科研或工业控制的场景这个精度不够DHT22或SHT系列是更好的选择。但对于“今天是不是比昨天干燥”、“房间温度是否适宜”这类定性或半定量判断DHT11完全胜任。很多市售廉价温湿度计用的就是同级别甚至更差的传感器。响应时间湿度响应较慢通常需要1-2秒才能稳定。编程时需要注意两次读取之间必须间隔至少2秒否则可能读到错误数据或没有响应。供电3.3V-5.5V与Arduino的5V或3.3V输出完美兼容。注意DHT11的塑料壳体顶部有一个小小的网格窗口那是传感器的感应区务必保持通畅不要被遮挡或沾上灰尘、水渍否则会影响测量准确性。2.2 DS1307实时时钟模块永不掉线的“时间管家”Arduino本身没有硬件时钟断电后时间信息就丢失了。DS1307就是为解决这个问题而生的。它是一个低功耗的I2C接口实时时钟/日历芯片最大特点就是自带电池备份。模块上通常有一颗CR2032纽扣电池当主电源如Arduino的5V断开时芯片由这颗电池供电继续走时数据完全不会丢失。它的关键特性包括I2C接口只需要两根线SDA数据线SCL时钟线就能与Arduino通信节省IO口。I2C是主从模式Arduino作为主机发起读写请求。时钟精度典型精度还不错但受温度影响会有漂移一个月误差几分钟是正常的。对于日常显示完全可接受如果追求极致精度可以选择内置温度补偿的DS3231但价格也更高。内部寄存器DS1307的时间信息秒、分、时、日、月、年、星期存储在特定的寄存器中。我们编程的本质就是通过I2C协议去读写这些寄存器。初次使用或更换电池后必须通过程序向其“写入”一个正确的时间初始值。方形波输出它还有一个SQW/OUT引脚可以编程输出几种频率的方波信号在某些需要定时触发的应用中可能用到本项目可以暂不连接。2.3 诺基亚5110液晶屏复古可靠的显示终端这块屏源于古老的诺基亚5110手机因其极高的性价比和极低的功耗在电子爱好者中焕发第二春。它是一款84x48像素的图形液晶单色显示背光可调通常是LED背光。它的驱动特点SPI接口通常使用软件模拟SPISoftware SPI驱动只需要4-5根线RST复位、CE片选、DC命令/数据、DIN数据输入、CLK时钟。相比并行驱动的LCD1602它占用IO口更少且能显示任意图形。PCD8544控制器屏幕的核心驱动芯片。我们需要通过SPI向这个控制器发送命令和数据来控制显示内容、对比度等。好在有成熟的库如Adafruit_PCD8544或U8g2封装了这些底层操作让我们可以像在画布上画点、线、文字一样简单。对比度调节5110屏的显示效果非常依赖对比度设置。对比度电压由模块上的一个可调电阻或通过程序控制。设置不当屏幕可能全黑、全白或显示淡得看不清。通常需要通过程序尝试一个值比如lcd.setContrast(60)来获得最佳视觉效果。3. 硬件系统搭建与连接详解理论清楚了接下来就是动手连接。清晰的硬件连接是软件调试的前提。这里我们以最经典的Arduino Uno作为主控板来讲解连接方案。3.1 系统连接图与引脚定义首先我们需要规划好各个模块连接到Arduino的哪个引脚。遵循两个原则一是优先使用模块库推荐的默认引脚以便于示例代码运行二是合理分配避免冲突。下面是一个经过实践验证的可靠连接方案模块引脚连接到 Arduino Uno 引脚说明DHT11VCC5V电源正极GNDGND电源地DATADigital 2数据引脚需接一个4.7K-10K的上拉电阻到VCCDS1307 RTCVCC5V电源正极GNDGND电源地SDAAnalog A4I2C数据线在Uno上固定为A4SCLAnalog A5I2C时钟线在Uno上固定为A55110 LCDVCC3.3V或5V重要接5V时背光更亮但需确保模块支持5V输入。稳妥起见接3.3V。GNDGND电源地RST (RESET)Digital 8复位引脚低电平有效CE (CS)Digital 7片选引脚低电平选中DC (D/C)Digital 6数据/命令选择引脚DIN (MOSI)Digital 5数据输入引脚主设备输出从设备输入CLK (SCK)Digital 4时钟引脚LED (背光)通过220Ω电阻接Digital 3背光阳极阴极接GND。用PWM引脚可调亮度。连接要点与避坑指南DHT11的上拉电阻这是最容易忽略导致读取失败的点。DHT11的数据线是开漏输出必须接一个4.7KΩ - 10KΩ的电阻到VCC5V为总线提供一个稳定的高电平。很多模块已经内置了这个电阻如果购买的是模块可以检查一下。如果是单独的传感器务必自己加上。I2C引脚固定在Arduino Uno以及基于ATmega328P的板子上SDA对应A4SCL对应A5这是硬件的物理映射不能更改。其他板子如Mega、Leonardo、ESP32等引脚不同需要查对应板子的资料。5110液晶的电源选择这是一个经典的坑。5110液晶的逻辑电平是3.3V但其供电范围较宽。如果模块本身有3.3V稳压芯片LDO那么接5V是安全的电压会被降到3.3V。如果模块没有LDO直接接5V可能会烧毁屏幕最安全的做法是先用万用表测量模块上VCC和GND之间的电压或者仔细查看产品说明。不确定时一律接Arduino的3.3V输出引脚。接3.3V的缺点是背光可能稍暗。背光控制背光LED是一个发光二极管必须串联一个限流电阻通常220Ω然后由Arduino的一个数字引脚驱动。我推荐接到Digital 3因为它是PWM引脚你可以通过analogWrite(3, brightness)来调节背光亮度实现夜间模式等功能。电源去耦如果系统工作不稳定如屏幕闪烁、数据读取偶尔错误可能是电源噪声导致。可以在每个模块的VCC和GND之间就近焊接一个0.1uF104的陶瓷电容效果立竿见影。3.2 面包板布局与焊接建议对于原型验证面包板是最佳选择。布局时尽量让电源线红色黑色-沿着面包板两侧的电源轨走形成清晰的主干。各个模块围绕Arduino放置数据线尽量短且整齐。这样既美观也减少了信号串扰的可能性。如果你打算做一个永久性的作品焊接是更好的选择。可以使用一块洞洞板万用板按照连接图进行焊接。建议先焊接电源和地线的主干道。给Arduino、DS1307、DHT11的电源入口处都加上一个0.1uF的贴片电容。5110液晶最好使用排母焊接这样屏幕可以插拔方便更换或调试。所有连接线使用不同颜色的导线区分功能红-5V黑-GND黄-数据绿-时钟等后期排查故障会轻松无数倍。4. 软件开发环境与核心库配置硬件连好了接下来就是让大脑程序运转起来。我们将使用Arduino IDE进行开发。4.1 库的安装与管理这个项目需要三个核心库它们封装了与硬件通信的复杂细节。DHT Sensor Library用于驱动DHT11也支持DHT22等。这是读取温湿度的关键。在Arduino IDE中点击工具-管理库...打开库管理器。在搜索框中输入“DHT sensor”找到由Adafruit发布的“DHT sensor library”点击安装。通常它会提示你同时安装依赖库“Adafruit Unified Sensor”一并确认安装。RTClib用于驱动DS1307也支持DS3231等。这是读写时间的核心。同样在库管理器中搜索“RTClib”找到由Adafruit发布的版本进行安装。这是一个非常通用且稳定的RTC库。Adafruit PCD8544 Nokia 5110 LCD Library用于驱动5110液晶屏。在库管理器中搜索“PCD8544”找到“Adafruit PCD8544 Nokia 5110 LCD library”进行安装。这个库提供了基本的画点、画线、显示文本和位图的功能。安装完成后你可以在文件-示例菜单中找到这些库的示例程序强烈建议先分别运行一下每个示例单独测试每个模块是否工作正常。比如先运行DHT库的示例看看串口监视器里能否打印出温湿度再运行RTC库的示例设置并读取时间最后运行5110库的示例看看屏幕能否点亮并显示测试图形。这一步的“分步调试”能帮你快速定位问题是出在硬件连接、库安装还是代码逻辑上。4.2 代码架构与核心逻辑解析当三个模块单独测试都OK后我们就可以编写整合代码了。完整的代码较长这里我重点拆解核心逻辑和容易出错的环节。首先包含必要的头文件和定义引脚#include DHT.h #include Wire.h #include RTClib.h #include Adafruit_GFX.h #include Adafruit_PCD8544.h // 引脚定义 (必须与硬件连接一致) #define DHTPIN 2 #define DHTTYPE DHT11 #define LCD_RST 8 #define LCD_CE 7 #define LCD_DC 6 #define LCD_DIN 5 #define LCD_CLK 4 #define BACKLIGHT_PIN 3 // 初始化对象 DHT dht(DHTPIN, DHTTYPE); RTC_DS1307 rtc; Adafruit_PCD8544 display Adafruit_PCD8544(LCD_CLK, LCD_DIN, LCD_DC, LCD_CE, LCD_RST);在setup()函数中我们需要完成一系列初始化void setup() { Serial.begin(9600); // 用于调试输出 Wire.begin(); // 初始化I2C总线 dht.begin(); // 初始化DHT传感器 display.begin(); // 初始化液晶屏 display.setContrast(60); // 设置对比度这个值需要根据你的屏幕调整范围0-127 display.clearDisplay(); // 清屏 display.display(); // 将缓冲区内容刷到屏幕 pinMode(BACKLIGHT_PIN, OUTPUT); analogWrite(BACKLIGHT_PIN, 150); // 设置背光亮度PWM值0-255 // 初始化RTC如果RTC未运行则设置一个初始时间 if (!rtc.begin()) { Serial.println(找不到RTC模块); while (1); // 停止程序 } if (!rtc.isrunning()) { Serial.println(RTC未运行设置初始时间...); // 这行代码只在第一次运行或者RTC电池没电后需要运行。 // 设置时间为编译此程序的时间。 rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); // 你也可以手动设置一个固定时间例如 // rtc.adjust(DateTime(2024, 5, 27, 14, 30, 0)); // 2024年5月27日 14:30:00 } }关键点解析rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));这行代码非常巧妙它使用编译器宏__DATE__和__TIME__获取你编译程序时的电脑时间并将其设置为RTC的初始时间。这意味着你只需要在第一次烧录程序时确保电脑时间是准确的RTC就会获得一个近乎正确的时间。之后每次上电RTC都会从这个起点继续走时。切记这行代码在设置一次后应该注释掉否则每次重启Arduino时间都会被重置为编译时间。主循环loop()的逻辑是项目的核心void loop() { // 1. 读取RTC时间 DateTime now rtc.now(); // 2. 读取DHT11温湿度 (注意间隔至少2秒) float humidity dht.readHumidity(); float temperature dht.readTemperature(); // 读取摄氏温度 // 检查读取是否成功 if (isnan(humidity) || isnan(temperature)) { Serial.println(读取DHT11失败); // 可以在屏幕上显示错误信息这里简单返回下次循环再试 display.clearDisplay(); display.setCursor(0,0); display.println(Sensor Error); display.display(); delay(2000); // 等待一段时间再重试 return; } // 3. 在5110液晶上显示信息 display.clearDisplay(); // 清除显示缓冲区 display.setTextSize(1); // 设置字体大小1是默认每个字符6x8像素 display.setTextColor(BLACK); // 设置字体颜色单色屏只有黑和白 // 显示时间 (第一行) display.setCursor(0,0); display.print(now.year(), DEC); display.print(/); display.print(now.month(), DEC); display.print(/); display.print(now.day(), DEC); display.print( ); display.print(now.hour(), DEC); display.print(:); if(now.minute() 10) display.print(0); // 分钟补零 display.print(now.minute(), DEC); display.print(:); if(now.second() 10) display.print(0); // 秒钟补零 display.print(now.second(), DEC); // 显示温度 (第二行) display.setCursor(0, 10); display.print(Temp: ); display.print(temperature, 1); // 显示一位小数 display.print( C); // 显示湿度 (第三行) display.setCursor(0, 20); display.print(Humi: ); display.print(humidity, 1); // 显示一位小数 display.print( %); // 4. 将缓冲区内容发送到屏幕真正显示出来 display.display(); // 5. 延时。DHT11需要至少2秒的读取间隔这里延时3秒进行一次更新。 delay(3000); }代码中的经验与技巧DHT11读取间隔delay(3000)是必须的遵守了传感器的最小采样间隔要求。如果你想更实时地更新时间显示每秒刷新可以将时间显示和传感器读取分开。例如在主循环里每秒更新一次时间但每3秒才读取一次DHT11并更新温湿度显示。这需要用到millis()进行非阻塞定时是更高级的编程技巧。显示格式化代码中对分钟和秒钟进行了补零操作if(now.minute() 10) display.print(0);这样“14:05:09”就不会显示成“14:5:9”看起来更专业。错误处理对dht.read的返回值进行了isnan()检查。这是非常良好的习惯能防止因为偶尔的读取失败可能是信号干扰导致屏幕上显示“NaN”之类的乱码而是给出明确的错误提示。显示缓冲区display.clearDisplay()和display.display()是关键。前者清空的是内存中的图形缓冲区后者才将缓冲区的内容一次性发送到屏幕。所有print、draw操作都是在修改缓冲区最后调用display()才生效。这避免了屏幕刷新时的闪烁。5. 功能优化与深度扩展思路一个能显示时间和温湿度的基础版本已经完成。但作为一个DIY项目它的魅力在于可扩展性。这里分享几个我实践过的优化和扩展方向。5.1 提升显示效果与交互体验基础的文本显示有些单调。我们可以利用5110液晶的图形能力做得更好。绘制简易图标用display.drawBitmap()函数或自己画点display.drawPixel()在温度值旁边画一个温度计图标在湿度旁边画一个水滴图标直观性大大提升。自定义字体Adafruit_GFX库支持自定义字体。你可以找到或制作一套小号的数字字体专门用来显示时间让数字更大更醒目。添加背光自动控制通过一个光敏电阻检测环境光照在光线暗时自动调低或关闭背光analogWrite(BACKLIGHT_PIN, 0)节省电量也避免夜间刺眼。这需要增加一个模拟输入引脚读取光敏电阻值。按钮切换显示页面增加1-2个按钮实现显示内容的切换。比如页面1显示当前时间温湿度页面2显示今日的最高/最低温湿度记录页面3显示一个简单的温湿度随时间变化的趋势图需要记录历史数据。5.2 数据记录与离线分析单纯的实时显示价值有限记录历史数据才能分析趋势。添加SD卡模块使用SPI接口的SD卡模块每隔一段时间如10分钟将时间戳、温度、湿度数据以CSV格式写入文件。这样就能获得长时间的环境数据日志。使用EEPROM如果数据量不大比如只记录每天的最高最低值可以使用Arduino板载的EEPROM来存储。断电后数据不会丢失。但EEPROM有擦写次数限制约10万次不适合频繁写入。简单的趋势显示在5110液晶上实现一个简单的滚动图表。在内存中维护一个固定长度的数组比如84个数据点对应屏幕宽度每次新的温度读数进来就移除最旧的点绘制新的折线图。这能直观地看到最近一段时间内的温度变化趋势。5.3 联网与智能告警这是将本地设备升级为物联网节点的关键一步。更换主控为ESP8266或ESP32这两款芯片自带Wi-Fi功能价格与Arduino Uno相仿但性能更强。将代码移植过去引脚定义需要修改库大部分通用。数据上传云端通过Wi-Fi可以将温湿度数据上传到免费的物联网平台如Thingspeak、Blynk或者自建的Home Assistant服务器。这样你就可以在手机App或网页上远程查看数据甚至生成精美的图表。实现超限报警在代码中设定温度和湿度的合理范围如温度高于28℃或湿度低于30%。当数据超限时可以通过控制蜂鸣器发出警报或者通过Wi-Fi向你的手机发送一条推送通知需要配合云平台或IFTTT等服务。5.4 提升精度与稳定性如果对数据质量有更高要求可以从硬件和软件两方面入手。传感器升级将DHT11更换为DHT22或SHT30。DHT22测量范围更广-40~80℃精度更高湿度±2%温度±0.5℃。SHT30则是I2C接口的工业级传感器精度和稳定性更上一层楼当然价格也更高。更换传感器后只需在代码中修改传感器类型常量如#define DHTTYPE DHT22并调用对应的库即可整体框架不变。RTC升级将DS1307更换为DS3231。DS3231内部有温度补偿晶体振荡器年误差可控制在分钟级别远优于DS1307。它们都使用RTClib库且I2C地址相同通常是直接替换代码无需改动。软件滤波对于DHT11这类偶尔会有小跳变的传感器可以采用简单的软件滤波算法。比如“移动平均滤波”连续读取5次温度去掉一个最大值和一个最小值然后对剩下的3个值取平均。这能有效平滑数据消除偶然的毛刺。注意这需要额外的读取时间要平衡好实时性和稳定性。从一堆零散的模块到最终一个稳定运行、显示清晰的桌面小设备这个过程充满了调试的乐趣和解决问题的成就感。这个项目像一把钥匙打开了一扇门门后是传感器应用、人机交互、数据记录乃至物联网的广阔世界。我自己的那个版本后来加了一个小外壳放在书桌上已经稳定运行了两年多期间只换过一次RTC的电池。每当看到它准确显示着时间和环境数据都会想起当初连接第一根线、点亮第一行代码时的兴奋。动手去实现它你收获的将远不止一个温湿度计。