Waveshare e-paper displays with SPI

I think with "Frame number" they mean "number for the frame" and with "number" they mean "number of clock cycles": :slight_smile:

In IL0373.pdf they name it "NUMBER OF FRAMES", which is not clearer either.

We need to apply some common sense when reading "Chinese" specifications.

Hi there!

is there a way to identify different EPDs via micro controller?

For example I want to know if a 1.54 inch [1] or a 7.5 inch EPD [2] is attached and therefore do a different EPD handling...

[1] https://www.waveshare.com/wiki/1.54inch_e-Paper_Module_(B)
[2] https://www.waveshare.com/wiki/7.5inch_e-Paper_HAT_(B)

Greetings! :slight_smile:

@51M0N95,

The "Hi there!" most likely doesn't know. The "Who knows" might know something about this.

Most of these e-paper controllers have some read capability through SDIO SPI on the DIN pin.
But most don't have an ID like TFT controllers.
For details you would need to look at the controller specifications.
You can find the controller specs on the Good Display web site.

The standard Arduino SPIClass doesn't support SDIO SPI, only standard SPI with separate MISO and MOSI.

I have not yet tried to read from these controllers.

Please make web-links clickable by using the "Insert a link" command symbol.

Is it possible that the current GitHub Version is at least partially broken?

I just encountered this:

Setup: Arduino IDE 1.8.9 on Linux 64Bit
Latest GitHub Clone from ZinggJM's Repo
Arduino Mega 2650/ADK
2.9" Waveshare Epaper Display

wired like suggested in the comments in the GxEPD2_Example Sketch
imported the Libs from Example dir and src dir.

Upon compiling I have no Error reports about missing Headerfiles or missing things at all but I get a Linker failure when avr-gcc tries to link GxEPD2_Example together that reports various undefined references to functions that are decleared in GxEPD2_290.h :

/home/binarykitchen/Downloads/arduino-1.8.9/hardware/tools/avr/bin/avr-gcc -w -Os -g -flto -fuse-linker-plugin -Wl,--gc-sections,--relax -mmcu=atmega2560 -o /tmp/arduino_build_237695/GxEPD2_Example.ino.elf /tmp/arduino_build_237695/sketch/GxEPD2_Example.ino.cpp.o /tmp/arduino_build_237695/libraries/src/GxEPD2_EPD.cpp.o /tmp/arduino_build_237695/libraries/Adafruit-GFX-Library-master/Adafruit_GFX.cpp.o /tmp/arduino_build_237695/libraries/Adafruit-GFX-Library-master/Adafruit_SPITFT.cpp.o /tmp/arduino_build_237695/libraries/Adafruit-GFX-Library-master/glcdfont.c.o /tmp/arduino_build_237695/libraries/SPI/SPI.cpp.o /tmp/arduino_build_237695/core/core.a -L/tmp/arduino_build_237695 -lm
/tmp/ccD0vV5l.ltrans1.ltrans.o: In function main': <artificial>:(.text.startup+0xa0e): undefined reference to GxEPD2_290::powerOff()'
:(.text.startup+0xb18): undefined reference to GxEPD2_290::hibernate()' <artificial>:(.text.startup+0xd1c): undefined reference to GxEPD2_290::hibernate()'
/tmp/ccD0vV5l.ltrans2.ltrans.o: In function GxEPD2_BW<GxEPD2_290, 296u>::nextPage() [clone .constprop.45]': <artificial>:(.text+0x1ec): undefined reference to GxEPD2_290::writeImage(unsigned char const*, int, int, int, int, bool, bool, bool)'
:(.text+0x214): undefined reference to GxEPD2_290::refresh(int, int, int, int)' <artificial>:(.text+0x268): undefined reference to GxEPD2_290::writeImage(unsigned char const*, int, int, int, int, bool, bool, bool)'
:(.text+0x272): undefined reference to GxEPD2_290::refresh(bool)' <artificial>:(.text+0x360): undefined reference to GxEPD2_290::writeImage(unsigned char const*, int, int, int, int, bool, bool, bool)'
:(.text+0x3be): undefined reference to GxEPD2_290::refresh(int, int, int, int)' <artificial>:(.text+0x40a): undefined reference to GxEPD2_290::writeImage(unsigned char const*, int, int, int, int, bool, bool, bool)'
:(.text+0x462): undefined reference to GxEPD2_290::refresh(bool)' <artificial>:(.text+0x47e): undefined reference to GxEPD2_290::powerOff()'
/tmp/ccD0vV5l.ltrans2.ltrans.o: In function global constructors keyed to 65535_0_GxEPD2_Example.ino.cpp.o.3025': <artificial>:(.text.startup+0x7a): undefined reference to GxEPD2_290::GxEPD2_290(signed char, signed char, signed char, signed char)'
:(.text.startup+0x136): undefined reference to vtable for GxEPD2_290' <artificial>:(.text.startup+0x138): undefined reference to vtable for GxEPD2_290'
collect2: error: ld returned 1 exit status
Bibliothek src im Ordner: /home/binarykitchen/Arduino/libraries/src (legacy) wird verwendet
Bibliothek Adafruit-GFX-Library-master in Version 1.5.6 im Ordner: /home/binarykitchen/Arduino/libraries/Adafruit-GFX-Library-master wird verwendet
Bibliothek SPI in Version 1.0 im Ordner: /home/binarykitchen/Downloads/arduino-1.8.9/hardware/arduino/avr/libraries/SPI wird verwendet
exit status 1
Fehler beim Kompilieren für das Board Arduino Mega ADK.

However when I (additionally) istalled GxEPD and GxEPD2 from Library Manager those were gone and all worked fine.
Since the were no missing (header) files I suppose something is broken in the current GitHub version.

Cheers
Sebastian

@do5rsw,

Is it possible that the current GitHub Version is at least partially broken?

I don't think so.

I just updated all libraries on my 2nd notebook with Library Manager, and GxEPD2_Example compiles fine.

Latest GitHub Clone from ZinggJM's Repo

You would need to be more specific, what you did and how you installed the libraries.

Bibliothek src im Ordner: /home/binarykitchen/Arduino/libraries/src (legacy) wird verwendet

This looks suspicious. The library should be GxEPD2, not src!
Looks like you copied the src directory tree instead of the whole library!
Bad habit "learned" from the Waveshare "library"?

Note that source files are automatically included in the build process from subdirectories of src, if src is a first level subdirectory of the main directory of the library.

GxEPD2 and GxEPD can be installed or updated with the Library Manager. This method should be used.

I once had problems with update after I had replaced some files in the installed library. The Library Manager got confused with directories used for the library. In this case you need to delete the library, and do a fresh install using the Library Manager.

Installing a library using ZIP download and installing ZIP file using Library Manager also works; this is what I do on my development notebook to check a version before tagging it on GitHub to make it available for Library manager.

You should avoid to copy files from a GitHub clone to an installed library, unless you know what you do.

This might be a bit offtopic for this thread, but since I'm working with an e-paper display and ZinggJM wonderful library, I'm going to post here. Essentially I'm having troubles with data types in C. I'm just very bad at C.

1.) I receive an image as byte array via websockets.
2.) The payload of the websocket is JSON encoded.
3.) I use ArduinoJSON to parse the payload.
4.) I then hand over the image / byte array to the drawBitmap function of GxEPD2.

Somewhere along this way either the ESP8266 crashes, or there is just random noise displayed. The image data itself should be fine. If I take the byte array and save it in PROGMEM like here, I can load it just fine. I just don't know how to handle the incoming data (String to char array, char array to uint8_t, pointers, I just get lost...)

Here's my code:

#include <Arduino.h>

#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>

#include <ArduinoJson.h>

#include <WebSocketsClient.h>
#include <SocketIOclient.h>

#include <Hash.h>

#include <GxEPD2_BW.h>
#include "test.h"


ESP8266WiFiMulti WiFiMulti;
SocketIOclient socketIO;
GxEPD2_BW<GxEPD2_213_flex, GxEPD2_213_flex::HEIGHT> display(GxEPD2_213_flex(/*CS=15*/ SS, /*DC=4*/ 4, /*RST=5*/ 5, /*BUSY=16*/ 16)); // GDEW0213I5F

void socketIOEvent(socketIOmessageType_t type, uint8_t * payload, size_t length) {
    switch(type) {
        case sIOtype_DISCONNECT:
            Serial.printf("[IOc] Disconnected!\n");
            break;
        case sIOtype_CONNECT: {
            DynamicJsonDocument doc(1024);
            JsonArray array = doc.to<JsonArray>();
            array.add("identify");
            array.add("eink");
            String output;
            serializeJson(doc, output);
            socketIO.sendEVENT(output);

            // Print JSON for debugging
            Serial.println(output);
            Serial.printf("[IOc] Connected to url: %s\n", payload);
        }
            break;
        case sIOtype_EVENT:
        {
            Serial.printf("[IOc] got event: %s", payload);
            const size_t capacity = JSON_ARRAY_SIZE(2) + 15320;
            DynamicJsonDocument doc(capacity);
            DeserializationError error = deserializeJson(doc, payload, length);
            if(error) {
                Serial.printf("deserializeJson() failed: ");
                Serial.println(error.c_str());
                return;
            }



            String eventName = doc[0];
            Serial.printf("[IOc] event name: %s\n", eventName.c_str());
            String image = doc[1];
            //uint8_t test[] = doc[1];
            //Serial.println(image);
            //unsigned char* test[3000] = {doc[1]};

            if (eventName == "full") {
                fullRefresh(image);
            } else if (eventName == "part") {
                partialRefresh(image);
            } else if (eventName == "area") {
                //areaRefresh(image);
                // TODO and coords + size
            }
        }
            break;
        case sIOtype_ACK:
            Serial.printf("[IOc] get ack: %u\n", length);
            hexdump(payload, length);
            break;
        case sIOtype_ERROR:
            Serial.printf("[IOc] get error: %u\n", length);
            hexdump(payload, length);
            break;
        case sIOtype_BINARY_EVENT:
            Serial.printf("[IOc] get binary: %u\n", length);
            hexdump(payload, length);
            break;
        case sIOtype_BINARY_ACK:
            Serial.printf("[IOc] get binary ack: %u\n", length);
            hexdump(payload, length);
            break;
    }
}

void setup() {
    Serial.begin(115200);
    //Serial.setDebugOutput(true);

    WiFi.mode(WIFI_STA);

    WiFiMulti.addAP("**", "*");
    WiFiMulti.addAP("**", "*");

    while (WiFiMulti.run() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }

    String ip = WiFi.localIP().toString();
    Serial.printf("[SETUP] WiFi Connected %s\n", ip.c_str());

    // server address, port and URL
    //socketIO.begin("141.76.67.231", 3000);
    socketIO.begin("192.168.178.75", 3000);
    display.init(115200);

    // event handler
    socketIO.onEvent(socketIOEvent);
}

void loop() {
    socketIO.loop();
}

void fullRefresh(String image) {
    display.setFullWindow();
    Serial.println(image);
    uint8_t buffer[13779];
    image.getBytes(buffer, 13779);    
    Serial.println(image.length());
    display.firstPage();
    do
    {
        Serial.println('#');
        display.fillScreen(GxEPD_WHITE);
        display.drawBitmap(0, 0, buffer, display.epd2.WIDTH, display.epd2.HEIGHT, GxEPD_BLACK);    
    }   
    while (display.nextPage());
    Serial.println('.');
}

void partialRefresh(String image) {
    display.setPartialWindow(0, 0, display.width(), display.height());
    display.firstPage();
    do {
        Serial.println();

        display.fillScreen(GxEPD_WHITE);


        char buffer[3000];
        image.toCharArray(buffer, 3000);    
        //const unsigned char test1[] = {0XFF, ...,0XFF};
        //const unsigned char test2[] = {0XFF, ..., 0XFF};
        display.drawBitmap(0, 0, (uint8_t*) buffer, display.epd2.WIDTH, display.epd2.HEIGHT, GxEPD_BLACK);    
    }
    while (display.nextPage());
    delay(1000);
    
    //uint8_t test = image.c_str();
    //char buffer[2756];
    //const char *buffer = image.c_str();
    //image.toCharArray(buffer, 2756);
    //image.toCharArray(buffer, 2756);
} 

void areaRefresh(char image) {

}

@anselmpaul,

If I take the byte array and save it in PROGMEM

I am still not sure if you receive the image data in binary format or ASCII encoded.
The C source would be in ASCII. You would need to convert ASCII to binary for drawBitmap.

Unfortunately I have no experience with JSON.

I receive the image data in the format of a String which looks like this "0XFF,0XFF,0XFF,0XFF,...,0XFF".
Right now the arduino crashes after executing this do-while block exactly once. The 8 is the last Number printed before the software watch dog resets.

I've done some poking and removed the String class - now the ESP doesn't crash anymore, which is a win. But the display still prints noise. So somehow the image data gets handled wrong.

the code, simplified, now looks like this:

const char* image = doc[1]; // the received image data "0xFF,0xFF,...,0xFF"
partialRefresh(image);


void partialRefresh(const char* image) {
    display.setPartialWindow(0, 0, display.width(), display.height());
    display.firstPage();
    do {
        display.fillScreen(GxEPD_WHITE);
        display.drawBitmap(0, 0, (unsigned char*) image, display.epd2.WIDTH, display.epd2.HEIGHT, GxEPD_BLACK);    
    }
    while (display.nextPage());

What do you think?

Sorry, I can't help you with this.
You seem to have 2 issues: you got an ASCII (textual) representation, but drawBitmap needs a binary representation, and you may have too big data on the stack (local variables) for ESP8266. Most likely the crash is because of the second issue.

Hi ZinggJM,

I have a question about partial update, I'm using a 2.13 epaper display and want to update the screen with data getting over TX/RX. I'm getting the bitmap data as char[160] and want to ask which is the best way to draw a image in 25 iterations?

I'm currently at work, I can provide later the code I tried.

Thx

Edit, here is my code:

#define ENABLE_GxEPD2_GFX 0

#include <GxEPD2_BW.h>
#include <GxEPD2_3C.h>
#include <Fonts/FreeMonoBold9pt7b.h>



#if defined(__AVR)
#define MAX_DISPAY_BUFFER_SIZE 800 // 
#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? EPD::HEIGHT : MAX_DISPAY_BUFFER_SIZE / (EPD::WIDTH / 8))
GxEPD2_BW<GxEPD2_213, MAX_HEIGHT(GxEPD2_213)> display(GxEPD2_213(/*CS=10*/ SS, /*DC=*/ 8, /*RST=*/ 9, /*BUSY=*/ 7)); // GDE0213B1, phased out
#endif


#include "GxEPD2_boards_added.h"


#if !defined(__AVR) && !defined(_BOARD_GENERIC_STM32F103C_H_)
#include "bitmaps/Bitmaps128x250.h" // 2.13" b/w
#else
#include "bitmaps/Bitmaps128x250.h" // 2.13" b/w
#endif


#include <SoftwareSerial.h>

SoftwareSerial altSerial(4, 3); // RX, TX


int i = 0;
int line_counter = -10;

char bytes[160];


void setup()
{
  display.init(115200);
  altSerial.begin(115200);

  display.fillScreen(GxEPD_WHITE);

 display.setRotation(2);

  receive();
  display.drawInvertedBitmap(0, line_counter, bytes, 250, 128, GxEPD_BLACK);
    display.display(true); 
    receive();
  display.drawInvertedBitmap(0, line_counter, bytes, 250, 128, GxEPD_BLACK);
    display.display(true); 
    receive();
  display.drawInvertedBitmap(0, line_counter, bytes, 250, 128, GxEPD_BLACK);
    display.display(true); 
    receive();
  display.drawInvertedBitmap(0, line_counter, bytes, 250, 128, GxEPD_BLACK);
    display.display(true); 
    receive();
  display.drawInvertedBitmap(0, line_counter, bytes, 250, 128, GxEPD_BLACK);
    display.display(true); 
    receive();
  display.drawInvertedBitmap(0, line_counter, bytes, 250, 128, GxEPD_BLACK);
    display.display(true); 
    receive();
  display.drawInvertedBitmap(0, line_counter, bytes, 250, 128, GxEPD_BLACK);
    display.display(true);
    .
    .
    .
  display.powerOff();


}

void loop()
{

}


void receive()
{
  i = 0;
  while (i < 160) {
    if  (altSerial.available()) {
      bytes[i] = altSerial.read();
      Serial.println(bytes[i], DEC);
      i++;
    }
  }

  line_counter += 10;
}

@Moneyhunter,

If you want to get your update noticed early, you should add it in a new post. But now I've seen it!

It is always a good idea to report the hardware you use. From your code I assume an AVR Arduino.

160*8 gives indeed 10 lines of 128 pixels wide, so I don't need to suggest to use a multiple of width/8.

With AVR Arduino you would need to use paged drawing for buffered drawing.
This would mean you would need to download multiple times.
display.display(true); can only display as much as can be stored in the buffer.

But with GxEPD2 you can write bitmaps directly to the buffer in the controller; these bitmaps can be parts of the controller buffer.

    // write to controller memory, without screen refresh; x and w should be multiple of 8
    void writeImage(const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, bool pgm = false);

h would be 10 in your case.

After you have written all the parts, you call:

    void refresh(bool partial_update_mode = false); // screen refresh from controller memory to full screen

with either true or false.

Unfortunately you need to download and write all parts a second time, if you want to use any subsquent partial updates. For the general case for all displays that have "fast partial update", you would call the method:

    void writeImageAgain(const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h, bool invert = false, bool mirror_y = false, bool pgm = false);

instead, e.g. for the GDEH0213B72.

Note that this needs be done after the refresh(), to make differential buffers equal.

Hope this helps.

Jean-Marc

@ZinggJM

Sry I forgot to mention that I use a Arduino Pro Mini 3.3V.

You are right with h=10, I changed this already.

Today I changed my code a bit, now I have picture on my display, it's only a little bit shifted :confused:

I'm using the drawImage() method, because the writeImage() and refresh() method is already included there. Would I have any advantages to use drawImage(), refresh(), writeImageAgain(), refresh() ,... instead of drawImage()?

EDIT: I uploaded a picture of the screen and the source picture.
EDIT2: After adding a delay(1000) after each refresh, its now working like it should! Thanks!
EDIT3: It seems like it didn't solved the problem 100%, like every third picture transfer is shifted.

#define ENABLE_GxEPD2_GFX 0

#include <GxEPD2_BW.h>
#include <GxEPD2_3C.h>
#include <Fonts/FreeMonoBold9pt7b.h>

#if defined(__AVR)
#define MAX_DISPAY_BUFFER_SIZE 800 //
#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? EPD::HEIGHT : MAX_DISPAY_BUFFER_SIZE / (EPD::WIDTH / 8))
GxEPD2_BW<GxEPD2_213, MAX_HEIGHT(GxEPD2_213)> display(GxEPD2_213(/*CS=10*/ SS, /*DC=*/8, /*RST=*/9, /*BUSY=*/7)); // GDE0213B1, phased out
#endif

#include "GxEPD2_boards_added.h"

#if !defined(__AVR) && !defined(_BOARD_GENERIC_STM32F103C_H_)
#include "bitmaps/Bitmaps128x250.h" // 2.13" b/w
#else
#include "bitmaps/Bitmaps128x250.h" // 2.13" b/w
#endif

#include <SoftwareSerial.h>

SoftwareSerial altSerial(4, 3); // RX, TX

char bytes[160];

void setup()
{
  display.init(115200);
  altSerial.begin(115200);

  receive();
  display.drawImage(bytes, 0, 0, 128, 10, false, true, false);
  
  receive();
  display.drawImage(bytes, 0, 10, 128, 10, false, true, false);

  receive();
  display.drawImage(bytes, 0, 20, 128, 10, false, true, false);

  receive();
  display.drawImage(bytes, 0, 30, 128, 10, false, true, false);

  receive();
  display.drawImage(bytes, 0, 40, 128, 10, false, true, false);

  receive();
  display.drawImage(bytes, 0, 50, 128, 10, false, true, false);

  receive();
  display.drawImage(bytes, 0, 60, 128, 10, false, true, false);

  receive();
  display.drawImage(bytes, 0, 70, 128, 10, false, true, false);

  receive();
  display.drawImage(bytes, 0, 80, 128, 10, false, true, false);

  receive();
  display.drawImage(bytes, 0, 90, 128, 10, false, true, false);

  receive();
  display.drawImage(bytes, 0, 100, 128, 10, false, true, false);

  receive();
  display.drawImage(bytes, 0, 110, 128, 10, false, true, false);

  receive();
  display.drawImage(bytes, 0, 120, 128, 10, false, true, false);

  receive();
  display.drawImage(bytes, 0, 130, 128, 10, false, true, false);

  receive();
  display.drawImage(bytes, 0, 140, 128, 10, false, true, false);

  receive();
  display.drawImage(bytes, 0, 150, 128, 10, false, true, false);

  receive();
  display.drawImage(bytes, 0, 160, 128, 10, false, true, false);

  receive();
  display.drawImage(bytes, 0, 170, 128, 10, false, true, false);

  receive();
  display.drawImage(bytes, 0, 180, 128, 10, false, true, false);

  receive();
  display.drawImage(bytes, 0, 190, 128, 10, false, true, false);

  receive();
  display.drawImage(bytes, 0, 200, 128, 10, false, true, false);

  receive();
  display.drawImage(bytes, 0, 210, 128, 10, false, true, false);

  receive();
  display.drawImage(bytes, 0, 220, 128, 10, false, true, false);

  receive();
  display.drawImage(bytes, 0, 230, 128, 10, false, true, false);

  receive();
  display.drawImage(bytes, 0, 240, 128, 10, false, true, false);

  display.powerOff();
}

void loop()
{

}

void receive()
{
  int i = 0;
  while (i < 160)
  {
    if (altSerial.available())
    {
      bytes[i] = altSerial.read();
      Serial.println(bytes[i], DEC);
      i++;
    }
  }
}

source.png

drawImage() is better suited, as it does refresh() and writeImageAgain().

no delay() should be needed.

no shifting should occur. Except that the GDE0213B1 has a logical width of 128 but physical width is 122.

your receive() doesn't seem to have any handshaking or flow control.

After switching from SoftwareSerial to AltSoftSerial everything is working great, thank you again :slight_smile:

Hello, I'm trying to get one of these waveshare ePaper displays to work with Arduino. I have it almost working, except for one problem -- sleep mode. I want it powered down to minimum power consumption so I can run my device off a CR2032.

No matter what I do, it continues to pull approximately 400 microamps of power. The datasheet says 2 microamps, so something is not working as I expect. Below is the waveshare code:

void Epd::Sleep() {
    SendCommand(0X50);
    SendData(0xf7);
    SendCommand(0X02);  	//power off
    SendCommand(0X07);  	//deep sleep
    SendData(0xA5);
}

I've tried commenting parts of this out, as far as I can tell none of it has any damn effect on power consumption. Looking at the datasheet, the commands (0x02 and 0x07) are for power off and sleep mode, so this should be working.

The display I'm working with is a 2.13" flexible, and I'm using one of the waveshare breakout boards (in particular the "hat" intended for raspberry pi; I have the necessary pins directly connected to atmega328).

Any thoughts?

Thanks,
Scott

@smbaker,

your code is correct. I think you measure the current consumed by the level converter on the HAT.

Hi,
Thank you again for the great support. I am currently using Heltec LoRa v2 module and the module uses the default SPI for the LoRa Module. Pin layout can be found here. My Question is how to use the second SPI from ESP32 in order to drive the ePaper display.

I have tried with:

SPIClass hspi(HSPI);

GxIO_Class io(hspi, /*CS=5*/ 15, /*DC=*/ 37, /*RST=*/ 38);
GxEPD_Class display(io, /*RST=*/ 38, /*BUSY=*/ 34);

But nothing happens, do you have any suggestion on how to use the HSPI instead of VSPI?
Thanks in advance.

Hi

I like readable pinout diagrams. Google "Heltec LoRa v2" found e.g.:

This has a link to pinout diagram:

https://github.com/Heltec-Aaron-Lee/WiFi_Kit_series/blob/master/PinoutDiagram/WIFI_LoRa_32_V2.pdf

This pdf is readable. So I can start to find out about HSPI.

It looks like (some) HSPI pins are used for OLED.

Maybe you could continue with this investigation.

Pins with this arrow are used by on-board OLED or LoRa, they must not
be used for other purpose unless you know what you are doing!

Sorry for that, I have a printed pin layout here. Didn't notice I shared with you a low quality one, my bad.

Thats the thing I want to get rid of the OLED screen and use the ePaper instead. ESP32 gives users the opportunity to change the SPI pins, thats why the LoRa SPI connection is different from the pre-defined one (TTGO the other competitor has the same layout also). Initially I thought Heltec made a mistake with the pinlayout, until I stumbled upon this:

"GPIO matrix and IOMUX

Most peripheral signals in ESP32 can connect directly to a specific GPIO, which is called its IOMUX pin. When a peripheral signal is routed to a pin other than its IOMUX pin, ESP32 uses the less direct GPIO matrix to make this connection.

If the driver is configured with all SPI signals set to their specific IOMUX pins (or left unconnected), it will bypass the GPIO matrix. If any SPI signal is configured to a pin other than its IOMUx pin, the driver will automatically route all the signals via the GPIO Matrix. The GPIO matrix samples all signals at 80MHz and sends them between the GPIO and the peripheral.

When the GPIO matrix is used, signals faster than 40MHz cannot propagate and the setup time of MISO is more easily violated, since the input delay of MISO signal is increased. The maximum clock frequency with GPIO Matrix is 40MHz or less, whereas using all IOMUX pins allows 80MHz."

Since I plan on using the LoRa module and since Heltec decided to change the VSPI pins, now the compromise is that VSPI operates at max 50% speed. But since LoRa operates in low frequency it is not a problem to operate alone, but connecting another module may get messy.
So my question is in the code above is that the right approach to change to the other SPI? I was wondering since you library has dependencies from Adafruit library, and that one uses also SPI. So will that change in the code above, propagate to the adafruit libary also? I am not sure if my syntax is right.

Dependency Graph
|-- 1.5.6
| |-- 1.0
|-- 3.0.7
| |-- 1.0
| |-- 1.5.6
| | |-- 1.0

Your approach for using HSPI with GxEPD2 seems correct, but you told it doesn't work.

I do not know which pins your board uses with HSPI for SCK and MOSI, and you didn't tell which pins you use.

I do not have the time to go searching for a pin diagram with HSPI SPI pins. And I do not know if HSPI is free, or if it is used with SRAM and flash.

I would recommend to share VSPI with GxEPD (separate CS), I think it uses transactions, if LoRa also uses transactions.

GxEPD uses SPI directly, not through Adafruit_GFX.