Skip to Content

← ESP32 PLC Hardware Watchdog for Unattended Machines

Hydraulic moving floor (BLE app)ESP32 PLC 38RGPIOResilience / OTA

ESP32 PLC Hardware Watchdog for Unattended Machines — full example

Make ESP32 PLC firmware self-healing with esp_task_wdt. A 20-second hardware watchdog reboots a hung controller automatically, with NVS diagnostics.

Complete, runnable program for the ESP32 PLC 38R (hardware-watchdog-38r.ino): wiring header, requirements and integration notes included.

Download the full project pack — freeThis example + the related ones + bill of materials

Read-only preview.

/*
 * COMPLETE EXAMPLE — ESP32 hardware watchdog (esp_task_wdt)
 *
 * Hardware:  ESP32 PLC 38R (Industrial Shields)
 * Based on:  hydraulic moving floor project, main.cpp (resilience)
 *
 * Wiring:
 *   I0_0  "Simulate hang" push button (blocks the loop on purpose)
 *   R0_1  Heartbeat LED (blinks while the firmware is healthy)
 *
 * Logic:
 *   - esp_task_wdt_init() arms a 20 s watchdog on the loop task.
 *   - Every loop iteration "feeds" it with esp_task_wdt_reset().
 *   - If the firmware hangs (deadlock, infinite loop, blocked BLE
 *     stack...), the watchdog reboots the PLC by itself: the machine is
 *     operational again without anyone climbing up to power-cycle it.
 *   - The reason of the last reboot and a watchdog reboot counter are
 *     stored in NVS for field diagnostics.
 *
 * Integration with other catalog modules:
 *   - The reboot counter uses Preferences, just like the cycle
 *     counters (see ejemplos/piso-movil/nvs-persistent-counters-38r.ino).
 *   - After the reboot the BLE server advertises again and the app
 *     reconnects by itself (see ejemplos/piso-movil/ble-mobile-app-control-38r.ino).
 */

#include 
#include 

#define BTN_HANG   I0_0
#define LED_ALIVE  R0_1

#define WDT_TIMEOUT  20      // seconds without feeding -> reboot

Preferences prefs;
int wdtResets = 0;

// Translation of the reset reason for the diagnostics log
const char* resetReasonText(esp_reset_reason_t r) {
  switch (r) {
    case ESP_RST_POWERON:  return "power-on";
    case ESP_RST_SW:       return "software reboot";
    case ESP_RST_TASK_WDT: return "WATCHDOG (firmware hung)";
    case ESP_RST_BROWNOUT: return "brownout (power supply)";
    default:               return "other";
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(BTN_HANG, INPUT);
  pinMode(LED_ALIVE, OUTPUT);

  // 1. Diagnostics: why did we boot?
  esp_reset_reason_t reason = esp_reset_reason();
  Serial.print("Reason of the last reboot: ");
  Serial.println(resetReasonText(reason));

  // 2. Persistent counter of watchdog reboots (NVS pattern)
  prefs.begin("nvdata", false);
  wdtResets = prefs.getInt("wdt_resets", 0);
  if (reason == ESP_RST_TASK_WDT) {
    wdtResets++;
    prefs.putInt("wdt_resets", wdtResets);
  }
  prefs.end();
  Serial.println("Accumulated watchdog reboots: " + String(wdtResets));

  // 3. Arm the watchdog: 20 s and panic=true (reboots instead of just warning)
  esp_task_wdt_init(WDT_TIMEOUT, true);
  esp_task_wdt_add(NULL);              // watches the current task (loop)
  Serial.println("Watchdog armed: " + String(WDT_TIMEOUT) + " s");
}

void loop() {
  // Feed the watchdog: as long as the loop keeps spinning, nothing happens
  esp_task_wdt_reset();

  // Heartbeat LED at 1 Hz: at a glance you can see the firmware is healthy
  digitalWrite(LED_ALIVE, (millis() / 500) % 2);

  // Hang simulation: infinite loop WITHOUT feeding the watchdog.
  // After 20 s the PLC reboots by itself and the NVS counter increments.
  if (digitalRead(BTN_HANG)) {
    Serial.println("Firmware 'hung' on purpose. Rebooting in ~20 s...");
    while (true) {
      // no esp_task_wdt_reset() and no delay(): a real hang
    }
  }

  delay(50);
}
Download the full project pack — freeThis example + the related ones + bill of materials