Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -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
1 change: 0 additions & 1 deletion common
Submodule common deleted from 58f97e
1 change: 1 addition & 0 deletions eil-cmake-utils
Submodule eil-cmake-utils added at c91055
68 changes: 67 additions & 1 deletion i2cdev.c
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,65 @@ esp_err_t i2c_dev_give_mutex(i2c_dev_t *dev)
return ESP_OK;
}

esp_err_t i2c_dev_invalidate_handle(i2c_dev_t *dev)
{
if (!dev)
return ESP_ERR_INVALID_ARG;

ESP_LOGV(TAG, "[0x%02x at %d] Invalidating device handle for reconfiguration", dev->addr, dev->port);

// Check if port is valid and initialized
if (dev->port >= I2C_NUM_MAX)
{
ESP_LOGE(TAG, "[0x%02x at %d] Invalid port number", dev->addr, dev->port);
return ESP_ERR_INVALID_ARG;
}

i2c_port_state_t *port_state = &i2c_ports[dev->port];

// Check if port mutex exists
if (!port_state->lock)
{
ESP_LOGE(TAG, "[0x%02x at %d] Port not initialized - call i2cdev_init() first", dev->addr, dev->port);
return ESP_ERR_INVALID_STATE;
}

// Take port mutex for thread-safe handle removal
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 handle invalidation", dev->addr, dev->port);
return ESP_ERR_TIMEOUT;
}

esp_err_t result = ESP_OK;

// Remove device handle if it exists
if (dev->dev_handle)
{
ESP_LOGV(TAG, "[0x%02x at %d] Removing device handle %p", dev->addr, dev->port, dev->dev_handle);

esp_err_t rm_res = i2c_master_bus_rm_device((i2c_master_dev_handle_t)dev->dev_handle);
if (rm_res != ESP_OK)
{
ESP_LOGW(TAG, "[0x%02x at %d] Failed to remove device handle: %s",
dev->addr, dev->port, esp_err_to_name(rm_res));
result = rm_res;
}

// Always NULL the handle to force recreation on next operation
dev->dev_handle = NULL;
ESP_LOGV(TAG, "[0x%02x at %d] Device handle invalidated, will be recreated on next I2C operation",
dev->addr, dev->port);
}
else
{
ESP_LOGV(TAG, "[0x%02x at %d] No device handle to invalidate", dev->addr, dev->port);
}

xSemaphoreGive(port_state->lock);
return result;
}

// i2c_setup_port: Initializes the I2C master bus for a given port if not already done.
// It uses pin configurations from dev->cfg.sda_io_num and dev->cfg.scl_io_num.
// The pins for a port are fixed after the first device initializes it.
Expand Down Expand Up @@ -529,14 +588,21 @@ static esp_err_t i2c_setup_device(i2c_dev_t *dev) // dev is non-const - modifies
.dev_addr_length = dev->addr_bit_len,
.device_address = dev->addr,
.scl_speed_hz = effective_dev_speed,
.flags.disable_ack_check = false,
.flags.disable_ack_check = dev->disable_ack_check,
};

res = i2c_master_bus_add_device(port_state->bus_handle, &dev_config, (i2c_master_dev_handle_t *)&dev->dev_handle);
if (res == ESP_OK)
{
ESP_LOGI(TAG, "[0x%02x at %d] Device added successfully (Device Handle: %p, Speed: %" PRIu32 " Hz).", dev->addr, dev->port, dev->dev_handle, effective_dev_speed);

// Log if ACK checking is disabled (non-standard configuration)
if (dev->disable_ack_check)
{
ESP_LOGW(TAG, "[0x%02x at %d] WARNING: ACK checking DISABLED - communication errors won't be detected",
dev->addr, dev->port);
}

// Increment the port reference count for each device successfully added
port_state->ref_count++;
ESP_LOGV(TAG, "[Port %d] Incremented ref_count to %" PRIu32, dev->port, port_state->ref_count);
Expand Down
33 changes: 33 additions & 0 deletions i2cdev.h
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ typedef enum
* │ - dev->cfg.sda_pullup_en - Enable internal SDA pullup │
* │ - dev->cfg.scl_pullup_en - Enable internal SCL pullup │
* │ - dev->timeout_ticks - Legacy driver timeout (legacy only) │
* │ - dev->disable_ack_check - Disable ACK checking (false=default) │
* │ WARNING: Changing after first transaction requires handle │
* │ invalidation via i2c_dev_invalidate_handle() │
* └──────────────────────────────────────────────────────────────────────┘
*
* ┌─── AUTO-POPULATED (library fills these) ─────────────────────────────┐
Expand Down Expand Up @@ -175,6 +178,11 @@ typedef struct
// ═══ Legacy Driver Compatibility ═══
uint32_t timeout_ticks; //!< Clock stretching timeout - Legacy driver only

// ═══ Device Behavior Configuration (OPTIONAL) ═══
bool disable_ack_check; //!< Disable ACK checking (false=enabled [default], true=disabled)
//!< WARNING: PERMANENT after first I2C transaction unless handle invalidated
//!< Use i2c_dev_invalidate_handle() to force reconfiguration

// ═══ User Configuration (REQUIRED) ═══
// Configuration structure with i2c_config_t compatible field layout.
struct
Expand Down Expand Up @@ -251,6 +259,31 @@ esp_err_t i2c_dev_take_mutex(i2c_dev_t *dev);
*/
esp_err_t i2c_dev_give_mutex(i2c_dev_t *dev);

/**
* @brief Invalidate device handle to force recreation on next I2C operation
*
* Use this when you need to change device configuration (like disable_ack_check)
* after the device has already been used. The handle will be recreated with the
* new config on the next I2C transaction.
*
* This is needed because ESP-IDF caches device handles for performance, so just
* changing the config field won't do anything unless you invalidate the handle first.
*
* @note Thread-safe (uses port mutex), but don't call during active I2C operations
*
* Example (SCD4x wake-up that doesn't ACK):
* dev->disable_ack_check = true;
* i2c_dev_invalidate_handle(dev);
* i2c_dev_write(...); // No ACK required
* dev->disable_ack_check = false;
* i2c_dev_invalidate_handle(dev);
*
* @param dev Device descriptor
* @return ESP_OK on success, ESP_ERR_INVALID_ARG if dev is NULL,
* ESP_ERR_TIMEOUT if mutex couldn't be acquired
*/
esp_err_t i2c_dev_invalidate_handle(i2c_dev_t *dev);

/**
* @brief Check the availability of a device on the I2C bus (New Driver) - legacy's i2c_dev_probe function equivalent.
*
Expand Down