Garbled display output and inverted colors on monochrome LCD

Hi all, I’m dealing with a strange problem using LVGL v8 with my monochrome display (summary at the bottom).

I set breakpoints in my set_px_cb function to observe the pixel values being passed in. The pixels are always black and widgets don’t render at all. Creating a label does seem to produce white pixels in a non-random pattern. Photo below shows a side-by-side difference of a label printing “A B C” and “A A A”.


Has anyone experienced something like this before, or knows what the problem may be coming from? I’ve been struggling to get LVGL working for weeks but I don’t want to give up on this yet.

Devices and Tools:

  • STM32 Nucleo-F401RE
  • NHDC1AZ + ST7565 driver (monochrome 128x64 LCD)
  • STM32CubeIDE
  • LVGL v8.3.3

Summary of Problem

What works:

  • Display wiring/config/init is fine, works properly with vendor demo code and graphics before including LVGL
  • LVGL driver code is filling and flushing a buffer
  • Label displays something on-screen

What doesn’t work:

  • Screen is black and should be white
  • Widgets don’t render
  • Label text is not rendering properly

Including potentially relevant code below.

lv_conf changes

#define LV_HOR_RES_MAX (128)
#define LV_VER_RES_MAX (64)

/*Color depth: 1 (1 byte per pixel), 8 (RGB332), 16 (RGB565), 32 (ARGB8888)*/
#define LV_COLOR_DEPTH 1

#define LV_COLOR_CHROMA_KEY lv_color_hex(0x00ff00)

#define LV_ANTIALIAS 0
#define LV_USE_ANIMATION 0
#define USE_LV_GPU 0
#define USE_LV_SHADOW 0
#define USE_LV_GROUP 0
#define USE_LV_GPU 0
#define USE_LV_THEME_MONO 1

#define LV_THEME_DEFAULT_INIT lv_theme_mono_init

/*Color depth: 1 (1 byte per pixel), 8 (RGB332), 16 (RGB565), 32 (ARGB8888)*/
#define LV_COLOR_DEPTH 1

Main

(planned on cleaning this up and moving the tick inc after confirming functional LVGL integration)

lv_init();
  lv_port_disp_init();

  lv_obj_t * scr = lv_disp_get_scr_act(NULL);
  lv_theme_t * th = lv_theme_mono_init(NULL, false, &lv_font_montserrat_20);

  lv_obj_t * label1 =  lv_label_create(scr);
  lv_obj_align(label1, LV_ALIGN_CENTER, 0, 0);
  lv_label_set_text(label1, "A A A");

  while (1)
  {
	  lv_task_handler();
	  lv_tick_inc(1);
	  HAL_Delay(1);
  }

Set Px Callback

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)
{
 buf += buf_w * (y >> 3) + x;
 if(lv_color_brightness(color) > 128) (*buf) |= (1 << (y % 8));
 else (*buf) &= ~(1 << (y % 8));
}

Rounder Callback

void my_rounder_cb(lv_disp_drv_t * disp_drv, lv_area_t * area)
{
  /* Update the areas as needed. Can be only larger.
   * For example to always have lines 8 px height:*/

//	area->x1 = area->x1 & ~(0x7);
//	area->x2 = area->x2 |  (0x7);

   area->y1 = area->y1 & 0x07;
   area->y2 = (area->y2 & 0x07) + 8;
}

Flush Callback

void st7565_flush(int32_t x1, int32_t y1, int32_t x2, int32_t y2, const lv_color_t * color_p)
{
    /*Return if the area is out the screen*/
	if(x2 < 0) return;
	if(y2 < 0) return;
	if(x1 > ST7565_HOR_RES - 1) return;
	if(y1 > ST7565_VER_RES - 1) return;

    /*Truncate the area to the screen*/
    int32_t act_x1 = x1 < 0 ? 0 : x1;
    int32_t act_y1 = y1 < 0 ? 0 : y1;
    int32_t act_x2 = x2 > ST7565_HOR_RES - 1 ? ST7565_HOR_RES - 1 : x2;
    int32_t act_y2 = y2 > ST7565_VER_RES - 1 ? ST7565_VER_RES - 1 : y2;

    int32_t x, y;

    /*Set the first row in */

    /*Refresh frame buffer*/
    for(y = act_y1; y <= act_y2; y++) {
        for(x = act_x1; x <= act_x2; x++) {
            if(lv_color_to1(*color_p) != 0) {
                lcd_fb[x + (y / 8)*ST7565_HOR_RES] &= ~(1 << (7 - (y % 8)));
            } else {
                lcd_fb[x + (y / 8)*ST7565_HOR_RES] |= (1 << (7 - (y % 8)));
            }
            color_p ++;
        }

        color_p += x2 - act_x2; /*Next row*/
    }

    st7565_sync(act_x1, act_y1, act_x2, act_y2);
    lv_disp_flush_ready(disp_drv);
}

Small mistake in my OP — the first photo shows “A B C a b c”, not “A B C”. The display output is the same regardless of the font size I use. I can invert the colors to black-on-white by flipping the bits before flushing the display, but I have a feeling the black screen on init may be related to the problem.

I think you should only use set_px_cb or flush_cb, don’t use them both at the same time.

According to the tutorials and official LVGL docs, set_px_cb is only used to pack pixel values into a bitfield instead of being stored as individual bytes. The function is not intended to flush directly (and I have no individual pixel set/flush function in my display driver code, also provided by LVGL’s github), so I’m not sure what you mean by this. I’m pretty certain the flush_cb is necessary in most/all cases.