Zephyr on the ESP32

This topic is to explore running Zephyr on ESP32 devices. To start, we have four boards:

Two boards with the ESP32-WROOM modules (Xtensa microprocessor) and two boards with the ESP32-C3 (RISC-V).

The process to build and flash Zephyr is similar for both architectures:

C3 RiscV:

west build -b esp32c3_devkitm --sysbuild samples/hello_world

Xtensa:

west build -b esp_wrover_kit --sysbuild samples/hello_world

And to flash:

west flash

You can run tio on the serial port to view the console.

ESP-ROM:esp32c3-api1-20210207
Build:Feb  7 2021
rst:0x1 (POWERON),boot:0xc (SPI_FAST_FLASH_BOOT)
SPIWP:0xee
mode:DIO, clock div:2
load:0x3fcc1e00,len:0x1fd0
load:0x403b6e00,len:0x7844
load:0x403bee00,len:0x1200
entry 0x403b92e6
I (48) boot: MCUboot 2nd stage bootloader
I (48) boot: compile time May 23 2024 18:07:00
I (48) boot: Multicore bootloader
I (49) spi_flash: detected chip: generic
I (52) spi_flash: flash io: dio
W (54) spi_flash: Detected size(4096k) larger than the size in the binary image header(2048k). Using the size in the binary image header.
I (67) boot: chip revision: v0.3
I (69) boot.esp32c3: SPI Speed      : 40MHz
I (73) boot.esp32c3: SPI Mode       : SLOW READ
I (78) boot.esp32c3: SPI Flash Size : 4MB
I (81) boot: Enabling RNG early entropy source...
I (97) spi_flash: flash io: dio
[esp32c3] [INF] Image index: 0, Swap type: none
[esp32c3] [INF] Loading image 0 - slot 0 from flash, area id: 1
[esp32c3] [INF] Application start=40380730h
[esp32c3] [INF] DRAM segment: paddr=000136f0h, vaddr=3fc836c0h, size=0053ch (  1340) load
[esp32c3] [INF] IRAM segment: paddr=00010040h, vaddr=40380000h, size=036b0h ( 14000) load
[esp32c3] [INF] DROM segment: paddr=00020000h, vaddr=3c000000h, size=005B0h (  1456) map
[esp32c3] [INF] IROM segment: paddr=00030000h, vaddr=42010000h, size=02508h (  9480) map

*** Booting Zephyr OS build v3.6.0-4560-g11920e35a00e ***
Hello World! esp32c3_devkitm/esp32c3

The development process feels very similar between the Xtensa and RiscV devices.

Next steps will be to configure the WiFi networking and the Ethernet on the Olimex board.

1 Like

I think RISV on Zephyr is match made in heaven. We will see standard OSS on standard OpenSource ISA implementing h/w

Ethernet support

Zephyr includes support for the ESP32-ETHERNET-KIT:

Schematics

ESP32-ETHERNET-KIT Ethernet circuitry

Olimex ESP32-POE-ISO Ethernet Circuitry

Espressif has a nice page on the Zephyr efforts:

Looks like they are doing things right.

Ended up getting an ESP32-ETHERNET-KIT (far right in photo below):

I built the Ethernet sample:

west build -b esp32_ethernet_kit/esp32/procpu samples/boards/esp32/ethernet/

The Ethernet cable was detected by the Phy, but the Ethernet interface did not come up:

[00:00:01.104,000] <inf> phy_mii: PHY (1) ID 2430C54

*** Booting Zephyr OS build v3.6.0-5816-gaca152a94165 ***
[00:00:01.108,000] <inf> net_config: Initializing network
[00:00:01.108,000] <inf> net_config: Waiting interface 1 (0x3ffb5f08) to be up...
[00:00:06.916,000] <inf> phy_mii: PHY (1) Link speed 100 Mb, full duplex

[00:00:31.109,000] <inf> net_config: Running dhcpv4 client...
[00:00:31.109,000] <err> net_config: Timeout while waiting network interface
[00:00:31.109,000] <err> net_config: Network initialization failed (-115)

I then tried the ESP32 Ethernet sample on an STM32 H7 board – it worked fine. After reporting the issue on the Espressif channel on Discord, a developer from Espressif suggested a solution, and now it works! Apparently, something broke in some recent clock refactoring.

[00:00:07.017,000] <inf> phy_mii: PHY (1) Link speed 100 Mb, full duplex

[00:00:07.017,000] <inf> net_config: Interface 1 (0x3ffb5f08) coming up
[00:00:07.017,000] <inf> net_config: Running dhcpv4 client...
[00:00:09.607,000] <inf> net_dhcpv4: Received: 10.0.0.135
[00:00:09.607,000] <inf> net_config: IPv4 address: 10.0.0.135
[00:00:09.607,000] <inf> net_config: Lease time: 600 seconds
[00:00:09.607,000] <inf> net_config: Subnet: 255.255.255.0
[00:00:09.607,000] <inf> net_config: Router: 10.0.0.1

The above issue also links to another discussion that may be the key to getting the Ethernet working on the Olimex board, which uses a ESP32 GPIO to generate the 50MHz Phy clock.

This illustrates several things:

  1. Community is important. A developer from Espressif was aware of some recent changes that might have broke something – they had context.
  2. This also illustrates the advantage of developing on the main branch – you can get good support there as that is where the MCU developers are working. If you want support on an older LTS release, you better have a support contract in place.
  3. The support for using a GPIO pin for Phy clock was recently added – again, if you want to use features a little off the beaten path, developing on main is a good place to be.
  4. Proving something works on a competitor’s MCU is a good motivator and a good check to make sure the core OS components are working. It is amazing how I can build the same application on ESP32 and STM32 parts.
  5. Having the MCU vendor’s official development board allowed me to get quick support from the vendor as they could replicate the problem. If we had to debug this on the Olimex board, it would have been time-consuming as several things are different.
  6. When dealing with a complex system, break it down and verify it one step at a time.

Now to get the Phy clock running on the Olimex ES32-POE …

yeah more upstream you go, better the water is for drinking, its nature’s law :slight_smile:

1 Like

A post was split to a new topic: Zephyr SimpleIoT Application

Ethernet is now working on the Olimex ESP32-POE under Zephyr!

[00:00:00.210,000] <inf> phy_mii: PHY (0) ID 7C0F1
                                                                                                                     
[00:00:00.212,000] <inf> eth_esp32: APLL is occupied already, it is working at 50000000 Hz
*** Booting Zephyr OS build v3.7.0-rc1-375-g9f927f97847f ***
[00:00:00.225,000] <inf> net_config: Initializing network
[00:00:00.225,000] <inf> net_config: Waiting interface 1 (0x3ffb5d00) to be up...
uart:~$ SIOT Zephyr Application! esp32_poe/esp32/procpu
[00:00:03.418,000] <inf> phy_mii: PHY (0) Link speed 100 Mb, full duplex
                                                                                                                     
[00:00:03.418,000] <inf> net_config: Interface 1 (0x3ffb5d00) coming up
[00:00:03.418,000] <inf> net_config: Running dhcpv4 client...
[00:00:08.423,000] <inf> net_dhcpv4: Received: 10.0.0.148
[00:00:08.423,000] <inf> net_config: IPv4 address: 10.0.0.148
[00:00:08.423,000] <inf> net_config: Lease time: 600 seconds
[00:00:08.423,000] <inf> net_config: Subnet: 255.255.255.0
[00:00:08.423,000] <inf> net_config: Router: 10.0.0.1

Some fixes have been merged for ESP32 Ethernet and one PR is still pending. The complete build is here:

This is a Zephyr Workspace T2 topology that pulls in Zephyr as a dependency.

Notice right now, we are referencing a PR repo/branch that has the fixes we need.

manifest:
  remotes:
    - name: zephyrproject-rtos
      url-base: https://github.com/zephyrproject-rtos
    - name: zephyr-sylvio
      url-base: https://github.com/sylvioalves
    - name: zephyr-iwasz
      url-base: https://github.com/iwasz
  projects:
    - name: zephyr
      remote: zephyr-iwasz
      revision: initialize_ref_clk_in_mdio_esp32
      import: 
        name-allowlist:
          - hal_espressif
          - hal_stm32
          - cmsis
  self:
    path: siot

This has been a community effort:

Zephyr NVS (non-voltatile storage) storage works out-of-the box on ESP32 CPUs:

Currently storing boot count, but can eventually be used to store other config settings.

[00:00:03.322,000] <inf> net_config: Interface 1 (0x3ffb6610) coming up
[00:00:03.322,000] <inf> net_config: Running dhcpv4 client...
[00:00:03.322,000] <inf> siot: SIOT Zephyr Application! esp32_poe/esp32/procpu
[00:00:03.322,000] <dbg> fs_nvs: nvs_recover_last_ate: Recovering last ate from sector 0
[00:00:03.331,000] <inf> fs_nvs: 3 Sectors of 4096 bytes
[00:00:03.331,000] <inf> fs_nvs: alloc wra: 0, fc0
[00:00:03.331,000] <inf> fs_nvs: data wra: 0, 14
[00:00:03.331,000] <inf> siot: Id: 1, boot_counter: 4

The boot_counter is preserved even when you flash new firmware.

ESP32 Pinmux

The ESP32 Pinmux is pretty flexible – many of the peripherals can be mapped to any GPIO pin:

In the include/zephyr/dt-bindings/pinctrl/esp32-pinctrl.h, you find:

/* I2C0_SCL */
#define I2C0_SCL_GPIO0 \
	ESP32_PINMUX(0, ESP_I2CEXT0_SCL_IN, ESP_I2CEXT0_SCL_OUT)

#define I2C0_SCL_GPIO1 \
	ESP32_PINMUX(1, ESP_I2CEXT0_SCL_IN, ESP_I2CEXT0_SCL_OUT)

#define I2C0_SCL_GPIO2 \
	ESP32_PINMUX(2, ESP_I2CEXT0_SCL_IN, ESP_I2CEXT0_SCL_OUT)

#define I2C0_SCL_GPIO3 \
	ESP32_PINMUX(3, ESP_I2CEXT0_SCL_IN, ESP_I2CEXT0_SCL_OUT)

#define I2C0_SCL_GPIO4 \
	ESP32_PINMUX(4, ESP_I2CEXT0_SCL_IN, ESP_I2CEXT0_SCL_OUT)

#define I2C0_SCL_GPIO5 \
	ESP32_PINMUX(5, ESP_I2CEXT0_SCL_IN, ESP_I2CEXT0_SCL_OUT)

#define I2C0_SCL_GPIO6 \
	ESP32_PINMUX(6, ESP_I2CEXT0_SCL_IN, ESP_I2CEXT0_SCL_OUT)

#define I2C0_SCL_GPIO7 \
	ESP32_PINMUX(7, ESP_I2CEXT0_SCL_IN, ESP_I2CEXT0_SCL_OUT)

#define I2C0_SCL_GPIO8 \
	ESP32_PINMUX(8, ESP_I2CEXT0_SCL_IN, ESP_I2CEXT0_SCL_OUT)

Was able to get a 6x4 keyboard matrix driver configured with the following:

/ {
	kbd_matrix: kbd-matrix {
	     compatible = "gpio-kbd-matrix";
	     row-gpios = <&gpio1 4 (GPIO_ACTIVE_LOW)>,
	                 <&gpio1 3 (GPIO_ACTIVE_LOW)>,
	                 <&gpio1 2 (GPIO_ACTIVE_LOW)>,
	                 <&gpio1 1 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
	     col-gpios = <&gpio1 0 GPIO_ACTIVE_LOW>,
	                 <&gpio0 13 GPIO_ACTIVE_LOW>,
	                 <&gpio0 0 GPIO_ACTIVE_LOW>,
	                 <&gpio0 4 GPIO_ACTIVE_LOW>,
	                 <&gpio0 16 GPIO_ACTIVE_LOW>,
	                 <&gpio0 5 GPIO_ACTIVE_LOW>;
	};
};

Some things to note:

  • gpios above 31 are accessed in the gpio1 bank starting at 0
  • gpio 34-39 are input only, and do not appear to have internal pullups available

The CONFIG_INPUT_SHELL_KBD_MATRIX_STATE=y option enables a handy shell command for dumping out the keyboard state:

[00:00:08.417,000] <inf> input: kbd-matrix state [01 -- -- -- -- --] (1)
[00:00:09.789,000] <inf> input: kbd-matrix state [-- -- -- -- -- --] (0)
[00:00:11.560,000] <inf> input: kbd-matrix state [02 -- -- -- -- --] (1)
[00:00:12.488,000] <inf> input: kbd-matrix state [-- -- -- -- -- --] (0)
[00:00:14.327,000] <inf> input: kbd-matrix state [04 -- -- -- -- --] (1)
[00:00:14.740,000] <inf> input: kbd-matrix state [-- -- -- -- -- --] (0)
[00:00:17.085,000] <inf> input: kbd-matrix state [08 -- -- -- -- --] (1)
[00:00:17.366,000] <inf> input: kbd-matrix state [-- -- -- -- -- --] (0)
[00:00:32.835,000] <inf> input: kbd-matrix state [-- 01 -- -- -- --] (1)
[00:00:33.473,000] <inf> input: kbd-matrix state [-- -- -- -- -- --] (0)
[00:00:35.790,000] <inf> input: kbd-matrix state [-- 02 -- -- -- --] (1)
[00:00:36.162,000] <inf> input: kbd-matrix state [-- -- -- -- -- --] (0)
[00:00:37.389,000] <inf> input: kbd-matrix state [-- 04 -- -- -- --] (1)
[00:00:37.705,000] <inf> input: kbd-matrix state [-- -- -- -- -- --] (0)
[00:00:40.140,000] <inf> input: kbd-matrix state [-- 08 -- -- -- --] (1)
[00:00:40.538,000] <inf> input: kbd-matrix state [-- -- -- -- -- --] (0)
[00:01:01.959,000] <inf> input: kbd-matrix state [-- -- 01 -- -- --] (1)
[00:01:02.383,000] <inf> input: kbd-matrix state [-- -- -- -- -- --] (0)
[00:01:04.518,000] <inf> input: kbd-matrix state [-- -- 02 -- -- --] (1)
[00:01:04.915,000] <inf> input: kbd-matrix state [-- -- -- -- -- --] (0)
[00:01:05.544,000] <inf> input: kbd-matrix state [-- -- 04 -- -- --] (1)
[00:01:05.865,000] <inf> input: kbd-matrix state [-- -- -- -- -- --] (0)
[00:01:06.447,000] <inf> input: kbd-matrix state [-- -- 08 -- -- --] (1)
[00:01:06.758,000] <inf> input: kbd-matrix state [-- -- -- -- -- --] (0)
[00:01:22.857,000] <inf> input: kbd-matrix state [-- -- -- 01 -- --] (1)
[00:01:23.306,000] <inf> input: kbd-matrix state [-- -- -- -- -- --] (0)
[00:01:23.868,000] <inf> input: kbd-matrix state [-- -- -- 02 -- --] (1)
[00:01:24.347,000] <inf> input: kbd-matrix state [-- -- -- -- -- --] (0)
[00:01:24.782,000] <inf> input: kbd-matrix state [-- -- -- 04 -- --] (1)
[00:01:25.129,000] <inf> input: kbd-matrix state [-- -- -- -- -- --] (0)
[00:01:25.615,000] <inf> input: kbd-matrix state [-- -- -- 08 -- --] (1)
[00:01:25.962,000] <inf> input: kbd-matrix state [-- -- -- -- -- --] (0)
[00:01:44.944,000] <inf> input: kbd-matrix state [-- -- -- -- 01 --] (1)
[00:01:45.301,000] <inf> input: kbd-matrix state [-- -- -- -- -- --] (0)
[00:01:45.810,000] <inf> input: kbd-matrix state [-- -- -- -- 02 --] (1)
[00:01:46.142,000] <inf> input: kbd-matrix state [-- -- -- -- -- --] (0)
[00:01:46.568,000] <inf> input: kbd-matrix state [-- -- -- -- 04 --] (1)
[00:01:46.869,000] <inf> input: kbd-matrix state [-- -- -- -- -- --] (0)
[00:01:47.317,000] <inf> input: kbd-matrix state [-- -- -- -- 08 --] (1)
[00:01:47.587,000] <inf> input: kbd-matrix state [-- -- -- -- -- --] (0)
[00:01:58.452,000] <inf> input: kbd-matrix state [-- -- -- -- -- 01] (1)
[00:01:58.809,000] <inf> input: kbd-matrix state [-- -- -- -- -- --] (0)
[00:01:59.127,000] <inf> input: kbd-matrix state [-- -- -- -- -- 02] (1)
[00:01:59.464,000] <inf> input: kbd-matrix state [-- -- -- -- -- --] (0)
[00:01:59.784,000] <inf> input: kbd-matrix state [-- -- -- -- -- 04] (1)
[00:02:00.074,000] <inf> input: kbd-matrix state [-- -- -- -- -- --] (0)
[00:02:00.382,000] <inf> input: kbd-matrix state [-- -- -- -- -- 08] (1)
[00:02:00.729,000] <inf> input: kbd-matrix state [-- -- -- -- -- --] (0)

ESP32 memory challenges

Making progress with zephyr-siot – we now have a http server serving JSON data to an Elm frontend. However, we are getting squeezed for DRAM (data RAM):

Memory region         Used Size  Region Size  %age Used
           FLASH:      438844 B    4194048 B     10.46%
     iram0_0_seg:       70912 B       224 KB     30.92%
     dram0_0_seg:      190844 B       192 KB     97.07%  <-- Problem
     irom0_0_seg:      176700 B      4092 KB      4.22%
     drom0_0_seg:         64 KB      4092 KB      1.56%
    rtc_iram_seg:          0 GB         8 KB      0.00%
    rtc_slow_seg:          0 GB         4 KB      0.00%
        IDT_LIST:          0 GB         8 KB      0.00%

It turns out the ESP32 has 3 SRAM banks. SRAM0/1 are used for instructions (I assume caching instructions from Flash), and SRAM2 is used for data RAM. The following article is helpful:

Supposedly, you can use SRAM1 for DRAM:

More notes on using PSRAM collected from discord:

  • For the Xtensa processors (ESP32, -S2, and -S3) it is done dynamically at link time in soc/espressif/esp32XX/default.ld by placing the instructions first and then the data into the internal RAM. Not all of the internal RAM regions are on both the instruction and data bus which is why you see a different region size for both of them.
  • Marek did a write-up for that last year that is much easier than reading through the TRM and the TRM does not show the ROM-code reserved data which these diagrams do: ESP32's family Memory Map 101 · Espressif Developer Portal
  • You will need to read through the linker script soc/espressif/esp32/default.ld for that and also look at your build/zephyr/zephyr.stat and zephyr.map files. Use Zephyr 4.0 or mainline since the Espressif team has done a large amount of changes recently to the HAL.
  • yes we are using SRAM2 as DRAM. Unfortunately, the address space SRAM2->SRAM1 is ‘interrupted’ by the reserved memory areas and couldn’t be use as continuous address space. You can read more about how linking is done in Zephyr in this article ESP32 bootstrapping in Zephyr · Espressif Developer Portal
  • You can use the SRAM1 as DRAM if you want, but you need to start allocating at the usable beginning of SRAM1, and limit will be approaching IRAM allocations end. If you are interested in using both SRAM2 → SRAM1 as your data segment, try to look for --enable-non-contiguous-regions linker option. You’d also need to alter your linker script
  • Are you writing to flash at all? When writing to flash, SPIRAM flash and PSRAM cannot be accessed. If it is, it will cause a double exception and just reboot. The other common cause is a large stack overflow. The stack checking only really works for minor stack overflows. If it is severe enough, the system just crashes. Checking the stacks using kernel stacks and making sure you don’t exceed say 75% is a good way to eliminate some failures.
  • There is a section to run code. Relocation is not working, as some people reported. You must make sure that the code running on PSRAM is not doing flash write. Besides, you should consider that code running on PSRAM is affected by flash writes.
  • We are currently using NVS on ESP32 flash, does that mean we can’t use PSRAM at all, or just the code that writes to NVS on flash?
  • The code that writes to Flash, and the content to be written to flash should not be on PSRAM.
  • That seems manageable.
  • It is. You can use DRAM for some tricks.
  • writing to flash disables cache, access to PSRAM is through cache.
  • BTW, a bit of the problem is that SRAM1 appears on both IRAM and DRAM reports. DRAM reports is SRAM1 - (ROM CODE SPACE). Rom Code space = 98816B. So, you don’t have that much IRAM to spare. The math for IRAM is a bit more complex, as there is bootloader taking out 16384B. Its SRAM0+SRAM1-ROMCODE-BootSpace. SRAM2 could help, but there is some tinkering needed and it is not implemented yet. So, there is PSRAM and RTC_RAM. RTC_RAM is both IRAM and DRAM.
    In cases where more DRAM is needed. Another option is to use external PSRAM and Espressif supplies ESP32 modules with 8MB of external RAM.

Pull requests:

There is also a sample application on how to use PSRAM:

Will be investigating this next …

Zephyr support for the ESP32_POE_WROVER

In an effort to get PSRAM going on a ESP32 module, trying to to use the Olimex variant that has PSRAM. This has a few changes in that the EMAC clock needs to be changed from GPIO16 to GPIO0.

When I try to set:

ref-clk-output-gpios = <&gpio0 0 0>;

I get: "Only GPIO16/17 are allowed as a GPIO REF_CLK source!"

I then tried modifying mdio_esp32.c and eth_esp32.c to allow GPIO0, but that is not working yet.

In the mux table, we have:

Then in the espressif HAL:

void emac_hal_iomux_rmii_clk_output(int num)
{
    switch (num) {
    case 0:
        /* APLL clock output to GPIO0 (must be configured to 50MHz!) */
        gpio_ll_iomux_func_sel(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1);
        PIN_INPUT_DISABLE(GPIO_PIN_MUX_REG[0]);
        break;
    case 16:
        /* RMII CLK (50MHz) output to GPIO16 */
        gpio_ll_iomux_func_sel(PERIPHS_IO_MUX_GPIO16_U, FUNC_GPIO16_EMAC_CLK_OUT);
        PIN_INPUT_DISABLE(GPIO_PIN_MUX_REG[16]);
        break;
    case 17:
        /* RMII CLK (50MHz) output to GPIO17 */
        gpio_ll_iomux_func_sel(PERIPHS_IO_MUX_GPIO17_U, FUNC_GPIO17_EMAC_CLK_OUT_180);
        PIN_INPUT_DISABLE(GPIO_PIN_MUX_REG[17]);
        break;
    default:
        break;
    }
}

So, it seems that GPIO0 can possibly be used. 

Still researching APLL setup ...