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
}