lp-i2c: Added support for LP I2C peripheral to LP core

This commit adds support for the LP I2C peripheral driver to be used by
the LP core. An example is also added to demonstrate the usage of the LP
I2C peripheral from the LP core.
This commit is contained in:
Sudeep Mohanty 2023-03-20 16:08:38 +01:00
parent 224430f6b4
commit ec742abb25
27 changed files with 1246 additions and 198 deletions

View File

@ -110,7 +110,11 @@ static const char *I2C_TAG = "i2c";
#if SOC_I2C_NUM >= 2
_Static_assert(I2C_NUM_1 == 1, "I2C_NUM_1 must be equal to 1");
#endif // SOC_I2C_NUM >= 2
#if SOC_LP_I2C_SUPPORTED
_Static_assert(I2C_NUM_MAX == (SOC_I2C_NUM + SOC_LP_I2C_NUM), "I2C_NUM_MAX must be equal to SOC_I2C_NUM + SOC_LP_I2C_NUM");
#else
_Static_assert(I2C_NUM_MAX == SOC_I2C_NUM, "I2C_NUM_MAX must be equal to SOC_I2C_NUM");
#endif /* SOC_LP_I2C_SUPPORTED */
typedef struct {
i2c_ll_hw_cmd_t hw_cmd;
@ -259,6 +263,10 @@ esp_err_t i2c_driver_install(i2c_port_t i2c_num, i2c_mode_t mode, size_t slv_rx_
int intr_alloc_flags)
{
ESP_RETURN_ON_FALSE(i2c_num < I2C_NUM_MAX, ESP_ERR_INVALID_ARG, I2C_TAG, I2C_NUM_ERROR_STR);
#if SOC_LP_I2C_SUPPORTED
// TODO: IDF-5817
ESP_RETURN_ON_FALSE(i2c_num != LP_I2C_NUM_0, ESP_ERR_INVALID_ARG, I2C_TAG, "LP_I2C is not supported via i2c_driver_intall()");
#endif // SOC_LP_I2C_SUPPORTED
#if SOC_I2C_SUPPORT_SLAVE
ESP_RETURN_ON_FALSE(mode == I2C_MODE_MASTER || ( slv_rx_buf_len > 100 || slv_tx_buf_len > 100 ),
ESP_ERR_INVALID_ARG, I2C_TAG, I2C_SLAVE_BUFFER_LEN_ERR_STR);

View File

@ -51,6 +51,9 @@ uint32_t *freq_value)
case SOC_MOD_CLK_XTAL32K:
clk_src_freq = esp_clk_tree_xtal32k_get_freq_hz(precision);
break;
case SOC_MOD_CLK_XTAL_D2:
clk_src_freq = (clk_hal_xtal_get_freq_mhz() * MHZ) >> 1;
break;
default:
break;
}

View File

@ -11,6 +11,7 @@
#include "soc/periph_defs.h"
#include "soc/pcr_reg.h"
#include "soc/soc.h"
#include "soc/lpperi_reg.h"
#include "esp_attr.h"
#ifdef __cplusplus
@ -78,6 +79,9 @@ static inline uint32_t periph_ll_get_clk_en_mask(periph_module_t periph)
return PCR_SDIO_SLAVE_CLK_EN;
case PERIPH_REGDMA_MODULE:
return PCR_REGDMA_CLK_EN;
//TODO: LP_PERIPH modules are added temporarily and will be moved to a separate API (IDF-7374).
case PERIPH_LP_I2C0_MODULE:
return LPPERI_LP_EXT_I2C_CK_EN;
// case PERIPH_RNG_MODULE:
// return PCR_WIFI_CLK_RNG_EN;
// case PERIPH_WIFI_MODULE:
@ -171,6 +175,9 @@ static inline uint32_t periph_ll_get_rst_en_mask(periph_module_t periph, bool en
return PCR_SDIO_SLAVE_RST_EN;
case PERIPH_REGDMA_MODULE:
return PCR_REGDMA_RST_EN;
//TODO: LP_PERIPH modules are added temporarily and will be moved to a separate API (IDF-7374).
case PERIPH_LP_I2C0_MODULE:
return LPPERI_LP_EXT_I2C_RESET_EN;
// case PERIPH_RNG_MODULE:
// return PCR_WIFI_CLK_RNG_EN;
// case PERIPH_WIFI_MODULE:
@ -257,6 +264,9 @@ static uint32_t periph_ll_get_clk_en_reg(periph_module_t periph)
return PCR_SDIO_SLAVE_CONF_REG;
case PERIPH_REGDMA_MODULE:
return PCR_REGDMA_CONF_REG;
//TODO: LP_PERIPH modules are added temporarily and will be moved to a separate API (IDF-7374).
case PERIPH_LP_I2C0_MODULE:
return LPPERI_CLK_EN_REG;
default:
return 0;
}
@ -323,6 +333,9 @@ static uint32_t periph_ll_get_rst_en_reg(periph_module_t periph)
return PCR_SDIO_SLAVE_CONF_REG;
case PERIPH_REGDMA_MODULE:
return PCR_REGDMA_CONF_REG;
//TODO: LP_PERIPH modules are added temporarily and will be moved to a separate API (IDF-7374).
case PERIPH_LP_I2C0_MODULE:
return LPPERI_RESET_EN_REG;
default:
return 0;
}

View File

@ -17,6 +17,7 @@
#include "soc/pcr_struct.h"
#include "hal/i2c_types.h"
#include "soc/clk_tree_defs.h"
#include "soc/lp_clkrst_struct.h"
#ifdef __cplusplus
extern "C" {
@ -62,7 +63,7 @@ typedef enum {
} i2c_ll_slave_intr_t;
// Get the I2C hardware instance
#define I2C_LL_GET_HW(i2c_num) (&I2C0)
#define I2C_LL_GET_HW(i2c_num) (((i2c_num) == I2C_NUM_0) ? (&I2C0) : (&LP_I2C))
#define I2C_LL_MASTER_EVENT_INTR (I2C_NACK_INT_ENA_M|I2C_TIME_OUT_INT_ENA_M|I2C_TRANS_COMPLETE_INT_ENA_M|I2C_ARBITRATION_LOST_INT_ENA_M|I2C_END_DETECT_INT_ENA_M)
#define I2C_LL_SLAVE_EVENT_INTR (I2C_RXFIFO_WM_INT_ENA_M|I2C_TRANS_COMPLETE_INT_ENA_M|I2C_TXFIFO_WM_INT_ENA_M)
@ -665,11 +666,42 @@ static inline void i2c_ll_master_clr_bus(i2c_dev_t *hw)
*/
static inline void i2c_ll_set_source_clk(i2c_dev_t *hw, i2c_clock_source_t src_clk)
{
(void)hw;
if (hw == &LP_I2C) {
// Do nothing
return;
}
// src_clk : (1) for RTC_CLK, (0) for XTAL
PCR.i2c_sclk_conf.i2c_sclk_sel = (src_clk == I2C_CLK_SRC_RC_FAST) ? 1 : 0;
}
#if SOC_LP_I2C_SUPPORTED
/**
* @brief Set LP I2C source clock
*
* @param hw Address offset of the LP I2C peripheral registers
* @param src_clk Source clock for the LP I2C peripheral
*
* @return None
*/
static inline void lp_i2c_ll_set_source_clk(i2c_dev_t *hw, soc_periph_lp_i2c_clk_src_t src_clk)
{
(void)hw;
// src_clk : (0) for LP_FAST_CLK (RTC Fast), (1) for XTAL_D2_CLK
switch (src_clk) {
case LP_I2C_SCLK_LP_FAST:
LP_CLKRST.lpperi.lp_i2c_clk_sel = 0;
break;
case LP_I2C_SCLK_XTAL_D2:
LP_CLKRST.lpperi.lp_i2c_clk_sel = 1;
break;
default:
// Invalid source clock selected
abort();
}
}
#endif /* SOC_LP_I2C_SUPPORTED */
/**
* @brief Enable I2C peripheral controller clock
*
@ -678,7 +710,11 @@ static inline void i2c_ll_set_source_clk(i2c_dev_t *hw, i2c_clock_source_t src_c
*/
static inline void i2c_ll_enable_controller_clock(i2c_dev_t *hw, bool en)
{
(void)hw;
if (hw == &LP_I2C) {
// Do nothing
return;
}
PCR.i2c_sclk_conf.i2c_sclk_en = en;
}

View File

@ -19,11 +19,14 @@ extern "C" {
* @brief I2C port number, can be I2C_NUM_0 ~ (I2C_NUM_MAX-1).
*/
typedef enum {
I2C_NUM_0 = 0, /*!< I2C port 0 */
I2C_NUM_0 = 0, /*!< I2C port 0 */
#if SOC_I2C_NUM >= 2
I2C_NUM_1, /*!< I2C port 1 */
#endif
I2C_NUM_MAX, /*!< I2C port max */
I2C_NUM_1, /*!< I2C port 1 */
#endif /* SOC_I2C_NUM >= 2 */
#if SOC_LP_I2C_NUM >= 1
LP_I2C_NUM_0, /*< LP_I2C port 0 */
#endif /* SOC_LP_I2C_NUM >= 1 */
I2C_NUM_MAX, /*!< I2C port max */
} i2c_port_t;
/**

View File

@ -183,6 +183,10 @@ config SOC_LP_AON_SUPPORTED
bool
default y
config SOC_LP_I2C_SUPPORTED
bool
default y
config SOC_XTAL_SUPPORT_40M
bool
default y
@ -479,6 +483,14 @@ config SOC_I2C_SUPPORT_RTC
bool
default y
config SOC_LP_I2C_NUM
int
default 1
config SOC_LP_I2C_FIFO_LEN
int
default 16
config SOC_I2S_NUM
int
default 1

View File

@ -121,6 +121,7 @@ typedef enum {
SOC_MOD_CLK_XTAL32K, /*!< XTAL32K_CLK comes from the external 32kHz crystal, passing a clock gating to the peripherals */
SOC_MOD_CLK_RC_FAST, /*!< RC_FAST_CLK comes from the internal 20MHz rc oscillator, passing a clock gating to the peripherals */
SOC_MOD_CLK_XTAL, /*!< XTAL_CLK comes from the external 40MHz crystal */
SOC_MOD_CLK_XTAL_D2, /*!< XTAL_D2_CLK comes from the external 40MHz crystal, passing a div of 2 to the LP peripherals */
SOC_MOD_CLK_INVALID, /*!< Indication of the end of the available module clock sources */
} soc_module_clk_t;
@ -286,6 +287,21 @@ typedef enum {
I2C_CLK_SRC_DEFAULT = SOC_MOD_CLK_XTAL, /*!< Select XTAL as the default source clock */
} soc_periph_i2c_clk_src_t;
///////////////////////////////////////////////LP_I2C///////////////////////////////////////////////////////////////////
/**
* @brief Array initializer for all supported clock sources of LP_I2C
*/
#define SOC_LP_I2C_CLKS {SOC_MOD_CLK_RTC_FAST, SOC_MOD_CLK_XTAL_D2}
/**
* @brief Type of LP_I2C clock source.
*/
typedef enum {
LP_I2C_SCLK_LP_FAST = SOC_MOD_CLK_RTC_FAST, /*!< LP_I2C source clock is RTC_FAST */
LP_I2C_SCLK_XTAL_D2 = SOC_MOD_CLK_XTAL_D2, /*!< LP_I2C source clock is XTAL_D2 */
LP_I2C_SCLK_DEFAULT = SOC_MOD_CLK_RTC_FAST, /*!< LP_I2C source clock default choice is RTC_FAST */
} soc_periph_lp_i2c_clk_src_t;
/////////////////////////////////////////////////SPI////////////////////////////////////////////////////////////////////

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
*/
@ -1013,6 +1013,7 @@ typedef struct i2c_dev_t {
} i2c_dev_t;
extern i2c_dev_t I2C0;
extern i2c_dev_t LP_I2C;
#ifndef __cplusplus
_Static_assert(sizeof(i2c_dev_t) == 0x184, "Invalid size of i2c_dev_t structure");

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
*/
@ -775,194 +775,32 @@ typedef union {
/** Group: Command registers */
/** Type of comd0 register
* I2C command register 0
/** Type of command register
* I2C command register
*/
typedef union {
struct {
/** command0 : R/W; bitpos: [13:0]; default: 0;
* This is the content of command 0. It consists of three parts:
* op_code is the command, 0: RSTART, 1: WRITE, 2: READ, 3: STOP, 4: END.
/** command : R/W; bitpos: [13:0]; default: 0;
* This is the content of a command. It consists of three parts:
* op_code is the command, 6: RSTART, 1: WRITE, 3: READ, 2: STOP, 4: END.
* Byte_num represents the number of bytes that need to be sent or received.
* ack_check_en, ack_exp and ack are used to control the ACK bit. See I2C cmd
* structure for more
* Information.
* structure for more information.
*/
uint32_t command0:14;
uint32_t byte_num:8;
uint32_t ack_en:1;
uint32_t ack_exp:1;
uint32_t ack_val:1;
uint32_t op_code:3;
uint32_t reserved_14:17;
/** command0_done : R/W/SS; bitpos: [31]; default: 0;
* When command 0 is done in I2C Master mode, this bit changes to high
/** command_done : R/W/SS; bitpos: [31]; default: 0;
* When command is done in I2C Master mode, this bit changes to high
* level.
*/
uint32_t command0_done:1;
uint32_t command_done:1;
};
uint32_t val;
} lp_i2c_comd0_reg_t;
/** Type of comd1 register
* I2C command register 1
*/
typedef union {
struct {
/** command1 : R/W; bitpos: [13:0]; default: 0;
* This is the content of command 1. It consists of three parts:
* op_code is the command, 0: RSTART, 1: WRITE, 2: READ, 3: STOP, 4: END.
* Byte_num represents the number of bytes that need to be sent or received.
* ack_check_en, ack_exp and ack are used to control the ACK bit. See I2C cmd
* structure for more
* Information.
*/
uint32_t command1:14;
uint32_t reserved_14:17;
/** command1_done : R/W/SS; bitpos: [31]; default: 0;
* When command 1 is done in I2C Master mode, this bit changes to high
* level.
*/
uint32_t command1_done:1;
};
uint32_t val;
} lp_i2c_comd1_reg_t;
/** Type of comd2 register
* I2C command register 2
*/
typedef union {
struct {
/** command2 : R/W; bitpos: [13:0]; default: 0;
* This is the content of command 2. It consists of three parts:
* op_code is the command, 0: RSTART, 1: WRITE, 2: READ, 3: STOP, 4: END.
* Byte_num represents the number of bytes that need to be sent or received.
* ack_check_en, ack_exp and ack are used to control the ACK bit. See I2C cmd
* structure for more
* Information.
*/
uint32_t command2:14;
uint32_t reserved_14:17;
/** command2_done : R/W/SS; bitpos: [31]; default: 0;
* When command 2 is done in I2C Master mode, this bit changes to high
* Level.
*/
uint32_t command2_done:1;
};
uint32_t val;
} lp_i2c_comd2_reg_t;
/** Type of comd3 register
* I2C command register 3
*/
typedef union {
struct {
/** command3 : R/W; bitpos: [13:0]; default: 0;
* This is the content of command 3. It consists of three parts:
* op_code is the command, 0: RSTART, 1: WRITE, 2: READ, 3: STOP, 4: END.
* Byte_num represents the number of bytes that need to be sent or received.
* ack_check_en, ack_exp and ack are used to control the ACK bit. See I2C cmd
* structure for more
* Information.
*/
uint32_t command3:14;
uint32_t reserved_14:17;
/** command3_done : R/W/SS; bitpos: [31]; default: 0;
* When command 3 is done in I2C Master mode, this bit changes to high
* level.
*/
uint32_t command3_done:1;
};
uint32_t val;
} lp_i2c_comd3_reg_t;
/** Type of comd4 register
* I2C command register 4
*/
typedef union {
struct {
/** command4 : R/W; bitpos: [13:0]; default: 0;
* This is the content of command 4. It consists of three parts:
* op_code is the command, 0: RSTART, 1: WRITE, 2: READ, 3: STOP, 4: END.
* Byte_num represents the number of bytes that need to be sent or received.
* ack_check_en, ack_exp and ack are used to control the ACK bit. See I2C cmd
* structure for more
* Information.
*/
uint32_t command4:14;
uint32_t reserved_14:17;
/** command4_done : R/W/SS; bitpos: [31]; default: 0;
* When command 4 is done in I2C Master mode, this bit changes to high
* level.
*/
uint32_t command4_done:1;
};
uint32_t val;
} lp_i2c_comd4_reg_t;
/** Type of comd5 register
* I2C command register 5
*/
typedef union {
struct {
/** command5 : R/W; bitpos: [13:0]; default: 0;
* This is the content of command 5. It consists of three parts:
* op_code is the command, 0: RSTART, 1: WRITE, 2: READ, 3: STOP, 4: END.
* Byte_num represents the number of bytes that need to be sent or received.
* ack_check_en, ack_exp and ack are used to control the ACK bit. See I2C cmd
* structure for more
* Information.
*/
uint32_t command5:14;
uint32_t reserved_14:17;
/** command5_done : R/W/SS; bitpos: [31]; default: 0;
* When command 5 is done in I2C Master mode, this bit changes to high level.
*/
uint32_t command5_done:1;
};
uint32_t val;
} lp_i2c_comd5_reg_t;
/** Type of comd6 register
* I2C command register 6
*/
typedef union {
struct {
/** command6 : R/W; bitpos: [13:0]; default: 0;
* This is the content of command 6. It consists of three parts:
* op_code is the command, 0: RSTART, 1: WRITE, 2: READ, 3: STOP, 4: END.
* Byte_num represents the number of bytes that need to be sent or received.
* ack_check_en, ack_exp and ack are used to control the ACK bit. See I2C cmd
* structure for more
* Information.
*/
uint32_t command6:14;
uint32_t reserved_14:17;
/** command6_done : R/W/SS; bitpos: [31]; default: 0;
* When command 6 is done in I2C Master mode, this bit changes to high level.
*/
uint32_t command6_done:1;
};
uint32_t val;
} lp_i2c_comd6_reg_t;
/** Type of comd7 register
* I2C command register 7
*/
typedef union {
struct {
/** command7 : R/W; bitpos: [13:0]; default: 0;
* This is the content of command 7. It consists of three parts:
* op_code is the command, 0: RSTART, 1: WRITE, 2: READ, 3: STOP, 4: END.
* Byte_num represents the number of bytes that need to be sent or received.
* ack_check_en, ack_exp and ack are used to control the ACK bit. See I2C cmd
* structure for more
* Information.
*/
uint32_t command7:14;
uint32_t reserved_14:17;
/** command7_done : R/W/SS; bitpos: [31]; default: 0;
* When command 7 is done in I2C Master mode, this bit changes to high level.
*/
uint32_t command7_done:1;
};
uint32_t val;
} lp_i2c_comd7_reg_t;
} lp_i2c_command_reg_t;
/** Group: Version register */
@ -1031,14 +869,7 @@ typedef struct lp_i2c_dev_t {
volatile lp_i2c_scl_stop_setup_reg_t scl_stop_setup;
volatile lp_i2c_filter_cfg_reg_t filter_cfg;
volatile lp_i2c_clk_conf_reg_t clk_conf;
volatile lp_i2c_comd0_reg_t comd0;
volatile lp_i2c_comd1_reg_t comd1;
volatile lp_i2c_comd2_reg_t comd2;
volatile lp_i2c_comd3_reg_t comd3;
volatile lp_i2c_comd4_reg_t comd4;
volatile lp_i2c_comd5_reg_t comd5;
volatile lp_i2c_comd6_reg_t comd6;
volatile lp_i2c_comd7_reg_t comd7;
volatile lp_i2c_command_reg_t command[8];
volatile lp_i2c_scl_st_time_out_reg_t scl_st_time_out;
volatile lp_i2c_scl_main_st_time_out_reg_t scl_main_st_time_out;
volatile lp_i2c_scl_sp_conf_reg_t scl_sp_conf;
@ -1050,7 +881,8 @@ typedef struct lp_i2c_dev_t {
volatile lp_i2c_rxfifo_start_addr_reg_t rxfifo_start_addr;
} lp_i2c_dev_t;
extern lp_i2c_dev_t LP_I2C;
// We map the LP_I2C instance to the i2c_dev_t struct for convinience of using the same HAL/LL. See soc/i2c_struct.h
//extern lp_i2c_dev_t LP_I2C;
#ifndef __cplusplus
_Static_assert(sizeof(lp_i2c_dev_t) == 0x184, "Invalid size of lp_i2c_dev_t structure");

View File

@ -42,6 +42,7 @@ typedef enum {
PERIPH_SARADC_MODULE,
PERIPH_TEMPSENSOR_MODULE,
PERIPH_REGDMA_MODULE,
PERIPH_LP_I2C0_MODULE,
/* Peripherals clock managed by the modem_clock driver must be listed last in the enumeration */
PERIPH_WIFI_MODULE,
PERIPH_BT_MODULE,

View File

@ -70,6 +70,7 @@
#define SOC_PAU_SUPPORTED 1
#define SOC_LP_TIMER_SUPPORTED 1
#define SOC_LP_AON_SUPPORTED 1
#define SOC_LP_I2C_SUPPORTED 1
/*-------------------------- XTAL CAPS ---------------------------------------*/
#define SOC_XTAL_SUPPORT_40M 1
@ -218,6 +219,12 @@
#define SOC_I2C_SUPPORT_XTAL (1)
#define SOC_I2C_SUPPORT_RTC (1)
/*-------------------------- LP_I2C CAPS -------------------------------------*/
// ESP32-C6 has 1 LP_I2C
#define SOC_LP_I2C_NUM (1U)
#define SOC_LP_I2C_FIFO_LEN (16) /*!< LP_I2C hardware FIFO depth */
/*-------------------------- I2S CAPS ----------------------------------------*/
#define SOC_I2S_NUM (1U)
#define SOC_I2S_HW_VERSION_2 (1)

View File

@ -42,7 +42,8 @@ if(CONFIG_ULP_COPROC_TYPE_LP_CORE)
list(APPEND srcs
"lp_core/lp_core.c"
"lp_core/shared/ulp_lp_core_memory_shared.c"
"lp_core/shared/ulp_lp_core_lp_timer_shared.c")
"lp_core/shared/ulp_lp_core_lp_timer_shared.c"
"lp_core/lp_core_i2c.c")
list(APPEND includes
"lp_core/include"

View File

@ -83,7 +83,8 @@ elseif(ULP_COCPU_IS_LP_CORE)
"${IDF_PATH}/components/ulp/lp_core/shared/ulp_lp_core_memory_shared.c"
"${IDF_PATH}/components/ulp/lp_core/shared/ulp_lp_core_lp_timer_shared.c"
"${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_startup.c"
"${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_utils.c")
"${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_utils.c"
"${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_i2c.c")
target_link_options(${ULP_APP_NAME} PRIVATE "-nostartfiles")
target_link_options(${ULP_APP_NAME} PRIVATE "-Wl,--no-warn-rwx-segments")

View File

@ -0,0 +1,77 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stdbool.h>
#include "hal/i2c_types.h"
#include "hal/gpio_types.h"
#include "esp_err.h"
typedef struct {
gpio_num_t sda_io_num; // GPIO pin for SDA signal. Only GPIO#6 can be used as the SDA pin.
gpio_num_t scl_io_num; // GPIO pin for SCL signal. Only GPIO#7 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 scl_pullup_en; // SCL line enable internal pullup. Can be configured if external pullup is not used.
} lp_core_i2c_pin_cfg_t;
typedef struct {
uint32_t clk_speed_hz; // LP I2C clock speed for master mode
} lp_core_i2c_timing_cfg_t;
typedef struct {
lp_core_i2c_pin_cfg_t i2c_pin_cfg; // LP I2C pin configuration
lp_core_i2c_timing_cfg_t i2c_timing_cfg; // LP I2C timing configuration
soc_periph_lp_i2c_clk_src_t i2c_src_clk; // LP I2C source clock type
} lp_core_i2c_cfg_t;
/* Default LP I2C GPIO settings */
#define LP_I2C_DEFAULT_GPIO_CONFIG() \
.i2c_pin_cfg.sda_io_num = GPIO_NUM_6, \
.i2c_pin_cfg.scl_io_num = GPIO_NUM_7, \
.i2c_pin_cfg.sda_pullup_en = true, \
.i2c_pin_cfg.scl_pullup_en = true, \
/* LP I2C fast mode config. Max SCL freq of 400 KHz. */
#define LP_I2C_FAST_MODE_CONFIG() \
.i2c_timing_cfg.clk_speed_hz = 400000, \
/* LP I2C standard mode config. Max SCL freq of 100 KHz. */
#define LP_I2C_STANDARD_MODE_CONFIG() \
.i2c_timing_cfg.clk_speed_hz = 100000, \
#define LP_I2C_DEFAULT_SRC_CLK() \
.i2c_src_clk = LP_I2C_SCLK_LP_FAST, \
/* Default LP I2C GPIO settings and timing parametes */
#define LP_CORE_I2C_DEFAULT_CONFIG() \
{ \
LP_I2C_DEFAULT_GPIO_CONFIG() \
LP_I2C_FAST_MODE_CONFIG() \
LP_I2C_DEFAULT_SRC_CLK() \
}
/**
* @brief Initialize and configure the LP I2C for use by the LP core
* Currently LP I2C can only be used in master mode
*
* @param cfg Configuration parameters
* @return esp_err_t ESP_OK when successful
*
* @note The internal pull-up resistors for SDA and SCL pins, if enabled, will
* provide a weak pull-up value of about 30-50 kOhm. Users are adviced to enable
* external pull-ups for better performance at higher SCL frequencies.
*/
esp_err_t lp_core_i2c_master_init(i2c_port_t lp_i2c_num, const lp_core_i2c_cfg_t *cfg);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,104 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stdbool.h>
#include "hal/i2c_types.h"
#include "esp_err.h"
/**
* @brief Read from I2C device
*
* @note The LP I2C must have been initialized from the HP core using the lp_core_i2c_master_init() API
* before invoking this API.
*
* @param lp_i2c_num LP I2C port number
* @param device_addr I2C device address (7-bit)
* @param data_rd Buffer to hold data to be read
* @param size Size of data to be read in bytes
* @param timeout Operation timeout in CPU cycles. Set to -1 to wait forever.
*
* @return esp_err_t ESP_OK when successful
*
* @note the LP I2C does not support 10-bit I2C device addresses.
* @note the LP I2C port number is ignored at the moment.
*/
esp_err_t lp_core_i2c_master_read_from_device(i2c_port_t lp_i2c_num, uint16_t device_addr,
uint8_t *data_rs, size_t size,
int32_t ticks_to_wait);
/**
* @brief Write to I2C device
*
* @note The LP I2C must have been initialized from the HP core using the lp_core_i2c_master_init() API
* before invoking this API.
*
* @param lp_i2c_num LP I2C port number
* @param device_addr I2C device address (7-bit)
* @param data_wr Buffer which holds the data to be written
* @param size Size of data to be written in bytes
* @param timeout Operation timeout in CPU cycles. Set to -1 to wait forever.
*
* @return esp_err_t ESP_OK when successful
*
* @note the LP I2C does not support 10-bit I2C device addresses.
* @note the LP I2C port number is ignored at the moment.
*/
esp_err_t lp_core_i2c_master_write_to_device(i2c_port_t lp_i2c_num, uint16_t device_addr,
const uint8_t *data_wr, size_t size,
int32_t ticks_to_wait);
/**
* @brief Write to and then read from an I2C device in a single transaction
*
* @note The LP I2C must have been initialized from the HP core using the lp_core_i2c_master_init() API
* before invoking this API.
*
* @param lp_i2c_num LP I2C port number
* @param device_addr I2C device address (7-bit)
* @param data_wr Buffer which holds the data to be written
* @param write_size Size of data to be written in bytes
* @param data_rd Buffer to hold data to be read
* @param read_size Size of data to be read in bytes
* @param timeout Operation timeout in CPU cycles. Set to -1 to wait forever.
*
* @return esp_err_t ESP_OK when successful
*
* @note the LP I2C does not support 10-bit I2C device addresses.
* @note the LP I2C port number is ignored at the moment.
*/
esp_err_t lp_core_i2c_master_write_read_device(i2c_port_t lp_i2c_num, uint16_t device_addr,
const uint8_t *data_wr, size_t write_size,
uint8_t *data_rd, size_t read_size,
int32_t ticks_to_wait);
/**
* @brief Enable or disable ACK checking by the LP_I2C controller during write operations
*
* When ACK checking is enabled, the hardware will check the ACK/NACK level received during write
* operations against the expected ACK/NACK level. If the received ACK/NACK level does not match the
* expected ACK/NACK level then the hardware will generate the I2C_NACK_INT and a STOP condition
* will be generated to stop the data transfer.
*
* @note ACK checking is enabled by default
*
* @param lp_i2c_num LP I2C port number
* @param ack_check_en true: enable ACK check
* false: disable ACK check
*
* @note the LP I2C port number is ignored at the moment.
*/
void lp_core_i2c_master_set_ack_check_en(i2c_port_t lp_i2c_num, bool ack_check_en);
#ifdef __cplusplus
}
#endif

View File

@ -23,7 +23,6 @@ extern "C" {
*/
void ulp_lp_core_wakeup_main_processor(void);
/**
* @brief Makes the co-processor busy wait for a certain number of microseconds
*
@ -31,6 +30,12 @@ void ulp_lp_core_wakeup_main_processor(void);
*/
void ulp_lp_core_delay_us(uint32_t us);
/**
* @brief Makes the co-processor busy wait for a certain number of cycles
*
* @param cycles Number of cycles to busy-wait for
*/
void ulp_lp_core_delay_cycles(uint32_t cycles);
/**
* @brief Finishes the ULP program and powers down the ULP

View File

@ -0,0 +1,479 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "ulp_lp_core_i2c.h"
#include "ulp_lp_core_utils.h"
#include "soc/lp_i2c_reg.h"
#include "soc/i2c_struct.h"
#include "hal/i2c_ll.h"
#define LP_I2C_FIFO_LEN SOC_LP_I2C_FIFO_LEN
#define LP_I2C_READ_MODE I2C_MASTER_READ
#define LP_I2C_WRITE_MODE I2C_MASTER_WRITE
#define LP_I2C_ACK I2C_MASTER_ACK
#define LP_I2C_NACK I2C_MASTER_NACK
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
/* I2C LL context */
i2c_dev_t *dev = I2C_LL_GET_HW(LP_I2C_NUM_0);
/* ACK check enable control variable. Enabled by default */
static bool s_ack_check_en = true;
/*
* The LP I2C controller uses the LP I2C HW command registers to perform read/write operations.
* The cmd registers have the following format:
*
* 31 30:14 13:11 10 9 8 7:0
* |----------|----------|---------|---------|----------|------------|---------|
* | CMD_DONE | Reserved | OPCODE |ACK Value|ACK Expect|ACK Check En|Byte Num |
* |----------|----------|---------|---------|----------|------------|---------|
*/
static void lp_core_i2c_format_cmd(uint32_t cmd_idx, uint8_t op_code, uint8_t ack_val,
uint8_t ack_expected, uint8_t ack_check_en, uint8_t byte_num)
{
if (cmd_idx >= sizeof(dev->command)) {
/* We only have limited HW command registers.
* Although unlikely, make sure that we do not write to an out of bounds index.
*/
return;
}
/* Form new command */
i2c_ll_hw_cmd_t hw_cmd = {
.done = 0, // CMD Done
.op_code = op_code, // Opcode
.ack_val = ack_val, // ACK bit sent by I2C controller during READ.
// Ignored during RSTART, STOP, END and WRITE cmds.
.ack_exp = ack_expected, // ACK bit expected by I2C controller during WRITE.
// Ignored during RSTART, STOP, END and READ cmds.
.ack_en = ack_check_en, // I2C controller verifies that the ACK bit sent by the
// slave device matches the ACK expected bit during WRITE.
// Ignored during RSTART, STOP, END and READ cmds.
.byte_num = byte_num, // Byte Num
};
/* Write new command to cmd register */
i2c_ll_write_cmd_reg(dev, hw_cmd, cmd_idx);
}
static inline esp_err_t lp_core_i2c_wait_for_interrupt(uint32_t intr_mask, int32_t ticks_to_wait)
{
uint32_t intr_status = 0;
uint32_t to = 0;
while (1) {
i2c_ll_get_intr_mask(dev, &intr_status);
if (intr_status & intr_mask) {
if (intr_status & LP_I2C_NACK_INT_ST) {
/* The ACK/NACK received during a WRITE operation does not match the expected ACK/NACK level
* Abort and return an error.
*/
i2c_ll_clear_intr_mask(dev, intr_mask);
return ESP_ERR_INVALID_RESPONSE;
} else if (intr_status & LP_I2C_TRANS_COMPLETE_INT_ST_M) {
/* Transaction complete.
* Disable and clear interrupt bits and break
*/
i2c_ll_disable_intr_mask(dev, intr_mask);
i2c_ll_clear_intr_mask(dev, intr_mask);
break;
} else {
/* We received an I2C_END_DETECT_INT.
* This means we are not yet done with the transaction.
* Simply clear the interrupt bit and break.
*/
i2c_ll_clear_intr_mask(dev, intr_mask);
break;
}
break;
}
if (ticks_to_wait > -1) {
/* If the ticks_to_wait value is not -1, keep track of ticks and
* break from the loop once the timeout is reached.
*/
ulp_lp_core_delay_cycles(1);
to++;
if (to >= ticks_to_wait) {
/* Disable and clear interrupt bits */
i2c_ll_disable_intr_mask(dev, intr_mask);
i2c_ll_clear_intr_mask(dev, intr_mask);
return ESP_ERR_TIMEOUT;
}
}
}
/* We reach here only if we are in a good state */
return ESP_OK;
}
static inline void lp_core_i2c_config_device_addr(uint32_t cmd_idx, uint16_t device_addr, uint32_t rw_mode, uint8_t *addr_len)
{
uint8_t data_byte = 0;
uint8_t data_len = 0;
/* 7-bit addressing mode. We do not support 10-bit addressing mode yet (IDF-7364) */
// Write the device address + R/W mode in the first Tx FIFO slot
data_byte = (uint8_t)(((device_addr & 0xFF) << 1) | (rw_mode << 0));
i2c_ll_write_txfifo(dev, &data_byte, 1);
data_len++;
/* Update the HW command register. Expect an ACK from the device */
lp_core_i2c_format_cmd(cmd_idx, I2C_LL_CMD_WRITE, 0, LP_I2C_ACK, s_ack_check_en, data_len);
/* Return the address length in bytes */
*addr_len = data_len;
}
void lp_core_i2c_master_set_ack_check_en(i2c_port_t lp_i2c_num, bool ack_check_en)
{
(void)lp_i2c_num;
s_ack_check_en = ack_check_en;
}
esp_err_t lp_core_i2c_master_read_from_device(i2c_port_t lp_i2c_num, uint16_t device_addr,
uint8_t *data_rd, size_t size,
int32_t ticks_to_wait)
{
(void)lp_i2c_num;
esp_err_t ret = ESP_OK;
uint32_t cmd_idx = 0;
if (size == 0) {
// Quietly return
return ESP_OK;
} else if (size > UINT8_MAX) {
// HW register only has an 8-bit byte-num field
return ESP_ERR_INVALID_SIZE;
}
/* Execute RSTART command to send the START bit */
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_RESTART, 0, 0, 0, 0);
/* Write device addr and update the HW command register */
uint8_t addr_len = 0;
lp_core_i2c_config_device_addr(cmd_idx++, device_addr, LP_I2C_READ_MODE, &addr_len);
/* Enable trans complete interrupt and end detect interrupt for read/write operation */
uint32_t intr_mask = (1 << LP_I2C_TRANS_COMPLETE_INT_ST_S) | (1 << LP_I2C_END_DETECT_INT_ST_S);
i2c_ll_enable_intr_mask(dev, intr_mask);
/* Read data */
uint32_t fifo_size = 0;
uint32_t data_idx = 0;
int32_t remaining_bytes = size;
/* The data is received in sequential slots of the Rx FIFO.
* We must account for FIFO wraparound in case the length of data being received is greater than LP_I2C_FIFO_LEN.
*/
while (remaining_bytes > 0) {
/* Select the amount of data that fits in the Rx FIFO */
fifo_size = MIN(remaining_bytes, LP_I2C_FIFO_LEN);
/* Update the number of bytes remaining to be read */
remaining_bytes -= fifo_size;
/* Update HW command register to read bytes */
if (fifo_size == 1) {
/* Read 1 byte and send NACK */
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_READ, LP_I2C_NACK, 0, 0, 1);
/* STOP */
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_STOP, 0, 0, 0, 0);
} else if ((fifo_size > 1) && (remaining_bytes == 0)) {
/* This means it is the last transaction.
* Read fifo_size - 1 bytes and send ACKs
*/
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_READ, LP_I2C_ACK, 0, 0, fifo_size - 1);
/* Read last byte and send NACK */
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_READ, LP_I2C_NACK, 0, 0, 1);
/* STOP */
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_STOP, 0, 0, 0, 0);
} else {
/* This means we have to read data more than what can fit in the Rx FIFO.
* Read fifo_size bytes and send ACKs
*/
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_READ, LP_I2C_ACK, 0, 0, fifo_size);
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_END, 0, 0, 0, 0);
cmd_idx = 0;
}
/* Initiate I2C transfer */
i2c_ll_update(dev);
i2c_ll_trans_start(dev);
/* Wait for the transfer to complete */
ret = lp_core_i2c_wait_for_interrupt(intr_mask, ticks_to_wait);
if (ret != ESP_OK) {
/* Transaction error. Abort. */
return ret;
}
/* Read Rx FIFO */
i2c_ll_read_rxfifo(dev, &data_rd[data_idx], fifo_size);
/* Update data_idx */
data_idx += fifo_size;
}
return ret;
}
esp_err_t lp_core_i2c_master_write_to_device(i2c_port_t lp_i2c_num, uint16_t device_addr,
const uint8_t *data_wr, size_t size,
int32_t ticks_to_wait)
{
(void)lp_i2c_num;
esp_err_t ret = ESP_OK;
uint32_t cmd_idx = 0;
if (size == 0) {
// Quietly return
return ESP_OK;
} else if (size > UINT8_MAX) {
// HW register only has an 8-bit byte-num field
return ESP_ERR_INVALID_SIZE;
}
/* If SCL is busy, reset the Master FSM */
if (i2c_ll_is_bus_busy(dev)) {
i2c_ll_master_fsm_rst(dev);
}
/* Reset the Tx and Rx FIFOs */
i2c_ll_txfifo_rst(dev);
i2c_ll_rxfifo_rst(dev);
/* Execute RSTART command to send the START bit */
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_RESTART, 0, 0, 0, 0);
/* Write device addr and update the HW command register */
uint8_t addr_len = 0;
lp_core_i2c_config_device_addr(cmd_idx++, device_addr, LP_I2C_WRITE_MODE, &addr_len);
/* Enable trans complete interrupt and end detect interrupt for read/write operation */
uint32_t intr_mask = (1 << LP_I2C_TRANS_COMPLETE_INT_ST_S) | (1 << LP_I2C_END_DETECT_INT_ST_S);
if (s_ack_check_en) {
/* Enable LP_I2C_NACK_INT to check for ACK errors */
intr_mask |= (1 << LP_I2C_NACK_INT_ST_S);
}
i2c_ll_enable_intr_mask(dev, intr_mask);
/* Write data */
uint32_t fifo_available = LP_I2C_FIFO_LEN - addr_len; // Initially, 1 or 2 fifo slots are taken by the device address
uint32_t fifo_size = 0;
uint32_t data_idx = 0;
int32_t remaining_bytes = size;
/* The data to be sent must occupy sequential slots of the Tx FIFO.
* We must account for FIFO wraparound in case the length of data being sent is greater than LP_I2C_FIFO_LEN.
*/
while (remaining_bytes > 0) {
/* Select the amount of data that fits in the Tx FIFO */
fifo_size = MIN(remaining_bytes, fifo_available);
/* Update the number of bytes remaining to be sent */
remaining_bytes -= fifo_size;
/* Write data to the Tx FIFO and update the HW command register. Expect ACKs from the device */
i2c_ll_write_txfifo(dev, &data_wr[data_idx], fifo_size);
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_WRITE, 0, LP_I2C_ACK, s_ack_check_en, fifo_size);
if (remaining_bytes == 0) {
/* This means it is the last transaction. Insert a Stop command. */
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_STOP, 0, 0, 0, 0);
} else {
/* This means we have to send more than what can fit in the Tx FIFO. Insert an End command. */
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_END, 0, 0, 0, 0);
cmd_idx = 0;
}
/* Initiate I2C transfer */
i2c_ll_update(dev);
i2c_ll_trans_start(dev);
/* Wait for the transfer to complete */
ret = lp_core_i2c_wait_for_interrupt(intr_mask, ticks_to_wait);
if (ret != ESP_OK) {
/* Transaction error. Abort. */
return ret;
}
/* Update data_idx */
data_idx += fifo_size;
/* We now have the full fifo available for writing */
fifo_available = LP_I2C_FIFO_LEN;
}
return ret;
}
esp_err_t lp_core_i2c_master_write_read_device(i2c_port_t lp_i2c_num, uint16_t device_addr,
const uint8_t *data_wr, size_t write_size,
uint8_t *data_rd, size_t read_size,
int32_t ticks_to_wait)
{
(void)lp_i2c_num;
esp_err_t ret = ESP_OK;
uint32_t cmd_idx = 0;
if ((write_size == 0) || (read_size == 0)) {
// Quietly return
return ESP_OK;
} else if ((write_size > UINT8_MAX) || (read_size > UINT8_MAX)) {
// HW register only has an 8-bit byte-num field
return ESP_ERR_INVALID_SIZE;
}
/* If SCL is busy, reset the Master FSM */
if (i2c_ll_is_bus_busy(dev)) {
i2c_ll_master_fsm_rst(dev);
}
/* Reset the Tx and Rx FIFOs */
i2c_ll_txfifo_rst(dev);
i2c_ll_rxfifo_rst(dev);
/* Enable trans complete interrupt and end detect interrupt for read/write operation */
uint32_t intr_mask = (1 << LP_I2C_TRANS_COMPLETE_INT_ST_S) | (1 << LP_I2C_END_DETECT_INT_ST_S);
if (s_ack_check_en) {
/* Enable LP_I2C_NACK_INT to check for ACK errors */
intr_mask |= (1 << LP_I2C_NACK_INT_ST_S);
}
i2c_ll_enable_intr_mask(dev, intr_mask);
/* Execute RSTART command to send the START bit */
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_RESTART, 0, 0, 0, 0);
/* Write device addr and update the HW command register */
uint8_t addr_len = 0;
lp_core_i2c_config_device_addr(cmd_idx++, device_addr, LP_I2C_WRITE_MODE, &addr_len);
/* Write data */
uint32_t fifo_available = LP_I2C_FIFO_LEN - addr_len; // Initially, 1 or 2 fifo slots are taken by the device address
uint32_t fifo_size = 0;
uint32_t data_idx = 0;
int32_t remaining_bytes = write_size;
/* The data to be sent must occupy sequential slots of the Tx FIFO.
* We must account for FIFO wraparound in case the length of data being sent is greater than LP_I2C_FIFO_LEN.
*/
while (remaining_bytes > 0) {
/* Select the amount of data that fits in the Tx FIFO */
fifo_size = MIN(remaining_bytes, fifo_available);
/* Update the number of bytes remaining to be sent */
remaining_bytes -= fifo_size;
/* Write data to the Tx FIFO and update the HW command register. Expect ACKs from the device */
i2c_ll_write_txfifo(dev, &data_wr[data_idx], fifo_size);
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_WRITE, 0, LP_I2C_ACK, s_ack_check_en, fifo_size);
if (remaining_bytes) {
/* This means we have to send more than what can fit in the Tx FIFO. Insert an End command. */
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_END, 0, 0, 0, 0);
cmd_idx = 0;
}
/* Initiate I2C transfer */
i2c_ll_update(dev);
i2c_ll_trans_start(dev);
/* Wait for the transfer to complete */
ret = lp_core_i2c_wait_for_interrupt(intr_mask, ticks_to_wait);
if (ret != ESP_OK) {
/* Transaction error. Abort. */
return ret;
}
/* Update data_idx */
data_idx += fifo_size;
/* We now have the full fifo available for writing */
fifo_available = LP_I2C_FIFO_LEN;
}
/* Reset command index */
cmd_idx = 0;
/* Execute RSTART command again to send a START condition for the read operation */
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_RESTART, 0, 0, 0, 0);
/* Write device addr again in read mode */
lp_core_i2c_config_device_addr(cmd_idx++, device_addr, LP_I2C_READ_MODE, &addr_len);
/* Read data */
fifo_size = 0;
data_idx = 0;
remaining_bytes = read_size;
/* The data is received in sequential slots of the Rx FIFO.
* We must account for FIFO wraparound in case the length of data being received is greater than LP_I2C_FIFO_LEN.
*/
while (remaining_bytes > 0) {
/* Select the amount of data that fits in the Rx FIFO */
fifo_size = MIN(remaining_bytes, LP_I2C_FIFO_LEN);
/* Update the number of bytes remaining to be read */
remaining_bytes -= fifo_size;
/* Update HW command register to read bytes */
if (fifo_size == 1) {
/* Read 1 byte and send NACK */
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_READ, LP_I2C_NACK, 0, 0, 1);
/* STOP */
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_STOP, 0, 0, 0, 0);
} else if ((fifo_size > 1) && (remaining_bytes == 0)) {
/* This means it is the last transaction.
* Read fifo_size - 1 bytes and send ACKs
*/
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_READ, LP_I2C_ACK, 0, 0, fifo_size - 1);
/* Read last byte and send NACK */
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_READ, LP_I2C_NACK, 0, 0, 1);
/* STOP */
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_STOP, 0, 0, 0, 0);
} else {
/* This means we have to read data more than what can fit in the Rx FIFO.
* Read fifo_size bytes and send ACKs
*/
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_READ, LP_I2C_ACK, 0, 0, fifo_size);
lp_core_i2c_format_cmd(cmd_idx++, I2C_LL_CMD_END, 0, 0, 0, 0);
cmd_idx = 0;
}
/* Initiate I2C transfer */
i2c_ll_update(dev);
i2c_ll_trans_start(dev);
/* Wait for the transfer to complete */
ret = lp_core_i2c_wait_for_interrupt(intr_mask, ticks_to_wait);
if (ret != ESP_OK) {
/* Transaction error. Abort. */
return ret;
}
/* Read Rx FIFO */
i2c_ll_read_rxfifo(dev, &data_rd[data_idx], fifo_size);
/* Update data_idx */
data_idx += fifo_size;
}
return ret;
}

View File

@ -42,6 +42,21 @@ void ulp_lp_core_delay_us(uint32_t us)
}
}
/**
* @brief Makes the co-processor busy wait for a certain number of cycles
*
* @param cycles Number of cycles to busy-wait for
*/
void ulp_lp_core_delay_cycles(uint32_t cycles)
{
uint32_t start = RV_READ_CSR(mcycle);
uint32_t end = cycles;
while ((RV_READ_CSR(mcycle) - start) < end) {
/* nothing to do */
}
}
void ulp_lp_core_halt(void)
{
REG_SET_FIELD(PMU_LP_CPU_PWR1_REG, PMU_LP_CPU_SLEEP_REQ, 1);

View File

@ -0,0 +1,150 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "lp_core_i2c.h"
#include "esp_check.h"
#include "hal/i2c_hal.h"
#include "soc/lp_io_struct.h"
#include "driver/rtc_io.h"
#include "soc/rtc_io_channel.h"
#include "esp_private/esp_clk_tree_common.h"
#include "esp_private/periph_ctrl.h"
static const char *LPI2C_TAG = "lp_core_i2c";
#define LP_I2C_FILTER_CYC_NUM_DEF (7)
/* I2C LL context */
i2c_hal_context_t i2c_hal;
/* Use the register structure to access LP_IO module registers */
lp_io_dev_t *lp_io_dev = &LP_IO;
static esp_err_t lp_i2c_gpio_is_cfg_valid(gpio_num_t sda_io_num, gpio_num_t scl_io_num)
{
/* Verify that the SDA and SCL GPIOs are valid LP IO (RTCIO) pins */
ESP_RETURN_ON_ERROR(!rtc_gpio_is_valid_gpio(sda_io_num), LPI2C_TAG, "LP I2C SDA GPIO invalid");
ESP_RETURN_ON_ERROR(!rtc_gpio_is_valid_gpio(scl_io_num), LPI2C_TAG, "LP I2C SCL GPIO invalid");
/* Verify that the SDA and SCL line belong to the LP IO Mux I2C function group */
if (sda_io_num != RTCIO_GPIO6_CHANNEL) {
ESP_LOGE(LPI2C_TAG, "SDA pin can only be configured as GPIO#6");
return ESP_ERR_INVALID_ARG;
}
if (scl_io_num != RTCIO_GPIO7_CHANNEL) {
ESP_LOGE(LPI2C_TAG, "SCL pin can only be configured as GPIO#7");
return ESP_ERR_INVALID_ARG;
}
return ESP_OK;
}
static esp_err_t lp_i2c_configure_io(gpio_num_t io_num, bool pullup_en)
{
/* Initialize IO Pin */
ESP_RETURN_ON_ERROR(rtc_gpio_init(io_num), LPI2C_TAG, "LP GPIO Init failed for GPIO %d", io_num);
/* Set direction to input+output */
ESP_RETURN_ON_ERROR(rtc_gpio_set_direction(io_num, RTC_GPIO_MODE_INPUT_OUTPUT), LPI2C_TAG, "LP GPIO Set direction failed for %d", io_num);
/* Disable pulldown on the io pin */
ESP_RETURN_ON_ERROR(rtc_gpio_pulldown_dis(io_num), LPI2C_TAG, "LP GPIO pulldown disable failed for %d", io_num);
/* Enable pullup based on pullup_en flag */
if (pullup_en) {
ESP_RETURN_ON_ERROR(rtc_gpio_pullup_en(io_num), LPI2C_TAG, "LP GPIO pullup enable failed for %d", io_num);
} else {
ESP_RETURN_ON_ERROR(rtc_gpio_pullup_dis(io_num), LPI2C_TAG, "LP GPIO pullup disable failed for %d", io_num);
}
return ESP_OK;
}
static esp_err_t lp_i2c_set_pin(const lp_core_i2c_cfg_t *cfg)
{
gpio_num_t sda_io_num = cfg->i2c_pin_cfg.sda_io_num;
gpio_num_t scl_io_num = cfg->i2c_pin_cfg.scl_io_num;
bool sda_pullup_en = cfg->i2c_pin_cfg.sda_pullup_en;
bool scl_pullup_en = cfg->i2c_pin_cfg.scl_pullup_en;
/* Verify that the LP I2C GPIOs are valid */
ESP_RETURN_ON_ERROR(lp_i2c_gpio_is_cfg_valid(sda_io_num, scl_io_num), LPI2C_TAG, "LP I2C GPIO config invalid");
/* Initialize SDA Pin */
ESP_RETURN_ON_ERROR(lp_i2c_configure_io(sda_io_num, sda_pullup_en), LPI2C_TAG, "LP I2C SDA pin config failed");
/* Initialize SCL Pin */
ESP_RETURN_ON_ERROR(lp_i2c_configure_io(scl_io_num, scl_pullup_en), LPI2C_TAG, "LP I2C SCL pin config failed");
/* Select LP I2C function for the SDA Pin */
lp_io_dev->gpio[sda_io_num].mcu_sel = 1;
/* Select LP I2C function for the SCL Pin */
lp_io_dev->gpio[scl_io_num].mcu_sel = 1;
return ESP_OK;
}
static esp_err_t lp_i2c_config_clk(const lp_core_i2c_cfg_t *cfg)
{
esp_err_t ret = ESP_OK;
uint32_t source_freq = 0;
soc_periph_lp_i2c_clk_src_t source_clk = LP_I2C_SCLK_DEFAULT;
/* Check if we have a valid user configured source clock */
soc_periph_lp_i2c_clk_src_t clk_srcs[] = SOC_LP_I2C_CLKS;
for (int i = 0; i < sizeof(clk_srcs)/sizeof(clk_srcs[0]); i++) {
if (clk_srcs[i] == cfg->i2c_src_clk) {
/* Clock source matches. Override the source clock type with the user configured value */
source_clk = cfg->i2c_src_clk;
break;
}
}
/* Fetch the clock source fequency */
ESP_RETURN_ON_ERROR(esp_clk_tree_src_get_freq_hz(source_clk, ESP_CLK_TREE_SRC_FREQ_PRECISION_APPROX, &source_freq), LPI2C_TAG, "Invalid LP I2C source clock selected");
/* Verify that the I2C_SCLK operates at a frequency 20 times larger than the requested SCL bus frequency */
ESP_RETURN_ON_FALSE(cfg->i2c_timing_cfg.clk_speed_hz * 20 <= source_freq, ESP_ERR_INVALID_ARG, LPI2C_TAG, "I2C_SCLK frequency (%"PRId32") should operate at a frequency at least 20 times larger than the I2C SCL bus frequency (%"PRId32")", source_freq, cfg->i2c_timing_cfg.clk_speed_hz);
/* Set LP I2C source clock */
lp_i2c_ll_set_source_clk(i2c_hal.dev, source_clk);
/* Configure LP I2C timing paramters. source_clk is ignored for LP_I2C in this call */
i2c_hal_set_bus_timing(&i2c_hal, (i2c_clock_source_t)source_clk, cfg->i2c_timing_cfg.clk_speed_hz, source_freq);
return ret;
}
esp_err_t lp_core_i2c_master_init(i2c_port_t lp_i2c_num, const lp_core_i2c_cfg_t *cfg)
{
/* Verify LP_I2C port number */
ESP_RETURN_ON_FALSE((lp_i2c_num == LP_I2C_NUM_0), ESP_ERR_INVALID_ARG, LPI2C_TAG, "LP I2C port number incorrect");
/* Verify that the input cfg param is valid */
ESP_RETURN_ON_FALSE(cfg, ESP_ERR_INVALID_ARG, LPI2C_TAG, "LP I2C configuration is NULL");
/* Configure LP I2C GPIOs */
ESP_RETURN_ON_ERROR(lp_i2c_set_pin(cfg), LPI2C_TAG, "Failed to configure LP I2C GPIOs");
/* Initialize LP I2C HAL */
i2c_hal_init(&i2c_hal, lp_i2c_num);
/* Enable LP I2C controller clock */
periph_module_enable(PERIPH_LP_I2C0_MODULE);
/* Initialize LP I2C Master mode */
i2c_hal_master_init(&i2c_hal);
/* Configure LP I2C clock and timing paramters */
ESP_RETURN_ON_ERROR(lp_i2c_config_clk(cfg), LPI2C_TAG, "Failed to configure LP I2C source clock");
/* Enable SDA and SCL filtering. This configuration matches the HP I2C filter config */
i2c_ll_set_filter(i2c_hal.dev, LP_I2C_FILTER_CYC_NUM_DEF);
/* Synchronize the config register values to the LP I2C peripheral clock */
i2c_ll_update(i2c_hal.dev);
return ESP_OK;
}

View File

@ -90,6 +90,10 @@ examples/system/light_sleep:
temporary: true
reason: target(s) not supported yet
examples/system/lp_core/lp_i2c:
enable:
- if: SOC_LP_I2C_SUPPORTED == 1
examples/system/ota/advanced_https_ota:
disable:
- if: IDF_TARGET == "esp32h2"

View File

@ -0,0 +1,7 @@
# This is the project CMakeLists.txt file for the test subproject
cmake_minimum_required(VERSION 3.16)
list(APPEND SDKCONFIG_DEFAULTS "sdkconfig.defaults")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(lp_i2c_example)

View File

@ -0,0 +1,63 @@
| Supported Targets | ESP32-C6 |
| ----------------- | -------- |
# LP I2C Example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
## Overview
This example demonstrates basic usage of the LP I2C driver from the LP core by reading to and writing from a sensor connected over I2C.
## How to use example
### Hardware Required
To run this example, you should have a ESP32-C6 based development board as well as a BH1750 sensor. BH1750 is an ambient light sensor. More information about it can be found in the [BH1750 datasheet](https://www.mouser.com/datasheet/2/348/bh1750fvi-e-186247.pdf).
#### Pin Assignment:
**Note:** The following pin assignments are used by default.
| | SDA | SCL |
| ----------------------- | ------| ------|
| ESP32-C6 LP I2C Master | GPIO6 | GPIO7 |
| BH1750 Sensor | SDA | SCL |
**Note:** There's no need to add an external pull-up resistors for SDA/SCL pin, because the driver enables the internal pull-up resistors.
### Build and Flash
Enter `idf.py -p PORT flash monitor` to build, flash and monitor the project.
(To exit the serial monitor, type ``Ctrl-]``.)
See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
## Example Output
The log output should indicate that the LP core and the LP I2C peripheral have been successfully initialized. The main CPU would then enter deep sleep mode. The LP Core should wakeup the main CPU if the Light Intensity Value breaches the set thresholds.
```bash
Not an LP core wakeup. Cause = 0
Initializing...
LP core loaded with firmware successfully
LP I2C initialized successfully
Entering deep sleep...
(When the BH1750 sensor is shielded from a light source, the Lux value should be smaller and the LP core should wakeup the main CPU)
LP core woke up the main CPU
Lux = 3
Entering deep sleep...
(When the BH1750 sensor is exposed to a direct light source, the Lux value should be larger and the LP core should wakup the main CPU)
LP core woke up the main CPU
Lux = 1222
Entering deep sleep...
```
## Troubleshooting
(For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you as soon as possible.)

View File

@ -0,0 +1,25 @@
# Register the component
idf_component_register(SRCS "lp_i2c_main.c"
INCLUDE_DIRS ""
REQUIRES ulp)
#
# ULP support additions to component CMakeLists.txt.
#
# 1. The LP Core app name must be unique (if multiple components use LP Core).
set(ulp_app_name lp_core_${COMPONENT_NAME})
#
# 2. Specify all C files.
# Files should be placed into a separate directory (in this case, lp_core/),
# which should not be added to COMPONENT_SRCS.
set(ulp_lp_core_sources "lp_core/main.c")
#
# 3. List all the component source files which include automatically
# generated LP Core export file, ${ulp_app_name}.h:
set(ulp_exp_dep_srcs "lp_i2c_main.c")
#
# 4. Call function to build ULP binary and embed in project using the argument
# values above.
ulp_embed_binary(${ulp_app_name} "${ulp_lp_core_sources}" "${ulp_exp_dep_srcs}")

View File

@ -0,0 +1,23 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
/***************************************************
* BH1750 Register Addresses
***************************************************/
#define BH1750_I2C_ADDR 0x23
#define BH1750_POWER_ON 0x01
#define BH1750_CONTINUOUS_H_RES_MODE 0x10
#define BH1750_CONTINUOUS_H_RES_MODE2 0x11
#define BH1750_CONTINUOUS_L_RES_MODE 0x13
#define BH1750_ONE_TIME_H_RES_MODE 0x20
#define BH1750_ONE_TIME_H_RES_MODE2 0x21
#define BH1750_ONE_TIME_L_RES_MODE 0x23
/***************************************************
* Example configurations
***************************************************/
#define EXAMPLE_RES_MODE BH1750_CONTINUOUS_H_RES_MODE

View File

@ -0,0 +1,75 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include "ulp_lp_core_i2c.h"
#include "ulp_lp_core_utils.h"
#include "../bh1750_defs.h"
#define LP_I2C_TRANS_TIMEOUT_CYCLES 5000
#define LP_I2C_TRANS_WAIT_FOREVER -1
#define LUX_THRESHOLD_LOW 5
#define LUX_THRESHOLD_HIGH 1000
static uint32_t sensor_on = 0;
static uint32_t res_update_done = 0;
volatile uint32_t lux = 0;
static void bh1750_read()
{
uint8_t data_rd[2];
esp_err_t ret = lp_core_i2c_master_read_from_device(LP_I2C_NUM_0, BH1750_I2C_ADDR, data_rd, sizeof(data_rd), LP_I2C_TRANS_TIMEOUT_CYCLES);
if (ret != ESP_OK) {
// Skip this round of calculation and return
return;
}
/* Calculate light intensity value */
uint16_t level = ((data_rd[0] << 8) | data_rd[1]);
lux = (level * 10) / 12;
/* Wakeup main CPU if the Lux breaches the thresholds */
if (lux < LUX_THRESHOLD_LOW || lux > LUX_THRESHOLD_HIGH) {
ulp_lp_core_wakeup_main_processor();
}
}
int main (void)
{
uint8_t data_wr = 0;
esp_err_t ret = ESP_OK;
while (1) {
if (!sensor_on) {
/* Power ON the sensor */
data_wr = BH1750_POWER_ON;
ret = lp_core_i2c_master_write_to_device(LP_I2C_NUM_0, BH1750_I2C_ADDR, &data_wr, sizeof(data_wr), LP_I2C_TRANS_WAIT_FOREVER);
if (ret != ESP_OK) {
// Bail and try again
continue;
}
sensor_on = 1;
}
if (!res_update_done) {
data_wr = EXAMPLE_RES_MODE;
ret = lp_core_i2c_master_write_to_device(LP_I2C_NUM_0, BH1750_I2C_ADDR, &data_wr, sizeof(data_wr), LP_I2C_TRANS_WAIT_FOREVER);
if (ret != ESP_OK) {
// Bail and try again
continue;
}
res_update_done = 1;
}
/* Read BH1750 sensor data */
bh1750_read();
}
return 0;
}

View File

@ -0,0 +1,77 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include "esp_sleep.h"
#include "lp_core_main.h"
#include "ulp_lp_core.h"
#include "lp_core_i2c.h"
extern const uint8_t lp_core_main_bin_start[] asm("_binary_lp_core_main_bin_start");
extern const uint8_t lp_core_main_bin_end[] asm("_binary_lp_core_main_bin_end");
static void lp_core_init(void)
{
esp_err_t ret = ESP_OK;
ulp_lp_core_cfg_t cfg = {
.wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU,
};
ret = ulp_lp_core_load_binary(lp_core_main_bin_start, (lp_core_main_bin_end - lp_core_main_bin_start));
if (ret != ESP_OK) {
printf("LP Core load failed\n");
abort();
}
ret = ulp_lp_core_run(&cfg);
if (ret != ESP_OK) {
printf("LP Core run failed\n");
abort();
}
printf("LP core loaded with firmware successfully\n");
}
static void lp_i2c_init(void)
{
esp_err_t ret = ESP_OK;
/* Initialize LP I2C with default configuration */
const lp_core_i2c_cfg_t i2c_cfg = LP_CORE_I2C_DEFAULT_CONFIG();
ret = lp_core_i2c_master_init(LP_I2C_NUM_0, &i2c_cfg);
if (ret != ESP_OK) {
printf("LP I2C init failed\n");
abort();
}
printf("LP I2C initialized successfully\n");
}
void app_main(void)
{
esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
if (cause != ESP_SLEEP_WAKEUP_ULP) {
printf("Not an LP core wakeup. Cause = %d\n", cause);
printf("Initializing...\n");
/* Initialize LP_I2C from the main processor */
lp_i2c_init();
/* Load LP Core binary and start the coprocessor */
lp_core_init();
} else if (cause == ESP_SLEEP_WAKEUP_ULP) {
printf("LP core woke up the main CPU\n");
printf("Lux = %ld\n", ulp_lux);
}
/* Setup wakeup triggers */
ESP_ERROR_CHECK(esp_sleep_enable_ulp_wakeup());
/* Enter Deep Sleep */
printf("Entering deep sleep...\n");
esp_deep_sleep_start();
}

View File

@ -0,0 +1,10 @@
# Enable LP Core
CONFIG_ULP_COPROC_ENABLED=y
CONFIG_ULP_COPROC_TYPE_LP_CORE=y
CONFIG_ULP_COPROC_RESERVE_MEM=4096
# Set log level to Warning to produce clean output
CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y
CONFIG_BOOTLOADER_LOG_LEVEL=2
CONFIG_LOG_DEFAULT_LEVEL_WARN=y
CONFIG_LOG_DEFAULT_LEVEL=2