ESP32 網路時鐘:用 NodeMCU-32S 打造分秒不差的 NTP 原子鐘

告別手動對時!用 NodeMCU-32S 打造分秒不差的 WiFi 原子鐘。免 RTC模組、自動網路校正,搭配精美 OLED 介面。程式碼複製貼上即刻啟用,百元內享受永久精準的 IoT 工藝!

ESP32 網路時鐘:用 NodeMCU-32S 打造分秒不差的 NTP 原子鐘

以前用 Arduino 做時鐘,總是要買一個額外的 RTC 模組 (如 DS3231) 還要裝水銀電池,時間久了還是會跑掉。

但既然我們用的是 NodeMCU-32S (ESP32),它天生就能連上網路!我們可以直接利用 NTP (Network Time Protocol) 技術,讓它自動連上網路時間伺服器校正時間。這意味著:它永遠不需要調整時間,而且分秒不差。

這篇教學將帶你製作一個精緻的桌面網路時鐘,並深入講解它背後的運作原理。

準備材料

  1. NodeMCU-32S 開發板
  2. 0.96 吋 OLED 顯示螢幕 (I2C 介面)
  3. 杜邦線
NodeMCU-32S 相容版本 ESP32開發板 WiFi 藍牙 可用Arduino IDE
安可信原廠貨! 全腳位引出,還保持迷你的身型,插上麵包後還能插杜邦線,真的是太棒了! 和NodeMCU V2幾乎一樣尺寸! 有5V供電輸出,非常方便! 有了ESP32開發板,真的可以忘記原來的那些Arduino板子了! 可以用Arduino IDE開發,但效能更強大,還內建WiFi 傑森實測記錄,大家可以到 F 粉 絲 團 B 看貼文哦! ESP32-D0WDQ6 內置兩個低功耗 Xtensa® 32-bit LX6 MCU。片上存儲包括: • 448 KB 的 ROM,用於程序啟動和內核功能
ESP32S擴展板 適用於Nodemcu-32s 38Pin全引出
※ 不含ESP32S開發板,需另購! ESP32S專用擴展板 適用於Nodemcu-32s,其它型號都不相容哦!請留意。 38Pin全引出,無敵方便!
0.96吋 OLED 128x64 低耗電 高解析 可顯示點陣圖 大勝LCD
尺寸:0.96吋 解析度:128x64 使用電源:3.3v~5v (實測3.3v即可) 顏色:上方1/4為黃色,以下為藍色 傑森實際,效果非常好! 送完整範例程式,一看就會。 大家可以利用LCDAssistant等程式,將點陣圖轉為Byte Array,即可顯示在螢幕上!

第一步:硬體接線

NodeMCU-32S 的 I2C 腳位是固定的,請依照下表連接:

OLED 引腳NodeMCU-32S說明
GNDGND接地
VCC3V3或5V3.3或5V 電源
SDAGPIO 21I2C SDA
SCLGPIO 22I2C SCL

第二步:程式碼解析 (燒錄前必看)

在燒錄程式之前,有兩個關於 NTP 的運作機制,是新手最常感到困惑的,這裡特別說明一下:

1. 它是跟哪台主機對時的?

程式碼中設定的伺服器是 pool.ntp.org

這不是「單一台」電腦,而是一個由全球志願者組成的伺服器叢集 (Pool)。當你的 ESP32 連線時,系統會自動將你導向到離你最近(例如台灣或亞洲區)且目前有空的主機。這保證了連線速度快且不會因為某台主機掛掉而失效。

2. 多久會校正一次時間?

這支程式設定每 1 秒 刷新一次螢幕,但這不代表它每秒都在連網。

ESP32 的運作邏輯是:

  • 每 1 小時 (3600秒):ESP32 會在背景自動連上 NTP 伺服器,修正內部的時間誤差。
  • 每 1 秒:程式讀取 ESP32 內部計時器顯示在螢幕上。這樣設計既省電,又能保證時間準確度(一小時內的誤差通常僅有幾毫秒,肉眼無法察覺)。

第三步:燒錄程式碼

請將以下程式碼複製到 Arduino IDE。

注意: 這次我們不需要安裝額外的時間函式庫,因為 ESP32 核心已經內建強大的 time.h

👇 請修改這兩行即可:

  • ssid: 你的 WiFi 名稱
  • password: 你的 WiFi 密碼
/*
 * NodeMCU-32S NTP 網路時鐘 (防當機 & 介面優化版)
 * 硬體:NodeMCU-32S (ESP32) + 0.96 OLED
 * 接線:SDA -> GPIO 21, SCL -> GPIO 22
 */

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <WiFi.h>
#include "time.h" // ESP32 內建強大的時間函式庫

// ================= 使用者設定區 =================
const char* ssid     = "你的WiFi名稱";
const char* password = "你的WiFi密碼";

// NTP 伺服器設定 (使用全球叢集 pool.ntp.org)
const char* ntpServer = "pool.ntp.org";
const long  gmtOffset_sec = 28800;  // 台灣時區 UTC+8 (8 * 3600 = 28800)
const int   daylightOffset_sec = 0; // 台灣無日光節約時間
// ==============================================

// OLED 設定
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define SDA_PIN 21 // NodeMCU 標準 SDA
#define SCL_PIN 22 // NodeMCU 標準 SCL
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

// 變數
unsigned long lastTime = 0;
unsigned long timerDelay = 1000; // 每秒更新螢幕

void setup() {
  Serial.begin(115200);
  delay(1000); 

  // 1. 初始化 OLED
  Wire.begin(SDA_PIN, SCL_PIN);
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("OLED 啟動失敗"));
    for(;;);
  }
  
  display.clearDisplay();
  display.setTextColor(SSD1306_WHITE);
  display.setTextSize(1);
  display.setCursor(0, 10);
  display.println(F("System Booting..."));
  display.display();

  // 2. WiFi 設定 (加入防當機參數)
  WiFi.mode(WIFI_STA);
  
  // [優化] 降低發射功率,避免 NodeMCU 因電流突波重啟
  WiFi.setTxPower(WIFI_POWER_8_5dBm); 
  // [優化] 關閉省電模式,確保 NTP 校時順暢
  WiFi.setSleep(false); 

  Serial.print("連線 WiFi 中");
  WiFi.begin(ssid, password);
  
  int limit = 0;
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
    limit++;
    if (limit > 60) ESP.restart(); // 超時重啟
  }
  Serial.println("\nWiFi 已連線!");

  // 3. 啟動 NTP 校時 (關鍵步驟)
  // 只要執行這行,ESP32 就會自動在背景每小時校正一次時間
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
  
  // 等待第一次時間同步
  struct tm timeinfo;
  Serial.print("等待 NTP 同步");
  while(!getLocalTime(&timeinfo)){
    Serial.print(".");
    delay(100);
  }
  Serial.println("\n時間同步完成!");
  
  updateDisplay();
}

void loop() {
  // 每秒更新一次畫面
  if ((millis() - lastTime) > timerDelay) {
    updateDisplay();
    lastTime = millis();
  }
}

// 介面繪圖函式
void updateDisplay() {
  struct tm timeinfo;
  // 再次確認時間是否有效
  if(!getLocalTime(&timeinfo)){
    return;
  }

  // 準備字串 (年-月-日, 時:分, :秒)
  char dateStr[20];
  char timeStr[10];
  char secStr[5];

  // 使用 strftime 格式化時間
  strftime(dateStr, 20, "%Y-%m-%d", &timeinfo);
  strftime(timeStr, 10, "%H:%M", &timeinfo);
  strftime(secStr, 5, ":%S", &timeinfo);

  display.clearDisplay();

  // --- 區塊 1: 頂部日期列 (白底黑字) ---
  display.fillRect(0, 0, 128, 16, SSD1306_WHITE);
  display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
  display.setTextSize(1);
  
  // 自動置中演算法: (螢幕寬 - 文字寬) / 2
  int dateLen = strlen(dateStr) * 6; 
  int dateX = (128 - dateLen) / 2;
  display.setCursor(dateX, 4); 
  display.print(dateStr); 

  // --- 區塊 2: 主時間 (黑底白字) ---
  display.setTextColor(SSD1306_WHITE); 
  
  // 顯示時:分 (大字體)
  // 設定在最左邊 (x=0),為秒數騰出空間
  display.setTextSize(3); 
  display.setCursor(0, 30); 
  display.print(timeStr);

  // 顯示 :秒 (中字體)
  // 緊接在時間後面 (x=90),避免換行爆版
  display.setTextSize(2);
  display.setCursor(90, 37); 
  display.print(secStr);

  // --- 區塊 3: 底部星期 ---
  char weekStr[10];
  strftime(weekStr, 10, "%A", &timeinfo); // 取得星期全名
  
  display.setTextSize(1);
  int weekLen = strlen(weekStr) * 6;
  int weekX = (128 - weekLen) / 2; // 一樣自動置中
  display.setCursor(weekX, 56); 
  display.print(weekStr);

  display.display();
}

成果展示與介面設計

為了在小小的 0.96 吋螢幕上塞入所有資訊且不顯得擁擠,我們在程式碼中做了精確的版面計算:

  1. 日期欄 (上方):採用「反白」設計(白底黑字),模仿手機的狀態列,視覺上有區隔感。
  2. 時間欄 (中間)
    • 時與分:使用 Size 3 的超大字體,一目了然。
    • 秒數:使用 Size 2 字體,並將座標精確設定在 (90, 37)。這是為了解決「爆版」問題,如果依照預設排列,秒數的個位數會被擠到下一行。
  3. 星期欄 (下方):自動置中顯示英文星期(如 Sunday),讓畫面平衡。

現在,只要把 NodeMCU-32S 接上 USB 電源,你就有一個永遠不需要對時、精準無比的桌面時鐘了!

NodeMCU-32S 相容版本 ESP32開發板 WiFi 藍牙 可用Arduino IDE
安可信原廠貨! 全腳位引出,還保持迷你的身型,插上麵包後還能插杜邦線,真的是太棒了! 和NodeMCU V2幾乎一樣尺寸! 有5V供電輸出,非常方便! 有了ESP32開發板,真的可以忘記原來的那些Arduino板子了! 可以用Arduino IDE開發,但效能更強大,還內建WiFi 傑森實測記錄,大家可以到 F 粉 絲 團 B 看貼文哦! ESP32-D0WDQ6 內置兩個低功耗 Xtensa® 32-bit LX6 MCU。片上存儲包括: • 448 KB 的 ROM,用於程序啟動和內核功能