Merge branch 'feat/lcd_i2c_use_configurable_timeout_v5.3' into 'release/v5.3'

feat(lcd): add configurable timeout for lcd i2c panel (v5.3)

See merge request espressif/esp-idf!48673
This commit is contained in:
morris
2026-06-17 10:53:16 +08:00
8 changed files with 105 additions and 19 deletions
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -21,6 +21,7 @@
#include "esp_check.h"
#include "freertos/FreeRTOS.h"
#include "esp_heap_caps.h"
static const char *TAG = "lcd_panel.io.i2c";
#define BYTESHIFT(VAR, IDX) (((VAR) >> ((IDX) * 8)) & 0xFF)
@@ -44,6 +45,7 @@ typedef struct {
uint32_t control_phase_data; // control byte when transferring data
esp_lcd_panel_io_color_trans_done_cb_t on_color_trans_done; // User register's callback, invoked when color data trans done
void *user_ctx; // User's private data, passed directly to callback on_color_trans_done()
int transaction_timeout_ms; // I2C xfer timeout passed to i2c_master_* (-1 = infinite)
} lcd_panel_io_i2c_t;
esp_err_t esp_lcd_new_panel_io_i2c_v2(i2c_master_bus_handle_t bus, const esp_lcd_panel_io_i2c_config_t *io_config, esp_lcd_panel_io_handle_t *ret_io)
@@ -56,6 +58,7 @@ esp_err_t esp_lcd_new_panel_io_i2c_v2(i2c_master_bus_handle_t bus, const esp_lcd
i2c_master_dev_handle_t i2c_handle = NULL;
ESP_GOTO_ON_FALSE(io_config && ret_io, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
ESP_GOTO_ON_FALSE(io_config->control_phase_bytes * 8 > io_config->dc_bit_offset, ESP_ERR_INVALID_ARG, err, TAG, "D/C bit exceeds control bytes");
ESP_GOTO_ON_FALSE(io_config->transaction_timeout_ms >= -1, ESP_ERR_INVALID_ARG, err, TAG, "invalid transaction_timeout_ms");
i2c_panel_io = calloc(1, sizeof(lcd_panel_io_i2c_t));
ESP_GOTO_ON_FALSE(i2c_panel_io, ESP_ERR_NO_MEM, err, TAG, "no mem for i2c panel io");
@@ -74,6 +77,8 @@ esp_err_t esp_lcd_new_panel_io_i2c_v2(i2c_master_bus_handle_t bus, const esp_lcd
i2c_panel_io->control_phase_data = (!io_config->flags.dc_low_on_data) << (io_config->dc_bit_offset);
i2c_panel_io->control_phase_cmd = (io_config->flags.dc_low_on_data) << (io_config->dc_bit_offset);
i2c_panel_io->dev_addr = io_config->dev_addr;
/* transaction_timeout_ms == 0: omitted or zero-init, keep legacy infinite wait (same as -1). */
i2c_panel_io->transaction_timeout_ms = (io_config->transaction_timeout_ms == 0) ? -1 : io_config->transaction_timeout_ms;
i2c_panel_io->base.del = panel_io_i2c_del;
i2c_panel_io->base.rx_param = panel_io_i2c_rx_param;
i2c_panel_io->base.tx_param = panel_io_i2c_tx_param;
@@ -137,9 +142,9 @@ static esp_err_t panel_io_i2c_rx_buffer(esp_lcd_panel_io_t *io, int lcd_cmd, voi
write_size += cmds_size;
}
ESP_GOTO_ON_ERROR(i2c_master_transmit_receive(i2c_panel_io->i2c_handle, write_buffer, write_size, buffer, buffer_size, -1), err, TAG, "i2c transaction failed");
ESP_GOTO_ON_ERROR(i2c_master_transmit_receive(i2c_panel_io->i2c_handle, write_buffer, write_size, buffer, buffer_size, i2c_panel_io->transaction_timeout_ms), err, TAG, "i2c transaction failed");
} else {
ESP_GOTO_ON_ERROR(i2c_master_receive(i2c_panel_io->i2c_handle, buffer, buffer_size, -1), err, TAG, "i2c transaction failed");
ESP_GOTO_ON_ERROR(i2c_master_receive(i2c_panel_io->i2c_handle, buffer, buffer_size, i2c_panel_io->transaction_timeout_ms), err, TAG, "i2c transaction failed");
}
return ESP_OK;
@@ -152,32 +157,53 @@ static esp_err_t panel_io_i2c_tx_buffer(esp_lcd_panel_io_t *io, int lcd_cmd, con
esp_err_t ret = ESP_OK;
lcd_panel_io_i2c_t *i2c_panel_io = __containerof(io, lcd_panel_io_i2c_t, base);
bool send_param = (lcd_cmd != -1);
int write_size = 0;
uint8_t *write_buffer = (uint8_t*)heap_caps_malloc(CONTROL_PHASE_LENGTH + CMD_LENGTH + buffer_size, MALLOC_CAP_8BIT);
ESP_GOTO_ON_FALSE(write_buffer, ESP_ERR_NO_MEM, err, TAG, "no mem for write buffer");
uint8_t control_phase_byte = 0;
size_t control_phase_size = 0;
if (i2c_panel_io->control_phase_enabled) {
write_buffer[0] = is_param ? i2c_panel_io->control_phase_cmd : i2c_panel_io->control_phase_data;
write_size += 1;
control_phase_byte = is_param ? i2c_panel_io->control_phase_cmd : i2c_panel_io->control_phase_data;
control_phase_size = 1;
}
uint8_t *cmd_buffer = NULL;
size_t cmd_buffer_size = 0;
// some displays don't want any additional commands on data transfers
uint8_t cmds[4] = {BYTESHIFT(lcd_cmd, 3), BYTESHIFT(lcd_cmd, 2), BYTESHIFT(lcd_cmd, 1), BYTESHIFT(lcd_cmd, 0)};
if (send_param) {
uint8_t cmds[4] = {BYTESHIFT(lcd_cmd, 3), BYTESHIFT(lcd_cmd, 2), BYTESHIFT(lcd_cmd, 1), BYTESHIFT(lcd_cmd, 0)};
size_t cmds_size = i2c_panel_io->lcd_cmd_bits / 8;
if (cmds_size > 0 && cmds_size <= sizeof(cmds)) {
memcpy(write_buffer + write_size, cmds + (sizeof(cmds) - cmds_size), cmds_size);
write_size += cmds_size;
cmd_buffer = cmds + (sizeof(cmds) - cmds_size);
cmd_buffer_size = cmds_size;
}
}
const uint8_t *lcd_buffer = NULL;
size_t lcd_buffer_size = 0;
if (buffer) {
memcpy(write_buffer + write_size, buffer, buffer_size);
write_size += buffer_size;
lcd_buffer = (const uint8_t *)buffer;
lcd_buffer_size = buffer_size;
}
ESP_GOTO_ON_ERROR(i2c_master_transmit(i2c_panel_io->i2c_handle, write_buffer, write_size, -1), err, TAG, "i2c transaction failed");
size_t write_buffer_size = control_phase_size + cmd_buffer_size + lcd_buffer_size;
ESP_GOTO_ON_FALSE(write_buffer_size > 0, ESP_ERR_INVALID_ARG, err, TAG, "invalid i2c transaction size");
uint8_t *write_buffer = malloc(write_buffer_size);
ESP_GOTO_ON_FALSE(write_buffer, ESP_ERR_NO_MEM, err, TAG, "no mem for i2c transaction buffer");
size_t offset = 0;
if (control_phase_size) {
write_buffer[offset++] = control_phase_byte;
}
if (cmd_buffer_size) {
memcpy(write_buffer + offset, cmd_buffer, cmd_buffer_size);
offset += cmd_buffer_size;
}
if (lcd_buffer_size) {
memcpy(write_buffer + offset, lcd_buffer, lcd_buffer_size);
}
ret = i2c_master_transmit(i2c_panel_io->i2c_handle, write_buffer, write_buffer_size, i2c_panel_io->transaction_timeout_ms);
free(write_buffer);
ESP_GOTO_ON_ERROR(ret, err, TAG, "i2c transaction failed");
if (!is_param) {
// trans done callback
if (i2c_panel_io->on_color_trans_done) {
@@ -187,9 +213,6 @@ static esp_err_t panel_io_i2c_tx_buffer(esp_lcd_panel_io_t *io, int lcd_cmd, con
return ESP_OK;
err:
if (write_buffer) {
free(write_buffer);
}
return ret;
}
@@ -33,6 +33,7 @@ typedef struct {
unsigned int disable_control_phase: 1; /*!< If this flag is enabled, the control phase isn't used */
} flags; /*!< Extra flags to fine-tune the I2C device */
uint32_t scl_speed_hz; /*!< I2C LCD SCL frequency (hz) */
int transaction_timeout_ms; /*!< Timeout for each I2C transfer in ms, 0/-1: wait forever, >0: finite timeout */
} esp_lcd_panel_io_i2c_config_t;
/**
@@ -44,6 +44,7 @@ TEST_CASE("lcd_panel_with_i2c_interface_(ssd1306)", "[lcd]")
.dc_bit_offset = 6, // According to SSD1306 datasheet
.lcd_cmd_bits = 8, // According to SSD1306 datasheet
.lcd_param_bits = 8, // According to SSD1306 datasheet
.transaction_timeout_ms = 0, // 0 keeps the legacy infinite wait behavior
};
TEST_ESP_OK(esp_lcd_new_panel_io_i2c(bus_handle, &io_config, &io_handle));
@@ -44,6 +44,7 @@ TEST_CASE("lcd_panel_with_i2c_interface legacy_(ssd1306)", "[lcd]")
.dc_bit_offset = 6, // According to SSD1306 datasheet
.lcd_cmd_bits = 8, // According to SSD1306 datasheet
.lcd_param_bits = 8, // According to SSD1306 datasheet
.transaction_timeout_ms = 0, // wait infinitely
};
TEST_ESP_OK(esp_lcd_new_panel_io_i2c((esp_lcd_i2c_bus_handle_t)TEST_I2C_HOST_ID, &io_config, &io_handle));