基于Arduino与DHT11、DS1307、5110 LCD的温湿度时钟DIY全攻略

基于Arduino与DHT11、DS1307、5110 LCD的温湿度时钟DIY全攻略
1. 项目概述与核心价值最近在整理工作室翻出来一堆闲置的电子模块像DHT11温湿度传感器、DS1307实时时钟模块还有一块经典的诺基亚5110液晶屏。看着它们我就在想能不能用最少的成本把这些“老伙计”组合起来做一个既实用又有DIY乐趣的小玩意儿于是这个“液晶温湿度计兼时钟”的项目就诞生了。它的核心目标很简单在一块屏幕上同时、清晰地显示当前的温度、湿度以及精确的日期时间成为一个桌面上的环境信息小管家。对于刚接触Arduino或者嵌入式开发的朋友来说这个项目堪称“黄金练手项目”。它几乎涵盖了入门到进阶的所有关键环节你需要学习如何与数字传感器DHT11通信如何与I2C接口的实时时钟芯片DS1307打交道还要驱动一个非标准的点阵液晶屏5110 LCD。整个过程下来你对引脚配置、时序控制、库函数调用、数据格式化显示会有非常深刻的理解。而对于有经验的老手这个项目则是一个绝佳的“框架”你可以轻松地在此基础上扩展比如增加数据记录到SD卡、通过Wi-Fi/蓝牙将数据上传到手机、设置温湿度报警阈值甚至做成一个迷你气象站。我选择Arduino Uno作为主控原因无他就是生态好、资料多、社区活跃。无论你遇到什么问题几乎都能找到解决方案。DHT11虽然精度不算顶尖温度±2°C湿度±5%RH但对于室内环境监测完全够用关键是价格极其便宜几块钱就能买到。DS1307需要一颗纽扣电池作为后备电源这样即使主控断电时间也不会丢失这是做一个可靠时钟的基础。诺基亚5110液晶屏84x48的分辨率单色显示对比度高且非常省电虽然“年纪”不小了但显示字符和简单图形效果依然出色性价比无敌。这个项目的成品你可以放在书桌、床头、实验室或者温室里实时了解环境状况。下面我就把从硬件连接到软件调试再到功能优化的完整过程以及我踩过的那些坑毫无保留地分享出来。2. 硬件选型、电路设计与连接详解动手之前先把所有家当摆出来理清思路。硬件连接是项目的骨架连接错了代码写得再好也是白搭。2.1 核心元件清单与特性分析主控制器Arduino Uno R3为什么是它开发板中的“瑞士军刀”ATmega328P芯片性能足够14路数字I/O其中6路支持PWM6路模拟输入一个16MHz晶振。最重要的是其丰富的库支持和庞大的社区能让你在遇到问题时快速找到答案。对于本项目它提供了充足的I/O口和稳定的5V/3.3V输出。温湿度传感器DHT11工作原理它是一款复合传感器内部包含一个电阻式感湿元件和一个NTC测温元件并集成了一个8位单片机负责将模拟信号转换为数字信号通过单总线协议与主控通信。关键参数测量范围湿度20-90%RH温度0-50°C精度湿度±5%RH温度±2°C响应时间5秒。虽然精度一般但胜在稳定、响应快、接口简单。注意DHT11对时序要求比较严格必须按照数据手册的时序图来操作这也是为什么我们强烈建议使用库的原因。实时时钟模块DS1307工作原理一款经典的I2C接口实时时钟芯片内部有56字节的NV SRAM最重要的是它自带一个计时器可以产生秒、分、时、日、月、年等信息。外接一个32.768kHz的晶振提供时钟基准一颗CR2032纽扣电池作为备用电源保证主电源断开后时钟继续运行。为什么选模块直接买DS1307模块商家通常已经焊好了晶振、备用电池座以及上拉电阻省去了自己搭建外围电路的麻烦即插即用。显示模块诺基亚5110 LCD (PCD8544控制器)屏幕参数84列 x 48行像素单色显示。控制器是Philips的PCD8544通过SPI或模拟SPI协议通信。优点对比度可调显示清晰功耗极低价格便宜。缺点是分辨率低但显示几行文字和简单图标绰绰有余。注意市面上5110屏的引脚顺序可能不同最常见的是8Pin排针连接时务必对照屏幕背面或卖家提供的引脚图。2.2 电路连接图与分步接线指南这里给出一个清晰的接线表格。请务必在断电状态下进行所有连接Arduino Uno 引脚连接的模块与引脚说明与注意事项5VDHT11 VCC, DS1307模块 VCC, 5110 LCD VCC为所有模块提供电源。确保你的5110 LCD是5V兼容的大多数是如果是3.3V版本请接3.3V。GNDDHT11 GND, DS1307模块 GND, 5110 LCD GND共地所有模块的GND必须连接到Arduino的GND这是电路正常工作的基础。Digital 2DHT11 DATA (或OUT)数据引脚。DHT11是单总线这个引脚需要接一个4.7kΩ - 10kΩ的上拉电阻到VCC正极模块上通常已集成。Analog 4 (A4)DS1307模块 SDAI2C数据线。Analog 5 (A5)DS1307模块 SCLI2C时钟线。Digital 75110 LCD RST (复位)复位引脚低电平有效。Digital 65110 LCD CE (或CS)片选引脚低电平选中。Digital 55110 LCD DC (或D/C)数据/命令选择引脚高电平为数据低电平为命令。Digital 45110 LCD DINSPI数据输入线。Digital 35110 LCD CLKSPI时钟线。3.3V5110 LCD LED (背光)背光阳极。重要背光LED通常需要串联一个限流电阻如100-220Ω再接到3.3V直接接5V可能烧毁背光。很多模块已集成电阻接3.3V即可点亮。接线心得与避坑指南电源顺序建议先接GND再接VCC最后接信号线。拆卸时顺序相反。杜邦线检查劣质杜邦线接触不良是新手最常见的“玄学”问题。接线后轻轻拉扯一下线头确保插紧了。如果设备工作不稳定首先怀疑杜邦线。5110屏引脚这是最容易接错的地方。一定以你手上屏幕的丝印或卖家资料为准。如果接反了通常不会烧屏但肯定不显示。I2C地址冲突DS1307的固定I2C地址是0x68。如果你的项目未来要添加其他I2C设备如OLED屏、BMP280气压计要确保地址不冲突。背光供电给5110背光供电时一定要确认模块是否自带限流电阻。如果不确定最保险的方法是在正极LED串联一个220Ω的电阻再接到3.3V。直接接5V极易导致背光过流发烫缩短寿命甚至当场损坏。3. 软件环境搭建与核心库解析硬件连好了接下来就是让大脑代码运转起来。我们需要准备好编程环境和帮手库文件。3.1 Arduino IDE安装与基础配置下载与安装前往Arduino官网下载IDE。建议使用较新的稳定版如1.8.x或2.x。安装过程很简单一路下一步即可。选择开发板与端口用USB线连接Arduino Uno和电脑。打开IDE在工具-开发板中选择Arduino Uno。然后在工具-端口中选择对应的COM口Windows或/dev/tty.usbmodemXXXMac/Linux。测试Blink打开文件-示例-01.Basics-Blink点击上传。如果Arduino上的“L”灯开始闪烁恭喜你环境和连接都正常。3.2 必需库的安装与功能介绍本项目需要三个核心库它们封装了与硬件通信的复杂细节让我们能用简单的函数调用来操作设备。DHT sensor library功能用于读取DHT11以及DHT22等传感器的温湿度数据。它严格处理了单总线通信时序并提供了温度单位转换摄氏/华氏和计算热指数体感温度的功能。安装在IDE中点击项目-加载库-管理库...在搜索框中输入“DHT sensor”找到由Adafruit维护的库进行安装。注意安装此库时通常会提示你同时安装Adafruit Unified Sensor库这是它的依赖库务必一起安装。RTClib功能一个通用的实时时钟库完美支持DS1307。它提供了设置、读取时间的简单接口并能处理闰年、每月天数等复杂逻辑。安装同样在库管理中搜索“RTClib”选择由Adafruit或Jeelib等维护的版本安装。Adafruit PCD8544 Nokia 5110 LCD Library功能专门用于驱动PCD8544控制器的5110液晶屏。它提供了绘制像素、线条、矩形、圆形以及显示文本内置了字体的高级功能。安装库管理中搜索“PCD8544”安装Adafruit的版本。同样它可能依赖Adafruit GFX库请一并安装。库管理心得尽量使用库管理器安装避免手动下载复制可能带来的路径或版本问题。安装后可以在文件-示例中找到对应库的示例程序这是学习库用法的最佳途径。如果遇到编译错误提示找不到某个头文件首先检查库是否安装成功其次检查库的依赖库是否都已安装。4. 代码逐行解析与功能实现现在进入核心环节——编写代码。我将把代码分成几个逻辑块并详细解释每一部分的作用和原理。4.1 全局定义、库引入与对象初始化// 1. 引入必要的库 #include DHT.h #include Wire.h // I2C通信库DS1307需要 #include RTClib.h // 实时时钟库 #include Adafruit_GFX.h // 图形库5110库依赖它 #include Adafruit_PCD8544.h // 5110液晶驱动库 // 2. 定义硬件连接引脚 (根据你的实际接线修改) #define DHTPIN 2 // DHT11数据引脚接在数字引脚2 #define DHTTYPE DHT11 // 指定传感器类型为DHT11 // 5110 LCD引脚定义 (使用软件SPI) #define LCD_RST 7 #define LCD_CE 6 #define LCD_DC 5 #define LCD_DIN 4 #define LCD_CLK 3 // 3. 创建传感器和显示对象 DHT dht(DHTPIN, DHTTYPE); // 初始化DHT对象 RTC_DS1307 rtc; // 初始化RTC对象 // 初始化LCD对象参数为CLK, DIN, DC, CE, RST Adafruit_PCD8544 display Adafruit_PCD8544(LCD_CLK, LCD_DIN, LCD_DC, LCD_CE, LCD_RST); // 4. 定义全局变量 float temperature 0.0; float humidity 0.0; char daysOfTheWeek[7][4] {Sun, Mon, Tue, Wed, Thu, Fri, Sat}; // 星期缩写代码解读与注意事项#define是宏定义用于给引脚起一个有意义的名字提高代码可读性也方便后期修改。创建对象如dht,rtc,display相当于为每个硬件模块在代码中建立了一个“遥控器”后续通过这个“遥控器”来发送指令。daysOfTheWeek数组用于将RTC库返回的星期数0-60代表周日转换为易读的字符串。4.2 Setup()函数一次性初始化工作void setup() { Serial.begin(9600); // 启动串口用于调试输出非必需但强烈推荐 // 初始化DHT传感器 dht.begin(); Serial.println(DHT11 Initialized.); // 初始化I2C总线这是DS1307通信的基础 Wire.begin(); // 初始化RTC if (!rtc.begin()) { Serial.println(Couldnt find RTC!); while (1); // 如果找不到RTC程序停在这里 } // 检查RTC是否正在运行如果未运行则设置为编译时间 if (!rtc.isrunning()) { Serial.println(RTC is NOT running, setting the time...); // 这行代码会将当前电脑的编译时间设置为RTC的时间。 // 仅第一次使用或更换电池后需要。设置完成后请注释掉这行 rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); } Serial.println(RTC Initialized.); // 初始化5110 LCD display.begin(); // 设置对比度值范围40-60之间效果较好根据你的屏幕调整 display.setContrast(50); display.clearDisplay(); // 清屏 display.setTextSize(1); // 设置字体大小1是标准大小 display.setTextColor(BLACK); // 设置字体颜色单色屏只有黑和白 display.setCursor(0,0); // 设置光标起始位置左上角 display.println(System Booting...); display.display(); // 将缓冲区内容刷到屏幕上显示 delay(2000); // 显示启动信息2秒 display.clearDisplay(); Serial.println(All modules initialized successfully.); }关键点解析rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));这行是设置RTC时间的关键。它利用编译时Arduino IDE获取的电脑当前时间来设置RTC。重要上传代码设置一次时间后必须把这行代码注释掉或删除否则每次重启Arduino时间都会被重置为编译时刻。display.display();是5110库的一个特点。所有的绘图、文字操作都是在内存中的一个“缓冲区”进行的只有调用display()函数才会把缓冲区的内容一次性更新到屏幕上。这避免了屏幕闪烁。串口输出 (Serial.println) 是调试神器。通过它你可以确认传感器是否成功初始化、读出的数据是否正确极大提高排查效率。4.3 Loop()函数持续读取与显示void loop() { // 1. 读取温湿度数据 humidity dht.readHumidity(); // 读湿度 temperature dht.readTemperature(); // 读温度摄氏度 // 检查读取是否成功返回NaN表示失败 if (isnan(humidity) || isnan(temperature)) { Serial.println(Failed to read from DHT sensor!); display.clearDisplay(); display.setCursor(0,0); display.println(DHT Error!); display.display(); delay(2000); // 错误时等待2秒再重试 return; // 跳过本次循环的剩余部分 } // 2. 读取RTC时间 DateTime now rtc.now(); // 获取当前时间对象 // 3. 在串口监视器打印数据调试用 Serial.print(Temp: ); Serial.print(temperature); Serial.print( *C, ); Serial.print(Humidity: ); Serial.print(humidity); Serial.println( %); Serial.print(Date: ); Serial.print(now.year(), DEC); Serial.print(/); Serial.print(now.month(), DEC); Serial.print(/); Serial.print(now.day(), DEC); Serial.print( (); Serial.print(daysOfTheWeek[now.dayOfTheWeek()]); Serial.print() ); Serial.print(Time: ); Serial.print(now.hour(), DEC); Serial.print(:); Serial.print(now.minute(), DEC); Serial.print(:); Serial.println(now.second(), DEC); // 4. 在5110 LCD上显示数据 display.clearDisplay(); // 清除上一帧的显示 // 显示温度 display.setCursor(0, 0); // 第0行第0列 display.print(Temp: ); display.print(temperature, 1); // 显示一位小数 display.print( C); // 绘制一个温度计小图标简单矩形加线 display.drawRect(55, 0, 10, 8, BLACK); // 温度计主体 display.fillRect(65, 2, 2, 4, BLACK); // 温度计底部 // 显示湿度 display.setCursor(0, 10); // 第10像素行大约第二行 display.print(Hum: ); display.print(humidity, 1); // 显示一位小数 display.print( %); // 绘制一个水滴小图标 display.drawCircle(55, 13, 3, BLACK); display.fillCircle(55, 15, 2, BLACK); // 显示日期 display.setCursor(0, 20); display.print(now.year(), DEC); display.print(/); if(now.month()10) display.print(0); // 月份补零 display.print(now.month(), DEC); display.print(/); if(now.day()10) display.print(0); // 日期补零 display.print(now.day(), DEC); display.print( ); display.print(daysOfTheWeek[now.dayOfTheWeek()]); // 显示时间 display.setCursor(0, 30); if(now.hour()10) display.print(0); // 小时补零 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); // 5. 更新屏幕显示 display.display(); // 6. 延时。DHT11两次读取之间需要至少2秒间隔。 delay(2000); }代码逻辑与优化技巧数据有效性检查if (isnan(humidity)...)这段判断至关重要。DHT11通信容易受干扰读取失败是常有的事。如果不检查屏幕上会显示“NaN”非数字导致程序可能崩溃。这里我们选择在读取失败时显示错误信息然后return跳出本次循环等待下一次loop()再尝试。时间格式化if(now.hour()10) display.print(0);这段代码是为了让时间显示更美观例如“09:05:08”而不是“9:5:8”。这是产品化思维的一个小体现。显示布局通过setCursor(x, y)精确控制每行文字的起始位置。5110屏幕的y轴坐标单位是像素字体大小为1时一行字符大约8像素高。我们通过计算0 10 20 30来让四行信息均匀分布。简单图形drawRect,fillCircle这些函数来自Adafruit_GFX库让我们可以绘制简单的图标增加显示界面的趣味性。你可以发挥创意绘制更复杂的图案。延时delay(2000);放在循环末尾。一方面满足DHT11的采样间隔要求数据手册建议不小于2秒另一方面也控制了屏幕刷新率。太快刷新没必要且增加传感器负担太慢则感觉迟钝。5. 高级功能扩展与界面优化基础功能实现后我们可以让它变得更智能、更美观。5.1 增加温湿度历史趋势显示5110屏幕有84x48像素我们可以利用下部区域绘制一个简单的趋势图。例如只显示温度用一行像素的高度约8像素来代表温度范围。// 在全局变量区增加 float tempHistory[84]; // 存储最近84个温度值对应屏幕宽度 int historyIndex 0; // 在loop()中读取温度后将数据存入数组 tempHistory[historyIndex] temperature; historyIndex (historyIndex 1) % 84; // 循环覆盖旧数据 // 在显示时间后绘制趋势图 display.drawRect(0, 38, 84, 10, BLACK); // 绘制一个趋势图框 float maxTemp -100, minTemp 100; for (int i0; i84; i) { if(tempHistory[i] maxTemp) maxTemp tempHistory[i]; if(tempHistory[i] minTemp) minTemp tempHistory[i]; } // 防止除零 if (maxTemp minTemp) { maxTemp minTemp 1.0; } // 绘制折线 for (int i0; i83; i) { int x0 i; int y0 47 - map(tempHistory[i], minTemp, maxTemp, 0, 9); // 映射到框内高度 int x1 i1; int y1 47 - map(tempHistory[i1], minTemp, maxTemp, 0, 9); display.drawLine(x0, y0, x1, y1, BLACK); }原理我们用一个数组tempHistory循环记录最近84次的温度读数。绘制时先找出这段时间内的最高和最低温度然后将每个温度值线性映射到图表框的垂直像素位置上map函数最后用drawLine将点连成线。这样就能直观看到温度是上升还是下降趋势。5.2 添加按钮控制与功能切换增加一个按钮实现显示模式的切换比如在“温湿度时钟”和“纯数字大时钟”之间切换。// 定义按钮引脚 #define BUTTON_PIN 8 // 全局变量 int displayMode 0; // 0: 综合模式 1: 大时钟模式 // 在setup()中初始化按钮引脚为输入上拉模式 pinMode(BUTTON_PIN, INPUT_PULLUP); // 在loop()开头检测按钮简单防抖 if (digitalRead(BUTTON_PIN) LOW) { // 按钮按下接GND delay(50); // 简单延时防抖 if (digitalRead(BUTTON_PIN) LOW) { displayMode 1 - displayMode; // 切换模式 while(digitalRead(BUTTON_PIN) LOW); // 等待按钮释放 delay(200); // 释放后延时 } } // 在loop()的显示部分根据displayMode值决定显示内容 if (displayMode 0) { // 显示原有的综合信息温度、湿度、日期、时间 // ... (原有显示代码) } else { // 大时钟模式只显示放大的时间 display.clearDisplay(); display.setTextSize(2); // 使用2倍字体 display.setCursor(5, 5); if(now.hour()10) display.print(0); display.print(now.hour(), DEC); display.print(:); if(now.minute()10) display.print(0); display.print(now.minute(), DEC); display.setTextSize(1); display.setCursor(20, 30); display.print(now.year(), DEC); display.print(/); display.print(now.month(), DEC); display.print(/); display.print(now.day(), DEC); display.display(); }实现要点这里使用了Arduino的内部上拉电阻INPUT_PULLUP按钮另一端接地。按下时引脚读到低电平LOW。简单的延时去抖和等待释放逻辑可以避免一次按下触发多次。模式切换的逻辑清晰易于扩展更多模式。5.3 优化显示效果与低功耗考量自定义字体Adafruit_GFX库支持使用自定义字体。你可以找一些小号的数字字体让大时钟模式显示更美观。但这会占用更多的程序存储空间。局部刷新为了减少屏幕闪烁和加快刷新可以只刷新变化的部分比如秒数。但这需要更复杂的逻辑来判断哪些区域需要更新。低功耗模式如果使用电池供电可以考虑让Arduino在大部分时间进入休眠模式Sleep Mode每秒或每10秒被DS1307的SQW/OUT引脚输出的方波信号通过中断唤醒一次读取数据并刷新屏幕然后继续休眠。这能极大延长电池寿命。这涉及到中断和低功耗编程是进阶的好课题。6. 常见问题排查与调试心得实录做项目不可能一帆风顺以下是我和学员们常遇到的问题及解决方法。6.1 编译与上传问题问题编译时提示fatal error: DHT.h: No such file or directory。排查库没有正确安装。打开项目-加载库-管理库...确认DHT sensor library和Adafruit Unified Sensor已安装。有时需要重启Arduino IDE。问题上传代码时提示avrdude: stk500_recv(): programmer is not responding。排查检查USB线是否插好尝试换一根线有些线只能充电不能传数据。检查IDE中选择的端口和开发板型号是否正确。尝试按一下Arduino板上的复位按钮然后在几秒内迅速点击上传按钮。6.2 硬件连接与通信问题问题5110屏幕一片漆黑背光亮但无显示。排查首要怀疑接线90%的问题出在这里。请严格按照接线表逐一核对每根线特别是RST, CE, DC这几个控制引脚。检查对比度在setup()中尝试调整setContrast()的值从40调到60。对比度不对屏幕有内容也看不见。检查初始化顺序确保display.begin()被调用。问题串口监视器显示Couldnt find RTC!。排查检查DS1307模块的VCC和GND是否接反或接触不良。检查A4(SDA)和A5(SCL)是否接对线序是否正确。I2C设备需要上拉电阻。虽然模块通常自带但如果线路较长或干扰大可以尝试在SDA和SCL线上各加一个4.7kΩ电阻上拉到5V。使用I2C扫描程序Arduino IDE示例Wire-scanner来检测设备地址0x68是否存在。问题串口持续打印Failed to read from DHT sensor!。排查DHT11对时序极其敏感。确保数据引脚如D2的连接牢固。必须接上拉电阻检查你的DHT11模块是否自带黄色板子通常有如果没有必须在DATA引脚和VCC之间焊接一个4.7kΩ - 10kΩ的电阻。尝试增加读取之间的延迟。delay(2000)是最低要求可以尝试delay(2500)。电源问题。尝试单独给DHT11供电或者换一个电源接口。长距离接线也会导致电压下降。6.3 功能与数据问题问题时间显示不正确或者每次重启都回到一个固定时间。排查你没有注释掉rtc.adjust(...)这行代码这是最常见的原因。设置一次时间后必须注释或删除这行代码再重新上传。DS1307的备用电池CR2032没电了。换一颗新电池。检查32.768kHz晶振是否焊接牢固。问题温湿度读数明显不准或者跳变很大。排查DHT11的精度本身有限这是正常现象。避免将其放置在空调出风口、暖气旁或阳光直射下。传感器需要时间与环境平衡。刚上电或移动位置后等待几分钟再读数。检查传感器表面的保护膜是否撕掉。问题屏幕显示乱码或部分内容不更新。排查程序逻辑问题。检查在显示新内容前是否调用了display.clearDisplay()。如果没有清屏新旧内容会叠加。内存溢出。如果使用了大量变量或自定义字体可能导致内存不足程序行为异常。可以尝试简化代码。电源干扰。尝试在Arduino的5V和GND之间并联一个100uF的电解电容以稳定电源。6.4 我的实操心得调试分步进行不要一次性写完所有代码。先分别测试各个模块。例如先上传一个只读取DHT11并在串口打印的程序确保传感器工作正常。再单独测试DS1307最后单独测试5110显示“Hello World”。所有模块独立工作后再整合到一起。善用串口监视器它是你最好的朋友。把所有关键变量原始读数、处理后的数据、状态标志都打印出来能帮你快速定位问题出在哪个环节。电源要干净当同时驱动多个模块特别是屏幕背光瞬间电流较大时USB供电可能不足导致系统不稳定。使用一个可靠的5V/2A的手机充电器给Arduino供电会稳定很多。耐心处理杜邦线接触不良是“万恶之源”。对于长期运行的项目考虑使用焊接或者排母/排针直接插接比杜邦线可靠得多。代码版本管理当你尝试添加新功能如趋势图、按钮时先把能稳定工作的代码另存为一个新文件。这样如果新代码搞砸了你可以轻松回退到上一个稳定版本。这个项目虽然小但“麻雀虽小五脏俱全”。从硬件连接到软件调试从基础功能到进阶优化它完整地走完了一个嵌入式产品原型的开发流程。当你看到屏幕上稳定地显示出温湿度和时间的那一刻所有的调试和排查都是值得的。希望这份超详细的指南能帮你少走弯路顺利做出属于自己的桌面环境信息站。