Seeking help for GC9A01 display - colors off, font/text fuzzy

Hello there,
I have been doing a lot of tinkering for my understanding of C, ESP-IDF and LVGL for the last days. I tried things I found in this forum, on github, tried different LLMs like chatGPT or perplexity and still I cannot get the necessary understanding of what I am doing wrong or need to change.

Therefore I am reaching out to you and I am asking for advise. I am pretty novice in working with such low level (for my understanding/skill) programming.

I have an ESP32C6 from seeedstudio (link) MCU.
I am using VSCode, ESP-IDF v5.4.3 and LVGL 9.4. Additional components added thru the ESP-IDF plugin (Component Manager) in VSCode are espresssif/esp_lvgl_port 2.6.2 and espressif/esp_lcd_gc9a01 2.0.4

Problem: I cannot get the colors right and the font sharp.
I tried LV_COLOR_16_SWAP in lv_conf.h both 1 and 0 but I cannot see any difference. I did full clean and rebuild after changing from 1 to 0 and 0 to 1.
I have LV_COLOR_DEPTH 16 in my lv_conf.h

Only if I use esp_lcd_panel_invert_color(panel_handle, true) the screen’s background is white and the font black (if set it to false lv_obj_set_style_bg_color(lv_screen_active(), lv_color_hex(0xFFFFFF), LV_PART_MAIN); will give me a black background on the screen)

edit: I just realised that the colors are actually wrong since lv_color_hex is 0xRRGGBB and I thought when using .rgb_endian = LCD_RGB_ENDIAN_BGR, in the panel_config it’s also BBGGRR).
I attach a picture of the screen drawn with the code below.
The 3 buttons left to right shown are:
left: BLUE & should be lv_color_hex(0xFF0000) (for BGR colors thats fine)
middle: RED & should be lv_color_hex(0x00FF00) (for BGR colors thats swapped with green)
right: GREEN & should be lv_color_hex(0x0000FF) (for BGR colors thats swapped with red)

If I use
lv_palette_main(LV_PALETTE_BLUE)
lv_palette_main(LV_PALETTE_GREEN)
lv_palette_main(LV_PALETTE_RED)
the colors are as in this image:

I have also been running a ESP32 (WROOM32) with lv_micropython and there I could get everything up and running. I compiled the firmware with these flags make BOARD=GENERIC LV_CFLAGS="-DLV_COLOR_DEPTH=16 -DLV_COLOR_16_SWAP=1" and selected invert = False & colormode = ..BGR so that’s what I tried first using C but no luck so far.
Here is a picture of the same display driven with a WROOM32 using lv_micropython:

That’s my code below in hope you can help me and tell me what I am doing wrong and why/how to fix it. I have the feeling that both the color swap and fuzzy font are related but cannot find the solution on my own.

Thanks a lot in advance!

#include <stdio.h>

#include "include/time.h"

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"

#include "esp_log.h"
#include "esp_timer.h"

#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_lcd_panel_ops.h"
#include "esp_lcd_gc9a01.h"

#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "driver/ledc.h"

#include "lv_conf.h"
#include "lvgl.h"

static const char *ESP_LOG_TAG = "RIoT4ESP";

// GC9A01 display
#define DISP_MOSI                           GPIO_NUM_18
#define DISP_CLK                            GPIO_NUM_19
#define DISP_RES                            GPIO_NUM_21
#define DISP_DC                             GPIO_NUM_2
#define DISP_CS                             GPIO_NUM_1
#define DISP_BLK                            GPIO_NUM_0
#define DISP_FREQ                           10000000 // 10 MHz
#define DISP_CMD_BITS                       8
#define DISP_CMD_PARAM_BITS                 8
#define DISP_HRES                           240
#define DISP_VRES                           240
#define DISP_SPI_HOST                            SPI2_HOST

static uint8_t disp_buf1[DISP_HRES * DISP_VRES * 2]; // 2 bytes per pixel for RGB565 & maybe multiply with 1/10 for partial render mode
static uint8_t disp_buf2[DISP_HRES * DISP_VRES * 2]; // 2 bytes per pixel for RGB565 & maybe multiply with 1/10 for partial render mode
static lv_display_t *lv_disp = NULL;
static esp_lcd_panel_handle_t panel_handle = NULL;

// lvgl needs a callback which provides the (run)time in milliseconds
uint32_t lvgl_get_milliseconds_cb(void)
{
    return esp_timer_get_time() / 1000;
}

// green and blue are swapped with my GC9A01 display so I need to do that in the flus function
static void lvgl_flush_cb(lv_display_t *display, const lv_area_t *area, uint8_t *color_map)
{
    esp_lcd_panel_draw_bitmap(panel_handle, area->x1, area->y1, area->x2 + 1, area->y2 + 1, color_map);
    lv_display_flush_ready(display);
}

void app_main(void)
{
    ESP_LOGI(ESP_LOG_TAG, "Hello, starting up!");

    lv_init();
    lv_tick_set_cb(lvgl_get_milliseconds_cb);

    // config SPI bus according to https://github.com/espressif/esp-bsp/blob/master/components/lcd/esp_lcd_gc9a01/README.md
    const spi_bus_config_t bus_config = GC9A01_PANEL_BUS_SPI_CONFIG(DISP_CLK, DISP_MOSI,
                                                                    DISP_HRES * 80 * sizeof(uint16_t));
    ESP_ERROR_CHECK(spi_bus_initialize(DISP_SPI_HOST, &bus_config, SPI_DMA_CH_AUTO));

    // config io handle and io config according to https://github.com/espressif/esp-bsp/blob/master/components/lcd/esp_lcd_gc9a01/README.md
    esp_lcd_panel_io_handle_t io_handle = NULL;
    const esp_lcd_panel_io_spi_config_t io_config = GC9A01_PANEL_IO_SPI_CONFIG(DISP_CS, DISP_DC,
                                                                               NULL, NULL);
    ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)DISP_SPI_HOST, &io_config, &io_handle));
 

    ESP_LOGI(ESP_LOG_TAG, "Install GC9A01 panel driver");
    esp_lcd_panel_dev_config_t panel_config = {
        .reset_gpio_num = DISP_RES,
        //.rgb_endian = LCD_RGB_ENDIAN_RGB, LCD_RGB_ENDIAN_BGR
		//.rgb_ele_order = LCD_RGB_ENDIAN_RGB, 
        .rgb_endian = LCD_RGB_ENDIAN_BGR,
        .bits_per_pixel = 16,
    };
    ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(io_handle, &panel_config, &panel_handle));
    ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
    ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
    ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, true));

    ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel_handle, false));
    ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_handle, true, false));
    ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));

    lv_disp = lv_display_create(DISP_HRES, DISP_VRES);
    lv_display_set_flush_cb(lv_disp, lvgl_flush_cb);
    lv_display_set_buffers(lv_disp, disp_buf1, disp_buf2, sizeof(disp_buf1) + sizeof(disp_buf2), LV_DISPLAY_RENDER_MODE_DIRECT);

    lv_obj_t *b1 = lv_button_create(lv_screen_active());
    lv_obj_set_size(b1, 50, 50);
    lv_obj_set_style_bg_color(b1, lv_color_hex(0xFF0000), LV_PART_MAIN);
    // rgb_endian = LCD_RGB_ENDIAN_BGR: yellow, esp_lcd_panel_invert_color false
    // rgb_endian = LCD_RGB_ENDIAN_RGB: teal, esp_lcd_panel_invert_color false
    // rgb_endian = LCD_RGB_ENDIAN_BGR: blue, esp_lcd_panel_invert_color true
    // rgb_endian = LCD_RGB_ENDIAN_RGB: red, esp_lcd_panel_invert_color true
    lv_obj_align(b1, LV_ALIGN_CENTER, -75, 0);

    lv_obj_t *b2 = lv_button_create(lv_screen_active());
    lv_obj_set_size(b2, 50, 50);
    lv_obj_set_style_bg_color(b2, lv_color_hex(0x00FF00), LV_PART_MAIN);
    // rgb_endian = LCD_RGB_ENDIAN_BGR: teal, esp_lcd_panel_invert_color false
    // rgb_endian = LCD_RGB_ENDIAN_RGB: yellow, esp_lcd_panel_invert_color false
    // rgb_endian = LCD_RGB_ENDIAN_BGR: red, esp_lcd_panel_invert_color true
    // rgb_endian = LCD_RGB_ENDIAN_RGB: blue, esp_lcd_panel_invert_color true
    lv_obj_align(b2, LV_ALIGN_CENTER, 0, 0);

    lv_obj_t *b3 = lv_button_create(lv_screen_active());
    lv_obj_set_size(b3, 50, 50);
    lv_obj_set_style_bg_color(b3, lv_color_hex(0x0000FF), LV_PART_MAIN);
    // rgb_endian = LCD_RGB_ENDIAN_BGR: purple, esp_lcd_panel_invert_color false
    // rgb_endian = LCD_RGB_ENDIAN_RGB: purple, esp_lcd_panel_invert_color false
    // rgb_endian = LCD_RGB_ENDIAN_BGR: green, esp_lcd_panel_invert_color true
    // rgb_endian = LCD_RGB_ENDIAN_RGB: green, esp_lcd_panel_invert_color true
    lv_obj_align(b3, LV_ALIGN_CENTER, 75, 0);

    lv_obj_set_style_bg_color(lv_screen_active(), lv_color_hex(0xFFFFFF), LV_PART_MAIN);
    lv_obj_t *label = lv_label_create(lv_screen_active());
    lv_obj_set_style_text_color(lv_screen_active(), lv_color_hex(0x000000), LV_PART_MAIN);
    lv_obj_align(label, LV_ALIGN_CENTER, 0, 75);
    lv_label_set_text(label, "Example\nText\nfuzzy & blurry");

    while (true)
    {
        lv_timer_handler();
        sleep_ms(100);
    }
}

I have found a solution trying things from the lvgl docs found here: lvgl docs color format

I my flush function I added: lv_draw_sw_rgb565_swap(disp_buf1, DISP_HRES * DISP_VRES); and changed from double buffer to single buffer (since I don’t know how to do the color swap function for double buffer). I also changed the render mode from direct to full (since I got graphic glitches using direct render mode)

static void lvgl_flush_cb(lv_display_t *display, const lv_area_t *area, uint8_t *color_map)
{
    lv_draw_sw_rgb565_swap(disp_buf1, DISP_HRES * DISP_VRES);
    esp_lcd_panel_draw_bitmap(panel_handle, area->x1, area->y1, area->x2 + 1, area->y2 + 1, color_map);
    lv_display_flush_ready(display);
}

I have no idea if that’s a good solution in regards of performance and memory impact. but: the fonts are sharp and the colors are right.

Maybe someone can please explain if thats a vaild way or if there is another/better way using configs instead of swapping in the flush function?