Trying to use LVGL on ESP32-S3 and make a simple hello world

Description:
I have purchased a Waveshare 1.69" display including a ESP32-S3. I have a project, where I want to use this display, but before making custom PCB, I would like to make a simple hello world example.

What am I using:
Waveshare 1.69" with ESP32-S3 and 240x280 display using ST7789

What do you want to achieve?
Simplest possible Hello World example

What have you tried so far?
A lot… over the last 3 months. I have finally got something to compile, and that have been a struggle in it selves, since not many online examples seems to compile out of the box. My problem is, that my software crashes at this line:

lv_display_t *disp = lv_st7789_create(240, 280, LV_LCD_FLAG_NONE, send_cmd, send_color);

and I have used weeks on it, without getting anywhere. Any input is welcome, please help a struggling guy :slight_smile:

complete code

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "lvgl/lvgl.h"
#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "drivers/display/st7789/lv_st7789.h"
#include "esp_timer.h"
#include <string.h>

#define TAG "Main"
/*
#define CONFIG_WIDTH 240
#define CONFIG_HEIGHT 240
#define CONFIG_MOSI_GPIO 23
#define CONFIG_SCLK_GPIO 18
#define CONFIG_CS_GPIO -1
#define CONFIG_DC_GPIO 19
#define CONFIG_RESET_GPIO 15
#define CONFIG_BL_GPIO -1
*/
#define BLINK_GPIO GPIO_NUM_2  // The built-in LED is connected to GPIO2

#define ST7789_SPI_HOST SPI3_HOST
#define ST7789_DMA_CHAN 1
#define ST7789_PIN_NUM_CS 5
#define ST7789_PIN_NUM_DC 4

#define ST7789_PIN_NUM_MOSI 7
#define ST7789_PIN_NUM_SCLK 6

#define ST7789_PIN_NUM_RST 8
#define ST7789_PIN_NUM_BCKL 15 // bruges vi ikke lige nu

// SPI device handle
spi_device_handle_t spi;

//static void lv_tick_task(void *arg) {
//    lv_tick_inc(portTICK_PERIOD_MS);
//}

/* Send short command to the LCD. This function shall wait until the transaction finishes. */
void send_cmd(lv_display_t *disp, const uint8_t *cmd, size_t cmd_size, const uint8_t *param, size_t param_size)
{
    spi_transaction_t t;
    memset(&t, 0, sizeof(t));
    t.length = 8;
    t.tx_buffer = &cmd;
    t.user = (void *)0; // D/C line low for command
    ESP_ERROR_CHECK(spi_device_polling_transmit(spi, &t));
}

/* Send large array of pixel data to the LCD. If necessary, this function has to do the byte-swapping. This function can do the transfer in the background. */
void send_color(lv_display_t *disp, const uint8_t *cmd, size_t cmd_size, uint8_t *param, size_t param_size)
{
    spi_transaction_t t;
    memset(&t, 0, sizeof(t));
    t.length = 8 * cmd_size;
    t.tx_buffer = cmd;
    t.user = (void *)1; // D/C line high for data
    ESP_ERROR_CHECK(spi_device_polling_transmit(spi, &t));
}

void send_data(const uint8_t *data, int len) {
    spi_transaction_t t;
    memset(&t, 0, sizeof(t));
    t.length = 8 * len;
    t.tx_buffer = data;
    t.user = (void *)1; // D/C line high for data
    ESP_ERROR_CHECK(spi_device_polling_transmit(spi, &t));
}

void display_flush(lv_display_t *disp, const lv_area_t *area, lv_color_t *color_p) {

    uint32_t size = (area->x2 - area->x1 + 1) * (area->y2 - area->y1 + 1);

    // Set the address window
    uint8_t cmd_column[] = {0x2A, (area->x1 >> 8) & 0xFF, area->x1 & 0xFF, (area->x2 >> 8) & 0xFF, area->x2 & 0xFF};
    spi_transaction_t t;
    memset(&t, 0, sizeof(t));
    t.length = 8 * sizeof(cmd_column); // 8 bits per byte
    t.tx_buffer = cmd_column;
    t.user = (void *)0; // D/C line low for command
    ESP_ERROR_CHECK(spi_device_polling_transmit(spi, &t));

    uint8_t cmd_row[] = {0x2B, (area->y1 >> 8) & 0xFF, area->y1 & 0xFF, (area->y2 >> 8) & 0xFF, area->y2 & 0xFF};
    memset(&t, 0, sizeof(t));
    t.length = 8 * sizeof(cmd_row); // 8 bits per byte
    t.tx_buffer = cmd_row;
    t.user = (void *)0; // D/C line low for command
    ESP_ERROR_CHECK(spi_device_polling_transmit(spi, &t));

    uint8_t cmd_write[] = {0x2C};
    memset(&t, 0, sizeof(t));
    t.length = 8 * sizeof(cmd_write); // 8 bits per byte
    t.tx_buffer = cmd_write;
    t.user = (void *)0; // D/C line low for command
    ESP_ERROR_CHECK(spi_device_polling_transmit(spi, &t));

    // Send the pixel data using send_data function
    send_data((uint8_t *)color_p, size * 2); // size * 2 because each pixel is 16 bits

    // Tell LVGL that flushing is done
    lv_disp_flush_ready(disp);
}

void lvgl_driver_init(void)
{
// SPI bus configuration
    spi_bus_config_t buscfg = {
        .mosi_io_num = ST7789_PIN_NUM_MOSI,
        .sclk_io_num = ST7789_PIN_NUM_SCLK,
        .miso_io_num = -1,
        .quadwp_io_num = -1,
        .quadhd_io_num = -1,
        .max_transfer_sz = 240 * 280 * 2 + 8 // Adjust based on your display resolution
    };
    ESP_ERROR_CHECK(spi_bus_initialize(ST7789_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO));

    // SPI device configuration
    spi_device_interface_config_t devcfg = {
        .clock_speed_hz = 10 * 1000 * 1000, // Clock out at 10 MHz
        .mode = 0,                          // SPI mode 0
        .spics_io_num = ST7789_PIN_NUM_CS,  // CS pin
        .queue_size = 7,
        .flags = SPI_DEVICE_HALFDUPLEX
    };
    ESP_ERROR_CHECK(spi_bus_add_device(ST7789_SPI_HOST, &devcfg, &spi));
}

void init_gpio(void) {
    gpio_config_t io_conf;
    
    // Configure DC pin
    io_conf.intr_type = GPIO_INTR_DISABLE;
    io_conf.mode = GPIO_MODE_OUTPUT;
    io_conf.pin_bit_mask = (1ULL << ST7789_PIN_NUM_DC);
    io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
    io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
    gpio_config(&io_conf);

    // Configure RST pin
    io_conf.pin_bit_mask = (1ULL << ST7789_PIN_NUM_RST);
    gpio_config(&io_conf);

    // Configure backlight pin
    io_conf.pin_bit_mask = (1ULL << ST7789_PIN_NUM_BCKL);
    gpio_config(&io_conf);
}

void reset_display(void) {
    gpio_set_level(ST7789_PIN_NUM_RST, 0);
    vTaskDelay(pdMS_TO_TICKS(100));
    gpio_set_level(ST7789_PIN_NUM_RST, 1);
    vTaskDelay(pdMS_TO_TICKS(100));
}


void app_main(void)
{
    // Initialize LVGL
    lv_init();

    // Initialize SPI and I2C bus used by the drivers
    lvgl_driver_init();

    // Display resolution
    uint32_t hor_res = 240;
    uint32_t ver_res = 280;

    // Initialize display buffer
    static lv_color_t buf1[240 * 10]; // LV_HOR_RES_MAX
    static lv_color_t buf2[240 * 10]; // LV_HOR_RES_MAX

/**
 * Create an LCD display with ST7789 driver
 * @param hor_res       horizontal resolution
 * @param ver_res       vertical resolution
 * @param flags         default configuration settings (mirror, RGB ordering, etc.)
 * @param send_cmd      platform-dependent function to send a command to the LCD controller (usually uses polling transfer)
 * @param send_color    platform-dependent function to send pixel data to the LCD controller (usually uses DMA transfer: must implement a 'ready' callback)
 * @return              pointer to the created display
 */

    lv_display_t *disp = lv_st7789_create(240, 280, LV_LCD_FLAG_NONE, send_cmd, send_color);
    // Initialize display driver
//    lv_disp_t *disp = lv_display_create(hor_res, ver_res);
    lv_display_set_flush_cb(disp, &display_flush);
    lv_display_set_buffers(disp, &buf1, &buf2, sizeof(buf1), LV_DISP_RENDER_MODE_PARTIAL);
/*
    // Set up tick task
    const esp_timer_create_args_t periodic_timer_args = {
        .callback = &lv_tick_task,
        .name = "periodic_gui"
    };
    esp_timer_handle_t periodic_timer;
    ESP_ERROR_CHECK(esp_timer_create(&periodic_timer_args, &periodic_timer));
    ESP_ERROR_CHECK(esp_timer_start_periodic(periodic_timer, 1 * 1000));
*/
    // Example: Create a simple label
    lv_obj_t *label = lv_label_create(lv_scr_act());
    lv_label_set_text(label, "Hello, LVGL!");
    lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);

//    init_gpio();
//    reset_display();

    long blinker = esp_timer_get_time()/1000;
    bool blink = false;
    gpio_reset_pin(BLINK_GPIO);
    gpio_set_direction(BLINK_GPIO, GPIO_MODE_OUTPUT);

    // Handle LVGL tasks
    while (1) {

        vTaskDelay(pdMS_TO_TICKS(10));
        lv_task_handler();

        if (esp_timer_get_time()/1000 - blinker > 500) {
            gpio_set_level(BLINK_GPIO, blink);
            blinker = esp_timer_get_time()/1000;
            blink = !blink;

//            printf("blinking\n");

            lv_obj_t *label = lv_label_create(lv_scr_act());
            lv_label_set_text(label, "Hello, LVGL!");
            lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
        }
    }
}