Merge branch 'setup' into feature/mtr-app
This commit is contained in:
@@ -22,7 +22,12 @@ EInkDisplayHandler::~EInkDisplayHandler() {
|
|||||||
vTaskDelete(_touch_task_handle);
|
vTaskDelete(_touch_task_handle);
|
||||||
}
|
}
|
||||||
if (_lvgl_display != nullptr) {
|
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) {
|
if (_lvgl_touch_indev != nullptr) {
|
||||||
lvgl_port_remove_touch(_lvgl_touch_indev);
|
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.clock_speed_hz = 10 * 1000 * 1000; // 10 MHz (max for GDEY075T7)
|
||||||
devcfg.mode = 0; // SPI mode 0
|
devcfg.mode = 0; // SPI mode 0
|
||||||
devcfg.spics_io_num = PIN_CS;
|
devcfg.spics_io_num = PIN_CS;
|
||||||
devcfg.queue_size = 1;
|
devcfg.queue_size = 7; // Queue size for non-blocking transactions
|
||||||
devcfg.pre_cb = nullptr;
|
devcfg.pre_cb = nullptr;
|
||||||
|
|
||||||
ret = spi_bus_add_device(SPI2_HOST, &devcfg, &_spi);
|
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
|
memset(_framebuffer, 0xFF, DISPLAY_BUFFER_SIZE); // Initialize to white
|
||||||
|
|
||||||
// Create LVGL display driver
|
// Create LVGL display manually (no esp_lcd panel for e-paper)
|
||||||
lvgl_port_display_cfg_t disp_cfg = {};
|
lv_display_t* disp = lv_display_create(DISPLAY_WIDTH, DISPLAY_HEIGHT);
|
||||||
|
if (disp == nullptr) {
|
||||||
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) {
|
|
||||||
ESP_LOGE(TAG, "Failed to create LVGL display");
|
ESP_LOGE(TAG, "Failed to create LVGL display");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set custom flush callback
|
/* 1-bit e-paper display */
|
||||||
lv_display_set_flush_cb(_lvgl_display, _lvgl_flush_cb);
|
lv_display_set_color_format(disp, LV_COLOR_FORMAT_I1);
|
||||||
lv_display_set_user_data(_lvgl_display, this);
|
|
||||||
|
|
||||||
ESP_LOGI(TAG, "LVGL display registered");
|
/* 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);
|
||||||
// Register GT911 touch input with LVGL
|
if (_lvgl_draw_buf == nullptr) {
|
||||||
const lvgl_port_touch_cfg_t touch_cfg = {
|
ESP_LOGE(TAG, "Failed to create LVGL draw buffer");
|
||||||
.disp = _lvgl_display,
|
lv_display_delete(disp);
|
||||||
.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");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Override touch read callback to check BUSY pin
|
lv_display_set_draw_buffers(disp, _lvgl_draw_buf, NULL);
|
||||||
lv_indev_set_read_cb(_lvgl_touch_indev, _lvgl_touch_read_cb);
|
lv_display_set_render_mode(disp, LV_DISPLAY_RENDER_MODE_DIRECT);
|
||||||
lv_indev_set_user_data(_lvgl_touch_indev, this);
|
|
||||||
|
|
||||||
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
|
// Perform initial full refresh to clear display
|
||||||
ESP_LOGI(TAG, "Performing initial display clear...");
|
ESP_LOGI(TAG, "Performing initial display clear...");
|
||||||
@@ -302,19 +308,40 @@ void EInkDisplayHandler::_perform_full_refresh(const uint8_t* framebuffer) {
|
|||||||
|
|
||||||
_wait_for_busy();
|
_wait_for_busy();
|
||||||
|
|
||||||
|
spi_transaction_t* rtrans; // Declare once for entire function
|
||||||
|
|
||||||
// Step 1: Write old data (0x10) - typically all zeros for full refresh
|
// Step 1: Write old data (0x10) - typically all zeros for full refresh
|
||||||
epd_write_cmd(0x10);
|
epd_write_cmd(0x10);
|
||||||
|
|
||||||
xSemaphoreTake(_spi_mutex, portMAX_DELAY);
|
xSemaphoreTake(_spi_mutex, portMAX_DELAY);
|
||||||
gpio_set_level(PIN_DC, 1); // Data mode
|
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++) {
|
for (size_t i = 0; i < DISPLAY_BUFFER_SIZE; i++) {
|
||||||
spi_transaction_t t = {};
|
spi_transaction_t t = {};
|
||||||
t.length = 8;
|
t.length = 8;
|
||||||
uint8_t byte = 0x00; // Old data (cleared screen)
|
t.tx_buffer = &zero_byte;
|
||||||
t.tx_buffer = &byte;
|
|
||||||
spi_device_polling_transmit(_spi, &t);
|
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);
|
xSemaphoreGive(_spi_mutex);
|
||||||
|
|
||||||
// Step 2: Write new data (0x13) with data inversion
|
// 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);
|
xSemaphoreTake(_spi_mutex, portMAX_DELAY);
|
||||||
gpio_set_level(PIN_DC, 1); // Data mode
|
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++) {
|
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 = {};
|
spi_transaction_t t = {};
|
||||||
t.length = 8;
|
t.length = 8;
|
||||||
uint8_t byte = ~framebuffer[i]; // Invert data per manufacturer spec
|
t.tx_buffer = &tx_buffer[buf_idx];
|
||||||
t.tx_buffer = &byte;
|
|
||||||
spi_device_polling_transmit(_spi, &t);
|
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);
|
xSemaphoreGive(_spi_mutex);
|
||||||
|
|
||||||
// Step 3: Trigger display refresh (DRF)
|
// Step 3: Trigger display refresh (DRF)
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ private:
|
|||||||
// LVGL display and input device handles
|
// LVGL display and input device handles
|
||||||
lv_display_t* _lvgl_display = nullptr;
|
lv_display_t* _lvgl_display = nullptr;
|
||||||
lv_indev_t* _lvgl_touch_indev = nullptr;
|
lv_indev_t* _lvgl_touch_indev = nullptr;
|
||||||
|
lv_draw_buf_t* _lvgl_draw_buf = nullptr;
|
||||||
|
|
||||||
// Framebuffer
|
// Framebuffer
|
||||||
uint8_t* _framebuffer = nullptr;
|
uint8_t* _framebuffer = nullptr;
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ void NVSStorageHandler::put(const std::string& key, const std::string& value) {
|
|||||||
ESP_LOGE(TAG, "Error (%s) setting key-value pair in NVS!", esp_err_to_name(err));
|
ESP_LOGE(TAG, "Error (%s) setting key-value pair in NVS!", esp_err_to_name(err));
|
||||||
} else {
|
} else {
|
||||||
nvs_commit(this->nvsHandle);
|
nvs_commit(this->nvsHandle);
|
||||||
ESP_LOGI(TAG, "Key-value pair (%s, %s) stored in NVS.", key.c_str(), value.c_str());
|
// ESP_LOGI(TAG, "Key-value pair (%s, %s) stored in NVS.", key.c_str(), value.c_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,8 +67,9 @@ std::string NVSStorageHandler::get(const std::string& key) const {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string value;
|
// Allocate string buffer with correct size (includes null terminator)
|
||||||
err = nvs_get_str(this->nvsHandle, key.c_str(), value.data(), &required_size);
|
std::string value(required_size - 1, '\0');
|
||||||
|
err = nvs_get_str(this->nvsHandle, key.c_str(), &value[0], &required_size);
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
ESP_LOGE(TAG, "Error (%s) getting value for key %s from NVS!", esp_err_to_name(err), key.c_str());
|
ESP_LOGE(TAG, "Error (%s) getting value for key %s from NVS!", esp_err_to_name(err), key.c_str());
|
||||||
return "";
|
return "";
|
||||||
|
|||||||
Reference in New Issue
Block a user