feat: enhance EInk display handler with improved resource management and non-blocking SPI transactions
This commit is contained in:
@@ -22,7 +22,12 @@ EInkDisplayHandler::~EInkDisplayHandler() {
|
||||
vTaskDelete(_touch_task_handle);
|
||||
}
|
||||
if (_lvgl_display != nullptr) {
|
||||
lvgl_port_remove_disp(_lvgl_display);
|
||||
lv_display_delete(_lvgl_display);
|
||||
_lvgl_display = nullptr;
|
||||
if (_lvgl_draw_buf != nullptr) {
|
||||
lv_draw_buf_destroy(_lvgl_draw_buf);
|
||||
_lvgl_draw_buf = nullptr;
|
||||
}
|
||||
}
|
||||
if (_lvgl_touch_indev != nullptr) {
|
||||
lvgl_port_remove_touch(_lvgl_touch_indev);
|
||||
@@ -73,7 +78,7 @@ void EInkDisplayHandler::init() {
|
||||
devcfg.clock_speed_hz = 10 * 1000 * 1000; // 10 MHz (max for GDEY075T7)
|
||||
devcfg.mode = 0; // SPI mode 0
|
||||
devcfg.spics_io_num = PIN_CS;
|
||||
devcfg.queue_size = 1;
|
||||
devcfg.queue_size = 7; // Queue size for non-blocking transactions
|
||||
devcfg.pre_cb = nullptr;
|
||||
|
||||
ret = spi_bus_add_device(SPI2_HOST, &devcfg, &_spi);
|
||||
@@ -102,57 +107,58 @@ void EInkDisplayHandler::init() {
|
||||
}
|
||||
memset(_framebuffer, 0xFF, DISPLAY_BUFFER_SIZE); // Initialize to white
|
||||
|
||||
// Create LVGL display driver
|
||||
lvgl_port_display_cfg_t disp_cfg = {};
|
||||
|
||||
disp_cfg.io_handle = nullptr;
|
||||
disp_cfg.panel_handle = nullptr;
|
||||
disp_cfg.buffer_size = DISPLAY_WIDTH * 40; // 40 lines buffer
|
||||
disp_cfg.double_buffer = false;
|
||||
disp_cfg.hres = DISPLAY_WIDTH;
|
||||
disp_cfg.vres = DISPLAY_HEIGHT;
|
||||
disp_cfg.monochrome = true;
|
||||
|
||||
disp_cfg.rotation.swap_xy = false;
|
||||
disp_cfg.rotation.mirror_x = false;
|
||||
disp_cfg.rotation.mirror_y = false;
|
||||
|
||||
disp_cfg.flags.buff_dma = _framebuffer_in_psram ? false : true;
|
||||
disp_cfg.flags.buff_spiram = _framebuffer_in_psram;
|
||||
disp_cfg.flags.swap_bytes = false;
|
||||
disp_cfg.flags.full_refresh = false;
|
||||
disp_cfg.flags.direct_mode = false;
|
||||
|
||||
_lvgl_display = lvgl_port_add_disp(&disp_cfg);
|
||||
if (_lvgl_display == nullptr) {
|
||||
// Create LVGL display manually (no esp_lcd panel for e-paper)
|
||||
lv_display_t* disp = lv_display_create(DISPLAY_WIDTH, DISPLAY_HEIGHT);
|
||||
if (disp == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to create LVGL display");
|
||||
return;
|
||||
}
|
||||
|
||||
// Set custom flush callback
|
||||
lv_display_set_flush_cb(_lvgl_display, _lvgl_flush_cb);
|
||||
lv_display_set_user_data(_lvgl_display, this);
|
||||
/* 1-bit e-paper display */
|
||||
lv_display_set_color_format(disp, LV_COLOR_FORMAT_I1);
|
||||
|
||||
ESP_LOGI(TAG, "LVGL display registered");
|
||||
|
||||
// Register GT911 touch input with LVGL
|
||||
const lvgl_port_touch_cfg_t touch_cfg = {
|
||||
.disp = _lvgl_display,
|
||||
.handle = get_touch_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");
|
||||
/* Create a draw buffer covering ~40 lines */
|
||||
_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(disp);
|
||||
return;
|
||||
}
|
||||
|
||||
// Override touch read callback to check BUSY pin
|
||||
lv_indev_set_read_cb(_lvgl_touch_indev, _lvgl_touch_read_cb);
|
||||
lv_indev_set_user_data(_lvgl_touch_indev, this);
|
||||
lv_display_set_draw_buffers(disp, _lvgl_draw_buf, NULL);
|
||||
lv_display_set_render_mode(disp, LV_DISPLAY_RENDER_MODE_DIRECT);
|
||||
|
||||
ESP_LOGI(TAG, "LVGL touch input registered");
|
||||
// Set custom flush callback and user data
|
||||
lv_display_set_flush_cb(disp, _lvgl_flush_cb);
|
||||
lv_display_set_user_data(disp, this);
|
||||
|
||||
_lvgl_display = disp;
|
||||
|
||||
ESP_LOGI(TAG, "LVGL display registered");
|
||||
|
||||
// Register GT911 touch input with LVGL, only if touch handle is valid
|
||||
esp_lcd_touch_handle_t tp_handle = get_touch_handle();
|
||||
if (tp_handle == nullptr) {
|
||||
ESP_LOGE(TAG, "Touch handle is NULL — touch initialization failed; skipping LVGL touch registration");
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
|
||||
// Override touch read callback to check BUSY pin
|
||||
lv_indev_set_read_cb(_lvgl_touch_indev, _lvgl_touch_read_cb);
|
||||
lv_indev_set_user_data(_lvgl_touch_indev, this);
|
||||
|
||||
ESP_LOGI(TAG, "LVGL touch input registered");
|
||||
}
|
||||
|
||||
// Perform initial full refresh to clear display
|
||||
ESP_LOGI(TAG, "Performing initial display clear...");
|
||||
@@ -302,19 +308,40 @@ void EInkDisplayHandler::_perform_full_refresh(const uint8_t* framebuffer) {
|
||||
|
||||
_wait_for_busy();
|
||||
|
||||
spi_transaction_t* rtrans; // Declare once for entire function
|
||||
|
||||
// Step 1: Write old data (0x10) - typically all zeros for full refresh
|
||||
epd_write_cmd(0x10);
|
||||
|
||||
xSemaphoreTake(_spi_mutex, portMAX_DELAY);
|
||||
gpio_set_level(PIN_DC, 1); // Data mode
|
||||
|
||||
// Use queued transactions to allow task switching
|
||||
static uint8_t zero_byte = 0x00; // Static to persist across queue operations
|
||||
for (size_t i = 0; i < DISPLAY_BUFFER_SIZE; i++) {
|
||||
spi_transaction_t t = {};
|
||||
t.length = 8;
|
||||
uint8_t byte = 0x00; // Old data (cleared screen)
|
||||
t.tx_buffer = &byte;
|
||||
spi_device_polling_transmit(_spi, &t);
|
||||
t.tx_buffer = &zero_byte;
|
||||
|
||||
esp_err_t ret = spi_device_queue_trans(_spi, &t, portMAX_DELAY);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to queue SPI transaction: %s", esp_err_to_name(ret));
|
||||
break;
|
||||
}
|
||||
|
||||
// Retrieve result every 100 bytes to prevent queue overflow and allow yielding
|
||||
if (i % 100 == 99) {
|
||||
for (int j = 0; j < 100; j++) {
|
||||
spi_device_get_trans_result(_spi, &rtrans, portMAX_DELAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get remaining results
|
||||
for (size_t i = 0; i < (DISPLAY_BUFFER_SIZE % 100); i++) {
|
||||
spi_device_get_trans_result(_spi, &rtrans, portMAX_DELAY);
|
||||
}
|
||||
|
||||
xSemaphoreGive(_spi_mutex);
|
||||
|
||||
// Step 2: Write new data (0x13) with data inversion
|
||||
@@ -323,13 +350,35 @@ void EInkDisplayHandler::_perform_full_refresh(const uint8_t* framebuffer) {
|
||||
xSemaphoreTake(_spi_mutex, portMAX_DELAY);
|
||||
gpio_set_level(PIN_DC, 1); // Data mode
|
||||
|
||||
// Use queued transactions with inverted framebuffer data
|
||||
static uint8_t tx_buffer[100]; // Buffer for batch of inverted bytes
|
||||
for (size_t i = 0; i < DISPLAY_BUFFER_SIZE; i++) {
|
||||
size_t buf_idx = i % 100;
|
||||
tx_buffer[buf_idx] = ~framebuffer[i]; // Invert data per manufacturer spec
|
||||
|
||||
spi_transaction_t t = {};
|
||||
t.length = 8;
|
||||
uint8_t byte = ~framebuffer[i]; // Invert data per manufacturer spec
|
||||
t.tx_buffer = &byte;
|
||||
spi_device_polling_transmit(_spi, &t);
|
||||
t.tx_buffer = &tx_buffer[buf_idx];
|
||||
|
||||
esp_err_t ret = spi_device_queue_trans(_spi, &t, portMAX_DELAY);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to queue SPI transaction: %s", esp_err_to_name(ret));
|
||||
break;
|
||||
}
|
||||
|
||||
// Retrieve result every 100 bytes to prevent queue overflow and allow yielding
|
||||
if (buf_idx == 99) {
|
||||
for (int j = 0; j < 100; j++) {
|
||||
spi_device_get_trans_result(_spi, &rtrans, portMAX_DELAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get remaining results
|
||||
for (size_t i = 0; i < (DISPLAY_BUFFER_SIZE % 100); i++) {
|
||||
spi_device_get_trans_result(_spi, &rtrans, portMAX_DELAY);
|
||||
}
|
||||
|
||||
xSemaphoreGive(_spi_mutex);
|
||||
|
||||
// Step 3: Trigger display refresh (DRF)
|
||||
|
||||
Reference in New Issue
Block a user