Skip to content

Commit 816b9cd

Browse files
authored
Merge pull request #8 from quinkq/fix-i2cdev-critical-bugs
Fix i2cdev critical bugs
2 parents bae73f9 + f9eb2b8 commit 816b9cd

5 files changed

Lines changed: 154 additions & 5 deletions

File tree

.eil.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: i2cdev
22
description: ESP-IDF I2C master thread-safe utilities
3-
version: 2.1.0
3+
version: 2.1.1
44
groups:
55
- common
66
code_owners:

.gitmodules

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
[submodule "eil-cmake-utils"]
22
path = eil-cmake-utils
3-
url = git@github.com:esp-idf-lib/eil-cmake-utils.git
3+
url = https://github.com/esp-idf-lib/eil-cmake-utils.git

i2cdev.c

Lines changed: 91 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,15 @@ static void deregister_device(i2c_dev_t *dev)
110110
esp_err_t i2cdev_init(void)
111111
{
112112
ESP_LOGV(TAG, "Initializing I2C subsystem...");
113+
114+
// Guard against re-initialization to prevent resource leaks
115+
static bool initialized = false;
116+
if (initialized)
117+
{
118+
ESP_LOGW(TAG, "I2C subsystem already initialized, skipping re-initialization");
119+
return ESP_OK;
120+
}
121+
113122
memset(active_devices, 0, sizeof(active_devices));
114123
for (int i = 0; i < I2C_NUM_MAX; i++)
115124
{
@@ -129,6 +138,8 @@ esp_err_t i2cdev_init(void)
129138
i2c_ports[i].sda_pin_current = -1;
130139
i2c_ports[i].scl_pin_current = -1;
131140
}
141+
142+
initialized = true; // Mark as initialized
132143
ESP_LOGV(TAG, "I2C subsystem initialized.");
133144
return ESP_OK;
134145
}
@@ -218,9 +229,16 @@ esp_err_t i2c_dev_delete_mutex(i2c_dev_t *dev)
218229
// Update port reference count if port is valid
219230
if (dev->port < I2C_NUM_MAX)
220231
{
221-
if (xSemaphoreTake(i2c_ports[dev->port].lock, pdMS_TO_TICKS(CONFIG_I2CDEV_TIMEOUT)) == pdTRUE)
232+
// Check if port mutex is initialized
233+
if (!i2c_ports[dev->port].lock)
234+
{
235+
ESP_LOGW(TAG, "[0x%02x at %d] Port mutex not initialized, skipping ref_count update", dev->addr, dev->port);
236+
// Continue with cleanup - just skip the mutex-protected section
237+
}
238+
else if (xSemaphoreTake(i2c_ports[dev->port].lock, pdMS_TO_TICKS(CONFIG_I2CDEV_TIMEOUT)) == pdTRUE)
222239
{
223-
if (i2c_ports[dev->port].installed && i2c_ports[dev->port].ref_count > 0)
240+
// Only decrement ref_count if THIS device was actually added to the bus
241+
if (i2c_ports[dev->port].installed && i2c_ports[dev->port].ref_count > 0 && dev->dev_handle != NULL)
224242
{
225243
i2c_ports[dev->port].ref_count--;
226244
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)
245263
i2c_ports[dev->port].scl_pin_current = -1;
246264
}
247265
}
266+
else if (dev->dev_handle == NULL)
267+
{
268+
ESP_LOGV(TAG, "[0x%02x at %d] Device was never added to bus, skipping ref_count decrement", dev->addr, dev->port);
269+
}
248270
xSemaphoreGive(i2c_ports[dev->port].lock);
249271
}
250272
else
@@ -336,6 +358,13 @@ static esp_err_t i2c_setup_port(i2c_dev_t *dev) // dev is non-const to update de
336358
esp_err_t res = ESP_OK;
337359
i2c_port_state_t *port_state = &i2c_ports[dev->port];
338360

361+
// Check if i2cdev subsystem is initialized
362+
if (!port_state->lock)
363+
{
364+
ESP_LOGE(TAG, "[Port %d] I2C subsystem not initialized - call i2cdev_init() first", dev->port);
365+
return ESP_ERR_INVALID_STATE;
366+
}
367+
339368
ESP_LOGV(TAG, "[Port %d] Setup request for device 0x%02x", dev->port, dev->addr);
340369
if (xSemaphoreTake(port_state->lock, pdMS_TO_TICKS(CONFIG_I2CDEV_TIMEOUT)) != pdTRUE)
341370
{
@@ -498,6 +527,14 @@ static esp_err_t i2c_setup_device(i2c_dev_t *dev) // dev is non-const - modifies
498527
if (dev->dev_handle == NULL)
499528
{
500529
i2c_port_state_t *port_state = &i2c_ports[dev->port];
530+
531+
// Check if i2cdev subsystem is initialized
532+
if (!port_state->lock)
533+
{
534+
ESP_LOGE(TAG, "[0x%02x at %d] I2C subsystem not initialized - call i2cdev_init() first", dev->addr, dev->port);
535+
return ESP_ERR_INVALID_STATE;
536+
}
537+
501538
if (xSemaphoreTake(port_state->lock, pdMS_TO_TICKS(CONFIG_I2CDEV_TIMEOUT)) != pdTRUE)
502539
{
503540
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
750787

751788
esp_err_t i2c_dev_read_reg(const i2c_dev_t *dev, uint8_t reg, void *data, size_t size)
752789
{
790+
if (!dev)
791+
return ESP_ERR_INVALID_ARG;
792+
753793
ESP_LOGV(TAG, "[0x%02x at %d] i2c_dev_read_reg called (reg: 0x%02x, size: %u)", dev->addr, dev->port, reg, size);
754794
return i2c_dev_read(dev, &reg, 1, data, size);
755795
}
756796

757797
esp_err_t i2c_dev_write_reg(const i2c_dev_t *dev, uint8_t reg, const void *data, size_t size)
758798
{
799+
if (!dev)
800+
return ESP_ERR_INVALID_ARG;
801+
759802
ESP_LOGV(TAG, "[0x%02x at %d] i2c_dev_write_reg called (reg: 0x%02x, size: %u)", dev->addr, dev->port, reg, size);
760803
return i2c_dev_write(dev, &reg, 1, data, size);
761804
}
@@ -824,6 +867,9 @@ esp_err_t i2c_dev_check_present(const i2c_dev_t *dev_const)
824867
// The new driver implementation uses i2c_master_probe which doesn't need operation_type
825868
esp_err_t i2c_dev_probe(const i2c_dev_t *dev, i2c_dev_type_t operation_type)
826869
{
870+
if (!dev)
871+
return ESP_ERR_INVALID_ARG;
872+
827873
ESP_LOGV(TAG, "[0x%02x at %d] Legacy probe called (operation_type %d), redirecting to new implementation", dev->addr, dev->port, operation_type);
828874

829875
return i2c_dev_check_present(dev);
@@ -895,3 +941,46 @@ esp_err_t i2cdev_done(void)
895941
ESP_LOGV(TAG, "I2C subsystem cleanup finished with result: %d", result);
896942
return result;
897943
}
944+
945+
esp_err_t i2cdev_get_shared_handle(i2c_port_t port, void **bus_handle)
946+
{
947+
if (port >= I2C_NUM_MAX || bus_handle == NULL)
948+
{
949+
ESP_LOGE(TAG, "Invalid arguments: port=%d, bus_handle=%p", port, bus_handle);
950+
return ESP_ERR_INVALID_ARG;
951+
}
952+
953+
i2c_port_state_t *port_state = &i2c_ports[port];
954+
955+
// Check if i2cdev subsystem is initialized
956+
if (port_state->lock == NULL)
957+
{
958+
ESP_LOGE(TAG, "[Port %d] I2C subsystem not initialized - call i2cdev_init() first", port);
959+
return ESP_ERR_INVALID_STATE;
960+
}
961+
962+
// Take port mutex for thread-safe access
963+
if (xSemaphoreTake(port_state->lock, pdMS_TO_TICKS(CONFIG_I2CDEV_TIMEOUT)) != pdTRUE)
964+
{
965+
ESP_LOGE(TAG, "[Port %d] Could not take port mutex for get_shared_handle", port);
966+
return ESP_ERR_TIMEOUT;
967+
}
968+
969+
// Check if bus is initialized
970+
if (!port_state->installed || port_state->bus_handle == NULL)
971+
{
972+
xSemaphoreGive(port_state->lock);
973+
ESP_LOGE(TAG, "[Port %d] Bus not initialized - create an i2c_dev_t device first to initialize the bus", port);
974+
return ESP_ERR_INVALID_STATE;
975+
}
976+
977+
// Return the bus handle
978+
*bus_handle = (void *)port_state->bus_handle;
979+
980+
ESP_LOGI(TAG, "[Port %d] Shared bus handle retrieved: %p (SDA=%d, SCL=%d, ref_count=%" PRIu32 ")",
981+
port, port_state->bus_handle, port_state->sda_pin_current,
982+
port_state->scl_pin_current, port_state->ref_count);
983+
984+
xSemaphoreGive(port_state->lock);
985+
return ESP_OK;
986+
}

i2cdev.h

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,65 @@ esp_err_t i2c_dev_read_reg(const i2c_dev_t *dev, uint8_t reg, void *data, size_t
321321
*/
322322
esp_err_t i2c_dev_write_reg(const i2c_dev_t *dev, uint8_t reg, const void *data, size_t size);
323323

324+
/**
325+
* @brief Get shared I2C bus handle for external ESP-IDF components
326+
*
327+
* This function allows external ESP-IDF components (like esp_lcd) to access
328+
* the i2cdev-managed I2C bus handle for shared bus operations. This enables
329+
* thread-safe sharing of the I2C bus between i2cdev-based sensors and native
330+
* ESP-IDF components.
331+
*
332+
* @note USAGE PATTERN:
333+
* 1. Initialize i2cdev subsystem with i2cdev_init()
334+
* 2. Create at least one i2c_dev_t device on the desired port (this initializes the bus)
335+
* 3. Call this function to retrieve the bus handle
336+
* 4. Pass the handle to ESP-IDF components (e.g., esp_lcd_new_panel_io_i2c)
337+
*
338+
* @note USAGE EXAMPLE:
339+
* // 1. Initialize i2cdev
340+
* i2cdev_init();
341+
*
342+
* // 2. Create a sensor device (initializes the bus)
343+
* i2c_dev_t sensor = {
344+
* .port = I2C_NUM_0,
345+
* .addr = 0x29, // TSL2591 light sensor
346+
* .cfg = {
347+
* .sda_io_num = GPIO_NUM_21,
348+
* .scl_io_num = GPIO_NUM_22,
349+
* .master.clk_speed = 400000
350+
* }
351+
* };
352+
* i2c_dev_create_mutex(&sensor);
353+
*
354+
* // 3. Get the shared bus handle
355+
* i2c_master_bus_handle_t bus_handle;
356+
* ESP_ERROR_CHECK(i2cdev_get_shared_handle(I2C_NUM_0, (void **)&bus_handle));
357+
*
358+
* // 4. Use it with esp_lcd or other ESP-IDF components
359+
* esp_lcd_panel_io_handle_t io_handle;
360+
* esp_lcd_panel_io_i2c_config_t io_config = {
361+
* .dev_addr = 0x3C, // SSD1306 display
362+
* .control_phase_bytes = 1,
363+
* .dc_bit_offset = 6,
364+
* .lcd_cmd_bits = 8,
365+
* .lcd_param_bits = 8,
366+
* };
367+
* ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(bus_handle, &io_config, &io_handle));
368+
*
369+
* // Both the sensor and display now share the same I2C bus with proper synchronization!
370+
*
371+
* @param port I2C port number (e.g., I2C_NUM_0)
372+
* @param[out] bus_handle Pointer to store the bus handle
373+
* @return ESP_OK on success
374+
* @return ESP_ERR_INVALID_ARG if port is invalid or bus_handle is NULL
375+
* @return ESP_ERR_INVALID_STATE if i2cdev_init() was not called or bus for this port is not initialized yet
376+
* @return ESP_ERR_TIMEOUT if port mutex could not be acquired
377+
*
378+
* @warning The returned handle is managed by i2cdev. Do NOT call i2c_del_master_bus()
379+
* on it directly - i2cdev will handle cleanup when the last device is removed.
380+
*/
381+
esp_err_t i2cdev_get_shared_handle(i2c_port_t port, void **bus_handle);
382+
324383
/**
325384
* @brief Take device mutex with error checking
326385
*/

idf_component.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
version: 2.1.0
2+
version: 2.1.1
33
description: ESP-IDF I2C master thread-safe utilities
44
license: MIT
55
targets:
@@ -18,6 +18,7 @@ dependencies:
1818
version: "*"
1919
maintainers:
2020
- Ruslan V. Uss (@UncleRus) <unclerus@gmail.com>
21+
- Piotr P. (@quinkq)
2122
url: https://github.com/esp-idf-lib/core
2223
repository: https://github.com/esp-idf-lib/i2cdev
2324
documentation: https://esp-idf-lib.github.io/i2cdev/

0 commit comments

Comments
 (0)