Programming the esp32s2 RiscV coprocessor.

Olof Astrand
2 min readAug 14, 2020


On the espressif esp32s2 they have two coprocessors. One of them is a RV32IMC RiscV core. Here I will look at how to program it and what is added by the esp-idf

You need to install esp-idf and the risc-v toolchain.

$ install riscv-none-embed-gcc

What does the name RV32IMC reveal

RISCV Is a opensource Instruction Set Architecture (ISA) spec
It includes base and extensions specifications. In this case,

RV32I is Base Integer Instruction Set, 32-bit

  • M-Multiply and divide
  • C-Compressed instructions

Building the app

First I forgot to set target as s2. The esp32 does not have a riscV coprocessor so first do set-target esp32s2

error: ulp_riscv/ulp_riscv.h: No such file or directory
#include “ulp_riscv/ulp_riscv.h” build
is not a full path and was not found in the PATH.

Path had to be added manually

export PATH=$PATH:~/.espressif/tools/riscv-none-embed-gcc/riscv-none-embed-gcc-8.2.0/xPacks/riscv-none-embed-gcc/8.2.0-3.1/bin

Then finally we ended up with a binary in build/esp-idf/main/ulp_main/ulp_main

This is a an elf file and can be loaded whith ghidra.


Reset vector

// .text
00000000 09 a8 c.j __start undefined __start(void)

irq_vector and __start

undefined __stdcall irq_vector(void)
undefined a0:1 <RETURN>
irq_vector XREF[1]: Entry Point(*)
00000010 82 80 ret

The _start function is added by the esp-idf

void __start(void){
do {
/* WARNING: Do nothing block loop */
} while( true );

Assembly listing

undefined __stdcall __start(void)
undefined a0:1 <RETURN>
__start XREF[1]: 00000000(c)
00000012 17 11 00 00 auipc sp,0x1
00000016 13 01 e1 fe addi sp,sp,-0x12
0000001a 3d 28 c.jal ulp_riscv_rescue_from_monitor
0000001c 09 28 c.jal main
0000001e a9 28 c.jal ulp_riscv_shutdown
loop XREF[1]: 00000020(j)
00000020 01 a0 c.j loop

You can read more about the riscV ISA here.

The complete source code for this example is available in,



Olof Astrand