Dây LED RGB WS2812 đổi màu, hiệu ứng bằng điện thoại (30 bóng)

Dây LED 7 màu WS2812B 30 bóng (0,5m dây 60 bóng/m hoặc 1m dây 30 bóng/m) chọn màu và 10 hiệu ứng (cầu vồng, sao băng, thở, lửa…) trên điện thoại qua WiFi. Code tự giới hạn tổng dòng dưới 0,6A nên chạy được bằng cục sạc điện thoại 5V 1A.

⏱ 40 phút🔧 Mới học💰 Linh kiện ~158.200đ🧠 ESP32 DevKit V1⚡ Nguồn USB 5V

✅ Đã kiểm thử

Luật điệnKhông lỗi · 1 lưu ýSai chân, chập nguồn, quá áp 5V vào chân 3,3V, thiếu đi-ốt, thiếu trở kéo…
Mô phỏng SPICE1/1 kịch bản · 3 phép đo đạtDòng/áp từng linh kiện ở trường hợp xấu nhất (ngspice)
Biên dịch codeĐạt · dùng 75% bộ nhớesp32:esp32:esp32doit-devkit-v1
Xem chi tiết 3 phép đo

Đang gửi dữ liệu (DIN mức cao), dây LED sáng ở mức giới hạn

  • Tổng dòng đỉnh 850.1mA: dùng cục sạc điện thoại 5V ≥1A, cổng USB máy tính có thể không đủ.
  • Mức tín hiệu tới chân DIN: 3.29V ✓
  • Dòng chân GPIO13: 0.0mA ✓
  • Áp nuôi dây LED: 5.00V ✓
Lưu ý:
  • LS1.DIN cần mức cao ≥3.5V, ESP32 DevKit V1 chỉ xuất 3.3V. Thường vẫn chạy với dây ngắn; nếu chập chờn hãy thêm IC chuyển mức 74AHCT125.
  • Tổng dòng đỉnh 850.1mA: dùng cục sạc điện thoại 5V ≥1A, cổng USB máy tính có thể không đủ.
Kiểm lần cuối: 17:09:54 12/11/2026

Nguyên lý hoạt động

Mỗi bóng WS2812B có chip riêng, nhận chuỗi dữ liệu màu qua một dây DIN rồi chuyển tiếp cho bóng sau. ESP32 gửi dữ liệu từ GPIO13 qua trở 330Ω (chống dội tín hiệu, bảo vệ bóng đầu). Tụ 1000µF đặt ở đầu dây LED dập xung dòng lúc bật. Mỗi bóng trắng tối đa ăn 60mA, 30 bóng có thể tới 1,8A – vượt sức cục sạc; vì vậy code tính tổng dòng mỗi khung hình và tự giảm độ sáng để luôn dưới 0,6A. Mức 3,3V của ESP32 thấp hơn chuẩn 3,5V của WS2812 nhưng với dây tín hiệu ngắn (<30cm) chạy ổn định.

Sơ đồ đấu dây

Mỗi màu là một dây. Chấm tròn là điểm hàn. Chạm vào tên dây trong bảng để làm nổi bật.

⚡ Mô phỏng tương tácBấm thử — mạch được giải ngay trên máy bạn, điện áp và dòng hiện lên sơ đồ bên dưới.
Chân GPIO13điều khiển R1

Đây là mô phỏng một chiều (DC) ở trạng thái tĩnh: dùng để kiểm tra mức áp, dòng và công suất. Hiệu ứng nhấp nháy, PWM hay giao tiếp số do code quyết định nên không chạy ở đây.

5V5VGNDGNDDIN_DRVDIN_DRVDINDINU1 · ESP32 DevKit V1VINGPIO13 (D13)GNDR1 · Điện trở 330Ω12LS1 · Dây LED RGB WS2812B5VDINGNDC1 · Tụ điện 1000µF-+
#DâyNối các điểmÁp mô phỏng
1đỏ
5V
U1 VIN ⟷ LS1 5V ⟷ C1 +—
2đen
GND
U1 GND ⟷ LS1 GND ⟷ C1 -—
3vàng
DIN_DRV
U1 GPIO13 (D13) ⟷ R1 1—
4xanh lá
DIN
R1 2 ⟷ LS1 DIN—

Danh sách linh kiện

Ký hiệuLinh kiệnGiá tham khảo
U1ESP32 DevKit V1 30 chân (ESP32-WROOM-32)110.000đTìm mua
LS1Dây LED WS2812B 5V (30 bóng; loại 60 bóng/m cắt 0,5m)45.000đTìm mua
R1Điện trở 330Ω 1/4W (cam-cam-nâu)200đTìm mua
C1Tụ hóa 1000µF 10V (hoặc 16V) (1000u)3.000đTìm mua
Tổng (chưa gồm board hàn, dây, thiếc)158.200đ

Các bước làm

Code nạp cho mạch

Cài thư viện: Adafruit NeoPixel (Arduino IDE → Library Manager). Chọn board DOIT ESP32 DEVKIT V1.

/*
  Mạch Gọn — Dây LED RGB WS2812B điều khiển qua WiFi
  Board : ESP32 DevKit V1 (chọn "DOIT ESP32 DEVKIT V1")
  Thư viện: Adafruit NeoPixel
  Chân  : GPIO13 -> trở 330Ω -> DIN dây LED; dây LED cấp từ VIN (5V USB)
  Lần đầu: vào WiFi "MachGon-RGB" (mật khẩu machgon123) -> mở 192.168.4.1
*/
#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <Preferences.h>
#include <Adafruit_NeoPixel.h>

// ---- Cấu hình ----
const uint8_t  PIN_DATA = 13;
const uint16_t NUM_LEDS = 30;
const uint16_t MAX_MA   = 550;     // giới hạn tổng dòng dây LED (mA), chừa phần cho ESP32
const float    MA_PER_CH = 20.0;   // mA mỗi kênh màu ở mức 255
const char*    AP_SSID  = "MachGon-RGB";
const char*    HOSTNAME = "rgb";

Adafruit_NeoPixel strip(NUM_LEDS, PIN_DATA, NEO_GRB + NEO_KHZ800);
WebServer server(80);
Preferences cfg;

enum { FX_SOLID, FX_RAINBOW, FX_RAINBOW_RUN, FX_BREATHE, FX_COMET, FX_TWINKLE, FX_FIRE, FX_CHASE, FX_WIPE, FX_POLICE, FX_COUNT };
const char* FX_NAMES[FX_COUNT] = {"Màu tĩnh", "Cầu vồng", "Cầu vồng chạy", "Thở", "Sao băng", "Lấp lánh", "Lửa", "Đuổi nhau", "Quét màu", "Đèn còi"};

uint8_t  buf[NUM_LEDS][3];
uint8_t  colR = 255, colG = 80, colB = 0;
int      effect = FX_RAINBOW_RUN, bright = 60, speed = 50;
bool     power = true;
uint32_t frame = 0;
uint16_t lastMa = 0;

void setPx(int i, uint8_t r, uint8_t g, uint8_t b) { if (i >= 0 && i < NUM_LEDS) { buf[i][0] = r; buf[i][1] = g; buf[i][2] = b; } }
void fillAll(uint8_t r, uint8_t g, uint8_t b) { for (int i = 0; i < NUM_LEDS; i++) setPx(i, r, g, b); }
void wheel(uint8_t pos, uint8_t& r, uint8_t& g, uint8_t& b) {
  uint32_t c = Adafruit_NeoPixel::ColorHSV((uint16_t)pos * 256);
  r = c >> 16; g = c >> 8; b = c;
}
void fadeAll(uint8_t k) { for (int i = 0; i < NUM_LEDS; i++) for (int j = 0; j < 3; j++) buf[i][j] = (uint16_t)buf[i][j] * k / 256; }

void renderEffect() {
  uint8_t r, g, b;
  switch (effect) {
    case FX_SOLID: fillAll(colR, colG, colB); break;
    case FX_RAINBOW: wheel(frame, r, g, b); fillAll(r, g, b); break;
    case FX_RAINBOW_RUN: for (int i = 0; i < NUM_LEDS; i++) { wheel(frame * 2 + i * 256 / NUM_LEDS, r, g, b); setPx(i, r, g, b); } break;
    case FX_BREATHE: { float k = 0.05f + 0.95f * (0.5f - 0.5f * cosf(frame * 0.05f)); fillAll(colR * k, colG * k, colB * k); break; }
    case FX_COMET: { fadeAll(170); int p = frame % (NUM_LEDS + 8); setPx(p, colR, colG, colB); break; }
    case FX_TWINKLE: { fadeAll(220); if (esp_random() % 3 == 0) { wheel(esp_random(), r, g, b); setPx(esp_random() % NUM_LEDS, r, g, b); } break; }
    case FX_FIRE: for (int i = 0; i < NUM_LEDS; i++) { uint8_t h = 120 + esp_random() % 136; setPx(i, h, h / 3 + esp_random() % 20, 0); } break;
    case FX_CHASE: for (int i = 0; i < NUM_LEDS; i++) (i + frame) % 3 == 0 ? setPx(i, colR, colG, colB) : setPx(i, 0, 0, 0); break;
    case FX_WIPE: { int p = frame % (NUM_LEDS * 2); for (int i = 0; i < NUM_LEDS; i++) (p < NUM_LEDS ? i <= p : i > p - NUM_LEDS) ? setPx(i, colR, colG, colB) : setPx(i, 0, 0, 0); break; }
    case FX_POLICE: { bool a = (frame / 4) % 2; for (int i = 0; i < NUM_LEDS; i++) (i < NUM_LEDS / 2) == a ? setPx(i, 255, 0, 0) : setPx(i, 0, 0, 255); break; }
  }
}

// Áp độ sáng và giới hạn dòng rồi xuất ra dây LED
void showFrame() {
  float k = power ? bright / 100.0f : 0;
  float sum = 0;
  for (int i = 0; i < NUM_LEDS; i++) sum += buf[i][0] + buf[i][1] + buf[i][2];
  float ma = sum * k / 255.0f * MA_PER_CH + NUM_LEDS * 1.0f;   // ~1mA/bóng cho chip
  if (ma > MAX_MA) { k *= (MAX_MA - NUM_LEDS) / (ma - NUM_LEDS); ma = MAX_MA; }
  lastMa = (uint16_t)ma;
  for (int i = 0; i < NUM_LEDS; i++) strip.setPixelColor(i, buf[i][0] * k, buf[i][1] * k, buf[i][2] * k);
  strip.show();
}

const char PAGE[] PROGMEM = R"HTML(<!doctype html><html lang="vi"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1"><title>LED RGB WiFi</title>
<style>
:root{--b:#0f172a;--c:#1e293b;--a:#a855f7;--t:#e2e8f0;--m:#94a3b8}*{box-sizing:border-box}
body{margin:0;font-family:system-ui,sans-serif;background:var(--b);color:var(--t)}
header{padding:12px 14px;background:var(--c);display:flex;justify-content:space-between;align-items:center}
.pw{border:0;border-radius:24px;padding:8px 18px;font:inherit;font-weight:700;background:#334155;color:var(--t)}.pw.on{background:var(--a);color:#fff}
section{margin:14px;padding:14px;background:var(--c);border-radius:12px}h3{margin:0 0 10px;font-size:16px}
input[type=range]{width:100%;accent-color:var(--a);height:32px}input[type=color]{width:100%;height:56px;border:0;background:none}
.sw{display:flex;gap:8px;flex-wrap:wrap;margin-top:8px}.sw b{width:36px;height:36px;border-radius:50%;display:inline-block;border:2px solid #334155}
.fx{display:grid;grid-template-columns:repeat(2,1fr);gap:8px}.fx button{font:inherit;padding:11px;border-radius:10px;border:1px solid #334155;background:#0b1220;color:var(--t)}
.fx button.on{background:var(--a);border-color:var(--a)}small{color:var(--m)}
input:not([type]),input[type=password]{width:100%;font:inherit;padding:9px;margin:4px 0 8px;border-radius:8px;border:1px solid #475569;background:#0b1220;color:var(--t)}
.s{font:inherit;border:0;border-radius:8px;padding:9px 14px;background:var(--a);color:#fff;font-weight:600}
</style></head><body>
<header><b>🌈 LED RGB WiFi</b><button class="pw" id="pw">…</button></header>
<section><h3>Màu</h3><input type="color" id="col"><div class="sw" id="sw"></div></section>
<section><h3>Hiệu ứng</h3><div class="fx" id="fx"></div></section>
<section><h3>Độ sáng <small id="ma"></small></h3><input type="range" id="b" min="1" max="100"><h3>Tốc độ</h3><input type="range" id="sp" min="1" max="100"></section>
<section><h3>Cài đặt WiFi nhà</h3><small id="wst"></small><input id="ssid" placeholder="Tên WiFi"><input id="pass" type="password" placeholder="Mật khẩu"><button class="s" onclick="wifi()">Lưu & khởi động lại</button></section>
<script>
const $=s=>document.querySelector(s);let S={},tm;const SW=['#ff0000','#ff6a00','#ffd000','#00ff40','#00d0ff','#0040ff','#a000ff','#ff00a0','#ffffff'];
async function api(p,o){const r=await fetch(p,o);return r.json()}
function draw(s){S=s;$('#pw').textContent=s.power?'BẬT':'TẮT';$('#pw').className='pw'+(s.power?' on':'');$('#b').value=s.b;$('#sp').value=s.sp;$('#col').value=s.col;
 $('#ma').textContent='~'+s.ma+'mA (giới hạn '+s.max+'mA)';
 $('#fx').innerHTML=s.names.map((n,i)=>`<button class="${i==s.fx?'on':''}" onclick="set('fx=${i}')">${n}</button>`).join('');
 $('#wst').textContent=s.sta?('Đã nối "'+s.ssid+'" — IP '+s.ip+' (hoặc http://rgb.local)'):'Chưa nối WiFi nhà'}
async function set(q){draw(await api('/api/set?'+q,{method:'POST'}))}
$('#sw').innerHTML=SW.map(c=>`<b style="background:${c}" onclick="set('c=${c.slice(1)}')"></b>`).join('');
$('#col').oninput=e=>{clearTimeout(tm);tm=setTimeout(()=>set('c='+e.target.value.slice(1)),80)};
$('#b').oninput=e=>{clearTimeout(tm);tm=setTimeout(()=>set('b='+e.target.value),80)};$('#sp').onchange=e=>set('sp='+e.target.value);
$('#pw').onclick=()=>set('pw='+(S.power?0:1));
async function wifi(){await fetch('/api/wifi',{method:'POST',body:new URLSearchParams({ssid:$('#ssid').value,pass:$('#pass').value})});alert('Đã lưu, mạch khởi động lại. Nối lại WiFi nhà rồi mở http://rgb.local')}
api('/api/state').then(draw);setInterval(()=>api('/api/state').then(s=>$('#ma').textContent='~'+s.ma+'mA (giới hạn '+s.max+'mA)'),3000);
</script></body></html>)HTML";

String stateJson() {
  String names = "[";
  for (int i = 0; i < FX_COUNT; i++) { if (i) names += ','; names += '"'; names += FX_NAMES[i]; names += '"'; }
  names += "]";
  char col[8]; snprintf(col, sizeof(col), "#%02x%02x%02x", colR, colG, colB);
  bool sta = WiFi.status() == WL_CONNECTED;
  return String("{\"b\":") + bright + ",\"sp\":" + speed + ",\"fx\":" + effect + ",\"power\":" + (power ? "true" : "false") +
         ",\"col\":\"" + col + "\",\"ma\":" + lastMa + ",\"max\":" + MAX_MA + ",\"names\":" + names +
         ",\"sta\":" + (sta ? "true" : "false") + ",\"ip\":\"" + (sta ? WiFi.localIP().toString() : WiFi.softAPIP().toString()) +
         "\",\"ssid\":\"" + cfg.getString("ssid", "") + "\"}";
}

void saveState() {
  cfg.putInt("fx", effect); cfg.putInt("b", bright); cfg.putInt("sp", speed); cfg.putBool("pw", power);
  cfg.putUInt("col", ((uint32_t)colR << 16) | (colG << 8) | colB);
}

void setupWeb() {
  server.on("/", []() { server.send_P(200, "text/html; charset=utf-8", PAGE); });
  server.on("/api/state", HTTP_GET, []() { server.send(200, "application/json", stateJson()); });
  server.on("/api/set", []() {
    if (server.hasArg("fx")) effect = constrain(server.arg("fx").toInt(), 0, FX_COUNT - 1);
    if (server.hasArg("b"))  bright = constrain(server.arg("b").toInt(), 1, 100);
    if (server.hasArg("sp")) speed = constrain(server.arg("sp").toInt(), 1, 100);
    if (server.hasArg("pw")) power = server.arg("pw") == "1";
    if (server.hasArg("c")) { uint32_t c = strtoul(server.arg("c").c_str(), nullptr, 16); colR = c >> 16; colG = c >> 8; colB = c; }
    saveState();
    server.send(200, "application/json", stateJson());
  });
  server.on("/api/wifi", HTTP_POST, []() {
    cfg.putString("ssid", server.arg("ssid")); cfg.putString("pass", server.arg("pass"));
    server.send(200, "application/json", "{\"ok\":true}"); delay(500); ESP.restart();
  });
  server.onNotFound([]() { server.sendHeader("Location", "/"); server.send(302); });
  server.begin();
}

void setup() {
  Serial.begin(115200);
  strip.begin(); strip.clear(); strip.show();
  cfg.begin("cfg", false);
  effect = constrain(cfg.getInt("fx", FX_RAINBOW_RUN), 0, FX_COUNT - 1);
  bright = cfg.getInt("b", 60); speed = cfg.getInt("sp", 50); power = cfg.getBool("pw", true);
  uint32_t c = cfg.getUInt("col", 0xFF5000); colR = c >> 16; colG = c >> 8; colB = c;
  memset(buf, 0, sizeof(buf));

  WiFi.mode(WIFI_AP_STA);
  WiFi.softAP(AP_SSID, cfg.getString("appass", "machgon123").c_str());
  String ssid = cfg.getString("ssid", "");
  if (ssid.length()) {
    WiFi.begin(ssid.c_str(), cfg.getString("pass", "").c_str());
    uint32_t t = millis();
    while (WiFi.status() != WL_CONNECTED && millis() - t < 15000) delay(250);
  }
  if (MDNS.begin(HOSTNAME)) MDNS.addService("http", "tcp", 80);
  setupWeb();
  Serial.printf("San sang. AP %s IP %s\n", AP_SSID, WiFi.softAPIP().toString().c_str());
}

void loop() {
  server.handleClient();
  static uint32_t last = 0;
  uint32_t interval = map(speed, 1, 100, 120, 8);
  if (millis() - last >= interval) { last = millis(); frame++; renderEffect(); showFrame(); }
  static uint32_t lastCheck = 0;
  if (millis() - lastCheck > 30000) {
    lastCheck = millis();
    if (cfg.getString("ssid", "").length() && WiFi.status() != WL_CONNECTED) WiFi.reconnect();
  }
}

🔌 Cắm thử trên breadboard

Cắm nháp trên board cắm trước cho chắc, chạy đúng rồi hãy làm mạch in. Sơ đồ này do máy tự xếp từ chính netlist ở trên và đã đối chiếu lại: hai chân cùng một cột (cùng nửa board) là thông nhau, không có net nào bị chập.

+−+−551010151520202525JIHGFEDCBAU1 · ESP32 DevKit V1 (30 chân)13GNDVINLS1R1 330C1 1000u+
  • Mỗi cột 5 lỗ trong cùng một nửa board thông nhau; hai nửa cách nhau bởi rãnh giữa.
  • Hai đường kẻ dọc mép board là đường nguồn: hàng + (vạch đỏ) và hàng − (vạch xanh). Nhớ bắc dây nối đường nguồn trên với đường nguồn dưới nếu dùng cả hai nửa.
  • Màu dây trên hình trùng với màu trong bảng nối dây ở mục Sơ đồ, cắm theo màu cho dễ dò.

🧲 Mạch in để ủi (PCB 1 mặt)

Bản mạch in một mặt đồng, tự xếp linh kiện và tự đi dây, đã kiểm quy tắc khoảng cách và đối chiếu đúng 100% với sơ đồ nguyên lý ở trên. In tỉ lệ 1:1 rồi ủi lên tấm đồng.

Kích thước board55,9×58,4 mm37 lỗ khoan · mũi 0,8 / 0,9 / 1mm
Đi dây 1 mặt6 đoạn · 224,8 mmKhông cần cầu nhảy nào
Kiểm tra bản mạchĐạt DRC · khớp 100% netlistĐường ≥0,8mm, khe ≥0,5mm; mọi net đối chiếu lại từ hình học thật
Mặt đồng — đã lật gương sẵn để in rồi ủi úp xuống tấm đồng
Mặt linh kiện — vị trí, chiều cắm và tên từng chân
U1LS1R1C1EN363934353233252627141213GNDVIN232213211918517164215GND23V35VDINGND
Hướng dẫn ủi – ăn mòn – khoan – hàn (đầy đủ)

Hướng dẫn làm mạch in bằng phương pháp ủi

Mạch: Dây LED RGB WS2812 đổi màu, hiệu ứng bằng điện thoại (30 bóng)

Kích thước board: 55,9×58,4 mm · 37 lỗ khoan (mũi 0,8, 0,9 và 1 mm) · 6 đoạn mạch, tổng 224,8 mm · 0 cầu nhảy.

Quy tắc đã kiểm: đường mạch rộng 0,8mm (nguồn và GND 1mm), khe cách tối thiểu 0,5mm. Mạch in đã được máy đối chiếu đúng 100% với sơ đồ nguyên lý.

1. In đúng tỉ lệ 1:1

  • In trang "MẶT ĐỒNG" bằng máy in LASER, đặt tỉ lệ 100%, KHÔNG chọn "Fit to page" hay "Scale to fit".
  • Dùng thước trên bản in đo lại đoạn 100mm: phải đúng 100mm mới dùng được.
  • In trên giấy in ảnh bóng, giấy decal bóc, hoặc giấy tạp chí láng. Mực càng dày càng tốt.
  • Trang "MẶT ĐỒNG" đã được LẬT GƯƠNG sẵn: úp mặt có mực vào tấm đồng là đúng chiều.

2. Chuẩn bị tấm đồng

  • Cắt phíp đồng (board đồng một mặt) lớn hơn kích thước mạch mỗi bề khoảng 5mm.
  • Chà mặt đồng bằng giấy nhám mịn (P800–P1200) rồi rửa sạch, lau khô. Mặt đồng phải sáng, không vết dầu mỡ, không chạm tay vào.

3. Ủi mực sang đồng

  • Úp mặt có mực vào tấm đồng, dùng băng keo giấy dán cố định một cạnh.
  • Bàn ủi để mức nóng nhất (cotton/linen), KHÔNG dùng chế độ phun hơi nước.
  • Ủi đè mạnh và đều 3–5 phút, tập trung từng vùng nhỏ, chú ý các góc và mép.
  • Để nguội hẳn rồi ngâm cả tấm vào nước ấm 5–10 phút, bóc giấy thật nhẹ. Giấy decal thì bóc khi còn ấm.
  • Soi kỹ: chỗ nào mực bị thiếu hoặc đứt đường thì tô lại bằng bút lông dầu (bút CD) rồi để khô.

4. Ăn mòn (etching)

  • Pha dung dịch sắt(III) clorua FeCl3 theo hướng dẫn trên bao bì, hoặc dung dịch oxy già + muối ăn + a-xít; dùng hộp nhựa, KHÔNG dùng đồ kim loại.
  • Nhiệt độ dung dịch khoảng 40–50°C thì ăn nhanh hơn. Lắc nhẹ hộp cho đều, thường 10–25 phút là xong.
  • An toàn: đeo găng tay cao su và kính bảo hộ, làm ở nơi thoáng. Dung dịch ăn mòn làm hỏng quần áo, bồn rửa và gây bỏng rát da.
  • Xong thì rửa board bằng nước sạch, lau khô, dùng xăng thơm hoặc axeton lau sạch lớp mực.
  • Dung dịch đã dùng KHÔNG đổ xuống bồn rửa hay cống; đựng vào can kín và bỏ đúng nơi thu gom chất thải.

5. Khoan lỗ

  • Đối chiếu trang "BẢN VẼ LỖ KHOAN" để biết lỗ nào dùng mũi nào.
  • Dùng mũi khoan 0,8mm, 0,9mm và 1mm, khoan từ MẶT ĐỒNG vào để mũi tự định tâm giữa pad.
  • Khoan tốc độ cao, đẩy nhẹ; mũi nhỏ rất dễ gãy. Khoan xong chà nhẹ bavia hai mặt.

6. Hàn linh kiện

  • Linh kiện cắm ở MẶT KHÔNG CÓ ĐỒNG, hàn ở mặt đồng. Theo trang "MẶT LINH KIỆN" để đặt đúng vị trí và đúng chiều.
  • Hàn linh kiện thấp trước (điện trở, đi-ốt), cao sau (tụ, hàng chân, board vi điều khiển).
  • Chú ý chiều: LED chân dài là (+); đi-ốt có vạch trắng là K; tụ hoá có vạch là (−); transistor theo mặt chữ như nét vẽ đỏ.
  • Trước khi cấp điện: đo thông mạch giữa chân nguồn và GND — KHÔNG được kêu (không được chập).

Lưu ý với board vi điều khiển

  • Board nhái có thể khác thứ tự chân. Đối chiếu tên chân in cạnh từng lỗ trên trang "MẶT LINH KIỆN" với chữ in trên board thật TRƯỚC KHI ăn mòn. Nếu khác, đừng hàn mà báo lại để sửa bản vẽ.
Bản vẽ lỗ khoan
BẢN VẼ LỖ KHOAN (nhìn từ mặt linh kiện)mũi khoan 0,8mm — 2 lỗmũi khoan 0,9mm — 2 lỗmũi khoan 1mm — 33 lỗ

Đo kiểm bằng đồng hồ vạn năng

+5V và GND ở đầu dây LED khi đang sáng4,5 – 5,1V
GPIO13 và GND1 – 2V (đang truyền dữ liệu liên tục, đồng hồ đọc trung bình)

Sửa lỗi thường gặp

Dây LED không sáng bóng nào

Hay gặp nhất: hàn vào đầu DO thay vì DIN. Kiểm mũi tên. Kiểm GND chung.

Màu sai (đỏ ra xanh)

Dây của bạn là loại thứ tự RGB. Trong code đổi NEO_GRB thành NEO_RGB.

Nhấp nháy loạn, bóng đầu sáng sai màu

Dây tín hiệu quá dài hoặc thiếu trở 330Ω. Rút ngắn dây; nếu vẫn lỗi thêm IC chuyển mức 74AHCT125.

ESP32 tự khởi động lại

Cục sạc yếu. Dùng loại 5V 2A, hoặc giảm MAX_MA trong code.

⚠️ An toàn

  • Mạch chạy 5V, an toàn.
  • Muốn dùng dây dài (>60 bóng) phải có nguồn 5V riêng công suất lớn, không cấp qua USB.