I built a smart planner for kids using the Elecrow ESP32 4.2” E-paper Display, LVGL 9. It includes a timetable, Google Calendar and Google Tasks integration, and more!
However, I’m having trouble implementing partial refresh with LVGL
Currently, I’m using the following for full and fast refresh:
EditEPD_Init();
EPD_Display(Image_BW); // Full refresh
EPD_Init_Fast(Fast_Seconds_1_s);
EPD_Display_Fast(Image_BW); // Fast refresh
I tried using:
EPD_Display_Part(0, 0, w, h, Image_BW);
…but it doesn’t work as expected. Has anyone managed to get partial refresh working with this display and LVGL? Any suggestions or examples would be appreciated!
LVGL setup :
#include "lvgl_setup.h"
// LVGL display
lv_display_t *display = NULL;
// LVGL buffers - increased for better performance
static uint8_t lvgl_buf[EPD_W * 40]; // Buffer for 40 rows
// Function to set a pixel in the image buffer (replacing Paint_SetPixel)
void SetPixel(uint16_t x, uint16_t y, uint8_t color) {
uint32_t addr;
uint8_t rdata;
// Calculate address in buffer
addr = x / 8 + y * ((EPD_W % 8 == 0) ? (EPD_W / 8) : (EPD_W / 8 + 1));
rdata = Image_BW[addr];
if (color == 0) { // BLACK
Image_BW[addr] = rdata & ~(0x80 >> (x % 8)); // Set bit to 0
} else {
Image_BW[addr] = rdata | (0x80 >> (x % 8)); // Set bit to 1
}
}
// Function to clear the buffer
void ClearBuffer(uint8_t color) {
uint16_t width_byte = (EPD_W % 8 == 0) ? (EPD_W / 8) : (EPD_W / 8 + 1);
uint16_t height = EPD_H;
for (uint16_t y = 0; y < height; y++) {
for (uint16_t x = 0; x < width_byte; x++) {
uint32_t addr = x + y * width_byte;
Image_BW[addr] = color;
}
}
}
// Flush callback for LVGL
static void epd_flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map) {
// Calculate dimensions
uint32_t width = area->x2 - area->x1 + 1;
uint32_t height = area->y2 - area->y1 + 1;
// Convert LVGL buffer to EPD format
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
uint8_t pixel = px_map[y * width + x];
// Map: 0-127 -> BLACK, 128-255 -> WHITE
SetPixel(area->x1 + x, area->y1 + y, pixel < 128 ? 0 : 1);
}
}
// Tell LVGL we're done with this section
lv_display_flush_ready(disp);
}
// Initialize LVGL
void lvgl_init() {
// Initialize LVGL
lv_init();
// Create LVGL display
display = lv_display_create(EPD_W, EPD_H);
lv_display_set_flush_cb(display, epd_flush_cb);
// Set up buffer with enough size to reduce number of flush operations
lv_display_set_buffers(display, lvgl_buf, NULL, sizeof(lvgl_buf), LV_DISPLAY_RENDER_MODE_PARTIAL);
// Set color format to 8-bit grayscale for E-Paper
lv_display_set_color_format(display, LV_COLOR_FORMAT_L8);
// Set white background
lv_obj_set_style_bg_color(lv_screen_active(), lv_color_hex(0xFFFFFF), LV_PART_MAIN);
}
EPD by Elecrow :
#include "EPD.h"
void EPD_ReadBusy(void)
{
while (1)
{
if (EPD_ReadBUSY == 0)
{
break;
}
}
}
void EPD_RESET(void)
{
EPD_RES_Set();
delay(100);
EPD_RES_Clr();
delay(10);
EPD_RES_Set();
delay(10);
}
void EPD_Sleep(void)
{
EPD_WR_REG(0x10);
EPD_WR_DATA8(0x01);
delay(50);
}
void EPD_Update(void)
{
EPD_WR_REG(0x22);
EPD_WR_DATA8(0xF7);
EPD_WR_REG(0x20);
EPD_ReadBusy();
}
void EPD_Update_Fast(void)
{
EPD_WR_REG(0x22);
EPD_WR_DATA8(0xC7);
EPD_WR_REG(0x20);
EPD_ReadBusy();
}
void EPD_Update_Part(void)
{
EPD_WR_REG(0x22);
EPD_WR_DATA8(0xFF);
EPD_WR_REG(0x20);
EPD_ReadBusy();
}
void EPD_Address_Set(uint16_t xs, uint16_t ys, uint16_t xe, uint16_t ye)
{
EPD_WR_REG(0x44); // SET_RAM_X_ADDRESS_START_END_POSITION
EPD_WR_DATA8((xs >> 3) & 0xFF);
EPD_WR_DATA8((xe >> 3) & 0xFF);
EPD_WR_REG(0x45); // SET_RAM_Y_ADDRESS_START_END_POSITION
EPD_WR_DATA8(ys & 0xFF);
EPD_WR_DATA8((ys >> 8) & 0xFF);
EPD_WR_DATA8(ye & 0xFF);
EPD_WR_DATA8((ye >> 8) & 0xFF);
}
void EPD_SetCursor(uint16_t xs, uint16_t ys)
{
EPD_WR_REG(0x4E); // SET_RAM_X_ADDRESS_COUNTER
EPD_WR_DATA8(xs & 0xFF);
EPD_WR_REG(0x4F); // SET_RAM_Y_ADDRESS_COUNTER
EPD_WR_DATA8(ys & 0xFF);
EPD_WR_DATA8((ys >> 8) & 0xFF);
}
void EPD_Init(void)
{
EPD_RESET();
EPD_ReadBusy();
EPD_WR_REG(0x12); // soft reset
EPD_ReadBusy();
EPD_WR_REG(0x21); // Display update control
EPD_WR_DATA8(0x40);
EPD_WR_DATA8(0x00);
EPD_WR_REG(0x3C); //BorderWavefrom
EPD_WR_DATA8(0x05);
EPD_WR_REG(0x11); // data entry mode
EPD_WR_DATA8(0x03); // X-mode
EPD_Address_Set(0, 0, EPD_W - 1, EPD_H - 1);
EPD_SetCursor(0, 0);
EPD_ReadBusy();
}
void EPD_Init_Fast(uint8_t mode)
{
EPD_RESET();
EPD_ReadBusy();
EPD_WR_REG(0x12); // soft reset
EPD_ReadBusy();
EPD_WR_REG(0x21);
EPD_WR_DATA8(0x40);
EPD_WR_DATA8(0x00);
EPD_WR_REG(0x3C);
EPD_WR_DATA8(0x05);
if (mode == Fast_Seconds_1_5s)
{
EPD_WR_REG(0x1A); // Write to temperature register
EPD_WR_DATA8(0x6E);
}
else if (mode == Fast_Seconds_1_s)
{
EPD_WR_REG(0x1A); // Write to temperature register
EPD_WR_DATA8(0x5A);
}
EPD_WR_REG(0x22); // Load temperature value
EPD_WR_DATA8(0x91);
EPD_WR_REG(0x20);
EPD_ReadBusy();
EPD_WR_REG(0x11); // data entry mode
EPD_WR_DATA8(0x03); // X-mode
EPD_Address_Set(0, 0, EPD_W - 1, EPD_H - 1);
EPD_SetCursor(0, 0);
EPD_ReadBusy();
}
void EPD_Clear(void)
{
uint16_t i, j, Width, Height;
Width = (EPD_W % 8 == 0) ? (EPD_W / 8) : (EPD_W / 8 + 1);
Height = EPD_H;
EPD_Init();
EPD_WR_REG(0x24);
for (j = 0; j < Height; j++)
{
for (i = 0; i < Width; i++)
{
EPD_WR_DATA8(0xFF);
}
}
EPD_WR_REG(0x26);
for (j = 0; j < Height; j++)
{
for (i = 0; i < Width; i++)
{
EPD_WR_DATA8(0xFF);
}
}
EPD_Update();
// EPD_Update_Fast();
// EPD_Update_Part();
}
void EPD_Clear_R26A6H(void)
{
uint16_t i, j, Width, Height;
Width = (EPD_W % 8 == 0) ? (EPD_W / 8) : (EPD_W / 8 + 1);
Height = EPD_H;
EPD_Init();
EPD_WR_REG(0x26);
for (j = 0; j < Height; j++)
{
for (i = 0; i < Width; i++)
{
EPD_WR_DATA8(0xFF);
}
}
EPD_WR_REG(0xA6);
for (j = 0; j < Height; j++)
{
for (i = 0; i < Width; i++)
{
EPD_WR_DATA8(0xFF);
}
}
}
void EPD_Display(const uint8_t *Image)
{
uint16_t i, j, Width, Height;
Width = (EPD_W % 8 == 0) ? (EPD_W / 8) : (EPD_W / 8 + 1);
Height = EPD_H;
EPD_WR_REG(0x24);
for (j = 0; j < Height; j++)
{
for (i = 0; i < Width; i++)
{
EPD_WR_DATA8(Image[i + j * Width]);
}
}
EPD_Update();
}
void EPD_Display_Fast(const uint8_t *Image)
{
uint16_t i, j, Width, Height;
Width = (EPD_W % 8 == 0) ? (EPD_W / 8) : (EPD_W / 8 + 1);
Height = EPD_H;
EPD_WR_REG(0x24);
for (j = 0; j < Height; j++)
{
for (i = 0; i < Width; i++)
{
EPD_WR_DATA8(Image[i + j * Width]);
}
}
EPD_Update_Fast();
}
void EPD_Display_Part(uint16_t x, uint16_t y, uint16_t sizex, uint16_t sizey, const uint8_t *Image)
{
uint16_t Width, Height, i, j;
Width = (sizex % 8 == 0) ? (sizex / 8) : (sizex / 8 + 1);
Height = sizey;
EPD_WR_REG(0x3C); //BorderWavefrom,
EPD_WR_DATA8(0x80);
EPD_WR_REG(0x21);
EPD_WR_DATA8(0x00);
EPD_WR_DATA8(0x00);
EPD_WR_REG(0x3C);
EPD_WR_DATA8(0x80);
EPD_Address_Set(x, y, x + sizex - 1, y + sizey - 1);
EPD_SetCursor(x, y);
EPD_WR_REG(0x24);
for (j = 0; j < Height; j++)
{
for (i = 0; i < Width; i++)
{
EPD_WR_DATA8(Image[i + j * Width]);
}
}
EPD_Update_Part();
}
void EPD_Init_Part(void)
{
EPD_RESET();
EPD_ReadBusy();
EPD_WR_REG(0x12); // soft reset
EPD_ReadBusy();
EPD_WR_REG(0x21); // Display update control
EPD_WR_DATA8(0x00);
EPD_WR_DATA8(0x00);
EPD_WR_REG(0x3C); // BorderWaveform
EPD_WR_DATA8(0x80);
EPD_WR_REG(0x11); // data entry mode
EPD_WR_DATA8(0x03); // X-mode
EPD_ReadBusy();
}
Complete code :
Elecrow examples :