diff --git a/components/esp_driver_i2c/i2c_master.c b/components/esp_driver_i2c/i2c_master.c index 657f433d18..6ca731b055 100644 --- a/components/esp_driver_i2c/i2c_master.c +++ b/components/esp_driver_i2c/i2c_master.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -219,14 +219,30 @@ static bool s_i2c_write_command(i2c_master_bus_handle_t i2c_master, i2c_operatio i2c_master->async_break = true; } } else { - i2c_master->cmd_idx++; - i2c_master->trans_idx++; - i2c_master->i2c_trans.cmd_count--; - if (i2c_master->async_trans == false) { - if (xPortInIsrContext()) { - xSemaphoreGiveFromISR(i2c_master->cmd_semphr, do_yield); + // Handle consecutive i2c write operations + i2c_operation_t next_transaction = i2c_master->i2c_trans.ops[i2c_master->trans_idx + 1]; + if (next_transaction.hw_cmd.op_code == I2C_LL_CMD_WRITE) { + portENTER_CRITICAL_SAFE(&handle->spinlock); + i2c_ll_master_write_cmd_reg(hal->dev, hw_end_cmd, i2c_master->cmd_idx + 1); + portEXIT_CRITICAL_SAFE(&handle->spinlock); + i2c_master->cmd_idx = 0; + i2c_master->trans_idx++; + i2c_master->i2c_trans.cmd_count--; + if (i2c_master->async_trans == false) { + i2c_hal_master_trans_start(hal); } else { - xSemaphoreGive(i2c_master->cmd_semphr); + i2c_master->async_break = true; + } + } else { + i2c_master->cmd_idx++; + i2c_master->trans_idx++; + i2c_master->i2c_trans.cmd_count--; + if (i2c_master->async_trans == false) { + if (xPortInIsrContext()) { + xSemaphoreGiveFromISR(i2c_master->cmd_semphr, do_yield); + } else { + xSemaphoreGive(i2c_master->cmd_semphr); + } } } } @@ -267,7 +283,7 @@ static bool s_i2c_read_command(i2c_master_bus_handle_t i2c_master, i2c_operation i2c_master->contains_read = true; #if !SOC_I2C_STOP_INDEPENDENT if (remaining_bytes < I2C_FIFO_LEN(i2c_master->base->port_num) - 1) { - if (i2c_operation->hw_cmd.ack_val == ACK_VAL) { + if (i2c_operation->hw_cmd.ack_val == I2C_ACK_VAL) { if (remaining_bytes != 0) { i2c_ll_master_write_cmd_reg(hal->dev, hw_cmd, i2c_master->cmd_idx); i2c_master->read_len_static = i2c_master->rx_cnt; @@ -303,7 +319,7 @@ static bool s_i2c_read_command(i2c_master_bus_handle_t i2c_master, i2c_operation portENTER_CRITICAL_SAFE(&handle->spinlock); // If the read command work with ack_val, but no bytes to read, we skip // this command, and run next command directly. - if (hw_cmd.ack_val == ACK_VAL) { + if (hw_cmd.ack_val == I2C_ACK_VAL) { if (i2c_operation->total_bytes == 0) { i2c_master->trans_idx++; hw_cmd = i2c_master->i2c_trans.ops[i2c_master->trans_idx].hw_cmd; @@ -347,17 +363,23 @@ static void s_i2c_start_end_command(i2c_master_bus_handle_t i2c_master, i2c_oper uint8_t cmd_address = i2c_master->i2c_trans.device_address; uint8_t addr_byte = 1; #endif + if (i2c_master->i2c_trans.device_address == I2C_DEVICE_ADDRESS_NOT_USED) { + // Bypass the address. + addr_byte = 0; + } uint8_t addr_write[addr_byte]; uint8_t addr_read[addr_byte]; - addr_write[0] = I2C_ADDRESS_TRANS_WRITE(cmd_address); - addr_read[0] = I2C_ADDRESS_TRANS_READ(cmd_address); + if (addr_byte != 0) { + addr_write[0] = I2C_ADDRESS_TRANS_WRITE(cmd_address); + addr_read[0] = I2C_ADDRESS_TRANS_READ(cmd_address); #if SOC_I2C_SUPPORT_10BIT_ADDR - if (i2c_master->addr_10bits_bus == I2C_ADDR_BIT_LEN_10) { - addr_write[1] = i2c_master->i2c_trans.device_address & 0xff; - addr_read[1] = i2c_master->i2c_trans.device_address & 0xff; - } + if (i2c_master->addr_10bits_bus == I2C_ADDR_BIT_LEN_10) { + addr_write[1] = i2c_master->i2c_trans.device_address & 0xff; + addr_read[1] = i2c_master->i2c_trans.device_address & 0xff; + } #endif + } portENTER_CRITICAL_SAFE(&i2c_master->base->spinlock); i2c_ll_master_write_cmd_reg(hal->dev, hw_cmd, i2c_master->cmd_idx); @@ -1147,8 +1169,8 @@ esp_err_t i2c_master_transmit_receive(i2c_master_dev_handle_t i2c_dev, const uin {.hw_cmd = I2C_TRANS_START_COMMAND}, {.hw_cmd = I2C_TRANS_WRITE_COMMAND(i2c_dev->ack_check_disable ? false : true), .data = (uint8_t *)write_buffer, .total_bytes = write_size}, {.hw_cmd = I2C_TRANS_START_COMMAND}, - {.hw_cmd = I2C_TRANS_READ_COMMAND(ACK_VAL), .data = read_buffer, .total_bytes = read_size - 1}, - {.hw_cmd = I2C_TRANS_READ_COMMAND(NACK_VAL), .data = (read_buffer + read_size - 1), .total_bytes = 1}, + {.hw_cmd = I2C_TRANS_READ_COMMAND(I2C_ACK_VAL), .data = read_buffer, .total_bytes = read_size - 1}, + {.hw_cmd = I2C_TRANS_READ_COMMAND(I2C_NACK_VAL), .data = (read_buffer + read_size - 1), .total_bytes = 1}, {.hw_cmd = I2C_TRANS_STOP_COMMAND}, }; @@ -1167,8 +1189,8 @@ esp_err_t i2c_master_receive(i2c_master_dev_handle_t i2c_dev, uint8_t *read_buff i2c_operation_t i2c_ops[] = { {.hw_cmd = I2C_TRANS_START_COMMAND}, - {.hw_cmd = I2C_TRANS_READ_COMMAND(ACK_VAL), .data = read_buffer, .total_bytes = read_size - 1}, - {.hw_cmd = I2C_TRANS_READ_COMMAND(NACK_VAL), .data = (read_buffer + read_size - 1), .total_bytes = 1}, + {.hw_cmd = I2C_TRANS_READ_COMMAND(I2C_ACK_VAL), .data = read_buffer, .total_bytes = read_size - 1}, + {.hw_cmd = I2C_TRANS_READ_COMMAND(I2C_NACK_VAL), .data = (read_buffer + read_size - 1), .total_bytes = 1}, {.hw_cmd = I2C_TRANS_STOP_COMMAND}, }; @@ -1234,6 +1256,54 @@ esp_err_t i2c_master_probe(i2c_master_bus_handle_t bus_handle, uint16_t address, return ret; } +esp_err_t i2c_master_execute_defined_operations(i2c_master_dev_handle_t i2c_dev, i2c_operation_job_t *i2c_operation, size_t operation_list_num, int xfer_timeout_ms) +{ + ESP_RETURN_ON_FALSE(i2c_dev != NULL, ESP_ERR_INVALID_ARG, TAG, "i2c handle not initialized"); + ESP_RETURN_ON_FALSE(i2c_operation != NULL, ESP_ERR_INVALID_ARG, TAG, "i2c operation pointer is invalid"); + ESP_RETURN_ON_FALSE(operation_list_num <= (SOC_I2C_CMD_REG_NUM), ESP_ERR_INVALID_ARG, TAG, "i2c command list cannot contain so many commands"); + + i2c_operation_t i2c_ops[operation_list_num] = {}; + for (int i = 0; i < operation_list_num; i++) { + switch (i2c_operation[i].command) { + case I2C_MASTER_CMD_START: + i2c_ops[i].hw_cmd.op_code = I2C_LL_CMD_RESTART; + break; + case I2C_MASTER_CMD_WRITE: + i2c_ops[i].hw_cmd.op_code = I2C_LL_CMD_WRITE; + i2c_ops[i].hw_cmd.ack_en = i2c_operation[i].write.ack_check; + i2c_ops[i].data = i2c_operation[i].write.data; + i2c_ops[i].total_bytes = i2c_operation[i].write.total_bytes; + break; + case I2C_MASTER_CMD_READ: + i2c_ops[i].hw_cmd.op_code = I2C_LL_CMD_READ; + i2c_ops[i].hw_cmd.ack_val = i2c_operation[i].read.ack_value; + i2c_ops[i].data = i2c_operation[i].read.data; + i2c_ops[i].total_bytes = i2c_operation[i].read.total_bytes; + // Add check: If current command is READ and the next command is STOP, ack_value must be NACK + if (i + 1 < operation_list_num && i2c_operation[i + 1].command == I2C_MASTER_CMD_STOP) { + if (i2c_operation[i].read.ack_value != I2C_NACK_VAL) { + ESP_LOGE(TAG, "ack_value must be NACK (1) when the next command of READ is STOP."); + return ESP_ERR_INVALID_ARG; + } + } + break; + case I2C_MASTER_CMD_STOP: + i2c_ops[i].hw_cmd.op_code = I2C_LL_CMD_STOP; + break; + default: + ESP_LOGE(TAG, "Invalid command."); + return ESP_ERR_INVALID_ARG; + } + } + + if (i2c_dev->master_bus->async_trans == false) { + ESP_RETURN_ON_ERROR(s_i2c_synchronous_transaction(i2c_dev, i2c_ops, operation_list_num, xfer_timeout_ms), TAG, "I2C transaction failed"); + } else { + ESP_RETURN_ON_ERROR(s_i2c_asynchronous_transaction(i2c_dev, i2c_ops, operation_list_num, xfer_timeout_ms), TAG, "I2C transaction failed"); + } + return ESP_OK; +} + esp_err_t i2c_master_register_event_callbacks(i2c_master_dev_handle_t i2c_dev, const i2c_master_event_callbacks_t *cbs, void *user_data) { ESP_RETURN_ON_FALSE(i2c_dev != NULL, ESP_ERR_INVALID_ARG, TAG, "i2c handle not initialized"); diff --git a/components/esp_driver_i2c/i2c_private.h b/components/esp_driver_i2c/i2c_private.h index 4eaad6be84..0751d5dddd 100644 --- a/components/esp_driver_i2c/i2c_private.h +++ b/components/esp_driver_i2c/i2c_private.h @@ -59,9 +59,6 @@ extern "C" { #define I2C_PM_LOCK_NAME_LEN_MAX 16 #define I2C_STATIC_OPERATION_ARRAY_MAX SOC_I2C_CMD_REG_NUM -#define ACK_VAL 0 -#define NACK_VAL 1 - #define I2C_TRANS_READ_COMMAND(ack_value) {.ack_val = (ack_value), .op_code = I2C_LL_CMD_READ} #define I2C_TRANS_WRITE_COMMAND(ack_check) {.ack_en = (ack_check), .op_code = I2C_LL_CMD_WRITE} #define I2C_TRANS_STOP_COMMAND {.op_code = I2C_LL_CMD_STOP} diff --git a/components/esp_driver_i2c/include/driver/i2c_master.h b/components/esp_driver_i2c/include/driver/i2c_master.h index 2e497de0c6..65cb84d1eb 100644 --- a/components/esp_driver_i2c/include/driver/i2c_master.h +++ b/components/esp_driver_i2c/include/driver/i2c_master.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -36,12 +36,14 @@ typedef struct { } flags; /*!< I2C master config flags */ } i2c_master_bus_config_t; +#define I2C_DEVICE_ADDRESS_NOT_USED (0xffff) /*!< Skip carry address bit in driver transmit and receive */ + /** * @brief I2C device configuration */ typedef struct { i2c_addr_bit_len_t dev_addr_length; /*!< Select the address length of the slave device. */ - uint16_t device_address; /*!< I2C device raw address. (The 7/10 bit address without read/write bit) */ + uint16_t device_address; /*!< I2C device raw address. (The 7/10 bit address without read/write bit). Macro I2C_DEVICE_ADDRESS_NOT_USED (0xFFFF) stands for skip the address config inside driver. */ uint32_t scl_speed_hz; /*!< I2C SCL line frequency. */ uint32_t scl_wait_us; /*!< Timeout value. (unit: us). Please note this value should not be so small that it can handle stretch/disturbance properly. If 0 is set, that means use the default reg value*/ struct { @@ -49,6 +51,38 @@ typedef struct { } flags; /*!< I2C device config flags */ } i2c_device_config_t; +/** + * @brief Structure representing an I2C operation job + * + * This structure is used to define individual I2C operations (write or read) + * within a sequence of I2C master transactions. + */ +typedef struct { + i2c_master_command_t command; /**< I2C command indicating the type of operation (START, WRITE, READ, or STOP) */ + union { + /** + * @brief Structure for WRITE command + * + * Used when the `command` is set to `I2C_MASTER_CMD_WRITE`. + */ + struct { + bool ack_check; /**< Whether to enable ACK check during WRITE operation */ + uint8_t *data; /**< Pointer to the data to be written */ + size_t total_bytes; /**< Total number of bytes to write */ + } write; + /** + * @brief Structure for READ command + * + * Used when the `command` is set to `I2C_MASTER_CMD_READ`. + */ + struct { + i2c_ack_value_t ack_value; /**< ACK value to send after the read (ACK or NACK) */ + uint8_t *data; /**< Pointer to the buffer for storing the data read from the bus */ + size_t total_bytes; /**< Total number of bytes to read */ + } read; + }; +} i2c_operation_job_t; + /** * @brief Group of I2C master callbacks, can be used to get status during transaction or doing other small things. But take care potential concurrency issues. * @note The callbacks are all running under ISR context @@ -189,6 +223,30 @@ esp_err_t i2c_master_receive(i2c_master_dev_handle_t i2c_dev, uint8_t *read_buff */ esp_err_t i2c_master_probe(i2c_master_bus_handle_t bus_handle, uint16_t address, int xfer_timeout_ms); +/** + * @brief Execute a series of pre-defined I2C operations. + * + * This function processes a list of I2C operations, such as start, write, read, and stop, + * according to the user-defined `i2c_operation_job_t` array. It performs these operations + * sequentially on the specified I2C master device. + * + * @param[in] i2c_dev Handle to the I2C master device. + * @param[in] i2c_operation Pointer to an array of user-defined I2C operation jobs. + * Each job specifies a command and associated parameters. + * @param[in] operation_list_num The number of operations in the `i2c_operation` array. + * @param[in] xfer_timeout_ms Timeout for the transaction, in milliseconds. + * + * @return + * - ESP_OK: Transaction completed successfully. + * - ESP_ERR_INVALID_ARG: One or more arguments are invalid. + * - ESP_ERR_TIMEOUT: Transaction timed out. + * - ESP_FAIL: Other error during transaction. + * + * @note The `ack_value` field in the READ operation must be set to `I2C_NACK_VAL` if the next + * operation is a STOP command. + */ +esp_err_t i2c_master_execute_defined_operations(i2c_master_dev_handle_t i2c_dev, i2c_operation_job_t *i2c_operation, size_t operation_list_num, int xfer_timeout_ms); + /** * @brief Register I2C transaction callbacks for a master device * diff --git a/components/esp_driver_i2c/include/driver/i2c_types.h b/components/esp_driver_i2c/include/driver/i2c_types.h index 9010309e8f..4db39ba97d 100644 --- a/components/esp_driver_i2c/include/driver/i2c_types.h +++ b/components/esp_driver_i2c/include/driver/i2c_types.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -44,6 +44,29 @@ typedef enum { I2C_EVENT_TIMEOUT, /*!< i2c bus timeout */ } i2c_master_event_t; +/** + * @brief Enum for I2C master commands + * + * These commands are used to define the I2C master operations. + * They correspond to hardware-level commands supported by the I2C peripheral. + */ +typedef enum { + I2C_MASTER_CMD_START, /**< Start or Restart condition */ + I2C_MASTER_CMD_WRITE, /**< Write operation */ + I2C_MASTER_CMD_READ, /**< Read operation */ + I2C_MASTER_CMD_STOP, /**< Stop condition */ +} i2c_master_command_t; + +/** + * @brief Enum for I2C master ACK values + * + * These values define the acknowledgment (ACK) behavior during read operations. + */ +typedef enum { + I2C_ACK_VAL = 0, /**< Acknowledge (ACK) signal */ + I2C_NACK_VAL = 1, /**< Not Acknowledge (NACK) signal */ +} __attribute__((packed)) i2c_ack_value_t; + /** * @brief Type of I2C master bus handle */ diff --git a/components/esp_driver_i2c/test_apps/i2c_test_apps/main/test_i2c_multi.c b/components/esp_driver_i2c/test_apps/i2c_test_apps/main/test_i2c_multi.c index 652bd53793..9cdfe617eb 100644 --- a/components/esp_driver_i2c/test_apps/i2c_test_apps/main/test_i2c_multi.c +++ b/components/esp_driver_i2c/test_apps/i2c_test_apps/main/test_i2c_multi.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Unlicense OR CC0-1.0 */ @@ -785,3 +785,94 @@ static void uart_test_i2c_master_freq(void) TEST_CASE_MULTIPLE_DEVICES("I2C master clock frequency test", "[i2c][test_env=generic_multi_device][timeout=150]", uart_test_i2c_master_freq, i2c_master_write_fsm_reset); #endif // CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3 + +static void i2c_master_write_test_with_customize_api(void) +{ + uint8_t data_wr[DATA_LENGTH] = { 0 }; + int i; + + i2c_master_bus_config_t i2c_mst_config = { + .clk_source = I2C_CLK_SRC_DEFAULT, + .i2c_port = TEST_I2C_PORT, + .scl_io_num = I2C_MASTER_SCL_IO, + .sda_io_num = I2C_MASTER_SDA_IO, + .flags.enable_internal_pullup = true, + }; + i2c_master_bus_handle_t bus_handle; + + TEST_ESP_OK(i2c_new_master_bus(&i2c_mst_config, &bus_handle)); + + i2c_device_config_t dev_cfg = { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = 0xFFFF, + .scl_speed_hz = 100000, + }; + + i2c_master_dev_handle_t dev_handle; + TEST_ESP_OK(i2c_master_bus_add_device(bus_handle, &dev_cfg, &dev_handle)); + + unity_wait_for_signal("i2c slave init finish"); + + unity_send_signal("master write"); + for (i = 0; i < DATA_LENGTH; i++) { + data_wr[i] = i; + } + + disp_buf(data_wr, i); + + uint8_t address = (0x58 << 1 | 0); + + i2c_operation_job_t i2c_ops[] = { + { .command = I2C_MASTER_CMD_START }, + { .command = I2C_MASTER_CMD_WRITE, .write = { .ack_check = true, .data = (uint8_t *) &address, .total_bytes = 1 } }, + { .command = I2C_MASTER_CMD_WRITE, .write = { .ack_check = true, .data = (uint8_t *) data_wr, .total_bytes = DATA_LENGTH } }, + { .command = I2C_MASTER_CMD_STOP }, + }; + + TEST_ESP_OK(i2c_master_execute_defined_operations(dev_handle, i2c_ops, sizeof(i2c_ops) / sizeof(i2c_operation_job_t), -1)); + unity_wait_for_signal("ready to delete"); + TEST_ESP_OK(i2c_master_bus_rm_device(dev_handle)); + + TEST_ESP_OK(i2c_del_master_bus(bus_handle)); +} + +static void i2c_slave_read_test_v1(void) +{ + uint8_t data_rd[DATA_LENGTH] = {0}; + + i2c_slave_config_t i2c_slv_config = { + .addr_bit_len = I2C_ADDR_BIT_LEN_7, + .clk_source = I2C_CLK_SRC_DEFAULT, + .i2c_port = TEST_I2C_PORT, + .send_buf_depth = 256, + .scl_io_num = I2C_SLAVE_SCL_IO, + .sda_io_num = I2C_SLAVE_SDA_IO, + .slave_addr = 0x58, + }; + + i2c_slave_dev_handle_t slave_handle; + TEST_ESP_OK(i2c_new_slave_device(&i2c_slv_config, &slave_handle)); + + s_receive_queue = xQueueCreate(1, sizeof(i2c_slave_rx_done_event_data_t)); + i2c_slave_event_callbacks_t cbs = { + .on_recv_done = test_i2c_rx_done_callback, + }; + ESP_ERROR_CHECK(i2c_slave_register_event_callbacks(slave_handle, &cbs, s_receive_queue)); + + i2c_slave_rx_done_event_data_t rx_data; + TEST_ESP_OK(i2c_slave_receive(slave_handle, data_rd, DATA_LENGTH)); + + unity_send_signal("i2c slave init finish"); + + unity_wait_for_signal("master write"); + xQueueReceive(s_receive_queue, &rx_data, pdMS_TO_TICKS(10000)); + disp_buf(data_rd, DATA_LENGTH); + for (int i = 0; i < DATA_LENGTH; i++) { + TEST_ASSERT(data_rd[i] == i); + } + vQueueDelete(s_receive_queue); + unity_send_signal("ready to delete"); + TEST_ESP_OK(i2c_del_slave_device(slave_handle)); +} + +TEST_CASE_MULTIPLE_DEVICES("I2C master write slave with customize api", "[i2c][test_env=generic_multi_device][timeout=150]", i2c_master_write_test_with_customize_api, i2c_slave_read_test_v1);