Periodic update of label causing strange alternating refresh behavior - SSD1608 eInk


So essentially I have fixed the display driver for my SSD1608 200x200 eInk panel and have got a basic monochrome button example working great. I had major problems with threading as the Arduino BSP is quite abstracted, so I had to manually port in the RTC drivers for my chip to implement a periodic timer but it is all working now.

I am now attempting to periodically update a label with the current time using an lv_timer - it calls a function every 1000ms and updates the value of the label with the current time. This works well, but I have some bizarre graphical behavior.

The part of the display which changes every second updates fine, but the other element (button) completely disappears until the next refresh. It then alternates between being shown and not being shown. If I press the button, it then starts alternating between the previous state and the current state. Please see video for detailed behavior. I suspect this may have something to do with the eInk display’s double framebuffer, essentially my display controller appears to have two different memory buffers. When I call the function to update the display, the controller toggles the framebuffer, so any memory writes affect the other framebuffer.
Perhaps the button is only being written to one framebuffer, so as the framebuffers alternate upon refresh it just disappears. This would make sense, because LVGL only sends the changed data to the display - the button state does not change and hence the alternating behavior does not stop until I press the button, which results in a different alternating sequence.

I would greatly appreciate any help I can get with working around this.

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

Nordic nRF52840-DK (adafruit BSP), GCC, SSD1608 200x200 eink display

What LVGL version are you using?


What do you want to achieve?

Fix this alternating behavior

What have you tried so far?

Introducing second memory buffer, playing with round_cb, enabling full_refresh in disp_drv.

Code to reproduce

Display functions:

//TODO - debug & fix for better performance
void epd_flush( lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p )
  uint8_t *buf = (uint8_t*) color_p;
  epd.SetFrameMemory(buf, area->x1, area->y1, area->x2 - area->x1+1, area->y2-area->y1+1);

void epd_set_px_cb(lv_disp_drv_t *disp, uint8_t *buf, lv_coord_t buf_w, lv_coord_t x, lv_coord_t y, lv_color_t color, lv_opa_t opa) 
  unsigned char bitIndex, a;
  uint16_t byteIndex = (y*(buf_w/8))+((x&0xF8)/8);
  bitIndex = x-(x&0xF8);
  a = 1;
  if(lv_color_brightness(color) > 128)
    buf[byteIndex] |= a << (7-bitIndex);
    buf[byteIndex] &= ~(a << (7-bitIndex));

void epd_rounder( lv_disp_drv_t *disp, lv_area_t *area)
  //area->x1 = area->x1 & ~(0x7);
  //area->x2 = area->x2 | 0x7;
 area->x1 = 0;
 area->x2 = 200;

LVGL functions:

void time_label_update_timer(lv_timer_t* timer)
  RTCTime =;
  char timeBuf[25];
  sprintf(timeBuf, "%02u:%02u:%02u", RTCTime.hour(), RTCTime.minute(), RTCTime.second());
  lv_label_set_text(time_label, timeBuf);
static void guiTask(void *pvParameter)
  (void) pvParameter;
  GuiSemaphore = xSemaphoreCreateMutex();
  if (epd.Init(lut_full_update) != 0) {
    SEGGER_RTT_WriteString(0, "e-Paper init failure");
  SEGGER_RTT_WriteString(0, "e-Paper init");
  digitalWrite(PIN_LED1, LOW);

#if LV_USE_LOG != 0
  lv_log_register_print_cb( my_print ); /* register print function for debugging */

  lv_disp_draw_buf_init( &draw_buf, buf_1, NULL, screenWidth * screenHeight / 8 );

  /*Initialising display*/
  static lv_disp_drv_t disp_drv;
  lv_disp_drv_init( &disp_drv );
  disp_drv.draw_buf = &draw_buf;
  disp_drv.flush_cb = epd_flush;
  disp_drv.set_px_cb = epd_set_px_cb;
  disp_drv.rounder_cb = epd_rounder;
  disp_drv.hor_res = screenWidth;
  disp_drv.ver_res = screenHeight;

  lv_disp_t* disp;
  disp = lv_disp_drv_register( &disp_drv );
  //KEYPAD Initialisation. ToDo - refactor into nice methods once eInk operational.
  static lv_indev_drv_t indev_drv;
  lv_indev_drv_init( &indev_drv );
  indev_drv.type = LV_INDEV_TYPE_KEYPAD;
  indev_drv.read_cb = button_reader;
  //indev_drv.feedback_cb = button_feedback;
  lv_indev_t* indev = lv_indev_drv_register( &indev_drv );

  lv_theme_t* theme = lv_theme_mono_init(disp, false, &lv_font_montserrat_30);
  lv_disp_set_theme(disp, theme);

#if 0
  /* Create simple label */
  lv_obj_t *label = lv_label_create( lv_scr_act() );
  lv_label_set_text( label, "Hello Arduino! (V8.0.X)" );
  lv_obj_align( label, LV_ALIGN_CENTER, 0, 0 );

  /* Try an example from the lv_examples Arduino library
    make sure to include it as written above.

  // uncomment one of these demos
  //lv_demo_widgets();            // OK
  //lv_demo_benchmark();          // OK
  //lv_demo_keypad_encoder();     // works, but I haven't an encoder
  // lv_demo_music();              // NOK
  // lv_demo_printer();
  // lv_demo_stress();             // seems to be OK
  //xTaskCreate(GUIUpdate, "LVGL", 256, NULL, tskIDLE_PRIORITY+4, &Handle_GUIUpdate);

  DateTime now =;
  char date[100];
  sprintf(date, "%d:%d:%d\n", now.hour(), now.minute(), now.second());
  SEGGER_RTT_WriteString(0, date);
  SEGGER_RTT_WriteString(0,  "Setup done" );
  lv_timer_t* timeUpdate = lv_timer_create(time_label_update_timer, 1000, NULL);
  //lv_timer_t* dateUpdate = lv_timer_create(date_label_update_timer, 4.32E7, NULL);

  while(1) {
    if(pdTRUE==xSemaphoreTake(GuiSemaphore, portMAX_DELAY)){
void lv_watch_display(lv_indev_t* indev)
  lv_group_t* g = lv_group_create();
  //lv_obj_t * label;
  lv_obj_t * test_btn = lv_btn_create(lv_scr_act());
  lv_obj_add_event_cb(test_btn, event_handler, LV_EVENT_ALL, NULL);
  lv_obj_align(test_btn, LV_ALIGN_CENTER, 0, 60);
  lv_obj_add_flag(test_btn, LV_OBJ_FLAG_CHECKABLE);
  lv_obj_set_height(test_btn, LV_SIZE_CONTENT);

  date_label = lv_label_create(test_btn);
  lv_label_set_text(date_label, "Toggle");
  lv_obj_set_style_text_font(date_label, &lv_font_montserrat_10, 0);
  time_label = lv_label_create(lv_scr_act());
  lv_label_set_text(time_label, "");
  lv_obj_set_style_text_font(time_label, &lv_font_montserrat_48, 0);
  lv_obj_align(time_label, LV_ALIGN_CENTER, 0, -40);
  lv_group_add_obj(g, test_btn);
  lv_indev_set_group(indev, g);


Screenshot and/or video
Apologies for the awful quality!

I have a potential clue. I have written a screen that presents watch data to the user as this is going to become a watch. I have meddled with the FreeRTOS task priorities and added some delay into the heart rate monitor - if I press a few buttons then this alternating behavior completely disappears. WTF??

Bump? Please help me with this, it’s really annoying! If you need any more information please ask. I am at my wits end.

This fixed my problem - I guess my eINK display cleared the framebuffer after every refresh, meaning when it switched to the second one the weird ghosts appeared again.
Modifying lv_refr.c and adding:

 com_area.x1 = 0;
 com_area.y1 = 0;
 com_area.x2 = LV_HOR_RES - 1;
 com_area.y2 = LV_VER_RES - 1;

as mentioned in constant buffer size in flushing · Issue #510 · lvgl/lvgl · GitHub has gotten rid of the alternating and pretty much fixed the weird partial refreshing behavior too. Maybe something to add on the documentation for other users of ePaper displays? Happy to make a PR!