理解Zephyr在一款soc上的啟動(dòng)流程,有利于分析和調(diào)試開(kāi)機(jī)過(guò)程卡死,驅(qū)動(dòng)異常等的問(wèn)題。因此在上手一款新的soc時(shí)掌握Z(yǔ)ephyr在其上面的啟動(dòng)流程非常必要。本文對(duì)Zephyr在ESP32上的啟動(dòng)流程進(jìn)行分析,說(shuō)明ESP32從上電開(kāi)始如何執(zhí)行到Zephyr應(yīng)用的main函數(shù)。
Zephyr支持兩種ESP32引導(dǎo)方式:
配置CONFIG_BOOTLOADER_ESP_IDF=n:ROM Boot -》 Zephyr
配置CONFIG_BOOTLOADER_ESP_IDF=y. ROM Boot -》 ESP32 Bootloader -》 Zephyr
本文只分析CONFIG_BOOTLOADER_ESP_IDF=y的流程,在該流程理解另一種也類似,在CONFIG_BOOTLOADER_ESP_IDF=n的情況下相當(dāng)于是在ESP32的bootloader處放了一個(gè)zephyr應(yīng)用。
ESP32下Zephyr是被當(dāng)作ESP32的APP被引導(dǎo),因此有必要簡(jiǎn)單了解ESP32的啟動(dòng)流程
ESP32啟動(dòng)階段
ESP32是雙核CPU,其中cpu0叫做PRO CPU, cpu1叫做APP CPU,啟動(dòng)流程如下:
SOC上電, PRO CPU開(kāi)始運(yùn)行,跳到ROM 0x40000400 處復(fù)位向量代碼處執(zhí)行
在PRO CPU上運(yùn)行ROM上一級(jí)引導(dǎo)代碼從Flash的0x1000讀出二級(jí)引導(dǎo)程序加載到內(nèi)部IRAM
跳轉(zhuǎn)到內(nèi)部IRAM上二級(jí)引導(dǎo)程序執(zhí)行
二級(jí)引導(dǎo)程序從 Flash 的 0x8000 偏移地址處讀取分區(qū)表, 從分區(qū)表中讀到APP的信息
二級(jí)引導(dǎo)程序?qū)ephyr數(shù)據(jù)和代碼段復(fù)制到DRAM和IRAM。對(duì)于Zephyr內(nèi)一些加載地址位于DROM和IROM區(qū)域的段,通過(guò)配置 Flash MMU 為其提供正確的映射。
二級(jí)引導(dǎo)程序會(huì)從Zephyr二進(jìn)制鏡像文件的頭部尋找的入口地址,然后跳轉(zhuǎn)到該地址處運(yùn)行。
以上流程中1~3是已經(jīng)被固化到ESP32的ROM中無(wú)法修改,4~6是由modules/hal/espressif/components/bootloader完成,可以做定制修改,但一般不修改。以上1~6都是在PRO CPU中執(zhí)行。
Zephyr的入口地址就是函數(shù)__start,第六步后就會(huì)跳轉(zhuǎn)到__start中執(zhí)行
Zephyr階段
Zephyr階段運(yùn)行到main主要步驟:__start-》z_cstart-》bg_thread_main-》main
__start
文件位置zephyr/soc/xtensa/esp32/soc.c, 主要完成下面內(nèi)容:
搬移中斷向量表
初始化bss段
關(guān)閉中斷
確保APP CPU沒(méi)有運(yùn)行(將在后面SMP初始化階段打開(kāi))
代碼摘要如下
void __attribute__((section(“.iram1”))) \_\_start(void)
{
//搬移中斷向量表
__asm__ __volatile__ (
“wsr %0, vecbase”
:
: “r”(&_init_start));
//BSS段初始化
(void)memset(&_bss_start, 0,
(&_bss_end - &_bss_start) * sizeof(_bss_start));
__asm__ __volatile__ (
“”
:
: “g”(&_bss_start)
: “memory”);
//關(guān)閉中斷
__asm__ __volatile__ (
“wsr %0, PS”
:
: “r”(PS_INTLEVEL(XCHAL_EXCM_LEVEL) | PS_UM | PS_WOE));
//關(guān)閉APP CPU
*app_cpu_config_reg &= ~DPORT_APPCPU_CLKGATE_EN;
//初始化cpu指針
__asm__ volatile(“wsr.MISC0 %0; rsync” : : “r”(&_kernel.cpus[0]));
//開(kāi)始zephyr初始化
z_cstart();
CODE_UNREACHABLE;
}
是否發(fā)現(xiàn)跳到zephyr的__start是一個(gè)C函數(shù),但之前Zephyr并沒(méi)有做C堆棧(SP指針)初始化?這是因?yàn)樵贓SP32的bootloader階段已經(jīng)做了,Zephyr無(wú)需再做。
z_cstart
主要完成kernel初始化,PRE_KERNEL_1和PRE_KERNEL_2級(jí)別的驅(qū)動(dòng)初始化,然后啟動(dòng)main thread:bg_thread_main,剩下的其它初始化和應(yīng)用程序的main都在bg_thread_main中。
代碼摘要如下
__boot_func
FUNC_NORETURN void z_cstart(void)
{
//架構(gòu)相關(guān)的內(nèi)核初始化
arch_kernel_init();
// static devices初始化
z_device_state_init();
//初始化PRE_KERNEL_1和PRE_KERNEL_2驅(qū)動(dòng),大多都是硬件相關(guān)
z_sys_init_run_level(_SYS_INIT_LEVEL_PRE_KERNEL_1);
z_sys_init_run_level(_SYS_INIT_LEVEL_PRE_KERNEL_2);
//創(chuàng)建并切換到main thread運(yùn)行
switch_to_main_thread(prepare_multithreading());
CODE_UNREACHABLE; /* LCOV_EXCL_LINE */
}
__boot_func
static char *prepare_multithreading(void)
{
char *stack_ptr;
//初始化OS調(diào)度器
z_sched_init();
//創(chuàng)建main thread
stack_ptr = z_setup_new_thread(&z_main_thread, z_main_stack,
CONFIG_MAIN_STACK_SIZE, bg_thread_main,
NULL, NULL, NULL,
CONFIG_MAIN_THREAD_PRIORITY,
K_ESSENTIAL, “main”);
//將main thread加入到就緒態(tài)
z_mark_thread_as_started(&z_main_thread);
z_ready_thread(&z_main_thread);
//為每顆CPU 創(chuàng)建idle thread
for (int i = 0; i 《 CONFIG_MP_NUM_CPUS; i++) {
init_idle_thread(i);
_kernel.cpus[i].idle_thread = &z_idle_threads[i];
_kernel.cpus[i].id = i;
_kernel.cpus[i].irq_stack =
(Z_KERNEL_STACK_BUFFER(z_interrupt_stacks[i]) +
K_KERNEL_STACK_SIZEOF(z_interrupt_stacks[i]));
}
initialize_timeouts();
return stack_ptr;
}
main thread被加入到就緒態(tài),因此下一次調(diào)度時(shí)bg_thread_main就會(huì)被執(zhí)行
bg_thread_main
在bg_thread_main中完成剩余的驅(qū)動(dòng)初始化,并且啟動(dòng)esp32的第二顆CPU: APP CPU, 然后運(yùn)行到應(yīng)用的main函數(shù)。
代碼摘要如下
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
__boot_func
static void bg_thread_main(void *unused1, void *unused2, void *unused3)
{
z_sys_post_kernel = true;
//初始化POST_KERNEL級(jí)別驅(qū)動(dòng)
z_sys_init_run_level(_SYS_INIT_LEVEL_POST_KERNEL);
boot_banner();
//初始化APPLICATION級(jí)別驅(qū)動(dòng)
z_sys_init_run_level(_SYS_INIT_LEVEL_APPLICATION);
//初始化靜態(tài)聲明的thread
z_init_static_threads();
#ifdef CONFIG_SMP
//初始化SMP, 到這里才會(huì)啟動(dòng)ESP32的另一顆CPU
z_smp_init();
//初始SMP級(jí)別的驅(qū)動(dòng),例如跨CPU通信的mailbox, ipm驅(qū)動(dòng)
z_sys_init_run_level(_SYS_INIT_LEVEL_SMP);
#endif
extern void main(void);
//執(zhí)行應(yīng)用程序的main
main();
/* Mark nonessenrial since main() has no more work to do */
z_main_thread.base.user_options &= ~K_ESSENTIAL;
}
關(guān)于main
這里調(diào)用的main函數(shù),是在Zephyr應(yīng)用程序中實(shí)現(xiàn),最后通過(guò)鏈接器鏈接在一起。Zephyr應(yīng)用程序的main是在main thread中執(zhí)行,由于main thread的默認(rèn)優(yōu)先級(jí)比較高0, 因此要注意不要在main中去做while(1),避免導(dǎo)致其它搶占式線程拿不到CPU。
關(guān)于SMP
從前面的分析可以看到z_smp_init前,Zephyr上包括main thread的所有代碼都是在PRO CPU上執(zhí)行,在z_smp_init后Zephyr的代碼才有機(jī)會(huì)運(yùn)行到APP CPU上, SMP是一個(gè)很大的議題,不是在本文分析范圍內(nèi)。這里簡(jiǎn)單列出esp32 SMP初始化的主要流程供參考:
z_smp_init(smp.c)-》arch_start_cpu(esp32-mp.c)-》appcpu_start-》esp32_rom_ets_set_appcpu_boot_addr-》appcpu_entry1-》z_appcpu_stack_switch-》appcpu_entry2-》smp_init_top(smp.c)
參考
https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-guides/startup.html
編輯:jq
-
cpu
+關(guān)注
關(guān)注
68文章
11063瀏覽量
216498 -
soc
+關(guān)注
關(guān)注
38文章
4363瀏覽量
222191 -
SMP
+關(guān)注
關(guān)注
0文章
78瀏覽量
20222 -
ESP32
+關(guān)注
關(guān)注
21文章
1012瀏覽量
19066
原文標(biāo)題:Zephyr ESP32啟動(dòng)流程
文章出處:【微信號(hào):ZephyrProject,微信公眾號(hào):ZephyrProject】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
ESP32-S3開(kāi)發(fā)板燒錄小智AI系統(tǒng)全流程指南

ESP32運(yùn)行網(wǎng)頁(yè)服務(wù)器 (Web-Server)-入門篇

【AI技術(shù)支持】ESP32模組接大電容無(wú)法正常啟動(dòng)處理

ESP32-S3-WROOM-1/ESP32-S3-WROOM-1U技術(shù)規(guī)格書
【AI技術(shù)支持】ESP32-WROVER-IE-N16R8模組上電啟動(dòng)失敗問(wèn)題處理

【AI技術(shù)支持】ESP32模組PSRAM的CS引腳上拉導(dǎo)致功耗上升處理

esp32上使用chatGPT做一些有意思的事情
在rt-thread bsp里esp32c3里編譯出的bin文件燒到esp32的開(kāi)發(fā)板上運(yùn)行提示chipid不對(duì),為什么?
esp32和esp8266代碼共用嗎
esp8266和esp32區(qū)別是什么
esp32用什么軟件編程
ESP32-WROOM-32E、ESP32-WROOM-32D、ESP32-WROOM-32U 有什么區(qū)別?ESP32-WROOM-32 后綴字母代表的意思是?

ESP32能取代STM32嗎?哪個(gè)更好?

評(píng)論