From f9f70d2f73d16f7fb50f59e05323cd041acce830 Mon Sep 17 00:00:00 2001 From: me-no-dev Date: Sat, 9 Oct 2021 14:30:20 +0300 Subject: [PATCH 1/3] I2C Slave Implementation --- cores/esp32/esp32-hal-i2c-slave.c | 829 ++++++++++++++++++ cores/esp32/esp32-hal-i2c-slave.h | 35 + .../Wire/examples/WireMaster/WireMaster.ino | 30 + libraries/Wire/examples/WireScan/WireScan.ino | 28 + .../Wire/examples/WireSlave/WireSlave.ino | 37 + libraries/Wire/src/Wire.cpp | 101 +++ libraries/Wire/src/Wire.h | 8 + 7 files changed, 1068 insertions(+) create mode 100755 cores/esp32/esp32-hal-i2c-slave.c create mode 100755 cores/esp32/esp32-hal-i2c-slave.h create mode 100644 libraries/Wire/examples/WireMaster/WireMaster.ino create mode 100644 libraries/Wire/examples/WireScan/WireScan.ino create mode 100644 libraries/Wire/examples/WireSlave/WireSlave.ino diff --git a/cores/esp32/esp32-hal-i2c-slave.c b/cores/esp32/esp32-hal-i2c-slave.c new file mode 100755 index 00000000000..96750c5a960 --- /dev/null +++ b/cores/esp32/esp32-hal-i2c-slave.c @@ -0,0 +1,829 @@ +// Copyright 2015-2021 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sdkconfig.h" +#include "esp_attr.h" +#include "rom/gpio.h" +#include "soc/gpio_sig_map.h" +#include "hal/gpio_types.h" +#include "driver/gpio.h" +#include "esp_err.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "freertos/semphr.h" +#include "freertos/ringbuf.h" + +#include "esp_intr_alloc.h" +#include "driver/periph_ctrl.h" +#include "soc/i2c_reg.h" +#include "soc/i2c_struct.h" +#include "hal/i2c_ll.h" +#include "esp32-hal-log.h" +#include "esp32-hal-i2c-slave.h" + +#define I2C_SLAVE_USE_RX_QUEUE 0 // 1: Queue, 0: RingBuffer + +#if SOC_I2C_NUM > 1 +#define I2C_SCL_IDX(p) ((p==0)?I2CEXT0_SCL_OUT_IDX:((p==1)?I2CEXT1_SCL_OUT_IDX:0)) +#define I2C_SDA_IDX(p) ((p==0)?I2CEXT0_SDA_OUT_IDX:((p==1)?I2CEXT1_SDA_OUT_IDX:0)) +#else +#define I2C_SCL_IDX(p) I2CEXT0_SCL_OUT_IDX +#define I2C_SDA_IDX(p) I2CEXT0_SDA_OUT_IDX +#endif + +#if CONFIG_IDF_TARGET_ESP32 + #define I2C_TXFIFO_WM_INT_ENA I2C_TXFIFO_EMPTY_INT_ENA + #define I2C_RXFIFO_WM_INT_ENA I2C_RXFIFO_FULL_INT_ENA +#endif + +enum { + I2C_SLAVE_EVT_RX, I2C_SLAVE_EVT_TX +}; + +typedef struct i2c_slave_struct_t { + i2c_dev_t * dev; + uint8_t num; + int8_t sda; + int8_t scl; + i2c_slave_request_cb_t request_callback; + i2c_slave_receive_cb_t receive_callback; + void * arg; + intr_handle_t intr_handle; + TaskHandle_t task_handle; + xQueueHandle event_queue; +#if I2C_SLAVE_USE_RX_QUEUE + xQueueHandle rx_queue; +#else + RingbufHandle_t rx_ring_buf; +#endif + xQueueHandle tx_queue; + uint32_t rx_data_count; +#if !CONFIG_DISABLE_HAL_LOCKS + xSemaphoreHandle lock; +#endif +} i2c_slave_struct_t; + +typedef union { + struct { + uint32_t event : 2; + uint32_t stop : 1; + uint32_t param : 29; + }; + uint32_t val; +} i2c_slave_queue_event_t; + +static i2c_slave_struct_t _i2c_bus_array[SOC_I2C_NUM] = { + { &I2C0, 0, -1, -1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0 +#if !CONFIG_DISABLE_HAL_LOCKS + , NULL +#endif + }, +#if SOC_I2C_NUM > 1 + { &I2C1, 1, -1, -1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0 +#if !CONFIG_DISABLE_HAL_LOCKS + , NULL +#endif + } +#endif +}; + +#if CONFIG_DISABLE_HAL_LOCKS +#define I2C_SLAVE_MUTEX_LOCK() +#define I2C_SLAVE_MUTEX_UNLOCK() +#else +#define I2C_SLAVE_MUTEX_LOCK() if(i2c->lock){xSemaphoreTake(i2c->lock, portMAX_DELAY);} +#define I2C_SLAVE_MUTEX_UNLOCK() if(i2c->lock){xSemaphoreGive(i2c->lock);} +#endif + +//-------------------------------------- HAL_LL (Missing Functions) ------------------------------------------------ +typedef enum { + I2C_STRETCH_CAUSE_MASTER_READ, + I2C_STRETCH_CAUSE_TX_FIFO_EMPTY, + I2C_STRETCH_CAUSE_RX_FIFO_FULL, + I2C_STRETCH_CAUSE_MAX +} i2c_stretch_cause_t; + +static inline i2c_stretch_cause_t i2c_ll_stretch_cause(i2c_dev_t *hw) +{ +#if CONFIG_IDF_TARGET_ESP32C3 + return hw->sr.stretch_cause; +#elif CONFIG_IDF_TARGET_ESP32S2 + return hw->status_reg.stretch_cause; +#else + return I2C_STRETCH_CAUSE_MAX; +#endif +} + +static inline void i2c_ll_set_stretch(i2c_dev_t *hw, uint16_t time) +{ +#ifndef CONFIG_IDF_TARGET_ESP32 + typeof(hw->scl_stretch_conf) scl_stretch_conf; + scl_stretch_conf.val = 0; + scl_stretch_conf.slave_scl_stretch_en = (time > 0); + scl_stretch_conf.stretch_protect_num = time; + scl_stretch_conf.slave_scl_stretch_clr = 1; + hw->scl_stretch_conf.val = scl_stretch_conf.val; + if(time > 0){ + //enable interrupt + hw->int_ena.val |= I2C_SLAVE_STRETCH_INT_ENA; + } else { + //disable interrupt + hw->int_ena.val &= (~I2C_SLAVE_STRETCH_INT_ENA); + } +#endif +} + +static inline void i2c_ll_stretch_clr(i2c_dev_t *hw) +{ +#ifndef CONFIG_IDF_TARGET_ESP32 + hw->scl_stretch_conf.slave_scl_stretch_clr = 1; +#endif +} + +static inline bool i2c_ll_slave_addressed(i2c_dev_t *hw) +{ +#if CONFIG_IDF_TARGET_ESP32C3 + return hw->sr.slave_addressed; +#else + return hw->status_reg.slave_addressed; +#endif +} + +static inline bool i2c_ll_slave_rw(i2c_dev_t *hw)//not exposed by hal_ll +{ +#if CONFIG_IDF_TARGET_ESP32C3 + return hw->sr.slave_rw; +#else + return hw->status_reg.slave_rw; +#endif +} + +//-------------------------------------- PRIVATE (Function Prototypes) ------------------------------------------------ +static void i2c_slave_delay_us(uint64_t us); +static void i2c_slave_gpio_mode(int8_t pin, gpio_mode_t mode); +static bool i2c_slave_check_line_state(int8_t sda, int8_t scl); +static bool i2c_slave_attach_gpio(i2c_slave_struct_t * i2c, int8_t sda, int8_t scl); +static bool i2c_slave_detach_gpio(i2c_slave_struct_t * i2c); +static bool i2c_slave_set_frequency(i2c_slave_struct_t * i2c, uint32_t clk_speed); +static bool i2c_slave_send_event(i2c_slave_struct_t * i2c, i2c_slave_queue_event_t* event); +static bool i2c_slave_handle_tx_fifo_empty(i2c_slave_struct_t * i2c); +static bool i2c_slave_handle_rx_fifo_full(i2c_slave_struct_t * i2c, uint32_t len); +static size_t i2c_slave_read_rx(i2c_slave_struct_t * i2c, uint8_t * data, size_t len); +static void i2c_slave_isr_handler(void* arg); +static void i2c_slave_task(void *pv_args); + + +//===================================================================================================================== +//-------------------------------------- Public Functions ------------------------------------------------------------- +//===================================================================================================================== + +esp_err_t i2c_slave_attach_callbacks(uint8_t num, i2c_slave_request_cb_t request_callback, i2c_slave_receive_cb_t receive_callback, void * arg){ + if(num >= SOC_I2C_NUM){ + log_e("Invalid port num: %u", num); + return ESP_ERR_INVALID_ARG; + } + i2c_slave_struct_t * i2c = &_i2c_bus_array[num]; + I2C_SLAVE_MUTEX_LOCK(); + i2c->request_callback = request_callback; + i2c->receive_callback = receive_callback; + i2c->arg = arg; + I2C_SLAVE_MUTEX_UNLOCK(); + return ESP_OK; +} + +esp_err_t i2c_slave_deinit(uint8_t num){ + if(num >= SOC_I2C_NUM){ + log_e("Invalid port num: %u", num); + return ESP_ERR_INVALID_ARG; + } + + i2c_slave_struct_t * i2c = &_i2c_bus_array[num]; + I2C_SLAVE_MUTEX_LOCK(); + i2c_slave_detach_gpio(i2c); + i2c_ll_set_slave_addr(i2c->dev, 0, false); + i2c_ll_disable_intr_mask(i2c->dev, I2C_LL_INTR_MASK); + i2c_ll_clr_intsts_mask(i2c->dev, I2C_LL_INTR_MASK); + + if (i2c->intr_handle) { + esp_intr_free(i2c->intr_handle); + i2c->intr_handle = NULL; + } + + if(i2c->task_handle){ + vTaskDelete(i2c->task_handle); + i2c->task_handle = NULL; + } + +#if I2C_SLAVE_USE_RX_QUEUE + if (i2c->rx_queue) { + vQueueDelete(i2c->rx_queue); + i2c->rx_queue = NULL; + } +#else + if (i2c->rx_ring_buf) { + vRingbufferDelete(i2c->rx_ring_buf); + i2c->rx_ring_buf = NULL; + } +#endif + + if (i2c->tx_queue) { + vQueueDelete(i2c->tx_queue); + i2c->tx_queue = NULL; + } + + if (i2c->event_queue) { + vQueueDelete(i2c->event_queue); + i2c->event_queue = NULL; + } + + i2c->rx_data_count = 0; + I2C_SLAVE_MUTEX_UNLOCK(); + return ESP_OK; +} + +esp_err_t i2c_slave_init(uint8_t num, int sda, int scl, uint16_t slaveID, uint32_t frequency, size_t rx_len, size_t tx_len) { + if(num >= SOC_I2C_NUM){ + log_e("Invalid port num: %u", num); + return ESP_ERR_INVALID_ARG; + } + + if (sda < 0 || scl < 0) { + log_e("invalid pins sda=%d, scl=%d", sda, scl); + return ESP_ERR_INVALID_ARG; + } + + log_i("Initialising I2C Slave: sda=%d scl=%d freq=%d, addr=0x%x", sda, scl, frequency, slaveID); + + i2c_slave_struct_t * i2c = &_i2c_bus_array[num]; + esp_err_t ret = ESP_OK; + +#if !CONFIG_DISABLE_HAL_LOCKS + if(!i2c->lock){ + i2c->lock = xSemaphoreCreateMutex(); + if (i2c->lock == NULL) { + log_e("RX queue create failed"); + return ESP_ERR_NO_MEM; + } + } +#endif + i2c_slave_deinit(num); + + I2C_SLAVE_MUTEX_LOCK(); + +#if I2C_SLAVE_USE_RX_QUEUE + i2c->rx_queue = xQueueCreate(rx_len, sizeof(uint8_t)); + if (i2c->rx_queue == NULL) { + log_e("RX queue create failed"); + ret = ESP_ERR_NO_MEM; + goto fail; + } +#else + i2c->rx_ring_buf = xRingbufferCreate(rx_len, RINGBUF_TYPE_BYTEBUF); + if (i2c->rx_ring_buf == NULL) { + log_e("RX RingBuf create failed"); + ret = ESP_ERR_NO_MEM; + goto fail; + } +#endif + + i2c->tx_queue = xQueueCreate(tx_len, sizeof(uint8_t)); + if (i2c->tx_queue == NULL) { + log_e("TX queue create failed"); + ret = ESP_ERR_NO_MEM; + goto fail; + } + + i2c->event_queue = xQueueCreate(16, sizeof(i2c_slave_queue_event_t)); + if (i2c->event_queue == NULL) { + log_e("Event queue create failed"); + ret = ESP_ERR_NO_MEM; + goto fail; + } + + xTaskCreate(i2c_slave_task, "i2c_slave_task", 4096, i2c, 20, &i2c->task_handle); + if(i2c->task_handle == NULL){ + log_e("Event thread create failed"); + ret = ESP_ERR_NO_MEM; + goto fail; + } + + if (frequency == 0) { + frequency = 100000L; + } + frequency = (frequency * 5) / 4; + + if (i2c->num == 0) { + periph_module_enable(PERIPH_I2C0_MODULE); +#if SOC_I2C_NUM > 1 + } else { + periph_module_enable(PERIPH_I2C1_MODULE); +#endif + } + + i2c_ll_slave_init(i2c->dev); + i2c_ll_set_fifo_mode(i2c->dev, true); + i2c_ll_set_slave_addr(i2c->dev, slaveID, false); + i2c_ll_set_tout(i2c->dev, 32000); + i2c_slave_set_frequency(i2c, frequency); + + if (!i2c_slave_check_line_state(sda, scl)) { + log_e("bad pin state"); + ret = ESP_FAIL; + goto fail; + } + + i2c_slave_attach_gpio(i2c, sda, scl); + + if (i2c_ll_is_bus_busy(i2c->dev)) { + log_w("Bus busy, reinit"); + ret = ESP_FAIL; + goto fail; + } + + i2c_ll_disable_intr_mask(i2c->dev, I2C_LL_INTR_MASK); + i2c_ll_clr_intsts_mask(i2c->dev, I2C_LL_INTR_MASK); + i2c_ll_set_fifo_mode(i2c->dev, true); + + if (!i2c->intr_handle) { + uint32_t flags = ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_SHARED; + if(i2c->num == 0) { + ret = esp_intr_alloc(ETS_I2C_EXT0_INTR_SOURCE, flags, &i2c_slave_isr_handler, i2c, &i2c->intr_handle); +#if SOC_I2C_NUM > 1 + } else { + ret = esp_intr_alloc(ETS_I2C_EXT1_INTR_SOURCE, flags, &i2c_slave_isr_handler, i2c, &i2c->intr_handle); +#endif + } + + if (ret != ESP_OK) { + log_e("install interrupt handler Failed=%d", ret); + goto fail; + } + } + + i2c_ll_txfifo_rst(i2c->dev); + i2c_ll_rxfifo_rst(i2c->dev); + i2c_ll_slave_enable_rx_it(i2c->dev); + i2c_ll_set_stretch(i2c->dev, 0x3FF); + i2c_ll_update(i2c->dev); + I2C_SLAVE_MUTEX_UNLOCK(); + return ret; + +fail: + I2C_SLAVE_MUTEX_UNLOCK(); + i2c_slave_deinit(num); + return ret; +} + +size_t i2c_slave_write(uint8_t num, const uint8_t *buf, uint32_t len, uint32_t timeout_ms) { + if(num >= SOC_I2C_NUM){ + log_e("Invalid port num: %u", num); + return 0; + } + size_t to_queue = 0, to_fifo = 0; + i2c_slave_struct_t * i2c = &_i2c_bus_array[num]; + if(!i2c->tx_queue){ + return 0; + } + I2C_SLAVE_MUTEX_LOCK(); +#if CONFIG_IDF_TARGET_ESP32 + //make sure that tx is idle + uint64_t tout_at = esp_timer_get_time() + (timeout_ms * 1000); + while(i2c_ll_slave_addressed(i2c->dev) && i2c_ll_slave_rw(i2c->dev)) { + // ongoing MASTER READ + //wait up to timeout_ms for current transaction to finish + vTaskDelay(2); + if((uint64_t)esp_timer_get_time() >= tout_at){ + log_e("TX IDLE WAIT TIMEOUT!"); + I2C_SLAVE_MUTEX_UNLOCK(); + return 0; + } + } + i2c_ll_slave_disable_tx_it(i2c->dev); + if (i2c_ll_get_txfifo_len(i2c->dev) < SOC_I2C_FIFO_LEN) { + i2c_ll_txfifo_rst(i2c->dev); + } +#endif + to_fifo = i2c_ll_get_txfifo_len(i2c->dev); + if(len < to_fifo){ + to_fifo = len; + } + i2c_ll_write_txfifo(i2c->dev, (uint8_t*)buf, to_fifo); + buf += to_fifo; + len -= to_fifo; + //reset tx_queue + xQueueReset(i2c->tx_queue); + //write the rest of the bytes to the queue + if(len){ + to_queue = uxQueueSpacesAvailable(i2c->tx_queue); + if(len < to_queue){ + to_queue = len; + } + for (size_t i = 0; i < to_queue; i++) { + if (xQueueSend(i2c->tx_queue, &buf[i], timeout_ms / portTICK_RATE_MS) != pdTRUE) { + xQueueReset(i2c->tx_queue); + to_queue = 0; + break; + } + } + //no need to enable TX_EMPTY if tx_queue is empty + if(to_queue){ + i2c_ll_slave_enable_tx_it(i2c->dev); + } + } + I2C_SLAVE_MUTEX_UNLOCK(); + return to_queue + to_fifo; +} + +//===================================================================================================================== +//-------------------------------------- Private Functions ------------------------------------------------------------ +//===================================================================================================================== + +static bool i2c_slave_set_frequency(i2c_slave_struct_t * i2c, uint32_t clk_speed) +{ + if (i2c == NULL) { + log_e("no control buffer"); + return false; + } + if(clk_speed > 1100000UL){ + clk_speed = 1100000UL; + } + + // Adjust Fifo thresholds based on frequency + uint32_t a = (clk_speed / 50000L) + 2; + log_d("Fifo thresholds: rx_fifo_full = %d, tx_fifo_empty = %d", SOC_I2C_FIFO_LEN - a, a); + + i2c_clk_cal_t clk_cal; +#if SOC_I2C_SUPPORT_APB + i2c_ll_cal_bus_clk(APB_CLK_FREQ, clk_speed, &clk_cal); + i2c_ll_set_source_clk(i2c->dev, I2C_SCLK_APB); /*!< I2C source clock from APB, 80M*/ +#elif SOC_I2C_SUPPORT_XTAL + i2c_ll_cal_bus_clk(XTAL_CLK_FREQ, clk_speed, &clk_cal); + i2c_ll_set_source_clk(i2c->dev, I2C_SCLK_XTAL); /*!< I2C source clock from XTAL, 40M */ +#endif + i2c_ll_set_txfifo_empty_thr(i2c->dev, a); + i2c_ll_set_rxfifo_full_thr(i2c->dev, SOC_I2C_FIFO_LEN - a); + i2c_ll_set_bus_timing(i2c->dev, &clk_cal); + i2c_ll_set_filter(i2c->dev, 3); + return true; +} + +static void i2c_slave_delay_us(uint64_t us) +{ + uint64_t m = esp_timer_get_time(); + if (us) { + uint64_t e = (m + us); + if (m > e) { //overflow + while ((uint64_t)esp_timer_get_time() > e); + } + while ((uint64_t)esp_timer_get_time() < e); + } +} + +static void i2c_slave_gpio_mode(int8_t pin, gpio_mode_t mode) +{ + gpio_config_t conf = { + .pin_bit_mask = 1LL << pin, + .mode = mode, + .pull_up_en = GPIO_PULLUP_ENABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE + }; + gpio_config(&conf); +} + +static bool i2c_slave_check_line_state(int8_t sda, int8_t scl) +{ + if (sda < 0 || scl < 0) { + return false;//return false since there is nothing to do + } + // if the bus is not 'clear' try the cycling SCL until SDA goes High or 9 cycles + gpio_set_level(sda, 1); + gpio_set_level(scl, 1); + i2c_slave_gpio_mode(sda, GPIO_MODE_INPUT | GPIO_MODE_DEF_OD); + i2c_slave_gpio_mode(scl, GPIO_MODE_INPUT | GPIO_MODE_DEF_OD); + gpio_set_level(scl, 1); + + if (!gpio_get_level(sda) || !gpio_get_level(scl)) { // bus in busy state + log_w("invalid state sda(%d)=%d, scl(%d)=%d", sda, gpio_get_level(sda), scl, gpio_get_level(scl)); + for (uint8_t a=0; a<9; a++) { + i2c_slave_delay_us(5); + if (gpio_get_level(sda) && gpio_get_level(scl)) { // bus recovered + log_w("Recovered after %d Cycles",a); + gpio_set_level(sda,0); // start + i2c_slave_delay_us(5); + for (uint8_t a=0;a<9; a++) { + gpio_set_level(scl,1); + i2c_slave_delay_us(5); + gpio_set_level(scl,0); + i2c_slave_delay_us(5); + } + gpio_set_level(scl,1); + i2c_slave_delay_us(5); + gpio_set_level(sda,1); // stop + break; + } + gpio_set_level(scl, 0); + i2c_slave_delay_us(5); + gpio_set_level(scl, 1); + } + } + + if (!gpio_get_level(sda) || !gpio_get_level(scl)) { // bus in busy state + log_e("Bus Invalid State, Can't init sda=%d, scl=%d",gpio_get_level(sda),gpio_get_level(scl)); + return false; // bus is busy + } + return true; +} + +static bool i2c_slave_attach_gpio(i2c_slave_struct_t * i2c, int8_t sda, int8_t scl) +{ + if (i2c == NULL) { + log_e("no control block"); + return false; + } + + if ((sda < 0)||( scl < 0)) { + log_e("bad pins sda=%d, scl=%d",sda,scl); + return false; + } + + i2c->scl = scl; + gpio_set_level(scl, 1); + i2c_slave_gpio_mode(scl, GPIO_MODE_INPUT_OUTPUT_OD); + gpio_matrix_out(scl, I2C_SCL_IDX(i2c->num), false, false); + gpio_matrix_in(scl, I2C_SCL_IDX(i2c->num), false); + + i2c->sda = sda; + gpio_set_level(sda, 1); + i2c_slave_gpio_mode(sda, GPIO_MODE_INPUT_OUTPUT_OD); + gpio_matrix_out(sda, I2C_SDA_IDX(i2c->num), false, false); + gpio_matrix_in(sda, I2C_SDA_IDX(i2c->num), false); + + return true; +} + +static bool i2c_slave_detach_gpio(i2c_slave_struct_t * i2c) +{ + if (i2c == NULL) { + log_e("no control Block"); + return false; + } + if (i2c->scl >= 0) { + gpio_matrix_out(i2c->scl, 0x100, false, false); + gpio_matrix_in(0x30, I2C_SCL_IDX(i2c->num), false); + i2c_slave_gpio_mode(i2c->scl, GPIO_MODE_INPUT); + i2c->scl = -1; // un attached + } + if (i2c->sda >= 0) { + gpio_matrix_out(i2c->sda, 0x100, false, false); + gpio_matrix_in(0x30, I2C_SDA_IDX(i2c->num), false); + i2c_slave_gpio_mode(i2c->sda, GPIO_MODE_INPUT); + i2c->sda = -1; // un attached + } + return true; +} + +static bool i2c_slave_send_event(i2c_slave_struct_t * i2c, i2c_slave_queue_event_t* event) +{ + bool pxHigherPriorityTaskWoken = false; + if(i2c->event_queue) { + if(xQueueSendFromISR(i2c->event_queue, event, (BaseType_t * const)&pxHigherPriorityTaskWoken) != pdTRUE){ + log_e("event_queue_full"); + } + } + return pxHigherPriorityTaskWoken; +} + +static bool i2c_slave_handle_tx_fifo_empty(i2c_slave_struct_t * i2c) +{ + bool pxHigherPriorityTaskWoken = false; + uint32_t d = 0, moveCnt = i2c_ll_get_txfifo_len(i2c->dev); + while (moveCnt > 0) { // read tx queue until Fifo is full or queue is empty + if(xQueueReceiveFromISR(i2c->tx_queue, &d, (BaseType_t * const)&pxHigherPriorityTaskWoken) == pdTRUE){ + i2c_ll_write_txfifo(i2c->dev, (uint8_t*)&d, 1); + moveCnt--; + } else { + i2c_ll_slave_disable_tx_it(i2c->dev); + break; + } + } + return pxHigherPriorityTaskWoken; +} + +static bool i2c_slave_handle_rx_fifo_full(i2c_slave_struct_t * i2c, uint32_t len) +{ +#if I2C_SLAVE_USE_RX_QUEUE + uint32_t d = 0; +#else + uint8_t data[SOC_I2C_FIFO_LEN]; +#endif + bool pxHigherPriorityTaskWoken = false; +#if I2C_SLAVE_USE_RX_QUEUE + while (len > 0) { + i2c_ll_read_rxfifo(i2c->dev, (uint8_t*)&d, 1); + if(xQueueSendFromISR(i2c->rx_queue, &d, (BaseType_t * const)&pxHigherPriorityTaskWoken) != pdTRUE){ + log_e("rx_queue_full"); + } else { + i2c->rx_data_count++; + } + if (--len == 0) { + len = i2c_ll_get_rxfifo_cnt(i2c->dev); + } +#else + if(len){ + i2c_ll_read_rxfifo(i2c->dev, data, len); + if(xRingbufferSendFromISR(i2c->rx_ring_buf, (void*) data, len, (BaseType_t * const)&pxHigherPriorityTaskWoken) != pdTRUE){ + log_e("rx_ring_buf_full"); + } else { + i2c->rx_data_count += len; + } +#endif + } + return pxHigherPriorityTaskWoken; +} + +static void i2c_slave_isr_handler(void* arg) +{ + bool pxHigherPriorityTaskWoken = false; + i2c_slave_struct_t * i2c = (i2c_slave_struct_t *) arg; // recover data + + uint32_t activeInt = i2c_ll_get_intsts_mask(i2c->dev); + i2c_ll_clr_intsts_mask(i2c->dev, activeInt); + uint8_t rx_fifo_len = i2c_ll_get_rxfifo_cnt(i2c->dev); + uint8_t tx_fifo_len = SOC_I2C_FIFO_LEN - i2c_ll_get_txfifo_len(i2c->dev); + bool slave_rw = i2c_ll_slave_rw(i2c->dev); + + if(activeInt & I2C_RXFIFO_WM_INT_ENA){ // RX FiFo Full + pxHigherPriorityTaskWoken |= i2c_slave_handle_rx_fifo_full(i2c, rx_fifo_len); + i2c_ll_slave_enable_rx_it(i2c->dev);//is this necessary? + } + + if(activeInt & I2C_TRANS_COMPLETE_INT_ENA){ // STOP + if(rx_fifo_len){ //READ RX FIFO + pxHigherPriorityTaskWoken |= i2c_slave_handle_rx_fifo_full(i2c, rx_fifo_len); + } + if(!slave_rw || i2c->rx_data_count){ //WRITE or RepeatedStart + //SEND RX Event + i2c_slave_queue_event_t event; + event.event = I2C_SLAVE_EVT_RX; + event.stop = !slave_rw; + event.param = i2c->rx_data_count; + pxHigherPriorityTaskWoken |= i2c_slave_send_event(i2c, &event); + //Zero RX count + i2c->rx_data_count = 0; + } + if(slave_rw){ // READ +#if CONFIG_IDF_TARGET_ESP32 + //SEND TX Event + i2c_slave_queue_event_t event; + event.event = I2C_SLAVE_EVT_TX; + pxHigherPriorityTaskWoken |= i2c_slave_send_event(i2c, &event); +#else + //reset TX data + i2c_ll_txfifo_rst(i2c->dev); + uint8_t d; + while (xQueueReceiveFromISR(i2c->tx_queue, &d, (BaseType_t * const)&pxHigherPriorityTaskWoken) == pdTRUE) ;//flush partial write +#endif + } + } + +#ifndef CONFIG_IDF_TARGET_ESP32 + if(activeInt & I2C_SLAVE_STRETCH_INT_ENA){ // STRETCH + i2c_stretch_cause_t cause = i2c_ll_stretch_cause(i2c->dev); + if(cause == I2C_STRETCH_CAUSE_MASTER_READ){ + //on C3 RX data dissapears with repeated start, so we need to get it here + if(rx_fifo_len){ + pxHigherPriorityTaskWoken |= i2c_slave_handle_rx_fifo_full(i2c, rx_fifo_len); + } + //SEND TX Event + i2c_slave_queue_event_t event; + event.event = I2C_SLAVE_EVT_TX; + pxHigherPriorityTaskWoken |= i2c_slave_send_event(i2c, &event); + //will clear after execution + } else if(cause == I2C_STRETCH_CAUSE_TX_FIFO_EMPTY){ + pxHigherPriorityTaskWoken |= i2c_slave_handle_tx_fifo_empty(i2c); + i2c_ll_stretch_clr(i2c->dev); + } else if(cause == I2C_STRETCH_CAUSE_RX_FIFO_FULL){ + pxHigherPriorityTaskWoken |= i2c_slave_handle_rx_fifo_full(i2c, rx_fifo_len); + i2c_ll_stretch_clr(i2c->dev); + } + } +#endif + + if(activeInt & I2C_TXFIFO_WM_INT_ENA){ // TX FiFo Empty + pxHigherPriorityTaskWoken |= i2c_slave_handle_tx_fifo_empty(i2c); + } + + if(pxHigherPriorityTaskWoken){ + portYIELD_FROM_ISR(); + } +} + +static size_t i2c_slave_read_rx(i2c_slave_struct_t * i2c, uint8_t * data, size_t len){ + if(!len){ + return 0; + } +#if I2C_SLAVE_USE_RX_QUEUE + uint8_t d = 0; + BaseType_t res = pdTRUE; + for(size_t i=0; irx_queue, &data[i], 0); + } else { + res = xQueueReceive(i2c->rx_queue, &d, 0); + } + if (res != pdTRUE) { + log_e("Read Queue(%u) Failed", i); + len = i; + break; + } + } + return (data)?len:0; +#else + size_t dlen = 0, + to_read = len, + so_far = 0, + available = 0; + uint8_t * rx_data = NULL; + + vRingbufferGetInfo(i2c->rx_ring_buf, NULL, NULL, NULL, NULL, &available); + if(available < to_read){ + log_e("Less available than requested. %u < %u", available, len); + to_read = available; + } + + while(to_read){ + dlen = 0; + rx_data = (uint8_t *)xRingbufferReceiveUpTo(i2c->rx_ring_buf, &dlen, 0, to_read); + if(!rx_data){ + log_e("Receive %u Failed", to_read); + return so_far; + } + if(data){ + memcpy(data+so_far, rx_data, dlen); + } + vRingbufferReturnItem(i2c->rx_ring_buf, rx_data); + so_far+=dlen; + to_read-=dlen; + } + return (data)?so_far:0; +#endif +} + +static void i2c_slave_task(void *pv_args) +{ + i2c_slave_struct_t * i2c = (i2c_slave_struct_t *)pv_args; + i2c_slave_queue_event_t event; + size_t len = 0; + bool stop = false; + uint8_t * data = NULL; + for(;;){ + if(xQueueReceive(i2c->event_queue, &event, portMAX_DELAY) == pdTRUE){ + // Write + if(event.event == I2C_SLAVE_EVT_RX){ + len = event.param; + stop = event.stop; + data = (len > 0)?(uint8_t*)malloc(len):NULL; + + if(len && data == NULL){ + log_e("Malloc (%u) Failed", len); + } + len = i2c_slave_read_rx(i2c, data, len); + if(i2c->receive_callback){ + i2c->receive_callback(i2c->num, data, len, stop, i2c->arg); + } + free(data); + + // Read + } else if(event.event == I2C_SLAVE_EVT_TX){ + if(i2c->request_callback){ + i2c->request_callback(i2c->num, i2c->arg); + } + i2c_ll_stretch_clr(i2c->dev); + } + } + } + vTaskDelete(NULL); +} diff --git a/cores/esp32/esp32-hal-i2c-slave.h b/cores/esp32/esp32-hal-i2c-slave.h new file mode 100755 index 00000000000..c63a2b0c681 --- /dev/null +++ b/cores/esp32/esp32-hal-i2c-slave.h @@ -0,0 +1,35 @@ +// Copyright 2015-2021 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "stdint.h" +#include "stddef.h" +#include "esp_err.h" + +typedef void (*i2c_slave_request_cb_t) (uint8_t num, void * arg); +typedef void (*i2c_slave_receive_cb_t) (uint8_t num, uint8_t * data, size_t len, bool stop, void * arg); +esp_err_t i2c_slave_attach_callbacks(uint8_t num, i2c_slave_request_cb_t request_callback, i2c_slave_receive_cb_t receive_callback, void * arg); + +esp_err_t i2c_slave_init(uint8_t num, int sda, int scl, uint16_t slaveID, uint32_t frequency, size_t rx_len, size_t tx_len); +esp_err_t i2c_slave_deinit(uint8_t num); +size_t i2c_slave_write(uint8_t num, const uint8_t *buf, uint32_t len, uint32_t timeout_ms); + +#ifdef __cplusplus +} +#endif diff --git a/libraries/Wire/examples/WireMaster/WireMaster.ino b/libraries/Wire/examples/WireMaster/WireMaster.ino new file mode 100644 index 00000000000..a04998304b3 --- /dev/null +++ b/libraries/Wire/examples/WireMaster/WireMaster.ino @@ -0,0 +1,30 @@ +#include "Wire.h" + +#define I2C_DEV_ADDR 0x55 + +uint32_t i = 0; + +void setup() { + Serial.begin(115200); + Serial.setDebugOutput(true); + Wire.begin(); +} + +void loop() { + delay(5000); + + //Write message to the slave + Wire.beginTransmission(I2C_DEV_ADDR); + Wire.printf("Hello World! %u", i++); + uint8_t error = Wire.endTransmission(true); + Serial.printf("endTransmission: %u\n", error); + + //Read 16 bytes from the slave + error = Wire.requestFrom(I2C_DEV_ADDR, 16); + Serial.printf("requestFrom: %u\n", error); + if(error){ + uint8_t temp[error]; + Wire.readBytes(temp, error); + log_print_buf(temp, error); + } +} diff --git a/libraries/Wire/examples/WireScan/WireScan.ino b/libraries/Wire/examples/WireScan/WireScan.ino new file mode 100644 index 00000000000..4f2750b1db8 --- /dev/null +++ b/libraries/Wire/examples/WireScan/WireScan.ino @@ -0,0 +1,28 @@ +#include "Wire.h" + +void setup() { + Serial.begin(115200); + Wire.begin(); +} + +void loop() { + byte error, address; + int nDevices = 0; + + delay(5000); + + Serial.println("Scanning for I2C devices ..."); + for(address = 0x01; address < 0x7f; address++){ + Wire.beginTransmission(address); + error = Wire.endTransmission(); + if (error == 0){ + Serial.printf("I2C device found at address 0x%02X\n", address); + nDevices++; + } else if(error != 2){ + Serial.printf("Error %d at address 0x%02X\n", error, address); + } + } + if (nDevices == 0){ + Serial.println("No I2C devices found"); + } +} diff --git a/libraries/Wire/examples/WireSlave/WireSlave.ino b/libraries/Wire/examples/WireSlave/WireSlave.ino new file mode 100644 index 00000000000..ea5c1995b71 --- /dev/null +++ b/libraries/Wire/examples/WireSlave/WireSlave.ino @@ -0,0 +1,37 @@ +#include "Wire.h" + +#define I2C_DEV_ADDR 0x55 + +uint32_t i = 0; + +void onRequest(){ + Wire.print(i++); + Wire.print(" Packets."); + Serial.println("onRequest"); +} + +void onReceive(int len){ + Serial.printf("onReceive[%d]: ", len); + while(Wire.available()){ + Serial.write(Wire.read()); + } + Serial.println(); +} + +void setup() { + Serial.begin(115200); + Serial.setDebugOutput(true); + Wire.onReceive(onReceive); + Wire.onRequest(onRequest); + Wire.begin((uint8_t)I2C_DEV_ADDR); + +#if CONFIG_IDF_TARGET_ESP32 + char message[64]; + snprintf(message, 64, "%u Packets.", i++); + Wire.slaveWrite((uint8_t *)message, strlen(message)); +#endif +} + +void loop() { + +} diff --git a/libraries/Wire/src/Wire.cpp b/libraries/Wire/src/Wire.cpp index 7000e7013bb..06e19d12c9c 100644 --- a/libraries/Wire/src/Wire.cpp +++ b/libraries/Wire/src/Wire.cpp @@ -30,6 +30,7 @@ extern "C" { } #include "esp32-hal-i2c.h" +#include "esp32-hal-i2c-slave.h" #include "Wire.h" #include "Arduino.h" @@ -47,6 +48,9 @@ TwoWire::TwoWire(uint8_t bus_num) ,nonStopTask(NULL) ,lock(NULL) #endif + ,is_slave(false) + ,user_onRequest(NULL) + ,user_onReceive(NULL) {} TwoWire::~TwoWire() @@ -402,5 +406,102 @@ uint8_t TwoWire::endTransmission(void) return endTransmission(true); } +bool TwoWire::begin(uint8_t addr, int sdaPin, int sclPin, uint32_t frequency) +{ + if(!frequency){ + frequency = 100000; + } else if(frequency > 1000000){ + frequency = 1000000; + } + + if(sdaPin < 0) { // default param passed + if(num == 0) { + if(sda==-1) { + sdaPin = SDA; //use Default Pin + } else { + sdaPin = sda; // reuse prior pin + } + } else { + if(sda==-1) { + log_e("no Default SDA Pin for Second Peripheral"); + return false; //no Default pin for Second Peripheral + } else { + sdaPin = sda; // reuse prior pin + } + } + } + + if(sclPin < 0) { // default param passed + if(num == 0) { + if(scl == -1) { + sclPin = SCL; // use Default pin + } else { + sclPin = scl; // reuse prior pin + } + } else { + if(scl == -1) { + log_e("no Default SCL Pin for Second Peripheral"); + return false; //no Default pin for Second Peripheral + } else { + sclPin = scl; // reuse prior pin + } + } + } + + sda = sdaPin; + scl = sclPin; + i2c_slave_attach_callbacks(num, onRequestService, onReceiveService, this); + if(i2c_slave_init(num, sda, scl, addr, frequency, I2C_BUFFER_LENGTH, I2C_BUFFER_LENGTH) != ESP_OK){ + Serial.println("INIT ERROR"); + return false; + } + is_slave = true; + return true; +} + +size_t TwoWire::slaveWrite(const uint8_t * buffer, size_t len) +{ + return i2c_slave_write(num, buffer, len, _timeOutMillis); +} + +void TwoWire::onReceiveService(uint8_t num, uint8_t* inBytes, size_t numBytes, bool stop, void * arg) +{ + TwoWire * wire = (TwoWire*)arg; + if(!wire->user_onReceive){ + return; + } + for(uint8_t i = 0; i < numBytes; ++i){ + wire->rxBuffer[i] = inBytes[i]; + } + wire->rxIndex = 0; + wire->rxLength = numBytes; + wire->user_onReceive(numBytes); +} + +void TwoWire::onRequestService(uint8_t num, void * arg) +{ + TwoWire * wire = (TwoWire*)arg; + if(!wire->user_onRequest){ + return; + } + wire->txLength = 0; + wire->user_onRequest(); + if(wire->txLength){ + wire->slaveWrite((uint8_t*)wire->txBuffer, wire->txLength); + } +} + +void TwoWire::onReceive( void (*function)(int) ) +{ + user_onReceive = function; +} + +// sets function called on slave read +void TwoWire::onRequest( void (*function)(void) ) +{ + user_onRequest = function; +} + + TwoWire Wire = TwoWire(0); TwoWire Wire1 = TwoWire(1); diff --git a/libraries/Wire/src/Wire.h b/libraries/Wire/src/Wire.h index 08e52f5d011..f7d6a0f7b09 100644 --- a/libraries/Wire/src/Wire.h +++ b/libraries/Wire/src/Wire.h @@ -61,6 +61,12 @@ class TwoWire: public Stream TaskHandle_t nonStopTask; SemaphoreHandle_t lock; #endif +private: + bool is_slave; + void (*user_onRequest)(void); + void (*user_onReceive)(int); + static void onRequestService(uint8_t, void *); + static void onReceiveService(uint8_t, uint8_t*, size_t, bool, void *); public: TwoWire(uint8_t bus_num); @@ -70,6 +76,7 @@ class TwoWire: public Stream bool setPins(int sda, int scl); bool begin(int sda=-1, int scl=-1, uint32_t frequency=0); // returns true, if successful init of i2c bus + bool begin(uint8_t slaveAddr, int sda=-1, int scl=-1, uint32_t frequency=0); bool end(); void setTimeOut(uint16_t timeOutMillis); // default timeout of i2c transactions is 50ms @@ -123,6 +130,7 @@ class TwoWire: public Stream void onReceive( void (*)(int) ); void onRequest( void (*)(void) ); + size_t slaveWrite(const uint8_t *, size_t); }; extern TwoWire Wire; From 335cedf4f7f4168e8c10abe220892d37f4317b31 Mon Sep 17 00:00:00 2001 From: me-no-dev Date: Sat, 9 Oct 2021 14:37:10 +0300 Subject: [PATCH 2/3] Update CMakeLists.txt --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 56dc43fb186..d70ac76938d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,7 @@ set(CORE_SRCS cores/esp32/esp32-hal-dac.c cores/esp32/esp32-hal-gpio.c cores/esp32/esp32-hal-i2c.c + cores/esp32/esp32-hal-i2c-slave.c cores/esp32/esp32-hal-ledc.c cores/esp32/esp32-hal-matrix.c cores/esp32/esp32-hal-misc.c From b145e6597589272956a75f9d1002e0ca2cbd0f30 Mon Sep 17 00:00:00 2001 From: me-no-dev Date: Mon, 11 Oct 2021 14:46:31 +0300 Subject: [PATCH 3/3] API Optimizations - Support Wire::end() for Slave - Prevent Master operations when in Slave mode --- cores/esp32/esp32-hal-i2c-slave.c | 133 ++++++++++-------- cores/esp32/esp32-hal-i2c-slave.h | 8 +- libraries/Wire/src/Wire.cpp | 219 +++++++++++++++++------------- libraries/Wire/src/Wire.h | 1 + 4 files changed, 206 insertions(+), 155 deletions(-) diff --git a/cores/esp32/esp32-hal-i2c-slave.c b/cores/esp32/esp32-hal-i2c-slave.c index 96750c5a960..458b74f2cb0 100755 --- a/cores/esp32/esp32-hal-i2c-slave.c +++ b/cores/esp32/esp32-hal-i2c-slave.c @@ -181,6 +181,7 @@ static inline bool i2c_ll_slave_rw(i2c_dev_t *hw)//not exposed by hal_ll } //-------------------------------------- PRIVATE (Function Prototypes) ------------------------------------------------ +static void i2c_slave_free_resources(i2c_slave_struct_t * i2c); static void i2c_slave_delay_us(uint64_t us); static void i2c_slave_gpio_mode(int8_t pin, gpio_mode_t mode); static bool i2c_slave_check_line_state(int8_t sda, int8_t scl); @@ -199,7 +200,7 @@ static void i2c_slave_task(void *pv_args); //-------------------------------------- Public Functions ------------------------------------------------------------- //===================================================================================================================== -esp_err_t i2c_slave_attach_callbacks(uint8_t num, i2c_slave_request_cb_t request_callback, i2c_slave_receive_cb_t receive_callback, void * arg){ +esp_err_t i2cSlaveAttachCallbacks(uint8_t num, i2c_slave_request_cb_t request_callback, i2c_slave_receive_cb_t receive_callback, void * arg){ if(num >= SOC_I2C_NUM){ log_e("Invalid port num: %u", num); return ESP_ERR_INVALID_ARG; @@ -213,57 +214,7 @@ esp_err_t i2c_slave_attach_callbacks(uint8_t num, i2c_slave_request_cb_t request return ESP_OK; } -esp_err_t i2c_slave_deinit(uint8_t num){ - if(num >= SOC_I2C_NUM){ - log_e("Invalid port num: %u", num); - return ESP_ERR_INVALID_ARG; - } - - i2c_slave_struct_t * i2c = &_i2c_bus_array[num]; - I2C_SLAVE_MUTEX_LOCK(); - i2c_slave_detach_gpio(i2c); - i2c_ll_set_slave_addr(i2c->dev, 0, false); - i2c_ll_disable_intr_mask(i2c->dev, I2C_LL_INTR_MASK); - i2c_ll_clr_intsts_mask(i2c->dev, I2C_LL_INTR_MASK); - - if (i2c->intr_handle) { - esp_intr_free(i2c->intr_handle); - i2c->intr_handle = NULL; - } - - if(i2c->task_handle){ - vTaskDelete(i2c->task_handle); - i2c->task_handle = NULL; - } - -#if I2C_SLAVE_USE_RX_QUEUE - if (i2c->rx_queue) { - vQueueDelete(i2c->rx_queue); - i2c->rx_queue = NULL; - } -#else - if (i2c->rx_ring_buf) { - vRingbufferDelete(i2c->rx_ring_buf); - i2c->rx_ring_buf = NULL; - } -#endif - - if (i2c->tx_queue) { - vQueueDelete(i2c->tx_queue); - i2c->tx_queue = NULL; - } - - if (i2c->event_queue) { - vQueueDelete(i2c->event_queue); - i2c->event_queue = NULL; - } - - i2c->rx_data_count = 0; - I2C_SLAVE_MUTEX_UNLOCK(); - return ESP_OK; -} - -esp_err_t i2c_slave_init(uint8_t num, int sda, int scl, uint16_t slaveID, uint32_t frequency, size_t rx_len, size_t tx_len) { +esp_err_t i2cSlaveInit(uint8_t num, int sda, int scl, uint16_t slaveID, uint32_t frequency, size_t rx_len, size_t tx_len) { if(num >= SOC_I2C_NUM){ log_e("Invalid port num: %u", num); return ESP_ERR_INVALID_ARG; @@ -274,6 +225,12 @@ esp_err_t i2c_slave_init(uint8_t num, int sda, int scl, uint16_t slaveID, uint32 return ESP_ERR_INVALID_ARG; } + if(!frequency){ + frequency = 100000; + } else if(frequency > 1000000){ + frequency = 1000000; + } + log_i("Initialising I2C Slave: sda=%d scl=%d freq=%d, addr=0x%x", sda, scl, frequency, slaveID); i2c_slave_struct_t * i2c = &_i2c_bus_array[num]; @@ -288,9 +245,9 @@ esp_err_t i2c_slave_init(uint8_t num, int sda, int scl, uint16_t slaveID, uint32 } } #endif - i2c_slave_deinit(num); I2C_SLAVE_MUTEX_LOCK(); + i2c_slave_free_resources(i2c); #if I2C_SLAVE_USE_RX_QUEUE i2c->rx_queue = xQueueCreate(rx_len, sizeof(uint8_t)); @@ -391,18 +348,39 @@ esp_err_t i2c_slave_init(uint8_t num, int sda, int scl, uint16_t slaveID, uint32 return ret; fail: + i2c_slave_free_resources(i2c); I2C_SLAVE_MUTEX_UNLOCK(); - i2c_slave_deinit(num); return ret; } -size_t i2c_slave_write(uint8_t num, const uint8_t *buf, uint32_t len, uint32_t timeout_ms) { +esp_err_t i2cSlaveDeinit(uint8_t num){ + if(num >= SOC_I2C_NUM){ + log_e("Invalid port num: %u", num); + return ESP_ERR_INVALID_ARG; + } + + i2c_slave_struct_t * i2c = &_i2c_bus_array[num]; + if(!i2c->lock){ + log_e("Lock is not initialized! Did you call i2c_slave_init()?"); + return ESP_ERR_NO_MEM; + } + I2C_SLAVE_MUTEX_LOCK(); + i2c_slave_free_resources(i2c); + I2C_SLAVE_MUTEX_UNLOCK(); + return ESP_OK; +} + +size_t i2cSlaveWrite(uint8_t num, const uint8_t *buf, uint32_t len, uint32_t timeout_ms) { if(num >= SOC_I2C_NUM){ log_e("Invalid port num: %u", num); return 0; } size_t to_queue = 0, to_fifo = 0; i2c_slave_struct_t * i2c = &_i2c_bus_array[num]; + if(!i2c->lock){ + log_e("Lock is not initialized! Did you call i2c_slave_init()?"); + return ESP_ERR_NO_MEM; + } if(!i2c->tx_queue){ return 0; } @@ -460,6 +438,47 @@ size_t i2c_slave_write(uint8_t num, const uint8_t *buf, uint32_t len, uint32_t t //-------------------------------------- Private Functions ------------------------------------------------------------ //===================================================================================================================== +static void i2c_slave_free_resources(i2c_slave_struct_t * i2c){ + i2c_slave_detach_gpio(i2c); + i2c_ll_set_slave_addr(i2c->dev, 0, false); + i2c_ll_disable_intr_mask(i2c->dev, I2C_LL_INTR_MASK); + i2c_ll_clr_intsts_mask(i2c->dev, I2C_LL_INTR_MASK); + + if (i2c->intr_handle) { + esp_intr_free(i2c->intr_handle); + i2c->intr_handle = NULL; + } + + if(i2c->task_handle){ + vTaskDelete(i2c->task_handle); + i2c->task_handle = NULL; + } + +#if I2C_SLAVE_USE_RX_QUEUE + if (i2c->rx_queue) { + vQueueDelete(i2c->rx_queue); + i2c->rx_queue = NULL; + } +#else + if (i2c->rx_ring_buf) { + vRingbufferDelete(i2c->rx_ring_buf); + i2c->rx_ring_buf = NULL; + } +#endif + + if (i2c->tx_queue) { + vQueueDelete(i2c->tx_queue); + i2c->tx_queue = NULL; + } + + if (i2c->event_queue) { + vQueueDelete(i2c->event_queue); + i2c->event_queue = NULL; + } + + i2c->rx_data_count = 0; +} + static bool i2c_slave_set_frequency(i2c_slave_struct_t * i2c, uint32_t clk_speed) { if (i2c == NULL) { @@ -610,7 +629,7 @@ static bool i2c_slave_send_event(i2c_slave_struct_t * i2c, i2c_slave_queue_event bool pxHigherPriorityTaskWoken = false; if(i2c->event_queue) { if(xQueueSendFromISR(i2c->event_queue, event, (BaseType_t * const)&pxHigherPriorityTaskWoken) != pdTRUE){ - log_e("event_queue_full"); + //log_e("event_queue_full"); } } return pxHigherPriorityTaskWoken; @@ -684,7 +703,7 @@ static void i2c_slave_isr_handler(void* arg) if(rx_fifo_len){ //READ RX FIFO pxHigherPriorityTaskWoken |= i2c_slave_handle_rx_fifo_full(i2c, rx_fifo_len); } - if(!slave_rw || i2c->rx_data_count){ //WRITE or RepeatedStart + if(i2c->rx_data_count){ //WRITE or RepeatedStart //SEND RX Event i2c_slave_queue_event_t event; event.event = I2C_SLAVE_EVT_RX; diff --git a/cores/esp32/esp32-hal-i2c-slave.h b/cores/esp32/esp32-hal-i2c-slave.h index c63a2b0c681..ceed8b10da1 100755 --- a/cores/esp32/esp32-hal-i2c-slave.h +++ b/cores/esp32/esp32-hal-i2c-slave.h @@ -24,11 +24,11 @@ extern "C" { typedef void (*i2c_slave_request_cb_t) (uint8_t num, void * arg); typedef void (*i2c_slave_receive_cb_t) (uint8_t num, uint8_t * data, size_t len, bool stop, void * arg); -esp_err_t i2c_slave_attach_callbacks(uint8_t num, i2c_slave_request_cb_t request_callback, i2c_slave_receive_cb_t receive_callback, void * arg); +esp_err_t i2cSlaveAttachCallbacks(uint8_t num, i2c_slave_request_cb_t request_callback, i2c_slave_receive_cb_t receive_callback, void * arg); -esp_err_t i2c_slave_init(uint8_t num, int sda, int scl, uint16_t slaveID, uint32_t frequency, size_t rx_len, size_t tx_len); -esp_err_t i2c_slave_deinit(uint8_t num); -size_t i2c_slave_write(uint8_t num, const uint8_t *buf, uint32_t len, uint32_t timeout_ms); +esp_err_t i2cSlaveInit(uint8_t num, int sda, int scl, uint16_t slaveID, uint32_t frequency, size_t rx_len, size_t tx_len); +esp_err_t i2cSlaveDeinit(uint8_t num); +size_t i2cSlaveWrite(uint8_t num, const uint8_t *buf, uint32_t len, uint32_t timeout_ms); #ifdef __cplusplus } diff --git a/libraries/Wire/src/Wire.cpp b/libraries/Wire/src/Wire.cpp index 06e19d12c9c..1c8775f0871 100644 --- a/libraries/Wire/src/Wire.cpp +++ b/libraries/Wire/src/Wire.cpp @@ -63,6 +63,47 @@ TwoWire::~TwoWire() #endif } +bool TwoWire::initPins(int sdaPin, int sclPin) +{ + if(sdaPin < 0) { // default param passed + if(num == 0) { + if(sda==-1) { + sdaPin = SDA; //use Default Pin + } else { + sdaPin = sda; // reuse prior pin + } + } else { + if(sda==-1) { + log_e("no Default SDA Pin for Second Peripheral"); + return false; //no Default pin for Second Peripheral + } else { + sdaPin = sda; // reuse prior pin + } + } + } + + if(sclPin < 0) { // default param passed + if(num == 0) { + if(scl == -1) { + sclPin = SCL; // use Default pin + } else { + sclPin = scl; // reuse prior pin + } + } else { + if(scl == -1) { + log_e("no Default SCL Pin for Second Peripheral"); + return false; //no Default pin for Second Peripheral + } else { + sclPin = scl; // reuse prior pin + } + } + } + + sda = sdaPin; + scl = sclPin; + return true; +} + bool TwoWire::setPins(int sdaPin, int sclPin) { #if !CONFIG_DISABLE_HAL_LOCKS @@ -80,8 +121,7 @@ bool TwoWire::setPins(int sdaPin, int sclPin) } #endif if(!i2cIsInit(num)){ - sda = sdaPin; - scl = sclPin; + initPins(sdaPin, sclPin); } else { log_e("bus already initialized. change pins only when not."); } @@ -92,10 +132,10 @@ bool TwoWire::setPins(int sdaPin, int sclPin) return !i2cIsInit(num); } -bool TwoWire::begin(int sdaPin, int sclPin, uint32_t frequency) +// Slave Begin +bool TwoWire::begin(uint8_t addr, int sdaPin, int sclPin, uint32_t frequency) { bool started = false; - esp_err_t err = ESP_OK; #if !CONFIG_DISABLE_HAL_LOCKS if(lock == NULL){ lock = xSemaphoreCreateMutex(); @@ -110,46 +150,64 @@ bool TwoWire::begin(int sdaPin, int sclPin, uint32_t frequency) return false; } #endif - if(i2cIsInit(num)){ + if(is_slave){ + log_w("Bus already started in Slave Mode."); started = true; goto end; } - if(sdaPin < 0) { // default param passed - if(num == 0) { - if(sda==-1) { - sdaPin = SDA; //use Default Pin - } else { - sdaPin = sda; // reuse prior pin - } - } else { - if(sda==-1) { - log_e("no Default SDA Pin for Second Peripheral"); - goto end; //no Default pin for Second Peripheral - } else { - sdaPin = sda; // reuse prior pin - } - } + if(i2cIsInit(num)){ + log_e("Bus already started in Master Mode."); + goto end; + } + if(!initPins(sdaPin, sclPin)){ + goto end; + } + i2cSlaveAttachCallbacks(num, onRequestService, onReceiveService, this); + if(i2cSlaveInit(num, sda, scl, addr, frequency, I2C_BUFFER_LENGTH, I2C_BUFFER_LENGTH) != ESP_OK){ + log_e("Slave Init ERROR"); + goto end; } + is_slave = true; + started = true; +end: +#if !CONFIG_DISABLE_HAL_LOCKS + //release lock + xSemaphoreGive(lock); +#endif + return started; +} - if(sclPin < 0) { // default param passed - if(num == 0) { - if(scl == -1) { - sclPin = SCL; // use Default pin - } else { - sclPin = scl; // reuse prior pin - } - } else { - if(scl == -1) { - log_e("no Default SCL Pin for Second Peripheral"); - goto end; //no Default pin for Second Peripheral - } else { - sclPin = scl; // reuse prior pin - } +// Master Begin +bool TwoWire::begin(int sdaPin, int sclPin, uint32_t frequency) +{ + bool started = false; + esp_err_t err = ESP_OK; +#if !CONFIG_DISABLE_HAL_LOCKS + if(lock == NULL){ + lock = xSemaphoreCreateMutex(); + if(lock == NULL){ + log_e("xSemaphoreCreateMutex failed"); + return false; } } - - sda = sdaPin; - scl = sclPin; + //acquire lock + if(xSemaphoreTake(lock, portMAX_DELAY) != pdTRUE){ + log_e("could not acquire lock"); + return false; + } +#endif + if(is_slave){ + log_e("Bus already started in Slave Mode."); + goto end; + } + if(i2cIsInit(num)){ + log_w("Bus already started in Master Mode."); + started = true; + goto end; + } + if(!initPins(sdaPin, sclPin)){ + goto end; + } err = i2cInit(num, sda, scl, frequency); started = (err == ESP_OK); @@ -173,7 +231,12 @@ bool TwoWire::end() return false; } #endif - if(i2cIsInit(num)){ + if(is_slave){ + err = i2cSlaveDeinit(num); + if(err == ESP_OK){ + is_slave = false; + } + } else if(i2cIsInit(num)){ err = i2cDeinit(num); } #if !CONFIG_DISABLE_HAL_LOCKS @@ -193,7 +256,11 @@ uint32_t TwoWire::getClock() log_e("could not acquire lock"); } else { #endif - i2cGetClock(num, &frequency); + if(is_slave){ + log_e("Bus is in Slave Mode"); + } else { + i2cGetClock(num, &frequency); + } #if !CONFIG_DISABLE_HAL_LOCKS //release lock xSemaphoreGive(lock); @@ -212,7 +279,12 @@ bool TwoWire::setClock(uint32_t frequency) return false; } #endif - err = i2cSetClock(num, frequency); + if(is_slave){ + log_e("Bus is in Slave Mode"); + err = ESP_FAIL; + } else { + err = i2cSetClock(num, frequency); + } #if !CONFIG_DISABLE_HAL_LOCKS //release lock xSemaphoreGive(lock); @@ -232,6 +304,10 @@ uint16_t TwoWire::getTimeOut() void TwoWire::beginTransmission(uint16_t address) { + if(is_slave){ + log_e("Bus is in Slave Mode"); + return; + } #if !CONFIG_DISABLE_HAL_LOCKS if(nonStop && nonStopTask == xTaskGetCurrentTaskHandle()){ log_e("Unfinished Repeated Start transaction! Expected requestFrom, not beginTransmission! Clearing..."); @@ -251,6 +327,10 @@ void TwoWire::beginTransmission(uint16_t address) uint8_t TwoWire::endTransmission(bool sendStop) { + if(is_slave){ + log_e("Bus is in Slave Mode"); + return 4; + } esp_err_t err = ESP_OK; if(sendStop){ err = i2cWrite(num, txAddress, txBuffer, txLength, _timeOutMillis); @@ -276,6 +356,10 @@ uint8_t TwoWire::endTransmission(bool sendStop) uint8_t TwoWire::requestFrom(uint16_t address, uint8_t size, bool sendStop) { + if(is_slave){ + log_e("Bus is in Slave Mode"); + return 0; + } esp_err_t err = ESP_OK; if(nonStop #if !CONFIG_DISABLE_HAL_LOCKS @@ -406,62 +490,9 @@ uint8_t TwoWire::endTransmission(void) return endTransmission(true); } -bool TwoWire::begin(uint8_t addr, int sdaPin, int sclPin, uint32_t frequency) -{ - if(!frequency){ - frequency = 100000; - } else if(frequency > 1000000){ - frequency = 1000000; - } - - if(sdaPin < 0) { // default param passed - if(num == 0) { - if(sda==-1) { - sdaPin = SDA; //use Default Pin - } else { - sdaPin = sda; // reuse prior pin - } - } else { - if(sda==-1) { - log_e("no Default SDA Pin for Second Peripheral"); - return false; //no Default pin for Second Peripheral - } else { - sdaPin = sda; // reuse prior pin - } - } - } - - if(sclPin < 0) { // default param passed - if(num == 0) { - if(scl == -1) { - sclPin = SCL; // use Default pin - } else { - sclPin = scl; // reuse prior pin - } - } else { - if(scl == -1) { - log_e("no Default SCL Pin for Second Peripheral"); - return false; //no Default pin for Second Peripheral - } else { - sclPin = scl; // reuse prior pin - } - } - } - - sda = sdaPin; - scl = sclPin; - i2c_slave_attach_callbacks(num, onRequestService, onReceiveService, this); - if(i2c_slave_init(num, sda, scl, addr, frequency, I2C_BUFFER_LENGTH, I2C_BUFFER_LENGTH) != ESP_OK){ - Serial.println("INIT ERROR"); - return false; - } - is_slave = true; - return true; -} - size_t TwoWire::slaveWrite(const uint8_t * buffer, size_t len) { - return i2c_slave_write(num, buffer, len, _timeOutMillis); + return i2cSlaveWrite(num, buffer, len, _timeOutMillis); } void TwoWire::onReceiveService(uint8_t num, uint8_t* inBytes, size_t numBytes, bool stop, void * arg) diff --git a/libraries/Wire/src/Wire.h b/libraries/Wire/src/Wire.h index f7d6a0f7b09..892846b3df9 100644 --- a/libraries/Wire/src/Wire.h +++ b/libraries/Wire/src/Wire.h @@ -67,6 +67,7 @@ class TwoWire: public Stream void (*user_onReceive)(int); static void onRequestService(uint8_t, void *); static void onReceiveService(uint8_t, uint8_t*, size_t, bool, void *); + bool initPins(int sdaPin, int sclPin); public: TwoWire(uint8_t bus_num);