【ESP32+freeRTOS学习笔记之“ESP32环境下使用freeRTOS的特性分析(1-启动分析)”】
迪丽瓦拉
2025-05-28 18:04:08
0

目录

  • 1、ESP32启动过程
    • 1.1 一级引导程序
    • 1.2 二级引导程序
    • 1.3 应用程序启动阶段
      • 1.3.1 第一阶段,硬件和基本 C 语言运行环境的端口初始化。
      • 1.3.2 第二阶段,软件服务和 FreeRTOS 的系统初始化。
      • 1.3.3 第三阶段,运行主任务并调用 app_main。
    • 1.4 APP CPU 的内核启动流程
  • 2 代码跟踪与特殊性说明
  • 3、总结

乐鑫的ESP-IDF对FreeRTOS做了深度的适配,使ESP32-IDF的用户能更好的使用FreeRTOS,而无需再去移植FreeRTOS。但开发者在使用适配后的FreeRTOS过程中,会与使用标准的FreeRTOS有一些不同,这些不同不是根本上的区别,只是一些使用顺序及逻辑上的些许改变,但对于在ESP32平台上开发应用却还是很关键。因此要讲情这些区别,必须从ESP32的启动这程说起。

具体的启动过程,在ESP官方资料上有明确的说明,本文第一节是把官方资料搬过来,便于大家阅读,以及快速了解启动的流程。本文的第二节则通过代码跟踪的方式,详细分析了在ESP-IDF中使用FreeRTOS的特殊性。下面进入主题。

1、ESP32启动过程

ESP32的启动,一共分为三个步骤,分别是一级引导、二级引导、主程序运行这三步。一级是由固化在芯片上ROM上的程序进行加载因此是我们无法看到源码进行解读的,只能根据乐鑫提供的技术资料里的内容进行理解。二级引导程序是BootLoad部分是我们可以跟踪和分析的。因此,这部分内容是我们重点开始分析的部分。

1.1 一级引导程序

被固化在了 ESP32 内部的 ROM 中,它会从 flash 的 0x1000 偏移地址处加载二级引导程序至 RAM (IRAM & DRAM) 中。这里一级引导程序会出现三种模式,但因这些不是讨论的重点,因此这里不再赘述。

1.2 二级引导程序

当一级引导程序校验并加载完二级引导程序后,它会从二进制镜像的头部找到二级引导程序的入口点,并跳转过去运行。在 ESP-IDF 中,存放在 flash 的 0x1000 偏移地址处的二进制镜像就是二级引导程序。二级引导程序的源码可以在 ESP-IDF 的 components/bootloader 目录下找到。

二级引导程序作用:从 flash 中加载分区表和主程序镜像至内存中,主程序中包含了 RAM 段和通过 flash 高速缓存映射的只读段。
二级引导程序默认从 flash 的 0x8000 偏移地址处(可配置的值)读取分区表。请参考 分区表 获取详细信息。引导程序会寻找工厂分区和 OTA 应用程序分区。如果在分区表中找到了 OTA 应用程序分区,引导程序将查询 otadata 分区以确定应引导哪个分区。

以上一级引导程序和二级引导程序都是相对固定的内容,只涉及到ESP32的硬件初始化以及环境的设置。不涉及到使用FreeRTOS的特殊部分,因此如果需要详细了解的,可以去乐鑫提供的技术资料里查阅。

1.3 应用程序启动阶段

这时第二个 CPU 和 RTOS 的调度器启动。应用程序启动包含了从应用程序开始执行到 app_main 函数在主任务内部运行前的所有过程。可分为三个阶段:

1.3.1 第一阶段,硬件和基本 C 语言运行环境的端口初始化。

ESP-IDF 应用程序的入口是 components/esp_system/port/cpu_start.c 文件中的 call_start_cpu0 函数。这个函数由二级引导加载程序执行,并且从不返回。

该端口层的初始化功能会初始化基本的 C 运行环境 (“CRT”),并对 SoC 的内部硬件进行了初始配置。

1、为应用程序重新配置 CPU 异常(允许应用程序中断处理程序运行,并使用为应用程序配置的选项来处理 严重错误,而不是使用 ROM 提供的简易版错误处理程序处理。
2、如果没有设置选项 CONFIG_BOOTLOADER_WDT_ENABLE,则不使能 RTC 看门狗定时器。
3、初始化内部存储器(数据和 bss)。
4、完成 MMU 高速缓存配置。
5、如果配置了 PSRAM,则使能 PSRAM。
6、将 CPU 时钟设置为项目配置的频率。
7、根据应用程序头部设置重新配置主 SPI flash,这是为了与 ESP-IDF V4.0 之前的引导程序版本兼容,请参考 引导加载程序兼容性。
8、如果应用程序被配置为在多个内核上运行,则启动另一个内核并等待其初始化(在类似的“端口层”初始化函数 call_start_cpu1 内)。
call_start_cpu0 完成运行后,将调用在 components/esp_system/startup.c 中找到的“系统层”初始化函数 start_cpu0。其他内核也将完成端口层的初始化,并调用同一文件中的 start_other_cores。

1.3.2 第二阶段,软件服务和 FreeRTOS 的系统初始化。

主要的系统初始化函数是 start_cpu0。默认情况下,这个函数与 start_cpu0_default 函数弱链接。这意味着可以覆盖这个函数,增加一些额外的初始化步骤。

主要的系统初始化阶段包括:

1、如果默认的日志级别允许,则记录该应用程序的相关信息(项目名称、应用程序版本 等)。
2、初始化堆分配器(在这之前,所有分配必须是静态的或在堆栈上)。
3、初始化 newlib 组件的系统调用和时间函数。
4、配置断电检测器。
5、根据 串行控制台配置 设置 libc stdin、stdout、和 stderr。
6、执行与安全有关的检查,包括为该配置烧录 efuse(包括 禁用 ESP32 V3 的 ROM 下载模式、CONFIG_ESP32_DISABLE_BASIC_ROM_CONSOLE)。
7、初始化 SPI flash API 支持。
8、调用全局 C++ 构造函数和任何标有 attribute((constructor)) 的 C 函数。
二级系统初始化允许单个组件被初始化。如果一个组件有一个用 ESP_SYSTEM_INIT_FN 宏注释的初始化函数,它将作为二级初始化的一部分被调用。

1.3.3 第三阶段,运行主任务并调用 app_main。

在所有其他组件都初始化后,主任务会被创建,FreeRTOS 调度器开始运行。

做完一些初始化任务后(需要启动调度器),主任务在固件中运行应用程序提供的函数 app_main。

运行 app_main 的主任务有一个固定的 RTOS 优先级(比最小值高)和一个 可配置的堆栈大小。

主任务的内核亲和性也是可以配置的,请参考 CONFIG_ESP_MAIN_TASK_AFFINITY。

与普通的 FreeRTOS 任务(或嵌入式 C 的 main 函数)不同,app_main 任务可以返回。如果app_main 函数返回,那么主任务将会被删除。系统将继续运行其他的 RTOS 任务。因此可以将 app_main 实现为一个创建其他应用任务然后返回的函数,或主应用任务本身。

1.4 APP CPU 的内核启动流程

APP CPU 的启动流程类似但更简单:

当运行系统初始化时,PRO CPU 上的代码会给 APP CPU 设置好入口地址,解除其复位状态,然后等待 APP CPU 上运行的代码设置一个全局标志,以表明 APP CPU 已经正常启动。 完成后,APP CPU 跳转到 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 调度器。

2 代码跟踪与特殊性说明

上面应用程序启动的三个阶段是描述是官方说明文档,但在代码跟踪下来后, 我认为从FreeRTOS的角度来看,实际是分为两个大阶段更为合适,一个是ESP32的启动部分,一个是FreeRTOS的启动部分。代码的跟踪流程如下所示:

->【components/esp_system/port/cpu_start.c中运行call_start_cpu0函数,并在最后调用了这个宏SYS_STARTUP_FN();】
->【components/esp_system/include/esp_private/startup_internal.h中的宏SYS_STARTUP_FN()实为调用g_startup_fn函数指针数组】
->【components/esp_system/startup.c实现数组g_startup_fn[0]=start_cpu0如果是双核则g_starup_fn[1]=start_cpu_other_cores】
->【而start_cpu0()函数又是start_cpu0_default()函数的别名,在最后又调用了esp_startup_start_app()】

这一步是重要的分水岭,在这之前是 ESP32的传统启动部分,但在这之后,就是FreeRTOS的启动部分了,具体见下面第二阶段的描述
->【components/freertos/port/xtensa/port.c中的void esp_startup_start_app(void)函数中调用esp_startup_start_app_common(void)】
->【components/freertos/port/port_common.c中void esp_startup_start_app_common(void)函数中创建了main_task()任务】
->【components/freertos/port/port_common.c 中的main_task()中调用app_main()】

以上内容是代码链接节点的说明,可能简单了一些,有兴趣可以打开程序自已根据上面的流程也跟踪一下,这里不再另外详述,这里重要的是理解FreeRTOS部分的代码,这里的启动方式和标准的FreeRTOS方式有不同。标准的方式是由开发者在main()程序里主动去调用vTaskStartScheduler()。而在ESP32里,在系统的启动阶段的最后会自动启动FreeRTOS的调度器。代码如下:

在这里插入图片描述
esp_startup_start_app_common(void)函数中创建了main_task()任务,该任务的优先级为1,在main_task()任务里,先是启动了esp32的任务软件狗并同时看护在两个内核中运行的FreeRTOS空闲任务Idle0与Idle1.并同时定义了空闲任务的钩子函数(回调函数)用于空闲任务每运行一次喂一次狗。然后启动了app_main()任务。该任务正是开发者可以自定义所有工作的入口。具体如下:

static void main_task(void* args)
{
/*。。。。。。此处省略部分代码。。。。。。*///Initialize task wdt if configured to do so  初始化任务看门狗
#ifdef CONFIG_ESP_TASK_WDT_PANICESP_ERROR_CHECK(esp_task_wdt_init(CONFIG_ESP_TASK_WDT_TIMEOUT_S, true));
#elif CONFIG_ESP_TASK_WDTESP_ERROR_CHECK(esp_task_wdt_init(CONFIG_ESP_TASK_WDT_TIMEOUT_S, false));
#endif//Add IDLE 0 to task wdt 把IDLE0加入任务看门狗的监控
#ifdef CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0TaskHandle_t idle_0 = xTaskGetIdleTaskHandleForCPU(0);if(idle_0 != NULL){ESP_ERROR_CHECK(esp_task_wdt_add(idle_0));}
#endif//Add IDLE 1 to task wdt 把IDLE1加入任务看门狗的监控
#ifdef CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1TaskHandle_t idle_1 = xTaskGetIdleTaskHandleForCPU(1);if(idle_1 != NULL){ESP_ERROR_CHECK(esp_task_wdt_add(idle_1));}
#endifapp_main();  //此处调用用户自定义的主任务。vTaskDelete(NULL);
}

从上面的代码可以看出,ESP32在适配FreeRTOS为了程序的安全性,是建议开发者打开任务看门狗对空闲任务进行监控的。因为空闲任务的优先级是0,是最低等级的,所以在开发时要特别注意所有自定义的,优先级高于0级的所有任务都要注意运行时间的管理,在必要时要有阻塞动作,及时让出CPU时间片给空闲任务。否则超出喂狗时间。
在这里插入图片描述
idle_hook_cb()是真正的喂狗动作,该函数存在idle_cb[]数组里,并最后在空闲函数的钩子函数(或叫回调函数)vApplicationIdleHook()内部调用了。这个喂狗动作藏得好深,我跟踪了好久。具体过程是这样的,FreeRTOS的空闲钩子函数对外的接口名称为vApplicationIdleHook()这个函数,而这个函数又在components/freertos/port/xtensa/include/freertos/portmacro.h里定义了一个原型叫esp_vApplicationIdleHook,因此 ,ESP32利用了这个原型,在components/esp_system/freertos_hooks.c的里定义了这个原型函数esp_vApplicationIdleHook()具体如下:

在这里插入图片描述

3、总结

上面这些原码分析,清楚地看到FreeRTOS在ESP32平台中的这些特殊的地方:
1、不需要我们再调用vTaskStartScheduler()函数来启动调度器,因此系统已经帮我们调用了。
2、主函数app_main()本身也是个任务,这个任务的优先级是1,而且该任务只要一返回,就被删除。所以我们在这个主函数中总是把该建的其它任务都建好,然后尽量返回,让系统回收了这个任务。
3、一般情况下ESP32会建立两个任务看门狗(这是可以配置的),用于监控空闲任务的运行时间,当然主要目的是为了防止某些任务进入死循环而导致系统崩溃。这点在调试时要记得,有时候系统不是任务逻辑有问题,可能只是运行时间久了导致空闲任务无法喂狗。
4、在写用户的应用时,要调整好各个任务的优先级,以免出现某任务长时间占用系统时间而导致FreeRTOS的任务调度平衡被打破。这时可以在相关任务里放入一些阻塞相关的函数,来使调度器正常调度。

以上这些内容与FreeRTOS官网里的内容不太一样。这是ESP32与FreeRTOS适配中的特别情况,需要开发者了然于胸。通过代码的追踪能使开发者知其所以然,更有信心的面对程序健壮性的挑战。

关联资料:
esp32应用程序启动流程
ESP32引导加载程序 (Bootloader)

相关内容

热门资讯

linux入门---制作进度条 了解缓冲区 我们首先来看看下面的操作: 我们首先创建了一个文件并在这个文件里面添加了...
C++ 机房预约系统(六):学... 8、 学生模块 8.1 学生子菜单、登录和注销 实现步骤: 在Student.cpp的...
A.机器学习入门算法(三):基... 机器学习算法(三):K近邻(k-nearest neigh...
数字温湿度传感器DHT11模块... 模块实例https://blog.csdn.net/qq_38393591/article/deta...
有限元三角形单元的等效节点力 文章目录前言一、重新复习一下有限元三角形单元的理论1、三角形单元的形函数(Nÿ...
Redis 所有支持的数据结构... Redis 是一种开源的基于键值对存储的 NoSQL 数据库,支持多种数据结构。以下是...
win下pytorch安装—c... 安装目录一、cuda安装1.1、cuda版本选择1.2、下载安装二、cudnn安装三、pytorch...
MySQL基础-多表查询 文章目录MySQL基础-多表查询一、案例及引入1、基础概念2、笛卡尔积的理解二、多表查询的分类1、等...
keil调试专题篇 调试的前提是需要连接调试器比如STLINK。 然后点击菜单或者快捷图标均可进入调试模式。 如果前面...
MATLAB | 全网最详细网... 一篇超超超长,超超超全面网络图绘制教程,本篇基本能讲清楚所有绘制要点&#...
IHome主页 - 让你的浏览... 随着互联网的发展,人们越来越离不开浏览器了。每天上班、学习、娱乐,浏览器...
TCP 协议 一、TCP 协议概念 TCP即传输控制协议(Transmission Control ...
营业执照的经营范围有哪些 营业执照的经营范围有哪些 经营范围是指企业可以从事的生产经营与服务项目,是进行公司注册...
C++ 可变体(variant... 一、可变体(variant) 基础用法 Union的问题: 无法知道当前使用的类型是什...
血压计语音芯片,电子医疗设备声... 语音电子血压计是带有语音提示功能的电子血压计,测量前至测量结果全程语音播报࿰...
MySQL OCP888题解0... 文章目录1、原题1.1、英文原题1.2、答案2、题目解析2.1、题干解析2.2、选项解析3、知识点3...
【2023-Pytorch-检... (肆十二想说的一些话)Yolo这个系列我们已经更新了大概一年的时间,现在基本的流程也走走通了,包含数...
实战项目:保险行业用户分类 这里写目录标题1、项目介绍1.1 行业背景1.2 数据介绍2、代码实现导入数据探索数据处理列标签名异...
记录--我在前端干工地(thr... 这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 前段时间接触了Th...
43 openEuler搭建A... 文章目录43 openEuler搭建Apache服务器-配置文件说明和管理模块43.1 配置文件说明...