Working out why your ESP32 keeps resetting

Tips, Tricks and methods for programming, learn ways of making your programming life easier, and share your knowledge with others.
Post Reply
medelec35
Valued Contributor
Posts: 2438
http://meble-kuchenne.info.pl
Joined: Wed Dec 02, 2020 11:07 pm
Has thanked: 783 times
Been thanked: 844 times

Working out why your ESP32 keeps resetting

Post by medelec35 »

If your board keeps rebooting and you can't work out why, you're in good company — it's one of the most common ESP32 questions on here.
We always assume it's a watchdog reset but that is not always the case as I recent had a code 4 reset cause by a component that has the wrong seeing by default.

The good news is the ESP32's operating system (ESP-IDF, which is what Flowcode's ESP32 target sits on top of) already keeps a note of why the last reset happened. You just have to ask it.

This is aimed at anyone who hasn't touched this side of Flowcode before, so it starts from scratch. By the end your board will tell you, in plain English, whether it was a power-on, a brownout, a crash, a watchdog timeout, and so on — either on a display, over serial, or both.

## Step 1 — a variable to hold the answer

Nothing fancy needed here.

1. Open the Project Explorer (View menu if it's hidden).
2. Right-click Variables > Add Variable.
3. Call it `LastResetReason`.
4. Type: `Byte` (Flowcode calls this `u8` under the hood — an unsigned 8-bit number). The reset reason is always a small number, 0 to 10, so a byte is plenty.
5. Leave the array size blank — it's a single number, not a list.

## Step 2 — ask the chip why it last reset

There's no ready-made Flowcode icon for this, since it's a fairly ESP32-specific thing. So we drop down to a couple of lines of real C using a C Code icon.

1. Drag a C Code icon onto Main.
2. Put it right at the top, before anything else runs — display init, WiFi, sensors, all of it. If your board is crash-looping early in normal startup, you want this captured before the same crash can happen again.
3. Double click it and type:

Code: Select all

#include "esp_system.h"
FCV_LASTRESETREASON = (uint8_t)esp_reset_reason();

Quick thing worth knowing if you haven't used C Code icons before: whatever you call your variable in Flowcode, the generated C always refers to it as `FCV_` plus the name in capitals. So `LastResetReason` becomes `FCV_LASTRESETREASON`. Miss this and you'll get a build error saying it can't find the variable.

## Step 3 — turn the number into a message

Here's what each code actually means:

| Code | Name | What it means |
|---|---|---|
| 0 | UNKNOWN | Reset reason not reported |
| 1 | POWERON | Board was switched on or plugged in |
| 2 | EXT | External reset pin pulled low (e.g. reset button) |
| 3 | SW | Something in your code called esp_restart() |
| 4 | PANIC | Code hit a fault — bad memory access etc. |
| 5 | INT_WDT | An interrupt handler ran too long |
| 6 | TASK_WDT | Main loop ran too long without a pause |
| 7 | WDT | Some other watchdog reset |
| 8 | DEEPSLEEP | Woke from deep sleep |
| 9 | BROWNOUT | Supply voltage dropped too low |
| 10 | SDIO | ESP32 used as an SDIO slave device |

Two worth flagging straight away. PANIC and BROWNOUT are the usual suspects if your board is randomly rebooting for no obvious reason. And SDIO — despite the name — has nothing to do with an SD card. It only shows up if the chip itself is being used as an SDIO slave to another processor, which is a fairly specialist setup. If you're logging to an SD card with a FileSD component, this code will never appear because of that.

To turn the number into text, add a second variable — `LastResetReasonStr`, type String (Flowcode's T8), array size 70 or more. Fifty would just about cover the longest message here, but give yourself some headroom in case you reword one later.

Now, rather than doing this bit in C, use Flowcode's own Switch icon (the same one you'd use for a menu) — drag it in after the C Code icon and set it to switch on `LastResetReason`. Add a case for each number 0–10, and in each one drop a Calculation icon that sets `LastResetReasonStr` to the matching text, e.g.

```
case 6:
LastResetReasonStr = "TASK_WDT - Loop ran too long without a pause - try adding 1ms delay"
```

Add a default case too, something like `LastResetReasonStr = "Unknown code"`, just in case a future ESP-IDF version adds a number that isn't in the list above.

If you're stuck with TASK_WDT and genuinely can't fit in even a 1ms delay — say you're bit-banging something time-critical — there's another way to keep the watchdog happy without ever pausing. Register once with `esp_task_wdt_add(NULL)`, then call `esp_task_wdt_reset()` (needs `#include "esp_task_wdt.h"`) as often as you like inside the loop. It resets the watchdog's countdown directly, no need to hand control back to anything.

## Step 4 — show it on a display

Add a Print command from your display component straight after the Switch icon, and pass it `LastResetReasonStr`.

One thing that catches people out: the Print icon only takes a plain variable, not text and a variable combined in the same box. So don't try typing `"Last reset: " + LastResetReasonStr` straight into Print — it won't build. Build the full line in a separate variable first with a Calculation icon:

```
TmpStr = "Last reset: " + LastResetReasonStr
```

then pass `TmpStr` into Print instead.

## Step 5 — watching it over serial (115200 baud)

Nearly every ESP32 board and Flowcode project uses 115200 baud for its main serial port — it's the standard default for the bootloader and console. Open a serial terminal (Flowcode's Console, Arduino IDE's Serial Monitor, PuTTY, whatever you're used to) on the right COM port at 115200 and reset the board. You'll actually see two things:

The chip's own bootloader message first — something short like `rst:0x1 (POWERON_RESET)`. That's the raw, low-level version, printed automatically before your program even starts.

Then your own decoded message, if you've wired up a Print to a UART/serial component the same way as Step 4. That's the readable version you just built, and it's a lot easier to parse at a glance than the bootloader's shorthand.

If nothing shows up at all, don't panic — check the baud rate on your UART/Console component first. A mismatch just gives you garbage or silence, not an error.

## A few things worth knowing

Seeing EXT after pressing reset, or POWERON right after uploading new firmware, is completely normal — not a bug.

DEEPSLEEP only needs investigating if you're not deliberately using deep sleep. If you are, that's expected.

PANIC only tells you that it crashed, not where. Scroll up in the same serial log to just before the reset — there's usually a full crash dump there with registers and a backtrace, which is where the real detail is.

Repeated BROWNOUT is almost always a power supply issue rather than a code issue. Try a different cable, port, or a powered hub before you go digging through your flowchart. WiFi draws current in short spikes, and a marginal supply often can't keep up.

The watchdog's default timeout is 5 seconds, so a stuck loop won't actually reset the board until about 5 seconds after it got stuck — worth remembering so the timing doesn't throw you off when you're trying to work out what caused it.

One catch if you're deliberately testing TASK_WDT: don't try to print anything from inside the loop you're using to trigger it. If your UART send has any internal delay while it flushes, that delay lets other tasks run, which feeds the watchdog — and your "stuck" loop will never actually trip it. Print the decoded reason once at the top of Main (as above), then let the loop itself do nothing at all. You'll see the result on the next boot instead of live, but the timeout will actually happen.

Keep the reset-reason C Code icon as the very first thing in Main, before anything else initialises. If your board is crashing early in your normal startup, this gives you the best chance of capturing and showing the reason before the same crash repeats.

## Quick recap

Create a Byte variable called `LastResetReason`.

C Code icon at the very top of Main: `#include "esp_system.h"` then `FCV_LASTRESETREASON = (uint8_t)esp_reset_reason();`

Create a String variable called `LastResetReasonStr`, array size 70+.

A Switch icon on `LastResetReason`, with a Calculation icon in each case setting `LastResetReasonStr` to the matching message.

Print `LastResetReasonStr` to your display and/or a serial port at 115200 baud.

Now instead of guessing why your ESP32 rebooted, it'll just tell you.

I have attached a demo project that causes a software reset.
You can see the cause by looking at the very bottom row:

Code: Select all

rst:0xc (SW_CPU_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:2
load:0x3fff0030,len:7176
load:0x40078000,len:15564
ho 0 tail 12 room 4
load:0x40080400,len:4
load:0x40080404,len:3904
entry 0x40080640
I (31) boot: ESP-IDF HEAD-HASH-NOTFOUND 2nd stage bootloader
I (31) boot: compile time Dec 11 2025 23:20:49
I (32) boot: Multicore bootloader
I (36) boot: chip revision: v1.0
I (40) boot.esp32: SPI Speed      : 40MHz
I (45) boot.esp32: SPI Mode       : DIO
I (49) boot.esp32: SPI Flash Size : 2MB
I (54) boot: Enabling RNG early entropy source...
I (59) boot: Partition Table:
I (63) boot: ## Label            Usage          Type ST Offset   Length
I (70) boot:  0 nvs              WiFi data        01 02 00009000 00006000
I (78) boot:  1 phy_init         RF data          01 01 0000f000 00001000
I (85) boot:  2 factory          factory app      00 00 00010000 00100000
I (92) boot: End of partition table
I (97) esp_image: segment 0: paddr=00010020 vaddr=3f400020 size=0ba58h ( 47704) map
I (121) esp_image: segment 1: paddr=0001ba80 vaddr=3ffbdb60 size=03724h ( 14116) load
I (127) esp_image: segment 2: paddr=0001f1ac vaddr=40080000 size=00e6ch (  3692) load
I (129) esp_image: segment 3: paddr=00020020 vaddr=400d0020 size=1d11ch (119068) map
I (177) esp_image: segment 4: paddr=0003d144 vaddr=40080e6c size=0df6ch ( 57196) load
I (207) boot: Loaded app from partition at offset 0x10000
I (207) boot: Disabling RNG early entropy source...
I (219) cpu_start: Multicore app
I (227) cpu_start: Pro cpu start user code
I (227) cpu_start: cpu freq: 240000000 Hz
I (227) app_init: Application information:
I (230) app_init: Project name:     esp-project
I (235) app_init: App version:      1
I (240) app_init: Compile time:     Dec 11 2025 23:20:30
I (246) app_init: ELF file SHA256:  09fe3a54ebb35bca...
I (252) app_init: ESP-IDF:          HEAD-HASH-NOTFOUND
I (258) efuse_init: Min chip rev:     v0.0
I (262) efuse_init: Max chip rev:     v3.99
I (267) efuse_init: Chip rev:         v1.0
I (272) heap_init: Initializing. RAM available for dynamic allocation:
I (279) heap_init: At 3FFAE6E0 len 0000F480 (61 KiB): DRAM
I (285) heap_init: At 3FFC1BE0 len 0001E420 (121 KiB): DRAM
I (292) heap_init: At 3FFE0440 len 00003AE0 (14 KiB): D/IRAM
I (298) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
I (304) heap_init: At 4008EDD8 len 00011228 (68 KiB): IRAM
I (312) spi_flash: detected chip: generic
I (315) spi_flash: flash io: dio
W (319) spi_flash: Detected size(4096k) larger than the size in the binary image header(2048k). Using the size in the binary image header.
I (333) coexist: coex firmware version: 4482466
I (338) main_task: Started on CPU0
I (348) main_task: Calling app_main()

 Reset Code = 3, Reason = SW - Code called esp_restart()
Attachments
ESP Reset causes.fcfx
(17.2 KiB) Downloaded 10 times
(view online)
Martin

chipfryer27
Valued Contributor
Posts: 2126
Joined: Thu Dec 03, 2020 10:57 am
Has thanked: 474 times
Been thanked: 705 times

Re: Working out why your ESP32 keeps resetting

Post by chipfryer27 »

Hi Martin

This is incredibly helpful. Thanks for creating. I'm sure many others will appreciate it too, and it will now form my "template".

Regards

medelec35
Valued Contributor
Posts: 2438
Joined: Wed Dec 02, 2020 11:07 pm
Has thanked: 783 times
Been thanked: 844 times

Re: Working out why your ESP32 keeps resetting

Post by medelec35 »

Thanks Iain, you're welcome.
It's my go to template as well for any new ESP32 projects.
It can save hours of frustrating debugging.
Martin

mnfisher
Valued Contributor
Posts: 2180
Joined: Wed Dec 09, 2020 9:37 pm
Has thanked: 177 times
Been thanked: 1022 times

Re: Working out why your ESP32 keeps resetting

Post by mnfisher »

Thanks Martin.

You can also use the 'reason code' to do different behaviours depending on reset reason.

The esp32 / espressif toolset has a lot of useful tricks.
For example - you can set the esp32 to run a gdb stub on a crash and then check it's state / single step via UART.

For example in menuconfig -> component settings -> ESP System Settings. Set Panic handler behaviour to GDBStub on panic, and if required (ie getting wdt error) - set invoke panic handler on Task Watchdog timeout.

Then at a command line (with framework_dir/export) - idf.py -p COMn monitor - and when the program crashes if will fire up gdb... Then you can inspect state "info locals" for example

Martin

medelec35
Valued Contributor
Posts: 2438
Joined: Wed Dec 02, 2020 11:07 pm
Has thanked: 783 times
Been thanked: 844 times

Re: Working out why your ESP32 keeps resetting

Post by medelec35 »

Thanks Martin.
GDB stub sounds interesting, worthy of further investigating.
Martin

mnfisher
Valued Contributor
Posts: 2180
Joined: Wed Dec 09, 2020 9:37 pm
Has thanked: 177 times
Been thanked: 1022 times

Re: Working out why your ESP32 keeps resetting

Post by mnfisher »

It's a powerful beast... A while ago i posted about using a second esp32 as a bridge - but today I see that you can use it via a UART.
It allows single stepping (per line of code for example or s 10 ) breakpoints and a host of other features....

Post Reply