转载自:https://blog.itpub.net/70040860/viewspace-3053923/
ESP32-S3启动流程
本文将会介绍ESP32-S3从上电到运行app_main函数中间所经历的步骤(即启动流程)。从宏观上,该启动流程可分为如下3个步骤。
①:一级引导程序,它被固化在ESP32-S3内部的ROM中,它会从flash的0x00处地址加载二级引导程序至RAM中。
②:二级引导程序从flash中加载分区表和主程序镜像至内存中,主程序中包含了RAM段和通过flash高速缓存映射的只读段。
③:应用程序启动阶段运行,这时第二个CPU和freeRTOS的调度器启动,最后进入app_main函数执行用户代码。
下面作者根据IDF库相关的代码来讲解这三个引导流程,如下:
一、一级引导程序
该部分程序是直接存储在ESP32-S3内部ROM中,所以普通开发者无法直接查看,它主要是做一些前期的准备工作(复位向量代码),然后从flash 0x00偏移地址中读取二级引导程序文件头中的配置信息,并使用这些信息来加载剩余的二级引导程序。
二、二级引导程序
该程序是可以查看且可被修改,在搭建ESP-IDF环境完成后,可在esp-idf\components\bootloader/subproject/main/路径下找到bootloader_start.c文件,此文件就是二级引导程序启动处。首先我们克隆ESP-IDF库,克隆过程如下所示。
图4.6.1 克隆ESP-IDF库
克隆完成后,使用VSCode打开ESP-IDF库,接着找到bootloader_start.c,如下图所示。
图4.6.2 bootloader_start.c文件路径
在这个文件下,找到call_start_cpu0函数,此函数是bootloader程序,如下是bootloader程序的部分代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
/* ROM引导加载程序完成从闪存加载第二阶段引导加载程序之后到达这里 */ void __attribute__(( noreturn )) call_start_cpu0( void ) { if (bootloader_before_init) { bootloader_before_init(); } /* 1. 硬件初始化:清楚bss段、开启cache、复位mmc等操作 bootloader_support/src/esp32s3/bootloader_esp32s3.c */ if (bootloader_init() != ESP_OK) { bootloader_reset(); } if (bootloader_after_init) { bootloader_after_init(); } /* 2. 选择启动分区的数量:加载分区表,选择boot分区 */ bootloader_state_t bs = {0}; int boot_index = select_partition_number(&bs); if (boot_index == INVALID_INDEX){ bootloader_reset(); } /* 3. 加载应用程序映像并启动 bootloader_support/src/esp32s3/bootloader_utility.c */ bootloader_utility_load_boot_image(&bs, boot_index); } |
ESP-IDF使用二级引导程序可以增加FLASH分区的灵活性(使用分区表),并且方便实现FLASH加密,安全引导和空中升级(OTA)等功能。主要的作用是从flash的0x8000处加载分区表(请看在线ESP32-IDF编程指南分区表章 节)。根据分区表运行应用程序。
三、三级引导程序
应用程序的入口是在esp-idf/components/esp_system/port/路径下的cpu_star.c文件,在此文件下找到call_start_cpu0函数(端口层初始化函数)。这个函数由二级引导加载程序执行,并且从不返回。因此你看不到是哪个函数调用了它,它是从汇编的最 底层直接调用的。
个函数会初始化基本的C运行环境(“CRT”),并对SOC的内部硬件进行了初始配置。执行call_start_cpu0函数完成之后,在components\esp_system\startup.c文件下调用start_cpu0(在110行中,弱关联start_cpu0_default函数)系统层初始化函数,如下start_cpu0_default函数的部分代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
static void start_cpu0_default( void ) { ESP_EARLY_LOGI(TAG, "Pro cpu start user code" ); /* 获取CPU时钟 */ int cpu_freq = esp_clk_cpu_freq(); ESP_EARLY_LOGI(TAG, "cpu freq: %d Hz" , cpu_freq); /* 初始化核心组件和服务 */ do_core_init(); /* 执行构造函数 */ do_global_ctors(); /* 执行其他组件的init函数 */ do_secondary_init(); /* 开启APP程序 */ esp_startup_start_app(); while (1); } |
到了这里,就完成了二级程序引导,并调用esp_startup_start_app函数进入三级引导程序,该函数的源码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
/* components/freertos/FreeRTOS-Kernel/portable/xtensa/port.c */ /* 开启APP程序 */ void esp_startup_start_app( void ) { /* 省略部分代码 */ /* 新建main任务函数 */ esp_startup_start_app_common(); /* 开启FreeRTOS任务调度 */ vTaskStartScheduler(); } /* components/freertos/FreeRTOS-Kernel/portable/port_common.c */ /* 新建main任务函数 */ void esp_startup_start_app_common( void ) { /* 省略部分代码 */ /* 创建main任务 */ portBASE_TYPE res = xTaskCreatePinnedToCore(&main_task, "main" , ESP_TASK_MAIN_STACK, NULL, ESP_TASK_MAIN_PRIO, NULL, ESP_TASK_MAIN_CORE); assert (res == pdTRUE); ( void )res; } /* main任务函数 */ static void main_task( void * args) { /* 省略部分代码 */ /* 执行app_main函数 */ app_main(); vTaskDelete(NULL); } |
从上述源码可知,首先在esp_startup_start_app_common函数调用FreeRTOS API创建main任务,然后开启freeRTOS任务调度器,最后在main任务下调用app_main函数(此函数在创建工程时,在main.c下定义的)。
同理,ESP32S3的MicroPython固件也是以这种方式启动的。此外,MicroPython的app_main函数是在ports/esp32/main.c文件中定义的。在该文件中,会进行UART、USB和特定库的初始化操作。