Use mmaped framebuffer as internal littlevgl buffer

Description

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

STM32L4, on NuttX

What do you want to achieve?

In NuttX I can mmap() a framebuffer into memory, which represents the interface to the display. In reality this is really an LCD which is offered as a framebuffer to avoid writing back to the LCD for every change to the video memory. I would like to make littlevgl draw into this buffer and then simply call the “flush” operation of my framebuffer. The point is to minimize memory usage since I already have a display-size framebuffer, I would like to avoid extra memory used by littlevgl and have littlevgl draw directly on my existing framebuffer.

What have you tried so far?

I have configured a single display buffer pointing to the mmaped area of the framebuffer. I have implemented the set_px and rounder operations apropriately. I have the display working and can write text to screen.

While performing a basic test where I initially set a text label at (0,0) and then move it by (1,1) in a loop (expecting the text to move diagonally on the display), I saw that the text only moved horizontally.

Looking at set_px I saw it was being invoked with y values corresponding to the range [0,10] (the height of the text). I realized that littlevgl is asking me to always write in the beggining of the supplied buffer and then expects me to copy these lines in the appropriate row in flush_cb.

Is it possible to tell littlevgl that I’m supplying a complete framebuffer and that when drawing it should draw directly on the absolute position of the supplied buffer? This way on my flush_cb I only need to call my display’s flush option since memory is already written at the apropriate location.

I’m pretty sure that you’re doubling up on the framebuffer to LCD handling using both nutx and lvg.
What is the LCD interface? Serial or RGB? What resolution?
How big is the framebuffer and what type of memory (internal or external)?
What do your lv_disp_buf_init() and lv_disp_drv_register() look like?

Of course, that is what I want to avoid. However, in NuttX, the LCD interface is at the OS level so it is not expected for an application to use it directly. There’s a whole graphics (NX) system that wraps around it, but I’m using littlevgl instead of that. One way to interact with the display is with this framebuffer interface. So, in my case I would need littlevgl to write there directly.

The display I’m testing is a Sharp Memory LCD (128x128, 1bpp), but my issue applies to any other LCD which is supported in NuttX (monochrome or not). The framebuffer is simply an internal (OS level) memory on the uC SRAM. NuttX lets me mmap that which makes the buffer available to the user.

This is the main code. fb_init() sets framebuffer to point to the framebuffer memory (128x16 bytes).

      /* initialize framebuffer */
  uint8_t* framebuffer;
  uint32_t framebuffer_pixel_count;
  fb_init(&framebuffer, &framebuffer_pixel_count);

  /* initialize littlevgl */
  lv_init();
  lv_disp_buf_init(&disp_buf, framebuffer, nullptr, framebuffer_pixel_count);

  lv_disp_drv_t disp_drv;                 /*A variable to hold the drivers. Can be local variable*/
  lv_disp_drv_init(&disp_drv);            /*Basic initialization*/
  disp_drv.buffer = &disp_buf;            /*Set an initialized buffer*/
  disp_drv.flush_cb = fb_flush_cb;        /*Set a flush callback to draw to the display*/

#if CONFIG_LVGL_COLOR_DEPTH == 1
  disp_drv.set_px_cb = fb_set_pixel_cb;
  disp_drv.rounder_cb = fb_rounder;
#endif

  lv_disp_drv_register(&disp_drv); /*Register the driver and save the created display objects*/

  /* draw some stuff */
  lv_obj_t* text = lv_label_create(lv_scr_act(), NULL);
  lv_obj_set_x(text, 0);
  lv_obj_set_y(text, 0);
  lv_label_set_static_text(text, "hola");

  while(true)
    {
      lv_task_handler();

      lv_obj_set_x(text, lv_obj_get_x(text) + 1);
      lv_obj_set_y(text, lv_obj_get_y(text) + 1);

      if (lv_obj_get_x(text) >= 127)
        {
          lv_obj_set_x(text, 0);
          lv_obj_set_y(text, 0);
        }

      usleep(30000);
    }
  return 0;

Ok, out of intuition I managed to get the expected behavior by making my rounder_cb always ask to rewrite the whole display.
I’m guessing this is what lets littlevgl know how much of the supplied buffer to use to draw. However, I’m not sure if there’s a performance penalty in doing so since maybe it is not designed to work like this.

Well, I’m seeing that now I get set_px_cb calls on the whole display, which indeed is really wasteful. I’m wondering if what I’m after goes against littlevgl design. Just to recap, what I would like is to:

  • have littlevgl know only about a whole screen sized buffer
  • make littlevgl draw at the apropriate absolute location in the buffer (ie, only call set_px on affected bits/bytes)
  • let me know in flush_cb about the modified parts of the buffer so I can let the driver know which lines to refresh from the framebuffer

This would achieve no more memory usage than one framebuffer.

Currently, the minimal use of memory I can think in this scenario with littlevgl is to have the display-sized device framebuffer as well as a small buffer for littlevgl. But this implies that littlevgl will draw in turns the size of this smaller buffer and I would have to copy this pieces to the bigger one.

Is littlevgl designed to hold the framebuffer itself and only have the user copy data from its buffer to the device? In that case I see no other way that to talk to the LCD directly to minimize memory use and copies.

Sorry, it is outside my expertise. I have a STM32F7 with double frame buffers in external memory (working well) so though I might be able to help.

No. That’s not how LittlevGL works. We deliberately use a small buffer and copy it in pieces like you described to the larger one. That way you get high quality animations without performance problems (i.e. tearing/flickering).

The only way to get what you want is to use true double buffering like what @xennex is using, but that requires 2 full-size framebuffers which you switch between (and from my experience with NuttX, I’m pretty sure that you can’t just switch the framebuffer pointer).

Got it, thank you.
I just created an LCD character driver in NuttX which exposes the LCD interface and can be operated with standard ioctl(). This allows me to use littlevgl as expected.