Passing an array to functions and classes questions.

Hi all!

I have an array defined in a .h header file:

// We need this header file to use FLASH as storage with PROGMEM directive:
#include <pgmspace.h>

// Icon width and height
const uint16_t imageWidth = 64;
const uint16_t imageHeight = 32;

const unsigned short imageButtonSample[2048] PROGMEM={

That defines a graphic image for display on an LCD touchscreen and I am having problems passing that array to a class I wrote.

Here's my .h file:

class ImageButton
{
  public:
    ImageButton(int x, int y, int w, int h, const unsigned short *image);

    void drawImageButton();
    void eraseImageButton();
    
  private:
    int _xLoc;                    // x location of button
    int _yLoc;                    // y location of button
    int _xSize;                   // width of button
    int _ySize;                   // height of button
    const unsigned short *_image[2048];  // image for button
    // need to do this without specifying size of image
      
};  // end - class ImageButton

and my .cpp file:

ImageButton::ImageButton(int x, int y, int w, int h, const unsigned short *image)
{
  _xLoc = x;
  _yLoc = y;
  _xSize = w;
  _ySize = h;
  *_image = image;
}

void ImageButton::drawImageButton()
{ 
  tft.setSwapBytes(true);     // Swap the colour byte order when rendering
  tft.pushImage((_xLoc - (_xSize/2)), (_yLoc - (_ySize/2)), _xSize, _ySize, *_image);
}

void ImageButton::eraseImageButton()
{
  tft.fillRect((_xLoc - (_xSize/2)), (_yLoc - (_ySize/2)), _xSize, _ySize, BGColor);
  
}

My problem is, if I don't specify the size of the const unsigned short *_image[] in the class .h file, then other data in the program gets corrupted. Is there anyway to pass that array to the class without specifying it's size in the .h file?

In my search on this, I cam across this:

and the second answer on that post seems to be where I should be looking?

Any easier way to pass that array without knowing the size of it?

Thanks for any help,
Randy

A couple of questions

1 - Why don't you want to pass the size ?
2 - Is the array not actually a fixed size ?

Hi and thanks for the reply!

UKHeliBob:
1 - Why don't you want to pass the size ?
2 - Is the array not actually a fixed size ?

In my case, the array will be a fixed size and I will know the size and I can simply hard code in the size, that really isn't much of a problem. The problem is when I start thinking about portability/re-usability of the class....

I currently have a button, a switch, and a slider GUI elements in a library. At some point, when I feel the code is good enough, I thought I would share the library with the community. So I'd like the library to be as easy to use as possible for everyone. Hard coding in the size of the array is not something I would consider easy to use code.

Additionally, if I wanted to create 2 buttons of different sizes, I would have to create 2 classes of the button, one for each size of button. Here again, not something that is easy to use. It would be great if I could use just one class for several buttons of different sizes. I guess I could accomplish this by just declaring the array to be the size of the largest button, but the downside would be smaller size buttons would use as much memory as the largest size button.

It would be nice if I could pass in the size each time I create an object of that class, but I don't see a way to do that.

Thanks for any help,
Randy

When you set the "Compiler warnings:" to "All" in Preferences, do you get any warnings when compiling your sketch?

You are passing a PROGMEM pointer to tft.pushImage(). How does that function know that the data is in PROGMEM? Is it always in PROGMEM? The compile does not keep track of which pointers point to RAM and which point to PROGMEM so you have to keep track yourself and never get the two address spaces mixed up.

Hi and thanks!

johnwasser:
When you set the "Compiler warnings:" to "All" in Preferences, do you get any warnings when compiling your sketch?

I get some warnings, but non that are related to this class.

johnwasser:
You are passing a PROGMEM pointer to tft.pushImage(). How does that function know that the data is in PROGMEM? Is it always in PROGMEM? The compile does not keep track of which pointers point to RAM and which point to PROGMEM so you have to keep track yourself and never get the two address spaces mixed up.

I assume the data is always in PROGMEM because the data is declared as such. See the first bit of code posted above...

// We need this header file to use FLASH as storage with PROGMEM directive:
#include <pgmspace.h>

// Icon width and height
const uint16_t imageWidth = 64;
const uint16_t imageHeight = 32;


const unsigned short imageButtonSample[2048] PROGMEM={

Am I right?
Thanks for any help,
Randy

revolt_randy:
I have an array defined in a .h header file:

That's a mistake. Variable definitions don't belong in .h files. If that file gets #included in multiple sources files (i.e. .ino, .cpp, c.), the linker will flag a multiple definition error. You should only declare the array (as external) in the .h and then define it in a separate .cpp file. See my Reply #3 in this thread.

Also:

UKHeliBob:
A couple of questions

1 - Why don't you want to pass the size ?
2 - Is the array not actually a fixed size ?

I didn't see an answer to Question #1.

revolt_randy:
I assume the data is always in PROGMEM because the data is declared as such.

Yes, but does the tft library EXPECT it to be in PROGMEM? The c++ pointer types are the same. So, the library must know (or be told) to extract it from PROGMEM.

Hi gfvalvo!

gfvalvo:
Yes, but does the tft library EXPECT it to be in PROGMEM? The c++ pointer types are the same. So, the library must know (or be told) to extract it from PROGMEM.

I assume the tft library expects it to be in PROGMEM as I am basically following one of the examples provided with the library:

gfvalvo:
Also:I didn't see an answer to Question #1.

Forgive me, but I don't see a way to pass in the size of the array. The array size is defined in the class's .h file, how do I pass in the size to that?
Thanks,
Randy

revolt_randy:
I get some warnings, but none that are related to this class.

Ignoring warnings can sometimes lead to memory corruption which is what you say you are experiencing.

revolt_randy:
I assume the data is always in PROGMEM because the data is declared as such. See the first bit of code posted above...

I see from the library example that this is likely true so that is not a problem.

Something like this?

#include "Images.h"

class ImageButton
{
  public:
    ImageButton(int x, int y, int w, int h, const uint16_t *image);

    void drawImageButton() {} // dummy implementation
    void eraseImageButton() {} // dummy implementation

  private:
    int _xLoc;                    // x location of button
    int _yLoc;                    // y location of button
    int _xSize;                   // width of button
    int _ySize;                   // height of button
    const uint16_t *_image;
};

ImageButton::ImageButton(int x, int y, int w, int h, const uint16_t *image) :
  _xLoc(x), _yLoc(y), _xSize(w), _ySize(h), _image(image) {
}

ImageButton b1(button1Width, button1Height, 0, 0, button1);
ImageButton b2(button2Width, button2Height, 0, 0, button2);

void setup() {
}

void loop() {
}

Images.h:

#ifndef IMAGES_H
#define IMAGES_H

#include <Arduino.h>

extern const uint16_t button1Width;
extern const uint16_t button1Height;
extern const uint16_t button1[];

extern const uint16_t button2Width;
extern const uint16_t button2Height;
extern const uint16_t button2[];

#endif

Images.cpp:

#include "Images.h"

const uint16_t button1Width = 64;
const uint16_t button1Height = 32;
const uint16_t button1[button1Height * button1Width] PROGMEM = {  // put data here
};

const uint16_t button2Width = 32;
const uint16_t button2Height = 16;
const uint16_t button2[button2Height * button2Width] PROGMEM = {  // put data here
};

I don't want to offend anyone, but I'm afraid if I talk about the compiler warnings, this thread may get off track. But for completeness, I will mention the warnings.

The first two warnings are about how I am passing text when I create an object from another class in my library. The message is: warning: ISO C++ forbids converting a string constant to 'char*' The code that causes these warnings is this:

Button button1(180, 60, 64, 32, TFT_WHITE, TFT_RED, TFT_YELLOW, "text", 1);
Button button2(80, 120, 64, 32, TFT_GREEN, TFT_DARKGREEN, TFT_YELLOW, "text2", 1);

I get the same warning when I compile this example from the TFT library found here:

My code, and the example code, both work fine despite the warning.

The third warning message I get is: warning: variable 'timer' set but not used - which is really nothing more than a sign I need to remove a variable declaration that I'm not using. This shouldn't be an issue.

The data corruption I experienced only happened when I didn't specify the size of the array in my class's .h file. This code works for my .h file:

class ImageButton
{
  public:
    ImageButton(int x, int y, int w, int h, const unsigned short *image);

    void drawImageButton();
    void eraseImageButton();
    
  private:
    int _xLoc;                    // x location of button
    int _yLoc;                    // y location of button
    int _xSize;                   // width of button
    int _ySize;                   // height of button
    const unsigned short *_image[2048];  // image for button
    // need to do this without specifying size of image
    

      
};  // end - class ImageButton

this code doesn't:

class ImageButton
{
  public:
    ImageButton(int x, int y, int w, int h, const unsigned short *image);

    void drawImageButton();
    void eraseImageButton();
    
  private:
    int _xLoc;                    // x location of button
    int _yLoc;                    // y location of button
    int _xSize;                   // width of button
    int _ySize;                   // height of button
    const unsigned short *_image[];  // image for button
    // need to do this without specifying size of image
    

      
};  // end - class ImageButton

I want to thank everyone who has posted for their help so far. You have all given me things to think about.

I will work on a better example and return. I'm not a fan of posting long replies.

Thanks,
Randy

gfvalvo:
Something like this?

Images.cpp:

#include "Images.h"

const uint16_t button1Width = 64;
const uint16_t button1Height = 32;
const uint16_t button1[button1Height * button1Width] PROGMEM = {  // put data here
};

const uint16_t button2Width = 32;
const uint16_t button2Height = 16;
const uint16_t button2[button2Height * button2Width] PROGMEM = {  // put data here
};

That's moving the images from it's own .h file to part of the .cpp file, right?
I'll give this idea a go, and report back....
Thanks,
Randy

revolt_randy:
That's moving the images from it's own .h file to part of the .cpp file, right?

Right. As I told you in Reply #6, variable definitions don't belong in .h files.

@gfvalvo - as per your suggestions, I modified my code to closely match what you have posted. Executing the code with one instance of the Button class and it works! Creating a 2nd instance of the class and only the second instance will display the image to the LCD. The first 2 instances used different size buttons, so I tried creating two instances using the same size image and got the same result, only the 2nd instance worked.

Here's my main program code:

// button

// display
#include <TFT_eSPI.h> // Hardware-specific library
#include <SPI.h>

//TFT_eSPI tft = TFT_eSPI();        // Invoke custom library

#include "Button.h"


ImageButton button3(120, 200, 64, 32, imageButtonSample);
//ImageButton button4(80, 80, 32, 32, info);
//ImageButton button5(160, 120, 32, 32, info);


void setup() {
  // put your setup code here, to run once:

  Serial.begin(115200);
  Serial.println(" starting... \n");
  
  tft.init();
  tft.setRotation(3);

  tft.fillScreen(TFT_BLACK);

  uint16_t calData[5] = { 449, 3418, 344, 3395, 1 };
  tft.setTouch(calData);

  button3.drawImageButton();
  //button4.drawImageButton();
  //button5.drawImageButton();
}


void loop() {
  // put your main code here, to run repeatedly:

  uint16_t x;
  uint16_t y;
  
  if (tft.getTouch(&x, &y))
  {
    Serial.println("touch");
    Serial.print("x = "); Serial.print(x); Serial.print("  y = "); Serial.print(y); Serial.println();
  }
  
}


//

Here's my .h file:

// Button.h

#ifndef Button_h
#define Button_h

#include <TFT_eSPI.h> // Hardware-specific library
#include <SPI.h>

#include "Arduino.h"

extern TFT_eSPI tft;

#define BGColor TFT_BLACK


extern const uint16_t imageButtonSample[];
extern const uint16_t info[];


class ImageButton
{
  public:
    ImageButton(int x, int y, int w, int h, const uint16_t *image);

    void drawImageButton();
    void eraseImageButton();
    
  private:
    int _xLoc;                    // x location of button
    int _yLoc;                    // y location of button
    int _xSize;                   // width of button
    int _ySize;                   // height of button
    const unsigned short *_image[];  // image for button
    // need to do this without specifying size of image
         
};  // end - class ImageButton


#endif

//

and my .cpp:

// Button.cpp

// We need this header file to use FLASH as storage with PROGMEM directive:
#include <pgmspace.h>

#include "Arduino.h"
#include "Button.h"

TFT_eSPI tft = TFT_eSPI();        // Invoke custom library


ImageButton::ImageButton(int x, int y, int w, int h, const uint16_t *image)
{
  _xLoc = x;
  _yLoc = y;
  _xSize = w;
  _ySize = h;
  *_image = image;  
}


void ImageButton::drawImageButton()
{ 
  tft.setSwapBytes(true);     // Swap the colour byte order when rendering
  tft.pushImage((_xLoc - (_xSize/2)), (_yLoc - (_ySize/2)), _xSize, _ySize, *_image);
}


void ImageButton::eraseImageButton()
{
  tft.fillRect((_xLoc - (_xSize/2)), (_yLoc - (_ySize/2)), _xSize, _ySize, BGColor);
  
}


// Button data goes here:

const uint16_t imageButtonSample[2048] PROGMEM = { data }

const uint16_t  info[1024] PROGMEM={ data }

I am also attaching my .ino, .h, and .cpp files for you to look at. They are the same as above.

I really like your suggestions and I will spend sometime troubleshooting what is going on, as I would really like this to work!

Thanks for any help,
Randy

ImageButton_test.ino (1016 Bytes)

Button.h (867 Bytes)

Button.cpp (27.9 KB)

Instead of passing the array as argument, can you try to pass a pointer to the array.
In this case you don’t need to define the size.
The pointer (let’s name it ptr) and the arrays will be the same
It is equivalent to *(ptr)==array[0]
*(ptr+1)==array[1] and so on.
The pointer will point at the first item in your array.

I'm guessing your problem is here:

const unsigned short *_image[];  // image for button

Why do you want an array of pointers? My code in Reply #10 only had a pointer:

const uint16_t *_image;

And, the code in the constructor should probably be:

_image = image;

So, I think you've sprinkled too many '*'s around.

gfvalvo:
So, I think you've sprinkled too many '*'s around.

Indeed, and there was one more in the .cpp's .drawImageButton() function as well. I admit, pointers are something I have struggled to understand. As I was removing the '*' from the code, I think I see where I was going wrong. I think I will add 'ptr' suffixes to my pointer's names to keep it straight that it's a pointer, and not just a regular variable. I think that was where I was going wrong, hence my creating an array of ponters.

Anyway, gfvalvo, a big thank you for helping me with this problem, and an even bigger thank you for helping me learn! :slight_smile:
Randy

Instead of passing the array as argument, can you try to pass a pointer to the array.

Is that not what happens by default ? The array name is actually a pointer to the first element of the array

Yes indeed.
@op was wondering about declaring the size.