LVGL9 ST7796 & Pico W red & blue are swapped, green is fine

Description

When drawing & specifying colors, green is fine but red & blue are swapped (red-tinted items are blue-tinted, and vice versa)

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

I’m using a Raspberry Pi Pico W with GCC (and ST7796 touchscreen 320x480 LCD).

What LVGL version are you using?

9 (latest). I’m using the ST7796 display driver from Github lv_port_disp.c, which I converted to use LVGL 9 from LGVL 8. The hardware specs are documented in the GeeekPi/52Pi breadboard product wiki

What do you want to achieve?

I want red to be red and blue to be blue :slightly_smiling_face:

What have you tried so far?

There’s a post in the Micropython forum here about a similar problem, but it was solved in LVGL 8, which apparently had more color options for
lv_display_set_color_format(disp, LV_COLOR_FORMAT_RGB888);
which works except for the color swapping. Note that I define
#define LV_COLOR_DEPTH 24
in lv_conf.h

Code to reproduce

I have an LVGL app with RGB sliders (see screenshot) that I use to create a color. In the top right corner is an LVGL object rectangle with the background from the color (it’s Blue even though the Red slider is set to 255), but when that color is applied to a NeoPixel, it is in fact red (so I know I haven’t done anything wrong).

Also, you can see the button at the top left using the default style should be blue but it’s orange.

Also, there are display anomalies (horizontal bars) but that’s a different issue (timing?).

Screenshot and/or video

If possible, add screenshots and/or videos about the current state.
IMG_8848

I suppose this belongs in the Getting Started forum. Any way to move it?

In the meantime, I’ll just point to this post from there.

Thanks.

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");

 }