feat(i2c): Add new API and implementation for I2C driver
This commit is contained in:
@@ -91,7 +91,10 @@ endif()
|
||||
|
||||
# I2C related source files
|
||||
if(CONFIG_SOC_I2C_SUPPORTED)
|
||||
list(APPEND srcs "i2c/i2c.c")
|
||||
list(APPEND srcs "i2c/i2c.c"
|
||||
"i2c/i2c_master.c"
|
||||
"i2c/i2c_common.c"
|
||||
)
|
||||
endif()
|
||||
|
||||
# I2S related source files
|
||||
@@ -216,7 +219,7 @@ else()
|
||||
INCLUDE_DIRS ${includes}
|
||||
PRIV_REQUIRES efuse esp_timer
|
||||
REQUIRES esp_pm esp_ringbuf freertos soc hal esp_hw_support
|
||||
LDFRAGMENTS linker.lf gptimer/linker.lf gpio/linker.lf twai/linker.lf)
|
||||
LDFRAGMENTS linker.lf gptimer/linker.lf gpio/linker.lf twai/linker.lf i2c/linker.lf)
|
||||
endif()
|
||||
|
||||
# If system needs to monitor USJ connection status, then usb_serial_jtag_connection_monitor object file has to be linked
|
||||
|
||||
@@ -486,4 +486,24 @@ menu "Driver Configurations"
|
||||
Enabling this option can improve driver performance as well.
|
||||
endmenu # LEDC Configuration
|
||||
|
||||
menu "I2C Configuration"
|
||||
config I2C_ISR_IRAM_SAFE
|
||||
bool "I2C ISR IRAM-Safe"
|
||||
default n
|
||||
help
|
||||
Ensure the I2C interrupt is IRAM-Safe by allowing the interrupt handler to be
|
||||
executable when the cache is disabled (e.g. SPI Flash write).
|
||||
note: This cannot be used in the I2C legacy driver.
|
||||
|
||||
config I2C_ENABLE_DEBUG_LOG
|
||||
bool "Enable I2C debug log"
|
||||
default n
|
||||
help
|
||||
Wether to enable the debug log message for I2C driver.
|
||||
Note that this option only controls the I2C driver log, will not affect other drivers.
|
||||
|
||||
note: This cannot be used in the I2C legacy driver.
|
||||
|
||||
endmenu # I2C Configurations
|
||||
|
||||
endmenu # Driver configurations
|
||||
|
||||
+30
-14
@@ -632,7 +632,7 @@ static esp_err_t i2c_master_clear_bus(i2c_port_t i2c_num)
|
||||
gpio_set_level(sda_io, 1); // STOP, SDA low -> high while SCL is HIGH
|
||||
i2c_set_pin(i2c_num, sda_io, scl_io, 1, 1, I2C_MODE_MASTER);
|
||||
#else
|
||||
i2c_ll_master_clr_bus(i2c_context[i2c_num].hal.dev);
|
||||
i2c_ll_master_clr_bus(i2c_context[i2c_num].hal.dev, I2C_CLR_BUS_SCL_NUM);
|
||||
#endif
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -650,7 +650,7 @@ static esp_err_t i2c_hw_fsm_reset(i2c_port_t i2c_num)
|
||||
uint8_t filter_cfg;
|
||||
|
||||
i2c_hal_get_timing_config(&i2c_context[i2c_num].hal, &timing_config);
|
||||
i2c_ll_get_filter(i2c_context[i2c_num].hal.dev, &filter_cfg);
|
||||
i2c_ll_master_get_filter(i2c_context[i2c_num].hal.dev, &filter_cfg);
|
||||
|
||||
//to reset the I2C hw module, we need re-enable the hw
|
||||
i2c_hw_disable(i2c_num);
|
||||
@@ -661,7 +661,7 @@ static esp_err_t i2c_hw_fsm_reset(i2c_port_t i2c_num)
|
||||
i2c_ll_disable_intr_mask(i2c_context[i2c_num].hal.dev, I2C_LL_INTR_MASK);
|
||||
i2c_ll_clear_intr_mask(i2c_context[i2c_num].hal.dev, I2C_LL_INTR_MASK);
|
||||
i2c_hal_set_timing_config(&i2c_context[i2c_num].hal, &timing_config);
|
||||
i2c_ll_set_filter(i2c_context[i2c_num].hal.dev, filter_cfg);
|
||||
i2c_ll_master_set_filter(i2c_context[i2c_num].hal.dev, filter_cfg);
|
||||
#else
|
||||
i2c_ll_master_fsm_rst(i2c_context[i2c_num].hal.dev);
|
||||
i2c_master_clear_bus(i2c_num);
|
||||
@@ -767,7 +767,7 @@ esp_err_t i2c_param_config(i2c_port_t i2c_num, const i2c_config_t *i2c_conf)
|
||||
{
|
||||
i2c_hal_master_init(&(i2c_context[i2c_num].hal));
|
||||
//Default, we enable hardware filter
|
||||
i2c_ll_set_filter(i2c_context[i2c_num].hal.dev, I2C_FILTER_CYC_NUM_DEF);
|
||||
i2c_ll_master_set_filter(i2c_context[i2c_num].hal.dev, I2C_FILTER_CYC_NUM_DEF);
|
||||
i2c_hal_set_bus_timing(&(i2c_context[i2c_num].hal), i2c_conf->master.clk_speed, src_clk, s_get_src_clk_freq(src_clk));
|
||||
}
|
||||
i2c_ll_update(i2c_context[i2c_num].hal.dev);
|
||||
@@ -802,7 +802,7 @@ esp_err_t i2c_filter_enable(i2c_port_t i2c_num, uint8_t cyc_num)
|
||||
ESP_RETURN_ON_FALSE(i2c_num < I2C_NUM_MAX, ESP_ERR_INVALID_ARG, I2C_TAG, I2C_NUM_ERROR_STR);
|
||||
ESP_RETURN_ON_FALSE(p_i2c_obj[i2c_num] != NULL, ESP_FAIL, I2C_TAG, I2C_DRIVER_ERR_STR);
|
||||
I2C_ENTER_CRITICAL(&(i2c_context[i2c_num].spinlock));
|
||||
i2c_ll_set_filter(i2c_context[i2c_num].hal.dev, cyc_num);
|
||||
i2c_ll_master_set_filter(i2c_context[i2c_num].hal.dev, cyc_num);
|
||||
i2c_ll_update(i2c_context[i2c_num].hal.dev);
|
||||
I2C_EXIT_CRITICAL(&(i2c_context[i2c_num].spinlock));
|
||||
return ESP_OK;
|
||||
@@ -812,7 +812,7 @@ esp_err_t i2c_filter_disable(i2c_port_t i2c_num)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(i2c_num < I2C_NUM_MAX, ESP_ERR_INVALID_ARG, I2C_TAG, I2C_NUM_ERROR_STR);
|
||||
I2C_ENTER_CRITICAL(&(i2c_context[i2c_num].spinlock));
|
||||
i2c_ll_set_filter(i2c_context[i2c_num].hal.dev, 0);
|
||||
i2c_ll_master_set_filter(i2c_context[i2c_num].hal.dev, 0);
|
||||
i2c_ll_update(i2c_context[i2c_num].hal.dev);
|
||||
I2C_EXIT_CRITICAL(&(i2c_context[i2c_num].spinlock));
|
||||
return ESP_OK;
|
||||
@@ -825,7 +825,7 @@ esp_err_t i2c_set_start_timing(i2c_port_t i2c_num, int setup_time, int hold_time
|
||||
ESP_RETURN_ON_FALSE((setup_time <= I2C_SCL_RSTART_SETUP_TIME_V) && (setup_time > 0), ESP_ERR_INVALID_ARG, I2C_TAG, I2C_TIMING_VAL_ERR_STR);
|
||||
|
||||
I2C_ENTER_CRITICAL(&(i2c_context[i2c_num].spinlock));
|
||||
i2c_ll_set_start_timing(i2c_context[i2c_num].hal.dev, setup_time, hold_time);
|
||||
i2c_ll_master_set_start_timing(i2c_context[i2c_num].hal.dev, setup_time, hold_time);
|
||||
i2c_ll_update(i2c_context[i2c_num].hal.dev);
|
||||
I2C_EXIT_CRITICAL(&(i2c_context[i2c_num].spinlock));
|
||||
return ESP_OK;
|
||||
@@ -847,7 +847,7 @@ esp_err_t i2c_set_stop_timing(i2c_port_t i2c_num, int setup_time, int hold_time)
|
||||
ESP_RETURN_ON_FALSE((hold_time <= I2C_SCL_STOP_HOLD_TIME_V) && (hold_time > 0), ESP_ERR_INVALID_ARG, I2C_TAG, I2C_TIMING_VAL_ERR_STR);
|
||||
|
||||
I2C_ENTER_CRITICAL(&(i2c_context[i2c_num].spinlock));
|
||||
i2c_ll_set_stop_timing(i2c_context[i2c_num].hal.dev, setup_time, hold_time);
|
||||
i2c_ll_master_set_stop_timing(i2c_context[i2c_num].hal.dev, setup_time, hold_time);
|
||||
i2c_ll_update(i2c_context[i2c_num].hal.dev);
|
||||
I2C_EXIT_CRITICAL(&(i2c_context[i2c_num].spinlock));
|
||||
return ESP_OK;
|
||||
@@ -1411,8 +1411,8 @@ static void IRAM_ATTR i2c_master_cmd_begin_static(i2c_port_t i2c_num, BaseType_t
|
||||
}
|
||||
hw_cmd.byte_num = fifo_fill;
|
||||
i2c_ll_write_txfifo(i2c_context[i2c_num].hal.dev, write_pr, fifo_fill);
|
||||
i2c_ll_write_cmd_reg(i2c_context[i2c_num].hal.dev, hw_cmd, p_i2c->cmd_idx);
|
||||
i2c_ll_write_cmd_reg(i2c_context[i2c_num].hal.dev, hw_end_cmd, p_i2c->cmd_idx + 1);
|
||||
i2c_ll_master_write_cmd_reg(i2c_context[i2c_num].hal.dev, hw_cmd, p_i2c->cmd_idx);
|
||||
i2c_ll_master_write_cmd_reg(i2c_context[i2c_num].hal.dev, hw_end_cmd, p_i2c->cmd_idx + 1);
|
||||
i2c_ll_master_enable_tx_it(i2c_context[i2c_num].hal.dev);
|
||||
p_i2c->cmd_idx = 0;
|
||||
if (i2c_cmd_is_single_byte(cmd) || cmd->total_bytes == cmd->bytes_used) {
|
||||
@@ -1428,13 +1428,13 @@ static void IRAM_ATTR i2c_master_cmd_begin_static(i2c_port_t i2c_num, BaseType_t
|
||||
fifo_fill = MIN(remaining_bytes, SOC_I2C_FIFO_LEN);
|
||||
p_i2c->rx_cnt = fifo_fill;
|
||||
hw_cmd.byte_num = fifo_fill;
|
||||
i2c_ll_write_cmd_reg(i2c_context[i2c_num].hal.dev, hw_cmd, p_i2c->cmd_idx);
|
||||
i2c_ll_write_cmd_reg(i2c_context[i2c_num].hal.dev, hw_end_cmd, p_i2c->cmd_idx + 1);
|
||||
i2c_ll_master_write_cmd_reg(i2c_context[i2c_num].hal.dev, hw_cmd, p_i2c->cmd_idx);
|
||||
i2c_ll_master_write_cmd_reg(i2c_context[i2c_num].hal.dev, hw_end_cmd, p_i2c->cmd_idx + 1);
|
||||
i2c_ll_master_enable_rx_it(i2c_context[i2c_num].hal.dev);
|
||||
p_i2c->status = I2C_STATUS_READ;
|
||||
break;
|
||||
} else {
|
||||
i2c_ll_write_cmd_reg(i2c_context[i2c_num].hal.dev, hw_cmd, p_i2c->cmd_idx);
|
||||
i2c_ll_master_write_cmd_reg(i2c_context[i2c_num].hal.dev, hw_cmd, p_i2c->cmd_idx);
|
||||
}
|
||||
p_i2c->cmd_idx++;
|
||||
p_i2c->cmd_link.head = p_i2c->cmd_link.head->next;
|
||||
@@ -1444,7 +1444,7 @@ static void IRAM_ATTR i2c_master_cmd_begin_static(i2c_port_t i2c_num, BaseType_t
|
||||
}
|
||||
}
|
||||
i2c_ll_update(i2c_context[i2c_num].hal.dev);
|
||||
i2c_ll_trans_start(i2c_context[i2c_num].hal.dev);
|
||||
i2c_ll_master_trans_start(i2c_context[i2c_num].hal.dev);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1643,3 +1643,19 @@ int i2c_slave_read_buffer(i2c_port_t i2c_num, uint8_t *data, size_t max_size, Ti
|
||||
return max_size - size_rem;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief This function will be called during start up, to check that this legacy i2c driver is not running along with the new I2C driver
|
||||
*/
|
||||
__attribute__((constructor))
|
||||
static void check_i2c_driver_conflict(void)
|
||||
{
|
||||
// This function was declared as weak here. The new I2C driver has the implementation.
|
||||
// So if the new I2C driver is not linked in, then `i2c_acquire_bus_handle()` should be NULL at runtime.
|
||||
extern __attribute__((weak)) esp_err_t i2c_acquire_bus_handle(int port_num, void *i2c_new_bus, int mode);
|
||||
if ((void *)i2c_acquire_bus_handle != NULL) {
|
||||
ESP_EARLY_LOGE(I2C_TAG, "CONFLICT! driver_ng is not allowed to be used with this old driver");
|
||||
abort();
|
||||
}
|
||||
ESP_EARLY_LOGW(I2C_TAG, "This driver is an old driver, please migrate your application code to adapt `driver/i2c_master.h`");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "esp_types.h"
|
||||
#if CONFIG_I2C_ENABLE_DEBUG_LOG
|
||||
// The local log level must be defined before including esp_log.h
|
||||
// Set the maximum log level for this source file
|
||||
#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
|
||||
#endif
|
||||
#include "esp_log.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_pm.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "hal/i2c_hal.h"
|
||||
#include "hal/gpio_hal.h"
|
||||
#include "esp_private/periph_ctrl.h"
|
||||
#include "esp_rom_gpio.h"
|
||||
#include "i2c_private.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "soc/clk_tree_defs.h"
|
||||
#include "soc/i2c_periph.h"
|
||||
#include "esp_clk_tree.h"
|
||||
#include "clk_ctrl_os.h"
|
||||
|
||||
static const char *TAG = "i2c.common";
|
||||
|
||||
typedef struct i2c_platform_t {
|
||||
_lock_t mutex; // platform level mutex lock.
|
||||
i2c_bus_handle_t buses[SOC_I2C_NUM]; // array of I2C bus instances.
|
||||
uint32_t count[SOC_I2C_NUM]; // reference count used to protect group install/uninstall.
|
||||
} i2c_platform_t;
|
||||
|
||||
static i2c_platform_t s_i2c_platform = {}; // singleton platform
|
||||
|
||||
esp_err_t i2c_acquire_bus_handle(i2c_port_num_t port_num, i2c_bus_handle_t *i2c_new_bus, i2c_bus_mode_t mode)
|
||||
{
|
||||
#if CONFIG_I2C_ENABLE_DEBUG_LOG
|
||||
esp_log_level_set(TAG, ESP_LOG_DEBUG);
|
||||
#endif
|
||||
bool new_bus = false;
|
||||
i2c_bus_t *bus = NULL;
|
||||
esp_err_t ret = ESP_OK;
|
||||
|
||||
if (!s_i2c_platform.buses[port_num]) {
|
||||
new_bus = true;
|
||||
bus = heap_caps_calloc(1, sizeof(i2c_bus_t), I2C_MEM_ALLOC_CAPS);
|
||||
if (bus) {
|
||||
s_i2c_platform.buses[port_num] = bus;
|
||||
bus->port_num = port_num;
|
||||
bus->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
|
||||
bus->bus_mode = mode;
|
||||
|
||||
// Enable the I2C module
|
||||
periph_module_enable(i2c_periph_signal[port_num].module);
|
||||
periph_module_reset(i2c_periph_signal[port_num].module);
|
||||
i2c_hal_init(&bus->hal, port_num);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "I2C bus id(%d) has already been acquired", port_num);
|
||||
bus = s_i2c_platform.buses[port_num];
|
||||
ret = ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
if (bus) {
|
||||
s_i2c_platform.count[port_num]++;
|
||||
}
|
||||
|
||||
if (new_bus) {
|
||||
ESP_LOGD(TAG, "new bus(%d) at %p", port_num, bus);
|
||||
}
|
||||
*i2c_new_bus = bus;
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool i2c_bus_occupied(i2c_port_num_t port_num)
|
||||
{
|
||||
bool bus_occupied = false;
|
||||
_lock_acquire(&s_i2c_platform.mutex);
|
||||
if (s_i2c_platform.buses[port_num]) {
|
||||
bus_occupied = true;
|
||||
}
|
||||
_lock_release(&s_i2c_platform.mutex);
|
||||
return bus_occupied;
|
||||
}
|
||||
|
||||
uint8_t i2c_release_bus_handle(i2c_bus_handle_t i2c_bus)
|
||||
{
|
||||
int port_num = i2c_bus->port_num;
|
||||
i2c_clock_source_t clk_src = i2c_bus->clk_src;
|
||||
bool do_deinitialize = false;
|
||||
_lock_acquire(&s_i2c_platform.mutex);
|
||||
if (s_i2c_platform.buses[port_num]) {
|
||||
s_i2c_platform.count[port_num]--;
|
||||
if (s_i2c_platform.count[port_num] == 0) {
|
||||
do_deinitialize = true;
|
||||
s_i2c_platform.buses[port_num] = NULL;
|
||||
|
||||
// Disable I2C module
|
||||
periph_module_disable(i2c_periph_signal[port_num].module);
|
||||
free(i2c_bus);
|
||||
}
|
||||
}
|
||||
_lock_release(&s_i2c_platform.mutex);
|
||||
|
||||
switch (clk_src) {
|
||||
#if SOC_I2C_SUPPORT_RTC
|
||||
case I2C_CLK_SRC_RC_FAST:
|
||||
periph_rtc_dig_clk8m_disable();
|
||||
break;
|
||||
#endif // SOC_I2C_SUPPORT_RTC
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (do_deinitialize) {
|
||||
ESP_LOGD(TAG, "delete bus %d", port_num);
|
||||
}
|
||||
return s_i2c_platform.count[port_num];
|
||||
}
|
||||
|
||||
esp_err_t i2c_select_periph_clock(i2c_bus_handle_t handle, i2c_clock_source_t clk_src)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "I2C empty controller handle");
|
||||
uint32_t periph_src_clk_hz = 0;
|
||||
bool clock_selection_conflict = 0;
|
||||
|
||||
portENTER_CRITICAL(&handle->spinlock);
|
||||
if (handle->clk_src == 0) {
|
||||
handle->clk_src = clk_src;
|
||||
} else {
|
||||
clock_selection_conflict = (handle->clk_src != clk_src);
|
||||
}
|
||||
portEXIT_CRITICAL(&handle->spinlock);
|
||||
ESP_RETURN_ON_FALSE(!clock_selection_conflict, ESP_ERR_INVALID_STATE, TAG,
|
||||
"group clock conflict, already is %d but attempt to %d", handle->clk_src, clk_src);
|
||||
|
||||
// TODO: [clk_tree] to use a generic clock enable/disable or acquire/release function for all clock source
|
||||
#if SOC_I2C_SUPPORT_RTC
|
||||
if (clk_src == I2C_CLK_SRC_RC_FAST) {
|
||||
// RC_FAST clock is not enabled automatically on start up, we enable it here manually.
|
||||
// Note there's a ref count in the enable/disable function, we must call them in pair in the driver.
|
||||
periph_rtc_dig_clk8m_enable();
|
||||
}
|
||||
#endif // SOC_I2C_SUPPORT_RTC
|
||||
|
||||
ESP_RETURN_ON_ERROR(esp_clk_tree_src_get_freq_hz(clk_src, ESP_CLK_TREE_SRC_FREQ_PRECISION_APPROX, &periph_src_clk_hz), TAG, "i2c get clock frequency error");
|
||||
|
||||
handle->clk_src_freq_hz = periph_src_clk_hz;
|
||||
|
||||
#if CONFIG_PM_ENABLE
|
||||
bool need_pm_lock = true;
|
||||
// to make the I2C work reliable, the source clock must stay alive and unchanged
|
||||
// driver will create different pm lock for that purpose, according to different clock source
|
||||
esp_pm_lock_type_t pm_lock_type = ESP_PM_NO_LIGHT_SLEEP;
|
||||
|
||||
#if SOC_I2C_SUPPORT_RTC
|
||||
if (clk_src == I2C_CLK_SRC_RC_FAST) {
|
||||
// I2C use fifo, which connected to APB, so we cannot use I2C either when in light sleep.
|
||||
need_pm_lock = ESP_PM_NO_LIGHT_SLEEP;
|
||||
}
|
||||
#endif // SOC_I2C_SUPPORT_RTC
|
||||
|
||||
#if SOC_I2C_SUPPORT_APB
|
||||
if (clk_src == I2C_CLK_SRC_APB) {
|
||||
// APB clock frequency can be changed during DFS
|
||||
pm_lock_type = ESP_PM_APB_FREQ_MAX;
|
||||
}
|
||||
#endif // SOC_I2C_SUPPORT_APB
|
||||
|
||||
if (need_pm_lock) {
|
||||
sprintf(handle->pm_lock_name, "I2C_%d", handle->port_num); // e.g. PORT_0
|
||||
ret = esp_pm_lock_create(pm_lock_type, 0, handle->pm_lock_name, &handle->pm_lock);
|
||||
ESP_RETURN_ON_ERROR(ret, TAG, "create pm lock failed");
|
||||
}
|
||||
#endif // CONFIG_PM_ENABLE
|
||||
|
||||
|
||||
ESP_LOGD(TAG, "bus clock source frequency: %"PRIu32"hz", periph_src_clk_hz);
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t i2c_common_set_pins(i2c_bus_handle_t handle)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_ARG, TAG, "I2C empty controller handle");
|
||||
int port_id = handle->port_num;
|
||||
|
||||
// SDA pin configurations
|
||||
gpio_config_t sda_conf = {
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
.mode = GPIO_MODE_INPUT_OUTPUT_OD,
|
||||
.pull_down_en = false,
|
||||
.pull_up_en = handle->pull_up_enable ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE,
|
||||
.pin_bit_mask = 1ULL << handle->sda_num,
|
||||
};
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
// on esp32, must enable internal pull-up
|
||||
sda_conf.pull_up_en = GPIO_PULLUP_ENABLE;
|
||||
#endif
|
||||
ESP_RETURN_ON_ERROR(gpio_config(&sda_conf), TAG, "config GPIO failed");
|
||||
ESP_RETURN_ON_ERROR(gpio_set_level(handle->sda_num, 1), TAG, "i2c sda pin set level failed");
|
||||
gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[handle->sda_num], PIN_FUNC_GPIO);
|
||||
esp_rom_gpio_connect_out_signal(handle->sda_num, i2c_periph_signal[port_id].sda_out_sig, 0, 0);
|
||||
esp_rom_gpio_connect_in_signal(handle->sda_num, i2c_periph_signal[port_id].sda_in_sig, 0);
|
||||
|
||||
|
||||
// SCL pin configurations
|
||||
gpio_config_t scl_conf = {
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
.mode = GPIO_MODE_INPUT_OUTPUT_OD,
|
||||
.pull_down_en = false,
|
||||
.pull_up_en = handle->pull_up_enable ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE,
|
||||
.pin_bit_mask = 1ULL << handle->scl_num,
|
||||
};
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
// on esp32, must enable internal pull-up
|
||||
scl_conf.pull_up_en = GPIO_PULLUP_ENABLE;
|
||||
#endif
|
||||
ESP_RETURN_ON_ERROR(gpio_config(&scl_conf), TAG, "config GPIO failed");
|
||||
ESP_RETURN_ON_ERROR(gpio_set_level(handle->scl_num, 1), TAG, "i2c scl pin set level failed");
|
||||
gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[handle->scl_num], PIN_FUNC_GPIO);
|
||||
esp_rom_gpio_connect_out_signal(handle->scl_num, i2c_periph_signal[port_id].scl_out_sig, 0, 0);
|
||||
esp_rom_gpio_connect_in_signal(handle->scl_num, i2c_periph_signal[port_id].scl_in_sig, 0);
|
||||
return ret;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdatomic.h>
|
||||
#include <sys/queue.h>
|
||||
#include "esp_err.h"
|
||||
#include "driver/i2c_types.h"
|
||||
#include "hal/i2c_hal.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/ringbuf.h"
|
||||
#include "esp_pm.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#if CONFIG_I2C_ISR_IRAM_SAFE
|
||||
#define I2C_MEM_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)
|
||||
#else
|
||||
#define I2C_MEM_ALLOC_CAPS (MALLOC_CAP_DEFAULT)
|
||||
#endif
|
||||
|
||||
// I2C driver object is per-mode, the interrupt source is shared between modes
|
||||
#if CONFIG_I2C_ISR_IRAM_SAFE
|
||||
#define I2C_INTR_ALLOC_FLAG (ESP_INTR_FLAG_SHARED | ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_LOWMED)
|
||||
#else
|
||||
#define I2C_INTR_ALLOC_FLAG (ESP_INTR_FLAG_SHARED | ESP_INTR_FLAG_LOWMED)
|
||||
#endif
|
||||
|
||||
#define I2C_ALLOW_INTR_PRIORITY_MASK ESP_INTR_FLAG_LOWMED
|
||||
|
||||
#define I2C_PM_LOCK_NAME_LEN_MAX 16
|
||||
#define I2C_STATIC_OPERATION_ARRAY_MAX 6
|
||||
|
||||
#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}
|
||||
#define I2C_TRANS_START_COMMAND {.op_code = I2C_LL_CMD_RESTART}
|
||||
|
||||
typedef struct i2c_bus_t i2c_bus_t;
|
||||
typedef struct i2c_master_bus_t i2c_master_bus_t;
|
||||
typedef struct i2c_bus_t *i2c_bus_handle_t;
|
||||
typedef struct i2c_master_dev_t i2c_master_dev_t;
|
||||
|
||||
typedef enum {
|
||||
I2C_BUS_MODE_MASTER = 0,
|
||||
I2C_BUS_MODE_SLAVE = 1,
|
||||
} i2c_bus_mode_t;
|
||||
|
||||
typedef enum {
|
||||
I2C_SLAVE_FIFO = 0,
|
||||
I2C_SLAVE_NONFIFO = 1,
|
||||
} i2c_slave_fifo_mode_t;
|
||||
|
||||
enum {
|
||||
I2C_TRANS_QUEUE_READY,
|
||||
I2C_TRANS_QUEUE_PROGRESS,
|
||||
I2C_TRANS_QUEUE_COMPLETE,
|
||||
I2C_TRANS_QUEUE_MAX,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
i2c_ll_hw_cmd_t hw_cmd; // I2C command, defined by hardware
|
||||
uint8_t *data; // Pointer to data
|
||||
uint16_t bytes_used; // I2C data has been used
|
||||
size_t total_bytes; // Total bytes to be transferred
|
||||
} i2c_operation_t;
|
||||
|
||||
typedef struct {
|
||||
uint32_t device_address; // Address of I2C device
|
||||
i2c_operation_t *ops; // Pointer to I2C operation structure
|
||||
size_t cmd_count; // Record how many I2C hardware commands in one transaction
|
||||
} i2c_transaction_t;
|
||||
|
||||
struct i2c_bus_t {
|
||||
i2c_port_num_t port_num; // Port(Bus) ID, index from 0
|
||||
portMUX_TYPE spinlock; // To protect pre-group register level concurrency access
|
||||
i2c_hal_context_t hal; // Hal layer for each port(bus)
|
||||
i2c_clock_source_t clk_src; // Record the port clock source
|
||||
uint32_t clk_src_freq_hz; // Record the clock source frequency
|
||||
int sda_num; // SDA pin number
|
||||
int scl_num; // SCL pin number
|
||||
bool pull_up_enable; // Disable pull-ups
|
||||
intr_handle_t intr_handle; // I2C interrupt handle
|
||||
esp_pm_lock_handle_t pm_lock; // power manange lock
|
||||
#if CONFIG_PM_ENABLE
|
||||
char pm_lock_name[I2C_PM_LOCK_NAME_LEN_MAX]; // pm lock name
|
||||
#endif
|
||||
i2c_bus_mode_t bus_mode; // I2C bus mode
|
||||
};
|
||||
|
||||
typedef struct i2c_master_device_list {
|
||||
i2c_master_dev_t *device;
|
||||
SLIST_ENTRY(i2c_master_device_list) next;
|
||||
} i2c_master_device_list_t;
|
||||
|
||||
struct i2c_master_bus_t {
|
||||
i2c_bus_t *base; // bus base class
|
||||
SemaphoreHandle_t bus_lock_mux; // semaphore to lock bus process
|
||||
int cmd_idx; //record current command index, for master mode
|
||||
_Atomic i2c_master_status_t status; // record current command status, for master mode
|
||||
i2c_master_event_t event; // record current i2c bus event
|
||||
int rx_cnt; // record current read index, for master mode
|
||||
i2c_transaction_t i2c_trans; // Pointer to I2C transfer structure
|
||||
i2c_operation_t i2c_ops[I2C_STATIC_OPERATION_ARRAY_MAX]; // I2C operation array
|
||||
_Atomic uint16_t trans_idx; // Index of I2C transaction command.
|
||||
SemaphoreHandle_t cmd_semphr; // Semaphore between task and interrupt, using for synchronizing ISR and I2C task.
|
||||
uint32_t read_buf_pos; // Read buffer position
|
||||
bool contains_read; // Whether command array includes read operation, true: yes, otherwise, false.
|
||||
uint32_t read_len_static; // Read static buffer length
|
||||
uint32_t w_r_size; // The size send/receive last time.
|
||||
bool trans_over_buffer; // Data length is more than hardware fifo length, needs interrupt.
|
||||
bool asnyc_trans; // asynchronous transaction, true after callback is installed.
|
||||
volatile bool trans_done; // transaction command finish
|
||||
SLIST_HEAD(i2c_master_device_list_head, i2c_master_device_list) device_list; // I2C device (instance) list
|
||||
// asnyc trans members
|
||||
bool async_break; // break transaction loop flag.
|
||||
i2c_addr_bit_len_t addr_10bits_bus; // Slave address is 10 bits.
|
||||
size_t queue_size; // I2C transaction queue size.
|
||||
size_t num_trans_inflight; // Indicates the number of transactions that are undergoing but not recycled to ready_queue
|
||||
size_t num_trans_inqueue; // Indicates the number of transaction in queue transaction.
|
||||
void* queues_storage; // storage of transaction queues
|
||||
bool sent_all; // true if the queue transaction is sent
|
||||
bool in_progress; // true if current transaction is in progress
|
||||
bool trans_finish; // true if current command has been sent out.
|
||||
bool queue_trans; // true if current transaction is in queue
|
||||
bool new_queue; // true if allow a new queue transaction
|
||||
size_t index; // transaction index
|
||||
QueueHandle_t trans_queues[I2C_TRANS_QUEUE_MAX]; // transaction queues.
|
||||
StaticQueue_t trans_queue_structs[I2C_TRANS_QUEUE_MAX]; // memory to store the static structure for trans_queues
|
||||
i2c_operation_t **i2c_anyc_ops; // pointer to asynchronous operation.
|
||||
uint8_t **anyc_write_buffer; // pointer to asynchronous write buffer.
|
||||
i2c_transaction_t i2c_trans_pool[]; // I2C transaction pool.
|
||||
};
|
||||
|
||||
struct i2c_master_dev_t {
|
||||
i2c_master_bus_t *master_bus; // I2C master bus base class
|
||||
uint16_t device_address; // I2C device address
|
||||
uint32_t scl_speed_hz; // SCL clock frequency
|
||||
i2c_addr_bit_len_t addr_10bits; // Whether I2C device is a 10-bits address device.
|
||||
i2c_master_callback_t on_trans_done; // I2C master transaction done callback.
|
||||
void *user_ctx; // Callback user context
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Acquire I2C bus handle
|
||||
*
|
||||
* @param port_num I2C port number.
|
||||
* @return
|
||||
* - ESP_OK: Acquire bus handle successfully.
|
||||
* - ESP_ERR_INVALID_ARG: Argument error.
|
||||
* - ESP_ERR_INVALID_STATE: Acquire bus invalid state because bus has already acquired.
|
||||
*/
|
||||
esp_err_t i2c_acquire_bus_handle(i2c_port_num_t port_num, i2c_bus_handle_t *i2c_new_bus, i2c_bus_mode_t mode);
|
||||
|
||||
/**
|
||||
* @brief Release I2C bus handle
|
||||
*
|
||||
* @param i2c_bus I2C bus handle, returned from `i2c_acquire_bus_handle`
|
||||
* @return ESP_OK: If release successfully
|
||||
*/
|
||||
uint8_t i2c_release_bus_handle(i2c_bus_handle_t i2c_bus);
|
||||
|
||||
/**
|
||||
* @brief Set clock source for I2C peripheral
|
||||
*
|
||||
* @param handle I2C bus handle
|
||||
* @param clk_src Clock source
|
||||
* @return
|
||||
* - ESP_OK: Set clock source successfully
|
||||
* - ESP_ERR_NOT_SUPPORTED: Set clock source failed because the clk_src is not supported
|
||||
* - ESP_ERR_INVALID_STATE: Set clock source failed because the clk_src is different from other I2C controller
|
||||
* - ESP_FAIL: Set clock source failed because of other error
|
||||
*/
|
||||
esp_err_t i2c_select_periph_clock(i2c_bus_handle_t handle, i2c_clock_source_t clk_src);
|
||||
|
||||
/**
|
||||
* @brief Set I2C SCL/SDA pins
|
||||
*
|
||||
* @param handle I2C bus handle
|
||||
* @return
|
||||
* - ESP_OK: I2C set SCL/SDA pins successfully.
|
||||
* - ESP_ERR_INVALID_ARG: Argument error.
|
||||
* - Otherwise: Set SCL/SDA IOs error.
|
||||
*/
|
||||
esp_err_t i2c_common_set_pins(i2c_bus_handle_t handle);
|
||||
|
||||
/**
|
||||
* @brief Check whether I2C bus is occupied
|
||||
*
|
||||
* @param port_num I2C port number.
|
||||
* @return true: occupied, otherwise, false.
|
||||
*/
|
||||
bool i2c_bus_occupied(i2c_port_num_t port_num);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
#include "driver/i2c_types.h"
|
||||
#include "hal/gpio_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief I2C master bus specific configurations
|
||||
*/
|
||||
typedef struct {
|
||||
i2c_port_num_t i2c_port; /*!< I2C port number, `-1` for auto selecting */
|
||||
gpio_num_t sda_io_num; /*!< GPIO number of I2C SDA signal, pulled-up internally */
|
||||
gpio_num_t scl_io_num; /*!< GPIO number of I2C SCL signal, pulled-up internally */
|
||||
i2c_clock_source_t clk_source; /*!< Clock source of I2C master bus, channels in the same group must use the same clock source */
|
||||
uint8_t glitch_ignore_cnt; /*!< If the glitch period on the line is less than this value, it can be filtered out, typically value is 7 (unit: I2C module clock cycle)*/
|
||||
int intr_priority; /*!< I2C interrupt priority, if set to 0, driver will select the default priority (1,2,3). */
|
||||
size_t trans_queue_depth; /*!< Depth of internal transfer queue, increase this value can support more transfers pending in the background, only valid in asynchronous transaction. (Typically max_device_num * per_transaction)*/
|
||||
struct {
|
||||
uint32_t enable_internal_pullup:1; /*!< Enable internal pullups */
|
||||
} flags;
|
||||
} i2c_master_bus_config_t;
|
||||
|
||||
/**
|
||||
* @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) */
|
||||
uint32_t scl_speed_hz; /*!< I2C SCL line frequency. */
|
||||
} i2c_device_config_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
|
||||
* @note When CONFIG_I2C_ISR_IRAM_SAFE is enabled, the callback itself and functions called by it should be placed in IRAM.
|
||||
* The variables used in the function should be in the SRAM as well.
|
||||
*/
|
||||
typedef struct {
|
||||
i2c_master_callback_t on_trans_done; /*!< I2C master transaction finish callback */
|
||||
} i2c_master_event_callbacks_t;
|
||||
|
||||
/**
|
||||
* @brief Allocate an I2C master bus
|
||||
*
|
||||
* @param[in] bus_config I2C master bus configuration.
|
||||
* @param[out] ret_bus_handle I2C bus handle
|
||||
* @return
|
||||
* - ESP_OK: I2C master bus initialized successfully.
|
||||
* - ESP_ERR_INVALID_ARG: I2C bus initialization failed because of invalid argument.
|
||||
* - ESP_ERR_NO_MEM: Create I2C bus failed because of out of memory.
|
||||
* - ESP_ERR_NOT_FOUND: No more free bus.
|
||||
*/
|
||||
esp_err_t i2c_new_master_bus(const i2c_master_bus_config_t *bus_config, i2c_master_bus_handle_t *ret_bus_handle);
|
||||
|
||||
/**
|
||||
* @brief Add I2C master BUS device.
|
||||
*
|
||||
* @param[in] bus_handle I2C bus handle.
|
||||
* @param[in] dev_config device config.
|
||||
* @param[out] ret_handle device handle.
|
||||
* @return
|
||||
* - ESP_OK: Create I2C master device successfully.
|
||||
* - ESP_ERR_INVALID_ARG: I2C bus initialization failed because of invalid argument.
|
||||
* - ESP_ERR_NO_MEM: Create I2C bus failed because of out of memory.
|
||||
*/
|
||||
esp_err_t i2c_master_bus_add_device(i2c_master_bus_handle_t bus_handle, const i2c_device_config_t *dev_config, i2c_master_dev_handle_t *ret_handle);
|
||||
|
||||
/**
|
||||
* @brief Deinitialize the I2C master bus and delete the handle.
|
||||
*
|
||||
* @param[in] bus_handle I2C bus handle.
|
||||
* @return
|
||||
* - ESP_OK: Delete I2C bus success, otherwise, failed.
|
||||
* - Otherwise: Some module delete failed.
|
||||
*/
|
||||
esp_err_t i2c_del_master_bus(i2c_master_bus_handle_t bus_handle);
|
||||
|
||||
/**
|
||||
* @brief I2C master bus delete device
|
||||
*
|
||||
* @param handle i2c device handle
|
||||
* @return
|
||||
* - ESP_OK: If device is successfully deleted.
|
||||
*/
|
||||
esp_err_t i2c_master_bus_rm_device(i2c_master_dev_handle_t handle);
|
||||
|
||||
/**
|
||||
* @brief Perform a write transaction on the I2C bus.
|
||||
* The transaction will be undergoing until it finishes or it reaches
|
||||
* the timeout provided.
|
||||
*
|
||||
* @note If a callback was registered with `i2c_master_register_event_callbacks`, the transaction will be asynchronous, and thus, this function will return directly, without blocking.
|
||||
* You will get finish information from callback. Besides, data buffer should always be completely prepared when callback is registered, otherwise, the data will get corrupt.
|
||||
*
|
||||
* @param[in] i2c_dev I2C master device handle that created by `i2c_master_bus_add_device`.
|
||||
* @param[in] write_buffer Data bytes to send on the I2C bus.
|
||||
* @param[in] write_size Size, in bytes, of the write buffer.
|
||||
* @param[in] xfer_timeout_ms Wait timeout, in ms. Note: -1 means wait forever.
|
||||
* @return
|
||||
* - ESP_OK: I2C master transmit success
|
||||
* - ESP_ERR_INVALID_ARG: I2c master transmit parameter invalid.
|
||||
* - ESP_ERR_TIMEOUT: Operation timeout(larger than xfer_timeout_ms) because the bus is busy or hardware crash.
|
||||
*/
|
||||
esp_err_t i2c_master_transmit(i2c_master_dev_handle_t i2c_dev, const uint8_t *write_buffer, size_t write_size, int xfer_timeout_ms);
|
||||
|
||||
/**
|
||||
* @brief Perform a write-read transaction on the I2C bus.
|
||||
* The transaction will be undergoing until it finishes or it reaches
|
||||
* the timeout provided.
|
||||
*
|
||||
* @note If a callback was registered with `i2c_master_register_event_callbacks`, the transaction will be asynchronous, and thus, this function will return directly, without blocking.
|
||||
* You will get finish information from callback. Besides, data buffer should always be completely prepared when callback is registered, otherwise, the data will get corrupt.
|
||||
*
|
||||
* @param[in] i2c_dev I2C master device handle that created by `i2c_master_bus_add_device`.
|
||||
* @param[in] write_buffer Data bytes to send on the I2C bus.
|
||||
* @param[in] write_size Size, in bytes, of the write buffer.
|
||||
* @param[out] read_buffer Data bytes received from i2c bus.
|
||||
* @param[in] read_size Size, in bytes, of the read buffer.
|
||||
* @param[in] xfer_timeout_ms Wait timeout, in ms. Note: -1 means wait forever.
|
||||
* @return
|
||||
* - ESP_OK: I2C master transmit-receive success
|
||||
* - ESP_ERR_INVALID_ARG: I2c master transmit parameter invalid.
|
||||
* - ESP_ERR_TIMEOUT: Operation timeout(larger than xfer_timeout_ms) because the bus is busy or hardware crash.
|
||||
*/
|
||||
esp_err_t i2c_master_transmit_receive(i2c_master_dev_handle_t i2c_dev, const uint8_t *write_buffer, size_t write_size, uint8_t *read_buffer, size_t read_size, int xfer_timeout_ms);
|
||||
|
||||
/**
|
||||
* @brief Perform a read transaction on the I2C bus.
|
||||
* The transaction will be undergoing until it finishes or it reaches
|
||||
* the timeout provided.
|
||||
*
|
||||
* @note If a callback was registered with `i2c_master_register_event_callbacks`, the transaction will be asynchronous, and thus, this function will return directly, without blocking.
|
||||
* You will get finish information from callback. Besides, data buffer should always be completely prepared when callback is registered, otherwise, the data will get corrupt.
|
||||
*
|
||||
* @param[in] i2c_dev I2C master device handle that created by `i2c_master_bus_add_device`.
|
||||
* @param[out] read_buffer Data bytes received from i2c bus.
|
||||
* @param[in] read_size Size, in bytes, of the read buffer.
|
||||
* @param[in] xfer_timeout_ms Wait timeout, in ms. Note: -1 means wait forever.
|
||||
* @return
|
||||
* - ESP_OK: I2C master receive success
|
||||
* - ESP_ERR_INVALID_ARG: I2c master receive parameter invalid.
|
||||
* - ESP_ERR_TIMEOUT: Operation timeout(larger than xfer_timeout_ms) because the bus is busy or hardware crash.
|
||||
*/
|
||||
esp_err_t i2c_master_receive(i2c_master_dev_handle_t i2c_dev, uint8_t *read_buffer, size_t read_size, int xfer_timeout_ms);
|
||||
|
||||
/**
|
||||
* @brief Probe I2C address, if address is correct and ACK is received, this function will return ESP_OK.
|
||||
*
|
||||
* @param[in] i2c_dev I2C master device handle that created by `i2c_master_bus_add_device`.
|
||||
* @param[in] xfer_timeout_ms Wait timeout, in ms. Note: -1 means wait forever (Not recommended in this function).
|
||||
* @return
|
||||
* - ESP_OK: I2C device probe successfully
|
||||
* - ESP_ERR_TIMEOUT: Operation timeout(larger than xfer_timeout_ms) because the bus is busy or hardware crash.
|
||||
*/
|
||||
// esp_err_t i2c_master_probe(i2c_master_dev_handle_t i2c_dev, int xfer_timeout_ms);
|
||||
esp_err_t i2c_master_probe(i2c_master_bus_handle_t i2c_master, uint16_t address, int xfer_timeout_ms);
|
||||
|
||||
/**
|
||||
* @brief Register I2C transaction callbacks for a master device
|
||||
*
|
||||
* @note User can deregister a previously registered callback by calling this function and setting the callback member in the `cbs` structure to NULL.
|
||||
* @note When CONFIG_I2C_ISR_IRAM_SAFE is enabled, the callback itself and functions called by it should be placed in IRAM.
|
||||
* The variables used in the function should be in the SRAM as well. The `user_data` should also reside in SRAM.
|
||||
* @note If the callback is used for helping asynchronous transaction. On the same bus, only one device can be used for performing asynchronous operation.
|
||||
*
|
||||
* @param[in] i2c_dev I2C master device handle that created by `i2c_master_bus_add_device`.
|
||||
* @param[in] cbs Group of callback functions
|
||||
* @param[in] user_data User data, which will be passed to callback functions directly
|
||||
* @return
|
||||
* - ESP_OK: Set I2C transaction callbacks successfully
|
||||
* - ESP_ERR_INVALID_ARG: Set I2C transaction callbacks failed because of invalid argument
|
||||
* - ESP_FAIL: Set I2C transaction callbacks failed because of other error
|
||||
*/
|
||||
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);
|
||||
|
||||
/**
|
||||
* @brief Reset the I2C master bus.
|
||||
*
|
||||
* @param handle I2C bus handle.
|
||||
* @return
|
||||
* - ESP_OK: Reset succeed.
|
||||
* - ESP_ERR_INVALID_ARG: I2C master bus handle is not initialized.
|
||||
* - Otherwise: Reset failed.
|
||||
*/
|
||||
esp_err_t i2c_master_bus_reset(i2c_master_bus_handle_t handle);
|
||||
|
||||
/**
|
||||
* @brief Wait for all pending I2C transactions done
|
||||
*
|
||||
* @param[in] i2c_master I2C bus handle
|
||||
* @param[in] timeout_ms Wait timeout, in ms. Specially, -1 means to wait forever.
|
||||
* @return
|
||||
* - ESP_OK: Flush transactions successfully
|
||||
* - ESP_ERR_INVALID_ARG: Flush transactions failed because of invalid argument
|
||||
* - ESP_ERR_TIMEOUT: Flush transactions failed because of timeout
|
||||
* - ESP_FAIL: Flush transactions failed because of other error
|
||||
*/
|
||||
esp_err_t i2c_master_wait_all_done(i2c_master_bus_handle_t i2c_master, int timeout_ms);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "hal/i2c_types.h"
|
||||
#include "soc/soc_caps.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief I2C port number.
|
||||
*/
|
||||
typedef int i2c_port_num_t;
|
||||
|
||||
/**
|
||||
* @brief Enumeration for I2C fsm status.
|
||||
*/
|
||||
typedef enum {
|
||||
I2C_STATUS_READ, /*!< read status for current master command */
|
||||
I2C_STATUS_WRITE, /*!< write status for current master command */
|
||||
I2C_STATUS_START, /*!< Start status for current master command */
|
||||
I2C_STATUS_STOP, /*!< stop status for current master command */
|
||||
I2C_STATUS_IDLE, /*!< idle status for current master command */
|
||||
I2C_STATUS_ACK_ERROR, /*!< ack error status for current master command */
|
||||
I2C_STATUS_DONE, /*!< I2C command done */
|
||||
I2C_STATUS_TIMEOUT, /*!< I2C bus status error, and operation timeout */
|
||||
} i2c_master_status_t;
|
||||
|
||||
|
||||
typedef enum {
|
||||
I2C_EVENT_ALIVE, /*!< i2c bus in alive status.*/
|
||||
I2C_EVENT_DONE, /*!< i2c bus transaction done */
|
||||
I2C_EVENT_NACK, /*!< i2c bus nack */
|
||||
} i2c_master_event_t;
|
||||
|
||||
/**
|
||||
* @brief Type of I2C master bus handle
|
||||
*/
|
||||
typedef struct i2c_master_bus_t *i2c_master_bus_handle_t;
|
||||
|
||||
/**
|
||||
* @brief Type of I2C master bus device handle
|
||||
*/
|
||||
typedef struct i2c_master_dev_t *i2c_master_dev_handle_t;
|
||||
|
||||
/**
|
||||
* @brief Data type used in I2C event callback
|
||||
*/
|
||||
typedef struct {
|
||||
i2c_master_event_t event; /**< The I2C hardware event that I2C callback is called. */
|
||||
} i2c_master_event_data_t;
|
||||
|
||||
/**
|
||||
* @brief An callback for I2C transaction.
|
||||
*
|
||||
* @param[in] i2c_dev Handle for I2C device.
|
||||
* @param[out] evt_data I2C capture event data, fed by driver
|
||||
* @param[in] arg User data, set in `i2c_master_register_event_callbacks()`
|
||||
*
|
||||
* @return Whether a high priority task has been waken up by this function
|
||||
*/
|
||||
typedef bool (*i2c_master_callback_t)(i2c_master_dev_handle_t i2c_dev, const i2c_master_event_data_t *evt_data, void *arg);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,8 @@
|
||||
[mapping:i2c_driver]
|
||||
archive: libdriver.a
|
||||
entries:
|
||||
if I2C_ISR_IRAM_SAFE = y:
|
||||
i2c_master: s_i2c_send_command_async (noflash)
|
||||
i2c_master: s_i2c_write_command (noflash)
|
||||
i2c_master: s_i2c_read_command (noflash)
|
||||
i2c_master: s_i2c_start_end_command (noflash)
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "unity.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_err.h"
|
||||
#include "soc/gpio_periph.h"
|
||||
#include "soc/clk_tree_defs.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "hal/gpio_hal.h"
|
||||
#include "hal/uart_ll.h"
|
||||
#include "esp_private/periph_ctrl.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/i2c_master.h"
|
||||
#include "esp_rom_gpio.h"
|
||||
#include "esp_log.h"
|
||||
#include "test_utils.h"
|
||||
|
||||
static const char TAG[] = "test-i2c";
|
||||
|
||||
#define TEST_I2C_SCL_PIN 2
|
||||
#define TEST_I2C_SDA_PIN 4
|
||||
#define TEST_I2C_SCL_FREQ (100 * 1000)
|
||||
#define TEST_I2C_PORT 0
|
||||
#define SLAVE_ADDR 0x58
|
||||
|
||||
|
||||
TEST_CASE("I2C bus install-uninstall test", "[i2c]")
|
||||
{
|
||||
i2c_master_bus_config_t i2c_mst_config_1 = {
|
||||
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||
.i2c_port = TEST_I2C_PORT,
|
||||
.scl_io_num = TEST_I2C_SCL_PIN,
|
||||
.sda_io_num = TEST_I2C_SDA_PIN,
|
||||
};
|
||||
i2c_master_bus_handle_t i2c_mst_handle1;
|
||||
|
||||
#if SOC_I2C_NUM > 1
|
||||
i2c_master_bus_config_t i2c_mst_config_2 = {
|
||||
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||
.i2c_port = 1,
|
||||
.scl_io_num = TEST_I2C_SCL_PIN,
|
||||
.sda_io_num = TEST_I2C_SDA_PIN,
|
||||
};
|
||||
i2c_master_bus_handle_t i2c_mst_handle2;
|
||||
#endif
|
||||
// Install master bus 0
|
||||
ESP_LOGI(TAG, "Initialize bus0");
|
||||
TEST_ESP_OK(i2c_new_master_bus(&i2c_mst_config_1, &i2c_mst_handle1));
|
||||
#if SOC_I2C_NUM > 1
|
||||
// Install master bus 1
|
||||
ESP_LOGI(TAG, "Initialize bus1");
|
||||
TEST_ESP_OK(i2c_new_master_bus(&i2c_mst_config_2, &i2c_mst_handle2));
|
||||
#endif
|
||||
// Install master bus 0 again
|
||||
ESP_LOGI(TAG, "Initialize bus0 again");
|
||||
TEST_ESP_ERR(ESP_ERR_INVALID_STATE, i2c_new_master_bus(&i2c_mst_config_1, &i2c_mst_handle1));
|
||||
ESP_LOGI(TAG, "Delete bus0");
|
||||
TEST_ESP_OK(i2c_del_master_bus(i2c_mst_handle1));
|
||||
#if SOC_I2C_NUM > 1
|
||||
ESP_LOGI(TAG, "Delete bus1");
|
||||
TEST_ESP_OK(i2c_del_master_bus(i2c_mst_handle2));
|
||||
#endif
|
||||
}
|
||||
|
||||
TEST_CASE("I2C driver memory leaking check", "[i2c]")
|
||||
{
|
||||
i2c_master_bus_config_t i2c_mst_config_1 = {
|
||||
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||
.i2c_port = TEST_I2C_PORT,
|
||||
.scl_io_num = TEST_I2C_SCL_PIN,
|
||||
.sda_io_num = TEST_I2C_SDA_PIN,
|
||||
};
|
||||
i2c_master_bus_handle_t bus_handle;
|
||||
|
||||
int size = esp_get_free_heap_size();
|
||||
for (uint32_t i = 0; i <= 5; i++) {
|
||||
TEST_ESP_OK(i2c_new_master_bus(&i2c_mst_config_1, &bus_handle));
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
TEST_ESP_OK(i2c_del_master_bus(bus_handle));
|
||||
}
|
||||
|
||||
TEST_ASSERT_INT_WITHIN(300, size, esp_get_free_heap_size());
|
||||
}
|
||||
|
||||
TEST_CASE("I2C device add & remove check", "[i2c]")
|
||||
{
|
||||
i2c_master_bus_config_t i2c_mst_config_1 = {
|
||||
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||
.i2c_port = TEST_I2C_PORT,
|
||||
.scl_io_num = TEST_I2C_SCL_PIN,
|
||||
.sda_io_num = TEST_I2C_SDA_PIN,
|
||||
};
|
||||
i2c_master_bus_handle_t bus_handle;
|
||||
|
||||
TEST_ESP_OK(i2c_new_master_bus(&i2c_mst_config_1, &bus_handle));
|
||||
|
||||
i2c_device_config_t dev_cfg_1 = {
|
||||
.scl_speed_hz = 100*1000,
|
||||
.device_address = 0x10,
|
||||
};
|
||||
i2c_master_dev_handle_t dev_1;
|
||||
i2c_master_bus_add_device(bus_handle, &dev_cfg_1, &dev_1);
|
||||
|
||||
i2c_device_config_t dev_cfg_2 = {
|
||||
.scl_speed_hz = 100*1000,
|
||||
.device_address = 0x20,
|
||||
};
|
||||
i2c_master_dev_handle_t dev_2;
|
||||
i2c_master_bus_add_device(bus_handle, &dev_cfg_2, &dev_2);
|
||||
|
||||
i2c_device_config_t dev_cfg_3 = {
|
||||
.scl_speed_hz = 100*1000,
|
||||
.device_address = 0x30,
|
||||
};
|
||||
i2c_master_dev_handle_t dev_3;
|
||||
i2c_master_bus_add_device(bus_handle, &dev_cfg_3, &dev_3);
|
||||
|
||||
i2c_master_bus_rm_device(dev_1);
|
||||
i2c_master_bus_rm_device(dev_2);
|
||||
i2c_master_bus_rm_device(dev_3);
|
||||
|
||||
TEST_ESP_OK(i2c_del_master_bus(bus_handle));
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
# SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
import pytest
|
||||
from pytest_embedded import Dut
|
||||
|
||||
|
||||
@pytest.mark.supported_targets
|
||||
@pytest.mark.generic
|
||||
@pytest.mark.parametrize(
|
||||
'config',
|
||||
[
|
||||
'release',
|
||||
'iram_safe',
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
def test_i2c(dut: Dut) -> None:
|
||||
dut.run_all_single_board_cases()
|
||||
@@ -0,0 +1,6 @@
|
||||
CONFIG_PM_ENABLE=y
|
||||
CONFIG_COMPILER_DUMP_RTL_FILES=y
|
||||
CONFIG_FREERTOS_USE_TICKLESS_IDLE=y
|
||||
CONFIG_COMPILER_OPTIMIZATION_NONE=y
|
||||
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y
|
||||
CONFIG_I2C_ISR_IRAM_SAFE=y
|
||||
@@ -0,0 +1,22 @@
|
||||
# This is the project CMakeLists.txt file for the test subproject
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
set(EXTRA_COMPONENT_DIRS
|
||||
"$ENV{IDF_PATH}/tools/unit-test-app/components"
|
||||
)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(legacy_i2c_test)
|
||||
|
||||
if(CONFIG_COMPILER_DUMP_RTL_FILES)
|
||||
add_custom_target(check_test_app_sections ALL
|
||||
COMMAND ${PYTHON} $ENV{IDF_PATH}/tools/ci/check_callgraph.py
|
||||
--rtl-dirs ${CMAKE_BINARY_DIR}/esp-idf/driver/,${CMAKE_BINARY_DIR}/esp-idf/hal/
|
||||
--elf-file ${CMAKE_BINARY_DIR}/legacy_i2c_test.elf
|
||||
find-refs
|
||||
--from-sections=.iram0.text
|
||||
--to-sections=.flash.text,.flash.rodata
|
||||
--exit-code
|
||||
DEPENDS ${elf}
|
||||
)
|
||||
endif()
|
||||
@@ -0,0 +1,2 @@
|
||||
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-S2 | ESP32-S3 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- |
|
||||
@@ -0,0 +1,6 @@
|
||||
set(srcs "test_app_main.c"
|
||||
"test_i2c.c"
|
||||
)
|
||||
|
||||
idf_component_register(SRCS ${srcs}
|
||||
WHOLE_ARCHIVE)
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "unity.h"
|
||||
#include "unity_test_runner.h"
|
||||
#include "unity_test_utils_memory.h"
|
||||
#include "esp_heap_caps.h"
|
||||
|
||||
// Some resources are lazy allocated in I2C driver, so we reserved this threshold when checking memory leak
|
||||
// A better way to check a potential memory leak is running a same case by twice, for the second time, the memory usage delta should be zero
|
||||
#define LEAKS (400)
|
||||
|
||||
void setUp(void)
|
||||
{
|
||||
unity_utils_record_free_mem();
|
||||
}
|
||||
|
||||
void tearDown(void)
|
||||
{
|
||||
unity_utils_evaluate_leaks_direct(LEAKS);
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
|
||||
// ___ ____ ____ _____ _
|
||||
// |_ _|___ \ / ___| |_ _|__ ___| |_
|
||||
// | | __) | | | |/ _ \/ __| __|
|
||||
// | | / __/| |___ | | __/\__ \ |_
|
||||
// |___|_____|\____| |_|\___||___/\__|
|
||||
|
||||
printf(" ___ ____ ____ _____ _ \n");
|
||||
printf("|_ _|___ \\ / ___| |_ _|__ ___| |_ \n");
|
||||
printf(" | | __) | | | |/ _ \\/ __| __|\n");
|
||||
printf(" | | / __/| |___ | | __/\\__ \\ |_ \n");
|
||||
printf("|___|_____|\\____| |_|\\___||___/\\__|\n");
|
||||
|
||||
unity_run_menu();
|
||||
}
|
||||
+2
-2
@@ -15,7 +15,7 @@ from pytest_embedded import Dut
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
def test_i2c(dut: Dut) -> None:
|
||||
def test_i2c_legacy(dut: Dut) -> None:
|
||||
dut.run_all_single_board_cases()
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ def test_i2c(dut: Dut) -> None:
|
||||
],
|
||||
indirect=True
|
||||
)
|
||||
def test_i2c_multi_dev(case_tester) -> None: # type: ignore
|
||||
def test_i2c_multi_dev_legacy(case_tester) -> None: # type: ignore
|
||||
for case in case_tester.test_menu:
|
||||
if case.attributes.get('test_env', 'generic_multi_device') == 'generic_multi_device':
|
||||
case_tester.run_multi_dev_case(case=case, reset=True)
|
||||
@@ -0,0 +1,2 @@
|
||||
CONFIG_FREERTOS_HZ=1000
|
||||
CONFIG_ESP_TASK_WDT=n
|
||||
@@ -0,0 +1,5 @@
|
||||
CONFIG_PM_ENABLE=y
|
||||
CONFIG_FREERTOS_USE_TICKLESS_IDLE=y
|
||||
CONFIG_COMPILER_OPTIMIZATION_SIZE=y
|
||||
CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y
|
||||
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y
|
||||
@@ -0,0 +1,2 @@
|
||||
CONFIG_FREERTOS_HZ=1000
|
||||
CONFIG_ESP_TASK_WDT=n
|
||||
Reference in New Issue
Block a user