LVGL9 + NuttX + eINK - Cannot render anything useful

Description

I’m trying to get LVGL v9.2.1 working on a custom board running NuttX v12.8 with an 1.54" e-ink display (200x200px) with SSD1680 controller.

I have set the following:

LV_COLOR_DEPTH=1
LV_DRAW_SW_SUPPORT_I1=y
LV_NUTTX_LCD_SINGLE_BUFFER=y
LV_NUTTX_LCD_BUFFER_COUNT=1
LV_USE_NUTTX_LCD=y

In NuttX I have /dev/lcd0 registered which is using lcd_dev.c driver. Also the SSD1680 is properly configured. I am certain of this because if I manually open /dev/lcd0 and write directly a buffer of 5000 bytes (200 x 200 / 8 = 5000) everything works correctly.

You can see the attached photo:

The code generating this is the following, where a bit of 1 means white and a bit of 0 means black.
The (0, 0) is in the pictures at bottom left. Given this, I believe my setup respects the requirements from Color Format I1 - Monochrome.

  int fd = open("/dev/lcd0", 0);
  uint8_t *data = malloc(5000);

  uint8_t vals[] = {0x0f, 0xf0};

  for (int i = 0; i < sizeof(vals); i++)
    {
      memset(data, vals[i], 5000);
      struct lcddev_area_s area = {0};

      area.row_start = 0 ;
      area.row_end = 50;
      area.col_start = 0;
      area.col_end = 100;
      area.stride = 25;
      area.data = data;

      ioctl(fd, LCDDEVIO_PUTAREA, (unsigned long) &area);
      sleep(2);
    }

  close(fd);

The LVGL + NuttX LCD Glue Layer

I have doubled check (and made a few fixes) in LVGL NuttX LCD port, more specifically in drivers/nuttx/lv_nuttx_lcd.c.

My flush callback has +8 to skip the palette:

static void flush_cb(lv_display_t * disp, const lv_area_t * area_p,
                     uint8_t * color_p)
{
    lv_nuttx_lcd_t * lcd = disp->driver_data;
    LV_LOG_TRACE("FLUSH CB");

    lcd->area.row_start = area_p->y1;
    lcd->area.row_end = area_p->y2;
    lcd->area.col_start = area_p->x1;
    lcd->area.col_end = area_p->x2;
    lcd->area.data = (uint8_t *)color_p + 8;
    ioctl(lcd->fd, LCDDEVIO_PUTAREA, (unsigned long) & (lcd->area));
    lv_display_flush_ready(disp);
}

Also, the buffer size is correct in lcd_init() function from the same file:

    uint32_t buf_size = hor_res * ver_res * px_size / 8 + 8;

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

ESP32 + custom board.

What do you want to achieve?

I want to display a label with text.

What have you tried so far?

I have tried to simply display a label on the display but the output is garbage (very similar to the one below - also looks like a line).

I have also tried to not display anything, just do the LVGL init, with no objects, but the output still doesn’t look right. I was expecting to have nothing rendered in the frame buffer, but there appears to be something:

I have dumped the frame buffer received in the SSD1680 driver, and this is the only row that is not filled with 0x00, row 0x19 → row 25. It corresponds to the white dotted line on the display ( (0,0) bottom-left).

ssd1680_update_all_and_redraw: 19: 0c 00 00 00 f4 bf 14 40 90 e1 fb 3f 32 00 00 00 14 00 00 00 84 e5 fb 3f 6c                                                                                                                                                            

Any help is really welcomed.
Shouldn’t the frame buffer be empty if no objects are added?

App code

int main(int argc, FAR char *argv[])
{
  lv_nuttx_dsc_t info;
  lv_nuttx_result_t result;
  int ret;
  lv_init();
  lv_nuttx_dsc_init(&info);
  info.input_path = NULL;

#ifdef CONFIG_LV_USE_NUTTX_LCD
  info.fb_path = "/dev/lcd0";
#endif

  lv_nuttx_init(&info, &result);
  if (result.disp == NULL)
    {
      LV_LOG_ERROR("lv_demos initialization failure!");
      return 1;
    }

  while (1)
    {
      uint32_t idle;
      idle = lv_timer_handler();
      /* Minimum sleep of 1ms */

      idle = idle ? idle : 1;
      usleep(idle * 1000);
    }
  return EXIT_SUCCESS;
}

I found a piece of code in the documentation which verifies if the display driver works correctly.

I adapted the code for I1 color format.

  #define BUF_W 20
  #define BUF_H 10
  uint8_t buf[BUF_W * BUF_H];
  uint8_t* buf_p = buf;
  uint16_t x, y;
  for(y = 0; y < BUF_H; y++) {
    for(x = 0; x < BUF_W; x++) {
      (*buf_p) = 0x00;
      buf_p++;
    }
  }
  lv_area_t a;
  a.x1 = 10;
  a.y1 = 40;
  a.x2 = a.x1 + BUF_W - 1;
  a.y2 = a.y1 + BUF_H - 1;

  flush_cb(result.disp, &a, buf);

It appears it does.

Good news! I managed to find the problem and get it working. Although there’s still a tiny issue I need to figure out, but I’m happy I made progress.

I found why it was displaying that garbage line. It was caused by the flush callback not setting the stride (correctly at all).

The code with the fix, lv_nuttx_lcd.c file:

static void flush_cb(lv_display_t * disp, const lv_area_t * area_p,
                     uint8_t * color_p)
{
    ...
    lcd->area.stride = (lcd->area.col_end - lcd->area.col_start + 1) / 8;
    lcd->area.data = (uint8_t *)color_p + 8;
    ioctl(lcd->fd, LCDDEVIO_PUTAREA, (unsigned long) & (lcd->area));
    ...
}

The flush callback gets here through the ioctl() call where it sends each row to the SSD1680 driver. The problem was that it was advancing in the buffer with the bad stride.

drivers/lcd/lcd_dev.c

case LCDDEVIO_PUTAREA:
  {
    ...
    size_t pixel_size = priv->planeinfo.bpp > 1 ?
                        priv->planeinfo.bpp >> 3 : 1;
    size_t row_size = lcd_area->stride > 0 ?
                      lcd_area->stride : cols * pixel_size;

    ...
    for (row = lcd_area->row_start; row <= lcd_area->row_end; row++)
        {
          ...
          buf += row_size; /* HERE: BAD INCREMENT! */
        }
  }

bpp = 1 so row_size ends up being cols, which in my case was 200. Setting the stride in the glue layer in LVGL fixed the problem.

The alternative is to fix it in NuttX, in the ioctl() call. planeinfo->bpp according to the header definition means bits per pixel. But being an uint8_t I don’t see how that’s suppose to allow 1 bit per pixel.

New problem to figure out…

… why is my output flipped on the X axis :smile:. This one I bet is from the SSD1680 driver.

I can make a pull request to fix this in the upstream LVGL, if you’d like :).

Later Edit/Update

Fixed the mirrored issue too. As I thought, it was from SSD1680 driver.

I learned something new today, there is a family of SSD168x:

  • SSD1680 is for displays with resolution up to 176 x 296 px.
  • SSD1681 is for 200 x 200 px (apparently this is the one I have).
  • SSD1683 is for 400 x 300 px.

In display terminology, lines are “gates” and columns are “sources”.