From 62a0efdd7c355ba74d840ea573f37033579585ff Mon Sep 17 00:00:00 2001 From: Cao Sen Miao Date: Tue, 16 Jan 2024 18:59:04 +0800 Subject: [PATCH] feat(i2c): Add pure hal i2c master driver example --- components/hal/.build-test-rules.yml | 4 + components/hal/esp32/include/hal/i2c_ll.h | 16 +- components/hal/esp32c2/include/hal/i2c_ll.h | 16 +- components/hal/esp32c3/include/hal/i2c_ll.h | 16 +- components/hal/esp32c6/include/hal/i2c_ll.h | 16 +- components/hal/esp32h2/include/hal/i2c_ll.h | 16 +- components/hal/esp32p4/include/hal/i2c_ll.h | 16 +- components/hal/esp32s2/include/hal/i2c_ll.h | 16 +- components/hal/esp32s3/include/hal/i2c_ll.h | 16 +- .../hal/test_apps/hal_i2c/CMakeLists.txt | 6 + components/hal/test_apps/hal_i2c/README.md | 55 +++ .../hal_i2c/components/hal_i2c/CMakeLists.txt | 2 + .../hal_i2c/components/hal_i2c/hal_i2c.c | 367 ++++++++++++++++++ .../hal_i2c/components/hal_i2c/hal_i2c.h | 84 ++++ .../hal/test_apps/hal_i2c/main/CMakeLists.txt | 4 + .../test_apps/hal_i2c/main/Kconfig.projbuild | 23 ++ .../hal/test_apps/hal_i2c/main/hal_i2c_main.c | 41 ++ 17 files changed, 706 insertions(+), 8 deletions(-) create mode 100644 components/hal/test_apps/hal_i2c/CMakeLists.txt create mode 100644 components/hal/test_apps/hal_i2c/README.md create mode 100644 components/hal/test_apps/hal_i2c/components/hal_i2c/CMakeLists.txt create mode 100644 components/hal/test_apps/hal_i2c/components/hal_i2c/hal_i2c.c create mode 100644 components/hal/test_apps/hal_i2c/components/hal_i2c/hal_i2c.h create mode 100644 components/hal/test_apps/hal_i2c/main/CMakeLists.txt create mode 100644 components/hal/test_apps/hal_i2c/main/Kconfig.projbuild create mode 100644 components/hal/test_apps/hal_i2c/main/hal_i2c_main.c diff --git a/components/hal/.build-test-rules.yml b/components/hal/.build-test-rules.yml index a360855ecc..00c6033be3 100644 --- a/components/hal/.build-test-rules.yml +++ b/components/hal/.build-test-rules.yml @@ -1,3 +1,7 @@ +components/hal/test_apps/hal_i2c: + disable: + - if: SOC_I2C_SUPPORTED != 1 + components/hal/test_apps/hal_utils: enable: - if: IDF_TARGET == "linux" diff --git a/components/hal/esp32/include/hal/i2c_ll.h b/components/hal/esp32/include/hal/i2c_ll.h index 9ca1b16b2f..19884d4b89 100644 --- a/components/hal/esp32/include/hal/i2c_ll.h +++ b/components/hal/esp32/include/hal/i2c_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -757,6 +757,20 @@ static inline void i2c_ll_slave_clear_stretch(i2c_dev_t *dev) // Not supported on esp32 } +/** + * @brief Check if i2c command is done. + * + * @param hw Beginning address of the peripheral registers + * @param cmd_idx The index of the command register, must be less than 16 + * + * @return True if the `cmd_idx` command is done. Otherwise false. + */ +__attribute__((always_inline)) +static inline bool i2c_ll_master_is_cmd_done(i2c_dev_t *hw, int cmd_idx) +{ + return hw->command[cmd_idx].done; +} + //////////////////////////////////////////Deprecated Functions////////////////////////////////////////////////////////// /////////////////////////////The following functions are only used by the legacy driver///////////////////////////////// /////////////////////////////They might be removed in the next major release (ESP-IDF 6.0)////////////////////////////// diff --git a/components/hal/esp32c2/include/hal/i2c_ll.h b/components/hal/esp32c2/include/hal/i2c_ll.h index 44e873aa2d..1624d7d2c3 100644 --- a/components/hal/esp32c2/include/hal/i2c_ll.h +++ b/components/hal/esp32c2/include/hal/i2c_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2020-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -687,6 +687,20 @@ static inline volatile void *i2c_ll_get_interrupt_status_reg(i2c_dev_t *dev) return &dev->int_status; } +/** + * @brief Check if i2c command is done. + * + * @param hw Beginning address of the peripheral registers + * @param cmd_idx The index of the command register, must be less than 8 + * + * @return True if the `cmd_idx` command is done. Otherwise false. + */ +__attribute__((always_inline)) +static inline bool i2c_ll_master_is_cmd_done(i2c_dev_t *hw, int cmd_idx) +{ + return hw->command[cmd_idx].command_done; +} + //////////////////////////////////////////Deprecated Functions////////////////////////////////////////////////////////// /////////////////////////////The following functions are only used by the legacy driver///////////////////////////////// /////////////////////////////They might be removed in the next major release (ESP-IDF 6.0)////////////////////////////// diff --git a/components/hal/esp32c3/include/hal/i2c_ll.h b/components/hal/esp32c3/include/hal/i2c_ll.h index f3e28ad02e..1423ca7978 100644 --- a/components/hal/esp32c3/include/hal/i2c_ll.h +++ b/components/hal/esp32c3/include/hal/i2c_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2020-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -860,6 +860,20 @@ static inline void i2c_ll_slave_clear_stretch(i2c_dev_t *dev) dev->scl_stretch_conf.slave_scl_stretch_clr = 1; } +/** + * @brief Check if i2c command is done. + * + * @param hw Beginning address of the peripheral registers + * @param cmd_idx The index of the command register, must be less than 8 + * + * @return True if the `cmd_idx` command is done. Otherwise false. + */ +__attribute__((always_inline)) +static inline bool i2c_ll_master_is_cmd_done(i2c_dev_t *hw, int cmd_idx) +{ + return hw->command[cmd_idx].command0_done; +} + //////////////////////////////////////////Deprecated Functions////////////////////////////////////////////////////////// /////////////////////////////The following functions are only used by the legacy driver///////////////////////////////// /////////////////////////////They might be removed in the next major release (ESP-IDF 6.0)////////////////////////////// diff --git a/components/hal/esp32c6/include/hal/i2c_ll.h b/components/hal/esp32c6/include/hal/i2c_ll.h index c0175ef121..e9224554e9 100644 --- a/components/hal/esp32c6/include/hal/i2c_ll.h +++ b/components/hal/esp32c6/include/hal/i2c_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -936,6 +936,20 @@ static inline void i2c_ll_slave_clear_stretch(i2c_dev_t *dev) dev->scl_stretch_conf.slave_scl_stretch_clr = 1; } +/** + * @brief Check if i2c command is done. + * + * @param hw Beginning address of the peripheral registers + * @param cmd_idx The index of the command register, must be less than 8 + * + * @return True if the `cmd_idx` command is done. Otherwise false. + */ +__attribute__((always_inline)) +static inline bool i2c_ll_master_is_cmd_done(i2c_dev_t *hw, int cmd_idx) +{ + return hw->command[cmd_idx].command_done; +} + //////////////////////////////////////////Deprecated Functions////////////////////////////////////////////////////////// /////////////////////////////The following functions are only used by the legacy driver///////////////////////////////// /////////////////////////////They might be removed in the next major release (ESP-IDF 6.0)////////////////////////////// diff --git a/components/hal/esp32h2/include/hal/i2c_ll.h b/components/hal/esp32h2/include/hal/i2c_ll.h index d1583c78e9..509bcedbc7 100644 --- a/components/hal/esp32h2/include/hal/i2c_ll.h +++ b/components/hal/esp32h2/include/hal/i2c_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -851,6 +851,20 @@ static inline void i2c_ll_slave_clear_stretch(i2c_dev_t *dev) dev->scl_stretch_conf.slave_scl_stretch_clr = 1; } +/** + * @brief Check if i2c command is done. + * + * @param hw Beginning address of the peripheral registers + * @param cmd_idx The index of the command register, must be less than 8 + * + * @return True if the `cmd_idx` command is done. Otherwise false. + */ +__attribute__((always_inline)) +static inline bool i2c_ll_master_is_cmd_done(i2c_dev_t *hw, int cmd_idx) +{ + return hw->command[cmd_idx].command_done; +} + //////////////////////////////////////////Deprecated Functions////////////////////////////////////////////////////////// /////////////////////////////The following functions are only used by the legacy driver///////////////////////////////// /////////////////////////////They might be removed in the next major release (ESP-IDF 6.0)////////////////////////////// diff --git a/components/hal/esp32p4/include/hal/i2c_ll.h b/components/hal/esp32p4/include/hal/i2c_ll.h index 4ae427662b..551e82eeea 100644 --- a/components/hal/esp32p4/include/hal/i2c_ll.h +++ b/components/hal/esp32p4/include/hal/i2c_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -891,6 +891,20 @@ static inline void i2c_ll_slave_clear_stretch(i2c_dev_t *dev) dev->scl_stretch_conf.slave_scl_stretch_clr = 1; } +/** + * @brief Check if i2c command is done. + * + * @param hw Beginning address of the peripheral registers + * @param cmd_idx The index of the command register, must be less than 8 + * + * @return True if the `cmd_idx` command is done. Otherwise false. + */ +__attribute__((always_inline)) +static inline bool i2c_ll_master_is_cmd_done(i2c_dev_t *hw, int cmd_idx) +{ + return hw->command[cmd_idx].command_done; +} + //////////////////////////////////////////Deprecated Functions////////////////////////////////////////////////////////// /////////////////////////////The following functions are only used by the legacy driver///////////////////////////////// /////////////////////////////They might be removed in the next major release (ESP-IDF 6.0)////////////////////////////// diff --git a/components/hal/esp32s2/include/hal/i2c_ll.h b/components/hal/esp32s2/include/hal/i2c_ll.h index f7942d9401..a04d15db64 100644 --- a/components/hal/esp32s2/include/hal/i2c_ll.h +++ b/components/hal/esp32s2/include/hal/i2c_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -805,6 +805,20 @@ static inline void i2c_ll_slave_clear_stretch(i2c_dev_t *dev) dev->scl_stretch_conf.slave_scl_stretch_clr = 1; } +/** + * @brief Check if i2c command is done. + * + * @param hw Beginning address of the peripheral registers + * @param cmd_idx The index of the command register, must be less than 16 + * + * @return True if the `cmd_idx` command is done. Otherwise false. + */ +__attribute__((always_inline)) +static inline bool i2c_ll_master_is_cmd_done(i2c_dev_t *hw, int cmd_idx) +{ + return hw->command[cmd_idx].done; +} + //////////////////////////////////////////Deprecated Functions////////////////////////////////////////////////////////// /////////////////////////////The following functions are only used by the legacy driver///////////////////////////////// /////////////////////////////They might be removed in the next major release (ESP-IDF 6.0)////////////////////////////// diff --git a/components/hal/esp32s3/include/hal/i2c_ll.h b/components/hal/esp32s3/include/hal/i2c_ll.h index 053ae83420..fcee10b115 100644 --- a/components/hal/esp32s3/include/hal/i2c_ll.h +++ b/components/hal/esp32s3/include/hal/i2c_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2020-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -862,6 +862,20 @@ static inline void i2c_ll_slave_clear_stretch(i2c_dev_t *dev) dev->scl_stretch_conf.slave_scl_stretch_clr = 1; } +/** + * @brief Check if i2c command is done. + * + * @param hw Beginning address of the peripheral registers + * @param cmd_idx The index of the command register, must be less than 8 + * + * @return True if the `cmd_idx` command is done. Otherwise false. + */ +__attribute__((always_inline)) +static inline bool i2c_ll_master_is_cmd_done(i2c_dev_t *hw, int cmd_idx) +{ + return hw->comd[cmd_idx].command_done; +} + //////////////////////////////////////////Deprecated Functions////////////////////////////////////////////////////////// /////////////////////////////The following functions are only used by the legacy driver///////////////////////////////// /////////////////////////////They might be removed in the next major release (ESP-IDF 6.0)////////////////////////////// diff --git a/components/hal/test_apps/hal_i2c/CMakeLists.txt b/components/hal/test_apps/hal_i2c/CMakeLists.txt new file mode 100644 index 0000000000..d40fddfb9b --- /dev/null +++ b/components/hal/test_apps/hal_i2c/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(hal_i2c) diff --git a/components/hal/test_apps/hal_i2c/README.md b/components/hal/test_apps/hal_i2c/README.md new file mode 100644 index 0000000000..e5485b5d19 --- /dev/null +++ b/components/hal/test_apps/hal_i2c/README.md @@ -0,0 +1,55 @@ +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | + +# HAL I2C test + +This code demonstrates how to use hal functions to write an I2C master driver. + +Note: This test is a pure hal i2c test. It CANNOT work with i2c driver in `esp_driver_i2c`. + +## Overview + +This test demonstrates basic usage of HAL (Hardware Abstract Layer) functions to write a simplest I2C master driver. This driver only relies on the I2C, GPIO and XTAL hardware features. Therefore, it can work on a bare metal environment, without any interrupt or memory allocation support. This example shows: + 1. How to write an I2C driver for bare metal applications, such as in a bootloader. + 2. How to use IDF HAL functions if you want to use the hardware I2C component to any 3rd party system or application. + +Restricted by source we can use, this test focuses on the usage of hal functions instead of providing perfect APIs. You can arrange better APIs based on your application usage. + +## How to use test + +### Hardware Required + +To run this test, you should have one ESP32, ESP32-S, ESP32-C, ESP32-P or ESP32-H based development board, call functions you can see the correct result. If possible, you can also see wave on logic analyzer or oscilloscope. + +#### Pin Assignment: + +**Note:** The following pin assignments are used by default, you can change these in the `menuconfig` . + +| | SDA | SCL | +| ---------------- | -------------- | -------------- | +| ESP I2C Master | I2C_MASTER_SDA | I2C_MASTER_SCL | +| DEVICE1 | SDA | SCL | +| DEVICEn | SDA | SCL | + +For the actual default value of `I2C_MASTER_SDA` and `I2C_MASTER_SCL` see `test Configuration` in `menuconfig`. + +**Note:** There's no need to add an external pull-up resistors for SDA/SCL pin, because the driver will enable the internal pull-up resistors. + +### 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. + +## test Output + +```bash +I (300) hal_i2c_main: HAL I2C initialized successfully +I (320) hal_i2c_main: HAL I2C write-read successfully +``` + +## 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.) diff --git a/components/hal/test_apps/hal_i2c/components/hal_i2c/CMakeLists.txt b/components/hal/test_apps/hal_i2c/components/hal_i2c/CMakeLists.txt new file mode 100644 index 0000000000..c61c607c78 --- /dev/null +++ b/components/hal/test_apps/hal_i2c/components/hal_i2c/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "hal_i2c.c" + INCLUDE_DIRS ".") diff --git a/components/hal/test_apps/hal_i2c/components/hal_i2c/hal_i2c.c b/components/hal/test_apps/hal_i2c/components/hal_i2c/hal_i2c.c new file mode 100644 index 0000000000..91adc7f3c6 --- /dev/null +++ b/components/hal/test_apps/hal_i2c/components/hal_i2c/hal_i2c.c @@ -0,0 +1,367 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include "esp_log.h" +#include "hal/i2c_ll.h" +#include "soc/io_mux_reg.h" +#include "soc/periph_defs.h" +#include "soc/gpio_sig_map.h" +#include "hal/gpio_ll.h" +#include "hal/gpio_hal.h" +#include "esp_rom_gpio.h" +#include "hal/i2c_ll.h" +#include "esp_cpu.h" +#include "esp_check.h" +#include "esp_private/periph_ctrl.h" +#include "hal/clk_tree_ll.h" +#include "hal/clk_tree_hal.h" +#include "hal_i2c.h" + +// Get current CPU tick count +#define RECORD_TIME_PREPARE() uint32_t __t1, __t2 +#define RECORD_TIME_START() do {__t1 = esp_cpu_get_cycle_count();} while(0) +#define RECORD_TIME_ELAPSED(p_time) do{__t2 = esp_cpu_get_cycle_count(); p_time = (__t2 - __t1);} while(0) + +static inline uint32_t time_get_us_by_ccount(uint32_t counter) +{ + return counter/CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ; +} + +#define ACK_VALUE (0) +#define NACK_VALUE (1) +#define NOT_CHECK_ACK_VALUE (0) +#define CHECK_ACK_VALUE (1) + +static const char *TAG = "hal-i2c"; + +// If Reset and Clock Control is independent, we need this macro to avoid concurrency issue +#if !SOC_RCC_IS_INDEPENDENT +#define I2C_RCC_ATOMIC() PERIPH_RCC_ATOMIC() +#else +#define I2C_RCC_ATOMIC() +#endif + +#if SOC_PERIPH_CLK_CTRL_SHARED +#define I2C_CLOCK_SRC_ATOMIC() PERIPH_RCC_ATOMIC() +#else +#define I2C_CLOCK_SRC_ATOMIC() +#endif + +typedef struct { + i2c_dev_t *i2c_dev; + bool initialized; +} hal_i2c_context; + +hal_i2c_context i2c_context[I2C_NUM_MAX] = { + { + .i2c_dev = I2C_LL_GET_HW(0), + .initialized = false, + }, +#if SOC_I2C_NUM > 1 + { + .i2c_dev = I2C_LL_GET_HW(1), + .initialized = false, + }, +#endif +}; + +esp_err_t hal_i2c_init(hal_i2c_config *cfg) +{ + ESP_RETURN_ON_FALSE(cfg->i2c_port < I2C_NUM_MAX, ESP_ERR_INVALID_ARG, TAG, "No such I2C port on this chip"); + int sda_io = cfg->sda_pin; + int scl_io = cfg->scl_pin; + uint32_t freq = cfg->freq; + i2c_dev_t *dev = i2c_context[cfg->i2c_port].i2c_dev; + + I2C_RCC_ATOMIC() { + i2c_ll_enable_bus_clock(0, true); + i2c_ll_reset_register(0); + } + + i2c_ll_enable_controller_clock(dev, true); + + // Note: Both SCL and SDA pins must be in open-drain configuration and the lines must be pulled-up. + // The internal pull-ups are very weak, so we strongly recommend you use an external pull-up. + + // In io pin configurations, we: + // 1. Set SCL and SDA to high level, because SCL/SDA valid when logic voltage change from 1 to 0. + // 2. Set both SCL and SDA open-drain + // 3. Set both SCL and SDA pullup enable and pulldown disable. (If you use external pullup, this can be ignored) + // 4. io mux function select + // 5. We connect out/in signal. As I2C master, out/in signal is necessary fpr both SCL and SDA according to esp hardware. + + // SDA pin configurations + if (sda_io != -1) { + assert(sda_io < SOC_GPIO_PIN_COUNT); + gpio_ll_set_level(&GPIO, sda_io, 1); + gpio_ll_od_enable(&GPIO, sda_io); + gpio_ll_pullup_en(&GPIO, sda_io); + gpio_ll_pulldown_dis(&GPIO, sda_io); + gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[sda_io], PIN_FUNC_GPIO); + esp_rom_gpio_connect_out_signal(sda_io, i2c_periph_signal[cfg->i2c_port].sda_out_sig, 0, 0); + esp_rom_gpio_connect_in_signal(sda_io, i2c_periph_signal[cfg->i2c_port].sda_in_sig, 0); + } + // SCL pin configurations + if (scl_io != -1) { + assert(scl_io < SOC_GPIO_PIN_COUNT); + gpio_ll_set_level(&GPIO, scl_io, 1); + gpio_ll_od_enable(&GPIO, scl_io); + gpio_ll_pullup_en(&GPIO, scl_io); + gpio_ll_pulldown_dis(&GPIO, scl_io); + gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[scl_io], PIN_FUNC_GPIO); + esp_rom_gpio_connect_out_signal(scl_io, i2c_periph_signal[cfg->i2c_port].scl_out_sig, 0, 0); + esp_rom_gpio_connect_in_signal(scl_io, i2c_periph_signal[cfg->i2c_port].scl_out_sig, 0); + } + // Initialize I2C master bus. Including enable its clock, choose its mode, etc. + i2c_ll_master_init(dev); + //MSB (I2C standard require the data to be sent with most MSB) + i2c_ll_set_data_mode(dev, I2C_DATA_MODE_MSB_FIRST, I2C_DATA_MODE_MSB_FIRST); + //Reset fifo + i2c_ll_txfifo_rst(dev); + i2c_ll_rxfifo_rst(dev); + + //disable intr + i2c_ll_disable_intr_mask(dev, I2C_LL_INTR_MASK); + + // init clock, always use xtal in hal driver. + i2c_hal_clk_config_t clk_cal = {0}; + I2C_CLOCK_SRC_ATOMIC() { + i2c_ll_set_source_clk(dev, SOC_MOD_CLK_XTAL); + } + i2c_ll_master_cal_bus_clk(clk_hal_xtal_get_freq_mhz() * MHZ, freq, &clk_cal); + i2c_ll_master_set_bus_timing(dev, &clk_cal); + + i2c_ll_update(dev); + i2c_context[cfg->i2c_port].initialized = true; + return ESP_OK; +} + +static esp_err_t i2c_wait_done(i2c_port_t port_num, int cmd_idx, uint32_t timeout_ms) +{ + uint32_t timeout_us = timeout_ms * 1000; + uint32_t wait_time = 0; + RECORD_TIME_PREPARE(); + RECORD_TIME_START(); + i2c_dev_t *dev = i2c_context[port_num].i2c_dev; + while (i2c_ll_master_is_cmd_done(dev, cmd_idx) == 0) { + RECORD_TIME_ELAPSED(wait_time); + if (time_get_us_by_ccount(wait_time) > timeout_us) { + return ESP_ERR_TIMEOUT; + } + } + return ESP_OK; +} + +static void i2c_format_cmd(i2c_port_t port_num, 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) +{ + i2c_dev_t *dev = i2c_context[port_num].i2c_dev; + /* Form new command */ + i2c_ll_hw_cmd_t hw_cmd = { + .done = 0, // CMD Done + // Different ESP target may have different op code, please + // refer to '{target}/hal/i2c_ll.h', they are + // #define I2C_LL_CMD_RESTART + // #define I2C_LL_CMD_WRITE + // #define I2C_LL_CMD_READ + // #define I2C_LL_CMD_STOP + // #define I2C_LL_CMD_END + .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_master_write_cmd_reg(dev, hw_cmd, cmd_idx); +} + +esp_err_t hal_i2c_write(i2c_port_t port_num, uint16_t addr, const uint8_t *txdata, uint32_t txlength, uint32_t timeout_ms) +{ + ESP_RETURN_ON_FALSE(i2c_context[port_num].initialized == true, ESP_ERR_INVALID_STATE, TAG, "This I2C port has not been initialized"); + ESP_RETURN_ON_FALSE(port_num < I2C_NUM_MAX, ESP_ERR_INVALID_STATE, TAG, "This ESP target does not have this port"); + ESP_RETURN_ON_FALSE(txdata, ESP_ERR_INVALID_STATE, TAG, "txdata pointer is null"); + i2c_dev_t *dev = i2c_context[port_num].i2c_dev; + uint32_t cmd_idx = 0; + 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 he I2C controller is based on a command-list where each cell describe the + next action to perform. The first action we want the controller to perform is a start, so create a START command and enqueue + it in the hardware commands list*/ + i2c_format_cmd(port_num, cmd_idx++, I2C_LL_CMD_RESTART, ACK_VALUE, 0, NOT_CHECK_ACK_VALUE, 0); + + /* Write device addr and update the HW command register. The 7-bit device address must be shift left once and the WRITE bit (0) must be appended */ + uint8_t addr_byte = (uint8_t)(((addr & 0xFF) << 1) | (0 << 0)); + /* The controller also has a RAM/FIFO that contains the data to send or receive whenever a READ or WRITE command is issued through list previously described */ + i2c_ll_write_txfifo(dev, &addr_byte, 1); + /* Update the HW command register. Expect an ACK from the device */ + i2c_format_cmd(port_num, cmd_idx++, I2C_LL_CMD_WRITE, ACK_VALUE, I2C_MASTER_ACK, NOT_CHECK_ACK_VALUE, 1); + + uint32_t remaining_byte = txlength; + while (remaining_byte) { + uint32_t tx_len_tmp = remaining_byte > SOC_I2C_FIFO_LEN - 1 ? SOC_I2C_FIFO_LEN - 1 : remaining_byte; + i2c_ll_write_txfifo(dev, txdata, tx_len_tmp); + /*The I2C controller support up to I2C commands, as such, if we need to enqueue more commands than that, + we can tell the hardware to start with the enqueued commands first and wait for the next commands to send afterwards. + To specify this behavior, we must use the END command. */ + i2c_format_cmd(port_num, cmd_idx++, I2C_LL_CMD_WRITE, ACK_VALUE, 0, NOT_CHECK_ACK_VALUE, tx_len_tmp); + i2c_format_cmd(port_num, cmd_idx++, I2C_LL_CMD_END, ACK_VALUE, 0, NOT_CHECK_ACK_VALUE, 0); + i2c_ll_update(dev); + i2c_ll_master_trans_start(dev); + ESP_RETURN_ON_ERROR(i2c_wait_done(port_num, cmd_idx - 1, timeout_ms), TAG, "wait done failed"); + cmd_idx = 0; + txdata += tx_len_tmp; + remaining_byte -= tx_len_tmp; + } + + i2c_format_cmd(port_num, cmd_idx++, I2C_LL_CMD_STOP, ACK_VALUE, 0, NOT_CHECK_ACK_VALUE, 0); + i2c_ll_update(dev); + i2c_ll_master_trans_start(dev); + ESP_RETURN_ON_ERROR(i2c_wait_done(port_num, cmd_idx - 1, timeout_ms), TAG, "wait done failed"); + return ESP_OK; +} + +esp_err_t hal_i2c_read(i2c_port_t port_num, uint16_t addr, uint8_t *rxdata, uint32_t rxlength, uint32_t timeout_ms) +{ + ESP_RETURN_ON_FALSE(i2c_context[port_num].initialized == true, ESP_ERR_INVALID_STATE, TAG, "This I2C port has not been initialized"); + i2c_dev_t *dev = i2c_context[port_num].i2c_dev; + uint32_t cmd_idx = 0; + uint32_t data_idx = 0; + /* Reset the Tx and Rx FIFOs */ + i2c_ll_txfifo_rst(dev); + i2c_ll_rxfifo_rst(dev); + + /* Execute RSTART command to send the START bit */ + i2c_format_cmd(port_num, cmd_idx++, I2C_LL_CMD_RESTART, ACK_VALUE, 0, NOT_CHECK_ACK_VALUE, 0); + uint8_t addr_byte = (uint8_t)(((addr & 0xFF) << 1) | 1); + i2c_ll_write_txfifo(dev, &addr_byte, 1); + /* Update the HW command register. Expect an ACK from the device */ + i2c_format_cmd(port_num, cmd_idx++, I2C_LL_CMD_WRITE, ACK_VALUE, 0, NOT_CHECK_ACK_VALUE, 1); + + uint32_t remaining_byte = rxlength; + + while (remaining_byte) { + uint32_t tmp_rx_length = (remaining_byte > SOC_I2C_FIFO_LEN) ? SOC_I2C_FIFO_LEN : remaining_byte; + remaining_byte -= tmp_rx_length; + if (tmp_rx_length == 1) { + i2c_format_cmd(port_num, cmd_idx++, I2C_LL_CMD_READ, NACK_VALUE, 0, NOT_CHECK_ACK_VALUE, 1); + i2c_format_cmd(port_num, cmd_idx++, I2C_LL_CMD_STOP, ACK_VALUE, 0, NOT_CHECK_ACK_VALUE, 0); + } else if ((tmp_rx_length > 1) && (remaining_byte == 0)) { + i2c_format_cmd(port_num, cmd_idx++, I2C_LL_CMD_READ, ACK_VALUE, 0, NOT_CHECK_ACK_VALUE, tmp_rx_length - 1); + i2c_format_cmd(port_num, cmd_idx++, I2C_LL_CMD_READ, NACK_VALUE, 0, NOT_CHECK_ACK_VALUE, 1); + i2c_format_cmd(port_num, cmd_idx++, I2C_LL_CMD_STOP, ACK_VALUE, 0, NOT_CHECK_ACK_VALUE, 0); + } else { + i2c_format_cmd(port_num, cmd_idx++, I2C_LL_CMD_READ, ACK_VALUE, 0, NOT_CHECK_ACK_VALUE, tmp_rx_length); + i2c_format_cmd(port_num, cmd_idx++, I2C_LL_CMD_END, ACK_VALUE, 0, NOT_CHECK_ACK_VALUE, 0); + } + + i2c_ll_update(dev); + i2c_ll_master_trans_start(dev); + ESP_RETURN_ON_ERROR(i2c_wait_done(port_num, cmd_idx - 1, timeout_ms), TAG, "wait done failed"); + cmd_idx = 0; + i2c_ll_read_rxfifo(dev, &rxdata[data_idx], 1); + data_idx += tmp_rx_length; + } + + return ESP_OK; +} + +esp_err_t hal_i2c_write_read(i2c_port_t port_num, uint16_t addr, const uint8_t *txdata, uint32_t txlength, uint8_t *rxdata, uint32_t rxlength, uint32_t timeout_ms) +{ + ESP_RETURN_ON_FALSE(i2c_context[port_num].initialized == true, ESP_ERR_INVALID_STATE, TAG, "This I2C port has not been initialized"); + i2c_dev_t *dev = i2c_context[port_num].i2c_dev; + uint32_t cmd_idx = 0; + uint32_t data_idx = 0; + /* Reset the Tx and Rx FIFOs */ + i2c_ll_txfifo_rst(dev); + i2c_ll_rxfifo_rst(dev); + + /* Execute RSTART command to send the START bit */ + i2c_format_cmd(port_num, cmd_idx++, I2C_LL_CMD_RESTART, 0, 0, NOT_CHECK_ACK_VALUE, 0); + uint8_t addr_byte = (uint8_t)(((addr & 0xFF) << 1) | 0); + i2c_ll_write_txfifo(dev, &addr_byte, 1); + /* Update the HW command register. Expect an ACK from the device */ + i2c_format_cmd(port_num, cmd_idx++, I2C_LL_CMD_WRITE, 0, 0, NOT_CHECK_ACK_VALUE, 1); + + // Write first + uint32_t remaining_byte = txlength; + while (remaining_byte) { + uint32_t tx_len_tmp = remaining_byte > SOC_I2C_FIFO_LEN - 1 ? SOC_I2C_FIFO_LEN - 1 : remaining_byte; + i2c_ll_write_txfifo(dev, txdata, tx_len_tmp); + /*The I2C controller support up to I2C commands, as such, if we need to enqueue more commands than that, + we can tell the hardware to start with the enqueued commands first and wait for the next commands to send afterwards. + To specify this behavior, we must use the END command. */ + i2c_format_cmd(port_num, cmd_idx++, I2C_LL_CMD_WRITE, ACK_VALUE, 0, NOT_CHECK_ACK_VALUE, tx_len_tmp); + i2c_format_cmd(port_num, cmd_idx++, I2C_LL_CMD_END, ACK_VALUE, 0, NOT_CHECK_ACK_VALUE, 0); + /* Initiate I2C transfer */ + i2c_ll_update(dev); + i2c_ll_master_trans_start(dev); + ESP_RETURN_ON_ERROR(i2c_wait_done(port_num, cmd_idx - 1, timeout_ms), TAG, "wait done failed"); + cmd_idx = 0; + txdata += tx_len_tmp; + remaining_byte -= tx_len_tmp; + } + + // Then, read + i2c_format_cmd(port_num, cmd_idx++, I2C_LL_CMD_RESTART, ACK_VALUE, 0, NOT_CHECK_ACK_VALUE, 0); + addr_byte = (uint8_t)(((addr & 0xFF) << 1) | 1); + i2c_ll_write_txfifo(dev, &addr_byte, 1); + /* Update the HW command register. Expect an ACK from the device */ + i2c_format_cmd(port_num, cmd_idx++, I2C_LL_CMD_WRITE, ACK_VALUE, 0, NOT_CHECK_ACK_VALUE, 1); + + remaining_byte = rxlength; + + while (remaining_byte) { + uint32_t tmp_rx_length = (remaining_byte > SOC_I2C_FIFO_LEN) ? SOC_I2C_FIFO_LEN : remaining_byte; + remaining_byte -= tmp_rx_length; + if (tmp_rx_length == 1) { + i2c_format_cmd(port_num, cmd_idx++, I2C_LL_CMD_READ, NACK_VALUE, 0, NOT_CHECK_ACK_VALUE, 1); + i2c_format_cmd(port_num, cmd_idx++, I2C_LL_CMD_STOP, ACK_VALUE, 0, NOT_CHECK_ACK_VALUE, 0); + } else if ((tmp_rx_length > 1) && (remaining_byte == 0)) { + i2c_format_cmd(port_num, cmd_idx++, I2C_LL_CMD_READ, ACK_VALUE, 0, NOT_CHECK_ACK_VALUE, tmp_rx_length - 1); + i2c_format_cmd(port_num, cmd_idx++, I2C_LL_CMD_READ, NACK_VALUE, 0, NOT_CHECK_ACK_VALUE, 1); + i2c_format_cmd(port_num, cmd_idx++, I2C_LL_CMD_STOP, ACK_VALUE, 0, NOT_CHECK_ACK_VALUE, 0); + } else { + i2c_format_cmd(port_num, cmd_idx++, I2C_LL_CMD_READ, ACK_VALUE, 0, NOT_CHECK_ACK_VALUE, tmp_rx_length); + i2c_format_cmd(port_num, cmd_idx++, I2C_LL_CMD_END, ACK_VALUE, 0, NOT_CHECK_ACK_VALUE, 0); + } + + i2c_ll_update(dev); + i2c_ll_master_trans_start(dev); + ESP_RETURN_ON_ERROR(i2c_wait_done(port_num, cmd_idx - 1, timeout_ms), TAG, "wait done failed"); + i2c_ll_read_rxfifo(dev, &rxdata[data_idx], tmp_rx_length); + cmd_idx = 0; + data_idx += tmp_rx_length; + + } + + return ESP_OK; + +} + +// I2C pure hal example cannot work with esp_driver_i2c. +__attribute__((constructor)) +static void check_hal_i2c_driver_conflict(void) +{ + // This function was declared as weak here. The new I2C driver has the implementation. + // So if the new I2C driver is not linked in, then `i2c_acquire_bus_handle()` should be NULL at runtime. + extern __attribute__((weak)) esp_err_t i2c_acquire_bus_handle(int port_num, void *i2c_new_bus, int mode); + if ((void *)i2c_acquire_bus_handle != NULL) { + // printf("CONFLICT! esp_driver cannot work with this pure hal driver\n"); + abort(); + } +} diff --git a/components/hal/test_apps/hal_i2c/components/hal_i2c/hal_i2c.h b/components/hal/test_apps/hal_i2c/components/hal_i2c/hal_i2c.h new file mode 100644 index 0000000000..083400a6b1 --- /dev/null +++ b/components/hal/test_apps/hal_i2c/components/hal_i2c/hal_i2c.h @@ -0,0 +1,84 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include +#include "esp_err.h" +#include "hal/i2c_types.h" +#include "hal/i2c_hal.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief hal i2c config structure + */ +typedef struct { + int scl_pin; /*!< SCL PIN, -1 means not change the current pin*/ + int sda_pin; /*!< SDA PIN, -1 means not change the current pin*/ + uint32_t freq; /*!< SCL frequency*/ + i2c_port_t i2c_port; /*!< i2c port */ +} hal_i2c_config; + +/** + * @brief Initialise hal i2c driver. + * + * @param cfg Configuration structure. + * @return + * - ESP_OK: Initialize hal i2c driver successfully. + * - ESP_ERR_INVALID_ARG: Invalid argument of this function. + */ +esp_err_t hal_i2c_init(hal_i2c_config *cfg); + +/** + * @brief I2C write data to slave + * + * @param[in] port_num I2C port + * @param[in] addr Address of slave + * @param[in] txdata The pointer data to be sent + * @param[in] txlength The length of data to be sent + * @param[in] timeout_ms The timeout(ms) value per command in this function + * @return + * - ESP_OK: I2C write data successfully + * - ESP_ERR_INVALID_STATE: The port you are using is not initialized + * - ESP_ERR_TIMEOUT: I2C write to slave timeout. + */ +esp_err_t hal_i2c_write(i2c_port_t port_num, uint16_t addr, const uint8_t *txdata, uint32_t txlength, uint32_t timeout_ms); + +/** + * @brief I2C read data from slave + * + * @param[in] port_num I2C port + * @param[in] addr Address of slave + * @param[out] rxdata The pointer of the memory to receive data + * @param[in] rxlength The length of data to be received + * @param[in] timeout_ms The timeout(ms) value per command in this function + * @return + * - ESP_OK: I2C write data successfully + * - ESP_ERR_INVALID_STATE: The port you are using is not initialized + * - ESP_ERR_TIMEOUT: I2C write to slave timeout. + */ +esp_err_t hal_i2c_read(i2c_port_t port_num, uint16_t addr, uint8_t *rxdata, uint32_t rxlength, uint32_t timeout_ms); + +/** + * @brief I2C write data to slave and then read from slave + * + * @param[in] port_num I2C port + * @param[in] addr Address of slave + * @param[in] txdata The pointer data to be sent + * @param[in] txlength The length of data to be sent + * @param[out] rxdata The pointer of the memory to receive data + * @param[in] rxlength The length of data to be received + * @param[in] timeout_ms The timeout(ms) value per command in this function + * @return + * - ESP_OK: I2C write data successfully + * - ESP_ERR_INVALID_STATE: The port you are using is not initialized + * - ESP_ERR_TIMEOUT: I2C write to slave timeout. + */ +esp_err_t hal_i2c_write_read(i2c_port_t port_num, uint16_t addr, const uint8_t *txdata, uint32_t txlength, uint8_t *rxdata, uint32_t rxlength, uint32_t timeout_ms); + +#ifdef __cplusplus +} +#endif diff --git a/components/hal/test_apps/hal_i2c/main/CMakeLists.txt b/components/hal/test_apps/hal_i2c/main/CMakeLists.txt new file mode 100644 index 0000000000..e99392033c --- /dev/null +++ b/components/hal/test_apps/hal_i2c/main/CMakeLists.txt @@ -0,0 +1,4 @@ +set(srcs "hal_i2c_main.c") + +idf_component_register(SRCS ${srcs} + INCLUDE_DIRS ".") diff --git a/components/hal/test_apps/hal_i2c/main/Kconfig.projbuild b/components/hal/test_apps/hal_i2c/main/Kconfig.projbuild new file mode 100644 index 0000000000..18404879ee --- /dev/null +++ b/components/hal/test_apps/hal_i2c/main/Kconfig.projbuild @@ -0,0 +1,23 @@ +menu "Example Configuration" + + menu "I2C Master" + config I2C_MASTER_SCL + int "SCL GPIO Num" + default 4 + help + GPIO number for I2C Master clock line. + + config I2C_MASTER_SDA + int "SDA GPIO Num" + default 5 + help + GPIO number for I2C Master data line. + + config I2C_MASTER_FREQUENCY + int "Master Frequency" + default 400000 + help + I2C Speed of Master device. + endmenu + +endmenu diff --git a/components/hal/test_apps/hal_i2c/main/hal_i2c_main.c b/components/hal/test_apps/hal_i2c/main/hal_i2c_main.c new file mode 100644 index 0000000000..f7aa78110e --- /dev/null +++ b/components/hal/test_apps/hal_i2c/main/hal_i2c_main.c @@ -0,0 +1,41 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include +#include +#include "sdkconfig.h" +#include "esp_log.h" +#include "hal_i2c.h" +#include "esp_check.h" + +#define SCL_IO_PIN CONFIG_I2C_MASTER_SCL +#define SDA_IO_PIN CONFIG_I2C_MASTER_SDA +#define MASTER_FREQUENCY CONFIG_I2C_MASTER_FREQUENCY +#define BUFFER_LEN (35) +#define TIMEOUT_MS (20) +static const uint8_t slave_address = 0x50; + +static const char *TAG = "hal_i2c_main"; + +void app_main(void) +{ + hal_i2c_config i2c_config = { + .freq = MASTER_FREQUENCY, + .i2c_port = I2C_NUM_0, + .scl_pin = SCL_IO_PIN, + .sda_pin = SDA_IO_PIN, + }; + ESP_ERROR_CHECK(hal_i2c_init(&i2c_config)); + ESP_LOGI(TAG, "HAL I2C initialized successfully"); + + // Use EEPROM as a device, write 0x33 and 0x44 to at address 0x0000, then, read value back + uint8_t tx_data[4] = {0x00, 0x00, 0x33, 0x44}; + ESP_ERROR_CHECK(hal_i2c_write(I2C_NUM_0, slave_address, tx_data, 4, TIMEOUT_MS)); + uint8_t rx_data[2]; + ESP_ERROR_CHECK(hal_i2c_write_read(I2C_NUM_0, slave_address, tx_data, 2, rx_data, 2, TIMEOUT_MS)); + ESP_LOGI(TAG, "Read back value 1 is %x, value two is %x\n", rx_data[0], rx_data[1]); + + ESP_LOGI(TAG, "HAL I2C write-read successfully"); +}