GxEPD2, how to declare 'display' object in .cpp file?

Hello again!

I posted about this problem in the programming topic area, but I feel my technical ability is just not good enough implement their advice specifically regarding the GxEPD2 library, I think the answer will have to come from people who have a more deep understanding of GxEPD2.

I have written a very stripped down version of my program that isolates my issue very nicely.
I have copied the code from my .ino file, .cpp file, and .h files below.

My goal is to move the function named page1Update() from the .ino file to the .cpp file.
In the copied .ino file code below the function is still there, but commented out. It is not commented out in the.cpp file. So if you copy all my code and compile, it should throw for you the same errors I'm seeing.


That error in question is...
In function 'void page1Update()': methods.cpp:10:3: error: 'display' was not declared in this scope
display.setPartialWindow(165, 15, 80, 20);

methods.cpp:13:24: error: 'GxEPD_WHITE' was not declared in this scope
display.fillScreen(GxEPD_WHITE);

methods.cpp:14:39: error: 'GxEPD_BLACK' was not declared in this scope
display.drawRect(165, 15, 80, 20, GxEPD_BLACK);


In my other post someone responded saying that
" You can add an extern declaration to let the .cpp file know that display exists and that it can find it later during linking.

Add this to the .cpp file
extern GxEPD2_BW<GxEPD2_213_B74, MAX_HEIGHT(GxEPD2_213_B74)> display; "

However when I added that line to my .cpp file it threw all kinds of errors about it. Clearly it's not written correctly to work for GxEPD2. I believe something needs to be added in order to declare the objects/functions/variables of GxEPD2 within my .cpp file, but I do not know or have the expertise to create that content.

So I'm hoping that either @ZinggJM or someone else can tell me very specifically what that code is to make this page1Update() work within the .cpp file without throwing these errors.

I have looked closely through the GxEPD2 library .cpp files and .h files and I can not seem to understand or find something that appears to be a copy/paste solution for me at my level of understanding.

Thankyou all again in advance. It is such a blessing to have this forum resource!

the .ino code...

//filename backstep2_code.ino
#include <Adafruit_GFX.h> // Core graphics library
#include <GxEPD2_BW.h>
#include <Fonts/FreeMonoBold9pt7b.h>
#include "methods.h"

#if defined(__AVR)
#define MAX_DISPLAY_BUFFER_SIZE 800
#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? EPD::HEIGHT : MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8))
GxEPD2_BW<GxEPD2_213_B74, MAX_HEIGHT(GxEPD2_213_B74)> display(GxEPD2_213_B74(/*CS=*/ 9, /*DC=*/ 10, /*RST=8*/ -1, /*BUSY=7*/7)); // GDEM0213B74 122x250, SSD1680
#endif

//int timeRemain;

void setup() {
  display.init(); // disable diagnostics to avoid delay and catch most rotary pulses
  //Serial.begin(9600);
  display.setTextColor(GxEPD_BLACK);
  display.setFont(&FreeMonoBold9pt7b);
  display.setRotation(3);

  display.setFullWindow();
  display.firstPage();//This sets up the screen for the non-changing/updating graphics
  do {
    display.fillScreen(GxEPD_WHITE);
    display.setCursor(20, 30);
    display.println("Next vote in ");
  } while (display.nextPage());
}//END SETUP----------------------------------------------------------------------------------------------

void loop() {
  eventSlotTiming();
  timeRemain = (vTime - millis()) / 1000; //as millis increases, this takes the difference between millis and vTime and converts it to seconds
  page1Update();
}//END LOOP-----------------------------------------------------------------------------------------------
/*
  void page1Update() {
  display.setPartialWindow(165, 15, 80, 20);
  display.firstPage();
  do {
    display.fillScreen(GxEPD_WHITE);
    display.drawRect(165, 15, 80, 20, GxEPD_BLACK);
    display.setCursor(175, 30);
    display.print(timeRemain);
    display.setCursor (200, 30);
    display.print("sec");
  } while (display.nextPage());
  }//END page1Update---------------------------------------------------------------------------------------
*/

the .cpp code...

//filename method.cpp
#include "arduino.h"
#include "methods.h"
#include <Adafruit_GFX.h> // Core graphics library
#include <GxEPD2_BW.h>
#include <Fonts/FreeMonoBold9pt7b.h>

long vTime = 60000;//vTime starts at a value of 1 minute in milliseconds

void page1Update() {
int timeRemain;

  display.setPartialWindow(165, 15, 80, 20);
  display.firstPage();
  do {
    display.fillScreen(GxEPD_WHITE);
    display.drawRect(165, 15, 80, 20, GxEPD_BLACK);
    display.setCursor(175, 30);
    display.print(timeRemain);
    display.setCursor (200, 30);
    display.print("sec");
  } while (display.nextPage());
}//END page1Update---------------------------------------------------------------------------------------

void eventSlotTiming() {
  if (millis() >= vTime) {
    vTime = millis() + 60000;//Every minute, this resets vTime variable to one minute into the future
  }
}//END eventSlotTiming------------------------------------------------------------------------------------

the .h code...

//filename method.h
#ifndef methods_h
#define methods_h
#include "arduino.h"

extern long vTime;
extern int timeRemain;

void page1Update();
void eventSlotTiming();

#endif

There are several options to solve this. The easiest is using a base class.
See example GxEPD2_GFX_Example and GxEPD2_MultiDisplayExample.
For this you enable having a base class for all template class instances:

// base class GxEPD2_GFX can be used to pass references or pointers to the display instance as parameter, uses ~1.2k more code
// enable GxEPD2_GFX base class
#define ENABLE_GxEPD2_GFX 1

I leave study of this option and adaptation to your need to you.

There is also a way to use template instance syntax on parameters e.g., but this is more complex.
There is an example here: https://github.com/ZinggJM/GxEPD2/blob/master/extras/examples/GxEPD2_T_MultiDisplayExample/GxEPD2_T_MultiDisplayExample.ino
It would take me quite some time to understand again what I did. Not recommended.
-jz-

Thankyou @ZinggJM for your response.
I went through that GxPED2_MultiDisplayExample sketch and it helped me imagine that I could simply copy the object instantiation for the display over from .ino to .cpp and give the object a new name within that .cpp instantiation.
I also put in the #define ENABLE_GxEPD2_GFX 1 line as you suggested, and as I also saw it in the example.

For the instantiation within .cpp I changed the object name from display. to displayAlt. and made sure any use of the library functions within .cpp used that new object name.

As I understand it, this effectively creates a whole new object for the display as though I am running two displays side-by-side. But instead of actually having a 2nd display, I'm just using the new object exclusively within my .cpp file where it is instantiated.

When compiling, this appears to have removed all of the errors saying the 'display' wasn't declared. The consequence is now that this whole 2nd instantiation of the display uses like 2x the memory throwing this error...


Global variables use 2323 bytes (113%) of dynamic memory, leaving -275 bytes for local variables. Maximum is 2048 bytes.
Not enough memory;


Is this what you were imagining as the solution @ZinggJM related to your suggestion of #define ENABLE_GxEPD2_GFX 1? Or is there something about how I'm instantiating the new object that is using far more memory than necessary?

Thankyou for your help so far!!!

Here is my new code for your reference...

//filename backstep2_code.ino
#define ENABLE_GxEPD2_GFX 1

#include <Adafruit_GFX.h> // Core graphics library
#include <GxEPD2_BW.h>
#include <Fonts/FreeMonoBold9pt7b.h>
#include "methods.h"

//int timeRemain;

#define MAX_DISPLAY_BUFFER_SIZE 800
#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? EPD::HEIGHT : MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8))
GxEPD2_BW<GxEPD2_213_B74, MAX_HEIGHT(GxEPD2_213_B74)> display(GxEPD2_213_B74(/*CS=*/ 9, /*DC=*/ 10, /*RST=8*/ -1, /*BUSY=7*/7)); // GDEM0213B74 122x250, SSD1680


void setup() {
  display.init(); // disable diagnostics to avoid delay and catch most rotary pulses

  display.setTextColor(GxEPD_BLACK);
  display.setFont(&FreeMonoBold9pt7b);
  display.setRotation(3);

  display.setFullWindow();
  display.firstPage();//This sets up the screen for the non-changing/updating graphics
  do {
    display.fillScreen(GxEPD_WHITE);
    display.setCursor(20, 30);
    display.println("Next vote in ");
  } while (display.nextPage());

}//END SETUP----------------------------------------------------------------------------------------------

void loop() {
  eventSlotTiming();
  timeRemain = (vTime - millis()) / 1000; //as millis increases, this takes the difference between millis and vTime and converts it to seconds
  page1Update();
}//END LOOP-----------------------------------------------------------------------------------------------
/*
  void page1Update() {
  display.setPartialWindow(165, 15, 80, 20);
  display.firstPage();
  do {
    display.fillScreen(GxEPD_WHITE);
    display.drawRect(165, 15, 80, 20, GxEPD_BLACK);
    display.setCursor(175, 30);
    display.print(timeRemain);
    display.setCursor (200, 30);
    display.print("sec");
  } while (display.nextPage());
  }//END page1Update---------------------------------------------------------------------------------------
*/
// base class GxEPD2_GFX can be used to pass references or pointers to the display instance as parameter, uses ~1.2k more code
// enable GxEPD2_GFX base class
#define ENABLE_GxEPD2_GFX 1

//filename method.cpp
#include "arduino.h"
#include "methods.h"
#include <Adafruit_GFX.h> // Core graphics library
#include <GxEPD2_BW.h>
#include <Fonts/FreeMonoBold9pt7b.h>

#define MAX_DISPLAY_BUFFER_SIZE 800
#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? EPD::HEIGHT : MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8))
GxEPD2_BW<GxEPD2_213_B74, MAX_HEIGHT(GxEPD2_213_B74)> displayAlt(GxEPD2_213_B74(/*CS=*/ 9, /*DC=*/ 10, /*RST=8*/ -1, /*BUSY=7*/7)); // GDEM0213B74 122x250, SSD1680

long vTime = 60000;//vTime starts at a value of 1 minute in milliseconds
int timeRemain;

void page1Update() {
  displayAlt.setPartialWindow(165, 15, 80, 20);
  displayAlt.firstPage();
  do {
    displayAlt.fillScreen(GxEPD_WHITE);
    displayAlt.drawRect(165, 15, 80, 20, GxEPD_BLACK);
    displayAlt.setCursor(175, 30);
    displayAlt.print(timeRemain);
    displayAlt.setCursor (200, 30);
    displayAlt.print("sec");
  } while (displayAlt.nextPage());
}//END page1Update---------------------------------------------------------------------------------------

void eventSlotTiming() {
  if (millis() >= vTime) {
    vTime = millis() + 60000;//Every minute, this resets vTime variable to one minute into the future
  }
}//END eventSlotTiming------------------------------------------------------------------------------------
//filename method.h
#ifndef methods_h
#define methods_h
#include "arduino.h"

extern long vTime;
extern int timeRemain;
extern bool hasRun;

void page1Update();
void eventSlotTiming();
void page1Static();

#endif

No, not at all.

Instead of helping you, I got you confused.
But when I see a Poster attempting an object oriented approach, I expect some knowledge in programming OO in C++.

I should only have mentioned the example GxEPD2_GFX_Example.ino.

In this example you see how to use GxEPD2 with a base class GxEPD2_GFX for references to a display instance. You could also use pointers to an instance.

Take a look at the class TextDisplay.h.
It shows the use of references to a display class as method parameters.

// TextDisplay shows the use of the display instance reference as a function parameter

More relevant for your use is class BitmapDisplay.h.
It shows the use of a reference to a display class as member of a class, passed to the class in the constructor.

// BitmapDisplay shows the use of the display instance reference as a class member
class BitmapDisplay
{
  private:
    GxEPD2_GFX& display;
  public:
    BitmapDisplay(GxEPD2_GFX& _display) : display(_display) {};
    void drawBitmaps();

Inside the class the reference member variable display can be used as usual.

In GxEPD2_GFX_Example.ino you can see how elegantly this can be used at line 51:

BitmapDisplay bitmaps(display);

and line 77:

  bitmaps.drawBitmaps();

or even more elegantly by uncommenting line 76:

  BitmapDisplay(display).drawBitmaps();

-jz-

I didn't answer your original question, as I am too lazy to research the syntax needed.
But if you use

#define ENABLE_GxEPD2_GFX 1

you could then create:

GxEPD2_GFX& TheDisplay(display);

which could then be used in your .cpp as:

extern GxEPD2_GFX& TheDisplay;

untested.

But the solution in GxEPD2_GFX_Example is more elegant in my view.
-jz-

Thankyou @ZinggJM for your guidance once again. I think this is going to require me to take a step back and do some more learning on OOP to be able to successfully implement your suggestions.

Sometimes it can be hard to stop doing it the way you have been and learn a better way, so I will have to come up with the time and energy to do that.

Thankyou again for being so generous with your time and help. I will be taking a break for a bit now, but I may have some more questions when I come back to this for another attempt.

You are welcome. When you come back, make sure you have added some needed #includes to your .cpp file. #includes in your .ino files are not seen in the .cpp files. Some programmers even forget to #include <Arduino.h> in their .cpp files (but in most cases it gets included by other includes already).
-jz-

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.