From 92b68d59a290b4e1ef397d270af702822df8f682 Mon Sep 17 00:00:00 2001 From: yanke Date: Tue, 14 Jan 2025 18:57:54 +0800 Subject: [PATCH] feat(i2c_bus): support software i2c --- .gitlab/ci/rules.yml | 22 ++ components/i2c_bus/CHANGELOG.md | 6 + components/i2c_bus/CMakeLists.txt | 6 + components/i2c_bus/Kconfig | 16 + components/i2c_bus/README.md | 2 + components/i2c_bus/i2c_bus.c | 227 +++++++++---- components/i2c_bus/i2c_bus_soft.c | 304 ++++++++++++++++++ components/i2c_bus/i2c_bus_v2.c | 254 +++++++++++---- components/i2c_bus/idf_component.yml | 4 +- components/i2c_bus/include/i2c_bus.h | 23 +- .../i2c_bus/private_include/i2c_bus_soft.h | 121 +++++++ .../i2c_bus/test_apps/main/test_i2c_bus.c | 131 +++++++- docs/en/basic/bus/i2c_bus.rst | 2 +- docs/zh_CN/basic/bus/i2c_bus.rst | 1 + 14 files changed, 963 insertions(+), 156 deletions(-) create mode 100644 components/i2c_bus/i2c_bus_soft.c create mode 100644 components/i2c_bus/private_include/i2c_bus_soft.h diff --git a/.gitlab/ci/rules.yml b/.gitlab/ci/rules.yml index b9b55738a..a8cb02956 100644 --- a/.gitlab/ci/rules.yml +++ b/.gitlab/ci/rules.yml @@ -1077,6 +1077,8 @@ changes: *patterns-components_motor_esp_simplefoc - <<: *if-dev-push changes: *patterns-example_motor_foc_openloop_control + - <<: *if-dev-push + changes: *patterns-components_i2c_bus .rules:build:example_motor_foc_velocity_control: rules: @@ -2041,6 +2043,8 @@ changes: *patterns-build_system - <<: *if-dev-push changes: *patterns-components_sensors_gesture_apds9960 + - <<: *if-dev-push + changes: *patterns-components_i2c_bus .rules:build:components_sensors_humiture_hdc2010_test_apps: rules: @@ -2052,6 +2056,8 @@ changes: *patterns-build_system - <<: *if-dev-push changes: *patterns-components_sensors_humiture_hdc2010 + - <<: *if-dev-push + changes: *patterns-components_i2c_bus .rules:build:components_sensors_humiture_hts221_test: rules: @@ -2074,6 +2080,8 @@ changes: *patterns-build_system - <<: *if-dev-push changes: *patterns-components_sensors_humiture_mvh3004d + - <<: *if-dev-push + changes: *patterns-components_i2c_bus .rules:build:components_sensors_humiture_sht3x_test_apps: rules: @@ -2085,6 +2093,8 @@ changes: *patterns-build_system - <<: *if-dev-push changes: *patterns-components_sensors_humiture_sht3x + - <<: *if-dev-push + changes: *patterns-components_i2c_bus .rules:build:components_sensors_imu_lis2dh12_test_apps: rules: @@ -2096,6 +2106,8 @@ changes: *patterns-build_system - <<: *if-dev-push changes: *patterns-components_sensors_imu_lis2dh12 + - <<: *if-dev-push + changes: *patterns-components_i2c_bus .rules:build:components_sensors_imu_mpu6050_test: rules: @@ -2129,6 +2141,8 @@ changes: *patterns-build_system - <<: *if-dev-push changes: *patterns-components_sensors_light_sensor_veml6040 + - <<: *if-dev-push + changes: *patterns-components_i2c_bus .rules:build:components_sensors_light_sensor_veml6075_test_apps: rules: @@ -2140,6 +2154,8 @@ changes: *patterns-build_system - <<: *if-dev-push changes: *patterns-components_sensors_light_sensor_veml6075 + - <<: *if-dev-push + changes: *patterns-components_i2c_bus .rules:build:components_sensors_pressure_bme280_test_apps: rules: @@ -2151,6 +2167,8 @@ changes: *patterns-build_system - <<: *if-dev-push changes: *patterns-components_sensors_pressure_bme280 + - <<: *if-dev-push + changes: *patterns-components_i2c_bus .rules:build:components_sensors_sensor_hub_test_apps: rules: @@ -2162,6 +2180,8 @@ changes: *patterns-build_system - <<: *if-dev-push changes: *patterns-components_sensors_sensor_hub + - <<: *if-dev-push + changes: *patterns-components_i2c_bus .rules:build:components_spi_bus_test_apps: rules: @@ -2320,6 +2340,8 @@ changes: *patterns-components_sensors_power_monitor_ina236 - <<: *if-dev-push changes: *patterns-components_bus + - <<: *if-dev-push + changes: *patterns-components_i2c_bus .rules:build:components_i2c_bus_test_apps: rules: diff --git a/components/i2c_bus/CHANGELOG.md b/components/i2c_bus/CHANGELOG.md index 74bce7e53..8919844cd 100644 --- a/components/i2c_bus/CHANGELOG.md +++ b/components/i2c_bus/CHANGELOG.md @@ -1,5 +1,11 @@ # ChangeLog +## v1.2.0 - 2025-1-14 + +### Enhancements: + +- Support enabling software I2C to extend the number of I2C ports. + ## v1.1.0 - 2024-11-22 ### Enhancements: diff --git a/components/i2c_bus/CMakeLists.txt b/components/i2c_bus/CMakeLists.txt index b1bdcb438..acce49842 100644 --- a/components/i2c_bus/CMakeLists.txt +++ b/components/i2c_bus/CMakeLists.txt @@ -6,8 +6,14 @@ else() set(REQ esp_driver_i2c driver) endif() +if (CONFIG_I2C_BUS_SUPPORT_SOFTWARE) + list(APPEND SRC_FILE "i2c_bus_soft.c") +endif() + idf_component_register(SRCS ${SRC_FILE} INCLUDE_DIRS "include" + PRIV_INCLUDE_DIRS "private_include" REQUIRES ${REQ}) + include(package_manager) cu_pkg_define_version(${CMAKE_CURRENT_LIST_DIR}) diff --git a/components/i2c_bus/Kconfig b/components/i2c_bus/Kconfig index 2ca16b2d0..59b398bcc 100644 --- a/components/i2c_bus/Kconfig +++ b/components/i2c_bus/Kconfig @@ -25,6 +25,22 @@ menu "Bus Options" depends on ESP_IDF_VERSION >= 5.3 help Enable this option for backward compatibility with the old I2C driver + + config I2C_BUS_SUPPORT_SOFTWARE + bool "Enable software I2C support" + default n + help + Enable this option to use a software-implemented I2C driver. This can be useful for scenarios where + hardware I2C is unavailable or additional I2C buses are needed beyond the hardware support. + + config I2C_BUS_SOFTWARE_MAX_PORT + int "Maximum number of software I2C ports" + default 2 + range 1 5 + depends on I2C_BUS_SUPPORT_SOFTWARE + help + Set the maximum number of software I2C ports that can be used. This option is only applicable when + software I2C support is enabled. endmenu endmenu diff --git a/components/i2c_bus/README.md b/components/i2c_bus/README.md index 6e7c9b4ab..89b2ebfc5 100644 --- a/components/i2c_bus/README.md +++ b/components/i2c_bus/README.md @@ -6,6 +6,8 @@ The I2C bus component (Bus) is a set of application-layer code built on top of t 1. Simplified peripheral initialization processes 2. Thread-safe device operations 3. Simple and flexible RW operations +4. Compatible with `driver/i2c` and `esp_driver_i2c` +5. Supports additional software I2C This component abstracts the following concepts: diff --git a/components/i2c_bus/i2c_bus.c b/components/i2c_bus/i2c_bus.c index f49b49e6d..33d47a0e8 100644 --- a/components/i2c_bus/i2c_bus.c +++ b/components/i2c_bus/i2c_bus.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -13,9 +13,12 @@ #include "esp_log.h" #include "i2c_bus.h" +#if CONFIG_I2C_BUS_SUPPORT_SOFTWARE +#include "i2c_bus_soft.h" +#endif -#define I2C_ACK_CHECK_EN 0x1 /*!< I2C master will check ack from slave*/ -#define I2C_ACK_CHECK_DIS 0x0 /*!< I2C master will not check ack from slave */ +#define I2C_ACK_CHECK_EN 0x1 /*!< I2C master will check ack from slave*/ +#define I2C_ACK_CHECK_DIS 0x0 /*!< I2C master will not check ack from slave */ #define I2C_BUS_FLG_DEFAULT (0) #define I2C_BUS_MASTER_BUF_LEN (0) #define I2C_BUS_MS_TO_WAIT CONFIG_I2C_MS_TO_WAIT @@ -23,21 +26,29 @@ #define I2C_BUS_MUTEX_TICKS_TO_WAIT (I2C_BUS_MS_TO_WAIT/portTICK_RATE_MS) typedef struct { - i2c_port_t i2c_port; /*!mode == I2C_MODE_MASTER, "i2c_bus only supports master mode", NULL); if (s_i2c_bus[port].is_init) { /**if i2c_bus has been inited and configs not changed, return the handle directly**/ if (i2c_config_compare(port, conf)) { - ESP_LOGW(TAG, "i2c%d has been inited, return handle directly, ref_counter=%"PRIu32"", port, s_i2c_bus[port].ref_counter); + ESP_LOGW(TAG, "i2c%d has been inited, return handle directly, ref_counter=%"PRIi32"", port, s_i2c_bus[port].ref_counter); return (i2c_bus_handle_t)&s_i2c_bus[port]; } } else { @@ -111,7 +126,7 @@ esp_err_t i2c_bus_delete(i2c_bus_handle_t *p_bus) /** if ref_counter == 0, de-init the bus**/ if ((i2c_bus->ref_counter) > 0) { - ESP_LOGW(TAG, "i2c%d is also handled by others ref_counter=%"PRIu32", won't be de-inited", i2c_bus->i2c_port, i2c_bus->ref_counter); + ESP_LOGW(TAG, "i2c%d is also handled by others ref_counter=%"PRIi32", won't be de-inited", i2c_bus->i2c_port, i2c_bus->ref_counter); return ESP_OK; } @@ -128,14 +143,22 @@ uint8_t i2c_bus_scan(i2c_bus_handle_t bus_handle, uint8_t *buf, uint8_t num) i2c_bus_t *i2c_bus = (i2c_bus_t *)bus_handle; I2C_BUS_INIT_CHECK(i2c_bus->is_init, 0); uint8_t device_count = 0; + esp_err_t ret = ESP_FAIL; I2C_BUS_MUTEX_TAKE_MAX_DELAY(i2c_bus->mutex, 0); - for (uint8_t dev_address = 1; dev_address < 127; dev_address++) { - i2c_cmd_handle_t cmd = i2c_cmd_link_create(); - i2c_master_start(cmd); - i2c_master_write_byte(cmd, (dev_address << 1) | I2C_MASTER_WRITE, I2C_ACK_CHECK_EN); - i2c_master_stop(cmd); - esp_err_t ret = i2c_master_cmd_begin(i2c_bus->i2c_port, cmd, I2C_BUS_TICKS_TO_WAIT); + for (uint8_t dev_address = 1; dev_address < 127; dev_address++) { +#if CONFIG_I2C_BUS_SUPPORT_SOFTWARE + if (i2c_bus->i2c_port > I2C_NUM_MAX) { + ret = i2c_master_soft_bus_probe(i2c_bus->soft_bus_handle, dev_address); + } else +#endif + { + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, (dev_address << 1) | I2C_MASTER_WRITE, I2C_ACK_CHECK_EN); + i2c_master_stop(cmd); + ret = i2c_master_cmd_begin(i2c_bus->i2c_port, cmd, I2C_BUS_TICKS_TO_WAIT); + } if (ret == ESP_OK) { ESP_LOGI(TAG, "found i2c device address = 0x%02x", dev_address); if (buf != NULL && device_count < num) { @@ -143,8 +166,6 @@ uint8_t i2c_bus_scan(i2c_bus_handle_t bus_handle, uint8_t *buf, uint8_t num) } device_count++; } - - i2c_cmd_link_delete(cmd); } I2C_BUS_MUTEX_GIVE(i2c_bus->mutex, 0); return device_count; @@ -332,22 +353,31 @@ static esp_err_t i2c_bus_read_reg8(i2c_bus_device_handle_t dev_handle, uint8_t m I2C_BUS_CHECK(dev_handle != NULL, "device handle error", ESP_ERR_INVALID_ARG); I2C_BUS_CHECK(data != NULL, "data pointer error", ESP_ERR_INVALID_ARG); i2c_bus_device_t *i2c_device = (i2c_bus_device_t *)dev_handle; + esp_err_t ret = ESP_FAIL; I2C_BUS_INIT_CHECK(i2c_device->i2c_bus->is_init, ESP_ERR_INVALID_STATE); I2C_BUS_MUTEX_TAKE(i2c_device->i2c_bus->mutex, ESP_ERR_TIMEOUT); - i2c_cmd_handle_t cmd = i2c_cmd_link_create(); - if (mem_address != NULL_I2C_MEM_ADDR) { +#if CONFIG_I2C_BUS_SUPPORT_SOFTWARE + if (i2c_device->i2c_bus->i2c_port > I2C_NUM_MAX) { + ret = i2c_master_soft_bus_change_frequency(i2c_device->i2c_bus->soft_bus_handle, i2c_device->conf.master.clk_speed); + ret = i2c_master_soft_bus_read_reg8(i2c_device->i2c_bus->soft_bus_handle, i2c_device->dev_addr, mem_address, data_len, data); + } else +#endif + { + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + if (mem_address != NULL_I2C_MEM_ADDR) { + i2c_master_start(cmd); + i2c_master_write_byte(cmd, (i2c_device->dev_addr << 1) | I2C_MASTER_WRITE, I2C_ACK_CHECK_EN); + i2c_master_write_byte(cmd, mem_address, I2C_ACK_CHECK_EN); + } + i2c_master_start(cmd); - i2c_master_write_byte(cmd, (i2c_device->dev_addr << 1) | I2C_MASTER_WRITE, I2C_ACK_CHECK_EN); - i2c_master_write_byte(cmd, mem_address, I2C_ACK_CHECK_EN); + i2c_master_write_byte(cmd, (i2c_device->dev_addr << 1) | I2C_MASTER_READ, I2C_ACK_CHECK_EN); + i2c_master_read(cmd, data, data_len, I2C_MASTER_LAST_NACK); + i2c_master_stop(cmd); + ret = i2c_master_cmd_begin_with_conf(i2c_device->i2c_bus->i2c_port, cmd, I2C_BUS_TICKS_TO_WAIT, &i2c_device->conf); + i2c_cmd_link_delete(cmd); } - - i2c_master_start(cmd); - i2c_master_write_byte(cmd, (i2c_device->dev_addr << 1) | I2C_MASTER_READ, I2C_ACK_CHECK_EN); - i2c_master_read(cmd, data, data_len, I2C_MASTER_LAST_NACK); - i2c_master_stop(cmd); - esp_err_t ret = i2c_master_cmd_begin_with_conf(i2c_device->i2c_bus->i2c_port, cmd, I2C_BUS_TICKS_TO_WAIT, &i2c_device->conf); - i2c_cmd_link_delete(cmd); I2C_BUS_MUTEX_GIVE(i2c_device->i2c_bus->mutex, ESP_FAIL); return ret; } @@ -359,23 +389,31 @@ esp_err_t i2c_bus_read_reg16(i2c_bus_device_handle_t dev_handle, uint16_t mem_ad i2c_bus_device_t *i2c_device = (i2c_bus_device_t *)dev_handle; I2C_BUS_INIT_CHECK(i2c_device->i2c_bus->is_init, ESP_ERR_INVALID_STATE); uint8_t memAddress8[2]; + esp_err_t ret = ESP_OK; memAddress8[0] = (uint8_t)((mem_address >> 8) & 0x00FF); memAddress8[1] = (uint8_t)(mem_address & 0x00FF); I2C_BUS_MUTEX_TAKE(i2c_device->i2c_bus->mutex, ESP_ERR_TIMEOUT); - i2c_cmd_handle_t cmd = i2c_cmd_link_create(); - if (mem_address != NULL_I2C_MEM_16BIT_ADDR) { +#if CONFIG_I2C_BUS_SUPPORT_SOFTWARE + if (i2c_device->i2c_bus->i2c_port > I2C_NUM_MAX) { + ret = i2c_master_soft_bus_change_frequency(i2c_device->i2c_bus->soft_bus_handle, i2c_device->conf.master.clk_speed); + ret = i2c_master_soft_bus_read_reg16(i2c_device->i2c_bus->soft_bus_handle, i2c_device->dev_addr, mem_address, data_len, data); + } else +#endif + { + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + if (mem_address != NULL_I2C_MEM_16BIT_ADDR) { + i2c_master_start(cmd); + i2c_master_write_byte(cmd, (i2c_device->dev_addr << 1) | I2C_MASTER_WRITE, I2C_ACK_CHECK_EN); + i2c_master_write(cmd, memAddress8, 2, I2C_ACK_CHECK_EN); + } i2c_master_start(cmd); - i2c_master_write_byte(cmd, (i2c_device->dev_addr << 1) | I2C_MASTER_WRITE, I2C_ACK_CHECK_EN); - i2c_master_write(cmd, memAddress8, 2, I2C_ACK_CHECK_EN); + i2c_master_write_byte(cmd, (i2c_device->dev_addr << 1) | I2C_MASTER_READ, I2C_ACK_CHECK_EN); + i2c_master_read(cmd, data, data_len, I2C_MASTER_LAST_NACK); + i2c_master_stop(cmd); + ret = i2c_master_cmd_begin_with_conf(i2c_device->i2c_bus->i2c_port, cmd, I2C_BUS_TICKS_TO_WAIT, &i2c_device->conf); + i2c_cmd_link_delete(cmd); } - - i2c_master_start(cmd); - i2c_master_write_byte(cmd, (i2c_device->dev_addr << 1) | I2C_MASTER_READ, I2C_ACK_CHECK_EN); - i2c_master_read(cmd, data, data_len, I2C_MASTER_LAST_NACK); - i2c_master_stop(cmd); - esp_err_t ret = i2c_master_cmd_begin_with_conf(i2c_device->i2c_bus->i2c_port, cmd, I2C_BUS_TICKS_TO_WAIT, &i2c_device->conf); - i2c_cmd_link_delete(cmd); I2C_BUS_MUTEX_GIVE(i2c_device->i2c_bus->mutex, ESP_FAIL); return ret; } @@ -385,20 +423,28 @@ static esp_err_t i2c_bus_write_reg8(i2c_bus_device_handle_t dev_handle, uint8_t I2C_BUS_CHECK(dev_handle != NULL, "device handle error", ESP_ERR_INVALID_ARG); I2C_BUS_CHECK(data != NULL, "data pointer error", ESP_ERR_INVALID_ARG); i2c_bus_device_t *i2c_device = (i2c_bus_device_t *)dev_handle; + esp_err_t ret = ESP_OK; I2C_BUS_INIT_CHECK(i2c_device->i2c_bus->is_init, ESP_ERR_INVALID_STATE); I2C_BUS_MUTEX_TAKE(i2c_device->i2c_bus->mutex, ESP_ERR_TIMEOUT); - i2c_cmd_handle_t cmd = i2c_cmd_link_create(); - i2c_master_start(cmd); - i2c_master_write_byte(cmd, (i2c_device->dev_addr << 1) | I2C_MASTER_WRITE, I2C_ACK_CHECK_EN); - if (mem_address != NULL_I2C_MEM_ADDR) { - i2c_master_write_byte(cmd, mem_address, I2C_ACK_CHECK_EN); +#if CONFIG_I2C_BUS_SUPPORT_SOFTWARE + if (i2c_device->i2c_bus->i2c_port > I2C_NUM_MAX) { + ret = i2c_master_soft_bus_change_frequency(i2c_device->i2c_bus->soft_bus_handle, i2c_device->conf.master.clk_speed); + ret = i2c_master_soft_bus_write_reg8(i2c_device->i2c_bus->soft_bus_handle, i2c_device->dev_addr, mem_address, data_len, data); + } else +#endif + { + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, (i2c_device->dev_addr << 1) | I2C_MASTER_WRITE, I2C_ACK_CHECK_EN); + if (mem_address != NULL_I2C_MEM_ADDR) { + i2c_master_write_byte(cmd, mem_address, I2C_ACK_CHECK_EN); + } + i2c_master_write(cmd, (uint8_t *)data, data_len, I2C_ACK_CHECK_EN); + i2c_master_stop(cmd); + ret = i2c_master_cmd_begin_with_conf(i2c_device->i2c_bus->i2c_port, cmd, I2C_BUS_TICKS_TO_WAIT, &i2c_device->conf); + i2c_cmd_link_delete(cmd); } - - i2c_master_write(cmd, (uint8_t *)data, data_len, I2C_ACK_CHECK_EN); - i2c_master_stop(cmd); - esp_err_t ret = i2c_master_cmd_begin_with_conf(i2c_device->i2c_bus->i2c_port, cmd, I2C_BUS_TICKS_TO_WAIT, &i2c_device->conf); - i2c_cmd_link_delete(cmd); I2C_BUS_MUTEX_GIVE(i2c_device->i2c_bus->mutex, ESP_FAIL); return ret; } @@ -410,21 +456,31 @@ esp_err_t i2c_bus_write_reg16(i2c_bus_device_handle_t dev_handle, uint16_t mem_a i2c_bus_device_t *i2c_device = (i2c_bus_device_t *)dev_handle; I2C_BUS_INIT_CHECK(i2c_device->i2c_bus->is_init, ESP_ERR_INVALID_STATE); uint8_t memAddress8[2]; + esp_err_t ret = ESP_OK; memAddress8[0] = (uint8_t)((mem_address >> 8) & 0x00FF); memAddress8[1] = (uint8_t)(mem_address & 0x00FF); I2C_BUS_MUTEX_TAKE(i2c_device->i2c_bus->mutex, ESP_ERR_TIMEOUT); - i2c_cmd_handle_t cmd = i2c_cmd_link_create(); - i2c_master_start(cmd); - i2c_master_write_byte(cmd, (i2c_device->dev_addr << 1) | I2C_MASTER_WRITE, I2C_ACK_CHECK_EN); - if (mem_address != NULL_I2C_MEM_16BIT_ADDR) { - i2c_master_write(cmd, memAddress8, 2, I2C_ACK_CHECK_EN); - } +#if CONFIG_I2C_BUS_SUPPORT_SOFTWARE + if (i2c_device->i2c_bus->i2c_port > I2C_NUM_MAX) { + ret = i2c_master_soft_bus_change_frequency(i2c_device->i2c_bus->soft_bus_handle, i2c_device->conf.master.clk_speed); + ret = i2c_master_soft_bus_write_reg16(i2c_device->i2c_bus->soft_bus_handle, i2c_device->dev_addr, mem_address, data_len, data); + } else +#endif + { + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, (i2c_device->dev_addr << 1) | I2C_MASTER_WRITE, I2C_ACK_CHECK_EN); - i2c_master_write(cmd, (uint8_t *)data, data_len, I2C_ACK_CHECK_EN); - i2c_master_stop(cmd); - esp_err_t ret = i2c_master_cmd_begin_with_conf(i2c_device->i2c_bus->i2c_port, cmd, I2C_BUS_TICKS_TO_WAIT, &i2c_device->conf); - i2c_cmd_link_delete(cmd); + if (mem_address != NULL_I2C_MEM_16BIT_ADDR) { + i2c_master_write(cmd, memAddress8, 2, I2C_ACK_CHECK_EN); + } + + i2c_master_write(cmd, (uint8_t *)data, data_len, I2C_ACK_CHECK_EN); + i2c_master_stop(cmd); + ret = i2c_master_cmd_begin_with_conf(i2c_device->i2c_bus->i2c_port, cmd, I2C_BUS_TICKS_TO_WAIT, &i2c_device->conf); + i2c_cmd_link_delete(cmd); + } I2C_BUS_MUTEX_GIVE(i2c_device->i2c_bus->mutex, ESP_FAIL); return ret; } @@ -432,19 +488,39 @@ esp_err_t i2c_bus_write_reg16(i2c_bus_device_handle_t dev_handle, uint16_t mem_a /**************************************** Private Functions*********************************************/ static esp_err_t i2c_driver_reinit(i2c_port_t port, const i2c_config_t *conf) { +#if CONFIG_I2C_BUS_SUPPORT_SOFTWARE + I2C_BUS_CHECK(((i2c_sw_port_t)port < I2C_NUM_SW_MAX) || (port == I2C_NUM_MAX), "I2C port error", ESP_ERR_INVALID_ARG); +#else I2C_BUS_CHECK(port < I2C_NUM_MAX, "i2c port error", ESP_ERR_INVALID_ARG); +#endif I2C_BUS_CHECK(conf != NULL, "pointer = NULL error", ESP_ERR_INVALID_ARG); + esp_err_t ret = ESP_OK; if (s_i2c_bus[port].is_init) { - i2c_driver_delete(port); +#if CONFIG_I2C_BUS_SUPPORT_SOFTWARE + if (port > I2C_NUM_MAX) { + i2c_del_master_soft_bus(s_i2c_bus[port].soft_bus_handle); + } else +#endif + { + i2c_driver_delete(port); + } s_i2c_bus[port].is_init = false; ESP_LOGI(TAG, "i2c%d bus deinited", port); } - esp_err_t ret = i2c_param_config(port, conf); - I2C_BUS_CHECK(ret == ESP_OK, "i2c param config failed", ret); - ret = i2c_driver_install(port, conf->mode, I2C_BUS_MASTER_BUF_LEN, I2C_BUS_MASTER_BUF_LEN, I2C_BUS_FLG_DEFAULT); - I2C_BUS_CHECK(ret == ESP_OK, "i2c driver install failed", ret); +#if CONFIG_I2C_BUS_SUPPORT_SOFTWARE + if (port > I2C_NUM_MAX) { + ret = i2c_new_master_soft_bus(conf, &s_i2c_bus[port].soft_bus_handle); + I2C_BUS_CHECK(ret == ESP_OK, "i2c software driver install failed", ret); + } else +#endif + { + ret = i2c_param_config(port, conf); + I2C_BUS_CHECK(ret == ESP_OK, "i2c param config failed", ret); + ret = i2c_driver_install(port, conf->mode, I2C_BUS_MASTER_BUF_LEN, I2C_BUS_MASTER_BUF_LEN, I2C_BUS_FLG_DEFAULT); + I2C_BUS_CHECK(ret == ESP_OK, "i2c driver install failed", ret); + } s_i2c_bus[port].is_init = true; ESP_LOGI(TAG, "i2c%d bus inited", port); return ESP_OK; @@ -452,9 +528,22 @@ static esp_err_t i2c_driver_reinit(i2c_port_t port, const i2c_config_t *conf) static esp_err_t i2c_driver_deinit(i2c_port_t port) { +#if CONFIG_I2C_BUS_SUPPORT_SOFTWARE + I2C_BUS_CHECK(((i2c_sw_port_t)port < I2C_NUM_SW_MAX) || (port == I2C_NUM_MAX), "I2C port error", ESP_ERR_INVALID_ARG); +#else I2C_BUS_CHECK(port < I2C_NUM_MAX, "i2c port error", ESP_ERR_INVALID_ARG); +#endif I2C_BUS_CHECK(s_i2c_bus[port].is_init == true, "i2c not inited", ESP_ERR_INVALID_STATE); - i2c_driver_delete(port); //always return ESP_OK + +#if CONFIG_I2C_BUS_SUPPORT_SOFTWARE + if (port > I2C_NUM_MAX) { + esp_err_t ret = i2c_del_master_soft_bus(s_i2c_bus[port].soft_bus_handle); + I2C_BUS_CHECK(ret == ESP_OK, "i2c software driver delete failed", ret); + } else +#endif + { + i2c_driver_delete(port); //always return ESP_OK + } s_i2c_bus[port].is_init = false; ESP_LOGI(TAG, "i2c%d bus deinited", port); return ESP_OK; diff --git a/components/i2c_bus/i2c_bus_soft.c b/components/i2c_bus/i2c_bus_soft.c new file mode 100644 index 000000000..7adfaaad6 --- /dev/null +++ b/components/i2c_bus/i2c_bus_soft.c @@ -0,0 +1,304 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "esp_err.h" +#include "esp_check.h" +#include "i2c_bus_soft.h" +#include "driver/gpio.h" + +static const char*TAG = "i2c_bus_soft"; + +static esp_err_t i2c_master_soft_bus_wait_ack(i2c_master_soft_bus_handle_t bus_handle) +{ + esp_rom_delay_us(bus_handle->time_delay_us); + ESP_RETURN_ON_ERROR(gpio_set_level(bus_handle->scl_io, 1), TAG, "Failed to set SCL high"); + esp_rom_delay_us(bus_handle->time_delay_us); + + bool ack = !gpio_get_level(bus_handle->sda_io); /*!< SDA should be low for ACK */ + ESP_RETURN_ON_ERROR(gpio_set_level(bus_handle->scl_io, 0), TAG, "Failed to set SCL low"); + esp_rom_delay_us(bus_handle->time_delay_us); + + return ack ? ESP_OK : ESP_ERR_NOT_FOUND; +} + +static esp_err_t i2c_master_soft_bus_send_ack(i2c_master_soft_bus_handle_t bus_handle, bool ack) +{ + ESP_RETURN_ON_ERROR(gpio_set_level(bus_handle->sda_io, ack ? 0 : 1), TAG, "Failed to set SDA for ACK/NACK"); /*!< Set SDA line to ACK (low) or NACK (high) */ + + // Generate clock pulse for ACK/NACK + ESP_RETURN_ON_ERROR(gpio_set_level(bus_handle->scl_io, 1), TAG, "Failed to set SCL high during ACK/NACK"); + esp_rom_delay_us(bus_handle->time_delay_us); + ESP_RETURN_ON_ERROR(gpio_set_level(bus_handle->scl_io, 0), TAG, "Failed to set SCL low after ACK/NACK"); + esp_rom_delay_us(bus_handle->time_delay_us); + + return ESP_OK; +} + +static esp_err_t i2c_master_soft_bus_start(i2c_master_soft_bus_handle_t bus_handle) +{ + ESP_RETURN_ON_ERROR(gpio_set_level(bus_handle->scl_io, 1), TAG, "Failed to set SCL high"); + ESP_RETURN_ON_ERROR(gpio_set_level(bus_handle->sda_io, 1), TAG, "Failed to set SDA high"); + esp_rom_delay_us(bus_handle->time_delay_us); + ESP_RETURN_ON_ERROR(gpio_set_level(bus_handle->sda_io, 0), TAG, "Failed to set SDA low"); + esp_rom_delay_us(bus_handle->time_delay_us); + ESP_RETURN_ON_ERROR(gpio_set_level(bus_handle->scl_io, 0), TAG, "Failed to set SCL low"); + esp_rom_delay_us(bus_handle->time_delay_us); + return ESP_OK; +} + +static esp_err_t i2c_master_soft_bus_stop(i2c_master_soft_bus_handle_t bus_handle) +{ + ESP_RETURN_ON_ERROR(gpio_set_level(bus_handle->sda_io, 0), TAG, "Failed to set SDA low"); + ESP_RETURN_ON_ERROR(gpio_set_level(bus_handle->scl_io, 1), TAG, "Failed to set SCL high"); + esp_rom_delay_us(bus_handle->time_delay_us); + ESP_RETURN_ON_ERROR(gpio_set_level(bus_handle->sda_io, 1), TAG, "Failed to set SDA high"); + return ESP_OK; +} + +static esp_err_t i2c_master_soft_bus_write_byte(i2c_master_soft_bus_handle_t bus_handle, uint8_t byte) +{ + for (int i = 0; i < 8; i++) { + if (byte & 0x80) { + ESP_RETURN_ON_ERROR(gpio_set_level(bus_handle->sda_io, 1), TAG, "Failed to set SDA high"); + } else { + ESP_RETURN_ON_ERROR(gpio_set_level(bus_handle->sda_io, 0), TAG, "Failed to set SDA low"); + } + esp_rom_delay_us(bus_handle->time_delay_us); + ESP_RETURN_ON_ERROR(gpio_set_level(bus_handle->scl_io, 1), TAG, "Failed to set SCL high"); + esp_rom_delay_us(bus_handle->time_delay_us); + ESP_RETURN_ON_ERROR(gpio_set_level(bus_handle->scl_io, 0), TAG, "Failed to set SCL low"); + + if (i == 7) { + ESP_RETURN_ON_ERROR(gpio_set_level(bus_handle->sda_io, 1), TAG, "Failed to release SDA"); /*!< Release SDA */ + } + + byte <<= 1; + } + return ESP_OK; +} + +static esp_err_t i2c_master_soft_bus_read_byte(i2c_master_soft_bus_handle_t bus_handle, uint8_t *byte) +{ + uint8_t value = 0; + ESP_RETURN_ON_ERROR(gpio_set_level(bus_handle->sda_io, 1), TAG, "Failed to release SDA"); /*!< First release SDA */ + for (int i = 0; i < 8; i++) { + value <<= 1; + ESP_RETURN_ON_ERROR(gpio_set_level(bus_handle->scl_io, 1), TAG, "Failed to set SCL high"); + esp_rom_delay_us(bus_handle->time_delay_us); + + if (gpio_get_level(bus_handle->sda_io)) { + value ++; + } + ESP_RETURN_ON_ERROR(gpio_set_level(bus_handle->scl_io, 0), TAG, "Failed to set SCL low"); + esp_rom_delay_us(bus_handle->time_delay_us); + } + + *byte = value; + return ESP_OK; +} + +esp_err_t i2c_master_soft_bus_write_reg8(i2c_master_soft_bus_handle_t bus_handle, uint8_t dev_addr, uint8_t mem_address, size_t data_len, const uint8_t *data) +{ + ESP_RETURN_ON_FALSE(bus_handle, ESP_ERR_INVALID_ARG, TAG, "Invalid I2C bus handle"); + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_start(bus_handle), TAG, "Failed to initiate start signal"); + + // Send device address with write bit (0) + uint8_t address_byte = (dev_addr << 1) | 0; + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_write_byte(bus_handle, address_byte), TAG, "Failed to write device address"); + + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_wait_ack(bus_handle), TAG, "No ACK for device address"); + + if (mem_address != NULL_I2C_MEM_ADDR) { + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_write_byte(bus_handle, mem_address), TAG, "Failed to write memory address"); + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_wait_ack(bus_handle), TAG, "No ACK for device address"); + } + + // Write data + for (size_t i = 0; i < data_len; i++) { + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_write_byte(bus_handle, data[i]), TAG, "Failed to write data byte"); + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_wait_ack(bus_handle), TAG, "No ACK for data byte"); + } + + // Generate STOP condition + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_stop(bus_handle), TAG, "Failed to initiate stop signal"); + return ESP_OK; +} + +esp_err_t i2c_master_soft_bus_write_reg16(i2c_master_soft_bus_handle_t bus_handle, uint8_t dev_addr, uint16_t mem_address, size_t data_len, const uint8_t *data) +{ + ESP_RETURN_ON_FALSE(bus_handle, ESP_ERR_INVALID_ARG, TAG, "Invalid I2C bus handle"); + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_start(bus_handle), TAG, "Failed to initiate start signal"); + + // Send device address with write bit (0) + uint8_t address_byte = (dev_addr << 1) | 0; + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_write_byte(bus_handle, address_byte), TAG, "Failed to write device address"); + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_wait_ack(bus_handle), TAG, "No ACK for device address"); + + if (mem_address != NULL_I2C_MEM_16BIT_ADDR) { + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_write_byte(bus_handle, (uint8_t)((mem_address >> 8) & 0x00FF)), TAG, "Failed to write memory address"); + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_wait_ack(bus_handle), TAG, "No ACK for mem address"); + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_write_byte(bus_handle, (uint8_t)(mem_address & 0x00FF)), TAG, "Failed to write memory address"); + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_wait_ack(bus_handle), TAG, "No ACK for mem address"); + } + + // Write data + for (size_t i = 0; i < data_len; i++) { + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_write_byte(bus_handle, data[i]), TAG, "Failed to write data byte"); + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_wait_ack(bus_handle), TAG, "No ACK for data byte"); + } + + // Generate STOP condition + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_stop(bus_handle), TAG, "Failed to initiate stop signal"); + return ESP_OK; +} + +esp_err_t i2c_master_soft_bus_read_reg8(i2c_master_soft_bus_handle_t bus_handle, uint8_t dev_addr, uint8_t mem_address, size_t data_len, uint8_t *data) +{ + ESP_RETURN_ON_FALSE(bus_handle, ESP_ERR_INVALID_ARG, TAG, "Invalid I2C bus handle"); + + // Send memory address + if (mem_address != NULL_I2C_MEM_ADDR) { + // Generate START condition + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_start(bus_handle), TAG, "Failed to initiate start signal"); + + // Send device address with write bit (0) to write the memory address + uint8_t write_address_byte = (dev_addr << 1) | 0; + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_write_byte(bus_handle, write_address_byte), TAG, "Failed to write device address"); + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_wait_ack(bus_handle), TAG, "No ACK for device address"); + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_write_byte(bus_handle, mem_address), TAG, "Failed to write memory address"); + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_wait_ack(bus_handle), TAG, "No ACK for mem address"); + } + + // Generate RESTART condition + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_start(bus_handle), TAG, "Failed to initiate repeated start signal"); + + // Send device address with read bit (1) + uint8_t read_address_byte = (dev_addr << 1) | 1; + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_write_byte(bus_handle, read_address_byte), TAG, "Failed to write device address with read bit"); + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_wait_ack(bus_handle), TAG, "No ACK for device address"); + + // Read data bytes + for (size_t i = 0; i < data_len; i++) { + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_read_byte(bus_handle, &data[i]), TAG, "Failed to read data byte"); + + // Send ACK for all but the last byte + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_send_ack(bus_handle, i != data_len - 1), TAG, "Failed to send ACK/NACK"); + } + + // Generate STOP condition + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_stop(bus_handle), TAG, "Failed to initiate stop signal"); + + return ESP_OK; +} + +esp_err_t i2c_master_soft_bus_read_reg16(i2c_master_soft_bus_handle_t bus_handle, uint8_t dev_addr, uint16_t mem_address, size_t data_len, uint8_t *data) +{ + ESP_RETURN_ON_FALSE(bus_handle, ESP_ERR_INVALID_ARG, TAG, "Invalid I2C bus handle"); + + // Send memory address + if (mem_address != NULL_I2C_MEM_16BIT_ADDR) { + // Generate START condition + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_start(bus_handle), TAG, "Failed to initiate start signal"); + + // Send device address with write bit (0) to write the memory address + uint8_t write_address_byte = (dev_addr << 1) | 0; + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_write_byte(bus_handle, write_address_byte), TAG, "Failed to write device address"); + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_wait_ack(bus_handle), TAG, "No ACK for device address"); + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_write_byte(bus_handle, (uint8_t)((mem_address >> 8) & 0x00FF)), TAG, "Failed to write memory address"); + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_wait_ack(bus_handle), TAG, "No ACK for mem address"); + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_write_byte(bus_handle, (uint8_t)(mem_address & 0x00FF)), TAG, "Failed to write memory address"); + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_wait_ack(bus_handle), TAG, "No ACK for mem address"); + } + + // Generate RESTART condition + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_start(bus_handle), TAG, "Failed to initiate repeated start signal"); + + // Send device address with read bit (1) + uint8_t read_address_byte = (dev_addr << 1) | 1; + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_write_byte(bus_handle, read_address_byte), TAG, "Failed to write device address with read bit"); + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_wait_ack(bus_handle), TAG, "No ACK for device address during read"); + + // Read data bytes + for (size_t i = 0; i < data_len; i++) { + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_read_byte(bus_handle, &data[i]), TAG, "Failed to read data byte"); + + // Send ACK for all but the last byte + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_send_ack(bus_handle, i != data_len - 1), TAG, "Failed to send ACK/NACK"); + } + + // Generate STOP condition + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_stop(bus_handle), TAG, "Failed to initiate stop signal"); + return ESP_OK; +} + +esp_err_t i2c_master_soft_bus_probe(i2c_master_soft_bus_handle_t bus_handle, uint8_t address) +{ + ESP_RETURN_ON_FALSE(bus_handle, ESP_ERR_INVALID_ARG, TAG, "Invalid I2C bus handle"); + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_start(bus_handle), TAG, "Failed to initiate start signal"); + + // Send device address with write bit (0) + uint8_t address_byte = (address << 1) | 0; + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_write_byte(bus_handle, address_byte), TAG, "Failed to write address byte"); + + // Wait for ACK + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_wait_ack(bus_handle), TAG, "Failed to wait for ACK"); + + // Generate STOP condition + ESP_RETURN_ON_ERROR(i2c_master_soft_bus_stop(bus_handle), TAG, "Failed to initiate stop signal"); + return ESP_OK; +} + +esp_err_t i2c_new_master_soft_bus(const i2c_config_t *conf, i2c_master_soft_bus_handle_t *ret_soft_bus_handle) +{ + esp_err_t ret = ESP_OK; + ESP_RETURN_ON_FALSE(GPIO_IS_VALID_GPIO(conf->scl_io_num) && GPIO_IS_VALID_GPIO(conf->sda_io_num), ESP_ERR_INVALID_ARG, TAG, "Invalid SDA/SCL pin number"); + ESP_RETURN_ON_FALSE(conf->master.clk_speed > 0, ESP_ERR_INVALID_ARG, TAG, "Invalid scl frequency"); + + gpio_config_t scl_io_conf = { + .mode = GPIO_MODE_OUTPUT_OD, + .pull_up_en = conf->scl_pullup_en, + .intr_type = GPIO_INTR_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .pin_bit_mask = (1ULL << conf->scl_io_num), + }; + ESP_RETURN_ON_ERROR(gpio_config(&scl_io_conf), TAG, "Failed to configure scl gpio"); + + gpio_config_t sda_io_conf = { + .mode = GPIO_MODE_INPUT_OUTPUT_OD, + .pull_up_en = conf->sda_pullup_en, + .intr_type = GPIO_INTR_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .pin_bit_mask = (1ULL << conf->sda_io_num), + }; + ESP_RETURN_ON_ERROR(gpio_config(&sda_io_conf), TAG, "Failed to configure sda gpio"); + + i2c_master_soft_bus_handle_t soft_bus_handle = calloc(1, sizeof(struct i2c_master_soft_bus_t)); + if (soft_bus_handle == NULL) { + ESP_LOGE(TAG, "Failed to allocate soft bus handle"); + return ESP_ERR_NO_MEM; + } + + soft_bus_handle->scl_io = conf->scl_io_num; + soft_bus_handle->sda_io = conf->sda_io_num; + soft_bus_handle->time_delay_us = (uint32_t)((1e6f / conf->master.clk_speed) / 2.0f + 0.5f); + *ret_soft_bus_handle = soft_bus_handle; + + return ret; +} + +esp_err_t i2c_master_soft_bus_change_frequency(i2c_master_soft_bus_handle_t bus_handle, uint32_t frequency) +{ + ESP_RETURN_ON_FALSE(bus_handle, ESP_ERR_INVALID_ARG, TAG, "Invalid I2C bus handle"); + ESP_RETURN_ON_FALSE(frequency > 0, ESP_ERR_INVALID_ARG, TAG, "Invalid scl frequency"); + bus_handle->time_delay_us = (uint32_t)((1e6f / frequency) / 2.0f + 0.5f); + return ESP_OK; +} + +esp_err_t i2c_del_master_soft_bus(i2c_master_soft_bus_handle_t bus_handle) +{ + ESP_RETURN_ON_FALSE(bus_handle, ESP_ERR_INVALID_ARG, TAG, "no memory for i2c master soft bus"); + free(bus_handle); + return ESP_OK; +} diff --git a/components/i2c_bus/i2c_bus_v2.c b/components/i2c_bus/i2c_bus_v2.c index d7fd54a4e..a8d6bdaca 100644 --- a/components/i2c_bus/i2c_bus_v2.c +++ b/components/i2c_bus/i2c_bus_v2.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -13,6 +13,9 @@ #include "esp_log.h" #include "i2c_bus.h" +#if CONFIG_I2C_BUS_SUPPORT_SOFTWARE +#include "i2c_bus_soft.h" +#endif #define I2C_ACK_CHECK_EN 0x1 /*!< I2C master will check ack from slave*/ #define I2C_ACK_CHECK_DIS 0x0 /*!< I2C master will not check ack from slave */ @@ -26,6 +29,9 @@ typedef struct { i2c_master_bus_config_t bus_config; /*!< I2C master bus specific configurations */ i2c_master_bus_handle_t bus_handle; /*!< I2C master bus handle */ i2c_device_config_t device_config; /*!< I2C device configuration, in order to get the frequency information of I2C */ +#if CONFIG_I2C_BUS_SUPPORT_SOFTWARE + i2c_master_soft_bus_handle_t soft_bus_handle; /*!< I2C master soft bus handle */ +#endif bool is_init; /*!< if bus is initialized */ i2c_config_t conf_activate; /*!< I2C active configuration */ SemaphoreHandle_t mutex; /*!< mutex to achieve thread-safe */ @@ -40,7 +46,12 @@ typedef struct { } i2c_bus_device_t; static const char *TAG = "i2c_bus"; + +#if CONFIG_I2C_BUS_SUPPORT_SOFTWARE +static i2c_bus_t s_i2c_bus[I2C_NUM_SW_MAX]; /*!< If software I2C is enabled, additional space is required to store the port. */ +#else static i2c_bus_t s_i2c_bus[I2C_NUM_MAX]; +#endif #define I2C_BUS_CHECK(a, str, ret) if(!(a)) { \ ESP_LOGE(TAG,"%s:%d (%s):%s", __FILE__, __LINE__, __FUNCTION__, str); \ @@ -81,14 +92,18 @@ inline static bool i2c_config_compare(i2c_port_t port, const i2c_config_t *conf) i2c_bus_handle_t i2c_bus_create(i2c_port_t port, const i2c_config_t *conf) { +#if CONFIG_I2C_BUS_SUPPORT_SOFTWARE + I2C_BUS_CHECK(((i2c_sw_port_t)port < I2C_NUM_SW_MAX) || (port == I2C_NUM_MAX), "I2C port error", NULL); +#else I2C_BUS_CHECK(port < I2C_NUM_MAX, "I2C port error", NULL); +#endif I2C_BUS_CHECK(conf != NULL, "pointer = NULL error", NULL); I2C_BUS_CHECK(conf->mode == I2C_MODE_MASTER, "i2c_bus only supports master mode", NULL); if (s_i2c_bus[port].is_init) { // if i2c_bus has been inited and configs not changed, return the handle directly if (i2c_config_compare(port, conf)) { - ESP_LOGW(TAG, "i2c%d has been inited, return handle directly, ref_counter=%"PRIu32"", port, s_i2c_bus[port].ref_counter); + ESP_LOGW(TAG, "i2c%d has been inited, return handle directly, ref_counter=%"PRIi32"", port, s_i2c_bus[port].ref_counter); return (i2c_bus_handle_t)&s_i2c_bus[port]; } } else { @@ -115,7 +130,7 @@ esp_err_t i2c_bus_delete(i2c_bus_handle_t *p_bus) // if ref_counter == 0, de-init the bus if ((i2c_bus->ref_counter) > 0) { - ESP_LOGW(TAG, "i2c%d is also handled by others ref_counter=%"PRIu32", won't be de-inited", i2c_bus->bus_config.i2c_port, i2c_bus->ref_counter); + ESP_LOGW(TAG, "i2c%d is also handled by others ref_counter=%"PRIi32", won't be de-inited", i2c_bus->bus_config.i2c_port, i2c_bus->ref_counter); return ESP_OK; } @@ -160,9 +175,14 @@ i2c_bus_device_handle_t i2c_bus_device_create(i2c_bus_handle_t bus_handle, uint8 i2c_device->device_config.scl_speed_hz = clk_speed; } - esp_err_t ret = i2c_master_bus_add_device(i2c_bus->bus_handle, &i2c_device->device_config, &i2c_device->dev_handle); - I2C_BUS_CHECK(ret == ESP_OK, "add device error", NULL); - +#if CONFIG_I2C_BUS_SUPPORT_SOFTWARE + // For software I2C, it is only necessary to save the device configuration. + if (i2c_bus->bus_config.i2c_port < I2C_NUM_MAX) +#endif + { + esp_err_t ret = i2c_master_bus_add_device(i2c_bus->bus_handle, &i2c_device->device_config, &i2c_device->dev_handle); + I2C_BUS_CHECK(ret == ESP_OK, "add device error", NULL); + } i2c_device->i2c_bus = i2c_bus; i2c_bus->ref_counter++; I2C_BUS_MUTEX_GIVE(i2c_bus->mutex, NULL); @@ -173,8 +193,14 @@ esp_err_t i2c_bus_device_delete(i2c_bus_device_handle_t *p_dev_handle) { I2C_BUS_CHECK(p_dev_handle != NULL && *p_dev_handle != NULL, "Null Device Handle", ESP_ERR_INVALID_ARG); i2c_bus_device_t *i2c_device = (i2c_bus_device_t *)(*p_dev_handle); - esp_err_t ret = i2c_master_bus_rm_device(i2c_device->dev_handle); - I2C_BUS_CHECK(ret == ESP_OK, "remove device error", ret); + +#if CONFIG_I2C_BUS_SUPPORT_SOFTWARE + if (i2c_device->i2c_bus->bus_config.i2c_port < I2C_NUM_MAX) +#endif + { + esp_err_t ret = i2c_master_bus_rm_device(i2c_device->dev_handle); + I2C_BUS_CHECK(ret == ESP_OK, "remove device error", ret); + } I2C_BUS_MUTEX_TAKE_MAX_DELAY(i2c_device->i2c_bus->mutex, ESP_ERR_TIMEOUT); i2c_device->i2c_bus->ref_counter--; /*!< ref_counter is reduced only when the device is removed successfully. */ I2C_BUS_MUTEX_GIVE(i2c_device->i2c_bus->mutex, ESP_FAIL); @@ -189,9 +215,18 @@ uint8_t i2c_bus_scan(i2c_bus_handle_t bus_handle, uint8_t *buf, uint8_t num) i2c_bus_t *i2c_bus = (i2c_bus_t *)bus_handle; I2C_BUS_INIT_CHECK(i2c_bus->is_init, 0); uint8_t device_count = 0; + esp_err_t ret = ESP_OK; I2C_BUS_MUTEX_TAKE_MAX_DELAY(i2c_bus->mutex, 0); + for (uint8_t dev_address = 1; dev_address < 127; dev_address++) { - esp_err_t ret = i2c_master_probe(i2c_bus->bus_handle, dev_address, I2C_BUS_TICKS_TO_WAIT); +#if CONFIG_I2C_BUS_SUPPORT_SOFTWARE + if (i2c_bus->bus_config.i2c_port > I2C_NUM_MAX) { + ret = i2c_master_soft_bus_probe(i2c_bus->soft_bus_handle, dev_address); + } else +#endif + { + ret = i2c_master_probe(i2c_bus->bus_handle, dev_address, I2C_BUS_TICKS_TO_WAIT); + } if (ret == ESP_OK) { ESP_LOGI(TAG, "found i2c device address = 0x%02x", dev_address); if (buf != NULL && device_count < num) { @@ -297,12 +332,20 @@ static esp_err_t i2c_bus_read_reg8(i2c_bus_device_handle_t dev_handle, uint8_t m I2C_BUS_MUTEX_TAKE(i2c_device->i2c_bus->mutex, ESP_ERR_TIMEOUT); esp_err_t ret = ESP_FAIL; - if (mem_address != NULL_I2C_MEM_ADDR) { - ret = i2c_master_transmit_receive(i2c_device->dev_handle, &mem_address, 1, data, data_len, I2C_BUS_TICKS_TO_WAIT); - } else { - ret = i2c_master_receive(i2c_device->dev_handle, data, data_len, I2C_BUS_TICKS_TO_WAIT); +#if CONFIG_I2C_BUS_SUPPORT_SOFTWARE + // Need to distinguish between hardware I2C and software I2C via port + if (i2c_device->i2c_bus->bus_config.i2c_port > I2C_NUM_MAX) { + ret = i2c_master_soft_bus_change_frequency(i2c_device->i2c_bus->soft_bus_handle, i2c_device->device_config.scl_speed_hz); + ret = i2c_master_soft_bus_read_reg8(i2c_device->i2c_bus->soft_bus_handle, i2c_device->device_config.device_address, mem_address, data_len, data); + } else +#endif + { + if (mem_address != NULL_I2C_MEM_ADDR) { + ret = i2c_master_transmit_receive(i2c_device->dev_handle, &mem_address, 1, data, data_len, I2C_BUS_TICKS_TO_WAIT); + } else { + ret = i2c_master_receive(i2c_device->dev_handle, data, data_len, I2C_BUS_TICKS_TO_WAIT); + } } - I2C_BUS_MUTEX_GIVE(i2c_device->i2c_bus->mutex, ESP_FAIL); return ret; } @@ -319,10 +362,19 @@ esp_err_t i2c_bus_read_reg16(i2c_bus_device_handle_t dev_handle, uint16_t mem_ad memAddress8[1] = (uint8_t)(mem_address & 0x00FF); I2C_BUS_MUTEX_TAKE(i2c_device->i2c_bus->mutex, ESP_ERR_TIMEOUT); - if (mem_address != NULL_I2C_MEM_16BIT_ADDR) { - ret = i2c_master_transmit_receive(i2c_device->dev_handle, memAddress8, 2, data, data_len, I2C_BUS_TICKS_TO_WAIT); - } else { - ret = i2c_master_receive(i2c_device->dev_handle, data, data_len, I2C_BUS_TICKS_TO_WAIT); +#if CONFIG_I2C_BUS_SUPPORT_SOFTWARE + // Need to distinguish between hardware I2C and software I2C via port + if (i2c_device->i2c_bus->bus_config.i2c_port > I2C_NUM_MAX) { + ret = i2c_master_soft_bus_change_frequency(i2c_device->i2c_bus->soft_bus_handle, i2c_device->device_config.scl_speed_hz); + ret = i2c_master_soft_bus_read_reg16(i2c_device->i2c_bus->soft_bus_handle, i2c_device->device_config.device_address, mem_address, data_len, data); + } else +#endif + { + if (mem_address != NULL_I2C_MEM_16BIT_ADDR) { + ret = i2c_master_transmit_receive(i2c_device->dev_handle, memAddress8, 2, data, data_len, I2C_BUS_TICKS_TO_WAIT); + } else { + ret = i2c_master_receive(i2c_device->dev_handle, data, data_len, I2C_BUS_TICKS_TO_WAIT); + } } I2C_BUS_MUTEX_GIVE(i2c_device->i2c_bus->mutex, ESP_FAIL); return ret; @@ -337,21 +389,30 @@ static esp_err_t i2c_bus_write_reg8(i2c_bus_device_handle_t dev_handle, uint8_t I2C_BUS_MUTEX_TAKE(i2c_device->i2c_bus->mutex, ESP_ERR_TIMEOUT); esp_err_t ret = ESP_FAIL; - if (mem_address != NULL_I2C_MEM_ADDR) { - uint8_t *data_addr = malloc(data_len + 1); - if (data_addr == NULL) { - ESP_LOGE(TAG, "data_addr memory alloc fail"); - I2C_BUS_MUTEX_GIVE(i2c_device->i2c_bus->mutex, ESP_FAIL); - return ESP_ERR_NO_MEM; /*!< If the memory request fails, unlock it immediately and return an error. */ - } - data_addr[0] = mem_address; - for (int i = 0; i < data_len; i++) { - data_addr[i + 1] = data[i]; +#if CONFIG_I2C_BUS_SUPPORT_SOFTWARE + // Need to distinguish between hardware I2C and software I2C via port + if (i2c_device->i2c_bus->bus_config.i2c_port > I2C_NUM_MAX) { + ret = i2c_master_soft_bus_change_frequency(i2c_device->i2c_bus->soft_bus_handle, i2c_device->device_config.scl_speed_hz); + ret = i2c_master_soft_bus_write_reg8(i2c_device->i2c_bus->soft_bus_handle, i2c_device->device_config.device_address, mem_address, data_len, data); + } else +#endif + { + if (mem_address != NULL_I2C_MEM_ADDR) { + uint8_t *data_addr = malloc(data_len + 1); + if (data_addr == NULL) { + ESP_LOGE(TAG, "data_addr memory alloc fail"); + I2C_BUS_MUTEX_GIVE(i2c_device->i2c_bus->mutex, ESP_FAIL); + return ESP_ERR_NO_MEM; /*!< If the memory request fails, unlock it immediately and return an error. */ + } + data_addr[0] = mem_address; + for (int i = 0; i < data_len; i++) { + data_addr[i + 1] = data[i]; + } + ret = i2c_master_transmit(i2c_device->dev_handle, data_addr, data_len + 1, I2C_BUS_TICKS_TO_WAIT); + free(data_addr); + } else { + ret = i2c_master_transmit(i2c_device->dev_handle, data, data_len, I2C_BUS_TICKS_TO_WAIT); } - ret = i2c_master_transmit(i2c_device->dev_handle, data_addr, data_len + 1, I2C_BUS_TICKS_TO_WAIT); - free(data_addr); - } else { - ret = i2c_master_transmit(i2c_device->dev_handle, data, data_len, I2C_BUS_TICKS_TO_WAIT); } I2C_BUS_MUTEX_GIVE(i2c_device->i2c_bus->mutex, ESP_FAIL); return ret; @@ -369,22 +430,30 @@ esp_err_t i2c_bus_write_reg16(i2c_bus_device_handle_t dev_handle, uint16_t mem_a I2C_BUS_MUTEX_TAKE(i2c_device->i2c_bus->mutex, ESP_ERR_TIMEOUT); esp_err_t ret = ESP_FAIL; - if (mem_address != NULL_I2C_MEM_16BIT_ADDR) { - uint8_t *data_addr = malloc(data_len + 2); - if (data_addr == NULL) { - ESP_LOGE(TAG, "data_addr memory alloc fail"); - I2C_BUS_MUTEX_GIVE(i2c_device->i2c_bus->mutex, ESP_FAIL); - return ESP_ERR_NO_MEM; /*!< If the memory request fails, unlock it immediately and return an error. */ - } - data_addr[0] = memAddress8[0]; - data_addr[1] = memAddress8[1]; - for (int i = 0; i < data_len; i++) { - data_addr[i + 2] = data[i]; +#if CONFIG_I2C_BUS_SUPPORT_SOFTWARE + if (i2c_device->i2c_bus->bus_config.i2c_port > I2C_NUM_MAX) { + ret = i2c_master_soft_bus_change_frequency(i2c_device->i2c_bus->soft_bus_handle, i2c_device->device_config.scl_speed_hz); + ret = i2c_master_soft_bus_write_reg16(i2c_device->i2c_bus->soft_bus_handle, i2c_device->device_config.device_address, mem_address, data_len, data); + } else +#endif + { + if (mem_address != NULL_I2C_MEM_16BIT_ADDR) { + uint8_t *data_addr = malloc(data_len + 2); + if (data_addr == NULL) { + ESP_LOGE(TAG, "data_addr memory alloc fail"); + I2C_BUS_MUTEX_GIVE(i2c_device->i2c_bus->mutex, ESP_FAIL); + return ESP_ERR_NO_MEM; /*!< If the memory request fails, unlock it immediately and return an error. */ + } + data_addr[0] = memAddress8[0]; + data_addr[1] = memAddress8[1]; + for (int i = 0; i < data_len; i++) { + data_addr[i + 2] = data[i]; + } + ret = i2c_master_transmit(i2c_device->dev_handle, data_addr, data_len + 2, I2C_BUS_TICKS_TO_WAIT); + free(data_addr); + } else { + ret = i2c_master_transmit(i2c_device->dev_handle, data, data_len, I2C_BUS_TICKS_TO_WAIT); } - ret = i2c_master_transmit(i2c_device->dev_handle, data_addr, data_len + 2, I2C_BUS_TICKS_TO_WAIT); - free(data_addr); - } else { - ret = i2c_master_transmit(i2c_device->dev_handle, data, data_len, I2C_BUS_TICKS_TO_WAIT); } I2C_BUS_MUTEX_GIVE(i2c_device->i2c_bus->mutex, ESP_FAIL); return ret; @@ -394,39 +463,74 @@ esp_err_t i2c_bus_write_reg16(i2c_bus_device_handle_t dev_handle, uint16_t mem_a static esp_err_t i2c_driver_reinit(i2c_port_t port, const i2c_config_t *conf) { +#if CONFIG_I2C_BUS_SUPPORT_SOFTWARE + I2C_BUS_CHECK(((i2c_sw_port_t)port < I2C_NUM_SW_MAX) || (port == I2C_NUM_MAX), "I2C port error", ESP_ERR_INVALID_ARG); +#else I2C_BUS_CHECK(port < I2C_NUM_MAX, "i2c port error", ESP_ERR_INVALID_ARG); +#endif I2C_BUS_CHECK(conf != NULL, "pointer = NULL error", ESP_ERR_INVALID_ARG); if (s_i2c_bus[port].is_init) { - i2c_del_master_bus(s_i2c_bus[port].bus_handle); +#if CONFIG_I2C_BUS_SUPPORT_SOFTWARE + if (port > I2C_NUM_MAX) { + i2c_del_master_soft_bus(s_i2c_bus[port].soft_bus_handle); + } else +#endif + { + i2c_del_master_bus(s_i2c_bus[port].bus_handle); + } s_i2c_bus[port].is_init = false; ESP_LOGI(TAG, "i2c%d bus deinited", port); } - // Convert i2c_config_t information to i2c_master_bus_config_t and i2c_device_config_t - s_i2c_bus[port].bus_config.clk_source = I2C_CLK_SRC_DEFAULT; - s_i2c_bus[port].bus_config.i2c_port = port; - s_i2c_bus[port].bus_config.scl_io_num = conf->scl_io_num; - s_i2c_bus[port].bus_config.sda_io_num = conf->sda_io_num; - s_i2c_bus[port].bus_config.glitch_ignore_cnt = 7; /*!< Set the burr cycle of the host bus */ - s_i2c_bus[port].bus_config.flags.enable_internal_pullup = (conf->scl_pullup_en | conf->sda_pullup_en); - s_i2c_bus[port].device_config.scl_speed_hz = conf->master.clk_speed; - s_i2c_bus[port].device_config.flags.disable_ack_check = false; - - esp_err_t ret = i2c_new_master_bus(&s_i2c_bus[port].bus_config, &s_i2c_bus[port].bus_handle); - I2C_BUS_CHECK(ret == ESP_OK, "i2c driver install failed", ret); - s_i2c_bus[port].is_init = true; - ESP_LOGI(TAG, "i2c%d bus inited", port); +#if CONFIG_I2C_BUS_SUPPORT_SOFTWARE + if (port > I2C_NUM_MAX) { + s_i2c_bus[port].bus_config.i2c_port = port; /*!< Similarly, it is necessary to preserve the I2C_PORT for later distinction. */ + esp_err_t ret = i2c_new_master_soft_bus(conf, &s_i2c_bus[port].soft_bus_handle); + I2C_BUS_CHECK(ret == ESP_OK, "i2c software driver install failed", ret); + s_i2c_bus[port].is_init = true; + ESP_LOGI(TAG, "i2c%d software bus inited", port); + } else +#endif + { + // Convert i2c_config_t information to i2c_master_bus_config_t and i2c_device_config_t + s_i2c_bus[port].bus_config.clk_source = I2C_CLK_SRC_DEFAULT; + s_i2c_bus[port].bus_config.i2c_port = port; + s_i2c_bus[port].bus_config.scl_io_num = conf->scl_io_num; + s_i2c_bus[port].bus_config.sda_io_num = conf->sda_io_num; + s_i2c_bus[port].bus_config.glitch_ignore_cnt = 7; /*!< Set the burr cycle of the host bus */ + s_i2c_bus[port].bus_config.flags.enable_internal_pullup = (conf->scl_pullup_en | conf->sda_pullup_en); + s_i2c_bus[port].device_config.scl_speed_hz = conf->master.clk_speed; + s_i2c_bus[port].device_config.flags.disable_ack_check = false; + + esp_err_t ret = i2c_new_master_bus(&s_i2c_bus[port].bus_config, &s_i2c_bus[port].bus_handle); + I2C_BUS_CHECK(ret == ESP_OK, "i2c driver install failed", ret); + s_i2c_bus[port].is_init = true; + ESP_LOGI(TAG, "i2c%d bus inited", port); + } return ESP_OK; } static esp_err_t i2c_driver_deinit(i2c_port_t port) { +#if CONFIG_I2C_BUS_SUPPORT_SOFTWARE + I2C_BUS_CHECK(((i2c_sw_port_t)port < I2C_NUM_SW_MAX) || (port == I2C_NUM_MAX), "I2C port error", ESP_ERR_INVALID_ARG); +#else I2C_BUS_CHECK(port < I2C_NUM_MAX, "i2c port error", ESP_ERR_INVALID_ARG); +#endif I2C_BUS_CHECK(s_i2c_bus[port].is_init == true, "i2c not inited", ESP_ERR_INVALID_STATE); - esp_err_t ret = i2c_del_master_bus(s_i2c_bus[port].bus_handle); - I2C_BUS_CHECK(ret == ESP_OK, "i2c driver delete failed", ret); + // Need to distinguish between hardware I2C and software I2C via port +#if CONFIG_I2C_BUS_SUPPORT_SOFTWARE + if (port > I2C_NUM_MAX) { + esp_err_t ret = i2c_del_master_soft_bus(s_i2c_bus[port].soft_bus_handle); + I2C_BUS_CHECK(ret == ESP_OK, "i2c software driver delete failed", ret); + } else +#endif + { + esp_err_t ret = i2c_del_master_bus(s_i2c_bus[port].bus_handle); + I2C_BUS_CHECK(ret == ESP_OK, "i2c driver delete failed", ret); + } s_i2c_bus[port].is_init = false; ESP_LOGI(TAG, "i2c%d bus deinited", port); return ESP_OK; @@ -442,11 +546,23 @@ static esp_err_t i2c_driver_deinit(i2c_port_t port) */ inline static bool i2c_config_compare(i2c_port_t port, const i2c_config_t *conf) { - if (s_i2c_bus[port].bus_config.sda_io_num == conf->sda_io_num - && s_i2c_bus[port].bus_config.scl_io_num == conf->scl_io_num - && s_i2c_bus[port].bus_config.flags.enable_internal_pullup == (conf->scl_pullup_en | conf->sda_pullup_en)) { - return true; +#if CONFIG_I2C_BUS_SUPPORT_SOFTWARE + if (port > I2C_NUM_MAX) { + if (s_i2c_bus[port].conf_activate.master.clk_speed == conf->master.clk_speed + && s_i2c_bus[port].conf_activate.sda_io_num == conf->sda_io_num + && s_i2c_bus[port].conf_activate.scl_io_num == conf->scl_io_num + && s_i2c_bus[port].conf_activate.scl_pullup_en == conf->scl_pullup_en + && s_i2c_bus[port].conf_activate.sda_pullup_en == conf->sda_pullup_en) { + return true; + } + } else +#endif + { + if (s_i2c_bus[port].bus_config.sda_io_num == conf->sda_io_num + && s_i2c_bus[port].bus_config.scl_io_num == conf->scl_io_num + && s_i2c_bus[port].bus_config.flags.enable_internal_pullup == (conf->scl_pullup_en | conf->sda_pullup_en)) { + return true; + } } - return false; } diff --git a/components/i2c_bus/idf_component.yml b/components/i2c_bus/idf_component.yml index 91c9e2887..67e865905 100644 --- a/components/i2c_bus/idf_component.yml +++ b/components/i2c_bus/idf_component.yml @@ -1,5 +1,5 @@ -version: "1.1.0" -description: I2C bus driver +version: "1.2.0" +description: The I2C Bus Driver supports both hardware and software I2C. url: https://github.com/espressif/esp-iot-solution/tree/master/components/i2c_bus repository: https://github.com/espressif/esp-iot-solution.git documentation: https://docs.espressif.com/projects/esp-iot-solution/en/latest/basic/bus/i2c_bus.html diff --git a/components/i2c_bus/include/i2c_bus.h b/components/i2c_bus/include/i2c_bus.h index 8a80f7e9b..9c87ba035 100644 --- a/components/i2c_bus/include/i2c_bus.h +++ b/components/i2c_bus/include/i2c_bus.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -30,6 +30,25 @@ extern "C" { #endif +#if CONFIG_I2C_BUS_SUPPORT_SOFTWARE +typedef enum { + I2C_NUM_SW_0 = I2C_NUM_MAX + 1, +#if CONFIG_I2C_BUS_SOFTWARE_MAX_PORT >= 2 + I2C_NUM_SW_1, +#endif +#if CONFIG_I2C_BUS_SOFTWARE_MAX_PORT >= 3 + I2C_NUM_SW_2, +#endif +#if CONFIG_I2C_BUS_SOFTWARE_MAX_PORT >= 4 + I2C_NUM_SW_3, +#endif +#if CONFIG_I2C_BUS_SOFTWARE_MAX_PORT >= 5 + I2C_NUM_SW_4, +#endif + I2C_NUM_SW_MAX, +} i2c_sw_port_t; +#endif + #if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)) #define gpio_pad_select_gpio esp_rom_gpio_pad_select_gpio #define portTICK_RATE_MS portTICK_PERIOD_MS @@ -63,7 +82,7 @@ typedef void *i2c_cmd_handle_t; /*!< I2C command handle */ * which means for an i2c port only one group parameter works. When i2c_bus_create is called more than one time for the * same i2c port, following parameter will override the previous one. * - * @param port I2C port number + * @param port I2C port number. Please note that enabling I2C_BUS_SUPPORT_SOFTWARE in menuconfig allows you to use ports in i2c_sw_port_t to enable software I2C. * @param conf Pointer to I2C bus configuration * @return i2c_bus_handle_t Return the I2C bus handle if created successfully, return NULL if failed. */ diff --git a/components/i2c_bus/private_include/i2c_bus_soft.h b/components/i2c_bus/private_include/i2c_bus_soft.h new file mode 100644 index 000000000..00e2ae1a7 --- /dev/null +++ b/components/i2c_bus/private_include/i2c_bus_soft.h @@ -0,0 +1,121 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include "i2c_bus.h" + +struct i2c_master_soft_bus_t { + gpio_num_t scl_io; /*!< SCL GPIO PIN */ + gpio_num_t sda_io; /*!< SDA GPIO PIN */ + uint32_t time_delay_us; /*!< Interval between SCL GPIO toggles in microseconds, determining the SCL frequency */ +}; + +typedef struct i2c_master_soft_bus_t *i2c_master_soft_bus_handle_t; + +/** + * @brief Allocate an I2C master soft bus + * + * @param conf I2C master soft bus configuration + * @param ret_soft_bus_handle I2C soft bus handle + * @return + * - ESP_OK: I2C master soft bus initialized successfully + * - ESP_ERR_INVALID_ARG: I2C soft bus initialization failed because of invalid argument + * - ESP_ERR_NO_MEM: Create I2C soft bus failed because of out of memory + */ +esp_err_t i2c_new_master_soft_bus(const i2c_config_t *conf, i2c_master_soft_bus_handle_t *ret_soft_bus_handle); + +/** + * @brief Delete the I2C master soft bus + * + * @param bus_handle I2C soft bus handle + * @return + * - ESP_OK: Delete I2C soft bus success + * - Otherwise: Some module delete failed + */ +esp_err_t i2c_del_master_soft_bus(i2c_master_soft_bus_handle_t bus_handle); + +/** + * @brief Change I2C soft bus frequency + * + * @param bus_handle I2C soft bus handle + * @param frequency I2C bus frequency + * @return + * - ESP_OK: Change I2C soft bus frequency success + * - ESP_ERR_INVALID_ARG: I2C soft bus change frequency failed because of invalid argument + */ +esp_err_t i2c_master_soft_bus_change_frequency(i2c_master_soft_bus_handle_t bus_handle, uint32_t frequency); + +/** + * @brief Probe I2C address, if address is correct and ACK is received, this function will return ESP_OK + * + * @param bus_handle I2C soft bus handle + * @param address I2C device address that you want to probe + * @return + * - ESP_OK: I2C device probe successfully + * - ESP_ERR_INVALID_ARG: I2C probe failed because of invalid argument + * - ESP_ERR_NOT_FOUND: I2C probe failed, doesn't find the device with specific address you gave + */ +esp_err_t i2c_master_soft_bus_probe(i2c_master_soft_bus_handle_t bus_handle, uint8_t address); + +/** + * @brief Write multiple byte to i2c device with 8-bit internal register/memory address + * + * @param bus_handle I2C soft bus handle + * @param dev_addr I2C device address + * @param mem_address The internal reg/mem address to write to, set to NULL_I2C_MEM_ADDR if no internal address + * @param data_len Number of bytes to write + * @param data Pointer to the bytes to write + * @return + * - ESP_OK: I2C master write success + * - ESP_ERR_INVALID_ARG: I2C master write failed because of invalid argument + * - Otherwise: I2C master write failed + */ +esp_err_t i2c_master_soft_bus_write_reg8(i2c_master_soft_bus_handle_t bus_handle, uint8_t dev_addr, uint8_t mem_address, size_t data_len, const uint8_t *data); + +/** + * @brief Write multiple byte to i2c device with 16-bit internal register/memory address + * + * @param bus_handle I2C soft bus handle + * @param dev_addr I2C device address + * @param mem_address The internal reg/mem address to write to, set to NULL_I2C_MEM_16BIT_ADDR if no internal address + * @param data_len Number of bytes to write + * @param data Pointer to the bytes to write + * @return + * - ESP_OK: I2C master write success + * - ESP_ERR_INVALID_ARG: I2C master write failed because of invalid argument + * - Otherwise: I2C master write failed + */ +esp_err_t i2c_master_soft_bus_write_reg16(i2c_master_soft_bus_handle_t bus_handle, uint8_t dev_addr, uint16_t mem_address, size_t data_len, const uint8_t *data); + +/** + * @brief Read multiple byte to i2c device with 8-bit internal register/memory address + * + * @param bus_handle I2C soft bus handle + * @param dev_addr I2C device address + * @param mem_address The internal reg/mem address to write to, set to NULL_I2C_MEM_ADDR if no internal address + * @param data_len Number of bytes to read + * @param data Pointer to the bytes to read + * @return + * - ESP_OK: I2C master read success + * - ESP_ERR_INVALID_ARG: I2C master read failed because of invalid argument + * - Otherwise: I2C master read failed + */ +esp_err_t i2c_master_soft_bus_read_reg8(i2c_master_soft_bus_handle_t bus_handle, uint8_t dev_addr, uint8_t mem_address, size_t data_len, uint8_t *data); + +/** + * @brief Read multiple byte to i2c device with 16-bit internal register/memory address + * + * @param bus_handle I2C soft bus handle + * @param dev_addr I2C device address + * @param mem_address The internal reg/mem address to write to, set to NULL_I2C_MEM_16BIT_ADDR if no internal address + * @param data_len Number of bytes to read + * @param data Pointer to the bytes to read + * @return + * - ESP_OK: I2C master read success + * - ESP_ERR_INVALID_ARG: I2C master read failed because of invalid argument + * - Otherwise: I2C master read failed + */ +esp_err_t i2c_master_soft_bus_read_reg16(i2c_master_soft_bus_handle_t bus_handle, uint8_t dev_addr, uint16_t mem_address, size_t data_len, uint8_t *data); diff --git a/components/i2c_bus/test_apps/main/test_i2c_bus.c b/components/i2c_bus/test_apps/main/test_i2c_bus.c index dbf12847b..a2759a439 100644 --- a/components/i2c_bus/test_apps/main/test_i2c_bus.c +++ b/components/i2c_bus/test_apps/main/test_i2c_bus.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -26,20 +26,20 @@ static size_t before_free_32bit; #define I2C_MASTER_SCL_IO (gpio_num_t)4 /*!< gpio number for I2C master clock */ #define I2C_MASTER_SDA_IO (gpio_num_t)5 /*!< gpio number for I2C master data */ -#define DATA_LENGTH 512 /*!= ESP_IDF_VERSION_VAL(5, 3, 0) && !CONFIG_I2C_BUS_BACKWARD_CONFIG static QueueHandle_t s_receive_queue; @@ -391,6 +391,111 @@ TEST_CASE("I2C bus scan test", "[i2c_bus][scan]") TEST_ASSERT(i2c_bus == NULL); } +#if CONFIG_I2C_BUS_SUPPORT_SOFTWARE + +TEST_CASE("I2C soft bus init-deinit test", "[soft][bus][i2c_bus]") +{ + i2c_config_t conf = { + .mode = I2C_MODE_MASTER, + .sda_io_num = I2C_MASTER_SDA_IO, + .sda_pullup_en = GPIO_PULLUP_ENABLE, + .scl_io_num = I2C_MASTER_SCL_IO, + .scl_pullup_en = GPIO_PULLUP_ENABLE, + .master.clk_speed = I2C_MASTER_FREQ_HZ, + }; + i2c_bus_handle_t i2c0_bus_1 = i2c_bus_create(I2C_NUM_SW_1, &conf); + TEST_ASSERT(i2c0_bus_1 != NULL); + /** configs not change**/ + i2c0_bus_1 = i2c_bus_create(I2C_NUM_SW_1, &conf); + TEST_ASSERT(i2c0_bus_1 != NULL); + /** configs change**/ + conf.master.clk_speed *= 2; + i2c0_bus_1 = i2c_bus_create(I2C_NUM_SW_0, &conf); + TEST_ASSERT(i2c0_bus_1 != NULL); + vTaskDelay(100 / portTICK_RATE_MS); + TEST_ASSERT(ESP_OK == i2c_bus_delete(&i2c0_bus_1)); + TEST_ASSERT(i2c0_bus_1 == NULL); +} + +TEST_CASE("I2C soft bus device add test", "[soft][bus][device][i2c_bus]") +{ + i2c_config_t conf = { + .mode = I2C_MODE_MASTER, + .sda_io_num = I2C_MASTER_SDA_IO, + .sda_pullup_en = GPIO_PULLUP_ENABLE, + .scl_io_num = I2C_MASTER_SCL_IO, + .scl_pullup_en = GPIO_PULLUP_ENABLE, + .master.clk_speed = I2C_MASTER_FREQ_HZ, + }; + i2c_bus_handle_t i2c0_bus_1 = i2c_bus_create(I2C_NUM_SW_0, &conf); + TEST_ASSERT(i2c0_bus_1 != NULL); + i2c_bus_device_handle_t i2c_device1 = i2c_bus_device_create(i2c0_bus_1, 0x01, 400000); + TEST_ASSERT(i2c_device1 != NULL); + i2c_bus_device_handle_t i2c_device2 = i2c_bus_device_create(i2c0_bus_1, 0x01, 100000); + TEST_ASSERT(i2c_device2 != NULL); + i2c_bus_device_delete(&i2c_device1); + TEST_ASSERT(i2c_device1 == NULL); + i2c_bus_device_delete(&i2c_device2); + TEST_ASSERT(i2c_device2 == NULL); + TEST_ASSERT(ESP_OK == i2c_bus_delete(&i2c0_bus_1)); + TEST_ASSERT(i2c0_bus_1 == NULL); +} + +TEST_CASE("I2C soft bus scan test", "[soft][i2c_bus][scan]") +{ + uint8_t addrs[I2C_SCAN_ADDR_NUM] = {0}; + + i2c_config_t conf = { + .mode = I2C_MODE_MASTER, + .sda_io_num = I2C_MASTER_SDA_IO, + .sda_pullup_en = GPIO_PULLUP_ENABLE, + .scl_io_num = I2C_MASTER_SCL_IO, + .scl_pullup_en = GPIO_PULLUP_ENABLE, + .master.clk_speed = I2C_MASTER_FREQ_HZ, + }; + i2c_bus_handle_t i2c_bus = i2c_bus_create(I2C_NUM_SW_0, &conf); + i2c_bus_scan(i2c_bus, addrs, I2C_SCAN_ADDR_NUM); + + TEST_ASSERT(ESP_OK == i2c_bus_delete(&i2c_bus)); + TEST_ASSERT(i2c_bus == NULL); +} + +TEST_CASE("I2C soft bus write under different frequency test", "[soft][i2c_bus]") +{ + uint8_t *data_wr = (uint8_t *) malloc(RW_TEST_LENGTH); + for (int i = 0; i < RW_TEST_LENGTH; i++) { + data_wr[i] = i; + } + + i2c_config_t conf = { + .mode = I2C_MODE_MASTER, + .sda_io_num = I2C_MASTER_SDA_IO, + .sda_pullup_en = GPIO_PULLUP_ENABLE, + .scl_io_num = I2C_MASTER_SCL_IO, + .scl_pullup_en = GPIO_PULLUP_ENABLE, + .master.clk_speed = I2C_MASTER_FREQ_HZ, + }; + i2c_bus_handle_t i2c_bus = i2c_bus_create(I2C_NUM_SW_0, &conf); + TEST_ASSERT(i2c_bus != NULL); + i2c_bus_device_handle_t i2c_device1 = i2c_bus_device_create(i2c_bus, 0x01, I2C_MASTER_FREQ_HZ); + TEST_ASSERT(i2c_device1 != NULL); + i2c_bus_write_bytes(i2c_device1, NULL_I2C_MEM_ADDR, RW_TEST_LENGTH, data_wr); + vTaskDelay(300 / portTICK_RATE_MS); + + i2c_bus_device_handle_t i2c_device2 = i2c_bus_device_create(i2c_bus, 0x02, 40 * 100); + TEST_ASSERT(i2c_device2 != NULL); + i2c_bus_write_bytes(i2c_device2, NULL_I2C_MEM_ADDR, RW_TEST_LENGTH, data_wr); + i2c_bus_device_delete(&i2c_device1); + TEST_ASSERT(i2c_device1 == NULL); + i2c_bus_device_delete(&i2c_device2); + TEST_ASSERT(i2c_device2 == NULL); + free(data_wr); + TEST_ASSERT(ESP_OK == i2c_bus_delete(&i2c_bus)); + TEST_ASSERT(i2c_bus == NULL); +} + +#endif + static void check_leak(size_t before_free, size_t after_free, const char *type) { ssize_t delta = after_free - before_free; diff --git a/docs/en/basic/bus/i2c_bus.rst b/docs/en/basic/bus/i2c_bus.rst index c123e75d9..254a3a220 100644 --- a/docs/en/basic/bus/i2c_bus.rst +++ b/docs/en/basic/bus/i2c_bus.rst @@ -45,7 +45,7 @@ Example: 1. When the address of a register is 16-bit, you can use :cpp:func:`i2c_bus_read_reg16` or :cpp:func:`i2c_bus_write_reg16` to read or write its data; 2. For devices that need to skip the address phase or need to add a command phase, you can operate using :cpp:func:`i2c_bus_cmd_begin` combined with `I2C command link `_. - + 3. For scenarios where I2C ports are insufficient or software I2C debugging is required, you can enable ``Enable software I2C support`` in ``menuconfig`` under ``(Top) → Component config → Bus Options → I2C Bus Options``. Then, pass a ``port`` of type ``i2c_sw_port_t`` (e.g., ``I2C_NUM_SW_0``) to the ``port`` parameter in the :cpp:func:`i2c_bus_create` function. Adapted IDF Versions --------------------------- diff --git a/docs/zh_CN/basic/bus/i2c_bus.rst b/docs/zh_CN/basic/bus/i2c_bus.rst index b4e46aa60..476bc1834 100644 --- a/docs/zh_CN/basic/bus/i2c_bus.rst +++ b/docs/zh_CN/basic/bus/i2c_bus.rst @@ -45,6 +45,7 @@ i2c_bus 的使用方法: 1. 当寄存器地址为 16 位时,可以使用 :cpp:func:`i2c_bus_read_reg16` 或 :cpp:func:`i2c_bus_write_reg16` 进行读写操作; 2. 对于需要跳过地址阶段或者需要增加命令阶段的设备,可以使用 :cpp:func:`i2c_bus_cmd_begin` 结合 `I2C command link `_ 进行操作。 + 3. 对于 I2C 端口不足或者需要软件 I2C 调试的场景下,可以使用 ``menuconfig`` 在 ``(Top) → Component config → Bus Options → I2C Bus Options`` 中开启 ``Enable software I2C support``,并在 :cpp:func:`i2c_bus_create` 的 ``port`` 中传入 ``i2c_sw_port_t`` 类型的 ``port``,例如 ``I2C_NUM_SW_0``。 已适配 IDF 版本