From cca6af44fd4432f0968c0484616f404f83a659af Mon Sep 17 00:00:00 2001 From: Marcio Ribeiro Date: Thu, 8 Aug 2024 17:49:19 -0300 Subject: [PATCH] article: zephyr: how to use PSRAM with ESP32 on Zephyr This article aims to show how to enable, configure and use external PSRAM memory on Zephyr applications running on ESP32 SoCs Signed-off-by: Marcio Ribeiro --- .../12/zephyr-how-to-use-psram/featured.webp | Bin 0 -> 42702 bytes .../2024/12/zephyr-how-to-use-psram/index.md | 767 ++++++++++++++++++ 2 files changed, 767 insertions(+) create mode 100644 content/blog/2024/12/zephyr-how-to-use-psram/featured.webp create mode 100644 content/blog/2024/12/zephyr-how-to-use-psram/index.md diff --git a/content/blog/2024/12/zephyr-how-to-use-psram/featured.webp b/content/blog/2024/12/zephyr-how-to-use-psram/featured.webp new file mode 100644 index 0000000000000000000000000000000000000000..d56399b808066b8ff70edff090fbf39d735931e6 GIT binary patch literal 42702 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 z

~D3N`3(VyqP9)k$>_{AMojqzwN{aya$J&`!us{W*Q0;#tu7V~0EQk~X^&kDw=G4bW)iT1nrMo{N zfB%>eE^0*E(vckbUx( ziy`VLqbOs7fdVR6y(}wRX!EXFZ^iJv0pm_W5(u`A5nLFG!+y5|XD5H@ETHN^KS#y3 ziQNUUu)Sl=alTPp zGm#3x5wU;<>#ZCR2Rm%7vuP4gL8}Xc<@y9HsCU6MD3tM^f#AxfzZD^qJKmKTuaSfY zTVc8Zrs}*{E{-Ar+~g)>bh6k4^Xa-$9!bQ&04fvvfkv~=QP&CQ!>KGzUA{t<7^Suh dGKY46Qbvd%Hc$Wn000000000000000008}rNXP&H literal 0 HcmV?d00001 diff --git a/content/blog/2024/12/zephyr-how-to-use-psram/index.md b/content/blog/2024/12/zephyr-how-to-use-psram/index.md new file mode 100644 index 00000000..3392ff9b --- /dev/null +++ b/content/blog/2024/12/zephyr-how-to-use-psram/index.md @@ -0,0 +1,767 @@ +--- +title: "Enabling External PSRAM for Zephyr Applications on ESP32 SoCs" +date: 2024-12-04T00:00:00-00:00 +showAuthor: false +authors: + - "marcio-ribeiro" +tags: ["ESP32", "ESP32-S2", "ESP32-S3", "Zephyr", "PSRAM", "SPIRAM"] +--- + +Although ESP32 SoCs typically feature a few hundred kilobytes of internal SRAM, some embedded IoT applications—such as those featuring sophisticated graphical user interfaces, handling large data volumes, or performing complex data processing—may require much more RAM. To meet these demands, Espressif provides modules that feature external PSRAM. + +In this article, you will learn how to configure Zephyr to enable your application to take full advantage of the PSRAM available on ESP32 modules. We will explore three different strategies for utilizing this PSRAM: using it as a heap for dynamic memory allocation, integrating it as a section into the memory map, and moving code from flash to PSRAM. + +## Introduction + +Before we start, let’s first understand exactly what PSRAM stands for and what it really is. The acronym PSRAM stands for pseudo-static random-access memory. It is a type of dynamic random-access memory (DRAM) that internally manages memory cell refresh, allowing it to function similarly to static random-access memory (SRAM) while retaining the cost and characteristics of DRAM. PSRAM is typically accessible via high-speed serial buses, and some ESP32 series SoCs can communicate with PSRAM and map its contents into the CPU's virtual memory space through a memory management unit (MMU). + +In a simplified way, we can describe the relationship among PSRAM, the MMU, and the CPU as follows: + +- Data is automatically copied from PSRAM to the data cache when the CPU attempts to access a memory position not currently in the cache. + +- Conversely, when CPU modifies data in the cache, the updated contents are copied back to PSRAM. + +At the time of writing, while many ESP32 SoCs support external PSRAM, not all of them are supported on Zephyr yet. + +| SoC series | PSRAM capable | Currently supported on Zephyr | +|:---------------|:-------------:|:-----------------------------:| +| ESP32 | yes | yes | +| ESP32-S2 | yes | yes | +| ESP32-S3 | yes | yes | +| ESP32-C5 | yes | no | +| ESP32-C61 | yes | no | +| ESP32-P4 | yes | no | + +## ESP32 modules and PSRAM + +To know which ESP32 modules contain PSRAM we have to understand how module part numbers are formed. As an example, let’s use the module whose part number is [ESP32-S3-WROOM-1-N8R8](https://www.espressif.com/sites/default/files/documentation/esp32-s3-wroom-1_wroom-1u_datasheet_en.pdf#page=3) + +| Field | Contents | Meaning | +|:---------------|:--------:|:-------------------:| +| SoC series | ESP32-S3 | ESP32-S3 SoC series | +| flash size | N8 | 8 MB | +| PSRAM size | R8 | 8 MB | + +If the module does not contain PSRAM, this field is absent. + +[Here](https://www.espressif.com/sites/default/files/documentation/esp32-s3_datasheet_en.pdf#page=13) you can find more details about ESP32-S3 SoC nomenclature. + +## Custom hardware and PSRAM + +Although Espressif offers modules featuring PSRAM across various ESP32 SoC series, there may be cases where custom hardware must be developed to meet specific requirements, such as PSRAM capacity or the SPI interface bus width, which impacts the time for each memory operation and overall data access speed. Additionally, there may be other reasons unrelated to PSRAM that necessitate custom hardware. + +In this case, PSRAM will share data I/O and clock lines with external flash memory, but not chip-select line. PSRAM and flash have separate chip select lines (for example, see the [ESP32-S3 Chip Datasheet](https://www.espressif.com/sites/default/files/documentation/esp32-s3_datasheet_en.pdf#cd-pins-mapping-flash)). For information related on how to connect off-package PSRAM lines, the respective SoC datasheet must be consulted. + +The following SPI modes are supported to access external PSRAM memories: + + - Single SPI + - Dual SPI + - Quad SPI + - QPI + - Octo SPI + - OPI + +There are limits regarding the maximum PSRAM capacity that each ESP32 SoC series can manage. As an example, ESP32-S3 can manage PSRAMs up to 32 MB. + +Espressif currently offers some PSRAM models: + +| Part number | Memory Capacity | Operating Voltage | +|:------------|:---------------:|:------------------:| +| ESP-PSRAM32 | 4 MB | 1.8V | +| ESP-PSRAM32H| 4 MB | 3.3V | +| ESP-PSRAM64 | 8 MB | 1.8V | +| ESP-PSRAM64H| 8 MB | 3.3V | + +## Configuring PSRAM for use in Zephyr + +The Kconfig parameters related to PSRAM usage in Zephyr can be found in [zephyr/soc/espressif/commom/Kconfig.spiram](https://github.com/zephyrproject-rtos/zephyr/blob/main/soc/espressif/common/Kconfig.spiram) + +Below are the main parameters related to PSRAM, along with a brief description of each: + +***ESP_SPIRAM:*** This configuration parameter enables support for an external SPI RAM chip, connected in parallel with the main SPI flash chip. If enabled, it automatically enables [SHARED_MULTI_HEAP](#dynamically-allocating-psram-memory). + +***ESP_HEAP_SEARCH_ALL_REGIONS:*** This configuration parameter enables searching all available heap regions. If the region of desired capability is exhausted, memory will be allocated from another available regions. + +***ESP_SPIRAM_HEAP_SIZE:*** This configuration parameter specifies SPIRAM heap size. + +***ESP_SPIRAM_MEMTEST:*** This configuration parameter controls SPIRAM memory test during initialization. It is enabled by default and can be disabled for faster startup. + +***SPIRAM_MODE:*** This configuration parameter selects the mode of SPI RAM chip in use. The permitted values are `SPIRAM_MODE_QUAD` and `SPIRAM_MODE_OCT`. Please note that SPIRAM_MODE_OCT is only available in ESP32-S3. + +***SPIRAM_TYPE:*** This configuration parameter defines the type of SPIRAM chip in use: + +| SPIRAM type | Your PSRAM capacity | +| ------------------------ |:-------------------:| +| `SPIRAM_TYPE_ESPPSRAM16` | 2 MB | +| `SPIRAM_TYPE_ESPPSRAM32` | 4 MB | +| `SPIRAM_TYPE_ESPPSRAM64` | 8 MB | + +***SPIRAM_SPEED:*** This configuration parameter sets the SPIRAM clock speed in MHz: + | Value | Clock speed | + | ----------------- |:-----------:| + | SPIRAM_SPEED_20M | 20 MHz | + | SPIRAM_SPEED_26M | 26 MHz | + | SPIRAM_SPEED_40M | 40 MHz | + | SPIRAM_SPEED_80M | 80 MHz | + | SPIRAM_SPEED_120M | 120 MHz | + +***SPIRAM_FETCH_INSTRUCTIONS:*** This configuration parameter allows moving instructions from flash to PSRAM. If enabled, instructions in flash will be moved into PSRAM on startup. If ```SPIRAM_RODATA``` parameter is also enabled, the code that normally requires execution during the SPI1 flash operation does not need to be placed in IRAM, thus optimizing RAM usage. By default, this parameter is disabled. + +***SPIRAM_RODATA:*** This configuration parameter allows moving read-only data from flash to PSRAM. If ```SPIRAM_FETCH_INSTRUCTIONS``` parameter is also enabled, the code that normally requires execution during the SPI1 flash operation does not need to be placed in IRAM, thus optimizing RAM usage. + +## Installing Zephyr and its dependencies + +To install Zephyr RTOS and the necessary tools, follow the instructions in the Zephyr's [Getting Started Guide](https://docs.zephyrproject.org/latest/develop/getting_started/index.html). By the end of the process, you will have a command-line Zephyr development environment set up and ready to build your application with `west` — the meta-tool responsible for building your application and flashing the generated binary, and other tasks. + +Additionally, you need to execute the following command to prepare your environment for building applications for Espressif SoCs: + +```sh +west blobs fetch hal_espressif +``` + +## Dynamically Allocating PSRAM Memory + +PSRAM memory blocks can be made available to applications through Zephyr's shared multi-heap library. The shared multi-heap memory pool manager uses the multi-heap allocator to manage a set of reserved memory regions with varying capabilities and attributes. For PSRAM, enabling the ```ESP_SPIRAM``` and ```SHARED_MULTI_HEAP``` parameters causes the external PSRAM to be mapped into the data virtual memory space during Zephyr's early initialization stage. The shared multi-heap framework is initialized, and the PSRAM memory region is added to the pool. + +If an application needs a memory block allocated from PSRAM, it must call ```shared_multi_heap_alloc()``` whith ```SMH_REG_ATTR_EXTERNAL``` as a parameter. This function will return an address pointing to a memory block inside PSRAM. If an aligned memory block is required, ```shared_multi_heap_aligned_alloc()``` should be called instead. + +With the ownership of this memory block, the application is granted permission to read from and write to its addresses. Once the memory block is no longer needed, it can be returned to the pool from which it was allocated by calling ```shared_multi_heap_free()``` and passing the pointer to the block as a parameter. + +The following sample code shows how to use Zephyr's shared multi-heap API to allocate, use, and free memory from PSRAM: + +prj.conf: + +```sh +CONFIG_LOG=y +CONFIG_ESP_SPIRAM=y +CONFIG_SHARED_MULTI_HEAP=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_TYPE_ESPPSRAM64=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_ESP32S3_DATA_CACHE_64KB=y +CONFIG_ESP_SPIRAM_MEMTEST=y +``` +main.c: + +```c +#include +#include +#include +#include + +LOG_MODULE_REGISTER(PSRAM_SAMPLE, LOG_LEVEL_INF); + +int main(void) +{ + uint32_t *p_mem, k; + + LOG_INF("Sample started"); + + p_mem = shared_multi_heap_aligned_alloc(SMH_REG_ATTR_EXTERNAL, 32, 1024*sizeof(uint32_t)); + + if (p_mem == NULL) { + LOG_ERR("PSRAM memory allocation failed!"); + return -ENOMEM; + } + + for (k = 0; k < 1024; k++) { + p_mem[k] = k; + } + + for (k = 0; k < 1024; k++) { + if (p_mem[k] != k) { + LOG_ERR("p_mem[%"PRIu32"]: %"PRIu32" (expected value %"PRIu32")", k, p_mem[k], k); + break; + } + } + + shared_multi_heap_free(p_mem); + + if (k < 1024) { + LOG_ERR("Failed checking memory contents."); + return -1; + } + + LOG_INF("Sample finished successfully!"); + + return 0; +} +``` + +To build a project containing these two files targeting a `ESP32-S3-DevKitC-1` board: + +```sh +west build -b esp32s3_devkitc/esp32s3/procpu --pristine +``` +This command will create a directory called `build`, which will contain the binary file of our sample along with other intermediate files produced during the building process. + +To flash the generated binary into the `ESP32-S3-DevKitC-1` board, run: + +```sh +west flash +``` + +To open a console and see the log messages produced during the sample execution, run: + +```sh +west espressif monitor +``` +Here are the sample messages: + +```sh +ESP-ROM:esp32s3-20210327 +Build:Mar 27 2021 +rst:0x1 (POWERON),boot:0x8 (SPI_FAST_FLASH_BOOT) +SPIWP:0xee +mode:DIO, clock div:2 +load:0x3fc90530,len:0x227c +load:0x40374000,len:0xc510 +SHA-256 comparison failed: +Calculated: f86c212b1724fcb97b17dbf917ed164707f67d52c2f4fffe151bd21081c9d03d +Expected: 0000000030180000000000000000000000000000000000000000000000000000 +Attempting to boot anyway... +entry 0x40379b3c +I (79) soc_init: ESP Simple boot +I (79) soc_init: compile time Dec 4 2024 17:19:37 +W (80) soc_init: Unicore bootloader +I (80) spi_flash: detected chip: gd +I (82) spi_flash: flash io: dio +W (85) spi_flash: Detected size(8192k) larger than the size in the binary image header(2048k). Using the size in the binary image header. +I (97) soc_init: chip revision: v0.2 +I (100) flash_init: Boot SPI Speed : 40MHz +I (104) flash_init: SPI Mode : DIO +I (108) flash_init: SPI Flash Size : 8MB +I (111) soc_random: Enabling RNG early entropy source +I (116) boot: DRAM: lma 0x00000020 vma 0x3fc90530 len 0x227c (8828) +I (122) boot: IRAM: lma 0x000022a4 vma 0x40374000 len 0xc510 (50448) +I (129) boot: padd: lma 0x0000e7c8 vma 0x00000000 len 0x1830 (6192) +I (135) boot: IMAP: lma 0x00010000 vma 0x42000000 len 0x4568 (17768) +I (141) boot: padd: lma 0x00014570 vma 0x00000000 len 0xba88 (47752) +I (147) boot: DMAP: lma 0x00020000 vma 0x3c010000 len 0x16d0 (5840) +I (153) boot: Image with 6 segments +I (157) boot: DROM segment: paddr=00020000h, vaddr=3c010000h, size=016D0h ( 5840) map +I (164) boot: IROM segment: paddr=00010000h, vaddr=42000000h, size=04566h ( 17766) map +I (183) soc_random: Disabling RNG early entropy source +I (183) boot: Disabling glitch detection +I (183) boot: Jumping to the main image... +I (184) octal_psram: vendor id : 0x0d (AP) +I (188) octal_psram: dev id : 0x02 (generation 3) +I (193) octal_psram: density : 0x03 (64 Mbit) +I (198) octal_psram: good-die : 0x01 (Pass) +I (202) octal_psram: Latency : 0x01 (Fixed) +I (206) octal_psram: VCC : 0x01 (3V) +I (210) octal_psram: SRF : 0x01 (Fast Refresh) +I (215) octal_psram: BurstType : 0x01 (Hybrid Wrap) +I (220) octal_psram: BurstLen : 0x01 (32 Byte) +I (225) octal_psram: Readlatency : 0x02 (10 cycles@Fixed) +I (230) octal_psram: DriveStrength: 0x00 (1/1) +I (235) MSPI Timing: PSRAM timing tuning index: 6 +I (239) esp_psram: Found 8MB PSRAM device +I (242) esp_psram: Speed: 80MHz +I (651) esp_psram: SPI SRAM memory test OK +I (708) heap_runtime: ESP heap runtime init at 0x3fcacae0 size 243 kB. + +*** Booting Zephyr OS build v4.0.0-1471-ge60f04096cd8 *** +[00:00:00.708,000] PSRAM_SAMPLE: Sample started +[00:00:00.708,000] PSRAM_SAMPLE: Sample finished successfully! +``` + +Once the sample finishes executing successfully, we can conclude that the memory allocated from PSRAM was read and written to without any issues. + +## Integrating PSRAM memory as a section into the memory map + +Once ```ESP_SPIRAM``` is enabled, a section called `.ext_ram.bss` will be created. This section will hold non-initialized global variables that will later be placed in PSRAM. These global variables must be declared with `__attribute__ ((section (".ext_ram.bss"))`. + +prj.conf: + +```sh +CONFIG_LOG=y +CONFIG_ESP_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_TYPE_ESPPSRAM64=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_ESP32S3_DATA_CACHE_64KB=y +CONFIG_ESP_SPIRAM_MEMTEST=n +``` +main.c: + +```c +#include +#include + +LOG_MODULE_REGISTER(PSRAM_SAMPLE, LOG_LEVEL_INF); + +#define PSRAM_TEST_VECTOR_LEN (512*1024) + +__attribute__ ((section (".ext_ram.bss"))) uint8_t psram_vector[PSRAM_TEST_VECTOR_LEN]; + +int main(void) +{ + uint32_t k; + + LOG_INF("Sample started"); + + LOG_DBG("Writing..."); + for (k = 0; k < PSRAM_TEST_VECTOR_LEN; k++) { + psram_vector[k] = (uint8_t)k; + LOG_DBG("psram_vector[%"PRIu32"]: %"PRIu8, k, psram_vector[k]); + } + + LOG_DBG("Reading..."); + for (k = 0; k < PSRAM_TEST_VECTOR_LEN; k++) { + if (psram_vector[k] != (uint8_t)k) { + LOG_ERR("psram_vector[%"PRIu32"]: %"PRIu8" (expected value %"PRIu8")", k, psram_vector[k], (uint8_t)k); + LOG_ERR("Verification failed!"); + return -1; + } + } + + LOG_INF("Sample finished successfully!"); + + return 0; +} +``` + +To build a project containing these two files targeting a `ESP32-S3-DevKitC-1` board, run: + +```sh +$ west build -b esp32s3_devkitc/esp32s3/procpu --pristine +``` + +This command will create a directory called `build`, which will contain the binary file of our sample along with other intermediate files produced during the building process. + +Before we move on, let's take a look inside the `build/zephyr/zephyr.map` file: + +``` +... + +10086 .ext_ram.bss 0x000000003c020000 0x100000 +10087 0x000000003c020000 _ext_ram_bss_start = ABSOLUTE (.) +10088 *(SORT_BY_ALIGNMENT(.ext_ram.bss*)) +10089 .ext_ram.bss 0x000000003c020000 0x80000 app/libapp.a(main.c.obj) +10090 0x000000003c020000 psram_vector +10091 0x000000003c0a0000 . = ALIGN (0x4) +10092 0x000000003c0a0000 _spiram_heap_start = ABSOLUTE (.) +10093 0x000000003c120000 . = ((. + 0x100000) - (_spiram_heap_start - _ext_ram_bss_start)) +10094 *fill* 0x000000003c0a0000 0x80000 +10095 0x000000003c120000 . = ALIGN (0x4) +10096 0x000000003c120000 _ext_ram_bss_end = ABSOLUTE (.) + +... +``` + +Here we can see that the first position of `psram_vector` is at the address ```0x3c020000``` which is inside the region mapping for the external PSRAM on ESP32-S3. We can also see that although `SHARED_MULTI_HEAP` parameter was not explicitly enabled, it has some area reserved for `spiram_head`. It happens because `SHARED_MULTI_HEAP` parameter is enabled by default once ```ESP_SPIRAM``` is enabled. + +Now let's flash the binary onto the `ESP32-S3-DevKitC-1` board and observe the messages from the sample: + +```sh +$ west flash +$ west espressif monitor +``` + +```sh +ESP-ROM:esp32s3-20210327 +Build:Mar 27 2021 +rst:0x1 (POWERON),boot:0x8 (SPI_FAST_FLASH_BOOT) +SPIWP:0xee +mode:DIO, clock div:2 +load:0x3fc90520,len:0x227c +load:0x40374000,len:0xc500 +SHA-256 comparison failed: +Calculated: 339e38ef89806c3327cd031629506600bfdbcee657c9337751c78707c3bcc339 +Expected: 0000000040180000000000000000000000000000000000000000000000000000 +Attempting to boot anyway... +entry 0x40379b2c +I (79) soc_init: ESP Simple boot +I (79) soc_init: compile time Dec 4 2024 17:35:34 +W (80) soc_init: Unicore bootloader +I (80) spi_flash: detected chip: gd +I (82) spi_flash: flash io: dio +W (85) spi_flash: Detected size(8192k) larger than the size in the binary image header(2048k). Using the size in the binary image header. +I (97) soc_init: chip revision: v0.2 +I (100) flash_init: Boot SPI Speed : 40MHz +I (104) flash_init: SPI Mode : DIO +I (108) flash_init: SPI Flash Size : 8MB +I (111) soc_random: Enabling RNG early entropy source +I (116) boot: DRAM: lma 0x00000020 vma 0x3fc90520 len 0x227c (8828) +I (122) boot: IRAM: lma 0x000022a4 vma 0x40374000 len 0xc500 (50432) +I (129) boot: padd: lma 0x0000e7b8 vma 0x00000000 len 0x1840 (6208) +I (135) boot: IMAP: lma 0x00010000 vma 0x42000000 len 0x43ec (17388) +I (141) boot: padd: lma 0x000143f4 vma 0x00000000 len 0xbc04 (48132) +I (147) boot: DMAP: lma 0x00020000 vma 0x3c010000 len 0x1690 (5776) +I (153) boot: Image with 6 segments +I (157) boot: DROM segment: paddr=00020000h, vaddr=3c010000h, size=01690h ( 5776) map +I (164) boot: IROM segment: paddr=00010000h, vaddr=42000000h, size=043EAh ( 17386) map +I (183) soc_random: Disabling RNG early entropy source +I (183) boot: Disabling glitch detection +I (183) boot: Jumping to the main image... +I (184) octal_psram: vendor id : 0x0d (AP) +I (188) octal_psram: dev id : 0x02 (generation 3) +I (193) octal_psram: density : 0x03 (64 Mbit) +I (198) octal_psram: good-die : 0x01 (Pass) +I (202) octal_psram: Latency : 0x01 (Fixed) +I (206) octal_psram: VCC : 0x01 (3V) +I (210) octal_psram: SRF : 0x01 (Fast Refresh) +I (215) octal_psram: BurstType : 0x01 (Hybrid Wrap) +I (220) octal_psram: BurstLen : 0x01 (32 Byte) +I (225) octal_psram: Readlatency : 0x02 (10 cycles@Fixed) +I (230) octal_psram: DriveStrength: 0x00 (1/1) +I (235) MSPI Timing: PSRAM timing tuning index: 6 +I (238) esp_psram: Found 8MB PSRAM device +I (242) esp_psram: Speed: 80MHz +I (301) heap_runtime: ESP heap runtime init at 0x3fcacad0 size 243 kB. + +*** Booting Zephyr OS build v4.0.0-1471-ge60f04096cd8 *** +[00:00:00.301,000] PSRAM_SAMPLE: Sample started +[00:00:00.373,000] PSRAM_SAMPLE: Sample finished successfully! +``` + +Here, we can observe that the application now starts earlier compared to the previous sample. This improvement results from disabling the ```ESP_SPIRAM_MEMTEST``` parameter, which bypasses the PSRAM memory test that previously took a few hundred milliseconds. + +## Moving code from FLASH to PSRAM + +Enabling the ```SPIRAM_FETCH_INSTRUCTIONS``` parameter will move instructions from FLASH to PSRAM during startup, and if the ```ESP_SPIRAM_MEMTEST``` parameter is also enabled, code that requires execution during an SPI1 Flash operation can forgo being placed in IRAM, thus optimizing RAM usage. + +To check the effects of ```SPIRAM_FETCH_INSTRUCTIONS``` and ```SPIRAM_RODATA``` parameters, let's build `hello_world` first without enabling them and then enable them. + +***Building `hello_world` with `SPIRAM_FETCH_INSTRUCTIONS` and `SPIRAM_RODATA` disbled*** + +```sh +west build -b esp32s3_devkitc/esp32s3/procpu zephyr/samples/hello_world/ --pristine +``` + +```sh +Memory region Used Size Region Size %age Used + FLASH: 135376 B 8388352 B 1.61% + iram0_0_seg: 39056 B 343552 B 11.37% + dram0_0_seg: 39072 B 327168 B 11.94% + irom0_0_seg: 14642 B 8 MB 0.17% + drom0_0_seg: 69840 B 8 MB 0.83% + rtc_iram_seg: 0 GB 8 KB 0.00% + rtc_data_seg: 0 GB 8 KB 0.00% + rtc_slow_seg: 0 GB 8 KB 0.00% + IDT_LIST: 0 GB 8 KB 0.00% +``` + +```sh +ESP-ROM:esp32s3-20210327 +Build:Mar 27 2021 +rst:0x1 (POWERON),boot:0xa (SPI_FAST_FLASH_BOOT) +SPIWP:0xee +mode:DIO, clock div:2 +load:0x3fc8d8a0,len:0x176c +load:0x40374000,len:0x9880 +SHA-256 comparison failed: +Calculated: 695635a387a5684addee1fafb971b13abbd4e67842bcf1cd46cd6df6e2ad5250 +Expected: 00000000d04f0000000000000000000000000000000000000000000000000000 +Attempting to boot anyway... +entry 0x40377bd0 +I (67) soc_init: ESP Simple boot +I (68) soc_init: compile time Dec 4 2024 19:25:19 +W (68) soc_init: Unicore bootloader +I (68) spi_flash: detected chip: gd +I (70) spi_flash: flash io: dio +W (73) spi_flash: Detected size(8192k) larger than the size in the binary image header(2048k). Using the size in the binary image header. +I (85) soc_init: chip revision: v0.2 +I (88) flash_init: Boot SPI Speed : 40MHz +I (92) flash_init: SPI Mode : DIO +I (96) flash_init: SPI Flash Size : 8MB +I (99) soc_random: Enabling RNG early entropy source +I (104) boot: DRAM: lma 0x00000020 vma 0x3fc8d8a0 len 0x176c (5996) +I (110) boot: IRAM: lma 0x00001794 vma 0x40374000 len 0x9880 (39040) +I (116) boot: padd: lma 0x0000b028 vma 0x00000000 len 0x4fd0 (20432) +I (123) boot: IMAP: lma 0x00010000 vma 0x42000000 len 0x3934 (14644) +I (129) boot: padd: lma 0x0001393c vma 0x00000000 len 0xc6bc (50876) +I (135) boot: DMAP: lma 0x00020000 vma 0x3c010000 len 0x10d0 (4304) +I (141) boot: Image with 6 segments +I (144) boot: DROM segment: paddr=00020000h, vaddr=3c010000h, size=010D0h ( 4304) map +I (152) boot: IROM segment: paddr=00010000h, vaddr=42000000h, size=03932h ( 14642) map +I (171) soc_random: Disabling RNG early entropy source +I (171) boot: Disabling glitch detection +I (171) boot: Jumping to the main image... +I (208) heap_runtime: ESP heap runtime init at 0x3fc918a0 size 351 kB. + +*** Booting Zephyr OS build v4.0.0-1471-ge60f04096cd8 *** +Hello World! esp32s3_devkitc/esp32s3/procpu +``` + +***Building `hello_world` with `PIRAM_FETCH_INSTRUCTIONS` and `SPIRAM_RODATA` enabled*** + +```sh +west build -b esp32s3_devkitc/esp32s3/procpu zephyr/samples/hello_world/ -DCONFIG_ESP_SPIRAM=y -DCONFIG_SPIRAM_TYPE_ESPPSRAM64=y -DCONFIG_SPIRAM_MODE_OCT=y -DCONFIG_SPIRAM_SPEED_80M=y -DCONFIG_ESP32S3_DATA_CACHE_64KB=y -DCONFIG_ESP_SPIRAM_MEMTEST=y -DCONFIG_SPIRAM_FETCH_INSTRUCTIONS=y -DCONFIG_SPIRAM_RODATA=y --pristine +``` + +```sh +Memory region Used Size Region Size %age Used + FLASH: 135916 B 8388352 B 1.62% + iram0_0_seg: 43384 B 343552 B 12.63% + dram0_0_seg: 45104 B 327168 B 13.79% + irom0_0_seg: 15942 B 8 MB 0.19% + drom0_0_seg: 70380 B 8 MB 0.84% + ext_ram_seg: 1152 KB 8388544 B 14.06% + rtc_iram_seg: 0 GB 8 KB 0.00% + rtc_data_seg: 0 GB 8 KB 0.00% + rtc_slow_seg: 0 GB 8 KB 0.00% + IDT_LIST: 0 GB 8 KB 0.00% +``` + +```sh +ESP-ROM:esp32s3-20210327 +Build:Mar 27 2021 +rst:0x1 (POWERON),boot:0x8 (SPI_FAST_FLASH_BOOT) +SPIWP:0xee +mode:DIO, clock div:2 +load:0x3fc8e988,len:0x1c2c +load:0x40374000,len:0xa968 +SHA-256 comparison failed: +Calculated: 8196087d6087abb51d92ad226e9d134e19497a3447ffb32faca13f70679ea728 +Expected: 00000000303a0000000000000000000000000000000000000000000000000000 +Attempting to boot anyway... +entry 0x40377dcc +I (72) soc_init: ESP Simple boot +I (72) soc_init: compile time Dec 4 2024 19:32:57 +W (72) soc_init: Unicore bootloader +I (72) spi_flash: detected chip: gd +I (75) spi_flash: flash io: dio +W (78) spi_flash: Detected size(8192k) larger than the size in the binary image header(2048k). Using the size in the binary image header. +I (90) soc_init: chip revision: v0.2 +I (93) flash_init: Boot SPI Speed : 40MHz +I (97) flash_init: SPI Mode : DIO +I (100) flash_init: SPI Flash Size : 8MB +I (104) soc_random: Enabling RNG early entropy source +I (109) boot: DRAM: lma 0x00000020 vma 0x3fc8e988 len 0x1c2c (7212) +I (115) boot: IRAM: lma 0x00001c54 vma 0x40374000 len 0xa968 (43368) +I (121) boot: padd: lma 0x0000c5c8 vma 0x00000000 len 0x3a30 (14896) +I (127) boot: IMAP: lma 0x00010000 vma 0x42000000 len 0x3e48 (15944) +I (134) boot: padd: lma 0x00013e50 vma 0x00000000 len 0xc1a8 (49576) +I (140) boot: DMAP: lma 0x00020000 vma 0x3c010000 len 0x12ec (4844) +I (146) boot: Image with 6 segments +I (149) boot: DROM segment: paddr=00020000h, vaddr=3c010000h, size=012F0h ( 4848) map +I (157) boot: IROM segment: paddr=00010000h, vaddr=42000000h, size=03E46h ( 15942) map +I (176) soc_random: Disabling RNG early entropy source +I (176) boot: Disabling glitch detection +I (176) boot: Jumping to the main image... +I (177) octal_psram: vendor id : 0x0d (AP) +I (181) octal_psram: dev id : 0x02 (generation 3) +I (186) octal_psram: density : 0x03 (64 Mbit) +I (190) octal_psram: good-die : 0x01 (Pass) +I (195) octal_psram: Latency : 0x01 (Fixed) +I (199) octal_psram: VCC : 0x01 (3V) +I (203) octal_psram: SRF : 0x01 (Fast Refresh) +I (208) octal_psram: BurstType : 0x01 (Hybrid Wrap) +I (213) octal_psram: BurstLen : 0x01 (32 Byte) +I (217) octal_psram: Readlatency : 0x02 (10 cycles@Fixed) +I (223) octal_psram: DriveStrength: 0x00 (1/1) +I (227) MSPI Timing: PSRAM timing tuning index: 6 +I (231) esp_psram: Found 8MB PSRAM device +I (235) esp_psram: Speed: 80MHz +I (245) mmu_psram: Instructions copied and mapped to SPIRAM +I (252) mmu_psram: Read only data copied and mapped to SPIRAM +I (652) esp_psram: SPI SRAM memory test OK +I (708) heap_runtime: ESP heap runtime init at 0x3fc93030 size 345 kB. + +*** Booting Zephyr OS build v4.0.0-1471-ge60f04096cd8 *** +Hello World! esp32s3_devkitc/esp32s3/procpu +``` + +Here, we can confirm that instructions and read-only data were copied and mapped to PSRAM, which can optimize RAM usage when there is code that needs to execute during an SPI1 flash operation. + +## Allowing the ESP32 Wi-Fi stack allocate from PSRAM + +Another potential use of PSRAM to reduce SRAM usage is by enabling `ESP32_WIFI_NET_ALLOC_SPIRAM` parameter, allowing the ESP32 Wi-Fi stack to dynamically allocate memory from PSRAM. + +To check the results, lets build the `zephyr/samples/net/wifi/shell`, first with `ESP32_WIFI_NET_ALLOC_SPIRAM` disabled and then with this parameter enabled. In both cases let's flash the binary into the board and launch a console to interact with the shell. As our last shell interation we will execute `net allocs` to get information from where memory is being allocated in both cases. + +***Building `zephyr/samples/net/wifi/shell` with `ESP32_WIFI_NET_ALLOC_SPIRAM` disabled*** + +Building, flashing and monitoring: + +``` +$ west build -b esp32s3_devkitc/esp32s3/procpu zephyr/samples/net/wifi/shell --pristine -DCONFIG_NET_DEBUG_NET_PKT_ALLOC=y +$ west flash +$ west espressif monitor +``` + +Booting and interacting with the wifi shell: + +``` +ESP-ROM:esp32s3-20210327 +Build:Mar 27 2021 +rst:0x1 (POWERON),boot:0xa (SPI_FAST_FLASH_BOOT) +SPIWP:0xee +mode:DIO, clock div:2 +load:0x3fc95c28,len:0x36fc +load:0x40374000,len:0x11c08 +SHA-256 comparison failed: +Calculated: 804456ce6d81d191858dc7a0857b3cdf94a27879af99c411302bc2add57816f4 +Expected: 00000000c0ac0000000000000000000000000000000000000000000000000000 +Attempting to boot anyway... +entry 0x4037c7dc +I (102) soc_init: ESP Simple boot +I (102) soc_init: compile time Dec 16 2024 13:25:28 +W (103) soc_init: Unicore bootloader +I (103) spi_flash: detected chip: gd +I (105) spi_flash: flash io: dio +W (108) spi_flash: Detected size(8192k) larger than the size in the binary image header(2048k). Using the size in the binary image header. +I (121) soc_init: chip revision: v0.2 +I (124) flash_init: Boot SPI Speed : 40MHz +I (128) flash_init: SPI Mode : DIO +I (131) flash_init: SPI Flash Size : 8MB +I (135) soc_random: Enabling RNG early entropy source +I (140) boot: DRAM: lma 0x00000020 vma 0x3fc95c28 len 0x36fc (14076) +I (146) boot: IRAM: lma 0x00003724 vma 0x40374000 len 0x11c08 (72712) +I (152) boot: padd: lma 0x00015338 vma 0x00000000 len 0xacc0 (44224) +I (158) boot: IMAP: lma 0x00020000 vma 0x42000000 len 0x5452c (345388) +I (165) boot: padd: lma 0x00074534 vma 0x00000000 len 0xbac4 (47812) +I (171) boot: DMAP: lma 0x00080000 vma 0x3c060000 len 0x15fa0 (90016) +I (177) boot: Image with 6 segments +I (180) boot: DROM segment: paddr=00080000h, vaddr=3c060000h, size=15FA0h ( 90016) map +I (188) boot: IROM segment: paddr=00020000h, vaddr=42000000h, size=5452Ah (345386) map +I (207) soc_random: Disabling RNG early entropy source +I (207) boot: Disabling glitch detection +I (207) boot: Jumping to the main image... +I (244) heap_runtime: ESP heap runtime init at 0x3fcac6b0 size 244 kB. + +*** Booting Zephyr OS build v4.0.0-2029-g6e0e5591a4fb *** +uart:~$ wifi scan +Scan requested + +Num | SSID (len) | Chan (Band) | RSSI | Security | BSSID | MFP +1 | Soares 6 | 11 (2.4GHz) | -49 | WPA2-PSK | 90:0A:62:42:A5:BF | Disable +... +uart:~$ wifi connect --key-mgmt 1 --ssid --passphrase +Connection requested +Connected +[00:04:27.263,000] net_dhcpv4: Received: 192.168.15.2 +uart:~$ net ping 192.168.15.17 +PING 192.168.15.17 +28 bytes from 192.168.15.17 to 192.168.15.2: icmp_seq=1 ttl=64 time=8 ms +28 bytes from 192.168.15.17 to 192.168.15.2: icmp_seq=2 ttl=64 time=224 ms +28 bytes from 192.168.15.17 to 192.168.15.2: icmp_seq=3 ttl=64 time=455 ms +uart:~$ net allocs +Network memory allocations +memory Status Pool Function alloc -> freed +0x3fca4af0 free RX eth_esp32_rx():135 -> processing_data():178 +0x3fca4870 free TX arp_prepare_reply():719 -> ethernet_send():762 +0x3fca3a2c free TDATA ethernet_fill_header():532 -> ethernet_remove_l2_header():615 +``` + +***Building `zephyr/samples/net/wifi/shell` with `ESP32_WIFI_NET_ALLOC_SPIRAM` enabled*** + +Building, flashing and monitoring: + +``` +$ west build -b esp32s3_devkitc/esp32s3/procpu zephyr/samples/net/wifi/shell --pristine -DCONFIG_ESP_SPIRAM=y -DCONFIG_SPIRAM_TYPE_ESPPSRAM64=y -DCONFIG_SPIRAM_MODE_OCT=y -DCONFIG_SPIRAM_SPEED_80M=y -DCONFIG_ESP32S3_DATA_CACHE_64KB=y -DCONFIG_ESP32_WIFI_NET_ALLOC_SPIRAM=y -DCONFIG_NET_DEBUG_NET_PKT_ALLOC=y +$ west flash +$ west espressif monitor +``` + +Booting and interacting with the wifi shell: + +``` +ESP-ROM:esp32s3-20210327 +Build:Mar 27 2021 +rst:0x1 (POWERON),boot:0x8 (SPI_FAST_FLASH_BOOT) +SPIWP:0xee +mode:DIO, clock div:2 +load:0x3fc96ae0,len:0x3a7c +load:0x40374000,len:0x12ac0 +0x40374000: _WindowOverflow4 at /home/wmrsouza/projects/espressif/00_zephyr-workspace/zephyr/arch/xtensa/core/window_vectors.S:56 + +SHA-256 comparison failed: +Calculated: 058fa1d1ebadeeafac9a16c62873a8ee1aeb287b513b11e7ef15664c08015ec4 +Expected: 00000000809a0000000000000000000000000000000000000000000000000000 +Attempting to boot anyway... +entry 0x4037c974 +I (106) soc_init: ESP Simple boot +I (106) soc_init: compile time Dec 16 2024 13:17:44 +W (106) soc_init: Unicore bootloader +I (107) spi_flash: detected chip: gd +I (109) spi_flash: flash io: dio +W (112) spi_flash: Detected size(8192k) larger than the size in the binary image header(2048k). Using the size in the binary image header. +I (124) soc_init: chip revision: v0.2 +I (128) flash_init: Boot SPI Speed : 40MHz +I (131) flash_init: SPI Mode : DIO +I (135) flash_init: SPI Flash Size : 8MB +I (139) soc_random: Enabling RNG early entropy source +I (144) boot: DRAM: lma 0x00000020 vma 0x3fc96ae0 len 0x3a7c (14972) +I (150) boot: IRAM: lma 0x00003aa4 vma 0x40374000 len 0x12ac0 (76480) +I (156) boot: padd: lma 0x00016578 vma 0x00000000 len 0x9a80 (39552) +I (162) boot: IMAP: lma 0x00020000 vma 0x42000000 len 0x549c4 (346564) +I (169) boot: padd: lma 0x000749cc vma 0x00000000 len 0xb62c (46636) +I (175) boot: DMAP: lma 0x00080000 vma 0x3c060000 len 0x1615c (90460) +I (181) boot: Image with 6 segments +I (184) boot: DROM segment: paddr=00080000h, vaddr=3c060000h, size=16160h ( 90464) map +I (192) boot: IROM segment: paddr=00020000h, vaddr=42000000h, size=549C2h (346562) map +I (211) soc_random: Disabling RNG early entropy source +I (211) boot: Disabling glitch detection +I (211) boot: Jumping to the main image... +I (212) octal_psram: vendor id : 0x0d (AP) +I (216) octal_psram: dev id : 0x02 (generation 3) +I (221) octal_psram: density : 0x03 (64 Mbit) +I (225) octal_psram: good-die : 0x01 (Pass) +I (230) octal_psram: Latency : 0x01 (Fixed) +I (234) octal_psram: VCC : 0x01 (3V) +I (238) octal_psram: SRF : 0x01 (Fast Refresh) +I (243) octal_psram: BurstType : 0x01 (Hybrid Wrap) +I (248) octal_psram: BurstLen : 0x01 (32 Byte) +I (252) octal_psram: Readlatency : 0x02 (10 cycles@Fixed) +I (258) octal_psram: DriveStrength: 0x00 (1/1) +I (262) MSPI Timing: PSRAM timing tuning index: 5 +I (266) esp_psram: Found 8MB PSRAM device +I (270) esp_psram: Speed: 80MHz +I (686) esp_psram: SPI SRAM memory test OK +I (714) heap_runtime: ESP heap runtime init at 0x3fca8788 size 259 kB. + +*** Booting Zephyr OS build v4.0.0-2029-g6e0e5591a4fb *** +uart:~$ wifi scan +Scan requested + +Num | SSID (len) | Chan (Band) | RSSI | Security | BSSID | MFP +1 | Soares 6 | 11 (2.4GHz) | -63 | WPA2-PSK | 90:0A:62:42:A5:BF | Disable +... +uart:~$ wifi connect --key-mgmt 1 --ssid --passphrase +Connection requested +Connected +[00:01:31.902,000] net_dhcpv4: Received: 192.168.15.2 +uart:~$ net ping 192.168.15.17 +PING 192.168.15.17 +28 bytes from 192.168.15.17 to 192.168.15.2: icmp_seq=1 ttl=64 time=231 ms +28 bytes from 192.168.15.17 to 192.168.15.2: icmp_seq=2 ttl=64 time=6 ms +28 bytes from 192.168.15.17 to 192.168.15.2: icmp_seq=3 ttl=64 time=338 ms +uart:~$ net allocs +Network memory allocations +memory Status Pool Function alloc -> freed +0x3c083920 free RX eth_esp32_rx():135 -> processing_data():178 +0x3c0836a0 free TX dhcpv4_create_message():287 -> ethernet_send():762 +0x3c082894 free TDATA ethernet_fill_header():532 -> ethernet_remove_l2_header():61 +``` + +Examining the output from both sessions, we can confirm that in the first case, network stack allocations were made using memory from internal RAM -- addresses 0x3fca4af0, 0x3fca4870, and 0x3fca3a2c. We can also confirm that in the second case, network stack allocations were taking memory from PSRAM -- addresses 0x3c083920, 0x3c0836a0, and 0x3c082894 -- thus proving that enabling `ESP32_WIFI_NET_ALLOC_SPIRAM` avoids allocating memory from the precious internal SRAM and takes advantage of PSRAM. + +## Conclusion + +Throughout this article, we explored three different strategies for utilizing PSRAM: using it as a heap for dynamic memory allocation, integrating it as a section in the memory map, and moving code from flash to PSRAM. + +In addition to these three strategies, it is also possible to leverage PSRAM to execute external code compiled as Position Independent Code (PIC), which can be placed in PSRAM by a loader for execution. + +## References + + - [Zephyr on ESP Devices](https://www.espressif.com/en/sdks/esp-zephyr) + - [SPI Flash and External SPI RAM Configuration](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-guides/flash_psram_config.html) + - Support for External RAM + - [ESP32](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/external-ram.html) + - [ESP32-S2](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-guides/external-ram.html) + - [ESP32-S3](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-guides/external-ram.html) + - [ESP32-P4](https://docs.espressif.com/projects/esp-idf/en/latest/esp32p4/api-guides/external-ram.html) + - [Zephyr Project Official Site](https://www.zephyrproject.org) + - [Zephyr Project Documentation Site](https://docs.zephyrproject.org/latest/index.html) + - [Getting Started Guide](https://docs.zephyrproject.org/latest/develop/getting_started/index.html) + - Supported Boards and Shields + - [Espressif](https://docs.zephyrproject.org/latest/boards/espressif/index.html) + - [ESP32-DevKitC-WROVER](https://docs.zephyrproject.org/latest/boards/espressif/esp32_devkitc_wrover/doc/index.html) + - [ESP32-S2-DevKitC](https://docs.zephyrproject.org/latest/boards/espressif/esp32s2_devkitc/doc/index.html) + - [ESP32-S3-DevKitC-1](https://docs.zephyrproject.org/latest/boards/espressif/esp32s3_devkitc/doc/index.html) + - [Shared Multi Heap](https://docs.zephyrproject.org/latest/kernel/memory_management/shared_multi_heap.html) + - [Code And Data Relocation](https://docs.zephyrproject.org/latest/kernel/code-relocation.html) + - [Linkable Loadable Extensions](https://docs.zephyrproject.org/latest/services/llext/index.html)