每次按下复位键,ESP32 都会先喷出三十来行字。它们滚得太快,又长得像乱码, 所以几乎所有人的第一反应都是「噪音,忽略」。我看它们看了两年,也是这么想的—— 直到有一天,一块板子开始每隔几分钟自己重启,而我手上没有任何调试器。

那一次我才发现:这三十行里藏着芯片型号、晶振频率、内存余量、Flash 分区和编译参数, 全部是硬件层面的信息,不用写一行代码就能读到。它是这台设备失控之前,唯一愿意开口说话的时刻。

下面按出现的顺序,逐段拆开。

01第一行就是个陷阱

uart0 · 115200
ets Jul 29 2019 12:21:46

2019 年 7 月 29 日。看到这行的人常有两个误解:以为是固件的编译日期,或者以为自己不小心烧录了一个三年前的旧版本。

都不是。这是 bootloader 的编译时间戳——这一段代码由 Espressif 出厂时就固化在芯片里, 他们多年没有改动过它,所以这个日期在你的板子上、在我的板子上、在别人刚拆封的新板子上,完全一样。 它不是一个会变化的信息,看到它就当没看到。

真正的固件编译时间是后面 Software Info 里的那一行,我们等会儿会走到那里。

02复位原因与启动模式

bootloader
rst:0x1 (POWERON_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:1

rst: 后面这个十六进制数是复位原因0x1 是上电复位,也就是你拔插电源、或者按下复位键时会看到的那种;除此之外还有软件复位、 看门狗复位、欠压复位等等。这一行是排查「设备莫名其妙重启」的起点: 如果是看门狗,说明你的某段代码把 CPU 卡住了;如果指向欠压,那就去查电源—— 我在另一篇笔记里就是靠这个思路找到电源问题的。 完整的码表在 ESP-IDF 的 esp_reset_reason() 文档里,不用背。

boot:0x13 是启动模式,括号里的 SPI_FAST_FLASH_BOOT 才是给人看的部分—— 意思是「从 SPI Flash 里快速启动」,这是正常工作时的取值。 如果你把它做成产品,客户不小心把某个引脚拉低了,这里会变成别的模式, 然后就表现为「板子通电没反应」。

中间那两行 configsipclk_drv 之类,是 bootloader 读到的 Flash 配置 和各信号线的驱动能力。它们平时是固定的,只有在换了 Flash 芯片或者改了硬件之后才会变—— 所以它们的用途是「记住正常时的样子」,将来出问题了拿来对比。

最后 mode:DIO, clock div:1 值得注意:bootloader 阶段这里用的是 DIO(双线), 而几分钟后你会看到 Flash Info 里写的是 QIO(四线)。 这不矛盾——ROM 阶段还没能力协商,只好用最保守的方式先把代码读进来,之后才切到更快的模式。

03三段 load:镜像有没有坏

bootloader
load:0x3fff0030,len:4876
    load:0x40078000,len:16560
    load:0x40080400,len:3500
    entry 0x400805b4

这是 bootloader 把自己从 Flash 搬进内存的过程:地址、长度、三段。 平时完全不用管,但有一种情况例外——如果这三行里出现校验失败或者长度异常,说明 Flash 里的镜像损坏了, 常见原因是烧录到一半拔线、或者供电不稳写坏了页。

entry 是跳转到应用程序入口的地址。看到它出现,就意味着「bootloader 干完了,接下来是固件的事」。

所以判断一个「通电没反应」的板子卡在哪一层,看日志停在哪一行就知道了: 停在 rst: 附近是 bootloader 阶段的问题,停在 entry 之后就是固件的问题。

04芯片信息:你手上到底是什么

Chip Info
  Model             : ESP32
      Package           : D0WD-Q5
      Revision          : 3.01
      Cores             : 2
      CPU Frequency     : 240 MHz
      XTAL Frequency    : 40 MHz
      Embedded Flash    : No
      Embedded PSRAM    : No
      2.4GHz WiFi       : Yes
      Classic BT        : Yes

Package: D0WD-Q5 给出的是这颗 die 的型号和封装代号。 它有用是因为——模组厂商在同一个「ESP32-WROOM-32」的名字下,装过不同批次的芯片, 而某些低功耗特性、蓝牙行为在修订版之间有差异。Revision: 3.01 就是芯片的修订版号。

XTAL Frequency: 40 MHz 是外部晶振。往下拉几行你会看到固件启动时打印的一句:

esp32-hal-cpu.c
[     3][D][esp32-hal-cpu.c:324] setCpuFrequencyMhz(): PLL: 480 / 2 = 240 Mhz, APB: 80000000 Hz

240 MHz 是怎么来的,日志直接告诉你了:480 MHz 的 PLL 二分频。 方括号里的 3 是开机后的毫秒数——三毫秒,它已经把主频设好了。

另外 Embedded Flash: NoEmbedded PSRAM: No 这两行, 是回答「我这颗芯片到底带不带内置 Flash / PSRAM」的最快方式, 比查数据手册快得多——尤其是当你手上只有一块来源不明的开发板时。

05内存:为什么不是 520 KB

INTERNAL Memory Info
  Total Size        :   376480 B ( 367.7 KB)
      Free Bytes        :   333984 B ( 326.2 KB)
      Allocated Bytes   :    34744 B (  33.9 KB)
      Minimum Free Bytes:   328368 B ( 320.7 KB)
      Largest Free Block:   110580 B ( 108.0 KB)

ESP32 的 SRAM 标称 520 KB,这里却只有 367.7 KB。差额不是被谁偷了—— 它是 ROM 里的代码、启动时的静态分配、以及协议栈占掉的部分。 所以这一行反映的是「系统初始化之后,留给动态分配的堆总量」,比标称值更接近你能用的现实。

这五个数字里,我认为最该盯的是最后两个,而不是 Free Bytes

  • Minimum Free Bytes 是历史最低水位。设备跑了一整天之后回来看这个值, 就知道它最紧张的时候剩下多少——这个数字比「当前还剩多少」有意义得多。
  • Largest Free Block最大的一块连续空闲。总共剩 326 KB, 但最大连续块只有 108 KB,意味着你想 malloc 一个 200 KB 的缓冲区会失败, 尽管「总空闲」看起来完全够。内存碎片就是这么咬人的。

那一次我遇到的「跑几小时就自己重启」,最后就是靠这两行定位的: Free 一直在 300 KB 以上,看着很健康,但 Largest Free Block 从 108 KB 一路缩到 30 多 KB, 最后某个需要连续内存的操作失败,触发了复位。

06分区表:一本地址账本

这是我后来觉得最值得看的一段。它把整块 Flash 按地址切给了不同用途:

Partitions Info
                nvs : addr: 0x00009000, size:    20.0 KB, type: DATA, subtype: NVS
                otadata : addr: 0x0000E000, size:     8.0 KB, type: DATA, subtype: OTA
                   app0 : addr: 0x00010000, size:  1280.0 KB, type:  APP, subtype: OTA_0
                   app1 : addr: 0x00150000, size:  1280.0 KB, type:  APP, subtype: OTA_1
                 spiffs : addr: 0x00290000, size:  1408.0 KB, type: DATA, subtype: SPIFFS
               coredump : addr: 0x003F0000, size:    64.0 KB, type: DATA, subtype: COREDUMP

把地址当成账目来核对一遍,你会发现它严丝合缝:

  • app00x010000(64 KB)开始,占 1280 KB,结束于 64 + 1280 = 1344 KB = 0x150000
  • 这正好是 app1 的起始地址。app1 再加 1280 KB,到 2624 KB = 0x290000
  • 这正好是 spiffs 的起始地址。spiffs 加 1408 KB,到 4032 KB = 0x3F0000
  • 这正好是 coredump 的起始地址。再加 64 KB ——刚好 4096 KB,也就是 4 MB

一个字节不多,一个字节不少。这说明分区表不是随便填的,它是按芯片容量精算出来的。 看懂这本账,你就能回答两个很实际的问题:

第一,我的固件还能长多大? app0 和 app1 各 1280 KB, 意味着你的固件必须同时塞进这两个槽(OTA 需要备份槽),所以上限就是 1280 KB。 编译时如果报 Sketch too big,数字就是从这里来的。

第二,文件存储还剩多少? spiffs 的 1408 KB 就是你能用来存图片、 日志、配置文件的全部空间。它不会随着你的固件变大而变大——它是固定的。

顺带一提,coredump 那 64 KB 是留给崩溃现场快照的。 如果它在账本里被删掉了,你的设备崩溃时就不会留下可分析的转储。

07最后几行:找出日志为什么这么多

Software / Board Info
  Compile Date/Time : Aug 21 2026 01:37:10
      Compile Host OS   : windows
      ESP-IDF Version   : v5.5.4
      Arduino Version   : 3.3.10

      Arduino FQBN      : esp32:esp32:esp32:UploadSpeed=921600,CPUFreq=240,
                          FlashFreq=80,FlashMode=qio,FlashSize=4M,PartitionScheme=default,
                          DebugLevel=verbose,PSRAM=disabled,LoopCore=1,EventsCore=1,EraseFlash=none

Compile Date/Time 才是真正的固件编译时间。 「我明明改了代码,怎么行为没变?」——先看这一行。它没变,说明你烧的不是你以为的那个固件。

而 FQBN 那一长串,是整个启动日志里最实用的一行,因为它把你的编译选项全部抄了一遍: 上传波特率、主频、Flash 频率与模式、Flash 容量、分区方案,一个不落。

如果你好奇「为什么我的串口刷得这么快、全是 [V] 开头的行」—— 答案就在里面:DebugLevel=verbose。这是 Arduino IDE 里 Tools → Core Debug Level 被设成了 verbose 的结果。改成 None, 你的串口立刻安静下来,只剩你自己打印的东西:

verbose 级别的日志长这样
[   529][V][esp32-hal-uart.c:807] uartSetPins(): UART0: Driver not yet installed,
                                    storing pins for later attachment (RX:3, TX:1)
    [   666][D][esp32-hal-uart.c:882] uartSetPins(): Attaching pin 3 to UART0 as RX
    [   673][V][esp32-hal-periman.c:173] perimanSetPinBus(): Pin 3 successfully set to
                                    type UART_RX (2) with bus 0x3ffbdb68

这些行在说「把 GPIO3 接到 UART0 的 RX 上」,对调试外设冲突其实有用—— 它能告诉你某个引脚被谁占用了。但如果你不需要,它就是纯占地方。

08现在我会怎么看这三十行

按顺序扫四个地方就够了:

  1. rst: —— 设备是为什么醒过来的。这是排查重启类问题的唯一入口。
  2. Largest Free Block —— 内存还有没有可能分配大块。别只看 Free。
  3. Partitions Info —— 固件和数据还有多少空间。算一遍地址账。
  4. FQBNCompile Date/Time —— 现在跑的到底是哪一个固件、带什么编译选项。

这四件事都不需要额外的工具,插上 USB 就能读。 对一个没有调试器、板子又开始不听话的下午来说,这已经是很好的起点了。

关于本文
整理自 2026 年 8 月的一段串口记录与当时的排查笔记。日志原文来自 ESP32-WROOM-32E(ESP-IDF v5.5.4 / Arduino 3.3.10),未作删改。
2026.09.20 整理
补上了 0x3F0000 + 64 KB = 0x400000 的地址核对过程——当初只是觉得「数字对得上」,没有真的算过。