feat(i2c): Add pure hal i2c master driver example

This commit is contained in:
Cao Sen Miao 2024-01-16 18:59:04 +08:00
parent b9f9e0d9f7
commit 62a0efdd7c
17 changed files with 706 additions and 8 deletions

View File

@ -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"

View File

@ -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)//////////////////////////////

View File

@ -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)//////////////////////////////

View File

@ -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)//////////////////////////////

View File

@ -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)//////////////////////////////

View File

@ -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)//////////////////////////////

View File

@ -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)//////////////////////////////

View File

@ -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)//////////////////////////////

View File

@ -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)//////////////////////////////

View File

@ -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)

View File

@ -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.)

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "hal_i2c.c"
INCLUDE_DIRS ".")

View File

@ -0,0 +1,367 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#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();
}
}

View File

@ -0,0 +1,84 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdint.h>
#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

View File

@ -0,0 +1,4 @@
set(srcs "hal_i2c_main.c")
idf_component_register(SRCS ${srcs}
INCLUDE_DIRS ".")

View File

@ -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

View File

@ -0,0 +1,41 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <string.h>
#include <stdio.h>
#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");
}