mirror of
https://github.com/alexandrebobkov/ESP-Nodes.git
synced 2025-10-26 03:07:51 +00:00
382 lines
20 KiB
C
382 lines
20 KiB
C
/**
|
|
* @file i2cdev.h
|
|
* @defgroup i2cdev i2cdev
|
|
* @{
|
|
*
|
|
* ESP-IDF I2C master thread-safe functions for communication with I2C slave
|
|
*
|
|
* This implementation uses the newer ESP-IDF I2C master driver (v5.0+).
|
|
* For ESP-IDF versions using the legacy I2C driver, use i2cdev_legacy.c instead.
|
|
*
|
|
* Copyright (C) 2018 Ruslan V. Uss <unclerus@gmail.com>
|
|
* Updated 2025 by quinkq to use newer ESP-IDF I2C master driver API
|
|
*
|
|
* MIT Licensed as described in the file LICENSE
|
|
*
|
|
* ============================================================================
|
|
* OPTIONAL I2C PULLUP AUTO-CONFIGURATION
|
|
* ============================================================================
|
|
*
|
|
* This library can optionally enable internal I2C pullups when no explicit
|
|
* pullup configuration is provided. Feature is DISABLED by default for
|
|
* backward compatibility (CONFIG_I2CDEV_AUTO_ENABLE_PULLUPS=n).
|
|
*
|
|
* Optional auto-pullup (CONFIG_I2CDEV_AUTO_ENABLE_PULLUPS=y):
|
|
* - If both pullup flags are false (not set/default state), automatically enables internal pullups
|
|
* - Only available on ESP32 family (modern driver)
|
|
* - Legacy driver always uses explicit configuration
|
|
*
|
|
*
|
|
* Example - Enable internal pullups:
|
|
* i2c_dev_t sensor = {
|
|
* .port = I2C_NUM_0,
|
|
* .addr = 0x48,
|
|
* .cfg = {
|
|
* .sda_io_num = GPIO_NUM_21,
|
|
* .scl_io_num = GPIO_NUM_22,
|
|
* .sda_pullup_en = true, // Enable internal pullups
|
|
* .scl_pullup_en = true, // Enable internal pullups
|
|
* .master.clk_speed = 400000
|
|
* }
|
|
* };
|
|
*
|
|
* ============================================================================
|
|
*/
|
|
|
|
#ifndef __I2CDEV_H__
|
|
#define __I2CDEV_H__
|
|
|
|
#include <driver/gpio.h>
|
|
#include <driver/i2c.h>
|
|
#include <esp_err.h>
|
|
#include <esp_idf_lib_helpers.h>
|
|
#include <freertos/FreeRTOS.h>
|
|
#include <freertos/semphr.h>
|
|
|
|
// Define missing types for older ESP-IDF versions
|
|
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 2, 0)
|
|
typedef enum
|
|
{
|
|
I2C_ADDR_BIT_LEN_7 = 0, /*!< I2C 7bit address for slave mode */
|
|
I2C_ADDR_BIT_LEN_10, /*!< I2C 10bit address for slave mode */
|
|
} i2c_addr_bit_len_t;
|
|
#endif
|
|
|
|
// Definition for I2CDEV_MAX_STRETCH_TIME
|
|
#if HELPER_TARGET_IS_ESP8266
|
|
#define I2CDEV_MAX_STRETCH_TIME 0xffffffff
|
|
#else
|
|
#include <soc/i2c_reg.h> // For I2C_TIME_OUT_VALUE_V, etc.
|
|
#if defined(I2C_TIME_OUT_VALUE_V)
|
|
#define I2CDEV_MAX_STRETCH_TIME I2C_TIME_OUT_VALUE_V
|
|
#elif defined(I2C_TIME_OUT_REG_V)
|
|
#define I2CDEV_MAX_STRETCH_TIME I2C_TIME_OUT_REG_V
|
|
#else
|
|
#define I2CDEV_MAX_STRETCH_TIME 0x00ffffff
|
|
#endif
|
|
#endif /* HELPER_TARGET_IS_ESP8266 */
|
|
|
|
#ifndef CONFIG_I2CDEV_TIMEOUT
|
|
#define CONFIG_I2CDEV_TIMEOUT 1000 // Default 1 second timeout
|
|
#endif
|
|
|
|
#ifndef CONFIG_I2CDEV_NOLOCK
|
|
#define CONFIG_I2CDEV_NOLOCK 0 // Enable locking by default
|
|
#endif
|
|
|
|
#ifndef CONFIG_I2CDEV_MAX_DEVICES_PER_PORT
|
|
#define CONFIG_I2CDEV_MAX_DEVICES_PER_PORT 8 // Maximum devices per I2C port
|
|
#endif
|
|
|
|
#ifndef CONFIG_I2CDEV_DEFAULT_SDA_PIN
|
|
#define CONFIG_I2CDEV_DEFAULT_SDA_PIN 21 // Default SDA pin
|
|
#endif
|
|
|
|
#ifndef CONFIG_I2CDEV_DEFAULT_SCL_PIN
|
|
#define CONFIG_I2CDEV_DEFAULT_SCL_PIN 22 // Default SCL pin
|
|
#endif
|
|
|
|
#ifndef CONFIG_FREERTOS_HZ
|
|
#define CONFIG_FREERTOS_HZ 100 // Default value in most ESP-IDF configs
|
|
#endif
|
|
|
|
#ifndef CONFIG_LOG_MAXIMUM_LEVEL
|
|
#define CONFIG_LOG_MAXIMUM_LEVEL 3 // INFO level as default
|
|
#endif
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
/**
|
|
* @brief I2C transaction type for legacy probe
|
|
*/
|
|
typedef enum
|
|
{
|
|
I2C_DEV_WRITE = 0, /**< Write operation for probe */
|
|
I2C_DEV_READ /**< Read operation for probe */
|
|
} i2c_dev_type_t;
|
|
|
|
/**
|
|
* I2C device descriptor
|
|
*
|
|
* This structure supports both legacy ESP-IDF I2C driver and modern i2c_master driver.
|
|
*
|
|
* @note INITIALIZATION CHECKLIST - Set these fields before calling i2c_dev_create_mutex():
|
|
*
|
|
* ┌─── REQUIRED (Set by user) ───────────────────────────────────────────┐
|
|
* │ - dev->port - I2C port number (e.g., I2C_NUM_0) │
|
|
* │ - dev->addr - Device I2C address (e.g., 0x48) │
|
|
* │ - dev->cfg.sda_io_num - SDA pin (-1 = use Kconfig default) │
|
|
* │ - dev->cfg.scl_io_num - SCL pin (-1 = use Kconfig default) │
|
|
* │ - dev->cfg.master.clk_speed - Clock speed in Hz (e.g., 400000) │
|
|
* └──────────────────────────────────────────────────────────────────────┘
|
|
*
|
|
* ┌─── OPTIONAL (Set by user if needed) ─────────────────────────────────┐
|
|
* │ - dev->addr_bit_len - Address format (defaults to 7-bit) - NEW │
|
|
* │ - 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) │
|
|
* └──────────────────────────────────────────────────────────────────────┘
|
|
*
|
|
* ┌─── AUTO-POPULATED (library fills these) ─────────────────────────────┐
|
|
* │ - dev->mutex - Device mutex handle │
|
|
* │ - dev->dev_handle - I2C device handle (modern driver) - NEW │
|
|
* │ - dev->sda_pin - Actual SDA pin used by bus │
|
|
* │ - dev->scl_pin - Actual SCL pin used by bus │
|
|
* └──────────────────────────────────────────────────────────────────────┘
|
|
*
|
|
* @note BACKWARD COMPATIBILITY DESIGN:
|
|
* The custom 'cfg' structure mimics ESP-IDF's deprecated i2c_config_t layout
|
|
* to maintain zero-change compatibility with existing device drivers.
|
|
* ESP-IDF ≥5.2 deprecated i2c_config_t and split it into separate bus/device
|
|
* configs, but this library preserves the familiar field paths like:
|
|
* dev->cfg.sda_io_num, dev->cfg.scl_io_num, dev->cfg.master.clk_speed
|
|
*/
|
|
typedef struct
|
|
{
|
|
// ═══ Core Device Identity (REQUIRED) ═══
|
|
i2c_port_t port; //!< I2C port number (e.g., I2C_NUM_0)
|
|
uint16_t addr; //!< Device I2C address (e.g., 0x48 for 7-bit)
|
|
i2c_addr_bit_len_t addr_bit_len; //!< Address format: I2C_ADDR_BIT_LEN_7 (default) or I2C_ADDR_BIT_LEN_10
|
|
|
|
// ═══ Library Internal State (AUTO-POPULATED) ═══
|
|
SemaphoreHandle_t mutex; //!< Device mutex - Created by i2c_dev_create_mutex()
|
|
void *dev_handle; //!< Device handle - Modern driver only, created lazily (when actual I2C operation is performed)
|
|
int sda_pin; //!< Actual SDA pin used - Populated after port setup
|
|
int scl_pin; //!< Actual SCL pin used - Populated after port setup
|
|
|
|
// ═══ Legacy Driver Compatibility ═══
|
|
uint32_t timeout_ticks; //!< Clock stretching timeout - Legacy driver only
|
|
|
|
// ═══ User Configuration (REQUIRED) ═══
|
|
// Configuration structure with i2c_config_t compatible field layout.
|
|
struct
|
|
{
|
|
gpio_num_t sda_io_num; //!< Desired SDA pin (-1 = use Kconfig default)
|
|
gpio_num_t scl_io_num; //!< Desired SCL pin (-1 = use Kconfig default)
|
|
uint8_t sda_pullup_en; //!< Enable internal SDA pullup (optional)
|
|
uint8_t scl_pullup_en; //!< Enable internal SCL pullup (optional)
|
|
uint32_t clk_flags; //!< Bitwise of ``I2C_SCLK_SRC_FLAG_**FOR_DFS**`` for clk source choice
|
|
struct
|
|
{
|
|
uint32_t clk_speed; //!< Clock speed in Hz
|
|
} master; //!< Master-specific config (mimics old i2c_config_t.master)
|
|
} cfg; //!< Configuration set by device drivers (i2c_config_t compatible layout)
|
|
} i2c_dev_t;
|
|
|
|
/**
|
|
* @brief Initialize I2C subsystem (port mutexes and internal states)
|
|
*
|
|
* @note This should be called once at the beginning of your application
|
|
* before any I2C devices are initialized.
|
|
*
|
|
* @return ESP_OK on success
|
|
*/
|
|
esp_err_t i2cdev_init(void);
|
|
|
|
/**
|
|
* @brief Release I2C subsystem (deletes all devices, buses, and mutexes)
|
|
*
|
|
* @note Call this when no more I2C operations will be performed
|
|
* to clean up resources.
|
|
*
|
|
* @return ESP_OK on success
|
|
*/
|
|
esp_err_t i2cdev_done(void);
|
|
|
|
/**
|
|
* @brief Create mutex for device descriptor and register device
|
|
*
|
|
* @note IMPORTANT: Before calling this function, you must properly initialize the i2c_dev_t
|
|
* structure with device address, port, and pin settings. For ESP-IDF legacy driver,
|
|
* set pins in dev->cfg.sda_io_num and dev->cfg.scl_io_num. For newer ESP-IDF version,
|
|
* either method is compatible. See the structure documentation for details.
|
|
*
|
|
* @param dev Pointer to device descriptor
|
|
* @return `ESP_OK` on success
|
|
*/
|
|
esp_err_t i2c_dev_create_mutex(i2c_dev_t *dev);
|
|
|
|
/**
|
|
* @brief Delete mutex for device descriptor and perform device cleanup
|
|
*
|
|
* @note This function performs cleanup tasks including removing the device from the
|
|
* I2C bus, deregistering it, and deleting its mutex.
|
|
*
|
|
* @param dev Pointer to device descriptor
|
|
* @return `ESP_OK` on success
|
|
*/
|
|
esp_err_t i2c_dev_delete_mutex(i2c_dev_t *dev);
|
|
|
|
/**
|
|
* @brief Take device mutex
|
|
*
|
|
* @param dev Pointer to device descriptor
|
|
* @return `ESP_OK` on success
|
|
*/
|
|
esp_err_t i2c_dev_take_mutex(i2c_dev_t *dev);
|
|
|
|
/**
|
|
* @brief Give device mutex
|
|
*
|
|
* @param dev Pointer to device descriptor
|
|
* @return `ESP_OK` on success
|
|
*/
|
|
esp_err_t i2c_dev_give_mutex(i2c_dev_t *dev);
|
|
|
|
/**
|
|
* @brief Check the availability of a device on the I2C bus (New Driver) - legacy's i2c_dev_probe function equivalent.
|
|
*
|
|
* This function attempts to communicate with the I2C device to see if it ACKs.
|
|
* It is non-intrusive; if the device is found, any temporary setup for
|
|
* the check is torn down. Uses the new I2C driver logic.
|
|
*
|
|
* @param dev Pointer to the device descriptor. Pins and address must be configured.
|
|
* @return `ESP_OK` if the device ACKs (is present), an error code otherwise.
|
|
*/
|
|
esp_err_t i2c_dev_check_present(const i2c_dev_t *dev);
|
|
|
|
/**
|
|
* @brief Check the availability of a device on the I2C bus (Legacy Driver).
|
|
*
|
|
* Issue an operation of `operation_type` to the I2C device then stops.
|
|
* Primarily for use with the legacy i2cdev_legacy.c implementation.
|
|
*
|
|
* @param dev Device descriptor.
|
|
* @param operation_type Operation type (I2C_DEV_WRITE or I2C_DEV_READ).
|
|
* @return `ESP_OK` if device is available for the specified operation type.
|
|
*/
|
|
esp_err_t i2c_dev_probe(const i2c_dev_t *dev, i2c_dev_type_t operation_type);
|
|
|
|
/**
|
|
* @brief Read from device
|
|
*
|
|
* @param dev Pointer to device descriptor
|
|
* @param[in] out_data Data to write before reading (can be NULL if out_size is 0)
|
|
* @param out_size Size of data to write
|
|
* @param[out] in_data Buffer to store data read
|
|
* @param in_size Number of bytes to read
|
|
* @return `ESP_OK` on success
|
|
*/
|
|
esp_err_t i2c_dev_read(const i2c_dev_t *dev, const void *out_data, size_t out_size, void *in_data, size_t in_size);
|
|
|
|
/**
|
|
* @brief Write to device
|
|
*
|
|
* @param dev Pointer to device descriptor
|
|
* @param[in] out_reg Register address to write to (can be NULL if out_reg_size is 0)
|
|
* @param out_reg_size Size of register address
|
|
* @param[in] out_data Data to write (can be NULL if out_size is 0)
|
|
* @param out_size Size of data to write
|
|
* @return `ESP_OK` on success
|
|
*/
|
|
esp_err_t i2c_dev_write(const i2c_dev_t *dev, const void *out_reg, size_t out_reg_size, const void *out_data, size_t out_size);
|
|
|
|
/**
|
|
* @brief Read from device register (8-bit register address)
|
|
*
|
|
* @param dev Pointer to device descriptor
|
|
* @param reg Command to write before reading
|
|
* @param[out] data Buffer to store data
|
|
* @param size Number of bytes to read
|
|
* @return `ESP_OK` on success
|
|
*/
|
|
esp_err_t i2c_dev_read_reg(const i2c_dev_t *dev, uint8_t reg, void *data, size_t size);
|
|
|
|
/**
|
|
* @brief Write to device register (8-bit register address)
|
|
*
|
|
* @param dev Pointer to device descriptor
|
|
* @param reg Command to write before writing data
|
|
* @param data Buffer with data to write
|
|
* @param size Number of bytes to write
|
|
* @return `ESP_OK` on success
|
|
*/
|
|
esp_err_t i2c_dev_write_reg(const i2c_dev_t *dev, uint8_t reg, const void *data, size_t size);
|
|
|
|
/**
|
|
* @brief Take device mutex with error checking
|
|
*/
|
|
#define I2C_DEV_TAKE_MUTEX(dev) \
|
|
do \
|
|
{ \
|
|
esp_err_t __ = i2c_dev_take_mutex(dev); \
|
|
if (__ != ESP_OK) \
|
|
return __; \
|
|
} \
|
|
while (0)
|
|
|
|
/**
|
|
* @brief Give device mutex with error checking
|
|
*/
|
|
#define I2C_DEV_GIVE_MUTEX(dev) \
|
|
do \
|
|
{ \
|
|
esp_err_t __ = i2c_dev_give_mutex(dev); \
|
|
if (__ != ESP_OK) \
|
|
return __; \
|
|
} \
|
|
while (0)
|
|
|
|
/**
|
|
* @brief Execute operation, assuming mutex is held. Gives mutex ONLY on error.
|
|
*/
|
|
#define I2C_DEV_CHECK(dev, X) \
|
|
do \
|
|
{ \
|
|
esp_err_t ___ = X; /* Execute operation */ \
|
|
if (___ != ESP_OK) \
|
|
{ \
|
|
/* Give mutex ONLY if error occurred */ \
|
|
i2c_dev_give_mutex(dev); \
|
|
return ___; \
|
|
} \
|
|
} \
|
|
while (0)
|
|
|
|
/**
|
|
* @brief Execute operation, assuming mutex is held. Gives mutex ONLY on error, logs error.
|
|
*/
|
|
#define I2C_DEV_CHECK_LOGE(dev, X, msg, ...) \
|
|
do \
|
|
{ \
|
|
esp_err_t ___ = X; /* Execute operation */ \
|
|
if (___ != ESP_OK) \
|
|
{ \
|
|
/* Give mutex ONLY if error occurred */ \
|
|
i2c_dev_give_mutex(dev); \
|
|
ESP_LOGE(TAG, msg, ##__VA_ARGS__); \
|
|
return ___; \
|
|
} \
|
|
} \
|
|
while (0)
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
/**@}*/
|
|
|
|
#endif /* __I2CDEV_H__ */
|