Đó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 triggeredrồ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òngrst:ngay sau đó thường ghiSW_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

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

- 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

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ằngpinMode(). 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ả.

| Từ | Nối tới | Ghi chú |
|---|---|---|
| Module relay GND | ESP32 GND | |
| Module relay IN1 | ESP32 D27 | Module 1 kênh: đặt jumper mức kích ở L |
| Module relay VCC | ESP32 VIN | Lần đo đầu: giữ jumper JD-VCC |
| R1 100 kΩ | VIN → điểm giữa | Cầu phân áp đo VIN |
| R2 100 kΩ | Điểm giữa → GND | |
| Điểm giữa cầu | ESP32 D34 | D34 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: đểtruevới module kích mức thấp; module kích mức cao thì đổi thànhfalse.- Ba biến khai báo
RTC_NOINIT_ATTRnằ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_BROWNOUTkhi 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:
- 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ả.
- 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.
- 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.

Đọ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ượng | Nguyên nhân hay gặp | Cách xử lý |
|---|---|---|
| Reset ngay khi relay đóng, Serial báo Brownout | Cuộn relay và ESP32 chung nguồn yếu | Nguồ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ơm | Hồ quang ở tiếp điểm | Mạ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ển | Nhiễu vào bus I2C | Dâ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ển | Sụt áp, nhiễu qua cáp USB | Cấ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 động | IN 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ồn | Chân chưa khai báo ngõ ra, module kích mức thấp | Khở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ạy | Dòng tải chạy qua dây GND chung với ESP32 | Tả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
- Sản phẩm: module relay 1, 2, 4 kênh 5 V và 12 V, mạch nguồn MP1584, mạch hạ áp LM2596.
- ESP32 điều khiển module relay 4 kênh an toàn.
- Đế ra chân ESP32 30 chân: cấp nguồn từ adapter, hàng chân G V S tiện cắm module relay.
- Bài cấp nguồn cho ESP32 và bài module relay 1, 2, 4 kênh trong cùng đợt bài này.
Cần tư vấn chọn nguồn, module relay cho tải cụ thể, liên hệ PYWORLD qua số 0915703319.
Xem thêm