OK, Plan B.
Instead of updating the old LVGL 8 display driver to work with LVGL 9, I started from scratch and followed the new instructions on creating an LVGL 9 ST7796 display driver (as per LVGL 9’s ST7796 LCD Controller Driver).
It runs up through instantiating the display driver, but that code calls lv_st7796.c lv_st7796_create()
and sends a list of commands to the display lv_lcd_generic_mipi_send_cmd_list()
. The first command comes through and then it hangs:
CMD_CSCON, 1, 0xC3, /* Enable extension command 2 partI */
CMD_CSCON, 1, 0x96, /* Enable extension command 2 partII */
CMD_INVCTR, 1, 0x01, /* 1-dot inversion */
...
Here is my code so far. Any thoughts? My C skills are a little rusty, but here goes:
/*********************
* DEFINES
*********************/
// Landscape orientation
#define ST7796_HORIZONTAL_RESOLUTION 480
#define ST7796_VERTICAL_RESOLUTION 320
/**********************
* TYPEDEFS
**********************/
typedef struct {
uint8_t cmd;
uint8_t data[16];
uint8_t databytes; //No of data in data; bit 7 = delay after set; 0xFF = end of cmds.
} lcd_init_cmd_t;
/**********************
* STATIC VARIABLES
**********************/
static const int gpio_clk = 2;
static const int gpio_din = 3;
static const int gpio_cs = 5;
static const int gpio_dc = 6;
static const int gpio_rst = 7;
static lv_display_t * st7796_puca_display;
/**********************
* GLOBAL FUNCTIONS
**********************/
/* Send short command to the LCD. This function shall wait until the transaction finishes. */
/* int32_t */ void st7796_puca_send_cmd(lv_display_t *disp, const uint8_t *cmd, size_t cmd_size, const uint8_t *param, size_t param_size)
{
printf("...... sending command 0x%x [%u], param 0x%x [%u] ...\n", *cmd, cmd_size, *param, param_size);
LV_UNUSED(disp); // ??
gpio_put(gpio_dc, 0);
gpio_put(gpio_cs, 0);
sleep_us(1);
spi_write_blocking(spi0, /* (uint8_t[]){ */ cmd /* } */, cmd_size);
printf("...... command sent, but no params yet ...\n", *cmd);
gpio_put(gpio_dc, 1);
sleep_us(1);
spi_write_blocking(spi0, /* (uint8_t[]){ */ param /* } */, (uint16_t)param_size);
gpio_put(gpio_cs, 1);
printf("...... command 0x%x PARAMS sent ...\n", *cmd);
}
/* 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. */
/* int32_t */ void st7796_puca_send_color(lv_display_t *disp, const uint8_t *cmd, size_t cmd_size, uint8_t *param, size_t param_size)
{
printf("...... sending COLOR 0x%x [%u], param 0x%x [%u] ...\n", *cmd, cmd_size, *param, param_size);
LV_UNUSED(disp); // ??
gpio_put(gpio_dc, 0);
gpio_put(gpio_cs, 0);
sleep_us(1);
spi_write_blocking(spi0, /* (uint8_t[]){ cmd } */ cmd, cmd_size);
sleep_us(1);
gpio_put(gpio_dc, 1);
sleep_us(1);
// TODO: use DMA as per LVGL9 display driver example??
spi_write_blocking(spi0, /* (uint8_t[]){ param } */ param, (uint16_t)param_size);
gpio_put(gpio_cs, 1);
printf("...... COLOR 0x%x sent ...\n", *cmd);
}
/*Initialize your display and the required peripherals.*/
static void disp_init(void)
{
lcd_init_cmd_t init_cmds[] = {
{0xCF, {0x00, 0x83, 0X30}, 3},
{0xED, {0x64, 0x03, 0X12, 0X81}, 4},
{0xE8, {0x85, 0x01, 0x79}, 3},
{0xCB, {0x39, 0x2C, 0x00, 0x34, 0x02}, 5},
{0xF7, {0x20}, 1},
{0xEA, {0x00, 0x00}, 2},
{0xC0, {0x26}, 1}, /*Power control*/
{0xC1, {0x11}, 1}, /*Power control */
{0xC5, {0x35, 0x3E}, 2}, /*VCOM control*/
{0xC7, {0xBE}, 1}, /*VCOM control*/
{0x36, {0x20}, 1}, // Memory Access Control (0x28 = BGR, 0x20 = RGB)
{0x3A, {0x07}, 1}, // Pixel Format Set (0x05 = 16 bits/pixel Control interface color format)
// 0x06 = 18 bits/pixel, 0x07 = 24 bits/pixel
// Also need to "#define LV_COLOR_DEPTH 24" (or 16 or 18) in lv_conf.h!!
{0xB1, {0x00, 0x1B}, 2},
{0xF2, {0x08}, 1},
{0x26, {0x01}, 1},
{0xE0, {0x1F, 0x1A, 0x18, 0x0A, 0x0F, 0x06, 0x45, 0X87, 0x32, 0x0A, 0x07, 0x02, 0x07, 0x05, 0x00}, 15},
{0XE1, {0x00, 0x25, 0x27, 0x05, 0x10, 0x09, 0x3A, 0x78, 0x4D, 0x05, 0x18, 0x0D, 0x38, 0x3A, 0x1F}, 15},
{0x2A, {0x00, 0x00, 0x00, 0xEF}, 4},
{0x2B, {0x00, 0x00, 0x01, 0x3f}, 4},
{0x2C, {0}, 0},
{0xB7, {0x07}, 1},
{0xB6, {0x0A, 0x82, 0x27, 0x00}, 4}, // Display Function Control (Display Data Path = Memory, DE Mode, System Interface, and other stuff)
{0x11, {0}, 0x80}, // Sleep OUT (OFF)
{0x29, {0}, 0x80}, // Display ON
// 0x88 == half backlight brightness?? Doesn't work
// {0x51, {0x09}, 1}, // WRDISBV (51h): Write Display Brightness
{0, {0}, 0xff}, // this tells the loop below ("while (init_cmds[cmd_num].databytes != 0xff)") to stop
};
spi_init(spi0, 62.5 * 1000 * 1000);
spi_set_format(spi0, 8, SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST);
gpio_set_function(gpio_din, GPIO_FUNC_SPI);
gpio_set_function(gpio_clk, GPIO_FUNC_SPI);
gpio_init(gpio_cs);
gpio_init(gpio_dc);
gpio_init(gpio_rst);
gpio_set_dir(gpio_cs, GPIO_OUT);
gpio_set_dir(gpio_dc, GPIO_OUT);
gpio_set_dir(gpio_rst, GPIO_OUT);
gpio_put(gpio_cs, 1);
gpio_put(gpio_dc, 1);
gpio_put(gpio_rst, 1);
sleep_ms(100);
gpio_put(gpio_rst, 0);
sleep_ms(100);
gpio_put(gpio_rst, 1);
sleep_ms(100);
printf("... about to send commands[] in disp_init()...\n");
//Send all the commands
uint16_t cmd_num = 0;
while (init_cmds[cmd_num].databytes != 0xff)
{
printf("...... sending command structure 0x%x [%u], param 0x%x [%u] ...\n", init_cmds[cmd_num].cmd, 1, init_cmds[cmd_num].data, init_cmds[cmd_num].databytes);
// st7796s_send_cmd(init_cmds[cmd].cmd);
// st7796s_send_data(init_cmds[cmd].data, init_cmds[cmd].databytes & 0x1F);
st7796_puca_send_cmd(st7796_puca_display, &(init_cmds[cmd_num].cmd), 1, init_cmds[cmd_num].data, init_cmds[cmd_num].databytes & 0x1F);
if (init_cmds[cmd_num].databytes & 0x80)
{
sleep_ms(100);
}
cmd_num++;
}
printf("\n... commands sent in disp_init()...\n\n");
sleep_ms(10);
// "PORTRAIT", "PORTRAIT_INVERTED", "LANDSCAPE", "LANDSCAPE_INVERTED"
// 0x48, 0x88, 0x28, 0xE8
// st7796s_set_orientation(0x28);
uint8_t displayCommand = 0x21;
st7796_puca_send_cmd(st7796_puca_display, &displayCommand, 1, NULL, 0);
}
void st7796_puca_display_init(void)
{
lv_lcd_flag_t flags = 0;
/* Initialize LCD I/O */
/*
if (lcd_io_init() != 0)
{
return;
}
*/
/*-------------------------
* Initialize physical display with hardware calls.
* -----------------------*/
disp_init();
printf("... display initialized ...\n");
sleep_ms(500);
printf("\nCreating display driver...\n");
st7796_puca_display = lv_st7796_create(
ST7796_HORIZONTAL_RESOLUTION,
ST7796_VERTICAL_RESOLUTION,
LV_LCD_FLAG_BGR, //LV_LCD_FLAG_NONE //flags,
st7796_puca_send_cmd,
st7796_puca_send_color);
printf("... display driver created. Rotating...\n");
sleep_ms(500);
lv_display_set_rotation(st7796_puca_display, LV_DISPLAY_ROTATION_270);
printf("... display rotated. Creating buffers...\n");
sleep_ms(500);
// setup buffers
/*-----------------------------
* Create a buffer for drawing
*----------------------------*/
/**
* LVGL requires a buffer where it internally draws the widgets.
* Later this buffer will passed to your display driver's `flush_cb` to copy its content to your display.
* The buffer has to be greater than 1 display row
*
* There are 3 buffering configurations:
* 1. Create ONE buffer:
* LVGL will draw the display's content here and writes it to your display
*
* 2. Create TWO buffer:
* LVGL will draw the display's content to a buffer and writes it your display.
* You should use DMA to write the buffer's content to the display.
* It will enable LVGL to draw the next part of the screen to the other buffer while
* the data is being sent form the first buffer. It makes rendering and flushing parallel.
*
* 3. Double buffering
* Set 2 screens sized buffers and set disp_drv.full_refresh = 1.
* This way LVGL will always provide the whole rendered screen in `flush_cb`
* and you only need to change the frame buffer's address.
*/
/* Example for 1) */
// lv_display_t * disp = lv_display_create(MY_DISP_HOR_RES, MY_DISP_VER_RES);
// is this needed for LVGL9?? It seems to have its own.
// lv_display_set_flush_cb(st7796_puca_display, disp_flush);
// lv_display_set_color_format(disp, LV_COLOR_FORMAT_RGB565);
/* Allocate draw buffers on the heap. In this example we use two partial buffers of 1/10th size of the screen */
lv_color_t * buf_1 = NULL;
uint32_t buf_size = ST7796_HORIZONTAL_RESOLUTION * ST7796_VERTICAL_RESOLUTION / 10 * lv_color_format_get_size(lv_display_get_color_format(st7796_puca_display));
// static lv_color_t buf_1[buf_size]; /*A buffer for 10 rows*/
buf_1 = lv_malloc(buf_size);
if(buf_1 == NULL) {
LV_LOG_ERROR("display draw buffer malloc failed");
return;
}
LV_LOG_INFO("Display draw buffer of %d bytes created!\n", buf_size);
lv_display_set_buffers(st7796_puca_display, buf_1, NULL, buf_size, LV_DISPLAY_RENDER_MODE_PARTIAL);
printf("... buffers created.\n\n");
}