ESP32 PSRAM allocating memory for multidimensional arrays

Hi,
in the past small environments, I allocated the global display memory with:

#define WIDTH 240
#define HEIGHT 135

uint16_t display[HEIGHT][WIDTH];

and afterward I could use it:

display[y] [x] = bg;

memcpy(display, ttgo, sizeof(ttgo));

tft.pushImage(40, 50, WIDTH, HEIGHT, (uint16_t *) display);

When things grew (HEIGHT = 320), I bought an ESP32 with PSRAM and I try:

uint16_t *display[HEIGHT][WIDTH] = ps_malloc(HEIGHT * WIDTH * sizeof(uint16_t));

which gives me:
error: array must be initialized with a brace-enclosed initializer

I am not going to waste your time by apologizing for not having done all my lessons and instead put straight forward questions:

What is the initializer?

How do I rewrite the above line?

How do I rewrite my 3 lines of "usage" to reflect the fact, that the display is now a pointer?

Thank you for help.

tonys_0:
I am not going to waste your time by apologizing for not having done all my lessons and instead put straight forward questions:

Here's some homework for you, maybe it will help: How to dynamically allocate a 2D array in C? - GeeksforGeeks

What is the initializer?

A literal giving the initial value to a variable.

How do I rewrite the above line?

uint16_t *display = (uint16_t **) ps_malloc(HEIGHT * WIDTH * sizeof(uint16_t));

How do I rewrite my 3 lines of "usage" to reflect the fact, that the display is now a pointer?

You don't have to change anything, maybe for the first one but you failed to use code tags, so I don't know what it originally was.

First of all, the current core of the ESP32 IDE only uses Himem as bank switching is NOT enabled. So, for now, you only get to use 4MB of the 8MB. BUT, Espressif, will be releasing a new Arduino core that will be able to use Himem and PSRAM and bank switching. With that.

Here is a task using the Himem

void fReceiveSerial_LIDAR( void * parameters  )
{
  bool BeginSentence = false;
  char OneChar;
  char *str;
  str = (char *)ps_calloc(300, sizeof(char) ); // put str buffer into PSRAM
  // log_i("Free PSRAM before String: %d", ESP.getFreePsram());
  for ( ;; )
  {
    EventBits_t xbit = xEventGroupWaitBits (eg, evtReceiveSerial_LIDAR, pdTRUE, pdTRUE, portMAX_DELAY);
    if ( LIDARSerial.available() >= 1 )
    {
      while ( LIDARSerial.available() )
      {
        OneChar = LIDARSerial.read();
        if ( BeginSentence )
        {
          if ( OneChar == '>')
          {
            if ( xSemaphoreTake( sema_ParseLIDAR_ReceivedSerial, xSemaphoreTicksToWait10 ) == pdTRUE )
            {
               xQueueOverwrite( xQ_LIDAR_Display_INFO, ( void * ) &str );
              xEventGroupSetBits( eg, evtParseLIDAR_ReceivedSerial );
              //
            }
            BeginSentence = false;
            break;
          }
          strncat( str, &OneChar, 1 );
        }
        else
        {
          if ( OneChar == '<' )
          {
            strcpy( str, ""); // clear string buffer
            BeginSentence = true; // found beginning of sentence
          }
        }
      } //  while ( LIDARSerial.available() )
    } //if ( LIDARSerial.available() >= 1 )
    xSemaphoreGive( sema_ReceiveSerial_LIDAR );
    //        log_i( "fReceiveSerial_LIDAR " );
    //        log_i(uxTaskGetStackHighWaterMark( NULL ));
  }
  free(str);
  vTaskDelete( NULL );
} //void fParseSerial( void * parameters  )

You will need this include

#include "esp32-hal-psram.h"

Here is my code that I use to experiement with using PSRAM:

#include "sdkconfig.h"  //
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_system.h" //This inclusion configures the peripherals in the ESP system.
#include "esp32-hal-psram.h"
// #include "rom/cache.h"
extern "C" 
{
#include <esp_himem.h>
#include <esp_spiram.h>
}
//extern "C" 
//{
//#include <esp_himem.h>
//}

////
const int SerialDataBits = 115200;
////
struct stuTime
{
  int iSeconds = 0;
  int iMinutes = 0;
  int iHours = 0;
};
////
void setup()
{
  // Serial.begin( SerialDataBits );
  // psramInit(); // !!!!! Not needed
//  esp_spiram_init();  // Not Needed
//   log_i("Total heap: %d", ESP.getHeapSize());
//   log_i("Free heap: %d", ESP.getFreeHeap());
   log_i("Total PSRAM: %d", ESP.getPsramSize());
   log_i("Free PSRAM: %d", ESP.getFreePsram());
//   log_i("spiram size %u", esp_spiram_get_size());
//  log_i("himem free %d", esp_himem_get_free_size());
  // log_i("himem free %u\n", esp_himem_get_free_size());
  // log_i("himem phys %u\n", esp_himem_get_phys_size());
//  log_i("himem reserved %u\n", esp_himem_reserved_area_size());
  //
   int *ptr, *ptr1;
   int n, n1, i, sum = 0;
   float *ptrFloat;
   // Get the number of elements for the array
   n = 10;
   n1 = 20;
   log_i("Number of elements ptr: %d", n);
   log_i("Number of elements ptr1: %d", n1);
   log_i("Number of elements ptr1: %d\n", n1);
   // Dynamically allocate memory using malloc()
   // ptr = (int*)ps_malloc(n * sizeof(int)); //works
   ptr = (int*)ps_calloc( n, sizeof(int) ); // works
   log_i("Free PSRAM: %d", ESP.getFreePsram());
   ptr1 = (int*)ps_calloc( n1, sizeof(int) ); // works
   log_i("Free PSRAM: %d", ESP.getFreePsram());
    // Check if the memory has been successfully
    // allocated in ps_ram
    ptrFloat = (float*)ps_calloc( n, sizeof(float) ); // works
    if (ptr == NULL) {
      log_i(" ptr memory not allocated.\n");
      exit(0);
    }
    if (ptr1 == NULL)
    {
      log_i("ptr1 memory not allocated.\n");
      exit(0);
    }
    else
    {
       // Memory has been successfully allocated
      // log_i("ps_ram memory successfully allocated using ps_calloc.");
      // put elements into ps_ram array
      for (i = 0; i < n; ++i)
      {
        ptr[i] = i + 1;
      }
      for (i = 0; i < n1; ++i)
      {
        ptr1[i] = i + 2;
      }
      for (i = 0; i < n; ++i)
      {
        ptrFloat[i] = (float)i + 1.06555f;
      }
      // Print the elements of the array
      log_i("The elements of the ptr array are: ");
      for (i = 0; i < n; ++i) {
        log_i("%d, ", ptr[i]);
      }
      log_i("The elements of the ptr1 array are: ");
      for (i = 0; i < n1; ++i) {
        log_i("%d, ", ptr1[i]);
      }
      log_i("The elements of the ptrFloat array are: ");
      for (i = 0; i < n1; ++i) {
        log_i("%f, ", ptrFloat[i]);
      }
    }
    //
    // put a structure into psram. Works.
    struct stuTime *ptrStuTime;
    log_i("size of structure: %d", sizeof(struct stuTime) );
     // 
    ptrStuTime = (struct stuTime *)ps_malloc(sizeof(struct stuTime));
    log_i("Free PSRAM after structure: %d", ESP.getFreePsram());
    ptrStuTime->iSeconds = 10;
    ptrStuTime->iMinutes = 60;
    ptrStuTime->iHours = 100;
    log_i("Seconds: %d Minutes: %d Hours: %d", ptrStuTime->iSeconds, ptrStuTime->iMinutes, ptrStuTime->iHours );
    free(ptr);
    free(ptr1);
    free(ptrStuTime);
    // works
    log_i("Free PSRAM before String: %d", ESP.getFreePsram());
    char *str;
    char OneChar = 'a';
    char TwoChar = 'b';
    char ThreeChar = 'c';
    str = (char *)ps_calloc(300, sizeof(char) );
    log_i("Free PSRAM after String: %d", ESP.getFreePsram());
    // concantenate one char variable to end of char array to the str
    strncat( str, &OneChar, 1 ); //works
    strncat( str, &TwoChar, 1 );
    strncat( str, &ThreeChar, 1 );
    //
    //*(str+0) = 'G';  // works
    //*(str+1) = 'f';
    //*(str+2) = 'G';
    //*(str+3) = '\0';
    log_i("%s", str );
    free(str);

  ///////
//size_t memfree=esp_himem_get_free_size();
//log_i( "himemFree %d", memfree );
//  int memcnt=esp_himem_get_phys_size();
  // int memfree=esp_himem_get_free_size();
//  log_i( "SpiRamPhysSize %d", memcnt );
 //  log_i( "SpiRamFree %d", memfree );
  // esp_himem_get_phys_size(void)
   // esp_himem_handle_t mh; //Handle for the address space we're using
   // esp_himem_rangehandle_t rh; //Handle for the actual RAM.
   // esp_err_t intError = esp_himem_alloc( 4095, &mh);
   // log_i( "%s", esp_err_to_name( intError) );
   // log_i("spiram size %u", esp_spiram_get_size());
   // log_i( "Handle %d", mh );


} // setup()
////
/* possible adds to the config
 * #define CONFIG_ESP32_SPIRAM_SUPPORT 1
#define CONFIG_SPIRAM_BOOT_INIT 1
#define CONFIG_SPIRAM_IGNORE_NOTFOUND 0
#define CONFIG_SPIRAM_USE_MALLOC 1
#define CONFIG_SPIRAM_TYPE_AUTO 1
#define CONFIG_SPIRAM_SIZE -1
#define CONFIG_SPIRAM_SPEED_40M 1
#define CONFIG_SPIRAM_MEMTEST 1
#define CONFIG_SPIRAM_CACHE_WORKAROUND 1
#define CONFIG_SPIRAM_BANKSWITCH_ENABLE 1
#define CONFIG_SPIRAM_BANKSWITCH_RESERVE 4
 */
void loop() {}
////
//Fill memory with pseudo-random data generated from the given seed.
//Fills the memory in 32-bit words for speed.
static void fill_mem_seed(int seed, void *mem, int len)
{
  //    uint32_t *p = (uint32_t *)mem;
  //    unsigned int rseed = seed ^ 0xa5a5a5a5;
  //    for (int i = 0; i < len / 4; i++) {
  //        *p++ = rand_r(&rseed);
  //    }
}

//Check the memory filled by fill_mem_seed. Returns true if the data matches the data
//that fill_mem_seed wrote (when given the same seed).
//Returns true if there's a match, false when the region differs from what should be there.
static bool check_mem_seed(int seed, void *mem, int len, int phys_addr)
{
  //    uint32_t *p = (uint32_t *)mem;
  //    unsigned int rseed = seed ^ 0xa5a5a5a5;
  //    for (int i = 0; i < len / 4; i++) {
  //        uint32_t ex = rand_r(&rseed);
  //        if (ex != *p) {
  //            printf("check_mem_seed: %x has 0x%08x expected 0x%08x\n", phys_addr+((char*)p-(char*)mem), *p, ex);
  //            return false;
  //        }
  //        p++;
  //    }
  return true;
}

//Allocate a himem region, fill it with data, check it and release it.
static bool test_region(int check_size, int seed)
{
  esp_himem_handle_t mh; //Handle for the address space we're using
  esp_himem_rangehandle_t rh; //Handle for the actual RAM.
  bool ret = true;

  //Allocate the memory we're going to check.
  ESP_ERROR_CHECK(esp_himem_alloc(check_size, &mh));
  //Allocate a block of address range
  //    ESP_ERROR_CHECK(esp_himem_alloc_map_range(ESP_HIMEM_BLKSZ, &rh));
  //    for (int i = 0; i < check_size; i += ESP_HIMEM_BLKSZ) {
  //        uint32_t *ptr = NULL;
  //        //Map in block, write pseudo-random data, unmap block.
  //        ESP_ERROR_CHECK(esp_himem_map(mh, rh, i, 0, ESP_HIMEM_BLKSZ, 0, (void**)&ptr));
  //        fill_mem_seed(i ^ seed, ptr, ESP_HIMEM_BLKSZ); //
  //        ESP_ERROR_CHECK(esp_himem_unmap(rh, ptr, ESP_HIMEM_BLKSZ));
  //    }
  //    vTaskDelay(5); //give the OS some time to do things so the task watchdog doesn't bark
  //    for (int i = 0; i < check_size; i += ESP_HIMEM_BLKSZ) {
  //        uint32_t *ptr;
  //        //Map in block, check against earlier written pseudo-random data, unmap block.
  //        ESP_ERROR_CHECK(esp_himem_map(mh, rh, i, 0, ESP_HIMEM_BLKSZ, 0, (void**)&ptr));
  //        if (!check_mem_seed(i ^ seed, ptr, ESP_HIMEM_BLKSZ, i)) {
  //            printf("Error in block %d\n", i / ESP_HIMEM_BLKSZ);
  //            ret = false;
  //        }
  //        ESP_ERROR_CHECK(esp_himem_unmap(rh, ptr, ESP_HIMEM_BLKSZ));
  //        if (!ret) break; //don't check rest of blocks if error occurred
  //    }
  //    //Okay, all done!
  //    ESP_ERROR_CHECK(esp_himem_free(mh));
  //    ESP_ERROR_CHECK(esp_himem_free_map_range(rh));
  return ret;
}

////
//void logMemory()
//{
//  log_d("Used PSRAM: %d", ESP.getPsramSize() - ESP.getFreePsram());
//}

note the use:

extern "C" 
{
#include <esp_himem.h>
#include <esp_spiram.h>
}

The ESP32 API is your friend https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/system/himem.html

pylon:
A literal giving the initial value to a variable.

uint16_t *display = (uint16_t **) ps_malloc(HEIGHT * WIDTH * sizeof(uint16_t));

You don't have to change anything, maybe for the first one but you failed to use code tags, so I don't know what it originally was.

Thank you for answering.

The suggested line with double-pointer gives me:
error: cannot convert 'uint16_t** {aka short unsigned int**}' to 'uint16_t* {aka short unsigned int*}' in initialization

Also: I see, that the array dimensions have gone from the suggested line, so I have to deal with it when I am addressing the elements.

I have corrected the post by using the code tags...

gfvalvo:
Here's some homework for you, maybe it will help: How to dynamically allocate a 2D array in C? - GeeksforGeeks

Thank you for your advice.

So I have looked at it and I must say this:

If a programming language is so complicated treating simple things (two-dimensional arrays are no rocket science) there is no wonder, that we are still like stupid monkeys running naked in the woods, while other civilizations are looking at us from their space ships not wanting to come any closer... :slight_smile:

uint16_t *display[HEIGHT][WIDTH]That’s a lot of pointers.

(Two dimensional arrays are computer science, not rocket science)

TheMemberFormerlyKnownAsAWOL:
uint16_t *display[HEIGHT][WIDTH]That’s a lot of pointers.

(Two dimensional arrays are computer science, not rocket science)

Again:
If we make two dimensional arrays to science, where do we end up thinking relativity?

Our task must be to make things simple, so we can cope with the more complicated ones. And this idea tortures me since I bought my first Arduino and after decades of programming in Algol, Fortran, Pascal, Modula, Lisp, Simula, VB, SQL, you name it… I entered the “blessing” world of C.

With that said, what do you mean by your code?

tonys_0:
If we make two dimensional arrays to science, where do we end up thinking relativity?

2-D arrays are straight forward in C/C++. You're choosing to make them complex by allocating them dynamically. So, you'll either need to deal with it or find a different platform.

tonys_0:
With that said, what do you mean by your code?

It’s not my code, it’s your code from the original post.
It’s …unusual to have a pointer for every pixel.

gfvalvo:
2-D arrays are straight forward in C/C++. You're choosing to make them complex by allocating them dynamically. So, you'll either need to deal with it or find a different platform.

Actually my point was not to allocate "them" dynamically, but simply allocate them in the PSRAM, since "they" do not fit in internal RAM anymore.
If there is a straight forward way to place the multidimensional array in PSRAM, just give me the hint, please.

In the meantime, I am going to use method 4 from How to dynamically allocate a 2D array in C? - GeeksforGeeks
as you suggested (very good reading - thank you).

tonys_0:
Actually my point was not to allocate "them" dynamically, but simply allocate them in the PSRAM, since "they" do not fit in internal RAM anymore.
If there is a straight forward way to place the multidimensional array in PSRAM, just give me the hint, please.

In the meantime, I am going to use method 4 from How to dynamically allocate a 2D array in C? - GeeksforGeeks
as you suggested (very good reading - thank you).

reply number 4

Idahowalker:
reply number 4

Well, I have looked at your experimenting, which is very useful and enlightening.
In my country, we have a "famous" scientist, that developed technics for inventing paths that do NOT lead to the intended goal. ("By blowing smoke into the water you do not create gold.") Many of your commented lines witness of similar technics :slight_smile: But I am very grateful for the "works" comments.

Still, I do not see how a multidimensional array can be created in PSRAM straight forward, like:

uint16_t display[HEIGHT][WIDTH];

An unnecessary comment:
Maybe I will see that after crunching C for several years, but if I spend that time in a world, that simplifies things instead of making simple things complicated, I might create something useful instead.
When I started with C, I soon realized, that I will need a "drawer" for simple tricks to solve basic problems. The drawer, that consists of a tree of folders and another tree of internet bookmarks is quite impressive today even if it contains only very simple things. Every time some disaster happens with Apollo, Hubble telescope, etc. I can only guess that somebody forgot an * or & in the code, that "worked" for a while or in some conditions. Is this the way we want to walk?

Why can't the use of PSRAM (HIMEM or whatever) in the code be similar with the PROGMEM for instance?

Hubble failed because there wasn’t the budget for a proper separate calibration jig; Apollo 13 “failed” because the voltage for the O2 tank purge heater was changed.

Neither was remotely related to software.

You should pick your analogies more carefully.

I can recommend Les Hatton’s “Safer C” for some interesting (aka scary) case studies, covering many languages, including some considered “safe(r)” (I’m looking at you, Ms Lovelace)

TheMemberFormerlyKnownAsAWOL:
Hubble failed because there wasn't the budget for a proper separate calibration jig; Apollo 13 "failed" because the voltage for the O2 tank purge heater was changed.

Neither was remotely related to software.

You should pick your analogies more carefully.

I can recommend Les Hatton's "Safer C" for some interesting (aka scary) case studies, covering many languages, including some considered "safe(r)" (I'm looking at you, Ms Lovelace)

  1. You don't know how lucky you are living in a country, where you can trust the authorities' explanations. Still: be careful...

  2. You are right, I should be more careful picking analogies, but I am sure you got the point, even if you do not reflect that. I am actually working with aliasing problem while rotating a picture, but how can I concentrate on that, when I spend days declaring two-dimensional array for the picture?

tonys_0:
In the meantime, I am going to use method 4 from How to dynamically allocate a 2D array in C? - GeeksforGeeks
as you suggested (very good reading - thank you).

I actually find Methods 2 and 3 more straightforward. But, they all work.

tonys_0:
but how can I concentrate on that, when I spend days declaring two-dimensional array for the picture?

Now, if you had only spent as much time implementing one of the methods I linked as you have complaining about having to do it ........... you would have gotten back to solving your real issue long ago.

gfvalvo:
I actually find Methods 2 and 3 more straightforward. But, they all work.
Now, if you had only spent as much time implementing one of the methods I linked as you have complaining about having to do it … you would have gotten back to solving your real issue long ago.

Thank you for your advice. I only want to remind you, that “complaining” is often the better start to solve a problem, than stupid following the known path. You can walk to Rome as usual, but inventing a car is better.

Why can’t we write:

uint16_t display[HEIGHT] [WIDTH] PSRAM;

and let the compiler take care of all pointers to rows etc.

gfvalvo:
I actually find Methods 2 and 3 more straightforward. But, they all work.

Every malloc spends some overhead space. Both methods 2 and 3 run malloc inside a loop. Not mentioning the cleaning with free().

gfvalvo:
Here's some homework for you, maybe it will help: How to dynamically allocate a 2D array in C? - GeeksforGeeks

I've dealt with ESP32 memory issues, and how at the end of the day you can't really get more than 160KB of continuous RAM out of that 520KB that people think the chip actually has available. I just gave a short talk about it:
http://marc.merlins.org/perso/arduino/post_2020-01-14_LCA-2020-Talk_-ESP32-Memory-Management_-Neopixels-and-RGBPanels.html

Long story short, you can't have real native 2D arrays on ESP32 without rewriting all the code that uses array [ a ][ b ] and without "4) Using double pointer and one malloc call" from How to dynamically allocate a 2D array in C? - GeeksforGeeks

The last example kind of works, but not as well as you think because if you have a 2D framebuffer, there is code that will access it directly by going to the right offset, and the last method adds a pointer list, that changes the offset of things.

So, you can use PSRAM and you can use mallocs, but you'll never get real C 2D arrays.
Also sizeof(array) now gives you the size of pointer and not the size of your 2D array anymore.