Can't draw on ESP32-S3-Touch-LCD-2.1 display

Hello! So I've got myself in a bit of a mess thinking I'd just use nice compact this round LCD display dev board from Waveshare in my project. The wiki seemed pretty straightforward... How wrong I was!

The product ships with a LVGL (v8.3) demo showing some board info. But trying the example code with more recent LVGL v9.2.2 just produces endless errors. I've laso tried the Arduino GFX Library route but it seems there isn't the correct setup available for this particular board (using ST7701(S?) LCD driver. So I only got as far as compiling code without any errors (yay!) and having the backlight on. I've tried countless options for the code based on Waveshare own examples and topics found online but I'm just getting no wiser getting suck into this rabbit hole of pins, registers and initialisation sequences!

Can anyone shed some light on how am I supposed to look at getting anything displayed on the screen? Below is a code I got so far – I'd appreciate any obvious mistakes or pointers. If it won't get me anywhere, I guess I either install the old libraries and modify the provided code or just swallow the cost and get a different board. Help!

#include <Arduino.h>
#include <Wire.h>
#include <SPI.h>
#include <Arduino_GFX_Library.h>

/******************************************************************************
 * SECTION 1: I2C & TCA9554 Setup
 * Adapted from the “TCA9554PWR.h/.cpp” and “I2C_Driver.h”
 ******************************************************************************/

// I2C pins for TCA9554
#define I2C_MASTER_SDA_IO  15
#define I2C_MASTER_SCL_IO   7
#define I2C_MASTER_NUM      I2C_NUM_0
#define I2C_MASTER_FREQ_HZ  400000
#define I2C_MASTER_TIMEOUT_MS 1000

// TCA9554 definitions
#define TCA9554_ADDRESS     0x20
#define TCA9554_INPUT_REG   0x00
#define TCA9554_OUTPUT_REG  0x01
#define TCA9554_POLARITY_REG 0x02
#define TCA9554_CONFIG_REG  0x03

// Some helper macros
#define Low  0
#define High 1

// EXIO pin IDs
#define EXIO_PIN1  1
#define EXIO_PIN2  2
#define EXIO_PIN3  3
#define EXIO_PIN4  4
#define EXIO_PIN5  5
#define EXIO_PIN6  6
#define EXIO_PIN7  7
#define EXIO_PIN8  8

// Forward declarations
static esp_err_t i2c_master_init();
static uint8_t TCA9554_ReadReg(uint8_t regAddr);
static void    TCA9554_WriteReg(uint8_t regAddr, uint8_t data);

// Cached copy of TCA9554’s “output register” so we can set bits individually.
static uint8_t tca9554_output_cache = 0x00; 
static uint8_t tca9554_config_cache = 0x00; // 0=output, 1=input by default

// Initialize I2C in Arduino “setup()” style
static esp_err_t i2c_master_init()
{
  Wire.begin(I2C_MASTER_SDA_IO, I2C_MASTER_SCL_IO, (uint32_t)I2C_MASTER_FREQ_HZ);
  // If you want more advanced IDF config, do it here, but for typical Arduino usage, this is enough.
  return ESP_OK;
}

// Read from a TCA9554 register
static uint8_t TCA9554_ReadReg(uint8_t regAddr)
{
  Wire.beginTransmission(TCA9554_ADDRESS);
  Wire.write(regAddr);
  esp_err_t err = Wire.endTransmission(false /* send stop? false for repeated start */);
  if (err != ESP_OK) {
    // handle error
    Serial.printf("TCA9554 ReadReg error code=%d\n", (int)err);
    return 0;
  }

  // request 1 byte
  Wire.requestFrom((int)TCA9554_ADDRESS, (int)1, (int)true /* send stop */);
  if (!Wire.available()) {
    Serial.println("TCA9554 no data!");
    return 0;
  }
  uint8_t data = Wire.read();
  return data;
}

// Write to a TCA9554 register
static void TCA9554_WriteReg(uint8_t regAddr, uint8_t data)
{
  Wire.beginTransmission(TCA9554_ADDRESS);
  Wire.write(regAddr);
  Wire.write(data);
  esp_err_t err = Wire.endTransmission(true);
  if (err != ESP_OK) {
    Serial.printf("TCA9554 WriteReg error code=%d\n", (int)err);
  }
}

// Configure one pin as input or output on TCA9554
static void Mode_EXIO(uint8_t pin, uint8_t mode /*0=output,1=input*/)
{
  // pin is 1..8, but TCA9554 registers use bit 0..7
  // note that the “top bit is not used if we only have 7 pins,” but we handle 8 anyway
  uint8_t bitMask = (1 << (pin - 1)); // e.g. pin1 => bit0, pin2 => bit1, etc.
  if (mode == 1) { 
    // set as input
    tca9554_config_cache |= bitMask;
  } else {
    // set as output
    tca9554_config_cache &= ~bitMask;
  }
  TCA9554_WriteReg(TCA9554_CONFIG_REG, tca9554_config_cache);
}

// Set a pin’s output level (without affecting other pins)
static void Set_EXIO(uint8_t pin, uint8_t state)
{
  uint8_t bitMask = (1 << (pin - 1));
  if (state == High) {
    tca9554_output_cache |= bitMask;
  } else {
    tca9554_output_cache &= ~bitMask;
  }
  TCA9554_WriteReg(TCA9554_OUTPUT_REG, tca9554_output_cache);
}

// We can read a pin if needed
static uint8_t Read_EXIO(uint8_t pin)
{
  uint8_t bitMask = (1 << (pin - 1));
  uint8_t val = TCA9554_ReadReg(TCA9554_INPUT_REG);
  return ((val & bitMask) ? High : Low);
}

// Initialize TCA9554 outputs (ex: all pins output=0 initially)
static void TCA9554PWR_Init(uint8_t initOutputValue)
{
  // By default, set all pins to output mode
  tca9554_config_cache = 0x00; // all output
  TCA9554_WriteReg(TCA9554_CONFIG_REG, tca9554_config_cache);

  // set initial output
  tca9554_output_cache = initOutputValue; 
  TCA9554_WriteReg(TCA9554_OUTPUT_REG, tca9554_output_cache);

  // Polarity? Usually 0 => normal
  TCA9554_WriteReg(TCA9554_POLARITY_REG, 0x00);

  Serial.println("TCA9554PWR_Init done");
}

/******************************************************************************
 * SECTION 2: ST7701 SPI & Waveshare Round LCD
 ******************************************************************************/

// Hardware SPI:
#define LCD_SCLK   2
#define LCD_MOSI   1
#define LCD_CS    -1  // we handle CS via TCA9554 EXIO3, so no direct pin

// “Bit‐pack” the command/data bit in top bit of 16 bits
SPIClass st7701SPI(FSPI);  // or VSPI on some boards

static void ST7701S_CS_Enable()  { Set_EXIO(EXIO_PIN3, Low);  delayMicroseconds(50); }
static void ST7701S_CS_Disable() { Set_EXIO(EXIO_PIN3, High); delayMicroseconds(50); }

// Similar to old code
static void ST7701S_WriteCommand(uint8_t cmd)
{
  st7701SPI.beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0));
  ST7701S_CS_Enable();
  uint16_t out = (0 << 8) | cmd; 
  st7701SPI.transfer16(out);
  ST7701S_CS_Disable();
  st7701SPI.endTransaction();
}

static void ST7701S_WriteData(uint8_t data)
{
  st7701SPI.beginTransaction(SPISettings(4000000, MSBFIRST, SPI_MODE0));
  ST7701S_CS_Enable();
  uint16_t out = (1 << 8) | data;
  st7701SPI.transfer16(out);
  ST7701S_CS_Disable();
  st7701SPI.endTransaction();
}

// The official Waveshare sequence for 2.1" ST7701
static void ST7701S_screen_init()
{
  // This matches the code from “ST7701S_screen_init(..., 1)”
  // using SPI_WriteComm/Data. 
  // 1:1 with the original:

  ST7701S_WriteCommand(0xFF);
  ST7701S_WriteData(0x77);
  ST7701S_WriteData(0x01);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x10);

  ST7701S_WriteCommand(0xC0);
  ST7701S_WriteData(0x3B);
  ST7701S_WriteData(0x00);

  ST7701S_WriteCommand(0xC1);
  ST7701S_WriteData(0x0B);
  ST7701S_WriteData(0x02);

  ST7701S_WriteCommand(0xC2);
  ST7701S_WriteData(0x07);
  ST7701S_WriteData(0x02);

  ST7701S_WriteCommand(0xCC);
  ST7701S_WriteData(0x10);

  ST7701S_WriteCommand(0xCD);
  ST7701S_WriteData(0x08);

  ST7701S_WriteCommand(0xB0);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x11);
  ST7701S_WriteData(0x16);
  ST7701S_WriteData(0x0e);
  ST7701S_WriteData(0x11);
  ST7701S_WriteData(0x06);
  ST7701S_WriteData(0x05);
  ST7701S_WriteData(0x09);
  ST7701S_WriteData(0x08);
  ST7701S_WriteData(0x21);
  ST7701S_WriteData(0x06);
  ST7701S_WriteData(0x13);
  ST7701S_WriteData(0x10);
  ST7701S_WriteData(0x29);
  ST7701S_WriteData(0x31);
  ST7701S_WriteData(0x18);

  ST7701S_WriteCommand(0xB1);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x11);
  ST7701S_WriteData(0x16);
  ST7701S_WriteData(0x0e);
  ST7701S_WriteData(0x11);
  ST7701S_WriteData(0x07);
  ST7701S_WriteData(0x05);
  ST7701S_WriteData(0x09);
  ST7701S_WriteData(0x09);
  ST7701S_WriteData(0x21);
  ST7701S_WriteData(0x05);
  ST7701S_WriteData(0x13);
  ST7701S_WriteData(0x11);
  ST7701S_WriteData(0x2a);
  ST7701S_WriteData(0x31);
  ST7701S_WriteData(0x18);

  ST7701S_WriteCommand(0xFF);
  ST7701S_WriteData(0x77);
  ST7701S_WriteData(0x01);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x11);

  ST7701S_WriteCommand(0xB0);
  ST7701S_WriteData(0x6d);

  ST7701S_WriteCommand(0xB1);
  ST7701S_WriteData(0x37);

  ST7701S_WriteCommand(0xB2);
  ST7701S_WriteData(0x81);

  ST7701S_WriteCommand(0xB3);
  ST7701S_WriteData(0x80);

  ST7701S_WriteCommand(0xB5);
  ST7701S_WriteData(0x43);

  ST7701S_WriteCommand(0xB7);
  ST7701S_WriteData(0x85);

  ST7701S_WriteCommand(0xB8);
  ST7701S_WriteData(0x20);

  ST7701S_WriteCommand(0xC1);
  ST7701S_WriteData(0x78);

  ST7701S_WriteCommand(0xC2);
  ST7701S_WriteData(0x78);

  ST7701S_WriteCommand(0xD0);
  ST7701S_WriteData(0x88);

  ST7701S_WriteCommand(0xE0);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x02);

  ST7701S_WriteCommand(0xE1);
  ST7701S_WriteData(0x03);
  ST7701S_WriteData(0xA0);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x04);
  ST7701S_WriteData(0xA0);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x20);
  ST7701S_WriteData(0x20);

  ST7701S_WriteCommand(0xE2);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x00);

  ST7701S_WriteCommand(0xE3);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x11);
  ST7701S_WriteData(0x00);

  ST7701S_WriteCommand(0xE4);
  ST7701S_WriteData(0x22);
  ST7701S_WriteData(0x00);

  ST7701S_WriteCommand(0xE5);
  ST7701S_WriteData(0x05);
  ST7701S_WriteData(0xEC);
  ST7701S_WriteData(0xA0);
  ST7701S_WriteData(0xA0);
  ST7701S_WriteData(0x07);
  ST7701S_WriteData(0xEE);
  ST7701S_WriteData(0xA0);
  ST7701S_WriteData(0xA0);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x00);

  ST7701S_WriteCommand(0xE6);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x11);
  ST7701S_WriteData(0x00);

  ST7701S_WriteCommand(0xE7);
  ST7701S_WriteData(0x22);
  ST7701S_WriteData(0x00);

  ST7701S_WriteCommand(0xE8);
  ST7701S_WriteData(0x06);
  ST7701S_WriteData(0xED);
  ST7701S_WriteData(0xA0);
  ST7701S_WriteData(0xA0);
  ST7701S_WriteData(0x08);
  ST7701S_WriteData(0xEF);
  ST7701S_WriteData(0xA0);
  ST7701S_WriteData(0xA0);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x00);

  ST7701S_WriteCommand(0xEB);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x40);
  ST7701S_WriteData(0x40);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x00);

  ST7701S_WriteCommand(0xED);
  ST7701S_WriteData(0xFF);
  ST7701S_WriteData(0xFF);
  ST7701S_WriteData(0xFF);
  ST7701S_WriteData(0xBA);
  ST7701S_WriteData(0x0A);
  ST7701S_WriteData(0xBF);
  ST7701S_WriteData(0x45);
  ST7701S_WriteData(0xFF);
  ST7701S_WriteData(0xFF);
  ST7701S_WriteData(0x54);
  ST7701S_WriteData(0xFB);
  ST7701S_WriteData(0xA0);
  ST7701S_WriteData(0xAB);
  ST7701S_WriteData(0xFF);
  ST7701S_WriteData(0xFF);
  ST7701S_WriteData(0xFF);

  ST7701S_WriteCommand(0xEF);
  ST7701S_WriteData(0x10);
  ST7701S_WriteData(0x0D);
  ST7701S_WriteData(0x04);
  ST7701S_WriteData(0x08);
  ST7701S_WriteData(0x3F);
  ST7701S_WriteData(0x1F);

  ST7701S_WriteCommand(0xFF);
  ST7701S_WriteData(0x77);
  ST7701S_WriteData(0x01);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x13);

  ST7701S_WriteCommand(0xEF);
  ST7701S_WriteData(0x08);

  ST7701S_WriteCommand(0xFF);
  ST7701S_WriteData(0x77);
  ST7701S_WriteData(0x01);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x00);
  ST7701S_WriteData(0x00);

  ST7701S_WriteCommand(0x36);
  ST7701S_WriteData(0x00); // rotation etc.

  ST7701S_WriteCommand(0x3A);
  ST7701S_WriteData(0x66); // 0x66 = 18 bits ?

  delay(50);

  ST7701S_WriteCommand(0x11); // sleep out
  delay(480);

  ST7701S_WriteCommand(0x20); // (Waveshare does “0x20” ?)
  delay(120);

  ST7701S_WriteCommand(0x29); // display on
}

/******************************************************************************
 * SECTION 3: Parallel “RGB” signals w/ Arduino_GFX
 ******************************************************************************/

// Our backlight pin on the ESP
#define LCD_BL  6

// Waveshare’s known signals
#define LCD_DE    40
#define LCD_VSYNC 39
#define LCD_HSYNC 38
#define LCD_PCLK  41

// R0..R5
#define LCD_R0   -1
#define LCD_R1   46
#define LCD_R2    3
#define LCD_R3    8
#define LCD_R4   18
#define LCD_R5   17

// G0..G5
#define LCD_G0   14
#define LCD_G1   13
#define LCD_G2   12
#define LCD_G3   11
#define LCD_G4   10
#define LCD_G5    9

// B0..B5
#define LCD_B0   -1
#define LCD_B1    5
#define LCD_B2   45
#define LCD_B3   48
#define LCD_B4   47
#define LCD_B5   21

// Create the parallel bus 
Arduino_ESP32RGBPanel *rgb_bus = new Arduino_ESP32RGBPanel(
  /* de */    LCD_DE,
  /* vsync */ LCD_VSYNC,
  /* hsync */ LCD_HSYNC,
  /* pclk */  LCD_PCLK,

  // R0..R4 
  LCD_R1, LCD_R2, LCD_R3, LCD_R4, LCD_R5, 
  // G0..G5
  LCD_G0, LCD_G1, LCD_G2, LCD_G3, LCD_G4, LCD_G5,
  // B0..B4
  LCD_B1, LCD_B2, LCD_B3, LCD_B4, LCD_B5, 

  // hsync_polarity, hsync_front_porch, hsync_pulse_width, hsync_back_porch
  0, 50, 8, 10,
  // vsync_polarity, vsync_front_porch, vsync_pulse_width, vsync_back_porch
  0, 8, 3, 8,

  // pclk_active_neg, prefer_speed, useBigEndian, de_idle_high, pclk_idle_high
  0, (18 * 1000000), false, 0, 0
);

// Create Arduino_RGB_Display 
Arduino_RGB_Display *gfx = new Arduino_RGB_Display(
  480,   // width
  480,   // height
  rgb_bus,
  0,     // rotation
  true,  // auto_flush
  NULL,  // bus
  -1,    // rst pin
  NULL,  // no init ops here, since we do SPI ourselves
  0,     // init ops len
  0, 0, 0, 0 // offsets
);

/******************************************************************************
 * SECTION 4: Arduino setup() and loop()
 ******************************************************************************/
void setup()
{
  Serial.begin(115200);
  delay(100);

  // 1) Initialize I2C for TCA9554
  i2c_master_init();
  // 2) Initialize the TCA9554 (set pins as outputs, all low initially)
  TCA9554PWR_Init(0x00); 

  // 3) Specifically set RST, CS lines to outputs (or 0 in config).
  Mode_EXIO(EXIO_PIN1, 0); // RST as output
  Mode_EXIO(EXIO_PIN3, 0); // CS as output

  // 4) Initialize the SPI 
  pinMode(LCD_MOSI, OUTPUT);
  pinMode(LCD_SCLK, OUTPUT);
  st7701SPI.begin(LCD_SCLK, -1, LCD_MOSI, -1);

  // 5) Perform ST7701 reset 
  //   RST=LOW, wait, RST=HIGH, wait
  Set_EXIO(EXIO_PIN1, Low);
  delay(10);
  Set_EXIO(EXIO_PIN1, High);
  delay(50);

  // 6) Do the official ST7701 init
  ST7701S_screen_init();

  // 7) Now config the parallel bus
  bool ok = gfx->begin();
  if (!ok) {
    Serial.println("gfx->begin() FAILED!");
    while (1) { delay(100); }
  }

  // 8) Turn on backlight
  pinMode(LCD_BL, OUTPUT);
  digitalWrite(LCD_BL, HIGH);

  // 9) Clear screen
  gfx->fillScreen(BLACK);

  // 10) Show text
  gfx->setCursor(80, 120);
  gfx->setTextColor(RED);
  gfx->println("Hello, world!"); // Nothing!
}

void loop()
{
  // do nothing, for now
}

Similar issues here. Struggling to get an lvgl based project to compile with this board at a very basic level :frowning:

This just might save you:

1 Like

[SUCCESS] I tried following the Waveshare wiki for my Waveshare ESP32-S3 2.1in LCD as well--ensuring I installed the specified library versions and board definitions, and modifying the lv_conf.h file as instructed, but the Arduino IDE compiler yielded error after error. I tried using Copilot to help correct the errors--made some headway but always ended up with another trail of errors. antoxa256, can't thank you enough for posting that GitHub link. The garagetinkering files linked above came through!!! I finally have something displayed on my screen! I'm a complete noob with Arduino and C++, so for anyone else in the noob boat trying to get their ESP32-S3 with ST7701 Display working, extract the files from the GitHub link above into a folder named to match the .ino file (in this case, "ST7701_for_ESP32_WS_Driver_Board"). Then open the .ino file, compile and upload! I have the latest LVGL library update and the latest ESP board definitions installed. I choose the board [Waveshare ESP32-S3-Touch-LCD-2.1"]. See attached screenshot for the other Arduino IDE settings I used.

1 Like