ESPIDF-系统启动流程

应用程序的启动流程

本文将会介绍 ESP32 从上电到运行app_main函数中间所经历的步骤(即启动流程)。
宏观上,该启动流程可以分为如下 3 个步骤:

  1. 一级引导程序 被固化在了 ESP32 内部的 ROM 中,它会从 flash 的 0x1000 偏移地址处加载二级引导程序至 RAM (IRAM & DRAM) 中。
  2. 二级引导程序 从 flash 中加载分区表和主程序镜像至内存中,主程序中包含了 RAM 段和通过 flash 高速缓存映射的只读段。
  3. 应用程序启动阶段 运行,这时第二个 CPU 和 RTOS 的调度器启动。

app_main() 调用流程

调用 app_main

port_idf.c中找到如下代码

1
2
3
4
5
6
7
void app_main(void);

static void main_task(void* args)
{
app_main();
vTaskDelete(NULL);
}

可以看到app_main函数返回后删除了该task,如果将变量定义在app_main函数中,任务被删除后栈上的数据会被自动释放。

调用 main_task

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
34
35
36
37
38
39
40
// ------------------ CPU0 App Startup ---------------------

void esp_startup_start_app(void)
{
#if CONFIG_ESP_INT_WDT
esp_int_wdt_init();
// Initialize the interrupt watch dog for CPU0.
esp_int_wdt_cpu_init();
#elif CONFIG_ESP32_ECO3_CACHE_LOCK_FIX
// If the INT WDT isn't enabled on ESP32 ECO3, issue an error regarding the cache lock bug
assert(!soc_has_cache_lock_bug() && "ESP32 Rev 3 + Dual Core + PSRAM requires INT WDT enabled in project config!");
#endif

// Initialize the cross-core interrupt on CPU0
esp_crosscore_int_init();

#if CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME && !CONFIG_IDF_TARGET_ESP32C2
void esp_gdbstub_init(void);
esp_gdbstub_init();
#endif // CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME

BaseType_t res = xTaskCreatePinnedToCore(main_task, "main",
ESP_TASK_MAIN_STACK, NULL,
ESP_TASK_MAIN_PRIO, NULL, ESP_TASK_MAIN_CORE);
assert(res == pdTRUE);
(void)res;

/*
If a particular FreeRTOS port has port/arch specific OS startup behavior, they can implement a function of type
"void port_start_app_hook(void)" in their `port.c` files. This function will be called below, thus allowing each
FreeRTOS port to implement port specific app startup behavior.
*/
void __attribute__((weak)) port_start_app_hook(void);
if (port_start_app_hook != NULL) {
port_start_app_hook();
}

ESP_EARLY_LOGI(APP_START_TAG, "Starting scheduler on CPU0");
vTaskStartScheduler();
}

esp_startup_start_app中创建了maintask,并将其分配至 CPU0 核心上。最后用vTaskStartScheduler()启动调度器,这也是为什么在app_main函数中不用我们手动启动调度器。

调用 esp_startup_start_app

在 esp_system 文件夹的startup.c中进行了调用。

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
static void start_cpu0_default(void)
{

ESP_EARLY_LOGI(TAG, "Pro cpu start user code");
int cpu_freq = esp_clk_cpu_freq();
ESP_EARLY_LOGI(TAG, "cpu freq: %d Hz", cpu_freq);

#ifdef WITH_APP_IMAGE_INFO
// Display information about the current running image.
if (LOG_LOCAL_LEVEL >= ESP_LOG_INFO) {
const esp_app_desc_t *app_desc = esp_app_get_description();
ESP_EARLY_LOGI(TAG, "Application information:");
#ifndef CONFIG_APP_EXCLUDE_PROJECT_NAME_VAR
ESP_EARLY_LOGI(TAG, "Project name: %s", app_desc->project_name);
#endif
#ifndef CONFIG_APP_EXCLUDE_PROJECT_VER_VAR
ESP_EARLY_LOGI(TAG, "App version: %s", app_desc->version);
#endif
#ifdef CONFIG_BOOTLOADER_APP_SECURE_VERSION
ESP_EARLY_LOGI(TAG, "Secure version: %d", app_desc->secure_version);
#endif
#ifdef CONFIG_APP_COMPILE_TIME_DATE
ESP_EARLY_LOGI(TAG, "Compile time: %s %s", app_desc->date, app_desc->time);
#endif
char buf[17];
esp_app_get_elf_sha256(buf, sizeof(buf));
ESP_EARLY_LOGI(TAG, "ELF file SHA256: %s...", buf);
ESP_EARLY_LOGI(TAG, "ESP-IDF: %s", app_desc->idf_ver);

ESP_EARLY_LOGI(TAG, "Min chip rev: v%d.%d", CONFIG_ESP_REV_MIN_FULL / 100, CONFIG_ESP_REV_MIN_FULL % 100);
ESP_EARLY_LOGI(TAG, "Max chip rev: v%d.%d %s",CONFIG_ESP_REV_MAX_FULL / 100, CONFIG_ESP_REV_MAX_FULL % 100,
efuse_ll_get_disable_wafer_version_major() ? "(constraint ignored)" : "");
unsigned revision = efuse_hal_chip_revision();
ESP_EARLY_LOGI(TAG, "Chip rev: v%d.%d", revision / 100, revision % 100);
}
#endif

// Initialize core components and services.
do_core_init();

// Execute constructors.
do_global_ctors();

// Execute init functions of other components; blocks
// until all cores finish (when !CONFIG_ESP_SYSTEM_SINGLE_CORE_MODE).
do_secondary_init();

// Now that the application is about to start, disable boot watchdog
#ifndef CONFIG_BOOTLOADER_WDT_DISABLE_IN_USER_CODE
wdt_hal_context_t rtc_wdt_ctx = RWDT_HAL_CONTEXT_DEFAULT();
wdt_hal_write_protect_disable(&rtc_wdt_ctx);
wdt_hal_disable(&rtc_wdt_ctx);
wdt_hal_write_protect_enable(&rtc_wdt_ctx);
#endif

#if SOC_CPU_CORES_NUM > 1 && !CONFIG_ESP_SYSTEM_SINGLE_CORE_MODE
s_system_full_inited = true;
#endif

esp_startup_start_app();
while (1);
}

调用 start_cpu0_default

1
2
// Entry point for core 0 from hardware init (port layer)
void start_cpu0(void) __attribute__((weak, alias("start_cpu0_default"))) __attribute__((noreturn));

调用 start_cpu0

1
2
3
4
5
const sys_startup_fn_t g_startup_fn[SOC_CPU_CORES_NUM] = { [0] = start_cpu0,
#if SOC_CPU_CORES_NUM > 1
[1 ... SOC_CPU_CORES_NUM - 1] = start_cpu_other_cores
#endif
};

g_startup_fn数组可以调用不同的 cpu 初始化程序。

调用 g_startup_fn

1
2
// Utility to execute sys_startup_fn_t for the current core.
#define SYS_STARTUP_FN() ((*g_startup_fn[(esp_cpu_get_core_id())])())

调用 SYS_STARTUP_FN

cpu_start.c中分别被call_start_cpu0()call_start_cpu1()调用。

调用 call_start_cpu0

在esp32的链接文件中调用。

1
ENTRY(call_start_cpu0);

也是应用程序启动阶段的最终入口,从这里开始对硬件进行初始化然后创建主task进行任务调度。启动顺序分为两个部分如下所示。

esp_system部分 freeRTOS部分
call_start_cpu0 esp_startup_start_app
SYS_STARTUP_FN main_task
start_cpu0 app_main
start_cpu0_default