Dải LED WS2812B với ESP32: điều khiển từng bóng bằng RMT không cần thư viện, tính dòng và chọn nguồn

Bên trong bóng WS2812B, tín hiệu một dây 24 bit GRB, cách tính dòng để chọn nguồn, nối dải LED với ESP32 và tạo tín hiệu bằng bộ RMT không cần thư viện, kèm ba hiệu ứng.

Dải LED WS2812B (hay gặp với tên "LED NeoPixel", "LED RGB địa chỉ") cho phép chỉnh màu và độ sáng của từng bóng riêng chỉ bằng một dây tín hiệu. Đây là loại LED dùng cho đèn trang trí, bảng hiệu chữ chạy, đèn hắt màn hình, đèn báo trạng thái thiết bị. Bài này giải thích bên trong một bóng WS2812B, tín hiệu điều khiển, cách tính dòng điện để chọn nguồn, rồi viết chương trình ESP32 tạo tín hiệu bằng bộ RMT có sẵn trong chip, không cần thư viện, chạy ba hiệu ứng: chấm màu chạy, cầu vồng, sáng tắt như hơi thở.

1. WS2812B là gì

Một đoạn dải LED WS2812B có các chân +5V, Din, GND ở đầu vào và DO ở đầu ra, bên cạnh là ảnh phóng to một bóng LED thấy ba chip LED và chip điều khiển
Một đoạn dải LED WS2812B (trái) và một bóng phóng to (phải). Ảnh: John McMaster, CC BY 4.0, ảnh trái, ảnh phải trên Wikimedia Commons; đã cắt và thu nhỏ.

Mỗi bóng WS2812B cỡ 5 × 5 mm chứa ba chip LED đỏ, xanh lá, xanh dương và một chip điều khiển nhỏ. Chip điều khiển nhận màu qua chân DIN, giữ lại phần dành cho nó, rồi chuyển phần còn lại sang chân DO để bóng sau nhận tiếp. Nhờ cách nối nối tiếp này, cả dải hàng trăm bóng chỉ cần ba dây: 5 V, GND và dữ liệu.

Thông sốWS2812B (theo datasheet)
Điện áp5 V (3,5–5,3 V)
MàuMỗi màu 256 mức, 24 bit một bóng, khoảng 16,7 triệu màu
Dòng tối đa mỗi màuKhoảng 20 mA, tức khoảng 60 mA một bóng khi sáng trắng hết cỡ
Tín hiệuMột dây, 800 kbit/s, mỗi bit 1,25 µs
Loại dải thường gặp30, 60, 144 bóng mỗi mét; có loại bọc silicon chống nước

2. Linh kiện cần chuẩn bị

3. Sơ đồ nối dây

Sơ đồ nối dải LED WS2812B với ESP32: chân D13 qua điện trở 330 ohm vào DIN, nguồn 5 V riêng cấp cho dải LED, tụ 1000 micro fara sát đầu dải, GND nối chung
Dữ liệu từ D13 qua điện trở 330 Ω; dải LED dùng nguồn 5 V riêng có tụ lọc (hình minh họa).
Dải LEDNối vào
DIN (đầu có mũi tên chỉ vào)D13 (GPIO13) qua điện trở 330 Ω
+5VCực dương nguồn 5 V riêng
GNDCực âm nguồn 5 V và GND của ESP32
  • Đúng đầu vào: trên dải có mũi tên in cạnh chân DIN chỉ chiều tín hiệu. Nối nhầm vào đầu DO thì không bóng nào sáng.
  • Tụ 1000 µF giữa +5V và GND ở đầu dải giúp nguồn không sụt khi cả dải đổi màu cùng lúc.
  • Mức tín hiệu 3,3 V: WS2812B cấp 5 V cần mức cao tối thiểu khoảng 3,5 V, cao hơn mức 3,3 V của ESP32 một chút. Phần lớn dải vẫn nhận đúng khi dây dữ liệu ngắn. Gặp hiện tượng nhấp nháy sai màu thì thêm IC chuyển mức 74AHCT125 hoặc 74HCT245; lý do có trong bài mức logic 3,3 V và 5 V.

4. Tín hiệu điều khiển

Dạng sóng bit 0 cao 0,4 micro giây thấp 0,8 micro giây, bit 1 cao 0,8 micro giây thấp 0,4 micro giây, và 24 bit của một LED theo thứ tự xanh lá, đỏ, xanh dương
Bit 0, bit 1 phân biệt bằng độ dài mức cao; mỗi LED nhận 24 bit theo thứ tự G, R, B (hình minh họa).

Mỗi bit dài khoảng 1,2 µs. Bit 0 có mức cao ngắn (0,4 µs), bit 1 có mức cao dài (0,8 µs). Mỗi bóng nhận 24 bit theo thứ tự xanh lá, đỏ, xanh dương (không phải đỏ trước), bit cao nhất gửi trước. Gửi xong cả dải thì giữ dây ở mức thấp hơn 280 µs: các bóng hiểu đó là hết khung và cùng lúc đổi sang màu mới.

Thời gian tính bằng phần mười micro giây nên không thể bật tắt chân GPIO bằng digitalWrite() rồi chờ: chỉ một ngắt Wi-Fi chen vào là sai bit. ESP32 có bộ RMT (Remote Control Transceiver) vốn làm ra để phát tín hiệu hồng ngoại: nó nhận một bảng "mức cao bao lâu, mức thấp bao lâu" rồi tự phát ra chân bằng phần cứng, chính xác tới 0,1 µs, CPU không phải làm gì.

5. Dải LED tốn bao nhiêu dòng

Biểu đồ dòng điện ước tính: 30 LED đỏ hoặc cầu vồng ở độ sáng 128 khoảng 331 mA, 30 LED trắng độ sáng 128 khoảng 933 mA, độ sáng 255 khoảng 1830 mA, 150 LED trắng khoảng 9150 mA
Dòng ước tính khi chưa giới hạn, theo đúng công thức trong chương trình (số liệu tính toán).

Đây là phần hay bị bỏ qua nhất. Mỗi màu sáng hết cỡ ăn khoảng 20 mA, nên một bóng trắng hết cỡ khoảng 60 mA. 30 bóng trắng là gần 2 A, cuộn 5 m loại 30 bóng/m sáng trắng cần khoảng 9 A. Cổng USB máy tính chỉ cho 0,5–0,9 A. Vì vậy:

  • Chọn nguồn 5 V chịu được dòng lớn hơn dòng ước tính 20–30 %.
  • Hiệu ứng cầu vồng tốn ít dòng hơn tưởng: mỗi bóng chỉ sáng tổng cộng một "phần màu" (tổng đỏ + xanh lá + xanh dương luôn bằng 255), nên chỉ bằng một dải toàn màu đỏ.
  • Dải dài trên 1–2 m thì điện áp tụt dần về cuối dải, các bóng cuối ngả vàng đỏ. Cách xử lý là cấp nguồn thêm ở cuối dải (hoặc cả hai đầu và giữa) bằng dây to.
  • Chương trình có biến DONG_NGUON_MA: khi dòng ước tính vượt mức này, độ sáng chung tự giảm cho vừa nguồn.

6. Chương trình điều khiển WS2812B bằng RMT

Sửa SO_LED đúng số bóng của dải và DONG_NGUON_MA theo nguồn đang dùng, chọn bo ESP32 Dev Module rồi nạp. Bài dùng gói esp32 phiên bản 3.3.10, chỉ dùng hàm RMT có sẵn.

// PYWORLD.VN - Dải LED WS2812B với ESP32: tạo tín hiệu bằng bộ RMT, không cần thư viện; chạy màu, cầu vồng, thở
// DIN của dải LED nối D13 qua điện trở 330 Ω; dải LED cấp 5 V từ nguồn riêng, GND chung với ESP32
// Tự giảm độ sáng khi dòng ước tính vượt DONG_NGUON_MA để không quá tải nguồn

const int CHAN_DIN = 13;
const int SO_LED = 30;
const uint32_t DONG_NGUON_MA = 1500;      // dòng nguồn 5 V chịu được (mA), chừa dư 20-30 %
const uint8_t DO_SANG_TOI_DA = 128;       // 0-255, giới hạn chung cho dịu mắt

uint8_t mau[SO_LED][3];                   // màu từng LED theo thứ tự R, G, B
rmt_data_t kyHieu[SO_LED * 24];           // mỗi LED 24 bit, mỗi bit một ký hiệu RMT

// RMT chạy 10 MHz: 1 nhịp = 0,1 us. Bit 0: cao 0,4 us, thấp 0,8 us. Bit 1: cao 0,8 us, thấp 0,4 us.
const rmt_data_t BIT_0 = {{4, 1, 8, 0}};
const rmt_data_t BIT_1 = {{8, 1, 4, 0}};

// Dòng ước tính (mA): mỗi màu sáng hết cỡ khoảng 20 mA, cộng 1 mA cho mạch điều khiển của mỗi LED.
uint32_t dongUocTinh(uint8_t doSang) {
  uint32_t tong = 0;
  for (int i = 0; i < SO_LED; i++) tong += mau[i][0] + mau[i][1] + mau[i][2];
  return tong * doSang / 255 * 20 / 255 + SO_LED;
}

uint32_t hienThi() {
  uint8_t doSang = DO_SANG_TOI_DA;
  uint32_t dong = dongUocTinh(doSang);
  if (dong > DONG_NGUON_MA) {             // quá dòng: hạ độ sáng chung cho vừa nguồn
    doSang = (uint32_t)doSang * (DONG_NGUON_MA - SO_LED) / (dong - SO_LED);
    dong = dongUocTinh(doSang);
  }
  int k = 0;
  for (int i = 0; i < SO_LED; i++) {
    uint8_t g = mau[i][1] * doSang / 255, r = mau[i][0] * doSang / 255, b = mau[i][2] * doSang / 255;
    uint32_t grb = ((uint32_t)g << 16) | ((uint32_t)r << 8) | b;   // WS2812B nhận thứ tự G, R, B
    for (int bit = 23; bit >= 0; bit--) kyHieu[k++] = (grb >> bit) & 1 ? BIT_1 : BIT_0;
  }
  rmtWrite(CHAN_DIN, kyHieu, SO_LED * 24, RMT_WAIT_FOR_EVER);
  delayMicroseconds(300);                 // giữ mức thấp trên 280 us: dải LED chốt màu mới
  return dong;
}

void datMau(int i, uint8_t r, uint8_t g, uint8_t b) {
  mau[i][0] = r;
  mau[i][1] = g;
  mau[i][2] = b;
}

// Bánh xe màu: vi_tri 0-255 chạy qua đỏ, xanh lá, xanh dương rồi về đỏ.
void banhXeMau(int i, uint8_t viTri) {
  if (viTri < 85) datMau(i, 255 - viTri * 3, viTri * 3, 0);
  else if (viTri < 170) datMau(i, 0, 255 - (viTri - 85) * 3, (viTri - 85) * 3);
  else datMau(i, (viTri - 170) * 3, 0, 255 - (viTri - 170) * 3);
}

void setup() {
  Serial.begin(115200);
  if (!rmtInit(CHAN_DIN, RMT_TX_MODE, RMT_MEM_NUM_BLOCKS_1, 10000000)) {
    Serial.println("Loi khoi dong RMT");
  }
  for (int i = 0; i < SO_LED; i++) datMau(i, 0, 0, 0);
  hienThi();
  Serial.printf("%d LED o chan GPIO%d, nguon %lu mA\n", SO_LED, CHAN_DIN, (unsigned long)DONG_NGUON_MA);
}

void loop() {
  static uint32_t batDauCheDo = 0;
  static int cheDo = -1;
  static uint32_t buoc = 0;
  if (cheDo < 0 || millis() - batDauCheDo > 10000) {   // 10 giây đổi hiệu ứng
    cheDo = (cheDo + 1) % 3;
    batDauCheDo = millis();
    buoc = 0;
    Serial.printf("Hieu ung %d: %s\n", cheDo, cheDo == 0 ? "chay mau" : cheDo == 1 ? "cau vong" : "tho trang");
  }
  if (cheDo == 0) {                       // một chấm xanh chạy dọc dải, có đuôi mờ dần
    for (int i = 0; i < SO_LED; i++) {
      int khoangCach = (int)(buoc % SO_LED) - i;
      if (khoangCach < 0) khoangCach += SO_LED;
      uint8_t sang = khoangCach < 5 ? 255 >> (khoangCach * 2) : 0;
      datMau(i, 0, sang / 3, sang);
    }
  } else if (cheDo == 1) {                // cầu vồng trôi
    for (int i = 0; i < SO_LED; i++) banhXeMau(i, (i * 256 / SO_LED + buoc * 3) & 255);
  } else {                                // cả dải trắng sáng lên tắt xuống như hơi thở
    uint8_t sang = (uint8_t)(127.5f * (1 - cosf(buoc * 0.05f)));
    for (int i = 0; i < SO_LED; i++) datMau(i, sang, sang, sang);
  }
  uint32_t dong = hienThi();
  if (buoc % 50 == 0) Serial.printf("Dong uoc tinh %lu mA\n", (unsigned long)dong);
  buoc++;
  delay(20);
}

7. Giải thích các phần chính

  • rmtInit(CHAN_DIN, RMT_TX_MODE, RMT_MEM_NUM_BLOCKS_1, 10000000) cho bộ RMT chạy 10 MHz, mỗi nhịp 0,1 µs.
  • BIT_0, BIT_1 là hai ký hiệu RMT: mức cao 4 hoặc 8 nhịp, mức thấp 8 hoặc 4 nhịp. Mỗi bóng cần 24 ký hiệu, nên mảng kyHieu có SO_LED × 24 phần tử.
  • hienThi() ghép từng bóng thành số 24 bit theo thứ tự G, R, B, biến từng bit thành ký hiệu, gọi rmtWrite() rồi chờ 300 µs để các bóng chốt màu.
  • dongUocTinh() cộng độ sáng ba màu của mọi bóng để ước tính dòng. Nếu vượt DONG_NGUON_MA, hệ số độ sáng chung được hạ xuống theo tỉ lệ.
  • banhXeMau() đổi một số 0–255 thành màu đi vòng đỏ → xanh lá → xanh dương → đỏ; mỗi bóng lệch một chút nên thành dải cầu vồng.
  • Hiệu ứng thở dùng hàm cosin để độ sáng tăng giảm mềm, mắt nhìn tự nhiên hơn tăng giảm đều.

Thư viện Adafruit NeoPixel hoặc FastLED cũng dùng RMT trên ESP32 và có sẵn nhiều hiệu ứng. Chương trình tự viết giúp hiểu tín hiệu và kiểm soát được dòng điện.

8. Kết quả trên Serial Monitor

Serial Monitor in số LED, chân dữ liệu, dòng nguồn, tên hiệu ứng đang chạy và dòng điện ước tính
Ba hiệu ứng đổi mỗi 10 giây; dòng ước tính in định kỳ (hình minh họa, số liệu tính toán).

9. Xử lý lỗi thường gặp

Hiện tượngNguyên nhân thường gặpCách xử lý
Không bóng nào sángNối vào đầu DO; thiếu dây GND chung; nguồn chưa bậtNối vào đầu có mũi tên chỉ vào; nối GND nguồn với GND ESP32
Bóng đầu sáng sai màu, nhấp nháy ngẫu nhiênMức 3,3 V không đủ, dây dữ liệu dài, thiếu điện trở 330 ΩRút ngắn dây; thêm điện trở; dùng IC chuyển mức 74AHCT125
Màu đỏ hiện thành xanh láGửi sai thứ tự màuWS2812B nhận G, R, B; một số loại LED khác nhận R, G, B
Cuối dải ngả vàng, đỏ khi bật trắngSụt áp dọc dảiCấp nguồn thêm ở cuối dải; giảm độ sáng
ESP32 khởi động lại khi bật cả dảiDải LED lấy chung nguồn USB với ESP32, nguồn yếuNguồn riêng cho dải; tụ 1000 µF; hạ DONG_NGUON_MA
Một bóng hỏng, các bóng sau không sángChip điều khiển của bóng đó hỏng nên không chuyển tín hiệuCắt bỏ bóng hỏng, nối DO của bóng trước sang DIN của bóng sau

10. Xem thêm


Tin tức liên quan

Điều khiển relay từ xa bằng ESP-NOW: remote ESP32 một nút nhấn có phản hồi trạng thái
Điều khiển relay từ xa bằng ESP-NOW: remote ESP32 một nút nhấn có phản hồi trạng thái

Hướng dẫn làm bộ điều khiển relay từ xa bằng hai mạch ESP32 và ESP-NOW hai chiều: remote gửi lệnh, mạch relay báo lại trạng thái, lọc theo địa chỉ MAC và bật mã hóa.

Cấu hình Wi-Fi cho ESP32 bằng điện thoại, không nạp lại code: tự làm trang cài đặt như WiFiManager
Cấu hình Wi-Fi cho ESP32 bằng điện thoại, không nạp lại code: tự làm trang cài đặt như WiFiManager

ESP32 tự phát mạng Wi-Fi khi chưa có cấu hình, điện thoại vào là bật trang chọn mạng, lưu bằng Preferences; chỉ dùng thư viện có sẵn, giữ BOOT 3 giây để xóa.

Phased array siêu âm 40 kHz với ESP32 (phần 2): mạch đệm 12 V, đầu thu và quét búp sóng
Phased array siêu âm 40 kHz với ESP32 (phần 2): mạch đệm 12 V, đầu thu và quét búp sóng

Phần 2 của loạt phased array siêu âm: thêm mạch đệm 12 V bằng hai module L298N, lắp mạch thu dùng LM358, đo biên độ 40 kHz bằng ADC của ESP32 và chạy chương trình tự quét góc lái để xem búp sóng.

Bàn phím 4x4 và còi với ESP32: quét ma trận, chống dội, khóa mật khẩu và âm báo không cần thư viện
Bàn phím 4x4 và còi với ESP32: quét ma trận, chống dội, khóa mật khẩu và âm báo không cần thư viện

Cách quét bàn phím ma trận 4x4, chống dội phím, nối còi thụ động qua transistor, chương trình khóa mật khẩu có đổi mật khẩu, khóa tạm khi nhập sai và âm báo không dùng delay.

Đóng relay làm ESP32, Arduino bị reset hoặc treo: nguyên nhân và cách khắc phục
Đóng relay làm ESP32, Arduino bị reset hoặc treo: nguyên nhân và cách khắc phục

Vì sao đóng relay làm ESP32, Arduino tự khởi động lại hoặc treo: sụt áp nguồn, xung cuộn dây, hồ quang tiếp điểm, chân khởi động; cách khắc phục và chương trình đo sụt áp.

Hướng dẫn nạp chương trình cho ESP32 qua Wi-Fi (OTA) bằng Arduino IDE
Hướng dẫn nạp chương trình cho ESP32 qua Wi-Fi (OTA) bằng Arduino IDE

Hướng dẫn bật OTA trên ESP32 bằng thư viện ArduinoOTA có sẵn, nạp chương trình qua Wi-Fi từ Arduino IDE 2.x, đặt mật khẩu và các quy tắc để không mất khả năng cập nhật từ xa.

Hướng dẫn ESP32 làm web server điều khiển relay 4 kênh qua WiFi
Hướng dẫn ESP32 làm web server điều khiển relay 4 kênh qua WiFi

Hướng dẫn dùng ESP32 DevKit V1 làm web server trong mạng WiFi nội bộ, mở trang trên điện thoại để bật, tắt module relay 4 kênh; có sơ đồ nối dây, code Arduino IDE và lưu ý an toàn.

Hướng dẫn dùng màn hình OLED 1.3 inch SH1106 với ESP32
Hướng dẫn dùng màn hình OLED 1.3 inch SH1106 với ESP32

Hướng dẫn nối màn hình OLED 1.3 inch SH1106 với ESP32 DevKit V1 qua I2C, cài thư viện Adafruit SH110X, chạy code hiển thị chữ và xử lý lỗi thường gặp.


Bình luận
Đã thêm vào giỏ hàng