more work with esp-idf v5 and esp32-s3 c++ code

I’m continuing to work with the latest release of ESP-IDF. I discovered some issues with an existing ESP32-S3 application I’ve presented here in the past. I’ll outline those issues as well as how those issues were fixed. I’ll also cover continuing problems I’m still having with ESP-IDF V5.

We need to configure certain MCU attributes with idf.py menuconfig, the most important the enabling of the external SPIRAM.

Configuration

The ESP32-S3-DevKitC-1-N8R8 comes with an ESP32-S3-WROOM-1 SOC, 8 GB (N8) of flash and 8 GB (R8) of external RAM. We’re going to configure some of the properties of the devkit to tell the software tool chain how much flash is present, and to enable the use of the external RAM. To do this we’re going to use idf.py menuconfig.

  • Set the amount of flash from the default of 2 GB to 8 GB. At the top-level of menuconfig select Serial flasher config; select Flash size (2 MB); move down to 8 MB and select it, then return to the top of menuconfig.
  • Enable the external RAM. At top-level of menuconfig select Component config; scroll down and select ESP PSRAM; enable Support for external, SPI-connected RAM; move down to and select SPI RAM config; select Mode (QUAD/OCT) of SPI RAM chip in use (Quad Mode PSRAM); select Octal Mode PSRAM; step back one level (left arrow).
    • move down to Initialize SPI RAM during startup and enable it;
    • move down to Run memory test on SPI RAM initialization and enable it.
  • Press ‘Q’ key and save these changes.

With the external RAM enable, the system will find it and allow it to become part of the overall memory pool, which means you now have 8.5 MB of memory to work with.

Updated Code

The code comes from the initial ESP32-S3 code I wrote a while back that flashes an external LED and the on-board NeoPixel. I’ll speak to changes after the listing.

The first issue I came across after enabling external RAM was with the NeoPixel blinking task. Before the external RAM was enabled, both tasks ran just fine. After, BlinkNeoPixel crashed its stack and caused the application to enter a continuous boot loop. I fixed that problem by first removing the NeoPixel initialization code and putting it into its own subroutine, which is called once to set up the NeoPixel (see lines 36 through 53). I should note that that is the way the original Espressif example code handled it. It was my decision to pull it into the NeoPixel blink task, even if it was only executed once before running in the while loop. This was a bad decision because it forces the task to consume more stack space than necessary. Once it was split apart again, there were no more stack crashes on that task. I also decided to increase the stack size on both tasks from 2048 bytes to 4096 bytes.

The second issue is with the external RAM itself. The way it’s configured is that it’s “lumped in” with the SoC’s 512K RAM, allowing regular applications to access it via malloc and free. I tried to access the memory more directly via esp_psram, but while I could write code with functions found in esp_psram, they failed to link because the psram subsystem would not build. And I couldn’t figure out how to configure my project to build that library. So I’m leaving the code as is and will move on with “normal” memory access to see what happens.

Here’s one run of the code on the idf.py monitor.

Hard resetting via RTS pin...Executing action: monitorRunning idf_monitor in directory /home/mint/Develop/esp/dualblink_v5Executing "/home/mint/.espressif/python_env/idf5.0_py3.10_env/bin/python /home/mint/Develop/esp/esp-idf-v5.0/tools/idf_monitor.py -p /dev/ttyUSB0 -b 115200 --toolchain-prefix xtensa-esp32s3-elf- --target esp32s3 /home/mint/Develop/esp/dualblink_v5/build/dualblink_v5.elf -m '/home/mint/.espressif/python_env/idf5.0_py3.10_env/bin/python' '/home/mint/Develop/esp/esp-idf-v5.0/tools/idf.py' '-p' '/dev/ttyUSB0'"...--- idf_monitor on /dev/ttyUSB0 115200 ------ Quit: Ctrl+] | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---p_image: segment 2: paddr=0001dbb8 vaddr=40374000 size=02460h (  9312)�ESP-ROM:esp32s3-20210327Build:Mar 27 2021rst:0x1 (POWERON),boot:0x8 (SPI_FAST_FLASH_BOOT)SPIWP:0xeemode:DIO, clock div:1load:0x3fce3810,len:0x167cload:0x403c9700,len:0xbe8load:0x403cc700,len:0x2e98entry 0x403c9904I (25) boot: ESP-IDF v5.0 2nd stage bootloaderI (25) boot: compile time 21:49:42I (25) boot: chip revision: v0.1I (26) boot_comm: chip revision: 1, min. bootloader chip revision: 0I (33) boot.esp32s3: Boot SPI Speed : 80MHzI (38) boot.esp32s3: SPI Mode   : DIOI (43) boot.esp32s3: SPI Flash Size : 8MBI (48) boot: Enabling RNG early entropy source...I (53) boot: Partition Table:I (57) boot: ## LabelUsage  Type ST Offset   LengthI (64) boot:  0 nvs  WiFi data01 02 00009000 00006000I (71) boot:  1 phy_init RF data  01 01 0000f000 00001000I (79) boot:  2 factory  factory app  00 00 00010000 00100000I (86) boot: End of partition tableI (91) boot_comm: chip revision: 1, min. application chip revision: 0I (98) esp_image: segment 0: paddr=00010020 vaddr=3c020020 size=0a9b4h ( 43444) mapI (114) esp_image: segment 1: paddr=0001a9dc vaddr=3fc92d00 size=031d4h ( 12756) loadI (117) esp_image: segment 2: paddr=0001dbb8 vaddr=40374000 size=02460h (  9312) loadI (125) esp_image: segment 3: paddr=00020020 vaddr=42000020 size=1d974h (121204) mapI (153) esp_image: segment 4: paddr=0003d99c vaddr=40376460 size=0c880h ( 51328) loadI (165) esp_image: segment 5: paddr=0004a224 vaddr=50000000 size=00010h (16) loadI (171) boot: Loaded app from partition at offset 0x10000I (172) boot: Disabling RNG early entropy source...I (185) octal_psram: vendor id: 0x0d (AP)I (185) octal_psram: dev id   : 0x02 (generation 3)I (185) octal_psram: density  : 0x03 (64 Mbit)I (190) octal_psram: good-die : 0x01 (Pass)I (195) octal_psram: Latency  : 0x01 (Fixed)I (201) octal_psram: VCC  : 0x01 (3V)I (206) octal_psram: SRF  : 0x01 (Fast Refresh)I (212) octal_psram: BurstType: 0x01 (Hybrid Wrap)I (217) octal_psram: BurstLen : 0x01 (32 Byte)I (223) octal_psram: Readlatency  : 0x02 (10 cycles@Fixed)I (229) octal_psram: DriveStrength: 0x00 (1/1)I (234) esp_psram: Found 8MB PSRAM deviceI (239) esp_psram: Speed: 40MHzI (243) cpu_start: Pro cpu up.I (247) cpu_start: Starting app cpu, entry point is 0x4037535c0x4037535c: call_start_cpu1 at /home/mint/Develop/esp/esp-idf-v5.0/components/esp_system/port/cpu_start.c:142I (0) cpu_start: App cpu up.I (984) esp_psram: SPI SRAM memory test OKI (993) cpu_start: Pro cpu start user codeI (993) cpu_start: cpu freq: 160000000 HzI (994) cpu_start: Application information:I (996) cpu_start: Project name: dualblink_v5I (1002) cpu_start: App version:  1I (1006) cpu_start: Compile time: Dec  6 2022 21:49:36I (1013) cpu_start: ELF file SHA256:  5c83520c39687425...I (1019) cpu_start: ESP-IDF:  v5.0I (1023) heap_init: Initializing. RAM available for dynamic allocation:I (1031) heap_init: At 3FC96960 len 00052DB0 (331 KiB): D/IRAMI (1037) heap_init: At 3FCE9710 len 00005724 (21 KiB): STACK/DRAMI (1044) heap_init: At 3FCF0000 len 00008000 (32 KiB): DRAMI (1050) heap_init: At 600FE010 len 00001FF0 (7 KiB): RTCRAMI (1057) esp_psram: Adding pool of 8192K of PSRAM memory to heap allocatorI (1065) spi_flash: detected chip: genericI (1069) spi_flash: flash io: dioI (1074) cpu_start: Starting scheduler on PRO CPU.I (0) cpu_start: Starting scheduler on APP CPU.I (1094) esp_psram: Reserving pool of 32K of internal memory for DMA/internal allocationsI (1094) DUAL_BLINK_V5: BEGINI (1104) DUAL_BLINK_V5: ESP-IDF VERSION v5.0I (1104) DUAL_BLINK_V5: APP_MAIN CORE 0I (1114) DUAL_BLINK_V5: FREE HEAP 8727288I (1114) DUAL_BLINK_V5: INITIALIZE NEOPIXELI (1124) gpio: GPIO[48]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 I (1134) DUAL_BLINK_V5: CREATE TASK 1I (1134) DUAL_BLINK_V5: CREATE TASK 2I (1134) DUAL_BLINK_V5: ENTER MAIN LOOPI (1154) gpio: GPIO[46]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 

So what’s happening during a boot?

  1. Lines 20-22 — The external RAM chip is first detected, and the size is displayed.
  2. Lines 39-51 — The external RAM chip is probed and detail information is printed.
  3. Line 71 — PSRAM is added to the full heap space.
  4. Line 80 — The initial amount of free heap is printed in bytes. This is printed by line 100 in dualblink_v5.cpp.

There’s more to come…

tinkering with c++ — replacing boost array with standard c++ array

Boost does more than provide a collection of advanced libraries for the C++ language. Many of Boost’s authors are also part of the C++ standards committee. Thus, many new features being considered for the C++ standard are implemented here first. One of those features that has now migrated into the official standard is boost::array, which as of C++11 is now std::array. The page for boost::array has the following line on its documentation front page:

Update: std::array is (as of C++11) part of the C++ standard. The differences between boost::array and std::array are minimal. If you are using C++11, you should consider using std::array instead of boost::array.

The following code shows some of the ways this container is used. If you change the #include <array> to <boost/array>, as well as the corresponding using statement, then it will compile and execute the same.

#include <array>#include <algorithm>#include <iostream>using std::array;using std::cout;using std::sort;using std::string;int main() {array<int, 10> iarray = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};cout << "Array of integers:" << "\n";for (const auto value : iarray) {cout << "Integer array value: " << value << "\n";}cout << "\n" << "Array of standard strings:" << "\n";array<string, 10> sarray ={"one","two","three","four","five","six","seven","eight","nine","ten"};for (const auto value : sarray) {cout << "String array value: " << value << "\n";}cout << "\n" << "Array of sorted strings:" << "\n";sort(sarray.begin(), sarray.end());for (const auto value : sarray) {cout << "String array value: " << value << "\n";}return 0;}

Compiling It

Compilation is a simple one liner.

g++ -std=c++20 array-test.cpp -o array-test

Sample Run

Here’s what happens when we run it.

~ ./array-test Array of integers:Integer array value: 1Integer array value: 2Integer array value: 3Integer array value: 4Integer array value: 5Integer array value: 6Integer array value: 7Integer array value: 8Integer array value: 9Integer array value: 10Array of standard strings:String array value: oneString array value: twoString array value: threeString array value: fourString array value: fiveString array value: sixString array value: sevenString array value: eightString array value: nineString array value: tenArray of sorted strings:String array value: eightString array value: fiveString array value: fourString array value: nineString array value: oneString array value: sevenString array value: sixString array value: tenString array value: threeString array value: two

Comments

What makes std::array better than std::vector is that there is no need for extra code to support a dynamic container like vector. The container array is like the simpler C fixed array, in that it’s fixed and static, but has enough container traits that it can be used in a C++ ranged for loop (lines 15,23, and 29), as show in the example above.

Using std::array in an Embedded Application

I have been writing ESP32-S3 and ESP32-C3 C++ code. I’ve published the following code more than once, but this time, I just want to highlight how I replaced the C++ standard library vector and tuple type with array. Here is where std::array helps in dropping the size of the binary. I checked the size of the generated binary (everything else the same) and discovered that I save a bit of 10K of generated binary, meaning that I saved 10K by switching to array. What’s more the ability to access the elements of the inner array in line 44 makes a lot more sense than the older way of declaring the inner colors a tuple and the convoluted way I accessed tuple members the way I did.

#include <stdio.h>#include <array>#include "freertos/FreeRTOS.h"#include "freertos/task.h"#include "driver/gpio.h"#include "esp_log.h"#include "led_strip.h"#include "sdkconfig.h"using std::array;const array<array<int, 3>, 7> colors {{{32,0,0},  // red{0,32,0},  // green{0,0,32},  // blue{0,32,32}, // cyan{32,0,32}, // magenta{32,16,0}, // yellow{0,0,0}// black}};// Task 1.//static void task_blink_neo_pixel(void * pvParameters) {static led_strip_t *pStrip_a;pStrip_a = led_strip_init(CONFIG_BLINK_LED_RMT_CHANNEL, CONFIG_BLINK_GPIO, 1);pStrip_a->clear(pStrip_a, 50);// Stay in an endless loop. Don't return from this task.//while(true) {for(auto color : colors) {pStrip_a->set_pixel(pStrip_a, 0, color[0], color[1], color[2]);pStrip_a->refresh(pStrip_a, 100);vTaskDelay(500 / portTICK_PERIOD_MS);// Set NeoPixel LED dark by clearing all its individual LEDs.pStrip_a->clear(pStrip_a, 50);vTaskDelay(500 / portTICK_PERIOD_MS);}}}// Task 2.//static void task_blink_led(void * pvParameters) {gpio_reset_pin(GPIO_NUM_46);// Set the GPIO as a push/pull output//gpio_set_direction(GPIO_NUM_46, GPIO_MODE_OUTPUT);// Stay in an endless loop. Don't return from this task.//while (true) {gpio_set_level(GPIO_NUM_46, true);   // LED onvTaskDelay(100 / portTICK_PERIOD_MS);gpio_set_level(GPIO_NUM_46, false);  // LED offvTaskDelay(100 / portTICK_PERIOD_MS);gpio_set_level(GPIO_NUM_46, true);   // LED onvTaskDelay(100 / portTICK_PERIOD_MS);gpio_set_level(GPIO_NUM_46, false);  // LED offvTaskDelay(2700 / portTICK_PERIOD_MS);}}extern "C" void app_main(void) {static const char *TAG = "DUAL_BLINK";int core = xPortGetCoreID();ESP_LOGI(TAG, "app_main running on core %i", core);ESP_LOGI(TAG, "CONFIG_BLINK_GPIO %i", CONFIG_BLINK_GPIO);// Create task 1.//TaskHandle_t xHandle1 = NULL;static uint8_t ucParameterToPass1 = 1;xTaskCreate(task_blink_neo_pixel,"BlinkNeoPixel",// human readable task name.2048,   // stack size in bytes.&ucParameterToPass1,tskIDLE_PRIORITY,&xHandle1);configASSERT(xHandle1);// Create task 2.//TaskHandle_t xHandle2 = NULL;static uint8_t ucParameterToPass2 = 1;xTaskCreate(task_blink_led,"BlinkLED", // human-readable task name.2048,   // stack size in bytes.&ucParameterToPass2,tskIDLE_PRIORITY,&xHandle2);configASSERT(xHandle2);// Stay in an endless loop. Don't return from this task.//while (true) {vTaskDelay(1000 / portTICK_PERIOD_MS);}}