🚀 ESP32-C6终极时钟项目:集成NTP校时、农历、温度显示的全功能时钟
> 打造一个功能强大的桌面时钟,支持自动网络校时、农历显示、温度监测、多页面动画切换。本文将带你从零开始完成这个项目!
📖 目录
1. [项目简介](项目简介)
2. [功能特性](功能特性)
3. [硬件准备](硬件准备)
4. [硬件连接](硬件连接)
5. [软件配置](软件配置)
6. [完整代码](完整代码)
7. [使用说明](使用说明)
8. [故障排除](故障排除)
🎯 项目简介
这是一个基于ESP32-C6 WROOM1开发板的多功能时钟系统,结合了DS3231高精度实时时钟模块和0.96寸OLED显示屏。不同于普通的时钟项目,这个时钟集成了以下亮点功能:
⏰ 超高精度:DS3231内置温补晶振,年误差小于2分钟
🌐 智能校时:自动连接WiFi获取网络时间(NTP)
📅 农历显示:完整的农历算法(支持2000-2050年)
🌡️ 温度监测:实时显示环境温度
🎨 动画效果:4个页面自动切换,带滑动过渡动画
🔋 断电保护:DS3231带纽扣电池,断电也保持时间
✨ 功能特性
1.核心功能
| 功能 | 说明 | 特点 |
|------|------|------|
| 🌐 NTP自动校时 | 启动时自动联网同步时间 | 支持阿里云、中国NTP服务器 |
| 📅 农历显示 | 完整农历转换算法 | 支持闰月计算 |
| 🌡️ 温度显示 | DS3231内置温度传感器 | 精度±3°C |
| 🎨 多页面显示 | 4种不同显示风格 | 自动切换,可调间隔 |
| ⏱️ 高精度计时 | DS3231温度补偿 | 年误差<2分钟 |
| 🔋 断电保护 | CR2032纽扣电池 | 断电保持时间 |
2.显示页面
1. 页面0 - 数字大时钟:大字体时间显示 + 日期 + 温度
2. 页面1 - 农历信息:公历日期 + 农历日期 + 闰月标识
3. 页面2 - 系统信息:详细的日期、时间、星期、温度
4. 页面3 - 模拟表盘:带时针、分针、秒针的圆形时钟
🛒 硬件准备
1.材料清单
| 序号 | 名称 | 规格 | 数量 | 参考价格 |
|------|------|------|------|---------|
| 1 | ESP32-C6开发板 | WROOM1模块 | 1 | ¥25 |
| 2 | DS3231时钟模块 | RTC + AT24C32 | 1 | ¥8 |
| 3 | OLED显示屏 | 0.96寸 128×64 IIC | 1 | ¥12 |
| 4 | 面包板 | 标准尺寸 | 1 | ¥5 |
| 5 | 杜邦线 | 母对母 | 若干 | ¥3 |
| 6 | USB数据线 | Type-C | 1 | ¥5 |
成本:约 ¥58
2.购买提示
ESP32-C6:选择WROOM1模块版本,确保有USB转串口芯片
DS3231:建议购买带AT24C32存储芯片的完整版本
OLED屏:必须是IIC接口(4针),不要买SPI接口(7针)
🔌 硬件连接
1.接线图
┌─────────────────────────────────────────┐
│ ESP32-C6 WROOM1 │
│ │
│ GPIO6 (SDA) ──┬─→ DS3231 SDA │
│ └─→ OLED SDA │
│ │
│ GPIO7 (SCL) ──┬─→ DS3231 SCL │
│ └─→ OLED SCL │
│ │
│ 3.3V ─────────┬─→ DS3231 VCC │
│ └─→ OLED VCC │
│ │
│ GND ──────────┬─→ DS3231 GND │
│ └─→ OLED GND │
└─────────────────────────────────────────┘
2.详细接线表
| ESP32-C6 引脚 | 连接到 |
|--------------|--------|
| GPIO6 (SDA) | DS3231 SDA + OLED SDA |
| GPIO7 (SCL) | DS3231 SCL + OLED SCL |
| 3.3V | DS3231 VCC + OLED VCC |
| GND | DS3231 GND + OLED GND |
⚠️ 注意事项
I2C总线可以连接多个设备,DS3231和OLED共用同一组SDA/SCL
确保所有设备共地(GND相连)
OLED和DS3231都使用3.3V供电
DS3231模块上的纽扣电池(CR2032)要安装好
💻 软件配置
1. Arduino IDE 安装
如果还没有安装Arduino IDE,请前往[官网下载](https://www.arduino.cc/en/software)(建议2.x版本)。
2. 添加ESP32支持
1. 打开 Arduino IDE
2. 文件 → 首选项
3. 在"附加开发板管理器网址"中添加:
https://espressif.github.io/arduino-esp32/package_esp32_index.json
4. 工具 → 开发板 → 开发板管理器
5. 搜索"ESP32",安装"esp32 by Espressif Systems"
3. 安装必需库文件
通过 工具 → 管理库 搜索并安装以下库:
| 库名称 | 作者 | 用途 |
|--------|------|------|
| RTClib | Adafruit | DS3231驱动 |
| Adafruit GFX Library | Adafruit | 图形基础库 |
| Adafruit SSD1306 | Adafruit | OLED驱动 |
4. 开发板配置
上传代码前,需要正确配置开发板参数:
开发板:ESP32C6 Dev Module
端口:自动检测(通常是COM3/COM4等)
Upload Speed:921600(如果上传失败改为115200)
USB CDC On Boot:Enabled
💾 完整代码
以下是完整的Arduino代码,可以直接复制使用:
/*
* ESP32-C6 Ultimate Clock System (English Version)
* Hardware: ESP32-C6 WROOM1 + DS3231 + 0.96" OLED(128x64)
* Author: XIUYUAN
* Date: 2025-10-21
*/
#include <Wire.h>
#include <RTClib.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <WiFi.h>
#include <time.h>
// ==================== Configuration ====================
// WiFi Settings - CHANGE THESE!
const char* ssid = "YOUR_WIFI_SSID"; // 修改为你的WiFi名称
const char* password = "YOUR_WIFI_PASSWORD"; // 修改为你的WiFi密码
// NTP Server Configuration
const char* ntpServer1 = "ntp.aliyun.com";
const char* ntpServer2 = "ntp1.aliyun.com";
const char* ntpServer3 = "cn.ntp.org.cn";
const long gmtOffset_sec = 8 * 3600; // UTC+8 Beijing Time
const int daylightOffset_sec = 0;
// I2C Pins
#define I2C_SDA 6
#define I2C_SCL 7
// OLED Configuration
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define OLED_ADDR 0x3C // 如果不显示尝试改为 0x3D
// Page Switch Interval (seconds)
#define PAGE_SWITCH_INTERVAL 5
// ==================== Global Objects ====================
RTC_DS3231 rtc;
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// Weekday Names
const char* weekDays[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
// Page Status
int currentPage = 0;
unsigned long lastPageSwitch = 0;
// ==================== Lunar Calendar Data ====================
const unsigned int lunarInfo[] = {
0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2,
0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977,
0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970,
0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950,
0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557
};
struct LunarDate {
int year;
int month;
int day;
bool isLeap;
};
// ==================== Lunar Functions ====================
int getLunarYearDays(int year) {
int i, sum = 348;
for(i = 0x8000; i > 0x8; i >>= 1) {
sum += (lunarInfo[year - 2000] & i) ? 1 : 0;
}
return sum + getLeapDays(year);
}
int getLeapDays(int year) {
if(getLeapMonth(year)) {
return (lunarInfo[year - 2000] & 0x10000) ? 30 : 29;
}
return 0;
}
int getLeapMonth(int year) {
return lunarInfo[year - 2000] & 0xf;
}
int getLunarMonthDays(int year, int month) {
return (lunarInfo[year - 2000] & (0x10000 >> month)) ? 30 : 29;
}
LunarDate solarToLunar(int year, int month, int day) {
LunarDate lunar;
int i, leap = 0, temp = 0;
DateTime base(2000, 1, 31, 0, 0, 0);
DateTime target(year, month, day, 0, 0, 0);
int offset = (target.unixtime() - base.unixtime()) / 86400;
for(i = 2000; i < 2051 && offset > 0; i++) {
temp = getLunarYearDays(i);
offset -= temp;
}
if(offset < 0) {
offset += temp;
i--;
}
lunar.year = i;
leap = getLeapMonth(i);
lunar.isLeap = false;
for(i = 1; i < 13 && offset > 0; i++) {
if(leap > 0 && i == (leap + 1) && lunar.isLeap == false) {
--i;
lunar.isLeap = true;
temp = getLeapDays(lunar.year);
} else {
temp = getLunarMonthDays(lunar.year, i);
}
if(lunar.isLeap == true && i == (leap + 1)) {
lunar.isLeap = false;
}
offset -= temp;
}
if(offset == 0 && leap > 0 && i == leap + 1) {
if(lunar.isLeap) {
lunar.isLeap = false;
} else {
lunar.isLeap = true;
--i;
}
}
if(offset < 0) {
offset += temp;
--i;
}
lunar.month = i;
lunar.day = offset + 1;
return lunar;
}
// ==================== WiFi & NTP ====================
bool connectWiFi() {
Serial.print("Connecting WiFi");
WiFi.begin(ssid, password);
int attempts = 0;
while(WiFi.status() != WL_CONNECTED && attempts < 20) {
delay(500);
Serial.print(".");
attempts++;
}
if(WiFi.status() == WL_CONNECTED) {
Serial.println("\nWiFi Connected!");
Serial.print("IP: ");
Serial.println(WiFi.localIP());
return true;
} else {
Serial.println("\nWiFi Failed!");
return false;
}
}
bool syncTimeWithNTP() {
Serial.println("Syncing NTP...");
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer1, ntpServer2, ntpServer3);
struct tm timeinfo;
int attempts = 0;
while(!getLocalTime(&timeinfo) && attempts < 10) {
Serial.print(".");
delay(1000);
attempts++;
}
if(attempts >= 10) {
Serial.println("\nNTP Sync Failed!");
return false;
}
rtc.adjust(DateTime(timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday,
timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec));
Serial.println("\nNTP Sync Success!");
Serial.printf("Time: %04d-%02d-%02d %02d:%02d:%02d\n",
timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday,
timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
return true;
}
// ==================== Animation ====================
void slideLeftAnimation() {
for(int x = 0; x < SCREEN_WIDTH; x += 8) {
display.clearDisplay();
display.setCursor(-x, 20);
display.setTextSize(2);
display.print("Loading...");
display.display();
delay(10);
}
}
void pageTransition() {
for(int x = 0; x <= SCREEN_WIDTH; x += 16) {
display.clearDisplay();
display.fillRect(0, 0, SCREEN_WIDTH - x, SCREEN_HEIGHT, SSD1306_BLACK);
display.display();
delay(10);
}
}
// ==================== Display Pages ====================
void displayPage0(DateTime now) {
display.clearDisplay();
display.setTextSize(1);
display.setCursor(10, 0);
display.printf("%04d-%02d-%02d", now.year(), now.month(), now.day());
display.setCursor(90, 0);
display.print(weekDays[now.dayOfTheWeek()]);
display.setTextSize(3);
display.setCursor(10, 20);
display.printf("%02d:%02d", now.hour(), now.minute());
display.setTextSize(2);
display.setCursor(100, 28);
display.printf("%02d", now.second());
float temp = rtc.getTemperature();
display.setTextSize(1);
display.setCursor(25, 55);
display.printf("Temp: %.1fC", temp);
display.display();
}
void displayPage1(DateTime now) {
display.clearDisplay();
display.setTextSize(1);
display.setCursor(0, 0);
display.printf("Solar:%04d-%02d-%02d", now.year(), now.month(), now.day());
display.setTextSize(2);
display.setCursor(20, 15);
display.printf("%02d:%02d:%02d", now.hour(), now.minute(), now.second());
LunarDate lunar = solarToLunar(now.year(), now.month(), now.day());
display.setTextSize(1);
display.setCursor(0, 38);
display.printf("Lunar: %d Year", lunar.year);
display.setCursor(0, 50);
if(lunar.isLeap) display.print("Leap ");
display.printf("M%d D%d", lunar.month, lunar.day);
display.display();
}
void displayPage2(DateTime now) {
display.clearDisplay();
display.setTextSize(1);
display.setCursor(30, 0);
display.print("System Info");
display.setCursor(0, 15);
display.printf("Time: %02d:%02d:%02d", now.hour(), now.minute(), now.second());
display.setCursor(0, 27);
display.printf("Date: %02d/%02d/%04d", now.month(), now.day(), now.year());
display.setCursor(0, 39);
display.print("Week: ");
display.print(weekDays[now.dayOfTheWeek()]);
float temp = rtc.getTemperature();
display.setCursor(0, 51);
display.printf("Temp: %.2f C", temp);
display.display();
}
void displayPage3(DateTime now) {
display.clearDisplay();
int centerX = 64, centerY = 36, radius = 26;
display.drawCircle(centerX, centerY, radius, SSD1306_WHITE);
display.drawCircle(centerX, centerY, radius - 1, SSD1306_WHITE);
for(int i = 0; i < 12; i++) {
float angle = (i * 30 - 90) * PI / 180;
int x1 = centerX + (radius - 4) * cos(angle);
int y1 = centerY + (radius - 4) * sin(angle);
int x2 = centerX + (radius - 1) * cos(angle);
int y2 = centerY + (radius - 1) * sin(angle);
display.drawLine(x1, y1, x2, y2, SSD1306_WHITE);
}
float hourAngle = ((now.hour() % 12) * 30 + now.minute() * 0.5 - 90) * PI / 180;
int hx = centerX + (radius - 12) * cos(hourAngle);
int hy = centerY + (radius - 12) * sin(hourAngle);
display.drawLine(centerX, centerY, hx, hy, SSD1306_WHITE);
display.drawLine(centerX + 1, centerY, hx + 1, hy, SSD1306_WHITE);
float minAngle = (now.minute() * 6 - 90) * PI / 180;
int mx = centerX + (radius - 6) * cos(minAngle);
int my = centerY + (radius - 6) * sin(minAngle);
display.drawLine(centerX, centerY, mx, my, SSD1306_WHITE);
float secAngle = (now.second() * 6 - 90) * PI / 180;
int sx = centerX + (radius - 2) * cos(secAngle);
int sy = centerY + (radius - 2) * sin(secAngle);
display.drawLine(centerX, centerY, sx, sy, SSD1306_WHITE);
display.fillCircle(centerX, centerY, 2, SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(35, 0);
display.printf("%02d:%02d:%02d", now.hour(), now.minute(), now.second());
display.display();
}
// ==================== Setup ====================
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("\n=== ESP32-C6 Ultimate Clock System ===");
Wire.begin(I2C_SDA, I2C_SCL);
if(!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
Serial.println("OLED Init Failed!");
while(1);
}
Serial.println("OLED Init OK");
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(15, 10);
display.println("Ultimate Clock");
display.setCursor(20, 25);
display.println("Starting...");
display.display();
delay(1000);
if(!rtc.begin()) {
Serial.println("DS3231 Init Failed!");
display.clearDisplay();
display.setCursor(10, 20);
display.println("RTC Error!");
display.display();
while(1);
}
Serial.println("DS3231 Init OK");
display.clearDisplay();
display.setCursor(5, 20);
display.println("Connecting WiFi...");
display.display();
if(connectWiFi()) {
display.clearDisplay();
display.setCursor(15, 20);
display.println("Syncing NTP...");
display.display();
if(syncTimeWithNTP()) {
display.clearDisplay();
display.setCursor(15, 20);
display.println("Time Synced!");
display.display();
delay(1500);
} else {
display.clearDisplay();
display.setCursor(10, 15);
display.println("NTP Sync Failed");
display.setCursor(10, 30);
display.println("Using RTC Time");
display.display();
delay(2000);
}
} else {
display.clearDisplay();
display.setCursor(15, 15);
display.println("WiFi Failed");
display.setCursor(10, 30);
display.println("Using RTC Time");
display.display();
delay(2000);
}
slideLeftAnimation();
Serial.println("System Ready!");
lastPageSwitch = millis();
}
// ==================== Loop ====================
void loop() {
DateTime now = rtc.now();
if(millis() - lastPageSwitch > PAGE_SWITCH_INTERVAL * 1000) {
pageTransition();
currentPage = (currentPage + 1) % 4;
lastPageSwitch = millis();
}
switch(currentPage) {
case 0: displayPage0(now); break;
case 1: displayPage1(now); break;
case 2: displayPage2(now); break;
case 3: displayPage3(now); break;
}
if(now.second() % 10 == 0) {
LunarDate lunar = solarToLunar(now.year(), now.month(), now.day());
Serial.printf("Date: %04d-%02d-%02d %s %02d:%02d:%02d | Lunar: M%d D%d | Temp: %.1fC\n",
now.year(), now.month(), now.day(), weekDays[now.dayOfTheWeek()],
now.hour(), now.minute(), now.second(),
lunar.month, lunar.day, rtc.getTemperature());
delay(1000);
}
delay(100);
}
🚀 使用说明
1. 修改WiFi配置
在代码开头修改为你的WiFi信息:
const char* ssid = "YOUR_WIFI_SSID"; // 修改为你的WiFi名称
const char* password = "YOUR_WIFI_PASSWORD"; // 修改为你的WiFi密码
2. 上传代码
1. 连接ESP32-C6到电脑
2. 选择正确的开发板和端口
3. 点击"上传"按钮
4. 等待编译和上传完成
3. 观察启动过程
打开串口监视器(波特率115200),你会看到:
=== ESP32-C6 Ultimate Clock System ===
OLED Init OK
DS3231 Init OK
Connecting WiFi........
WiFi Connected!
IP: 192.168.1.100
Syncing NTP...
NTP Sync Success!
Time: 2025-10-21 13:14:42
System Ready!
4. 正常运行
OLED会自动在4个页面之间切换(每5秒)
串口每10秒输出一次时间和温度信息
断电后时间由DS3231纽扣电池保持
🔧 故障排除
1.OLED不显示
解决方案:
1. 检查I2C地址,尝试将 `0x3C` 改为 `0x3D`
2. 确认接线正确:SDA接GPIO6,SCL接GPIO7
3. 检查供电是否正常
2.DS3231读取失败
解决方案:
1. 确认纽扣电池已安装
2. 检查SDA/SCL是否接反
3. 使用I2C扫描代码查找设备地址(通常为0x68)
3. WiFi连接失败
解决方案:
1. 确认WiFi名称和密码正确
2. 确保路由器开启2.4GHz频段(ESP32不支持5GHz)
3. 尝试靠近路由器测试
4.NTP校时失败
解决方案:
1. 检查网络连接
2. 更换其他NTP服务器
3. 或手动设置时间:
rtc.adjust(DateTime(2025, 10, 21, 14, 30, 0));
5.上传代码失败
解决方案:
1. 按住BOOT按钮,点击上传,等待"Connecting..."后松开
2. 降低上传速度到115200
3. 更换USB线或USB口
🎉 项目完成
恭喜!你已经完成了一个功能完整的ESP32-C6智能时钟项目。这个时钟具备:
✅ 网络自动校时
✅ 农历显示
✅ 温度监测
✅ 多页面动画
✅ 高精度计时
✅ 断电保护
可以作为桌面装饰,也是学习ESP32、I2C通信、NTP协议的绝佳项目!
📧 如有疑问欢迎留言讨论

