ESP32 ソフトウェア 回路設計

ESP32が突然再起動する原因|リセット理由からBrownout・Watchdogを切り分ける方法

投稿日:

ESP32でプログラムを動かしていると、

「しばらく動かしていると突然再起動する」

「Wi-Fi通信を始めた瞬間にリセットする」

「処理を追加したら、ときどき最初から起動し直すようになった」

といった現象に遭遇することがあります。

突然再起動すると、つい直前に追加したプログラムを疑いたくなります。

しかしESP32が再起動する原因は、プログラムだけとは限りません。

例えば、

  • 電源電圧の低下
  • Watchdog
  • CPU例外
  • Stack関連の異常
  • Heap破損
  • ソフトウェアからの再起動

などでも再起動します。

そこで重要なのが、

「なぜ再起動したのか」を最初に確認すること

です。

今回はESP32-C5とESP-IDFを例に、リセット理由を取得しながら原因を切り分ける方法を紹介します。

最初にリセット理由を確認する

ESP-IDFには、直前のリセット理由を取得するための

esp_reset_reason()

が用意されています。

突然再起動する現象が発生したら、まずこの情報を確認します。

例えば、

ESP_RST_BROWNOUT

なら電源側を疑う。

ESP_RST_TASK_WDT

ならTask Watchdogを疑う。

ESP_RST_PANIC

なら例外やPanic発生時のログを確認する。

このように、リセット理由が分かるだけでも調査範囲をかなり狭められます。

サンプルコード

今回は起動時に直前のリセット理由を取得し、ログへ表示するサンプルを使用します。

さらに現在のFree Heapと、起動後に記録されたMinimum Free Heapも定期的に表示します。

以下が今回使用するサンプルコード全文です。

#include <stdio.h>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

#include "esp_system.h"
#include "esp_log.h"

static const char *TAG = "RESET_CHECK";

static const char *reset_reason_to_string(
    esp_reset_reason_t reason)
{
    switch (reason)
    {
        case ESP_RST_POWERON:
            return "POWERON";

        case ESP_RST_SW:
            return "SOFTWARE";

        case ESP_RST_PANIC:
            return "PANIC";

        case ESP_RST_INT_WDT:
            return "INTERRUPT_WDT";

        case ESP_RST_TASK_WDT:
            return "TASK_WDT";

        case ESP_RST_WDT:
            return "OTHER_WDT";

        case ESP_RST_DEEPSLEEP:
            return "DEEPSLEEP";

        case ESP_RST_BROWNOUT:
            return "BROWNOUT";

        case ESP_RST_USB:
            return "USB";

        case ESP_RST_JTAG:
            return "JTAG";

        case ESP_RST_EFUSE:
            return "EFUSE_ERROR";

        case ESP_RST_PWR_GLITCH:
            return "POWER_GLITCH";

        case ESP_RST_CPU_LOCKUP:
            return "CPU_LOCKUP";

        case ESP_RST_UNKNOWN:
        default:
            return "UNKNOWN";
    }
}

static void print_reset_reason(void)
{
    esp_reset_reason_t reason;

    reason = esp_reset_reason();

    ESP_LOGI(
        TAG,
        "Reset reason: %s (%d)",
        reset_reason_to_string(reason),
        (int)reason);
}

static void print_memory_status(void)
{
    ESP_LOGI(
        TAG,
        "Free heap: %lu bytes",
        (unsigned long)esp_get_free_heap_size());

    ESP_LOGI(
        TAG,
        "Minimum free heap: %lu bytes",
        (unsigned long)esp_get_minimum_free_heap_size());
}

void app_main(void)
{
    ESP_LOGI(
        TAG,
        "ESP32 reset check start");

    print_reset_reason();

    while (1)
    {
        print_memory_status();

        vTaskDelay(pdMS_TO_TICKS(5000));
    }
}

このサンプルを実行すると、起動時に直前のリセット理由を表示します。

その後は5秒ごとにHeapの状態を表示します。

esp_reset_reason()で原因候補を絞る

先ほどのサンプルコードでは、print_reset_reason()内でリセット理由を取得しています。

static void print_reset_reason(void)
{
    esp_reset_reason_t reason;

    reason = esp_reset_reason();

    ESP_LOGI(
        TAG,
        "Reset reason: %s (%d)",
        reset_reason_to_string(reason),
        (int)reason);
}

esp_reset_reason()の戻り値はesp_reset_reason_tです。

代表的なリセット理由には、

  • ESP_RST_POWERON
  • ESP_RST_SW
  • ESP_RST_PANIC
  • ESP_RST_INT_WDT
  • ESP_RST_TASK_WDT
  • ESP_RST_WDT
  • ESP_RST_BROWNOUT
  • ESP_RST_PWR_GLITCH
  • ESP_RST_CPU_LOCKUP

などがあります。

ここで大切なのは、

リセット理由そのものが原因箇所を完全に教えてくれるわけではない

という点です。

例えばESP_RST_TASK_WDTなら「Task Watchdogによってリセットされた」ことは分かります。

しかし、どの処理がCPUを長時間占有したのかまでは、別途ログやTaskの処理内容を調べる必要があります。

リセット理由は原因を特定する答えというより、

次にどこを調べるべきかを決める情報

として使います。

Brownoutなら、まず電源を疑う

リセット理由がESP_RST_BROWNOUTだった場合は、プログラムを変更する前に電源を確認します。

ESP32-C5にはBrownout Detectorがあり、電源電圧が安全なレベルを下回るとリセットする仕組みがあります。

例えば、

  • Wi-Fi送信開始時
  • BLE動作開始時
  • 外部回路をONした瞬間
  • LEDやリレーなどの負荷を切り替えた瞬間

だけ再起動するのであれば、電源電圧の瞬間的な低下も疑います。

ここで、

「Wi-Fi処理を追加したら再起動した」

という理由だけで、

Wi-Fiプログラムにバグがある

と判断してはいけません。

Wi-Fiを動かした結果、消費電流が変化し、電源側の問題が表面化した可能性もあります。

Brownout発生時は電源波形を見る

Brownoutが疑われる場合は、3.3V電源をテスターだけで確認して終わらせない方がよい場合があります。

テスターで3.30Vと表示されていても、非常に短い時間だけ電圧が低下している可能性があります。

このような瞬間的な電圧変動は、テスターでは確認できない場合があります。

そのためBrownoutが発生しているなら、

オシロスコープでESP32直近の3.3V電源を確認する

のが有効です。

確認したいポイントは、

  • 電源ICの供給能力
  • 3.3Vラインの電圧低下
  • デカップリングコンデンサ
  • 電源配線
  • GND
  • 負荷切替時の電圧変動

などです。

Watchdogによるリセット

リセット理由がESP_RST_TASK_WDTESP_RST_INT_WDTの場合はWatchdogを疑います。

Watchdogは、プログラムが正常に処理を続けられなくなったときに、システムが永久に停止してしまうことを防ぐための仕組みです。

例えばTask内で長時間処理を続け、他のTaskへ実行権を渡せない状態になると、Task Watchdogが発生する可能性があります。

while(1)そのものが悪いわけではない

ESP32のプログラムではwhile (1)という構造をよく使用します。

重要なのはwhile (1)があることではなく、

ループ内部でどのような処理をしているか

です。

今回のサンプルコードでは次のようになっています。

while (1)
{
    print_memory_status();

    vTaskDelay(pdMS_TO_TICKS(5000));
}

vTaskDelay()によってTaskをBlock状態へ移しているため、CPUを永久に占有するループにはなっていません。

一方、長時間CPUを占有し続ける処理では、Task構成やWatchdog設定によって問題になる可能性があります。

したがってWatchdogが発生した場合は、

「どのTaskが、どれくらい長く処理を続けているのか」

を確認します。

Panicの場合は再起動前のログを見る

リセット理由がESP_RST_PANICだった場合は、再起動後の理由だけを見るのではなく、

再起動する直前に出力されたログ

が非常に重要です。

ESP-IDFではCPU例外などが発生すると、Guru Meditation Errorとともに原因やBacktraceなどが出力される場合があります。

例えば、

  • Illegal Instruction
  • Load/Store Prohibited
  • Load/Store Alignment Error
  • Stack Overflow
  • Heap Corruption

などです。

この場合、「ESP32が勝手に再起動した」のではなく、

Panic Handlerが異常を検出し、その結果として再起動した

と考えます。

Heapの状態も一緒に確認する

今回のサンプルでは、Free Heapも定期的に表示しています。

static void print_memory_status(void)
{
    ESP_LOGI(
        TAG,
        "Free heap: %lu bytes",
        (unsigned long)esp_get_free_heap_size());

    ESP_LOGI(
        TAG,
        "Minimum free heap: %lu bytes",
        (unsigned long)esp_get_minimum_free_heap_size());
}

esp_get_free_heap_size()では現在利用可能なHeap量を確認できます。

また、esp_get_minimum_free_heap_size()では、起動してから現在までで最も少なかったFree Heap量を確認できます。

例えば長時間運転してFree Heapが継続的に減少しているのであれば、Memory Leakなどを疑う材料になります。

ただし、Free Heapが減っただけでMemory Leakと断定してはいけません。

システムや各Componentが必要に応じてMemoryを確保している場合もあります。

ここでも重要なのは、

時間とともにどのように変化しているかを見ること

です。

再起動したら、まず分類する

突然再起動したときは、最初からすべてを疑うのではなく、まず大きく分類します。

Brownout → 電源を確認

Watchdog → Task・割り込み・長時間処理を確認

Panic → 再起動直前のログとBacktraceを確認

その他 → 発生条件とReset Reasonを確認

BrownoutなのにTask処理を延々と調べても、なかなか原因にはたどり着けません。

逆にTask Watchdogなのに電源回路だけを調べても同じです。

最初に現象を分類する。

これが突然再起動する問題を切り分ける第一歩です。

Task Watchdogが出たら何を見るか

前半では、esp_reset_reason()を使って再起動の原因を大きく分類しました。

ここからは、それぞれのリセット理由が確認できたあと、具体的に何を調べればよいのかを見ていきます。

まずはTask Watchdogです。

ESP_RST_TASK_WDTが確認できた場合、

「Watchdogを無効にすれば直る」

と考えるのはおすすめできません。

Watchdogは問題そのものではなく、プログラムが正常に処理を継続できていないことを知らせている可能性があるからです。

最初に確認したいのは、

  • 長時間終了しない処理
  • Blockしないループ
  • Task優先度
  • 他Taskへ実行機会を与えているか
  • MutexやSemaphoreの待ち状態
  • 処理時間が想定以上に長くなっていないか

などです。

vTaskDelay()の意味を確認する

前半で掲載したサンプルコードでは、メインループを次のようにしています。

while (1)
{
    print_memory_status();

    vTaskDelay(pdMS_TO_TICKS(5000));
}

ここで重要なのは、単に5秒待っていることではありません。

vTaskDelay()を実行すると、そのTaskは指定時間だけBlock状態になります。

その間、Schedulerは他の実行可能なTaskへCPU時間を割り当てられます。

一方、

while (1)の中で重い計算を延々と実行する、

非常に長い処理から戻ってこない、

高優先度TaskがCPUを占有し続ける、

といった構造では、他のTaskやIdle Taskが十分に実行されない可能性があります。

Watchdogが発生した場合は、

Watchdogの時間を長くする前に、CPUを占有している処理がないか確認する

ことを優先します。

Interrupt WatchdogはTask Watchdogと分けて考える

ESP_RST_INT_WDTESP_RST_TASK_WDTは、同じWatchdogでも調査対象が異なります。

Task WatchdogならTask処理を中心に確認します。

一方、Interrupt Watchdogが発生している場合は、

  • Interruptを長時間Disableしていないか
  • Critical Sectionが長すぎないか
  • ISR内で時間のかかる処理をしていないか

などを確認します。

割り込み処理では、

「ISRの中ですべてを処理する」

のではなく、

「ISRでは必要最低限の処理だけ行い、重い処理はTask側へ渡す」

という構成が基本になります。

PanicではReset Reasonだけで終わらない

次にESP_RST_PANICだった場合です。

この場合、Reset Reasonから分かるのは、

Panicによって再起動した

というところまでです。

本当に欲しい情報は、再起動する直前のログにあります。

ESP-IDFでは異常発生時に、例外の種類やRegister Dump、Backtraceなどが出力される場合があります。

例えば、

LoadProhibited

が表示されていた場合は、不正なMemory AddressへのReadを疑います。

StoreProhibited

なら、不正なMemory AddressへのWriteを疑います。

このように、Panicの種類によって確認すべきコードが変わります。

Backtraceを見る

Panic発生時にはBacktraceが表示されることがあります。

Backtraceから、

「どの関数を通って異常箇所へ到達したのか」

を追える場合があります。

ESP-IDF Monitorを使用している環境では、BacktraceのAddressからソースコード上の関数名や行番号へ変換された情報を確認できる場合があります。

ここで大切なのは、

最後に表示された関数だけを原因と決めつけないこと

です。

例えば不正なPointerを別の関数で生成し、そのPointerを後から使用した場所で例外が発生するケースがあります。

つまり、

異常が発生した場所と、異常を作り込んだ場所が同じとは限りません。

Backtraceを手掛かりに、その少し前の処理まで確認します。

Brownoutなら「発生する瞬間」を探す

Brownoutの場合は、電源電圧だけを見るのではなく、

何をした瞬間にBrownoutが発生するのか

を確認すると原因を絞りやすくなります。

例えば、

  • Wi-Fi送信開始
  • BLE通信開始
  • 外部回路ON
  • モーター駆動開始
  • リレー切替
  • LED点灯
  • USB接続状態の変化

などです。

「30分に1回再起動する」

という情報だけより、

「Wi-Fi送信開始直後に再起動する」

という情報の方が、はるかに原因を探しやすくなります。

ESP32直近で3.3Vを測る

電源波形を測定するときは、できるだけESP32の電源端子に近い場所で確認します。

電源ICの出力が正常でも、

配線抵抗、

パターンインピーダンス、

コネクタ、

GND、

負荷変動、

などによってESP32側の電圧が変動する可能性があります。

特に自作基板では、

「電源ICの出力は3.3Vだった」

だけで判断せず、

「ESP32が実際に受け取っている3.3Vはどうなっているか」

を見ることが重要です。

Heapは「現在値」だけでは判断しない

ESP_LOGI(
    TAG,
    "Free heap: %lu bytes",
    (unsigned long)esp_get_free_heap_size());

ESP_LOGI(
    TAG,
    "Minimum free heap: %lu bytes",
    (unsigned long)esp_get_minimum_free_heap_size());

ここで注目したいのは、単純なFree Heapの大きさだけではありません。

例えば起動直後に100kBあり、しばらくして80kBになったとしても、それだけでMemory Leakとは判断できません。

一方、

100kB
95kB
90kB
85kB
80kB
75kB

のように、特定操作を繰り返すたびに戻らず減少しているのであれば、調査する価値があります。

例えば、

接続する
→ Memory確保

切断する
→ Memory解放

という処理を繰り返したとき、確保したMemoryが適切に解放されているか確認します。

「何MB残っているか」ではなく「同じ操作を繰り返したときどう変化するか」

を見るのがポイントです。

HeapとStackを混同しない

ESP32のMemory問題を調査するときに注意したいのが、HeapとTask Stackは同じものではないという点です。

Free Heapに余裕があっても、特定Taskへ割り当てたStackが不足する可能性があります。

逆にTask Stackに余裕があっても、動的Memory確保を繰り返してHeap側で問題が発生することがあります。

そのため、

Free Heapが十分あるからMemory問題ではない

とは断定できません。

Task Stackが疑わしい場合は、FreeRTOSのStack High Water Markなども使って確認します。

ソフトウェアResetも確認する

再起動が発生した場合、ハードウェア異常とは限りません。

前半のサンプルコードではesp_reset_reason()の結果を文字列へ変換しています。

case ESP_RST_SW:
    return "SOFTWARE";

ESP_RST_SWが確認できた場合は、ソフトウェアからResetされた可能性を確認します。

例えばアプリケーション側で異常を検出した際に、意図的に再起動する設計になっている場合があります。

この場合、

「なぜESP32がResetしたのか」

ではなく、

「なぜプログラムがResetを要求したのか」

を追います。

ここでもReset Reasonを最初に確認していれば、電源やWatchdogを必要以上に調査せずに済みます。

再現条件を作る

原因調査で非常に重要なのが、再現条件です。

例えば、

「たまに再起動する」

だけでは調査が難しくなります。

そこで条件を変えてみます。

  • Wi-Fiを使用すると発生するか
  • Wi-Fiを停止すると発生しないか
  • 外部負荷を外すとどうなるか
  • 通信周期を変更するとどうなるか
  • 特定処理を繰り返すと発生するか
  • 数分なのか数時間なのか
  • 電源投入直後なのか長時間運転後なのか

こうして、

「何をすると発生し、何をすると発生しないのか」

を探します。

原因そのものがまだ分からなくても、再現条件が分かれば調査はかなり進みます。

一度に複数の条件を変えない

原因調査中に、

電源を変更する。

プログラムも変更する。

通信速度も変更する。

Task構成も変更する。

これを一度に行うと、問題が解消しても、

何が原因だったのか分からなくなります。

基本的には、一度に変更する条件をできるだけ少なくします。

例えばBrownoutが疑わしいなら、

まず電源波形を測定する。

次に電源条件を変更する。

それでも発生するなら次の条件を見る。

というように進めます。

現場では「再起動した」より前の情報が重要

突然再起動するトラブルでは、Reset後のESP32だけを見ても情報が少ないことがあります。

重要なのは、

再起動する直前に何が起きていたのか

です。

そのため開発中は、最低限でも、

  • 起動ログ
  • Reset Reason
  • 主要処理の開始・終了
  • Error
  • 通信状態
  • Memory状態

などをログとして残しておくと、後から原因を追いやすくなります。

特に再現頻度が低い問題では、

問題が起きてからログを追加するのでは遅い

ことがあります。

製品開発では、正常時には邪魔にならず、異常時には調査材料になるログ設計も重要です。

ESP32が突然再起動したときの切り分け手順

ここまでを実際の調査順にすると、次のようになります。

1. Reset Reasonを確認する

まずesp_reset_reason()で大分類します。

2. 再起動直前のログを保存する

PanicやWatchdogの情報が残っていないか確認します。

3. 発生条件を整理する

通信開始時、負荷切替時、長時間運転後など、再現条件を探します。

4. Brownoutなら電源波形を確認する

ESP32直近の3.3Vをオシロスコープで確認します。

5. WatchdogならTask・ISRを確認する

長時間処理、CPU占有、Critical Sectionなどを確認します。

6. PanicならBacktraceを確認する

異常発生箇所だけでなく、そこへ至った処理も追います。

7. Memoryを疑う場合は時間変化を見る

HeapやStackの余裕を継続的に確認します。

8. 一つずつ条件を変更する

変更結果から原因候補を減らします。

この順序なら、

「とりあえずコードを書き換えてみる」

よりも、原因を残しながら調査できます。

まとめ

ESP32が突然再起動すると、ソフトウェアのバグを最初に疑いたくなります。

しかし実際には、

Brownout
Watchdog
Panic
Memory
Software Reset

など、さまざまな原因があります。

そこで最初に行うのが、

Reset Reasonの確認です。

原因を推測して修正するのではなく、

観測する
→ 分類する
→ 再現する
→ 一つずつ切り分ける

という順序で調査します。

特に自作基板では、プログラムだけでなく電源やGND、負荷変動まで含めて考える必要があります。

ESP32が再起動したという結果だけを見るのではなく、

「再起動する直前に何が起きたのか」

を残しておくことが、原因特定への近道です。

技術は、経験から価値になる。

DLROW Design | 現場エンジニア

スポンサーリンク

スポンサーリンク

-ESP32, ソフトウェア, 回路設計

Copyright© 江藤良樹の仕事は物づくり , 2026 All Rights Reserved.