ulp-riscv-i2c: Updated ULP RISC-V I2C driver to abort when met with errors

The current ULP RISC-V RTC I2C driver got stuck in an infinite loop if
there is a I2C transaction error. This commit amends the driver flow to
abort the read/write operation if met with errors. It also adds a loop
timeout to avoid getting stuck in an infinite loop.The commit also
updates the default bus timing parameters for RTC I2C to be faster.
This commit also adds documentation help to guide users when they meet
with issues while working with the RTC I2C driver on the ULP RISC-V coprocessor.
This commit is contained in:
Sudeep Mohanty 2023-03-27 16:22:43 +02:00
parent 6e1d60ea48
commit 88e4c06028
7 changed files with 259 additions and 81 deletions

View File

@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
@ -15,40 +15,67 @@ extern "C" {
#include "hal/gpio_types.h" #include "hal/gpio_types.h"
#include "esp_err.h" #include "esp_err.h"
/**
* @brief ULP RISC-V RTC I2C pin config
*/
typedef struct { typedef struct {
uint32_t sda_io_num; // GPIO pin for SDA signal. Only GPIO#1 or GPIO#3 can be used as the SDA pin. uint32_t sda_io_num; /*!< GPIO pin for SDA signal. Only GPIO#1 or GPIO#3 can be used as the SDA pin. */
uint32_t scl_io_num; // GPIO pin for SCL signal. Only GPIO#0 or GPIO#2 can be used as the SCL pin. uint32_t scl_io_num; /*!< GPIO pin for SCL signal. Only GPIO#0 or GPIO#2 can be used as the SCL pin. */
bool sda_pullup_en; // SDA line enable internal pullup. Can be configured if external pullup is not used. bool sda_pullup_en; /*!< SDA line enable internal pullup. Can be configured if external pullup is not used. */
bool scl_pullup_en; // SCL line enable internal pullup. Can be configured if external pullup is not used. bool scl_pullup_en; /*!< SCL line enable internal pullup. Can be configured if external pullup is not used. */
} ulp_riscv_i2c_pin_cfg_t; } ulp_riscv_i2c_pin_cfg_t;
/**
* @brief ULP RISC-V RTC I2C timing config
*/
typedef struct { typedef struct {
uint32_t scl_low_period; // SCL low period float scl_low_period; /*!< SCL low period in micro seconds */
uint32_t scl_high_period; // SCL high period float scl_high_period; /*!< SCL high period in micro seconds */
uint32_t sda_duty_period; // Period between the SDA switch and the falling edge of SCL float sda_duty_period; /*!< Period between the SDA switch and the falling edge of SCL in micro seconds */
uint32_t scl_start_period; // Waiting time after the START condition float scl_start_period; /*!< Waiting time after the START condition in micro seconds */
uint32_t scl_stop_period; // Waiting time before the END condition float scl_stop_period; /*!< Waiting time before the END condition in micro seconds */
uint32_t i2c_trans_timeout; // I2C transaction timeout float i2c_trans_timeout; /*!< I2C transaction timeout in micro seconds */
} ulp_riscv_i2c_timing_cfg_t; } ulp_riscv_i2c_timing_cfg_t;
/**
* @brief ULP RISC-V RTC I2C init parameters
*/
typedef struct { typedef struct {
ulp_riscv_i2c_pin_cfg_t i2c_pin_cfg; // RTC I2C pin configuration ulp_riscv_i2c_pin_cfg_t i2c_pin_cfg; /*!< RTC I2C pin configuration */
ulp_riscv_i2c_timing_cfg_t i2c_timing_cfg; // RTC I2C timing configuration ulp_riscv_i2c_timing_cfg_t i2c_timing_cfg; /*!< RTC I2C timing configuration */
} ulp_riscv_i2c_cfg_t; } ulp_riscv_i2c_cfg_t;
/* Nominal default GPIO settings and timing parametes */ /* Default RTC I2C GPIO settings */
#define ULP_RISCV_I2C_DEFAULT_CONFIG() \ #define ULP_RISCV_I2C_DEFAULT_GPIO_CONFIG() \
{ \
.i2c_pin_cfg.sda_io_num = GPIO_NUM_3, \ .i2c_pin_cfg.sda_io_num = GPIO_NUM_3, \
.i2c_pin_cfg.scl_io_num = GPIO_NUM_2, \ .i2c_pin_cfg.scl_io_num = GPIO_NUM_2, \
.i2c_pin_cfg.sda_pullup_en = true, \ .i2c_pin_cfg.sda_pullup_en = true, \
.i2c_pin_cfg.scl_pullup_en = true, \ .i2c_pin_cfg.scl_pullup_en = true, \
/* Nominal I2C bus timing parameters for I2C fast mode. Max SCL freq of 400 KHz. */
#define ULP_RISCV_I2C_FAST_MODE_CONFIG() \
.i2c_timing_cfg.scl_low_period = 1.4, \
.i2c_timing_cfg.scl_high_period = 0.3, \
.i2c_timing_cfg.sda_duty_period = 1, \
.i2c_timing_cfg.scl_start_period = 2, \
.i2c_timing_cfg.scl_stop_period = 1.3, \
.i2c_timing_cfg.i2c_trans_timeout = 20, \
/* Nominal I2C bus timing parameters for I2C standard mode. Max SCL freq of 100 KHz. */
#define ULP_RISCV_I2C_STANDARD_MODE_CONFIG() \
.i2c_timing_cfg.scl_low_period = 5, \ .i2c_timing_cfg.scl_low_period = 5, \
.i2c_timing_cfg.scl_high_period = 5, \ .i2c_timing_cfg.scl_high_period = 5, \
.i2c_timing_cfg.sda_duty_period = 2, \ .i2c_timing_cfg.sda_duty_period = 2, \
.i2c_timing_cfg.scl_start_period = 3, \ .i2c_timing_cfg.scl_start_period = 3, \
.i2c_timing_cfg.scl_stop_period = 6, \ .i2c_timing_cfg.scl_stop_period = 6, \
.i2c_timing_cfg.i2c_trans_timeout = 20, \ .i2c_timing_cfg.i2c_trans_timeout = 20, \
/* Default RTC I2C configuration settings. Uses I2C fast mode. */
//TODO: Move to smaller units of time in the future like nano seconds to avoid floating point operations.
#define ULP_RISCV_I2C_DEFAULT_CONFIG() \
{ \
ULP_RISCV_I2C_DEFAULT_GPIO_CONFIG() \
ULP_RISCV_I2C_FAST_MODE_CONFIG() \
} }
/** /**

View File

@ -10,6 +10,7 @@
extern "C" { extern "C" {
#endif #endif
#include "sdkconfig.h"
#include <stdint.h> #include <stdint.h>
#include "ulp_riscv_register_ops.h" #include "ulp_riscv_register_ops.h"

View File

@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
@ -29,6 +29,9 @@
#define ULP_I2C_CMD_END I2C_LL_CMD_END /*!<I2C end command */ #define ULP_I2C_CMD_END I2C_LL_CMD_END /*!<I2C end command */
#endif // CONFIG_IDF_TARGET_ESP32S3 #endif // CONFIG_IDF_TARGET_ESP32S3
/* Read/Write timeout (number of iterationis) */
#define ULP_RISCV_I2C_RW_TIMEOUT 500
/* /*
* The RTC I2C controller follows the I2C command registers to perform read/write operations. * The RTC I2C controller follows the I2C command registers to perform read/write operations.
* The cmd registers have the following format: * The cmd registers have the following format:
@ -62,6 +65,37 @@ static void ulp_riscv_i2c_format_cmd(uint32_t cmd_idx, uint8_t op_code, uint8_t
((byte_num & 0xFF) << 0)); // Byte Num ((byte_num & 0xFF) << 0)); // Byte Num
} }
static inline int32_t ulp_riscv_i2c_wait_for_interrupt(uint32_t timeout)
{
uint32_t status = 0;
uint32_t to = 0;
while (to < timeout) {
status = READ_PERI_REG(RTC_I2C_INT_ST_REG);
/* Return 0 if Tx or Rx data interrupt bits are set. -1 otherwise */
if ((status & RTC_I2C_TX_DATA_INT_ST) ||
(status & RTC_I2C_RX_DATA_INT_ST)) {
return 0;
/* In case of errors return immidiately */
#if CONFIG_IDF_TARGET_ESP32S2
} else if ((status & RTC_I2C_TIMEOUT_INT_ST) ||
#elif CONFIG_IDF_TARGET_ESP32S3
} else if ((status & RTC_I2C_TIME_OUT_INT_ST) ||
#endif // CONFIG_IDF_TARGET_ESP32S2
(status & RTC_I2C_ACK_ERR_INT_ST) ||
(status & RTC_I2C_ARBITRATION_LOST_INT_ST)) {
return -1;
}
ulp_riscv_delay_cycles(ULP_RISCV_CYCLES_PER_MS);
to++;
}
/* If we reach here, it is a timeout error */
return -1;
}
void ulp_riscv_i2c_master_set_slave_addr(uint8_t slave_addr) void ulp_riscv_i2c_master_set_slave_addr(uint8_t slave_addr)
{ {
CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, I2C_CTRL_SLAVE_ADDR_MASK); CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, I2C_CTRL_SLAVE_ADDR_MASK);
@ -135,23 +169,30 @@ void ulp_riscv_i2c_master_read_from_device(uint8_t *data_rd, size_t size)
SET_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START); SET_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START);
for (i = 0; i < size; i++) { for (i = 0; i < size; i++) {
/* Poll for RTC I2C Rx Data interrupt bit to be set */ /* Poll for RTC I2C Rx Data interrupt bit to be set.
while (!REG_GET_FIELD(RTC_I2C_INT_ST_REG, RTC_I2C_RX_DATA_INT_ST)) { } * Set a loop timeout of 500 msec to bail in case of any driver
* and/or hardware errors.
/* Read the data
*
* Unfortunately, the RTC I2C has no fifo buffer to help us with reading and storing
* multiple bytes of data. Therefore, we need to read one byte at a time and clear the
* Rx interrupt to get ready for the next byte.
*/ */
if(!ulp_riscv_i2c_wait_for_interrupt(ULP_RISCV_I2C_RW_TIMEOUT)) {
/* Read the data
*
* Unfortunately, the RTC I2C has no fifo buffer to help us with reading and storing
* multiple bytes of data. Therefore, we need to read one byte at a time and clear the
* Rx interrupt to get ready for the next byte.
*/
#if CONFIG_IDF_TARGET_ESP32S2 #if CONFIG_IDF_TARGET_ESP32S2
data_rd[i] = REG_GET_FIELD(RTC_I2C_DATA_REG, RTC_I2C_RDATA); data_rd[i] = REG_GET_FIELD(RTC_I2C_DATA_REG, RTC_I2C_RDATA);
#elif CONFIG_IDF_TARGET_ESP32S3 #elif CONFIG_IDF_TARGET_ESP32S3
data_rd[i] = REG_GET_FIELD(RTC_I2C_DATA_REG, RTC_I2C_I2C_RDATA); data_rd[i] = REG_GET_FIELD(RTC_I2C_DATA_REG, RTC_I2C_I2C_RDATA);
#endif // CONFIG_IDF_TARGET_ESP32S2 #endif // CONFIG_IDF_TARGET_ESP32S2
/* Clear the Rx data interrupt bit */ /* Clear the Rx data interrupt bit */
SET_PERI_REG_MASK(RTC_I2C_INT_CLR_REG, RTC_I2C_RX_DATA_INT_CLR); SET_PERI_REG_MASK(RTC_I2C_INT_CLR_REG, RTC_I2C_RX_DATA_INT_CLR);
} else {
/* Error in transaction */
CLEAR_PERI_REG_MASK(RTC_I2C_INT_CLR_REG, READ_PERI_REG(RTC_I2C_INT_ST_REG));
break;
}
} }
/* Clear the RTC I2C transmission bits */ /* Clear the RTC I2C transmission bits */
@ -212,11 +253,17 @@ void ulp_riscv_i2c_master_write_to_device(uint8_t *data_wr, size_t size)
SET_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START); SET_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START);
} }
/* Poll for RTC I2C Tx Data interrupt bit to be set */ /* Poll for RTC I2C Tx Data interrupt bit to be set.
while (!REG_GET_FIELD(RTC_I2C_INT_ST_REG, RTC_I2C_TX_DATA_INT_ST)) { } * Set a loop timeout of 500 msec to bail in case of any driver
* and/or hardware errors.
/* Clear the Tx data interrupt bit */ */
SET_PERI_REG_MASK(RTC_I2C_INT_CLR_REG, RTC_I2C_TX_DATA_INT_CLR); if (!ulp_riscv_i2c_wait_for_interrupt(ULP_RISCV_I2C_RW_TIMEOUT)) {
/* Clear the Tx data interrupt bit */
SET_PERI_REG_MASK(RTC_I2C_INT_CLR_REG, RTC_I2C_TX_DATA_INT_CLR);
} else {
SET_PERI_REG_MASK(RTC_I2C_INT_CLR_REG, READ_PERI_REG(RTC_I2C_INT_ST_REG));
break;
}
} }
/* Clear the RTC I2C transmission bits */ /* Clear the RTC I2C transmission bits */

View File

@ -40,7 +40,10 @@ static const char *RTCI2C_TAG = "ulp_riscv_i2c";
rtc_i2c_dev_t *i2c_dev = &RTC_I2C; rtc_i2c_dev_t *i2c_dev = &RTC_I2C;
rtc_io_dev_t *rtc_io_dev = &RTCIO; rtc_io_dev_t *rtc_io_dev = &RTCIO;
#define MICROSEC_TO_RTC_FAST_CLK(period) (period) * ((SOC_CLK_RC_FAST_FREQ_APPROX) / (1000000)) #define MICROSEC_TO_RTC_FAST_CLK(period) (period) * ((float)(SOC_CLK_RC_FAST_FREQ_APPROX) / (1000000.0))
/* Read/Write timeout (number of iterations)*/
#define ULP_RISCV_I2C_RW_TIMEOUT 500
static esp_err_t i2c_gpio_is_cfg_valid(gpio_num_t sda_io_num, gpio_num_t scl_io_num) static esp_err_t i2c_gpio_is_cfg_valid(gpio_num_t sda_io_num, gpio_num_t scl_io_num)
{ {
@ -130,24 +133,26 @@ static esp_err_t i2c_set_timing(const ulp_riscv_i2c_cfg_t *cfg)
* RTC_FAST_CLK = 8.5 MHz for esp32s2 and 17.5 MHz for esp32s3. * RTC_FAST_CLK = 8.5 MHz for esp32s2 and 17.5 MHz for esp32s3.
* The following calculations approximate the period for each parameter. * The following calculations approximate the period for each parameter.
*/ */
uint32_t scl_low_period = MICROSEC_TO_RTC_FAST_CLK(cfg->i2c_timing_cfg.scl_low_period); float scl_low_period = MICROSEC_TO_RTC_FAST_CLK(cfg->i2c_timing_cfg.scl_low_period);
uint32_t scl_high_period = MICROSEC_TO_RTC_FAST_CLK(cfg->i2c_timing_cfg.scl_high_period); float scl_high_period = MICROSEC_TO_RTC_FAST_CLK(cfg->i2c_timing_cfg.scl_high_period);
uint32_t sda_duty_period = MICROSEC_TO_RTC_FAST_CLK(cfg->i2c_timing_cfg.sda_duty_period); float sda_duty_period = MICROSEC_TO_RTC_FAST_CLK(cfg->i2c_timing_cfg.sda_duty_period);
uint32_t scl_start_period = MICROSEC_TO_RTC_FAST_CLK(cfg->i2c_timing_cfg.scl_start_period); float scl_start_period = MICROSEC_TO_RTC_FAST_CLK(cfg->i2c_timing_cfg.scl_start_period);
uint32_t scl_stop_period = MICROSEC_TO_RTC_FAST_CLK(cfg->i2c_timing_cfg.scl_stop_period); float scl_stop_period = MICROSEC_TO_RTC_FAST_CLK(cfg->i2c_timing_cfg.scl_stop_period);
uint32_t i2c_trans_timeout = MICROSEC_TO_RTC_FAST_CLK(cfg->i2c_timing_cfg.i2c_trans_timeout); float i2c_trans_timeout = MICROSEC_TO_RTC_FAST_CLK(cfg->i2c_timing_cfg.i2c_trans_timeout);
uint32_t setup_time_start = (cfg->i2c_timing_cfg.scl_high_period + cfg->i2c_timing_cfg.sda_duty_period); float setup_time_start = (cfg->i2c_timing_cfg.scl_high_period + cfg->i2c_timing_cfg.sda_duty_period);
uint32_t hold_time_start = (cfg->i2c_timing_cfg.scl_start_period - cfg->i2c_timing_cfg.sda_duty_period); float hold_time_start = (cfg->i2c_timing_cfg.scl_start_period - cfg->i2c_timing_cfg.sda_duty_period);
uint32_t setup_time_data = (cfg->i2c_timing_cfg.scl_low_period - cfg->i2c_timing_cfg.sda_duty_period); float setup_time_data = (cfg->i2c_timing_cfg.scl_low_period - cfg->i2c_timing_cfg.sda_duty_period);
/* Verify timing constraints */ /* Verify timing constraints */
ESP_RETURN_ON_FALSE((float)cfg->i2c_timing_cfg.scl_low_period > 1.3, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "SCL low period cannot be less than 1.3 micro seconds"); ESP_RETURN_ON_FALSE(cfg->i2c_timing_cfg.scl_low_period >= 1.3f, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "SCL low period cannot be less than 1.3 micro seconds");
ESP_RETURN_ON_FALSE((float)cfg->i2c_timing_cfg.scl_high_period > 0.6, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "SCL high period cannot be less than 0.6 micro seconds"); // TODO: As per specs, SCL high period must be greater than 0.6 micro seconds but after tests it is found that we can have a the period as 0.3 micro seconds to
ESP_RETURN_ON_FALSE((float)setup_time_start > 0.6, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "Setup time cannot be less than 0.6 micro seconds"); // achieve performance close to I2C fast mode. Therefore, this criteria is relaxed.
ESP_RETURN_ON_FALSE((float)hold_time_start > 0.6, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "Data hold time cannot be less than 0.6 micro seconds"); ESP_RETURN_ON_FALSE(cfg->i2c_timing_cfg.scl_high_period >= 0.3f, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "SCL high period cannot be less than 0.3 micro seconds");
ESP_RETURN_ON_FALSE((float)cfg->i2c_timing_cfg.scl_stop_period > 0.6, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "Setup time cannot be less than 0.6 micro seconds"); ESP_RETURN_ON_FALSE(setup_time_start >= 0.6f, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "Setup time cannot be less than 0.6 micro seconds");
ESP_RETURN_ON_FALSE((float)cfg->i2c_timing_cfg.sda_duty_period < 3.45, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "Data hold time cannot be greater than 3.45 micro seconds"); ESP_RETURN_ON_FALSE(hold_time_start >= 0.6f, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "Data hold time cannot be less than 0.6 micro seconds");
ESP_RETURN_ON_FALSE((float)(setup_time_data * 1000) > 250, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "Data setup time cannot be less than 250 nano seconds"); ESP_RETURN_ON_FALSE(cfg->i2c_timing_cfg.scl_stop_period >= 0.6f, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "Setup time cannot be less than 0.6 micro seconds");
ESP_RETURN_ON_FALSE(cfg->i2c_timing_cfg.sda_duty_period <= 3.45f, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "Data hold time cannot be greater than 3.45 micro seconds");
ESP_RETURN_ON_FALSE((setup_time_data * 1000) >= 250, ESP_ERR_INVALID_ARG, RTCI2C_TAG, "Data setup time cannot be less than 250 nano seconds");
/* Verify filtering constrains /* Verify filtering constrains
* *
@ -228,6 +233,41 @@ static void ulp_riscv_i2c_format_cmd(uint32_t cmd_idx, uint8_t op_code, uint8_t
#endif // CONFIG_IDF_TARGET_ESP32S2 #endif // CONFIG_IDF_TARGET_ESP32S2
} }
static inline esp_err_t ulp_riscv_i2c_wait_for_interrupt(uint32_t timeout)
{
uint32_t status = 0;
uint32_t to = 0;
esp_err_t ret = ESP_ERR_TIMEOUT;
while (to < timeout) {
status = READ_PERI_REG(RTC_I2C_INT_ST_REG);
/* Return ESP_OK if Tx or Rx data interrupt bits are set. */
if ((status & RTC_I2C_TX_DATA_INT_ST) ||
(status & RTC_I2C_RX_DATA_INT_ST)) {
ret = ESP_OK;
break;
/* In case of error status, break and return ESP_FAIL */
#if CONFIG_IDF_TARGET_ESP32S2
} else if ((status & RTC_I2C_TIMEOUT_INT_ST) ||
#elif CONFIG_IDF_TARGET_ESP32S3
} else if ((status & RTC_I2C_TIME_OUT_INT_ST) ||
#endif // CONFIG_IDF_TARGET_ESP32S2
(status & RTC_I2C_ACK_ERR_INT_ST) ||
(status & RTC_I2C_ARBITRATION_LOST_INT_ST)) {
ret = ESP_FAIL;
break;
}
vTaskDelay(1);
/* Loop timeout. If this expires, we return ESP_ERR_TIMEOUT */
to++;
}
return ret;
}
void ulp_riscv_i2c_master_set_slave_addr(uint8_t slave_addr) void ulp_riscv_i2c_master_set_slave_addr(uint8_t slave_addr)
{ {
CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, I2C_CTRL_SLAVE_ADDR_MASK); CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, I2C_CTRL_SLAVE_ADDR_MASK);
@ -261,6 +301,7 @@ void ulp_riscv_i2c_master_read_from_device(uint8_t *data_rd, size_t size)
{ {
uint32_t i = 0; uint32_t i = 0;
uint32_t cmd_idx = 0; uint32_t cmd_idx = 0;
esp_err_t ret = ESP_OK;
if (size == 0) { if (size == 0) {
// Quietly return // Quietly return
@ -293,34 +334,40 @@ void ulp_riscv_i2c_master_read_from_device(uint8_t *data_rd, size_t size)
/* Configure the RTC I2C controller in read mode */ /* Configure the RTC I2C controller in read mode */
SET_PERI_REG_BITS(SENS_SAR_I2C_CTRL_REG, 0x1, 0, 27); SET_PERI_REG_BITS(SENS_SAR_I2C_CTRL_REG, 0x1, 0, 27);
/* Enable Rx data interrupt */
SET_PERI_REG_MASK(RTC_I2C_INT_ENA_REG, RTC_I2C_RX_DATA_INT_ENA);
/* Start RTC I2C transmission */ /* Start RTC I2C transmission */
SET_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START_FORCE); SET_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START_FORCE);
SET_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START); SET_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START);
for (i = 0; i < size; i++) { for (i = 0; i < size; i++) {
/* Poll for RTC I2C Rx Data interrupt bit to be set */ /* Poll for RTC I2C Rx Data interrupt bit to be set.
while (!REG_GET_FIELD(RTC_I2C_INT_ST_REG, RTC_I2C_RX_DATA_INT_ST)) { * Set a loop timeout of 500 iterations to bail in case of any driver
/* Minimal delay to avoid hogging the CPU */ * and/or hardware errors.
vTaskDelay(1);
}
/* Read the data
*
* Unfortunately, the RTC I2C has no fifo buffer to help us with reading and storing
* multiple bytes of data. Therefore, we need to read one byte at a time and clear the
* Rx interrupt to get ready for the next byte.
*/ */
ret = ulp_riscv_i2c_wait_for_interrupt(ULP_RISCV_I2C_RW_TIMEOUT);
if (ret == ESP_OK) {
/* Read the data
*
* Unfortunately, the RTC I2C has no fifo buffer to help us with reading and storing
* multiple bytes of data. Therefore, we need to read one byte at a time and clear the
* Rx interrupt to get ready for the next byte.
*/
#if CONFIG_IDF_TARGET_ESP32S2 #if CONFIG_IDF_TARGET_ESP32S2
data_rd[i] = REG_GET_FIELD(RTC_I2C_DATA_REG, RTC_I2C_RDATA); data_rd[i] = REG_GET_FIELD(RTC_I2C_DATA_REG, RTC_I2C_RDATA);
#elif CONFIG_IDF_TARGET_ESP32S3 #elif CONFIG_IDF_TARGET_ESP32S3
data_rd[i] = REG_GET_FIELD(RTC_I2C_DATA_REG, RTC_I2C_I2C_RDATA); data_rd[i] = REG_GET_FIELD(RTC_I2C_DATA_REG, RTC_I2C_I2C_RDATA);
#endif // CONFIG_IDF_TARGET_ESP32S2 #endif // CONFIG_IDF_TARGET_ESP32S2
/* Clear the Rx data interrupt bit */ /* Clear the Rx data interrupt bit */
SET_PERI_REG_MASK(RTC_I2C_INT_CLR_REG, RTC_I2C_RX_DATA_INT_CLR); SET_PERI_REG_MASK(RTC_I2C_INT_CLR_REG, RTC_I2C_RX_DATA_INT_CLR);
} else {
ESP_LOGE(RTCI2C_TAG, "Read Failed!");
uint32_t status = READ_PERI_REG(RTC_I2C_INT_RAW_REG);
ESP_LOGE(RTCI2C_TAG, "RTC I2C Interrupt Raw Reg 0x%"PRIx32"", status);
ESP_LOGE(RTCI2C_TAG, "RTC I2C Status Reg 0x%"PRIx32"", READ_PERI_REG(RTC_I2C_STATUS_REG));
SET_PERI_REG_MASK(RTC_I2C_INT_CLR_REG, status);
break;
}
} }
/* Clear the RTC I2C transmission bits */ /* Clear the RTC I2C transmission bits */
@ -349,6 +396,7 @@ void ulp_riscv_i2c_master_write_to_device(uint8_t *data_wr, size_t size)
{ {
uint32_t i = 0; uint32_t i = 0;
uint32_t cmd_idx = 0; uint32_t cmd_idx = 0;
esp_err_t ret = ESP_OK;
if (size == 0) { if (size == 0) {
// Quietly return // Quietly return
@ -367,9 +415,6 @@ void ulp_riscv_i2c_master_write_to_device(uint8_t *data_wr, size_t size)
/* Configure the RTC I2C controller in write mode */ /* Configure the RTC I2C controller in write mode */
SET_PERI_REG_BITS(SENS_SAR_I2C_CTRL_REG, 0x1, 1, 27); SET_PERI_REG_BITS(SENS_SAR_I2C_CTRL_REG, 0x1, 1, 27);
/* Enable Tx data interrupt */
SET_PERI_REG_MASK(RTC_I2C_INT_ENA_REG, RTC_I2C_TX_DATA_INT_ENA);
for (i = 0; i < size; i++) { for (i = 0; i < size; i++) {
/* Write the data to be transmitted */ /* Write the data to be transmitted */
CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, I2C_CTRL_MASTER_TX_DATA_MASK); CLEAR_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, I2C_CTRL_MASTER_TX_DATA_MASK);
@ -381,14 +426,23 @@ void ulp_riscv_i2c_master_write_to_device(uint8_t *data_wr, size_t size)
SET_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START); SET_PERI_REG_MASK(SENS_SAR_I2C_CTRL_REG, SENS_SAR_I2C_START);
} }
/* Poll for RTC I2C Tx Data interrupt bit to be set */ /* Poll for RTC I2C Tx Data interrupt bit to be set.
while (!REG_GET_FIELD(RTC_I2C_INT_ST_REG, RTC_I2C_TX_DATA_INT_ST)) { * Set a loop timeout of 500 iterations to bail in case of any driver
/* Minimal delay to avoid hogging the CPU */ * and/or hardware errors.
vTaskDelay(1); */
} ret = ulp_riscv_i2c_wait_for_interrupt(ULP_RISCV_I2C_RW_TIMEOUT);
/* Clear the Tx data interrupt bit */ if (ret == ESP_OK) {
SET_PERI_REG_MASK(RTC_I2C_INT_CLR_REG, RTC_I2C_TX_DATA_INT_CLR); /* Clear the Tx data interrupt bit */
SET_PERI_REG_MASK(RTC_I2C_INT_CLR_REG, RTC_I2C_TX_DATA_INT_CLR);
} else {
ESP_LOGE(RTCI2C_TAG, "Write Failed!");
uint32_t status = READ_PERI_REG(RTC_I2C_INT_RAW_REG);
ESP_LOGE(RTCI2C_TAG, "RTC I2C Interrupt Raw Reg 0x%"PRIx32"", status);
ESP_LOGE(RTCI2C_TAG, "RTC I2C Status Reg 0x%"PRIx32"", READ_PERI_REG(RTC_I2C_STATUS_REG));
SET_PERI_REG_MASK(RTC_I2C_INT_CLR_REG, status);
break;
}
} }
/* Clear the RTC I2C transmission bits */ /* Clear the RTC I2C transmission bits */
@ -441,5 +495,16 @@ esp_err_t ulp_riscv_i2c_master_init(const ulp_riscv_i2c_cfg_t *cfg)
/* Configure RTC I2C timing paramters */ /* Configure RTC I2C timing paramters */
ESP_RETURN_ON_ERROR(i2c_set_timing(cfg), RTCI2C_TAG, "Failed to configure RTC I2C timing"); ESP_RETURN_ON_ERROR(i2c_set_timing(cfg), RTCI2C_TAG, "Failed to configure RTC I2C timing");
/* Enable RTC I2C interrupts */
SET_PERI_REG_MASK(RTC_I2C_INT_ENA_REG, RTC_I2C_RX_DATA_INT_ENA |
RTC_I2C_TX_DATA_INT_ENA |
RTC_I2C_ARBITRATION_LOST_INT_ENA |
RTC_I2C_ACK_ERR_INT_ENA |
#if CONFIG_IDF_TARGET_ESP32S2
RTC_I2C_TIMEOUT_INT_ENA);
#elif CONFIG_IDF_TARGET_ESP32S3
RTC_I2C_TIME_OUT_INT_ENA);
#endif // CONFIG_IDF_TARGET_ESP32S2
return ESP_OK; return ESP_OK;
} }

View File

@ -266,6 +266,7 @@ INPUT = \
$(PROJECT_PATH)/components/ulp/ulp_fsm/include/ulp_fsm_common.h \ $(PROJECT_PATH)/components/ulp/ulp_fsm/include/ulp_fsm_common.h \
$(PROJECT_PATH)/components/ulp/ulp_riscv/include/ulp_riscv_lock.h \ $(PROJECT_PATH)/components/ulp/ulp_riscv/include/ulp_riscv_lock.h \
$(PROJECT_PATH)/components/ulp/ulp_riscv/include/ulp_riscv.h \ $(PROJECT_PATH)/components/ulp/ulp_riscv/include/ulp_riscv.h \
$(PROJECT_PATH)/components/ulp/ulp_riscv/include/ulp_riscv_i2c.h \
$(PROJECT_PATH)/components/ulp/ulp_riscv/shared/include/ulp_riscv_lock_shared.h \ $(PROJECT_PATH)/components/ulp/ulp_riscv/shared/include/ulp_riscv_lock_shared.h \
$(PROJECT_PATH)/components/usb/include/usb/usb_helpers.h \ $(PROJECT_PATH)/components/usb/include/usb/usb_helpers.h \
$(PROJECT_PATH)/components/usb/include/usb/usb_host.h \ $(PROJECT_PATH)/components/usb/include/usb/usb_host.h \

View File

@ -150,6 +150,37 @@ The program runs until the field ``RTC_CNTL_COCPU_DONE`` in register ``RTC_CNTL_
To disable the timer (effectively preventing the ULP program from running again), please clear the ``RTC_CNTL_ULP_CP_SLP_TIMER_EN`` bit in the ``RTC_CNTL_ULP_CP_TIMER_REG`` register. This can be done both from the ULP code and from the main program. To disable the timer (effectively preventing the ULP program from running again), please clear the ``RTC_CNTL_ULP_CP_SLP_TIMER_EN`` bit in the ``RTC_CNTL_ULP_CP_TIMER_REG`` register. This can be done both from the ULP code and from the main program.
ULP RISC-V Peripheral Support
------------------------------
To enhance the capabilities of the ULP RISC-V coprocessor, it has access to peripherals which operate in the low-power (RTC) domain. The ULP RISC-V coprocessor can interact with these peripherals when the main CPU is in sleep-mode and can wakeup the main CPU once a wakeup condition is reached. The following peripherals are supported:
RTC I2C
^^^^^^^^
The RTC I2C controller provides I2C Master functionality in the RTC domain. The ULP RISC-V coprocessor can read from or write to I2C slave devices using this controller. To use the RTC I2C peripheral call the :cpp:func:`ulp_riscv_i2c_master_init` from your application running on the main core before initializing the ULP RISC-V core and going in to sleep.
Once the RTC I2C controller is initialized, the I2C slave device address must be programmed via the :cpp:func:`ulp_riscv_i2c_master_set_slave_addr` API before any read or write operation is performed.
.. note:: The RTC I2C peripheral always expects a slave sub-register address to be programmed via the :cpp:func:`ulp_riscv_i2c_master_set_slave_reg_addr` API. If it is not, the I2C peripheral uses the ``SENS_SAR_I2C_CTRL_REG[18:11]`` as the sub register address for the subsequent read or write operations. This could make the RTC I2C peripheral incompatible with certain I2C devices or sensors which do not need any sub-register to be programmed.
.. note:: There is no hardware atomicity protection in accessing the RTC I2C peripheral between the main CPU and the ULP RISC-V core. Therefore, care must be taken that both cores are not accessing the peripheral simultaneously.
In case your RTC I2C based ULP RISC-V program is not working as expected, the following sanity checks can help in debugging the issue:
* Incorrect SDA/SCL pin selection: The SDA pin can only be setup as GPIO1 or GPIO3 and SCL pin can only be setup as GPIO0 or GPIO2. Make sure that the pin configuration is correct.
* Incorrect I2C timing parameters: The RTC I2C bus timing configuration is limited by the I2C standard bus specification. Any timining parameters which violate the standard I2C bus specifications would result in an error. For details on the timing parameters, please read the standard I2C bus specifications.
* If the I2C slave device or sensor does not require a sub-register address to be programmed, it may not be compatible with the RTC I2C peripheral. Please refer the notes above.
* If the RTC driver reports a `Write Failed!` or `Read Failed!` error log when run from the main CPU then make sure:
* The I2C slave device or sensor works correctly with the standard I2C master on Espressif SoCs. This would rule out any problems with the I2C slave device itself.
* If the RTC I2C interrupt status log reports a `TIMEOUT` error or `ACK` error, it could typically mean that the I2C device did not respond to a `START` condition sent out by the RTC I2C controller. This could happen if the I2C slave device is not connected properly to the controller pins or if the I2C slave device is in a bad state. Make sure that the I2C slave device is in a good state and connected properly before continuing.
* If the RTC I2C interrupt log does not report any error status, it could mean that the driver is not fast enough in receiving data from the I2C slave device. This could happen as the RTC I2C controller does not have a Tx/Rx FIFO to store multiple bytes of data but rather, it depends on single byte transmissions using an interrupt status polling mechanism. This could be mitigated to some extent by making sure that the SCL clock of the peripheral is running with as fast as possible. This can be tweaked by configuring the SCL low period and SCL high period values in the initialization config parameters for the peripheral.
* Other methods of debugging problems would be to ensure that the RTC I2C controller is operational *only* from the main CPU *without* any ULP RISC-V code interfering and *without* any sleepmode being activated. This is the basic configuration under which the RTC I2C peripheral must work. This way you can rule out any potential issues due to the ULP or sleep modes.
Debugging Your ULP RISC-V Program Debugging Your ULP RISC-V Program
---------------------------------- ----------------------------------
@ -170,6 +201,7 @@ Application Examples
* ULP RISC-V Coprocessor polls GPIO while main CPU is in deep sleep: :example:`system/ulp_riscv/gpio`. * ULP RISC-V Coprocessor polls GPIO while main CPU is in deep sleep: :example:`system/ulp_riscv/gpio`.
* ULP RISC-V Coprocessor uses bit-banged UART driver to print: :example:`system/ulp_riscv/uart_print`. * ULP RISC-V Coprocessor uses bit-banged UART driver to print: :example:`system/ulp_riscv/uart_print`.
* ULP RISC-V Coprocessor reads external temperature sensor while main CPU is in deep sleep: :example:`system/ulp_riscv/ds18b20_onewire`. * ULP RISC-V Coprocessor reads external temperature sensor while main CPU is in deep sleep: :example:`system/ulp_riscv/ds18b20_onewire`.
* ULP RISC-V Coprocessor reads external I2C temperature and humidity sensor (BMP180) while the main CPU is in deep sleep and wakes up the main CPU once a threshold is met: :example:`system/ulp_riscv/i2c`.
API Reference API Reference
------------- -------------
@ -177,3 +209,4 @@ API Reference
.. include-build-file:: inc/ulp_riscv.inc .. include-build-file:: inc/ulp_riscv.inc
.. include-build-file:: inc/ulp_riscv_lock_shared.inc .. include-build-file:: inc/ulp_riscv_lock_shared.inc
.. include-build-file:: inc/ulp_riscv_lock.inc .. include-build-file:: inc/ulp_riscv_lock.inc
.. include-build-file:: inc/ulp_riscv_i2c.inc

View File

@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Unlicense OR CC0-1.0 * SPDX-License-Identifier: Unlicense OR CC0-1.0
*/ */
@ -162,7 +162,11 @@ static void init_i2c(void)
/* Configure RTC I2C */ /* Configure RTC I2C */
printf("Initializing RTC I2C ...\n"); printf("Initializing RTC I2C ...\n");
ulp_riscv_i2c_cfg_t i2c_cfg = ULP_RISCV_I2C_DEFAULT_CONFIG(); ulp_riscv_i2c_cfg_t i2c_cfg = ULP_RISCV_I2C_DEFAULT_CONFIG();
ulp_riscv_i2c_master_init(&i2c_cfg); esp_err_t ret = ulp_riscv_i2c_master_init(&i2c_cfg);
if (ret!= ESP_OK) {
printf("ERROR: Failed to initialize RTC I2C. Aborting...\n");
abort();
}
} }
static void bmp180_read16(uint16_t *data_out, uint32_t reg_msb, uint32_t reg_lsb) static void bmp180_read16(uint16_t *data_out, uint32_t reg_msb, uint32_t reg_lsb)