OLED Display SSD1306 Heltec fashion

Hi,
I have a lot of code written for the SSD1306 that I use with many ESP32 boards.
I use to use the regular
#include <SSD1306Wire.h> // from GitHub - ThingPulse/esp8266-oled-ssd1306: Driver for the SSD1306 and SH1106 based 128x64, 128x32, 64x48 pixel OLED display running on ESP8266/ESP32
that I use to instantiate with
SSD1306Wire display(0x3c, I2C_SDA, I2C_SCL); //OLED 128*64 soldered
and use the functions as
display.drawString(10, 0, "| Set");

That does however not work with a Heltec ESP_LoRa module that I want to use.
The display use the Heltec library
#include "heltec.h"
is instantiated with
Heltec.begin(true /DisplayEnable Enable/, false /LoRa Disable/, true /Serial Enable/);
and use the functions as
Heltec.display->drawString(10, 0, "| Set");

Do I have a way to use the the classical notation
display.drawString(10, 0, "| Set");
with this library?

I just want to swap the libraries and eventually the instantiations with
#ifdef directives.
but keep the display instructions unchanged.

Thank you for your help.
Laszlo

Not tested but something like

#ifdef USE_HELTEC
#define DISPLAY Heltec.display
#else
#define DISPLAY display
#endif

display.drawString(10, 0, "| Set"); and Heltec.display.drawString(10, 0, "| Set"); will now be DISPLAY.drawString(10, 0, "| Set");

Hmm, thank you, one must be able to do it without macros.
Have you noted the difference?

Heltec uses an arrow (->) after display and the regular lib uses a dot (.).

IMHO one must have a c++ way to do it, but i am not expert enough to find it out.

OOPS
I did see it but copy/paste did not help :frowning:

Alternatively: what would have been the syntax for running the SSD1306Wire library with (->) arrows in the code?

Use a reference:

auto &display = *(Heltec.display);

Don't do this at the global scope, though, see Static Initialization Order Fiasco - cppreference.com

Either do it locally, or write a function that returns a reference to the display.

Thank you! that looks very promising!
Would that mean that I just enter

auto &display = *(Heltec.display);

in the display code block and i would then be able to use the codelines as

display.drawString(10, 0, "| Set");

Does the reference then prevent the usage of
display.drawString(10, 0, "| Set"); using the original library?

Trying to understand the caveat:
that means that if the library uses static variables (which is probably the case) I can't use two references in the whole program?
Normally I use display code lines only within setup() and within my function display()
So entering the reference there should be safe?

Indeed.

Yes, because it will shadow the definition of SSD1306Wire display. You'll need to have some way to decide which one you use at compile-time, probably using a preprocessor #if or #ifdef.

The problem is that the global variable Heltec is defined in a library file. If you define a global reference to Heltec.display, you don't know whether Heltec is going to be initialized before your reference. If Heltec is initialized first, there's no problem, but if your reference is initialized first, it'll refer to an uninitialized *Heltec.display, i.e. a null pointer in this case.

Since you cannot guarantee the initialization order of global variables across files (translation units), you either have to use a local reference:

#include <heltec.h>

void setup() {
  auto &display = *(Heltec.display);
  display.drawString(10, 0, "| Set");
}

or, wrap it in a function to make it global:

#include <heltec.h>

SSD1306Wire &display() {
  return *(Heltec.display);
}

void setup() {
  display().drawString(10, 0, "| Set");
}

You can then switch between Heltec and non-Heltec as follows:

#define HELTEC_DISPLAY 1

#if HELTEC_DISPLAY
SSD1306Wire &display() { return *(Heltec.display); }
#else
SSD1306Wire ssddisplay(0x3c, I2C_SDA, I2C_SCL); //OLED 128*64 soldered
SSD1306Wire &display() { return ssddisplay; }
#endif

I think you can avoid the initialization order problems by using a reference to the Heltec.display pointer:

#if HELTEC_DISPLAY
SSD1306Wire *const &display = Heltec.display; // This is a const reference to a non-const pointer
#else
SSD1306Wire ssddisplay(0x3c, I2C_SDA, I2C_SCL); //OLED 128*64 soldered
SSD1306Wire * const display = &ssddisplay;
#endif

void setup() {
  display->drawString(10, 0, "| Set");
}

You can also wrap everything in a struct that resolves all calls to the right display:

#define HELTEC_DISPLAY 1

#if !HELTEC_DISPLAY
SSD1306Wire ssddisplay(0x3c, I2C_SDA, I2C_SCL); //OLED 128*64 soldered
#endif

struct DisplayWrapper {
  SSD1306Wire &operator*() {
#if HELTEC_DISPLAY
    return *Heltec.display;
#else
    return ssddisplay;
#endif
  }
  SSD1306Wire *operator->() { return &(this->operator*()); }
} display;

void setup() { 
    display->drawString(10, 0, "| Set");
}

This avoids both the initialization order problem, and avoids the need for explicitly calling a function every time you want to use the display (display().drawString(...)), but it's a bit cryptic to my taste. Abstracted away in a library, I would be fine with it, but as boilerplate at the top of a sketch, it's a bit much, and I'd probably prefer the reference-to-pointer approach.

1 Like

Thank you for your extensive help.

I'm afraid being a bit lost now.

You are using

display.drawString(10, 0, "| Set");

then

  display().drawString(10, 0, "| Set");

then

    display->drawString(10, 0, "| Set");

I'd like to only use the first (dot) notation, since it is that one that is in use by other boards.

Globally I will have

#ifdef BOARD_IS HELTEC
#include <heltec.h>                 // heltec driver from Library
#else
#include <SSD1306Wire.h>         // from GitHub - ThingPulse/esp8266-oled-ssd1306
#endif

So I can insert in setup() and in f_display()
respectively:

#ifdef BOARD_IS HELTEC
auto &display = *(Heltec.display);
#endif

So I have the reference locally in setup() and h_display() only if my board is a Heltec.

Right?

In that case, I think the local reference is the best option.

Yes, that looks good to me.

1 Like

Thank you. I will test and return with my findings.
Takes a bit of time...

I got it working on a striped down Heltec demo.

/*
   HelTec Automation(TM) ESP32 Series Dev boards OLED draw Simple Function test code

   - Some OLED draw Simple Function function test;

   by LXYZN from HelTec AutoMation, ChengDu, China
   
   www.heltec.cn

   this project also realess in GitHub:
   https://github.com/HelTecAutomation/Heltec_ESP32
*/


// This example just provide basic function test;
// For more informations, please vist www.heltec.cn or mail to support@heltec.cn

#include "Arduino.h"
#include "heltec.h"
//#include "images.h"

#define DEMO_DURATION 3000
typedef void (*Demo)(void);

int demoMode = 0;
int counter = 1;

void setup() {
  auto &display = *(Heltec.display);
  Heltec.begin(true /*DisplayEnable Enable*/, false /*LoRa Disable*/, true /*Serial Enable*/);



  display.flipScreenVertically();
  display.setFont(ArialMT_Plain_10);

}


void loop() {
  auto &display = *(Heltec.display);
  // clear the display
  display.clear();
  display.setTextAlignment(TEXT_ALIGN_LEFT);
  display.setFont(ArialMT_Plain_10);
  display.drawString(0, 0, "Hello world");
  display.setFont(ArialMT_Plain_16);
  display.drawString(0, 10, "Hello world");
  display.setFont(ArialMT_Plain_24);
  display.drawString(0, 26, "Hello world");

  display.setTextAlignment(TEXT_ALIGN_RIGHT);
  display.drawString(10, 128, String(millis()));
  // write the buffer to the display
  display.display();


  delay(10);
}

Thank you! 
You are the best!

Got it working using all my original lines of codes:


:heart_eyes: