ESP32-C3 应用程序的启动流程
![ESP32-C3 应用程序的启动流程](https://static001.geekbang.org/infoq/3b/3b7559623426010f8b26f2acf3ca8d59.jpeg)
前言
对于 ARM 内核的 STM32 的启动流程,我以前的博文详细分析过,搞懂了 STM32 的启动流程对于芯片的使用和理解来说就会更上一个等级。现在我们新接触的 risc-v 内核的 ESP32-C3,如果能够搞明白他的启动流程,就能更深的理解 ESP32-C3。
在写文章之前也看了很多网上的文章,然后官方的说明也看过了,网上绝大多数都是官网文档复制一遍,这倒没什么,毕竟官网权威,问题是,复制一遍过来没有做过多的解释,没有额外的分析,还写个原创,我真是 XX 了。当然不排除我没看到的好的文章,也正是因为基本没有看到更加详细的分析,我决定要自己写一篇记录,一来当做自己记录,二来底层有些地方目前来说我确实不是理解透彻,肯定有不到位的地方,希望大家多提意见,能让文章更加完善。
按照惯例,我把能找到的文章和官方的资料都看了好多遍,官方提到的 3 大步骤:
![](https://static001.geekbang.org/infoq/28/287831a87628b16cac3f7c07cbde99b9.png)
对于上述提到的 3 大步骤,最简单的清晰的是第三步,第二步也有源码,倒是可以查看试着分析一下,但是第一步确实,只是知道这么回事,具体的实现方式因为程序是在 ESP32-C3 的内部 ROM 中,确实找不到可以分析的源码和资料。
芯片的启动流程,大多离不开启动文件和链接文件。ESP32-C3 应该也是这样,本来按照理解,应该找到底层的启动文件和连接文件开始按照步骤分析,但是查看了一会底层代码,因为对底层架构深入了解得还不够,没有找到= =! 所以想着怎么办?
不能按照从最开始到结束的顺序来,那么就按照从结束到开始的顺序来!我们从app_main
函数反过来一层一层网前看,看看经过了一些什么处理,程序才执行到app_main
函数的!
注意,文章以倒叙的方式说明~ ~
本文是基于 VScode 插件的工程结构来说明(Ubuntu 环境),环境搭建见下面博文:
ESP32-C3 VScode 开发环境搭建(基于乐鑫官方 ESP-IDF——Windows 和 Ubuntu 双环境)
https://xie.infoq.cn/article/5b639e112cabba00cc1b8941a
一、应用程序启动阶段
1.1 app_main.c
我们从app_main.c
的主函数app_main
中,我们直接通过转到定义看看上一层:
![](https://static001.geekbang.org/infoq/1a/1a97b7922422599e7a3321b2e67e3abc.png)
1.2 port_common.c
app_main 往上找的文件是 port_common.c
,路径如下:
![](https://static001.geekbang.org/infoq/b2/b287d72ccd7c95b90140657524f1bfef.png)
是哪一个任务调用了app_main
呢,我们直接往上就能看到,main_task
调用了app_main
,而main_task
这个任务就在esp_startup_start_app_common
函数中创建:
![](https://static001.geekbang.org/infoq/88/880c3b541bd58c4455dc708033291c65.png)
知道了esp_startup_start_app_common
函数创建了main_task
这个任务,那么这个函数在哪边有调用呢?我们继续往上看,找到esp_startup_start_app
函数中调用了esp_startup_start_app_common
,调用完成以后就开启了 FreeRTOS 任务调度。
![](https://static001.geekbang.org/infoq/73/73dc96c0264466dec83c2dbf13f0fcd9.png)
对应的,我们看一下启动 LOG:
![](https://static001.geekbang.org/infoq/49/4992eab7a9707a8e30ca9df41119bd4b.png)
1.3 port.c
我们又进入了一个新的文件 port.c
,路径如下:
![](https://static001.geekbang.org/infoq/9a/9a469a1b86d3cf54a9f0d82cfe5f90e7.png)
接着上面的,从esp_startup_start_app
函数往上找,又找到一个start_cpu0_default
函数,如图:
![](https://static001.geekbang.org/infoq/6b/6bef8e07cf1e5b28264d71f352d81355.png)
1.4 startup.c
又找到一个新文件startup.c
,路径如下:
![](https://static001.geekbang.org/infoq/fa/faa6c97a36ccb235d5ba144d384d6f2f.png)
start_cpu0_default
在 startup.c
文件的 start_cpu0_default
函数,其中进行了很多关键步骤,具体可以自行查看代码:
![](https://static001.geekbang.org/infoq/df/dfbb9752e90cd3db171a81be90006ae0.png)
对应的,我们看一下启动 LOG:
![](https://static001.geekbang.org/infoq/50/5097470ba60da43a7e7588274b123acf.png)
我们还想看 start_cpu0_default
函数从哪里调用,但是这时候已经无法跳转了,这时候我们依然在startup.c
文件中搜索,可以找到下面一句话:
![](https://static001.geekbang.org/infoq/78/785bbeec381d2a56e454b6ccbc35f733.png)
这句话的意思就是使得 start_cpu0_default
函数弱连接到start_cpu0
函数,如果没有额外的声明,start_cpu0_default
函数等价于start_cpu0
函数。(有错误请指出)
继续看看与 start_cpu0
函数 有关的程序,g_startup_fn
好像是一个数组?如图:
![](https://static001.geekbang.org/infoq/64/6467d0875ad72bfc11c49699f7f2297a.png)
尝试着对 g_startup_fn
使用转到定义,我们进入了另外一个新的文件,可以找到一个宏定义SYS_STARTUP_FN()
:
![](https://static001.geekbang.org/infoq/46/46f2060b162d95f16cb529bac1445080.png)
1.5 startup_internal.h
老样子先看文件路径如下:
![](https://static001.geekbang.org/infoq/cd/cd9e567103d96dcb7f6d65042e6f8253.png)
这是一个头文件,所以这里面肯定都是函数的声明和宏定义,我们在这个文件中需要关注的地方就是一个宏定义#define SYS_STARTUP_FN() ((*g_startup_fn[(cpu_hal_get_core_id())])())
:
![](https://static001.geekbang.org/infoq/89/89da306e1e2f9699e4dc8cecd097ee5a.png)
我们接下来要找的就是这个宏定义SYS_STARTUP_FN()
在哪里调用了。
因为是头文件,找函数调用就复杂一点,但是问题不大,如下图:
![](https://static001.geekbang.org/infoq/1c/1cbaddfeb3e4b6557f1ac1311b58de81.png)
点进去确实发现是我所需要查找的文件,如果找不到,可以在目录下面搜索,先小目录,还是找不到,再用上层目录。 这也是看底层源码的基本方法。
1.6 cpu_start.c
老样子先看文件路径如下:
![](https://static001.geekbang.org/infoq/51/51e40d6abec342dc4d5ad352a4b5f609.png)
在cpu_start.c
文件中,可以看到确实有上面头文件中宏定义SYS_STARTUP_FN()
的调用,里面主要就是几个启动 cpu 的函数,其中是call_start_cpu0
函数调用了这个宏定义:
![](https://static001.geekbang.org/infoq/6c/6cf18dd89d131ff8d18a996320ae4a5c.png)
函数的内容是很多的,这里我们就不一一分析。但是到了这里,我们再结合官方的说明文档再看一遍:
![](https://static001.geekbang.org/infoq/6d/6d5e347daf1cc56332d418f2df1dbbf8.png)
这样子去理解,感觉官方说得好简单,实际上都是层层调用。
分析到这里,我们都已经进入入口函数了,那么程序是如何调用。按照以前的理解,这个入口函数应该会在一个连接脚本里面使用 ENTRY
调用,就像ENTRY(call_start_cpu0);
实际上,如果去文件夹搜索可以找到很多文件中有ENTRY(call_start_cpu0);
,即便我们值选择与 ESP32-C3 有关的,也有不同的地方,那么到底是哪个呢?我们还得继续顺藤摸瓜!
1.7 esp32c3.project.ld.in
在组件的esp32c3
目录下面搜索call_start_cpu0
,找到一个使用了ENTRY(call_start_cpu0);
的链接文件:(有错误请指出)
![](https://static001.geekbang.org/infoq/1c/1c654af7b5335a5abbd06cdb072099a8.png)
这个和我们的 STM32 中分析中的类似,就是进入到入口函数执行:
![](https://static001.geekbang.org/infoq/3b/3b05171472d7fdbb09fc10b86dd8f7f5.png)
文件路径:
![](https://static001.geekbang.org/infoq/ee/ee366acbf253a8d70b0b1d2968015d79.png)
我们还得往下简单看看二级引导程序。
二、二级引导程序
二级引导程序,基于现在我的理解,还不足以能够完全的讲清楚,官方提到源码在 ESP-IDF 的 components/bootloader 目录下,我也简单的看了一下,在目录中找到比较“可能”的程序:
![](https://static001.geekbang.org/infoq/3a/3ac6aa72a23f207f5c228f90f98f4337.png)
在这个bootloader_start.c
文件中,正如官方的说明一样,ESP-IDF 使用二级引导程序可以增加 Flash 分区的灵活性(使用分区表)等一些功能:
![](https://static001.geekbang.org/infoq/65/65751b806244d761dbad9e2c92a0c0eb.png)
继续查看了一些代码(具体的就是在bootloader_start.c
文件相关联的地方查找),确定bootloader_start.c
就是二级引导程序的代码,根据 LOG 输出判断的 = =!
![](https://static001.geekbang.org/infoq/38/3865768a498f578375b4ac42ea1a373b.png)
在 bootloader 目录下也有 ESP32-C3 的链接文件:
![](https://static001.geekbang.org/infoq/d9/d903e65c96e333f4a8d5ebd47d51344f.png)
在其中也有ENTRY(call_start_cpu0);
的调用,这个应该是和二级引导程序中bootloader_start.c
中对应的链接文件(有问题清指出)。
![](https://static001.geekbang.org/infoq/d7/d76a49ea6d91ea43ff054f99bf4ccd78.png)
结语
文章在前面 C 语言启动的部分倒是还算满意,到底层以后,对陌生的 risc-v 内核和架构还是处于模糊的状态,希望小伙伴多多给些意见。
文章整体只能算是代码的流程解析,并没有分析函数的功能,这是我不满意的地方,冰冻三尺非一日之寒,加油!文章会随着博主的理解深入保持更新,希望以后能有时间从内存,从架构好好的完善一下文章!
版权声明: 本文为 InfoQ 作者【矜辰所致】的原创文章。
原文链接:【http://xie.infoq.cn/article/c7c59030b3e22ef1dcaa51368】。文章转载请联系作者。
评论