From bfa98c08d01f8559cb341ae9b6e5bb020ebfabbb Mon Sep 17 00:00:00 2001 From: quinkq Date: Fri, 26 Dec 2025 16:38:28 +0100 Subject: [PATCH 1/7] bugfix: fix CMakeLists.txt dependency check Only handled ESP-IDF 5.x correctly, but failed for ESP-IDF 4.x because, IDF_VERSION_MAJOR STREQUAL 5 is FALSE for version 4.x, so it fell through to else and could try to use modern driver components --- CMakeLists.txt | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a7ee126..2ed02a5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,4 @@ # ESP-IDF CMake component for i2cdev library -if(${IDF_VERSION_MAJOR} STREQUAL 5 AND ${IDF_VERSION_MINOR} LESS 3) - # Use driver component as esp_driver_gpio is not available before 5.3 - set(req driver freertos log esp_timer) -else() - set(req esp_driver_gpio esp_driver_i2c freertos esp_idf_lib_helpers) -endif() # ESP-IDF version detection for automatic driver selection # Check for manual override via Kconfig @@ -26,6 +20,13 @@ else() message(STATUS "i2cdev: ESP-IDF v${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR} detected, using new i2c_master driver") endif() +# Conditionally set dependencies based on driver version +if(USE_LEGACY_DRIVER) + set(req driver freertos esp_idf_lib_helpers) +else() + set(req esp_driver_gpio esp_driver_i2c freertos esp_idf_lib_helpers) +endif() + # Conditionally set the source file based on version detection or Kconfig override if(USE_LEGACY_DRIVER) set(SRCS "i2cdev_legacy.c") From 4ad38bcd891968c08f48cf489053051762c80f20 Mon Sep 17 00:00:00 2001 From: quinkq Date: Fri, 26 Dec 2025 16:50:15 +0100 Subject: [PATCH 2/7] feat: add shared i2c bus handle https://github.com/esp-idf-lib/core/discussions/53 --- i2cdev.c | 36 ++++++++++++++++++++++++++++++++++ i2cdev.h | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) diff --git a/i2cdev.c b/i2cdev.c index b690b17..48f3824 100644 --- a/i2cdev.c +++ b/i2cdev.c @@ -895,3 +895,39 @@ esp_err_t i2cdev_done(void) ESP_LOGV(TAG, "I2C subsystem cleanup finished with result: %d", result); return result; } + +esp_err_t i2cdev_get_shared_handle(i2c_port_t port, void **bus_handle) +{ + if (port >= I2C_NUM_MAX || bus_handle == NULL) + { + ESP_LOGE(TAG, "Invalid arguments: port=%d, bus_handle=%p", port, bus_handle); + return ESP_ERR_INVALID_ARG; + } + + i2c_port_state_t *port_state = &i2c_ports[port]; + + // Take port mutex for thread-safe access + if (xSemaphoreTake(port_state->lock, pdMS_TO_TICKS(CONFIG_I2CDEV_TIMEOUT)) != pdTRUE) + { + ESP_LOGE(TAG, "[Port %d] Could not take port mutex for get_shared_handle", port); + return ESP_ERR_TIMEOUT; + } + + // Check if bus is initialized + if (!port_state->installed || port_state->bus_handle == NULL) + { + xSemaphoreGive(port_state->lock); + ESP_LOGE(TAG, "[Port %d] Bus not initialized - create an i2c_dev_t device first to initialize the bus", port); + return ESP_ERR_INVALID_STATE; + } + + // Return the bus handle + *bus_handle = (void *)port_state->bus_handle; + + ESP_LOGI(TAG, "[Port %d] Shared bus handle retrieved: %p (SDA=%d, SCL=%d, ref_count=%" PRIu32 ")", + port, port_state->bus_handle, port_state->sda_pin_current, + port_state->scl_pin_current, port_state->ref_count); + + xSemaphoreGive(port_state->lock); + return ESP_OK; +} diff --git a/i2cdev.h b/i2cdev.h index 4dab9ba..13e3f73 100644 --- a/i2cdev.h +++ b/i2cdev.h @@ -321,6 +321,66 @@ esp_err_t i2c_dev_read_reg(const i2c_dev_t *dev, uint8_t reg, void *data, size_t */ esp_err_t i2c_dev_write_reg(const i2c_dev_t *dev, uint8_t reg, const void *data, size_t size); +/** + * @brief Get shared I2C bus handle for external ESP-IDF components + * + * This function allows external ESP-IDF components (like esp_lcd) to access + * the i2cdev-managed I2C bus handle for shared bus operations. This enables + * thread-safe sharing of the I2C bus between i2cdev-based sensors and native + * ESP-IDF components. + * + * @note USAGE PATTERN: + * 1. Initialize i2cdev subsystem with i2cdev_init() + * 2. Create at least one i2c_dev_t device on the desired port (this initializes the bus) + * 3. Call this function to retrieve the bus handle + * 4. Pass the handle to ESP-IDF components (e.g., esp_lcd_new_panel_io_i2c) + * + * @note USAGE EXAMPLE: + * @code{c} + * // 1. Initialize i2cdev + * i2cdev_init(); + * + * // 2. Create a sensor device (initializes the bus) + * i2c_dev_t sensor = { + * .port = I2C_NUM_0, + * .addr = 0x29, // TSL2591 light sensor + * .cfg = { + * .sda_io_num = GPIO_NUM_21, + * .scl_io_num = GPIO_NUM_22, + * .master.clk_speed = 400000 + * } + * }; + * i2c_dev_create_mutex(&sensor); + * + * // 3. Get the shared bus handle + * i2c_master_bus_handle_t bus_handle; + * ESP_ERROR_CHECK(i2cdev_get_shared_handle(I2C_NUM_0, (void **)&bus_handle)); + * + * // 4. Use it with esp_lcd or other ESP-IDF components + * esp_lcd_panel_io_handle_t io_handle; + * esp_lcd_panel_io_i2c_config_t io_config = { + * .dev_addr = 0x3C, // SSD1306 display + * .control_phase_bytes = 1, + * .dc_bit_offset = 6, + * .lcd_cmd_bits = 8, + * .lcd_param_bits = 8, + * }; + * ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(bus_handle, &io_config, &io_handle)); + * + * // Both the sensor and display now share the same I2C bus with proper synchronization! + * @endcode + * + * @param port I2C port number (e.g., I2C_NUM_0) + * @param[out] bus_handle Pointer to store the bus handle + * @return ESP_OK on success + * @return ESP_ERR_INVALID_ARG if port is invalid or bus_handle is NULL + * @return ESP_ERR_INVALID_STATE if bus for this port is not initialized yet + * + * @warning The returned handle is managed by i2cdev. Do NOT call i2c_del_master_bus() + * on it directly - i2cdev will handle cleanup when the last device is removed. + */ +esp_err_t i2cdev_get_shared_handle(i2c_port_t port, void **bus_handle); + /** * @brief Take device mutex with error checking */ From 37e131249158878575631b7a0abd0f1b642198cd Mon Sep 17 00:00:00 2001 From: quinkq Date: Fri, 26 Dec 2025 17:58:35 +0100 Subject: [PATCH 3/7] fix: add defensive NULL check before mutex acquisition --- i2cdev.c | 7 +++++++ i2cdev.h | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/i2cdev.c b/i2cdev.c index 48f3824..2eae660 100644 --- a/i2cdev.c +++ b/i2cdev.c @@ -906,6 +906,13 @@ esp_err_t i2cdev_get_shared_handle(i2c_port_t port, void **bus_handle) i2c_port_state_t *port_state = &i2c_ports[port]; + // Check if i2cdev subsystem is initialized + if (port_state->lock == NULL) + { + ESP_LOGE(TAG, "[Port %d] I2C subsystem not initialized - call i2cdev_init() first", port); + return ESP_ERR_INVALID_STATE; + } + // Take port mutex for thread-safe access if (xSemaphoreTake(port_state->lock, pdMS_TO_TICKS(CONFIG_I2CDEV_TIMEOUT)) != pdTRUE) { diff --git a/i2cdev.h b/i2cdev.h index 13e3f73..40a5df1 100644 --- a/i2cdev.h +++ b/i2cdev.h @@ -374,7 +374,8 @@ esp_err_t i2c_dev_write_reg(const i2c_dev_t *dev, uint8_t reg, const void *data, * @param[out] bus_handle Pointer to store the bus handle * @return ESP_OK on success * @return ESP_ERR_INVALID_ARG if port is invalid or bus_handle is NULL - * @return ESP_ERR_INVALID_STATE if bus for this port is not initialized yet + * @return ESP_ERR_INVALID_STATE if i2cdev_init() was not called or bus for this port is not initialized yet + * @return ESP_ERR_TIMEOUT if port mutex could not be acquired * * @warning The returned handle is managed by i2cdev. Do NOT call i2c_del_master_bus() * on it directly - i2cdev will handle cleanup when the last device is removed. From 31636ccaf8ff0e63c18b02b9b4cffc3f8becf445 Mon Sep 17 00:00:00 2001 From: quinkq Date: Sat, 27 Dec 2025 16:07:27 +0100 Subject: [PATCH 4/7] fix: i2cdev bug fixes - fix NULL mutex dereference in 3 functions (i2c_setup_port, i2c_dev_delete_mutex, i2c_setup_device) - fix NULL device pointer crashes in 3 wrapper functions (i2c_dev_read_reg, i2c_dev_write_reg, i2c_dev_probe) - fix reference counting asymmetry causing potential premature bus deletion - fix i2cdev_init reinitialization leak with static guard flag - fix CMakeLists.txt ESP-IDF version check for 4.x compatibility --- i2cdev.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/i2cdev.c b/i2cdev.c index 2eae660..5c106b6 100644 --- a/i2cdev.c +++ b/i2cdev.c @@ -110,6 +110,15 @@ static void deregister_device(i2c_dev_t *dev) esp_err_t i2cdev_init(void) { ESP_LOGV(TAG, "Initializing I2C subsystem..."); + + // Guard against re-initialization to prevent resource leaks + static bool initialized = false; + if (initialized) + { + ESP_LOGW(TAG, "I2C subsystem already initialized, skipping re-initialization"); + return ESP_OK; + } + memset(active_devices, 0, sizeof(active_devices)); for (int i = 0; i < I2C_NUM_MAX; i++) { @@ -129,6 +138,8 @@ esp_err_t i2cdev_init(void) i2c_ports[i].sda_pin_current = -1; i2c_ports[i].scl_pin_current = -1; } + + initialized = true; // Mark as initialized ESP_LOGV(TAG, "I2C subsystem initialized."); return ESP_OK; } @@ -218,9 +229,16 @@ esp_err_t i2c_dev_delete_mutex(i2c_dev_t *dev) // Update port reference count if port is valid if (dev->port < I2C_NUM_MAX) { - if (xSemaphoreTake(i2c_ports[dev->port].lock, pdMS_TO_TICKS(CONFIG_I2CDEV_TIMEOUT)) == pdTRUE) + // Check if port mutex is initialized + if (!i2c_ports[dev->port].lock) + { + ESP_LOGW(TAG, "[0x%02x at %d] Port mutex not initialized, skipping ref_count update", dev->addr, dev->port); + // Continue with cleanup - just skip the mutex-protected section + } + else if (xSemaphoreTake(i2c_ports[dev->port].lock, pdMS_TO_TICKS(CONFIG_I2CDEV_TIMEOUT)) == pdTRUE) { - if (i2c_ports[dev->port].installed && i2c_ports[dev->port].ref_count > 0) + // Only decrement ref_count if THIS device was actually added to the bus + if (i2c_ports[dev->port].installed && i2c_ports[dev->port].ref_count > 0 && dev->dev_handle != NULL) { i2c_ports[dev->port].ref_count--; ESP_LOGV(TAG, "[Port %d] Decremented ref_count to %" PRIu32, dev->port, i2c_ports[dev->port].ref_count); @@ -245,6 +263,10 @@ esp_err_t i2c_dev_delete_mutex(i2c_dev_t *dev) i2c_ports[dev->port].scl_pin_current = -1; } } + else if (dev->dev_handle == NULL) + { + ESP_LOGV(TAG, "[0x%02x at %d] Device was never added to bus, skipping ref_count decrement", dev->addr, dev->port); + } xSemaphoreGive(i2c_ports[dev->port].lock); } else @@ -336,6 +358,13 @@ static esp_err_t i2c_setup_port(i2c_dev_t *dev) // dev is non-const to update de esp_err_t res = ESP_OK; i2c_port_state_t *port_state = &i2c_ports[dev->port]; + // Check if i2cdev subsystem is initialized + if (!port_state->lock) + { + ESP_LOGE(TAG, "[Port %d] I2C subsystem not initialized - call i2cdev_init() first", dev->port); + return ESP_ERR_INVALID_STATE; + } + ESP_LOGV(TAG, "[Port %d] Setup request for device 0x%02x", dev->port, dev->addr); if (xSemaphoreTake(port_state->lock, pdMS_TO_TICKS(CONFIG_I2CDEV_TIMEOUT)) != pdTRUE) { @@ -498,6 +527,14 @@ static esp_err_t i2c_setup_device(i2c_dev_t *dev) // dev is non-const - modifies if (dev->dev_handle == NULL) { i2c_port_state_t *port_state = &i2c_ports[dev->port]; + + // Check if i2cdev subsystem is initialized + if (!port_state->lock) + { + ESP_LOGE(TAG, "[0x%02x at %d] I2C subsystem not initialized - call i2cdev_init() first", dev->addr, dev->port); + return ESP_ERR_INVALID_STATE; + } + if (xSemaphoreTake(port_state->lock, pdMS_TO_TICKS(CONFIG_I2CDEV_TIMEOUT)) != pdTRUE) { ESP_LOGE(TAG, "[0x%02x at %d] Could not take port mutex for device add", dev->addr, dev->port); @@ -750,12 +787,18 @@ esp_err_t i2c_dev_write(const i2c_dev_t *dev, const void *out_reg, size_t out_re esp_err_t i2c_dev_read_reg(const i2c_dev_t *dev, uint8_t reg, void *data, size_t size) { + if (!dev) + return ESP_ERR_INVALID_ARG; + ESP_LOGV(TAG, "[0x%02x at %d] i2c_dev_read_reg called (reg: 0x%02x, size: %u)", dev->addr, dev->port, reg, size); return i2c_dev_read(dev, ®, 1, data, size); } esp_err_t i2c_dev_write_reg(const i2c_dev_t *dev, uint8_t reg, const void *data, size_t size) { + if (!dev) + return ESP_ERR_INVALID_ARG; + ESP_LOGV(TAG, "[0x%02x at %d] i2c_dev_write_reg called (reg: 0x%02x, size: %u)", dev->addr, dev->port, reg, size); return i2c_dev_write(dev, ®, 1, data, size); } @@ -824,6 +867,9 @@ esp_err_t i2c_dev_check_present(const i2c_dev_t *dev_const) // The new driver implementation uses i2c_master_probe which doesn't need operation_type esp_err_t i2c_dev_probe(const i2c_dev_t *dev, i2c_dev_type_t operation_type) { + if (!dev) + return ESP_ERR_INVALID_ARG; + ESP_LOGV(TAG, "[0x%02x at %d] Legacy probe called (operation_type %d), redirecting to new implementation", dev->addr, dev->port, operation_type); return i2c_dev_check_present(dev); From 652f6cc6c0ad83b95f88d38278af68abb18104e2 Mon Sep 17 00:00:00 2001 From: quinkq Date: Sat, 27 Dec 2025 17:04:52 +0100 Subject: [PATCH 5/7] fix: add missing "log" reqs in CMakeLists --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2ed02a5..e460a5c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,9 +22,9 @@ endif() # Conditionally set dependencies based on driver version if(USE_LEGACY_DRIVER) - set(req driver freertos esp_idf_lib_helpers) + set(req driver freertos log esp_idf_lib_helpers) else() - set(req esp_driver_gpio esp_driver_i2c freertos esp_idf_lib_helpers) + set(req esp_driver_gpio esp_driver_i2c freertos log esp_idf_lib_helpers) endif() # Conditionally set the source file based on version detection or Kconfig override From 58744c1ad1a5126c0d8e189c4226a564d649dd43 Mon Sep 17 00:00:00 2001 From: quinkq Date: Sat, 27 Dec 2025 19:07:10 +0100 Subject: [PATCH 6/7] feat: Migrate to eil-cmake-utils submodule for CI validation - Remove legacy 'common' submodule - Add eil-cmake-utils submodule for cross-version CMake utilities - Update CMakeLists.txt to use eil_ci module - Required to pass repository structure validation tests --- .gitmodules | 6 +++--- CMakeLists.txt | 10 ++-------- common | 1 - eil-cmake-utils | 1 + i2cdev.h | 2 -- 5 files changed, 6 insertions(+), 14 deletions(-) delete mode 160000 common create mode 160000 eil-cmake-utils diff --git a/.gitmodules b/.gitmodules index a9554a2..ad39b0a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ -[submodule "common"] - path = common - url = https://github.com/esp-idf-lib/common.git +[submodule "eil-cmake-utils"] + path = eil-cmake-utils + url = https://github.com/esp-idf-lib/eil-cmake-utils.git diff --git a/CMakeLists.txt b/CMakeLists.txt index e460a5c..dc4759e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,5 @@ # ESP-IDF CMake component for i2cdev library +list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/eil-cmake-utils/cmake) # ESP-IDF version detection for automatic driver selection # Check for manual override via Kconfig @@ -41,11 +42,4 @@ idf_component_register(SRCS ${SRCS} INCLUDE_DIRS "." REQUIRES ${req}) - -# include common cmake file for components -set(ESP_IDF_LIB_CMAKE ${CMAKE_CURRENT_LIST_DIR}/common/cmake/esp-idf-lib.cmake) -if(EXISTS ${ESP_IDF_LIB_CMAKE}) - include(${ESP_IDF_LIB_CMAKE}) -else() - message(WARNING "${ESP_IDF_LIB_CMAKE} not found") -endif() +include(eil_ci) \ No newline at end of file diff --git a/common b/common deleted file mode 160000 index 58f97e3..0000000 --- a/common +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 58f97e3e8de40a3c8e3b0bb14bff176b389827ac diff --git a/eil-cmake-utils b/eil-cmake-utils new file mode 160000 index 0000000..c91055e --- /dev/null +++ b/eil-cmake-utils @@ -0,0 +1 @@ +Subproject commit c91055e05b68b6f0cdbd73969e725d0b91aa25fa diff --git a/i2cdev.h b/i2cdev.h index 40a5df1..5a56bba 100644 --- a/i2cdev.h +++ b/i2cdev.h @@ -336,7 +336,6 @@ esp_err_t i2c_dev_write_reg(const i2c_dev_t *dev, uint8_t reg, const void *data, * 4. Pass the handle to ESP-IDF components (e.g., esp_lcd_new_panel_io_i2c) * * @note USAGE EXAMPLE: - * @code{c} * // 1. Initialize i2cdev * i2cdev_init(); * @@ -368,7 +367,6 @@ esp_err_t i2c_dev_write_reg(const i2c_dev_t *dev, uint8_t reg, const void *data, * ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(bus_handle, &io_config, &io_handle)); * * // Both the sensor and display now share the same I2C bus with proper synchronization! - * @endcode * * @param port I2C port number (e.g., I2C_NUM_0) * @param[out] bus_handle Pointer to store the bus handle From f9eb2b85f7c12d2f6c8dcdabcfd4968429278e42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A0=D1=83=D1=81=D0=BB=D0=B0=D0=BD=20=D0=A3=D1=81?= Date: Mon, 26 Jan 2026 17:39:11 +0500 Subject: [PATCH 7/7] chore: new release, add new maintainer --- .eil.yml | 2 +- idf_component.yml | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.eil.yml b/.eil.yml index 0046b26..4e56f5f 100644 --- a/.eil.yml +++ b/.eil.yml @@ -1,6 +1,6 @@ name: i2cdev description: ESP-IDF I2C master thread-safe utilities -version: 2.1.0 +version: 2.1.1 groups: - common code_owners: diff --git a/idf_component.yml b/idf_component.yml index c180d17..8735235 100644 --- a/idf_component.yml +++ b/idf_component.yml @@ -1,5 +1,5 @@ --- -version: 2.1.0 +version: 2.1.1 description: ESP-IDF I2C master thread-safe utilities license: MIT targets: @@ -18,6 +18,7 @@ dependencies: version: "*" maintainers: - Ruslan V. Uss (@UncleRus) + - Piotr P. (@quinkq) url: https://github.com/esp-idf-lib/core repository: https://github.com/esp-idf-lib/i2cdev documentation: https://esp-idf-lib.github.io/i2cdev/