ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

ESP32-S3-启动流程解析

2026/9/6 2:33:09 拓冰建站 浏览量
ESP32-S3-启动流程解析 文章目录将启动log全部打开 可以看到启动流程。一、整体流程1、一级 (ROM) 引导加载程序2、二级引导加载程序3、应用程序启动阶段二、ROM 引导加载程序执行主体1、启动模式判断GPIO_STRAP_REG 寄存器2、关键特性3、执行逻辑4、执行流程1.执行流程图2.从深度睡眠模式复位3.上电复位、软件 SoC 复位、看门狗 SoC 复位4.软件 CPU 复位、看门狗 CPU 复位三、二级引导加载程序1、源码位置2、为什么需要二级 Bootloader3、执行步骤1.读取分区表默认 Flash 0x8000可配置2.分段加载 APP 镜像3.校验 APP 完整性 → 读取镜像头部入口地址 → 跳转执行。4、代码流程1.入口定义components\bootloader\subproject\main\ld\esp32s3\bootloader.ld.in2.components\bootloader\subproject\main\bootloader_start.c3.components\bootloader_support\src\bootloader_utility.c5、流程图四、应用层1、代码流程1.入口函数components/esp_system/port/cpu_start.c2.初始化 components/esp_system/startup.c 3.FreeRTOScomponents\freertos\app_startup.c2、流程图将启动log全部打开 可以看到启动流程。一、整体流程ROM 一级引导 → Bootloader 二级引导 → 应用运行时初始化 → IDF 系统初始化 → FreeRTOS 调度启动 → app_main 用户业务。所有源码路径基于 esp-idf/components/ 。1、一级 (ROM) 引导加载程序被固化在了 ESP32-S3 内部的 ROM 中它会从 flash 的 0x0 偏移地址处加载二级引导加载程序至 RAM (IRAM DRAM) 中。2、二级引导加载程序从 flash 中加载分区表和主程序镜像至内存中主程序中包含了 RAM 段和通过 flash 高速缓存映射的只读段。3、应用程序启动阶段运行这时第二个 CPU 和 RTOS 调度器启动接着运行 main_task从而执行 app_main。二、ROM 引导加载程序执行主体PRO CPU 单核执行APP CPU 保持复位。1、启动模式判断GPIO_STRAP_REG 寄存器2、关键特性PRO CPU 执行全部初始化APP CPU 复位保持到应用阶段 call_start_cpu0 才释放正常启动默认开启 RTC 看门狗卡死自动复位重启ROM 固件版本号 ≠ 芯片 ECO 版本ECO 不影响 ROM 行为时 ROM 版本不变。3、执行逻辑上电后 CPU 复位PC 跳转到芯片内部 ROM 固化代码乐鑫出厂烧死。1. 初始化基础时钟、看门狗、SPI Flash 外设。2. 读取 Flash 0x0000 位置的二级 bootloader 镜像。3. 将 bootloader 加载到内部 RAM跳转执行 bootloader 入口。4、执行流程1.执行流程图2.从深度睡眠模式复位如果 RTC_CNTL_STORE6_REG 寄存器的值非零且 RTC_CNTL_STORE7_REG 寄存器中的 RTC 内存的 CRC 校验值有效那么程序会使用 RTC_CNTL_STORE6_REG 寄存器的值作为入口地址并立即跳转到该地址运行。如果 RTC_CNTL_STORE6_REG 的值为零或 RTC_CNTL_STORE7_REG 中的 CRC 校验值无效又或通过 RTC_CNTL_STORE6_REG 调用的代码返回那么则像上电复位一样继续启动。注意如果想在这里运行自定义的代码可以参考 深度睡眠 文档里面介绍的深度睡眠存根机制方法。3.上电复位、软件 SoC 复位、看门狗 SoC 复位检查 GPIO_STRAP_REG 寄存器判断是否请求自定义启动模式如 UART 下载模式。如果是ROM 会执行此自定义加载模式否则会像软件 CPU 复位一样继续启动。4.软件 CPU 复位、看门狗 CPU 复位根据 EFUSE 中的值配置 SPI flash然后尝试从 flash 中加载代码。三、二级引导加载程序1、源码位置components/bootloader烧录在 Flash 0x0 偏移。2、为什么需要二级 Bootloader支持分区表灵活布局ROM 只能固定地址加载实现 Flash 加密、安全启动、OTA 空中升级等高级功能。3、执行步骤1.读取分区表默认 Flash 0x8000可配置查找 factory 分区和 OTA 分区OTA 场景查询 otadata 分区决定从哪个 OTA 分区启动。2.分段加载 APP 镜像3.校验 APP 完整性 → 读取镜像头部入口地址 → 跳转执行。4、代码流程1.入口定义components\bootloader\subproject\main\ld\esp32s3\bootloader.ld.in/* Default entry point: */ ENTRY(call_start_cpu0); 以上代码定义默认入口函数。2.components\bootloader\subproject\main\bootloader_start.c函数 call_start_cpu0() void __attribute__((noreturn)) call_start_cpu0(void) { // 加载镜像并跳转执行 bootloader_utility_load_boot_image(bs, boot_index); }3.components\bootloader_support\src\bootloader_utility.c函数bootloader_utility_load_boot_image() void bootloader_utility_load_boot_image(const bootloader_state_t *bs, int start_index) { // 加载镜像并跳转执行 Load_image(image_data); } 函数load_image() static void load_image(const esp_image_metadata_t *image_data) { //核心镜像加载入口 unpack_load_app(image_data); } 函数unpack_load_app(); static void unpack_load_app(const esp_image_metadata_t *data) { //配置Cache跳转APP set_cache_and_start_app(drom_addr, drom_load_addr, drom_size, irom_addr, irom_load_addr, irom_size, data); } 函数set_cache_and_start_app( static void set_cache_and_start_app( uint32_t drom_addr, uint32_t drom_load_addr, uint32_t drom_size, uint32_t irom_addr, uint32_t irom_load_addr, uint32_t irom_size, const esp_image_metadata_t *data) { //应用层入口函数地址 const uint32_t entry_addr >5、流程图当一级 (ROM) 引导加载程序校验并加载完二级引导加载程序后它会从二进制镜像的头部找到二级引导加载程序的入口点并跳转过去运行。二级引导加载程序默认从 flash 的 0x8000 偏移地址处可配置的值读取分区表。引导加载程序会寻找工厂分区和 OTA 应用程序分区。如果在分区表中找到了 OTA 应用程序分区引导加载程序将查询 otadata 分区以确定应引导哪个分区。对于选定的分区二级引导加载程序将从 flash 逐段读取二进制镜像对于在内部 IRAM指令 RAM 或 DRAM数据 RAM 中具有加载地址的段将把数据从 flash 复制到它们的加载地址处。对于一些加载地址位于 DROM数据存储在 flash 中 或 IROM代码从 flash 中运行 区域的段通过配置 flash MMU可为从 flash 到加载地址提供正确的映射。处理完所有段即加载了代码并设置了 flash MMU二级引导加载程序将验证应用程序的完整性并从二进制镜像文件的头部寻找入口地址然后跳转到该地址处运行。四、应用层跳转应用后最先执行的不是 app_main而是编译器自动插入的初始化逻辑。1、代码流程1.入口函数components/esp_system/port/cpu_start.c函数 call_start_cpu0() void IRAM_ATTR call_start_cpu0(void) { //调用 start_cpu0() SYS_STARTUP_FN(); //#define SYS_STARTUP_FN() ((*g_startup_fn[(esp_cpu_get_core_id())])()) }2.初始化 components/esp_system/startup.c 函数start_cpu0() void start_cpu0(void) __attribute__((weak, alias(start_cpu0_default))) __attribute__((noreturn)); 函数 start_cpu0_default(void) void call_start_cpu0(void) { //调用系统主入口 esp_startup_start_app() esp_startup_start_app(); }3.FreeRTOScomponents\freertos\app_startup.c函数 esp_startup_start_app() void esp_startup_start_app(void) { //创建main_tack线程任务 BaseType_t res xTaskCreatePinnedToCore(main_task, main, ESP_TASK_MAIN_STACK, NULL, ESP_TASK_MAIN_PRIO, NULL, ESP_TASK_MAIN_CORE); assert(res pdTRUE); } 线程函数main_task() static void main_task(void *arg) { // 系统预初始化完成进入用户入口 app_main(); }2、流程图