Arduino DS3231 数字时钟教程:三样零件自制桌面时钟

Ilustrasi jam digital Arduino yang siap dipasang dengan modul DS3231 dan paparan LCD1602 di atas meja

要做一个 Arduino 数字时钟,你只需要三样零件:Arduino Uno、DS3231 实时时钟模块(RTC)和带 I2C 转接板的 LCD1602 液晶显示屏。两个模块挂在同一条 I2C 总线上——每个模块只接四根线,而且数据线共用 A4 和 A5 两个引脚——DS3231 上的备用纽扣电池则保证断电后时间照样走。

为什么需要 DS3231 模块?Arduino 不能自己算时间吗?

DS3231 芯片之所以存在,是因为 Arduino Uno 其实不知道现在几点。millis() 函数只是在数板子通电了多久,而这个计数在每次断电或按下 reset 按钮时都会归零。没有 RTC(real-time clock,实时时钟),你的“时钟”每次重新上电都从零开始。

DS3231 靠两个优势解决这个问题。第一,它内置 TCXO——温度补偿晶振——精度达到 0°C 到 +40°C 范围内 ±2ppm,也就是一年大约只差 ±1 分钟。第二,模块背面的电池座让时间在 Arduino 断电几个月后照样继续走。时间设一次,时钟就一直准。

DS3231 和 LCD1602 怎么接到 Arduino Uno?

DS3231 模块和 LCD1602 I2C 显示屏都是通过 I2C 总线通信的,所以接线非常简单:两个模块共用 Arduino Uno 上同样的引脚。不用拉十根线到 LCD——每个模块四个引脚,就这么多。

模块引脚 Arduino Uno 引脚 备注
DS3231 VCC 5V 这个模块 3.3V 或 5V 都能接受;配 Uno 就用 5V。
DS3231 GND GND 地线必须共用。
DS3231 SDA A4 (SDA) I2C 数据线。
DS3231 SCL A5 (SCL) I2C 时钟线。
LCD1602 I2C VCC 5V LCD 转接板需要 5V 显示才清晰。
LCD1602 I2C GND GND 共用同一条地线。
LCD1602 I2C SDA A4 (SDA) 和 DS3231 同一个引脚——本来就是这样接的。
LCD1602 I2C SCL A5 (SCL) 和 DS3231 同一个引脚

注意看 SDA 和 SCL:两个模块接的是同样的引脚。这正是 I2C 的妙处——每个设备都有自己的地址,就像同一条街上的不同门牌。DS3231 用的是地址 0x68,而同一块模块上搭载的 AT24C32 EEPROM 芯片则在 0x57。LCD 转接板通常在 0x27(PCF8574 芯片)或 0x3F(PCF8574A)。不确定的话,上传(upload)一个 I2C scanner sketch——它会把总线上所有存在的地址列出来。

一个实际的小提醒:有些紧凑型 DS3231 模块出货时排针(header pin)还没焊上。开工前先把排针焊好——我们的排针焊接教程一步一步教你。面包板上接触不良,正是“RTC tak jumpa”(马来文,意思是“找不到 RTC”)这个报错的头号原因。

I2C 总线概念图:DS3231 和 LCD1602 共用同样的两条线连到 Arduino
一条 I2C 总线,多个设备:DS3231 和 LCD 共用 A4 和 A5 两个引脚。

LIR2032 还是 CR2032 纽扣电池——哪种对 DS3231 才安全?

大多数紧凑型 DS3231 模块上有一个简单的充电电路——一个 1N4148 二极管加一个 200Ω 电阻——设计给 LIR2032(可充电)型号的纽扣电池,模块每次接上 5V 电源就会给它充电。很多人就是在这里出事:普通的 CR2032 是不可充电的,但尺寸一模一样,放进同一个电池座刚刚好。

如果把 CR2032 装进充电电路还在工作的模块,充电电流就会被强行灌进一颗不该充电的电池。电池可能鼓包、漏液,最坏的情况会变成起火隐患。规则很简单:

  • 新手最安全的路:用 LIR2032。这本来就是为这块模块准备的电池,而且时钟每次通电它都会自动充电。
  • 只有在充电回路不存在或已经断开时才用 CR2032。有些模块出厂就没装充电二极管和电阻。如果你不确定自己的模块是哪一种,就乖乖用 LIR2032。
  • 有经验的玩家可以拆掉那个二极管或 200Ω 电阻来断开充电回路——但那是进阶级的 SMD 贴片焊接活,不是这个项目的必要步骤。
DS3231 模块上安全的 LIR2032 与有鼓包风险的 CR2032 电池对比
LIR2032 可充电,配有充电电路的模块用它才安全;CR2032 被强行充电则有鼓包风险。

这个 Arduino 数字时钟的完整代码是什么?

这个 DS3231 数字时钟需要从 Arduino IDE 的 Library Manager 安装两个库(library):Adafruit 的 RTClib 负责 RTC,Frank de Brabander 的 LiquidCrystal I2C 负责显示。打开 Sketch → Include Library → Manage Libraries,分别搜索名字,点 Install。

这个 sketch 里最关键的逻辑是 rtc.lostPower():只有当 DS3231 完全断过电(比如刚装上新电池)时它才为真。只有在那个时候才会设置时间——设成 sketch 编译那一刻你电脑的时间。之后就交给电池守着时间,你可以随便上传别的 sketch,时钟也不会被重置。记得第一次上传之前就把电池装好。

#include <Wire.h>
#include <RTClib.h>
#include <LiquidCrystal_I2C.h>

RTC_DS3231 rtc;
LiquidCrystal_I2C lcd(0x27, 16, 2);  // 如果你的转接板用 PCF8574A,改成 0x3F

// RTClib:dayOfTheWeek() 返回 0 = Ahad(周日)... 6 = Sabtu(周六)
const char namaHari[7][7] = {"Ahad", "Isnin", "Selasa", "Rabu",
                             "Khamis", "Jumaat", "Sabtu"};

void setup() {
  lcd.init();
  lcd.backlight();

  if (!rtc.begin()) {
    lcd.print("RTC tak jumpa");
    while (true);  // 停在这里 - 检查 SDA/SCL 接线
  }

  // 如果 RTC 是第一次通电(或者刚换了电池),
  // 就把时间设为编译这个 sketch 时的电脑时间
  if (rtc.lostPower()) {
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  }
}

void loop() {
  DateTime now = rtc.now();
  char baris[17];  // LCD 一行放得下 16 个字符 + 一个 null 结束符

  // 上面一行:HH:MM:SS 格式的时间
  snprintf(baris, sizeof(baris), "%02d:%02d:%02d",
           now.hour(), now.minute(), now.second());
  lcd.setCursor(0, 0);
  lcd.print(baris);

  // 下面一行:星期 + 日期 DD/MM/YY
  snprintf(baris, sizeof(baris), "%-6s %02d/%02d/%02d",
           namaHari[now.dayOfTheWeek()], now.day(),
           now.month(), now.year() % 100);
  lcd.setCursor(0, 1);
  lcd.print(baris);

  delay(1000);
}

上传之后,LCD 上面一行显示时、分、秒,下面一行显示星期和日期。代码里的 namaHari 数组存的是马来文星期名(Ahad = 周日……Sabtu = 周六)——LCD1602 的字符库显示不了汉字,想换的话可以改成 Sun、Mon 这类英文缩写。如果 LCD 亮着却一片空白,慢慢转动转接板背面的蓝色电位器,直到字符出现——那是对比度(contrast)调节,不是坏了。

手在调节 LCD1602 背面 I2C 转接板上的对比度电位器
I2C 转接板背面的蓝色电位器控制对比度——慢慢转到字出现为止。

这个视频一步一步演示了一个几乎相同的 DS3231 加 I2C LCD 的组装过程:

我们最常见到的几个错误

下面五个 DS3231 和 I2C LCD 的错误,在做时钟项目的学生当中最常重复出现。全部都很容易避开。

  1. 把 CR2032 装进还有充电电路的模块。几个星期后电池鼓包。用 LIR2032,或者先确认充电回路确实不存在再用 CR2032。
  2. LCD 亮着但没有字。多数时候只是对比度问题——转一转转接板背面的蓝色电位器。还是空白的话,才去检查 I2C 地址。
  3. 报“RTC tak jumpa”(找不到 RTC),但接线看起来没错。通常是 SDA 和 SCL 接反了,或者模块的排针没焊、只是松松地插着。用万用表的通断挡逐条检查——我们的数字万用表使用教程教你怎么量。
  4. 每次 reset 时钟就跳回旧时间。两个可能:电池没装或已经没电,或者 rtc.adjust() 没放在 lostPower() 条件里,每次重启都把时间设回旧的编译时间。
  5. 代码里 LCD 地址写错。PCF8574 转接板通常是 0x27,PCF8574A 则是 0x3F。屏幕不听话的话,跑一次 I2C scanner,把地址填进 LiquidCrystal_I2C 的 constructor。

常见问题 FAQ

为什么我的时钟比手机慢几秒?

因为 F(__DATE__), F(__TIME__) 这种方法取的是 sketch 编译那一刻的时间,而编译加上传本身就要花几秒。之后 DS3231 自己一年大约只漂移一分钟,所以这个差距不会越来越大。想要秒数更准,可以把 USB 和电池都拔一下,让 lostPower() 重新为真,然后在电脑时钟刚跳到新的一分钟时马上编译上传。

DS3231 模块上的电池能用多久?

电池只在主电源断开时才工作,而 DS3231 在备用模式下耗电极小——所以一颗小小的纽扣电池就能守住时间好几年。LIR2032 还会在时钟每次通电时自动充电,正常使用下你很少需要换它。

DS3231 能顺便显示温度吗?

能。DS3231 内置温度传感器——它本来就是 TCXO 温度补偿系统的一部分——RTClib 通过 rtc.getTemperature() 把它开放出来。调用这个函数,把数值显示在 LCD 第二行,你的时钟就顺便变成房间温度计。

能用更便宜的 DS1307 来做这个项目吗?

能跑,但 DS1307 依赖没有温度补偿的外部晶振,时间漂移快得多,而且漂移量还随环境温度变化。要做一个几个月不用重新对时的时钟,带 TCXO 的 DS3231 才是正确的选择,而且同一个 RTClib 库两种芯片都支持。

这个项目适合 RBT 或 FYP 吗?

非常适合——接线简单、效果立竿见影,而且容易扩展:加个蜂鸣器做闹钟功能,显示温度,或者试试其他 Uno 项目,比如雨滴传感器自动晾衣架。做 FYP 的话,这块模块也是数据记录器(data logger)的好起点,因为板上的 AT24C32 EEPROM 可以保存读数。

最后更新:2026年8月。有问题?WhatsApp 联系我们

Leave a Reply

Your email address will not be published. Required fields are marked *