How do you update an array within a struct array using a function

Using and esp32-s3 16mb
I have a struct array where one of the members is an unsigned short array allocating 4096 bytes that will hold a 64x64 RBG565 sprite. Currently, I can read the sprite off of the SD-Card and display it on the screen. I'm trying to speed up the display refresh times and avoid constantly reading off of the SD-Card every time I want to display each sprite. I can get it to work if I make the struct array a global variable. But, I'm trying to code "properly" and pass the struct to a function to load the sprite into memory. I've tried passing the struct array with and without pointer references, passing just a pointer to the member to be updated and other various combinations and am still unable to figure out exactly what I'm missing. Many times the code will compile with no errors, yet causes the arduino to continually reset upon reaching the Init_Sprite_File function.
Here is the working code using global variables:

// #########################  Includes  ##############################
#include <TFT_eSPI.h>
#include <SPI.h>                 // Library for SPI functions
#include <SD.h>                  // Library for SD Card

// ########################  Defines  ################################
// LCD Display control signals using Hardware SPI 
#define TFT_S1_CS_PIN        14         // Screen 1 Chip Select
#define TFT_S2_CS_PIN         8         // Screen 2 Chip Select

// These are defined in the TFT_eSPI User_Setup.h file
// #define TFT_MISO      13         // Master In - Slave Out
// #define TFT_MOSI      11         // Master Out - Slave In
// #define TFT_CLK       12         // Clock (40Mhz)
// #define TFT_DC        10         // Data/Command
// #define TFT_RST     -1         // Reset, connected to Arduino reset pin
// #define TFT_LED       -1         // Not using LED control

// SD Card control signals using Software SPI
#define SDC_CS_PIN        39         // SD Card Chip Select
#define SDC_CLK_PIN       36         // SD Card Clock
#define SDC_MOSI_PIN      35         // SD Card Master Out/Slave In
#define SDC_MISO_PIN      37         // SD Card Master In/Slave Out

#define BG_COLOR       0xF9D7        // Background Color as Transparent
#define DOUBLEBUFFER_X    160        // Double Buffer x = screen x
#define DOUBLEBUFFER_Y    128        // Double Buffer y = screen y
#define BACKGROUND_X      160        // Background x
#define BACKGROUND_Y      128        // Background y
#define NUM_BACKGROUNDS     4        // Number of background images
#define SPRITE_X           64        // Sprite x size
#define SPRITE_Y           64        // Sprite y size
#define NUM_SPRITES        12        // Nunber of sprites

#define MAX_PLAYERS         4

// ########################  Structures  #############################
struct st_Person {
  
  unsigned short sprite[4096];  // Actual Sprite graphic pre-loaded
  unsigned char sector, sector_x, sector_y;
  
};

// ########################  Function Prototypes  #####################
void Initialize_Screens(void);
void Put_Graphics(uint16_t x_loc, uint16_t y_loc, uint16_t x_size, uint16_t y_size, char *image_file);
void Init_Buffer(int res_x, int res_y, unsigned short color, unsigned short buffer2init[]);
void Plot_Pixel(unsigned int x, unsigned int y, unsigned short color);
void Push_Buffer(unsigned short x_res, unsigned short y_res, unsigned short buffer_to_show[], unsigned short screen_cs);
void Activate_Screen(unsigned short screen_cs);
void De_Activate_Screen(unsigned short screen_cs);
void Init_Sprite_File(unsigned short ref_number, char *file_to_open);


// ########################  Global Variables #########################
TFT_eSPI tft = TFT_eSPI();                   // Create instance for TFT Screen(s) -> 1 instance for 2 screens w/ soft /CS
SPIClass SDsoftSPI;                          // Create instance for Software SPI
unsigned short double_buffer[20480];         // Behind the scenes buffer for flicker free animation 160x128 screen

byte screen_width, screen_height;            // Self explanatory
st_Person person[MAX_PLAYERS];               // Struct that holds person information 


// ########################  Setup  ###################################
void setup() {  
    
  Serial.begin(115200);                      // Turn on Serial Monitor for debugging
  Serial.println();

  Initialize_Screens();                      // Setup screen for use and soft /CS
  screen_width = tft.getViewportWidth();         // Get the screen width and height
  screen_height = tft.getViewportHeight();
  
  Serial.print("Screen Resolution : ");          // Verify to serial monitor the screen resolution
  Serial.print(screen_width);
  Serial.print("x");
  Serial.println(screen_height);

  Serial.print("Initalizing SCD... ");                                           
  SDsoftSPI.begin(SDC_CLK_PIN, SDC_MISO_PIN, SDC_MOSI_PIN, SDC_CS_PIN);          // Initialized the secondary SPI Bus, used for slow devices (SD Card...)

  if (!SD.begin(SDC_CS_PIN, SDsoftSPI))                                          // Initialize the SD Card and test if working
    Serial.println("Initialization Failed");       
  else 
    Serial.println("done");    

}

  

// #############################  Main  ############################
void loop() {
    
  int bg_index, sprite_index, pin;
  char image_file[13];                         // Holds image filename  
  
  unsigned int dummy;
  unsigned int x, y;
 
  bg_index=0;
  sprite_index=0;
  pin = TFT_S1_CS_PIN;
  
  strcpy(image_file, "/SpWiYl.raw");
  Init_Sprite_File(0, image_file);   
  dummy=0;
  for(y=0; y<SPRITE_Y; y++) {
   for(x=0; x<SPRITE_X; x++) {
     Plot_Pixel(x, y, person[0].sprite[dummy]); 
     dummy++;
   }
  }
  Push_Buffer(DOUBLEBUFFER_X, DOUBLEBUFFER_Y, double_buffer, TFT_S2_CS_PIN);
  
  
  while(1); 
  
}

// #############################################  FUNCTIONS  #################################################

// ################################################################
// # Function: Initialize_Screens
// # Args: None
// # Description: Initialize the 2 screens used in this program
// ################################################################
void Initialize_Screens(void) {

  pinMode(TFT_S1_CS_PIN, OUTPUT);               // Screen 1 chip select set to output
  pinMode(TFT_S2_CS_PIN, OUTPUT);               // Screen 2 chip select set to output
  digitalWrite(TFT_S1_CS_PIN, HIGH);            // Initial /CS setting
  digitalWrite(TFT_S2_CS_PIN, HIGH);            // Initial /CS setting

  digitalWrite(TFT_S1_CS_PIN, LOW);             // Select Screen 1
  digitalWrite(TFT_S2_CS_PIN, LOW);             // Select Screen 2

  Serial.print("Initalizing Both LCDs... ");  
  tft.init();                                   // Initialize Screen(s)
    
  digitalWrite(TFT_S2_CS_PIN, HIGH);            // De-Select Screen 2
  tft.setRotation(1);                           // Set rotation of screen 1 
  tft.fillScreen(TFT_BLACK);
  digitalWrite(TFT_S1_CS_PIN, HIGH);            // De-Select Screen 1
  digitalWrite(TFT_S2_CS_PIN, LOW);             // Select Screen 2
  tft.setRotation(1);       
  tft.fillScreen(TFT_BLACK);                    // Set rotation of screen 2
  digitalWrite(TFT_S2_CS_PIN, HIGH);            // De-Select Screen 2
  Serial.println("done");

}

// ################################################################
// # Function: Put_Graphics
// # Args: uint16_t x_loc, uint16_t y_loc, uint16_t x_size, uint16_t y_size, char *file_to_open
// # Description: Reads a file off of the SD Card and places it on 
// #              the double buffer at location x_loc, y_loc
// #              x_size, y_size is the size of the image file
// ################################################################
void Put_Graphics(uint16_t x_loc, uint16_t y_loc, uint16_t x_size, uint16_t y_size, char *file_to_open) {

  File image_file;      
  uint16_t total_size;  
  uint16_t dummy, x, y;
  uint8_t pixello, pixelhi;
  uint16_t pixel_built;  
  
  if (!(image_file = SD.open(file_to_open, FILE_READ))) { // Check if file exists
    Serial.println(F("-> File not found"));              // If not, Error message
    return;                                              // and break
  }
      
  x=0;                                                   // Reset x, y coords
  y=0;    
  
  while (image_file.available())  {                      // Parse through entire image file
    pixelhi = image_file.read();                         // Pixel color is kept in 2 bytes
    pixello = image_file.read();
              
    if(!((pixello+(pixelhi<<8)) == BG_COLOR))            // If not the Background color
      Plot_Pixel(x_loc+x, y_loc+y, (pixello+(pixelhi<<8)));   // Put it on the screen      

    x++;                                                 // Next pixel
    if(x>=x_size) {                                      // If width of image is reached
      x=0;                                               // go to next line
      y++;
    }
  }

  image_file.close();                                    // Close the file
}


// ################################################################
// # Function: Init_Buffer
// # Args: int res_x, res_y - Dimensions of the buffer, 
// #       160x128 screen
// # Description: Fills the buffer with given color
// #              For some reason library is inverting color bytes
// #              flip bytes before rendering to buffer

// ################################################################
void Init_Buffer(int res_x, int res_y, unsigned short color, unsigned short buffer2init[]) {
  int dummy;
  unsigned short shiftbyte = 0x0000;                // Needed for byte shifting, clear shiftbyte

  shiftbyte = ((color & 0x00FF) << 8);              // Mask off upper bytes and shift lower bytes left 8
  color = ((color >> 8 ) | shiftbyte);              // Shift upper bytes right 8 and OR shifted lower bytes
  
  for(dummy=0; dummy<(res_x * res_y); dummy++) {  
    buffer2init[dummy]=color;    
  }
}


// ################################################################
// # Function: Plot_Pixel
// # Args: int x, int y, int color
// # Description: x, y are coords to plot the pixel on a 160x128 screen
// #              color is color of pixel using RGB 565 color scheme
// #              For some reason library is inverting color bytes
// #              flip bytes before rendering to buffer
// #              Screen resolution is 160x128
// #              double_buffer[160*y + x]=color;
// #              160*y = y*128 + y*32 ===  Optimized, bit shifting & adding is much faster than muliplying  
// #                      y<<7  +  y<<5
// ################################################################
void Plot_Pixel(unsigned int x, unsigned int y, unsigned short color) {
  unsigned short shiftbyte = 0x0000;                 // Needed for byte shifting, clear shiftbyte
  shiftbyte = ((color & 0x00FF) << 8);               // Mask off upper bytes and shift lower bytes left 8
  color = ((color >> 8 ) | shiftbyte);               // Shift upper bytes right 8 and OR shifted lower bytes
 
  double_buffer[((y<<7) + (y<<5)) + x] = color;
}


// ################################################################
// # Function: Push_Image
// # Args: unsigned short x_res, unsigned short y_res, unsigned short buffer[], unsigned short screen
// # Description: This function is a helper to tft.pushImage to accomodate more that 1 screen.
// #              x_res, y_res - resolution of the screen being used.
// #              buffer - double buffer to push
// #              screen_cs - chip select for screen to activate
// ################################################################
void Push_Buffer(unsigned short x_res, unsigned short y_res, unsigned short *buffer, unsigned short screen_cs) {
    
  digitalWrite(screen_cs, LOW);               // /CS is active LOW   
  tft.pushImage(0, 0, x_res, y_res, buffer);
  digitalWrite(screen_cs, HIGH);              // /CS is active LOW

}


// ################################################################
// ################################################################
void Init_Sprite_File(unsigned short ref_number, char *file_to_open) {


  File image_file;      
  uint16_t total_size;  
  uint16_t dummy, x, y;
  uint8_t pixello, pixelhi;
  uint16_t pixel_built;  
  
  if (!(image_file = SD.open(file_to_open, FILE_READ))) { // Check if file exists
    Serial.println(F("-> File not found"));              // If not, Error message
    return;                                              // and break
  }
  else {
    Serial.println(F("File w/in Function open"));
  }   
  x=0;                                                   // Reset x, y coords
  
  while (image_file.available())  {                      // Parse through entire image file
    pixelhi = image_file.read();                         // Pixel color is kept in 2 bytes
    pixello = image_file.read();
    
    person[ref_number].sprite[x] = (pixello+(pixelhi<<8));

    Serial.printf("%04x\n", person[0].sprite[x]);
    x++;                                                 // Next pixel
  }
  image_file.close();                                    // Close the file
  Serial.printf("X value in init function %d\n", x);
  
}

And... the not working code trying to pass the struct to a function:

// #########################  Includes  ##############################
#include <TFT_eSPI.h>
#include <SPI.h>                 // Library for SPI functions
#include <SD.h>                  // Library for SD Card

// ########################  Defines  ################################
// LCD Display control signals using Hardware SPI 
#define TFT_S1_CS_PIN        14         // Screen 1 Chip Select
#define TFT_S2_CS_PIN         8         // Screen 2 Chip Select

// These are defined in the TFT_eSPI User_Setup.h file
// #define TFT_MISO      13         // Master In - Slave Out
// #define TFT_MOSI      11         // Master Out - Slave In
// #define TFT_CLK       12         // Clock (40Mhz)
// #define TFT_DC        10         // Data/Command
// #define TFT_RST     -1         // Reset, connected to Arduino reset pin
// #define TFT_LED       -1         // Not using LED control

// SD Card control signals using Software SPI
#define SDC_CS_PIN        39         // SD Card Chip Select
#define SDC_CLK_PIN       36         // SD Card Clock
#define SDC_MOSI_PIN      35         // SD Card Master Out/Slave In
#define SDC_MISO_PIN      37         // SD Card Master In/Slave Out

#define BG_COLOR       0xF9D7        // Background Color as Transparent
#define DOUBLEBUFFER_X    160        // Double Buffer x = screen x
#define DOUBLEBUFFER_Y    128        // Double Buffer y = screen y
#define BACKGROUND_X      160        // Background x
#define BACKGROUND_Y      128        // Background y
#define NUM_BACKGROUNDS     4        // Number of background images
#define SPRITE_X           64        // Sprite x size
#define SPRITE_Y           64        // Sprite y size
#define NUM_SPRITES        12        // Nunber of sprites

#define MAX_PEOPLE         4

// ########################  Structures  #############################
struct st_Person {
  
  unsigned short sprite[4096];       // Actual Sprite graphic pre-loaded
  unsigned char sector, sector_x, sector_y;
  
};

// ########################  Function Prototypes  #####################
void Initialize_Screens(void);
void Put_Graphics(uint16_t x_loc, uint16_t y_loc, uint16_t x_size, uint16_t y_size, char *image_file);
void Init_Buffer(int res_x, int res_y, unsigned short color, unsigned short buffer2init[]);
void Plot_Pixel(unsigned int x, unsigned int y, unsigned short color);
void Push_Buffer(unsigned short x_res, unsigned short y_res, unsigned short buffer_to_show[], unsigned short screen_cs);
void Activate_Screen(unsigned short screen_cs);
void De_Activate_Screen(unsigned short screen_cs);

void Init_Sprite_File(struct st_Person st_passed[], unsigned short ref_number, char *file_to_open);

// ########################  Global Variables #########################
TFT_eSPI tft = TFT_eSPI();                   // Create instance for TFT Screen(s) -> 1 instance for 2 screens w/ soft /CS
SPIClass SDsoftSPI;                          // Create instance for Software SPI
unsigned short double_buffer[20480];         // Behind the scenes buffer for flicker free animation 160x128 screen

byte screen_width, screen_height;            // Self explanatory

// ########################  Setup  ###################################
void setup() {  
    
  Serial.begin(115200);                      // Turn on Serial Monitor for debugging
  Serial.println();

  Initialize_Screens();                      // Setup screen for use and soft /CS
  screen_width = tft.getViewportWidth();         // Get the screen width and height
  screen_height = tft.getViewportHeight();
  
  Serial.print("Screen Resolution : ");          // Verify to serial monitor the screen resolution
  Serial.print(screen_width);
  Serial.print("x");
  Serial.println(screen_height);

  Serial.print("Initalizing SCD... ");                                           
  SDsoftSPI.begin(SDC_CLK_PIN, SDC_MISO_PIN, SDC_MOSI_PIN, SDC_CS_PIN);          // Initialized the secondary SPI Bus, used for slow devices (SD Card...)

  if (!SD.begin(SDC_CS_PIN, SDsoftSPI))                                          // Initialize the SD Card and test if working
    Serial.println("Initialization Failed");       
  else 
    Serial.println("done");    

} 

// #############################  Main  ############################
void loop() {
  
  st_Person person[MAX_PEOPLE];     // Struct that holds person information  
  int bg_index, sprite_index, pin;
  char image_file[13];                         // Holds image filename  
  
  unsigned int dummy;
  unsigned int x, y;
 
  bg_index=0;
  sprite_index=0;
  pin = TFT_S1_CS_PIN;
  
  strcpy(image_file, "/SpWiYl.raw");  
  Init_Sprite_File(person, 0, image_file);
  dummy=0;
  for(y=0; y<SPRITE_Y; y++) {
   for(x=0; x<SPRITE_X; x++) {
     Plot_Pixel(x, y, person[0].sprite[dummy]); 
     dummy++;
   }
  }
  Push_Buffer(DOUBLEBUFFER_X, DOUBLEBUFFER_Y, double_buffer, TFT_S2_CS_PIN);  
  
  while(1); 
  
}

// #############################################  FUNCTIONS  #################################################

// ################################################################
// # Function: Initialize_Screens
// # Args: None
// # Description: Initialize the 2 screens used in this program
// ################################################################
void Initialize_Screens(void) {

  pinMode(TFT_S1_CS_PIN, OUTPUT);               // Screen 1 chip select set to output
  pinMode(TFT_S2_CS_PIN, OUTPUT);               // Screen 2 chip select set to output
  digitalWrite(TFT_S1_CS_PIN, HIGH);            // Initial /CS setting
  digitalWrite(TFT_S2_CS_PIN, HIGH);            // Initial /CS setting

  digitalWrite(TFT_S1_CS_PIN, LOW);             // Select Screen 1
  digitalWrite(TFT_S2_CS_PIN, LOW);             // Select Screen 2

  Serial.print("Initalizing Both LCDs... ");  
  tft.init();                                   // Initialize Screen(s)
    
  digitalWrite(TFT_S2_CS_PIN, HIGH);            // De-Select Screen 2
  tft.setRotation(1);                           // Set rotation of screen 1 
  tft.fillScreen(TFT_BLACK);
  digitalWrite(TFT_S1_CS_PIN, HIGH);            // De-Select Screen 1
  digitalWrite(TFT_S2_CS_PIN, LOW);             // Select Screen 2
  tft.setRotation(1);       
  tft.fillScreen(TFT_BLACK);                    // Set rotation of screen 2
  digitalWrite(TFT_S2_CS_PIN, HIGH);            // De-Select Screen 2
  Serial.println("done");

}

// ################################################################
// # Function: Put_Graphics
// # Args: uint16_t x_loc, uint16_t y_loc, uint16_t x_size, uint16_t y_size, char *file_to_open
// # Description: Reads a file off of the SD Card and places it on 
// #              the double buffer at location x_loc, y_loc
// #              x_size, y_size is the size of the image file
// ################################################################
void Put_Graphics(uint16_t x_loc, uint16_t y_loc, uint16_t x_size, uint16_t y_size, char *file_to_open) {

  File image_file;      
  uint16_t total_size;  
  uint16_t dummy, x, y;
  uint8_t pixello, pixelhi;
  uint16_t pixel_built;  
  
  if (!(image_file = SD.open(file_to_open, FILE_READ))) { // Check if file exists
    Serial.println(F("-> File not found"));              // If not, Error message
    return;                                              // and break
  }
      
  x=0;                                                   // Reset x, y coords
  y=0;    
  
  while (image_file.available())  {                      // Parse through entire image file
    pixelhi = image_file.read();                         // Pixel color is kept in 2 bytes
    pixello = image_file.read();
              
    if(!((pixello+(pixelhi<<8)) == BG_COLOR))            // If not the Background color
      Plot_Pixel(x_loc+x, y_loc+y, (pixello+(pixelhi<<8)));   // Put it on the screen      

    x++;                                                 // Next pixel
    if(x>=x_size) {                                      // If width of image is reached
      x=0;                                               // go to next line
      y++;
    }
  }

  image_file.close();                                    // Close the file
}


// ################################################################
// # Function: Init_Buffer
// # Args: int res_x, res_y - Dimensions of the buffer, 
// #       160x128 screen
// # Description: Fills the buffer with given color
// #              For some reason library is inverting color bytes
// #              flip bytes before rendering to buffer

// ################################################################
void Init_Buffer(int res_x, int res_y, unsigned short color, unsigned short buffer2init[]) {
  int dummy;
  unsigned short shiftbyte = 0x0000;                // Needed for byte shifting, clear shiftbyte

  shiftbyte = ((color & 0x00FF) << 8);              // Mask off upper bytes and shift lower bytes left 8
  color = ((color >> 8 ) | shiftbyte);              // Shift upper bytes right 8 and OR shifted lower bytes
  
  for(dummy=0; dummy<(res_x * res_y); dummy++) {  
    buffer2init[dummy]=color;    
  }
}


// ################################################################
// # Function: Plot_Pixel
// # Args: int x, int y, int color
// # Description: x, y are coords to plot the pixel on a 160x128 screen
// #              color is color of pixel using RGB 565 color scheme
// #              For some reason library is inverting color bytes
// #              flip bytes before rendering to buffer
// #              Screen resolution is 160x128
// #              double_buffer[160*y + x]=color;
// #              160*y = y*128 + y*32 ===  Optimized, bit shifting & adding is much faster than muliplying  
// #                      y<<7  +  y<<5
// ################################################################
void Plot_Pixel(unsigned int x, unsigned int y, unsigned short color) {
  unsigned short shiftbyte = 0x0000;                 // Needed for byte shifting, clear shiftbyte
  shiftbyte = ((color & 0x00FF) << 8);               // Mask off upper bytes and shift lower bytes left 8
  color = ((color >> 8 ) | shiftbyte);               // Shift upper bytes right 8 and OR shifted lower bytes
 
  double_buffer[((y<<7) + (y<<5)) + x] = color;
}


// ################################################################
// # Function: Push_Image
// # Args: unsigned short x_res, unsigned short y_res, unsigned short buffer[], unsigned short screen
// # Description: This function is a helper to tft.pushImage to accomodate more that 1 screen.
// #              x_res, y_res - resolution of the screen being used.
// #              buffer - double buffer to push
// #              screen_cs - chip select for screen to activate
// ################################################################
void Push_Buffer(unsigned short x_res, unsigned short y_res, unsigned short *buffer, unsigned short screen_cs) {
    
  digitalWrite(screen_cs, LOW);               // /CS is active LOW   
  tft.pushImage(0, 0, x_res, y_res, buffer);
  digitalWrite(screen_cs, HIGH);              // /CS is active LOW

}

// ################################################################
// ################################################################
void Init_Sprite_File(struct st_Person st_passed[], unsigned short ref_number, char *file_to_open) {

  File image_file;      
  uint16_t total_size;  
  uint16_t dummy, x, y;
  uint8_t pixello, pixelhi;
  uint16_t pixel_built;  
  
  if (!(image_file = SD.open(file_to_open, FILE_READ))) { // Check if file exists
    Serial.println(F("-> File not found"));              // If not, Error message
    return;                                              // and break
  }
  else {
    Serial.println(F("File w/in Function open"));
  }   
  x=0;                                                   
  
  while (image_file.available())  {                      // Parse through entire image file
    pixelhi = image_file.read();                         // Pixel color is kept in 2 bytes
    pixello = image_file.read();
    
    st_passed[ref_number].sprite[x] = (pixello+(pixelhi<<8));

    Serial.printf("%04x\n", st_passed[ref_number].sprite[x]);
    x++;                                                 // Next pixel
  }
  image_file.close();                                    // Close the file
  Serial.printf("X value in init function %d\n", x);

}

There are some #defines and variables that are not used, I realize that. I isolated the code from my larger sketch, to try to just get this to work w/o other problems. Thank you in advance.

If the code works when working with a global but fails when accessed by a pointer, then the pointer is incorrect.
I applaud your desire to code 'properly', but with these small programs, the danger of globals is much smaller than the hundreds of modules of the last century. Leave it as global. Later, you can try to change it, and I find that often when you leave a problem and come back to it, the solution is quickly found.

Passing an array decays to passing a pointer to the first element, so you need to pass the number of éléments as well to the function.

Here is an example, typed here (untested - mind typos)


constexpr size_t dataSize = 4;

struct Sensor {
  int id;
  float data[dataSize];
};

void printSensorData(Sensor sensors[], size_t count) {
  for (size_t i = 0; i < count; i++) {
    Serial.print("Sensor ID: ");
    Serial.print(sensors[i].id);
    Serial.print(", Data: ");
    for (size_t j = 0; j < dataSize; j++) {
      Serial.print(sensors[i].data[j], 4);
      Serial.print(" ");
    }
    Serial.println();
  }
}

void setup() {
  Serial.begin(115200);
  Sensor sensorArray[] = {
    {1, {1.23, 4.56, 7.89, 0.12}},
    {2, {2.34, 5.67, 8.90, 1.23}},
    {3, {3.45, 6.78, 9.01, 2.34}}
  };
  size_t numSensors = sizeof(sensorArray) / sizeof(sensorArray[0]);
  printSensorData(sensorArray, numSensors);
}

void loop() {}


Mind the stack size if you don’t use global variables

I searched for "elements in french" to confirm my suspicions, but the first link is actually a Japanese website.

Confirmed in a private/incognito window. Weird.

Thank you for the replies,

@sonofcy, leaving it global was my first thought too. If it works... but I was hoping to port this function to a couple of other sketches that I have in mind and would've like to just have it in a .h library and not have to custom alter the code.

@J-M-L, if I'm only updating/accessing 1 of the struct array elements, why do I need to pass the size of everything? I'm passing "ref_number" as the particular struct I want to update, then access that 1 by the line: st_passed[ref_number].sprite[x] = (pixello+(pixelhi<<8));
You also say mind the stack size but, if I'm only passing a pointer and not the whole value of the struct, I didn't think the stack size would become a problem.

It's been a while since I've written a program of this magnitude and I'm trying to refresh myself on pointers. I'm really trying to understand this concept and would like to get it right. I do understand, for the most part, pointers, addresses, de-referencing, etc. for single elements, but an array w/in a struct array just has me confused. I've tried to pass a pointer to the array and I've even tried to pass a pointer the the actual element I'm trying to update, but I think I'm just missing something simple. Once working, if I get to that point, my next task was to make it scalable and dynamically allocate memory based on user input. I do have a #define MAX_PEOPLE right now, but I would like to dynamically change that. Could be 1, 4 or 10 and then I would need the function to work with struct pointers.
If all else fails, I will just continue to use the global variable. It certainly makes it a lot easier to code, just less portable.

Could it be that the 32780 bytes needed for your array is too large for the stack (which is where the array is created)? It would likely be more practical to allocate memory for the array on the heap, the way you have it in your sketch the array ceases to exist if you leave the loop() function.

Ahh, ok, that makes more sense. My confusion on the stack size. So, by having the struct in the main(), that causes the memory allocation to be in the stack, where as globally it's allocated in the heap. That makes sense. So that raises the question, since malloc() allocates memory from the heap, as I understand it, and I write my code to dynamically allocate memory based on user input, "in theory", it should actually work as I originally had intended?

So I would have to define my struct as this:

struct st_Person {
  
  unsigned short sprite[4096];       // Actual Sprite graphic pre-loaded
  unsigned char sector, sector_x, sector_y;
  struct st_Person *next_person;
  
};

And in the main() create a struct st_Person pointer and malloc() sizeof struct and dynamically allocate the memory.

No and no. All non-static local variable are stored on the stack. Global variables are stored in separate area, neither on the stack or heap.

@gfvalvo. Learn something new everyday. Thank you.

64x64 is 4096 elements. If each is a 16-bit RGB565, then that's 8192 bytes.

Doing a diff between your two sketches

53d52
< void Init_Sprite_File(unsigned short ref_number, char *file_to_open);
54a54
> void Init_Sprite_File(struct st_Person st_passed[], unsigned short ref_number, char *file_to_open);

Before, you're passing an index of the global array of struct. What you should do after is simply pass a pointer to the struct, so it works whether it is in an array or not

void Init_Sprite_File(struct st_Person *person, char *file_to_open);

Then in the function, instead of

Now that the argument is a pointer, use -> instead of .

    person->sprite[x] = (pixello+(pixelhi<<8));

Then the call (after allocating on the heap to avoid blowing up the stack) could be

  unsigned short ref_number = 0;
  Init_Sprite_File(&person[ref_number], image_file);

using the & operator: "the address of the given element of the array". For the first element, number zero, that does happen to be the same as

  Init_Sprite_File(person, image_file);

But that's not generally useful. However, to give you something to think about, you can also do

  unsigned short ref_number = 0;
  Init_Sprite_File(person + ref_number, image_file);

which is pointer arithmetic. Be not afraid. Frankly, I think it reads better.

No need for a linked list. You can dynamically allocate an array of structs. Or keep an array of pointers to dynamically allocated structs.

You can do that while still using global variables. In your library module, use extern to declare: "There will be this thing with this name and type, but not defined here. (It can even be a type declared in that library; the library "delegates" the responsibility for creating an instance.) Compile the code with that assumption, and let the linker connect the dots." With an array, you'll likely have the number of elements as a second externed value.

Keep in mind: similar to the size limit for things allocated on the stack, there is a (usually) larger limit for global variables. You don't necessarily get any good indication when it is exceeded. Things can just stop working.

The number of entries / items in the array.

You can check the dictionary also

mean one of the parts of a compound or complex whole.
ELEMENT applies to any such part and often connotes irreducible simplicity.


This was to traverse the array. You don’t need to pass the size if you only want to access one element and you know it’s within the bounds.

Don’t second guess the address just use the array’s index.

You could also just pass the struct by reference to the function rather than pointer (not by value as you would make a costly copy)

Here is the example with more functions - hopefully self explanatory

constexpr size_t dataSize = 4;

struct Sensor {
  int id;
  float data[dataSize];
};

void printAllSensorData(Sensor sensors[], size_t count) {
  for (size_t i = 0; i < count; i++) {
    Serial.print("Sensor ID: ");
    Serial.print(sensors[i].id);
    Serial.print(", Data: ");
    for (size_t j = 0; j < dataSize; j++) {
      Serial.print(sensors[i].data[j], 4);
      Serial.print(" ");
    }
    Serial.println();
  }
}

void printSensorByReference(Sensor& sensor) {
  Serial.print("By reference - ID: ");
  Serial.print(sensor.id);
  Serial.print(", First data: ");
  Serial.println(sensor.data[0], 4);
}

void printSensorByPointer(Sensor* sensor) {
  Serial.print("By pointer - ID: ");
  Serial.print(sensor->id);
  Serial.print(", First data: ");
  Serial.println(sensor->data[0], 4);
}

void printSensorByCopy(Sensor sensor) {
  Serial.print("By copy - ID: ");
  Serial.print(sensor.id);
  Serial.print(", First data: ");
  Serial.println(sensor.data[0], 4);
}

void printSensorFromArray(Sensor* sensors, size_t index) {
  Serial.print("From array pointer - ID: ");
  Serial.print(sensors[index].id);
  Serial.print(", First data: ");
  Serial.println(sensors[index].data[0], 4);
}

void setup() {
  Serial.begin(115200);
  Sensor sensorArray[] = {
    {1, {1.23, 4.56, 7.89, 0.12}},
    {2, {2.34, 5.67, 8.90, 1.23}},
    {3, {3.45, 6.78, 9.01, 2.34}}
  };
  size_t numSensors = sizeof sensorArray / sizeof *sensorArray;
  printAllSensorData(sensorArray, numSensors);

  printSensorByReference(sensorArray[0]);
  printSensorByPointer(&sensorArray[1]);
  printSensorByCopy(sensorArray[2]);
  printSensorFromArray(sensorArray, 1);
}

void loop() {}

(Typed here mind typos)

For the stack size I was indeed referring to the definition in the loop as a non static local variable - thus on the stack.

Okay, so a few lessons learned and if memory is going to be an issue in the heap, stack or otherwise... after reading a few articles, I may have to re-think my approach. Since this is an esp32-s3 w/ 16Mb of flash memory, as I understand, and correct me if I'm wrong, which I know and hope you do...
My overall goal is to not read the images off of the SD Card all the time due to its lack of speed. I wrote a program a couple of years ago and those images where in .h files and set as:
const unsigned short racecar_blue[832] PROGMEM={...
From what I have read, and thank you for sending me down this road, PROGMEM is no longer needed w/ the esp32. So, I believe for now, I can have all the images in .h files set as
const unsigned short racecar_blue[832]={... and this would load them into the flash memory. The only downside right now, is that I would lose the ability to easily change the images w/o reloading the script onto the arduino. Unless, there is a way to read the SD Card images and pre-load them into flash memory before the loop() function. That way they would be read once per re-boot instead of everytime they needed to be accessed and they would be in the faster flash memory for easy access. But then, this also has problems of its own by the write limitations of the flash memory. Maybe I'll lose the ability to change the images for now and if the const definitions work, I'll modify the code later to add the change image ability.
I guess, as a bonus?, I wouldn't need the SD Card anymore and that would save some code and compilcation.

have a look at partitioning some flash as spiffs or Littlefs file space
where you can alter files on it as you see fit via wifi server or ota , a serial i/f.

+1 on the idea of dropping the SD card if what you want is to have the images in flash anyway.

Get them there initially as part of the code download and use a web server approach to manage those images, using a partition of the flash memory as file system.

This would work well if your image drawing API knows how to handle data from the file system.

If you have access to a recent version of the Standard Template Library, you may want to have a look at std::span. This container can be used to pass both the content as well as the size of an array to a function.

If this is not the case, you may want to have a look at the array-helpers library, in which a limited version of std::span is implemented.

Example (modified from the one given by @J-M-L):

#include <arrayHelpers.h>

struct Sensor {
  int id;
  float data[4];
};


void modifySensorData(Span<Sensor> sensors) {
  for (Sensor& sensor: sensors) {
    for (float& data: sensor.data) {
      data += 0.1;
    }
  }
}

void printSensorData(Span<Sensor> const sensors) {
  for (Sensor const& sensor: sensors) {
    Serial.print("Sensor ID: ");
    Serial.print(sensor.id);
    Serial.print(", Data: ");
    for (float const& data: sensor.data) {
      Serial.print(data, 4);
      Serial.print(" ");
    }
    Serial.println();
  }
  Serial.println();
}


void setup() {
  Serial.begin(115200);

  Sensor sensorArray[] {
    {1, {1.23, 4.56, 7.89, 0.12}},
    {2, {2.34, 5.67, 8.90, 1.23}},
    {3, {3.45, 6.78, 9.01, 2.34}}
  };

  printSensorData(sensorArray);
  modifySensorData(sensorArray);
  printSensorData(sensorArray);
}

void loop() {}

Output:

Sensor ID: 1, Data: 1.2300 4.5600 7.8900 0.1200 
Sensor ID: 2, Data: 2.3400 5.6700 8.9000 1.2300 
Sensor ID: 3, Data: 3.4500 6.7800 9.0100 2.3400 

Sensor ID: 1, Data: 1.3300 4.6600 7.9900 0.2200 
Sensor ID: 2, Data: 2.4400 5.7700 9.0000 1.3300 
Sensor ID: 3, Data: 3.5500 6.8800 9.1100 2.4400 

Wouldn't this: (pixelhi<<8)just rotate out all 8 bits to the left and leave zero?
So wouldn't the result always evaluate to the equivalent of pixelhi + 0?
Do pixello and at least pixelhi need to be defined as uint16_t?

I use that all the time. It is my understanding that expressions like the above default to 16 or 32 bit integer arithmetic, depending on the processor type.

But if you want to make it perfectly clear what result is expected, use something like this in your own code:
(((uint16_t)pixelhi)<<8)

Hmm, I guess if you have an MCU that has 16-bit or 32-bit registers, then you do have somewhere to shift those bits. Might that come unstuck on an 8-bit AVR though?

OP is using an ESP32, so I guess it would work there. Interesting to note.
Thank you for explaining.

@BitSeeker I learned something interesting in testing my theory on an 8 bit Arduino Uno R3.

The following code works as expected and prints 32640.

For a surprise that I will leave you to ponder, remove the keyword "unsigned" from the declaration/initialization of "y". Hint: it does validate what I proposed in reply #17.

void printit(int x){
  Serial.print(" x = "); 
  Serial.println(x);
}
void setup() {

Serial.begin(115200);
while(!Serial) delay(1); //wait for connection
unsigned char y = 255;
printit( y<<7);
}

void loop() {}

@BitSeeker, Good catch, I believe you are correct, although it does work as expected like @jremington eluded. When I originally wrote the function it had another variable called uint16_t pixel_built; and I copied the contents from pixelhi into it and then shifted. After the logic worked, I optimized the code and I guess, since it worked, I missed the declaration. I have changed my declaration to uint16_t and my function still seems to work as expected. Thank you.