Đó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.

Mạch chạy bình thường, nhưng cứ mỗi lần relay đóng hoặc nhả thì ESP32 khởi động lại, màn hình đứng hình, hoặc máy tính mất cổng COM. Đây là lỗi rất hay gặp khi ghép ESP32, Arduino với module relay. Nguyên nhân gần như luôn nằm ở nguồn, ở tải mà relay đóng cắt, hoặc ở chân điều khiển được chọn. Bài này đi qua từng nguyên nhân, cách khắc phục, và kèm một chương trình đo xem điện áp nguồn sụt bao nhiêu mỗi lần relay chuyển.

1. Nhận biết: reset do sụt áp hay do nhiễu

Mở Serial Monitor ở tốc độ 115200 và để relay chuyển vài lần:

  • Thấy dòng Brownout detector was triggered rồi ESP32 in lại thông báo khởi động: điện áp nguồn đã tụt dưới ngưỡng an toàn của chip (sụt áp, tiếng Anh là brownout). Dòng rst: ngay sau đó thường ghi SW_CPU_RESET, vì sau khi phát hiện sụt áp chương trình hệ thống tự gọi khởi động lại.
  • ESP32 khởi động lại mà không có dòng trên, hoặc không khởi động lại nhưng treo (màn hình OLED hiện rác, cảm biến I2C không trả lời, Wi-Fi rớt): thường là nhiễu từ tải hoặc từ cuộn relay lọt vào dây tín hiệu.
  • Lỗi chỉ xảy ra khi relay nhả một quạt, máy bơm, động cơ: gần như chắc chắn là hồ quang ở tiếp điểm.
  • Cắm module relay vào là ESP32 không khởi động, rút ra thì chạy: chân điều khiển rơi vào chân khởi động.

2. Bốn nguyên nhân thường gặp

Bốn nguyên nhân relay làm ESP32 reset: sụt áp khi cuộn relay hút, xung ngược của cuộn dây, hồ quang ở tiếp điểm, chân khởi động và mức lúc bật nguồn
Hai nguyên nhân đầu nằm ở nguồn, hai nguyên nhân sau nằm ở tải và chương trình (hình minh họa).

Sụt áp khi cuộn relay hút. Một relay 5 V loại thường gặp trên module hút khoảng 70–90 mA. Module 4 kênh đóng cả bốn relay là khoảng 300–360 mA, cộng thêm dòng của ESP32 lúc phát Wi-Fi. Nếu tất cả lấy chung một cổng USB máy tính qua cáp dài, điện áp tụt ngay lúc relay đóng. Bài cấp nguồn cho ESP32 trong cùng đợt này giải thích kỹ ngưỡng sụt áp và cách chọn nguồn.

Xung ngược của cuộn dây. Cuộn relay là cuộn cảm: khi bị ngắt dòng, nó sinh xung điện áp ngược rất cao. Module relay bán sẵn đã có diode dập song song với cuộn dây. Nếu tự lắp relay rời với transistor mà quên diode, xung này làm hỏng transistor và gây reset. Cách lắp diode xem bài dùng transistor làm công tắc.

Hồ quang ở tiếp điểm. Khi relay ngắt một tải cảm (quạt, máy bơm, động cơ, chấn lưu đèn, cuộn hút contactor), dòng điện không tắt ngay mà phóng thành tia lửa giữa hai tiếp điểm. Tia lửa sinh nhiễu cao tần lan theo dây điện và qua không khí vào dây tín hiệu của ESP32. Lỗi này không sửa được bằng nguồn khỏe hơn.

Chân khởi động và mức lúc bật nguồn. GPIO 0, 2, 12, 15 quyết định chế độ khởi động của ESP32. Module relay nối vào các chân này có thể kéo chân sang mức sai, ví dụ GPIO 12 bị kéo lên mức cao lúc khởi động làm ESP32 chọn sai điện áp bộ nhớ flash và không chạy được. Ngoài ra, từ lúc cấp nguồn tới khi chương trình khai báo chân là ngõ ra, chân điều khiển đang thả nổi: module kích mức thấp có thể đóng relay thoáng qua.

3. Tách nguồn cho cuộn relay và đi dây đúng

So sánh bố trí dễ reset và ổn định: relay và ESP32 chung nguồn USB yếu, dây tải 220 V đi sát ESP32; so với nguồn 5 V riêng cho cuộn relay qua JD-VCC, tụ 1000 µF sát ESP32
Bố trí dễ reset và bố trí ổn định (hình minh họa).
  • Module 2, 4 kênh có jumper JD-VCC: tháo jumper, cấp 5 V từ một nguồn riêng vào chân JD-VCC và GND bên cạnh; chân VCC ở hàng chân điều khiển nối 3V3 của ESP32, IN nối GPIO. Như vậy dòng cuộn relay không đi qua mạch ESP32 nữa, ESP32 chỉ cấp dòng nhỏ cho đèn opto. Cách hoạt động của jumper này giải thích trong bài module relay 1, 2, 4 kênh cùng đợt.
  • Module 1 kênh (không có JD-VCC): cấp DC+ và DC− của module từ nguồn 5 V riêng, nối chung GND với ESP32; hoặc ít nhất dùng củ sạc 5 V từ 1 A trở lên với cáp ngắn thay cho cổng USB máy tính.
  • Nguồn riêng có thể là adapter 5 V, hoặc adapter 9–12 V qua mạch nguồn MP1584 hay mạch hạ áp LM2596 chỉnh 5 V. Cách chỉnh xem bài mạch hạ áp LM2596 và MP1584.
  • Tụ dự trữ: tụ hóa 470–1000 µF (10 V trở lên) giữa VIN và GND, càng sát ESP32 càng tốt, thêm tụ gốm 100 nF song song. Tụ đỡ được những lần sụt áp ngắn, không thay được nguồn đủ dòng.
  • Đi dây: dây tải 220 V và dây động cơ đi riêng, cách xa dây tín hiệu, không bó chung. Dây IN, I2C, cảm biến càng ngắn càng tốt. Dòng tải không được chạy qua dây GND của ESP32: tải lấy GND riêng về thẳng nguồn của nó.

4. Mạch dập hồ quang cho tải cảm

Mạch dập hồ quang cho relay: tải 220 V xoay chiều dùng điện trở 100 Ω nối tiếp tụ X2 0,1 µF song song tiếp điểm COM NO; tải một chiều 12 V dùng diode song song tải, vạch diode về phía cực dương
RC cho tải xoay chiều, diode cho tải một chiều (hình minh họa, giá trị tham khảo).

Tải xoay chiều 220 V (quạt, máy bơm nhỏ): dùng mạch RC gồm điện trở khoảng 100 Ω 0,5 W nối tiếp tụ 0,1 µF loại X2 275 V AC. Có hai chỗ đặt:

  • Song song với hai tiếp điểm COM, NO: bảo vệ tiếp điểm tốt nhất. Khi relay nhả vẫn có dòng rò nhỏ qua tụ, tính ra khoảng 7 mA ở 220 V 50 Hz, đủ làm một số đèn LED sáng mờ.
  • Song song ngay hai đầu tải cảm (cuộn quạt, động cơ): không có dòng rò khi relay nhả, vẫn dập được phần lớn nhiễu.

Có thể thêm varistor 275 V AC song song tải để chặn xung áp. Tụ thường (không phải loại X2) không được dùng cho điện lưới. Mọi thao tác với mạch 220 V phải cắt điện trước, đặt cầu chì cho tải; cách chọn cầu chì và relay xem bài công tắc, nút nhấn, cầu chì và relay.

Tải một chiều (động cơ 12 V, van điện từ, cuộn hút): diode song song với tải, vạch diode về phía cực dương. Diode chọn loại chịu dòng bằng dòng tải trở lên, ví dụ 1N4007 cho tải dưới 1 A, loại 3–5 A cho động cơ lớn hơn. Động cơ có chổi than thì thêm tụ gốm 100 nF hàn ngay hai cực động cơ. Cách diode bảo vệ hoạt động xem bài diode: chỉnh lưu, chống cắm ngược nguồn và diode bảo vệ.

5. Chọn chân và khởi tạo chân đúng thứ tự

  • Tránh GPIO 0, 2, 12, 15 (chân khởi động), GPIO 1, 3 (TX, RX, có xung lúc khởi động), GPIO 6–11 (nối bộ nhớ flash), GPIO 34–39 (chỉ là ngõ vào).
  • Nên dùng GPIO 16, 17, 18, 19, 21, 22, 23, 25, 26, 27, 32, 33 để điều khiển relay. Chi tiết từng chân xem bài sơ đồ chân ESP32 DevKit V1.
  • Ghi mức "tắt" trước khi chân thành ngõ ra. Với gói esp32 bản 3.x, lệnh digitalWrite() chỉ có tác dụng khi chân đã được khai báo bằng pinMode(). Thứ tự an toàn: khai báo chân là ngõ vào, ghi mức tắt (HIGH với module kích mức thấp), rồi mới chuyển chân thành ngõ ra. Chương trình ở mục 6 làm đúng như vậy.
  • Không tắt bộ phát hiện sụt áp để "chữa" lỗi reset. Cách đó chỉ giấu lỗi: chip chạy dưới điện áp an toàn có thể tính sai, ghi hỏng dữ liệu trong flash. Sửa nguồn mới là cách đúng.

6. Thí nghiệm: đo sụt áp mỗi lần relay chuyển

Chương trình dưới đây bật tắt relay mỗi 3 giây và đo điện áp chân VIN liên tục trong 200 ms ngay sau mỗi lần chuyển, in ra mức thấp nhất. Chương trình còn đếm số lần ESP32 khởi động lại vì sụt áp, để thấy rõ nguồn đang dùng có đủ khỏe hay không.

Chuẩn bị: ESP32 DevKit V1 30 chân, module relay 5 V (1, 2 hoặc 4 kênh), hai điện trở 100 kΩ, breadboard, dây cắm. Muốn so sánh với nguồn riêng thì cần thêm một nguồn 5 V. Thí nghiệm không cần nối tải: chỉ nghe tiếng relay đóng nhả.

Sơ đồ thí nghiệm: module relay chân IN1 nối D27, VCC nối VIN, GND nối GND của ESP32; cầu phân áp hai điện trở 100 kΩ từ VIN xuống GND, điểm giữa vào D34
Sơ đồ thí nghiệm; nhãn cùng tên là cùng một dây (hình minh họa).
TừNối tớiGhi chú
Module relay GNDESP32 GND
Module relay IN1ESP32 D27Module 1 kênh: đặt jumper mức kích ở L
Module relay VCCESP32 VINLần đo đầu: giữ jumper JD-VCC
R1 100 kΩVIN → điểm giữaCầu phân áp đo VIN
R2 100 kΩĐiểm giữa → GND
Điểm giữa cầuESP32 D34D34 chỉ là ngõ vào, dùng được khi bật Wi-Fi

Cầu phân áp chia đôi điện áp VIN (khoảng 4,7 V khi cấp qua USB) xuống dưới 3,3 V cho chân đo. Chương trình nhân kết quả với 2 để ra điện áp VIN.

// PYWORLD.VN - Thí nghiệm relay làm ESP32 khởi động lại: bật tắt relay định kỳ và đo sụt áp chân VIN
// PYWORLD.VN - Relay kích mức thấp nối D27; cầu phân áp 100 kΩ / 100 kΩ từ VIN vào D34 như bài cấp nguồn ESP32
// PYWORLD.VN - In lý do khởi động, số lần reset do sụt áp và mức VIN thấp nhất ngay sau mỗi lần relay chuyển

#include "esp_system.h"
#include "esp_attr.h"

const int RELAY_PIN = 27;
const bool RELAY_KICH_MUC_THAP = true;     // module relay 2, 4 kênh: IN = LOW là relay đóng
const int CHAN_DO_VIN = 34;                // điểm giữa cầu phân áp 100 kΩ / 100 kΩ từ VIN xuống GND
const uint32_t HE_SO_CAU = 2;              // VIN = điện áp tại chân đo × 2
const uint32_t CHU_KY_MS = 3000;           // đổi trạng thái relay mỗi 3 giây
const uint32_t THOI_GIAN_DO_MS = 200;      // đo liên tục 200 ms ngay sau mỗi lần relay chuyển

// Vùng nhớ RTC không khởi tạo: giữ giá trị qua reset mềm và reset do sụt áp, mất khi rút nguồn
RTC_NOINIT_ATTR uint32_t danhDau;
RTC_NOINIT_ATTR uint32_t soLanKhoiDong;
RTC_NOINIT_ATTR uint32_t soLanBrownout;
const uint32_t DANH_DAU_HOP_LE = 0x50595752;

bool relayDangBat = false;

void datRelay(bool bat) {
  relayDangBat = bat;
  if (RELAY_KICH_MUC_THAP) {
    digitalWrite(RELAY_PIN, bat ? LOW : HIGH);
  } else {
    digitalWrite(RELAY_PIN, bat ? HIGH : LOW);
  }
}

// Đo VIN (mV): trung bình 16 lần đo
uint32_t doVinTrungBinh() {
  uint32_t tong = 0;
  for (int i = 0; i < 16; i++) {
    tong += analogReadMilliVolts(CHAN_DO_VIN);
  }
  return tong / 16 * HE_SO_CAU;
}

void setup() {
  // Giữ relay nhả khi khởi động: khai báo chân là ngõ vào, ghi sẵn mức "tắt" rồi mới chuyển thành ngõ ra
  // (gói esp32 3.x chỉ nhận digitalWrite() khi chân đã được khai báo bằng pinMode())
  pinMode(RELAY_PIN, INPUT);
  digitalWrite(RELAY_PIN, RELAY_KICH_MUC_THAP ? HIGH : LOW);
  pinMode(RELAY_PIN, OUTPUT);
  datRelay(false);

  Serial.begin(115200);
  delay(300);

  esp_reset_reason_t lyDo = esp_reset_reason();
  if (danhDau != DANH_DAU_HOP_LE || lyDo == ESP_RST_POWERON) {
    danhDau = DANH_DAU_HOP_LE;
    soLanKhoiDong = 0;
    soLanBrownout = 0;
  }
  soLanKhoiDong++;
  if (lyDo == ESP_RST_BROWNOUT) {
    soLanBrownout++;
  }

  Serial.println();
  Serial.printf("Khoi dong lan %u | ly do: %s\n", (unsigned)soLanKhoiDong,
                lyDo == ESP_RST_BROWNOUT ? "SUT AP (brownout)" : (lyDo == ESP_RST_POWERON ? "cap nguon" : "khac"));
  Serial.printf("So lan reset do sut ap: %u\n", (unsigned)soLanBrownout);
}

void loop() {
  static uint32_t lanTruoc = 0;
  if (millis() - lanTruoc < CHU_KY_MS) {
    return;
  }
  lanTruoc = millis();

  uint32_t vinTruoc = doVinTrungBinh();
  datRelay(!relayDangBat);

  // Ngay sau khi relay chuyển, đo liên tục để bắt mức VIN thấp nhất
  uint32_t thapNhat = vinTruoc;
  uint32_t batDau = millis();
  uint32_t dem = 0;
  while (millis() - batDau < THOI_GIAN_DO_MS) {
    uint32_t v = analogReadMilliVolts(CHAN_DO_VIN) * HE_SO_CAU;
    if (v < thapNhat) {
      thapNhat = v;
    }
    if (++dem % 50 == 0) {
      delay(1);  // nhường CPU cho các tác vụ khác
    }
  }

  Serial.printf("Relay %s | VIN truoc %u mV, thap nhat %u mV, sut %u mV\n", relayDangBat ? "BAT" : "TAT",
                (unsigned)vinTruoc, (unsigned)thapNhat, (unsigned)(vinTruoc - thapNhat));
}

Các điểm chính của chương trình:

  • RELAY_KICH_MUC_THAP: để true với module kích mức thấp; module kích mức cao thì đổi thành false.
  • Ba biến khai báo RTC_NOINIT_ATTR nằm trong vùng nhớ RTC không bị xóa khi ESP32 khởi động lại, nên đếm được số lần reset; rút nguồn thì mất. esp_reset_reason() cho biết lý do khởi động gần nhất, trả về ESP_RST_BROWNOUT khi do sụt áp.
  • analogReadMilliVolts() trả về điện áp tại chân đo theo mV, đã hiệu chỉnh theo từng chip. Mỗi lần đọc mất vài chục micro giây, nên chương trình bắt được độ sụt kéo dài; xung nhiễu rất ngắn (vài micro giây) phải đo bằng máy hiện sóng.

Cách đo và so sánh:

  1. Cắm ESP32 vào cổng USB máy tính, để relay lấy nguồn từ VIN như sơ đồ, xem 10–20 dòng kết quả.
  2. Rút nguồn. Tháo jumper JD-VCC, cấp 5 V từ nguồn riêng vào JD-VCC và GND bên cạnh, chuyển VCC của module sang 3V3 (module 1 kênh: cấp DC+ từ nguồn riêng, nối chung GND). Cấp nguồn lại và so sánh.
  3. Thử thêm tụ 470–1000 µF giữa VIN và GND ở lần đo thứ nhất để thấy mức sụt giảm bao nhiêu.
Serial Monitor của chương trình đo sụt áp relay: chung nguồn VIN sụt khoảng 446 mV khi relay bật và ESP32 đã khởi động lại hai lần do sụt áp; cuộn relay có nguồn riêng chỉ sụt vài chục mV
Ví dụ kết quả hai cách cấp nguồn (hình minh họa, số liệu ví dụ).

Đọc kết quả: sụt vài chục mV là nguồn tốt; sụt vài trăm mV là nguồn yếu, dễ reset khi bật thêm Wi-Fi hay tải khác; số lần reset do sụt áp lớn hơn 0 là phải sửa nguồn ngay. Các mức này là kinh nghiệm chung, mỗi nguồn và mỗi cáp cho số khác nhau.

7. Bảng lỗi thường gặp

Hiện tượngNguyên nhân hay gặpCách xử lý
Reset ngay khi relay đóng, Serial báo BrownoutCuộn relay và ESP32 chung nguồn yếuNguồn riêng cho cuộn relay, củ sạc tốt, tụ dự trữ
Reset hoặc treo khi relay nhả quạt, máy bơmHồ quang ở tiếp điểmMạch RC hoặc varistor; tách dây tải khỏi dây tín hiệu
Màn hình OLED đứng hình, hiện rác sau vài lần relay chuyểnNhiễu vào bus I2CDây I2C ngắn, đi xa dây tải; khởi tạo lại màn hình định kỳ
Máy tính mất cổng COM khi relay chuyểnSụt áp, nhiễu qua cáp USBCấp nguồn riêng cho relay, cáp USB ngắn tốt
Cắm module relay vào thì ESP32 không khởi độngIN nối GPIO 0, 2, 12 hoặc 15Đổi sang GPIO 16–33 ở danh sách mục 5
Relay đóng thoáng qua lúc cấp nguồnChân chưa khai báo ngõ ra, module kích mức thấpKhởi tạo chân theo thứ tự ở mục 5
Đã tách nguồn mà vẫn reset khi tải lớn chạyDòng tải chạy qua dây GND chung với ESP32Tải lấy GND riêng về nguồn của nó, nối chung GND tại một điểm

Khi lỗi khó tìm, cách chia đôi mạch và đo nguồn từng bước trong bài tìm lỗi mạch điện tử từng bước giúp khoanh vùng nhanh hơn.

8. Xem thêm

Cần tư vấn chọn nguồn, module relay cho tải cụ thể, liên hệ PYWORLD qua số 0915703319.


Tin tức liên quan

Hướng dẫn bắt đầu MicroPython trên ESP32 Lite với Thonny
Hướng dẫn bắt đầu MicroPython trên ESP32 Lite với Thonny

Hướng dẫn cài driver, cài Thonny và nạp firmware MicroPython cho mạch ESP32 Lite, dùng REPL, nháy LED, lưu main.py, đọc nút nhấn, quét I2C và xử lý lỗi thường gặp.

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.

ESP-NOW nhiều node với ESP32: nhiều mạch gửi về một trạm thu, hiển thị trên OLED
ESP-NOW nhiều node với ESP32: nhiều mạch gửi về một trạm thu, hiển thị trên OLED

Hướng dẫn dựng mô hình ESP-NOW nhiều node: tối đa bốn ESP32 gửi số đo về một trạm thu, trạm thu hiện điện áp, RSSI và tình trạng từng node trên màn hình OLED 0.96 inch.

Làm gateway LoRa sang MQTT bằng ESP32: node cảm biến chạy pin gửi dữ liệu về broker
Làm gateway LoRa sang MQTT bằng ESP32: node cảm biến chạy pin gửi dữ liệu về broker

Hướng dẫn làm hệ thống LoRa hoàn chỉnh với ESP32 và SX1278: node chạy pin đo điện áp, gửi gói rồi ngủ sâu; gateway nhận gói LoRa và publish dữ liệu JSON lên broker MQTT qua Wi-Fi.

Hướng dẫn lưu dữ liệu vào bộ nhớ flash của ESP32 bằng thư viện Preferences
Hướng dẫn lưu dữ liệu vào bộ nhớ flash của ESP32 bằng thư viện Preferences

Hướng dẫn dùng thư viện Preferences trên ESP32 với Arduino IDE để lưu số đếm, trạng thái relay và thông số cài đặt vào bộ nhớ flash, không mất khi tắt nguồn.

Hướng dẫn làm đồng hồ Internet với ESP32, NTP và màn hình OLED 0.96 inch
Hướng dẫn làm đồng hồ Internet với ESP32, NTP và màn hình OLED 0.96 inch

Hướng dẫn ESP32 lấy giờ Việt Nam từ Internet bằng NTP trên Arduino IDE, in ra Serial Monitor và làm đồng hồ hiển thị thứ, ngày, giờ trên màn hình OLED 0.96 inch SSD1306.

ESP32 điều khiển động cơ bước 28BYJ-48 với ULN2003: nửa bước, quay đúng góc, kim giây
ESP32 điều khiển động cơ bước 28BYJ-48 với ULN2003: nửa bước, quay đúng góc, kim giây

Hướng dẫn điều khiển động cơ bước 28BYJ-48 bằng ESP32 qua mạch ULN2003: chế độ nửa bước, 4076 bước một vòng, quay đúng góc theo lệnh, kim giây đồng hồ và thí nghiệm STEM.

Hướng dẫn sử dụng màn hình LCD 1602 I2C với Arduino Uno
Hướng dẫn sử dụng màn hình LCD 1602 I2C với Arduino Uno

Hướng dẫn nối màn hình LCD 1602 I2C với Arduino Uno, cài thư viện LiquidCrystal_I2C, chạy code hiển thị và quét địa chỉ I2C 0x27/0x3F.


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