Refractored epd handler
This commit is contained in:
@@ -11,45 +11,30 @@
|
||||
#define DISPLAY_BUFFER_SIZE (EINK_HEIGHT* EINK_WIDTH) / 8 // 1 bit per pixels
|
||||
#define MINIMUM_PIN_SETUP_DELAY_MS 10
|
||||
#define MINIMUM_POWER_ON_DELAY_MS 100
|
||||
#define BUSY_ACTIVE_LEVEL 0 // BUSY pin is active low
|
||||
#define BUSY_INACTIVE_LEVEL 1
|
||||
#define DMA_TRANSFER_CHUNK_SIZE 4096 // 4KB chunk size for DMA transfers
|
||||
|
||||
static uint8_t black_data[DISPLAY_BUFFER_SIZE]; // all black data
|
||||
static uint8_t white_data[DISPLAY_BUFFER_SIZE]; // all white data
|
||||
static uint8_t DRAW_BUFFER[DISPLAY_WIDTH * DISPLAY_HEIGHT / 8]; // 1 bit per pixel
|
||||
static uint8_t OLD_DRAW_BUFFER[DISPLAY_WIDTH * DISPLAY_HEIGHT / 8]; // 1 bit per pixel
|
||||
|
||||
EInkDisplayHandler::EInkDisplayHandler() {
|
||||
memset(black_data, 0xFF, sizeof(black_data)); // eink uses 1 for black
|
||||
memset(white_data, 0x00, sizeof(white_data));
|
||||
memset(DRAW_BUFFER, 0x00, sizeof(DRAW_BUFFER)); // start with all white
|
||||
memset(DRAW_BUFFER, 0x00, sizeof(DRAW_BUFFER)); // start with all white (0 = white in e-ink)
|
||||
memset(OLD_DRAW_BUFFER, 0x00, sizeof(OLD_DRAW_BUFFER)); // start with all white (0 = white in e-ink)
|
||||
draw_buffer_ = DRAW_BUFFER;
|
||||
spi_mutex_ = xSemaphoreCreateMutex();
|
||||
if (spi_mutex_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to create SPI mutex");
|
||||
}
|
||||
spi_transaction_mutex_ = xSemaphoreCreateMutex();
|
||||
if (spi_transaction_mutex_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to create SPI transaction mutex");
|
||||
}
|
||||
old_buffer_ = OLD_DRAW_BUFFER;
|
||||
|
||||
refresh_mutex_ = xSemaphoreCreateMutex();
|
||||
if (refresh_mutex_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to create refresh mutex");
|
||||
}
|
||||
}
|
||||
|
||||
EInkDisplayHandler::~EInkDisplayHandler() {
|
||||
if (spi_mutex_ != nullptr) {
|
||||
vSemaphoreDelete(spi_mutex_);
|
||||
}
|
||||
if (spi_transaction_mutex_ != nullptr) {
|
||||
vSemaphoreDelete(spi_transaction_mutex_);
|
||||
}
|
||||
if (refresh_mutex_ != nullptr) {
|
||||
vSemaphoreDelete(refresh_mutex_);
|
||||
}
|
||||
if (spi_ != nullptr) {
|
||||
spi_bus_remove_device(spi_);
|
||||
}
|
||||
if (tp_handle_ != nullptr) {
|
||||
esp_lcd_touch_del(tp_handle_);
|
||||
}
|
||||
@@ -66,26 +51,26 @@ esp_err_t EInkDisplayHandler::deep_sleep_display(void) {
|
||||
}
|
||||
{
|
||||
esp_err_t err = ESP_OK;
|
||||
TransactionGuard transaction_guard(*this);
|
||||
TransactionGuard transaction_guard(this->epd_handler_);
|
||||
err = transaction_guard.begin(pdMS_TO_TICKS(5000));
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to begin transaction for deep sleep: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
wait_for_idle();
|
||||
epd_handler_.wait_for_idle();
|
||||
|
||||
err = epd_write_cmd(0x02, transaction_guard.transaction_id()); // power off
|
||||
err = epd_handler_.epd_write_cmd(0x02, transaction_guard.transaction_id()); // power off
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to send power off command: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
wait_for_idle();
|
||||
err = epd_write_cmd(0x07, transaction_guard.transaction_id()); //deep sleep
|
||||
epd_handler_.wait_for_idle();
|
||||
err = epd_handler_.epd_write_cmd(0x07, transaction_guard.transaction_id()); //deep sleep
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to send deep sleep command: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
err = epd_write_data(0xA5, transaction_guard.transaction_id());
|
||||
err = epd_handler_.epd_write_data(0xA5, transaction_guard.transaction_id());
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to send deep sleep data: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
@@ -98,32 +83,32 @@ esp_err_t EInkDisplayHandler::deep_sleep_display(void) {
|
||||
esp_err_t EInkDisplayHandler::refresh_display() {
|
||||
esp_err_t err = ESP_OK;
|
||||
|
||||
if (is_deep_sleep_) {
|
||||
epd_init_();
|
||||
}
|
||||
|
||||
{
|
||||
ESP_LOGI(TAG, "Waiting for display to be idle...");
|
||||
TransactionGuard transaction_guard(*this);
|
||||
TransactionGuard transaction_guard(this->epd_handler_);
|
||||
err = transaction_guard.begin(pdMS_TO_TICKS(10000));
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to begin transaction for display refresh: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
wait_for_idle();
|
||||
if (is_deep_sleep_) {
|
||||
epd_init_internal_(transaction_guard.transaction_id());
|
||||
}
|
||||
|
||||
epd_handler_.wait_for_idle();
|
||||
ESP_LOGI(TAG, "Starting display refresh...");
|
||||
err = epd_write_cmd(0x92, transaction_guard.transaction_id()); // enter normal mode
|
||||
err = epd_handler_.epd_write_cmd(0x92, transaction_guard.transaction_id()); // enter normal mode
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to enter normal mode: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
err = epd_write_cmd(0x12, transaction_guard.transaction_id()); // display refresh
|
||||
err = epd_handler_.epd_write_cmd(0x12, transaction_guard.transaction_id()); // display refresh
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to send display refresh command: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(MINIMUM_PIN_SETUP_DELAY_MS)); // at least 200us delay
|
||||
wait_for_idle();
|
||||
epd_handler_.wait_for_idle();
|
||||
}
|
||||
|
||||
{
|
||||
@@ -136,12 +121,6 @@ esp_err_t EInkDisplayHandler::refresh_display() {
|
||||
force_full_refresh_ = false;
|
||||
}
|
||||
|
||||
err = deep_sleep_display();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to enter deep sleep after refresh: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Refresh complete");
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -150,35 +129,35 @@ esp_err_t EInkDisplayHandler::full_write(const uint8_t* framebuffer, const bool
|
||||
ESP_LOGI(TAG, "Starting full refresh (3 seconds)...");
|
||||
esp_err_t err = ESP_OK;
|
||||
|
||||
write_to_buffer_(framebuffer, RefreshArea { 0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1 });
|
||||
|
||||
if (is_deep_sleep_) {
|
||||
epd_init_();
|
||||
}
|
||||
|
||||
{
|
||||
TransactionGuard transaction_guard(*this);
|
||||
TransactionGuard transaction_guard(this->epd_handler_);
|
||||
err = transaction_guard.begin(pdMS_TO_TICKS(10000));
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to begin transaction for full refresh: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
if (is_deep_sleep_) {
|
||||
epd_init_internal_(transaction_guard.transaction_id());
|
||||
}
|
||||
|
||||
wait_for_idle();
|
||||
write_to_buffer_(framebuffer, RefreshArea { 0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1 });
|
||||
|
||||
epd_handler_.wait_for_idle();
|
||||
// Step 0: Enter normal mode
|
||||
err = epd_write_cmd(0x92, transaction_guard.transaction_id()); // enter normal mode
|
||||
err = epd_handler_.epd_write_cmd(0x92, transaction_guard.transaction_id()); // enter normal mode
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to enter normal mode: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
// Step 1: Write old data (0x10) - Arduino uses 0xFF (all white) for base map
|
||||
{
|
||||
err = epd_write_cmd(0x10, transaction_guard.transaction_id());
|
||||
err = epd_handler_.epd_write_cmd(0x10, transaction_guard.transaction_id());
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to send old data command: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
err = transfer_spi_data(white_basemap ? black_data : white_data, DISPLAY_BUFFER_SIZE, transaction_guard.transaction_id()); // Send all white data (0xFF)
|
||||
err = epd_handler_.transfer_spi_data(white_basemap ? black_data : white_data, DISPLAY_BUFFER_SIZE, transaction_guard.transaction_id()); // Send all white data (0xFF)
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to send all white data for old data: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
@@ -187,20 +166,20 @@ esp_err_t EInkDisplayHandler::full_write(const uint8_t* framebuffer, const bool
|
||||
|
||||
// Step 2: Write new data (0x13)
|
||||
{
|
||||
err = epd_write_cmd(0x13, transaction_guard.transaction_id());
|
||||
err = epd_handler_.epd_write_cmd(0x13, transaction_guard.transaction_id());
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to send new data command: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
err = transfer_spi_data(draw_buffer_, DISPLAY_BUFFER_SIZE, transaction_guard.transaction_id()); // Send new framebuffer data
|
||||
err = epd_handler_.transfer_spi_data(draw_buffer_, DISPLAY_BUFFER_SIZE, transaction_guard.transaction_id()); // Send new framebuffer data
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to send framebuffer data for new data: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
}
|
||||
// Step 3: Trigger display refresh (DRF)
|
||||
err = epd_write_cmd(0x12, transaction_guard.transaction_id());
|
||||
err = epd_handler_.epd_write_cmd(0x12, transaction_guard.transaction_id());
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to send display refresh command: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
@@ -210,7 +189,7 @@ esp_err_t EInkDisplayHandler::full_write(const uint8_t* framebuffer, const bool
|
||||
ESP_LOGI(TAG, "Display refresh triggered, BUSY pin: %d", gpio_get_level(PIN_BUSY));
|
||||
|
||||
// Wait for refresh to complete
|
||||
wait_for_idle();
|
||||
epd_handler_.wait_for_idle();
|
||||
}
|
||||
|
||||
err = deep_sleep_display();
|
||||
@@ -220,6 +199,7 @@ esp_err_t EInkDisplayHandler::full_write(const uint8_t* framebuffer, const bool
|
||||
}
|
||||
|
||||
refresh_area_.reset();
|
||||
memcpy(old_buffer_, draw_buffer_, DISPLAY_BUFFER_SIZE);
|
||||
|
||||
ESP_LOGI(TAG, "Full refresh complete");
|
||||
return ESP_OK;
|
||||
@@ -229,49 +209,14 @@ esp_err_t EInkDisplayHandler::partial_refresh(const uint8_t* incoming_partial_fr
|
||||
ESP_LOGI(TAG, "Starting partial refresh (0.3 seconds)...");
|
||||
esp_err_t err = ESP_OK;
|
||||
|
||||
write_to_buffer_(incoming_partial_framebuffer, incoming_area);
|
||||
|
||||
if (!is_last_partial_update) {
|
||||
ESP_LOGI(TAG, "Partial refresh skipped (not last partial update)");
|
||||
refresh_area_.expand_to_include(incoming_area);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
RefreshArea area = refresh_area_;
|
||||
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)");
|
||||
ESP_LOGI(TAG, "Given area: x1=%d, x2=%d", area.x1, area.x2);
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// Calculate partial buffer size based on the refresh area
|
||||
const uint32_t area_width_bytes = (area.x2 - area.x1 + 1) / 8;
|
||||
const uint32_t area_height = area.y2 - area.y1 + 1;
|
||||
const size_t partial_buffer_size = area_width_bytes * area_height;
|
||||
|
||||
uint8_t* partial_buffer = new uint8_t[partial_buffer_size];
|
||||
if (partial_buffer == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to allocate partial buffer for partial refresh");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
// Copy the relevant area from draw_buffer_ to partial_buffer
|
||||
for (int32_t row = 0; row < area_height; ++row) {
|
||||
uint32_t fb_y = area.y1 + row;
|
||||
uint32_t fb_x_byte_start = area.x1 / 8;
|
||||
uint8_t* fb_ptr = &draw_buffer_[fb_y * (DISPLAY_WIDTH / 8) + fb_x_byte_start];
|
||||
uint8_t* dest_ptr = &partial_buffer[row * area_width_bytes];
|
||||
memcpy(dest_ptr, fb_ptr, area_width_bytes);
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
TransactionGuard transaction_guard(*this);
|
||||
TransactionGuard transaction_guard(this->epd_handler_);
|
||||
err = transaction_guard.begin(pdMS_TO_TICKS(5000));
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to begin transaction for partial refresh: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
// Wake display from deep sleep INSIDE the transaction to prevent race conditions
|
||||
if (is_deep_sleep_) {
|
||||
err = epd_init_partial_internal_(transaction_guard.transaction_id());
|
||||
@@ -281,17 +226,54 @@ esp_err_t EInkDisplayHandler::partial_refresh(const uint8_t* incoming_partial_fr
|
||||
}
|
||||
}
|
||||
|
||||
wait_for_idle();
|
||||
write_to_buffer_(incoming_partial_framebuffer, incoming_area);
|
||||
|
||||
// Always expand refresh_area_ to include incoming_area
|
||||
refresh_area_.expand_to_include(incoming_area);
|
||||
|
||||
if (!is_last_partial_update) {
|
||||
ESP_LOGI(TAG, "Partial refresh skipped (not last partial update)");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
RefreshArea area = refresh_area_;
|
||||
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)");
|
||||
ESP_LOGI(TAG, "Given area: x1=%d, x2=%d", area.x1, area.x2);
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// Calculate partial buffer size based on the refresh area
|
||||
const uint32_t area_width_bytes = (area.x2 - area.x1 + 1) / 8;
|
||||
const uint32_t area_height = area.y2 - area.y1 + 1;
|
||||
const size_t partial_buffer_size = area_width_bytes * area_height;
|
||||
|
||||
uint8_t* partial_buffer = new uint8_t[partial_buffer_size];
|
||||
if (partial_buffer == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to allocate partial buffer for partial refresh");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
// Copy the relevant area from draw_buffer_ to partial_buffer
|
||||
for (int32_t row = 0; row < area_height; ++row) {
|
||||
uint32_t fb_y = area.y1 + row;
|
||||
uint32_t fb_x_byte_start = area.x1 / 8;
|
||||
uint8_t* fb_ptr = &draw_buffer_[fb_y * (DISPLAY_WIDTH / 8) + fb_x_byte_start];
|
||||
uint8_t* dest_ptr = &partial_buffer[row * area_width_bytes];
|
||||
memcpy(dest_ptr, fb_ptr, area_width_bytes);
|
||||
}
|
||||
|
||||
|
||||
epd_handler_.wait_for_idle();
|
||||
|
||||
// Step 1 VCOM setting
|
||||
std::vector<uint8_t> vcom_data = { 0xA9, 0x07 };
|
||||
err = epd_write_cmd_with_data(0x50, vcom_data, transaction_guard.transaction_id()); // VCOM for partial refresh
|
||||
err = epd_handler_.epd_write_cmd_with_data(0x50, vcom_data, transaction_guard.transaction_id()); // VCOM for partial refresh
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to set VCOM for partial refresh: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
// Step 2: Enter partial refresh mode
|
||||
err = epd_write_cmd(0x91, transaction_guard.transaction_id()); // Enter partial mode
|
||||
err = epd_handler_.epd_write_cmd(0x91, transaction_guard.transaction_id()); // Enter partial mode
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to enter partial refresh mode: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
@@ -335,33 +317,38 @@ esp_err_t EInkDisplayHandler::partial_refresh(const uint8_t* incoming_partial_fr
|
||||
ESP_LOGI(TAG, "Partial window data: %02X %02X %02X %02X %02X %02X %02X %02X",
|
||||
window_data[0], window_data[1], window_data[2], window_data[3], window_data[4],
|
||||
window_data[5], window_data[6], window_data[7]);
|
||||
err = epd_write_cmd_with_data(0x90, window_data, transaction_guard.transaction_id()); // Set partial window
|
||||
err = epd_handler_.epd_write_cmd_with_data(0x90, window_data, transaction_guard.transaction_id()); // Set partial window
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to send set partial window command: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 4: Write new data (0x13)
|
||||
// Step 5: Write new data (0x13)
|
||||
{
|
||||
err = epd_write_cmd(0x13, transaction_guard.transaction_id());
|
||||
err = epd_handler_.epd_write_cmd(0x13, transaction_guard.transaction_id());
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to send new data command for partial refresh: %s", esp_err_to_name(err));
|
||||
delete[] partial_buffer;
|
||||
return err;
|
||||
}
|
||||
|
||||
// Send only the partial area data, not the full display buffer
|
||||
ESP_LOGI(TAG, "Sending partial buffer: %zu bytes (area: %dx%d)",
|
||||
ESP_LOGI(TAG, "Sending new partial buffer: %zu bytes (area: %dx%d)",
|
||||
partial_buffer_size, area_width_bytes * 8, area_height);
|
||||
err = transfer_spi_data(partial_buffer, partial_buffer_size, transaction_guard.transaction_id());
|
||||
err = epd_handler_.transfer_spi_data(partial_buffer, partial_buffer_size, transaction_guard.transaction_id());
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to send partial_buffer data for partial refresh: %s", esp_err_to_name(err));
|
||||
delete[] partial_buffer;
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 5: Trigger partial display refresh (DRF) by ending the data write
|
||||
err = epd_write_cmd(0x11, transaction_guard.transaction_id());
|
||||
// Clean up partial buffer
|
||||
delete[] partial_buffer;
|
||||
|
||||
// Step 6: Trigger partial display refresh (DRF)
|
||||
err = epd_handler_.epd_write_cmd(0x11, transaction_guard.transaction_id());
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to send display refresh command for partial refresh: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
@@ -369,9 +356,9 @@ esp_err_t EInkDisplayHandler::partial_refresh(const uint8_t* incoming_partial_fr
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(MINIMUM_PIN_SETUP_DELAY_MS)); // at least 200us delay
|
||||
|
||||
wait_for_idle();
|
||||
// Step 6: Exit partial mode
|
||||
err = epd_write_cmd(0x92, transaction_guard.transaction_id());
|
||||
epd_handler_.wait_for_idle();
|
||||
// Step 7: Exit partial mode
|
||||
err = epd_handler_.epd_write_cmd(0x92, transaction_guard.transaction_id());
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to exit partial refresh mode: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
@@ -411,6 +398,8 @@ esp_err_t EInkDisplayHandler::partial_refresh(const uint8_t* incoming_partial_fr
|
||||
return err;
|
||||
}
|
||||
|
||||
memcpy(old_buffer_, draw_buffer_, DISPLAY_BUFFER_SIZE);
|
||||
|
||||
refresh_area_.reset();
|
||||
|
||||
return ESP_OK;
|
||||
@@ -454,26 +443,6 @@ void EInkDisplayHandler::request_full_refresh(void) {
|
||||
}
|
||||
}
|
||||
|
||||
// Check if display is busy (refreshing)
|
||||
bool EInkDisplayHandler::is_busy(void) const {
|
||||
return gpio_get_level(PIN_BUSY) != BUSY_ACTIVE_LEVEL; // BUSY is active LOW
|
||||
}
|
||||
void EInkDisplayHandler::wait_for_idle(void) const {
|
||||
ESP_LOGI(TAG, "Waiting for display ready (BUSY pin)...");
|
||||
int initial_level = gpio_get_level(PIN_BUSY);
|
||||
ESP_LOGI(TAG, "Initial BUSY pin level: %d (0=BUSY, 1=FREE)", initial_level);
|
||||
|
||||
// If already free, no need to wait
|
||||
if (initial_level == BUSY_INACTIVE_LEVEL) {
|
||||
ESP_LOGI(TAG, "Display already ready (BUSY pin = 1)");
|
||||
return;
|
||||
}
|
||||
while (gpio_get_level(PIN_BUSY) != BUSY_INACTIVE_LEVEL) {
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
ESP_LOGI(TAG, "Display is now ready (BUSY pin = 1)");
|
||||
}
|
||||
|
||||
|
||||
esp_err_t EInkDisplayHandler::init_devices(EventGroupHandle_t system_event_group) {
|
||||
esp_err_t err;
|
||||
@@ -482,9 +451,9 @@ esp_err_t EInkDisplayHandler::init_devices(EventGroupHandle_t system_event_group
|
||||
ESP_LOGE(TAG, "Failed to initialize display pins: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
err = epd_init_();
|
||||
err = this->epd_handler_.init();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to initialize EPD: %s", esp_err_to_name(err));
|
||||
ESP_LOGE(TAG, "Failed to initialize EPD handler: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
err = init_touch_();
|
||||
@@ -492,12 +461,6 @@ esp_err_t EInkDisplayHandler::init_devices(EventGroupHandle_t system_event_group
|
||||
ESP_LOGE(TAG, "Failed to initialize touch: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
err = deep_sleep_display();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to put display into deep sleep: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
// if system_event_group is provided, set display ready bits
|
||||
if (system_event_group != nullptr) {
|
||||
// Indicate that display is ready
|
||||
@@ -536,147 +499,88 @@ esp_err_t EInkDisplayHandler::init_display_pins_(void) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Initialize SPI bus
|
||||
spi_bus_config_t buscfg = {};
|
||||
buscfg.mosi_io_num = 11; // MOSI pin
|
||||
buscfg.miso_io_num = -1; // No MISO for e-paper
|
||||
buscfg.sclk_io_num = 12; // SCK pin
|
||||
buscfg.quadwp_io_num = -1;
|
||||
buscfg.quadhd_io_num = -1;
|
||||
buscfg.max_transfer_sz = DISPLAY_BUFFER_SIZE;
|
||||
|
||||
ret = spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to initialize SPI bus: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Add SPI device
|
||||
spi_device_interface_config_t devcfg = {};
|
||||
devcfg.clock_speed_hz = 10 * 1000 * 1000; // 10 MHz
|
||||
devcfg.mode = 0; // SPI mode 0
|
||||
devcfg.spics_io_num = PIN_CS;
|
||||
devcfg.queue_size = 7; // Queue size for non-blocking transactions
|
||||
devcfg.pre_cb = nullptr;
|
||||
|
||||
ret = spi_bus_add_device(SPI2_HOST, &devcfg, &spi_);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to add SPI device: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
||||
// required to be called by inheriting class after SPI device is created
|
||||
esp_err_t EInkDisplayHandler::epd_init_(void) {
|
||||
esp_err_t EInkDisplayHandler::epd_init_internal_(uint32_t transaction_id) {
|
||||
ESP_LOGI(TAG, "Initializing EPD...");
|
||||
esp_err_t err;
|
||||
|
||||
{
|
||||
TransactionGuard transaction_guard(*this);
|
||||
esp_err_t begin_err = transaction_guard.begin();
|
||||
if (begin_err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to begin transaction: %s", esp_err_to_name(begin_err));
|
||||
return begin_err;
|
||||
}
|
||||
|
||||
// 1. Hardware Reset
|
||||
err = gpio_set_level(PIN_RST, 0);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to set PIN_RST low: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(MINIMUM_PIN_SETUP_DELAY_MS));
|
||||
err = gpio_set_level(PIN_RST, 1);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to set PIN_RST high: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(MINIMUM_PIN_SETUP_DELAY_MS));
|
||||
|
||||
// 2. Initialization Sequence
|
||||
std::vector<uint8_t> panel_setting_data = { 0x1F };
|
||||
err = epd_write_cmd_with_data(0x00, panel_setting_data, transaction_guard.transaction_id()); // Panel Setting
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to send Panel Setting command: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(MINIMUM_PIN_SETUP_DELAY_MS));
|
||||
std::vector<uint8_t> vcom_data = { 0x10, 0x07 };
|
||||
err = epd_write_cmd_with_data(0x50, vcom_data, transaction_guard.transaction_id()); // VCOM
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to send VCOM command: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(MINIMUM_PIN_SETUP_DELAY_MS));
|
||||
err = epd_write_cmd(0x04, transaction_guard.transaction_id()); // Power ON
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to send Power ON command: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(MINIMUM_POWER_ON_DELAY_MS)); // Wait for power on
|
||||
|
||||
// Check BUSY pin with detailed logging
|
||||
ESP_LOGI(TAG, "Waiting for EPD to be ready after power on...");
|
||||
ESP_LOGI(TAG, "BUSY pin level after power on: %d (0=BUSY, 1=FREE)", gpio_get_level(PIN_BUSY));
|
||||
|
||||
int busy_timeout = 0;
|
||||
while (gpio_get_level(PIN_BUSY) == BUSY_ACTIVE_LEVEL) { // BUSY is active LOW
|
||||
vTaskDelay(pdMS_TO_TICKS(MINIMUM_PIN_SETUP_DELAY_MS));
|
||||
busy_timeout++;
|
||||
if (busy_timeout > 500) { // 5 second timeout
|
||||
ESP_LOGE(TAG, "EPD power on timeout! BUSY pin stuck at 0");
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
if (busy_timeout % 50 == 0) { // Log every 500ms
|
||||
ESP_LOGW(TAG, "Still waiting for EPD power on, timeout: %d/500", busy_timeout);
|
||||
}
|
||||
}
|
||||
ESP_LOGI(TAG, "EPD power on complete after %d * 10ms, BUSY pin: %d", busy_timeout, gpio_get_level(PIN_BUSY));
|
||||
std::vector<uint8_t> booster_data = { 0x27, 0x27, 0x18, 0x17 };
|
||||
err = epd_write_cmd_with_data(0x06, booster_data, transaction_guard.transaction_id()); // Booster Soft Start
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to send Booster Soft Start command: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(MINIMUM_PIN_SETUP_DELAY_MS));
|
||||
|
||||
// Enhanced display drive commands
|
||||
std::vector<uint8_t> e0_data = { 0x02 };
|
||||
err = epd_write_cmd_with_data(0xE0, e0_data, transaction_guard.transaction_id());
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to send Enhanced Display Drive command: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
std::vector<uint8_t> e5_data = { 0x5A };
|
||||
err = epd_write_cmd_with_data(0xE5, e5_data, transaction_guard.transaction_id());
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to send Enhanced Display Drive command: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
is_deep_sleep_ = false;
|
||||
|
||||
err = refresh_old_buffer_(transaction_guard.transaction_id());
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to refresh old buffer during init: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
// 1. Hardware Reset
|
||||
err = gpio_set_level(PIN_RST, 0);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to set PIN_RST low: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(MINIMUM_PIN_SETUP_DELAY_MS));
|
||||
err = gpio_set_level(PIN_RST, 1);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to set PIN_RST high: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(MINIMUM_PIN_SETUP_DELAY_MS));
|
||||
|
||||
// 2. Initialization Sequence
|
||||
std::vector<uint8_t> panel_setting_data = { 0x1F };
|
||||
err = epd_handler_.epd_write_cmd_with_data(0x00, panel_setting_data, transaction_id); // Panel Setting
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to send Panel Setting command: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(MINIMUM_PIN_SETUP_DELAY_MS));
|
||||
std::vector<uint8_t> vcom_data = { 0x10, 0x07 };
|
||||
err = epd_handler_.epd_write_cmd_with_data(0x50, vcom_data, transaction_id); // VCOM
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to send VCOM command: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(MINIMUM_PIN_SETUP_DELAY_MS));
|
||||
err = epd_handler_.epd_write_cmd(0x04, transaction_id); // Power ON
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to send Power ON command: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(MINIMUM_POWER_ON_DELAY_MS)); // Wait for power on
|
||||
|
||||
// Check BUSY pin with detailed logging
|
||||
ESP_LOGI(TAG, "Waiting for EPD to be ready after power on...");
|
||||
ESP_LOGI(TAG, "BUSY pin level after power on: %d (0=BUSY, 1=FREE)", gpio_get_level(PIN_BUSY));
|
||||
|
||||
epd_handler_.wait_for_idle();
|
||||
std::vector<uint8_t> booster_data = { 0x27, 0x27, 0x18, 0x17 };
|
||||
err = epd_handler_.epd_write_cmd_with_data(0x06, booster_data, transaction_id); // Booster Soft Start
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to send Booster Soft Start command: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(MINIMUM_PIN_SETUP_DELAY_MS));
|
||||
|
||||
// Enhanced display drive commands
|
||||
std::vector<uint8_t> e0_data = { 0x02 };
|
||||
err = epd_handler_.epd_write_cmd_with_data(0xE0, e0_data, transaction_id);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to send Enhanced Display Drive command: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
std::vector<uint8_t> e5_data = { 0x5A };
|
||||
err = epd_handler_.epd_write_cmd_with_data(0xE5, e5_data, transaction_id);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to send Enhanced Display Drive command: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
is_deep_sleep_ = false;
|
||||
|
||||
err = refresh_old_buffer_(transaction_id);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to refresh old buffer during init: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t EInkDisplayHandler::epd_init_partial_(void) {
|
||||
TransactionGuard transaction_guard(*this);
|
||||
esp_err_t begin_err = transaction_guard.begin();
|
||||
if (begin_err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to begin transaction: %s", esp_err_to_name(begin_err));
|
||||
return begin_err;
|
||||
}
|
||||
return epd_init_partial_internal_(transaction_guard.transaction_id());
|
||||
}
|
||||
|
||||
// Internal version that uses an existing transaction (no separate TransactionGuard)
|
||||
esp_err_t EInkDisplayHandler::epd_init_partial_internal_(uint32_t transaction_id) {
|
||||
ESP_LOGI(TAG, "Initializing EPD for partial refresh (internal)...");
|
||||
@@ -698,7 +602,7 @@ esp_err_t EInkDisplayHandler::epd_init_partial_internal_(uint32_t transaction_id
|
||||
|
||||
// 2. Panel Setting
|
||||
std::vector<uint8_t> panel_setting_data = { 0x1F };
|
||||
err = epd_write_cmd_with_data(0x00, panel_setting_data, transaction_id);
|
||||
err = epd_handler_.epd_write_cmd_with_data(0x00, panel_setting_data, transaction_id);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to send Panel Setting command: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
@@ -706,24 +610,24 @@ esp_err_t EInkDisplayHandler::epd_init_partial_internal_(uint32_t transaction_id
|
||||
vTaskDelay(pdMS_TO_TICKS(MINIMUM_PIN_SETUP_DELAY_MS));
|
||||
|
||||
// 3. Power ON
|
||||
err = epd_write_cmd(0x04, transaction_id);
|
||||
err = epd_handler_.epd_write_cmd(0x04, transaction_id);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to send Power ON command: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(MINIMUM_POWER_ON_DELAY_MS));
|
||||
wait_for_idle();
|
||||
epd_handler_.wait_for_idle();
|
||||
|
||||
// 4. Partial initialization sequence - Enhanced Display Drive
|
||||
std::vector<uint8_t> e0_data = { 0x02 };
|
||||
err = epd_write_cmd_with_data(0xE0, e0_data, transaction_id);
|
||||
err = epd_handler_.epd_write_cmd_with_data(0xE0, e0_data, transaction_id);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to send Enhanced Display Drive command (E0): %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> e5_data = { 0x6E };
|
||||
err = epd_write_cmd_with_data(0xE5, e5_data, transaction_id);
|
||||
err = epd_handler_.epd_write_cmd_with_data(0xE5, e5_data, transaction_id);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to send Enhanced Display Drive command (E5): %s", esp_err_to_name(err));
|
||||
return err;
|
||||
@@ -804,221 +708,43 @@ esp_err_t EInkDisplayHandler::init_touch_() {
|
||||
}
|
||||
|
||||
esp_err_t EInkDisplayHandler::refresh_old_buffer_(uint32_t transaction_id) {
|
||||
ESP_LOGI(TAG, "Refreshing old buffer to match current draw buffer...");
|
||||
ESP_LOGI(TAG, "Refreshing display SRAM to match current buffers...");
|
||||
esp_err_t err;
|
||||
|
||||
// Send command to write old data
|
||||
err = epd_write_cmd(0x10, transaction_id); // Command to write old data
|
||||
err = epd_handler_.epd_write_cmd(0x92, transaction_id); // enter normal mode
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to enter normal mode: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
// Send command to write old data (0x10)
|
||||
err = epd_handler_.epd_write_cmd(0x10, transaction_id);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to send old data command: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
// Send the current draw buffer as old data
|
||||
err = transfer_spi_data(draw_buffer_, DISPLAY_BUFFER_SIZE, transaction_id);
|
||||
// Send the old buffer as old data
|
||||
err = epd_handler_.transfer_spi_data(old_buffer_, DISPLAY_BUFFER_SIZE, transaction_id);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to send old buffer data: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Old buffer refreshed successfully");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t EInkDisplayHandler::epd_write_cmd(const uint8_t cmd, uint32_t transaction_id) {
|
||||
ESP_LOGI(TAG, "epd_write_cmd: waiting to send 0x%02X", cmd);
|
||||
|
||||
SemaphoreGuard transaction_guard(spi_transaction_mutex_);
|
||||
esp_err_t err =
|
||||
wait_for_transaction_end_(pdMS_TO_TICKS(5000), transaction_id, transaction_guard);
|
||||
// Also write to new data register (0x13) to ensure consistent baseline
|
||||
// This prevents undefined state after hardware reset
|
||||
err = epd_handler_.epd_write_cmd(0x13, transaction_id);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to wait for previous transaction end before sending cmd 0x%02X: %s",
|
||||
cmd, esp_err_to_name(err));
|
||||
ESP_LOGE(TAG, "Failed to send new data command: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
SemaphoreGuard guard(spi_mutex_);
|
||||
if (!guard.take(pdMS_TO_TICKS(5000))) {
|
||||
ESP_LOGE(TAG, "SPI mutex timeout for cmd 0x%02X", cmd);
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
err = dangerous_epd_write_cmd_without_lock_(cmd);
|
||||
ESP_LOGI(TAG, "epd_write_cmd: 0x%02X done", cmd);
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t EInkDisplayHandler::epd_write_data(const uint8_t data, uint32_t transaction_id) {
|
||||
ESP_LOGI(TAG, "epd_write_data: waiting to send 0x%02X", data);
|
||||
SemaphoreGuard transaction_guard(spi_transaction_mutex_);
|
||||
esp_err_t err =
|
||||
wait_for_transaction_end_(pdMS_TO_TICKS(5000), transaction_id, transaction_guard);
|
||||
// Send the draw buffer as new data (should match old_buffer_ after full refresh)
|
||||
err = epd_handler_.transfer_spi_data(old_buffer_, DISPLAY_BUFFER_SIZE, transaction_id);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to wait for previous transaction end before sending data 0x%02X: %s",
|
||||
data, esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
SemaphoreGuard guard(spi_mutex_);
|
||||
if (!guard.take(pdMS_TO_TICKS(5000))) {
|
||||
ESP_LOGE(TAG, "SPI mutex timeout for data 0x%02X", data);
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
err = dangerous_epd_write_data_without_lock_(data);
|
||||
ESP_LOGI(TAG, "epd_write_data: 0x%02X done", data);
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t EInkDisplayHandler::epd_write_cmd_with_data(const uint8_t cmd, std::vector<uint8_t>& data, uint32_t transaction_id) {
|
||||
const size_t data_len = data.size();
|
||||
ESP_LOGI(TAG, "epd_write_cmd_with_data: waiting to send cmd 0x%02X with %u bytes of data", cmd, data_len);
|
||||
|
||||
SemaphoreGuard transaction_guard(spi_transaction_mutex_);
|
||||
esp_err_t err =
|
||||
wait_for_transaction_end_(pdMS_TO_TICKS(5000), transaction_id, transaction_guard);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to wait for previous transaction end before sending cmd 0x%02X: %s, with data",
|
||||
cmd, esp_err_to_name(err));
|
||||
ESP_LOGE(TAG, "Failed to send draw buffer data: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
SemaphoreGuard guard(spi_mutex_);
|
||||
if (!guard.take(pdMS_TO_TICKS(5000))) {
|
||||
ESP_LOGE(TAG, "SPI mutex timeout for cmd with data 0x%02X", cmd);
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
err = dangerous_epd_write_cmd_without_lock_(cmd);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
};
|
||||
for (size_t i = 0; i < data_len; ++i) {
|
||||
err = dangerous_epd_write_data_without_lock_(data[i]);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
ESP_LOGI(TAG, "epd_write_cmd_with_data: cmd 0x%02X with %u bytes of data done", cmd, data_len);
|
||||
ESP_LOGI(TAG, "Display SRAM refreshed successfully");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
||||
esp_err_t EInkDisplayHandler::dangerous_epd_write_cmd_without_lock_(const uint8_t cmd) {
|
||||
ESP_LOGI(TAG, "dangerous_epd_write_cmd_without_lock_: sending 0x%02X", cmd);
|
||||
gpio_set_level(PIN_DC, 0); // Command mode
|
||||
spi_transaction_t t {};
|
||||
t.length = 8;t.tx_buffer = &cmd;
|
||||
esp_err_t err = spi_device_polling_transmit(spi_, &t);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to send data 0x%02X", cmd);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "dangerous_epd_write_cmd_without_lock_: 0x%02X sent", cmd);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t EInkDisplayHandler::dangerous_epd_write_data_without_lock_(const uint8_t data) {
|
||||
ESP_LOGI(TAG, "dangerous_epd_write_data_without_lock_: sending 0x%02X", data);
|
||||
gpio_set_level(PIN_DC, 1); // Data mode
|
||||
spi_transaction_t t = { };
|
||||
t.length = 8; t.tx_buffer = &data;
|
||||
esp_err_t err = spi_device_polling_transmit(spi_, &t);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to send data 0x%02X", data);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "dangerous_epd_write_data_without_lock_: 0x%02X sent", data);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t EInkDisplayHandler::transfer_spi_data(const uint8_t* data, const size_t& length, uint32_t transaction_id) {
|
||||
ESP_LOGI(TAG, "transfer_spi_data: waiting to send %zu bytes of data", length);
|
||||
|
||||
SemaphoreGuard transaction_guard(spi_transaction_mutex_);
|
||||
esp_err_t err =
|
||||
wait_for_transaction_end_(pdMS_TO_TICKS(5000), transaction_id, transaction_guard);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to wait for previous transaction end before sending data of %zu bytes: %s",
|
||||
length, esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
SemaphoreGuard guard(spi_mutex_);
|
||||
if (!guard.take(pdMS_TO_TICKS(5000))) {
|
||||
ESP_LOGE(TAG, "SPI mutex timeout for data transfer of %zu bytes", length);
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
ESP_LOGI(TAG, "transfer_spi_data: starting to send %zu bytes of data", length);
|
||||
|
||||
size_t offset = 0;
|
||||
size_t remaining = length;
|
||||
gpio_set_level(PIN_DC, 1); // Data mode
|
||||
while (remaining > 0) {
|
||||
size_t transfer_size = (remaining < DMA_TRANSFER_CHUNK_SIZE) ? remaining : DMA_TRANSFER_CHUNK_SIZE;
|
||||
|
||||
spi_transaction_t t = {};
|
||||
t.length = transfer_size * 8; // Length in bits
|
||||
t.tx_buffer = data + offset;
|
||||
|
||||
esp_err_t ret = spi_device_polling_transmit(spi_, &t);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to send SPI chunk at offset %zu: %s", offset, esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
remaining -= transfer_size;
|
||||
offset += transfer_size;
|
||||
|
||||
// Yield every 16KB to prevent watchdog timeout
|
||||
if (offset % (16 * 1024) == 0) {
|
||||
ESP_LOGI(TAG, "New data progress: %zu/%zu bytes sent, yielding...", offset, length);
|
||||
vTaskDelay(pdMS_TO_TICKS(1));
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "transfer_spi_data: completed sending %zu bytes of data", length);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t EInkDisplayHandler::begin_transaction_(TickType_t timeout, uint32_t& out_id) {
|
||||
ESP_LOGI(TAG, "begin_transaction_: waiting to obtain transaction mutex");
|
||||
if (xSemaphoreTake(spi_transaction_mutex_, timeout) != pdTRUE) {
|
||||
ESP_LOGE(TAG, "begin_transaction_: transaction mutex timeout");
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
out_id = ++spi_transaction_id;
|
||||
ESP_LOGI(TAG, "begin_transaction_: transaction mutex obtained");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t EInkDisplayHandler::end_transaction_(void) {
|
||||
ESP_LOGI(TAG, "end_transaction_: releasing transaction mutex");
|
||||
if (xSemaphoreGive(spi_transaction_mutex_) != pdTRUE) {
|
||||
ESP_LOGE(TAG, "end_transaction_: failed to release transaction mutex");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "end_transaction_: transaction mutex released");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t EInkDisplayHandler::wait_for_transaction_end_(TickType_t timeout, uint32_t awaiting_transaction_id, SemaphoreGuard& out_transaction_guard) {
|
||||
// Validate transaction ID if provided
|
||||
if (awaiting_transaction_id != 0 && awaiting_transaction_id != spi_transaction_id) {
|
||||
// Invalid transaction ID
|
||||
ESP_LOGE(TAG, "Invalid transaction ID 0x%08X while waiting, current transaction ID: 0x%08X",
|
||||
awaiting_transaction_id, spi_transaction_id);
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
SemaphoreGuard transaction_guard(spi_transaction_mutex_);
|
||||
if (awaiting_transaction_id == 0) {
|
||||
// wait for current transaction to complete
|
||||
ESP_LOGV(TAG, "Waiting for current transaction 0x%08X to complete",
|
||||
spi_transaction_id);
|
||||
// take the mutex to ensure no transaction is active
|
||||
if (!transaction_guard.take(timeout)) {
|
||||
ESP_LOGE(TAG, "SPI transaction mutex timeout while waiting for transaction end");
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
}
|
||||
// awaited_transaction_id is valid and matches current transaction ID or 0
|
||||
out_transaction_guard = std::move(transaction_guard);
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "common/semaphore_guard.h"
|
||||
#include <vector>
|
||||
#include <atomic>
|
||||
#include "epd_handler.h"
|
||||
|
||||
// Refresh mode configuration
|
||||
#define PARTIAL_REFRESH_THRESHOLD 10 // Full refresh every N partial refreshes
|
||||
@@ -62,74 +63,37 @@ public:
|
||||
// Request a full refresh on next flush
|
||||
void request_full_refresh(void);
|
||||
|
||||
// Check if display is busy (refreshing)
|
||||
bool is_busy(void) const;
|
||||
void wait_for_idle(void) const;
|
||||
bool is_busy() {
|
||||
return epd_handler_.is_busy();
|
||||
}
|
||||
|
||||
esp_lcd_touch_handle_t get_touch_handle() const { return tp_handle_; }
|
||||
|
||||
protected:
|
||||
esp_err_t epd_write_cmd(const uint8_t cmd, uint32_t transaction_id);
|
||||
esp_err_t epd_write_data(const uint8_t data, uint32_t transaction_id);
|
||||
esp_err_t epd_write_cmd_with_data(const uint8_t cmd, std::vector<uint8_t>& data, uint32_t transaction_id);
|
||||
esp_err_t transfer_spi_data(const uint8_t* data, const size_t& length, uint32_t transaction_id);
|
||||
|
||||
private:
|
||||
|
||||
esp_err_t init_display_pins_(void);
|
||||
esp_err_t epd_init_(void); // full fast refresh init
|
||||
esp_err_t epd_init_partial_(void); // partial refresh init (standalone)
|
||||
esp_err_t epd_init_internal_(uint32_t transaction_id); // full fast refresh init
|
||||
esp_err_t epd_init_partial_internal_(uint32_t transaction_id); // partial refresh init (within existing transaction)
|
||||
esp_err_t init_touch_(void);
|
||||
esp_err_t dangerous_epd_write_cmd_without_lock_(const uint8_t cmd);
|
||||
esp_err_t dangerous_epd_write_data_without_lock_(const uint8_t data);
|
||||
|
||||
// write to the internal draw buffer
|
||||
void write_to_buffer_(const uint8_t* src, const RefreshArea& area);
|
||||
// write the internal draw buffer to the display's old sram
|
||||
esp_err_t refresh_old_buffer_(uint32_t transaction_id);
|
||||
|
||||
esp_err_t begin_transaction_(TickType_t timeout, uint32_t& out_id);
|
||||
esp_err_t end_transaction_(void);
|
||||
// given a transaction ID, wait for current transaction to complete. The transaction ID will determine if the wait is needed.
|
||||
esp_err_t wait_for_transaction_end_(TickType_t timeout, uint32_t awaiting_transaction_id, SemaphoreGuard& out_transaction_guard);
|
||||
|
||||
friend class TransactionGuard;
|
||||
|
||||
EPDHandler epd_handler_;
|
||||
uint32_t partial_refresh_count_ = 0;
|
||||
bool force_full_refresh_ = false;
|
||||
std::atomic<bool> is_deep_sleep_ { false };
|
||||
|
||||
SemaphoreHandle_t spi_mutex_ = nullptr;
|
||||
SemaphoreHandle_t spi_transaction_mutex_ = nullptr;
|
||||
SemaphoreHandle_t refresh_mutex_ = nullptr;
|
||||
uint32_t spi_transaction_id = 0; // For tracking SPI transactions
|
||||
spi_device_handle_t spi_ = nullptr;
|
||||
esp_lcd_panel_io_handle_t tp_io_handle_ = nullptr;
|
||||
esp_lcd_touch_handle_t tp_handle_ = nullptr;
|
||||
|
||||
// this buffer reflects the current display state (1=black, 0=white)
|
||||
uint8_t* draw_buffer_ = nullptr;
|
||||
uint8_t* old_buffer_ = nullptr;
|
||||
RefreshArea refresh_area_ = { 0, 0, 0, 0 };
|
||||
};
|
||||
|
||||
|
||||
class TransactionGuard {
|
||||
public:
|
||||
TransactionGuard(EInkDisplayHandler& handler, TickType_t timeout = portMAX_DELAY)
|
||||
: handler_(handler) { }
|
||||
~TransactionGuard() { if (transaction_id_) handler_.end_transaction_(); }
|
||||
|
||||
esp_err_t begin(TickType_t timeout = portMAX_DELAY) {
|
||||
esp_err_t err = handler_.begin_transaction_(timeout, transaction_id_);
|
||||
return err;
|
||||
}
|
||||
uint32_t transaction_id() const { return transaction_id_; }
|
||||
bool is_active() const { return transaction_id_ != 0; }
|
||||
private:
|
||||
// delete copy constructor and assignment operator
|
||||
TransactionGuard(const TransactionGuard&) = delete;
|
||||
TransactionGuard& operator=(const TransactionGuard&) = delete;
|
||||
EInkDisplayHandler& handler_;
|
||||
uint32_t transaction_id_ = 0;
|
||||
};
|
||||
|
||||
287
main/display/epd_handler.cpp
Normal file
287
main/display/epd_handler.cpp
Normal file
@@ -0,0 +1,287 @@
|
||||
#include "display/epd_handler.h"
|
||||
#include "esp_log.h"
|
||||
#include "display/constants.h"
|
||||
#include "common/constants.h"
|
||||
#include "esp_lcd_touch_gt911.h"
|
||||
#include <driver/i2c.h>
|
||||
#define TAG "EPDHandler"
|
||||
|
||||
#define BUSY_ACTIVE_LEVEL 0 // BUSY pin is active low
|
||||
#define BUSY_INACTIVE_LEVEL 1
|
||||
#define DMA_TRANSFER_CHUNK_SIZE 4096 // 4KB chunk size for DMA transfers
|
||||
|
||||
EPDHandler::EPDHandler() {
|
||||
spi_mutex_ = xSemaphoreCreateMutex();
|
||||
if (spi_mutex_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to create SPI mutex");
|
||||
}
|
||||
spi_transaction_mutex_ = xSemaphoreCreateMutex();
|
||||
if (spi_transaction_mutex_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to create SPI transaction mutex");
|
||||
}
|
||||
}
|
||||
|
||||
EPDHandler::~EPDHandler() {
|
||||
if (spi_mutex_ != nullptr) {
|
||||
vSemaphoreDelete(spi_mutex_);
|
||||
}
|
||||
if (spi_transaction_mutex_ != nullptr) {
|
||||
vSemaphoreDelete(spi_transaction_mutex_);
|
||||
}
|
||||
if (spi_ != nullptr) {
|
||||
spi_bus_remove_device(spi_);
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t EPDHandler::init() {
|
||||
esp_err_t err;
|
||||
|
||||
// Initialize SPI bus
|
||||
spi_bus_config_t buscfg = {};
|
||||
buscfg.mosi_io_num = 11; // MOSI pin
|
||||
buscfg.miso_io_num = -1; // No MISO for e-paper
|
||||
buscfg.sclk_io_num = 12; // SCK pin
|
||||
buscfg.quadwp_io_num = -1;
|
||||
buscfg.quadhd_io_num = -1;
|
||||
buscfg.max_transfer_sz = DMA_TRANSFER_CHUNK_SIZE;
|
||||
|
||||
err = spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to initialize SPI bus: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
// Add SPI device
|
||||
spi_device_interface_config_t devcfg = {};
|
||||
devcfg.clock_speed_hz = 10 * 1000 * 1000; // 10 MHz
|
||||
devcfg.mode = 0; // SPI mode 0
|
||||
devcfg.spics_io_num = PIN_CS;
|
||||
devcfg.queue_size = 7; // Queue size for non-blocking transactions
|
||||
devcfg.pre_cb = nullptr;
|
||||
|
||||
err = spi_bus_add_device(SPI2_HOST, &devcfg, &spi_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to add SPI device: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// Check if display is busy (refreshing)
|
||||
bool EPDHandler::is_busy(void) const {
|
||||
return gpio_get_level(PIN_BUSY) != BUSY_ACTIVE_LEVEL; // BUSY is active LOW
|
||||
}
|
||||
void EPDHandler::wait_for_idle(void) const {
|
||||
ESP_LOGI(TAG, "Waiting for display ready (BUSY pin)...");
|
||||
int initial_level = gpio_get_level(PIN_BUSY);
|
||||
ESP_LOGI(TAG, "Initial BUSY pin level: %d (0=BUSY, 1=FREE)", initial_level);
|
||||
|
||||
// If already free, no need to wait
|
||||
if (initial_level == BUSY_INACTIVE_LEVEL) {
|
||||
ESP_LOGI(TAG, "Display already ready (BUSY pin = 1)");
|
||||
return;
|
||||
}
|
||||
while (gpio_get_level(PIN_BUSY) != BUSY_INACTIVE_LEVEL) {
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
ESP_LOGI(TAG, "Display is now ready (BUSY pin = 1)");
|
||||
}
|
||||
|
||||
esp_err_t EPDHandler::epd_write_cmd(const uint8_t cmd, uint32_t transaction_id) {
|
||||
ESP_LOGI(TAG, "epd_write_cmd: waiting to send 0x%02X", cmd);
|
||||
|
||||
SemaphoreGuard transaction_guard(spi_transaction_mutex_);
|
||||
esp_err_t err =
|
||||
wait_for_transaction_end_(pdMS_TO_TICKS(5000), transaction_id, transaction_guard);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to wait for previous transaction end before sending cmd 0x%02X: %s",
|
||||
cmd, esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
SemaphoreGuard guard(spi_mutex_);
|
||||
if (!guard.take(pdMS_TO_TICKS(5000))) {
|
||||
ESP_LOGE(TAG, "SPI mutex timeout for cmd 0x%02X", cmd);
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
err = dangerous_epd_write_cmd_without_lock_(cmd);
|
||||
ESP_LOGI(TAG, "epd_write_cmd: 0x%02X done", cmd);
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t EPDHandler::epd_write_data(const uint8_t data, uint32_t transaction_id) {
|
||||
ESP_LOGI(TAG, "epd_write_data: waiting to send 0x%02X", data);
|
||||
SemaphoreGuard transaction_guard(spi_transaction_mutex_);
|
||||
esp_err_t err =
|
||||
wait_for_transaction_end_(pdMS_TO_TICKS(5000), transaction_id, transaction_guard);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to wait for previous transaction end before sending data 0x%02X: %s",
|
||||
data, esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
SemaphoreGuard guard(spi_mutex_);
|
||||
if (!guard.take(pdMS_TO_TICKS(5000))) {
|
||||
ESP_LOGE(TAG, "SPI mutex timeout for data 0x%02X", data);
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
err = dangerous_epd_write_data_without_lock_(data);
|
||||
ESP_LOGI(TAG, "epd_write_data: 0x%02X done", data);
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t EPDHandler::epd_write_cmd_with_data(const uint8_t cmd, std::vector<uint8_t>& data, uint32_t transaction_id) {
|
||||
const size_t data_len = data.size();
|
||||
ESP_LOGI(TAG, "epd_write_cmd_with_data: waiting to send cmd 0x%02X with %u bytes of data", cmd, data_len);
|
||||
|
||||
SemaphoreGuard transaction_guard(spi_transaction_mutex_);
|
||||
esp_err_t err =
|
||||
wait_for_transaction_end_(pdMS_TO_TICKS(5000), transaction_id, transaction_guard);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to wait for previous transaction end before sending cmd 0x%02X: %s, with data",
|
||||
cmd, esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
SemaphoreGuard guard(spi_mutex_);
|
||||
if (!guard.take(pdMS_TO_TICKS(5000))) {
|
||||
ESP_LOGE(TAG, "SPI mutex timeout for cmd with data 0x%02X", cmd);
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
err = dangerous_epd_write_cmd_without_lock_(cmd);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
};
|
||||
for (size_t i = 0; i < data_len; ++i) {
|
||||
err = dangerous_epd_write_data_without_lock_(data[i]);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
}
|
||||
ESP_LOGI(TAG, "epd_write_cmd_with_data: cmd 0x%02X with %u bytes of data done", cmd, data_len);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
||||
esp_err_t EPDHandler::dangerous_epd_write_cmd_without_lock_(const uint8_t cmd) {
|
||||
ESP_LOGI(TAG, "dangerous_epd_write_cmd_without_lock_: sending 0x%02X", cmd);
|
||||
gpio_set_level(PIN_DC, 0); // Command mode
|
||||
spi_transaction_t t {};
|
||||
t.length = 8;t.tx_buffer = &cmd;
|
||||
esp_err_t err = spi_device_polling_transmit(spi_, &t);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to send data 0x%02X", cmd);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "dangerous_epd_write_cmd_without_lock_: 0x%02X sent", cmd);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t EPDHandler::dangerous_epd_write_data_without_lock_(const uint8_t data) {
|
||||
ESP_LOGI(TAG, "dangerous_epd_write_data_without_lock_: sending 0x%02X", data);
|
||||
gpio_set_level(PIN_DC, 1); // Data mode
|
||||
spi_transaction_t t = { };
|
||||
t.length = 8; t.tx_buffer = &data;
|
||||
esp_err_t err = spi_device_polling_transmit(spi_, &t);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to send data 0x%02X", data);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "dangerous_epd_write_data_without_lock_: 0x%02X sent", data);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t EPDHandler::transfer_spi_data(const uint8_t* data, const size_t& length, uint32_t transaction_id) {
|
||||
ESP_LOGI(TAG, "transfer_spi_data: waiting to send %zu bytes of data", length);
|
||||
|
||||
SemaphoreGuard transaction_guard(spi_transaction_mutex_);
|
||||
esp_err_t err =
|
||||
wait_for_transaction_end_(pdMS_TO_TICKS(5000), transaction_id, transaction_guard);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to wait for previous transaction end before sending data of %zu bytes: %s",
|
||||
length, esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
SemaphoreGuard guard(spi_mutex_);
|
||||
if (!guard.take(pdMS_TO_TICKS(5000))) {
|
||||
ESP_LOGE(TAG, "SPI mutex timeout for data transfer of %zu bytes", length);
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
ESP_LOGI(TAG, "transfer_spi_data: starting to send %zu bytes of data", length);
|
||||
|
||||
size_t offset = 0;
|
||||
size_t remaining = length;
|
||||
gpio_set_level(PIN_DC, 1); // Data mode
|
||||
while (remaining > 0) {
|
||||
size_t transfer_size = (remaining < DMA_TRANSFER_CHUNK_SIZE) ? remaining : DMA_TRANSFER_CHUNK_SIZE;
|
||||
|
||||
spi_transaction_t t = {};
|
||||
t.length = transfer_size * 8; // Length in bits
|
||||
t.tx_buffer = data + offset;
|
||||
|
||||
esp_err_t ret = spi_device_polling_transmit(spi_, &t);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to send SPI chunk at offset %zu: %s", offset, esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
remaining -= transfer_size;
|
||||
offset += transfer_size;
|
||||
|
||||
// Yield every 16KB to prevent watchdog timeout
|
||||
if (offset % (16 * 1024) == 0) {
|
||||
ESP_LOGI(TAG, "New data progress: %zu/%zu bytes sent, yielding...", offset, length);
|
||||
vTaskDelay(pdMS_TO_TICKS(1));
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "transfer_spi_data: completed sending %zu bytes of data", length);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t EPDHandler::begin_transaction_(TickType_t timeout, uint32_t& out_id) {
|
||||
ESP_LOGI(TAG, "begin_transaction_: waiting to obtain transaction mutex");
|
||||
if (xSemaphoreTake(spi_transaction_mutex_, timeout) != pdTRUE) {
|
||||
ESP_LOGE(TAG, "begin_transaction_: transaction mutex timeout");
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
out_id = ++spi_transaction_id;
|
||||
ESP_LOGI(TAG, "begin_transaction_: transaction mutex obtained");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t EPDHandler::end_transaction_(void) {
|
||||
ESP_LOGI(TAG, "end_transaction_: releasing transaction mutex");
|
||||
if (xSemaphoreGive(spi_transaction_mutex_) != pdTRUE) {
|
||||
ESP_LOGE(TAG, "end_transaction_: failed to release transaction mutex");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "end_transaction_: transaction mutex released");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t EPDHandler::wait_for_transaction_end_(TickType_t timeout, uint32_t awaiting_transaction_id, SemaphoreGuard& out_transaction_guard) {
|
||||
// Validate transaction ID if provided
|
||||
if (awaiting_transaction_id != 0 && awaiting_transaction_id != spi_transaction_id) {
|
||||
// Invalid transaction ID
|
||||
ESP_LOGE(TAG, "Invalid transaction ID 0x%08X while waiting, current transaction ID: 0x%08X",
|
||||
awaiting_transaction_id, spi_transaction_id);
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
SemaphoreGuard transaction_guard(spi_transaction_mutex_);
|
||||
if (awaiting_transaction_id == 0) {
|
||||
// wait for current transaction to complete
|
||||
ESP_LOGV(TAG, "Waiting for current transaction 0x%08X to complete",
|
||||
spi_transaction_id);
|
||||
// take the mutex to ensure no transaction is active
|
||||
if (!transaction_guard.take(timeout)) {
|
||||
ESP_LOGE(TAG, "SPI transaction mutex timeout while waiting for transaction end");
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
}
|
||||
// awaited_transaction_id is valid and matches current transaction ID or 0
|
||||
out_transaction_guard = std::move(transaction_guard);
|
||||
return ESP_OK;
|
||||
}
|
||||
41
main/display/epd_handler.h
Normal file
41
main/display/epd_handler.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "driver/spi_master.h"
|
||||
#include "common/semaphore_guard.h"
|
||||
#include <vector>
|
||||
#include "display/transaction_guard.h"
|
||||
|
||||
class EPDHandler : public WithTransaction {
|
||||
public:
|
||||
EPDHandler();
|
||||
~EPDHandler();
|
||||
esp_err_t init();
|
||||
|
||||
esp_err_t epd_write_cmd(const uint8_t cmd, uint32_t transaction_id);
|
||||
esp_err_t epd_write_data(const uint8_t data, uint32_t transaction_id);
|
||||
esp_err_t epd_write_cmd_with_data(const uint8_t cmd, std::vector<uint8_t>& data, uint32_t transaction_id);
|
||||
esp_err_t transfer_spi_data(const uint8_t* data, const size_t& length, uint32_t transaction_id);
|
||||
|
||||
bool is_busy(void) const;
|
||||
void wait_for_idle(void) const;
|
||||
|
||||
private:
|
||||
|
||||
esp_err_t dangerous_epd_write_cmd_without_lock_(const uint8_t cmd);
|
||||
esp_err_t dangerous_epd_write_data_without_lock_(const uint8_t data);
|
||||
|
||||
esp_err_t begin_transaction_(TickType_t timeout, uint32_t& out_id) override;
|
||||
esp_err_t end_transaction_(void) override;
|
||||
// given a transaction ID, wait for current transaction to complete. The transaction ID will determine if the wait is needed.
|
||||
esp_err_t wait_for_transaction_end_(TickType_t timeout, uint32_t awaiting_transaction_id, SemaphoreGuard& out_transaction_guard);
|
||||
|
||||
spi_device_handle_t spi_ = nullptr;
|
||||
|
||||
SemaphoreHandle_t spi_mutex_ = nullptr;
|
||||
SemaphoreHandle_t spi_transaction_mutex_ = nullptr;
|
||||
|
||||
uint32_t spi_transaction_id = 0; // For tracking SPI transactions
|
||||
|
||||
friend class TransactionGuard;
|
||||
};
|
||||
@@ -103,6 +103,10 @@ void LVGLHandler::flush_cb_(lv_display_t* disp, const lv_area_t* area, uint8_t*
|
||||
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) {
|
||||
// revert the pixel data for e-ink (LVGL: 1=white, 0=black; E-Ink: 1=black, 0=white)
|
||||
for (size_t i = 0; i < DISPLAY_BUFFER_SIZE; ++i) {
|
||||
pixel_data[i] = ~pixel_data[i];
|
||||
}
|
||||
esp_err_t err = handler->display_handler_->full_write(
|
||||
pixel_data,
|
||||
true // white basemap
|
||||
|
||||
33
main/display/transaction_guard.h
Normal file
33
main/display/transaction_guard.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include <esp_err.h>
|
||||
#include <type_traits>
|
||||
|
||||
class WithTransaction {
|
||||
protected:
|
||||
virtual esp_err_t end_transaction_() = 0;
|
||||
virtual esp_err_t begin_transaction_(TickType_t timeout, uint32_t& out_id) = 0;
|
||||
|
||||
friend class TransactionGuard;
|
||||
};
|
||||
|
||||
class TransactionGuard {
|
||||
public:
|
||||
TransactionGuard(WithTransaction& handler, TickType_t timeout = portMAX_DELAY)
|
||||
: handler_(handler) { }
|
||||
~TransactionGuard() { if (transaction_id_) handler_.end_transaction_(); }
|
||||
|
||||
esp_err_t begin(TickType_t timeout = portMAX_DELAY) {
|
||||
esp_err_t err = handler_.begin_transaction_(timeout, transaction_id_);
|
||||
return err;
|
||||
}
|
||||
uint32_t transaction_id() const { return transaction_id_; }
|
||||
bool is_active() const { return transaction_id_ != 0; }
|
||||
private:
|
||||
// delete copy constructor and assignment operator
|
||||
TransactionGuard(const TransactionGuard&) = delete;
|
||||
TransactionGuard& operator=(const TransactionGuard&) = delete;
|
||||
WithTransaction& handler_;
|
||||
uint32_t transaction_id_ = 0;
|
||||
};
|
||||
Reference in New Issue
Block a user