forked from espressif/esp-idf
Merge branch 'feat/i2c_slave_v2_v5.4' into 'release/v5.4'
feat(i2c_slave): Add new i2c slave driver --version two with great stretch handling (backport v5.4) See merge request espressif/esp-idf!34907
This commit is contained in:
@@ -10,7 +10,11 @@ if(CONFIG_SOC_I2C_SUPPORTED)
|
||||
"i2c_common.c"
|
||||
)
|
||||
if(CONFIG_SOC_I2C_SUPPORT_SLAVE)
|
||||
list(APPEND srcs "i2c_slave.c")
|
||||
if(CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2)
|
||||
list(APPEND srcs "i2c_slave_v2.c")
|
||||
else()
|
||||
list(APPEND srcs "i2c_slave.c")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
endif()
|
||||
|
@@ -17,4 +17,10 @@ menu "ESP-Driver:I2C Configurations"
|
||||
|
||||
note: This cannot be used in the I2C legacy driver.
|
||||
|
||||
config I2C_ENABLE_SLAVE_DRIVER_VERSION_2
|
||||
bool "Enable I2C slave driver version 2"
|
||||
default n
|
||||
help
|
||||
I2C slave version 2 solves some existing known issues. Such as write/read workflow, stretch handling, etc.
|
||||
|
||||
endmenu # I2C Configurations
|
||||
|
@@ -19,6 +19,7 @@
|
||||
#include "driver/i2c_slave.h"
|
||||
#include "esp_private/periph_ctrl.h"
|
||||
#include "esp_pm.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -196,6 +197,8 @@ typedef struct {
|
||||
uint32_t rcv_fifo_cnt; // receive fifo count.
|
||||
} i2c_slave_receive_t;
|
||||
|
||||
#if !CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2
|
||||
|
||||
struct i2c_slave_dev_t {
|
||||
i2c_bus_t *base; // bus base class
|
||||
SemaphoreHandle_t slv_rx_mux; // Mutex for slave rx direction
|
||||
@@ -213,6 +216,22 @@ struct i2c_slave_dev_t {
|
||||
uint32_t already_receive_len; // Data length already received in ISR.
|
||||
};
|
||||
|
||||
#else // CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2
|
||||
|
||||
struct i2c_slave_dev_t {
|
||||
i2c_bus_t *base; // bus base class
|
||||
SemaphoreHandle_t operation_mux; // Mux for i2c slave operation
|
||||
i2c_slave_request_callback_t request_callback; // i2c slave request callback
|
||||
i2c_slave_received_callback_t receive_callback; // i2c_slave receive callback
|
||||
void *user_ctx; // Callback user context
|
||||
RingbufHandle_t rx_ring_buf; // receive ringbuffer
|
||||
RingbufHandle_t tx_ring_buf; // transmit ringbuffer
|
||||
uint32_t rx_data_count; // receive data count
|
||||
i2c_slave_receive_t receive_desc; // slave receive descriptor
|
||||
};
|
||||
|
||||
#endif // CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2
|
||||
|
||||
/**
|
||||
* @brief Acquire I2C bus handle
|
||||
*
|
||||
|
433
components/esp_driver_i2c/i2c_slave_v2.c
Normal file
433
components/esp_driver_i2c/i2c_slave_v2.c
Normal file
@@ -0,0 +1,433 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "esp_attr.h"
|
||||
#include "esp_rom_gpio.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "hal/gpio_ll.h"
|
||||
#include "esp_err.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/ringbuf.h"
|
||||
#include "esp_intr_alloc.h"
|
||||
#include "hal/i2c_ll.h"
|
||||
#include "i2c_private.h"
|
||||
#include "driver/i2c_slave.h"
|
||||
#include "esp_memory_utils.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"
|
||||
|
||||
static const char *TAG = "i2c.slave";
|
||||
|
||||
IRAM_ATTR static bool i2c_slave_read_rx(i2c_slave_dev_t *i2c_slave, uint8_t *data, size_t len, size_t *read_len)
|
||||
{
|
||||
BaseType_t xTaskWoken = pdFALSE;
|
||||
size_t read_size = len;
|
||||
size_t actual_size = 0;
|
||||
size_t available_size = 0;
|
||||
size_t get_size = 0;
|
||||
uint8_t *rx_data = NULL;
|
||||
|
||||
vRingbufferGetInfo(i2c_slave->rx_ring_buf, NULL, NULL, NULL, NULL, &available_size);
|
||||
if (available_size < read_size) {
|
||||
read_size = available_size;
|
||||
}
|
||||
|
||||
while (read_size) {
|
||||
actual_size = 0;
|
||||
rx_data = (uint8_t *)xRingbufferReceiveUpToFromISR(i2c_slave->rx_ring_buf, &actual_size, read_size);
|
||||
if (rx_data != NULL && actual_size != 0) {
|
||||
memcpy(data + get_size, rx_data, actual_size);
|
||||
vRingbufferReturnItemFromISR(i2c_slave->rx_ring_buf, rx_data, &xTaskWoken);
|
||||
get_size += actual_size;
|
||||
read_size -= actual_size;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
*read_len = get_size;
|
||||
return xTaskWoken;
|
||||
}
|
||||
|
||||
IRAM_ATTR static bool i2c_slave_handle_tx_fifo(i2c_slave_dev_t *i2c_slave)
|
||||
{
|
||||
BaseType_t xTaskWoken = pdFALSE;
|
||||
i2c_hal_context_t *hal = &i2c_slave->base->hal;
|
||||
uint8_t *data;
|
||||
uint32_t fifo_len = 0;
|
||||
xSemaphoreTakeFromISR(i2c_slave->operation_mux, &xTaskWoken);
|
||||
i2c_ll_get_txfifo_len(hal->dev, &fifo_len);
|
||||
size_t actual_get_len = 0;
|
||||
while (fifo_len > 0) {
|
||||
data = xRingbufferReceiveUpToFromISR(i2c_slave->tx_ring_buf, &actual_get_len, fifo_len);
|
||||
if (data) {
|
||||
portENTER_CRITICAL_ISR(&i2c_slave->base->spinlock);
|
||||
i2c_ll_write_txfifo(hal->dev, data, actual_get_len);
|
||||
fifo_len -= actual_get_len;
|
||||
portEXIT_CRITICAL_ISR(&i2c_slave->base->spinlock);
|
||||
vRingbufferReturnItemFromISR(i2c_slave->tx_ring_buf, data, &xTaskWoken);
|
||||
} else {
|
||||
// No data in ringbuffer, so disable the tx interrupt.
|
||||
i2c_ll_slave_disable_tx_it(hal->dev);
|
||||
break;
|
||||
}
|
||||
}
|
||||
xSemaphoreGiveFromISR(i2c_slave->operation_mux, &xTaskWoken);
|
||||
return xTaskWoken;
|
||||
}
|
||||
|
||||
IRAM_ATTR static bool i2c_slave_handle_rx_fifo(i2c_slave_dev_t *i2c_slave, uint32_t len)
|
||||
{
|
||||
i2c_hal_context_t *hal = &i2c_slave->base->hal;
|
||||
uint8_t data[SOC_I2C_FIFO_LEN];
|
||||
BaseType_t xTaskWoken = pdFALSE;
|
||||
xSemaphoreTakeFromISR(i2c_slave->operation_mux, &xTaskWoken);
|
||||
if (len) {
|
||||
portENTER_CRITICAL_ISR(&i2c_slave->base->spinlock);
|
||||
i2c_ll_read_rxfifo(hal->dev, data, len);
|
||||
portEXIT_CRITICAL_ISR(&i2c_slave->base->spinlock);
|
||||
BaseType_t res = xRingbufferSendFromISR(i2c_slave->rx_ring_buf, (void *)data, len, &xTaskWoken);
|
||||
if (res == pdTRUE) {
|
||||
i2c_slave->rx_data_count += len;
|
||||
}
|
||||
}
|
||||
xSemaphoreTakeFromISR(i2c_slave->operation_mux, &xTaskWoken);
|
||||
return xTaskWoken;
|
||||
}
|
||||
|
||||
#if SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE
|
||||
IRAM_ATTR static bool i2c_slave_handle_stretch_event(i2c_slave_dev_t *i2c_slave, uint32_t rx_fifo_exist_len)
|
||||
{
|
||||
i2c_slave_stretch_cause_t cause;
|
||||
BaseType_t xTaskWoken = pdFALSE;
|
||||
i2c_hal_context_t *hal = &i2c_slave->base->hal;
|
||||
i2c_ll_slave_get_stretch_cause(hal->dev, &cause);
|
||||
if (cause == I2C_SLAVE_STRETCH_CAUSE_ADDRESS_MATCH) {
|
||||
if (rx_fifo_exist_len) {
|
||||
xTaskWoken |= i2c_slave_handle_rx_fifo(i2c_slave, rx_fifo_exist_len);
|
||||
}
|
||||
if (i2c_slave->rx_data_count) {
|
||||
uint32_t len = i2c_slave->rx_data_count;
|
||||
size_t read_len;
|
||||
xTaskWoken |= i2c_slave_read_rx(i2c_slave, i2c_slave->receive_desc.buffer, len, &read_len);
|
||||
i2c_slave_rx_done_event_data_t edata = {};
|
||||
edata.buffer = i2c_slave->receive_desc.buffer;
|
||||
edata.length = read_len;
|
||||
if (i2c_slave->receive_callback) {
|
||||
xTaskWoken |= i2c_slave->receive_callback(i2c_slave, &edata, i2c_slave->user_ctx);
|
||||
}
|
||||
i2c_slave->rx_data_count = 0;
|
||||
}
|
||||
i2c_slave_request_event_data_t evt_data = {};
|
||||
if (i2c_slave->request_callback) {
|
||||
xTaskWoken |= i2c_slave->request_callback(i2c_slave, &evt_data, i2c_slave->user_ctx);
|
||||
}
|
||||
//will clear after request callback
|
||||
} else if (cause == I2C_SLAVE_STRETCH_CAUSE_TX_EMPTY) {
|
||||
xTaskWoken |= i2c_slave_handle_tx_fifo(i2c_slave);
|
||||
i2c_ll_slave_clear_stretch(hal->dev);
|
||||
} else if (cause == I2C_SLAVE_STRETCH_CAUSE_RX_FULL) {
|
||||
xTaskWoken |= i2c_slave_handle_rx_fifo(i2c_slave, rx_fifo_exist_len);
|
||||
i2c_ll_slave_clear_stretch(hal->dev);
|
||||
}
|
||||
return xTaskWoken;
|
||||
}
|
||||
#endif
|
||||
|
||||
IRAM_ATTR static void i2c_slave_isr_handler(void *arg)
|
||||
{
|
||||
BaseType_t pxHigherPriorityTaskWoken = false;
|
||||
i2c_slave_dev_t *i2c_slave = (i2c_slave_dev_t *)arg;
|
||||
|
||||
i2c_hal_context_t *hal = &i2c_slave->base->hal;
|
||||
uint32_t int_mask = 0;
|
||||
i2c_ll_get_intr_mask(hal->dev, &int_mask);
|
||||
i2c_ll_clear_intr_mask(hal->dev, int_mask);
|
||||
uint32_t rx_fifo_exist_len = 0;
|
||||
i2c_ll_get_rxfifo_cnt(hal->dev, &rx_fifo_exist_len);
|
||||
i2c_slave_read_write_status_t slave_rw = i2c_ll_slave_get_read_write_status(hal->dev);
|
||||
|
||||
if (int_mask & I2C_INTR_SLV_RXFIFO_WM) {
|
||||
pxHigherPriorityTaskWoken |= i2c_slave_handle_rx_fifo(i2c_slave, rx_fifo_exist_len);
|
||||
}
|
||||
|
||||
if (int_mask & I2C_INTR_SLV_COMPLETE) {
|
||||
if (rx_fifo_exist_len) {
|
||||
pxHigherPriorityTaskWoken |= i2c_slave_handle_rx_fifo(i2c_slave, rx_fifo_exist_len);
|
||||
}
|
||||
if (i2c_slave->rx_data_count) {
|
||||
uint32_t len = i2c_slave->rx_data_count;
|
||||
size_t read_len;
|
||||
pxHigherPriorityTaskWoken |= i2c_slave_read_rx(i2c_slave, i2c_slave->receive_desc.buffer, len, &read_len);
|
||||
i2c_slave_rx_done_event_data_t edata = {};
|
||||
edata.buffer = i2c_slave->receive_desc.buffer;
|
||||
edata.length = read_len;
|
||||
if (i2c_slave->receive_callback) {
|
||||
pxHigherPriorityTaskWoken |= i2c_slave->receive_callback(i2c_slave, &edata, i2c_slave->user_ctx);
|
||||
}
|
||||
i2c_slave->rx_data_count = 0;
|
||||
}
|
||||
if (slave_rw == I2C_SLAVE_READ_BY_MASTER) {
|
||||
#if !SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE
|
||||
i2c_slave_request_event_data_t evt_data = {};
|
||||
if (i2c_slave->request_callback) {
|
||||
pxHigherPriorityTaskWoken |= i2c_slave->request_callback(i2c_slave, &evt_data, i2c_slave->user_ctx);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#if SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE
|
||||
if (int_mask & I2C_INTR_STRETCH) { // STRETCH
|
||||
pxHigherPriorityTaskWoken |= i2c_slave_handle_stretch_event(i2c_slave, rx_fifo_exist_len);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (int_mask & I2C_INTR_SLV_TXFIFO_WM) { // TX FiFo Empty
|
||||
pxHigherPriorityTaskWoken |= i2c_slave_handle_tx_fifo(i2c_slave);
|
||||
}
|
||||
|
||||
if (pxHigherPriorityTaskWoken) {
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
}
|
||||
|
||||
static esp_err_t i2c_slave_device_destroy(i2c_slave_dev_handle_t i2c_slave)
|
||||
{
|
||||
i2c_ll_disable_intr_mask(i2c_slave->base->hal.dev, I2C_LL_SLAVE_EVENT_INTR);
|
||||
if (i2c_slave->rx_ring_buf) {
|
||||
vRingbufferDeleteWithCaps(i2c_slave->rx_ring_buf);
|
||||
i2c_slave->rx_ring_buf = NULL;
|
||||
}
|
||||
if (i2c_slave->tx_ring_buf) {
|
||||
vRingbufferDeleteWithCaps(i2c_slave->tx_ring_buf);
|
||||
i2c_slave->tx_ring_buf = NULL;
|
||||
}
|
||||
if (i2c_slave->operation_mux) {
|
||||
vSemaphoreDeleteWithCaps(i2c_slave->operation_mux);
|
||||
i2c_slave->operation_mux = NULL;
|
||||
}
|
||||
if (i2c_slave->receive_desc.buffer) {
|
||||
free(i2c_slave->receive_desc.buffer);
|
||||
}
|
||||
esp_err_t ret = i2c_release_bus_handle(i2c_slave->base);
|
||||
|
||||
free(i2c_slave);
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t i2c_new_slave_device(const i2c_slave_config_t *slave_config, i2c_slave_dev_handle_t *ret_handle)
|
||||
{
|
||||
#if CONFIG_I2C_ENABLE_DEBUG_LOG
|
||||
esp_log_level_set(TAG, ESP_LOG_DEBUG);
|
||||
#endif
|
||||
esp_err_t ret = ESP_OK;
|
||||
i2c_slave_dev_t *i2c_slave = NULL;
|
||||
ESP_RETURN_ON_FALSE(slave_config && ret_handle, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
ESP_RETURN_ON_FALSE(GPIO_IS_VALID_GPIO(slave_config->sda_io_num) && GPIO_IS_VALID_GPIO(slave_config->scl_io_num), ESP_ERR_INVALID_ARG, TAG, "invalid SDA/SCL pin number");
|
||||
#if SOC_LP_I2C_SUPPORTED
|
||||
ESP_RETURN_ON_FALSE(slave_config->i2c_port != (SOC_I2C_NUM - 1), ESP_ERR_NOT_SUPPORTED, TAG, "LP i2c is not supported in I2C slave");
|
||||
#endif
|
||||
ESP_RETURN_ON_FALSE(slave_config->i2c_port < SOC_HP_I2C_NUM || slave_config->i2c_port == -1, ESP_ERR_INVALID_ARG, TAG, "invalid i2c port number");
|
||||
#if SOC_I2C_SLAVE_SUPPORT_BROADCAST
|
||||
ESP_RETURN_ON_FALSE(((slave_config->addr_bit_len != I2C_ADDR_BIT_LEN_10) || (!slave_config->flags.broadcast_en)), ESP_ERR_INVALID_STATE, TAG, "10bits address cannot used together with broadcast");
|
||||
#endif
|
||||
|
||||
i2c_slave = heap_caps_calloc(1, sizeof(i2c_slave_dev_t), I2C_MEM_ALLOC_CAPS);
|
||||
ESP_RETURN_ON_FALSE(i2c_slave, ESP_ERR_NO_MEM, TAG, "no memory for i2c slave bus");
|
||||
|
||||
ESP_GOTO_ON_ERROR(i2c_acquire_bus_handle(slave_config->i2c_port, &i2c_slave->base, I2C_BUS_MODE_SLAVE), err, TAG, "I2C bus acquire failed");
|
||||
|
||||
i2c_hal_context_t *hal = &i2c_slave->base->hal;
|
||||
i2c_slave->base->scl_num = slave_config->scl_io_num;
|
||||
i2c_slave->base->sda_num = slave_config->sda_io_num;
|
||||
i2c_slave->base->pull_up_enable = slave_config->flags.enable_internal_pullup;
|
||||
i2c_slave->rx_data_count = 0;
|
||||
int i2c_port_num = slave_config->i2c_port;
|
||||
ESP_GOTO_ON_ERROR(i2c_common_set_pins(i2c_slave->base), err, TAG, "i2c slave set pins failed");
|
||||
|
||||
i2c_slave->rx_ring_buf = xRingbufferCreateWithCaps(slave_config->receive_buf_depth, RINGBUF_TYPE_BYTEBUF, I2C_MEM_ALLOC_CAPS);
|
||||
ESP_GOTO_ON_FALSE(i2c_slave->rx_ring_buf != NULL, ESP_ERR_INVALID_STATE, err, TAG, "ringbuffer create failed");
|
||||
|
||||
i2c_slave->operation_mux = xSemaphoreCreateBinaryWithCaps(I2C_MEM_ALLOC_CAPS);
|
||||
ESP_GOTO_ON_FALSE(i2c_slave->operation_mux, ESP_ERR_NO_MEM, err, TAG, "No memory for binary semaphore");
|
||||
xSemaphoreGive(i2c_slave->operation_mux);
|
||||
|
||||
uint8_t *rcv_buffer = heap_caps_calloc(1, slave_config->receive_buf_depth, I2C_MEM_ALLOC_CAPS);
|
||||
ESP_RETURN_ON_FALSE(rcv_buffer, ESP_ERR_NO_MEM, TAG, "no memory for i2c slave receive internal buffer");
|
||||
|
||||
i2c_slave->receive_desc.buffer = rcv_buffer;
|
||||
i2c_slave->receive_desc.rcv_fifo_cnt = slave_config->receive_buf_depth;
|
||||
|
||||
i2c_slave->tx_ring_buf = xRingbufferCreateWithCaps(slave_config->send_buf_depth, RINGBUF_TYPE_BYTEBUF, I2C_MEM_ALLOC_CAPS);
|
||||
ESP_RETURN_ON_FALSE(i2c_slave->tx_ring_buf, ESP_ERR_NO_MEM, TAG, "no memory for i2c slave transmit ringbuffer");
|
||||
|
||||
#if I2C_USE_RETENTION_LINK
|
||||
if (slave_config->flags.allow_pd != 0) {
|
||||
i2c_create_retention_module(i2c_slave->base);
|
||||
}
|
||||
#endif // I2C_USE_RETENTION_LINK
|
||||
|
||||
int isr_flags = I2C_INTR_ALLOC_FLAG;
|
||||
if (slave_config->intr_priority) {
|
||||
isr_flags |= 1 << (slave_config->intr_priority);
|
||||
}
|
||||
ret = esp_intr_alloc_intrstatus(i2c_periph_signal[i2c_port_num].irq, isr_flags, (uint32_t)i2c_ll_get_interrupt_status_reg(hal->dev), I2C_LL_SLAVE_EVENT_INTR, i2c_slave_isr_handler, i2c_slave, &i2c_slave->base->intr_handle);
|
||||
ESP_GOTO_ON_ERROR(ret, err, TAG, "install i2c slave interrupt failed");
|
||||
|
||||
portENTER_CRITICAL(&i2c_slave->base->spinlock);
|
||||
i2c_hal_slave_init(hal);
|
||||
i2c_ll_slave_set_fifo_mode(hal->dev, true);
|
||||
i2c_ll_set_slave_addr(hal->dev, slave_config->slave_addr, false);
|
||||
i2c_ll_set_tout(hal->dev, I2C_LL_MAX_TIMEOUT);
|
||||
|
||||
I2C_CLOCK_SRC_ATOMIC() {
|
||||
i2c_ll_set_source_clk(hal->dev, slave_config->clk_source);
|
||||
}
|
||||
bool addr_10bit_en = slave_config->addr_bit_len != I2C_ADDR_BIT_LEN_7;
|
||||
i2c_ll_set_slave_addr(hal->dev, slave_config->slave_addr, addr_10bit_en);
|
||||
|
||||
#if SOC_I2C_SLAVE_SUPPORT_BROADCAST
|
||||
i2c_ll_slave_broadcast_enable(hal->dev, slave_config->flags.broadcast_en);
|
||||
#endif
|
||||
|
||||
i2c_ll_set_txfifo_empty_thr(hal->dev, SOC_I2C_FIFO_LEN / 2);
|
||||
i2c_ll_set_rxfifo_full_thr(hal->dev, SOC_I2C_FIFO_LEN / 2);
|
||||
i2c_ll_set_sda_timing(hal->dev, 10, 10);
|
||||
|
||||
i2c_ll_disable_intr_mask(hal->dev, I2C_LL_INTR_MASK);
|
||||
i2c_ll_clear_intr_mask(hal->dev, I2C_LL_INTR_MASK);
|
||||
|
||||
i2c_ll_enable_intr_mask(hal->dev, I2C_LL_SLAVE_RX_EVENT_INTR);
|
||||
|
||||
// Configure stretch
|
||||
i2c_ll_slave_set_stretch_protect_num(hal->dev, I2C_LL_STRETCH_PROTECT_TIME);
|
||||
i2c_ll_slave_enable_scl_stretch(hal->dev, true);
|
||||
i2c_ll_slave_clear_stretch(hal->dev);
|
||||
|
||||
i2c_ll_update(hal->dev);
|
||||
portEXIT_CRITICAL(&i2c_slave->base->spinlock);
|
||||
|
||||
*ret_handle = i2c_slave;
|
||||
return ret;
|
||||
|
||||
err:
|
||||
if (i2c_slave) {
|
||||
i2c_slave_device_destroy(i2c_slave);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t i2c_del_slave_device(i2c_slave_dev_handle_t i2c_slave)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(i2c_slave, ESP_ERR_INVALID_ARG, TAG, "i2c slave not initialized");
|
||||
int port_id = i2c_slave->base->port_num;
|
||||
ESP_LOGD(TAG, "del i2c bus(%d)", port_id);
|
||||
ESP_RETURN_ON_ERROR(i2c_slave_device_destroy(i2c_slave), TAG, "destroy i2c bus failed");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t i2c_slave_write(i2c_slave_dev_handle_t i2c_slave, const uint8_t *data, uint32_t len, uint32_t *write_len, int timeout_ms)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(i2c_slave, ESP_ERR_INVALID_ARG, TAG, "i2c slave not initialized");
|
||||
ESP_RETURN_ON_FALSE(data, ESP_ERR_INVALID_ARG, TAG, "invalid data buffer");
|
||||
ESP_RETURN_ON_FALSE(write_len, ESP_ERR_INVALID_ARG, TAG, "invalid write length pointer");
|
||||
uint32_t free_fifo_len = 0;
|
||||
uint32_t write_ringbuffer_len = 0;
|
||||
uint32_t actual_write_fifo_size = 0;
|
||||
uint8_t *existing_data = NULL;
|
||||
size_t existing_size = 0;
|
||||
i2c_hal_context_t *hal = &i2c_slave->base->hal;
|
||||
TickType_t wait_ticks = (timeout_ms == -1) ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms);
|
||||
|
||||
xSemaphoreTake(i2c_slave->operation_mux, wait_ticks);
|
||||
|
||||
portENTER_CRITICAL(&i2c_slave->base->spinlock);
|
||||
#if !SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE
|
||||
i2c_ll_slave_disable_tx_it(hal->dev);
|
||||
uint32_t txfifo_len = 0;
|
||||
i2c_ll_get_txfifo_len(hal->dev, &txfifo_len);
|
||||
if (txfifo_len < SOC_I2C_FIFO_LEN) {
|
||||
// For the target (esp32) cannot stretch, reset the fifo when there is any dirty data in fifo.
|
||||
i2c_ll_txfifo_rst(hal->dev);
|
||||
}
|
||||
#endif
|
||||
i2c_ll_get_txfifo_len(hal->dev, &free_fifo_len);
|
||||
portEXIT_CRITICAL(&i2c_slave->base->spinlock);
|
||||
|
||||
// Check if there is any data in the ringbuffer in last transaction
|
||||
existing_data = xRingbufferReceiveUpTo(i2c_slave->tx_ring_buf, &existing_size, 0, free_fifo_len);
|
||||
if (existing_data) {
|
||||
// has data, fill to the fifo
|
||||
i2c_ll_write_txfifo(hal->dev, existing_data, existing_size);
|
||||
free_fifo_len -= existing_size;
|
||||
vRingbufferReturnItem(i2c_slave->tx_ring_buf, existing_data);
|
||||
}
|
||||
|
||||
// Write data.
|
||||
if (free_fifo_len) {
|
||||
portENTER_CRITICAL(&i2c_slave->base->spinlock);
|
||||
if (len < free_fifo_len) {
|
||||
actual_write_fifo_size = len;
|
||||
}
|
||||
i2c_ll_write_txfifo(hal->dev, (uint8_t *)data, actual_write_fifo_size);
|
||||
data += actual_write_fifo_size;
|
||||
len -= actual_write_fifo_size;
|
||||
portEXIT_CRITICAL(&i2c_slave->base->spinlock);
|
||||
//write the rest of the bytes to the ringbuffer
|
||||
}
|
||||
|
||||
if (len) {
|
||||
write_ringbuffer_len = xRingbufferGetCurFreeSize(i2c_slave->tx_ring_buf);
|
||||
if (len < write_ringbuffer_len) {
|
||||
write_ringbuffer_len = len;
|
||||
}
|
||||
|
||||
if (xRingbufferSend(i2c_slave->tx_ring_buf, data, write_ringbuffer_len, wait_ticks) != pdTRUE) {
|
||||
write_ringbuffer_len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
*write_len = write_ringbuffer_len + actual_write_fifo_size;
|
||||
i2c_ll_slave_enable_tx_it(hal->dev);
|
||||
i2c_ll_slave_clear_stretch(hal->dev);
|
||||
|
||||
xSemaphoreGive(i2c_slave->operation_mux);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t i2c_slave_register_event_callbacks(i2c_slave_dev_handle_t i2c_slave, const i2c_slave_event_callbacks_t *cbs, void *user_data)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(i2c_slave != NULL, ESP_ERR_INVALID_ARG, TAG, "i2c slave handle not initialized");
|
||||
ESP_RETURN_ON_FALSE(cbs, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
#if CONFIG_I2C_ISR_IRAM_SAFE
|
||||
if (cbs->on_request) {
|
||||
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_request), ESP_ERR_INVALID_ARG, TAG, "i2c request occur callback not in IRAM");
|
||||
}
|
||||
if (cbs->on_receive) {
|
||||
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_receive), ESP_ERR_INVALID_ARG, TAG, "i2c receive occur callback not in IRAM");
|
||||
}
|
||||
if (user_data) {
|
||||
ESP_RETURN_ON_FALSE(esp_ptr_internal(user_data), ESP_ERR_INVALID_ARG, TAG, "user context not in internal RAM");
|
||||
}
|
||||
#endif
|
||||
|
||||
i2c_slave->user_ctx = user_data;
|
||||
i2c_slave->request_callback = cbs->on_request;
|
||||
i2c_slave->receive_callback = cbs->on_receive;
|
||||
return ESP_OK;
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@@ -10,11 +10,14 @@
|
||||
#include "driver/i2c_types.h"
|
||||
#include "hal/gpio_types.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#if !CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2
|
||||
|
||||
/**
|
||||
* @brief I2C slave specific configurations
|
||||
*/
|
||||
@@ -59,28 +62,6 @@ typedef struct {
|
||||
i2c_slave_received_callback_t on_recv_done; /*!< I2C slave receive done callback */
|
||||
} i2c_slave_event_callbacks_t;
|
||||
|
||||
/**
|
||||
* @brief Initialize an I2C slave device
|
||||
*
|
||||
* @param[in] slave_config I2C slave device configurations
|
||||
* @param[out] ret_handle Return a generic I2C device handle
|
||||
* @return
|
||||
* - ESP_OK: I2C slave device initialized successfully
|
||||
* - ESP_ERR_INVALID_ARG: I2C device initialization failed because of invalid argument.
|
||||
* - ESP_ERR_NO_MEM: Create I2C device failed because of out of memory.
|
||||
*/
|
||||
esp_err_t i2c_new_slave_device(const i2c_slave_config_t *slave_config, i2c_slave_dev_handle_t *ret_handle);
|
||||
|
||||
/**
|
||||
* @brief Deinitialize the I2C slave device
|
||||
*
|
||||
* @param[in] i2c_slave I2C slave device handle that created by `i2c_new_slave_device`.
|
||||
* @return
|
||||
* - ESP_OK: Delete I2C device successfully.
|
||||
* - ESP_ERR_INVALID_ARG: I2C device initialization failed because of invalid argument.
|
||||
*/
|
||||
esp_err_t i2c_del_slave_device(i2c_slave_dev_handle_t i2c_slave);
|
||||
|
||||
/**
|
||||
* @brief Read bytes from I2C internal buffer. Start a job to receive I2C data.
|
||||
*
|
||||
@@ -116,23 +97,6 @@ esp_err_t i2c_slave_receive(i2c_slave_dev_handle_t i2c_slave, uint8_t *data, siz
|
||||
*/
|
||||
esp_err_t i2c_slave_transmit(i2c_slave_dev_handle_t i2c_slave, const uint8_t *data, int size, int xfer_timeout_ms);
|
||||
|
||||
/**
|
||||
* @brief Set I2C slave event callbacks for I2C slave channel.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @param[in] i2c_slave I2C slave device handle that created by `i2c_new_slave_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_slave_register_event_callbacks(i2c_slave_dev_handle_t i2c_slave, const i2c_slave_event_callbacks_t *cbs, void *user_data);
|
||||
|
||||
#if SOC_I2C_SLAVE_SUPPORT_I2CRAM_ACCESS
|
||||
/**
|
||||
* @brief Read bytes from I2C internal ram. This can be only used when `access_ram_en` in configuration structure set to true.
|
||||
@@ -164,6 +128,103 @@ esp_err_t i2c_slave_read_ram(i2c_slave_dev_handle_t i2c_slave, uint8_t ram_addre
|
||||
esp_err_t i2c_slave_write_ram(i2c_slave_dev_handle_t i2c_slave, uint8_t ram_address, const uint8_t *data, size_t size);
|
||||
|
||||
#endif
|
||||
|
||||
#elif CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2 || __DOXYGEN__
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////// I2C SLAVE VERSION TWO /////////////////////////////////////////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* @brief I2C slave specific configurations
|
||||
*/
|
||||
typedef struct {
|
||||
i2c_port_num_t i2c_port; /*!< I2C port number, `-1` for auto selecting */
|
||||
gpio_num_t sda_io_num; /*!< SDA IO number used by I2C bus */
|
||||
gpio_num_t scl_io_num; /*!< SCL IO number used by I2C bus */
|
||||
i2c_clock_source_t clk_source; /*!< Clock source of I2C bus. */
|
||||
uint32_t send_buf_depth; /*!< Depth of internal transfer ringbuffer */
|
||||
uint32_t receive_buf_depth; /*!< Depth of receive internal software buffer */
|
||||
uint16_t slave_addr; /*!< I2C slave address */
|
||||
i2c_addr_bit_len_t addr_bit_len; /*!< I2C slave address in bit length */
|
||||
int intr_priority; /*!< I2C interrupt priority, if set to 0, driver will select the default priority (1,2,3). */
|
||||
struct {
|
||||
uint32_t allow_pd: 1; /*!< If set, the driver will backup/restore the I2C registers before/after entering/exist sleep mode.
|
||||
By this approach, the system can power off I2C's power domain.
|
||||
This can save power, but at the expense of more RAM being consumed */
|
||||
uint32_t enable_internal_pullup: 1; /*!< Enable internal pullups. Note: This is not strong enough to pullup buses under high-speed frequency. Recommend proper external pull-up if possible */
|
||||
#if SOC_I2C_SLAVE_SUPPORT_BROADCAST
|
||||
uint32_t broadcast_en: 1; /*!< I2C slave enable broadcast, able to respond to broadcast address */
|
||||
#endif
|
||||
} flags; /*!< I2C slave config flags */
|
||||
} i2c_slave_config_t;
|
||||
|
||||
/**
|
||||
* @brief Group of I2C slave callbacks. Take care of 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_slave_request_callback_t on_request; /*!< Callback for when a master requests data from the slave */
|
||||
i2c_slave_received_callback_t on_receive; /*!< Callback for when the slave receives data from the master */
|
||||
} i2c_slave_event_callbacks_t;
|
||||
|
||||
/**
|
||||
* @brief Write buffer to hardware fifo. If write length is larger than hardware fifo, then restore in software buffer.
|
||||
*
|
||||
* @param[in] i2c_slave I2C slave device handle that created by `i2c_new_slave_device`.
|
||||
* @param[in] data Buffer to write to slave fifo, can pickup by master.
|
||||
* @param[in] len In bytes, of `data` buffer.
|
||||
* @param[out] write_len In bytes, actually write length.
|
||||
* @param[in] timeout_ms Wait timeout, in ms. Note: -1 means wait forever.
|
||||
* @return
|
||||
* - ESP_OK: I2C slave write success.
|
||||
* - ESP_ERR_INVALID_ARG: I2C slave write parameter invalid.
|
||||
* - ESP_ERR_TIMEOUT: Operation timeout(larger than xfer_timeout_ms) because the device is busy or hardware crash.
|
||||
*/
|
||||
esp_err_t i2c_slave_write(i2c_slave_dev_handle_t i2c_slave, const uint8_t *data, uint32_t len, uint32_t *write_len, int timeout_ms);
|
||||
|
||||
#endif // CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2
|
||||
|
||||
/**
|
||||
* @brief Initialize an I2C slave device
|
||||
*
|
||||
* @param[in] slave_config I2C slave device configurations
|
||||
* @param[out] ret_handle Return a generic I2C device handle
|
||||
* @return
|
||||
* - ESP_OK: I2C slave device initialized successfully
|
||||
* - ESP_ERR_INVALID_ARG: I2C device initialization failed because of invalid argument.
|
||||
* - ESP_ERR_NO_MEM: Create I2C device failed because of out of memory.
|
||||
*/
|
||||
esp_err_t i2c_new_slave_device(const i2c_slave_config_t *slave_config, i2c_slave_dev_handle_t *ret_handle);
|
||||
|
||||
/**
|
||||
* @brief Set I2C slave event callbacks for I2C slave channel.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @param[in] i2c_slave I2C slave device handle that created by `i2c_new_slave_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_slave_register_event_callbacks(i2c_slave_dev_handle_t i2c_slave, const i2c_slave_event_callbacks_t *cbs, void *user_data);
|
||||
|
||||
/**
|
||||
* @brief Deinitialize the I2C slave device
|
||||
*
|
||||
* @param[in] i2c_slave I2C slave device handle that created by `i2c_new_slave_device`.
|
||||
* @return
|
||||
* - ESP_OK: Delete I2C device successfully.
|
||||
* - ESP_ERR_INVALID_ARG: I2C device initialization failed because of invalid argument.
|
||||
*/
|
||||
esp_err_t i2c_del_slave_device(i2c_slave_dev_handle_t i2c_slave);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <stdbool.h>
|
||||
#include "hal/i2c_types.h"
|
||||
#include "soc/soc_caps.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -82,6 +83,9 @@ typedef bool (*i2c_master_callback_t)(i2c_master_dev_handle_t i2c_dev, const i2c
|
||||
*/
|
||||
typedef struct {
|
||||
uint8_t *buffer; /**< Pointer for buffer received in callback. */
|
||||
#if CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2
|
||||
uint32_t length; /**< Length for buffer received in callback. */
|
||||
#endif
|
||||
} i2c_slave_rx_done_event_data_t;
|
||||
|
||||
/**
|
||||
@@ -117,6 +121,25 @@ typedef bool (*i2c_slave_stretch_callback_t)(i2c_slave_dev_handle_t i2c_slave, c
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Event structure used in I2C slave request.
|
||||
*/
|
||||
typedef struct {
|
||||
|
||||
} i2c_slave_request_event_data_t;
|
||||
|
||||
/**
|
||||
* @brief Callback signature for I2C slave request event. When this callback is triggered that means master want to read data
|
||||
* from slave while there is no data in slave fifo. So user should write data to fifo via `i2c_slave_write`
|
||||
*
|
||||
* @param[in] i2c_slave Handle for I2C slave.
|
||||
* @param[out] evt_data I2C receive event data, fed by driver
|
||||
* @param[in] arg User data, set in `i2c_slave_register_event_callbacks()`
|
||||
*
|
||||
* @return Whether a high priority task has been waken up by this function
|
||||
*/
|
||||
typedef bool (*i2c_slave_request_callback_t)(i2c_slave_dev_handle_t i2c_slave, const i2c_slave_request_event_data_t *evt_data, void *arg);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
@@ -3,32 +3,37 @@ set(srcs "test_app_main.c"
|
||||
)
|
||||
|
||||
if(CONFIG_SOC_I2C_SUPPORT_SLAVE)
|
||||
list(APPEND srcs "test_i2c_multi.c")
|
||||
if(CONFIG_I2C_ISR_IRAM_SAFE)
|
||||
list(APPEND srcs "test_i2c_iram.c")
|
||||
if(CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2)
|
||||
list(APPEND srcs "test_i2c_slave_v2.c")
|
||||
else()
|
||||
list(APPEND srcs "test_i2c_multi.c")
|
||||
|
||||
if(CONFIG_SOC_I2C_SLAVE_SUPPORT_BROADCAST)
|
||||
list(APPEND srcs "test_i2c_broadcast.c")
|
||||
endif()
|
||||
|
||||
if(CONFIG_SOC_I2C_SLAVE_SUPPORT_I2CRAM_ACCESS)
|
||||
list(APPEND srcs "test_i2c_ram.c")
|
||||
endif()
|
||||
|
||||
if(CONFIG_SOC_I2C_SUPPORT_10BIT_ADDR AND CONFIG_SOC_I2C_SUPPORT_SLAVE)
|
||||
list(APPEND srcs "test_i2c_10bit.c")
|
||||
endif()
|
||||
|
||||
if(CONFIG_SOC_LP_I2C_SUPPORTED)
|
||||
list(APPEND srcs "test_lp_i2c.c")
|
||||
endif()
|
||||
|
||||
if(CONFIG_SOC_I2C_SUPPORT_SLEEP_RETENTION)
|
||||
list(APPEND srcs "test_i2c_sleep_retention.c")
|
||||
endif()
|
||||
|
||||
if(CONFIG_I2C_ISR_IRAM_SAFE)
|
||||
list(APPEND srcs "test_i2c_iram.c")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(CONFIG_SOC_I2C_SLAVE_SUPPORT_BROADCAST)
|
||||
list(APPEND srcs "test_i2c_broadcast.c")
|
||||
endif()
|
||||
|
||||
if(CONFIG_SOC_I2C_SLAVE_SUPPORT_I2CRAM_ACCESS)
|
||||
list(APPEND srcs "test_i2c_ram.c")
|
||||
endif()
|
||||
|
||||
if(CONFIG_SOC_I2C_SUPPORT_10BIT_ADDR AND CONFIG_SOC_I2C_SUPPORT_SLAVE)
|
||||
list(APPEND srcs "test_i2c_10bit.c")
|
||||
endif()
|
||||
|
||||
if(CONFIG_SOC_LP_I2C_SUPPORTED)
|
||||
list(APPEND srcs "test_lp_i2c.c")
|
||||
endif()
|
||||
|
||||
if(CONFIG_SOC_I2C_SUPPORT_SLEEP_RETENTION)
|
||||
list(APPEND srcs "test_i2c_sleep_retention.c")
|
||||
endif()
|
||||
|
||||
idf_component_register(SRCS ${srcs}
|
||||
PRIV_REQUIRES unity driver test_utils
|
||||
WHOLE_ARCHIVE)
|
||||
|
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 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/queue.h"
|
||||
#include "esp_err.h"
|
||||
#include "driver/i2c_master.h"
|
||||
#include "driver/i2c_slave.h"
|
||||
#include "esp_rom_gpio.h"
|
||||
#include "esp_log.h"
|
||||
#include "test_utils.h"
|
||||
#include "test_board.h"
|
||||
|
||||
#if SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE
|
||||
|
||||
static QueueHandle_t event_queue;
|
||||
static uint8_t *temp_data;
|
||||
static size_t temp_len = 0;
|
||||
|
||||
typedef enum {
|
||||
I2C_SLAVE_EVT_RX,
|
||||
I2C_SLAVE_EVT_TX
|
||||
} i2c_slave_event_t;
|
||||
|
||||
void disp_buf(uint8_t *buf, int len)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < len; i++) {
|
||||
printf("%02x ", buf[i]);
|
||||
if ((i + 1) % 16 == 0) {
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
static bool i2c_slave_request_cb(i2c_slave_dev_handle_t i2c_slave, const i2c_slave_request_event_data_t *evt_data, void *arg)
|
||||
{
|
||||
BaseType_t xTaskWoken;
|
||||
i2c_slave_event_t evt = I2C_SLAVE_EVT_TX;
|
||||
xQueueSendFromISR(event_queue, &evt, &xTaskWoken);
|
||||
return xTaskWoken;
|
||||
}
|
||||
|
||||
static bool i2c_slave_receive_cb(i2c_slave_dev_handle_t i2c_slave, const i2c_slave_rx_done_event_data_t *evt_data, void *arg)
|
||||
{
|
||||
BaseType_t xTaskWoken;
|
||||
i2c_slave_event_t evt = I2C_SLAVE_EVT_RX;
|
||||
memcpy(temp_data, evt_data->buffer, evt_data->length);
|
||||
temp_len = evt_data->length;
|
||||
xQueueSendFromISR(event_queue, &evt, &xTaskWoken);
|
||||
return xTaskWoken;
|
||||
}
|
||||
|
||||
static void i2c_slave_read_test_v2(void)
|
||||
{
|
||||
i2c_slave_dev_handle_t handle;
|
||||
event_queue = xQueueCreate(2, sizeof(i2c_slave_event_t));
|
||||
assert(event_queue);
|
||||
temp_data = malloc(DATA_LENGTH);
|
||||
assert(temp_data);
|
||||
|
||||
i2c_slave_config_t i2c_slv_config = {
|
||||
.i2c_port = TEST_I2C_PORT,
|
||||
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||
.scl_io_num = I2C_SLAVE_SCL_IO,
|
||||
.sda_io_num = I2C_SLAVE_SDA_IO,
|
||||
.slave_addr = ESP_SLAVE_ADDR,
|
||||
.send_buf_depth = DATA_LENGTH,
|
||||
.receive_buf_depth = DATA_LENGTH,
|
||||
};
|
||||
|
||||
TEST_ESP_OK(i2c_new_slave_device(&i2c_slv_config, &handle));
|
||||
|
||||
i2c_slave_event_callbacks_t cbs = {
|
||||
.on_receive = i2c_slave_receive_cb,
|
||||
.on_request = i2c_slave_request_cb,
|
||||
};
|
||||
|
||||
TEST_ESP_OK(i2c_slave_register_event_callbacks(handle, &cbs, NULL));
|
||||
|
||||
unity_send_signal("i2c slave init finish");
|
||||
|
||||
unity_wait_for_signal("master write");
|
||||
|
||||
i2c_slave_event_t evt;
|
||||
if (xQueueReceive(event_queue, &evt, 1) == pdTRUE) {
|
||||
if (evt == I2C_SLAVE_EVT_RX) {
|
||||
disp_buf(temp_data, temp_len);
|
||||
printf("length is %x\n", temp_len);
|
||||
for (int i = 0; i < temp_len; i++) {
|
||||
TEST_ASSERT(temp_data[i] == i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unity_send_signal("ready to delete");
|
||||
free(temp_data);
|
||||
vQueueDelete(event_queue);
|
||||
TEST_ESP_OK(i2c_del_slave_device(handle));
|
||||
}
|
||||
|
||||
static void i2c_master_write_test_v2(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 = ESP_SLAVE_ADDR,
|
||||
.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);
|
||||
TEST_ESP_OK(i2c_master_transmit(dev_handle, data_wr, DATA_LENGTH, -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));
|
||||
}
|
||||
|
||||
TEST_CASE_MULTIPLE_DEVICES("I2C master write slave v2 test", "[i2c][test_env=generic_multi_device][timeout=150]", i2c_master_write_test_v2, i2c_slave_read_test_v2);
|
||||
|
||||
static void master_read_slave_test_v2(void)
|
||||
{
|
||||
uint8_t data_rd[DATA_LENGTH] = {0};
|
||||
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 = ESP_SLAVE_ADDR,
|
||||
.scl_speed_hz = 100000,
|
||||
.scl_wait_us = 20000,
|
||||
};
|
||||
|
||||
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");
|
||||
|
||||
TEST_ESP_OK(i2c_master_receive(dev_handle, data_rd, DATA_LENGTH, -1));
|
||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||
for (int i = 0; i < DATA_LENGTH; i++) {
|
||||
printf("%x\n", data_rd[i]);
|
||||
TEST_ASSERT(data_rd[i] == i);
|
||||
}
|
||||
unity_send_signal("ready to delete master read test");
|
||||
|
||||
TEST_ESP_OK(i2c_master_bus_rm_device(dev_handle));
|
||||
TEST_ESP_OK(i2c_del_master_bus(bus_handle));
|
||||
}
|
||||
|
||||
static void slave_write_buffer_test_v2(void)
|
||||
{
|
||||
i2c_slave_dev_handle_t handle;
|
||||
uint8_t data_wr[DATA_LENGTH];
|
||||
event_queue = xQueueCreate(2, sizeof(i2c_slave_event_t));
|
||||
assert(event_queue);
|
||||
|
||||
i2c_slave_config_t i2c_slv_config = {
|
||||
.i2c_port = TEST_I2C_PORT,
|
||||
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||
.scl_io_num = I2C_SLAVE_SCL_IO,
|
||||
.sda_io_num = I2C_SLAVE_SDA_IO,
|
||||
.slave_addr = ESP_SLAVE_ADDR,
|
||||
.send_buf_depth = DATA_LENGTH,
|
||||
.receive_buf_depth = DATA_LENGTH,
|
||||
};
|
||||
|
||||
TEST_ESP_OK(i2c_new_slave_device(&i2c_slv_config, &handle));
|
||||
|
||||
i2c_slave_event_callbacks_t cbs = {
|
||||
.on_receive = i2c_slave_receive_cb,
|
||||
.on_request = i2c_slave_request_cb,
|
||||
};
|
||||
|
||||
TEST_ESP_OK(i2c_slave_register_event_callbacks(handle, &cbs, NULL));
|
||||
|
||||
unity_send_signal("i2c slave init finish");
|
||||
|
||||
for (int i = 0; i < DATA_LENGTH; i++) {
|
||||
data_wr[i] = i;
|
||||
}
|
||||
|
||||
i2c_slave_event_t evt;
|
||||
uint32_t write_len;
|
||||
while (true) {
|
||||
if (xQueueReceive(event_queue, &evt, portMAX_DELAY) == pdTRUE) {
|
||||
if (evt == I2C_SLAVE_EVT_TX) {
|
||||
TEST_ESP_OK(i2c_slave_write(handle, data_wr, DATA_LENGTH, &write_len, 1000));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unity_wait_for_signal("ready to delete master read test");
|
||||
vQueueDelete(event_queue);
|
||||
TEST_ESP_OK(i2c_del_slave_device(handle));
|
||||
}
|
||||
|
||||
TEST_CASE_MULTIPLE_DEVICES("I2C master read slave test", "[i2c][test_env=generic_multi_device][timeout=150]", master_read_slave_test_v2, slave_write_buffer_test_v2);
|
||||
|
||||
#endif // SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE
|
@@ -33,6 +33,7 @@ def test_i2c(dut: Dut) -> None:
|
||||
(2, 'defaults',),
|
||||
(2, 'release',),
|
||||
(2, 'iram_safe',),
|
||||
(2, 'slave_v2',),
|
||||
],
|
||||
indirect=True
|
||||
)
|
||||
|
@@ -0,0 +1 @@
|
||||
CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2=y
|
@@ -67,11 +67,13 @@ typedef enum {
|
||||
// Get the I2C hardware instance
|
||||
#define I2C_LL_GET_HW(i2c_num) (((i2c_num) == 0) ? &I2C0 : &I2C1)
|
||||
#define I2C_LL_MASTER_EVENT_INTR (I2C_ACK_ERR_INT_ENA_M|I2C_TIME_OUT_INT_ENA_M|I2C_TRANS_COMPLETE_INT_ENA_M|I2C_ARBITRATION_LOST_INT_ENA_M|I2C_END_DETECT_INT_ENA_M)
|
||||
#define I2C_LL_SLAVE_EVENT_INTR (I2C_TRANS_COMPLETE_INT_ENA_M|I2C_TXFIFO_EMPTY_INT_ENA_M|I2C_RX_REC_FULL_INT_ST_M)
|
||||
#define I2C_LL_SLAVE_EVENT_INTR (I2C_TRANS_COMPLETE_INT_ENA_M|I2C_TXFIFO_EMPTY_INT_ENA_M|I2C_RX_REC_FULL_INT_ST_M|I2C_RXFIFO_FULL_INT_ENA)
|
||||
#define I2C_LL_SLAVE_RX_EVENT_INTR (I2C_TRANS_COMPLETE_INT_ENA_M|I2C_RX_REC_FULL_INT_ST_M)
|
||||
#define I2C_LL_SLAVE_TX_EVENT_INTR (I2C_TXFIFO_EMPTY_INT_ENA_M)
|
||||
#define I2C_LL_SCL_WAIT_US_VAL_DEFAULT (2000) // 2000 is not default value on esp32, but 0 is not good to be default
|
||||
|
||||
#define I2C_LL_STRETCH_PROTECT_TIME (0x3ff) // Not supported on esp32, keep consistent with other chips.
|
||||
|
||||
/**
|
||||
* @brief Calculate I2C bus frequency
|
||||
*
|
||||
@@ -808,6 +810,16 @@ static inline void i2c_ll_slave_clear_stretch(i2c_dev_t *dev)
|
||||
// Not supported on esp32
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set I2C clock stretch protect num
|
||||
*
|
||||
* @param dev Beginning address of the peripheral registers
|
||||
*/
|
||||
static inline void i2c_ll_slave_set_stretch_protect_num(i2c_dev_t *dev, uint32_t protect_num)
|
||||
{
|
||||
// Not supported on esp32
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if i2c command is done.
|
||||
*
|
||||
@@ -835,6 +847,18 @@ static inline uint32_t i2c_ll_calculate_timeout_us_to_reg_val(uint32_t src_clk_h
|
||||
return clk_cycle_num_per_us * timeout_us;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get status of i2c slave
|
||||
*
|
||||
* @param Beginning address of the peripheral registers
|
||||
* @return i2c slave working status
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline i2c_slave_read_write_status_t i2c_ll_slave_get_read_write_status(i2c_dev_t *hw)
|
||||
{
|
||||
return (hw->status_reg.slave_rw == 0) ? I2C_SLAVE_WRITE_BY_MASTER : I2C_SLAVE_READ_BY_MASTER;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////Deprecated Functions//////////////////////////////////////////////////////////
|
||||
/////////////////////////////The following functions are only used by the legacy driver/////////////////////////////////
|
||||
/////////////////////////////They might be removed in the next major release (ESP-IDF 6.0)//////////////////////////////
|
||||
|
@@ -76,6 +76,8 @@ typedef enum {
|
||||
#define I2C_LL_RESET_SLV_SCL_PULSE_NUM_DEFAULT (9)
|
||||
#define I2C_LL_SCL_WAIT_US_VAL_DEFAULT (2000) // Approximate value for SCL timeout regs (in us).
|
||||
|
||||
#define I2C_LL_STRETCH_PROTECT_TIME (0x3ff)
|
||||
|
||||
/**
|
||||
* @brief Calculate I2C bus frequency
|
||||
* Note that the clock accuracy is affected by the external pull-up resistor,
|
||||
@@ -916,6 +918,16 @@ static inline void i2c_ll_slave_clear_stretch(i2c_dev_t *dev)
|
||||
dev->scl_stretch_conf.slave_scl_stretch_clr = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set I2C clock stretch protect num
|
||||
*
|
||||
* @param dev Beginning address of the peripheral registers
|
||||
*/
|
||||
static inline void i2c_ll_slave_set_stretch_protect_num(i2c_dev_t *dev, uint32_t protect_num)
|
||||
{
|
||||
dev->scl_stretch_conf.stretch_protect_num = protect_num;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if i2c command is done.
|
||||
*
|
||||
@@ -944,6 +956,18 @@ static inline uint32_t i2c_ll_calculate_timeout_us_to_reg_val(uint32_t src_clk_h
|
||||
return 32 - __builtin_clz(clk_cycle_num_per_us * timeout_us);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get status of i2c slave
|
||||
*
|
||||
* @param Beginning address of the peripheral registers
|
||||
* @return i2c slave working status
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline i2c_slave_read_write_status_t i2c_ll_slave_get_read_write_status(i2c_dev_t *hw)
|
||||
{
|
||||
return (hw->sr.slave_rw == 0) ? I2C_SLAVE_WRITE_BY_MASTER : I2C_SLAVE_READ_BY_MASTER;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////Deprecated Functions//////////////////////////////////////////////////////////
|
||||
/////////////////////////////The following functions are only used by the legacy driver/////////////////////////////////
|
||||
/////////////////////////////They might be removed in the next major release (ESP-IDF 6.0)//////////////////////////////
|
||||
|
@@ -78,6 +78,8 @@ typedef enum {
|
||||
#define I2C_LL_RESET_SLV_SCL_PULSE_NUM_DEFAULT (9)
|
||||
#define I2C_LL_SCL_WAIT_US_VAL_DEFAULT (2500) // Approximate value for SCL timeout regs (in us).
|
||||
|
||||
#define I2C_LL_STRETCH_PROTECT_TIME (0x3ff)
|
||||
|
||||
// Record for Pins usage logs
|
||||
|
||||
#define LP_I2C_SCL_PIN_ERR_LOG "SCL pin can only be configured as GPIO#7"
|
||||
@@ -954,6 +956,16 @@ static inline void i2c_ll_slave_clear_stretch(i2c_dev_t *dev)
|
||||
dev->scl_stretch_conf.slave_scl_stretch_clr = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set I2C clock stretch protect num
|
||||
*
|
||||
* @param dev Beginning address of the peripheral registers
|
||||
*/
|
||||
static inline void i2c_ll_slave_set_stretch_protect_num(i2c_dev_t *dev, uint32_t protect_num)
|
||||
{
|
||||
dev->scl_stretch_conf.stretch_protect_num = protect_num;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if i2c command is done.
|
||||
*
|
||||
@@ -982,6 +994,18 @@ static inline uint32_t i2c_ll_calculate_timeout_us_to_reg_val(uint32_t src_clk_h
|
||||
return 32 - __builtin_clz(clk_cycle_num_per_us * timeout_us);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get status of i2c slave
|
||||
*
|
||||
* @param Beginning address of the peripheral registers
|
||||
* @return i2c slave working status
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline i2c_slave_read_write_status_t i2c_ll_slave_get_read_write_status(i2c_dev_t *hw)
|
||||
{
|
||||
return (hw->sr.slave_rw == 0) ? I2C_SLAVE_WRITE_BY_MASTER : I2C_SLAVE_READ_BY_MASTER;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////Deprecated Functions//////////////////////////////////////////////////////////
|
||||
/////////////////////////////The following functions are only used by the legacy driver/////////////////////////////////
|
||||
/////////////////////////////They might be removed in the next major release (ESP-IDF 6.0)//////////////////////////////
|
||||
|
@@ -77,6 +77,8 @@ typedef enum {
|
||||
#define I2C_LL_RESET_SLV_SCL_PULSE_NUM_DEFAULT (9)
|
||||
#define I2C_LL_SCL_WAIT_US_VAL_DEFAULT (2000) // Approximate value for SCL timeout regs (in us).
|
||||
|
||||
#define I2C_LL_STRETCH_PROTECT_TIME (0x3ff)
|
||||
|
||||
// Record for Pins usage logs
|
||||
|
||||
#define LP_I2C_SCL_PIN_ERR_LOG "SCL pin can only be configured as GPIO#7"
|
||||
@@ -956,6 +958,16 @@ static inline void i2c_ll_slave_clear_stretch(i2c_dev_t *dev)
|
||||
dev->scl_stretch_conf.slave_scl_stretch_clr = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set I2C clock stretch protect num
|
||||
*
|
||||
* @param dev Beginning address of the peripheral registers
|
||||
*/
|
||||
static inline void i2c_ll_slave_set_stretch_protect_num(i2c_dev_t *dev, uint32_t protect_num)
|
||||
{
|
||||
dev->scl_stretch_conf.stretch_protect_num = protect_num;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if i2c command is done.
|
||||
*
|
||||
@@ -984,6 +996,18 @@ static inline uint32_t i2c_ll_calculate_timeout_us_to_reg_val(uint32_t src_clk_h
|
||||
return 32 - __builtin_clz(clk_cycle_num_per_us * timeout_us);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get status of i2c slave
|
||||
*
|
||||
* @param Beginning address of the peripheral registers
|
||||
* @return i2c slave working status
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline i2c_slave_read_write_status_t i2c_ll_slave_get_read_write_status(i2c_dev_t *hw)
|
||||
{
|
||||
return (hw->sr.slave_rw == 0) ? I2C_SLAVE_WRITE_BY_MASTER : I2C_SLAVE_READ_BY_MASTER;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////Deprecated Functions//////////////////////////////////////////////////////////
|
||||
/////////////////////////////The following functions are only used by the legacy driver/////////////////////////////////
|
||||
/////////////////////////////They might be removed in the next major release (ESP-IDF 6.0)//////////////////////////////
|
||||
|
@@ -78,6 +78,8 @@ typedef enum {
|
||||
#define I2C_LL_RESET_SLV_SCL_PULSE_NUM_DEFAULT (9)
|
||||
#define I2C_LL_SCL_WAIT_US_VAL_DEFAULT (2500) // Approximate value for SCL timeout regs (in us).
|
||||
|
||||
#define I2C_LL_STRETCH_PROTECT_TIME (0x3ff)
|
||||
|
||||
/**
|
||||
* @brief Calculate I2C bus frequency
|
||||
* Note that the clock accuracy is affected by the external pull-up resistor,
|
||||
@@ -880,6 +882,16 @@ static inline void i2c_ll_slave_clear_stretch(i2c_dev_t *dev)
|
||||
dev->scl_stretch_conf.slave_scl_stretch_clr = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set I2C clock stretch protect num
|
||||
*
|
||||
* @param dev Beginning address of the peripheral registers
|
||||
*/
|
||||
static inline void i2c_ll_slave_set_stretch_protect_num(i2c_dev_t *dev, uint32_t protect_num)
|
||||
{
|
||||
dev->scl_stretch_conf.stretch_protect_num = protect_num;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if i2c command is done.
|
||||
*
|
||||
@@ -908,6 +920,18 @@ static inline uint32_t i2c_ll_calculate_timeout_us_to_reg_val(uint32_t src_clk_h
|
||||
return 32 - __builtin_clz(clk_cycle_num_per_us * timeout_us);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get status of i2c slave
|
||||
*
|
||||
* @param Beginning address of the peripheral registers
|
||||
* @return i2c slave working status
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline i2c_slave_read_write_status_t i2c_ll_slave_get_read_write_status(i2c_dev_t *hw)
|
||||
{
|
||||
return (hw->sr.slave_rw == 0) ? I2C_SLAVE_WRITE_BY_MASTER : I2C_SLAVE_READ_BY_MASTER;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////Deprecated Functions//////////////////////////////////////////////////////////
|
||||
/////////////////////////////The following functions are only used by the legacy driver/////////////////////////////////
|
||||
/////////////////////////////They might be removed in the next major release (ESP-IDF 6.0)//////////////////////////////
|
||||
|
@@ -76,6 +76,8 @@ typedef enum {
|
||||
#define I2C_LL_RESET_SLV_SCL_PULSE_NUM_DEFAULT (9)
|
||||
#define I2C_LL_SCL_WAIT_US_VAL_DEFAULT (2500) // Approximate value for SCL timeout regs (in us).
|
||||
|
||||
#define I2C_LL_STRETCH_PROTECT_TIME (0x3ff)
|
||||
|
||||
/**
|
||||
* @brief Calculate I2C bus frequency
|
||||
* Note that the clock accuracy is affected by the external pull-up resistor,
|
||||
@@ -863,6 +865,16 @@ static inline void i2c_ll_slave_clear_stretch(i2c_dev_t *dev)
|
||||
dev->scl_stretch_conf.slave_scl_stretch_clr = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set I2C clock stretch protect num
|
||||
*
|
||||
* @param dev Beginning address of the peripheral registers
|
||||
*/
|
||||
static inline void i2c_ll_slave_set_stretch_protect_num(i2c_dev_t *dev, uint32_t protect_num)
|
||||
{
|
||||
dev->scl_stretch_conf.stretch_protect_num = protect_num;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if i2c command is done.
|
||||
*
|
||||
@@ -891,6 +903,18 @@ static inline uint32_t i2c_ll_calculate_timeout_us_to_reg_val(uint32_t src_clk_h
|
||||
return 32 - __builtin_clz(clk_cycle_num_per_us * timeout_us);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get status of i2c slave
|
||||
*
|
||||
* @param Beginning address of the peripheral registers
|
||||
* @return i2c slave working status
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline i2c_slave_read_write_status_t i2c_ll_slave_get_read_write_status(i2c_dev_t *hw)
|
||||
{
|
||||
return (hw->sr.slave_rw == 0) ? I2C_SLAVE_WRITE_BY_MASTER : I2C_SLAVE_READ_BY_MASTER;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////Deprecated Functions//////////////////////////////////////////////////////////
|
||||
/////////////////////////////The following functions are only used by the legacy driver/////////////////////////////////
|
||||
/////////////////////////////They might be removed in the next major release (ESP-IDF 6.0)//////////////////////////////
|
||||
|
@@ -82,6 +82,8 @@ typedef enum {
|
||||
#define I2C_LL_RESET_SLV_SCL_PULSE_NUM_DEFAULT (9)
|
||||
#define I2C_LL_SCL_WAIT_US_VAL_DEFAULT (2000) // Approximate value for SCL timeout regs (in us).
|
||||
|
||||
#define I2C_LL_STRETCH_PROTECT_TIME (0x3ff)
|
||||
|
||||
/**
|
||||
* @brief Calculate I2C bus frequency
|
||||
* Note that the clock accuracy is affected by the external pull-up resistor,
|
||||
@@ -353,6 +355,36 @@ static inline void i2c_ll_slave_broadcast_enable(i2c_dev_t *hw, bool broadcast_e
|
||||
hw->ctr.addr_broadcasting_en = broadcast_en;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the cause of SCL clock stretching in slave mode
|
||||
*
|
||||
* @param hw Beginning address of the peripheral registers
|
||||
* @param stretch_cause Pointer to stretch cause in the slave mode.
|
||||
*
|
||||
* @return None
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline void i2c_ll_slave_get_stretch_cause(i2c_dev_t *hw, i2c_slave_stretch_cause_t *stretch_cause)
|
||||
{
|
||||
switch (hw->sr.stretch_cause) {
|
||||
case 0:
|
||||
*stretch_cause = I2C_SLAVE_STRETCH_CAUSE_ADDRESS_MATCH;
|
||||
break;
|
||||
case 1:
|
||||
*stretch_cause = I2C_SLAVE_STRETCH_CAUSE_TX_EMPTY;
|
||||
break;
|
||||
case 2:
|
||||
*stretch_cause = I2C_SLAVE_STRETCH_CAUSE_RX_FULL;
|
||||
break;
|
||||
case 3:
|
||||
*stretch_cause = I2C_SLAVE_STRETCH_CAUSE_SENDING_ACK;
|
||||
break;
|
||||
default:
|
||||
HAL_ASSERT(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Configure I2C slave address
|
||||
*
|
||||
@@ -963,6 +995,16 @@ static inline void i2c_ll_slave_clear_stretch(i2c_dev_t *dev)
|
||||
dev->scl_stretch_conf.slave_scl_stretch_clr = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set I2C clock stretch protect num
|
||||
*
|
||||
* @param dev Beginning address of the peripheral registers
|
||||
*/
|
||||
static inline void i2c_ll_slave_set_stretch_protect_num(i2c_dev_t *dev, uint32_t protect_num)
|
||||
{
|
||||
dev->scl_stretch_conf.stretch_protect_num = protect_num;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if i2c command is done.
|
||||
*
|
||||
@@ -991,6 +1033,18 @@ static inline uint32_t i2c_ll_calculate_timeout_us_to_reg_val(uint32_t src_clk_h
|
||||
return 32 - __builtin_clz(clk_cycle_num_per_us * timeout_us);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get status of i2c slave
|
||||
*
|
||||
* @param Beginning address of the peripheral registers
|
||||
* @return i2c slave working status
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline i2c_slave_read_write_status_t i2c_ll_slave_get_read_write_status(i2c_dev_t *hw)
|
||||
{
|
||||
return (hw->sr.slave_rw == 0) ? I2C_SLAVE_WRITE_BY_MASTER : I2C_SLAVE_READ_BY_MASTER;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////Deprecated Functions//////////////////////////////////////////////////////////
|
||||
/////////////////////////////The following functions are only used by the legacy driver/////////////////////////////////
|
||||
/////////////////////////////They might be removed in the next major release (ESP-IDF 6.0)//////////////////////////////
|
||||
|
@@ -15,6 +15,7 @@
|
||||
#include "hal/i2c_types.h"
|
||||
#include "esp_attr.h"
|
||||
#include "hal/misc.h"
|
||||
#include "hal/assert.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -72,6 +73,8 @@ typedef enum {
|
||||
#define I2C_LL_RESET_SLV_SCL_PULSE_NUM_DEFAULT (9)
|
||||
#define I2C_LL_SCL_WAIT_US_VAL_DEFAULT (2000) // 2000 is not default value on esp32s2, but 0 is not good to be default
|
||||
|
||||
#define I2C_LL_STRETCH_PROTECT_TIME (0x3ff)
|
||||
|
||||
/**
|
||||
* @brief Calculate I2C bus frequency
|
||||
*
|
||||
@@ -309,7 +312,23 @@ static inline void i2c_ll_slave_broadcast_enable(i2c_dev_t *hw, bool broadcast_e
|
||||
__attribute__((always_inline))
|
||||
static inline void i2c_ll_slave_get_stretch_cause(i2c_dev_t *hw, i2c_slave_stretch_cause_t *stretch_cause)
|
||||
{
|
||||
// Not supported on esp32s2
|
||||
switch (hw->status_reg.stretch_cause) {
|
||||
case 0:
|
||||
*stretch_cause = I2C_SLAVE_STRETCH_CAUSE_ADDRESS_MATCH;
|
||||
break;
|
||||
case 1:
|
||||
*stretch_cause = I2C_SLAVE_STRETCH_CAUSE_TX_EMPTY;
|
||||
break;
|
||||
case 2:
|
||||
*stretch_cause = I2C_SLAVE_STRETCH_CAUSE_RX_FULL;
|
||||
break;
|
||||
case 3:
|
||||
*stretch_cause = I2C_SLAVE_STRETCH_CAUSE_SENDING_ACK;
|
||||
break;
|
||||
default:
|
||||
HAL_ASSERT(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -858,6 +877,16 @@ static inline void i2c_ll_slave_clear_stretch(i2c_dev_t *dev)
|
||||
dev->scl_stretch_conf.slave_scl_stretch_clr = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set I2C clock stretch protect num
|
||||
*
|
||||
* @param dev Beginning address of the peripheral registers
|
||||
*/
|
||||
static inline void i2c_ll_slave_set_stretch_protect_num(i2c_dev_t *dev, uint32_t protect_num)
|
||||
{
|
||||
dev->scl_stretch_conf.stretch_protect_num = protect_num;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if i2c command is done.
|
||||
*
|
||||
@@ -885,6 +914,18 @@ static inline uint32_t i2c_ll_calculate_timeout_us_to_reg_val(uint32_t src_clk_h
|
||||
return clk_cycle_num_per_us * timeout_us;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get status of i2c slave
|
||||
*
|
||||
* @param Beginning address of the peripheral registers
|
||||
* @return i2c slave working status
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline i2c_slave_read_write_status_t i2c_ll_slave_get_read_write_status(i2c_dev_t *hw)
|
||||
{
|
||||
return (hw->status_reg.slave_rw == 0) ? I2C_SLAVE_WRITE_BY_MASTER : I2C_SLAVE_READ_BY_MASTER;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////Deprecated Functions//////////////////////////////////////////////////////////
|
||||
/////////////////////////////The following functions are only used by the legacy driver/////////////////////////////////
|
||||
/////////////////////////////They might be removed in the next major release (ESP-IDF 6.0)//////////////////////////////
|
||||
|
@@ -76,6 +76,8 @@ typedef enum {
|
||||
#define I2C_LL_RESET_SLV_SCL_PULSE_NUM_DEFAULT (9)
|
||||
#define I2C_LL_SCL_WAIT_US_VAL_DEFAULT (2000) // Approximate value for SCL timeout regs (in us).
|
||||
|
||||
#define I2C_LL_STRETCH_PROTECT_TIME (0x3ff)
|
||||
|
||||
/**
|
||||
* @brief Calculate I2C bus frequency
|
||||
* Note that the clock accuracy is affected by the external pull-up resistor,
|
||||
@@ -920,6 +922,16 @@ static inline void i2c_ll_slave_clear_stretch(i2c_dev_t *dev)
|
||||
dev->scl_stretch_conf.slave_scl_stretch_clr = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set I2C clock stretch protect num
|
||||
*
|
||||
* @param dev Beginning address of the peripheral registers
|
||||
*/
|
||||
static inline void i2c_ll_slave_set_stretch_protect_num(i2c_dev_t *dev, uint32_t protect_num)
|
||||
{
|
||||
dev->scl_stretch_conf.stretch_protect_num = protect_num;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if i2c command is done.
|
||||
*
|
||||
@@ -948,6 +960,18 @@ static inline uint32_t i2c_ll_calculate_timeout_us_to_reg_val(uint32_t src_clk_h
|
||||
return 32 - __builtin_clz(clk_cycle_num_per_us * timeout_us);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get status of i2c slave
|
||||
*
|
||||
* @param Beginning address of the peripheral registers
|
||||
* @return i2c slave working status
|
||||
*/
|
||||
__attribute__((always_inline))
|
||||
static inline i2c_slave_read_write_status_t i2c_ll_slave_get_read_write_status(i2c_dev_t *hw)
|
||||
{
|
||||
return (hw->sr.slave_rw == 0) ? I2C_SLAVE_WRITE_BY_MASTER : I2C_SLAVE_READ_BY_MASTER;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////Deprecated Functions//////////////////////////////////////////////////////////
|
||||
/////////////////////////////The following functions are only used by the legacy driver/////////////////////////////////
|
||||
/////////////////////////////They might be removed in the next major release (ESP-IDF 6.0)//////////////////////////////
|
||||
|
@@ -98,6 +98,11 @@ typedef enum {
|
||||
I2C_SLAVE_STRETCH_CAUSE_SENDING_ACK = 3, /*!< Stretching SCL low when slave sending ACK */
|
||||
} i2c_slave_stretch_cause_t;
|
||||
|
||||
typedef enum {
|
||||
I2C_SLAVE_WRITE_BY_MASTER = 0,
|
||||
I2C_SLAVE_READ_BY_MASTER = 1,
|
||||
} i2c_slave_read_write_status_t;
|
||||
|
||||
#if SOC_I2C_SUPPORTED
|
||||
/**
|
||||
* @brief I2C group clock source
|
||||
|
@@ -779,6 +779,10 @@ config SOC_I2C_SLAVE_SUPPORT_BROADCAST
|
||||
bool
|
||||
default y
|
||||
|
||||
config SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE
|
||||
bool
|
||||
default y
|
||||
|
||||
config SOC_I2C_SLAVE_SUPPORT_I2CRAM_ACCESS
|
||||
bool
|
||||
default y
|
||||
|
@@ -303,6 +303,7 @@
|
||||
#define SOC_I2C_SUPPORT_RTC (1)
|
||||
#define SOC_I2C_SUPPORT_10BIT_ADDR (1)
|
||||
#define SOC_I2C_SLAVE_SUPPORT_BROADCAST (1)
|
||||
#define SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE (1)
|
||||
#define SOC_I2C_SLAVE_SUPPORT_I2CRAM_ACCESS (1)
|
||||
#define SOC_I2C_SLAVE_SUPPORT_SLAVE_UNMATCH (1)
|
||||
|
||||
|
@@ -451,6 +451,10 @@ config SOC_I2C_SUPPORT_SLAVE
|
||||
bool
|
||||
default y
|
||||
|
||||
config SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE
|
||||
bool
|
||||
default y
|
||||
|
||||
config SOC_I2C_SUPPORT_HW_CLR_BUS
|
||||
bool
|
||||
default y
|
||||
|
@@ -200,6 +200,7 @@
|
||||
#define SOC_I2C_FIFO_LEN (32) /*!< I2C hardware FIFO depth */
|
||||
#define SOC_I2C_CMD_REG_NUM (16) /*!< Number of I2C command registers */
|
||||
#define SOC_I2C_SUPPORT_SLAVE (1)
|
||||
#define SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE (1)
|
||||
|
||||
// FSM_RST only resets the FSM, not using it. So SOC_I2C_SUPPORT_HW_FSM_RST not defined.
|
||||
//ESP32-S2 support hardware clear bus
|
||||
|
@@ -551,6 +551,10 @@ config SOC_I2C_SLAVE_SUPPORT_I2CRAM_ACCESS
|
||||
bool
|
||||
default y
|
||||
|
||||
config SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE
|
||||
bool
|
||||
default y
|
||||
|
||||
config SOC_I2S_NUM
|
||||
int
|
||||
default 2
|
||||
|
@@ -219,6 +219,7 @@
|
||||
#define SOC_I2C_SUPPORT_10BIT_ADDR (1)
|
||||
#define SOC_I2C_SLAVE_SUPPORT_BROADCAST (1)
|
||||
#define SOC_I2C_SLAVE_SUPPORT_I2CRAM_ACCESS (1)
|
||||
#define SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE (1)
|
||||
|
||||
/*-------------------------- I2S CAPS ----------------------------------------*/
|
||||
#define SOC_I2S_NUM (2U)
|
||||
|
@@ -36,6 +36,14 @@ Typically, an I2C slave device has a 7-bit address or 10-bit address. {IDF_TARGE
|
||||
|
||||
Keep in mind that the higher the frequency, the smaller the pull-up resistor should be (but not less than 1 kΩ). Indeed, large resistors will decline the current, which will increase the clock switching time and reduce the frequency. A range of 2 kΩ to 5 kΩ is recommended, but adjustments may also be necessary depending on their current draw requirements.
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
|
||||
i2c_slave_v1
|
||||
|
||||
.. note::
|
||||
|
||||
We realized that our first version of the I2C slave driver had some problems and was not easy to use, so we have prepared a second version of the I2C slave driver, which solves many of the problems with our current I2C slave and which will be the focus of our maintenance. We encourage and recommend that you use the second version of the I2C slave driver, which you can do by enabling :ref:`CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2`. This document focuses on the content of I2C slave v2.0. If you still want to read programming guide of I2C slave v1.0, please refer to :ref:`i2c-slave-v1`. The I2C slave v1.0 driver will be removed with the IDF v6.0 update.
|
||||
|
||||
I2C Clock Configuration
|
||||
-----------------------
|
||||
@@ -218,27 +226,26 @@ I2C slave requires the configuration specified by :cpp:type:`i2c_slave_config_t`
|
||||
- :cpp:member:`i2c_slave_config_t::sda_io_num` sets the GPIO number for serial data bus (SDA).
|
||||
- :cpp:member:`i2c_slave_config_t::scl_io_num` sets the GPIO number for serial clock bus (SCL).
|
||||
- :cpp:member:`i2c_slave_config_t::clk_source` selects the source clock for I2C bus. The available clocks are listed in :cpp:type:`i2c_clock_source_t`. For the effect on power consumption of different clock source, please refer to `Power Management <#power-management>`__ section.
|
||||
- :cpp:member:`i2c_slave_config_t::send_buf_depth` sets the sending buffer length.
|
||||
- :cpp:member:`i2c_slave_config_t::slave_addr` sets the slave address.
|
||||
- :cpp:member:`i2c_master_bus_config_t::intr_priority` sets the priority of the interrupt. If set to ``0`` , then the driver will use a interrupt with low or medium priority (priority level may be one of 1, 2 or 3), otherwise use the priority indicated by :cpp:member:`i2c_master_bus_config_t::intr_priority`. Please use the number form (1, 2, 3), instead of the bitmask form ((1<<1), (1<<2), (1<<3)). Please pay attention that once the interrupt priority is set, it cannot be changed until :cpp:func:`i2c_del_master_bus` is called.
|
||||
- :cpp:member:`i2c_slave_config_t::addr_bit_len`. Set this variable to ``I2C_ADDR_BIT_LEN_10`` if the slave should have a 10-bit address.
|
||||
:SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE: - :cpp:member:`i2c_slave_config_t::stretch_en`. Set this variable to true, then the slave controller stretch will work. Please refer to [`TRM <{IDF_TARGET_TRM_EN_URL}#i2c>`__] to learn how I2C stretch works.
|
||||
:SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE: - :cpp:member:`i2c_slave_config_t::broadcast_en`. Set this to true to enable the slave broadcast. When the slave receives the general call address 0x00 from the master and the R/W bit followed is 0, it responds to the master regardless of its own address.
|
||||
:SOC_I2C_SLAVE_SUPPORT_I2CRAM_ACCESS: - :cpp:member:`i2c_slave_config_t::access_ram_en`. Set this to true to enable the non-FIFO mode. Thus the I2C data FIFO can be used as RAM, and double addressing will be synchronised opened.
|
||||
:SOC_I2C_SLAVE_SUPPORT_SLAVE_UNMATCH: - :cpp:member:`i2c_slave_config_t::slave_unmatch_en`. Set this to true to enable the slave unmatch interrupt. If the command address sent by master can't match the slave address, then unmatch interrupt will be triggered.
|
||||
- :cpp:member:`i2c_slave_config_t::send_buf_depth` sets the sending software buffer length.
|
||||
- :cpp:member:`i2c_slave_config_t::receive_buf_depth` sets the receiving software buffer length.
|
||||
- :cpp:member:`i2c_slave_config_t::intr_priority` sets the priority of the interrupt. If set to ``0`` , then the driver will use a interrupt with low or medium priority (priority level may be one of 1, 2 or 3), otherwise use the priority indicated by :cpp:member:`i2c_slave_config_t::intr_priority`. Please use the number form (1, 2, 3), instead of the bitmask form ((1<<1), (1<<2), (1<<3)). Please pay attention that once the interrupt priority is set, it cannot be changed until :cpp:func:`i2c_del_slave_device` is called.
|
||||
- :cpp:member:`i2c_slave_config_t::addr_bit_len` Set this variable to ``I2C_ADDR_BIT_LEN_10`` if the slave should have a 10-bit address.
|
||||
- :cpp:member:`i2c_slave_config_t::allow_pd` If set, the driver will backup/restore the I2C registers before/after entering/exist sleep mode. By this approach, the system can power off I2C's power domain. This can save power, but at the expense of more RAM being consumed.
|
||||
:SOC_I2C_SLAVE_SUPPORT_BROADCAST: - :cpp:member:`i2c_slave_config_t::broadcast_en` Set this to true to enable the slave broadcast. When the slave receives the general call address 0x00 from the master and the R/W bit followed is 0, it responds to the master regardless of its own address.
|
||||
- :cpp:member:`i2c_slave_config_t::enable_internal_pullup` Set this to enable internal pull-up. Even though, an output pull-up resistance is strongly recommended.
|
||||
|
||||
Once the :cpp:type:`i2c_slave_config_t` structure is populated with mandatory parameters, :cpp:func:`i2c_new_slave_device` can be called to allocate and initialize an I2C master bus. This function will return an I2C bus handle if it runs correctly. Specifically, when there are no more I2C port available, this function will return :c:macro:`ESP_ERR_NOT_FOUND` error.
|
||||
|
||||
.. code:: c
|
||||
|
||||
i2c_slave_config_t i2c_slv_config = {
|
||||
.addr_bit_len = I2C_ADDR_BIT_LEN_7,
|
||||
.i2c_port = I2C_SLAVE_NUM,
|
||||
.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,
|
||||
.slave_addr = ESP_SLAVE_ADDR,
|
||||
.send_buf_depth = 100,
|
||||
.receive_buf_depth = 100,
|
||||
};
|
||||
|
||||
i2c_slave_dev_handle_t slave_handle;
|
||||
@@ -437,142 +444,72 @@ I2C Slave Controller
|
||||
|
||||
After installing the I2C slave driver by :cpp:func:`i2c_new_slave_device`, {IDF_TARGET_NAME} is ready to communicate with other I2C masters as a slave.
|
||||
|
||||
The I2C slave is not as subjective as the I2C master which knows when it should send data and when it should receive data. The I2C slave is very passive in most cases, that means the I2C slave's ability to send and receive data is largely dependent on the master's actions. Therefore, we throw two callback functions in the driver that represent read requests and write requests from the I2C master.
|
||||
|
||||
I2C Slave Write
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
The send buffer of the I2C slave is used as a FIFO to store the data to be sent. The data will queue up until the master requests them. You can call :cpp:func:`i2c_slave_transmit` to transfer data.
|
||||
You can get I2C slave write event be register :cpp:member:`i2c_slave_event_callbacks_t::on_request` callback, and in a task when get the request event, you can call `i2c_slave_write` to send data.
|
||||
|
||||
Simple example for writing data to FIFO:
|
||||
Simple example for transmitting data:
|
||||
|
||||
.. code:: c
|
||||
|
||||
uint8_t *data_wr = (uint8_t *) malloc(DATA_LENGTH);
|
||||
|
||||
i2c_slave_config_t i2c_slv_config = {
|
||||
.addr_bit_len = I2C_ADDR_BIT_LEN_7, // 7-bit address
|
||||
.clk_source = I2C_CLK_SRC_DEFAULT, // set the clock source
|
||||
.i2c_port = TEST_I2C_PORT, // set I2C port number
|
||||
.send_buf_depth = 256, // set TX buffer length
|
||||
.scl_io_num = I2C_SLAVE_SCL_IO, // SCL GPIO number
|
||||
.sda_io_num = I2C_SLAVE_SDA_IO, // SDA GPIO number
|
||||
.slave_addr = 0x58, // slave address
|
||||
};
|
||||
|
||||
i2c_slave_dev_handle_t slave_handle;
|
||||
ESP_ERROR_CHECK(i2c_new_slave_device(&i2c_slv_config, &slave_handle));
|
||||
for (int i = 0; i < DATA_LENGTH; i++) {
|
||||
data_wr[i] = i;
|
||||
// Prepare a callback function
|
||||
static bool i2c_slave_request_cb(i2c_slave_dev_handle_t i2c_slave, const i2c_slave_request_event_data_t *evt_data, void *arg)
|
||||
{
|
||||
i2c_slave_event_t evt = I2C_SLAVE_EVT_TX;
|
||||
BaseType_t xTaskWoken = 0;
|
||||
xQueueSendFromISR(context->event_queue, &evt, &xTaskWoken);
|
||||
return xTaskWoken;
|
||||
}
|
||||
|
||||
ESP_ERROR_CHECK(i2c_slave_transmit(slave_handle, data_wr, DATA_LENGTH, 10000));
|
||||
// Register callback in a task
|
||||
i2c_slave_event_callbacks_t cbs = {
|
||||
.on_request = i2c_slave_request_cb,
|
||||
};
|
||||
ESP_ERROR_CHECK(i2c_slave_register_event_callbacks(context.handle, &cbs, &context));
|
||||
|
||||
// Waiting for request event and send data in a task
|
||||
static void i2c_slave_task(void *arg)
|
||||
{
|
||||
uint8_t buffer_size = 64;
|
||||
uint32_t write_len;
|
||||
uint8_t *data_buffer;
|
||||
|
||||
while (true) {
|
||||
i2c_slave_event_t evt;
|
||||
if (xQueueReceive(context->event_queue, &evt, 10) == pdTRUE) {
|
||||
ESP_ERROR_CHECK(i2c_slave_write(handle, data_buffer, buffer_size, &write_len, 1000));
|
||||
}
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
I2C Slave Read
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Whenever the master writes data to the slave, the slave will automatically store data in the receive buffer. This allows the slave application to call the function :cpp:func:`i2c_slave_receive` as its own discretion. As :cpp:func:`i2c_slave_receive` is designed as a non-blocking interface, users need to register callback :cpp:func:`i2c_slave_register_event_callbacks` to know when the receive has finished.
|
||||
Same as write, you can get I2C slave read event be register :cpp:member:`i2c_slave_event_callbacks_t::on_receive` callback, and in a task when get the request event, you can save the data and do what you want.
|
||||
|
||||
Simple example for receiving data:
|
||||
|
||||
.. code:: c
|
||||
|
||||
static IRAM_ATTR bool i2c_slave_rx_done_callback(i2c_slave_dev_handle_t channel, const i2c_slave_rx_done_event_data_t *edata, void *user_data)
|
||||
// Prepare a callback function
|
||||
static bool i2c_slave_receive_cb(i2c_slave_dev_handle_t i2c_slave, const i2c_slave_rx_done_event_data_t *evt_data, void *arg)
|
||||
{
|
||||
BaseType_t high_task_wakeup = pdFALSE;
|
||||
QueueHandle_t receive_queue = (QueueHandle_t)user_data;
|
||||
xQueueSendFromISR(receive_queue, edata, &high_task_wakeup);
|
||||
return high_task_wakeup == pdTRUE;
|
||||
i2c_slave_event_t evt = I2C_SLAVE_EVT_RX;
|
||||
BaseType_t xTaskWoken = 0;
|
||||
// You can get data and length via i2c_slave_rx_done_event_data_t
|
||||
xQueueSendFromISR(context->event_queue, &evt, &xTaskWoken);
|
||||
return xTaskWoken;
|
||||
}
|
||||
|
||||
uint8_t *data_rd = (uint8_t *) malloc(DATA_LENGTH);
|
||||
uint32_t size_rd = 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;
|
||||
ESP_ERROR_CHECK(i2c_new_slave_device(&i2c_slv_config, &slave_handle));
|
||||
|
||||
s_receive_queue = xQueueCreate(1, sizeof(i2c_slave_rx_done_event_data_t));
|
||||
// Register callback in a task
|
||||
i2c_slave_event_callbacks_t cbs = {
|
||||
.on_recv_done = i2c_slave_rx_done_callback,
|
||||
.on_receive = i2c_slave_receive_cb,
|
||||
};
|
||||
ESP_ERROR_CHECK(i2c_slave_register_event_callbacks(slave_handle, &cbs, s_receive_queue));
|
||||
|
||||
i2c_slave_rx_done_event_data_t rx_data;
|
||||
ESP_ERROR_CHECK(i2c_slave_receive(slave_handle, data_rd, DATA_LENGTH));
|
||||
xQueueReceive(s_receive_queue, &rx_data, pdMS_TO_TICKS(10000));
|
||||
// Receive done.
|
||||
|
||||
.. only:: SOC_I2C_SLAVE_SUPPORT_I2CRAM_ACCESS
|
||||
|
||||
Put Data In I2C Slave RAM
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
I2C slave FIFO mentioned above can be used as RAM, which means user can access the RAM directly via address fields. For example, write data to the third RAM block with following graph. Before using this, please note that :cpp:member:`i2c_slave_config_t::access_ram_en` needs to be set to true.
|
||||
|
||||
.. figure:: ../../../_static/diagrams/i2c/i2c_slave_write_slave_ram.png
|
||||
:align: center
|
||||
:alt: Put data in I2C slave RAM
|
||||
|
||||
Put data in I2C slave RAM
|
||||
|
||||
.. code:: c
|
||||
|
||||
uint8_t data_rd[DATA_LENGTH_RAM] = {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,
|
||||
.flags.access_ram_en = true,
|
||||
};
|
||||
|
||||
// Master writes to slave.
|
||||
|
||||
i2c_slave_dev_handle_t slave_handle;
|
||||
ESP_ERROR_CHECK(i2c_new_slave_device(&i2c_slv_config, &slave_handle));
|
||||
ESP_ERROR_CHECK(i2c_slave_read_ram(slave_handle, 0x5, data_rd, DATA_LENGTH_RAM));
|
||||
ESP_ERROR_CHECK(i2c_del_slave_device(slave_handle));
|
||||
|
||||
Get Data From I2C Slave RAM
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Data can be stored in the RAM with a specific offset by the slave controller, and the master can read this data directly via the RAM address. For example, if the data is stored in the third RAM block, master can read this data by the following graph. Before using this, please note that :cpp:member:`i2c_slave_config_t::access_ram_en` needs to be set to true.
|
||||
|
||||
.. figure:: ../../../_static/diagrams/i2c/i2c_slave_read_slave_ram.png
|
||||
:align: center
|
||||
:alt: Get data from I2C slave RAM
|
||||
|
||||
Get data from I2C slave RAM
|
||||
|
||||
.. code:: c
|
||||
|
||||
uint8_t data_wr[DATA_LENGTH_RAM] = {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,
|
||||
.flags.access_ram_en = true,
|
||||
};
|
||||
|
||||
i2c_slave_dev_handle_t slave_handle;
|
||||
ESP_ERROR_CHECK(i2c_new_slave_device(&i2c_slv_config, &slave_handle));
|
||||
ESP_ERROR_CHECK(i2c_slave_write_ram(slave_handle, 0x2, data_wr, DATA_LENGTH_RAM));
|
||||
ESP_ERROR_CHECK(i2c_del_slave_device(slave_handle));
|
||||
ESP_ERROR_CHECK(i2c_slave_register_event_callbacks(context.handle, &cbs, &context));
|
||||
|
||||
Register Event Callbacks
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
@@ -601,8 +538,8 @@ I2C slave event callbacks are listed in the :cpp:type:`i2c_slave_event_callbacks
|
||||
|
||||
.. list::
|
||||
|
||||
- :cpp:member:`i2c_slave_event_callbacks_t::on_recv_done` sets a callback function for "receive-done" event. The function prototype is declared in :cpp:type:`i2c_slave_received_callback_t`.
|
||||
:SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE: - :cpp:member:`i2c_slave_event_callbacks_t::on_stretch_occur` sets a callback function for "stretch" cause. The function prototype is declared in :cpp:type:`i2c_slave_stretch_callback_t`.
|
||||
- :cpp:member:`i2c_slave_event_callbacks_t::on_request` sets a callback function for request event.
|
||||
- :cpp:member:`i2c_slave_event_callbacks_t::on_receive` sets a callback function for receive event. The function prototype is declared in :cpp:type:`i2c_slave_received_callback_t`.
|
||||
|
||||
Power Management
|
||||
^^^^^^^^^^^^^^^^
|
||||
@@ -637,13 +574,28 @@ This will allow the interrupt to run while the cache is disabled but will come a
|
||||
Thread Safety
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
The factory function :cpp:func:`i2c_new_master_bus` and :cpp:func:`i2c_new_slave_device` are guaranteed to be thread safe by the driver, which means that the functions can be called from different RTOS tasks without protection by extra locks. Other public I2C APIs are not thread safe, which means the user should avoid calling them from multiple tasks, if it is necessary to call them in multiple tasks, please add extra locks.
|
||||
The factory function :cpp:func:`i2c_new_master_bus` and :cpp:func:`i2c_new_slave_device` are guaranteed to be thread safe by the driver, which means that the functions can be called from different RTOS tasks without protection by extra locks.
|
||||
|
||||
I2C master operation functions are also guaranteed to be thread safe by bus operation semaphore.
|
||||
|
||||
- :cpp:func:`i2c_master_transmit`
|
||||
- :cpp:func:`i2c_master_multi_buffer_transmit`
|
||||
- :cpp:func:`i2c_master_transmit_receive`
|
||||
- :cpp:func:`i2c_master_receive`
|
||||
- :cpp:func:`i2c_master_probe`
|
||||
|
||||
I2C slave operation functions are also guaranteed to be thread safe by bus operation semaphore.
|
||||
|
||||
- :cpp:func:`i2c_slave_write`
|
||||
|
||||
Other functions are not guaranteed to be thread-safe. Thus, you should avoid calling them in different tasks without mutex protection.
|
||||
|
||||
Kconfig Options
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
- :ref:`CONFIG_I2C_ISR_IRAM_SAFE` controls whether the default ISR handler can work when cache is disabled, see also `IRAM Safe <#iram-safe>`__ for more information.
|
||||
- :ref:`CONFIG_I2C_ENABLE_DEBUG_LOG` is used to enable the debug log at the cost of increased firmware binary size.
|
||||
- :ref:`CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2` is used to enable the I2C slave driver v2.0.
|
||||
|
||||
Application Examples
|
||||
--------------------
|
||||
@@ -652,6 +604,7 @@ Application Examples
|
||||
|
||||
- :example:`peripherals/i2c/i2c_tools` demonstrates how to use the I2C Tools for developing I2C related applications, providing command-line tools for configuring the I2C bus, scanning for devices, reading and setting registers, and examining registers.
|
||||
|
||||
- :example:`peripherals/i2c/i2c_slave_network_sensor` demonstrates how to use the I2C slave for developing I2C related applications, providing how I2C slave can behave as a network sensor, and use event callbacks to receive and send data.
|
||||
|
||||
API Reference
|
||||
-------------
|
||||
|
202
docs/en/api-reference/peripherals/i2c_slave_v1.rst
Normal file
202
docs/en/api-reference/peripherals/i2c_slave_v1.rst
Normal file
@@ -0,0 +1,202 @@
|
||||
.. _i2c-slave-v1:
|
||||
|
||||
I2C Slave v1.0
|
||||
==============
|
||||
|
||||
.. warning::
|
||||
|
||||
This I2C slave driver version 1 will be removed when idf v6.0 update. We suggest you use I2C slave version 2 via :ref:`CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2`.
|
||||
|
||||
After installing the I2C slave driver by :cpp:func:`i2c_new_slave_device`, {IDF_TARGET_NAME} is ready to communicate with other I2C masters as a slave.
|
||||
|
||||
Install I2C slave device
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
I2C slave requires the configuration specified by :cpp:type:`i2c_slave_config_t`:
|
||||
|
||||
.. list::
|
||||
|
||||
- :cpp:member:`i2c_slave_config_t::i2c_port` sets the I2C port used by the controller.
|
||||
- :cpp:member:`i2c_slave_config_t::sda_io_num` sets the GPIO number for serial data bus (SDA).
|
||||
- :cpp:member:`i2c_slave_config_t::scl_io_num` sets the GPIO number for serial clock bus (SCL).
|
||||
- :cpp:member:`i2c_slave_config_t::clk_source` selects the source clock for I2C bus. The available clocks are listed in :cpp:type:`i2c_clock_source_t`. For the effect on power consumption of different clock source, please refer to `Power Management <#power-management>`__ section.
|
||||
- :cpp:member:`i2c_slave_config_t::send_buf_depth` sets the sending buffer length.
|
||||
- :cpp:member:`i2c_slave_config_t::slave_addr` sets the slave address.
|
||||
- :cpp:member:`i2c_slave_config_t::intr_priority` sets the priority of the interrupt. If set to ``0`` , then the driver will use a interrupt with low or medium priority (priority level may be one of 1, 2 or 3), otherwise use the priority indicated by :cpp:member:`i2c_slave_config_t::intr_priority`. Please use the number form (1, 2, 3), instead of the bitmask form ((1<<1), (1<<2), (1<<3)). Please pay attention that once the interrupt priority is set, it cannot be changed until :cpp:func:`i2c_del_slave_bus` is called.
|
||||
- :cpp:member:`i2c_slave_config_t::addr_bit_len`. Set this variable to ``I2C_ADDR_BIT_LEN_10`` if the slave should have a 10-bit address.
|
||||
:SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE: - :cpp:member:`i2c_slave_config_t::stretch_en`. Set this variable to true, then the slave controller stretch will work. Please refer to [`TRM <{IDF_TARGET_TRM_EN_URL}#i2c>`__] to learn how I2C stretch works.
|
||||
:SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE: - :cpp:member:`i2c_slave_config_t::broadcast_en`. Set this to true to enable the slave broadcast. When the slave receives the general call address 0x00 from the master and the R/W bit followed is 0, it responds to the master regardless of its own address.
|
||||
:SOC_I2C_SLAVE_SUPPORT_I2CRAM_ACCESS: - :cpp:member:`i2c_slave_config_t::access_ram_en`. Set this to true to enable the non-FIFO mode. Thus the I2C data FIFO can be used as RAM, and double addressing will be synchronised opened.
|
||||
:SOC_I2C_SLAVE_SUPPORT_SLAVE_UNMATCH: - :cpp:member:`i2c_slave_config_t::slave_unmatch_en`. Set this to true to enable the slave unmatch interrupt. If the command address sent by master can't match the slave address, then unmatch interrupt will be triggered.
|
||||
|
||||
Once the :cpp:type:`i2c_slave_config_t` structure is populated with mandatory parameters, :cpp:func:`i2c_new_slave_device` can be called to allocate and initialize an I2C master bus. This function will return an I2C bus handle if it runs correctly. Specifically, when there are no more I2C port available, this function will return :c:macro:`ESP_ERR_NOT_FOUND` error.
|
||||
|
||||
.. code:: c
|
||||
|
||||
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;
|
||||
ESP_ERROR_CHECK(i2c_new_slave_device(&i2c_slv_config, &slave_handle));
|
||||
|
||||
Uninstall I2C slave device
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If a previously installed I2C bus is no longer needed, it's recommended to recycle the resource by calling :cpp:func:`i2c_del_slave_device`, so that to release the underlying hardware.
|
||||
|
||||
|
||||
I2C Slave Write
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
The send buffer of the I2C slave is used as a FIFO to store the data to be sent. The data will queue up until the master requests them. You can call :cpp:func:`i2c_slave_transmit` to transfer data.
|
||||
|
||||
Simple example for writing data to FIFO:
|
||||
|
||||
.. code:: c
|
||||
|
||||
uint8_t *data_wr = (uint8_t *) malloc(DATA_LENGTH);
|
||||
|
||||
i2c_slave_config_t i2c_slv_config = {
|
||||
.addr_bit_len = I2C_ADDR_BIT_LEN_7, // 7-bit address
|
||||
.clk_source = I2C_CLK_SRC_DEFAULT, // set the clock source
|
||||
.i2c_port = TEST_I2C_PORT, // set I2C port number
|
||||
.send_buf_depth = 256, // set TX buffer length
|
||||
.scl_io_num = I2C_SLAVE_SCL_IO, // SCL GPIO number
|
||||
.sda_io_num = I2C_SLAVE_SDA_IO, // SDA GPIO number
|
||||
.slave_addr = 0x58, // slave address
|
||||
};
|
||||
|
||||
i2c_slave_dev_handle_t slave_handle;
|
||||
ESP_ERROR_CHECK(i2c_new_slave_device(&i2c_slv_config, &slave_handle));
|
||||
for (int i = 0; i < DATA_LENGTH; i++) {
|
||||
data_wr[i] = i;
|
||||
}
|
||||
|
||||
ESP_ERROR_CHECK(i2c_slave_transmit(slave_handle, data_wr, DATA_LENGTH, 10000));
|
||||
|
||||
I2C Slave Read
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Whenever the master writes data to the slave, the slave will automatically store data in the receive buffer. This allows the slave application to call the function :cpp:func:`i2c_slave_receive` as its own discretion. As :cpp:func:`i2c_slave_receive` is designed as a non-blocking interface, users need to register callback :cpp:func:`i2c_slave_register_event_callbacks` to know when the receive has finished.
|
||||
|
||||
.. code:: c
|
||||
|
||||
static IRAM_ATTR bool i2c_slave_rx_done_callback(i2c_slave_dev_handle_t channel, const i2c_slave_rx_done_event_data_t *edata, void *user_data)
|
||||
{
|
||||
BaseType_t high_task_wakeup = pdFALSE;
|
||||
QueueHandle_t receive_queue = (QueueHandle_t)user_data;
|
||||
xQueueSendFromISR(receive_queue, edata, &high_task_wakeup);
|
||||
return high_task_wakeup == pdTRUE;
|
||||
}
|
||||
|
||||
uint8_t *data_rd = (uint8_t *) malloc(DATA_LENGTH);
|
||||
uint32_t size_rd = 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;
|
||||
ESP_ERROR_CHECK(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 = i2c_slave_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;
|
||||
ESP_ERROR_CHECK(i2c_slave_receive(slave_handle, data_rd, DATA_LENGTH));
|
||||
xQueueReceive(s_receive_queue, &rx_data, pdMS_TO_TICKS(10000));
|
||||
// Receive done.
|
||||
|
||||
.. only:: SOC_I2C_SLAVE_SUPPORT_I2CRAM_ACCESS
|
||||
|
||||
Put Data In I2C Slave RAM
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
I2C slave FIFO mentioned above can be used as RAM, which means user can access the RAM directly via address fields. For example, write data to the third RAM block with following graph. Before using this, please note that :cpp:member:`i2c_slave_config_t::access_ram_en` needs to be set to true.
|
||||
|
||||
.. figure:: ../../../_static/diagrams/i2c/i2c_slave_write_slave_ram.png
|
||||
:align: center
|
||||
:alt: Put data in I2C slave RAM
|
||||
|
||||
Put data in I2C slave RAM
|
||||
|
||||
.. code:: c
|
||||
|
||||
uint8_t data_rd[DATA_LENGTH_RAM] = {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,
|
||||
.flags.access_ram_en = true,
|
||||
};
|
||||
|
||||
// Master writes to slave.
|
||||
|
||||
i2c_slave_dev_handle_t slave_handle;
|
||||
ESP_ERROR_CHECK(i2c_new_slave_device(&i2c_slv_config, &slave_handle));
|
||||
ESP_ERROR_CHECK(i2c_slave_read_ram(slave_handle, 0x5, data_rd, DATA_LENGTH_RAM));
|
||||
ESP_ERROR_CHECK(i2c_del_slave_device(slave_handle));
|
||||
|
||||
Get Data From I2C Slave RAM
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Data can be stored in the RAM with a specific offset by the slave controller, and the master can read this data directly via the RAM address. For example, if the data is stored in the third RAM block, master can read this data by the following graph. Before using this, please note that :cpp:member:`i2c_slave_config_t::access_ram_en` needs to be set to true.
|
||||
|
||||
.. figure:: ../../../_static/diagrams/i2c/i2c_slave_read_slave_ram.png
|
||||
:align: center
|
||||
:alt: Get data from I2C slave RAM
|
||||
|
||||
Get data from I2C slave RAM
|
||||
|
||||
.. code:: c
|
||||
|
||||
uint8_t data_wr[DATA_LENGTH_RAM] = {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,
|
||||
.flags.access_ram_en = true,
|
||||
};
|
||||
|
||||
i2c_slave_dev_handle_t slave_handle;
|
||||
ESP_ERROR_CHECK(i2c_new_slave_device(&i2c_slv_config, &slave_handle));
|
||||
ESP_ERROR_CHECK(i2c_slave_write_ram(slave_handle, 0x2, data_wr, DATA_LENGTH_RAM));
|
||||
ESP_ERROR_CHECK(i2c_del_slave_device(slave_handle));
|
||||
|
||||
I2C slave callbacks
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When an I2C slave bus triggers an interrupt, a specific event will be generated and notify the CPU. If you have some function that needs to be called when those events occurred, you can hook your function to the ISR (Interrupt Service Routine) by calling :cpp:func:`i2c_slave_register_event_callbacks`. Since the registered callback functions are called in the interrupt context, users should ensure the callback function doesn't attempt to block (e.g. by making sure that only FreeRTOS APIs with ``ISR`` suffix are called from the function). The callback function has a boolean return value, to tell the caller whether a high priority task is woken up by it.
|
||||
|
||||
I2C slave event callbacks are listed in the :cpp:type:`i2c_slave_event_callbacks_t`.
|
||||
|
||||
.. list::
|
||||
|
||||
- :cpp:member:`i2c_slave_event_callbacks_t::on_recv_done` sets a callback function for "receive-done" event. The function prototype is declared in :cpp:type:`i2c_slave_received_callback_t`.
|
||||
:SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE: - :cpp:member:`i2c_slave_event_callbacks_t::on_stretch_occur` sets a callback function for "stretch" cause. The function prototype is declared in :cpp:type:`i2c_slave_stretch_callback_t`.
|
@@ -36,6 +36,15 @@ I2C 是一种串行同步半双工通信协议,总线上可以同时挂载多
|
||||
|
||||
请注意,SCL 的频率越高,上拉电阻应该越小(但不能小于 1 kΩ)。较大的电阻会降低电流,增加时钟切换时间并降低频率。通常推荐 2 kΩ 到 5 kΩ 左右的电阻,也可根据电流需求进行一定调整。
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
|
||||
i2c_slave_v1
|
||||
|
||||
.. note::
|
||||
|
||||
我们发现 :ref:`i2c-slave-v1` 存在一些问题,且使用体验不够友好。为此,我们推出了 I2C 从机驱动 v2.0,此版本不仅解决了现有问题,还将成为我们未来的主要维护版本。我们建议并鼓励你使用 I2C 从机驱动 v2.0,你可以通过配置选项 :ref:`CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2` 启用该功能。本文档主要介绍 I2C 从机驱动 v2.0 的功能。如果你想使用 I2C 从机驱动 v1.0,请参考 :ref:`i2c-slave-v1`。I2C 从机驱动 v1.0 将在 ESP-IDF v6.0 中移除。
|
||||
|
||||
|
||||
I2C 时钟配置
|
||||
------------
|
||||
@@ -218,27 +227,26 @@ I2C 从机设备需要 :cpp:type:`i2c_slave_config_t` 指定的配置:
|
||||
- :cpp:member:`i2c_slave_config_t::sda_io_num` 设置串行数据总线 (SDA) 的 GPIO 编号。
|
||||
- :cpp:member:`i2c_slave_config_t::scl_io_num` 设置串行时钟总线 (SCL) 的 GPIO 编号。
|
||||
- :cpp:member:`i2c_slave_config_t::clk_source` 选择 I2C 总线的时钟源。可用时钟列表见 :cpp:type:`i2c_clock_source_t`。有关不同时钟源对功耗的影响,请参阅 `电源管理 <#power-management>`__。
|
||||
- :cpp:member:`i2c_slave_config_t::send_buf_depth` 设置发送 buffer 的长度。
|
||||
- :cpp:member:`i2c_slave_config_t::send_buf_depth` 设置发送软件 buffer 的长度。
|
||||
- :cpp:member:`i2c_slave_config_t::slave_addr` 设置从机地址。
|
||||
- :cpp:member:`i2c_master_bus_config_t::intr_priority` 设置中断的优先级。如果设置为 ``0`` ,则驱动程序将使用低或中优先级的中断(优先级可设为 1、2 或 3 中的一个),若未设置,则将使用 :cpp:member:`i2c_master_bus_config_t::intr_priority` 指示的优先级。请使用数字形式(1、2、3),不要用位掩码形式((1<<1)、(1<<2)、(1<<3))。请注意,中断优先级一旦设置完成,在调用 :cpp:func:`i2c_del_master_bus` 之前都无法更改。
|
||||
- :cpp:member:`i2c_slave_config_t::addr_bit_len`。如果需要从机设备具有 10 位地址,则将该成员变量设为 ``I2C_ADDR_BIT_LEN_10``。
|
||||
:SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE: - :cpp:member:`i2c_slave_config_t::stretch_en`。如果要启用从机控制器拉伸功能,请将该成员变量设为 true。有关 I2C 拉伸的工作原理,请参阅 [`TRM <{IDF_TARGET_TRM_EN_URL}#i2c>`__]。
|
||||
:SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE: - :cpp:member:`i2c_slave_config_t::broadcast_en`。如果要启用从机广播,请将该成员变量设为 true。当从机设备接收到来自主机设备的通用调用地址 0x00,且后面的读写位为 0 时,无论从机设备自身地址如何,都会响应主机设备。
|
||||
:SOC_I2C_SLAVE_SUPPORT_I2CRAM_ACCESS: - :cpp:member:`i2c_slave_config_t::access_ram_en`。如果要启用 non-FIFO 模式,请将该成员变量设为 true,则 I2C 数据 FIFO 可用作 RAM,并将同步打开双地址。
|
||||
:SOC_I2C_SLAVE_SUPPORT_SLAVE_UNMATCH: - :cpp:member:`i2c_slave_config_t::slave_unmatch_en`。将该成员变量设为 true,将启用从机设备不匹配中断。如果主机设备发送的命令地址与从机设备地址不匹配,则会触发不匹配中断。
|
||||
- :cpp:member:`i2c_slave_config_t::intr_priority` 设置中断的优先级。如果设置为 ``0`` ,则驱动程序将使用低或中优先级的中断(优先级可设为 1、2 或 3 中的一个),若未设置,则将使用 :cpp:member:`i2c_slave_config_t::intr_priority` 指示的优先级。请使用数字形式(1、2、3),不要用位掩码形式((1<<1)、(1<<2)、(1<<3))。请注意,中断优先级一旦设置完成,在调用 :cpp:func:`i2c_del_slave_device` 之前都无法更改。
|
||||
- :cpp:member:`i2c_slave_config_t::addr_bit_len` 如果需要从机设备具有 10 位地址,则将该成员变量设为 ``I2C_ADDR_BIT_LEN_10``。
|
||||
- :cpp:member:`i2c_slave_config_t::allow_pd` 配置驱动程序是否允许系统在睡眠模式下关闭外设电源。在进入睡眠之前,系统将备份 I2C 寄存器上下文,当系统退出睡眠模式时,这些上下文将被恢复。关闭外设可以节省更多功耗,但代价是消耗更多内存来保存寄存器上下文。你需要在功耗和内存消耗之间做权衡。此配置选项依赖于特定的硬件功能,如果在不支持的芯片上启用它,你将看到类似 ``not able to power down in light sleep`` 的错误消息。
|
||||
:SOC_I2C_SLAVE_SUPPORT_BROADCAST: - :cpp:member:`i2c_slave_config_t::broadcast_en` 如果要启用从机广播,请将该成员变量设为 true。当从机设备接收到来自主机设备的通用调用地址 0x00,且后面的读写位为 0 时,无论从机设备自身地址如何,都会响应主机设备。
|
||||
- :cpp:member:`i2c_slave_config_t::enable_internal_pullup` 置 true 使能内部上拉。尽管如此,我们强烈建议您使用外部上拉电阻。
|
||||
|
||||
一旦填充好 :cpp:type:`i2c_slave_config_t` 结构体的必要参数,就可调用 :cpp:func:`i2c_new_slave_device` 来分配和初始化 I2C 主机总线。如果函数运行正确,则将返回一个 I2C 总线句柄。若没有可用的 I2C 端口,此函数将返回 :c:macro:`ESP_ERR_NOT_FOUND` 错误。
|
||||
|
||||
.. code:: c
|
||||
|
||||
i2c_slave_config_t i2c_slv_config = {
|
||||
.addr_bit_len = I2C_ADDR_BIT_LEN_7,
|
||||
.i2c_port = I2C_SLAVE_NUM,
|
||||
.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,
|
||||
.slave_addr = ESP_SLAVE_ADDR,
|
||||
.send_buf_depth = 100,
|
||||
.receive_buf_depth = 100,
|
||||
};
|
||||
|
||||
i2c_slave_dev_handle_t slave_handle;
|
||||
@@ -435,144 +443,73 @@ I2C 驱动程序可以使用 :cpp:func:`i2c_master_probe` 来检测设备是否
|
||||
I2C 从机控制器
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
通过调用 :cpp:func:`i2c_new_slave_device` 安装好 I2C 从机驱动程序后,{IDF_TARGET_NAME} 就可以作为从机与其他 I2C 主机进行通信了。
|
||||
I2C 从机不像 I2C 主机那样主观,主机知道自己何时应该发送数据,何时应该接收数据。在绝大多数情况下,I2C 从机是非常被动的,这意味着 I2C 从机发送和接收数据的能力在很大程度上取决于主机的操作。因此,我们在驱动程序中抛出了两个回调函数,分别代表 I2C 主机的读取请求和写入请求。
|
||||
|
||||
I2C 从机写入
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
I2C 从机的发送 buffer 可作为 FIFO 来存储要发送的数据。在主机请求这些数据前,它们会一直排队。可通过调用 :cpp:func:`i2c_slave_transmit` 来传输数据。
|
||||
你可以通过注册 :cpp:member:`i2c_slave_event_callbacks_t::on_request` 回调来获取 I2C 从机写事件,并在获取请求事件的任务中调用 `i2c_slave_write` 来发送数据。
|
||||
|
||||
将数据写入 FIFO 的简单示例:
|
||||
传输数据的简单示例:
|
||||
|
||||
.. code:: c
|
||||
|
||||
uint8_t *data_wr = (uint8_t *) malloc(DATA_LENGTH);
|
||||
|
||||
i2c_slave_config_t i2c_slv_config = {
|
||||
.addr_bit_len = I2C_ADDR_BIT_LEN_7, // 7 位地址
|
||||
.clk_source = I2C_CLK_SRC_DEFAULT, // 设置时钟源
|
||||
.i2c_port = TEST_I2C_PORT, // 设置 I2C 端口编号
|
||||
.send_buf_depth = 256, // 设置 TX buffer 长度
|
||||
.scl_io_num = I2C_SLAVE_SCL_IO, // SCL 管脚编号
|
||||
.sda_io_num = I2C_SLAVE_SDA_IO, // SDA 管脚编号
|
||||
.slave_addr = 0x58, // 从机地址
|
||||
};
|
||||
|
||||
i2c_slave_dev_handle_t slave_handle;
|
||||
ESP_ERROR_CHECK(i2c_new_slave_device(&i2c_slv_config, &slave_handle));
|
||||
for (int i = 0; i < DATA_LENGTH; i++) {
|
||||
data_wr[i] = i;
|
||||
// Prepare a callback function
|
||||
static bool i2c_slave_request_cb(i2c_slave_dev_handle_t i2c_slave, const i2c_slave_request_event_data_t *evt_data, void *arg)
|
||||
{
|
||||
i2c_slave_event_t evt = I2C_SLAVE_EVT_TX;
|
||||
BaseType_t xTaskWoken = 0;
|
||||
xQueueSendFromISR(context->event_queue, &evt, &xTaskWoken);
|
||||
return xTaskWoken;
|
||||
}
|
||||
|
||||
ESP_ERROR_CHECK(i2c_slave_transmit(slave_handle, data_wr, DATA_LENGTH, 10000));
|
||||
// Register callback in a task
|
||||
i2c_slave_event_callbacks_t cbs = {
|
||||
.on_request = i2c_slave_request_cb,
|
||||
};
|
||||
ESP_ERROR_CHECK(i2c_slave_register_event_callbacks(context.handle, &cbs, &context));
|
||||
|
||||
// Waiting for request event and send data in a task
|
||||
static void i2c_slave_task(void *arg)
|
||||
{
|
||||
uint8_t buffer_size = 64;
|
||||
uint32_t write_len;
|
||||
uint8_t *data_buffer;
|
||||
|
||||
while (true) {
|
||||
i2c_slave_event_t evt;
|
||||
if (xQueueReceive(context->event_queue, &evt, 10) == pdTRUE) {
|
||||
ESP_ERROR_CHECK(i2c_slave_write(handle, data_buffer, buffer_size, &write_len, 1000));
|
||||
}
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
I2C 从机读取
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
每当主机将数据写入从机,从机都会自动将数据存储在接收 buffer 中,从而使从机应用程序能自由调用 :cpp:func:`i2c_slave_receive`。:cpp:func:`i2c_slave_receive` 为非阻塞接口,因此要想知道接收是否完成,需注册回调函数 :cpp:func:`i2c_slave_register_event_callbacks`。
|
||||
与写入一样,您可以通过注册 :cpp:member:`i2c_slave_event_callbacks_t::on_receive` 回调来获取 I2C 从机读取事件,在任务中获取请求事件时,您可以保存数据并做您想做的事情。
|
||||
|
||||
接收数据的简单示例
|
||||
|
||||
.. code:: c
|
||||
|
||||
static IRAM_ATTR bool i2c_slave_rx_done_callback(i2c_slave_dev_handle_t channel, const i2c_slave_rx_done_event_data_t *edata, void *user_data)
|
||||
// Prepare a callback function
|
||||
static bool i2c_slave_receive_cb(i2c_slave_dev_handle_t i2c_slave, const i2c_slave_rx_done_event_data_t *evt_data, void *arg)
|
||||
{
|
||||
BaseType_t high_task_wakeup = pdFALSE;
|
||||
QueueHandle_t receive_queue = (QueueHandle_t)user_data;
|
||||
xQueueSendFromISR(receive_queue, edata, &high_task_wakeup);
|
||||
return high_task_wakeup == pdTRUE;
|
||||
i2c_slave_event_t evt = I2C_SLAVE_EVT_RX;
|
||||
BaseType_t xTaskWoken = 0;
|
||||
// You can get data and length via i2c_slave_rx_done_event_data_t
|
||||
xQueueSendFromISR(context->event_queue, &evt, &xTaskWoken);
|
||||
return xTaskWoken;
|
||||
}
|
||||
|
||||
uint8_t *data_rd = (uint8_t *) malloc(DATA_LENGTH);
|
||||
uint32_t size_rd = 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;
|
||||
ESP_ERROR_CHECK(i2c_new_slave_device(&i2c_slv_config, &slave_handle));
|
||||
|
||||
s_receive_queue = xQueueCreate(1, sizeof(i2c_slave_rx_done_event_data_t));
|
||||
// Register callback in a task
|
||||
i2c_slave_event_callbacks_t cbs = {
|
||||
.on_recv_done = i2c_slave_rx_done_callback,
|
||||
.on_receive = i2c_slave_receive_cb,
|
||||
};
|
||||
ESP_ERROR_CHECK(i2c_slave_register_event_callbacks(slave_handle, &cbs, s_receive_queue));
|
||||
ESP_ERROR_CHECK(i2c_slave_register_event_callbacks(context.handle, &cbs, &context));
|
||||
|
||||
i2c_slave_rx_done_event_data_t rx_data;
|
||||
ESP_ERROR_CHECK(i2c_slave_receive(slave_handle, data_rd, DATA_LENGTH));
|
||||
xQueueReceive(s_receive_queue, &rx_data, pdMS_TO_TICKS(10000));
|
||||
// 接收完成。
|
||||
|
||||
.. only:: SOC_I2C_SLAVE_SUPPORT_I2CRAM_ACCESS
|
||||
|
||||
将数据放入 I2C 从机 RAM 中
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
如上所述,I2C 从机 FIFO 可被用作 RAM,即可以通过地址字段直接访问 RAM。例如,可参照下图将数据写入第三个 RAM 块。请注意,在进行操作前需要先将 :cpp:member:`i2c_slave_config_t::access_ram_en` 设为 true。
|
||||
|
||||
.. figure:: ../../../_static/diagrams/i2c/i2c_slave_write_slave_ram.png
|
||||
:align: center
|
||||
:alt: 将数据放入 I2C 从机 RAM 中
|
||||
|
||||
将数据放入 I2C 从机 RAM 中
|
||||
|
||||
.. code:: c
|
||||
|
||||
uint8_t data_rd[DATA_LENGTH_RAM] = {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,
|
||||
.flags.access_ram_en = true,
|
||||
};
|
||||
|
||||
// 主机将数据写入从机。
|
||||
|
||||
i2c_slave_dev_handle_t slave_handle;
|
||||
ESP_ERROR_CHECK(i2c_new_slave_device(&i2c_slv_config, &slave_handle));
|
||||
ESP_ERROR_CHECK(i2c_slave_read_ram(slave_handle, 0x5, data_rd, DATA_LENGTH_RAM));
|
||||
ESP_ERROR_CHECK(i2c_del_slave_device(slave_handle));
|
||||
|
||||
从 I2C 从机 RAM 中获取数据
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
数据可被存储在相对从机一定偏移量的 RAM 中,且主机可直接通过 RAM 地址读取这些数据。例如,如果数据被存储在第三个 RAM 块中,则主机可参照下图读取这些数据。请注意,在操作前需要先将 :cpp:member:`i2c_slave_config_t::access_ram_en` 设为 true。
|
||||
|
||||
.. figure:: ../../../_static/diagrams/i2c/i2c_slave_read_slave_ram.png
|
||||
:align: center
|
||||
:alt: 从 I2C 从机 RAM 中获取数据
|
||||
|
||||
从 I2C 从机 RAM 中获取数据
|
||||
|
||||
.. code:: c
|
||||
|
||||
uint8_t data_wr[DATA_LENGTH_RAM] = {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,
|
||||
.flags.access_ram_en = true,
|
||||
};
|
||||
|
||||
i2c_slave_dev_handle_t slave_handle;
|
||||
ESP_ERROR_CHECK(i2c_new_slave_device(&i2c_slv_config, &slave_handle));
|
||||
ESP_ERROR_CHECK(i2c_slave_write_ram(slave_handle, 0x2, data_wr, DATA_LENGTH_RAM));
|
||||
ESP_ERROR_CHECK(i2c_del_slave_device(slave_handle));
|
||||
|
||||
注册事件回调函数
|
||||
^^^^^^^^^^^^^^^^^
|
||||
@@ -601,8 +538,8 @@ I2C 从机事件回调函数列表见 :cpp:type:`i2c_slave_event_callbacks_t`。
|
||||
|
||||
.. list::
|
||||
|
||||
- :cpp:member:`i2c_slave_event_callbacks_t::on_recv_done` 可设置用于“接收完成”事件的回调函数。该函数原型在 :cpp:type:`i2c_slave_received_callback_t` 中声明。
|
||||
:SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE: - :cpp:member:`i2c_slave_event_callbacks_t::on_stretch_occur` 可设置用于“时钟拉伸”事件的回调函数。该函数原型在 :cpp:type:`i2c_slave_stretch_callback_t` 中声明。
|
||||
- :cpp:member:`i2c_slave_event_callbacks_t::on_request` 为请求事件设置回调函数。
|
||||
- :cpp:member:`i2c_slave_event_callbacks_t::on_receive` 为 receive 事件设置回调函数。函数原型在 :cpp:type:`i2c_slave_received_callback_t` 中声明。
|
||||
|
||||
电源管理
|
||||
^^^^^^^^^^
|
||||
@@ -637,13 +574,28 @@ Kconfig 选项 :ref:`CONFIG_I2C_ISR_IRAM_SAFE` 能够做到以下几点:
|
||||
线程安全
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
工厂函数 :cpp:func:`i2c_new_master_bus` 和 :cpp:func:`i2c_new_slave_device` 由驱动程序保证其线程安全,不需要额外的锁保护,也可从不同的 RTOS 任务中调用这些函数。应避免从多个任务中调用其他非线程安全的公共 I2C API,若确实需要调用,请添加额外的锁。
|
||||
工厂函数 :cpp:func:`i2c_new_master_bus` 和 :cpp:func:`i2c_new_slave_device` 由驱动程序保证线程安全,这意味着可以从不同的 RTOS 任务调用这些函数,而无需额外的锁保护。
|
||||
|
||||
I2C 主机操作函数也通过总线操作信号保证线程安全。
|
||||
|
||||
- :cpp:func:`i2c_master_transmit`.
|
||||
- :cpp:func:`i2c_master_multi_buffer_transmit`.
|
||||
- :cpp:func:`i2c_master_transmit_receive`.
|
||||
- :cpp:func:`i2c_master_receive`.
|
||||
- :cpp:func:`i2c_master_probe`.
|
||||
|
||||
I2C 从机操作函数也通过总线操作信号保证线程安全。
|
||||
|
||||
- :cpp:func:`i2c_slave_write`.
|
||||
|
||||
其他函数不保证线程安全。因此,应避免在没有互斥保护的不同任务中调用这些函数。
|
||||
|
||||
Kconfig 选项
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
- :ref:`CONFIG_I2C_ISR_IRAM_SAFE` 将在 cache 被禁用时控制默认的 ISR 处理程序正常工作,详情请参阅 `IRAM 安全 <#iram-safe>`__。
|
||||
- :ref:`CONFIG_I2C_ENABLE_DEBUG_LOG` 可启用调试日志,但会增加固件二进制文件大小。
|
||||
- :ref:`CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2` 用于启用 I2C 从机驱动 v2.0 程序。
|
||||
|
||||
应用示例
|
||||
--------
|
||||
@@ -652,6 +604,7 @@ Kconfig 选项
|
||||
|
||||
- :example:`peripherals/i2c/i2c_tools` 演示了如何使用 I2C 工具开发 I2C 相关的应用程序,提供了用于配置 I2C 总线、扫描设备、读取、设置和检查寄存器的命令行工具。
|
||||
|
||||
- :example:`peripherals/i2c/i2c_slave_network_sensor` 演示如何使用 I2C 从机开发 I2C 相关应用程序,提供 I2C 从机如何充当网络传感器,以及如何使用事件回调接收和发送数据。
|
||||
|
||||
API 参考
|
||||
--------
|
||||
|
206
docs/zh_CN/api-reference/peripherals/i2c_slave_v1.rst
Normal file
206
docs/zh_CN/api-reference/peripherals/i2c_slave_v1.rst
Normal file
@@ -0,0 +1,206 @@
|
||||
.. _i2c-slave-v1:
|
||||
|
||||
I2C Slave v1.0
|
||||
=================
|
||||
|
||||
.. warning::
|
||||
|
||||
该 I2C 从机驱动程序 v1.0 将在 ESP-IDF v6.0 中删除。我们建议你通过 :ref:`CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2` 使用 I2C 驱动程序 v2.0。
|
||||
|
||||
通过 :cpp:func:`i2c_new_slave_device` 安装 I2C 从机驱动程序后,{IDF_TARGET_NAME} 就可以作为从站与其他 I2C Master 通信了。
|
||||
|
||||
安装 I2C 从机设备
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
I2C 从机设备需要 :cpp:type:`i2c_slave_config_t` 指定的配置:
|
||||
|
||||
.. list::
|
||||
|
||||
- :cpp:member:`i2c_slave_config_t::i2c_port` 设置控制器使用的 I2C 端口。
|
||||
- :cpp:member:`i2c_slave_config_t::sda_io_num` 设置串行数据总线 (SDA) 的 GPIO 编号。
|
||||
- :cpp:member:`i2c_slave_config_t::scl_io_num` 设置串行时钟总线 (SCL) 的 GPIO 编号。
|
||||
- :cpp:member:`i2c_slave_config_t::clk_source` 选择 I2C 总线的时钟源。可用时钟列表见 :cpp:type:`i2c_clock_source_t`。有关不同时钟源对功耗的影响,请参阅 `电源管理 <#power-management>`__。
|
||||
- :cpp:member:`i2c_slave_config_t::send_buf_depth` 设置发送 buffer 的长度。
|
||||
- :cpp:member:`i2c_slave_config_t::slave_addr` 设置从机地址。
|
||||
- :cpp:member:`i2c_slave_config_t::intr_priority` 设置中断的优先级。如果设置为 ``0`` ,则驱动程序将使用低或中优先级的中断(优先级可设为 1、2 或 3 中的一个),若未设置,则将使用 :cpp:member:`i2c_slave_config_t::intr_priority` 指示的优先级。请使用数字形式(1、2、3),不要用位掩码形式((1<<1)、(1<<2)、(1<<3))。请注意,中断优先级一旦设置完成,在调用 :cpp:func:`i2c_del_slave_bus` 之前都无法更改。
|
||||
- :cpp:member:`i2c_slave_config_t::addr_bit_len`。如果需要从机设备具有 10 位地址,则将该成员变量设为 ``I2C_ADDR_BIT_LEN_10``。
|
||||
:SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE: - :cpp:member:`i2c_slave_config_t::stretch_en`。如果要启用从机控制器拉伸功能,请将该成员变量设为 true。有关 I2C 拉伸的工作原理,请参阅 [`TRM <{IDF_TARGET_TRM_EN_URL}#i2c>`__]。
|
||||
:SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE: - :cpp:member:`i2c_slave_config_t::broadcast_en`。如果要启用从机广播,请将该成员变量设为 true。当从机设备接收到来自主机设备的通用调用地址 0x00,且后面的读写位为 0 时,无论从机设备自身地址如何,都会响应主机设备。
|
||||
:SOC_I2C_SLAVE_SUPPORT_I2CRAM_ACCESS: - :cpp:member:`i2c_slave_config_t::access_ram_en`。如果要启用 non-FIFO 模式,请将该成员变量设为 true,则 I2C 数据 FIFO 可用作 RAM,并将同步打开双地址。
|
||||
:SOC_I2C_SLAVE_SUPPORT_SLAVE_UNMATCH: - :cpp:member:`i2c_slave_config_t::slave_unmatch_en`。将该成员变量设为 true,将启用从机设备不匹配中断。如果主机设备发送的命令地址与从机设备地址不匹配,则会触发不匹配中断。
|
||||
|
||||
一旦填充好 :cpp:type:`i2c_slave_config_t` 结构体的必要参数,就可调用 :cpp:func:`i2c_new_slave_device` 来分配和初始化 I2C 主机总线。如果函数运行正确,则将返回一个 I2C 总线句柄。若没有可用的 I2C 端口,此函数将返回 :c:macro:`ESP_ERR_NOT_FOUND` 错误。
|
||||
|
||||
.. code:: c
|
||||
|
||||
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;
|
||||
ESP_ERROR_CHECK(i2c_new_slave_device(&i2c_slv_config, &slave_handle));
|
||||
|
||||
卸载 I2C 从机设备
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
如果不再需要之前安装的 I2C 总线,建议调用 :cpp:func:`i2c_del_slave_device` 来回收资源,以释放底层硬件。
|
||||
|
||||
I2C 从机控制器
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
通过调用 :cpp:func:`i2c_new_slave_device` 安装好 I2C 从机驱动程序后,{IDF_TARGET_NAME} 就可以作为从机与其他 I2C 主机进行通信了。
|
||||
|
||||
I2C 从机写入
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
I2C 从机的发送 buffer 可作为 FIFO 来存储要发送的数据。在主机请求这些数据前,它们会一直排队。可通过调用 :cpp:func:`i2c_slave_transmit` 来传输数据。
|
||||
|
||||
将数据写入 FIFO 的简单示例:
|
||||
|
||||
.. code:: c
|
||||
|
||||
uint8_t *data_wr = (uint8_t *) malloc(DATA_LENGTH);
|
||||
|
||||
i2c_slave_config_t i2c_slv_config = {
|
||||
.addr_bit_len = I2C_ADDR_BIT_LEN_7, // 7 位地址
|
||||
.clk_source = I2C_CLK_SRC_DEFAULT, // 设置时钟源
|
||||
.i2c_port = TEST_I2C_PORT, // 设置 I2C 端口编号
|
||||
.send_buf_depth = 256, // 设置 TX buffer 长度
|
||||
.scl_io_num = I2C_SLAVE_SCL_IO, // SCL 管脚编号
|
||||
.sda_io_num = I2C_SLAVE_SDA_IO, // SDA 管脚编号
|
||||
.slave_addr = 0x58, // 从机地址
|
||||
};
|
||||
|
||||
i2c_slave_dev_handle_t slave_handle;
|
||||
ESP_ERROR_CHECK(i2c_new_slave_device(&i2c_slv_config, &slave_handle));
|
||||
for (int i = 0; i < DATA_LENGTH; i++) {
|
||||
data_wr[i] = i;
|
||||
}
|
||||
|
||||
ESP_ERROR_CHECK(i2c_slave_transmit(slave_handle, data_wr, DATA_LENGTH, 10000));
|
||||
|
||||
I2C 从机读取
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
每当主机将数据写入从机,从机都会自动将数据存储在接收 buffer 中,从而使从机应用程序能自由调用 :cpp:func:`i2c_slave_receive`。:cpp:func:`i2c_slave_receive` 为非阻塞接口,因此要想知道接收是否完成,需注册回调函数 :cpp:func:`i2c_slave_register_event_callbacks`。
|
||||
|
||||
.. code:: c
|
||||
|
||||
static IRAM_ATTR bool i2c_slave_rx_done_callback(i2c_slave_dev_handle_t channel, const i2c_slave_rx_done_event_data_t *edata, void *user_data)
|
||||
{
|
||||
BaseType_t high_task_wakeup = pdFALSE;
|
||||
QueueHandle_t receive_queue = (QueueHandle_t)user_data;
|
||||
xQueueSendFromISR(receive_queue, edata, &high_task_wakeup);
|
||||
return high_task_wakeup == pdTRUE;
|
||||
}
|
||||
|
||||
uint8_t *data_rd = (uint8_t *) malloc(DATA_LENGTH);
|
||||
uint32_t size_rd = 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;
|
||||
ESP_ERROR_CHECK(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 = i2c_slave_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;
|
||||
ESP_ERROR_CHECK(i2c_slave_receive(slave_handle, data_rd, DATA_LENGTH));
|
||||
xQueueReceive(s_receive_queue, &rx_data, pdMS_TO_TICKS(10000));
|
||||
// 接收完成。
|
||||
|
||||
.. only:: SOC_I2C_SLAVE_SUPPORT_I2CRAM_ACCESS
|
||||
|
||||
将数据放入 I2C 从机 RAM 中
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
如上所述,I2C 从机 FIFO 可被用作 RAM,即可以通过地址字段直接访问 RAM。例如,可参照下图将数据写入第三个 RAM 块。请注意,在进行操作前需要先将 :cpp:member:`i2c_slave_config_t::access_ram_en` 设为 true。
|
||||
|
||||
.. figure:: ../../../_static/diagrams/i2c/i2c_slave_write_slave_ram.png
|
||||
:align: center
|
||||
:alt: 将数据放入 I2C 从机 RAM 中
|
||||
|
||||
将数据放入 I2C 从机 RAM 中
|
||||
|
||||
.. code:: c
|
||||
|
||||
uint8_t data_rd[DATA_LENGTH_RAM] = {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,
|
||||
.flags.access_ram_en = true,
|
||||
};
|
||||
|
||||
// 主机将数据写入从机。
|
||||
|
||||
i2c_slave_dev_handle_t slave_handle;
|
||||
ESP_ERROR_CHECK(i2c_new_slave_device(&i2c_slv_config, &slave_handle));
|
||||
ESP_ERROR_CHECK(i2c_slave_read_ram(slave_handle, 0x5, data_rd, DATA_LENGTH_RAM));
|
||||
ESP_ERROR_CHECK(i2c_del_slave_device(slave_handle));
|
||||
|
||||
从 I2C 从机 RAM 中获取数据
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
数据可被存储在相对从机一定偏移量的 RAM 中,且主机可直接通过 RAM 地址读取这些数据。例如,如果数据被存储在第三个 RAM 块中,则主机可参照下图读取这些数据。请注意,在操作前需要先将 :cpp:member:`i2c_slave_config_t::access_ram_en` 设为 true。
|
||||
|
||||
.. figure:: ../../../_static/diagrams/i2c/i2c_slave_read_slave_ram.png
|
||||
:align: center
|
||||
:alt: 从 I2C 从机 RAM 中获取数据
|
||||
|
||||
从 I2C 从机 RAM 中获取数据
|
||||
|
||||
.. code:: c
|
||||
|
||||
uint8_t data_wr[DATA_LENGTH_RAM] = {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,
|
||||
.flags.access_ram_en = true,
|
||||
};
|
||||
|
||||
i2c_slave_dev_handle_t slave_handle;
|
||||
ESP_ERROR_CHECK(i2c_new_slave_device(&i2c_slv_config, &slave_handle));
|
||||
ESP_ERROR_CHECK(i2c_slave_write_ram(slave_handle, 0x2, data_wr, DATA_LENGTH_RAM));
|
||||
ESP_ERROR_CHECK(i2c_del_slave_device(slave_handle));
|
||||
|
||||
I2C 从机回调
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
当 I2C 从机总线触发中断时,将生成特定事件并通知 CPU。如果需要在发生这些事件时调用某些函数,可通过 :cpp:func:`i2c_slave_register_event_callbacks` 将这些函数挂接到中断服务程序 (ISR) 上。由于注册的回调函数是在中断上下文中被调用的,所以应确保这些函数不会导致延迟(例如,确保仅从函数中调用带有 ``ISR`` 后缀的 FreeRTOS API)。回调函数需要返回一个布尔值,告诉调用者是否唤醒了高优先级任务。
|
||||
|
||||
I2C 从机事件回调函数列表见 :cpp:type:`i2c_slave_event_callbacks_t`。
|
||||
|
||||
.. list::
|
||||
|
||||
- :cpp:member:`i2c_slave_event_callbacks_t::on_recv_done` 可设置用于“接收完成”事件的回调函数。该函数原型在 :cpp:type:`i2c_slave_received_callback_t` 中声明。
|
||||
:SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE: - :cpp:member:`i2c_slave_event_callbacks_t::on_stretch_occur` 可设置用于“时钟拉伸”事件的回调函数。该函数原型在 :cpp:type:`i2c_slave_stretch_callback_t` 中声明。
|
@@ -67,6 +67,12 @@ examples/peripherals/i2c/i2c_eeprom:
|
||||
depends_components:
|
||||
- esp_driver_i2c
|
||||
|
||||
examples/peripherals/i2c/i2c_slave_network_sensor:
|
||||
disable:
|
||||
- if: SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE != 1 or (SOC_WIFI_SUPPORTED != 1 and SOC_EMAC_SUPPORTED != 1)
|
||||
depends_components:
|
||||
- esp_driver_i2c
|
||||
|
||||
examples/peripherals/i2c/i2c_tools:
|
||||
disable:
|
||||
- if: SOC_I2C_SUPPORTED != 1
|
||||
|
@@ -0,0 +1,10 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
|
||||
|
||||
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
|
||||
idf_build_set_property(MINIMAL_BUILD ON)
|
||||
project(i2c_slave_network_sensor)
|
129
examples/peripherals/i2c/i2c_slave_network_sensor/README.md
Normal file
129
examples/peripherals/i2c/i2c_slave_network_sensor/README.md
Normal file
@@ -0,0 +1,129 @@
|
||||
| Supported Targets | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
|
||||
| ----------------- | -------- | -------- | -------- | --------- | -------- | -------- | -------- |
|
||||
|
||||
|
||||
# I2C slave example
|
||||
|
||||
This code demonstrates how to use the I2C slave driver to make a "network sensor". For illustration, the "sensor" will fetch some data from Github. You can attach this "sensor" to any I2C bus, then the master should be able to read the network data. This example uses the stretch mechanism to solve the problem of the i2c slave not knowing when it should send or receive data. In this example, we have a task to periodically update esp-idf's data on github. When the i2c master comes to visit, if the i2c slave fifo has data, it will return the data directly, if there is no data, it will set off the stretch interrupt, then there will be a callback to fill in the updated github data into the i2c fifo, release the stretch, and the master will return the data.
|
||||
|
||||
## Overview
|
||||
|
||||
This example demonstrates the possible usage of I2C slave driver
|
||||
|
||||
## How to use example
|
||||
|
||||
### Hardware Required
|
||||
|
||||
To run this example, you should have one ESP32-S, ESP32-C based development board. Also this example need wifi signal to access the internet.
|
||||
|
||||
#### Pin Assignment:
|
||||
|
||||
**Note:** The following pin assignments are used by default, you can change these in the `menuconfig` .
|
||||
|
||||
| | SDA | SCL |
|
||||
| ------------------------------ | -------------- | -------------- |
|
||||
| ESP I2C Master (this example) | I2C_MASTER_SDA | I2C_MASTER_SCL |
|
||||
| ESP I2C Slave (this example) | I2C_SLAVE_SDA | I2C_SLAVE_SCL |
|
||||
|
||||
For the actual default value of `I2C_SLAVE_SDA` and `I2C_SLAVE_SCL` see `Example Configuration` in `menuconfig`.
|
||||
|
||||
**Note:** There's no need to add an external pull-up resistors for SDA/SCL pin, because the driver will enable the internal pull-up resistors.
|
||||
|
||||
|
||||
### Command Assignment:
|
||||
|
||||
| Command | Content |
|
||||
| --------- | ------------ |
|
||||
| 0x10 | stars |
|
||||
| 0x20 | forks |
|
||||
| 0x30 | open_issues |
|
||||
| 0x40 | description |
|
||||
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Enter `idf.py menuconfig` to config the internet information. In Example Connection Configuration option to config your wifi ssid and password.
|
||||
|
||||
Enter `idf.py -p PORT flash monitor` to build, flash and monitor the project.
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
|
||||
```
|
||||
I (11200) wifi:dp: 1, bi: 102400, li: 3, scale listen interval from 307200 us to 307200 us
|
||||
I (11210) wifi:set rx beacon pti, rx_bcn_pti: 0, bcn_timeout: 25000, mt_pti: 0, mt_time: 10000
|
||||
I (11250) wifi:<ba-add>idx:0 (ifx:0, f6:0e:57:fd:ee:8d), tid:0, ssn:1, winSize:64
|
||||
I (11610) wifi:dp: 2, bi: 102400, li: 4, scale listen interval from 307200 us to 409600 us
|
||||
I (11620) wifi:AP's beacon interval = 102400 us, DTIM period = 2
|
||||
I (12460) example_connect: Got IPv6 event: Interface "example_netif_sta" address: fe80:0000:0000:0000:86f7:03ff:fe80:0284, type: ESP_IP6_ADDR_IS_LINK_LOCAL
|
||||
I (12720) esp_netif_handlers: example_netif_sta ip: 192.168.70.133, mask: 255.255.255.0, gw: 192.168.70.132
|
||||
I (12720) example_connect: Got IPv4 event: Interface "example_netif_sta" address: 192.168.70.133
|
||||
I (12730) example_common: Connected to example_netif_sta
|
||||
I (12730) example_common: - IPv4 address: 192.168.70.133,
|
||||
I (12740) example_common: - IPv6 address: fe80:0000:0000:0000:86f7:03ff:fe80:0284, type: ESP_IP6_ADDR_IS_LINK_LOCAL
|
||||
I (12750) gpio: GPIO[5]| InputEn: 1| OutputEn: 1| OpenDrain: 1| Pullup: 0| Pulldown: 0| Intr:0
|
||||
I (12760) gpio: GPIO[4]| InputEn: 1| OutputEn: 1| OpenDrain: 1| Pullup: 0| Pulldown: 0| Intr:0
|
||||
I (12770) main_task: Returned from app_main()
|
||||
I (13470) wifi:<ba-add>idx:1 (ifx:0, f6:0e:57:fd:ee:8d), tid:3, ssn:0, winSize:64
|
||||
Star count: 13401
|
||||
Forks count: 7248
|
||||
issue count: 1767
|
||||
the description is: Espressif IoT Development Framework. Official development framework for Espressif SoCs.
|
||||
I (16280) HTTP_CLIENT: HTTP GET Status = 200, content_length = 6165
|
||||
```
|
||||
|
||||
Then you can read the information from an I2C master device.
|
||||
|
||||
We can get information via `i2c_tool` example with another esp chip plays in a i2c master role:
|
||||
|
||||
```
|
||||
I (203) main_task: Calling app_main()
|
||||
|
||||
==============================================================
|
||||
| Steps to Use i2c-tools |
|
||||
| |
|
||||
| 1. Try 'help', check all supported commands |
|
||||
| 2. Try 'i2cconfig' to configure your I2C bus |
|
||||
| 3. Try 'i2cdetect' to scan devices on the bus |
|
||||
| 4. Try 'i2cget' to get the content of specific register |
|
||||
| 5. Try 'i2cset' to set the value of specific register |
|
||||
| 6. Try 'i2cdump' to dump all the register (Experiment) |
|
||||
| |
|
||||
==============================================================
|
||||
|
||||
|
||||
Type 'help' to get the list of commands.
|
||||
Use UP/DOWN arrows to navigate through command history.
|
||||
Press TAB when typing command name to auto-complete.
|
||||
I (303) main_task: Returned from app_main()
|
||||
i2c-tools> i2cconfig --port=0 --freq=100000 --sda=5 --scl=4
|
||||
i2c-tools> i2cdetect
|
||||
0 1 2 3 4 5 6 7 8 9 a b c d e f
|
||||
00: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
|
||||
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
|
||||
20: -- -- -- -- -- -- -- -- 28 -- -- -- -- -- -- --
|
||||
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
|
||||
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
|
||||
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
|
||||
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
|
||||
70: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
|
||||
i2c-tools> i2cget -c 0x28 -r 0x10 -l 4
|
||||
0x4b 0x35 0x00 0x00
|
||||
i2c-tools> i2cget -c 0x28 -r 0x20 -l 4
|
||||
0x75 0x1c 0x00 0x00
|
||||
i2c-tools> i2cget -c 0x28 -r 0x40 -l 100
|
||||
0x45 0x73 0x70 0x72 0x65 0x73 0x73 0x69 0x66 0x20 0x49 0x6f 0x54 0x20 0x44 0x65
|
||||
0x76 0x65 0x6c 0x6f 0x70 0x6d 0x65 0x6e 0x74 0x20 0x46 0x72 0x61 0x6d 0x65 0x77
|
||||
0x6f 0x72 0x6b 0x2e 0x20 0x4f 0x66 0x66 0x69 0x63 0x69 0x61 0x6c 0x20 0x64 0x65
|
||||
0x76 0x65 0x6c 0x6f 0x70 0x6d 0x65 0x6e 0x74 0x20 0x66 0x72 0x61 0x6d 0x65 0x77
|
||||
0x6f 0x72 0x6b 0x20 0x66 0x6f 0x72 0x20 0x45 0x73 0x70 0x72 0x65 0x73 0x73 0x69
|
||||
0x66 0x20 0x53 0x6f 0x43 0x73 0x2e 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
|
||||
0x00 0x00 0x00 0x00
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
(For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you as soon as possible.)
|
@@ -0,0 +1,5 @@
|
||||
set(srcs "i2c_slave_main.c")
|
||||
|
||||
idf_component_register(SRCS ${srcs}
|
||||
PRIV_REQUIRES esp_http_client esp_wifi nvs_flash json esp_driver_i2c
|
||||
INCLUDE_DIRS ".")
|
@@ -0,0 +1,24 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
menu "I2C Slave"
|
||||
config I2C_SLAVE_SCL
|
||||
int "SCL GPIO Num"
|
||||
default 4
|
||||
help
|
||||
GPIO number for I2C Slave clock line.
|
||||
|
||||
config I2C_SLAVE_SDA
|
||||
int "SDA GPIO Num"
|
||||
default 5
|
||||
help
|
||||
GPIO number for I2C Slave data line.
|
||||
|
||||
config I2C_SLAVE_ADDRESS
|
||||
hex "I2C SLAVE ADDRESS"
|
||||
default 0x28
|
||||
help
|
||||
Address of I2C slave
|
||||
|
||||
endmenu
|
||||
|
||||
endmenu
|
@@ -0,0 +1,252 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "esp_log.h"
|
||||
#include "sdkconfig.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_log.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_netif.h"
|
||||
#include "protocol_examples_common.h"
|
||||
#include "esp_http_client.h"
|
||||
#include "cJSON.h"
|
||||
#include "driver/i2c_slave.h"
|
||||
|
||||
static const char *TAG = "example";
|
||||
|
||||
#define I2C_SLAVE_SCL_IO CONFIG_I2C_SLAVE_SCL /*!< gpio number for i2c slave clock */
|
||||
#define I2C_SLAVE_SDA_IO CONFIG_I2C_SLAVE_SDA /*!< gpio number for i2c slave data */
|
||||
#define I2C_SLAVE_NUM 0
|
||||
#define ESP_SLAVE_ADDR CONFIG_I2C_SLAVE_ADDRESS /*!< ESP slave address, you can set any 7bit value */
|
||||
|
||||
// Command Lists
|
||||
#define STARS_COMMAND (0x10)
|
||||
#define FORKS_COMMAND (0x20)
|
||||
#define OPENISSUES_COMMAND (0x30)
|
||||
#define DESCRIPTIONS_COMMAND (0x40)
|
||||
|
||||
#define GITHUB_API_URL "https://api.github.com/repos/espressif/esp-idf"
|
||||
|
||||
typedef struct {
|
||||
char *json_buffer;
|
||||
int json_size;
|
||||
uint8_t tmp_buffer_stars[sizeof(int)];
|
||||
uint8_t tmp_buffer_forks[sizeof(int)];
|
||||
uint8_t tmp_buffer_open_issues[sizeof(int)];
|
||||
uint8_t tmp_buffer_descriptions[100];
|
||||
QueueHandle_t event_queue;
|
||||
uint8_t command_data;
|
||||
i2c_slave_dev_handle_t handle;
|
||||
} i2c_slave_github_context_t;
|
||||
|
||||
typedef enum {
|
||||
I2C_SLAVE_EVT_RX,
|
||||
I2C_SLAVE_EVT_TX
|
||||
} i2c_slave_event_t;
|
||||
|
||||
static esp_err_t _http_event_handler(esp_http_client_event_t *evt)
|
||||
{
|
||||
i2c_slave_github_context_t *context = (i2c_slave_github_context_t *)evt->user_data;
|
||||
int star_count = 0, forks_count = 0, open_issues_count = 0;
|
||||
|
||||
switch (evt->event_id) {
|
||||
case HTTP_EVENT_ON_DATA:
|
||||
if (evt->data_len > 0) {
|
||||
if (context->json_buffer == NULL) {
|
||||
context->json_buffer = malloc(evt->data_len + 1);
|
||||
} else {
|
||||
context->json_buffer = realloc(context->json_buffer, context->json_size + evt->data_len + 1);
|
||||
}
|
||||
if (context->json_buffer == NULL) {
|
||||
ESP_LOGE("HTTP_CLIENT", "Failed to allocate memory for data json_buffer");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
memcpy(context->json_buffer + context->json_size, evt->data, evt->data_len);
|
||||
context->json_size += evt->data_len;
|
||||
context->json_buffer[context->json_size] = '\0'; // Null-terminate the string
|
||||
}
|
||||
break;
|
||||
case HTTP_EVENT_ON_FINISH:
|
||||
if (context->json_buffer != NULL) {
|
||||
// Process received data
|
||||
cJSON *root = cJSON_Parse(context->json_buffer);
|
||||
cJSON *stars = cJSON_GetObjectItem(root, "stargazers_count");
|
||||
|
||||
if (stars != NULL) {
|
||||
star_count = stars->valueint;
|
||||
printf("Star count: %d\n", star_count);
|
||||
memcpy(context->tmp_buffer_stars, &star_count, sizeof(int));
|
||||
}
|
||||
cJSON *forks = cJSON_GetObjectItem(root, "forks_count");
|
||||
if (forks != NULL) {
|
||||
forks_count = forks->valueint;
|
||||
printf("Forks count: %d\n", forks_count);
|
||||
memcpy(context->tmp_buffer_forks, &forks_count, sizeof(int));
|
||||
}
|
||||
cJSON *open_issues = cJSON_GetObjectItem(root, "open_issues_count");
|
||||
if (open_issues != NULL) {
|
||||
open_issues_count = open_issues->valueint;
|
||||
printf("issue count: %d\n", open_issues_count);
|
||||
memcpy(context->tmp_buffer_open_issues, &open_issues_count, sizeof(int));
|
||||
}
|
||||
cJSON *descriptions = cJSON_GetObjectItem(root, "description");
|
||||
if (descriptions != NULL) {
|
||||
printf("the description is: %s\n", descriptions->valuestring);
|
||||
memcpy(context->tmp_buffer_descriptions, descriptions->valuestring, strlen(descriptions->valuestring));
|
||||
}
|
||||
cJSON_Delete(root);
|
||||
free(context->json_buffer);
|
||||
context->json_buffer = NULL;
|
||||
context->json_size = 0;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void http_get_task(void *pvParameters)
|
||||
{
|
||||
i2c_slave_github_context_t *context = (i2c_slave_github_context_t *)pvParameters;
|
||||
|
||||
esp_http_client_config_t config = {
|
||||
.url = GITHUB_API_URL,
|
||||
.event_handler = _http_event_handler,
|
||||
.method = HTTP_METHOD_GET,
|
||||
.buffer_size = 2048,
|
||||
.user_data = context,
|
||||
};
|
||||
|
||||
while (1) {
|
||||
esp_http_client_handle_t client = esp_http_client_init(&config);
|
||||
esp_err_t err = esp_http_client_perform(client);
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI("HTTP_CLIENT", "HTTP GET Status = %d, content_length = %lld",
|
||||
esp_http_client_get_status_code(client),
|
||||
esp_http_client_get_content_length(client));
|
||||
} else {
|
||||
ESP_LOGE("HTTP_CLIENT", "HTTP GET request failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
esp_http_client_cleanup(client);
|
||||
vTaskDelay(30 * 60 * 1000 / portTICK_PERIOD_MS);
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
static bool i2c_slave_request_cb(i2c_slave_dev_handle_t i2c_slave, const i2c_slave_request_event_data_t *evt_data, void *arg)
|
||||
{
|
||||
i2c_slave_github_context_t *context = (i2c_slave_github_context_t *)arg;
|
||||
i2c_slave_event_t evt = I2C_SLAVE_EVT_TX;
|
||||
BaseType_t xTaskWoken = 0;
|
||||
xQueueSendFromISR(context->event_queue, &evt, &xTaskWoken);
|
||||
return xTaskWoken;
|
||||
}
|
||||
|
||||
static bool i2c_slave_receive_cb(i2c_slave_dev_handle_t i2c_slave, const i2c_slave_rx_done_event_data_t *evt_data, void *arg)
|
||||
{
|
||||
i2c_slave_github_context_t *context = (i2c_slave_github_context_t *)arg;
|
||||
i2c_slave_event_t evt = I2C_SLAVE_EVT_RX;
|
||||
BaseType_t xTaskWoken = 0;
|
||||
// Command only contains one byte, so just save one bytes here.
|
||||
context->command_data = *evt_data->buffer;
|
||||
xQueueSendFromISR(context->event_queue, &evt, &xTaskWoken);
|
||||
return xTaskWoken;
|
||||
}
|
||||
|
||||
static void i2c_slave_task(void *arg)
|
||||
{
|
||||
i2c_slave_github_context_t *context = (i2c_slave_github_context_t *)arg;
|
||||
i2c_slave_dev_handle_t handle = (i2c_slave_dev_handle_t)context->handle;
|
||||
|
||||
uint8_t zero_buffer[32] = {}; // Use this buffer to clear the fifo.
|
||||
uint32_t write_len, total_written;
|
||||
uint32_t buffer_size = 0;
|
||||
|
||||
while (true) {
|
||||
i2c_slave_event_t evt;
|
||||
if (xQueueReceive(context->event_queue, &evt, 10) == pdTRUE) {
|
||||
if (evt == I2C_SLAVE_EVT_TX) {
|
||||
uint8_t *data_buffer;
|
||||
switch (context->command_data) {
|
||||
case STARS_COMMAND:
|
||||
data_buffer = context->tmp_buffer_stars;
|
||||
buffer_size = sizeof(context->tmp_buffer_stars);
|
||||
break;
|
||||
case FORKS_COMMAND:
|
||||
data_buffer = context->tmp_buffer_forks;
|
||||
buffer_size = sizeof(context->tmp_buffer_forks);
|
||||
break;
|
||||
case OPENISSUES_COMMAND:
|
||||
data_buffer = context->tmp_buffer_open_issues;
|
||||
buffer_size = sizeof(context->tmp_buffer_open_issues);
|
||||
break;
|
||||
case DESCRIPTIONS_COMMAND:
|
||||
data_buffer = context->tmp_buffer_descriptions;
|
||||
buffer_size = sizeof(context->tmp_buffer_descriptions);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGE(TAG, "Invalid command");
|
||||
data_buffer = zero_buffer;
|
||||
buffer_size = sizeof(zero_buffer);
|
||||
break;
|
||||
}
|
||||
|
||||
total_written = 0;
|
||||
while (total_written < buffer_size) {
|
||||
ESP_ERROR_CHECK(i2c_slave_write(handle, data_buffer + total_written, buffer_size - total_written, &write_len, 1000));
|
||||
if (write_len == 0) {
|
||||
ESP_LOGE(TAG, "Write error or timeout");
|
||||
break;
|
||||
}
|
||||
total_written += write_len;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
static i2c_slave_github_context_t context = {0};
|
||||
|
||||
ESP_ERROR_CHECK(nvs_flash_init());
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
ESP_ERROR_CHECK(example_connect());
|
||||
|
||||
context.event_queue = xQueueCreate(16, sizeof(i2c_slave_event_t));
|
||||
if (!context.event_queue) {
|
||||
ESP_LOGE(TAG, "Creating queue failed");
|
||||
return;
|
||||
}
|
||||
|
||||
i2c_slave_config_t i2c_slv_config = {
|
||||
.i2c_port = I2C_SLAVE_NUM,
|
||||
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||
.scl_io_num = I2C_SLAVE_SCL_IO,
|
||||
.sda_io_num = I2C_SLAVE_SDA_IO,
|
||||
.slave_addr = ESP_SLAVE_ADDR,
|
||||
.send_buf_depth = 100,
|
||||
.receive_buf_depth = 100,
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(i2c_new_slave_device(&i2c_slv_config, &context.handle));
|
||||
i2c_slave_event_callbacks_t cbs = {
|
||||
.on_receive = i2c_slave_receive_cb,
|
||||
.on_request = i2c_slave_request_cb,
|
||||
};
|
||||
ESP_ERROR_CHECK(i2c_slave_register_event_callbacks(context.handle, &cbs, &context));
|
||||
|
||||
xTaskCreate(http_get_task, "http_get_task", 4096, &context, 20, NULL);
|
||||
xTaskCreate(i2c_slave_task, "i2c_slave_task", 1024 * 4, &context, 10, NULL);
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
dependencies:
|
||||
protocol_examples_common:
|
||||
path: ${IDF_PATH}/examples/common_components/protocol_examples_common
|
@@ -0,0 +1,5 @@
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
|
||||
nvs, data,nvs,0x9000,24K,
|
||||
phy_init, data,phy,0xf000,4K,
|
||||
factory, app,factory,0x10000,2M,
|
|
@@ -0,0 +1,5 @@
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
|
||||
CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2=y
|
||||
CONFIG_ESP_TLS_INSECURE=y
|
||||
CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY=y
|
Reference in New Issue
Block a user