本系列开始将会回顾一下之前所学的esp32s3驱动的一些外设以及wifi和蓝牙等,主要是贴一些代码用来重温所学的知识,大体内容基本也都是一笔带过
一,开发环境
我使用的是vscode安装的esp-ide这个插件也就是乐鑫官方推出的,主要有两个环境需要配置一下

具体的配置环境内容也可以去b站搜一下,这些都有很多,这里不再赘述
二,工程目录

这个.devcontainer文件夹的作用是,如果我们远程连接到某一台服务器上进行开发的话,这个文件夹就可以确保所有开发者都在相同配置的环境中工作,避免了在我的机器上能运行,在你的机器上就不能运行的问题
这个.clangd他是用来代码的自动补齐和提示的

我们可以打开我图中的路径下的文件夹看一下


三,存储分布分析

这张图是ESP32-S3 的内存地址映射全景图 ,它把芯片的物理内存(内部 / 外部、高速 / 低速)通过 指令总线(Instruction bus)和数据总线(Data bus)映射到不同的虚拟地址段,
先掌握 3 个前置核心概念(理解图的前提)
| 概念 | 定义 | 关键作用 |
|---|---|---|
| 指令总线(Instruction bus) | 传输 CPU 要执行的代码指令的总线 | 对应内存区域可执行代码,要求 4 字节对齐访问(仅能按 4 字节块读写) |
| 数据总线(Data bus) | 传输 CPU 处理的数据的总线 | 对应内存区域仅存数据,不可执行,支持字节级访问(可单独读写 1 字节) |
| MMU + Cache | MMU(内存管理单元)负责虚拟地址→物理地址映射;Cache 缓存外部内存的常用数据 | 让外部 Flash/PSRAM 可以像内部内存一样被 CPU 访问,同时通过 Cache 提升速度 |
图中 1-10 号分区
| 编号 | 地址范围 | 大小 | 总线类型 | 对应内存区域 | 核心用途 | 关联知识点 |
|---|---|---|---|---|---|---|
| 1 | 0x3C000000~0x3DFFFFFF |
32MB | 数据总线 | 外部内存(Flash/PSRAM) | 存放大容量数据或溢出代码 | 需 MMU 映射,配合 Cache 加速 |
| 2 | 0x3FC80000~0x3FCFFFFF |
80KB | 数据总线 | 内部 SRAM | 存放动态数据(堆、栈、全局变量) | ESP-IDF 默认malloc的分配区域 |
| 3 | 0x3FF00000~0x3FF1FFFF |
128KB | 数据总线 | 内部 SRAM | 存放动态数据(补充堆、缓冲区) | 速度最快的内存区域之一 |
| 4 | 0x40000000~0x4005FFFF |
384KB | 指令总线 | 内部 ROM | 固化第一阶段引导程序(ROM Bootloader) | 芯片上电后第一个执行代码的地方 |
| 5 | 0x40370000~0x403DFFFF |
448KB | 指令总线 | 内部 SRAM | 存放可执行代码(如中断函数、高频代码) | 第二阶段引导程序(Flash Bootloader)的加载区域 |
| 6 | 0x42000000~0x43FFFFFF |
32MB | 指令总线 | 外部内存(Flash/PSRAM) | 存放超出内部容量的代码 | 需 MMU 映射,代码执行依赖 Cache |
| 7 | 0x50000000~0x50001FFF |
8KB | 数据 / 指令总线 | RTC Slow 内存 | 深度睡眠时保留数据(如睡眠唤醒参数) | 由备用电源供电,数据不丢失 |
| 8 | 0x600F_E000~0x600F_FFFF |
8KB | 数据 / 指令总线 | RTC Fast 内存 | 存放睡眠唤醒后快速执行的代码 | 低功耗场景专用,速度比 RTC Slow 快 |
| 9 | - | 384KB | 指令总线 | 内部 ROM | 物理 ROM 硬件,对应 4 号地址段 | 出厂固化,不可修改 |
| 10 | - | 512KB | 数据 / 指令总线 | 内部 SRAM 总容量 | 物理 SRAM 硬件,被拆分为 2、3、5 号地址段 | 核心高速内存,优先用于关键代码 / 数据 |
总线与内存核心规则
- 指令总线内存 → 可执行、4 字节对齐
- 代码(如
.text段、中断函数)必须放到指令总线内存(如 4 号、5 号、6 号)才能被 CPU 执行 - 示例:用
IRAM_ATTR修饰中断函数,就是强制将函数放到 5 号内部 SRAM 指令总线
- 代码(如
- 数据总线内存 → 不可执行、字节访问
- 动态数据(堆、栈、全局变量)必须放到数据总线内存(如 2 号、3 号、1 号)
- 示例:
malloc默认分配 2 号 / 3 号内部 SRAM 数据总线的内存
- 外部内存 → 依赖 MMU+Cache
- 1 号 / 6 号外部内存需要 MMU 做虚拟地址映射,CPU 通过 Cache 访问
- 速度比内部内存慢,适合存放非高频访问的代码 / 数据


四,ESP32-S3 启动引导流程图


一、宏观总纲:ESP32-S3 启动流程的 3 个核心阶段(官方定义)
整个流程是 **"单向递进、层层接管"的关系,前一个阶段完成后才会跳转到下一个阶段,且只有 Flash 二级引导和应用程序阶段可配置 / 修改 **,ROM 一级引导是芯片出厂固化的,不可改动。
上电/复位 → 【ROM一级引导】(固化不可改,PRO CPU独跑)→ 加载【Flash二级引导】(可配置,IDF源码可改)→ 加载【应用程序】(用户开发核心)→ 启动FreeRTOS → 执行app_main
核心特点 :ESP32-S3 是双核芯片,全程由 PRO CPU(CPU0)主导启动,APP CPU(CPU1)在应用程序阶段才会被解除复位、启动运行,这是理解所有细节的前提。
二、阶段 1:ROM 一级引导加载程序(第一阶段,固化在内部 ROM,不可改)
对应内存映射4 号区域(0x40000000~0x4005FFFF) ,是芯片上电 / 复位后第一个执行代码的地方,源码由乐鑫写死在芯片 ROM 中,用户无法修改 / 访问,核心作用是 **"判断启动模式、初始化基础硬件、加载 Flash 二级引导到内部 SRAM"**。
2.1 核心前置动作
- SoC 复位后,PRO CPU 立即运行 ,执行 ROM 中的「复位向量代码」;APP CPU 保持复位状态,直到应用程序阶段才会被唤醒。
- 复位向量代码会读取GPIO_STRAP_REG 寄存器 的值(该寄存器保存了复位后 GPIO 引导引脚的电平),以此判断启动模式(如正常 Flash 启动、UART 下载模式、深度睡眠唤醒等)。
2.2 核心逻辑:根据「复位原因」执行不同操作(官方重点)
ROM 引导会先识别芯片的复位原因,再执行对应的逻辑,覆盖所有实际使用场景,正常开发中最常见的是「上电复位」和「深度睡眠唤醒复位」:
- 深度睡眠唤醒复位 :优先检查 RTC 内存中的 "深度睡眠存根代码",如果校验有效,直接跳转到 RTC 内存执行(无需走完整启动流程,降低唤醒功耗);校验无效则按「上电复位」执行。
- 实用价值:开发低功耗项目时,可通过「深度睡眠存根机制」自定义唤醒后的代码,跳过繁琐的启动流程,提升唤醒速度。
- 上电 / 软件 / 看门狗 SoC 复位:检查 GPIO 引导引脚电平,判断是否为「UART 下载模式」(如烧录程序时的模式);若是则进入下载模式,否则按「CPU 复位」执行。
- 软件 / 看门狗 CPU 复位 :初始化 SPI Flash(根据 Efuse 中的硬件配置),从 Flash 的0x00000 偏移地址 读取Flash 二级引导程序 ,校验通过后加载到内部 SRAM 指令总线(5 号区域,0x40370000),然后跳转到二级引导的入口地址,完成自身使命。
2.3 官方重点注意事项
- 正常启动模式下会自动使能RTC 看门狗:如果 ROM 引导流程被中断 / 卡死,看门狗会自动复位 SoC,重新执行启动流程,防止芯片死机。
- Flash 二级引导的加载偏移:ESP32-S3 的二级引导固定从 Flash0x00000偏移加载,其前 8KB 扇区为密钥管理器保留(用于 Flash 加密 / 安全引导),这是硬件层面的规定。
三、阶段 2:Flash 二级引导加载程序(第二阶段,可配置 / 修改,IDF 开源)
对应内存映射5 号区域(0x40370000~0x403DFFFF) ,源码在 ESP-IDF 的components/bootloader目录下,是用户可配置、可修改 的引导程序,核心作用是 **"初始化系统硬件、解析分区表、选择启动分区、加载应用程序镜像到对应内存"。 这一阶段对应你启动日志中 I (24) 到 I (145)的内容,是日志中第一个可看到的引导阶段 **。
3.1 核心前置动作
ROM 引导将二级引导加载到内部 SRAM 并跳转后,PRO CPU 开始执行二级引导代码,此时仍独占所有硬件资源,APP CPU 还是复位状态。
3.2 核心执行步骤(官方定义,与日志一一对应)
- 基础硬件初始化:初始化 MMU、Flash Cache、UART(配置为 115200 波特率,所以你能看到日志)、时钟,关闭 ROM 阶段的 RTC 看门狗(可在 menuconfig 中重新开启)。
- 读取并解析分区表 :从 Flash 的0x08000 偏移地址 读取分区表(对应日志
I (46) boot: Partition Table:),解析出所有分区(nvs/phy_init/factory/OTA 等)。- 关键作用:分区表是 Flash 的 "目录",二级引导通过它找到应用程序分区(如 factory 出厂分区、OTA 升级分区)。
- 选择要启动的应用分区 :
- 无 OTA 时:直接选择factory 分区 (对应日志
I (68) boot: 2 factory factory app 00 00 00010000 00100000); - 有 OTA 时:读取 otadata 分区的信息,判断哪个 OTA 分区是 "有效分区",选择该分区启动(OTA 功能的核心)。
- 无 OTA 时:直接选择factory 分区 (对应日志
- 加载应用程序镜像到内存 :从选定的应用分区(如 factory 分区 0x10000 偏移)逐段读取应用镜像 ,并按「段的属性」处理(对应日志
I (78) esp_image: segment 0...),这是内存映射的实战核心 :load段:将 Flash 中的数据复制到内部 SRAM(IRAM/DRAM,对应内存映射 2/3/5 号区域),如全局初始化数据、中断函数;map段:不复制到内部 SRAM,通过MMU 配置 Flash Cache ,将 Flash 地址映射到虚拟内存地址(对应内存映射 1/6 号外部内存区域),如普通应用代码,节省内部 SRAM。
- 校验并跳转 :校验应用镜像的完整性(CRC / 签名),校验通过后,从应用镜像的头部找到入口地址,跳转到该地址,交权给应用程序。
3.3 核心价值(官方设计初衷)
乐鑫设计二级引导的核心目的是 **"提升灵活性和安全性"**,也是 ESP-IDF 的核心特性基础:
- 支持分区表:让 Flash 可以按需划分区域,实现应用、数据、OTA 的分离;
- 支持Flash 加密 / 安全引导:保护应用程序不被篡改 / 反编译;
- 支持OTA 空中升级:实现远程升级应用,无需物理烧录;
- 可自定义配置:通过
idf.py menuconfig可配置看门狗、日志级别、启动分区规则等。
四、阶段 3:应用程序启动阶段(第三阶段,用户开发核心)
这是用户代码的入口前置阶段 ,对应你启动日志中I (155) 到 I (297)的内容,核心作用是"完成硬件 / 软件的全量初始化、启动双核、启动 FreeRTOS、创建主任务并执行 app_main" 。官方将该阶段拆分为3 个子阶段 ,且针对 ESP32-S3 的双核特性补充了APP CPU 的启动细节,这是开发中需要重点理解的部分。
4.1 子阶段 1:端口初始化(硬件 + C 运行环境初始化,底层)
入口函数 :call_start_cpu0(在 IDF 的components/esp_system/port/cpu_start.c中),由二级引导跳转执行,从不返回,核心作用是 **"初始化 SoC 硬件和 C 语言运行环境(CRT)"**,为后续运行做基础。
核心执行动作(官方定义)
- 重新配置 CPU 异常处理(让用户的中断函数可以运行,替代 ROM 的简易错误处理);
- 初始化内部内存:对 **data 段(初始化全局变量)和bss 段(未初始化全局变量,清零)** 做初始化;
- 完成 MMU/Cache 的最终配置(优化外部内存访问);
- 配置 CPU 时钟:将 PRO CPU 时钟设置为 menuconfig 中配置的频率(你的日志中是 160MHz,可改 240MHz);
- 启动 APP CPU(双核核心) :为 APP CPU 设置入口地址,解除其复位状态,等待 APP CPU 完成自身的端口初始化;
- 若配置了 PSRAM,自动使能并初始化 PSRAM(补充外部内存)。
APP CPU 的端口初始化 :解除复位后,APP CPU 会执行call_start_cpu1函数,完成与 PRO CPU 类似的硬件初始化,然后设置 "启动完成标志",告知 PRO CPU 自己已准备好。
4.2 子阶段 2:系统初始化(软件服务 + FreeRTOS 基础环境初始化)
入口函数 :start_cpu0(PRO CPU)/start_cpu_other_cores(APP CPU),均为弱链接函数(用户可自定义覆盖,增加额外初始化步骤),核心作用是 **"初始化 ESP-IDF 的软件服务,为 FreeRTOS 运行做准备"**。
核心执行动作(官方定义,对应日志关键行)
- 输出应用程序信息(对应日志
I (165) app_init: Application information:,包括项目名、版本、IDF 版本等); - 初始化堆分配器 (对应日志
I (209) heap_init: Initializing...),从此刻开始,用户可以使用malloc/psram_malloc等动态内存分配函数; - 初始化 newlibC 库、串口控制台(将 printf/ESP_LOGI 映射到 UART)、SPI Flash API;
- 执行全局 C++ 构造函数 和 C 语言的
__attribute__((constructor))修饰的函数(用户可利用该特性做全局初始化); - 初始化各组件的基础服务(如 GPIO、UART、WiFi 等组件的底层驱动);
- 执行所有组件的二级初始化函数 (通过
ESP_SYSTEM_INIT_FN宏注册的函数,如 WiFi、蓝牙的初始化)。
APP CPU 的系统初始化 :执行start_cpu_other_cores函数,完成基础配置后,自旋等待PRO CPU 启动 FreeRTOS 调度器(不做其他操作,等待指令)。
4.3 子阶段 3:运行主任务,调用 app_main(用户代码入口)
这是启动流程的最终步骤 ,对应日志I (277) main_task: Started on CPU0和I (297) main_task: Calling app_main(),核心是 **"启动 FreeRTOS 调度器,创建主任务,执行用户编写的 app_main 函数"**。
核心执行动作(官方定义,开发重点)
- 启动 FreeRTOS 调度器 :PRO CPU 启动全局的 FreeRTOS 调度器,同时触发中断,让 APP CPU 也启动调度器,双核开始并行运行;
- 创建 main_task 主任务 :由 IDF 自动创建,属性固定(可在 menuconfig 中配置):
- 优先级:比 FreeRTOS 最低优先级高 1 级;
- 堆栈大小:默认 16KB,可通过
CONFIG_ESP_MAIN_TASK_STACK_SIZE配置; - 内核亲和性:可配置为仅运行在 PRO CPU/APP CPU / 双核(默认 PRO CPU);
- main_task 调用 app_main :主任务执行后,立即调用用户编写的
app_main函数,用户代码正式开始执行; - app_main 的特殊特性 :与普通 C 语言的
main函数不同,app_main可以返回 :- 若 app_main 返回,main_task 会被自动删除,但 FreeRTOS 调度器仍在运行,其他由用户创建的任务会继续执行;
- 开发中可将 app_main 作为 "任务创建入口":在 app_main 中创建其他业务任务,然后直接 return,节省 main_task 的资源。
五、ESP32-S3 双核启动的专属细节(官方重点,开发必知)
APP CPU 的启动全程由 PRO CPU 主导,核心流程可总结为:
PRO CPU端口初始化 → 解除APP CPU复位 → APP CPU完成端口初始化 → PRO CPU系统初始化 → APP CPU自旋等待 → PRO CPU启动FreeRTOS → APP CPU启动FreeRTOS → 双核并行运行
开发注意:
- 无需手动处理双核启动,IDF 已做了所有底层工作,用户只需关注业务代码;
- 若需要指定任务运行在某个内核,可通过 FreeRTOS 的内核亲和性 API 配置(如
xTaskCreatePinnedToCore)。
六、核心关联:官方文档与你已学知识的对应(打通知识体系)
这是深入理解的关键,把官方的抽象步骤和你之前学的内存映射、启动日志、引导流程图对应起来,形成完整的知识闭环:
| 官方阶段 | 对应内存映射区域 | 对应启动日志行范围 | 对应引导流程图步骤 |
|---|---|---|---|
| ROM 一级引导 | 4 号(ROM)、5 号(IRAM) | 无(波特率 74880) | 1-5 步(上电→加载二级引导) |
| Flash 二级引导 | 5 号(IRAM)、Flash | I(24)-I(145) | 6-9 步(解析分区表→加载应用) |
| 应用端口初始化 | 2/3/5 号(内部 SRAM) | I(155)-I(204) | 10 步(应用初始化前置) |
| 应用系统初始化 | 2/3/5 号(内部 SRAM) | I(209)-I(269) | 10 步(应用初始化核心) |
| 执行 app_main | 所有可用内存 | I (277)- 最后 | 11 步(应用运行) |
七、实战重点提炼(开发必知,官方文档的核心落地点)
- 可修改 / 配置的阶段 :只有 Flash 二级引导(
components/bootloader)和应用程序阶段可改,ROM 阶段不可碰; - 分区表的核心作用 :二级引导通过分区表找到应用程序,分区表的偏移(0x08000)和应用分区偏移(0x10000)是 ESP32-S3 的固定规范,开发中不要随意修改;
- app_main 的特性:可返回、不是真正的 "主函数",只是 FreeRTOS 的一个普通任务,开发中建议在 app_main 中创建业务任务后直接 return;
- 动态内存的可用时机 :只有在系统初始化阶段 完成
heap_init后,才能使用malloc等函数,提前使用会导致程序崩溃; - 二级引导的配置 :通过
idf.py menuconfig的Bootloader config可配置看门狗、日志级别、OTA 回滚等,满足不同项目需求; - 深度睡眠存根:开发低功耗项目时,可利用 ROM 引导的深度睡眠存根机制,跳过完整启动流程,提升唤醒速度、降低功耗。
八、总结
ESP32-S3 的启动流程是 **"硬件固化→可配置引导→用户应用"** 的层层递进过程,核心由 PRO CPU 主导,APP CPU 在最后阶段才启动运行。
- 对新手开发 :只需关注应用程序阶段 ,重点掌握
app_main的使用、动态内存的分配、任务的创建,底层的启动流程由 IDF 自动处理; - 对深入开发:需要理解 Flash 二级引导的配置、内存映射的段加载规则、双核的运行机制,才能开发低功耗、大内存、高安全性的项目;
- 对调试排错:需要结合启动日志,定位问题出在哪个阶段(如二级引导阶段的分区表错误、应用阶段的内存初始化失败),再针对性解决。
这份官方文档是所有 ESP32-S3 开发的底层理论基础,把它和内存映射、启动日志、引导流程图结合起来,你就真正掌握了 ESP32-S3 的启动核心,为后续的深入开发(如底层驱动、低功耗、OTA)打下了坚实的基础。
五,组件和组件管理器

我们可以看到,再cmakelist里面是最主要的就是idf_component_register这句代码,这句代码把组件注册到了项目里,SRCS指定了组件包含的源文件,INCLUDE_DIRS指定了要包含的头文件目录,图中的include实际上就是左侧log文件夹下的include文件夹
然后再来介绍一下组件依赖

在ap_wifi文件夹下的cmakelist中我们包含了三个源文件"ap_wifi.c" "ws_server.c" "wifi_manager.c",如果我们在这三个源文件中引用了组件的头文件


比如说引用了一个#include "cJSON.h"那么我们就需要在cmakelist里面REQUIRES json这样引用一下,不同版本的esp-ide可能会导致名字不同,这个需要自己注意一下,如果不引用的话就会报头文件找不到,但是,我们如果是在main函数中声明头文件的话,就不会报这个错误,因为构建系统会对这个main组件进行特殊处理,其他的组件会自动的作为它的依赖被添加到构建系统中,
还有个概念是通用组件依赖,就比如说你在"ap_wifi.c"这个源文件中使用了#include "esp_log.h",即使你在cmakelist中不使用INCLUDE_DIRS log他也不会报错头文件找不到,这是因为esp-idf为了避免这些重复的工作,会自动地把一些非常常用的组件自动的包含在构建系统中,我们不需要去显示的引用他,除了log还有freertos等等具体的请到官网查询