From 69c97183c3f027b7f55d348a03cc9546ab95061d Mon Sep 17 00:00:00 2001 From: Chen Chen Date: Tue, 12 May 2026 15:01:03 +0800 Subject: [PATCH] feat(lcd): add configurable timeout for lcd i2c panel Closes https://github.com/espressif/esp-idf/issues/18542 Closes https://github.com/espressif/esp-idf/pull/18543 --- .../esp_lcd/i2c/esp_lcd_panel_io_i2c_v2.c | 57 ++++++++++++------ components/esp_lcd/include/esp_lcd_io_i2c.h | 1 + .../i2c_lcd/main/test_i2c_lcd_panel.c | 1 + .../main/test_i2c_lcd_legacy_panel.c | 1 + .../api-reference/peripherals/lcd/i2c_lcd.rst | 3 +- .../api-reference/peripherals/lcd/i2c_lcd.rst | 59 ++++++++++++++++++- .../lcd/i2c_oled/main/i2c_oled_example_main.c | 1 + .../cxx_build_test/main/test_i2c_lcd.cpp | 1 + 8 files changed, 105 insertions(+), 19 deletions(-) diff --git a/components/esp_lcd/i2c/esp_lcd_panel_io_i2c_v2.c b/components/esp_lcd/i2c/esp_lcd_panel_io_i2c_v2.c index 30cea14ea4f..deff534e773 100644 --- a/components/esp_lcd/i2c/esp_lcd_panel_io_i2c_v2.c +++ b/components/esp_lcd/i2c/esp_lcd_panel_io_i2c_v2.c @@ -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; } diff --git a/components/esp_lcd/include/esp_lcd_io_i2c.h b/components/esp_lcd/include/esp_lcd_io_i2c.h index e98f18acbd7..42b45470779 100644 --- a/components/esp_lcd/include/esp_lcd_io_i2c.h +++ b/components/esp_lcd/include/esp_lcd_io_i2c.h @@ -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; /** diff --git a/components/esp_lcd/test_apps/i2c_lcd/main/test_i2c_lcd_panel.c b/components/esp_lcd/test_apps/i2c_lcd/main/test_i2c_lcd_panel.c index 19884dee57b..fd85fb46470 100644 --- a/components/esp_lcd/test_apps/i2c_lcd/main/test_i2c_lcd_panel.c +++ b/components/esp_lcd/test_apps/i2c_lcd/main/test_i2c_lcd_panel.c @@ -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)); diff --git a/components/esp_lcd/test_apps/i2c_lcd_legacy/main/test_i2c_lcd_legacy_panel.c b/components/esp_lcd/test_apps/i2c_lcd_legacy/main/test_i2c_lcd_legacy_panel.c index 4a55b60b253..c245e64245e 100644 --- a/components/esp_lcd/test_apps/i2c_lcd_legacy/main/test_i2c_lcd_legacy_panel.c +++ b/components/esp_lcd/test_apps/i2c_lcd_legacy/main/test_i2c_lcd_legacy_panel.c @@ -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)); diff --git a/docs/en/api-reference/peripherals/lcd/i2c_lcd.rst b/docs/en/api-reference/peripherals/lcd/i2c_lcd.rst index 24c51f775f6..8e2ca813946 100644 --- a/docs/en/api-reference/peripherals/lcd/i2c_lcd.rst +++ b/docs/en/api-reference/peripherals/lcd/i2c_lcd.rst @@ -20,7 +20,8 @@ I2C Interfaced LCD - :cpp:member:`esp_lcd_panel_io_i2c_config_t::dev_addr` sets the I2C device address of the LCD controller chip. The LCD driver uses this address to communicate with the LCD controller chip. - :cpp:member:`esp_lcd_panel_io_i2c_config_t::scl_speed_hz` sets the I2C clock frequency in Hz. The value should not exceed the range recommended in the LCD spec. - - :cpp:member:`esp_lcd_panel_io_i2c_config_t::lcd_cmd_bits` and :cpp:member:`esp_lcd_panel_io_i2c_config_t::lcd_param_bits` set the bit width of the command and parameter that recognized by the LCD controller chip. This is chip specific, you should refer to your LCD spec in advance. + - :cpp:member:`esp_lcd_panel_io_i2c_config_t::lcd_cmd_bits` and :cpp:member:`esp_lcd_panel_io_i2c_config_t::lcd_param_bits` set the bit width of the command and parameter recognized by the LCD controller chip. This is chip specific, you should refer to your LCD spec in advance. + - :cpp:member:`esp_lcd_panel_io_i2c_config_t::transaction_timeout_ms` sets the timeout (in milliseconds) for each underlying I2C transfer. Setting this to 0 or -1 means to wait indefinitely. If a positive value is specified, panel IO calls will return ``ESP_ERR_TIMEOUT`` when the timeout is reached. This is useful for cases like shared buses or when a slave device could potentially hang the bus. .. code-block:: c diff --git a/docs/zh_CN/api-reference/peripherals/lcd/i2c_lcd.rst b/docs/zh_CN/api-reference/peripherals/lcd/i2c_lcd.rst index 27e480956d5..dad3f27afea 100644 --- a/docs/zh_CN/api-reference/peripherals/lcd/i2c_lcd.rst +++ b/docs/zh_CN/api-reference/peripherals/lcd/i2c_lcd.rst @@ -1 +1,58 @@ -.. include:: ../../../../en/api-reference/peripherals/lcd/i2c_lcd.rst +I2C 接口的 LCD +--------------- + +:link_to_translation:`en:[English]` + +#. 创建 I2C 总线。详细信息,请参阅 :doc:`I2C API 文档 `。 + + .. code-block:: c + + i2c_master_bus_handle_t i2c_bus = NULL; + i2c_master_bus_config_t bus_config = { + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .i2c_port = I2C_BUS_PORT, + .sda_io_num = EXAMPLE_PIN_NUM_SDA, + .scl_io_num = EXAMPLE_PIN_NUM_SCL, + .flags.enable_internal_pullup = true, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &i2c_bus)); + +#. 从 I2C 总线分配一个 LCD IO 设备句柄。在此步骤中,需要提供以下信息: + + - :cpp:member:`esp_lcd_panel_io_i2c_config_t::dev_addr` 设置 LCD 控制器芯片的 I2C 设备地址。LCD 驱动程序使用此地址与 LCD 控制器芯片通信。 + - :cpp:member:`esp_lcd_panel_io_i2c_config_t::scl_speed_hz` 设置 I2C 时钟频率 (Hz)。该值不应超过 LCD 规格书中推荐的范围。 + - :cpp:member:`esp_lcd_panel_io_i2c_config_t::lcd_cmd_bits` 和 :cpp:member:`esp_lcd_panel_io_i2c_config_t::lcd_param_bits` 分别设置 LCD 控制器芯片可识别的命令及参数的位宽。不同芯片对位宽要求不同,请提前参阅 LCD 规格书。 + - :cpp:member:`esp_lcd_panel_io_i2c_config_t::transaction_timeout_ms` 设置每次底层 I2C 传输的超时时间(毫秒)。设为 0 或 -1 时表示无限等待;如指定为正值,则面板 IO 相关调用会在超时后返回 ``ESP_ERR_TIMEOUT``,适用于共享总线或总线可能被从设备挂死的场景。 + + .. code-block:: c + + esp_lcd_panel_io_handle_t io_handle = NULL; + esp_lcd_panel_io_i2c_config_t io_config = { + .dev_addr = EXAMPLE_I2C_HW_ADDR, + .scl_speed_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ, + .control_phase_bytes = 1, // 参阅 LCD 规格 + .dc_bit_offset = 6, // 参阅 LCD 规格 + .lcd_cmd_bits = EXAMPLE_LCD_CMD_BITS, + .lcd_param_bits = EXAMPLE_LCD_CMD_BITS, + }; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(i2c_bus, &io_config, &io_handle)); + +#. 安装 LCD 控制器驱动程序。LCD 控制器驱动程序负责向 LCD 控制器芯片发送命令和参数。在此步骤中,需要指定上一步骤中分配到的 I2C IO 设备句柄以及一些面板特定配置: + + - :cpp:member:`esp_lcd_panel_dev_config_t::reset_gpio_num` 设置 LCD 的硬件复位 GPIO 编号。如果 LCD 没有硬件复位管脚,则将此设置为 ``-1``。 + - :cpp:member:`esp_lcd_panel_dev_config_t::bits_per_pixel` 设置像素颜色数据的位宽。LCD 驱动程序使用此值计算要发送到 LCD 控制器芯片的字节数。 + + .. code-block:: c + + esp_lcd_panel_handle_t panel_handle = NULL; + esp_lcd_panel_dev_config_t panel_config = { + .bits_per_pixel = 1, + .reset_gpio_num = EXAMPLE_PIN_NUM_RST, + }; + ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(io_handle, &panel_config, &panel_handle)); + +API 参考 +-------- + +.. include-build-file:: inc/esp_lcd_io_i2c.inc diff --git a/examples/peripherals/lcd/i2c_oled/main/i2c_oled_example_main.c b/examples/peripherals/lcd/i2c_oled/main/i2c_oled_example_main.c index 1276da8aefe..f778ff63a86 100644 --- a/examples/peripherals/lcd/i2c_oled/main/i2c_oled_example_main.c +++ b/examples/peripherals/lcd/i2c_oled/main/i2c_oled_example_main.c @@ -70,6 +70,7 @@ void app_main(void) .control_phase_bytes = 1, // According to SSD1306 datasheet .lcd_cmd_bits = EXAMPLE_LCD_CMD_BITS, // According to SSD1306 datasheet .lcd_param_bits = EXAMPLE_LCD_CMD_BITS, // According to SSD1306 datasheet + .transaction_timeout_ms = 0, // wait infinitely #if CONFIG_EXAMPLE_LCD_CONTROLLER_SSD1306 .dc_bit_offset = 6, // According to SSD1306 datasheet #elif CONFIG_EXAMPLE_LCD_CONTROLLER_SH1107 diff --git a/tools/test_apps/system/cxx_build_test/main/test_i2c_lcd.cpp b/tools/test_apps/system/cxx_build_test/main/test_i2c_lcd.cpp index cb058d62c06..5aa34662925 100644 --- a/tools/test_apps/system/cxx_build_test/main/test_i2c_lcd.cpp +++ b/tools/test_apps/system/cxx_build_test/main/test_i2c_lcd.cpp @@ -72,6 +72,7 @@ void test_i2c_lcd_apis(void) .disable_control_phase = false, }, .scl_speed_hz = 10 * 1000, + .transaction_timeout_ms = 0, }; esp_lcd_new_panel_io_i2c(bus_handle, &io_config, &io_handle);