In Zephyr, there are two boot processes for the ESP32 series SOC:
-
No MCUboot: ROM-> ESP Bootloader-> Zephyr.bin (App) -
With MCUboot: ROM-> MCUboot -> Zephyr.bin (App)
This article focuses on the no MCUboot part.
ROM
In ESP32C3, the ROM is referred to as the “First stage bootloader”. The ROM is hard-coded into the ESP32C3 and runs from the ROM when the chip is powered on. The image guided by the ROM requires its ELF file to be converted using esptool.py’s elf2image tool, which converts the ELF into a header+segment+segment….+segment format. When booting from ROM, it first reads the header, which contains information about each segment, and copies the corresponding segment content from the bin file to memory or performs an MMU mapping based on the segment information. The header contains the entry address, and after copying the segments, it jumps to the entry address to execute.
For image format and header format reference:
-
https://docs.espressif.com/projects/esptool/en/latest/esp32c3/advanced-topics/firmware-image-format.html -
https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32c3/api-reference/system/app_image_format.html
Besides guiding the ESP Bootloader, the ROM also supports directly booting application images. Refer to https://github.com/espressif/esp32c3-direct-boot-example/blob/main/README.md. The ROM is hard-coded into the chip and has no source code.
ESP Bootloader
In ESP32C3, the ESP Bootloader is referred to as the “Second stage bootloader”. The image guided by the ESP Bootloader also needs to be converted using esptool.py’s elf2image. After starting, the ESP Bootloader by default reads the partition table from the flash at offset address 0x8000, selects the program to boot from the partition table, and copies it to memory or performs an MMU mapping, which is consistent with the ROM process.
The Zephyr image booted by the ESP Bootloader needs to be converted through elf2image.
No MCUboot Process
Build
Use west build -b esp32c3_zgp zephyr_sample/
to build without MCUboot. When not using MCUboot, building the Zephyr image for ESP32C3 will default to this method. The Zephyr build will follow these steps:
-
Build modules/hal/espressif/components/bootloader
and convert it to a ROM-bootable bin file -
Build modules/hal/espressif/partition_table
to generate the partition table bin -
Build the Zephyr app, generating ELF -
Convert zephyr.elf to a bootloader-bootable bin file
Step 3 above is the normal Zephyr build, while steps 1, 2, and 4 are described in zephyr/soc/riscv/esp32c3/CMakeLists.txt
with the part controlled by CONFIG_BOOTLOADER_ESP_IDF.
if ( CONFIG_BOOTLOADER_ESP_IDF )
include ( ExternalProject )
## we use hello-world project, but I think any can be used.
set ( espidf_components_dir ${ESP_IDF_PATH}/components )
set ( espidf_prefix ${CMAKE_BINARY_DIR}/esp-idf )
set ( espidf_build_dir ${espidf_prefix}/build )
# 生成 bootloader
ExternalProject_Add (
EspIdfBootloader
PREFIX ${espidf_prefix}
SOURCE_DIR ${espidf_components_dir}/bootloader/subproject
BINARY_DIR ${espidf_build_dir}/bootloader
CONFIGURE_COMMAND
${CMAKE_COMMAND} -G${CMAKE_GENERATOR}
-S ${espidf_components_dir}/bootloader/subproject
-B ${espidf_build_dir}/bootloader -DSDKCONFIG=${espidf_build_dir}/sdkconfig
-DIDF_PATH=${ESP_IDF_PATH} -DIDF_TARGET=${CONFIG_SOC}
-DPYTHON_DEPS_CHECKED=1
-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}
-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
-DCMAKE_ASM_COMPILER=${CMAKE_ASM_COMPILER}
-DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}
-DPYTHON=${PYTHON_EXECUTABLE}
BUILD_COMMAND
${CMAKE_COMMAND} --build .
INSTALL_COMMAND "" # This particular build system has no install command
)
# 生成分区表
ExternalProject_Add (
EspPartitionTable
SOURCE_DIR ${espidf_components_dir}/partition_table
BINARY_DIR ${espidf_build_dir}
CONFIGURE_COMMAND ""
BUILD_COMMAND
${PYTHON_EXECUTABLE}${ESP_IDF_PATH}/components/partition_table/gen_esp32part.py -q
--offset 0x8000 --flash-size ${esptoolpy_flashsize}MB ${ESP_IDF_PATH}/components/partition_table/partitions_singleapp.csv ${espidf_build_dir}/partitions_singleapp.bin
INSTALL_COMMAND ""
)
set_property ( TARGET bintools PROPERTY disassembly_flag_inline_source )
# app 的依赖于 bootloader 和分区表
add_dependencies ( app EspIdfBootloader EspPartitionTable )
# 指定烧写时使用的 bootloader
board_finalize_runner_args ( esp32 "--esp-flash-bootloader=${espidf_build_dir}/bootloader/bootloader.bin" )
# 指定烧写时使用的分区表
board_finalize_runner_args ( esp32 "--esp-flash-partition_table=${espidf_build_dir}/partitions_singleapp.bin" )
# 指定烧写时分区表的地址
board_finalize_runner_args ( esp32 "--esp-partition-table-address=0x8000" )
endif ( )
if ( CONFIG_MCUBOOT OR CONFIG_BOOTLOADER_ESP_IDF )
# 将 zephyr.elf 用 esptool 转化成 ROM/Bootloader 可引导的 zephyr.bin,也就是前面说的加头和分段
# 当编译的是 mcuboot 时,mcuboot 可以被 ROM 引导
if ( CONFIG_BUILD_OUTPUT_BIN )
set_property ( GLOBAL APPEND PROPERTY extra_post_build_commands
COMMAND ${PYTHON_EXECUTABLE}${ESP_IDF_PATH}/components/esptool_py/esptool/esptool.py
ARGS --chip esp32c3 elf2image --flash_mode dio --flash_freq 40m --flash_size ${esptoolpy_flashsize}MB
-o ${CMAKE_BINARY_DIR}/zephyr/${CONFIG_KERNEL_BIN_NAME}.bin
${CMAKE_BINARY_DIR}/zephyr/${CONFIG_KERNEL_BIN_NAME}.elf )
endif ( )
# 如果编译的是 mcuboot,指定烧写的 bootloader 为 mcuboot
if ( CONFIG_MCUBOOT )
board_finalize_runner_args ( esp32 "--esp-flash-bootloader=${CMAKE_BINARY_DIR}/zephyr/${CONFIG_KERNEL_BIN_NAME}.bin" )
endif ( )
endif ( )
The ld file used for building ESP32C3 is zephyr/soc/riscv/esp32c3/linker.ld
, while the ld used for building the app is zephyr/soc/riscv/esp32c3/default.ld
, with the memory region as follows:
MEMORY
{
mcuboot_hdr ( RX ) : org = 0x0, len = 0x20
metadata ( RX ) : org = 0x20, len = 0x20
ROM ( RX ) : org = 0x40, len = 4194304-0x40
iram0_0_seg ( RX ) : org = ( 0x4037C000 + 0x4000 ) , len = 0x403D0000 - ( 0x4037C000-0x3FC7C000 ) - ( 0x3FC7C000 + 0x4000 )
irom0_0_seg ( RX ) : org = 0x42000020, len = ( 4194304-0x20 )
drom0_0_seg ( R ) : org = 0x3C000040, len = 4194304-0x40
dram0_0_seg ( RW ) : org = ( 0x3FC7C000 + 0x4000 ) , len = 0x403D0000 - ( 0x4037C000-0x3FC7C000 ) - ( 0x3FC7C000 + 0x4000 )
rtc_iram_seg ( RWX ) : org = 0x50000000, len = 0x2000
IDT_LIST ( RW ) : org = 0x3ebfe010, len = 0x2000
}
For images that are not booted by MCUboot, mcuboot_hdr, metadata, ROM, and IDT_LIST are all useless. The remaining corresponding flash map:
-
iram0_0_seg: Internal Instruction RAM -
irom0_0_seg: External Flash, Instruction, mapped to Flash via MMU -
drom0_0_seg: External Flash, Data, mapped to Flash via MMU -
dram0_0_seg: Internal Data RAM -
rtc_iram_seg: RTC uses internal instruction/data RAM
Corresponding information in the SOC spec:
The built zephyr.bin can be used to dump information about each segment with esptool.
frank@DESKTOP-OCT9105:/mnt/g/project/v3.4.0$ /mnt/g/project/v3.4.0/modules/hal/espressif/components/esptool_py/esptool/esptool.py --chip esp32c3 image_info build/zephyr/zephyr.bin
esptool.py v4.5
Image version: 1
Entry point: 4038620a
8 segments
Segment 1: len 0x0001c load 0x00000020 file_offs 0x00000018 [PADDING]
Segment 2: len 0x00eac load 0x3fc9e898 file_offs 0x0000003c [DRAM,BYTE_ACCESSIBLE]
Segment 3: len 0x0069c load 0x3fc9f744 file_offs 0x00000ef0 [DRAM,BYTE_ACCESSIBLE]
Segment 4: len 0x0ea9c load 0x40380000 file_offs 0x00001594 [IRAM]
Segment 5: len 0x07a84 load 0x3c000040 file_offs 0x00010038 [DROM]
Segment 6: len 0x018a0 load 0x4038ea9c file_offs 0x00017ac4 [IRAM]
Segment 7: len 0x06ca4 load 0x00000000 file_offs 0x0001936c [PADDING]
Segment 8: len 0x17510 load 0x42010020 file_offs 0x00020018 [IROM]
Checksum: ac ( valid )
Validation Hash: 19c68846341fcc4a9bd19d9913b26b302a4aae622ef2b3bd50247e2c8f8b9a17 ( valid )
Flashing
All build results are placed in the build directory:
-
Bootloader: build/esp-idf/build/bootloader/bootloader.bin -
Partition Table: build/esp-idf/build/partitions_singleapp.bin -
App: build/zephyr/zephyr.bin
When executing flashing with west flash, it relies on the parameters added by board_finalize_runner_args, and will use esptool to flash the bootloader, partition table, and zephyr.bin simultaneously.
runners.esp32: /usr/bin/python3 /mnt/g/project/v3.4.0/modules/hal/espressif/components/esptool_py/esptool/esptool.py --chip auto --baud 921600 --before default_reset --after hard_reset write_flash -u --flash_mode dio --flash_freq 40m --flash_size detect 0x0 /mnt/g/project/v3.4.0/build/esp-idf/build/bootloader/bootloader.bin 0x8000 /mnt/g/project/v3.4.0/build/esp-idf/build/partitions_singleapp.bin 0x10000 /mnt/g/project/v3.4.0/build/zephyr/zephyr.bin
Boot Process
-
ROM reads the Bootloader from address 0, loads the Bootloader according to the header, and the Bootloader runs in the chip’s internal IRAM. -
The Bootloader reads the partition table, and according to the information in the partition table, reads the app, copies the text and data of the app to the internal SRAM according to the app’s header, establishes the flash MMU, and jumps to the app’s entry address to execute. The app runs on flash.