ESP32 S2 mini custom SPI with GxEPD2

For an experiment I tried do remap the SPI pins of my ESP32 S2 mini connected to my 2.13 inch Epaper display v4 from Waveshare. Using the standard SPI pins MOSI: 11 MISO: 9 SCK: 7 SS: 12 it works perfectly fine.
However if I connect the display to different pins, i get no output on the display. I read alot about custon SPI pins the last days and understood that you have to

  • establish a new SPIClass
  • define the new SPI pins
  • begin the new SPI
  • set the settings for the new SPI
  • and connect the new SPI to the display
    But whatever pins I use, or what variation of the points I try, I get no output.
    Does anyone here have experience with using different SPI pins than the standard ones and can tell me what I did wrong?

The code is very long but it is only relevant to the end of the setup section

// Display Library example for SPI e-paper panels from Dalian Good Display and boards from Waveshare.
// Requires HW SPI and Adafruit_GFX. Caution: the e-paper panels require 3.3V supply AND data lines!
//
// Display Library based on Demo Example from Good Display: http://www.e-paper-display.com/download_list/downloadcategoryid=34&isMode=false.html
//
// Author: Jean-Marc Zingg
//
// Version: see library.properties
//
// Library: https://github.com/ZinggJM/GxEPD2

#include <SPI.h>

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

// uncomment next line to use class GFX of library GFX_Root instead of Adafruit_GFX
//#include <GFX.h>
// Note: if you use this with ENABLE_GxEPD2_GFX 1:
//       uncomment it in GxEPD2_GFX.h too, or add #include <GFX.h> before any #include <GxEPD2_GFX.h>

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

#define EPD_BUSY  4
#define EPD_CS    9
#define EPD_RST   16
#define EPD_DC    17
#define EPD_SCK   5
#define EPD_MISO  -1 // Master-In Slave-Out not used, as no data from display
#define EPD_MOSI  3 // EPD DIN
SPIClass hspi(HSPI);

GxEPD2_BW<GxEPD2_213_B74, GxEPD2_213_B74::HEIGHT> display(GxEPD2_213_B74(/*CS=5*/ EPD_CS, /*DC=*/ EPD_DC, /*RST=*/ EPD_RST, /*BUSY=*/ EPD_BUSY)); // GDEM0213B74 122x250, SSD1680

#include "bitmaps/Bitmaps128x250.h" // 2.13" b/w

void setup()
{
  Serial.begin(115200);
  Serial.println();
  Serial.println("setup");
  delay(100);

  hspi.begin(EPD_SCK, EPD_MISO, EPD_MOSI, EPD_CS); // remap hspi for EPD (swap pins)
  display.epd2.selectSPI(hspi, SPISettings(4000000, MSBFIRST, SPI_MODE0));
pinMode(EPD_CS, OUTPUT);
  //display.init(115200); // default 10ms reset pulse, e.g. for bare panels with DESPI-C02
  display.init(115200, true, 2, false); // USE THIS for Waveshare boards with "clever" reset circuit, 2ms reset pulse
  //display.init(115200, true, 10, false, SPI0, SPISettings(4000000, MSBFIRST, SPI_MODE0)); // extended init method with SPI channel and/or settings selection
  if (display.pages() > 1)
  {
    delay(100);
    Serial.print("pages = "); Serial.print(display.pages()); Serial.print(" page height = "); Serial.println(display.pageHeight());
    delay(1000);
  }
  // first update should be full refresh
  helloWorld();
  delay(1000);
  // partial refresh mode can be used to full screen,
  // effective if display panel hasFastPartialUpdate
  helloFullScreenPartialMode();
  delay(1000);
  helloArduino();
  delay(1000);
  helloEpaper();
  delay(1000);
  //helloValue(123.9, 1);
  //delay(1000);
  showFont("FreeMonoBold9pt7b", &FreeMonoBold9pt7b);
  delay(1000);


  drawBitmaps();

  //return;

  if (display.epd2.hasPartialUpdate)
  {
    showPartialUpdate();
    delay(1000);
  } // else // on GDEW0154Z04 only full update available, doesn't look nice


  display.powerOff();
  deepSleepTest();
  Serial.println("setup done");
  display.end();
}

void loop()
{
}

// note for partial update window and setPartialWindow() method:
// partial update window size and position is on byte boundary in physical x direction
// the size is increased in setPartialWindow() if x or w are not multiple of 8 for even rotation, y or h for odd rotation
// see also comment in GxEPD2_BW.h, GxEPD2_3C.h or GxEPD2_GFX.h for method setPartialWindow()

const char HelloWorld[] = "Hello World!";
const char HelloArduino[] = "Hello Arduino!";
const char HelloEpaper[] = "Hello E-Paper!";

void helloWorld()
{
  //Serial.println("helloWorld");
  display.setRotation(1);
  display.setFont(&FreeMonoBold9pt7b);
  if (display.epd2.WIDTH < 104) display.setFont(0);
  display.setTextColor(GxEPD_BLACK);
  int16_t tbx, tby; uint16_t tbw, tbh;
  display.getTextBounds(HelloWorld, 0, 0, &tbx, &tby, &tbw, &tbh);
  // center bounding box by transposition of origin:
  uint16_t x = ((display.width() - tbw) / 2) - tbx;
  uint16_t y = ((display.height() - tbh) / 2) - tby;
  display.setFullWindow();
  display.firstPage();
  do
  {
    display.fillScreen(GxEPD_WHITE);
    display.setCursor(x, y);
    display.print(HelloWorld);
  }
  while (display.nextPage());
  //Serial.println("helloWorld done");
}

void helloWorldForDummies()
{
  //Serial.println("helloWorld");
  const char text[] = "Hello World!";
  // most e-papers have width < height (portrait) as native orientation, especially the small ones
  // in GxEPD2 rotation 0 is used for native orientation (most TFT libraries use 0 fix for portrait orientation)
  // set rotation to 1 (rotate right 90 degrees) to have enough space on small displays (landscape)
  display.setRotation(1);
  // select a suitable font in Adafruit_GFX
  display.setFont(&FreeMonoBold9pt7b);
  // on e-papers black on white is more pleasant to read
  display.setTextColor(GxEPD_BLACK);
  // Adafruit_GFX has a handy method getTextBounds() to determine the boundary box for a text for the actual font
  int16_t tbx, tby; uint16_t tbw, tbh; // boundary box window
  display.getTextBounds(text, 0, 0, &tbx, &tby, &tbw, &tbh); // it works for origin 0, 0, fortunately (negative tby!)
  // center bounding box by transposition of origin:
  uint16_t x = ((display.width() - tbw) / 2) - tbx;
  uint16_t y = ((display.height() - tbh) / 2) - tby;
  // full window mode is the initial mode, set it anyway
  display.setFullWindow();
  // here we use paged drawing, even if the processor has enough RAM for full buffer
  // so this can be used with any supported processor board.
  // the cost in code overhead and execution time penalty is marginal
  // tell the graphics class to use paged drawing mode
  display.firstPage();
  do
  {
    // this part of code is executed multiple times, as many as needed,
    // in case of full buffer it is executed once
    // IMPORTANT: each iteration needs to draw the same, to avoid strange effects
    // use a copy of values that might change, don't read e.g. from analog or pins in the loop!
    display.fillScreen(GxEPD_WHITE); // set the background to white (fill the buffer with value for white)
    display.setCursor(x, y); // set the postition to start printing text
    display.print(text); // print some text
    // end of part executed multiple times
  }
  // tell the graphics class to transfer the buffer content (page) to the controller buffer
  // the graphics class will command the controller to refresh to the screen when the last page has been transferred
  // returns true if more pages need be drawn and transferred
  // returns false if the last page has been transferred and the screen refreshed for panels without fast partial update
  // returns false for panels with fast partial update when the controller buffer has been written once more, to make the differential buffers equal
  // (for full buffered with fast partial update the (full) buffer is just transferred again, and false returned)
  while (display.nextPage());
  //Serial.println("helloWorld done");
}

void helloFullScreenPartialMode()
{
  //Serial.println("helloFullScreenPartialMode");
  const char fullscreen[] = "full screen update";
  const char fpm[] = "fast partial mode";
  const char spm[] = "slow partial mode";
  const char npm[] = "no partial mode";
  display.setPartialWindow(0, 0, display.width(), display.height());
  display.setRotation(1);
  display.setFont(&FreeMonoBold9pt7b);
  if (display.epd2.WIDTH < 104) display.setFont(0);
  display.setTextColor(GxEPD_BLACK);
  const char* updatemode;
  if (display.epd2.hasFastPartialUpdate)
  {
    updatemode = fpm;
  }
  else if (display.epd2.hasPartialUpdate)
  {
    updatemode = spm;
  }
  else
  {
    updatemode = npm;
  }
  // do this outside of the loop
  int16_t tbx, tby; uint16_t tbw, tbh;
  // center update text
  display.getTextBounds(fullscreen, 0, 0, &tbx, &tby, &tbw, &tbh);
  uint16_t utx = ((display.width() - tbw) / 2) - tbx;
  uint16_t uty = ((display.height() / 4) - tbh / 2) - tby;
  // center update mode
  display.getTextBounds(updatemode, 0, 0, &tbx, &tby, &tbw, &tbh);
  uint16_t umx = ((display.width() - tbw) / 2) - tbx;
  uint16_t umy = ((display.height() * 3 / 4) - tbh / 2) - tby;
  // center HelloWorld
  display.getTextBounds(HelloWorld, 0, 0, &tbx, &tby, &tbw, &tbh);
  uint16_t hwx = ((display.width() - tbw) / 2) - tbx;
  uint16_t hwy = ((display.height() - tbh) / 2) - tby;
  display.firstPage();
  do
  {
    display.fillScreen(GxEPD_WHITE);
    display.setCursor(hwx, hwy);
    display.print(HelloWorld);
    display.setCursor(utx, uty);
    display.print(fullscreen);
    display.setCursor(umx, umy);
    display.print(updatemode);
  }
  while (display.nextPage());
  //Serial.println("helloFullScreenPartialMode done");
}

void helloArduino()
{
  //Serial.println("helloArduino");
  display.setRotation(1);
  display.setFont(&FreeMonoBold9pt7b);
  if (display.epd2.WIDTH < 104) display.setFont(0);
  display.setTextColor(display.epd2.hasColor ? GxEPD_RED : GxEPD_BLACK);
  int16_t tbx, tby; uint16_t tbw, tbh;
  // align with centered HelloWorld
  display.getTextBounds(HelloWorld, 0, 0, &tbx, &tby, &tbw, &tbh);
  uint16_t x = ((display.width() - tbw) / 2) - tbx;
  // height might be different
  display.getTextBounds(HelloArduino, 0, 0, &tbx, &tby, &tbw, &tbh);
  uint16_t y = ((display.height() / 4) - tbh / 2) - tby; // y is base line!
  // make the window big enough to cover (overwrite) descenders of previous text
  uint16_t wh = FreeMonoBold9pt7b.yAdvance;
  uint16_t wy = (display.height() / 4) - wh / 2;
  display.setPartialWindow(0, wy, display.width(), wh);
  display.firstPage();
  do
  {
    display.fillScreen(GxEPD_WHITE);
    //display.drawRect(x, y - tbh, tbw, tbh, GxEPD_BLACK);
    display.setCursor(x, y);
    display.print(HelloArduino);
  }
  while (display.nextPage());
  delay(1000);
  //Serial.println("helloArduino done");
}

void helloEpaper()
{
  //Serial.println("helloEpaper");
  display.setRotation(1);
  display.setFont(&FreeMonoBold9pt7b);
  if (display.epd2.WIDTH < 104) display.setFont(0);
  display.setTextColor(display.epd2.hasColor ? GxEPD_RED : GxEPD_BLACK);
  int16_t tbx, tby; uint16_t tbw, tbh;
  // align with centered HelloWorld
  display.getTextBounds(HelloWorld, 0, 0, &tbx, &tby, &tbw, &tbh);
  uint16_t x = ((display.width() - tbw) / 2) - tbx;
  // height might be different
  display.getTextBounds(HelloEpaper, 0, 0, &tbx, &tby, &tbw, &tbh);
  uint16_t y = ((display.height() * 3 / 4) - tbh / 2) - tby; // y is base line!
  // make the window big enough to cover (overwrite) descenders of previous text
  uint16_t wh = FreeMonoBold9pt7b.yAdvance;
  uint16_t wy = (display.height() * 3 / 4) - wh / 2;
  display.setPartialWindow(0, wy, display.width(), wh);
  display.firstPage();
  do
  {
    display.fillScreen(GxEPD_WHITE);
    display.setCursor(x, y);
    display.print(HelloEpaper);
  }
  while (display.nextPage());
  //Serial.println("helloEpaper done");
}




#if defined(ESP8266) || defined(ESP32)
#include <StreamString.h>
#define PrintString StreamString
#else
class PrintString : public Print, public String
{
  public:
    size_t write(uint8_t data) override
    {
      return concat(char(data));
    };
};
#endif

void helloValue(double v, int digits)
{
  //Serial.println("helloValue");
  display.setRotation(1);
  display.setFont(&FreeMonoBold9pt7b);
  display.setTextColor(display.epd2.hasColor ? GxEPD_RED : GxEPD_BLACK);
  PrintString valueString;
  valueString.print(v, digits);
  int16_t tbx, tby; uint16_t tbw, tbh;
  display.getTextBounds(valueString, 0, 0, &tbx, &tby, &tbw, &tbh);
  uint16_t x = ((display.width() - tbw) / 2) - tbx;
  uint16_t y = ((display.height() * 3 / 4) - tbh / 2) - tby; // y is base line!
  // show what happens, if we use the bounding box for partial window
  uint16_t wx = (display.width() - tbw) / 2;
  uint16_t wy = ((display.height() * 3 / 4) - tbh / 2);
  display.setPartialWindow(wx, wy, tbw, tbh);
  display.firstPage();
  do
  {
    display.fillScreen(GxEPD_WHITE);
    display.setCursor(x, y);
    display.print(valueString);
  }
  while (display.nextPage());
  delay(2000);
  // make the partial window big enough to cover the previous text
  uint16_t ww = tbw; // remember window width
  display.getTextBounds(HelloEpaper, 0, 0, &tbx, &tby, &tbw, &tbh);
  // adjust, because HelloEpaper was aligned, not centered (could calculate this to be precise)
  ww = max(ww, uint16_t(tbw + 12)); // 12 seems ok
  wx = (display.width() - tbw) / 2;
  // make the window big enough to cover (overwrite) descenders of previous text
  uint16_t wh = FreeMonoBold9pt7b.yAdvance;
  wy = (display.height() * 3 / 4) - wh / 2;
  display.setPartialWindow(wx, wy, ww, wh);
  // alternately use the whole width for partial window
  //display.setPartialWindow(0, wy, display.width(), wh);
  display.firstPage();
  do
  {
    display.fillScreen(GxEPD_WHITE);
    display.setCursor(x, y);
    display.print(valueString);
  }
  while (display.nextPage());
  //Serial.println("helloValue done");
}

void deepSleepTest()
{
  //Serial.println("deepSleepTest");
  const char hibernating[] = "hibernating ...";
  const char wokeup[] = "woke up";
  const char from[] = "from deep sleep";
  const char again[] = "again";
  display.setRotation(1);
  display.setFont(&FreeMonoBold9pt7b);
  if (display.epd2.WIDTH < 104) display.setFont(0);
  display.setTextColor(GxEPD_BLACK);
  int16_t tbx, tby; uint16_t tbw, tbh;
  // center text
  display.getTextBounds(hibernating, 0, 0, &tbx, &tby, &tbw, &tbh);
  uint16_t x = ((display.width() - tbw) / 2) - tbx;
  uint16_t y = ((display.height() - tbh) / 2) - tby;
  display.setFullWindow();
  display.firstPage();
  do
  {
    display.fillScreen(GxEPD_WHITE);
    display.setCursor(x, y);
    display.print(hibernating);
  }
  while (display.nextPage());
  display.hibernate();
  delay(5000);
  display.getTextBounds(wokeup, 0, 0, &tbx, &tby, &tbw, &tbh);
  uint16_t wx = (display.width() - tbw) / 2;
  uint16_t wy = ((display.height() / 3) - tbh / 2) - tby; // y is base line!
  display.getTextBounds(from, 0, 0, &tbx, &tby, &tbw, &tbh);
  uint16_t fx = (display.width() - tbw) / 2;
  uint16_t fy = ((display.height() * 2 / 3) - tbh / 2) - tby; // y is base line!
  display.firstPage();
  do
  {
    display.fillScreen(GxEPD_WHITE);
    display.setCursor(wx, wy);
    display.print(wokeup);
    display.setCursor(fx, fy);
    display.print(from);
  }
  while (display.nextPage());
  delay(5000);
  display.getTextBounds(hibernating, 0, 0, &tbx, &tby, &tbw, &tbh);
  uint16_t hx = (display.width() - tbw) / 2;
  uint16_t hy = ((display.height() / 3) - tbh / 2) - tby; // y is base line!
  display.getTextBounds(again, 0, 0, &tbx, &tby, &tbw, &tbh);
  uint16_t ax = (display.width() - tbw) / 2;
  uint16_t ay = ((display.height() * 2 / 3) - tbh / 2) - tby; // y is base line!
  display.firstPage();
  do
  {
    display.fillScreen(GxEPD_WHITE);
    display.setCursor(hx, hy);
    display.print(hibernating);
    display.setCursor(ax, ay);
    display.print(again);
  }
  while (display.nextPage());
  display.hibernate();
  //Serial.println("deepSleepTest done");
}


void showFont(const char name[], const GFXfont* f)
{
  display.setFullWindow();
  display.setRotation(0);
  display.setTextColor(GxEPD_BLACK);
  display.firstPage();
  do
  {
    drawFont(name, f);
  }
  while (display.nextPage());
}

void drawFont(const char name[], const GFXfont* f)
{
  //display.setRotation(0);
  display.fillScreen(GxEPD_WHITE);
  display.setTextColor(GxEPD_BLACK);
  display.setFont(f);
  display.setCursor(0, 0);
  display.println();
  display.println(name);
  display.println(" !\"#$%&'()*+,-./");
  display.println("0123456789:;<=>?");
  display.println("@ABCDEFGHIJKLMNO");
  display.println("PQRSTUVWXYZ[\\]^_");
  if (display.epd2.hasColor)
  {
    display.setTextColor(GxEPD_RED);
  }
  display.println("`abcdefghijklmno");
  display.println("pqrstuvwxyz{|}~ ");
}

// note for partial update window and setPartialWindow() method:
// partial update window size and position is on byte boundary in physical x direction
// the size is increased in setPartialWindow() if x or w are not multiple of 8 for even rotation, y or h for odd rotation
// see also comment in GxEPD2_BW.h, GxEPD2_3C.h or GxEPD2_GFX.h for method setPartialWindow()
// showPartialUpdate() purposely uses values that are not multiples of 8 to test this

void showPartialUpdate()
{
  // some useful background
  helloWorld();
  // use asymmetric values for test
  uint16_t box_x = 10;
  uint16_t box_y = 15;
  uint16_t box_w = 70;
  uint16_t box_h = 20;
  uint16_t cursor_y = box_y + box_h - 6;
  if (display.epd2.WIDTH < 104) cursor_y = box_y + 6;
  float value = 13.95;
  uint16_t incr = display.epd2.hasFastPartialUpdate ? 1 : 3;
  display.setFont(&FreeMonoBold9pt7b);
  if (display.epd2.WIDTH < 104) display.setFont(0);
  display.setTextColor(GxEPD_BLACK);
  // show where the update box is
  for (uint16_t r = 0; r < 4; r++)
  {
    display.setRotation(r);
    display.setPartialWindow(box_x, box_y, box_w, box_h);
    display.firstPage();
    do
    {
      display.fillRect(box_x, box_y, box_w, box_h, GxEPD_BLACK);
      //display.fillScreen(GxEPD_BLACK);
    }
    while (display.nextPage());
    delay(2000);
    display.firstPage();
    do
    {
      display.fillRect(box_x, box_y, box_w, box_h, GxEPD_WHITE);
    }
    while (display.nextPage());
    delay(1000);
  }
  //return;
  // show updates in the update box
  for (uint16_t r = 0; r < 4; r++)
  {
    display.setRotation(r);
    display.setPartialWindow(box_x, box_y, box_w, box_h);
    for (uint16_t i = 1; i <= 10; i += incr)
    {
      display.firstPage();
      do
      {
        display.fillRect(box_x, box_y, box_w, box_h, GxEPD_WHITE);
        display.setCursor(box_x, cursor_y);
        display.print(value * i, 2);
      }
      while (display.nextPage());
      delay(500);
    }
    delay(1000);
    display.firstPage();
    do
    {
      display.fillRect(box_x, box_y, box_w, box_h, GxEPD_WHITE);
    }
    while (display.nextPage());
    delay(1000);
  }
}

void drawBitmaps()
{
  display.setFullWindow();


#ifdef _GxBitmaps128x250_H_
  drawBitmaps128x250();
#endif
#ifdef _GxBitmaps128x296_H_
  drawBitmaps128x296();
#endif

  // 3-color
#ifdef _GxBitmaps3c128x250_H_
  drawBitmaps3c128x250();
#endif
#ifdef _GxBitmaps3c128x296_H_
  drawBitmaps3c128x296();
#endif
#ifdef _GxBitmaps3c152x296_H_
  drawBitmaps3c152x296();
#endif

}




#ifdef _GxBitmaps128x250_H_
void drawBitmaps128x250()
{
#if !defined(__AVR)
  const unsigned char* bitmaps[] =
  {
    Bitmap128x250_1, logo128x250, first128x250, second128x250, third128x250
  };
#else
  const unsigned char* bitmaps[] =
  {
    Bitmap128x250_1, logo128x250, first128x250, second128x250, third128x250
  };
#endif
  if ((display.epd2.WIDTH == 128) && (display.epd2.HEIGHT == 250) && !display.epd2.hasColor)
  {
    bool m = display.mirror(true);
    for (uint16_t i = 0; i < sizeof(bitmaps) / sizeof(char*); i++)
    {
      display.firstPage();
      do
      {
        display.fillScreen(GxEPD_WHITE);
        display.drawInvertedBitmap(0, 0, bitmaps[i], 128, 250, GxEPD_BLACK);
      }
      while (display.nextPage());
      delay(2000);
    }
    display.mirror(m);
  }
}
#endif

#ifdef _GxBitmaps128x296_H_
void drawBitmaps128x296()
{
#if !defined(__AVR)
  const unsigned char* bitmaps[] =
  {
    Bitmap128x296_1, logo128x296, first128x296, second128x296, third128x296
  };
#else
  const unsigned char* bitmaps[] =
  {
    Bitmap128x296_1, logo128x296 //, first128x296, second128x296, third128x296
  };
#endif
  if ((display.epd2.WIDTH == 128) && (display.epd2.HEIGHT == 296) && !display.epd2.hasColor)
  {
    bool m = display.mirror(true);
    for (uint16_t i = 0; i < sizeof(bitmaps) / sizeof(char*); i++)
    {
      display.firstPage();
      do
      {
        display.fillScreen(GxEPD_WHITE);
        display.drawInvertedBitmap(0, 0, bitmaps[i], 128, 296, GxEPD_BLACK);
      }
      while (display.nextPage());
      delay(2000);
    }
    display.mirror(m);
  }
}
#endif



struct bitmap_pair
{
  const unsigned char* black;
  const unsigned char* red;
};


#ifdef _GxBitmaps3c128x250_H_
void drawBitmaps3c128x250()
{
  if ((display.epd2.WIDTH == 128) && (display.epd2.HEIGHT == 250) && display.epd2.hasColor)
  {
    bool mirrored = display.mirror(true);
    display.firstPage();
    do
    {
      display.fillScreen(GxEPD_WHITE);
      display.drawInvertedBitmap(0, 0, Bitmap3c128x250_1_black, 128, 250, GxEPD_BLACK);
      display.drawInvertedBitmap(0, 0, Bitmap3c128x250_1_red, 128, 250, GxEPD_RED);
    }
    while (display.nextPage());
    delay(2000);
#if !defined(__AVR)
    display.firstPage();
    do
    {
      display.fillScreen(GxEPD_WHITE);
      display.drawInvertedBitmap(0, 0, Bitmap3c128x250_2_black, 128, 250, GxEPD_BLACK);
      display.drawBitmap(0, 0, Bitmap3c128x250_2_red, 128, 250, GxEPD_RED);
    }
    while (display.nextPage());
    delay(2000);
#endif
    display.mirror(mirrored);
  }
}
#endif

#ifdef _GxBitmaps3c128x296_H_
void drawBitmaps3c128x296()
{
#if !defined(__AVR)
  bitmap_pair bitmap_pairs[] =
  {
    {Bitmap3c128x296_1_black, Bitmap3c128x296_1_red},
    {Bitmap3c128x296_2_black, Bitmap3c128x296_2_red},
    {WS_Bitmap3c128x296_black, WS_Bitmap3c128x296_red}
  };
#else
  bitmap_pair bitmap_pairs[] =
  {
    //{Bitmap3c128x296_1_black, Bitmap3c128x296_1_red},
    //{Bitmap3c128x296_2_black, Bitmap3c128x296_2_red},
    {WS_Bitmap3c128x296_black, WS_Bitmap3c128x296_red}
  };
#endif
  if ((display.epd2.WIDTH == 128) && (display.epd2.HEIGHT == 296) && display.epd2.hasColor)
  {
    for (uint16_t i = 0; i < sizeof(bitmap_pairs) / sizeof(bitmap_pair); i++)
    {
      display.firstPage();
      do
      {
        display.fillScreen(GxEPD_WHITE);
        display.drawInvertedBitmap(0, 0, bitmap_pairs[i].black, 128, 296, GxEPD_BLACK);
        if (bitmap_pairs[i].red == WS_Bitmap3c128x296_red)
        {
          display.drawInvertedBitmap(0, 0, bitmap_pairs[i].red, 128, 296, GxEPD_RED);
        }
        else display.drawBitmap(0, 0, bitmap_pairs[i].red, 128, 296, GxEPD_RED);
      }
      while (display.nextPage());
      delay(2000);
    }
  }
}
#endif



That i have.

That may be more complex, You see, i think you more or less did it right, but it does depend on how the library accepts the passing of the SPI object, so for that we need to look at that relevant part of the code.
the init function actually calls the 'selectSPI as well.

void init(uint32_t serial_diag_bitrate, bool initial, uint16_t reset_duration, bool pulldown_rst_mode, SPIClass& spi, SPISettings spi_settings)
    {
      epd2.selectSPI(spi, spi_settings);
      epd2.init(serial_diag_bitrate, initial, reset_duration, pulldown_rst_mode);
      _using_partial_mode = false;
      _current_page = 0;
      setFullWindow();
    }

so i would try the init like this

display.init(115200, true, 10, false, hspi, SPISettings(4000000, MSBFIRST, SPI_MODE0)); // extended init method with SPI channel and/or settings selection

and leave both of these out

  display.epd2.selectSPI(hspi, SPISettings(4000000, MSBFIRST, SPI_MODE0));
  display.init(115200, true, 2, false);

And see how you go.
What pins did you choose ?

The other thing is that the begin() method from the SPI class overrules any previous pin assignments if they are not included, and i did see this line in the library code (so it's there at least once)

 _pSPIx->begin();

inside of init()

void GxEPD2_EPD::init(uint32_t serial_diag_bitrate, bool initial, uint16_t reset_duration, bool pulldown_rst_mode)
{
  _initial_write = initial;
  _initial_refresh = initial;
  _pulldown_rst_mode = pulldown_rst_mode;
  _power_is_on = false;
  _using_partial_mode = false;
  _hibernating = false;
  _init_display_done = false;
  _reset_duration = reset_duration;
  if (serial_diag_bitrate > 0)
  {
    Serial.begin(serial_diag_bitrate);
    _diag_enabled = true;
  }
  if (_cs >= 0)
  {
    digitalWrite(_cs, HIGH); // preset (less glitch for any analyzer)
    pinMode(_cs, OUTPUT);
    digitalWrite(_cs, HIGH); // set (needed e.g. for RP2040)
  }
  if (_dc >= 0)
  {
    digitalWrite(_dc, HIGH); // preset (less glitch for any analyzer)
    pinMode(_dc, OUTPUT);
    digitalWrite(_dc, HIGH); // set (needed e.g. for RP2040)
  }
  _reset();
  if (_busy >= 0)
  {
    pinMode(_busy, INPUT);
  }
  _pSPIx->begin();
  if (_busy == MISO) // may be overridden
  {
    pinMode(_busy, INPUT);
  }
  if (_dc == MISO) // may be overridden, TTGO T5 V2.66
  {
    pinMode(_dc, OUTPUT);
  }
  if (_cs == MISO) // may be overridden
  {
    pinMode(_cs, INPUT);
  }
}
type or paste code here

which makes me come to the conclusion that the library is not written for boards with custom pin assignments.
what you could do (and what i have done in MIDI.h) is exclude the begin() method for any esp32 board, by adding a compiler directive to the .cpp file.

#if (!defined (ARDUINO_ARCH_ESP32)) && (!defined(ESP32))
  _pSPIx->begin();
#endif

Any call to SPI.begin() without the custom pin assignments will revert them back to their original ones, but the call to begin() with custom pins is platform specific and many libraries will not have that implemented.

Unfortunately, you see that characteristic with a lot of Arduino libraries as well as questions in the forum from people trying to make their own libraries.

They all want to put the initialization for some class (typically an I/O class like Stream or SPIClass) inside of their library to make it “easier” for the user. But it really makes it harder because, as you mentioned, the detailed implementation and use of special functions in classes derived from the abstract class is hardware-specific. It’s much more general to have the user initialize the I/O object and pass into the library a reference to the highest possible parent class. For example, let the user call Serial2.begin() with the parameters they want and then pass in a reference to Stream.

I tried it, but unfortunately it does not work

I chose those custom pins for hspi:
#define EPD_BUSY 4
#define EPD_CS 9
#define EPD_RST 16
#define EPD_DC 17
#define EPD_SCK 5
#define EPD_MISO -1 // Master-In Slave-Out not used, as no data from display
#define EPD_MOSI 3 // EPD DIN

I just went back to the original example file (mine was edited quite a bit to reduce the length of code) and found those lines:

// uncomment next line to use HSPI for EPD (and e.g VSPI for SD), e.g. with Waveshare ESP32 Driver Board
//#define USE_HSPI_FOR_EPD

and

#if defined(ESP32) && defined(USE_HSPI_FOR_EPD)

SPIClass hspi(HSPI);

#endif

and in setup

#if defined(ESP32) && defined(USE_HSPI_FOR_EPD)
  hspi.begin(13, 12, 14, 15); // remap hspi for EPD (swap pins)
  display.epd2.selectSPI(hspi, SPISettings(4000000, MSBFIRST, SPI_MODE0));
#endif
  //display.init(115200); // default 10ms reset pulse, e.g. for bare panels with DESPI-C02
  display.init(115200, true, 2, false); // USE THIS for Waveshare boards with "clever" reset circuit, 2ms reset pulse

So I tried putting those lines back into the code to see if it works, but unfortunately it did not. Then I went back to the original example file, uncommented the line for HSPI and just put my pins in, but it did not work too...
The parts of the original code give me the impression that the library should work with custom pins, I'm just wondering why they do not in my case since its working so good with the standard spi pins

Yes that does appear to be the case, but if it doesn't work, that may not be easy to fix.
The ESP32-S2 is the 'yet another varaint' of the ESP32, and the architectures may make them a bit different, also

#if defined(ESP32)

may not be applicable in this case, have you tried changing it for ?

#if defined (ARDUINO_ARCH_ESP32)

those compiler directives may serve a purpose depending on which type of code the library is included in, Arduino is not the only way to program an ESP32.

One thing to consider is always, how much energy do you wish to put into getting the screen to respond to those particular pins.

For an experiment it sounds like it is not worth all that much energy, Even when faced with these sort of things in an actual project, I think i would start looking for a way around this. I use an ESP32 using 2 SPI ports, one for an SD-card and one for an Ethernet Card, It was easy enough to remap the pins for the SD-card, but the ethernet card library did not allow for passing of the SPI object, so i connected it with default pins, and mapped all of the rest around that.

A failed result of an experiment is also a result and does not mean that in the future for some reason the result can not change into a success.

Yes I have tried, I've even taken commented the "#if defined" and "endif" lines out and made it completely fit to the ESP. It might be the case that, as you said earlier, it is confusion the SPI configurations between the main file and the library ones. So I guess either I find a way to debug the SPI to find out which pins the MCU ultimately uses, or I have to study how I can edit the library somehow.

The reason I really want it to work is, because I'm currently trying to design my own ESP32 (S3) PCB. It is finished mostly but I'm afraid of ordering it because I can't get the SPI running on my ESP 32 S2.
Let me show you a picture of my PCB:


The antenna needs to be on top which puts the standard SPI pins on the bottom. The display connector needs to be on the right because of the flex cable of the epaper display. If I used the SPI pins on the bottom, it would mess the whole design up and I would have to root too much through the bottom PCB layer. The way it is now, the pins go straight from the right to the connector.

After all, the manufacturer of the ESP Lineup advertises that the SPI pins are reprogrammable, so it needs to work somehow :smiley:

I haven't looked at the library's source code as deeply as @Deva_Rishi. But, it seems to me the easiest thing to do (given you don't want to modify the PWB) would be to make your own copy of it. Then modify to remove it's built-in initialization of the SPIClass object, initialize it in your setup() code, and then pass it in by reference.

that is also a perfectly good method, it is quite an extensive library supporting different screens, which usually obfuscates (i love that word) what is used when and how, because the code gets divider over different files and classes are calling other classes. I tend to go for the laziest option unless there is no way around that somehow, like PCB's that are made already or something. If the project hasn't really started yet, it makes sense to just use the default pins, unless there is an actual reason not to. (XY ?)

But I imagine that setting up of the SPIClass object is only done in a few places.

Yes it is but it will be a fair search, and well do you want to do it ? or does the OP think he can do it, It's part XY for me if there is no actual goal other than the exercise.

Or contact the Author on github, start a discussion there and if you can prove an issue, open one.

So I did the following for now:
deleted the parts of the code and the files:

//#include <GxEPD2_3C.h>
//#include <GxEPD2_4C.h>
//#include <GxEPD2_7C.h>
//#include "GxEPD2_display_selection.h"
//#include "GxEPD2_display_selection_added.h"
//#include "GxEPD2_display_selection_new_style.h"
GxEPD2_wiring_examples
GxEPD2_selection_check

Then I deleted all the bitmaps from the .ino file

Then I copied the files from the library folder to the project folder:

GxEPD2.h and renamed it to GxEPD2Test.h
GxEPD2_BW.h and renamed it to GxEPD2_BWTest.h so the original librar isnt called accidenntally
GxEPD2_EPD.cpp
GxEPD2_EPD.h and renamed it to GxEPD2_EPDTest.h
GxEPD2_GFX.h (is not called though)

So there are SPI settings in the following files now:
GxEPD2Test.h:
This file has no special SPI parts
GxEPD2_EPDTest.h:

    void selectSPI(SPIClass& spi, SPISettings spi_settings);
and further down
    SPIClass* _pSPIx;
    SPISettings _spi_settings;

I guess these are not the problem?
And now what seems to be the problem file:
GxEPD2_BWTest.h which has the parts:

    void init(uint32_t serial_diag_bitrate, bool initial, uint16_t reset_duration, bool pulldown_rst_mode, SPIClass& spi, SPISettings spi_settings)
    {
      epd2.selectSPI(spi, spi_settings);
      epd2.init(serial_diag_bitrate, initial, reset_duration, pulldown_rst_mode);
      _using_partial_mode = false;
      _current_page = 0;
      setFullWindow();
    }

I changed the spi to hspi, so the result is:

    void init(uint32_t serial_diag_bitrate, bool initial, uint16_t reset_duration, bool pulldown_rst_mode, SPIClass& hspi, SPISettings spi_settings)
    {
      epd2.selectSPI(hspi, spi_settings);
      epd2.init(serial_diag_bitrate, initial, reset_duration, pulldown_rst_mode);
      _using_partial_mode = false;
      _current_page = 0;
      setFullWindow();
    }

and deleted all the

#if __has_include("epd/GxEPD2_102.h")
#include "epd/GxEPD2_102.h"
#endif

except

#if __has_include("epd/GxEPD2_213_B74.h")
#include "epd/GxEPD2_213_B74.h"
#endif

it would not compile so I had to make another folder named epd and copied GxEPD2_213_B74.h and GxEPD2_213_B74.cpp of the original library.
and change
#include "../GxEPD2_EPD.h"
to
#include "../GxEPD2_EPDTest.h"

and now I get tons of errors:

c:/users/lenov/appdata/local/arduino15/packages/esp32/tools/xtensa-esp32s2-elf-gcc/esp-12.2.0_20230208/bin/../lib/gcc/xtensa-esp32s2-elf/12.2.0/../../../../xtensa-esp32s2-elf/bin/ld.exe: C:\Users\lenov\AppData\Local\Temp\arduino\sketches\FEB401EE6DBC456886826F30EDA50363\sketch\epepaerlibrary.ino.cpp.o:(.literal._ZN9GxEPD2_BWI14GxEPD2_213_B74Lt250EE8nextPageEv[_ZN9GxEPD2_BWI14GxEPD2_213_B74Lt250EE8nextPageEv]+0x0): undefined reference to `GxEPD2_213_B74::writeImage(unsigned char const*, short, short, short, short, bool, bool, bool)'
c:/users/lenov/appdata/local/arduino15/packages/esp32/tools/xtensa-esp32s2-elf-gcc/esp-12.2.0_20230208/bin/../lib/gcc/xtensa-esp32s2-elf/12.2.0/../../../../xtensa-esp32s2-elf/bin/ld.exe: C:\Users\lenov\AppData\Local\Temp\arduino\sketches\FEB401EE6DBC456886826F30EDA50363\sketch\epepaerlibrary.ino.cpp.o:(.literal._ZN9GxEPD2_BWI14GxEPD2_213_B74Lt250EE8nextPageEv[_ZN9GxEPD2_BWI14GxEPD2_213_B74Lt250EE8nextPageEv]+0x4): undefined reference to `GxEPD2_213_B74::refresh(short, short, short, short)'
c:/users/lenov/appdata/local/arduino15/packages/esp32/tools/xtensa-esp32s2-elf-gcc/esp-12.2.0_20230208/bin/../lib/gcc/xtensa-esp32s2-elf/12.2.0/../../../../xtensa-esp32s2-elf/bin/ld.exe: C:\Users\lenov\AppData\Local\Temp\arduino\sketches\FEB401EE6DBC456886826F30EDA50363\sketch\epepaerlibrary.ino.cpp.o:(.literal._ZN9GxEPD2_BWI14GxEPD2_213_B74Lt250EE8nextPageEv[_ZN9GxEPD2_BWI14GxEPD2_213_B74Lt250EE8nextPageEv]+0x8): undefined reference to `GxEPD2_213_B74::writeImageAgain(unsigned char const*, short, short, short, short, bool, bool, bool)'
c:/users/lenov/appdata/local/arduino15/packages/esp32/tools/xtensa-esp32s2-elf-gcc/esp-12.2.0_20230208/bin/../lib/gcc/xtensa-esp32s2-elf/12.2.0/../../../../xtensa-esp32s2-elf/bin/ld.exe: C:\Users\lenov\AppData\Local\Temp\arduino\sketches\FEB401EE6DBC456886826F30EDA50363\sketch\epepaerlibrary.ino.cpp.o:(.literal._ZN9GxEPD2_BWI14GxEPD2_213_B74Lt250EE8nextPageEv[_ZN9GxEPD2_BWI14GxEPD2_213_B74Lt250EE8nextPageEv]+0xc): undefined reference to `GxEPD2_213_B74::writeImageForFullRefresh(unsigned char const*, short, short, short, short, bool, bool, bool)'
c:/users/lenov/appdata/local/arduino15/packages/esp32/tools/xtensa-esp32s2-elf-gcc/esp-12.2.0_20230208/bin/../lib/gcc/xtensa-esp32s2-elf/12.2.0/../../../../xtensa-esp32s2-elf/bin/ld.exe: C:\Users\lenov\AppData\Local\Temp\arduino\sketches\FEB401EE6DBC456886826F30EDA50363\sketch\epepaerlibrary.ino.cpp.o:(.literal._ZN9GxEPD2_BWI14GxEPD2_213_B74Lt250EE8nextPageEv[_ZN9GxEPD2_BWI14GxEPD2_213_B74Lt250EE8nextPageEv]+0x10): undefined reference to `GxEPD2_213_B74::refresh(bool)'
c:/users/lenov/appdata/local/arduino15/packages/esp32/tools/xtensa-esp32s2-elf-gcc/esp-12.2.0_20230208/bin/../lib/gcc/xtensa-esp32s2-elf/12.2.0/../../../../xtensa-esp32s2-elf/bin/ld.exe: C:\Users\lenov\AppData\Local\Temp\arduino\sketches\FEB401EE6DBC456886826F30EDA50363\sketch\epepaerlibrary.ino.cpp.o:(.literal._ZN9GxEPD2_BWI14GxEPD2_213_B74Lt250EE8nextPageEv[_ZN9GxEPD2_BWI14GxEPD2_213_B74Lt250EE8nextPageEv]+0x14): undefined reference to `GxEPD2_213_B74::powerOff()'
c:/users/lenov/appdata/local/arduino15/packages/esp32/tools/xtensa-esp32s2-elf-gcc/esp-12.2.0_20230208/bin/../lib/gcc/xtensa-esp32s2-elf/12.2.0/../../../../xtensa-esp32s2-elf/bin/ld.exe: C:\Users\lenov\AppData\Local\Temp\arduino\sketches\FEB401EE6DBC456886826F30EDA50363\sketch\epepaerlibrary.ino.cpp.o:(.literal._Z13deepSleepTestv+0x1c): undefined reference to `GxEPD2_213_B74::hibernate()'
c:/users/lenov/appdata/local/arduino15/packages/esp32/tools/xtensa-esp32s2-elf-gcc/esp-12.2.0_20230208/bin/../lib/gcc/xtensa-esp32s2-elf/12.2.0/../../../../xtensa-esp32s2-elf/bin/ld.exe: C:\Users\lenov\AppData\Local\Temp\arduino\sketches\FEB401EE6DBC456886826F30EDA50363\sketch\epepaerlibrary.ino.cpp.o:(.literal.startup._GLOBAL__sub_I_hspi+0x4): undefined reference to `vtable for GxEPD2_213_B74'
c:/users/lenov/appdata/local/arduino15/packages/esp32/tools/xtensa-esp32s2-elf-gcc/esp-12.2.0_20230208/bin/../lib/gcc/xtensa-esp32s2-elf/12.2.0/../../../../xtensa-esp32s2-elf/bin/ld.exe: C:\Users\lenov\AppData\Local\Temp\arduino\sketches\FEB401EE6DBC456886826F30EDA50363\sketch\epepaerlibrary.ino.cpp.o:(.literal.startup._GLOBAL__sub_I_hspi+0xc): undefined reference to `GxEPD2_213_B74::GxEPD2_213_B74(short, short, short, short)'
c:/users/lenov/appdata/local/arduino15/packages/esp32/tools/xtensa-esp32s2-elf-gcc/esp-12.2.0_20230208/bin/../lib/gcc/xtensa-esp32s2-elf/12.2.0/../../../../xtensa-esp32s2-elf/bin/ld.exe: C:\Users\lenov\AppData\Local\Temp\arduino\sketches\FEB401EE6DBC456886826F30EDA50363\sketch\epepaerlibrary.ino.cpp.o: in function `GxEPD2_BW<GxEPD2_213_B74, (unsigned short)250>::nextPage()':
C:\Users\lenov\Desktop\epepaerlibrary/GxEPD2_BWTest.h:257: undefined reference to `GxEPD2_213_B74::writeImage(unsigned char const*, short, short, short, short, bool, bool, bool)'
c:/users/lenov/appdata/local/arduino15/packages/esp32/tools/xtensa-esp32s2-elf-gcc/esp-12.2.0_20230208/bin/../lib/gcc/xtensa-esp32s2-elf/12.2.0/../../../../xtensa-esp32s2-elf/bin/ld.exe: C:\Users\lenov\Desktop\epepaerlibrary/GxEPD2_BWTest.h:258: undefined reference to `GxEPD2_213_B74::refresh(short, short, short, short)'
c:/users/lenov/appdata/local/arduino15/packages/esp32/tools/xtensa-esp32s2-elf-gcc/esp-12.2.0_20230208/bin/../lib/gcc/xtensa-esp32s2-elf/12.2.0/../../../../xtensa-esp32s2-elf/bin/ld.exe: C:\Users\lenov\Desktop\epepaerlibrary/GxEPD2_BWTest.h:261: undefined reference to `GxEPD2_213_B74::writeImageAgain(unsigned char const*, short, short, short, short, bool, bool, bool)'
c:/users/lenov/appdata/local/arduino15/packages/esp32/tools/xtensa-esp32s2-elf-gcc/esp-12.2.0_20230208/bin/../lib/gcc/xtensa-esp32s2-elf/12.2.0/../../../../xtensa-esp32s2-elf/bin/ld.exe: C:\Users\lenov\Desktop\epepaerlibrary/GxEPD2_BWTest.h:267: undefined reference to `GxEPD2_213_B74::writeImageForFullRefresh(unsigned char const*, short, short, short, short, bool, bool, bool)'
c:/users/lenov/appdata/local/arduino15/packages/esp32/tools/xtensa-esp32s2-elf-gcc/esp-12.2.0_20230208/bin/../lib/gcc/xtensa-esp32s2-elf/12.2.0/../../../../xtensa-esp32s2-elf/bin/ld.exe: C:\Users\lenov\Desktop\epepaerlibrary/GxEPD2_BWTest.h:268: undefined reference to `GxEPD2_213_B74::refresh(bool)'
c:/users/lenov/appdata/local/arduino15/packages/esp32/tools/xtensa-esp32s2-elf-gcc/esp-12.2.0_20230208/bin/../lib/gcc/xtensa-esp32s2-elf/12.2.0/../../../../xtensa-esp32s2-elf/bin/ld.exe: C:\Users\lenov\Desktop\epepaerlibrary/GxEPD2_BWTest.h:271: undefined reference to `GxEPD2_213_B74::writeImageAgain(unsigned char const*, short, short, short, short, bool, bool, bool)'
c:/users/lenov/appdata/local/arduino15/packages/esp32/tools/xtensa-esp32s2-elf-gcc/esp-12.2.0_20230208/bin/../lib/gcc/xtensa-esp32s2-elf/12.2.0/../../../../xtensa-esp32s2-elf/bin/ld.exe: C:\Users\lenov\Desktop\epepaerlibrary/GxEPD2_BWTest.h:337: undefined reference to `GxEPD2_213_B74::powerOff()'
c:/users/lenov/appdata/local/arduino15/packages/esp32/tools/xtensa-esp32s2-elf-gcc/esp-12.2.0_20230208/bin/../lib/gcc/xtensa-esp32s2-elf/12.2.0/../../../../xtensa-esp32s2-elf/bin/ld.exe: C:\Users\lenov\Desktop\epepaerlibrary/GxEPD2_BWTest.h:290: undefined reference to `GxEPD2_213_B74::writeImage(unsigned char const*, short, short, short, short, bool, bool, bool)'
c:/users/lenov/appdata/local/arduino15/packages/esp32/tools/xtensa-esp32s2-elf-gcc/esp-12.2.0_20230208/bin/../lib/gcc/xtensa-esp32s2-elf/12.2.0/../../../../xtensa-esp32s2-elf/bin/ld.exe: C:\Users\lenov\Desktop\epepaerlibrary/GxEPD2_BWTest.h:305: undefined reference to `GxEPD2_213_B74::refresh(short, short, short, short)'
c:/users/lenov/appdata/local/arduino15/packages/esp32/tools/xtensa-esp32s2-elf-gcc/esp-12.2.0_20230208/bin/../lib/gcc/xtensa-esp32s2-elf/12.2.0/../../../../xtensa-esp32s2-elf/bin/ld.exe: C:\Users\lenov\Desktop\epepaerlibrary/GxEPD2_BWTest.h:291: undefined reference to `GxEPD2_213_B74::writeImageAgain(unsigned char const*, short, short, short, short, bool, bool, bool)'
c:/users/lenov/appdata/local/arduino15/packages/esp32/tools/xtensa-esp32s2-elf-gcc/esp-12.2.0_20230208/bin/../lib/gcc/xtensa-esp32s2-elf/12.2.0/../../../../xtensa-esp32s2-elf/bin/ld.exe: C:\Users\lenov\Desktop\epepaerlibrary/GxEPD2_BWTest.h:320: undefined reference to `GxEPD2_213_B74::writeImageForFullRefresh(unsigned char const*, short, short, short, short, bool, bool, bool)'
c:/users/lenov/appdata/local/arduino15/packages/esp32/tools/xtensa-esp32s2-elf-gcc/esp-12.2.0_20230208/bin/../lib/gcc/xtensa-esp32s2-elf/12.2.0/../../../../xtensa-esp32s2-elf/bin/ld.exe: C:\Users\lenov\Desktop\epepaerlibrary/GxEPD2_BWTest.h:330: undefined reference to `GxEPD2_213_B74::refresh(bool)'
c:/users/lenov/appdata/local/arduino15/packages/esp32/tools/xtensa-esp32s2-elf-gcc/esp-12.2.0_20230208/bin/../lib/gcc/xtensa-esp32s2-elf/12.2.0/../../../../xtensa-esp32s2-elf/bin/ld.exe: C:\Users\lenov\Desktop\epepaerlibrary/GxEPD2_BWTest.h:321: undefined reference to `GxEPD2_213_B74::writeImageAgain(unsigned char const*, short, short, short, short, bool, bool, bool)'
c:/users/lenov/appdata/local/arduino15/packages/esp32/tools/xtensa-esp32s2-elf-gcc/esp-12.2.0_20230208/bin/../lib/gcc/xtensa-esp32s2-elf/12.2.0/../../../../xtensa-esp32s2-elf/bin/ld.exe: C:\Users\lenov\AppData\Local\Temp\arduino\sketches\FEB401EE6DBC456886826F30EDA50363\sketch\epepaerlibrary.ino.cpp.o: in function `deepSleepTest()':
C:\Users\lenov\Desktop\epepaerlibrary/epepaerlibrary.ino:374: undefined reference to `GxEPD2_213_B74::hibernate()'
c:/users/lenov/appdata/local/arduino15/packages/esp32/tools/xtensa-esp32s2-elf-gcc/esp-12.2.0_20230208/bin/../lib/gcc/xtensa-esp32s2-elf/12.2.0/../../../../xtensa-esp32s2-elf/bin/ld.exe: C:\Users\lenov\Desktop\epepaerlibrary/epepaerlibrary.ino:400: undefined reference to `GxEPD2_213_B74::hibernate()'
c:/users/lenov/appdata/local/arduino15/packages/esp32/tools/xtensa-esp32s2-elf-gcc/esp-12.2.0_20230208/bin/../lib/gcc/xtensa-esp32s2-elf/12.2.0/../../../../xtensa-esp32s2-elf/bin/ld.exe: C:\Users\lenov\AppData\Local\Temp\arduino\sketches\FEB401EE6DBC456886826F30EDA50363\sketch\epepaerlibrary.ino.cpp.o: in function `setup()':
C:\Users\lenov\Desktop\epepaerlibrary/epepaerlibrary.ino:63: undefined reference to `GxEPD2_213_B74::powerOff()'
c:/users/lenov/appdata/local/arduino15/packages/esp32/tools/xtensa-esp32s2-elf-gcc/esp-12.2.0_20230208/bin/../lib/gcc/xtensa-esp32s2-elf/12.2.0/../../../../xtensa-esp32s2-elf/bin/ld.exe: C:\Users\lenov\AppData\Local\Temp\arduino\sketches\FEB401EE6DBC456886826F30EDA50363\sketch\epepaerlibrary.ino.cpp.o: in function `_GLOBAL__sub_I_hspi':
C:\Users\lenov\Desktop\epepaerlibrary/epepaerlibrary.ino:71: undefined reference to `GxEPD2_213_B74::GxEPD2_213_B74(short, short, short, short)'
collect2.exe: error: ld returned 1 exit status

exit status 1

Compilation error: exit status 1

I copied the files that I have so far here, if someone know what I did wrong:
https://drive.google.com/drive/folders/1Oax4AnwzPdzqvLTQHHKTU1CnxZMcfRR3?usp=sharing

You should be looking at where the calls are to

 _pSPIx.begin();

because that is where the pin assignment is done, and reverting back to the default pins happens.

The best approach to modifying a library is by leaving it in it's folder structure, Copy the whole thing to a folder with a different name. renaming the main header file, correct the #ifdef HEADER to prevent multiple loads of it, and find all other header & cpp files that are part of that library that have that main header file included in them and include the heade file with the new name instead.

Now you can include the library that you have renamed in a basic example, and you can modify any part of the library structure.

You can do all this in notepad++ , while you keep the sketch in the Arduino IDE.

The library takes the pointer to the SPI bus just fine, it just overwrites the pin assignments, you can probably then just comment any call to begin() out, there is no real sense in re-initiating the SPI bus at some other moment, unless you want to apply new setting, but for that begin() is also not needed the settings can be modified thru other commands.

That is, well not the best of excuses. You can have traces running underneath the ESP32 just fine as long as you leave the space under the antenna free of copper and parts. The bottom of a PCB is there for a reason, so you can also route traces thru there and even put parts if required (depending on whether you populate the PCB yourself, some manufacturers do not support parts on both sides. I don't see a design issue, other than that it would have to be done again, but i always start out on a breadboard, then move to a prototype once a have a basic code running, but after i have decided on which pins to use for what. The PCB design is in the step after that, once i actually know for sure that everything will work as i want it to (well almost usually, but hardware wise it should all work !)

It looks like you're throwing random crap at the wall hoping something will stick.

I'd simply copy the entire library source code into the sketch file and #include it using quotes rather than '<>' brackets. No need to change any names.

#include "GxEPD2_BW.h"

The call to '_pSPIx->begin();' appears exactly once in the entire library .... Line 74 of 'GxEPD2_EPD.cpp'. Comment out that line and set up the SPI object correctly in your .ino file before calling ' display.epd2.selectSPI()'.

I know, I could just change the pins to the default ones. Yet it bugging me that I could chose the pins myself and why it is not working. After all, why even make the board myself, there are commercial products you can buy that probably work much better and are cheaper. But somehow I have the urge to try making it custom and if I go back to standard everywhere it is not as custom as I want, plus, I didnt learn much about fixing future issues.
I will try for some more days and eventually give in and and make it custom, but I will try a little further.

I have to confess that I did not look into the .cpp file since my pc did not open it initially. Opened it with Editor now and it opened.
I just edited the original file and deleted the line '_pSPIx->begin();'
No reaction on the display...

Sorry you lost me here... so I take the same code from the post #1 of this thread and add the line display.epd2.selectSPI()? because I thought it was already started with

display.init(115200, true, 10, false, hspi, SPISettings(4000000, MSBFIRST, SPI_MODE0)); // extended init method with SPI channel and/or settings selection

  hspi.begin(EPD_SCK, EPD_MISO, EPD_MOSI, EPD_CS); // remap hspi for EPD (swap pins)

With Line 74 commented out, this should be sufficient:

  hspi.begin(EPD_SCK, EPD_MISO, EPD_MOSI, EPD_CS); // remap hspi for EPD (swap pins)
  display.epd2.selectSPI(hspi, SPISettings(4000000, MSBFIRST, SPI_MODE0));

If that doesn't work, the two things I'd try next:

  1. Confirm in ESP32 Technical Reference Manual, etc that the pin you've chosen can be used for the purposes you've selected. The chip has a couple pin that have special functions on power-up etc and the ones you chose may not be appropriate.

  2. Put a logic analyzer on the SPI bus and see what the signals are doing.

Your perseverance is admirable.

Also possible, i tend to get confused with these things at some point, mixing them up having the wrong ones in the editor etc, but yeah, works.

I saw it, but i was a little lazy to confirm that.

i think -1 sets it to the default pin, not to not used. You should actually set it to a free pin, and ideally have it pulled up (INPUT_PULLUP should do the trick. GPIO 0 is also usually a good choice because it is usually left open since it's boot mode choice use)

Yep there is that. S2 is different from the normal ESP32, it has native USB (i think so that means that GPIO 3 should be ok to use, i am less sure about GPIO 9 though. On most ESP32's that is one of the pins connected to the flash, and can't be used, but on an S2 ? The datasheet did not really provide.
me with an answer on that. The other pins should be OK though i think. It may be worth it to experiment with different pins as well.

Notepad++ is the way to go, simple light and opens lots things. The Arduino IDE works to. but can be unpredictable.

Did you ever try to use a valid pin for MISO? Any unused pin?
Method begin() of HW SPIClass may fail if no valid pin specified.
Note: as far as I know, SPIClass of ESP32 will ignore any further calls to begin(), unless the previous call is ended by a call to end(). This differs from the behavior of e.g. Serial.

If any help from me is needed, I would need complete information. E.g. schematics of the board used. Wiring used pin-to-pin. Display used.
-jz-

Added: use a valid spi_bus value in constructor!
See also: Number of SPI channels on ESP32C3 is inconsistent.

https://github.com/espressif/arduino-esp32/blob/master/cores/esp32/esp32-hal-spi.h line 31:

#if CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2 || CONFIG_IDF_TARGET_ESP32S3
#define FSPI  0
#define HSPI  1
#else
#define FSPI  1 //SPI bus attached to the flash (can use the same data lines but different SS)
#define HSPI  2 //SPI bus normally mapped to pins 12 - 15, but can be matrixed to any pins
#if CONFIG_IDF_TARGET_ESP32
#define VSPI  3 //SPI bus normally attached to pins 5, 18, 19 and 23, but can be matrixed to any pins
#endif
#endif

https://github.com/espressif/arduino-esp32/blob/master/cores/esp32/esp32-hal-spi.c line 99:

#elif CONFIG_IDF_TARGET_ESP32C2 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
// ESP32C3
#define SPI_COUNT           (1)

https://github.com/espressif/arduino-esp32/blob/master/cores/esp32/esp32-hal-spi.c line 575:

spi_t * spiStartBus(uint8_t spi_num, uint32_t clockDiv, uint8_t dataMode, uint8_t bitOrder)
{
    if(spi_num >= SPI_COUNT){
        return NULL;
    }

Will fail on ESP32C2 and ESP32C3, as HSPI is 1 or 2 and SPI_COUNT is 1.

I started from scratch again this morning using the example file HelloWorld because it is much shorter.
To confirm the display is still working, I used the standard SPI pins
MOSI: 11 MISO: 9 SCK: 7 SS: 12
and the constructor line
GxEPD2_BW<GxEPD2_213_B74, GxEPD2_213_B74::HEIGHT> display(GxEPD2_213_B74(/*CS=5*/ 12, /*DC=*/ 17, /*RST=*/ 16, /*BUSY=*/ 4)); // GDEM0213B74 122x250, SSD1680
The includes

//#include "GxEPD2_display_selection_new_style.h"
//#include "GxEPD2_display_selection.h"
//#include "GxEPD2_display_selection_added.h"
//#include <GxEPD2_3C.h>
//#include <GxEPD2_7C.h>

are deleted out because they are not used and take long to compile.

-> With this code, it works perfectly fine.

Now I edit the top to be able to set the pins and put the HSPI in, but still using the default pins. So the code up to setup looks now like this:

// GxEPD2_HelloWorld.ino by Jean-Marc Zingg

#include <SPI.h>
#define EPD_BUSY  4
#define EPD_CS    12
#define EPD_RST   16
#define EPD_DC    17
#define EPD_SCK   7
#define EPD_MISO  6 // Master-In Slave-Out not used, as no data from display
#define EPD_MOSI  11 // EPD DIN
SPIClass hspi(HSPI);

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

GxEPD2_BW<GxEPD2_213_B74, GxEPD2_213_B74::HEIGHT> display(GxEPD2_213_B74(/*CS=5*/ EPD_CS, /*DC=*/ EPD_DC, /*RST=*/ EPD_RST, /*BUSY=*/ EPD_BUSY)); // GDEM0213B74 122x250, SSD1680

void setup()
{
  hspi.begin(EPD_SCK, EPD_MISO, EPD_MOSI, EPD_CS); // remap hspi for EPD (swap pins)
  display.init(115200, true, 10, false, hspi, SPISettings(4000000, MSBFIRST, SPI_MODE0)); // extended init method with SPI channel and/or settings selection

 // display.init(115200, true, 2, false); // USE THIS for Waveshare boards with "clever" reset circuit, 2ms reset pulse
  helloWorld();
  display.hibernate();
}

Compiles, uploads, works fine.

Now I change the cable connections and the pins in the file to new pins

#define EPD_BUSY  38
#define EPD_CS    35
#define EPD_RST   37
#define EPD_DC    36
#define EPD_SCK   16
#define EPD_MISO  6 // Master-In Slave-Out not used, as no data from display. Floating.
#define EPD_MOSI  34 // EPD DIN

Uploaded it, did not work.
Then I changed the lines

  hspi.begin(EPD_SCK, EPD_MISO, EPD_MOSI, EPD_CS); // remap hspi for EPD (swap pins)
  display.init(115200, true, 10, false, hspi, SPISettings(4000000, MSBFIRST, SPI_MODE0)); // extended init method with SPI channel and/or settings selection

with

hspi.begin(EPD_SCK, EPD_MISO, EPD_MOSI, EPD_CS); // remap hspi for EPD (swap pins)
  display.epd2.selectSPI(hspi, SPISettings(4000000, MSBFIRST, SPI_MODE0));

Still does not work.

I already looked in the datasheet of the ESP32 S2 chip which is here: esp32-s2_datasheet_en.pdf (espressif.com) The pins are normal IO pins and should be ok to use.
I just googled logic analyzer and I would have to buy one first which wont be possible so quickly because of the christmas holidays.

About the SPIClass hspi(HSPI);
I dont even find any information on the internet to all that... the whole SPIClass thing, how those command are defined, how to unbind a class, how to make a new one... the only thing I find is how to start one and a lot of Forum Threads that go over 100 pages with no explanation. Why do I even have to put SPIClass hspi(HSPI); the (HSPI) in the brackets if I do not want to use it? Why can I not just "unbind" all those SPI instances and create a fresh one and only that one is used?
I found in some forum posts SPIClass * hspi = NULL; but no explanation how these things work, no where I can find at least.

Check, I implemented it in the new code, but unfortunately it did not make a difference so far

I just did in the new attempt just now

I implemented the end() on the start of setup, but it still does not work :frowning:

The display is this: 2.13inch e-Paper HAT - Waveshare Wiki
The MCU: S2 mini — WEMOS documentation with the schematics: S2_mini.pdf (wemos.cc)
And the pins used as follws:

#define EPD_BUSY  38
#define EPD_CS    35
#define EPD_RST   37
#define EPD_DC    36
#define EPD_SCK   16
#define EPD_MISO  6 // Master-In Slave-Out not used, as no data from display. Floating.
#define EPD_MOSI  34 // EPD DIN

Sorry, but I don't understand fully. The constructor is the line
GxEPD2_BW<GxEPD2_213_B74, GxEPD2_213_B74::HEIGHT> display(GxEPD2_213_B74(/*CS=5*/ EPD_CS, /*DC=*/ EPD_DC, /*RST=*/ EPD_RST, /*BUSY=*/ EPD_BUSY)); // GDEM0213B74 122x250, SSD1680
right? How can I add a bus value here?

This is the code currently:

// GxEPD2_HelloWorld.ino by Jean-Marc Zingg

#include <SPI.h>
#define EPD_BUSY  38
#define EPD_CS    35
#define EPD_RST   37
#define EPD_DC    36
#define EPD_SCK   16
#define EPD_MISO  6 // Master-In Slave-Out not used, as no data from display. Floating.
#define EPD_MOSI  34 // EPD DIN
SPIClass hspi(HSPI);

//uninitalised pointers to SPI objects
 

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

GxEPD2_BW<GxEPD2_213_B74, GxEPD2_213_B74::HEIGHT> display(GxEPD2_213_B74(/*CS=5*/ EPD_CS, /*DC=*/ EPD_DC, /*RST=*/ EPD_RST, /*BUSY=*/ EPD_BUSY)); // GDEM0213B74 122x250, SSD1680

void setup()
{
SPI.end();
hspi.end();
//_pSPIx.end();
  hspi.begin(EPD_SCK, EPD_MISO, EPD_MOSI, EPD_CS); // remap hspi for EPD (swap pins)
   display.epd2.selectSPI(hspi, SPISettings(4000000, MSBFIRST, SPI_MODE0));
  //display.init(115200, true, 10, false, hspi, SPISettings(4000000, MSBFIRST, SPI_MODE0)); // extended init method with SPI channel and/or settings selection

 // display.init(115200, true, 2, false); // USE THIS for Waveshare boards with "clever" reset circuit, 2ms reset pulse
  helloWorld();
  display.hibernate();
}

const char HelloWorld[] = "Hello World!";

void helloWorld()
{
  display.setRotation(1);
  display.setFont(&FreeMonoBold9pt7b);
  display.setTextColor(GxEPD_BLACK);
  int16_t tbx, tby; uint16_t tbw, tbh;
  display.getTextBounds(HelloWorld, 0, 0, &tbx, &tby, &tbw, &tbh);
  // center the bounding box by transposition of the origin:
  uint16_t x = ((display.width() - tbw) / 2) - tbx;
  uint16_t y = ((display.height() - tbh) / 2) - tby;
  display.setFullWindow();
  display.firstPage();
  do
  {
    display.fillScreen(GxEPD_WHITE);
    display.setCursor(x, y);
    display.print(HelloWorld);
  }
  while (display.nextPage());
}

void loop() {};