WS2812 是什麼?
ESP32 WS2812 RGB LED(又稱 NeoPixel)是物聯網視覺化專案中最受歡迎的可尋址 LED,每顆 LED 內部整合了驅動 IC,只需一條 GPIO 訊號線就能獨立控制每顆 LED 的顏色和亮度。單顆 WS2812 可顯示 24-bit 色彩(16,777,216 色),並支援無限級聯。

WS2812 單線通訊協定
WS2812 使用專屬的單線(One-Wire)通訊協定,時脈為 800kHz,透過精確的脈衝寬度編碼來區分邏輯 '0' 和 '1':
關鍵時序參數(容差 ±150ns):
- T0H = 0.35μs(code '0' 的高電位時間)
- T0L = 0.90μs(code '0' 的低電位時間)
- T1H = 0.70μs(code '1' 的高電位時間)
- T1L = 0.60μs(code '1' 的低電位時間)
- RESET = 最低 280μs 的低電位(標示訊框結束)
位元速率約 800 kbps,每個 LED 需 24 bits = 30μs 傳輸時間。
24-bit 資料訊框與級聯機制
WS2812 的資料格式與常見的 RGB 順序不同:
- 資料順序:G(7:0) → R(7:0) → B(7:0),由高位元(MSB)先傳
- 綠色 = bit 23~16,紅色 = bit 15~8,藍色 = bit 7~0
- 級聯(Daisy Chain):第一顆 LED 接收完整資料後,將後續資料透過 DOUT 轉發給下一顆
- 因此傳送 N 顆 LED 的資料 = N × 24 bits + RESET(≥ 280μs)
用 delayMicroseconds() 實作(最簡單)
// ESP32 WS2812 — 最簡方式(delayMicroseconds)
#include <Adafruit_NeoPixel.h>
#define PIN 18
#define NUM_LEDS 64
Adafruit_NeoPixel strip(NUM_LEDS, PIN, NEO_GRB + NEO_KHZ800);
void setup() {
strip.begin();
strip.show(); // 初始化後全部熄滅
}
void loop() {
// 彩虹漸變
for (int i = 0; i < NUM_LEDS; i++) {
int hue = (i * 360 / NUM_LEDS + millis() / 10) % 360;
strip.setPixelColor(i, strip.gamma32(strip.ColorHSV(hue)));
}
strip.show();
delay(20);
}
直接 GPIO 操作(理解底層原理)
// ESP32 WS2812 — 直接 GPIO 操作(理解協定原理)
#define WS2812_PIN 18
#define NUM_LEDS 16
// 使用 ESP32 GPIO 輸出暫存器(高速操作)
#define GPIO_OUT_REG (GPIO.out_w1ts.val) // 設 HIGH
#define GPIO_CLR_REG (GPIO.out_w1tc.val) // 設 LOW
#define BIT_MASK (1ULL << WS2812_PIN)
// 精確延時(使用 ESP32 的 esp_timer 或 Xtensa 指令週期)
// ESP32 時脈 240MHz → 1 個指令 ≈ 4.17ns
// 但需考慮函式呼叫和 GPIO 操作的開銷
static inline void write_ws2812_byte(uint8_t byte) {
for (int i = 7; i >= 0; i--) { // MSB first
if (byte & (1 << i)) {
// 送出 '1': HIGH 0.70μs → LOW 0.60μs
GPIO_OUT_REG = BIT_MASK;
__asm__ __volatile__("nop; nop; nop; nop; nop; nop; nop;"); // ~0.7μs
GPIO_CLR_REG = BIT_MASK;
__asm__ __volatile__("nop; nop; nop; nop; nop;"); // ~0.6μs
} else {
// 送出 '0': HIGH 0.35μs → LOW 0.90μs
GPIO_OUT_REG = BIT_MASK;
__asm__ __volatile__("nop; nop; nop; nop;"); // ~0.35μs
GPIO_CLR_REG = BIT_MASK;
__asm__ __volatile__("nop; nop; nop; nop; nop; nop; nop; nop; nop; nop;"); // ~0.9μs
}
}
}
void ws2812_show(uint8_t leds[][3], int count) {
// 關閉中斷,確保時序精確
taskDISABLE_INTERRUPTS();
for (int i = 0; i < count; i++) {
write_ws2812_byte(leds[i][0]); // G
write_ws2812_byte(leds[i][1]); // R
write_ws2812_byte(leds[i][2]); // B
}
taskENABLE_INTERRUPTS();
// RESET ≥ 280μs
GPIO_CLR_REG = BIT_MASK;
delayMicroseconds(300);
}
使用 ESP32 RMT 外設(推薦方式)
直接 GPIO 操作的時序容易受中斷影響。ESP32 的 RMT 外設能硬體產生精確脈衝,不受 CPU 干擾:
// ESP32 WS2812 — 使用 RMT 外設(專業做法)
#include <driver/rmt.h>
#define RMT_TX_CH 0
#define WS2812_PIN 18
#define NUM_LEDS 64
// 編碼 WS2812 的 '0' (T0H=0.35μs, T0L=0.90μs)
void setupRMT() {
rmt_config_t config = {
.rmt_mode = RMT_MODE_TX,
.channel = (rmt_channel_t)RMT_TX_CH,
.gpio_num = (gpio_num_t)WS2812_PIN,
.clk_div = 4, // 80MHz / 4 = 20MHz → 1 tick = 50ns
.mem_block_num = 1,
.tx_config = {
.carrier_freq_hz = 0, // 無載波
.carrier_en = false,
.idle_level = 0,
.loop_en = false,
}
};
rmt_config(&config);
rmt_driver_install((rmt_channel_t)RMT_TX_CH, 0, 0);
}
// 用 RMT item 建構 WS2812 資料
// 邏輯 '0': T0H=7 ticks(350ns) + T0L=18 ticks(900ns)
// 邏輯 '1': T1H=14 ticks(700ns) + T1L=12 ticks(600ns)
#define T0H_TICKS 7
#define T0L_TICKS 18
#define T1H_TICKS 14
#define T1L_TICKS 12
void ws2812_rmt_show(rmt_item32_t* items, int num_leds,
uint8_t leds[][3]) {
int idx = 0;
for (int i = 0; i < num_leds; i++) {
// G (MSB first)
for (int b = 7; b >= 0; b--) {
items[idx].level0 = 1;
items[idx].level1 = 0;
if (leds[i][0] & (1 << b)) {
items[idx].duration0 = T1H_TICKS;
items[idx].duration1 = T1L_TICKS;
} else {
items[idx].duration0 = T0H_TICKS;
items[idx].duration1 = T0L_TICKS;
}
idx++;
}
// R
for (int b = 7; b >= 0; b--) {
items[idx].level0 = 1;
items[idx].level1 = 0;
if (leds[i][1] & (1 << b)) {
items[idx].duration0 = T1H_TICKS;
items[idx].duration1 = T1L_TICKS;
} else {
items[idx].duration0 = T0H_TICKS;
items[idx].duration1 = T0L_TICKS;
}
idx++;
}
// B
for (int b = 7; b >= 0; b--) {
items[idx].level0 = 1;
items[idx].level1 = 0;
if (leds[i][2] & (1 << b)) {
items[idx].duration0 = T1H_TICKS;
items[idx].duration1 = T1L_TICKS;
} else {
items[idx].duration0 = T0H_TICKS;
items[idx].duration1 = T0L_TICKS;
}
idx++;
}
}
// 寫入 RESET (≥ 280μs = 5600 ticks)
items[idx].duration0 = 0;
items[idx].level0 = 0;
items[idx].duration1 = 6000;
items[idx].level1 = 0;
idx++;
rmt_write_items((rmt_channel_t)RMT_TX_CH, items, idx, true);
}
// 全域緩衝區
rmt_item32_t ledItems[NUM_LEDS * 24 + 1];
uint8_t ledData[NUM_LEDS][3] = {0};
void loop() {
// 製作彩虹效果
for (int i = 0; i < NUM_LEDS; i++) {
int hue = (i * 256 / NUM_LEDS + millis() / 5) & 0xFF;
ledData[i][0] = hue; // G
ledData[i][1] = 255 - hue; // R
ledData[i][2] = (hue * 2) & 0xFF; // B
}
ws2812_rmt_show(ledItems, NUM_LEDS, ledData);
delay(30);
}
使用 Adafruit NeoPixel 函式庫(開發最快)
// ESP32 WS2812 — Adafruit NeoPixel 函式庫
#include <Adafruit_NeoPixel.h>
#define PIN 18
#define NUM_LEDS 256 // 8×8 矩陣或 4 條 64 燈燈條
Adafruit_NeoPixel strip(NUM_LEDS, PIN, NEO_GRB + NEO_KHZ800);
uint32_t Wheel(byte WheelPos) {
WheelPos = 255 - WheelPos;
if (WheelPos < 85) {
return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
}
if (WheelPos < 170) {
WheelPos -= 85;
return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
}
WheelPos -= 170;
return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}
void theaterChase(uint32_t color, int wait) {
for (int a = 0; a < 10; a++) {
for (int b = 0; b < 3; b++) {
strip.clear();
for (int c = b; c < strip.numPixels(); c += 3) {
strip.setPixelColor(c, color);
}
strip.show();
delay(wait);
}
}
}
void rainbow(int wait) {
for (long firstPixelHue = 0; firstPixelHue < 5 * 65536; firstPixelHue += 256) {
for (int i = 0; i < strip.numPixels(); i++) {
int pixelHue = firstPixelHue + (i * 65536L / strip.numPixels());
strip.setPixelColor(i, strip.gamma32(strip.ColorHSV(pixelHue)));
}
strip.show();
delay(wait);
}
}
void setup() {
strip.begin();
strip.setBrightness(50);
strip.show();
}
void loop() {
rainbow(10);
theaterChase(strip.Color(255, 0, 0), 50);
theaterChase(strip.Color(0, 255, 0), 50);
theaterChase(strip.Color(0, 0, 255), 50);
}
硬體注意事項
- 電源:每顆 WS2812 全白時需 60mA(5V)。100 顆 = 6A!務必使用獨立電源,不可從 ESP32 的 3.3V 穩壓器供電
- 電容:LED 電源端並聯 1000μF 電解電容,防止瞬間大電流導致電壓驟降
- 電平轉換:ESP32 GPIO 為 3.3V,WS2812 DIN 邏輯準位為 5V(Vih 最低 0.7VCC ≈ 3.5V),建議使用 3.3V→5V 電平移位器(如 74HCT125)
- 串聯電阻:DIN 路徑串聯 300~500Ω 電阻,減少訊號反射和雜訊
- 地線:ESP32 和 LED 電源必須共地

ESP32 vs Arduino Uno:WS2812 控制能力對比
| 特性 | Arduino Uno | ESP32 |
|---|---|---|
| 最高 LED 數 | ~300(軟體 PWM) | ~3000(RMT + DMA) |
| 更新率(300 LEDs) | ~30 fps | ~100 fps |
| 中斷影響 | 中斷時畫面閃爍 | RMT 硬體發送,不受影響 |
| 同時驅動燈條數 | 1 條 | 8 條(RMT 8 通道) |
| WiFi 並行 | 不支援 | RMT 背景發送,CPU 可跑 WiFi |
| Gamma 校正 | 需軟體實作 | 內建 gamma32() 硬體加速 |
進階:RMT + DMA + 雙緩衝
對於 > 500 顆 LED 的大型專案(如 LED 矩陣牆),需要使用 DMA 雙緩衝技術:
// ESP32 WS2812 — RMT + DMA + 雙緩衝(大型專案)
#include <driver/rmt.h>
#define RMT_TX_CH 0
static void rmt_tx_done_cb(rmt_channel_t channel, void* arg) {
// DMA 傳輸完成回呼
xSemaphoreGiveFromISR((SemaphoreHandle_t)arg, NULL);
}
void setupRMT_DMA() {
rmt_config_t config = {
.rmt_mode = RMT_MODE_TX,
.channel = (rmt_channel_t)RMT_TX_CH,
.gpio_num = (gpio_num_t)WS2812_PIN,
.clk_div = 4,
.mem_block_num = 8,
.tx_config = {
.loop_en = false,
.idle_level = 0,
.carrier_en = false,
}
};
rmt_config(&config);
rmt_driver_install((rmt_channel_t)RMT_TX_CH, 4096, 0);
rmt_register_tx_end_callback(rmt_tx_done_cb, NULL);
}
實戰:WiFi 控制 LED 矩陣
// ESP32 WS2812 — WiFi + Web Server 控制
// 結合之前介紹的 ESP32 Web Server,用手機控制 LED 顏色
#include <WiFi.h>
#include <WebServer.h>
#include <Adafruit_NeoPixel.h>
#define NUM_LEDS 64
#define PIN 18
Adafruit_NeoPixel strip(NUM_LEDS, PIN, NEO_GRB + NEO_KHZ800);
WebServer server(80);
// HTML + JavaScript 從 R"rawliteral(...)rawliteral" 取得
void handleRoot() { server.send(200, "text/html", html); }
void handleSet() {
String color = server.arg("color");
int bright = server.arg("bright").toInt();
long rgb = strtol(color.c_str(), NULL, 16);
uint8_t r = (rgb >> 16) & 0xFF;
uint8_t g = (rgb >> 8) & 0xFF;
uint8_t b = rgb & 0xFF;
strip.setBrightness(bright);
for (int i = 0; i < NUM_LEDS; i++) {
strip.setPixelColor(i, strip.Color(r, g, b));
}
strip.show();
server.send(200, "text/plain", "OK");
}
void setup() {
WiFi.begin("SSID", "PASSWORD");
while (WiFi.status() != WL_CONNECTED) delay(500);
strip.begin(); strip.show();
server.on("/", handleRoot);
server.on("/set", handleSet);
server.begin();
}
void loop() { server.handleClient(); }
總結
WS2812 以其簡潔的單線通訊協定和豐富的色彩表現,成為 IoT 視覺化專案的首選。ESP32 的 RMT 外設讓 WS2812 控制變得簡單可靠,不受 CPU 中斷影響,同時支援 WiFi 連線實現遠端控制。
選擇指引:
- 小型專案(≤ 64 LEDs):Adafruit NeoPixel 函式庫,簡單快速
- 中型專案(64~500 LEDs):RMT 外設 + 直接記憶體操作
- 大型專案(≥ 500 LEDs):RMT + DMA + 雙緩衝,搭配 PSRAM
文章評論