Heap Memory Error (ESP32)

Hi guys, I am having some issues with my project, resulting in some HEAP errors
I am using both the Adafruit AMG8833 and GUISlice libraries together.
Hardware is an ESP32 with a 2.8" TFT resistive touch display (RM68090).

Ther problem arises when using the Interpolation example from the AMG8833 library

Here is the decoded stack error:

 PC: 0x40081fd1
EXCVADDR: 0x00000000

Decoding stack results
0x40089a6b: multi_heap_internal_unlock at /Users/ficeto/Desktop/ESP32/ESP32/esp-idf-public/components/heap/multi_heap.c line 380
0x4008a0a2: multi_heap_malloc at /Users/ficeto/Desktop/ESP32/ESP32/esp-idf-public/components/heap/multi_heap_poisoning.c line 206
0x40084b61: heap_caps_malloc at /Users/ficeto/Desktop/ESP32/ESP32/esp-idf-public/components/heap/heap_caps.c line 111
0x40084b92: heap_caps_malloc_default at /Users/ficeto/Desktop/ESP32/ESP32/esp-idf-public/components/heap/heap_caps.c line 140
0x40084e65: _malloc_r at /Users/ficeto/Desktop/ESP32/ESP32/esp-idf-public/components/newlib/syscalls.c line 37
0x400d7dbd: i2cAddQueue at C:\Users\MickW\Documents\Arduino\hardware\espressif\esp32\cores\esp32\esp32-hal-i2c.c line 1076
0x400d7e43: i2cAddQueueRead at C:\Users\MickW\Documents\Arduino\hardware\espressif\esp32\cores\esp32\esp32-hal-i2c.c line 1110

My main code will be posted in the following comments due to post size restrictions.

// ------------------------------------------------
// Headers to include
// ------------------------------------------------
#include "AdafruitTest_GSLC.h"
#include <Adafruit_AMG88xx.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <inttypes.h>

//AMG8833 globals ---------------------------------------------------
#define MINTEMP 25
#define MAXTEMP 35
Adafruit_AMG88xx amg;
unsigned long delayTime;
#define AMG_COLS 8
#define AMG_ROWS 8
float pixels[AMG_COLS * AMG_ROWS];
#define INTERPOLATED_COLS 42
#define INTERPOLATED_ROWS 42
float value;
float value2;
float r, g, b;
float get_point(float *p, uint8_t rows, uint8_t cols, int8_t x, int8_t y);
void set_point(float *p, uint8_t rows, uint8_t cols, int8_t x, int8_t y, float f);
void get_adjacents_1d(float *src, float *dest, uint8_t rows, uint8_t cols, int8_t x, int8_t y);
void get_adjacents_2d(float *src, float *dest, uint8_t rows, uint8_t cols, int8_t x, int8_t y);
float cubicInterpolate(float p[], float x);
float bicubicInterpolate(float p[], float x, float y);
void interpolate_image(float *src, uint8_t src_rows, uint8_t src_cols,
                      float *dest, uint8_t dest_rows, uint8_t dest_cols);
//Finish AMG8833 Globals ------------------------------------------------------------------------

gslc_tsElemRef* homeBtn           = NULL;
gslc_tsElemRef* thermalBtn        = NULL;

static int16_t DebugOut(char ch) {
 if (ch == (char)'\n') Serial.println("");
 else Serial.write(ch);
 return 0;
}
bool CbBtnCommon(void* pvGui, void *pvElemRef, gslc_teTouch eTouch, int16_t nX, int16_t nY)
{
 gslc_tsGui*     pGui     = (gslc_tsGui*)(pvGui);
 gslc_tsElemRef* pElemRef = (gslc_tsElemRef*)(pvElemRef);
 gslc_tsElem*    pElem    = gslc_GetElemFromRef(pGui, pElemRef);
 if ( eTouch == GSLC_TOUCH_UP_IN ) {
   switch (pElem->nId) {
     case TERMAL_BTN:
       gslc_SetPageCur(&m_gui, E_PG2);
       break;
     case HOME_BTN:
       gslc_SetPageCur(&m_gui, E_PG_MAIN);
       break;
     default:
       break;
   }
 }
 return true;
}

void setup()
{
 Serial.begin(9600);
 gslc_InitDebug(&DebugOut);
 InitGUIslice_gen();
 amg.begin();
}

void loop()
{
 if (gslc_GetPageCur(&m_gui) == E_PG2) {            //If thermal cam page is open run thermal cam code
   amg.readPixels(pixels);
   float dest_2d[INTERPOLATED_ROWS * INTERPOLATED_COLS];
   interpolate_image(pixels, AMG_ROWS, AMG_COLS, dest_2d, INTERPOLATED_ROWS, INTERPOLATED_COLS);
   uint16_t boxsize = min(1, 1);
   drawpixels(dest_2d, INTERPOLATED_ROWS, INTERPOLATED_COLS, boxsize, boxsize, false);
 }
 gslc_Update(&m_gui); //update GUI
}

void getHeatMapColor(float value, float *red, float *green, float *blue)     // Generate a color map from a couple of base colors
{
 const int NUM_COLORS = 5;
 static float color[][3] = { {0, 0, 20}, {125, 0, 155}, {225, 60, 35}, {225, 165, 0}, {255, 250, 210}  };
 // A static array of 5 colors:  (blue, red,  yellow, white) using {r,g,b} for each.
 // guess at matching intensities -- works pretty well!
 int idx1;        // |-- Our desired color will be between these two indexes in "color".
 int idx2;        // |
 float fractBetween = 0;  // Fraction between "idx1" and "idx2" where our value is.

 if (value <= 0)      {
   idx1 = idx2 = 0;             // accounts for an input <=0
 }
 else if (value >= 1)  {
   idx1 = idx2 = NUM_COLORS - 1;  // accounts for an input >=0
 }
 else
 {
   value = value * (NUM_COLORS - 1);      // Will multiply value by 3.
   idx1  = floor(value);                  // Our desired color will be after this index.
   idx2  = idx1 + 1;                      // ... and before this index (inclusive).
   fractBetween = value - (float)idx1;    // Distance between the two indexes (0-1).
 }
 *red   = (color[idx2][0] - color[idx1][0]) * fractBetween + color[idx1][0];
 *green = (color[idx2][1] - color[idx1][1]) * fractBetween + color[idx1][1];
 *blue  = (color[idx2][2] - color[idx1][2]) * fractBetween + color[idx1][2];
}


void drawpixels(float *p, uint8_t rows, uint8_t cols, uint8_t boxWidth, uint8_t boxHeight, boolean showVal) { //From Adafruit example
 int colorTemp;
 for (int y = 0; y < rows; y++) {
   for (int x = 0; x < cols; x++) {
     float val = get_point(p, rows, cols, x, y);
     value = map (val, MINTEMP, MAXTEMP, 0, 1000); //We need to create a float between 0 and 1 for the colormap genorator
     value2 = value / 1000;
     gslc_tsRect rCamRect = {                //Rect size using GUISlice struct
       boxWidth * x,
       boxHeight * y,
       boxWidth,
       boxHeight
     };
     getHeatMapColor(value2, &r, &g, &b);  //Add colors from color map genorator function to GUISlice color struct
     gslc_tsColor nCamCol_rgb888;
     nCamCol_rgb888.r =  r;
     nCamCol_rgb888.g =  g;
     nCamCol_rgb888.b =  b;
     gslc_DrvDrawFillRect(&m_gui, rCamRect, nCamCol_rgb888); //Draw rect using GUISlice drawfillrect function
   }
 }
}

The problem appears to be related to my program, I could not find any mention of similar problems when using the adafruit example by itsself.

The problem only arises when #define INTERPOLATED_COLS and #define INTERPOLATED_ROWS are larger than 41.
If they are set to 41 or less the sketch functions as expected.

The program uses 2 extra files, the .h file generated by the GUISlice builder, and interpolation.cpp, which was provided in the Adafruit example.

I will include the code from these in the following comments.

//<File !Start!>
// FILE: [AdafruitTest_GSLC.h]
// Created by GUIslice Builder version: [0.16.0]
//
// GUIslice Builder Generated GUI Framework File
//
// For the latest guides, updates and support view:
// https://github.com/ImpulseAdventure/GUIslice
//
//<File !End!>

#ifndef _GUISLICE_GEN_H
#define _GUISLICE_GEN_H

// ------------------------------------------------
// Headers to include
// ------------------------------------------------
#include "GUIslice.h"
#include "GUIslice_drv.h"

// Include any extended elements
//<Includes !Start!>
//<Includes !End!>

// ------------------------------------------------
// Headers and Defines for fonts
// Note that font files are located within the Adafruit-GFX library folder:
// ------------------------------------------------
//<Fonts !Start!>
#if defined(DRV_DISP_TFT_ESPI)
  #error Project tab->Target Platform should be tft_espi
#endif
#include <Adafruit_GFX.h>
//<Fonts !End!>

// ------------------------------------------------
// Defines for resources
// ------------------------------------------------
//<Resources !Start!>
//<Resources !End!>

// ------------------------------------------------
// Enumerations for pages, elements, fonts, images
// ------------------------------------------------
//<Enum !Start!>
enum {E_PG_MAIN,E_PG2};
enum {E_ELEM_BOX1,HOME_BTN,TERMAL_BTN};
// Must use separate enum for fonts with MAX_FONT at end to use gslc_FontSet.
enum {E_BUILTIN5X8,MAX_FONT};
//<Enum !End!>

// ------------------------------------------------
// Instantiate the GUI
// ------------------------------------------------

// ------------------------------------------------
// Define the maximum number of elements and pages
// ------------------------------------------------
//<ElementDefines !Start!>
#define MAX_PAGE                2

#define MAX_ELEM_PG_MAIN 2 // # Elems total on page
#define MAX_ELEM_PG_MAIN_RAM MAX_ELEM_PG_MAIN // # Elems in RAM

#define MAX_ELEM_PG2 1 // # Elems total on page
#define MAX_ELEM_PG2_RAM MAX_ELEM_PG2 // # Elems in RAM
//<ElementDefines !End!>

// ------------------------------------------------
// Create element storage
// ------------------------------------------------
gslc_tsGui                      m_gui;
gslc_tsDriver                   m_drv;
gslc_tsFont                     m_asFont[MAX_FONT];
gslc_tsPage                     m_asPage[MAX_PAGE];

//<GUI_Extra_Elements !Start!>
gslc_tsElem                     m_asPage1Elem[MAX_ELEM_PG_MAIN_RAM];
gslc_tsElemRef                  m_asPage1ElemRef[MAX_ELEM_PG_MAIN];
gslc_tsElem                     m_asPage2Elem[MAX_ELEM_PG2_RAM];
gslc_tsElemRef                  m_asPage2ElemRef[MAX_ELEM_PG2];

#define MAX_STR                 100

//<GUI_Extra_Elements !End!>

// ------------------------------------------------
// Program Globals
// ------------------------------------------------

// Element References for direct access
//<Extern_References !Start!>
extern gslc_tsElemRef* homeBtn;
extern gslc_tsElemRef* thermalBtn;
//<Extern_References !End!>

// Define debug message function
static int16_t DebugOut(char ch);

// ------------------------------------------------
// Callback Methods
// ------------------------------------------------
bool CbBtnCommon(void* pvGui,void *pvElemRef,gslc_teTouch eTouch,int16_t nX,int16_t nY);
bool CbCheckbox(void* pvGui, void* pvElemRef, int16_t nSelId, bool bState);
bool CbDrawScanner(void* pvGui,void* pvElemRef,gslc_teRedrawType eRedraw);
bool CbKeypad(void* pvGui, void *pvElemRef, int16_t nState, void* pvData);
bool CbListbox(void* pvGui, void* pvElemRef, int16_t nSelId);
bool CbSlidePos(void* pvGui,void* pvElemRef,int16_t nPos);
bool CbSpinner(void* pvGui, void *pvElemRef, int16_t nState, void* pvData);
bool CbTickScanner(void* pvGui,void* pvScope);

// ------------------------------------------------
// Create page elements
// ------------------------------------------------
void InitGUIslice_gen()
{
  gslc_tsElemRef* pElemRef = NULL;

  if (!gslc_Init(&m_gui,&m_drv,m_asPage,MAX_PAGE,m_asFont,MAX_FONT)) { return; }

  // ------------------------------------------------
  // Load Fonts
  // ------------------------------------------------
//<Load_Fonts !Start!>
    if (!gslc_FontSet(&m_gui,E_BUILTIN5X8,GSLC_FONTREF_PTR,NULL,1)) { return; }
//<Load_Fonts !End!>

//<InitGUI !Start!>
  gslc_PageAdd(&m_gui,E_PG_MAIN,m_asPage1Elem,MAX_ELEM_PG_MAIN_RAM,m_asPage1ElemRef,MAX_ELEM_PG_MAIN);
  gslc_PageAdd(&m_gui,E_PG2,m_asPage2Elem,MAX_ELEM_PG2_RAM,m_asPage2ElemRef,MAX_ELEM_PG2);

  // NOTE: The current page defaults to the first page added. Here we explicitly
  //       ensure that the main page is the correct page no matter the add order.
  gslc_SetPageCur(&m_gui,E_PG_MAIN);
  
  // Set Background to a flat color
  gslc_SetBkgndColor(&m_gui,GSLC_COL_BLACK);

  // -----------------------------------
  // PAGE: E_PG_MAIN
  
   
  // Create E_ELEM_BOX1 box
  pElemRef = gslc_ElemCreateBox(&m_gui,E_ELEM_BOX1,E_PG_MAIN,(gslc_tsRect){10,10,300,220});
  gslc_ElemSetCol(&m_gui,pElemRef,GSLC_COL_BLACK,GSLC_COL_BLACK,GSLC_COL_BLACK);
  
  // create TERMAL_BTN button with text label
  pElemRef = gslc_ElemCreateBtnTxt(&m_gui,TERMAL_BTN,E_PG_MAIN,
    (gslc_tsRect){120,100,80,40},(char*)"",0,E_BUILTIN5X8,&CbBtnCommon);
  gslc_ElemSetFrameEn(&m_gui,pElemRef,true);
  thermalBtn = pElemRef;

  // -----------------------------------
  // PAGE: E_PG2
  
  
  // create HOME_BTN button with text label
  pElemRef = gslc_ElemCreateBtnTxt(&m_gui,HOME_BTN,E_PG2,
    (gslc_tsRect){230,10,80,40},(char*)"",0,E_BUILTIN5X8,&CbBtnCommon);
  gslc_ElemSetFrameEn(&m_gui,pElemRef,true);
  homeBtn = pElemRef;
//<InitGUI !End!>

//<Startup !Start!>
  gslc_SetTransparentColor(&m_gui, ((gslc_tsColor){26,26,26}));
//<Startup !End!>

}

#endif // end _GUISLICE_GEN_H
#include <Arduino.h>

float get_point(float *p, uint8_t rows, uint8_t cols, int8_t x, int8_t y);
void set_point(float *p, uint8_t rows, uint8_t cols, int8_t x, int8_t y,
               float f);
void get_adjacents_1d(float *src, float *dest, uint8_t rows, uint8_t cols,
                      int8_t x, int8_t y);
void get_adjacents_2d(float *src, float *dest, uint8_t rows, uint8_t cols,
                      int8_t x, int8_t y);
float cubicInterpolate(float p[], float x);
float bicubicInterpolate(float p[], float x, float y);
void interpolate_image(float *src, uint8_t src_rows, uint8_t src_cols,
                       float *dest, uint8_t dest_rows, uint8_t dest_cols);

float get_point(float *p, uint8_t rows, uint8_t cols, int8_t x, int8_t y) {
  if (x < 0)
    x = 0;
  if (y < 0)
    y = 0;
  if (x >= cols)
    x = cols - 1;
  if (y >= rows)
    y = rows - 1;
  return p[y * cols + x];
}

void set_point(float *p, uint8_t rows, uint8_t cols, int8_t x, int8_t y,
               float f) {
  if ((x < 0) || (x >= cols))
    return;
  if ((y < 0) || (y >= rows))
    return;
  p[y * cols + x] = f;
}

// src is a grid src_rows * src_cols
// dest is a pre-allocated grid, dest_rows*dest_cols
void interpolate_image(float *src, uint8_t src_rows, uint8_t src_cols,
                       float *dest, uint8_t dest_rows, uint8_t dest_cols) {
  float mu_x = (src_cols - 1.0) / (dest_cols - 1.0);
  float mu_y = (src_rows - 1.0) / (dest_rows - 1.0);

  float adj_2d[16]; // matrix for storing adjacents

  for (uint8_t y_idx = 0; y_idx < dest_rows; y_idx++) {
    for (uint8_t x_idx = 0; x_idx < dest_cols; x_idx++) {
      float x = x_idx * mu_x;
      float y = y_idx * mu_y;
      // Serial.print("("); Serial.print(y_idx); Serial.print(", ");
      // Serial.print(x_idx); Serial.print(") = "); Serial.print("(");
      // Serial.print(y); Serial.print(", "); Serial.print(x); Serial.print(") =
      // ");
      get_adjacents_2d(src, adj_2d, src_rows, src_cols, x, y);
      /*
      Serial.print("[");
      for (uint8_t i=0; i<16; i++) {
        Serial.print(adj_2d[i]); Serial.print(", ");
      }
      Serial.println("]");
      */
      float frac_x = x - (int)x; // we only need the ~delta~ between the points
      float frac_y = y - (int)y; // we only need the ~delta~ between the points
      float out = bicubicInterpolate(adj_2d, frac_x, frac_y);
      // Serial.print("\tInterp: "); Serial.println(out);
      set_point(dest, dest_rows, dest_cols, x_idx, y_idx, out);
    }
  }
}

// p is a list of 4 points, 2 to the left, 2 to the right
float cubicInterpolate(float p[], float x) {
  float r = p[1] + (0.5 * x *
                    (p[2] - p[0] +
                     x * (2.0 * p[0] - 5.0 * p[1] + 4.0 * p[2] - p[3] +
                          x * (3.0 * (p[1] - p[2]) + p[3] - p[0]))));
  /*
    Serial.print("interpolating: [");
    Serial.print(p[0],2); Serial.print(", ");
    Serial.print(p[1],2); Serial.print(", ");
    Serial.print(p[2],2); Serial.print(", ");
    Serial.print(p[3],2); Serial.print("] w/"); Serial.print(x); Serial.print("
    = "); Serial.println(r);
  */
  return r;
}

// p is a 16-point 4x4 array of the 2 rows & columns left/right/above/below
float bicubicInterpolate(float p[], float x, float y) {
  float arr[4] = {0, 0, 0, 0};
  arr[0] = cubicInterpolate(p + 0, x);
  arr[1] = cubicInterpolate(p + 4, x);
  arr[2] = cubicInterpolate(p + 8, x);
  arr[3] = cubicInterpolate(p + 12, x);
  return cubicInterpolate(arr, y);
}

// src is rows*cols and dest is a 4-point array passed in already allocated!
void get_adjacents_1d(float *src, float *dest, uint8_t rows, uint8_t cols,
                      int8_t x, int8_t y) {
  // Serial.print("("); Serial.print(x); Serial.print(", "); Serial.print(y);
  // Serial.println(")");
  // pick two items to the left
  dest[0] = get_point(src, rows, cols, x - 1, y);
  dest[1] = get_point(src, rows, cols, x, y);
  // pick two items to the right
  dest[2] = get_point(src, rows, cols, x + 1, y);
  dest[3] = get_point(src, rows, cols, x + 2, y);
}

// src is rows*cols and dest is a 16-point array passed in already allocated!
void get_adjacents_2d(float *src, float *dest, uint8_t rows, uint8_t cols,
                      int8_t x, int8_t y) {
  // Serial.print("("); Serial.print(x); Serial.print(", "); Serial.print(y);
  // Serial.println(")");
  float arr[4];
  for (int8_t delta_y = -1; delta_y < 3; delta_y++) { // -1, 0, 1, 2
    float *row = dest + 4 * (delta_y + 1); // index into each chunk of 4
    for (int8_t delta_x = -1; delta_x < 3; delta_x++) { // -1, 0, 1, 2
      row[delta_x + 1] = get_point(src, rows, cols, x + delta_x, y + delta_y);
    }
  }
}

There was a topic with a similar issue where the sketch could not allocate more memory even if there appeared to be enough memory available.

Looks very complicated, a bit beyont me..
I can say this, i get the error instantly, but if I remove the if statement "if (gslc_GetPageCur(&m_gui) == E_PG2) {"
ok the program will runn for a few seconds before crashing.

So I know the following to be true;
-The crash only occurs when the number of rows and columns exceeds 41
-The crash can be delayed by a few seconds by removing the if statement that only runs the interpolation routine when a certain gui page is active.
-If I add some Serial.prints I can see that the loop doesnt make it past "if (gslc_GetPageCur(&m_gui) == E_PG2) {" I crashes as soon as this statement is reached.
-if I add a counter and serial print it, I can see without "if (gslc_GetPageCur(&m_gui) == E_PG2) {" the loop cycles about 15-20 times before crashing
-if I then increase the number of rows/columns I get a new error message:

    0x400857f8: invoke_abort at /Users/ficeto/Desktop/ESP32/ESP32/esp-idf-public/components/esp32/panic.c line 156
0x40085a6d: abort at /Users/ficeto/Desktop/ESP32/ESP32/esp-idf-public/components/esp32/panic.c line 171
0x40085a84: vApplicationStackOverflowHook at /Users/ficeto/Desktop/ESP32/ESP32/esp-idf-public/components/esp32/panic.c line 122
0x40087597: vTaskSwitchContext at /Users/ficeto/Desktop/ESP32/ESP32/esp-idf-public/components/freertos/tasks.c line 2769

Any advice on how to proceed?

According to this, the ESP32 has 320KB available memory, 160KB for static allocations (stack) and 160KB for dynamic allocations (heap). My suggestions is that you decrease your dynamic memory usage, maybe some of it can be statically allocated instead.

byte buffer[1024]; //Static
byte buffer* = new byte[1024]; //Dynamic
byte buffer* = (byte*)malloc(1024); //Dynamic

How do can I tell what is dynamically stored and what is statically stored? I dont fully understand what you mean, or how to implement it.

Static means that it is allocated at "compile time" and dynamic means that it is allocated during runtime.

Its beginning to make sense, but im still having difficulty. I believe this part of the code to be causing the problem (located in interpolation.cpp)

void set_point(float *p, uint8_t rows, uint8_t cols, int8_t x, int8_t y,
               float f) {
  if ((x < 0) || (x >= cols))
    return;
  if ((y < 0) || (y >= rows))
    return;
  p[y * cols + x] = f;
}

I am thinking this as another member has supplied code that replaces p[y * cols + x] = f; and sends the interpolated value directly to the display. Unfortunately I cant use his code as my display driver is different, but it makes me think this could be the culprit, as when running his code the ESP32 doesnt crash.

Do you think its possible that f is causing the issue?

Try printing out the value of the computed index in the last line and comparing it to the amount of memory available at pointer 'p'.

Impossible to tell. Just because an error happens in a specific line of code, that line of code may not be the cause of the error at all. If “p[y * cols + x] = f” causes an error, then “p” would be less than “cols*rows” in size.

I would suggest you to move “float dest_2d[INTERPOLATED_ROWS * INTERPOLATED_COLS]” from “loop()” to right under the defines for “INTERPOLATED_*” and see if that changes anything.

Yep. that was the problem. Its working now, thankyou very much

mickymik:
Yep. that was the problem. Its working now, thankyou very much

What was the problem? Thanks for the karma btw.

Danois90:
What was the problem? Thanks for the karma btw.

Moving the float like you said fixed the problem

Danois90:
Impossible to tell. Just because an error happens in a specific line of code, that line of code may not be the cause of the error at all. If "p[y * cols + x] = f" causes an error, then "p" would be less than "cols*rows" in size.

I would suggest you to move "float dest_2d[INTERPOLATED_ROWS * INTERPOLATED_COLS]" from "loop()" to right under the defines for "INTERPOLATED_*" and see if that changes anything.

Once I did that everything worked as expected.