Simple DIY Electronic Music Projects<p><strong>Arduino with Multiple Displays – Part 2</strong></p><p>As I mentioned in my last post on <a href="https://diyelectromusic.com/2025/07/19/arduino-with-multiple-displays/" rel="nofollow noopener" target="_blank">Arduino with Multiple Displays</a> I’m going to look at other microcontrollers too. This post takes a wander through my <a href="https://diyelectromusic.com/2025/02/17/waveshare-zero-pimoroni-tiny-and-neopixels/" rel="nofollow noopener" target="_blank">Waveshare Zero and similar</a> format boards that each support one of the RP2040, ESP32-C3 or ESP32-S3.</p><p><em><strong>Warning!</strong> I strongly recommend using old or second hand equipment for your experiments. I am not responsible for any damage to expensive instruments!</em></p><p>These are the key Arduino tutorials for the main concepts used in this project:</p><ul><li><a href="https://diyelectromusic.com/2025/07/19/arduino-with-multiple-displays/" rel="nofollow noopener" target="_blank">Arduino with Multiple Displays</a></li><li><a href="https://emalliab.wordpress.com/2025/07/19/small-microcontroller-displays/" rel="nofollow noopener" target="_blank">https://emalliab.wordpress.com/2025/07/19/small-microcontroller-displays/</a></li></ul><p>If you are new to microcontrollers, see the <a href="https://diyelectromusic.wordpress.com/getting-started/" rel="nofollow noopener" target="_blank">Getting Started</a> pages.</p><p><strong>Parts list</strong></p><ul><li>A Waveshare Zero format board or similar</li><li>2x 0.96″ ST7735 60×180 SPI TFT displays.</li><li>Breadboard and jumper wires.</li></ul><p>Once again I’m using displays that look like this – note the order of the pins.</p><p><strong>The Circuit</strong></p><p>All circuits are a variation on the above, requiring the following ideal connections:</p>DisplayFunctionRP2040ESP32-C3ESP32-S3BLKBacklight control<br>(not required)N/CN/CN/CCSChip select<br>One per display.5 or any SPI0 CS1010DCData/Command888RESReset1499SDAData (MOSI)3 or any SPI0 MOSI6 or 711SCLClock (SCLK)2 or any SPI0 SCLK4 or 612VCCPower3V33V33V3GNDGroundGNDGNDGND<p>For the explanations of the pin choices, and what it means for the code, see the following sections.</p><p><strong>ESP32-S3 Zero</strong></p><p>In the Arduino IDE, using board ESP32-> Waveshare ESP32-S3-Zero.</p><p>There are several SPI buses on the ESP32-S3, but they have fixed uses as follows (see the ESP32-S3 Technical Reference Manual Chapter 30 “SPI Controller”):</p><ul><li>SPI 0: Reserved for internal use.</li><li>SPI 1: Reserved for internal use.</li><li>SPI 2: General purpose use – often called FSPI in the documentation.</li><li>SPI 3: General purpose use – often called SPI or SPI3.</li></ul><p>Sometimes the two SPI buses are called VSPI and HSPI but I think that is really terminology from the original ESP32 rather than the ESP32-S3.</p><p>The ESP32 Arduino core for the <a href="https://github.com/espressif/arduino-esp32/blob/master/variants/waveshare_esp32_s3_zero/pins_arduino.h" rel="nofollow noopener" target="_blank">Waveshare ESP32-S3 Zero variant</a> defines the following:</p><pre>// Mapping based on the ESP32S3 data sheet - alternate for SPI2<br>static const uint8_t SS = 34; // FSPICS0<br>static const uint8_t MOSI = 35; // FSPID<br>static const uint8_t MISO = 37; // FSPIQ<br>static const uint8_t SCK = 36; // FSPICLK</pre><p>By default the Adafruit libraries will use the boards default SPI interface, as defined in the variants.h file – i.e. the above.</p><p>When it comes to assigning SPI devices to GPIO there are a few considerations (see the “ESP32-S3 Technical Reference Manual, Chapter 6 “IO MUX and GPIO Matrix”):</p><ul><li>In general, any GPIO can be mapped onto any SPI function. However…</li><li>Some GPIO have special “strapping” functions so are best avoided.</li><li>Some GPIOs have a default SPI function that bypasses the GPIO MUX routing, so allows for better performance (see section 6.6 “Direct Input and Output via IO MUX”).</li></ul><p>From my reading of the reference manual I believe the following are default “non-MUX” SPI connections:</p><p>In the previous table, where SPI3 is mentioned, then the entry for “Direct IO via IO MUX” is set to “no”, so I’m guessing that isn’t available.</p><p>But now we can see why the Arduino core is using GPIO 34-37, but we can also see that GPIO 10-13 would be an alternative (fast) option too.</p><p>The problem is that not all of GPIO 34-37 are broken out on a Waveshare ESP32-S3 Zero, so I need to use the alternative pinouts. Aside: this makes no sense to me that these are the defaults in the Waveshare ESP32-S3 Zero’s “variant.h” file, but anyway…</p><p>To use a different SPI interface requires using a constructor that passes in an initialised SPI instance. There is an example in the ESP32 core for setting up multiple SPI buses here: <a href="https://github.com/espressif/arduino-esp32/blob/master/libraries/SPI/examples/SPI_Multiple_Buses/SPI_Multiple_Buses.ino" rel="nofollow noopener" target="_blank">https://github.com/espressif/arduino-esp32/blob/master/libraries/SPI/examples/SPI_Multiple_Buses/SPI_Multiple_Buses.ino</a></p><p>This leads to the pins as defined in the previous table, and the code below to setup one of the displays.</p><pre>#include <Adafruit_GFX.h> // Core graphics library<br>#include <Adafruit_ST7735.h> // Hardware-specific library for ST7735<br>#include <SPI.h><br><br>#define SPI_SS 10<br>#define SPI_MOSI 11<br>#define SPI_SCLK 12<br>#define SPI_MISO 13<br>SPIClass MySPI(FSPI);<br><br>#define TFT_CS SPI_SS<br>#define TFT_RST 9<br>#define TFT_DC 8<br>Adafruit_ST7735 tft = Adafruit_ST7735(&MySPI, TFT_CS, TFT_DC, TFT_RST);<br><br>void setup() {<br> MySPI.begin(SPI_SCLK, SPI_MISO, SPI_MOSI, SPI_SS);<br> pinMode(SPI_SS, OUTPUT);<br> tft.initR(INITR_MINI160x80_PLUGIN);<br>}</pre><p><strong>ESP32-C3 Zero</strong></p><p>In the Arduino IDE, using board ESP32-> ESP32C3 Dev Module.</p><p>Again there are several SPI buses on the ESP32-C3, with the same fixed uses as follows (see the ESP32-C3 Technical Reference Manual Chapter 30 “SPI Controller”):</p><ul><li>SPI 0: Reserved for internal use.</li><li>SPI 1: Reserved for internal use.</li><li>SPI 2: General purpose use – sometimes called GP-SPI in the documentation.</li></ul><p>The ESP32-C3 also has a very similar SPI arrangement to the ESP32-S3, in that whilst any pin can be configured for SPI usage, there are certain hard-wired optional arrangements that bypass the GPIO routing matrix.</p><p>The faster (direct to IO MUX) pins are as follows (<a href="https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/peripherals/spi_master.html#gpio-matrix-and-io-mux" rel="nofollow noopener" target="_blank">more here</a>):</p><ul><li>CS0 – 10</li><li>SCLK – 6</li><li>MISO – 2</li><li>MOSI – 7</li></ul><p>Curiously, the <a href="https://github.com/espressif/arduino-esp32/blob/master/variants/esp32c3/pins_arduino.h" rel="nofollow noopener" target="_blank">general ESP32-C3 Arduino variant</a> defines them as follows:</p><pre>static const uint8_t SS = 7;<br>static const uint8_t MOSI = 6;<br>static const uint8_t MISO = 5;<br>static const uint8_t SCK = 4;</pre><p>From the Technical Reference manual, we can see that the default Arduino definitions above, do not support the non-routed, direct-to-IO MUX pin mappings, which from the table below do indeed map onto GPIO 2, 6, 7, 10.</p><p>In terms of using a Waveshare ESP32-C3 Zero, both combinations would be supported on the broken out GPIO, so from a software point of view, the Adafruit libraries could be used “as is” with the default mapping, or with a custom SPI definition (as shown above) with the more bespoke, but faster, mapping.</p><p><strong>RP2040 Zero</strong></p><p>This is using the (unofficial) RP2040 core from here: <a href="https://github.com/earlephilhower/arduino-pico" rel="nofollow noopener" target="_blank">https://github.com/earlephilhower/arduino-pico</a>, where this is an entry: RP2040 -> Waveshare RP2040 Zero.</p><p>The RP2040 has two SPI peripherals and the SPI functions are mapped onto specific sets of GPIO pins. This gives a range of flexibility, but not arbitrary flexibility. The board definition file for the Waveshare RP2040 Zero provides this as a default:</p><pre>// SPI<br>#define PIN_SPI0_MISO (4u)<br>#define PIN_SPI0_MOSI (3u)<br>#define PIN_SPI0_SCK (2u)<br>#define PIN_SPI0_SS (5u)<br><br>#define PIN_SPI1_MISO (12u)<br>#define PIN_SPI1_MOSI (15u)<br>#define PIN_SPI1_SCK (14u)<br>#define PIN_SPI1_SS (13u)</pre><p>Note that the SPI1 pins for the Waveshare RP2040 Zero are not all on the standard header connections, some are on the additional pin headers across the bottom.</p><p>Using a bespoke configuration is possible using a series of calls to set the SPI pins as shown below.</p><pre> SPI.setRX(SPI_MISO);<br> SPI.setCS(SPI_SS);<br> SPI.setSCK(SPI_SCLK);<br> SPI.setTX(SPI_MOSI);<br> SPI.begin(true);</pre><p>To use pins for SPI1, replace SPI above with SPI1. As long as this happens prior to the call to the Adafruit libraries, everything works fine.</p><p><strong>A Common Option</strong></p><p>It would be nice to find a set of physical pin connections that I know would always work regardless of the board in use: RP2040, ESP32-S3 or ESP32-C3.</p><p>With careful noting of the RP2040 limitations, I think that is largely possible with the following. Even though the GPIO numbers are different, the physical pins are common on all three boards.</p>DisplayFunctionWS PinRP2040ESP32-C3ESP32-S3BLKBacklight control<br>(not required)N/CN/CN/CCS1Chip select<br>Display 1H2 P6GP5GP9GP10DCData/CommandH2 P5GP4GP10GP11RESResetH2 P9GP8GP6GP7SDAData (MOSI)H2 P8GP7GP7GP8SCLClock (SCLK)H2 P7GP6GP8GP9VCCPowerH1 P33V33V33V3GNDGroundH1 P2GNDGNDGNDCS2CS Display 2H1 P9GP14GP5GP6CS3CS Display 3H1 P8GP15GP4GP5CS4CS Display 4H1 P7GP26GP3GP4<p>A couple of notes:</p><ul><li>I’ve avoided pins 1-4 on header 2, as the ESP32-C3 can’t use them for SPI and they support either the UART or USB.</li><li>I’ve had to include a MISO (SPI RX) pin in each configuration too, so I’ve just picked something that can be ignored. For RP2040 that has to be one of GP0, GP4 or GP16 however, which could clash with either the UART, the above configuration for DC pin, or the onboard WS2812 LED, but there isn’t much that can be done.</li><li>I’ve allowed three consecutive pins on the first header for optional additional CS pins for displays 2 to 4.</li></ul><p>Here is the full set of configurable code for the above:</p><pre>#include <Adafruit_GFX.h> // Core graphics library<br>#include <Adafruit_ST7735.h> // Hardware-specific library for ST7735<br>#include <SPI.h><br><br>//#define WS_RP2040_ZERO<br>//#define WS_ESP32C3_ZERO<br>#define WS_ESP32S3_ZERO<br><br>#ifdef WS_RP2040_ZERO<br>#define SPI_SS 5<br>#define SPI_MOSI 7<br>#define SPI_SCLK 6<br>#define SPI_MISO 4 // Not used<br>#define SPI_BUS SPI<br>#define TFT_CS1 SPI_SS<br>#define TFT_CS2 14<br>#define TFT_CS3 15<br>#define TFT_CS4 26<br>#define TFT_RST 8<br>#define TFT_DC 4<br>#endif<br><br>#ifdef WS_ESP32C3_ZERO<br>#define SPI_SS 9<br>#define SPI_MOSI 7<br>#define SPI_SCLK 8<br>#define SPI_MISO 0 // Not used<br>SPIClass MySPI(FSPI);<br>#define TFT_CS1 SPI_SS<br>#define TFT_CS2 5<br>#define TFT_CS3 4<br>#define TFT_CS4 3<br>#define TFT_RST 6<br>#define TFT_DC 10<br>#endif<br><br>#ifdef WS_ESP32S3_ZERO<br>#define SPI_SS 10<br>#define SPI_MOSI 8<br>#define SPI_SCLK 9<br>#define SPI_MISO 1 // Not used<br>SPIClass MySPI(FSPI);<br>#define TFT_CS1 SPI_SS<br>#define TFT_CS2 6<br>#define TFT_CS3 5<br>#define TFT_CS4 4<br>#define TFT_RST 7<br>#define TFT_DC 11<br>#endif<br><br>#ifdef WS_RP2040_ZERO<br>Adafruit_ST7735 tft1 = Adafruit_ST7735(TFT_CS1, TFT_DC, TFT_RST);<br>Adafruit_ST7735 tft2 = Adafruit_ST7735(TFT_CS2, TFT_DC, -1);<br>#else<br>Adafruit_ST7735 tft1 = Adafruit_ST7735(&MySPI, TFT_CS1, TFT_DC, TFT_RST);<br>Adafruit_ST7735 tft2 = Adafruit_ST7735(&MySPI, TFT_CS2, TFT_DC, -1);<br>#endif<br><br>void setup() {<br>#ifdef WS_RP2040_ZERO<br> SPI_BUS.setRX(SPI_MISO);<br> SPI_BUS.setCS(SPI_SS);<br> SPI_BUS.setSCK(SPI_SCLK);<br> SPI_BUS.setTX(SPI_MOSI);<br> SPI_BUS.begin(true);<br>#else<br> MySPI.begin(SPI_SCLK, SPI_MISO, SPI_MOSI, SPI_SS);<br> pinMode(SPI_SS, OUTPUT);<br>#endif<br><br> tft1.initR(INITR_MINI160x80_PLUGIN);<br> tft2.initR(INITR_MINI160x80_PLUGIN);<br> tft1.setRotation(3);<br> tft1.fillScreen(ST77XX_BLACK);<br> tft2.setRotation(3);<br> tft2.fillScreen(ST77XX_BLACK);<br>}<br><br>void loop() {<br> unsigned long time = millis();<br> tft1.fillRect(10, 20, tft1.width(), 20, ST77XX_BLACK);<br> tft1.setTextColor(ST77XX_GREEN);<br> tft1.setCursor(10, 20);<br> tft1.print(time, DEC);<br> delay(100);<br><br> time = millis();<br> tft2.fillRect(10, 20, tft2.width(), 20, ST77XX_BLACK);<br> tft2.setTextColor(ST77XX_MAGENTA);<br> tft2.setCursor(10, 20);<br> tft2.print(time, DEC);<br> delay(400);<br>}</pre><p><strong>Closing Thoughts</strong></p><p>It is a little annoying that these great boards don’t share a re-usable, common pinout in terms of naming and positions, but I guess that isn’t the main focus for these systems.</p><p>Still, it seems that a common hardware pinout can be made that supports many displays, which is great, as I’d really like to get a number of them onto a PCB!</p><p>Kevin</p><p></p><p><a rel="nofollow noopener" class="hashtag u-tag u-category" href="https://diyelectromusic.com/tag/arduino-uno/" target="_blank">#arduinoUno</a> <a rel="nofollow noopener" class="hashtag u-tag u-category" href="https://diyelectromusic.com/tag/esp32c3/" target="_blank">#esp32c3</a> <a rel="nofollow noopener" class="hashtag u-tag u-category" href="https://diyelectromusic.com/tag/esp32s3/" target="_blank">#ESP32s3</a> <a rel="nofollow noopener" class="hashtag u-tag u-category" href="https://diyelectromusic.com/tag/rp2040/" target="_blank">#rp2040</a> <a rel="nofollow noopener" class="hashtag u-tag u-category" href="https://diyelectromusic.com/tag/st7735/" target="_blank">#st7735</a> <a rel="nofollow noopener" class="hashtag u-tag u-category" href="https://diyelectromusic.com/tag/tft-display/" target="_blank">#tftDisplay</a> <a rel="nofollow noopener" class="hashtag u-tag u-category" href="https://diyelectromusic.com/tag/waveshare-zero/" target="_blank">#WaveshareZero</a></p>