🚀 ESP32-C6终极时钟项目:集成NTP校时、农历、温度显示的全功能时钟
分享作者:XIUYUAN
作者昵称:XIUYUAN
评测品牌:iCEasy
评测型号:DS3231高精度时钟模块
发布时间:2025-10-21 14:22:45
1
视频链接
【ESP32-C6终极时钟项目:集成NTP校时、农历、温度显示的全功能时钟-哔哩哔哩】 https://b23.tv/ZkvyHnO
前言
打造一个功能强大的桌面时钟,支持自动网络校时、农历显示、温度监测、多页面动画切换。本文将带你从零开始完成这个项目!
开源口碑分享内容

🚀 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协议的绝佳项目!

📧 如有疑问欢迎留言讨论  



全部评论
暂无评论
0/144