2024-11-08 17:59:10 +08:00
/*
* 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 ;
}
}
2024-12-04 11:33:55 +08:00
xSemaphoreGiveFromISR ( i2c_slave - > operation_mux , & xTaskWoken ) ;
2024-11-08 17:59:10 +08:00
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 ) ;
2024-11-20 18:16:34 +08:00
i2c_ll_enable_fifo_mode ( hal - > dev , true ) ;
2024-11-08 17:59:10 +08:00
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 ;
}