Arduino 气象时钟 – 实时显示日期、时间、温度与湿度
组件与物料
| Arduino UNO | ×1 |
| DHT11 温湿度传感器(4针) | ×1 |
| Maxim Integrated DS3231 RTC 模块 | ×1 |
| 10kΩ 电阻 | ×1 |
| Adafruit 128×64 OLED 图形显示屏 | ×1 |
| 面包板 | ×1 |
| 杜邦线 | ×15 |
项目简介
想自制一个能同时显示日期、时间、温度和湿度的设备吗?其实比你想象的简单得多。只需几个关键组件即可实现。
Arduino Uno / Nano
首先需要一块微控制器板。本项目使用 Arduino Uno,Arduino Nano 也有足够的引脚可用。
DHT11 传感器
DHT11 是一款高性价比的数字温湿度传感器,利用电容式湿度传感元件和热敏电阻测量环境空气。读取间隔建议不低于 2 秒。
重要提示:设置好时间后,务必将设置时间的代码行注释掉并重新编译上传,否则 RTC 每次上电都会重置为编译时的时间。
DS3231 RTC 模块
DS3231 是一款高精度实时时钟模块,内置温度补偿晶振(TCXO)和晶体,比常见的 DS1307 精准得多。自带电池备份,主电源断开后仍能准确走时。
128×64 OLED 图形显示屏
用于显示所有数据。屏幕紧凑、亮度高,支持 SPI 或 I2C 接口(背部可切换)。也可使用其他兼容 Adafruit 库的同尺寸 OLED 屏。
代码
文件:Time_Temp_Humidity_Display.ino
// Arduino 气象时钟 - 日期、时间、温湿度显示
///////////////////////// OLED 部分 /////////////////////////
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// 软件 SPI 引脚定义
#define OLED_MOSI 9
#define OLED_CLK 10
#define OLED_DC 11
#define OLED_CS 12
#define OLED_RESET 13
Adafruit_SSD1306 display(OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);
#if (SSD1306_LCDHEIGHT != 64)
#error("高度不正确,请修复 Adafruit_SSD1306.h!");
#endif
//////////////////////////// DHT11 部分 ////////////////////////////
#include <dht.h>
dht DHT;
#define DHT11_PIN 3
int DHTtimer = 0;
//////////////////////////// DS3231 RTC 部分 ////////////////////////////
// SDA 接 A4,SCL 接 A5
#define DS3231_I2C_ADDRESS 0x68
// 十进制转 BCD
byte decToBcd(byte val) {
return ((val / 10 * 16) + (val % 10));
}
// BCD 转十进制
byte bcdToDec(byte val) {
return ((val / 16 * 10) + (val % 16));
}
void setup() {
Wire.begin();
Serial.begin(9600);
// 首次设置时间,设置后请注释掉下面这行并重新上传
// setDS3231time(0, 24, 12, 6, 3, 2, 17); // 秒、分、时、周、日、月、年
display.begin(SSD1306_SWITCHCAPVCC);
display.clearDisplay();
}
// 设置 DS3231 时间
void setDS3231time(byte second, byte minute, byte hour, byte dayOfWeek, byte dayOfMonth, byte month, byte year) {
Wire.beginTransmission(DS3231_I2C_ADDRESS);
Wire.write(0);
Wire.write(decToBcd(second));
Wire.write(decToBcd(minute));
Wire.write(decToBcd(hour));
Wire.write(decToBcd(dayOfWeek));
Wire.write(decToBcd(dayOfMonth));
Wire.write(decToBcd(month));
Wire.write(decToBcd(year));
Wire.endTransmission();
}
// 读取 DS3231 时间
void readDS3231time(byte *second, byte *minute, byte *hour, byte *dayOfWeek, byte *dayOfMonth, byte *month, byte *year) {
Wire.beginTransmission(DS3231_I2C_ADDRESS);
Wire.write(0);
Wire.endTransmission();
Wire.requestFrom(DS3231_I2C_ADDRESS, 7);
*second = bcdToDec(Wire.read() & 0x7f);
*minute = bcdToDec(Wire.read());
*hour = bcdToDec(Wire.read() & 0x3f);
*dayOfWeek = bcdToDec(Wire.read());
*dayOfMonth = bcdToDec(Wire.read());
*month = bcdToDec(Wire.read());
*year = bcdToDec(Wire.read());
}
// 显示时间、日期、星期、月份及模拟时钟
void displayTime() {
byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
readDS3231time(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month, &year);
// 串口输出
Serial.print(hour, DEC); Serial.print(":");
if (minute < 10) Serial.print("0");
Serial.print(minute, DEC); Serial.print(":");
if (second < 10) Serial.print("0");
Serial.print(second, DEC); Serial.print(" ");
Serial.print(dayOfMonth, DEC); Serial.print("/");
Serial.print(month, DEC); Serial.print("/");
Serial.print(year, DEC); Serial.print(" 星期: ");
// OLED 显示日期、年份
display.setCursor(50, 0);
display.print(dayOfMonth, DEC); display.print(",");
display.setCursor(91, 0);
display.print("20"); display.print(year, DEC);
// 显示时分秒
if (hour < 10) { display.setCursor(40, 10); display.print("0"); display.print(hour, DEC); display.print(":"); }
else { display.setCursor(40, 10); display.print(hour, DEC); display.print(":"); }
if (minute < 10) { display.setCursor(58, 10); display.print("0"); display.print(minute, DEC); display.print(":"); }
else { display.setCursor(58, 10); display.print(minute, DEC); display.print(":"); }
if (second < 10) { display.setCursor(75, 10); display.print("0"); display.print(second, DEC); }
else { display.setCursor(75, 10); display.print(second, DEC); }
// 星期显示
switch (dayOfWeek) {
case 1: Serial.println("Sunday"); display.setCursor(20, 0); display.print("Sun"); break;
case 2: Serial.println("Monday"); display.setCursor(20, 0); display.print("Mon"); break;
case 3: Serial.println("Tuesday"); display.setCursor(20, 0); display.print("Tue"); break;
case 4: Serial.println("Wednesday"); display.setCursor(20, 0); display.print("Wed"); break;
case 5: Serial.println("Thursday"); display.setCursor(20, 0); display.print("Thu"); break;
case 6: Serial.println("Friday"); display.setCursor(20, 0); display.print("Fri"); break;
case 7: Serial.println("Saturday"); display.setCursor(20, 0); display.print("Sat"); break;
}
// 月份显示
switch (month) {
case 1: display.setCursor(70, 0); display.print("Jan"); break;
case 2: display.setCursor(70, 0); display.print("Feb"); break;
case 3: display.setCursor(70, 0); display.print("Mar"); break;
case 4: display.setCursor(70, 0); display.print("Apr"); break;
case 5: display.setCursor(70, 0); display.print("May"); break;
case 6: display.setCursor(70, 0); display.print("Jun"); break;
case 7: display.setCursor(70, 0); display.print("Jul"); break;
case 8: display.setCursor(70, 0); display.print("Aug"); break;
case 9: display.setCursor(70, 0); display.print("Sep"); break;
case 10: display.setCursor(70, 0); display.print("Oct"); break;
case 11: display.setCursor(70, 0); display.print("Nov"); break;
case 12: display.setCursor(70, 0); display.print("Dec"); break;
}
// 绘制模拟时钟指针(时、分、秒)
float x1, y1, a, b;
const float pi = 3.14;
// 时针
a = ((hour - 15) * 30);
b = (a * pi) / 180;
x1 = 40 + (9 * cos(b));
y1 = 41 + (9 * sin(b));
display.drawLine(40, 41, x1, y1, WHITE);
// 分针
a = ((minute - 15) * 6);
b = (a * pi) / 180;
x1 = 40 + (17 * cos(b));
y1 = 41 + (17 * sin(b));
display.drawLine(40, 41, x1, y1, WHITE);
// 秒针
a = ((second - 15) * 6);
b = (a * pi) / 180;
x1 = 40 + (19 * cos(b));
y1 = 41 + (19 * sin(b));
display.drawLine(40, 41, x1, y1, WHITE);
// 绘制静止的表盘刻度
display.drawCircle(40, 41, 22, WHITE);
display.drawCircle(40, 41, 1, WHITE);
display.drawLine(40, 20, 40, 25, WHITE); // 12
display.drawLine(40, 63, 40, 58, WHITE); // 6
display.drawLine(62, 41, 57, 41, WHITE); // 3
display.drawLine(19, 41, 24, 41, WHITE); // 9
// 其余刻度省略...
}
// 读取温湿度并在 OLED 显示
void ReadTempAndHum() {
int chk = DHT.read11(DHT11_PIN);
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(70, 45);
display.print("Temp: "); display.print(DHT.temperature, 0); display.print("C");
display.setCursor(70, 56);
display.print("Hum: "); display.print(DHT.humidity, 0); display.print("%");
Serial.print(" Humidity: "); Serial.print(DHT.humidity, 1); Serial.print("%");
Serial.print(" | Temperature: "); Serial.print(DHT.temperature, 1); Serial.println("C");
}
void loop() {
displayTime();
DHTtimer++;
if (DHTtimer > 1) { // 每 2 秒读取一次 DHT11
display.fillRect(70, 45, 128, 64, BLACK);
ReadTempAndHum();
DHTtimer = 0;
}
delay(1000);
display.display();
// 清除时间日期区域,准备下一帧
display.fillRect(0, 0, 64, 64, BLACK);
display.fillRect(0, 0, 128, 20, BLACK);
}
原理图与 PCB
原理图文件:breadboard_diagram_QvhPKCpIo5.fzz、schematic_XKU0jQoWbx.fzz
注意:Fritzing 中未找到完全匹配的 OLED 模块,OLED 接线请参考代码中的引脚定义注释。
所需库文件
- Adafruit_SSD1306
- Adafruit_GFX
- DHT 传感器库
- Arduino RTC 库
制造工艺