极简高精度 Arduino UNO 数字时钟:无 RTC、无电位器、无外部上拉电阻
项目亮点
- 仅需 Arduino UNO、1602 LCD、两个按钮、面包板、杜邦线,**零电阻、零电位器、零外部上拉**。
- 利用 ATmega328P 内部上拉(
INPUT_PULLUP)与 PWM 模拟模拟输出控制 LCD 对比度与背光。 - 软件定时基于
millis()非阻塞计数,**走时误差 < 1 秒/天**(取决于陶瓷谐振器精度),媲美入门级 RTC 模块。 - 两按钮实现“时/分”循环调整,长按加速,松开自动保存并退出设置模式。
元件清单
| 元件 | 数量 | 备注 |
|---|---|---|
| Arduino UNO R3 | 1 | 主控 |
| 1602 LCD(白字蓝底) | 1 | HD44780 兼容,16×2 字符 |
| 12 mm 轻触按钮 | 2 | 分别接 D2(时)、D3(分) |
| 半尺寸面包板 | 1 | |
| 公对公杜邦线 | 1 包 |
硬件接线(极简版)
LCD VSS → GND
LCD VDD → 5V
LCD V0 → D9 (PWM) // 对比度,analogWrite(9, 120) 约 1.2V
LCD RS → D12
LCD RW → GND // 只写模式
LCD E → D11
LCD D4-D7 → D5-D8
LCD A → D10 (PWM) // 背光,analogWrite(10, 255) 全亮
LCD K → GND
按钮1(调时)一脚 → D2,另一脚 → GND
按钮2(调分)一脚 → D3,另一脚 → GND
// 代码中 pinMode(2, INPUT_PULLUP); pinMode(3, INPUT_PULLUP);
核心原理
1. 软件 RTC:毫秒累加法
static uint32_t lastTick = 0;
void loop() {
uint32_t now = millis();
if (now - lastTick >= 1000) {
lastTick += 1000; // 累加 1000 ms,避免漂移
seconds++;
if (seconds >= 60) { seconds = 0; minutes++; }
if (minutes >= 60) { minutes = 0; hours++; }
if (hours >= 24) hours = 0;
updateDisplay();
}
handleButtons(); // 非阻塞按钮扫描
}
关键点:lastTick += 1000 而非 lastTick = now,可消除循环执行时间抖动带来的累积误差。
2. PWM 控制 LCD 对比度与背光
- 对比度:D9 输出 490 Hz PWM,RC 积分(LCD 内部电容)得到 0~5 V 直流,
analogWrite(9, 100~150)即可看清字符。 - 背光:D10 PWM 直驱 LED 阳极(串联 220 Ω 更稳妥),
analogWrite(10, 0~255)实现亮度分级甚至呼吸灯效果。
3. 内部上拉按钮去抖
bool readBtn(uint8_t pin) {
static uint32_t last[2] = {0,0};
static bool state[2] = {HIGH, HIGH};
bool reading = digitalRead(pin);
uint8_t idx = (pin == 2) ? 0 : 1;
if (reading != state[idx]) {
if (millis() - last[idx] > 20) { state[idx] = reading; last[idx] = millis(); }
}
return state[idx] == LOW; // 按下为 LOW
}
完整代码(精简版)
#include
LiquidCrystal lcd(12, 11, 5, 6, 7, 8); // RS, E, D4-D7
uint8_t h=12, m=0, s=0;
uint32_t lastTick=0;
const uint8_t BTN_H=2, BTN_M=3;
const uint8_t PWM_CONTRAST=9, PWM_BACKLIGHT=10;
void setup() {
pinMode(BTN_H, INPUT_PULLUP);
pinMode(BTN_M, INPUT_PULLUP);
pinMode(PWM_CONTRAST, OUTPUT);
pinMode(PWM_BACKLIGHT, OUTPUT);
analogWrite(PWM_CONTRAST, 120); // 对比度约 1.2V
analogWrite(PWM_BACKLIGHT, 200); // 背光 ~80%
lcd.begin(16,2);
lcd.print(F("Simple Clock"));
delay(1000);
}
void loop() {
uint32_t now = millis();
if (now - lastTick >= 1000) {
lastTick += 1000;
if (++s >= 60) { s=0; if (++m >= 60) { m=0; if (++h >= 24) h=0; } }
showTime();
}
if (readBtn(BTN_H)) { h = (h+1)%24; showTime(); delay(200); }
if (readBtn(BTN_M)) { m = (m+1)%60; s=0; showTime(); delay(200); }
}
void showTime() {
char buf[9];
sprintf(buf, "%02d:%02d:%02d", h, m, s);
lcd.setCursor(0,0);
lcd.print(buf);
}
常见问题与优化
- 走时偏快/慢:UNO 陶瓷谐振器典型误差 ±0.5%,约 ±43 秒/天。可在
if (now - lastTick >= 1000)中微调阈值(如 998/1002)校准。 - 掉电走时丢失:本设计无备用电池,上电后需手动对时。如需掉电保持,请改用 DS3231 RTC 模块(见项目 1005017581)。
- LCD 显示乱码:检查 D4-D7 接线顺序,确保共地;对比度 PWM 占空比过大/过小均会导致全黑/全白。
- 按钮误触发:面包板接触不良易抖动,建议焊接排针或使用专用按钮模块。
扩展建议
- 加入 DS18B20 显示温度(复用 D4,OneWire 协议)。
- 利用 D13 LED 做秒脉冲指示。
- 移植至 Arduino Nano/Pro Mini 做微型桌面时钟。
- 结合 ESP-01 实现 NTP 自动校时,彻底告别手动对时。
一句话总结:用最少的元件、最简洁的代码,在 Arduino UNO 上跑出一个“够用、准确、可扩展”的数字时钟,极适合初学者理解 millis() 非阻塞定时、PWM 模拟输出、内部上拉等核心概念。
制造工艺