图 2 - 启动时序图
ESP32 从按下开关到设备进入"待机听唤醒词"状态,中间到底发生了多少件事?
如果上方图无法显示,点这里看 PNG 版本

一眼看懂启动顺序
整个启动序列总共约 3-8 秒(取决于 Wi-Fi 是否要走配网、OTA 是否要拉资源):
| 阶段 | 时长 | 内容 |
|---|---|---|
| Bootloader → 跳固件 | 100ms | Flash 校验 + 时钟设置 + PSRAM 上电 |
| Board 构造 | 200-400ms | 板子专属的 I²C / SPI / 显示屏 / 按键 / codec 初始化 |
| Application::Initialize | 500ms | 音频任务创建 + 状态机注册 + MCP 工具注册 |
| Application::Run + StartNetwork | 1-3s | 连 Wi-Fi 或拨 4G(视信号而定) |
| OTA 检查 + Protocol 建链 | 500-2000ms | POST 设备信息、可选下载新资源、协议 hello 握手 |
| 进入 Idle | 即时 | 状态机 → kDeviceStateIdle,唤醒词监听开始 |
三个关键设计点
1. Board 在 Application 之前构造
为什么?Application 需要先知道板子有什么硬件(麦克风/喇叭/屏幕的具体引脚),才能调对应驱动。所以 Board::GetInstance() 一定先于 Application::GetInstance().Initialize()。
2. Initialize 和 Run 是两步
Initialize():只做"准备工作"(注册回调、创建任务);Run():进入主事件循环,开始处理状态变更、网络事件、MCP 调用。
分开的好处:测试时可以 Initialize 后 mock 一些状态再 Run。
3. 网络初始化是异步的
StartNetwork() 返回后并不代表已连上 Wi-Fi——它启动连接尝试就返回。真正连上后通过回调 OnNetworkEvent(Connected) 通知 Application,Application 再决定接下来做什么(启动 OTA 任务、打开音频通道等)。
一句话讲清
"Bootloader 跳到固件 → Board 把硬件初始化好 → Application 把软件子系统接好 → 开始联网 → 网通了去 OTA 检查并建协议链 → 进 Idle 状态等用户喊它。"
关联章节
/02-main-application§2.3-2.5(Initialize / ActivationTask / InitializeProtocol)/03-state-machine(状态枚举的语义)/09-boards(Board 构造里干了啥)