Skip to main content
Planet Zephyr

RISC-V Bytes: Zephyr on the ESP32

By April 16, 2023No Comments

In the most recent

in the RISC-V Bytes series,
we had our first experience with real hardware, exploring the bootloader on an
module. We were using esp-idf, which,
behind the scenes uses an implementation of
In this post, we’ll swap out the Espressif FreeRTOS implementation for
Zephyr, exploring some
similarities and differences between the build process and produced artifacts.
We’ll also see how we can slim down a Zephyr installation, only fetching the
code and tools that we need to target the ESP32-C3 chipset.


Please excuse my attempt at an illustration of inside of a
Trojan Horse.

Zephyr encourages the use of
west, which
essentially is an extensible wrapper around a variety of other tools. If you are
familiar with the underlying tools, using west can feel a bit cumbersome, but
opting out of doing so means that much of the guides and documentation will not
be applicable to your workflow. I found myself using west in somewhat of a
minimal fashion, but not going fully into using Zephyr without

west-based workflows allow developing Zephyr applications via three different

  • repository:
    building an application within the Zephyr repository
  • workspace:
    building an application in the same workspace as the Zephyr repository (i.e.
    sharing a parent directory)
  • freestanding:
    building an application that is configured to point to some installation of

While repository seems to be the most straightforward for building the in-tree
sample applications, and workspace appears to be the recommended route for
most projects, I found freestanding to be more familiar to my typical
development workflow. That being said, there are pros and cons of all three
options and you should select the one that is most appropriate for your project.
We’ll explore the resulting directory structure when using a freestanding
model in the next section.

Initial tooling setup mostly looks the same regardless of what model you are
going to use. The full steps for installing cmake, python, and dtc are
outlined in the Getting Started
I opted, partially due to my desire to use the freestanding model, to do a
global installation of west.

$ pip3 install --user -U west

With tools in place, it is now time to download Zephyr itself. Rather than using
west init ~/zephyrproject, which will clone the latest Zephyr version into
~/zephyrproject/zephyr, I created a new subdirectory in my existing
~/code/ directory for workspaces, and a subdirectory
within it for the workspace we will use for the remainder of this blog post.

$ mkdir -p workspaces/v3.3.0

As the name suggests, this workspace will be set up to with Zephyr v3.3.0,
which is the latest release. Within the v3.3.0 directory, rather than
running a bare west init as the guide suggests, we’ll create a manifest
, which will inform the version of Zephyr we want and any additional

Note: if we were using the workspace application model, the manifest repository could
be our application repository itself. If we were using the repository
application model, the manifest repository would be Zephyr.


    - name: zephyr
      revision: v3.3.0
      west-commands: scripts/west-commands.yml
          - hal_espressif

Here we are specifying that we want the v3.3.0 release of Zephyr, and we only
want the hal_espressif project imported from the list of projects in its
west.yml. Now we can run west init in the v3.3.0 directory and point
it to our local manifest repository.

$ west init -l manifest/
=== Initializing from existing manifest repository manifest
--- Creating /home/hasheddan/code/ and local configuration file
=== Initialized. Now run "west update" inside /home/hasheddan/code/

All this will do is setup west to use the local manifest for future
operations. Following the instructions in the output, along with some extra
flags, will download Zephyr and the hal_espressif.

$ west update --narrow -o=--depth=1
=== updating zephyr (zephyr):
--- zephyr: initializing
Initialized empty Git repository in /home/hasheddan/code/
--- zephyr: fetching, need revision v3.3.0
remote: Enumerating objects: 28549, done.
remote: Counting objects: 100% (28549/28549), done.
remote: Compressing objects: 100% (22159/22159), done.
remote: Total 28549 (delta 6571), reused 14659 (delta 4784), pack-reused 0
Receiving objects: 100% (28549/28549), 57.04 MiB | 23.41 MiB/s, done.
Resolving deltas: 100% (6571/6571), done.
 * tag                 v3.3.0     -> FETCH_HEAD
Updating files: 100% (24352/24352), done.
HEAD is now at 07c6af3b release: Zephyr 3.3.0
HEAD is now at 07c6af3b release: Zephyr 3.3.0
=== updating hal_espressif (modules/hal/espressif):
--- hal_espressif: initializing
Initialized empty Git repository in /home/hasheddan/code/
--- hal_espressif: fetching, need revision 73e7af1e2ed64571ce49ff9f07dc02690b9f2df5
remote: Enumerating objects: 11222, done.
remote: Counting objects: 100% (11222/11222), done.
remote: Compressing objects: 100% (9234/9234), done.
remote: Total 11222 (delta 2547), reused 6008 (delta 1450), pack-reused 0
Receiving objects: 100% (11222/11222), 64.90 MiB | 22.79 MiB/s, done.
Resolving deltas: 100% (2547/2547), done.
 * branch            73e7af1e2ed64571ce49ff9f07dc02690b9f2df5 -> FETCH_HEAD
Updating files: 100% (9878/9878), done.
HEAD is now at 73e7af1 hal: clock: esp32c3: Fix default cpu frequency magnitude
HEAD is now at 73e7af1 hal: clock: esp32c3: Fix default cpu frequency magnitude

Without the name-allowlist restriction, we would instead download all
projects in the Zephyr west.yml, which is much more than we need. There is a
tradeoff in this slimmer approach in that if we wanted to target multiple
chipsets, or just expand functionality, with the same project, we would need to
either add to the allowlist, or setup a new workspace. The philosophy of
Zephyr’s getting started guide ensures that users will have everything they need
when getting started, but it also means that they will likely download a number
of projects they will never use.

The --narrow and -o=--depth=1 flags are also useful in reducing the amount
of content we download and store. Without the former,
is passed to git fetch,
causing all tags to be fetched from the remote. The latter passes the
flag to git fetch.
The result of these operations can be observed by viewing the git history of
Zephyr and hal_espressif.

$ git -C zephyr/ log
commit 07c6af3b8c35c1e49186578ca61a25c76e2fb308 (grafted, HEAD, manifest-rev)
Author: Stephanos Ioannidis 
Date:   Sat Feb 18 07:13:22 2023 +0900

    release: Zephyr 3.3.0
    This commit sets the Zephyr version to v3.3.0.
    Signed-off-by: Stephanos Ioannidis 
    Signed-off-by: Lauren Murphy 
$ git -C modules/hal/espressif/ log
commit 73e7af1e2ed64571ce49ff9f07dc02690b9f2df5 (grafted, HEAD, manifest-rev)
Author: Lucas Tamborrino 
Date:   Mon Jan 9 10:22:24 2023 -0300

    hal: clock: esp32c3: Fix default cpu frequency magnitude
    The value must be corrected in hal after fetching it from dts
    Signed-off-by: Lucas Tamborrino 

Note: If your application depends on functionality that is only available in
binary form, it may be necessary to fetch
Examples of blobs that may be required when targeting ESP32 systems can be
found in the hal_espressif module.yml
and instructions for how to fetch are present in the ESP32-C3

This may not be a great strategy for other project layouts where it is expected
that versions may be updated. However, if you plan to use a single version per
workspace strategy, as we have chosen here, we are not concerned about having a
partial history. The only subsequent update operations we should be performing
in this workspace would be to add a new project, which would not require any
changes to the existing Zephyr installation or any modules.

The last steps are to export this version of Zephyr as a CMake package and
install all Python dependencies. The latter can be done with the following
command, which will tell CMake where to look for configuration for this version
of Zephyr. This will allow us to add the Zephyr package to our freestanding
project a bit later.

$ west zephyr-export
Zephyr (/home/hasheddan/code/
has been added to the user package registry in:

ZephyrUnittest (/home/hasheddan/code/
has been added to the user package registry in:

Installing Python dependencies is a simple as doing a pip install on the
Zephyr repository’s requirements.txt.

pip3 install --user -r zephyr/scripts/requirements.txt

We have all of the code we will need, but we need a toolchain in order to build
our image. Zephyr has made this quite straightforward via the Zephyr
. It provides toolchains for
every Zephyr supported target. However, the getting started guide will once
again have you install all of them by default. This is RISC-V Bytes and we only
need our RISC-V toolchain to target the ESP32-C3, so we’ll instead use the
minimal bundle and only specify the toolchain we want when performing setup.

$ cd ~
$ wget
--2023-04-16 20:46:36--
Resolving (
Connecting to (||:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: [following]
--2023-04-16 20:46:36--
Resolving (,,, ...
Connecting to (||:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 41512804 (40M) [application/octet-stream]
Saving to: ‘zephyr-sdk-0.16.0_linux-x86_64_minimal.tar.xz’

zephyr-sdk-0.16.0_linux-x86_64_minimal.tar.xz      100%[===============================================================================================================>]  39.59M  27.6MB/s    in 1.4s    

2023-04-16 20:46:38 (27.6 MB/s) - ‘zephyr-sdk-0.16.0_linux-x86_64_minimal.tar.xz’ saved [41512804/41512804]

$ wget -O - | shasum --check --ignore-missing
--2023-04-16 20:46:38--
Resolving (
Connecting to (||:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: [following]
--2023-04-16 20:46:43--
Resolving (,,, ...
Connecting to (||:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 12148 (12K) [application/octet-stream]
Saving to: ‘STDOUT’

-                                                  100%[===============================================================================================================>]  11.86K  --.-KB/s    in 0.001s  

2023-04-16 20:46:48 (15.7 MB/s) - written to stdout [12148/12148]

zephyr-sdk-0.16.0_linux-x86_64_minimal.tar.xz: OK

The minimal bundle includes a script that will prompt the user for
toolchains that should be installed. We can extract the bundle, then run

$ tar xvf zephyr-sdk-0.16.0_linux-x86_64_minimal.tar.xz -C ~/.local/

$ ~/.local/zephyr-sdk-0.16.0/ 
Zephyr SDK 0.16.0 Setup

** NOTE **
You only need to run this script once after extracting the Zephyr SDK
distribution bundle archive.

Install toolchains for all targets [y/n]? n
Install 'aarch64-zephyr-elf' toolchain [y/n]? n
Install 'arc64-zephyr-elf' toolchain [y/n]? n
Install 'arc-zephyr-elf' toolchain [y/n]? n
Install 'arm-zephyr-eabi' toolchain [y/n]? n
Install 'microblazeel-zephyr-elf' toolchain [y/n]? n
Install 'mips-zephyr-elf' toolchain [y/n]? n
Install 'nios2-zephyr-elf' toolchain [y/n]? n
Install 'riscv64-zephyr-elf' toolchain [y/n]? y
Install 'sparc-zephyr-elf' toolchain [y/n]? n
Install 'x86_64-zephyr-elf' toolchain [y/n]? n
Install 'xtensa-espressif_esp32_zephyr-elf' toolchain [y/n]? n
Install 'xtensa-espressif_esp32s2_zephyr-elf' toolchain [y/n]? n
Install 'xtensa-espressif_esp32s3_zephyr-elf' toolchain [y/n]? n
Install 'xtensa-intel_apl_adsp_zephyr-elf' toolchain [y/n]? n
Install 'xtensa-intel_s1000_zephyr-elf' toolchain [y/n]? n
Install 'xtensa-nxp_imx_adsp_zephyr-elf' toolchain [y/n]? n
Install 'xtensa-nxp_imx8m_adsp_zephyr-elf' toolchain [y/n]? n
Install 'xtensa-sample_controller_zephyr-elf' toolchain [y/n]? n
Install host tools [y/n]? y
Register Zephyr SDK CMake package [y/n]? y

Installing 'riscv64-zephyr-elf' toolchain ...
toolchain_linux-x86_64_riscv64-zephyr-elf.tar.xz   100%[===============================================================================================================>] 105.29M  35.9MB/s    in 2.9s    

Installing host tools ...

Registering Zephyr SDK CMake package ...
Zephyr-sdk (/home/hasheddan/.local/zephyr-sdk-0.16.0/cmake)
has been added to the user package registry in:

All done.
Press any key to exit ...

Note: You may have noticed that the installed toolchain is for 64-bit RISC-V
(riscv64), but we are targeting a 32-bit chipset. The 64-bit toolchain can
target both 32-bit and 64-bit architectures (as specified by flags).

You should see all RISC-V tools now present.

$ ls ~/.local/zephyr-sdk-0.16.0/riscv64-zephyr-elf/bin/
riscv64-zephyr-elf-addr2line  riscv64-zephyr-elf-cpp           riscv64-zephyr-elf-gcc-ar      riscv64-zephyr-elf-gdb               riscv64-zephyr-elf-ld        riscv64-zephyr-elf-ranlib
riscv64-zephyr-elf-ar         riscv64-zephyr-elf-ct-ng.config  riscv64-zephyr-elf-gcc-nm      riscv64-zephyr-elf-gdb-add-index     riscv64-zephyr-elf-ld.bfd    riscv64-zephyr-elf-readelf
riscv64-zephyr-elf-as         riscv64-zephyr-elf-elfedit       riscv64-zephyr-elf-gcc-ranlib  riscv64-zephyr-elf-gdb-add-index-py  riscv64-zephyr-elf-lto-dump  riscv64-zephyr-elf-size
riscv64-zephyr-elf-c++        riscv64-zephyr-elf-g++           riscv64-zephyr-elf-gcov        riscv64-zephyr-elf-gdb-py            riscv64-zephyr-elf-nm        riscv64-zephyr-elf-strings
riscv64-zephyr-elf-cc         riscv64-zephyr-elf-gcc           riscv64-zephyr-elf-gcov-dump   riscv64-zephyr-elf-gprof             riscv64-zephyr-elf-objcopy   riscv64-zephyr-elf-strip
riscv64-zephyr-elf-c++filt    riscv64-zephyr-elf-gcc-12.2.0    riscv64-zephyr-elf-gcov-tool   riscv64-zephyr-elf-gprof-py          riscv64-zephyr-elf-objdump

After quite a bit of setup, we are now ready to build! We can setup a simple
freestanding repository that builds a similar image to the sample

Create a new directory outside of any Zephyr workspace (I chose
~/code/, and create a
CMakeLists.txt file, as well as src/main.c.

$ mkdir -p zephyr-freestanding/src
$ cd zephyr-freestanding && touch CMakeLists.txt src/main.c
$ tree
├── CMakeLists.txt
└── src
    └── main.c

1 directory, 2 files

All that our application will do is print a message and exit. Including
zepher/kernel.h gives us access to printk, which allows for printing kernel debugging messages.



int main(void)
	printk("Hello, RISC-V Bytes!n");
	return 0;

In CMakeLists.txt, we’ll need to inform the build system where to find our
Zephyr installation, as well as where the source(s) for our application live.


cmake_minimum_required(VERSION 3.20.0)

find_package(Zephyr 3.3.0 REQUIRED)

target_sources(app PRIVATE src/main.c)

CMake will be able to identify the Zephyr package due to the west zephyr-export command we ran earlier. We add the version restriction (3.3.0)
and the REQUIRED directive such that we will fail early if CMake is unable to
identify a suitable package. However, in order to use CMake via the recommended
west build command, we’ll still need to set ZEPHYR_BASE. This is due to the
fact that the build command is implemented as a plugin that lives in the
rather than a built-in command. The only argument that needs to be supplied is
--board, which informs our target, and correspondingly, toolchain and required
modules. Let’s run the build and analyze the output.

Note: We are specifying esp32c3_devkitm despite using the
ESP32-C3-DevKitC-02. Because these two boards both use the ESP32-C3 chip, the
configuration is mostly the same.

$ export ZEPHYR_BASE=~/code/
$ west build -b esp32c3_devkitm
-- west build: generating a build system
Loading Zephyr default modules (Zephyr base).
-- Application: /home/hasheddan/code/
-- CMake version: 3.22.1
-- Found Python3: /usr/bin/python3.10 (found suitable exact version "3.10.6") found components: Interpreter 
-- Cache files will be written to: /home/hasheddan/.cache/zephyr
-- Zephyr version: 3.3.0 (/home/hasheddan/code/
-- Found west (found suitable version "1.0.0", minimum required is "0.7.1")
-- Board: esp32c3_devkitm
-- ZEPHYR_TOOLCHAIN_VARIANT not set, trying to locate Zephyr SDK
-- Found host-tools: zephyr 0.16.0 (/home/hasheddan/.local/zephyr-sdk-0.16.0)
-- Found toolchain: zephyr 0.16.0 (/home/hasheddan/.local/zephyr-sdk-0.16.0)
-- Found Dtc: /home/hasheddan/.local/zephyr-sdk-0.16.0/sysroots/x86_64-pokysdk-linux/usr/bin/dtc (found suitable version "1.6.0", minimum required is "1.4.6") 
-- Found BOARD.dts: /home/hasheddan/code/
-- Generated zephyr.dts: /home/hasheddan/code/
-- Generated devicetree_generated.h: /home/hasheddan/code/
-- Including generated dts.cmake file: /home/hasheddan/code/
Parsing /home/hasheddan/code/
Loaded configuration '/home/hasheddan/code/'
Configuration saved to '/home/hasheddan/code/'
Kconfig header saved to '/home/hasheddan/code/'
-- The C compiler identification is GNU 12.2.0
-- The CXX compiler identification is GNU 12.2.0
-- The ASM compiler identification is GNU
-- Found assembler: /home/hasheddan/.local/zephyr-sdk-0.16.0/riscv64-zephyr-elf/bin/riscv64-zephyr-elf-gcc
-- Configuring done
-- Generating done
-- Build files have been written to: /home/hasheddan/code/
-- west build: building application
[1/179] Preparing syscall dependency handling

[2/179] Generating include/generated/version.h
-- Zephyr version: 3.3.0 (/home/hasheddan/code/, build: 07c6af3b8c35
[162/179] Performing configure step for 'EspIdfBootloader'
-- Found Git: /usr/bin/git (found version "2.34.1") 
-- Building ESP-IDF components for target esp32c3
-- Project sdkconfig file /home/hasheddan/code/
-- Adding linker script /home/hasheddan/code/
-- Adding linker script /home/hasheddan/code/
-- Adding linker script /home/hasheddan/code/
-- Adding linker script /home/hasheddan/code/
-- Adding linker script /home/hasheddan/code/
-- Adding linker script /home/hasheddan/code/
-- Adding linker script /home/hasheddan/code/
-- Components: bootloader bootloader_support efuse esp32c3 esp_common esp_hw_support esp_rom esp_system esptool_py freertos hal log main micro-ecc newlib partition_table riscv soc spi_flash
-- Component paths: /home/hasheddan/code/ /home/hasheddan/code/ /home/hasheddan/code/ /home/hasheddan/code/ /home/hasheddan/code/ /home/hasheddan/code/ /home/hasheddan/code/ /home/hasheddan/code/ /home/hasheddan/code/ /home/hasheddan/code/ /home/hasheddan/code/ /home/hasheddan/code/ /home/hasheddan/code/ /home/hasheddan/code/ /home/hasheddan/code/ /home/hasheddan/code/ /home/hasheddan/code/ /home/hasheddan/code/ /home/hasheddan/code/
-- Configuring done
-- Generating done
-- Build files have been written to: /home/hasheddan/code/
[163/179] Performing build step for 'EspIdfBootloader'
[1/88] Generating project_elf_src_esp32c3.c
[2/88] Building C object esp-idf/soc/CMakeFiles/__idf_soc.dir/soc_include_legacy_warn.c.obj
[86/88] Linking C executable bootloader.elf
[87/88] Generating binary image from built executable v3.3
Creating esp32c3 image...
Merged 1 ELF section
Successfully created esp32c3 image.
Generated /home/hasheddan/code/
[88/88] cd /home/hasheddan/code/ && /usr/bin/python3.10 /home/hasheddan/code/ --offset 0x8000 bootloader 0x0 /home/hasheddan/code/
Bootloader binary size 0x4ba0 bytes. 0x3460 bytes (41%) free.
[169/179] Linking C executable zephyr/zephyr_pre0.elf

[173/179] Linking C executable zephyr/zephyr_pre1.elf

[179/179] Linking C executable zephyr/zephyr.elf
Memory region         Used Size  Region Size  %age Used
     mcuboot_hdr:          32 B         32 B    100.00%
        metadata:          28 B         32 B     87.50%
             ROM:       32164 B    4194240 B      0.77%
     iram0_0_seg:       11376 B       320 KB      3.47%
     irom0_0_seg:       83568 B    4194272 B      1.99%
     drom0_0_seg:        2008 B    4194240 B      0.05%
     dram0_0_seg:       21268 B       320 KB      6.49%
    rtc_iram_seg:          0 GB         8 KB      0.00%
        IDT_LIST:          0 GB         8 KB      0.00% v3.3
Creating esp32c3 image...
Merged 6 ELF sections
Successfully created esp32c3 image.

If some of this output looks familiar, it is likely because you have seen many
of the same steps when using / The
Zephyr build system wraps these tools from
hal_espressif, which is
essentially a fork of esp-idf. In
fact, until we get to building the actual Zephyr application, we are building
the same second stage bootloader from esp-idf that we saw in the last post.

$ ls build/esp-idf/build/bootloader
bootloader.bin  CMakeCache.txt  cmake_install.cmake    config      esp-idf     project_elf_src_esp32c3.c
bootloader.elf     CMakeFiles      compile_commands.json  config.env  project_description.json

If we examine the bootloader.elf file, we’ll see the same steps to load and
jump to the application image.

$ ~/.local/zephyr-sdk-0.16.0/riscv64-zephyr-elf/bin/riscv64-zephyr-elf-objdump -D -j .iram.text build/esp-idf/build/bootloader/bootloader.elf | head -48

build/esp-idf/build/bootloader/bootloader.elf:     file format elf32-littleriscv

Disassembly of section .iram.text:

403ce000 :
403ce000:	7131                	addi	sp,sp,-192
403ce002:	df06                	sw	ra,188(sp)
403ce004:	00000793          	li	a5,0
403ce008:	c789                	beqz	a5,403ce012 
403ce00a:	00000097          	auipc	ra,0x0
403ce00e:	000000e7          	jalr	zero # 0 
403ce012:	288d                	jal	403ce084 
403ce014:	c119                	beqz	a0,403ce01a 
403ce016:	444030ef          	jal	ra,403d145a 
403ce01a:	00000793          	li	a5,0
403ce01e:	c789                	beqz	a5,403ce028 
403ce020:	00000097          	auipc	ra,0x0
403ce024:	000000e7          	jalr	zero # 0 
403ce028:	0a000613          	li	a2,160
403ce02c:	4581                	li	a1,0
403ce02e:	0808                	addi	a0,sp,16
403ce030:	ffc32097          	auipc	ra,0xffc32
403ce034:	324080e7          	jalr	804(ra) # 40000354 
403ce038:	0808                	addi	a0,sp,16
403ce03a:	076030ef          	jal	ra,403d10b0 
403ce03e:	e10d                	bnez	a0,403ce060 
403ce040:	5be020ef          	jal	ra,403d05fe 
403ce044:	85aa                	mv	a1,a0
403ce046:	3fcd6637          	lui	a2,0x3fcd6
403ce04a:	3fcd6537          	lui	a0,0x3fcd6
403ce04e:	14c60613          	addi	a2,a2,332 # 3fcd614c 
403ce052:	15450513          	addi	a0,a0,340 # 3fcd6154 
403ce056:	ffc32097          	auipc	ra,0xffc32
403ce05a:	fea080e7          	jalr	-22(ra) # 40000040 
403ce05e:	bf65                	j	403ce016 
403ce060:	0808                	addi	a0,sp,16
403ce062:	27c030ef          	jal	ra,403d12de 
403ce066:	f9d00793          	li	a5,-99
403ce06a:	c62a                	sw	a0,12(sp)
403ce06c:	faf505e3          	beq	a0,a5,403ce016 
403ce070:	4501                	li	a0,0
403ce072:	2029                	jal	403ce07c 
403ce074:	45b2                	lw	a1,12(sp)
403ce076:	0808                	addi	a0,sp,16
403ce078:	400030ef          	jal	ra,403d1478 

The application it loads lives at build/zephyr/zephyr.elf. We can see the
assembly dump of our main() function in the .flash.text section.

$ ~/.local/zephyr-sdk-0.16.0/riscv64-zephyr-elf/bin/riscv64-zephyr-elf-objdump -D -j .flash.text build/zephyr/zephyr.elf | head -20

build/zephyr/zephyr.elf:     file format elf32-littleriscv

Disassembly of section .flash.text:

42010020 :
42010020:	00008067          	ret

42010024: 3c000537 lui a0,0x3c000 42010028: ff010113 addi sp,sp,-16 4201002c: 04050513 addi a0,a0,64 # 3c000040 42010030: 00112623 sw ra,12(sp) 42010034: fe373097 auipc ra,0xfe373 42010038: ad4080e7 jalr -1324(ra) # 40382b08 4201003c: 00c12083 lw ra,12(sp) 42010040: 00000513 li a0,0 42010044: 01010113 addi sp,sp,16 42010048: 00008067 ret

Lastly, we can find our partition table at
build/esp-idf/build/partitions_singleapp.bin, which looks very similar to the
one we saw when using esp-idf directly.

$ hexdump build/esp-idf/build/partitions_singleapp.bin 
0000000 50aa 0201 9000 0000 6000 0000 766e 0073
0000010 0000 0000 0000 0000 0000 0000 0000 0000
0000020 50aa 0101 f000 0000 1000 0000 6870 5f79
0000030 6e69 7469 0000 0000 0000 0000 0000 0000
0000040 50aa 0000 0000 0001 0000 0010 6166 7463
0000050 726f 0079 0000 0000 0000 0000 0000 0000
0000060 ebeb ffff ffff ffff ffff ffff ffff ffff
0000070 adf4 454f 5638 5d4b 3574 2cb6 b675 2495
0000080 ffff ffff ffff ffff ffff ffff ffff ffff

With our three binaries constructed, all that is left to do is flashing and
running. One thing we skipped in the getting started guide was setting up udev
that allow us to access the board. The
Zephyr SDK bundles rules from OpenOCD, but we need to
ensure that it includes a rule that gives us access to the USB-to-UART bridge
that lives on the ESP32-C3-DevKitC-02 board. We can use lsusb to determine the
vendor and product ID for the bridge.

$ lsusb | grep UART
Bus 002 Device 009: ID 10c4:ea60 Silicon Labs CP210x UART Bridge

The bundled rules can be found in the
sysroots/x86_64-pokysdk-linux/usr/share/openocd/contrib/60-openocd.rules file.
There is not a matching entry for the 10c4 vendor ID and ea60 product ID,
but we can add one in the following format.

# Silicon Labs USB-UART bridge
ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60", MODE="660", GROUP="plugdev", TAG+="uaccess"

After doing so, we can copy these rules into the /etc/udev/rules.d/ directory,
then reload the rules.

$ sudo cp ~/.local/zephyr-sdk-0.16.0/sysroots/x86_64-pokysdk-linux/usr/share/openocd/contrib/60-openocd.rules /etc/udev/rules.d/
$ sudo udevadm control --reload

Now we are ready to run west flash. This time we’ll supply the -v flag to
instruct west to emit verbose output, which will allow us to see exactly how
it is wrapping our Espressif tools.

Note: west flash is also a subcommand provided by Zephyr, not built into
west. Ensure that your ZEPHYR_BASE is still set to the proper path to use

$ west -v flash
-- west flash: rebuilding
cmake version 3.22.1 is OK; minimum version is 3.13.1
Running CMake: /usr/bin/cmake --build /home/hasheddan/code/
ninja: no work to do.
-- west flash: using runner esp32
-- runners.esp32: Flashing esp32 chip on None (921600bps)
runners.esp32: /usr/bin/python3 /home/hasheddan/code/ --chip auto --baud 921600 --before default_reset --after hard_reset write_flash -u --flash_mode dio --flash_freq 40m --flash_size detect 0x0 /home/hasheddan/code/ 0x8000 /home/hasheddan/code/ 0x10000 /home/hasheddan/code/ v3.3
Found 1 serial ports
Serial port /dev/ttyUSB0
Detecting chip type... ESP32-C3
Chip is ESP32-C3 (revision 3)
Features: Wi-Fi
Crystal is 40MHz
MAC: 58:cf:79:16:7d:a0
Uploading stub...
Running stub...
Stub running...
Changing baud rate to 921600
Configuring flash size...
Auto-detected Flash size: 4MB
Flash will be erased from 0x00000000 to 0x00004fff...
Flash will be erased from 0x00008000 to 0x00008fff...
Flash will be erased from 0x00010000 to 0x00034fff...
Flash params set to 0x0220
Wrote 32768 bytes at 0x00000000 in 0.5 seconds (556.5 kbit/s)...
Hash of data verified.
Wrote 16384 bytes at 0x00008000 in 0.3 seconds (497.4 kbit/s)...
Hash of data verified.
Wrote 163840 bytes at 0x00010000 in 2.2 seconds (589.2 kbit/s)...
Hash of data verified.

Hard resetting via RTS pin...

We are once again using the esptool write_flash command and writing our three
binaries to flash in the same locations we did when building with esp-idf. We
can use the west espressif monitor command to reset the board and stream the
output, or we can connect with minicom and use the RST button.

$ minicom -D /dev/ttyUSB0

Build:Feb  7 2021
rst:0x1 (POWERON),boot:0xc (SPI_FAST_FLASH_BOOT)
mode:DIO, clock div:2
SHA-256 comparison failed:
Calculated: b77f73687acc559bca77cc4a4bea5409988930863749b80f171cc4a0501ff313
Expected: 318a48640313d299386e53a1953994f766ddfffed726be1320e4e027aa578799
Attempting to boot anyway...
entry 0x403ce000
I (49) boot: ESP-IDF 73e7af1 2nd stage bootloader
I (49) boot: compile time 22:29:26
I (49) boot: chip revision: 3
I (51) boot.esp32c3: SPI Speed      : 40MHz
I (56) boot.esp32c3: SPI Mode       : DIO
I (60) boot.esp32c3: SPI Flash Size : 4MB
I (65) boot: Enabling RNG early entropy source...
I (70) boot: Partition Table:
I (74) boot: ## Label            Usage          Type ST Offset   Length
I (81) boot:  0 nvs              WiFi data        01 02 00009000 00006000
I (89) boot:  1 phy_init         RF data          01 01 0000f000 00001000
I (96) boot:  2 factory          factory app      00 00 00010000 00100000
I (104) boot: End of partition table
I (108) esp_image: segment 0: paddr=00010020 vaddr=00000020 size=0001ch (    28) 
I (116) esp_image: segment 1: paddr=00010044 vaddr=3fc85020 size=000b8h (   184) load
I (125) esp_image: segment 2: paddr=00010104 vaddr=3fc850d8 size=0023ch (   572) load
I (133) esp_image: segment 3: paddr=00010348 vaddr=40380000 size=02c68h ( 11368) load
I (145) esp_image: segment 4: paddr=00012fb8 vaddr=00000000 size=0d080h ( 53376) 
I (162) esp_image: segment 5: paddr=00020040 vaddr=3c000040 size=007d8h (  2008) map
I (162) esp_image: segment 6: paddr=00020820 vaddr=00000000 size=0f7f8h ( 63480) 
I (181) esp_image: segment 7: paddr=00030020 vaddr=42010020 size=04670h ( 18032) map
I (186) boot: Loaded app from partition at offset 0x10000
I (186) boot: Disabling RNG early entropy source...
�*** Booting Zephyr OS build 07c6af3b8c35 ***
Hello, RISC-V Bytes!

Though Zephyr introduces its own flavors of tooling and workflow, we are still
using the same underlying protocols and bootloader when working with ESP32
chips. It is not until we actually boot the application that we start to see
where Zephyr and FreeRTOS diverge. We’ll look deeper into these differences in a
future RISC-V Bytes post.

As always, these posts are meant to serve as a useful resource for folks who are
interested in learning more about RISC-V and low-level software in general. If I
can do a better job of reaching that goal, or you have any questions or
comments, please feel free to send me a message
@hasheddan on Twitter or on Mastodon!

Benjamin Cabé