這篇文章是一篇教學文,我有一個朋友很喜歡 Wordclock,它是一款很精緻的時鐘,但是它的報時方式是切換盤面上的數字,如下圖。

Word Clock
材料:
要做出一個 Word clock,我們需要的材料如下
- IKEA 買的 SANNAHED 框,我買的尺寸是 50*50cm,這個東西會當成 wordclock 的外框。
- 一個印有時鐘字母圖案的客製化字母遮罩,燈從後面照的時候就可以有字母的形狀。網路上有很多德文或英文的模板,其他語言的就很少。我的作法是找一個英文的模板,匯入一個叫做 INKSCAPE 的軟體,然後讓 AI 生成西文版的排版,接著開始複製貼上成西文的。我在德國的作法是在這個網站下單,大概兩周後送過來。
- 一個 microcontroller,我買的是 ESP88266 ,要包含 WiFi 模組,因為我們的時鐘要透過 WiFi 校準。構想是當你把 wordclock 帶到一個有 WiFi 的環境並插電時,wordclock 會變成一個基地台,然後你在手機上點 wordclock 的網路,接著從手機把你所在地的網路名稱和密碼輸入 wordclock,然後它就會停止當基地台並連上網。
- WS2812B 燈條,買 30LED/公尺的規格,因為這個規格的燈和燈的間距剛好會對到字母遮罩。
- Level Converter,要把 5V 轉成 3.3V 用。5V 是 LED 燈,3.3V 是 microcontroller 要用。
- 兩捲顏色不一樣的電纜線,焊接時才不會眼花。一條不要的 USB 線。
- 一個乳白色壓克力板,以及一個黑色 PVC 發泡板 (原文是 Hartschaumplatte,我不太確定翻譯的對不對),長寬都是 50*50 cm,厚度是 3mm。黑色板子之後要割成網格狀,目的是隔絕光線,不要一個字母的光透到隔壁字母去。白色板子是柔和光線,這樣才不會太刺眼。
- 電烙鐵,你需要把電線和燈焊在一起。
- 一把美工刀、一把尺(鐵尺是最好的,因為你可以拿美工刀沿著尺割板子)
Wordclock 的製作有點像三明治,透過在 IKEA 的 SANNAHED 框內疊上好幾層不同的材料達到目的。第一層是客製化字母遮罩 (顯示字體),第二層要放乳白色壓克力板 (柔和光線),第三層是 黑色 PVC 發泡板 (確保光線不溢散),第四層是 SANNAHED 附贈的一塊木板 (LED 燈條貼在這)。
步驟:
這些步驟不一定有前後關係,可以根據取得材料的順序合理地自行調換。
- 把 PVC 發泡板用美工刀割出 110 個洞,確保模放在板子後面可以看到每一個字母。
- 把 LED 燈條,每排11個燈剪成10排。用剪刀剪就可以了,剪的地方就是銅箔的地方。要注意方向,燈條上的左右是不一樣的,一邊是輸入,一邊是輸出 (燈條上會標示)。我們要的是把它貼成 S 型的,第一排輸入在左輸出在右,第二排輸入在右輸出在左,這樣不斷交替下去。接下來把每排連起來的時候,就可以用很短的線。

貼上10排,每排11個的LED燈條(你要買一捲自己用剪刀剪)
- 開始焊接,把燈條的上下兩排全部接起來,接成 S 型。建議用不同顏色的線,這樣你才不會不小心把 GND 跟 5V 焊在一起。

把燈條焊接成 S 型,GND接GND,Vo接 Vin,5V接5V
- 我們接著來燒錄 LED 燈的測試程式碼,這步主要是和我們焊接有沒有接好,能不能用 ESP 8266 點亮所有的 LED 燈。首先拿一條線,把 ESP 8266 接到筆電上,然後在筆電上裝 Arduino IDE。點開後選好型號,去 Library Manager 安裝 FastLED,貼上下面的測試程式碼後按上傳 (程式碼是給 Gemini 生成的)。
#include <FastLED.h>
// --- 硬體設定 ---
#define NUM_LEDS 110 // 你的總燈數
#define DATA_PIN 4 // 接到 ESP8266 的 D2 腳位 (GPIO4)
#define BRIGHTNESS 50 // 亮度 (0-255,測試時先調低以免燒毀 USB 孔)
#define LED_TYPE WS2812B // 燈條型號
#define COLOR_ORDER GRB // WS2812B 的標準色彩順序
CRGB leds[NUM_LEDS];
void setup() {
delay(2000); // 開機安全延遲
// 初始化 FastLED
FastLED.addLeds<LED_TYPE, DATA_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection(TypicalLEDStrip);
FastLED.setBrightness(BRIGHTNESS);
}
void loop() {
// 測試 1:紅色跑馬燈
for(int i = 0; i < NUM_LEDS; i++) {
leds[i] = CRGB::Red;
FastLED.show();
delay(30);
leds[i] = CRGB::Black;
}
// 測試 2:全亮綠色
fill_solid(leds, NUM_LEDS, CRGB::Green);
FastLED.show();
delay(1000);
// 測試 3:全亮藍色
fill_solid(leds, NUM_LEDS, CRGB::Blue);
FastLED.show();
delay(1000);
// 全部熄滅,準備下一次循環
FastLED.clear();
FastLED.show();
delay(500);
- 接著我們要把微控制器、Level Converter、和剛剛的 LED 燈條焊接在一起。我的做法是拿一條不要的 usb 線,然後把 usb c 的那端剪開,剪開後你會看到一堆亂七八糟的線纏在一起,我們取出裡面的紅線和黑線 (他們分別是5V和GND)。我們接下來要拿出六根電纜線,三根和 usb 的紅線纏起來,三根和 usb 的黑線纏起來,纏得跟麻花捲一樣,然後把他們焊起來。然後用電火布把他們包起來,確保他們不會碰到。
- 電源分配: 紅線麻花捲的那三根,分別接往燈條的5V、Level Converter 的 HV、ESP8266的5V。
- 接地分配: 黑色麻花線的那三根,則是接到燈條起點的 GND、Level Converter 的 GND (我接在 HV 那側)、ESP8266 的 GND 孔。
- 訊號及參考電壓: ESP8266 的 D2 孔接到 Level Converter 的 LV1 孔,並且把 Level Converter 的 HV1 孔接上燈條的 Din、LV 接到 ESP8266 的 3V3 孔位。
- 把以下程式碼燒到微控制器上 (這整段程式碼都是給 Gemini 寫的,我就複製貼上)。
#include <ESP8266WiFi.h>
#include <WiFiManager.h> // 神器:不用寫死 Wi-Fi 密碼
#include <FastLED.h>
#include <time.h> // 處理網路對時
#define NUM_LEDS 110
#define DATA_PIN 4 // D2 腳位
#define BRIGHTNESS 150 // 亮度 (0-255,太亮可以調低)
#define LED_TYPE WS2812B
#define COLOR_ORDER GRB
CRGB leds[NUM_LEDS];
// 設定顯示顏色 (你可以自己改成 CRGB::Red, CRGB::Blue, 或是自訂顏色)
CRGB timeColor = CRGB::White;
// 時區設定 (目前設定為西班牙馬德里時間,含日光節約自動切換)
// 如果你要顯示台灣時間,請改成 "CST-8"
#define TZ_INFO "CET-1CEST,M3.5.0,M10.5.0/3"
// ==========================================
// 核心座標轉換:處理 S 型走線
// ==========================================
int XY(int x, int y) {
if(y % 2 == 0) {
// 偶數排 (0, 2, 4...) 從左到右
return (y * 11) + x;
} else {
// 奇數排 (1, 3, 5...) 從右到左
return (y * 11) + (10 - x);
}
}
// 點亮單字的輔助函數 (y: 第幾排, x_start: 起始行數, len: 單字長度)
void lightWord(int y, int x_start, int len) {
for(int i = 0; i < len; i++) {
leds[XY(x_start + i, y)] = timeColor;
}
}
// ==========================================
// 西語單字座標映射 (對應你提供的 11x10 網格)
// ==========================================
void showPrefix(int h) {
if(h == 1) {
lightWord(0, 0, 2); // ES
lightWord(0, 5, 2); // LA
} else {
lightWord(0, 1, 3); // SON
lightWord(0, 5, 3); // LAS
}
}
void showHour(int h) {
switch(h) {
case 1: lightWord(0, 8, 3); break; // UNA
case 2: lightWord(1, 0, 3); break; // DOS
case 3: lightWord(1, 4, 4); break; // TRES
case 4: lightWord(2, 0, 6); break; // CUATRO
case 5: lightWord(2, 6, 5); break; // CINCO
case 6: lightWord(3, 0, 4); break; // SEIS
case 7: lightWord(3, 5, 5); break; // SIETE
case 8: lightWord(4, 0, 4); break; // OCHO
case 9: lightWord(4, 4, 5); break; // NUEVE
case 10: lightWord(5, 2, 4); break; // DIEZ
case 11: lightWord(5, 7, 4); break; // ONCE
case 0:
case 12: lightWord(6, 0, 4); break; // DOCE
}
}
void showMinute(int mStep) {
switch(mStep) {
case 1: lightWord(6, 5, 1); lightWord(8, 6, 5); break; // Y CINCO
case 2: lightWord(6, 5, 1); lightWord(7, 7, 4); break; // Y DIEZ
case 3: lightWord(6, 5, 1); lightWord(9, 5, 6); break; // Y CUARTO (對應面板拼寫)
case 4: lightWord(6, 5, 1); lightWord(7, 1, 6); break; // Y VEINTE
case 5: lightWord(6, 5, 1); lightWord(8, 0, 11); break;// Y VEINTICINCO
case 6: lightWord(6, 5, 1); lightWord(9, 0, 5); break; // Y MEDIA
case 7: lightWord(6, 6, 5); lightWord(8, 0, 11); break;// MENOS VEINTICINCO
case 8: lightWord(6, 6, 5); lightWord(7, 1, 6); break; // MENOS VEINTE
case 9: lightWord(6, 6, 5); lightWord(9, 5, 6); break; // MENOS CUARTO
case 10: lightWord(6, 6, 5); lightWord(7, 7, 4); break; // MENOS DIEZ
case 11: lightWord(6, 6, 5); lightWord(8, 6, 5); break; // MENOS CINCO
}
}
void setup() {
Serial.begin(115200);
FastLED.addLeds<LED_TYPE, DATA_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection(TypicalLEDStrip);
FastLED.setBrightness(BRIGHTNESS);
// 開機時先亮一顆藍燈,表示正在處理 Wi-Fi
FastLED.clear();
leds[XY(0,0)] = CRGB::Blue;
FastLED.show();
// 啟動 WiFiManager
WiFiManager wifiManager;
// 取消這行的註解可以清除記住的 WiFi (測試用)
// wifiManager.resetSettings();
// 如果連不上,會發出名為 "WordClock_Setup" 的 Wi-Fi
wifiManager.autoConnect("WordClock_Setup");
Serial.println("Wi-Fi 連線成功!");
// 設定 NTP 網路時間
configTime(0, 0, "pool.ntp.org", "time.nist.gov");
setenv("TZ", TZ_INFO, 1);
tzset();
}
void loop() {
time_t now = time(nullptr);
struct tm* timeinfo = localtime(&now);
// 確認年份大於 2000,代表時間已經從網路同步成功
if (timeinfo->tm_year > 100) {
int h = timeinfo->tm_hour;
int m = timeinfo->tm_min;
// 轉為 12 小時制
h = h % 12;
if (h == 0) h = 12;
int mStep = m / 5;
// 西語時間邏輯:超過 30 分後,小時要加 1 (用 MENOS 表達差幾分)
if (mStep >= 7) {
h = h + 1;
if (h > 12) h = 1;
}
FastLED.clear();
showPrefix(h);
showHour(h);
showMinute(mStep);
FastLED.show();
} else {
// 時間還沒抓到時,讓第一顆藍燈閃爍
FastLED.clear();
leds[XY(0,0)] = (millis() % 1000 < 500) ? CRGB::Blue : CRGB::Black;
FastLED.show();
}
delay(1000); // 每秒更新一次
}
7. 接著把 usb 沒被剪的那端接上一個 5V 豆腐頭插上插座就大功告成!

成品展示
最好笑的是我把右下角拼錯了,應該是 CUARTO 才對,因為我原本想寫的是 1/4。另外貼上字母遮罩的時候要謹慎,否則就會像我的一樣凹凸不平,近看很醜。
參考資料:
寫論文都要參考資料,做 DIY 也是。我的材料都是跟著這個教學買的,然後有些地方自己改一下。比如影片中用了一塊木板,然後割圓孔出來,但是我沒有雷射切割,所以我改成買黑色板子然後用美工刀割。














