📺 影片版(約 5 分鐘):想先看實際操作的話可以直接看這支影片,完整步驟、設定與程式碼在下方文字版。
繼用ESP32改造家中天花板的LED燈後, 想說用 ESP32來做一個時鐘, 可以顯示日期時間, 溫度, 濕度, PM2.5等的資訊!
最簡單的架構, 一片ESP32加上顯示板!
顯示板上網搜尋後, 決定用這種 RGB LED Matrix
硬體零件決定後, 開始軟體的編成
1. 首先還是將Arduino IDE載入額外的開發板管理員網址, 裝入ESP32開發板Package
https://dl.espressif.com/dl/package_esp32_index.json
2. RGB Led Matrix 顯示的 library
3. 接下來這個步驟很重要
需要在RGB matrix Panel的libeary將原先
//static const uint8_t defaultrgbpins[] = {2, 3, 4, 5, 6, 7};改為
static const uint8_t defaultrgbpins[] = {25, 26, 27, 21, 22, 23};
以下 為我的IO設定
//------------------------------------------------------------------------
#define A 12
#define B 13
#define C 14
#define D 15
#define CLK 4
#define LAT 2
#define OE 0
RGBmatrixPanel matrix(A, B, C, D, CLK, LAT, OE, false, 64);
4. 將範例中的testshapes32*64 demo上傳到板子就可以出現下面畫面
5. ESP32 NTP的寫法
可參考網路上資料如
https://lastminuteengineers.com/esp32-ntp-server-date-time-tutorial/
https://randomnerdtutorials.com/esp32-ntp-client-date-time-arduino-ide/
補充一、硬體架構與 HUB75 接腳
整台時鐘只有三樣東西:ESP32 開發板、64×32 RGB LED Matrix(4mm pitch,HUB75 介面)、一顆 5V 電源。面板背面的 16 pin 排針就是 HUB75,照文中的 IO 設定接到 ESP32:
| HUB75 腳位 | 功能 | ESP32 GPIO |
| R1 / G1 / B1 | 上半部(第 0~15 列)的紅綠藍資料 | 25 / 26 / 27 |
| R2 / G2 / B2 | 下半部(第 16~31 列)的紅綠藍資料 | 21 / 22 / 23 |
| A / B / C / D | 列位址(4 條線選 16 組列) | 12 / 13 / 14 / 15 |
| CLK | 資料移位時脈 | 4 |
| LAT(STB) | 鎖存,一列資料送完後拉一下 | 2 |
| OE | 輸出致能(低電位亮) | 0 |
| GND | 接地,要和 ESP32 共地 | GND |
文中把函式庫的
defaultrgbpins[] 改成
{25, 26, 27, 21, 22, 23},順序就是 R1、G1、B1、R2、G2、B2。
補充二、64×32 面板是怎麼亮的
這種面板本身沒有記憶體,要靠 MCU 不停地「掃描」:
- 1/16 掃描:A~D 四條位址線可以選 2⁴ = 16 組列,每次同時點亮上半部一列和下半部一列(所以有 R1G1B1 和 R2G2B2 兩組資料線),16 次就掃完 32 列。
- 一列怎麼送:64 個像素的顏色從資料線一位一位移進去(每個 CLK 一位),送完拉 LAT 鎖存,再用 OE 打開這一列。
- 顏色深淺:LED 只有亮和不亮,Adafruit RGB-matrix-Panel 在 ESP32 上用 4 個位元平面(nPlanes = 4)做 PWM,每色 16 階、總共 4096 色。所以
Color888() 給的 24-bit 顏色實際上會被降到每色 4 bit。
- 誰在掃描:函式庫在
begin() 裡設定 ESP32 的硬體計時器中斷(TIMER_GROUP_1、40 MHz),掃描在背景進行,loop() 只需要畫圖。
補充三、不改函式庫也能換腳位
文中的做法是直接改函式庫原始碼,缺點是之後更新函式庫就被蓋掉。目前版本的 RGB-matrix-Panel(1.1.9)在 ESP32 / SAMD 上,建構子最後多了一個
pinlist 參數,可以直接把 RGB 腳位傳進去:
#include <RGBmatrixPanel.h>
#define A 12
#define B 13
#define C 14
#define D 15
#define CLK 4
#define LAT 2
#define OE 0
uint8_t rgbPins[] = {25, 26, 27, 21, 22, 23}; // R1 G1 B1 R2 G2 B2
// 最後兩個參數:寬度 64、RGB 腳位表
RGBmatrixPanel matrix(A, B, C, D, CLK, LAT, OE, false, 64, rgbPins);
第 8 個參數
false 是「不用雙緩衝」;改成
true 會多用一倍 RAM,但可以先在背景畫好再用
matrix.swapBuffers(true) 一次切換,畫面更新時比較不會閃。
補充四、ESP32 腳位與電源要注意的地方
- Strapping pins:這組設定裡的 GPIO 0、2、12、15 都是 ESP32 開機時會讀取電位的腳位(Espressif 文件)。GPIO0 開機時是低電位會進入下載模式、GPIO12 開機時是高電位會把 Flash 電壓設成 1.8V。平常接面板沒事,但如果遇到「插著面板就燒錄失敗或開不了機」,先把面板拔掉試試,或把 A / OE 等改到其他 GPIO。
- 不要用的腳:GPIO 6~11 接 Flash,不能拿來用;GPIO 34~39 只能輸入,不能當輸出。
- 3.3V 訊號:ESP32 輸出 3.3V,面板是 5V 邏輯,多數面板直接接就能動;如果畫面出現雜點或錯位,可以加 74HCT245 之類的電平轉換。
- 電源:LED 面板要接自己的 5V 電源,不要從 ESP32 的 5V 腳取電。全亮白色時電流可達數安培,時鐘這種黑底彩字的畫面耗電小很多,但電源還是建議留 5V 4A 左右的餘裕,GND 和 ESP32 共地。
補充五、NTP 網路對時
ESP32 Arduino core 內建 SNTP,不用另外裝函式庫。連上 WiFi 後呼叫一次
configTime(),之後系統時間會自動校正,用
getLocalTime() 取出來:
#include <WiFi.h>
#include "time.h"
const long gmtOffset_sec = 8 * 3600; // 台灣 UTC+8
const int daylightOffset_sec = 0; // 台灣沒有夏令時間
void setupTime() {
configTime(gmtOffset_sec, daylightOffset_sec, "time.stdtime.gov.tw", "pool.ntp.org");
}
bool readTime(char *hms, char *week, char *ymd) {
struct tm t;
if (!getLocalTime(&t)) return false; // 還沒對到時間
strftime(hms, 9, "%H:%M:%S", &t); // 20:24:51
strftime(week, 10, "%A", &t); // Sunday
strftime(ymd, 11, "%Y/%m/%d", &t); // 2021/04/11
return true;
}
%A 會輸出英文星期,剛好就是照片裡紅色那一行。
補充六、四行版面與輪播
照片裡的版面是 64×32 分成四行,每行 8 像素高,剛好是 Adafruit GFX 內建 5×7 字型(字距 6、行距 8)的大小:
| 行 | 內容 | 顏色 |
| 第 1 行(y=0) | 時:分:秒 | 綠 |
| 第 2 行(y=8) | 星期 | 紅 |
| 第 3 行(y=16) | 年/月/日 | 紫 |
| 第 4 行(y=24) | Temp → Hum → PM2.5 輪流 | 黃 |
畫面更新的寫法重點是「只在秒數改變時重畫」,而且只清掉要改的那一行,不要每次
fillScreen():
unsigned long lastSec = 0;
int page = 0;
void drawLine(int y, const char *s, uint16_t color) {
matrix.fillRect(0, y, 64, 8, 0); // 只清這一行
matrix.setCursor(1, y);
matrix.setTextColor(color);
matrix.print(s);
}
void loop() {
if (millis() - lastSec < 1000) return;
lastSec = millis();
char hms[9], week[10], ymd[11], info[12];
if (!readTime(hms, week, ymd)) return;
drawLine(0, hms, matrix.Color333(0, 7, 0));
drawLine(8, week, matrix.Color333(7, 0, 0));
drawLine(16, ymd, matrix.Color333(5, 0, 7));
// 第 4 行每 5 秒換一個項目;readTemp() 等依實際使用的感測器或資料來源實作
int p = (lastSec / 5000) % 3;
if (p == 0) snprintf(info, sizeof info, "Temp:%.2f", readTemp());
if (p == 1) snprintf(info, sizeof info, "Hum:%.2f", readHum());
if (p == 2) snprintf(info, sizeof info, "PM2.5:%d", readPM25());
drawLine(24, info, matrix.Color333(7, 7, 0));
}
原文沒有寫溫濕度和 PM2.5 的資料來源,所以這裡用
readTemp()、
readHum()、
readPM25() 代替,依自己用的感測器或網路資料替換。
補充七、常見問題
| 現象 | 先檢查 |
| 整片不亮 | 面板 5V 電源、GND 有沒有和 ESP32 共地、OE 腳位 |
| 只有上半部或下半部亮 | R1G1B1 或 R2G2B2 那組沒接好,或腳位表順序錯 |
| 畫面上下重複、列錯位 | 建構子有沒有用帶 D 腳的版本、寬度有沒有給 64 |
| 顏色對調 | 部分面板 R / B 或 G / B 順序不同,調換腳位表裡的順序 |
| 燒錄失敗、開不了機 | GPIO 0 / 2 / 12 / 15 被面板拉住,先拔面板再燒 |
| 時間顯示 1970 年或不動 | WiFi 沒連上或 NTP 還沒回應,getLocalTime() 會回 false |
| 板子管理員只裝得到舊版 ESP32 | 文中 dl.espressif.com 的網址是舊的,新版改用 https://espressif.github.io/arduino-esp32/package_esp32_index.json |
| 新版 ESP32 core 編譯錯誤 | 函式庫用的是舊的 timer 介面,可以先退回 2.0.x 版 core,或改用 Adafruit 建議給 ESP32 的 Adafruit_Protomatter |
延伸閱讀