Writing a flush callback for a monochrome display with full refresh


I’m porting LVGL to an e-Paper display for the first time using the Raspberry Pi 4. I’ve used the vendor supplied code to write bitmaps, text, graphic primitives, etc. to the display and now I want to take advantage of LVGL to build a UI. I’m running into some trouble and I believe the issue is with my flush callback.

If it helps troubleshoot, I’m using a raw e-Paper panel from Waveshare.
Edit: Specifically, it’s GDEW042T2.
The e-Paper’s datasheet: https://www.waveshare.com/w/upload/6/6a/4.2inch-e-paper-specification.pdf
The controller’s datasheet: https://www.waveshare.com/w/upload/8/88/UC8176.pdf

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

Raspberry Pi 4 b+ with gcc

What do you want to achieve?

Write to an e-Paper display

What have you tried so far?

I’ve gotten the code to compile and run by following the example in lvgl/examples/porting/lv_port_disp_template.c and using the lv_example_label_1() example in lvgl/examples/widgets/lv_example_widgets.h, but the output is black and white stripes.

I think my problem is with lv_color_t and how to convert it to something the vendor supplied display code can render.

Code to reproduce

A minimal example of my flush callback

static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
    / * some display setup */
    for (uint16_t j = 0; j < DISP_HEIGHT; j++) {
        for (uint16_t i = 0; i < DISP_WIDTH; i++) {
            // is this the correct approach? I can ignore area because it's full refresh right?
    /* some display cleanup */

display_sendData is usually called like

 uint8_t* imageBuffer;
 /* fill the buffer */
 for (uint16_t j = 0; j < DISP_HEIGHT; j++) {
        for (uint16_t i = 0; i < DISP_WIDTH; i++) {
            display_sendData(imageBuffer[i + j * DISP_WIDTH]);

For completeness, here is my main function

    static lv_disp_draw_buf_t draw_buf_dsc_1;
    static lv_color_t buf_1[DISP_HOR_RES * DISP_VER_RES];                          
    lv_disp_draw_buf_init(&draw_buf_dsc_1, buf_1, NULL, DISP_HOR_RES * DISP_VER_RES);   
    static lv_disp_drv_t disp_drv; 
    disp_drv.draw_buf = &draw_buf_dsc_1;

    disp_drv.hor_res = DISP_HOR_RES;
    disp_drv.ver_res = DISP_VER_RES;

    disp_drv.flush_cb = disp_flush;
    disp_drv.full_refresh = 1;
    lv_disp_t * disp;
    disp = lv_disp_drv_register(&disp_drv);
    while (1)
    return 0;

I’ve changed two lines in lv_conf.h. Line 15:

- #if 0 /*Set it to "1" to enable content */
+ #if 1 /*Set it to "1" to enable content */

and line 27:

- #define LV_COLOR_DEPTH 16
+ #define LV_COLOR_DEPTH 1

Hello, without any specifics on the workings of your display, I can’t tell you what should be done here to make lv_flush work.

However, there are a multitude of existing LVGL projects that use e-paper displays. For instance: ESP32-S3 Smart Planter with Soil Sensor and E-Paper Display, Integrated with ChatGPT - YouTube

The source code for this one is even available here: GitHub - 0015/New-Plant-Pots: Smart Planter with ChatGPT Integration

It might not be applicable to your use case, but hopefully it gives you some clues. I think the creator of this particular project also uses this forum… @Eric_N ?

Thank you for the reply @Tinus . I’ve edited my original post with links to datasheets for the e-Paper display and its controller. I’ve duplicated them below

Display datasheet: https://www.waveshare.com/w/upload/6/6a/4.2inch-e-paper-specification.pdf
Controller datasheet: https://www.waveshare.com/w/upload/6/6a/4.2inch-e-paper-specification.pdf

I’m not seeing lvgl in the project you linked, but I have been following this medium post about using LVGL with monochrome displays. I think the root of my problem is I don’t fully understand what the lv_color_t type is when LV_COLOR_DEPTH is 1. According to the medium post, lv_color_t “collapses to a single-byte union”, but the docs say it becomes lv_color1_t where all the color members have the same value. Has this changed in recent versions of LVGL? Or am I misinterpreting the docs?

I did get rid of the black and white stripes by using the set pixel callback found in the Getting Started docs

void my_set_px_cb(lv_disp_drv_t * disp_drv, uint8_t * buf, lv_coord_t buf_w, lv_coord_t x, lv_coord_t y, lv_color_t color, lv_opa_t opa)
   /* Write to the buffer as required for the display.
    * For example it writes only 1-bit for monochrome displays mapped vertically.*/
   buf += buf_w * (y >> 3) + x;
   if(lv_color_brightness(color) > 128) (*buf) |= (1 << (y % 8));
   else (*buf) &= ~(1 << (y % 8));

Now it looks like the display image is scrambled, but I’m optimistic that I’m moving in the right direction.

Again, I’m not sure what the recommended way to interact with lv_color1_t is. Should you use color->full? Should you pick one of the color channels like color->ch.red? Should you use lv_color_brightness? I’m not sure. Any guidance would be greatly appreciated.

Fixed! lv_color1_t stores one byte per pixel where the display driver expects one bit per pixel. I figured out this approached from the uc8451 driver in the lvgl_esp32_drivers

#define DISP_HOR_RES 400
#define DISP_VER_RES 300
#define EPD_ROW_LEN         (DISP_HOR_RES / 8u)
#define BIT_SET(a, b)       ((a) |= (1U << (b)))
#define BIT_CLEAR(a, b)     ((a) &= ~(1U << (b)))

/* omitted irrelevant code */
void my_set_px_cb(lv_disp_drv_t * disp_drv, uint8_t * buf, lv_coord_t buf_w, lv_coord_t x, lv_coord_t y, lv_color_t color, lv_opa_t opa)
    uint16_t byte_index = (x >> 3u) + (y * EPD_ROW_LEN);
    uint8_t bit_index = x & 0x07u;

    if (color.full) {
        BIT_SET(buf[byte_index], 7 - bit_index);
    } else {
        BIT_CLEAR(buf[byte_index], 7 - bit_index);
1 Like