LVGL port to be used with epaper displays

Yes, exactly.

In flush_cb it’s lv_color_t becasue set_px_cb is only rarely used and most people need lv_color_t. You can convert it to uint8_t*.

Where? In set_px_cb or flush_cb? How do you copy color_map to framebuffer in flush_cb?

In flush_cb:

epd_copy_to_framebuffer(update_area, (uint8_t*)color_map, framebuffer);

This is a function by the EPDiy component that copies a buffer into the big framebuffer. To debug it I also tried just to set each pixel that comes as 0 in the buf, just as test:

    int16_t x1 = (int16_t)x;
    int16_t y1 = (int16_t)y;
    uint16_t byte_index = y1 * buf_w / 2 + x1 / 2;
    buf[byte_index] = 0;

I will have to sit down with all this info and debug via Serial what is coming in buf from one side to the other. But so far you helped me a lot to understand how it is.
I just wish there was a simpler way to simply set a pixel in the buffer without having to do this from one function to the other. But I know is a complex thing to do!

If you set a rounder to full screen or in v8 you can set disp_drv.full_refresh = 1. THis way LVGL will always redraw to whole screen and you can use frambuffer as LVGL’s draw buffer so you don’t have to copy anything in flush_cb.

Update using the color_map buffer:

Now is working. But the problem is that I cannot determine the color_map length and when it updates full screen in this display (960*540) the number of pixels to copy is 51840/2(2 pix per byte), but color_map has no more than 111684 elements (Then it resets). As you can see in the first screen is also printing more than it should and I see part of the memory dumped in the display.
But other than that it has potential to interact already!

@kisvegabor It would possible that I change the LVGL code so the x, y that come into set_px_cb are absolute instead of relative?

New file updated so you can see the progress:

Last update:
Made some progress. Now interaction is better, the only thing is that I cannot print more than 30% of the screen.
Like mentioned in last post, for this screen size, in order to refresh full screen I should need buf to be at least 51840/2 (259 Kb) is quite a big buffer. But I have plenty of SPI Ram available, what I really don’t know is where I can make that bigger.

@kisvegabor Is the size of *buf defined in lvgl_helpers file?

DISP_BUF_SIZE value doesn’t have an special meaning, but it’s the size of the buffer(s) passed to LVGL as display buffers.

Many thanks for all your help so far. Proud to see it working in a parallel epaper with touch!

I think there is a simpler way to copy buf to framebuffer:

/* A copy from epd_copy_to_framebuffer with temporary lenght prediction */
void buf_copy_to_framebuffer(const lv_area_t * area, const uint8_t *image_data) {
  assert(framebuffer != NULL);

  uint8_t *fb_ptr = &framebuffer[area->y1 * EPD_WIDTH / 2 + area->x1 / 2];
  lv_coord_t img_w = lv_area_get_width(area);
  for(uint32_t y = area->y1; y < area->y2; y++) {
      memcpy(fb_ptr, image_data, img_w / 2);
      fb_ptr += EPD_WIDTH / 2;
      image_data += img_w / 2;

It requires a rounder_cb like:

void rounder_cb(lv_disp_drv_t * disp_drv, lv_area_t * area)
   area->x1 = area->x1 & 0x01;
   area->x2 = (area->x2 & 0x01) + 1;

I haven’t tested so typos are possible but conceptually should work with any buffer size.

Awesome it works, here the proof-of-concept video:

I just need to discover how to make buf bigger, it needs to be Width/2height, so calculating with 960*540 that is 259200 bytes.
That won’t work if I place it on the heap. I need to discover where this is instantiated so I can place it directly on the SPI Ram of this ESP32. Thanks a lot Gabor!
Awesome support.

Finally after much testing is working like it should!

Thanks all for the feedback!
Available on my fork, @kisvegabor just tell me if this will be a good candidate to make a merge request!

Yupppiii :tada: :tada: :tada:

Exactly what changes can be merged back and into which repo?

I’ve added EPDiys as a controller both in Kconfig here:

(Need to compare my fork but there where minimal updates except that in order to use Epdiy it needs an additional require in CMakeLists)
And then I added epdiy_epaper as a TFT driver in the drivers. With that will be already possible to support a wide range of parallel epapers since they are much more simpler than their SPI models.
Then additionally I added an L58 hook since this touch IC was used by Lilygo in their EPD47 ESP32 model. But that is only for this model. Other epapers may have also FocalTech drivers that are already supported (GoodDisplay uses FC drivers for touch)
What I’m proposing is to add the EPDiy driver and the requirement is that it needs an additional submodule if the developers want to use it.
Adding the full parallel logic is out of my reach and will be counterproductive since that EPDiy component has a good support and every months get’s more supported modules,

I’m not an ESP expert so I suggest opening a PR. :slightly_smiling_face:

Thanks Gabor!
I still have a small detail that I could not fix. And I think it’s due to the touch driver:

This touch has an INT pin (Interrupt) and when it goes LOW then I read the x, y and state using I2C. So it’s the same as the existing focal tech driver but with this extra pin.
What I’m experimenting is that except of the keyboard that works great, the Buttons/Checkboxes/Switches need always an extra click (That it does not really matter if it’s made over the UX element, it can also be done anywhere)
It’s like if the release event comes too late for LVGL to recognize was an actual “Tap” to the button.

If anyone has any insights or pointers about where I can start looking it will be really help in my research. Thanks in advance

Is Touch.loop() a blocking function, by chance? The expectation is that your read_cb function does not block.

1 Like

Thanks a lot for your reply. Touch.loop() is calling my component method processTouch()

Basically if the Semaphore is not given then it will stay there. Only when INT pin is LOW then touch data is ready to be taken. Then the way to make touch is to read all the time without any Semaphore?
Will have to check in detail how the other drivers are done.

The simplest solution is probably to store the touchscreen’s current state in global variables and update them when you receive the interrupt. Then, when the read callback is called, just pass whatever is in the global variables to LVGL.

Awesome. Removing semaphores it works as expected. Thanks a lot again for pushing me in the right direction.
I can confirm now that LVGL works great, of course a bit slower als TFT, with parallel epapers with touch.
Ideal to make a low consumption UX.

Thanks @kisvegabor Gabor and @embeddedt thank to your guidance and support is now possible to do UX in LVGL with low consumption epapers. This is how it looks after refactoring my touch driver:

It’s a bit slow compared to LVGL on a TFT display. But It’s a tradeoff between display type and consumption vs. speed. I dunno if someone will find it useful but I will personally use it.
Again it won’t have been possible with your support for this great component that is LVGL.
If anyone want’s to try this with the Lilygo EPD47 ESP32 epaper, find my forked repository here:

1 Like

Thanks for sharing the result!

It’d be great to see the drivers in our ESP32 repo. :slight_smile:

Sure no problem, I will clean it up and add some comments in the next days so it’s understandable and submit a pull request to merge them in the ESP32 repos (After corrections)
Was a really cool thing to get working and I will make some real world demos soon.

1 Like

Looking forward for both the PR and the demos :slight_smile: