【ESP 乐鑫相关】ESP32-S3启动流程

转载自: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和特定库的初始化操作。

posted @ 2025-01-18 09:36  FBshark  阅读(10)  评论(0编辑  收藏  举报