#include <WiFi.h>
#include <U8g2lib.h>
#include <SPI.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
/* ---------- WiFi配置 ---------- */
const char *ssid = "Wokwi-GUEST";
const char *password = "";
/* ---------- NTP配置 ---------- */
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org", 8 * 3600, 60000); // 东八区,每60秒同步一次
/* ---------- MAX7219 驱动 ---------- */
U8G2_MAX7219_32X8_F_4W_SW_SPI u8g2(U8G2_R0, 18, 23, 5, U8X8_PIN_NONE, U8X8_PIN_NONE);
/* ---------- 翻页动画变量 ---------- */
byte digits[6] = {0, 0, 0, 0, 0, 0}; // 时、时、分、分、秒、秒
byte prev_digits[6] = {0, 0, 0, 0, 0, 0}; // 上一秒的数字
byte digits_offset_perc[6] = {0, 0, 0, 0, 0, 0}; // 动画百分比
char digit_char[2];
char digit_char_next[2];
float y_offset;
void setup() {
Serial.begin(115200);
// 初始化显示
u8g2.begin();
u8g2.setFont(u8g2_font_4x6_tr); // 使用4×6紧凑字体
u8g2.setContrast(120);
u8g2.clearBuffer();
u8g2.drawStr(0, 6, "Connecting...");
u8g2.sendBuffer();
// 连接WiFi
WiFi.begin(ssid, password);
Serial.print("Connecting to WiFi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nConnected!");
// 初始化NTP客户端
timeClient.begin();
timeClient.update();
// 初始化数字
updateTimeDigits();
for(int i = 0; i < 6; i++) {
prev_digits[i] = digits[i];
}
}
void updateTimeDigits() {
timeClient.update();
int hours = timeClient.getHours();
int minutes = timeClient.getMinutes();
int seconds = timeClient.getSeconds();
digits[0] = hours / 10;
digits[1] = hours % 10;
digits[2] = minutes / 10;
digits[3] = minutes % 10;
digits[4] = seconds / 10;
digits[5] = seconds % 10;
}
void checkAnimations() {
for(int i = 0; i < 6; i++) {
if(digits[i] != prev_digits[i] && digits_offset_perc[i] == 0) {
digits_offset_perc[i] = 2;
prev_digits[i] = digits[i];
}
}
}
void loop() {
static uint32_t lastTimeUpdate = 0;
static uint32_t lastNTPUpdate = 0;
// 每秒更新时间
if (millis() - lastTimeUpdate >= 1000) {
updateTimeDigits();
checkAnimations();
lastTimeUpdate = millis();
}
// 每60秒同步NTP时间
if (millis() - lastNTPUpdate >= 60000) {
timeClient.forceUpdate();
lastNTPUpdate = millis();
}
// 显示动画
u8g2.clearBuffer();
for (int i = 0; i < 6; i++) {
if (digits_offset_perc[i] > 0) {
digits_offset_perc[i] += 4;
// 处理进位动画
if ((digits[i] == 0) && (i > 0) && (digits_offset_perc[i-1] == 0) && (digits_offset_perc[i] > 20)) {
digits_offset_perc[i-1] = 2;
}
if (digits_offset_perc[i] >= 100) {
digits_offset_perc[i] = 0;
}
}
// 计算Y偏移量(余弦缓动效果)
y_offset = round((1-((cos(digits_offset_perc[i] / 100.0 * 3.141592654) / 2.0)+0.5)) * 7.0);
itoa(digits[i], digit_char, 10);
itoa((digits[i] + 1) % 10, digit_char_next, 10);
// 计算X位置(紧凑布局)
int x_pos = i * 4;
if(i == 2) x_pos += 1; // 小时和分钟之间的冒号位置
if(i == 4) x_pos += 2; // 分钟和秒之间的冒号位置
// 绘制数字
u8g2.drawStr(x_pos, 6 - y_offset, digit_char);
u8g2.drawStr(x_pos, 6 - y_offset + 7, digit_char_next);
}
// 绘制冒号分隔符
u8g2.drawPixel(8, 2);
u8g2.drawPixel(8, 4);
u8g2.drawPixel(18, 2);
u8g2.drawPixel(18, 4);
u8g2.sendBuffer();
delay(25);
}