|
|
@@ -4,60 +4,54 @@
|
|
|
|
|
|
{IDF_TARGET_BOOTLOADER_OFFSET:default="0x0", esp32="0x1000", esp32s2="0x1000"}
|
|
|
|
|
|
-本文将会介绍 {IDF_TARGET_NAME} 从上电到运行 ``app_main``
|
|
|
-函数中间所经历的步骤(即启动流程)。
|
|
|
+本文将会介绍 {IDF_TARGET_NAME} 从上电到运行 ``app_main`` 函数中间所经历的步骤(即启动流程)。
|
|
|
|
|
|
宏观上,该启动流程可以分为如下 3 个步骤:
|
|
|
|
|
|
-1. 一级引导程序被固化在了 {IDF_TARGET_NAME} 内部的 ROM 中,它会从 Flash 的
|
|
|
- {IDF_TARGET_BOOTLOADER_OFFSET} 偏移地址处加载二级引导程序至 RAM(IRAM & DRAM) 中。
|
|
|
+1. :ref:`first-stage-bootloader` 被固化在了 {IDF_TARGET_NAME} 内部的 ROM 中,它会从 flash 的 {IDF_TARGET_BOOTLOADER_OFFSET} 偏移地址处加载二级引导程序至 RAM (IRAM & DRAM) 中。
|
|
|
|
|
|
-2. 二级引导程序从 Flash 中加载分区表和主程序镜像至内存中,主程序中包含了
|
|
|
- RAM 段和通过 Flash 高速缓存映射的只读段。
|
|
|
+2. :ref:`second-stage-bootloader` 从 flash 中加载分区表和主程序镜像至内存中,主程序中包含了 RAM 段和通过 flash 高速缓存映射的只读段。
|
|
|
|
|
|
-3. 主程序运行,这时第二个 CPU 和 RTOS 的调度器可以开始运行。
|
|
|
+3. :ref:`application-startup` 运行,这时第二个 CPU 和 RTOS 的调度器启动。
|
|
|
|
|
|
下面会对上述过程进行更为详细的阐述。
|
|
|
|
|
|
+.. _first-stage-bootloader:
|
|
|
+
|
|
|
一级引导程序
|
|
|
~~~~~~~~~~~~
|
|
|
|
|
|
-SoC 复位后,PRO CPU 会立即开始运行,执行复位向量代码,而 APP CPU
|
|
|
-仍然保持复位状态。在启动过程中,PRO CPU 会执行所有的初始化操作。APP CPU
|
|
|
-的复位状态会在应用程序启动代码的 ``call_start_cpu0``
|
|
|
-函数中失效。复位向量代码位于 {IDF_TARGET_NAME} 芯片掩膜 ROM 的 ``0x40000400``
|
|
|
-地址处,该地址不能被修改。
|
|
|
-
|
|
|
-复位向量调用的启动代码会根据 ``GPIO_STRAP_REG`` 寄存器的值来确定 {IDF_TARGET_NAME}
|
|
|
-的工作模式,该寄存器保存着复位后 bootstrap
|
|
|
-引脚的电平状态。根据不同的复位原因,程序会执行不同的操作:
|
|
|
-
|
|
|
-1. 从深度睡眠模式复位:如果 ``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``
|
|
|
- 地址处运行的程序返回,那么将会执行上电复位的相关操作。 **注意** :如果想在这里运行自定义的代码,可以参考
|
|
|
- :doc:`深度睡眠 <deep-sleep-stub>` 文档里面介绍的方法。
|
|
|
-
|
|
|
-2. 上电复位、软件 SoC 复位、看门狗 SoC 复位:检查 ``GPIO_STRAP_REG``
|
|
|
- 寄存器,判断是否 UART 或 SDIO 请求进入下载模式。如果是,则配置好 UART
|
|
|
- 或者 SDIO,然后等待下载代码。否则程序将会执行软件 CPU
|
|
|
- 复位的相关操作。
|
|
|
-
|
|
|
-3. 软件 CPU 复位、看门狗 CPU 复位:根据 EFUSE 中的值配置 SPI
|
|
|
- Flash,然后尝试从 Flash
|
|
|
- 中加载代码,这部分的内存将会在后面一小节详细介绍。如果从 Flash
|
|
|
- 中加载代码失败,就会将 BASIC 解析器加压缩到 RAM
|
|
|
- 中启动。需要注意的是,此时 RTC
|
|
|
- 看门狗还在使能状态,如果在几百毫秒内没有任何输入事件,那么看门狗会再次复位
|
|
|
- SoC,重复整个过程。如果解析器收到了来自 UART
|
|
|
- 的输入,程序会关闭看门狗。
|
|
|
-
|
|
|
-应用程序的二进制镜像会从 Flash 的 {IDF_TARGET_BOOTLOADER_OFFSET} 地址处加载。Flash 的第一个
|
|
|
-4kB
|
|
|
-扇区用于存储安全引导程序和应用程序镜像的签名。有关详细信息,请查看安全启动文档。
|
|
|
+.. only:: not CONFIG_FREERTOS_UNICORE
|
|
|
+
|
|
|
+ SoC 复位后,PRO CPU 会立即开始运行,执行复位向量代码,而 APP CPU 仍然保持复位状态。在启动过程中,PRO CPU 会执行所有的初始化操作。APP CPU 的复位状态会在应用程序启动代码的 ``call_start_cpu0`` 函数中失效。复位向量代码位于 {IDF_TARGET_NAME} 芯片掩膜 ROM 处,且不能被修改。
|
|
|
+
|
|
|
+.. only:: CONFIG_FREERTOS_UNICORE
|
|
|
+
|
|
|
+ SoC 复位后,CPU 会立即开始运行,执行所有的初始化操作。复位向量代码位于 {IDF_TARGET_NAME} 芯片掩膜 ROM 处,且不能被修改。
|
|
|
+
|
|
|
+复位向量调用的启动代码会根据 ``GPIO_STRAP_REG`` 寄存器的值来确定 {IDF_TARGET_NAME} 的启动模式,该寄存器保存着复位后 bootstrap 引脚的电平状态。根据不同的复位原因,程序会执行如下操作:
|
|
|
+
|
|
|
+1. 从深度睡眠模式复位:如果 ``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`` 调用的代码返回,那么则像上电复位一样继续启动。 **注意**:如果想在这里运行自定义的代码,可以参考 :doc:`深度睡眠 <deep-sleep-stub>` 文档里面介绍的深度睡眠存根机制方法。
|
|
|
+
|
|
|
+2. 上电复位、软件 SoC 复位、看门狗 SoC 复位:检查 ``GPIO_STRAP_REG`` 寄存器,判断是否请求自定义启动模式,如 UART 下载模式。如果是,ROM 会执行此自定义加载器模式。否则程会像软件 CPU 复位一样继续启动。请参考 {IDF_TARGET_NAME} 技术规格书了解 SoC 启动模式以及具体执行过程。
|
|
|
+
|
|
|
+3. 软件 CPU 复位、看门狗 CPU 复位:根据 EFUSE 中的值配置 SPI flash,然后尝试从 flash 中加载代码,这部分将会在后面一小节详细介绍。
|
|
|
+
|
|
|
+.. note::
|
|
|
+
|
|
|
+ 正常启动模式下会使能 RTC 看门狗,因此,如果进程中断或停止,看门狗将自动重置 SOC 并重复启动过程。如果 strapping GPIOs 已更改,则可能导致 SoC 陷入新的启动模式。
|
|
|
+
|
|
|
+.. only:: esp32
|
|
|
+
|
|
|
+ 二级引导程序二进制镜像会从 flash 的 ``0x1000`` 偏移地址处加载。如果正在使用 :doc:`/security/secure-boot-v1`,则 flash 的第一个 4 kB 扇区用于存储安全启动 IV 以及引导程序镜像的摘要,否则不使用该扇区。
|
|
|
+
|
|
|
+.. only:: esp32s2
|
|
|
+
|
|
|
+ 二级引导程序二进制镜像会从 flash 的 ``0x1000`` 偏移地址处加载。该地址前面的 flash 4 kB 扇区未使用。
|
|
|
+
|
|
|
+ .. only:: not (esp32 or esp32s2)
|
|
|
+
|
|
|
+ 二级引导程序二进制镜像会从 flash 的 `` 0x0`` 偏移地址处加载。
|
|
|
|
|
|
.. TODO: describe application binary image format, describe optional flash configuration commands.
|
|
|
|
|
|
@@ -66,66 +60,116 @@ SoC 复位后,PRO CPU 会立即开始运行,执行复位向量代码,而 A
|
|
|
二级引导程序
|
|
|
~~~~~~~~~~~~
|
|
|
|
|
|
-在 ESP-IDF 中,存放在 Flash 的 {IDF_TARGET_BOOTLOADER_OFFSET}
|
|
|
-偏移地址处的二进制镜像就是二级引导程序。二级引导程序的源码可以在 ESP-IDF
|
|
|
-的 components/bootloader 目录下找到。请注意,对于 {IDF_TARGET_NAME}
|
|
|
-芯片来说,这并不是唯一的安排程序镜像的方式。事实上用户完全可以把一个功能齐全的应用程序烧写到
|
|
|
-Flash 的 {IDF_TARGET_BOOTLOADER_OFFSET} 偏移地址处运行,但这超出本文档的范围。ESP-IDF
|
|
|
-使用二级引导程序可以增加 Flash 分区的灵活性(使用分区表),并且方便实现
|
|
|
-Flash 加密,安全引导和空中升级(OTA)等功能。
|
|
|
+在 ESP-IDF 中,存放在 flash 的 {IDF_TARGET_BOOTLOADER_OFFSET} 偏移地址处的二进制镜像就是二级引导程序。二级引导程序的源码可以在 ESP-IDF 的 :idf:`components/bootloader` 目录下找到。ESP-IDF 使用二级引导程序可以增加 flash 分区的灵活性(使用分区表),并且方便实现 flash 加密,安全引导和空中升级(OTA)等功能。
|
|
|
|
|
|
当一级引导程序校验并加载完二级引导程序后,它会从二进制镜像的头部找到二级引导程序的入口点,并跳转过去运行。
|
|
|
|
|
|
-二级引导程序从 Flash 的 ``0x8000``
|
|
|
-偏移地址处读取分区表。详细信息请参阅分区表文档
|
|
|
-:doc:`分区表 <partition-tables>` 。二级引导程序会寻找出厂分区和 OTA
|
|
|
-分区,然后根据 OTA *信息* 分区的数据决引导哪个分区。
|
|
|
-
|
|
|
-对于选定的分区,二级引导程序将映射到 IRAM 和 DRAM
|
|
|
-的数据和代码段复制到它们的加载地址处。对于一些加载地址位于 DROM 和 IROM
|
|
|
-区域的段,会通过配置 Flash MMU
|
|
|
-为其提供正确的映射。请注意,二级引导程序会为 PRO CPU 和 APP CPU 都配置
|
|
|
-Flash MMU,但它只使能了 PRO CPU 的 Flash
|
|
|
-MMU。这么做的原因在于二级引导程序的代码被加载到了 APP CPU
|
|
|
-的高速缓存使用的内存区域,因此使能 APP CPU
|
|
|
-高速缓存的任务就交给了应用程序。一旦代码加载完毕并且设置好 Flash
|
|
|
-MMU,二级引导程序会从应用程序二进制镜像文件的头部寻找入口地址,然后跳转到该地址处运行。
|
|
|
-
|
|
|
-目前还不支持添加钩子函数到二级引导程序中以自定义应用程序分区选择的逻辑,但是可以通过别的途径实现这个需求,比如根据某个
|
|
|
-GPIO 的不同状态来引导不同的应用程序镜像。此类自定义的功能将在未来添加到
|
|
|
-ESP-IDF 中。目前,可以通过将 bootloader
|
|
|
-组件复制到应用程序目录并在那里进行必要的更改来自定义引导程序。在这种情况下,ESP-IDF
|
|
|
-的编译系统将编译应用程序目录中的组件而不是 ESP-IDF 组件目录。
|
|
|
+二级引导程序默认从 flash 的 ``0x8000`` 偏移地址处 :ref:`可配置的值 <CONFIG_PARTITION_TABLE_OFFSET>`)读取分区表。请参考 :doc:`分区表 <partition-tables>` 获取详细信息。引导程序会寻找工厂分区和 OTA 应用程序分区。如果在分区表中找到了 OTA 应用程序分区,引导程序将查询 ``otadata`` 分区以确定应引导哪个分区。更多信息请参考 :doc:`/api-reference/system/ota`。
|
|
|
+
|
|
|
+关于 ESP-IDF 引导程序可用的配置选项,请参考 :doc:`bootloader`。
|
|
|
+
|
|
|
+对于选定的分区,二级引导程序将从 flash 逐段读取二进制镜像:
|
|
|
+
|
|
|
+- 对于在内部 :ref:`iram` 或 :ref:`dram` 中具有加载地址的段,将把数据从 flash 复制到它们的加载地址处。
|
|
|
+- 对于一些加载地址位于 :ref:`drom` 或 :ref:`irom` 区域的段,通过配置 flash MMU,可为从 flash 到加载地址提供正确的映射。
|
|
|
+
|
|
|
+.. only:: not CONFIG_FREERTOS_UNICORE
|
|
|
+
|
|
|
+ 请注意,二级引导程序同时为 PRO CPU 和 APP CPU 配置 flash MMU,但仅使能 PRO CPU 的 flash MMU。原因是二级引导程序代码已加载到 APP CPU 的高速缓存使用的内存区域中。因此使能 APP CPU 高速缓存的任务就交给了应用程序。
|
|
|
+
|
|
|
+一旦处理完所有段(即加载了代码并设置了 flash MMU),二级引导程序将验证应用程序的完整性,并从二进制镜像文件的头部寻找入口地址,然后跳转到该地址处运行。
|
|
|
+
|
|
|
+.. _application-startup:
|
|
|
|
|
|
应用程序启动阶段
|
|
|
~~~~~~~~~~~~~~~~
|
|
|
|
|
|
-ESP-IDF 应用程序的入口是 ``components/{IDF_TARGET_PATH_NAME}/cpu_start.c`` 文件中的
|
|
|
-``call_start_cpu0``
|
|
|
-函数,该函数主要完成了两件事,一是启用堆分配器,二是使 APP CPU
|
|
|
-跳转到其入口点—— ``call_start_cpu1`` 函数。PRO CPU 上的代码会给 APP
|
|
|
-CPU 设置好入口地址,解除其复位状态,然后等待 APP CPU
|
|
|
-上运行的代码设置一个全局标志,以表明 APP CPU 已经正常启动。 完成后,PRO
|
|
|
-CPU 跳转到 ``start_cpu0`` 函数,APP CPU 跳转到 ``start_cpu1`` 函数。
|
|
|
-
|
|
|
-``start_cpu0`` 和 ``start_cpu1``
|
|
|
-这两个函数都是弱类型的,这意味着如果某些特定的应用程序需要修改初始化顺序,就可以通过重写这两个函数来实现。 ``start_cpu0``
|
|
|
-默认的实现方式是初始化用户在 ``menuconfig``
|
|
|
-中选择的组件,具体实现步骤可以阅读 ``components/{IDF_TARGET_PATH_NAME}/cpu_start.c``
|
|
|
-文件中的源码。请注意,此阶段会调用应用程序中存在的 C++
|
|
|
-全局构造函数。一旦所有必要的组件都初始化好,就会创建 *main
|
|
|
-task* ,并启动 FreeRTOS 的调度器。
|
|
|
-
|
|
|
-当 PRO CPU 在 ``start_cpu0`` 函数中进行初始化的时候,APP CPU 在
|
|
|
-``start_cpu1`` 函数中自旋,等待 PRO CPU 上的调度器启动。一旦 PRO CPU
|
|
|
-上的调度器启动后,APP CPU 上的代码也会启动调度器。
|
|
|
+应用程序启动包含了从应用程序开始执行到 ``app_main`` 函数在主任务内部运行前的所有过程。可分为三个阶段:
|
|
|
+
|
|
|
+- 硬件和基本 C 语言运行环境的端口初始化。
|
|
|
+- 软件服务和 FreeRTOS 的系统初始化。
|
|
|
+- 运行主任务并调用 ``app_main``。
|
|
|
+
|
|
|
+.. note::
|
|
|
+
|
|
|
+ 通常不需要了解 ESP-IDF 应用程序初始化的所有阶段。如果需要仅从应用程序开发人员的角度了解初始化,请跳至 :ref:`app-main-task`。
|
|
|
+
|
|
|
+端口初始化
|
|
|
+------------------
|
|
|
+
|
|
|
+ESP-IDF 应用程序的入口是 :idf_file:`components/esp_system/port/cpu_start.c` 文件中的 ``call_start_cpu0`` 函数。这个函数由二级引导加载程序执行,并且从不返回。
|
|
|
+
|
|
|
+该端口层的初始化功能会初始化基本的 C 运行环境 ("CRT"),并对 SoC 的内部硬件进行了初始配置。
|
|
|
+
|
|
|
+.. list::
|
|
|
+
|
|
|
+ - 为应用程序重新配置 CPU 异常(允许应用程序中断处理程序运行,并使用为应用程序配置的选项来处理 :doc:`fatal-errors`,而不是使用 ROM 提供的简易版错误处理程序处理。
|
|
|
+ - 如果没有设置选项 :ref:`CONFIG_BOOTLOADER_WDT_ENABLE`,则不使能 RTC 看门狗定时器。
|
|
|
+ - 初始化内部存储器(数据和 bss)。
|
|
|
+ - 完成 MMU 高速缓存配置。
|
|
|
+ :SOC_SPIRAM_SUPPORTED: - 如果配置了 PSRAM,则使能 PSRAM。
|
|
|
+ - 将 CPU 时钟设置为项目配置的频率。
|
|
|
+ :CONFIG_ESP_SYSTEM_MEMPROT_FEATURE: - 如果配置了内存保护,则初始化内存保护。
|
|
|
+ :esp32: - 根据应用程序头部设置重新配置主 SPI flash,这是为了与 ESP-IDF V4.0 之前的引导程序版本兼容,请参考 :ref:`bootloader-compatibility`。
|
|
|
+ :not CONFIG_FREERTOS_UNICORE: - 如果应用程序被配置为在多个内核上运行,则启动另一个内核并等待其初始化(在类似的“端口层”初始化函数 ``call_start_cpu1`` 内)。
|
|
|
+
|
|
|
+.. only:: not CONFIG_FREERTOS_UNICORE
|
|
|
+
|
|
|
+ ``call_start_cpu0`` 完成运行后,将调用在 :idf_file:`components/esp_system/startup.c` 中找到的“系统层”初始化函数 ``start_cpu0``。其他内核也将完成端口层的初始化,并调用同一文件中的 ``start_other_cores``。
|
|
|
+
|
|
|
+.. only:: CONFIG_FREERTOS_UNICORE
|
|
|
+
|
|
|
+ ``call_start_cpu0`` 完成运行后,将调用在 :idf_file:`components/esp_system/startup.c` 中找到的“系统层”初始化函数 ``start_cpu0``。
|
|
|
+
|
|
|
+系统初始化
|
|
|
+---------------------
|
|
|
+
|
|
|
+主要的系统初始化函数是 ``start_cpu0``。默认情况下,这个函数与 ``start_cpu0_default`` 函数弱链接。这意味着可以覆盖这个函数,增加一些额外的初始化步骤。
|
|
|
+
|
|
|
+主要的系统初始化阶段包括:
|
|
|
+
|
|
|
+.. list::
|
|
|
+
|
|
|
+ - 如果默认的日志级别允许,则记录该应用程序的相关信息(项目名称、:ref:`app-version` 等)。
|
|
|
+ - 初始化堆分配器(在这之前,所有分配必须是静态的或在堆栈上)。
|
|
|
+ - 初始化 newlib 组件的系统调用和时间函数。
|
|
|
+ - 配置断电检测器。
|
|
|
+ - 根据 :ref:`串行控制台配置 <CONFIG_ESP_CONSOLE_UART>` 设置 libc stdin、stdout、和 stderr。
|
|
|
+ :esp32: - 执行与安全有关的检查,包括为该配置烧录 efuse(包括 :ref:`禁用 ESP32 V3 的 ROM 下载模式 <CONFIG_SECURE_UART_ROM_DL_MODE>`、:ref:`CONFIG_ESP32_DISABLE_BASIC_ROM_CONSOLE`)。
|
|
|
+ :not esp32: - 执行与安全有关的检查,包括为该配置烧录 efuse(包括 :ref:`永久限制 ROM 下载模式 <CONFIG_SECURE_UART_ROM_DL_MODE>`)。
|
|
|
+ - 初始化 SPI flash API 支持。
|
|
|
+ - 调用全局 C++ 构造函数和任何标有 ``__attribute__((constructor))`` 的 C 函数。
|
|
|
+
|
|
|
+二级系统初始化允许单个组件被初始化。如果一个组件有一个用 ``ESP_SYSTEM_INIT_FN`` 宏注释的初始化函数,它将作为二级初始化的一部分被调用。
|
|
|
|
|
|
.. _app-main-task:
|
|
|
|
|
|
-Running the main task
|
|
|
+运行主任务
|
|
|
---------------------
|
|
|
|
|
|
-主任务是指运行 ``app_main`` 函数的任务,主任务的堆栈大小和优先级可以在
|
|
|
-``menuconfig``
|
|
|
-中进行配置。应用程序可以用此任务来完成用户程序相关的初始化设置,比如启动其他的任务。应用程序还可以将主任务用于事件循环和其他通用活动。如果
|
|
|
-``app_main`` 函数返回,那么主任务将会被删除。
|
|
|
+在所有其他组件都初始化后,主任务会被创建,FreeRTOS 调度器开始运行。
|
|
|
+
|
|
|
+做完一些初始化任务后(需要启动调度器),主任务在固件中运行应用程序提供的函数 ``app_main``。
|
|
|
+
|
|
|
+运行 ``app_main`` 的主任务有一个固定的 RTOS 优先级(比最小值高)和一个 :ref:`可配置的堆栈大小<CONFIG_ESP_MAIN_TASK_STACK_SIZE>`。
|
|
|
+
|
|
|
+.. only:: not CONFIG_FREERTOS_UNICORE
|
|
|
+
|
|
|
+ 主任务的内核亲和性也是可以配置的,请参考 :ref:`CONFIG_ESP_MAIN_TASK_AFFINITY`。
|
|
|
+
|
|
|
+与普通的 FreeRTOS 任务(或嵌入式 C 的 ``main`` 函数)不同,``app_main`` 任务可以返回。如果``app_main`` 函数返回,那么主任务将会被删除。系统将继续运行其他的 RTOS 任务。因此可以将 ``app_main`` 实现为一个创建其他应用任务然后返回的函数,或主应用任务本身。
|
|
|
+
|
|
|
+.. only:: not CONFIG_FREERTOS_UNICORE
|
|
|
+
|
|
|
+ APP CPU 的内核启动流程
|
|
|
+ ------------------------------------
|
|
|
+
|
|
|
+ APP CPU 的启动流程类似但更简单:
|
|
|
+
|
|
|
+ 当运行系统初始化时,PRO CPU 上的代码会给 APP CPU 设置好入口地址,解除其复位状态,然后等待 APP CPU 上运行的代码设置一个全局标志,以表明 APP CPU 已经正常启动。 完成后,APP CPU 跳转到 :idf_file:`components/esp_system/port/cpu_start.c` 中的 ``call_start_cpu1`` 函数。
|
|
|
+
|
|
|
+ 当 ``start_cpu0`` 函数对 PRO CPU 进行初始化的时候,APP CPU 运行 ``start_cpu_other_cores`` 函数。与 ``start_cpu0`` 函数类似,``start_cpu_other_cores`` 函数是弱链接的,默认为 ``start_cpu_other_cores_default`` 函数,但可以由应用程序替换为不同的函数。
|
|
|
+
|
|
|
+ ``start_cpu_other_cores_default`` 函数做了一些与内核相关的系统初始化,然后等待 PRO CPU 启动 FreeRTOS 的调度器,启动完成后,它会执行 ``esp_startup_start_app_other_cores`` 函数,这是另一个默认为 ``esp_startup_start_app_other_cores_default`` 的弱链接函数。
|
|
|
+
|
|
|
+ 默认情况下,``esp_startup_start_app_other_cores_default`` 只会自旋,直到 PRO CPU 上的调度器触发中断,以启动 APP CPU 上的 RTOS 调度器。
|