Printing, plotting and debugging
Serial.println is the debugger, and using it well is a real skill. Format decides whether you get a readable log, a live graph, or a wall of numbers you cannot read while the thing is moving.
Four ways to print two numbers
println calls make two lines, and you have lost which is which. The Plotter reads them as one variable jumping between 22 and 61. This is the default mistake and it looks like a broken sensor.Beyond printing
Three things the chip will tell you if you ask, and all three are worth putting in a debug line from the start:
ESP.getFreeHeap()— a number that falls steadily is a memory leak, and it is the reason a board crashes on day three rather than on day one.esp_reset_reason()— why it last restarted. Panic, watchdog, brownout and deep sleep are four different bugs, and they look identical from outside.Serial.printf— take the C formatting over string concatenation."t=" + String(t)allocates, and allocating in a fast loop fragments the heap.
Switch it off before shipping
Serial printing blocks. At 115200 baud each character takes about 87 µs, so a 40-character line inside a loop that runs a thousand times a second is not a diagnostic — it is the reason the loop is slow. Put debug output behind a macro and compile it out.
The code
One sketch, three outputs. The plotter format is exact — labels, a colon, the value, a tab between pairs, one println at the end — and nobody documents it.
#define DEBUG 1
#if DEBUG
#define LOG(...) Serial.printf(__VA_ARGS__)
#else
#define LOG(...)
#endif
void setup() { Serial.begin(115200); }
void loop() {
float t = 22.5 + random(-20, 20) / 10.0;
int h = 55 + random(-5, 5);
// human-readable, for scrolling back through
LOG("t=%.1fC rh=%d%% heap=%u\n", t, h, ESP.getFreeHeap());
// machine-readable, for the Serial Plotter
Serial.printf("temp:%.1f\thum:%d\n", t, h);
delay(200);
}Wrap debug prints in a macro you can switch off. Serial output at 115200 costs about 87 µs per character of blocking time, which is enough to change the timing of the bug you are chasing.
Same two formats. MicroPython also gives you something the Arduino side does not - a live REPL, so you can poke at a running board instead of guessing and reflashing.
import time, gc, random
DEBUG = True
def log(msg):
if DEBUG:
print(msg)
while True:
t = 22.5 + random.uniform(-2, 2)
h = 55 + random.randint(-5, 5)
log('t={:.1f}C rh={}% free={}'.format(t, h, gc.mem_free()))
print('temp:{:.1f}\thum:{}'.format(t, h)) # plotter format
time.sleep(0.2)Ctrl-C at the REPL stops a running script and drops you into the prompt with all its variables still alive. That is often faster than any amount of printing.
When it does not work
The board starts printing before the Serial Monitor has attached. Add a short delay after Serial.begin, or on a native-USB board wait for the port with while (!Serial) and a timeout so it still runs unplugged.
Baud mismatch. Set the monitor to the same number as Serial.begin. A second possibility on the classic ESP32 is the bootloader's own 74880-baud startup message, which is meant to look like that.
Two separate println calls make two lines, so the plotter reads them as one variable jumping between values. Put both on one line separated by a tab.
Then the bug is a timing one, and the print is slowing something down enough to hide it. Log to a buffer in RAM and print it afterwards, rather than printing inside the fast path.
Enough tooling. The next chapter is the board itself, starting with the two buttons and the one pin that decides how it starts.
The EN and BOOT buttons →Edit this page — content/esp32/printing-plotting-and-debugging.mdx
Discuss this article
Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.