Understanding SPI pin-remapping for GXEPD2 on a ESP32-C3-Mini

Hello,
I am using a C3 Mini from wemos:
https://www.wemos.cc/en/latest/c3/c3_mini.html
Lolin EPD 2.3" BWR SSD1680:
https://www.wemos.cc/en/latest/d1_mini_shield/epd_2_13_ssd1680.html

I am able to use the EPD with the Lolin Library: 2 link limit, cannot link

In the LOLIN library example, I just have to change the #defined pin numbers, including MOSI and CLK. The pin numbers from a D1 do not coordinate identically to a C3 mini.

So after having successfully redefined the pins in the Lolin library. I attempted to understand how that would be done in the GXEPD2 library example for ESP32

GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display(GxEPD2_DRIVER_CLASS(/*CS=*/ 15, /*DC=*/ 27, /*RST=*/ 26, /*BUSY=*/ 25));
#endif

I changed the pins in this driver class to whatever my C3 was connected to and worked with the Lolin Library example.

But when it comes to the MOSI and CLK pins, I don't know if I've found the correct location in which they are defined.
I assumed it was here:

void setup()
{
  Serial.begin(115200);
  Serial.println();
  Serial.println("setup");
  // *** special handling for Waveshare ESP32 Driver board *** //
  // ********************************************************* //
#if defined(ESP32) && defined(USE_HSPI_FOR_EPD)
  hspi.begin(13, 12, 14, 15); // remap hspi for EPD (swap pins)

hspi.begin has pins listed but I don't know the true order and cannot find reference to it elsewhere in the code, perhaps I would have to dive into one of the included headers?

I tried to understand this comment of the code, but it does not have a mapping example for MOSI even though the text description discusses MOSI.

// mapping of Waveshare ESP32 Driver Board
// BUSY -> 25, RST -> 26, DC -> 27, CS-> 15, CLK -> 13, DIN -> 14

// NOTE: this board uses "unusual" SPI pins and requires re-mapping of HW SPI to these pins in SPIClass
//       this example shows how this can be done easily, updated for use with HSPI
//
// The Wavehare ESP32 Driver Board uses uncommon SPI pins for the FPC connector. It uses HSPI pins, but SCK and MOSI are swapped.
// To use HW SPI with the ESP32 Driver Board, HW SPI pins need be re-mapped in any case. Can be done using either HSPI or VSPI.
// Other SPI clients can either be connected to the same SPI bus as the e-paper, or to the other HW SPI bus, or through SW SPI.
// The logical configuration would be to use the e-paper connection on HSPI with re-mapped pins, and use VSPI for other SPI clients.
// VSPI with standard VSPI pins is used by the global SPI instance of the Arduino IDE ESP32 package.

Any guidance is appreciated. All of my arduino experience is just through hacky hobbyist methods, reverse engineering my understanding of the smallest amount of information I understand until things work.

1 Like

@klowhouse, Hi, welcome to the forum!

Thank you for having provided clickable links! Excellent for a first post!

The board you compile for does it for you. The SPIClass uses the SPI pins defined in pins_arduino.h for the global SPIClass instance SPI.
The pins are attached to the HW SPI processor module, automatically re-mapped if needed (if not the default HW SPI pins).

Most likely you use "LOLIN C3 mini" to compile for, else I suggest you do so.

In https://github.com/espressif/arduino-esp32/blob/master/boards.txt,
or in your local installation C:\Users\xxx\AppData\Local\Arduino15\packages\esp32\hardware\esp32\2.0.5\boards.txt
you can search for LOLIN C3 mini, in there you find lolin_c3_mini.build.variant=lolin_c3_mini.
Then you look at https://github.com/espressif/arduino-esp32/blob/master/variants/lolin_c3_mini/pins_arduino.h
and find

static const uint8_t SS    = 5;
static const uint8_t MOSI  = 4;
static const uint8_t MISO  = 3;
static const uint8_t SCK   = 2;

These are the HW SPI pins the global SPI instance uses (maps to).

If you use these pins, then you don't need to re-map. You can use these pins, as your display board is not a direct shield for your processor board.

Good Luck!

Jean-Marc

1 Like

@ZinggJM, for me to remember:

https://github.com/espressif/arduino-esp32/blob/master/libraries/SPI/src/SPI.cpp

#if CONFIG_IDF_TARGET_ESP32
SPIClass SPI(VSPI);
#else
SPIClass SPI(FSPI);
#endif

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

#if CONFIG_IDF_TARGET_ESP32C3 || 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

More information is contained there, e.g.

//Attach/Detach Signal Pins
void spiAttachSCK(spi_t * spi, int8_t sck);
void spiAttachMISO(spi_t * spi, int8_t miso);
void spiAttachMOSI(spi_t * spi, int8_t mosi);
void spiDetachSCK(spi_t * spi, int8_t sck);
void spiDetachMISO(spi_t * spi, int8_t miso);
void spiDetachMOSI(spi_t * spi, int8_t mosi);

//Attach/Detach SS pin to SPI_CSx signal
void spiAttachSS(spi_t * spi, uint8_t cs_num, int8_t ss);
void spiDetachSS(spi_t * spi, int8_t ss);

esp32-c3_datasheet_en.pdf

esp32-c3-mini-1_datasheet_en.pdf

https://www.wemos.cc/en/latest/_static/files/sch_c3_mini_v2.1.0.pdf

Now I am confused. I assumed that FSPI pins can't be re-mapped, as FSPI is also used for internal SPI Flash. I wanted to check that SS (GPIO5) is not used by the internal SPI Flash.
But GPIO5 is also used as FSPIWP (write protect?, would not hurt).
MOSI (GPIO4) is also used as FSPIHD (host data?) could be consistent.
MISO (GPIO3) isn't named as a FSPI pin, GPIO7 is FSPID, there my confusion comes from.
SCK (GPIO2) is named as FSPIQ (data in?), more confusion. GPIO6 is named FSPICLK.

Maybe because this is not for ESP32C3 and ESP32S3.

Needs more investigation. Maybe later.

Interim findings:

ESP32C3 and ESP32S3 and ESP32C3 all have 3 HW SPI modules. But only FSPI and HSPI constants are defined. The third SPI is only internal, for the internal flash.
The FSPI SPI module is called FSPI, because it has QSPI capability for QSPI flash, most likely.

See https://www.espressif.com/sites/default/files/documentation/esp32-c3_datasheet_en.pdf
Page 12, and note 7 for unavailable pins on mini.

But there is a discrepancy between hardware description and software implementation:

In https://github.com/espressif/arduino-esp32/blob/master/libraries/SPI/src/SPI.cpp

#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
        _sck = (_spi_num == FSPI) ? SCK : -1;
        _miso = (_spi_num == FSPI) ? MISO : -1;
        _mosi = (_spi_num == FSPI) ? MOSI : -1;
        _ss = (_spi_num == FSPI) ? SS : -1;
#elif CONFIG_IDF_TARGET_ESP32C3
        _sck = SCK;
        _miso = MISO;
        _mosi = MOSI;
        _ss = SS;
#else
        _sck = (_spi_num == VSPI) ? SCK : 14;
        _miso = (_spi_num == VSPI) ? MISO : 12;
        _mosi = (_spi_num == VSPI) ? MOSI : 13;
        _ss = (_spi_num == VSPI) ? SS : 15;
#endif

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

#if CONFIG_IDF_TARGET_ESP32S2
// ESP32S2
#define SPI_COUNT           (3)

#define SPI_CLK_IDX(p)      ((p==0)?SPICLK_OUT_MUX_IDX:((p==1)?FSPICLK_OUT_MUX_IDX:((p==2)?SPI3_CLK_OUT_MUX_IDX:0)))
#define SPI_MISO_IDX(p)     ((p==0)?SPIQ_OUT_IDX:((p==1)?FSPIQ_OUT_IDX:((p==2)?SPI3_Q_OUT_IDX:0)))
#define SPI_MOSI_IDX(p)     ((p==0)?SPID_IN_IDX:((p==1)?FSPID_IN_IDX:((p==2)?SPI3_D_IN_IDX:0)))

#define SPI_SPI_SS_IDX(n)   ((n==0)?SPICS0_OUT_IDX:((n==1)?SPICS1_OUT_IDX:0))
#define SPI_HSPI_SS_IDX(n)  ((n==0)?SPI3_CS0_OUT_IDX:((n==1)?SPI3_CS1_OUT_IDX:((n==2)?SPI3_CS2_OUT_IDX:SPI3_CS0_OUT_IDX)))
#define SPI_FSPI_SS_IDX(n)  ((n==0)?FSPICS0_OUT_IDX:((n==1)?FSPICS1_OUT_IDX:((n==2)?FSPICS2_OUT_IDX:FSPICS0_OUT_IDX)))
#define SPI_SS_IDX(p, n)    ((p==0)?SPI_SPI_SS_IDX(n):((p==1)?SPI_SPI_SS_IDX(n):((p==2)?SPI_HSPI_SS_IDX(n):0)))

#elif CONFIG_IDF_TARGET_ESP32S3
// ESP32S3
#define SPI_COUNT           (2)

#define SPI_CLK_IDX(p)      ((p==0)?FSPICLK_OUT_IDX:((p==1)?SPI3_CLK_OUT_IDX:0))
#define SPI_MISO_IDX(p)     ((p==0)?FSPIQ_OUT_IDX:((p==1)?SPI3_Q_OUT_IDX:0))
#define SPI_MOSI_IDX(p)     ((p==0)?FSPID_IN_IDX:((p==1)?SPI3_D_IN_IDX:0))

#define SPI_HSPI_SS_IDX(n)  ((n==0)?SPI3_CS0_OUT_IDX:((n==1)?SPI3_CS1_OUT_IDX:0))
#define SPI_FSPI_SS_IDX(n)  ((n==0)?FSPICS0_OUT_IDX:((n==1)?FSPICS1_OUT_IDX:0))
#define SPI_SS_IDX(p, n)    ((p==0)?SPI_FSPI_SS_IDX(n):((p==1)?SPI_HSPI_SS_IDX(n):0))

#elif CONFIG_IDF_TARGET_ESP32C3
// ESP32C3
#define SPI_COUNT           (1)

#define SPI_CLK_IDX(p)      FSPICLK_OUT_IDX
#define SPI_MISO_IDX(p)     FSPIQ_OUT_IDX
#define SPI_MOSI_IDX(p)     FSPID_IN_IDX

#define SPI_SPI_SS_IDX(n)   ((n==0)?FSPICS0_OUT_IDX:((n==1)?FSPICS1_OUT_IDX:((n==2)?FSPICS2_OUT_IDX:FSPICS0_OUT_IDX)))
#define SPI_SS_IDX(p, n)    SPI_SPI_SS_IDX(n)

#else
// ESP32
#define SPI_COUNT           (4)

// ESP32C3
#define SPI_COUNT (1)

only one SPI channel!

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 C3 for HSPI.

Maybe the Arduino package is just not ready to consistently support the new ESP32 versions.

I opened an issue: Number of SPI channels on ESP32C3 is inconsistent · Issue #7596 · espressif/arduino-esp32 (github.com)
but there are so many open issues, currently 321.

Thank you very much for the response. I'm amazed at your ability to show up in pretty much every GXEPD thread. I understand that you are the creator, but still the dedication is impressive.

Before I get into my own problems, I wanted to double-check an error I may have caught. I went into my local file and the github for the C3 Mini build variant that you linked to show me the designations for SS, MOSI, MISO, and SCK.
It seems that the SCK is incorrectly designated as 2. I see it as 1 on the official wemos site image and schematic PDF (C3 pico — WEMOS documentation) that is a link for the C3 Pico but they are supposed to be the same board and I confirmed on the C3 Mini schematic that wemos provides it is the same designation.
So IF the arduino designations are incorrect for the C3 mini, will that cause problems for the automatic remapping? Which leads me into my problem, my inability to correctly understand what is required of me to alter within the library and what I should just leave alone.

As I believe it to be, the GxEPD2_WS_ESP32_Driver is designed to automatically remap the SPI pins to the selected board that Arduino is compiling for. In my case, C3 Mini is selected. So I should not be changing any parameters (maybe the incorrect term) within this function (again, maybe incorrect term):

hspi.begin(13, 12, 14, 15); // remap hspi for EPD (swap pins)

So, I am not supposed to change 13, 12, 14, 15. Even though my C3's pins for SPI are actually

static const uint8_t SS    = 5;
static const uint8_t MOSI  = 4;
static const uint8_t MISO  = 3;
static const uint8_t SCK   = 1; //<<<-- altered pin number from the first discussed point of this post

Previously I had cross referenced what pins 13, 12, 14, 15 were on the D1 and translated those numbers to corresponding C3 pins. So I changed the parameters to 4, 3, 1, 5 instead of allowing the function to automatically re-map.
Additionally, I have been changing the numbered pins within this line to the appropriate numbered pins instead of expecting automatic re-mapping:

GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display(GxEPD2_DRIVER_CLASS(/*CS=*/ 15, /*DC=*/ 27, /*RST=*/ 26, /*BUSY=*/ 25));

So, should I be replacing some of these designated numbers? None of these designated numbers? All of these designated numbers? I imagine that I should leave the hspi.begin parameters alone so that way it can be automatically re-mapped but probably change the CS/DC/RST/BUSY parameters for the Display_Class/Driver_Class

@klowhouse

I wonder in what time zone you live. My time zone is CET, Central Europe, Switzerland.

I woke up too early. Now I have to decide if I provide an immediate answer, or do more investigation first.

I give you an immediate answer. I can update later if proven wrong.

Forget about re-mapping and changing pins_arduino.h!

As you can connect your display pins to any processor pins - you don't have pins wired wrongly like the Waveshare ESP32 Driver board - you don't need to re-map or change.

Connect the SPI pins of your display to the SPI pins defined by pins_arduino.h as in post #2.
You can freely chose which pins to connect DC, RST and BUSY to, and adapt the constructor parameters accordingly. You can also choose CS freely, but using SS is the preferred choice.

Report if this doesn't work.

Don't use an SPI instance for HSPI. Let GxEPD2 use the global SPI instance.

Yes.

Jean-Marc

Yes, there is a discrepancy between the docs and pins_arduino.h.
But this is of no importance. The automatic mapping uses pins_arduino.h.

I have not read your answer past your time zone question, I felt a need to reply immediately. Go to sleep my friend. Thank you for working so hard for strangers.

Your answer is of no help. I did sleep, but woke up early. If you don't like my answer, then tell so, then I can shut up.

Don't use an SPI instance for HSPI. Let GxEPD2 use the global SPI instance.

I am sorry I am just not experienced enough to understand. Only assuming, I should comment out the hspi.begin(). I believe I understand what you mean by letting GxEPD2 use the global SPI instance. I just do not know how or where to implement/designate that I want GxEPD2 to use the Global SPI instance. By commenting out or removing the single line of code for hspi.begin does that implement the global SPI instance by "default"?

And perhaps remove the definition of #define USE_HSPI_FOR_EPD

Use GxEPD2_Example instead of GxEPD2_WS_ESP32_Driver, there it is commented out.

Explanation: originally GxEPD2 only used the global SPI instance. The option to selectSPI() was added lately. GxEPD2_WS_ESP32_Driver originally re-mapped the global SPI pins.

If my poor performance causes stress or is too menial for your time, just tell me and I will be done with my questions, no problem.

GxEPD2_Example does have //#define USE_HSPI_FOR_EPD commented out. And I see later that re-mapping is not initiated because USER_HSPI_FOR_EPD is not defined. This should result in letting GxEPD2 use the global SPI instance.

Connect the SPI pins of your display to the SPI pins defined by pins_arduino.h as in post #2.
You can freely chose which pins to connect DC, RST and BUSY to, and adapt the constructor parameters accordingly. You can also choose CS freely, but using SS is the preferred choice.

Report if this doesn't work.

I do not see anywhere in GxEPD2_Example to adapt the constructor parameters for DC, RST and BUSY, I actually cannot find any reference to thos pins at any point. But I do see something in Gx_EPD_Example (Gx_EPD"1") that may be what you are describing?

#elif defined(ESP32)

// for SPI pin definitions see e.g.:
// C:\Users\xxx\Documents\Arduino\hardware\espressif\esp32\variants\lolin32\pins_arduino.h

GxIO_Class io(SPI, /*CS=5*/ SS, /*DC=*/ 17, /*RST=*/ 16); // arbitrary selection of 17, 16
GxEPD_Class display(io, /*RST=*/ 16, /*BUSY=*/ 4); // arbitrary selection of (16), 4

In this example I think I see how the Global SPI instance is used, and parameters for pins can be adapted. Where am I missing this in Gx_EPD2?

Some reading (capability) is required to successfully use GxEPD2.

GxEPD2_Example.ino Line 44...

// select the display constructor line in one of the following files (old style):
#include "GxEPD2_display_selection.h"
#include "GxEPD2_display_selection_added.h"
//#include "GxEPD2_display_selection_more.h" // private

// or select the display class and display driver class in the following file (new style):
#include "GxEPD2_display_selection_new_style.h"

The epd is working, but I did have to change my pins_arduino.h.

I took the constructor from the "GxEPD2_display_selection_new_style.h" and adapted the wiring for my CS, DC, RST, BUSY. It may be the wrong approach (probably/definitely), but I pasted the most of the lines from "GxEPD2_display_selection_new_style.h" into the example.

After one compile and upload, it did not work. I went into the arduino pins designation for my C3 Mini and changed the CLK defined pin to 1 instead of 2. Recompile and upload resulted in successful presentation of graphics to the EPD.

Thanks for your participation and patience with this. Like I said, all of this is far outside my scope. Very thankful for the education.

1 Like

@klowhouse, Thank you for your report! This will help me to support other users.

Yes, this is a valid approach. But it is more complicated than just modifying the header file, like I intended how it might be used.
I had taken out the selection part to separate files, so it can be more easily maintained when adding new e-paper panels. No need to update all examples, just have to copy these header files.

I am surprised that you needed to change pins_arduino.h.
In esp32-c3_datasheet_en.pdf Table 10 on Page 32 it is noted that SPI2 signals can be mapped through matrix to any GPIO pin. And so far I did not find any code in HAL that would prevent it.

I have ordered some ESP32C3, ESP32S3 and ESP32S2 boards from LOLIN Official Store on AliExpress.

Jean-Marc

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