Đèn LED nháy theo nhạc với ESP32: micro INMP441, FFT tự viết, dải WS2812B, bắt nhịp trống

Làm đèn LED nháy theo nhạc với ESP32: micro INMP441 thu tiếng, FFT 512 điểm chia 6 dải tần, bắt nhịp trống tính BPM, dải WS2812B với ba chế độ phổ, nhịp và VU.

Đèn LED nháy theo nhạc là dự án vui mắt mà lại học được nhiều thứ: thu âm bằng micro số, phân tích tần số bằng FFT, bắt nhịp trống và điều khiển dải LED địa chỉ. Bài này dùng micro INMP441 (I2S), dải 30 LED WS2812B và ESP32, với ba chế độ đổi bằng nút BOOT: phổ 6 dải, nháy theo nhịp trống và VU từ giữa ra. FFT viết ngay trong chương trình, WS2812B điều khiển bằng bộ RMT của ESP32, không cần thư viện ngoài.

1. Đèn nghe nhạc như thế nào

  1. Thu âm: INMP441 gửi mẫu âm thanh 24 bit qua I2S, 16.000 mẫu mỗi giây. Chương trình lấy từng khung 512 mẫu (32 ms).
  2. FFT (biến đổi Fourier nhanh) tách khung âm thanh thành 256 ô tần số, mỗi ô rộng 31,25 Hz, cho biết tần số nào mạnh, tần số nào yếu.
  3. Gom thành 6 dải: trầm (trống, bass), trung, cao (hi-hat, tiếng xì). Mỗi dải được chuẩn hóa về 0–100 % so với mức nền của chính nó, nên không cần chỉnh âm lượng.
  4. Bắt nhịp: khi năng lượng dải trầm tăng vọt so với trung bình gần đây thì coi là một tiếng trống.
  5. Vẽ lên LED theo chế độ đang chọn, khoảng 30 lần mỗi giây.

2. Chuẩn bị

3. Nối dây

Sơ đồ nối: INMP441 VDD 3V3, GND, L/R xuống GND, SCK D26, WS D25, SD D33; dải WS2812B DIN vào D13 qua 330 ohm, 5 V từ nguồn riêng, GND chung
Micro I2S và dải LED; dải LED dùng nguồn 5 V riêng.
  • Chân micro giống bài INMP441, chân dải LED giống bài WS2812B trong cùng loạt mô-đun, nên có thể thử từng phần bằng chương trình của hai bài đó trước.
  • Dải 30 LED sáng trắng hết cỡ có thể kéo khoảng 1,8 A, quá sức cổng USB của máy tính: cấp 5 V riêng cho dải, nối GND chung với ESP32, đặt tụ 1000 µF ở đầu dải. Chương trình tự giảm độ sáng khi dòng ước tính vượt 1,5 A.
  • Đặt micro cách loa vài chục cm, tránh đặt sát loa trầm làm mức âm luôn đầy.

4. FFT và 6 dải tần

Đồ thị phổ của một khung 32 ms theo thang tần số log từ 31 Hz tới 8000 Hz, có đỉnh ở dải trầm và ở khoảng 600 Hz của giai điệu; sáu dải tô màu 60–150, 150–300, 300–600, 600–1200, 1200–2400, 2400–4800 Hz
Phổ của một khung và 6 dải tần (đoạn nhạc tổng hợp).
  • Cửa sổ Hann: trước khi FFT, mỗi mẫu được nhân với một đường cong hình chuông để hai đầu khung về 0. Không có bước này, chỗ khung bị cắt ngang làm phổ bị "nhòe" sang các ô bên cạnh.
  • Dải rộng gấp đôi nhau (60–150, 150–300, 300–600 Hz…): tai người nghe cao độ theo tỉ lệ, mỗi lần gấp đôi tần số là lên một quãng tám. Chia đều theo Hz thì gần hết LED sẽ dành cho tiếng xì ở dải cao.
  • Chuẩn hóa tự động (chuanHoa()): mỗi dải có mức nền đi xuống nhanh khi yên tĩnh, đi lên rất chậm khi có tiếng, và một mức đỉnh giảm dần. Mức hiển thị là vị trí của dải giữa nền và đỉnh. Nhờ vậy nhạc nhỏ hay to đèn vẫn nhảy đủ.

5. Bắt nhịp trống

Bản đồ nhiệt mức 6 dải theo thời gian trong 8 giây; dải 60–150 Hz sáng đều mỗi 0,5 giây; hàng chấm đỏ là các nhịp chương trình bắt được, 16 nhịp, ước tính 120 nhịp mỗi phút
Mức 6 dải và các nhịp bắt được trên đoạn nhạc tổng hợp 120 nhịp mỗi phút.

batNhip() giữ trung bình trượt của năng lượng dải trầm. Một khung có năng lượng trầm lớn hơn 1,6 lần trung bình, cách nhịp trước ít nhất 250 ms, được tính là một nhịp. Khoảng cách giữa 8 nhịp gần nhất cho ra số nhịp mỗi phút (BPM). Cách này hợp với nhạc có trống rõ (pop, EDM, nhạc sàn); nhạc không lời, nhạc cổ điển ít trống thì chế độ nhịp sẽ ít nháy.

6. Chương trình

// PYWORLD.VN - Đèn LED nháy theo nhạc: micro INMP441 thu tiếng, ESP32 tự tính FFT 512 điểm, dải 30 LED WS2812B đổi theo phổ và nhịp
// PYWORLD.VN - INMP441: SCK GPIO26, WS GPIO25, SD GPIO33, L/R xuống GND; WS2812B: DIN GPIO13 qua 330 Ω, nguồn 5 V riêng, GND chung
// PYWORLD.VN - Nút BOOT đổi chế độ: phổ 6 dải, nháy theo nhịp trống, VU từ giữa ra; chỉ dùng thư viện có sẵn trong gói esp32

#include <ESP_I2S.h>

const int CHAN_SCK = 26, CHAN_WS = 25, CHAN_SD = 33;
const int CHAN_DIN = 13;
const int CHAN_NUT = 0;
const int SO_LED = 30;
const uint32_t DONG_NGUON_MA = 1500;
const uint8_t DO_SANG_TOI_DA = 110;
const uint32_t TAN_SO_MAU = 16000;
const int N = 512;                         // 32 ms mỗi khung, mỗi ô tần số rộng 31,25 Hz
const int SO_DAI = 6;
const float BIEN_DAI[SO_DAI + 1] = {60, 150, 300, 600, 1200, 2400, 4800};   // Hz
const char *TEN_CHE_DO[3] = {"pho 6 dai", "nhay theo nhip", "VU tu giua"};

I2SClass i2s;
int32_t dem[N * 2];
float thuc[N], ao[N], cuaSo[N];
float nen[SO_DAI], dinh[SO_DAI], hien[SO_DAI];
float trungBinhBass = 0, sangNhip = 0;
uint32_t lanNhip = 0, khoangNhip[8];
int soKhoang = 0, viTriKhoang = 0, cheDo = 0, kenh = 0, mauNhip = 0;
uint8_t mau[SO_LED][3];
rmt_data_t kyHieu[SO_LED * 24];
const rmt_data_t BIT_0 = {{4, 1, 8, 0}};
const rmt_data_t BIT_1 = {{8, 1, 4, 0}};

// ---- WS2812B qua RMT, có giới hạn dòng (như bài WS2812B) ----
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;
}

void hienThi() {
  uint8_t doSang = DO_SANG_TOI_DA;
  uint32_t dong = dongUocTinh(doSang);
  if (dong > DONG_NGUON_MA) doSang = (uint32_t)doSang * (DONG_NGUON_MA - SO_LED) / (dong - SO_LED);
  int k = 0;
  for (int i = 0; i < SO_LED; i++) {
    uint32_t grb = ((uint32_t)(mau[i][1] * doSang / 255) << 16) | ((uint32_t)(mau[i][0] * doSang / 255) << 8) | (mau[i][2] * doSang / 255);
    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);
}

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;
}

void banhXeMau(int i, uint8_t v, float sang) {
  uint8_t r, g, b;
  if (v < 85) { r = 255 - v * 3; g = v * 3; b = 0; }
  else if (v < 170) { r = 0; g = 255 - (v - 85) * 3; b = (v - 85) * 3; }
  else { r = (v - 170) * 3; g = 0; b = 255 - (v - 170) * 3; }
  datMau(i, r * sang, g * sang, b * sang);
}

// ---- FFT cơ số 2, tính tại chỗ ----
void fft(float *re, float *im, int n) {
  for (int i = 1, j = 0; i < n; i++) {     // sắp lại theo thứ tự đảo bit
    int bit = n >> 1;
    for (; j & bit; bit >>= 1) j ^= bit;
    j ^= bit;
    if (i < j) {
      float t = re[i];
      re[i] = re[j];
      re[j] = t;
      t = im[i];
      im[i] = im[j];
      im[j] = t;
    }
  }
  for (int len = 2; len <= n; len <<= 1) {  // ghép các phép biến đổi nhỏ thành lớn (cánh bướm)
    float wr = cosf(-2 * PI / len), wi = sinf(-2 * PI / len);
    for (int i = 0; i < n; i += len) {
      float cr = 1, ci = 0;
      for (int k = 0; k < len / 2; k++) {
        int a = i + k, b = a + len / 2;
        float tr = re[b] * cr - im[b] * ci, ti = re[b] * ci + im[b] * cr;
        re[b] = re[a] - tr;
        im[b] = im[a] - ti;
        re[a] += tr;
        im[a] += ti;
        float t = cr * wr - ci * wi;
        ci = cr * wi + ci * wr;
        cr = t;
      }
    }
  }
}

// ---- Đọc micro, tính năng lượng từng dải ----
bool docKhung(float muc[SO_DAI], float &bass) {
  size_t n = i2s.readBytes((char *)dem, sizeof(dem));
  if (n < sizeof(dem)) return false;
  float tb = 0;
  for (int i = 0; i < N; i++) {
    thuc[i] = (dem[2 * i + kenh] >> 8) / 8388608.0f;   // 24 bit có dấu, đổi về -1..1
    tb += thuc[i];
  }
  tb /= N;
  for (int i = 0; i < N; i++) {
    thuc[i] = (thuc[i] - tb) * cuaSo[i];   // bỏ phần một chiều, nhân cửa sổ Hann
    ao[i] = 0;
  }
  fft(thuc, ao, N);
  for (int d = 0; d < SO_DAI; d++) {
    int o1 = BIEN_DAI[d] * N / TAN_SO_MAU, o2 = BIEN_DAI[d + 1] * N / TAN_SO_MAU;
    double tong = 0;
    for (int o = o1; o < o2; o++) tong += thuc[o] * thuc[o] + ao[o] * ao[o];
    muc[d] = 10 * log10f(tong + 1e-12f);   // dB tương đối
    if (d == 0) bass = tong;
  }
  return true;
}

// Mức nền theo dõi chậm, đỉnh giảm dần: đổi dB của mỗi dải ra 0..1 mà không cần chỉnh âm lượng.
void chuanHoa(const float muc[SO_DAI], float ra[SO_DAI]) {
  for (int d = 0; d < SO_DAI; d++) {
    nen[d] += (muc[d] - nen[d]) * (muc[d] < nen[d] ? 0.1f : 0.002f);
    dinh[d] = max(muc[d], dinh[d] - 0.15f);
    float duoi = nen[d] + 6, khoang = max(12.0f, dinh[d] - duoi);
    ra[d] = constrain((muc[d] - duoi) / khoang, 0.0f, 1.0f);
  }
}

// Nhịp trống: năng lượng dải trầm vượt 1,6 lần mức trung bình gần đây, cách nhịp trước ít nhất 250 ms.
bool batNhip(float bass, uint32_t bay) {
  bool co = trungBinhBass > 0 && bass > 1.6f * trungBinhBass && bay - lanNhip > 250;
  trungBinhBass += (bass - trungBinhBass) * 0.05f;
  if (!co) return false;
  if (lanNhip && bay - lanNhip < 2000) {
    khoangNhip[viTriKhoang] = bay - lanNhip;
    viTriKhoang = (viTriKhoang + 1) % 8;
    if (soKhoang < 8) soKhoang++;
  }
  lanNhip = bay;
  return true;
}

int nhipMoiPhut() {
  if (soKhoang < 4) return 0;
  uint32_t tong = 0;
  for (int i = 0; i < soKhoang; i++) tong += khoangNhip[i];
  return 60000 * soKhoang / tong;
}

void veLed(const float lv[SO_DAI], bool nhip) {
  if (cheDo == 0) {                        // phổ: 6 cột, mỗi cột 5 LED
    for (int d = 0; d < SO_DAI; d++) {
      hien[d] = max(lv[d], hien[d] * 0.85f);   // lên ngay, xuống từ từ
      int cao = lroundf(hien[d] * 5);
      for (int k = 0; k < 5; k++) {
        if (k < cao) banhXeMau(d * 5 + k, d * 40, 1.0f);
        else datMau(d * 5 + k, 0, 0, 0);
      }
    }
  } else if (cheDo == 1) {                 // nhịp: cả dải sáng lên rồi tắt dần, mỗi nhịp đổi màu
    if (nhip) {
      sangNhip = 1;
      mauNhip = (mauNhip + 37) % 256;
    }
    for (int i = 0; i < SO_LED; i++) banhXeMau(i, mauNhip, sangNhip);
    sangNhip *= 0.8f;
  } else {                                 // VU: tổng mức sáng từ giữa ra hai đầu, xanh lá tới đỏ
    float tong = 0;
    for (int d = 0; d < SO_DAI; d++) tong += lv[d];
    hien[0] = max(tong / SO_DAI, hien[0] * 0.9f);
    int nua = lroundf(hien[0] * SO_LED / 2);
    for (int k = 0; k < SO_LED / 2; k++) {
      uint8_t v = 85 - k * 85 / (SO_LED / 2);  // giữa xanh lá, ra ngoài chuyển dần sang đỏ
      if (k < nua) {
        banhXeMau(SO_LED / 2 + k, v, 1.0f);
        banhXeMau(SO_LED / 2 - 1 - k, v, 1.0f);
      } else {
        datMau(SO_LED / 2 + k, 0, 0, 0);
        datMau(SO_LED / 2 - 1 - k, 0, 0, 0);
      }
    }
  }
  hienThi();
}

void kiemTraNut() {
  static bool truoc = HIGH;
  static uint32_t lucBam = 0;
  bool doc = digitalRead(CHAN_NUT);
  if (doc == LOW && truoc == HIGH && millis() - lucBam > 300) {
    lucBam = millis();
    cheDo = (cheDo + 1) % 3;
    for (int d = 0; d < SO_DAI; d++) hien[d] = 0;
    Serial.printf("Che do %d: %s\n", cheDo, TEN_CHE_DO[cheDo]);
  }
  truoc = doc;
}

void setup() {
  Serial.begin(115200);
  pinMode(CHAN_NUT, INPUT_PULLUP);
  for (int i = 0; i < N; i++) cuaSo[i] = 0.5f - 0.5f * cosf(2 * PI * i / (N - 1));
  for (int d = 0; d < SO_DAI; d++) {
    nen[d] = -60;
    dinh[d] = -30;
  }
  if (!rmtInit(CHAN_DIN, RMT_TX_MODE, RMT_MEM_NUM_BLOCKS_1, 10000000)) Serial.println("Loi khoi dong RMT");
  i2s.setPins(CHAN_SCK, CHAN_WS, -1, CHAN_SD);
  if (!i2s.begin(I2S_MODE_STD, TAN_SO_MAU, I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_STEREO)) {
    Serial.println("Khong khoi dong duoc I2S");
    while (true) delay(1000);
  }
  Serial.printf("Den LED theo nhac: %d LED, che do %s. Bam BOOT de doi che do\n", SO_LED, TEN_CHE_DO[cheDo]);
}

void loop() {
  kiemTraNut();
  float muc[SO_DAI], lv[SO_DAI], bass;
  if (!docKhung(muc, bass)) return;
  chuanHoa(muc, lv);
  bool nhip = batNhip(bass, millis());
  veLed(lv, nhip);
  static uint32_t lanIn = 0;
  if (millis() - lanIn >= 2000) {
    lanIn = millis();
    Serial.print("Muc 6 dai (%):");
    for (int d = 0; d < SO_DAI; d++) Serial.printf(" %3.0f", lv[d] * 100);
    int bpm = nhipMoiPhut();
    if (bpm) Serial.printf(" | nhip %d BPM\n", bpm);
    else Serial.println(" | chua bat duoc nhip");
  }
}

Giải thích

  • fft() là FFT cơ số 2 kinh điển: sắp lại mẫu theo thứ tự đảo bit, rồi ghép dần các phép biến đổi 2, 4, 8… 512 điểm bằng phép "cánh bướm". Với 512 điểm, ESP32 tính xong trong khoảng vài mili giây, còn rất nhiều thời gian trong mỗi khung 32 ms.
  • docKhung() đọc 512 mẫu, bỏ phần một chiều, nhân cửa sổ, FFT, rồi cộng bình phương biên độ các ô trong từng dải và đổi ra dB.
  • veLed(): chế độ phổ chia 30 LED thành 6 cột × 5 LED, cột lên ngay khi có tiếng và xuống từ từ (nhân 0,85 mỗi khung); chế độ nhịp làm cả dải sáng một màu rồi tắt dần, mỗi nhịp đổi màu; chế độ VU sáng từ giữa ra hai đầu theo tổng mức của 6 dải.
  • hienThi() giữ nguyên cách của bài WS2812B: mỗi bit một ký hiệu RMT, tự hạ độ sáng chung nếu dòng ước tính vượt DONG_NGUON_MA.

7. Kết quả

Ba nhóm, mỗi nhóm 8 hàng chấm màu là trạng thái 30 LED qua 8 khung liên tiếp: chế độ phổ có 6 cột màu đỏ, vàng, xanh lá, xanh ngọc, xanh dương, tím cao thấp khác nhau; chế độ nhịp cả dải sáng một màu rồi tối dần; chế độ VU sáng từ giữa ra
Dải LED qua 8 khung liên tiếp ở ba chế độ (màu tính đúng như chương trình).
Serial Monitor: mỗi 2 giây in mức 6 dải theo phần trăm và số nhịp mỗi phút, khoảng 120 BPM; bấm BOOT đổi sang chế độ nháy theo nhịp
Serial Monitor khi phát đoạn nhạc tổng hợp.

Các hình trong bài tính từ một đoạn nhạc tổng hợp bằng máy tính (trống trầm 120 nhịp mỗi phút, hi-hat, giai điệu) để kết quả lặp lại được; với nhạc thật, phổ và nhịp sẽ thay đổi theo bài hát và độ vang của phòng.

Lưu ý: ánh sáng chớp nhanh, mạnh có thể gây khó chịu, và với người có bệnh động kinh nhạy cảm ánh sáng thì có thể nguy hiểm. Giữ độ sáng vừa phải (DO_SANG_TOI_DA), không để chế độ nhịp nháy chớp liên tục trong phòng tối.

8. Lỗi hay gặp

Hiện tượngNguyên nhân thường gặpCách xử lý
LED không sáng hoặc màu loạnThiếu GND chung, dây DIN dài, thiếu điện trở 330 ΩNối GND chung; dây DIN ngắn; với dải dài thêm mạch chuyển mức 3,3 V lên 5 V
Đèn đứng yên dù đang mở nhạcSai dây I2S, chân L/R để trốngKiểm tra SCK D26, WS D25, SD D33; L/R nối GND
Mọi cột luôn đầyMicro sát loa, nhạc quá toĐặt micro xa loa hơn; chờ vài giây cho mức đỉnh tự thích nghi
Chế độ nhịp nháy loạnNhạc nhiều tiếng trầm liên tục (bass kéo dài)Tăng hệ số 1,6 trong batNhip() lên 2,0
ESP32 khởi động lại khi LED sáng trắngNguồn 5 V yếu, dải LED lấy điện qua ESP32Nguồn riêng đủ dòng cho dải; giảm DO_SANG_TOI_DA

9. Hướng mở rộng

  • Lấy tín hiệu thẳng từ cổng tai nghe vào chân ADC (qua tụ và cầu phân áp đưa về giữa 0–3,3 V) thay cho micro, để đèn không bắt tiếng ồn trong phòng.
  • Dải dài hơn: tăng SO_LED, chia lại số LED mỗi cột, nhớ tính lại nguồn.
  • Đồng bộ nhiều dải đèn bằng ESP-NOW: một ESP32 nghe nhạc, gửi mức 6 dải cho các ESP32 khác.

10. Xem thêm


Tin tức liên quan

Hướng dẫn dùng màn hình OLED SSD1306 với ESP8266
Hướng dẫn dùng màn hình OLED SSD1306 với ESP8266

Hướng dẫn nối OLED SSD1306 với ESP8266, quét địa chỉ I2C, cài thư viện và hiển thị thời gian bằng mã Arduino mẫu.

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.

Cảm biến nhịp tim MAX30102 với ESP32: tín hiệu PPG, đếm nhịp không cần thư viện và giới hạn của SpO2
Cảm biến nhịp tim MAX30102 với ESP32: tín hiệu PPG, đếm nhịp không cần thư viện và giới hạn của SpO2

Nguyên lý PPG, nối MAX30102 với ESP32, cấu hình thanh ghi, lọc tín hiệu và đếm nhịp tim không cần thư viện, tỉ số R của SpO2 và vì sao kết quả tự làm chỉ để học.

Web điều khiển ESP32 cập nhật tức thời trên nhiều điện thoại: hỏi định kỳ bằng fetch hay WebSocket
Web điều khiển ESP32 cập nhật tức thời trên nhiều điện thoại: hỏi định kỳ bằng fetch hay WebSocket

Làm trang web trên ESP32 tự cập nhật trạng thái relay trên mọi điện thoại đang mở bằng fetch() và JSON, chỉ dùng WebServer có sẵn, so sánh với WebSocket.

Hướng dẫn dùng màn hình LCD 2004 I2C với ESP32
Hướng dẫn dùng màn hình LCD 2004 I2C với ESP32

Hướng dẫn nối màn hình LCD 2004 I2C (mạch PCF8574) với ESP32 DevKit V1, quét địa chỉ I2C, hiển thị 4 dòng chữ, tạo ký tự riêng, làm bộ đếm thời gian và đổi sang LCD 1602.

Đo dòng điện với ESP32: INA219 hay ACS712, đo dòng, điện áp, công suất và mAh không cần thư viện
Đo dòng điện với ESP32: INA219 hay ACS712, đo dòng, điện áp, công suất và mAh không cần thư viện

So sánh INA219 (điện trở shunt, I2C) và ACS712 (cảm biến Hall), nối với ESP32 có cầu phân áp, chương trình đo dòng, điện áp, công suất, điện lượng mAh không cần thư viện, kèm bảng lỗi.

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.

Mạch hạ áp LM2596 và MP1584: cách chọn, chỉnh điện áp và cấp nguồn ESP32
Mạch hạ áp LM2596 và MP1584: cách chọn, chỉnh điện áp và cấp nguồn ESP32

So sánh mạch hạ áp LM2596 và MP1584, cách chỉnh điện áp ra bằng đồng hồ vạn năng trước khi nối tải, cấp nguồn 5 V cho ESP32 DevKit V1 từ nguồn 12 V và xử lý lỗi thường gặp.


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