Serial monitor and debugging on the ESP32: stop guessing

How to actually debug on the ESP32: a properly configured serial monitor, leveled logs, brownout, watchdog, panic backtraces and when to use JTAG.

Also available in: EN, ES

Series Introduction to the ESP32 Part 21 View the series →
Serial monitor and debugging on the ESP32

When something does not work, the temptation is to change code blindly. That makes it worse. What really saves time is seeing what is happening. And for that you have the serial monitor and logs.

The serial monitor, properly configured

void setup() {
  Serial.begin(115200);
  Serial.println("Starting…");
}

Three things almost everyone forgets:

  • The baud rate must match (115200 is the standard). If you see ����, it does not.
  • After Serial.begin() give it a moment before printing; the first lines are sometimes lost.
  • The monitor resets the board when it opens in many setups: do not panic if it “boots on its own”.

If you see nothing:

  1. Is the monitor on the right port?
  2. Does the board use native USB (S3/C3) and you are watching the wrong port?
  3. Did you close another program holding the port open?

Leveled logs (if you use ESP-IDF)

Instead of printf everywhere, use levels:

#include "esp_log.h"
static const char *TAG = "app";

ESP_LOGI(TAG, "info");
ESP_LOGW(TAG, "warning");
ESP_LOGE(TAG, "error");

You can raise or lower the level per component from menuconfig, and in production keep only errors. A printf does not give you that.

The messages that are telling you the problem

On boot, the ESP32 prints a lot of useful information:

  • rst:0x... → why it reset: power-on, watchdog, panic, brownout.
  • Brownout detector was triggerednot enough power. Change the cable, the USB port, or add a capacitor.
  • Guru Meditation Error → a panic. Below it comes a backtrace with addresses; with idf.py monitor it is translated to function names for you.
  • Task watchdog got triggered → your loop() or a task got stuck.

Learning to read those four lines solves half the problems.

Techniques that work

  1. Bisection: comment out half the code. The problem is in the other half. Repeat.
  2. Strategic Serial.println: mark where it passes, not random values. Better [sensor] read: %d.
  3. Status LED: a fast-blinking LED = “I am here”. No monitor needed.
  4. Multimeter: measure power and levels before blaming the code.
  5. Logic analyzer (cheap): essential for I2C/SPI. Seeing the bus is another level.
  6. JTAG (S3/C3): real breakpoints when the bug is slippery.

Typical mistakes

  • Changing code at random without watching the monitor.
  • Ignoring a brownout thinking it is software.
  • Not reading the backtrace (the exact line is there).
  • Using delay() in the loop and hitting the watchdog.
  • Debugging at 9600 baud and expecting everything to come out.

A loop() that does not block (and does not trip the watchdog)

unsigned long last = 0;

void loop() {
  if (millis() - last >= 1000) {
    last = millis();
    // heavy work here
  }
  // the rest of the time, breathe
}

Summary

  • Set the monitor to 115200 and check port and speed.
  • Use leveled logs (ESP-IDF) instead of loose printfs.
  • rst:, brownout, backtrace and watchdog tell you what is wrong; learn them.
  • Bisection + status LED + multimeter solve most cases.
  • For buses (I2C/SPI), a logic analyzer is worth its weight in gold.
  • When the bug hides, JTAG (S3/C3).

Next step: with this you close Introduction to the ESP32. From here you can continue to Explore or go back to the series hub.

Related posts

↑↓ navigate ↵ open Esc close