#include #include "sdkconfig.h" /* FreeRTOS */ #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" #include "freertos/task.h" /* IDF drivers */ #include "esp_log.h" #include "esp_sleep.h" #include "esp_spi_flash.h" #include "esp_system.h" /* EPD driver */ #include "epd_board_specific.h" #include "epd_driver.h" #include "epd_highlevel.h" /* LVGL */ #include "lvgl.h" /* Private */ #include "app_bkp_ram.h" #include "app_lvgl.h" #define LVGL_TASK_HEAP 4096 #define LVGL_TASK_INTERVAL 100 #define APP_LOG_TAG "APP_LVGL" #define EPD_MAX_REFR_COUNT 8 static EpdiyHighlevelState s_epd_hl; static int s_temp = 26; static lv_disp_draw_buf_t s_disp_buf; static lv_color_t *s_screen_buf; static lv_disp_drv_t s_disp_drv; static SemaphoreHandle_t s_lvgl_semphr; static SemaphoreHandle_t s_lvgl_flush_cmd_semphr; /* This semaphore is used to start the drawing */ static SemaphoreHandle_t s_lvgl_flush_ready_semphr; /* This semaphore is used to notify the drawing has completed */ static void app_lvgl_task(void *pvParameters); static void app_lvgl_flush_cb(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p); static void app_lvgl_set_px_cb(lv_disp_drv_t *disp_drv, uint8_t *buf, lv_coord_t buf_w, lv_coord_t x, lv_coord_t y, lv_color_t color, lv_opa_t opa); static void app_lvgl_log_cb(const char *buf); int app_lvgl_init(void) { /* Initialize EPDIY */ epd_init(EPD_OPTIONS_DEFAULT); s_epd_hl = epd_hl_init(EPD_BUILTIN_WAVEFORM); s_screen_buf = (lv_color_t *)epd_hl_get_framebuffer(&s_epd_hl); /* Initialize LVGL */ lv_init(); lv_log_register_print_cb(app_lvgl_log_cb); lv_disp_draw_buf_init(&s_disp_buf, s_screen_buf, NULL, 960 * 540); lv_disp_drv_init(&s_disp_drv); s_disp_drv.user_data = &s_epd_hl; s_disp_drv.draw_buf = &s_disp_buf; s_disp_drv.hor_res = 960; s_disp_drv.ver_res = 540; s_disp_drv.full_refresh = true; s_disp_drv.flush_cb = app_lvgl_flush_cb; s_disp_drv.set_px_cb = app_lvgl_set_px_cb; lv_disp_drv_register(&s_disp_drv); s_lvgl_semphr = xSemaphoreCreateMutex(); if (s_lvgl_semphr == NULL) { ESP_LOGE(APP_LOG_TAG, "Failed to create LVGL semaphore."); return -1; } s_lvgl_flush_ready_semphr = xSemaphoreCreateBinary(); if (s_lvgl_flush_ready_semphr == NULL) { ESP_LOGE(APP_LOG_TAG, "Failed to create flush semaphore."); return -2; } s_lvgl_flush_cmd_semphr = xSemaphoreCreateBinary(); if (s_lvgl_flush_cmd_semphr == NULL) { ESP_LOGE(APP_LOG_TAG, "Failed to create flush command semaphore."); return -3; } if (xTaskCreate(app_lvgl_task, "LV_TASK", LVGL_TASK_HEAP, NULL, 2, NULL) != pdPASS) { ESP_LOGE(APP_LOG_TAG, "Failed to create LVGL task, available heap: %d.", esp_get_free_heap_size()); return -4; } ESP_LOGI(APP_LOG_TAG, "LVGL initialized."); return 0; } int app_lvgl_deinit(void) { epd_powerdown_lilygo_t5_47(); epd_deinit(); /* LVGL is not required to be de-init. */ return 0; } int app_lvgl_lock(uint32_t ms) { if (xSemaphoreTake(s_lvgl_semphr, pdMS_TO_TICKS(ms)) != pdPASS) { return -1; } return 0; } int app_lvgl_unlock(void) { if (xSemaphoreGive(s_lvgl_semphr) != pdPASS) { return -1; } return 0; } int app_lvgl_start_flush(void) { if (xSemaphoreGive(s_lvgl_flush_cmd_semphr) != pdPASS) { return -1; } return 0; } int app_lvgl_wait_flush(uint32_t timeout_ms) { if (xSemaphoreTake(s_lvgl_flush_ready_semphr, pdMS_TO_TICKS(timeout_ms)) != pdPASS) { return -1; } return 0; } static void app_lvgl_task(void *pvParameters) { if (xSemaphoreTake(s_lvgl_flush_cmd_semphr, portMAX_DELAY) != pdPASS) { vTaskSuspend(NULL); } ESP_LOGI(APP_LOG_TAG, "Received flush GO signal, start flushing..."); epd_poweron(); epd_fullclear(&s_epd_hl, s_temp); epd_poweroff(); for (;;) { if (app_lvgl_lock(50) == 0) { lv_timer_handler(); ESP_LOGD(APP_LOG_TAG, "LVGL timer handler executed."); app_lvgl_unlock(); } vTaskDelay(pdMS_TO_TICKS(LVGL_TASK_INTERVAL)); } } static void app_lvgl_flush_cb(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p) { EpdiyHighlevelState *hl = disp_drv->user_data; EpdRect rect = { .x = area->x1, .y = area->y1, .width = area->x2 - area->x1 + 1, .height = area->y2 - area->y1 + 1, }; ESP_LOGI(APP_LOG_TAG, "Flush called."); epd_poweron(); epd_hl_update_area(hl, MODE_GL16, s_temp, rect); epd_poweroff(); // Done flushing.. xSemaphoreGive(s_lvgl_flush_ready_semphr); lv_disp_flush_ready(disp_drv); } static void app_lvgl_set_px_cb(lv_disp_drv_t *disp_drv, uint8_t *buf, lv_coord_t buf_w, lv_coord_t x, lv_coord_t y, lv_color_t color, lv_opa_t opa) { uint32_t px_offset = (buf_w * y / 2) + x / 2; uint8_t pix_y = lv_color_brightness(color); pix_y = (pix_y / 16) & 0x0FU; if (x % 2 == 0) { buf[px_offset] &= 0xF0; buf[px_offset] |= pix_y; } else { buf[px_offset] &= 0x0F; buf[px_offset] |= pix_y << 4; } } static void app_lvgl_log_cb(const char *buf) { ESP_LOGI(APP_LOG_TAG, "LVGL: %s", buf); }