Added lvgl init, display not refresh correctly
This commit is contained in:
@@ -151,7 +151,7 @@ esp_err_t EInkDisplayHandler::full_write(const uint8_t* framebuffer) {
|
|||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t EInkDisplayHandler::partial_refresh(const uint8_t* framebuffer, const RefreshArea& area) {
|
esp_err_t EInkDisplayHandler::partial_refresh(const uint8_t* partial_framebuffer, const RefreshArea& area) {
|
||||||
ESP_LOGI(TAG, "Starting partial refresh (0.3 seconds)...");
|
ESP_LOGI(TAG, "Starting partial refresh (0.3 seconds)...");
|
||||||
esp_err_t err = ESP_OK;
|
esp_err_t err = ESP_OK;
|
||||||
|
|
||||||
@@ -179,13 +179,18 @@ esp_err_t EInkDisplayHandler::partial_refresh(const uint8_t* framebuffer, const
|
|||||||
}
|
}
|
||||||
// Step 3: Set partial window
|
// Step 3: Set partial window
|
||||||
{
|
{
|
||||||
|
if (area.x1 % 8 != 0 || area.x2 % 8 != 7) {
|
||||||
|
ESP_LOGE(TAG, "Partial refresh area x1 and x2 must be byte-aligned (x1 %% 8 == 0 and x2 %% 8 == 7)");
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
// no rounding needed, area is expected to be aligned
|
||||||
std::vector<uint8_t> window_data = {
|
std::vector<uint8_t> window_data = {
|
||||||
// x start
|
// x start
|
||||||
static_cast<uint8_t>((area.x1 >> 8) & 0xFF), // x start high byte
|
static_cast<uint8_t>((area.x1 >> 8) & 0xFF), // x start high byte
|
||||||
static_cast<uint8_t>(area.x1 & 0xFF), // x start low byte
|
static_cast<uint8_t>(area.x1 & 0x07), // x start low byte
|
||||||
// x end
|
// x end
|
||||||
static_cast<uint8_t>((area.x2 >> 8) & 0xFF),
|
static_cast<uint8_t>((area.x2 >> 8) & 0xFF),
|
||||||
static_cast<uint8_t>(area.x2 & 0xFF),
|
static_cast<uint8_t>(area.x2 & 0x07),
|
||||||
// y start
|
// y start
|
||||||
static_cast<uint8_t>((area.y1 >> 8) & 0xFF),
|
static_cast<uint8_t>((area.y1 >> 8) & 0xFF),
|
||||||
static_cast<uint8_t>(area.y1 & 0xFF),
|
static_cast<uint8_t>(area.y1 & 0xFF),
|
||||||
@@ -209,9 +214,9 @@ esp_err_t EInkDisplayHandler::partial_refresh(const uint8_t* framebuffer, const
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
err = transfer_spi_data(framebuffer, DISPLAY_BUFFER_SIZE, transaction_guard.transaction_id()); // Send new framebuffer data
|
err = transfer_spi_data(partial_framebuffer, DISPLAY_BUFFER_SIZE, transaction_guard.transaction_id()); // Send new framebuffer data
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
ESP_LOGE(TAG, "Failed to send framebuffer data for partial refresh: %s", esp_err_to_name(err));
|
ESP_LOGE(TAG, "Failed to send partial_framebuffer data for partial refresh: %s", esp_err_to_name(err));
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
354
main/display/lvgl_handler.cpp
Normal file
354
main/display/lvgl_handler.cpp
Normal file
@@ -0,0 +1,354 @@
|
|||||||
|
#include "display/lvgl_handler.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "common/semaphore_guard.h"
|
||||||
|
#include "common/constants.h"
|
||||||
|
#include <portmacro.h>
|
||||||
|
|
||||||
|
#define DISPLAY_BUFFER_SIZE (DISPLAY_WIDTH * DISPLAY_HEIGHT) / 8 // 1 bit per pixels
|
||||||
|
#define LVGL_BUFFER_SIZE (DISPLAY_BUFFER_SIZE + 8) // 1 bit per pixels + 8 bytes for palette
|
||||||
|
#define TAG "LVGLHandler"
|
||||||
|
|
||||||
|
LVGLHandler::LVGLHandler(
|
||||||
|
std::unique_ptr<EInkDisplayHandler> display_handler_in
|
||||||
|
) : display_handler_(std::move(display_handler_in)) {
|
||||||
|
lvgl_mutex_ = xSemaphoreCreateMutex();
|
||||||
|
if (lvgl_mutex_ == nullptr) {
|
||||||
|
ESP_LOGE(TAG, "Failed to create LVGL mutex");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LVGLHandler::~LVGLHandler() {
|
||||||
|
if (lvgl_display_ != nullptr) {
|
||||||
|
lv_display_delete(lvgl_display_);
|
||||||
|
lvgl_display_ = nullptr;
|
||||||
|
}
|
||||||
|
if (lvgl_touch_indev_ != nullptr) {
|
||||||
|
lvgl_port_remove_touch(lvgl_touch_indev_);
|
||||||
|
lvgl_touch_indev_ = nullptr;
|
||||||
|
}
|
||||||
|
if (lvgl_draw_buf_ != nullptr) {
|
||||||
|
lv_draw_buf_destroy(lvgl_draw_buf_);
|
||||||
|
lvgl_draw_buf_ = nullptr;
|
||||||
|
}
|
||||||
|
if (framebuffer_ != nullptr) {
|
||||||
|
heap_caps_free(framebuffer_);
|
||||||
|
framebuffer_ = nullptr;
|
||||||
|
}
|
||||||
|
if (lvgl_mutex_ != nullptr) {
|
||||||
|
vSemaphoreDelete(lvgl_mutex_);
|
||||||
|
lvgl_mutex_ = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t LVGLHandler::initLVGL(EventGroupHandle_t system_event_group) {
|
||||||
|
esp_err_t err = initLVGLPort_();
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
err = initLVGLDisplay_();
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
err = registerLVGLTouch_();
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
auto lvgl_tick_timer_callback = [](TimerHandle_t xTimer) {
|
||||||
|
lv_tick_inc(5);
|
||||||
|
};
|
||||||
|
TickType_t lvgl_tick_period = pdMS_TO_TICKS(5);
|
||||||
|
if (lvgl_tick_period == 0) {
|
||||||
|
lvgl_tick_period = 1; // ensure at least 1 tick to avoid FreeRTOS assert
|
||||||
|
}
|
||||||
|
ESP_LOGV(TAG, "Creating LVGL tick timer with period %u ticks...\n", (unsigned)lvgl_tick_period);
|
||||||
|
TimerHandle_t lvgl_tick_timer = xTimerCreate(
|
||||||
|
"lvgl_tick_timer",
|
||||||
|
lvgl_tick_period,
|
||||||
|
pdTRUE,
|
||||||
|
NULL,
|
||||||
|
lvgl_tick_timer_callback
|
||||||
|
);
|
||||||
|
if (lvgl_tick_timer == NULL) {
|
||||||
|
ESP_LOGE("Main", "Failed to create LVGL tick timer");
|
||||||
|
vTaskDelay(5000 / portTICK_PERIOD_MS);
|
||||||
|
return ESP_ERR_NO_MEM;
|
||||||
|
}
|
||||||
|
ESP_LOGV(TAG, "Starting LVGL tick timer...\n");
|
||||||
|
xTimerStart(lvgl_tick_timer, 0);
|
||||||
|
|
||||||
|
if (system_event_group != nullptr) {
|
||||||
|
xEventGroupSetBits(system_event_group, DISPLAY_READY_BIT | TOUCH_CALIBRATED_BIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Private methods
|
||||||
|
//
|
||||||
|
|
||||||
|
void LVGLHandler::rounder_cb_(lv_display_t* disp, lv_area_t* area) {
|
||||||
|
// align x to byte boundary
|
||||||
|
area->x1 = (area->x1 & ~0x7);
|
||||||
|
area->x2 = (area->x2 | 0x7);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LVGLHandler::flush_cb_(lv_display_t* disp, const lv_area_t* area, uint8_t* px_map) {
|
||||||
|
LVGLHandler* handler = static_cast<LVGLHandler*>(lv_display_get_user_data(disp));
|
||||||
|
if (handler == nullptr || handler->display_handler_ == nullptr) {
|
||||||
|
ESP_LOGE(TAG, "Invalid handler in flush callback");
|
||||||
|
lv_display_flush_ready(disp);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uint8_t* pixel_data = px_map + 8; // Skip palette
|
||||||
|
//
|
||||||
|
ESP_LOGI(TAG, "Flush callback: x1=%d, y1=%d, x2=%d, y2=%d", area->x1, area->y1, area->x2, area->y2);
|
||||||
|
// take mutex
|
||||||
|
SemaphoreGuard guard(handler->lvgl_mutex_);
|
||||||
|
if (!guard.take(pdMS_TO_TICKS(5000))) {
|
||||||
|
ESP_LOGE(TAG, "LVGL mutex timeout in flush callback");
|
||||||
|
lv_display_flush_ready(disp);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy data to framebuffer
|
||||||
|
int32_t area_w = lv_area_get_width(area);
|
||||||
|
int32_t area_h = lv_area_get_height(area);
|
||||||
|
if (area->x1 == 0 && area->y1 == 0 && area_w == DISPLAY_WIDTH && area_h == DISPLAY_HEIGHT) {
|
||||||
|
ESP_LOGI(TAG, "Full screen update");
|
||||||
|
memcpy(framebuffer_, pixel_data, DISPLAY_BUFFER_SIZE);
|
||||||
|
// request full refresh
|
||||||
|
esp_err_t err = handler->display_handler_->refresh_display();
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "Full refresh request failed: %s", esp_err_to_name(err));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// partial update
|
||||||
|
ESP_LOGI(TAG, "Partial update: x1=%d, y1=%d, w=%d, h=%d", area->x1, area->y1, area_w, area_h);
|
||||||
|
|
||||||
|
// 1. Calculate Strides
|
||||||
|
int32_t fb_stride_bytes = DISPLAY_WIDTH / 8; // Stride of the full framebuffer
|
||||||
|
int32_t src_stride_bytes = area_w / 8; // Stride of the LVGL partial buffer
|
||||||
|
|
||||||
|
// 2. Safety: Ensure we don't write out of bounds
|
||||||
|
// (The rounder_cb should prevent this, but clipping is safe practice)
|
||||||
|
int32_t safe_h = area_h;
|
||||||
|
if (area->y1 + safe_h > DISPLAY_HEIGHT) {
|
||||||
|
safe_h = DISPLAY_HEIGHT - area->y1;
|
||||||
|
ESP_LOGW(TAG, "Clipping height to %d to prevent OOB", safe_h);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Iterate Rows
|
||||||
|
uint8_t* partial_buffer = new uint8_t[src_stride_bytes * safe_h];
|
||||||
|
if (partial_buffer == nullptr) {
|
||||||
|
ESP_LOGE(TAG, "Failed to allocate partial buffer for refresh");
|
||||||
|
lv_display_flush_ready(disp);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (int32_t y = 0; y < safe_h; y++) {
|
||||||
|
// Calculate Absolute Y in Framebuffer
|
||||||
|
int32_t fb_y = area->y1 + y;
|
||||||
|
|
||||||
|
// Calculate Source Pointer (Start of current line in partial buffer)
|
||||||
|
// NOTE: Use src_stride_bytes, NOT fb_stride_bytes
|
||||||
|
uint8_t* src_line = pixel_data + (y * src_stride_bytes);
|
||||||
|
|
||||||
|
// Calculate Destination Pointer (Start of current line in full framebuffer)
|
||||||
|
// Offset = (Row * Stride) + (X_Start_Byte)
|
||||||
|
uint8_t* dst_line = handler->framebuffer_ + (fb_y * fb_stride_bytes) + (area->x1 / 8);
|
||||||
|
|
||||||
|
// 4. Block Copy
|
||||||
|
// Since x1 is byte-aligned by rounder_cb, we can copy the whole row directly.
|
||||||
|
memcpy(dst_line, src_line, src_stride_bytes);
|
||||||
|
// also copy to partial_buffer for refresh
|
||||||
|
memcpy(partial_buffer + (y * src_stride_bytes), src_line, src_stride_bytes);
|
||||||
|
}
|
||||||
|
// 5. Request partial refresh
|
||||||
|
esp_err_t err = handler->display_handler_->partial_refresh(partial_buffer,
|
||||||
|
RefreshArea(area->x1, area->y1, area->x2, area->y2));
|
||||||
|
delete[] partial_buffer;
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "Partial refresh request failed: %s", esp_err_to_name(err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//
|
||||||
|
lv_display_flush_ready(disp);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LVGLHandler::touch_read_cb_(lv_indev_t* indev, lv_indev_data_t* data) {
|
||||||
|
LVGLHandler* handler = static_cast<LVGLHandler*>(lv_indev_get_user_data(indev));
|
||||||
|
if (handler == nullptr || handler->display_handler_ == nullptr) {
|
||||||
|
data->state = LV_INDEV_STATE_RELEASED;
|
||||||
|
ESP_LOGE(TAG, "Invalid handler in touch read callback");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable touch input during display refresh (BUSY)
|
||||||
|
if (handler->display_handler_->is_busy()) {
|
||||||
|
data->state = LV_INDEV_STATE_RELEASED;
|
||||||
|
data->continue_reading = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_lcd_touch_handle_t tp_handle = handler->display_handler_->get_touch_handle();
|
||||||
|
if (tp_handle == nullptr) {
|
||||||
|
data->state = LV_INDEV_STATE_RELEASED;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read touch data from GT911
|
||||||
|
esp_err_t ret = esp_lcd_touch_read_data(tp_handle);
|
||||||
|
if (ret == ESP_OK) {
|
||||||
|
uint8_t touch_cnt = 0;
|
||||||
|
// Get touch data using new API
|
||||||
|
esp_lcd_touch_point_data_t point_data[1];
|
||||||
|
esp_lcd_touch_get_data(tp_handle, point_data, &touch_cnt, 1);
|
||||||
|
|
||||||
|
if (touch_cnt > 0) {
|
||||||
|
ESP_LOGI(TAG, "Touch data read successfully: x=%d, y=%d", point_data[0].x, point_data[0].y);
|
||||||
|
data->point.x = point_data[0].x;
|
||||||
|
data->point.y = point_data[0].y;
|
||||||
|
data->state = LV_INDEV_STATE_PRESSED;
|
||||||
|
} else {
|
||||||
|
data->state = LV_INDEV_STATE_RELEASED;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
data->state = LV_INDEV_STATE_RELEASED;
|
||||||
|
}
|
||||||
|
|
||||||
|
data->continue_reading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t LVGLHandler::initLVGLDisplay_() {
|
||||||
|
if (display_handler_ == nullptr) {
|
||||||
|
return ESP_ERR_INVALID_STATE;
|
||||||
|
}
|
||||||
|
esp_err_t err = ESP_OK;
|
||||||
|
|
||||||
|
// Lock LVGL to prevent the timer task from accessing partially initialized display
|
||||||
|
if (!lvgl_port_lock(pdMS_TO_TICKS(1000))) {
|
||||||
|
ESP_LOGE(TAG, "Failed to lock LVGL port for display initialization");
|
||||||
|
return ESP_ERR_TIMEOUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create LVGL display
|
||||||
|
lvgl_display_ = lv_display_create(DISPLAY_WIDTH, DISPLAY_HEIGHT);
|
||||||
|
if (lvgl_display_ == nullptr) {
|
||||||
|
ESP_LOGE(TAG, "Failed to create LVGL display");
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set framebuffer
|
||||||
|
framebuffer_ = (uint8_t*)heap_caps_malloc(LVGL_BUFFER_SIZE, MALLOC_CAP_SPIRAM);
|
||||||
|
if (framebuffer_ != nullptr) {
|
||||||
|
framebuffer_in_psram_ = true;
|
||||||
|
ESP_LOGI(TAG, "Framebuffer allocated in PSRAM (%zu bytes)", LVGL_BUFFER_SIZE);
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(TAG, "PSRAM not available, allocating framebuffer in internal RAM");
|
||||||
|
framebuffer_ = (uint8_t*)heap_caps_malloc(LVGL_BUFFER_SIZE, MALLOC_CAP_INTERNAL);
|
||||||
|
framebuffer_in_psram_ = false;
|
||||||
|
if (framebuffer_ == nullptr) {
|
||||||
|
ESP_LOGE(TAG, "Failed to allocate framebuffer");
|
||||||
|
lvgl_port_unlock();
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
ESP_LOGI(TAG, "Framebuffer allocated in internal RAM (%zu bytes)", LVGL_BUFFER_SIZE);
|
||||||
|
}
|
||||||
|
memset(framebuffer_, 0xFF, LVGL_BUFFER_SIZE); // Initialize to white
|
||||||
|
// Create a draw buffer covering the entire display
|
||||||
|
lvgl_draw_buf_ = lv_draw_buf_create(DISPLAY_WIDTH, DISPLAY_HEIGHT, LV_COLOR_FORMAT_I1, LV_STRIDE_AUTO);
|
||||||
|
if (lvgl_draw_buf_ == nullptr) {
|
||||||
|
ESP_LOGE(TAG, "Failed to create LVGL draw buffer");
|
||||||
|
lv_display_delete(lvgl_display_);
|
||||||
|
lvgl_port_unlock();
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
lv_display_set_draw_buffers(lvgl_display_, lvgl_draw_buf_, nullptr);
|
||||||
|
lv_display_set_render_mode(lvgl_display_, LV_DISPLAY_RENDER_MODE_DIRECT);
|
||||||
|
//
|
||||||
|
ESP_LOGI(TAG, "Performing initial display write...");
|
||||||
|
err = display_handler_->full_write(framebuffer_);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "Initial display write failed: %d", err);
|
||||||
|
} else {
|
||||||
|
ESP_LOGI(TAG, "Initial display write complete");
|
||||||
|
}
|
||||||
|
// Configure LVGL display
|
||||||
|
lv_display_set_color_format(lvgl_display_, LV_COLOR_FORMAT_I1);
|
||||||
|
lv_display_set_user_data(lvgl_display_, this);
|
||||||
|
|
||||||
|
lv_display_add_event_cb(lvgl_display_, [](lv_event_t* e) {
|
||||||
|
LVGLHandler* handler = static_cast<LVGLHandler*>(lv_display_get_user_data(static_cast<lv_display_t*>(lv_event_get_target(e))));
|
||||||
|
if (handler != nullptr) {
|
||||||
|
handler->rounder_cb_(static_cast<lv_display_t*>(lv_event_get_target(e)),
|
||||||
|
static_cast<lv_area_t*>(lv_event_get_param(e)));
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(TAG, "Invalid handler in rounder callback");
|
||||||
|
}
|
||||||
|
}, LV_EVENT_INVALIDATE_AREA, lvgl_display_);
|
||||||
|
|
||||||
|
lv_display_set_flush_cb(lvgl_display_, [](lv_display_t* disp, const lv_area_t* area, uint8_t* px_map) {
|
||||||
|
LVGLHandler* handler = static_cast<LVGLHandler*>(lv_display_get_user_data(disp));
|
||||||
|
if (handler != nullptr) {
|
||||||
|
handler->flush_cb_(disp, area, px_map);
|
||||||
|
} else {
|
||||||
|
lv_display_flush_ready(disp);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Unlock LVGL now that display is fully initialized
|
||||||
|
lvgl_port_unlock();
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "LVGL display registered");
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t LVGLHandler::registerLVGLTouch_() {
|
||||||
|
if (display_handler_ == nullptr) {
|
||||||
|
return ESP_ERR_INVALID_STATE;
|
||||||
|
}
|
||||||
|
esp_lcd_touch_handle_t tp_handle = display_handler_->get_touch_handle();
|
||||||
|
if (tp_handle == nullptr) {
|
||||||
|
ESP_LOGE(TAG, "Touch handle is NULL — touch initialization failed; skipping LVGL touch registration");
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lvgl_port_touch_cfg_t touch_cfg = {
|
||||||
|
.disp = lvgl_display_,
|
||||||
|
.handle = tp_handle,
|
||||||
|
.scale = {}, // Default scaling
|
||||||
|
};
|
||||||
|
|
||||||
|
lvgl_touch_indev_ = lvgl_port_add_touch(&touch_cfg);
|
||||||
|
if (lvgl_touch_indev_ == nullptr) {
|
||||||
|
ESP_LOGE(TAG, "Failed to register LVGL touch input");
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
|
||||||
|
lv_indev_set_user_data(lvgl_touch_indev_, this);
|
||||||
|
lv_indev_set_read_cb(lvgl_touch_indev_, [](lv_indev_t* indev, lv_indev_data_t* data) {
|
||||||
|
LVGLHandler* handler = static_cast<LVGLHandler*>(lv_indev_get_user_data(indev));
|
||||||
|
if (handler != nullptr) {
|
||||||
|
handler->touch_read_cb_(indev, data);
|
||||||
|
} else {
|
||||||
|
data->state = LV_INDEV_STATE_RELEASED;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "LVGL touch input registered");
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t LVGLHandler::initLVGLPort_() {
|
||||||
|
const lvgl_port_cfg_t lvgl_cfg = ESP_LVGL_PORT_INIT_CONFIG();
|
||||||
|
esp_err_t err = lvgl_port_init(&lvgl_cfg);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "LVGL port initialization failed: %s", esp_err_to_name(err));
|
||||||
|
vTaskDelay(5000 / portTICK_PERIOD_MS);
|
||||||
|
return ESP_ERR_INVALID_STATE;
|
||||||
|
}
|
||||||
|
ESP_LOGI(TAG, "LVGL port initialized successfully.\n");
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
39
main/display/lvgl_handler.h
Normal file
39
main/display/lvgl_handler.h
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "lvgl.h"
|
||||||
|
#include "esp_lvgl_port.h"
|
||||||
|
#include "display/eink_display_handler.h"
|
||||||
|
#include "freertos/semphr.h"
|
||||||
|
#include "freertos/event_groups.h"
|
||||||
|
#include "esp_err.h"
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
|
||||||
|
class LVGLHandler {
|
||||||
|
public:
|
||||||
|
LVGLHandler(
|
||||||
|
// an owning pointer to the display handler
|
||||||
|
// The display handler must outlive the LVGLHandler
|
||||||
|
// The display handler must be fully initialized before calling initLVGLDisplay
|
||||||
|
std::unique_ptr<EInkDisplayHandler> display_handler_in
|
||||||
|
);
|
||||||
|
~LVGLHandler();
|
||||||
|
esp_err_t initLVGL(EventGroupHandle_t system_event_group = nullptr);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void rounder_cb_(lv_display_t* disp, lv_area_t* area);
|
||||||
|
void flush_cb_(lv_display_t* disp, const lv_area_t* area, uint8_t* px_map);
|
||||||
|
void touch_read_cb_(lv_indev_t* indev, lv_indev_data_t* data);
|
||||||
|
esp_err_t initLVGLDisplay_();
|
||||||
|
esp_err_t registerLVGLTouch_();
|
||||||
|
esp_err_t initLVGLPort_();
|
||||||
|
|
||||||
|
std::unique_ptr<EInkDisplayHandler> display_handler_ = nullptr;
|
||||||
|
|
||||||
|
lv_display_t* lvgl_display_ = nullptr;
|
||||||
|
lv_indev_t* lvgl_touch_indev_ = nullptr;
|
||||||
|
lv_draw_buf_t* lvgl_draw_buf_ = nullptr;
|
||||||
|
uint8_t* framebuffer_ = nullptr;
|
||||||
|
bool framebuffer_in_psram_ = false;
|
||||||
|
|
||||||
|
SemaphoreHandle_t lvgl_mutex_ = nullptr;
|
||||||
|
};
|
||||||
297
main/main.cpp
297
main/main.cpp
@@ -15,6 +15,7 @@
|
|||||||
#include "io/nvs_handler.h"
|
#include "io/nvs_handler.h"
|
||||||
#include "info/info.h"
|
#include "info/info.h"
|
||||||
#include "display/eink_display_handler.h"
|
#include "display/eink_display_handler.h"
|
||||||
|
#include "display/lvgl_handler.h"
|
||||||
#include "ui/ui_handler.h"
|
#include "ui/ui_handler.h"
|
||||||
#include "ui/app_registry.h"
|
#include "ui/app_registry.h"
|
||||||
#include "ui/apps/shutdown_app.h"
|
#include "ui/apps/shutdown_app.h"
|
||||||
@@ -26,6 +27,8 @@
|
|||||||
#include "network.h"
|
#include "network.h"
|
||||||
#include <esp_task_wdt.h>
|
#include <esp_task_wdt.h>
|
||||||
|
|
||||||
|
#include "lvgl.h"
|
||||||
|
|
||||||
// nvs storage namespaces, 15 characters max
|
// nvs storage namespaces, 15 characters max
|
||||||
#define DEFAULT_STORAGE_NAMESPACE "storage"
|
#define DEFAULT_STORAGE_NAMESPACE "storage"
|
||||||
#define WIFI_CREDENTIALS_STORAGE_NAMESPACE "wifi_cred"
|
#define WIFI_CREDENTIALS_STORAGE_NAMESPACE "wifi_cred"
|
||||||
@@ -40,120 +43,10 @@ void init_queues(
|
|||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
void app_main(void) {
|
|
||||||
display_chip_info();
|
|
||||||
|
|
||||||
QueueHandle_t touch_event_queue = NULL;
|
void EInk_Checkerboard(
|
||||||
EventGroupHandle_t system_event_group = NULL, system_lifecycle_event_group = NULL;
|
EInkDisplayHandler* display_handler
|
||||||
|
) {
|
||||||
init_queues(touch_event_queue, system_event_group, system_lifecycle_event_group);
|
|
||||||
if (touch_event_queue == NULL || system_event_group == NULL || system_lifecycle_event_group == NULL) {
|
|
||||||
ESP_LOGE("Main", "Failed to create one or more queues/event groups");
|
|
||||||
vTaskDelay(5000 / portTICK_PERIOD_MS);
|
|
||||||
return esp_restart();
|
|
||||||
}
|
|
||||||
ESP_LOGI(TAG, "Queues initialized.\n");
|
|
||||||
|
|
||||||
// Initialize LVGL
|
|
||||||
const lvgl_port_cfg_t lvgl_cfg = ESP_LVGL_PORT_INIT_CONFIG();
|
|
||||||
esp_err_t err = lvgl_port_init(&lvgl_cfg);
|
|
||||||
if (err != ESP_OK) {
|
|
||||||
ESP_LOGE(TAG, "LVGL port initialization failed: %s", esp_err_to_name(err));
|
|
||||||
vTaskDelay(5000 / portTICK_PERIOD_MS);
|
|
||||||
return esp_restart();
|
|
||||||
}
|
|
||||||
ESP_LOGI(TAG, "LVGL port initialized successfully.\n");
|
|
||||||
|
|
||||||
SemaphoreHandle_t lvgl_mutex = xSemaphoreCreateMutex();
|
|
||||||
if (lvgl_mutex == NULL) {
|
|
||||||
ESP_LOGE("Main", "Failed to create LVGL mutex");
|
|
||||||
vTaskDelay(5000 / portTICK_PERIOD_MS);
|
|
||||||
return esp_restart();
|
|
||||||
}
|
|
||||||
//
|
|
||||||
// KVStorageHandler* kv_storage_handler = new NVSStorageHandler(
|
|
||||||
// DEFAULT_STORAGE_NAMESPACE
|
|
||||||
// );
|
|
||||||
|
|
||||||
// auto wifi_handler = std::make_unique<WifiHandler>(
|
|
||||||
// std::unique_ptr<KVStorageHandler>(new NVSStorageHandler(WIFI_CREDENTIALS_STORAGE_NAMESPACE))
|
|
||||||
// );
|
|
||||||
// NetworkHandler* network_handler = new NetworkHandler(std::move(wifi_handler));
|
|
||||||
EInkDisplayHandler* display_handler = new EInkDisplayHandler();
|
|
||||||
//
|
|
||||||
// kv_storage_handler->init(system_event_group);
|
|
||||||
// network_handler->init(system_event_group);
|
|
||||||
|
|
||||||
// Initialize display and touch
|
|
||||||
display_handler->init_devices(system_event_group);
|
|
||||||
display_handler->clear_display();
|
|
||||||
// ESP_LOGV(TAG, "Starting touch task...\n");
|
|
||||||
// display_handler->start_touch_task();
|
|
||||||
// ESP_LOGV(TAG, "Touch task started.\n");
|
|
||||||
//
|
|
||||||
// LVGL tick timer
|
|
||||||
// auto lvgl_tick_timer_callback = [](TimerHandle_t xTimer) {
|
|
||||||
// lv_tick_inc(5);
|
|
||||||
// };
|
|
||||||
// TickType_t lvgl_tick_period = pdMS_TO_TICKS(5);
|
|
||||||
// if (lvgl_tick_period == 0) {
|
|
||||||
// lvgl_tick_period = 1; // ensure at least 1 tick to avoid FreeRTOS assert
|
|
||||||
// }
|
|
||||||
// ESP_LOGV(TAG, "Creating LVGL tick timer with period %u ticks...\n", (unsigned)lvgl_tick_period);
|
|
||||||
// TimerHandle_t lvgl_tick_timer = xTimerCreate(
|
|
||||||
// "lvgl_tick_timer",
|
|
||||||
// lvgl_tick_period,
|
|
||||||
// pdTRUE,
|
|
||||||
// NULL,
|
|
||||||
// lvgl_tick_timer_callback
|
|
||||||
// );
|
|
||||||
// if (lvgl_tick_timer == NULL) {
|
|
||||||
// ESP_LOGE("Main", "Failed to create LVGL tick timer");
|
|
||||||
// vTaskDelay(5000 / portTICK_PERIOD_MS);
|
|
||||||
// return esp_restart();
|
|
||||||
// }
|
|
||||||
// ESP_LOGV(TAG, "Starting LVGL tick timer...\n");
|
|
||||||
// xTimerStart(lvgl_tick_timer, 0);
|
|
||||||
|
|
||||||
//
|
|
||||||
ESP_LOGI(TAG, "Waiting for system to be ready...\n");
|
|
||||||
xEventGroupWaitBits(
|
|
||||||
system_event_group,
|
|
||||||
// DISPLAY_READY_BIT | STORAGE_READY_BIT | NETWORK_READY_BIT,
|
|
||||||
DISPLAY_READY_BIT,
|
|
||||||
// do not clear on exit, require explicit reset
|
|
||||||
pdFALSE,
|
|
||||||
pdTRUE,
|
|
||||||
portMAX_DELAY
|
|
||||||
);
|
|
||||||
ESP_LOGI(TAG, "System is ready. Starting main application...\n");
|
|
||||||
|
|
||||||
// Register apps with AppRegistry by creating their descriptors
|
|
||||||
// Each descriptor will create and register the app instance
|
|
||||||
// DemoAppDescriptor* demo_descriptor = new DemoAppDescriptor();
|
|
||||||
// ShutdownAppDescriptor* shutdown_descriptor = new ShutdownAppDescriptor();
|
|
||||||
// DiscordAppDescriptor::instance(); // Use singleton pattern for Discord app
|
|
||||||
// MtrAppDescriptor* mtr_descriptor = new MtrAppDescriptor();
|
|
||||||
|
|
||||||
// Pass network handler to MtrApp so it can fetch arrival data
|
|
||||||
// MtrApp* mtr_app = dynamic_cast<MtrApp*>(mtr_descriptor->get_app_instance());
|
|
||||||
// if (mtr_app) {
|
|
||||||
// mtr_app->set_network_handler(network_handler);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// ESP_LOGI(TAG, "Apps registered with AppRegistry\n");
|
|
||||||
|
|
||||||
// Initialize UI Handler (will render app icons from registry)
|
|
||||||
// UIHandler ui_handler;
|
|
||||||
// if (ui_handler.init() != ESP_OK) {
|
|
||||||
// ESP_LOGE(TAG, "Failed to initialize UI handler");
|
|
||||||
// vTaskDelay(5000 / portTICK_PERIOD_MS);
|
|
||||||
// return esp_restart();
|
|
||||||
// }
|
|
||||||
// ESP_LOGI(TAG, "UI handler initialized successfully\n");
|
|
||||||
// ESP_LOGI(TAG, "Main screen displayed with app icons. Tap an icon to launch an app.\n");
|
|
||||||
|
|
||||||
// Run checkerboard draw in its own FreeRTOS task to avoid watchdog triggers
|
|
||||||
struct CheckerboardTaskParams {
|
struct CheckerboardTaskParams {
|
||||||
EInkDisplayHandler* display_handler;
|
EInkDisplayHandler* display_handler;
|
||||||
};
|
};
|
||||||
@@ -230,6 +123,181 @@ void app_main(void) {
|
|||||||
ESP_LOGE(TAG, "Failed to create checkerboard task");
|
ESP_LOGE(TAG, "Failed to create checkerboard task");
|
||||||
delete checker_params;
|
delete checker_params;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LVGL_Checkerboard(
|
||||||
|
LVGLHandler* lvgl_handler
|
||||||
|
) {
|
||||||
|
struct CheckerboardTaskParams {
|
||||||
|
LVGLHandler* lvgl_handler;
|
||||||
|
};
|
||||||
|
auto checkerboard_task_fn = [](void* pvParameters) {
|
||||||
|
CheckerboardTaskParams* params = static_cast<CheckerboardTaskParams*>(pvParameters);
|
||||||
|
if (params == nullptr || params->lvgl_handler == nullptr) {
|
||||||
|
ESP_LOGE(TAG, "Invalid parameters for LVGL checkerboard task");
|
||||||
|
delete params;
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto* handler = static_cast<LVGLHandler*>(params->lvgl_handler);
|
||||||
|
|
||||||
|
// Add safety checks
|
||||||
|
if (!handler) {
|
||||||
|
ESP_LOGE("LVGL", "Handler is null!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI("HEAP", "Free: %d", esp_get_free_heap_size());
|
||||||
|
|
||||||
|
// Acquire LVGL lock with proper timeout
|
||||||
|
if (!lvgl_port_lock(pdMS_TO_TICKS(1000))) {
|
||||||
|
ESP_LOGE(TAG, "Failed to acquire LVGL lock for checkerboard");
|
||||||
|
delete params;
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create LVGL objects for checkerboard
|
||||||
|
lv_obj_t* scr = lv_scr_act();
|
||||||
|
if (scr == nullptr) {
|
||||||
|
ESP_LOGE(TAG, "Failed to get active LVGL screen");
|
||||||
|
lvgl_port_unlock();
|
||||||
|
delete params;
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lv_obj_t* checkerboard = lv_obj_create(scr);
|
||||||
|
if (checkerboard == nullptr) {
|
||||||
|
ESP_LOGE(TAG, "Failed to create LVGL checkerboard object");
|
||||||
|
lvgl_port_unlock();
|
||||||
|
delete params;
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lv_obj_set_size(checkerboard, DISPLAY_WIDTH, DISPLAY_HEIGHT);
|
||||||
|
lv_obj_center(checkerboard);
|
||||||
|
const int CELL_SIZE = 40;
|
||||||
|
// Create checkerboard pattern using LVGL
|
||||||
|
for (int y = 0; y < DISPLAY_HEIGHT; y += CELL_SIZE) {
|
||||||
|
for (int x = 0; x < DISPLAY_WIDTH; x += CELL_SIZE) {
|
||||||
|
lv_color_t color = (((x / CELL_SIZE) % 2) == ((y / CELL_SIZE) % 2)) ? lv_color_hex(0xFFFFFF) : lv_color_hex(0x000000);
|
||||||
|
lv_obj_t* cell = lv_obj_create(checkerboard);
|
||||||
|
if (cell == nullptr) {
|
||||||
|
ESP_LOGE(TAG, "Failed to create LVGL checkerboard cell");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
lv_obj_set_size(cell, CELL_SIZE, CELL_SIZE);
|
||||||
|
lv_obj_set_style_bg_color(cell, color, 0);
|
||||||
|
lv_obj_set_pos(cell, x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "LVGL Checkerboard pattern displayed successfully.");
|
||||||
|
lvgl_port_unlock();
|
||||||
|
delete params;
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
};
|
||||||
|
CheckerboardTaskParams* checker_params = new CheckerboardTaskParams();
|
||||||
|
checker_params->lvgl_handler = lvgl_handler;
|
||||||
|
BaseType_t res = xTaskCreate(
|
||||||
|
checkerboard_task_fn,
|
||||||
|
"lvgl_checkerboard_task",
|
||||||
|
8192,
|
||||||
|
static_cast<void*>(checker_params),
|
||||||
|
tskIDLE_PRIORITY + 1,
|
||||||
|
NULL
|
||||||
|
);
|
||||||
|
if (res != pdPASS) {
|
||||||
|
ESP_LOGE(TAG, "Failed to create LVGL checkerboard task");
|
||||||
|
delete checker_params;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void app_main(void) {
|
||||||
|
display_chip_info();
|
||||||
|
|
||||||
|
QueueHandle_t touch_event_queue = NULL;
|
||||||
|
EventGroupHandle_t system_event_group = NULL, system_lifecycle_event_group = NULL;
|
||||||
|
|
||||||
|
init_queues(touch_event_queue, system_event_group, system_lifecycle_event_group);
|
||||||
|
if (touch_event_queue == NULL || system_event_group == NULL || system_lifecycle_event_group == NULL) {
|
||||||
|
ESP_LOGE("Main", "Failed to create one or more queues/event groups");
|
||||||
|
vTaskDelay(5000 / portTICK_PERIOD_MS);
|
||||||
|
return esp_restart();
|
||||||
|
}
|
||||||
|
ESP_LOGI(TAG, "Queues initialized.\n");
|
||||||
|
|
||||||
|
//
|
||||||
|
// KVStorageHandler* kv_storage_handler = new NVSStorageHandler(
|
||||||
|
// DEFAULT_STORAGE_NAMESPACE
|
||||||
|
// );
|
||||||
|
|
||||||
|
// auto wifi_handler = std::make_unique<WifiHandler>(
|
||||||
|
// std::unique_ptr<KVStorageHandler>(new NVSStorageHandler(WIFI_CREDENTIALS_STORAGE_NAMESPACE))
|
||||||
|
// );
|
||||||
|
// NetworkHandler* network_handler = new NetworkHandler(std::move(wifi_handler));
|
||||||
|
EInkDisplayHandler* display_handler = new EInkDisplayHandler();
|
||||||
|
// Initialize display and touch
|
||||||
|
display_handler->init_devices();
|
||||||
|
display_handler->clear_display();
|
||||||
|
ESP_LOGI(TAG, "E-Ink display handler initialized.\n");
|
||||||
|
// LVGL Handler
|
||||||
|
std::unique_ptr<EInkDisplayHandler> display_uptr(display_handler);
|
||||||
|
LVGLHandler lvgl_handler(std::move(display_uptr));
|
||||||
|
esp_err_t err = lvgl_handler.initLVGL(system_event_group);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
ESP_LOGE(TAG, "Failed to initialize LVGL handler: %s", esp_err_to_name(err));
|
||||||
|
vTaskDelay(5000 / portTICK_PERIOD_MS);
|
||||||
|
return esp_restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// kv_storage_handler->init(system_event_group);
|
||||||
|
// network_handler->init(system_event_group);
|
||||||
|
|
||||||
|
//
|
||||||
|
ESP_LOGI(TAG, "Waiting for system to be ready...\n");
|
||||||
|
xEventGroupWaitBits(
|
||||||
|
system_event_group,
|
||||||
|
// DISPLAY_READY_BIT | STORAGE_READY_BIT | NETWORK_READY_BIT,
|
||||||
|
DISPLAY_READY_BIT,
|
||||||
|
// do not clear on exit, require explicit reset
|
||||||
|
pdFALSE,
|
||||||
|
pdTRUE,
|
||||||
|
portMAX_DELAY
|
||||||
|
);
|
||||||
|
ESP_LOGI(TAG, "System is ready. Starting main application...\n");
|
||||||
|
|
||||||
|
// Show checkerboard pattern on display for testing
|
||||||
|
LVGL_Checkerboard(&lvgl_handler);
|
||||||
|
|
||||||
|
// Register apps with AppRegistry by creating their descriptors
|
||||||
|
// Each descriptor will create and register the app instance
|
||||||
|
// DemoAppDescriptor* demo_descriptor = new DemoAppDescriptor();
|
||||||
|
// ShutdownAppDescriptor* shutdown_descriptor = new ShutdownAppDescriptor();
|
||||||
|
// DiscordAppDescriptor::instance(); // Use singleton pattern for Discord app
|
||||||
|
// MtrAppDescriptor* mtr_descriptor = new MtrAppDescriptor();
|
||||||
|
|
||||||
|
// Pass network handler to MtrApp so it can fetch arrival data
|
||||||
|
// MtrApp* mtr_app = dynamic_cast<MtrApp*>(mtr_descriptor->get_app_instance());
|
||||||
|
// if (mtr_app) {
|
||||||
|
// mtr_app->set_network_handler(network_handler);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// ESP_LOGI(TAG, "Apps registered with AppRegistry\n");
|
||||||
|
|
||||||
|
// Initialize UI Handler (will render app icons from registry)
|
||||||
|
// UIHandler ui_handler;
|
||||||
|
// if (ui_handler.init() != ESP_OK) {
|
||||||
|
// ESP_LOGE(TAG, "Failed to initialize UI handler");
|
||||||
|
// vTaskDelay(5000 / portTICK_PERIOD_MS);
|
||||||
|
// return esp_restart();
|
||||||
|
// }
|
||||||
|
// ESP_LOGI(TAG, "UI handler initialized successfully\n");
|
||||||
|
// ESP_LOGI(TAG, "Main screen displayed with app icons. Tap an icon to launch an app.\n");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// wait for shutdown signal
|
// wait for shutdown signal
|
||||||
ESP_LOGI(TAG, "Waiting for shutdown signal...\n");
|
ESP_LOGI(TAG, "Waiting for shutdown signal...\n");
|
||||||
@@ -255,8 +323,7 @@ void app_main(void) {
|
|||||||
// delete demo_descriptor;
|
// delete demo_descriptor;
|
||||||
// delete shutdown_descriptor;
|
// delete shutdown_descriptor;
|
||||||
// delete mtr_descriptor;
|
// delete mtr_descriptor;
|
||||||
delete display_handler;
|
|
||||||
vSemaphoreDelete(lvgl_mutex);
|
|
||||||
vEventGroupDelete(system_event_group);
|
vEventGroupDelete(system_event_group);
|
||||||
vQueueDelete(touch_event_queue);
|
vQueueDelete(touch_event_queue);
|
||||||
ESP_LOGI(TAG, "Cleanup complete.\n");
|
ESP_LOGI(TAG, "Cleanup complete.\n");
|
||||||
|
|||||||
Reference in New Issue
Block a user