Arduino 带日期时间 LCD 时钟:DS3231 RTC + I2C 接口 + 三键调校全攻略
项目定位
本项目在“极简时钟”基础上引入 **DS3231 高精度 RTC** 与 **I2C LCD1602**,实现掉电走时、自动闰年/闰月、三按钮菜单式调校(年/月/日/时/分/秒),适合作为数据记录仪、定时开关、智能家居节点的时间基准模块。
硬件清单
| 元件 | 数量 | 关键参数 |
|---|---|---|
| Arduino UNO R3 | 1 | |
| I2C LCD1602(带 PCF8574 背包) | 1 | 地址通常 0x27 或 0x3F |
| DS3231 RTC 模块(带 CR2032 电池座) | 1 | 精度 ±2 ppm,-40~+85°C |
| 12 mm 轻触按钮 | 3 | 菜单/加/减,接 D2/D3/D4 |
| 10 kΩ 电阻 | 3 | 按钮下拉(也可用内部上拉反向逻辑) |
| 面包板 + 杜邦线 | 1套 |
I2C 总线拓扑
Arduino UNO DS3231 LCD1602(I2C)
A4 (SDA) ──────── SDA ──────── SDA
A5 (SCL) ──────── SCL ──────── SCL
5V ──────── VCC ──────── VCC
GND ──────── GND ──────── GND
// 两设备共享总线,只需各自地址不同即可
库依赖
Wire.h(内置)RTClib.h(Adafruit fork,支持 DS3231)LiquidCrystal_I2C.h(Frank de Brabander 版)
核心代码逻辑
1. 初始化与首次授时
#include
#include
#include
RTC_DS3231 rtc;
LiquidCrystal_I2C lcd(0x27, 16, 2); // 地址按实测修改
const uint8_t BTN_MENU=2, BTN_UP=3, BTN_DOWN=4;
enum { VIEW, SET_YEAR, SET_MONTH, SET_DAY, SET_HOUR, SET_MIN, SET_SEC } mode = VIEW;
DateTime now;
void setup() {
pinMode(BTN_MENU, INPUT_PULLUP);
pinMode(BTN_UP, INPUT_PULLUP);
pinMode(BTN_DOWN, INPUT_PULLUP);
lcd.init(); lcd.backlight();
if (!rtc.begin()) { lcd.print(F("RTC FAIL")); while(1); }
if (rtc.lostPower()) { // 首次上电或电池耗尽
rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); // 编译时刻写入
}
}
2. 非阻塞主循环 + 状态机菜单
void loop() {
now = rtc.now();
static uint32_t lastDraw = 0;
if (millis() - lastDraw >= 500) { lastDraw = millis(); drawView(); }
handleMenu();
}
void drawView() {
char buf[17];
sprintf(buf, "%04d/%02d/%02d", now.year(), now.month(), now.day());
lcd.setCursor(0,0); lcd.print(buf);
sprintf(buf, "%02d:%02d:%02d", now.hour(), now.minute(), now.second());
lcd.setCursor(0,1); lcd.print(buf);
}
void handleMenu() {
static uint32_t lastBtn = 0;
if (millis() - lastBtn < 150) return; // 简单去抖
if (digitalRead(BTN_MENU) == LOW) {
lastBtn = millis();
mode = (mode + 1) % 7; // 循环切换设置项
lcd.setCursor(0,1); lcd.print(F("SET: "));
lcd.print(modeNames[mode]);
}
if (mode != VIEW) {
if (digitalRead(BTN_UP) == LOW) { adjust(+1); lastBtn = millis(); }
if (digitalRead(BTN_DOWN) == LOW) { adjust(-1); lastBtn = millis(); }
}
}
void adjust(int8_t delta) {
DateTime t = now;
switch(mode) {
case SET_YEAR: t = DateTime(t.year()+delta, t.month(), t.day(), t.hour(), t.minute(), t.second()); break;
case SET_MONTH: t = DateTime(t.year(), constrain(t.month()+delta,1,12), t.day(), t.hour(), t.minute(), t.second()); break;
case SET_DAY: t = DateTime(t.year(), t.month(), constrain(t.day()+delta,1,31), t.hour(), t.minute(), t.second()); break;
case SET_HOUR: t = DateTime(t.year(), t.month(), t.day(), (t.hour()+delta+24)%24, t.minute(), t.second()); break;
case SET_MIN: t = DateTime(t.year(), t.month(), t.day(), t.hour(), (t.minute()+delta+60)%60, t.second()); break;
case SET_SEC: t = DateTime(t.year(), t.month(), t.day(), t.hour(), t.minute(), (t.second()+delta+60)%60); break;
}
rtc.adjust(t); // 立即写入 RTC
now = t; // 同步本地变量,避免读取延迟
}
进阶功能与避坑指南
- DS3231 温度补偿:芯片内置 TCXO,无需外部晶振,年误差 < 1 分钟。
- I2C 地址冲突:LCD 常见 0x27/0x3F,DS3231 固定 0x68。用
I2C_scanner例程确认。 - 按钮下拉 vs 上拉:原文用外部 10k 下拉(按下=HIGH),代码示例改用
INPUT_PULLUP(按下=LOW),硬件只需按钮一脚接 GND,省去电阻。 - 闰年/月天数:
DateTime构造函数自动校验,非法日期会自动修正(如 2 月 30 日 → 3 月 2 日)。 - 备用电池:CR2032 典型续航 3~5 年,建议每两年测量电压(>2.9 V 正常)。
扩展应用场景
- 定时开关:在
loop()中判断now.hour()==7 && now.minute()==0触发继电器。 - 数据记录器:SD 卡模块 + RTC 时间戳,生成
YYYYMMDD.CSV文件。 - NTP 校时网关:配合 ESP8266/ESP32 定期从 NTP 同步并写入 DS3231,实现“断网也准”。
总结:DS3231 + I2C LCD + 三键菜单,构建一个“上电即用、掉电不停、随时可调”的工业级时间显示模块,代码量 < 100 行,极适合二次集成。
制造工艺
- 复古 Numitron 管时钟:Arduino Nano 驱动,低温可靠省电
- 精准文字时钟:分钟级文字显示时间
- Arduino Due 驱动 3.2″ TFT 实现温度监测与实时时钟
- 用 Arduino UNO 制作柏林时钟 – DIY LED 矩阵时钟
- 3D 打印月相时钟:Arduino Nano 驱动 OLED+NeoPixel 还原真实月相
- 用 DS1302 RTC 和 Arduino UNO 制作可靠闹钟
- C++日期时间完全指南:ctime库、tm结构体与格式化输出
- Python 获取当前日期和时间:实用指南
- Python 日期时间处理实战:time 与 calendar 模块完全指南
- 掌握 Java 日期时间:构造器、方法与最佳实践
- Java 8 全新日期时间 API:线程安全、设计优雅、时区处理轻松搞定
- 确保新疫苗与生物制剂及时生产