LVGL using a display with 3-wire SPI (no D/C pin, 9 bit data)

Description

What MCU/Processor/Board and compiler are you using?

esp32s3

What do you want to achieve?

I want to see a demo using a display with 3-wire SPI (no D/C pin, 9 bit data).

What have you tried so far?

I managed to show the first frame on the screen but then it doesn’t update.

Code to reproduce

#include <stdio.h>
#include <unistd.h>
#include <sys/lock.h>
#include <sys/param.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "sdkconfig.h"
#include "esp_rom_sys.h"
#include "esp_timer.h"
#include "esp_err.h"
#include "esp_log.h"

#include "hal/gpio_ll.h"
#include "soc/gpio_struct.h"
#include "soc/io_mux_reg.h"

#include "lvgl.h"

#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_lcd_panel_ops.h"
#include "driver/spi_master.h"


// Define pin numbers for the display
#define PIN_NUM_SCLK  12
#define PIN_NUM_MOSI  11
#define PIN_NUM_CS    10
#define PIN_NUM_RST   1
#define PIN_NUM_BL    41

// Define LCD dimensions
#define LCD_WIDTH     240
#define LCD_HEIGHT    320

static const char *TAG = "ST7789_LVGL";


// External function to display demo UI
extern void example_lvgl_demo_ui(lv_disp_t *disp);

static inline void spi_delay() {
    //esp_rom_delay_us(1);
}

// Function to send a 9-bit data or command to the display via SPI
static void spi_send_9bit(bool is_data, uint8_t byte) {
    uint16_t word = (is_data ? 0x100 : 0x000) | byte;

    // Optimized bit-banging
    GPIO.out_w1tc = (1 << PIN_NUM_CS);  // Set CS low

    for (int i = 8; i >= 0; i--) {
        // Clock low
        GPIO.out_w1tc = (1 << PIN_NUM_SCLK);

        if ((word >> i) & 0x01)
            GPIO.out_w1ts = (1 << PIN_NUM_MOSI);  // Set MOSI high if bit is 1
        else
            GPIO.out_w1tc = (1 << PIN_NUM_MOSI);  // Set MOSI low if bit is 0

        // Clock high
        GPIO.out_w1ts = (1 << PIN_NUM_SCLK);
    }

    GPIO.out_w1ts = (1 << PIN_NUM_CS);  // Set CS high
}

// Function to send a command to the LCD
static void lcd_send_cmd(uint8_t cmd) { spi_send_9bit(false, cmd); }
// Function to send data to the LCD
static void lcd_send_data(uint8_t data) { spi_send_9bit(true, data); }

// Function to reset the LCD display
static void lcd_reset() {
    gpio_set_level(PIN_NUM_RST, 0);  // Set reset pin low
    vTaskDelay(pdMS_TO_TICKS(100));  // Wait for 100ms
    gpio_set_level(PIN_NUM_RST, 1);  // Set reset pin high
    vTaskDelay(pdMS_TO_TICKS(100));  // Wait for 100ms
}

// Function to initialize the ST7789 LCD display
static void st7789_init() {
    lcd_reset();
    lcd_send_cmd(0x01); vTaskDelay(pdMS_TO_TICKS(150));  // Software reset
    lcd_send_cmd(0x11); vTaskDelay(pdMS_TO_TICKS(120));  // Sleep out
    lcd_send_cmd(0x36); lcd_send_data(0x00);  // Memory data access control
    lcd_send_cmd(0x3A); lcd_send_data(0x55);  // Pixel format
    lcd_send_cmd(0x21);  // Inversion on
    lcd_send_cmd(0x29);  // Display on
    vTaskDelay(pdMS_TO_TICKS(50));  // Wait for 50ms
}

// Function to set the window for drawing on the display
static void lcd_set_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) {
    lcd_send_cmd(0x2A);  // Set column address
    lcd_send_data(x0 >> 8); lcd_send_data(x0 & 0xFF);
    lcd_send_data(x1 >> 8); lcd_send_data(x1 & 0xFF);
    lcd_send_cmd(0x2B);  // Set row address
    lcd_send_data(y0 >> 8); lcd_send_data(y0 & 0xFF);
    lcd_send_data(y1 >> 8); lcd_send_data(y1 & 0xFF);
    lcd_send_cmd(0x2C);  // Write to memory
}

// Function to flush the screen buffer to the display
void my_flush_cb(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p) {

    int32_t w = area->x2 - area->x1 + 1;  // Calculate width of the area
    int32_t h = area->y2 - area->y1 + 1;  // Calculate height of the area

    lcd_set_window(area->x1, area->y1, area->x2, area->y2);  // Set window to draw

    // Send the buffer contents to the screen
    for (int y = 0; y < h; y++) {
        for (int x = 0; x < w; x++) {
            uint16_t color = lv_color_to16(*color_p);  // Convert LVGL color to RGB565
            lcd_send_data(color >> 8);  // Send high byte
            lcd_send_data(color & 0xFF);  // Send low byte
            color_p++;  // Move to the next pixel
        }
    }

    // Signal that the flush is complete
    lv_disp_flush_ready(disp_drv);  // Mark the display as ready for next update
}

// ========== app_main ==============
// Main application entry point
void app_main(void) {
    gpio_config_t io_conf = {
        .mode = GPIO_MODE_OUTPUT,
        .pin_bit_mask = (1ULL << PIN_NUM_SCLK) |
                        (1ULL << PIN_NUM_MOSI) |
                        (1ULL << PIN_NUM_CS)   |
                        (1ULL << PIN_NUM_RST)  |
                        (1ULL << PIN_NUM_BL)   // Configure GPIO pins
    };
    gpio_config(&io_conf);
    gpio_set_level(PIN_NUM_CS, 1);  // Set CS high
    gpio_set_level(PIN_NUM_SCLK, 1);  // Set SCLK high
    gpio_set_level(PIN_NUM_BL, 1);  // Set backlight on

    ESP_LOGI(TAG, "Initializing ST7789...");
    st7789_init();  // Initialize the display

    lv_init();  // Initialize LVGL library

    // Memory for the screen buffers
    static lv_color_t buf1[LCD_WIDTH * 40];  // First buffer with 40 lines
    static lv_color_t buf2[LCD_WIDTH * 40];  // Second buffer for double buffering

    // Create the display driver
    static lv_disp_drv_t disp_drv;
    lv_disp_drv_init(&disp_drv);  // Initialize display driver structure

    disp_drv.hor_res = LCD_WIDTH;  // Set horizontal resolution
    disp_drv.ver_res = LCD_HEIGHT;  // Set vertical resolution
    disp_drv.flush_cb = my_flush_cb;  // Set flush callback for screen updates
    static lv_disp_draw_buf_t draw_buf;
    lv_disp_draw_buf_init(&draw_buf, buf1, buf2, LCD_WIDTH * 40);  // Initialize the draw buffer
    disp_drv.draw_buf = &draw_buf;

    // Register the display driver
    lv_disp_drv_register(&disp_drv);

    example_lvgl_demo_ui(NULL);  // Run the LVGL demo UI

    // Main loop
    while (1) {
        lv_timer_handler();  // Handle LVGL tasks
        vTaskDelay(pdMS_TO_TICKS(20));  // Delay to allow for updates
    }
}