Watchdog trên ESP32: vì sao mạch tự khởi động lại, đọc lý do reset và tự cứu khi chương trình treo

Watchdog là gì, task watchdog, interrupt watchdog, panic và brownout trên ESP32, in lý do khởi động lại bằng esp_reset_reason, đếm số lần khởi động, cho task tự đăng ký watchdog.

"Mạch ESP32 cứ chạy một lúc lại tự khởi động lại" là một trong những câu hỏi gặp nhiều nhất. Thủ phạm thường là watchdog, lỗi chương trình hoặc nguồn yếu, và ESP32 luôn ghi lại lần khởi động gần nhất là do đâu. Bài này giải thích watchdog là gì, ESP32 có những loại nào, cách in lý do khởi động lại ngay đầu chương trình, cách dùng task watchdog để mạch tự cứu khi bị treo, và cách phân biệt với sụt áp nguồn.

1. Watchdog là gì

Đồ thị thời gian còn lại trước khi watchdog ra tay: răng cưa giữa 2,5 và 3 giây trong 4 giây đầu vì được cho ăn mỗi 0,5 giây; từ giây thứ 4 task bị treo, đường giảm thẳng về 0 ở giây thứ 7 và chip khởi động lại
Hạn 3 giây, task cho ăn mỗi 0,5 giây; treo ở giây thứ 4 thì 3 giây sau chip khởi động lại.

Watchdog (chó canh cửa) là một bộ đếm ngược chạy độc lập với chương trình. Chương trình phải đều đặn "cho ăn" (đặt lại bộ đếm) để chứng tỏ mình vẫn chạy bình thường. Nếu chương trình bị kẹt trong một vòng lặp, chờ một thứ không bao giờ tới, hay bị lỗi làm đứng, việc cho ăn dừng lại; bộ đếm về 0 và watchdog khởi động lại chip.

Với thiết bị đặt ở xa (trạm đo ngoài vườn, bộ điều khiển trong tủ điện), khởi động lại tốt hơn nhiều so với đứng im mãi tới khi có người tới rút điện. Nhưng watchdog không sửa lỗi: nó chỉ đưa mạch về trạng thái ban đầu, lỗi vẫn còn và sẽ lặp lại.

2. Những thứ làm ESP32 tự khởi động lại

Bảng bốn loại: task watchdog theo dõi task rỗi của nhân 0 và task tự đăng ký, hạn 5 giây; interrupt watchdog theo dõi ngắt, hạn 300 ms; panic do lỗi CPU; brownout do điện áp nguồn tụt
Hai loại watchdog, lỗi chương trình và sụt áp nguồn (giá trị mặc định của gói esp32 3.3).
  • Task watchdog (TWDT): theo dõi các task đã đăng ký. Mặc định trên Arduino ESP32 nó theo dõi task rỗi (IDLE) của nhân 0: nếu một task chạy liên tục không nhường quá 5 giây ở nhân 0, task rỗi không được chạy và watchdog báo lỗi. loop() không được theo dõi trừ khi gọi enableLoopWDT().
  • Interrupt watchdog (IWDT): bảo đảm ngắt luôn được phục vụ. Hàm ngắt chạy quá lâu, hoặc chương trình tắt ngắt (đoạn khóa portENTER_CRITICAL) quá 300 ms, là bị khởi động lại.
  • Panic: không phải watchdog mà là lỗi CPU: ghi vào con trỏ rỗng, chia số nguyên cho 0, tràn stack, gọi hàm không có trong IRAM khi đang ghi flash. Serial in "Guru Meditation Error" kèm địa chỉ lệnh lỗi.
  • Brownout: điện áp nguồn của chip tụt dưới ngưỡng an toàn. Hay gặp nhất lúc bật Wi-Fi (dòng vọt lên vài trăm mA), đóng relay, chạy động cơ, hoặc dùng dây USB dài, mảnh.

3. In lý do khởi động lại

esp_reset_reason() trả về lý do của lần khởi động gần nhất: bật nguồn, lệnh ESP.restart(), panic, interrupt watchdog, task watchdog, thức dậy từ deep sleep, brownout. Chương trình trong bài in lý do này ngay đầu setup(). Thêm hai dòng này vào mọi dự án là cách nhanh nhất để biết mạch "tự reset" vì đâu.

Để đếm số lần khởi động, chương trình dùng biến RTC_NOINIT_ATTR: biến này nằm trong bộ nhớ RTC, không bị xóa khi chip khởi động lại bằng phần mềm hay watchdog, nhưng chứa giá trị rác khi vừa cấp điện. Vì vậy chương trình kiểm tra lý do là bật nguồn, hoặc giá trị "dấu hiệu" không khớp, thì đặt lại bộ đếm. Cách này dùng được để phát hiện mạch đang khởi động lại liên tục và chuyển sang chế độ an toàn (ví dụ tắt tính năng vừa cập nhật).

4. Chương trình

Chương trình đổi hạn của task watchdog thành 3 giây và bật chế độ khởi động lại khi quá hạn (trigger_panic = true), rồi tạo một task làm việc tự đăng ký với watchdog bằng esp_task_wdt_add(NULL) và cho ăn bằng esp_task_wdt_reset() mỗi vòng. Gõ một chữ trên Serial Monitor để thử từng loại:

  • T: task làm việc bị kẹt trong vòng lặp vô hạn, ngừng cho ăn → task watchdog.
  • I: tắt ngắt rồi kẹt → interrupt watchdog sau khoảng 300 ms.
  • P: ghi vào địa chỉ 0 → panic.
  • R: ESP.restart() → khởi động lại bằng lệnh.
// PYWORLD.VN - Watchdog trên ESP32: in lý do khởi động lại, đếm số lần khởi động, task watchdog 3 giây tự cứu khi chương trình treo
// Gõ trên Serial Monitor: T = task bị treo (task watchdog), I = treo khi tắt ngắt (interrupt watchdog), P = lỗi con trỏ (panic), R = khởi động lại bằng lệnh
// Chỉ cần ESP32, không cần thư viện ngoài. Mỗi lệnh thử đều làm chip khởi động lại: đó là điều bài muốn quan sát

#include <esp_task_wdt.h>

RTC_NOINIT_ATTR uint32_t dauHieu;        // vùng RTC không bị xóa khi khởi động lại bằng phần mềm (mất khi rút điện)
RTC_NOINIT_ATTR uint32_t soLanKhoiDong;
const uint32_t DAU_HIEU = 0x50574C44;

TaskHandle_t taskLamViec;
volatile bool giaLapTreo = false;

const char *lyDo(esp_reset_reason_t r) {
  switch (r) {
    case ESP_RST_POWERON: return "cap dien (bat nguon)";
    case ESP_RST_SW: return "lenh khoi dong lai (esp_restart)";
    case ESP_RST_PANIC: return "loi chuong trinh (panic)";
    case ESP_RST_INT_WDT: return "interrupt watchdog";
    case ESP_RST_TASK_WDT: return "task watchdog";
    case ESP_RST_WDT: return "watchdog khac";
    case ESP_RST_DEEPSLEEP: return "thuc day tu deep sleep";
    case ESP_RST_BROWNOUT: return "sut ap nguon (brownout)";
    default: return "khac";
  }
}

// Task đăng ký với task watchdog: mỗi vòng làm việc phải "cho ăn" (esp_task_wdt_reset), quá 3 giây không cho ăn là bị xử lý.
void lamViec(void *thamSo) {
  esp_task_wdt_add(NULL);
  uint32_t vong = 0;
  for (;;) {
    if (giaLapTreo) {
      Serial.println("Task lam viec bi treo (vong lap khong thoat)...");
      while (true) {}                    // giả lập lỗi: kẹt mãi, không bao giờ cho ăn
    }
    vong++;
    if (vong % 10 == 0) Serial.printf("Lam viec binh thuong, vong %lu\n", (unsigned long)vong);
    esp_task_wdt_reset();
    vTaskDelay(pdMS_TO_TICKS(500));
  }
}

void setup() {
  Serial.begin(115200);
  delay(300);
  esp_reset_reason_t r = esp_reset_reason();
  if (r == ESP_RST_POWERON || dauHieu != DAU_HIEU) {   // bật nguồn: vùng RTC chứa rác, khởi tạo lại
    dauHieu = DAU_HIEU;
    soLanKhoiDong = 0;
  }
  soLanKhoiDong++;
  Serial.printf("\nKhoi dong lan %lu. Ly do: %s\n", (unsigned long)soLanKhoiDong, lyDo(r));

  esp_task_wdt_config_t cauHinh = {
    .timeout_ms = 3000,                  // 3 giây không cho ăn thì báo lỗi
    .idle_core_mask = 1 << 0,            // vẫn theo dõi task rỗi của nhân 0
    .trigger_panic = true,               // báo lỗi rồi khởi động lại, không chỉ in cảnh báo
  };
  esp_task_wdt_reconfigure(&cauHinh);
  xTaskCreatePinnedToCore(lamViec, "lam viec", 4096, NULL, 1, &taskLamViec, 1);
  Serial.println("Go T, I, P hoac R de thu");
}

void loop() {
  if (!Serial.available()) {
    vTaskDelay(pdMS_TO_TICKS(50));
    return;
  }
  char c = toupper(Serial.read());
  if (c == 'T') {
    giaLapTreo = true;
  } else if (c == 'I') {
    Serial.println("Tat ngat roi treo: interrupt watchdog se khoi dong lai sau khoang 300 ms");
    Serial.flush();
    portDISABLE_INTERRUPTS();
    while (true) {}
  } else if (c == 'P') {
    Serial.println("Ghi vao dia chi 0: chip bao loi (Guru Meditation) roi khoi dong lai");
    Serial.flush();
    volatile uint32_t *conTro = nullptr;
    *conTro = 1;
  } else if (c == 'R') {
    Serial.println("Khoi dong lai bang lenh");
    Serial.flush();
    ESP.restart();
  }
}

Mỗi lệnh thử đều làm chip khởi động lại, không hại gì tới mạch. Sau mỗi lần, dòng đầu tiên cho biết đúng lý do vừa xảy ra và số lần khởi động tăng thêm 1.

5. Kết quả mong đợi

Serial Monitor: khởi động lần 1 do cấp điện, task làm việc bình thường, gõ T thì task bị treo, hệ thống in task_wdt Task watchdog got triggered, tên task lam viec CPU 1, Aborting, Rebooting; lần khởi động 2 ghi lý do task watchdog
Gõ T: task bị treo, 3 giây sau task watchdog khởi động lại chip; lần khởi động sau in đúng lý do (số liệu ví dụ).

Dòng task_wdt cho biết tên task không cho ăn đúng hạn: trong dự án thật, đó là manh mối để tìm vòng lặp bị kẹt. Với panic, Serial in thêm dãy địa chỉ (backtrace); công cụ "ESP Exception Decoder" đổi các địa chỉ đó thành tên hàm và dòng lệnh.

6. Dùng watchdog cho đúng

  • Đừng tắt watchdog để "hết lỗi". Lỗi vẫn còn, chỉ là mạch đứng im thay vì tự khởi động lại.
  • Vòng lặp tính toán dài: chèn vTaskDelay(1) hoặc yield() định kỳ, không chỉ cho ăn watchdog.
  • Task quan trọng (đọc cảm biến, điều khiển) nên tự đăng ký watchdog: nếu nó kẹt, cả mạch khởi động lại thay vì chạy tiếp mà không làm việc chính.
  • Chỉ cho ăn ở chỗ chứng tỏ việc chính đã làm xong, không đặt esp_task_wdt_reset() trong một task phụ luôn chạy được.
  • Brownout: sửa nguồn chứ không tắt bộ phát hiện. Dùng dây USB ngắn, tốt; thêm tụ 470–1000 µF ở chân 5 V; relay, động cơ dùng nguồn riêng có GND chung.

7. Đọc nhanh lý do và cách xử lý

Lý do in raThường doCách xử lý
task watchdogVòng lặp dài không nhường; chờ mạng, chờ cảm biến không có thời hạnThêm thời hạn cho mọi lần chờ; vTaskDelay trong vòng lặp dài
interrupt watchdogHàm ngắt dài, in Serial trong ngắt, đoạn khóa dàiNgắt chỉ dựng cờ; đoạn khóa vài lệnh
loi chuong trinh (panic)Con trỏ sai, tràn stack task, chia cho 0Giải mã backtrace; tăng stack task; kiểm tra con trỏ
sut ap nguon (brownout)Nguồn, dây USB kém; tải lớn dùng chung nguồnSửa nguồn, thêm tụ, tách nguồn tải
lenh khoi dong laiChương trình (hoặc thư viện, OTA) gọi ESP.restart()Tìm chỗ gọi; bình thường sau khi cập nhật OTA
cap dienRút cắm nguồn, hoặc nguồn chập chờn mất hẳnKiểm tra dây nguồn, cổng cắm

8. Xem thêm


Tin tức liên quan

KiCad cho người mới: từ sơ đồ nguyên lý tới mạch in 2 lớp, đổ đồng GND và xuất Gerber
KiCad cho người mới: từ sơ đồ nguyên lý tới mạch in 2 lớp, đổ đồng GND và xuất Gerber

Vẽ mạch in 2 lớp bằng KiCad: symbol và footprint, ERC, các lớp F.Cu, B.Cu, mask, silkscreen, đi dây, đổ đồng GND có cầu nhiệt, phím tắt hay dùng, xuất Gerber và tệp khoan.

Hàn linh kiện dán SMD bằng tay: cỡ 0805, SOT-23, SOIC, dụng cụ, các bước và lỗi hay gặp
Hàn linh kiện dán SMD bằng tay: cỡ 0805, SOT-23, SOIC, dụng cụ, các bước và lỗi hay gặp

Các cỡ linh kiện dán 1206, 0805, 0603, 0402, SOT-23, SOIC; dụng cụ cần có; bốn bước hàn điện trở, tụ dán; hàn IC bằng kéo thiếc; nhận ra mối hàn tốt và cách sửa lỗi.

Mô phỏng ESP32 trên Wokwi: thử mạch và chương trình ngay trên trình duyệt, có cả Wi-Fi
Mô phỏng ESP32 trên Wokwi: thử mạch và chương trình ngay trên trình duyệt, có cả Wi-Fi

Wokwi mô phỏng ESP32 làm được gì và chưa làm được gì, ba tệp sketch.ino, diagram.json, libraries.txt, dựng mạch LED, nút, biến trở, Wi-Fi ảo và cách chuyển sang mạch thật.

Thuật toán qua trò chơi cho trẻ: đoán số, xếp hàng, tìm đường ngắn nhất (kiểu đề Bebras)
Thuật toán qua trò chơi cho trẻ: đoán số, xếp hàng, tìm đường ngắn nhất (kiểu đề Bebras)

Ba trò chơi giúp trẻ hiểu thuật toán: đoán số bằng chia đôi, xếp hàng bằng sắp xếp nổi bọt, tìm đường ngắn nhất kiểu Dijkstra, kèm câu đố kiểu Bebras có lời giải từng bước.

Jack Type-C sạc pin loại 4 dây: một cổng vừa sạc pin 1S vừa truyền dữ liệu USB
Jack Type-C sạc pin loại 4 dây: một cổng vừa sạc pin 1S vừa truyền dữ liệu USB

Jack Type-C tích hợp mạch sạc loại 4 dây có thêm D+ D− để một cổng vừa sạc pin 1S vừa nối USB với máy tính. Bài hướng dẫn xác định từng dây bằng đồng hồ, nối vào vi điều khiển và các lưu ý khi dùng chung cổng.

Cảm biến từ trường: Hall, từ trở, fluxgate, lưỡi gà – cấu tạo, nguyên lý, chế tạo và đo bằng ESP32
Cảm biến từ trường: Hall, từ trở, fluxgate, lưỡi gà – cấu tạo, nguyên lý, chế tạo và đo bằng ESP32

Cảm biến từ trường hoạt động thế nào: hiệu ứng Hall và ba kiểu IC Hall, công tắc lưỡi gà, từ trở, fluxgate, cuộn cảm ứng, cách chế tạo và đo mật độ từ thông bằng Hall tuyến tính 49E với ESP32.

Cảm biến khí ga: MQ, điện hóa, xúc tác, NDIR – cấu tạo, nguyên lý, chế tạo và đọc MQ-2 bằng ESP32
Cảm biến khí ga: MQ, điện hóa, xúc tác, NDIR – cấu tạo, nguyên lý, chế tạo và đọc MQ-2 bằng ESP32

Cảm biến khí ga hoạt động thế nào: bán dẫn SnO₂ họ MQ, điện hóa, xúc tác, hồng ngoại NDIR, cách chế tạo cảm biến bán dẫn và đọc module MQ-2 bằng ESP32 để tính Rs/R0, ước lượng ppm.

Sơ đồ chân ESP32 DevKit V1 30 chân và 38 chân: chân nào dùng được, chân nào nên tránh
Sơ đồ chân ESP32 DevKit V1 30 chân và 38 chân: chân nào dùng được, chân nào nên tránh

Sơ đồ chân ESP32 DevKit V1 30 chân và 38 chân: số GPIO, ADC1, ADC2, cảm ứng, I2C, SPI, UART, chân khởi động, chân chỉ vào, chân nối flash và cách chọn chân đúng cho từng việc.


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