Skip to content

Commit 49dc3d2

Browse files
committed
ESP32-H2 support
1 parent 0def400 commit 49dc3d2

File tree

15 files changed

+415
-31
lines changed

15 files changed

+415
-31
lines changed

.github/workflows/build.yml

+14-4
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,24 @@ jobs:
1111
runs-on: ubuntu-22.04
1212
strategy:
1313
matrix:
14+
target: [esp32c3, esp32h2]
15+
example: ["blink", "hello_world"]
1416
include:
15-
- example: "blink"
17+
- target: esp32c3
18+
example: "blink"
1619
expected_size: 7436
17-
- example: "hello_world"
20+
- target: esp32c3
21+
example: "hello_world"
1822
expected_size: 10216
23+
- target: esp32h2
24+
example: "blink"
25+
expected_size: 4561
26+
- target: esp32h2
27+
example: "hello_world"
28+
expected_size: 8484
1929
steps:
2030
- uses: actions/checkout@v2
21-
- name: Build for ESP32-C3
31+
- name: Build ${{ matrix.example }} for ${{ matrix.target }}
2232
shell: bash
2333
run: |
2434
export TOOLCHAIN_VERSION="12.2.0-3"
@@ -30,7 +40,7 @@ jobs:
3040
export PATH=$PWD/${TOOLCHAIN_DIR}/bin:$PATH
3141
cd examples/${{ matrix.example }}
3242
mkdir -p build
33-
cmake -S . -B build
43+
cmake -S . -B build -D target=${{ matrix.target }}
3444
cmake --build build
3545
bin_size=$(stat --format="%s" build/${{ matrix.example }}.bin)
3646
expected_size=${{ matrix.expected_size }}

README.md

+32-16
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,29 @@
1-
# ESP32-C3 Direct Boot example
1+
# Direct Boot example
22

3-
This is an example of ESP32-C3 "direct boot" feature. It allows an application to be executed directly from flash, without using the 2nd stage bootloader.
3+
Supported chips: ESP32-C3, ESP32-H2
4+
5+
This is an example of "direct boot" feature.
6+
It allows an application to be executed directly from flash, without using the 2nd stage bootloader.
47

58
## Background
69

7-
ESP8266 and ESP32 series of chips share the common [binary image format](https://github.com/espressif/esptool/wiki/Firmware-Image-Format). This format describes how the binary image stored in flash should be loaded into IRAM/DRAM by the ROM bootloader. In typical applications, the ROM bootloader doesn't load the application binary directly. Instead, it loads the 2nd stage bootloader into RAM. The 2nd stage bootloader then loads the application: sections which should reside in RAM are copied from flash into RAM, and cache MMU is configured to map the remaining sections, which are accessed from flash.
10+
ESP8266 and ESP32 series of chips share the common [binary image format](https://docs.espressif.com/projects/esptool/en/latest/esp32/advanced-topics/firmware-image-format.html). This format describes how the binary image stored in flash should be loaded into IRAM/DRAM by the ROM bootloader. In typical applications, the ROM bootloader doesn't load the application binary directly. Instead, it loads the 2nd stage bootloader into RAM. The 2nd stage bootloader then loads the application: sections which should reside in RAM are copied from flash into RAM, and cache MMU is configured to map the remaining sections, which are accessed from flash.
811

912
Compared to other microcontrollers, where the program in flash is executed directly without the need for additional stages of bootloaders, this arrangement does add complexity. However, it should be noted that in most production applications the 2nd stage bootloader is required to support firmware update, rollback, and security features. Because of this, the 2nd stage bootloader is used in ESP-IDF, despite the extra complexity.
1013

11-
## Direct boot in ESP32-C3
14+
## Direct boot feature
1215

13-
ESP32-C3 (starting from silicon revision 3) allows an application stored in flash to be executed directly, without being copied into RAM. This makes it possible to link an application with a relatively simple linker script, and produce the binary using `objcopy` command, then flash the resulting binary to the ESP32-C3.
16+
Chips, supported in this example (for example, ESP32-C3, starting from silicon revision 3) allow an application stored in flash to be executed directly, without being copied into RAM. This makes it possible to link an application with a relatively simple linker script, and produce the binary using `objcopy` command, then flash the resulting binary to the target chip.
1417

1518
Direct boot feature is activated under the following conditions:
1619
* Secure boot is disabled.
1720
* Direct boot feature is not disabled via `EFUSE_DIS_LEGACY_SPI_BOOT` eFuse bit.
1821
* The ROM bootloader doesn't detect a valid binary image [in the usual format](https://github.com/espressif/esptool/wiki/Firmware-Image-Format)
1922
* The first 8 bytes in flash are `1d 04 db ae 1d 04 db ae` — that is a "magic number" 0xaedb041d repeated twice.
2023

21-
In this case, the ROM bootloader sets up Flash MMU to map 4 MB of Flash to addresses 0x42000000 (for code execution) and 0x3C000000 (for read-only data access). The bootloader then jumps to address 0x42000008, i.e. to the instruction at offset 8 in flash, immediately after the magic numbers.
24+
In this case, the ROM bootloader sets up Flash MMU to map all amount of Flash then jumps to address `Flash address + 8`, i.e. to the instruction at offset 8 in flash, immediately after the magic numbers.
25+
26+
For example, the ROM bootloader of ESP32-C3 sets up Flash MMU to map 4 MB of Flash to addresses 0x42000000 (for code execution) and 0x3C000000 (for read-only data access).
2227

2328
The application entry function needs to:
2429
1. set up global pointer register
@@ -41,7 +46,7 @@ Use it if all of the below are true:
4146
* The code doesn't fit into RAM, therefore execution from flash is required.
4247
* Dependency on the ESP-specific binary image format or the ESP-IDF 2nd stage bootloader is undesirable.
4348

44-
This feature can also be useful in an educational context to "hide" the added complexity of ESP32-C3 Flash MMU and cache configuration.
49+
This feature can also be useful in an educational context to "hide" the added complexity of chip Flash MMU and cache configuration.
4550

4651
## Alternatives to direct boot
4752

@@ -56,16 +61,17 @@ If the entire application code is small enough to fit into RAM, then the direct
5661
This example contains the following parts:
5762

5863
* [common/](common/) directory with the application entrypoint, placeholder for the vector table, and a simple implementation of `_write` syscall.
59-
* [ld/](ld/) directory with the linker scripts.
60-
* [examples/hello_world/](examples/hello_world/) directory with the minimal example project which prints "Hello, world!" to the UART.
6164
* [examples/blink/](examples/blink/) directory with an example project which blinks an LED.
65+
* [examples/hello_world/](examples/hello_world/) directory with the minimal example project which prints "Hello, world!" to the UART.
66+
* [img/](img/) directory with *.svg format diagrams which illustrate the run-time memory layout and binary image layout when direct boot is used.
67+
* [ld/](ld/) directory with the linker scripts.
6268

6369

6470
## Prerequisites
6571

6672
### Cross-compiler
6773

68-
Download and install `riscv-none-elf-gcc` toolchain, for example from the [xPack project](https://github.com/xpack-dev-tools/riscv-none-elf-gcc-xpack/releases).
74+
Download and install `riscv-none-elf-gcc` toolchain, for example from the [xPack project](https://github.com/xpack-dev-tools/riscv-none-elf-gcc-xpack/releases).
6975

7076
This example has been built and tested with toolchain release `12.2.0-3`.
7177

@@ -79,7 +85,7 @@ This example uses CMake. Make sure that CMake and your build system of choice (e
7985

8086
### esptool.py
8187

82-
To flash binaries into the ESP32-C3, [esptool.py](https://github.com/espressif/esptool) is used.
88+
To flash binaries into the chip, [esptool.py](https://github.com/espressif/esptool) is used.
8389

8490
If you have Python and pip installed, you can install esptool using:
8591
```bash
@@ -105,7 +111,7 @@ To debug the examples using JTAG and GDB, follow these steps:
105111
riscv-none-elf-gdb -x gdbinit build/blink
106112
```
107113
This will use the provided gdbinit file to:
108-
- Launch OpenOCD in pipe mode. Adjust the `gdbinit` file if you need to change OpenOCD launch configuration. You can also launch OpenOCD manually, in that case use `target extended-remote :3333` in `gdbinit`.
114+
- Launch OpenOCD in pipe mode. Adjust the `gdbinit` file if you need to change OpenOCD launch configuration or select another target chip. You can also launch OpenOCD manually, in that case use `target extended-remote :3333` in `gdbinit`.
109115
- Flash the program over JTAG
110116
- Reset the target
111117
- Set a temporary breakpoint at `main`
@@ -114,16 +120,26 @@ To debug the examples using JTAG and GDB, follow these steps:
114120

115121
## Memory layout
116122

117-
The following diagram illustrates the run-time memory layout and binary image layout when direct boot is used.
118-
119-
![img/esp32c3-directboot.svg](img/esp32c3-directboot.svg)
123+
### ESP32-C3
120124

121125
The sections shown in blue on the left are parts of the flash image.
122126

127+
![img/esp32c3-directboot.svg](img/esp32c3-directboot.svg)
128+
123129
ROM bootloader maps the 0 – 4 MB region of flash to the CPU address space twice: to the "DROM" region using the data cache, and to the "IROM" region using the instruction cache.
124130

125131
As it is obvious from the diagram, some parts of this mapping are unnecessary. These parts are shown in gray on the right. For example, `.text` section gets mapped not only to the IROM, but also to DROM, even though code execution only happens through IROM.
126132

127133
Such uniform mapping was chosen simply because it is universal, and can be set up by the ROM code without any prior knowledge about the application being loaded. This mapping isn't in any way a limitation of ESP32-C3 cache hardware; for example, ESP-IDF 2nd stage bootloader maps only those regions which are necessary in the given part of the address space.
128134

129-
The run-time memory layout and flash binary image layout shown above are achieved in the linker script ([ld/common.ld](ld/common.ld)) by specifying the LMAs (load addresses). LMAs start at 0, and match the addresses in flash. VMAs for IROM (`entry` and `.text`) and DROM (`.rodata`) sections are set in such a way that LMA == VMA - BASE, where *BASE* is the starting address of IROM or DROM. Non-cached `.data` section is then added at the next available LMA.
135+
The run-time memory layout and flash binary image layout shown above are achieved in the linker script ([ld/esp32c3/common.ld](ld/esp32c3/common.ld)) by specifying the LMAs (load addresses). LMAs start at 0, and match the addresses in flash. VMAs for IROM (`entry` and `.text`) and DROM (`.rodata`) sections are set in such a way that LMA == VMA - BASE, where *BASE* is the starting address of IROM or DROM. Non-cached `.data` section is then added at the next available LMA.
136+
137+
### ESP32-H2
138+
139+
![img/esp32h2-directboot.svg](img/esp32h2-directboot.svg)
140+
141+
ROM bootloader maps the 0 – 4 MB region of flash to the CPU address space using the cache and the Flash MMU.
142+
143+
The memory layout can be found in liker script ([ld/esp32h2/memory.ld](ld/esp32h2/memory.ld)).
144+
145+
The run-time memory layout and flash binary image layout shown above are achieved in the linker script ([ld/esp32h2/common.ld](ld/esp32h2/common.ld)) by specifying the LMAs (load addresses). LMAs start at 0, and match the addresses in flash. VMAs for ROM (`entry`, `.text` and `.rodata`) section is set in such a way that LMA == VMA - BASE, where *BASE* is the starting address of ROM. Non-cached `.data` section is then added at the next available LMA.

common/CMakeLists.txt

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1-
enable_language(ASM)
1+
enable_language(C ASM)
22

3-
list(APPEND srcs
3+
list(APPEND srcs
44
"start.S"
55
"vectors.S"
66
"syscalls.c"
77
)
88

99
add_library(common STATIC ${srcs})
10-
target_include_directories(common PUBLIC include)
10+
target_include_directories(common PUBLIC)
1111
set_target_properties(common PROPERTIES C_STANDARD 11)
12+
target_compile_options(common PRIVATE -g -Og)
1213
target_link_libraries(common INTERFACE -nostartfiles)
1314

1415

examples/blink/CMakeLists.txt

-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ target_link_libraries(blink PRIVATE common hal)
1414

1515
target_compile_options(blink PRIVATE -g -Og)
1616
target_link_options(blink PRIVATE -g)
17-
1817
target_compile_options(blink PRIVATE -Wall -Werror -Wextra)
1918

2019
include(${ROOT_DIR}/utils.cmake)

examples/blink/README.md

+4-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Hardware
44

5-
Attach an LED and a current limiting resistor between GPIO 2 and 3V3 pins of an ESP32-C3 development board.
5+
Attach an LED and a current limiting resistor between GPIO 2 and 3V3 pins of a development board.
66

77
## Building and running the example
88

@@ -11,9 +11,10 @@ Attach an LED and a current limiting resistor between GPIO 2 and 3V3 pins of an
1111
```bash
1212
cd examples/blink
1313
mkdir build
14-
cmake -B build -G Ninja .
14+
cmake -B build -D target=esp32c3 -G Ninja .
1515
cmake --build build
1616
```
17+
For other chip, please use the `target=chip_name`, where `chip_name` can be any from the supported ones.
1718
You should get the following output at the end:
1819
```
1920
[3/4] Running utility command for blink-size
@@ -31,4 +32,4 @@ Attach an LED and a current limiting resistor between GPIO 2 and 3V3 pins of an
3132
esptool.py --port /dev/ttyUSB0 --baud 921600 write_flash 0x0000 build/blink.bin
3233
```
3334
(Adjust the serial port name as needed.)
34-
4. The LED attached to GPIO 2 should be blinking at around 3 Hz rate.
35+
4. The LED attached to GPIO 2 should be blinking at around 3 Hz rate for ESP32-C3 (frequency can be vary depending on the maximum frequency of the selected chip).

examples/hello_world/README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@
77
```bash
88
cd examples/hello_world
99
mkdir build
10-
cmake -B build -G Ninja .
10+
cmake -B build -D target=esp32c3 -G Ninja .
1111
cmake --build build
1212
```
13+
For other chip, please use the `target=chip_name`, where `chip_name` can be any from the supported ones.
1314
You should get the following output at the end:
1415
```
1516
[2/3] Generating hello_world.bin

hal/CMakeLists.txt

+7-1
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,14 @@ FetchContent_GetProperties(
1515
)
1616
message(STATUS "Downloaded esp-hal-components into ${esp_hal_components_srcdir}")
1717

18+
set(supported_mcu esp32c3 esp32h2)
19+
if(DEFINED target AND target IN_LIST supported_mcu)
20+
message(STATUS "Configure project for ${target} ... ")
21+
else()
22+
message(FATAL_ERROR "Invalid target. \nUse argument '-D target=chip_name', where chip_name=[${supported_mcu}]")
23+
endif()
24+
1825
# FIXME: soc component depends on sdkconfig.h
19-
set(target esp32c3)
2026
string(TOUPPER ${target} target_uppercase)
2127
set(config_dir ${CMAKE_CURRENT_BINARY_DIR}/config)
2228
file(MAKE_DIRECTORY ${config_dir})

img/esp32h2-directboot.svg

+1
Loading

ld/common.ld ld/esp32c3/common.ld

File renamed without changes.

ld/esp32c3.ld ld/esp32c3/memory.ld

File renamed without changes.
File renamed without changes.

ld/esp32h2/common.ld

+162
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
ENTRY(_start)
2+
3+
SECTIONS
4+
{
5+
.header : AT(0)
6+
{
7+
_rom_start = .;
8+
LONG(0xaedb041d)
9+
LONG(0xaedb041d)
10+
} > rom
11+
12+
.text.entry ORIGIN(rom) + 8 :
13+
{
14+
KEEP(*(.text.entry))
15+
} > rom
16+
17+
.text :
18+
{
19+
*(.text .stub .text.* .gnu.linkonce.t.*)
20+
*(.rela.text .rela.text.* .rela.gnu.linkonce.t.*)
21+
*(.gnu.warning)
22+
}
23+
. = ALIGN(4);
24+
25+
.rodata :
26+
{
27+
*(.rodata .rodata1 .rodata.* .srodata .srodata.* .sdata2 .sdata2.* .gnu.linkonce.r.*)
28+
*(.rela.data .rela.data.* .rela.gnu.linkonce.r.*)
29+
} > rom
30+
31+
.init_array :
32+
{
33+
PROVIDE_HIDDEN (__init_array_start = .);
34+
KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)))
35+
KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors))
36+
PROVIDE_HIDDEN (__init_array_end = .);
37+
} > rom
38+
39+
.fini_array :
40+
{
41+
PROVIDE_HIDDEN (__fini_array_start = .);
42+
KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*)))
43+
KEEP (*(.fini_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .dtors))
44+
PROVIDE_HIDDEN (__fini_array_end = .);
45+
} > rom
46+
47+
.ctors :
48+
{
49+
KEEP (*crtbegin.o(.ctors))
50+
KEEP (*crtbegin?.o(.ctors))
51+
KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors))
52+
KEEP (*(SORT(.ctors.*)))
53+
KEEP (*(.ctors))
54+
} > rom
55+
56+
.dtors :
57+
{
58+
KEEP (*crtbegin.o(.dtors))
59+
KEEP (*crtbegin?.o(.dtors))
60+
KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors))
61+
KEEP (*(SORT(.dtors.*)))
62+
KEEP (*(.dtors))
63+
} > rom
64+
65+
PROVIDE (__etext = .);
66+
PROVIDE (_etext = .);
67+
PROVIDE (etext = .);
68+
_rom_size = . - _rom_start;
69+
70+
.data ORIGIN(ram) : AT(_rom_size)
71+
{
72+
_data_start = .;
73+
__DATA_BEGIN__ = .;
74+
*(.data .data.* .gnu.linkonce.d.*)
75+
*(.data.rel.ro.local* .gnu.linkonce.d.rel.ro.local.*) *(.data.rel.ro .data.rel.ro.* .gnu.linkonce.d.rel.ro.*)
76+
SORT(CONSTRUCTORS)
77+
} > ram
78+
.data1 :
79+
{
80+
*(.data1)
81+
}
82+
.sdata :
83+
{
84+
__SDATA_BEGIN__ = .;
85+
*(.sdata .sdata.* .gnu.linkonce.s.*)
86+
}
87+
. = ALIGN(4);
88+
_edata = .; PROVIDE (edata = .);
89+
_data_lma = ORIGIN(rom) + LOADADDR(.data);
90+
_data_size = _edata - _data_start;
91+
92+
__bss_start = .;
93+
.sbss :
94+
{
95+
*(.dynsbss)
96+
*(.sbss .sbss.* .gnu.linkonce.sb.*)
97+
*(.scommon)
98+
}
99+
.bss :
100+
{
101+
*(.dynbss)
102+
*(.bss .bss.* .gnu.linkonce.b.*)
103+
*(COMMON)
104+
}
105+
. = ALIGN(4);
106+
__BSS_END__ = .;
107+
__global_pointer$ = MIN(__SDATA_BEGIN__ + 0x800,
108+
MAX(__DATA_BEGIN__ + 0x800, __BSS_END__ - 0x800));
109+
_end = .; PROVIDE (end = .);
110+
111+
/* Stack */
112+
.stack :
113+
{
114+
__stack_bottom = .;
115+
__stack_top = ORIGIN(ram) + LENGTH(ram);
116+
__stack_size_min = 0x4000;
117+
ASSERT(__stack_bottom + __stack_size_min < __stack_top, "Error: no space for stack");
118+
}
119+
120+
/* Stabs debugging sections. */
121+
.stab 0 : { *(.stab) }
122+
.stabstr 0 : { *(.stabstr) }
123+
.stab.excl 0 : { *(.stab.excl) }
124+
.stab.exclstr 0 : { *(.stab.exclstr) }
125+
.stab.index 0 : { *(.stab.index) }
126+
.stab.indexstr 0 : { *(.stab.indexstr) }
127+
.comment 0 : { *(.comment) }
128+
.gnu.build.attributes : { *(.gnu.build.attributes .gnu.build.attributes.*) }
129+
/* DWARF debug sections.
130+
Symbols in the DWARF debugging sections are relative to the beginning
131+
of the section so we begin them at 0. */
132+
/* DWARF 1 */
133+
.debug 0 : { *(.debug) }
134+
.line 0 : { *(.line) }
135+
/* GNU DWARF 1 extensions */
136+
.debug_srcinfo 0 : { *(.debug_srcinfo) }
137+
.debug_sfnames 0 : { *(.debug_sfnames) }
138+
/* DWARF 1.1 and DWARF 2 */
139+
.debug_aranges 0 : { *(.debug_aranges) }
140+
.debug_pubnames 0 : { *(.debug_pubnames) }
141+
/* DWARF 2 */
142+
.debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) }
143+
.debug_abbrev 0 : { *(.debug_abbrev) }
144+
.debug_line 0 : { *(.debug_line .debug_line.* .debug_line_end) }
145+
.debug_frame 0 : { *(.debug_frame) }
146+
.debug_str 0 : { *(.debug_str) }
147+
.debug_loc 0 : { *(.debug_loc) }
148+
.debug_macinfo 0 : { *(.debug_macinfo) }
149+
/* SGI/MIPS DWARF 2 extensions */
150+
.debug_weaknames 0 : { *(.debug_weaknames) }
151+
.debug_funcnames 0 : { *(.debug_funcnames) }
152+
.debug_typenames 0 : { *(.debug_typenames) }
153+
.debug_varnames 0 : { *(.debug_varnames) }
154+
/* DWARF 3 */
155+
.debug_pubtypes 0 : { *(.debug_pubtypes) }
156+
.debug_ranges 0 : { *(.debug_ranges) }
157+
/* DWARF Extension. */
158+
.debug_macro 0 : { *(.debug_macro) }
159+
.debug_addr 0 : { *(.debug_addr) }
160+
.gnu.attributes 0 : { KEEP (*(.gnu.attributes)) }
161+
/DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) }
162+
}

0 commit comments

Comments
 (0)