Merge branch 'doc/i3c_programming_guide' into 'master'

Docs(i3c): Add I3C programming guide

See merge request espressif/esp-idf!44568
This commit is contained in:
C.S.M
2026-01-14 14:30:43 +08:00
25 changed files with 1288 additions and 15 deletions

View File

@@ -791,5 +791,4 @@ mainmenu "Espressif IoT Development Framework Configuration"
- CONFIG_ESP_WIFI_ENABLE_ROAMING_APP
- CONFIG_USB_HOST_EXT_PORT_RESET_ATTEMPTS
- CONFIG_GDMA_ENABLE_WEIGHTED_ARBITRATION
- CONFIG_I3C_MASTER_ENABLED
- CONFIG_ESPTOOLPY_FAST_REFLASHING

View File

@@ -4,7 +4,7 @@ set(srcs)
set(include "include")
# I3C related source files.
if(CONFIG_I3C_MASTER_ENABLED)
if(CONFIG_SOC_I3C_MASTER_SUPPORTED)
list(APPEND srcs "i3c_master.c"
)
endif()

View File

@@ -1,10 +1,5 @@
menu "ESP-Driver:I3C Master Configurations"
config I3C_MASTER_ENABLED
bool
default y if SOC_I3C_MASTER_SUPPORTED && IDF_EXPERIMENTAL_FEATURES
default n
config I3C_MASTER_ISR_CACHE_SAFE
bool "I3C ISR Cache-Safe"
select I3C_MASTER_ISR_HANDLER_IN_IRAM

View File

@@ -777,8 +777,10 @@ esp_err_t i3c_new_master_bus(const i3c_master_bus_config_t *bus_config, i3c_mast
// Convert float duty cycle to numerator and denominator for LL function
// Use 10000 as denominator to support 0.01 precision
const uint32_t duty_cycle_denom = 100;
uint32_t od_duty_cycle_num = (uint32_t)(bus_config->i3c_scl_od_duty_cycle * duty_cycle_denom);
uint32_t pp_duty_cycle_num = (uint32_t)(bus_config->i3c_scl_pp_duty_cycle * duty_cycle_denom);
float od_duty_cycle = (bus_config->i3c_scl_od_duty_cycle == 0) ? 0.5 : bus_config->i3c_scl_od_duty_cycle;
float pp_duty_cycle = (bus_config->i3c_scl_pp_duty_cycle == 0) ? 0.5 : bus_config->i3c_scl_pp_duty_cycle;
uint32_t od_duty_cycle_num = (uint32_t)(od_duty_cycle * duty_cycle_denom);
uint32_t pp_duty_cycle_num = (uint32_t)(pp_duty_cycle * duty_cycle_denom);
i3c_master_ll_set_i3c_open_drain_timing(i3c_master_handle->hal.dev, periph_src_clk_hz, bus_config->i3c_scl_freq_hz_od, od_duty_cycle_num, duty_cycle_denom);
i3c_master_ll_set_i3c_push_pull_timing(i3c_master_handle->hal.dev, periph_src_clk_hz, bus_config->i3c_scl_freq_hz_pp, pp_duty_cycle_num, duty_cycle_denom);
// Convert hold time from nanoseconds to clock cycles

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -29,8 +29,8 @@ typedef struct {
int intr_priority; /*!< I3C interrupt priority, if set to 0, driver will select the default priority (1,2,3). */
uint32_t i3c_scl_freq_hz_od; /*!< I3C SCL frequency for Open-Drain mode */
uint32_t i3c_scl_freq_hz_pp; /*!< I3C SCL frequency for Push-Pull mode */
float i3c_scl_pp_duty_cycle; /*!< Duty cycle of I3C Push-Pull SCL signal, range from (0, 1) */
float i3c_scl_od_duty_cycle; /*!< Duty cycle of I3C Open-Drain SCL signal, range from (0, 1) */
float i3c_scl_pp_duty_cycle; /*!< Duty cycle of I3C Push-Pull SCL signal, range from (0, 1). When set 0, then duty cycle is 0.5 */
float i3c_scl_od_duty_cycle; /*!< Duty cycle of I3C Open-Drain SCL signal, range from (0, 1). When set 0, then duty cycle is 0.5 */
uint32_t i3c_sda_od_hold_time_ns; /*!< I3C Open-Drain sda drive point after scl neg, in nanoseconds, default 25 */
uint32_t i3c_sda_pp_hold_time_ns; /*!< I3C Push-Pull sda drive point after scl neg, in nanoseconds, default 0 */
uint8_t entdaa_device_num; /*!< Maximum number of devices can be discovered by ENTDAA, range from [0x0, 0x7F], 0x0 means no entdaa is used. */

View File

@@ -1,3 +1,2 @@
CONFIG_FREERTOS_HZ=1000
CONFIG_ESP_TASK_WDT_INIT=n
CONFIG_IDF_EXPERIMENTAL_FEATURES=y

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 18 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 21 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 24 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 18 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 17 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 26 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 22 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -0,0 +1,26 @@
{
"head": {
"text": "Standard I3C Transaction Timing Diagram"
},
"signal": [
{
"node":"A.....B.......................................C...D.E...............F...G",
"period": 0.5},
{
"name": "SDA",
"wave": "1.0.......3.....3.|...3......4.......5.......1...0.6...6.|.6...7...10..1", "data": "A6 . A0 R/W ACK D7 . D0 T",
"period": 0.5},
{
"name": "SCL",
"wave": "1.....0....1...0.1|0...1...0...1...0...1...0........1.0.1|0.1.0.1.0..1..",
"period": 0.5}
],
"config":
{
"skin": "narrow"
},
"edge": [
"B<->C Write address (open drain)",
"E<->F Write data (push pull)"
]
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -209,6 +209,10 @@ I2C_DOCS = [
'api-reference/peripherals/i2c_slave_v1.rst',
]
I3C_DOCS = [
'api-reference/peripherals/i3c_master.rst',
]
SPI_DOCS = [
'api-reference/peripherals/spi_master.rst',
'api-reference/peripherals/spi_slave.rst',
@@ -377,6 +381,7 @@ conditional_include_dict = {
'SOC_TOUCH_SENSOR_SUPPORTED': TOUCH_SENSOR_DOCS,
'SOC_TWAI_SUPPORTED': TWAI_DOCS,
'SOC_I2C_SUPPORTED': I2C_DOCS,
'SOC_I3C_MASTER_SUPPORTED': I3C_DOCS,
'SOC_GPSPI_SUPPORTED': SPI_DOCS,
'SOC_I2S_SUPPORTED': I2S_DOCS,
'SOC_LP_I2S_SUPPORTED': LP_I2S_DOCS,

View File

@@ -127,6 +127,9 @@ INPUT = \
$(PROJECT_PATH)/components/esp_driver_i2s/include/driver/i2s_std.h \
$(PROJECT_PATH)/components/esp_driver_i2s/include/driver/i2s_tdm.h \
$(PROJECT_PATH)/components/esp_driver_i2s/include/driver/i2s_types.h \
$(PROJECT_PATH)/components/esp_driver_i3c/include/driver/i3c_master.h \
$(PROJECT_PATH)/components/esp_driver_i3c/include/driver/i3c_master_i2c.h \
$(PROJECT_PATH)/components/esp_driver_i3c/include/driver/i3c_master_types.h \
$(PROJECT_PATH)/components/esp_driver_pcnt/include/driver/pulse_cnt.h \
$(PROJECT_PATH)/components/esp_driver_rmt/include/driver/rmt_common.h \
$(PROJECT_PATH)/components/esp_driver_rmt/include/driver/rmt_encoder.h \
@@ -175,6 +178,7 @@ INPUT = \
$(PROJECT_PATH)/components/esp_hal_timg/include/hal/timer_types.h \
$(PROJECT_PATH)/components/esp_hal_i2c/include/hal/i2c_types.h \
$(PROJECT_PATH)/components/esp_hal_i2s/include/hal/i2s_types.h \
$(PROJECT_PATH)/components/esp_hal_i3c/include/hal/i3c_master_types.h \
$(PROJECT_PATH)/components/esp_hal_jpeg/include/hal/jpeg_types.h \
$(PROJECT_PATH)/components/esp_hal_lcd/include/hal/lcd_types.h \
$(PROJECT_PATH)/components/esp_hal_ledc/include/hal/ledc_types.h \

View File

@@ -0,0 +1,600 @@
I3C master interface
====================
:link_to_translation:`zh_CN:[中文]`
{IDF_TARGET_I3C_INTERNAL_PULLUP_PIN:default="Not updated!", esp32p4="GPIO32/GPIO33"}
This document introduces the I3C master driver functionality of ESP-IDF. The table of contents is as follows:
.. contents::
:local:
:depth: 2
Overview
========
I3C is a serial synchronous half-duplex communication protocol and an enhanced version of the I2C protocol. While maintaining most compatibility with I2C, I3C provides higher speed, lower power consumption, and richer features.
For hardware-related information about I3C, please refer to the I3C Technical Reference Manual.
The main features of the I3C protocol include:
- **Backward compatibility with I2C**: I3C bus can support both I2C and I3C devices simultaneously
- **Higher speed**: I3C can reach up to 12.5 MHz, while I2C can reach up to 1 MHz
- **Static address assignment**: Manually assign dynamic addresses based on static addresses through the SETDASA procedure
- **Dynamic address assignment**: Automatically assign dynamic addresses through the ENTDAA procedure to avoid address conflicts
- **In-band interrupt (IBI)**: Supports slave devices sending interrupt requests through the I3C bus without additional interrupt lines
- **Common Command Code (CCC)**: Supports broadcast and direct CCC commands for bus management and device configuration
.. important::
1. When using with I2C devices, ensure that I2C devices mounted on the I3C bus **must not** support or enable clock stretching, otherwise when I2C slaves stretch the clock, it will cause the hardware state machine to hang.
2. The I3C frequency depends on circuit design and timing adjustment. Please refer to the I3C device manual you are using.
3. Some I3C slave devices have strict timing requirements for their acknowledgment mechanism (ACK/NACK). Please refer to the I3C slave device manual you are using.
Quick Start
===========
This section will quickly guide you through using the I3C master driver. It demonstrates how to create a bus, add devices, and perform data transfers. The general usage flow is as follows:
.. blockdiag::
:scale: 100%
:caption: General usage flow of I3C driver (click image to view full size)
:align: center
blockdiag {
default_fontsize = 14;
node_width = 250;
node_height = 80;
class emphasis [color = pink, style = dashed];
create_bus [label="Create I3C Bus\n(i3c_new_master_bus)"];
add_device [label="Add Device\n(add_i2c_device / \nadd_i3c_static_device / \nscan_devices_by_entdaa)"];
transfer [label="Data Transfer\n(transmit / receive / \ntransmit_receive)", class="emphasis"];
cleanup [label="Remove Device and Bus\n(rm_device / del_master_bus)"];
create_bus -> add_device -> transfer -> cleanup;
}
Create I3C Bus
--------------
The I3C master bus is represented by :cpp:type:`i3c_master_bus_handle_t` in the driver. The driver internally maintains a resource pool that can manage multiple buses and allocates free bus ports when requested.
.. figure:: ../../../_static/diagrams/i3c/i3c_bus_structure.svg
:align: center
:alt: I3C Bus Structure
I3C Bus Structure
When creating an I3C bus instance, we need to configure GPIO pins, clock source, frequency, and other parameters through :cpp:type:`i3c_master_bus_config_t`. These parameters will determine how the bus operates. The following code shows how to create a basic I3C bus:
.. code:: c
#include "driver/i3c_master.h"
i3c_master_bus_config_t i3c_mst_config = {
.sda_io_num = I3C_MASTER_SDA_IO, // GPIO number for SDA signal line
.scl_io_num = I3C_MASTER_SCL_IO, // GPIO number for SCL signal line
.i3c_scl_freq_hz_od = 600 * 1000, // SCL clock frequency in Open-Drain mode, please refer to device manual for appropriate values
.i3c_scl_freq_hz_pp = 2 * 1000 * 1000, // SCL clock frequency in Push-Pull mode, please refer to device manual for appropriate values
.i3c_sda_od_hold_time_ns = 25, // Hold time of SDA after SCL falling edge in Open-Drain mode (nanoseconds), recommended to set to 25, please refer to device manual for appropriate values
.i3c_sda_pp_hold_time_ns = 0, // Hold time of SDA after SCL falling edge in Push-Pull mode (nanoseconds), default is 0, please refer to device manual for appropriate values
.entdaa_device_num = 0, // Maximum number of devices allowed to be dynamically discovered through ENTDAA, range from [0x0, 0x7F], 0x0 means dynamic device discovery is not used
};
i3c_master_bus_handle_t bus_handle;
ESP_ERROR_CHECK(i3c_new_master_bus(&i3c_mst_config, &bus_handle));
.. note::
The I3C protocol requires automatic switching between open-drain and push-pull modes during each transfer between the addressing phase and data transfer phase. On ESP32-P4, only {IDF_TARGET_I3C_INTERNAL_PULLUP_PIN} support automatic opening/closing of internal pull-up switches and support user adjustment of internal pull-up resistance values. When using other GPIOs, the internal pull-up may be insufficient, and it is recommended to add external pull-up resistors. However, in push-pull mode, this pull-up cannot be canceled, which may increase additional power consumption.
Add and Drive Legacy I2C Devices
---------------------------------
.. figure:: ../../../_static/diagrams/i3c/i3c_i2c_write.svg
:align: center
:alt: Write to legacy I2C device
Write to legacy I2C device
.. figure:: ../../../_static/diagrams/i3c/i3c_i2c_read.svg
:align: center
:alt: Read from legacy I2C device
Read from legacy I2C device
The I3C bus supports compatibility with legacy I2C devices. If you need to connect a legacy I2C device (such as EEPROM, sensors, etc.) to the I3C bus, please note that I2C slaves **must not** perform clock stretching during I3C communication. The specific process can be done as follows:
.. code:: c
// 1. Create I3C bus (refer to the code above)
i3c_master_bus_handle_t bus_handle;
ESP_ERROR_CHECK(i3c_new_master_bus(&i3c_mst_config, &bus_handle));
// 2. Add I2C device
i3c_device_i2c_config_t i2c_dev_cfg = {
.device_address = 0x50, // 7-bit address of I2C device
.scl_freq_hz = 100 * 1000, // Clock frequency of I2C device (100 kHz)
};
i3c_master_i2c_device_handle_t i2c_dev_handle;
ESP_ERROR_CHECK(i3c_master_bus_add_i2c_device(bus_handle, &i2c_dev_cfg, &i2c_dev_handle));
// 3. Write data to I2C device
uint8_t write_data[10] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A};
ESP_ERROR_CHECK(i3c_master_i2c_device_transmit(i2c_dev_handle, write_data, sizeof(write_data), -1)); // -1 means infinite timeout
// 4. Read data from I2C device
uint8_t read_data[10] = {0};
ESP_ERROR_CHECK(i3c_master_i2c_device_receive(i2c_dev_handle, read_data, sizeof(read_data), -1));
// 5. Write-read combined transaction (write register address first, then read data, no STOP in between)
uint8_t reg_addr = 0x00;
uint8_t read_buffer[5] = {0};
ESP_ERROR_CHECK(i3c_master_i2c_device_transmit_receive(i2c_dev_handle, &reg_addr, 1, read_buffer, sizeof(read_buffer), -1));
// 6. Clean up resources
ESP_ERROR_CHECK(i3c_master_bus_rm_i2c_device(i2c_dev_handle));
ESP_ERROR_CHECK(i3c_del_master_bus(bus_handle));
In this scenario, we:
1. Created an I3C bus instance through :cpp:func:`i3c_new_master_bus`
2. Added an I2C device through :cpp:func:`i3c_master_bus_add_i2c_device`, which requires specifying the device's static address and clock frequency
3. Used :cpp:func:`i3c_master_i2c_device_transmit` to write data. By default, it works in blocking mode. For non-blocking mode, please refer to :ref:`dma-support`. The same applies to other transfer functions.
4. Used :cpp:func:`i3c_master_i2c_device_receive` to read data
5. Used :cpp:func:`i3c_master_i2c_device_transmit_receive` to execute write-read combined transactions (commonly used to write register address first, then read data, with no STOP bit in between)
6. Finally cleaned up resources
Add and Drive I3C Devices via SETDASA
--------------------------------------
For specific behaviors that may occur during I3C transfers, please refer to the standard I3C protocol. The following diagram is used to briefly explain the behavior in I3C transfers to understand the I3C transfer diagrams in this document:
.. figure:: ../../../_static/diagrams/i3c/i3c_icon.svg
:align: center
:alt: I3C Transfer Legend
I3C Transfer Legend
If you know the static address of an I3C device, you can add the device using the SETDASA method:
.. figure:: ../../../_static/diagrams/i3c/i3c_setdasa.svg
:align: center
:alt: I3C Directed Dynamic Address Assignment
I3C Directed Dynamic Address Assignment
.. code:: c
// 1. Create I3C bus
i3c_master_bus_handle_t bus_handle;
ESP_ERROR_CHECK(i3c_new_master_bus(&i3c_mst_config, &bus_handle));
// 2. Add I3C device (using SETDASA)
i3c_device_i3c_config_t i3c_dev_cfg = {
.dynamic_addr = 0x08, // Dynamic address assigned to the device, can be any value except reserved addresses in the I3C protocol, or can be obtained through `i3c_master_get_valid_address_slot` to get an available dynamic address
.static_addr = 0x74, // Static address of the device (obtained from device manual)
};
i3c_master_i3c_device_handle_t i3c_dev_handle;
ESP_ERROR_CHECK(i3c_master_bus_add_i3c_static_device(bus_handle, &i3c_dev_cfg, &i3c_dev_handle));
// 3. Write data to I3C device
uint8_t write_data[100] = {0};
ESP_ERROR_CHECK(i3c_master_i3c_device_transmit(i3c_dev_handle, write_data, sizeof(write_data), -1));
// 4. Read data from I3C device
uint8_t read_data[100] = {0};
ESP_ERROR_CHECK(i3c_master_i3c_device_receive(i3c_dev_handle, read_data, sizeof(read_data), -1));
// 5. Write-read combined transaction
uint8_t reg_addr = 0x12;
uint8_t read_buffer[10] = {0};
ESP_ERROR_CHECK(i3c_master_i3c_device_transmit_receive(i3c_dev_handle, &reg_addr, 1, read_buffer, sizeof(read_buffer), -1));
// 6. Clean up resources
ESP_ERROR_CHECK(i3c_master_bus_rm_i3c_device(i3c_dev_handle));
ESP_ERROR_CHECK(i3c_del_master_bus(bus_handle));
In this scenario:
1. We use :cpp:func:`i3c_master_bus_add_i3c_static_device` to add an I3C device
2. We need to provide the device's static address (obtained from device manual) and the dynamic address to be assigned
3. The driver automatically executes the SETDASA procedure to assign the dynamic address to the device. If there is an address conflict, it will return ``ESP_ERR_INVALID_STATE``.
4. After that, we can use the dynamic address for data transfers through :cpp:func:`i3c_master_i3c_device_transmit` or :cpp:func:`i3c_master_i3c_device_receive` or :cpp:func:`i3c_master_i3c_device_transmit_receive`. By default, it works in blocking mode. For non-blocking mode, please refer to :ref:`dma-support`. The same applies to other transfer functions.
5. Finally clean up resources
Add and Drive I3C Devices via ENTDAA
-------------------------------------
If you don't know which I3C devices are on the bus, or want the system to automatically discover and assign addresses, you can use the ENTDAA method:
.. figure:: ../../../_static/diagrams/i3c/i3c_entdaa.svg
:align: center
:alt: I3C Automatic Dynamic Address Assignment
I3C Automatic Dynamic Address Assignment
.. code:: c
// 1. Create I3C bus (need to set entdaa_device_num)
i3c_master_bus_config_t i3c_mst_config = {
// ... other configurations ...
.entdaa_device_num = 5, // Maximum number of devices that can be dynamically discovered by the driver
};
i3c_master_bus_handle_t bus_handle;
ESP_ERROR_CHECK(i3c_new_master_bus(&i3c_mst_config, &bus_handle));
// 2. Scan I3C devices on the bus
i3c_master_i3c_device_table_handle_t table_handle = NULL;
ESP_ERROR_CHECK(i3c_master_scan_devices_by_entdaa(bus_handle, &table_handle));
// 3. Get the number of discovered devices
size_t device_count = 0;
ESP_ERROR_CHECK(i3c_master_get_device_count(table_handle, &device_count));
printf("Found %zu I3C devices\n", device_count);
// 4. Iterate through all devices and get device information
i3c_master_i3c_device_handle_t dev = NULL;
for (size_t i = 0; i < device_count; i++) {
i3c_master_i3c_device_handle_t dev_handle = NULL;
ESP_ERROR_CHECK(i3c_master_get_device_handle(table_handle, i, &dev_handle));
// Get device information
i3c_device_information_t info;
ESP_ERROR_CHECK(i3c_master_i3c_device_get_info(dev_handle, &info));
printf("Device %d: Dynamic Addr=0x%02X, BCR=0x%02X, DCR=0x%02X, PID=0x%016llX\n",
i, info.dynamic_addr, info.bcr, info.dcr, info.pid);
if (info.pid == /* Device PID, obtained from device manual */) {
dev = dev_handle;
break;
}
}
// Release device handle table, call when no longer needed
ESP_ERROR_CHECK(i3c_master_free_device_handle_table(table_handle));
// 5. Perform data transfer through transmit or receive
ESP_ERROR_CHECK(i3c_master_i3c_device_transmit(dev, data, sizeof(data), -1));
ESP_ERROR_CHECK(i3c_master_i3c_device_receive(dev, data, sizeof(data), -1));
In this scenario:
1. When creating the bus, we need to set `entdaa_device_num`, which represents the expected number of devices to be discovered
2. Use :cpp:func:`i3c_master_scan_devices_by_entdaa` to scan all I3C devices on the bus
3. The system automatically assigns dynamic addresses to each device
4. We can get the device count through :cpp:func:`i3c_master_get_device_count`
5. Get each device's handle through :cpp:func:`i3c_master_get_device_handle`
6. Use :cpp:func:`i3c_master_i3c_device_get_info` to get detailed device information (dynamic address, BCR, DCR, PID)
7. Perform data transfers through :cpp:func:`i3c_master_i3c_device_transmit` or :cpp:func:`i3c_master_i3c_device_receive` based on the obtained device information
.. note::
:cpp:func:`i3c_master_scan_devices_by_entdaa` is thread-safe, and there will not be two threads addressing simultaneously. According to the protocol, when a slave is addressed and discovered by :cpp:func:`i3c_master_scan_devices_by_entdaa`, it no longer has the ability to respond to a second addressing. Therefore, there will be no address changes due to addressing in different threads. This interface supports adding new devices after initialization. To rescan, use the CCC mechanism to reset addresses on the I3C bus, or clear address information on the bus by power cycling.
Common Command Code (CCC) Transfer
-----------------------------------
The I3C protocol uses Common Command Code (CCC) for bus management and device configuration. You can use the :cpp:func:`i3c_master_transfer_ccc` function to send CCC commands.
CCC transfers can be broadcast (sent to all devices) or direct (sent to a specific device):
.. figure:: ../../../_static/diagrams/i3c/i3c_broadcast_ccc.svg
:align: center
:alt: I3C Broadcast Command
I3C Broadcast Command
.. figure:: ../../../_static/diagrams/i3c/i3c_direct_ccc.svg
:align: center
:alt: I3C Direct Command
I3C Direct Command
.. code:: c
// Broadcast CCC command example: Send RSTDAA (Reset All Dynamic Addresses)
i3c_master_ccc_transfer_config_t ccc_trans = {
.ccc_command = I3C_CCC_RSTDAA,
.direction = I3C_MASTER_TRANSFER_DIRECTION_WRITE,
.device_address = 0, // Broadcast command, this field is ignored
.data = NULL,
.data_size = 0,
};
ESP_ERROR_CHECK(i3c_master_transfer_ccc(bus_handle, &ccc_trans));
// Direct CCC command example: Read device's GETPID (Get Device ID)
uint8_t pid_data[6] = {0};
ccc_trans = (i3c_master_ccc_transfer_config_t) {
.ccc_command = I3C_CCC_GETPID,
.direction = I3C_MASTER_TRANSFER_DIRECTION_READ,
.device_address = 0x08, // Target device address, which is the dynamic address
.data = pid_data,
.data_size = sizeof(pid_data),
};
ESP_ERROR_CHECK(i3c_master_transfer_ccc(bus_handle, &ccc_trans));
.. note::
:cpp:func:`i3c_master_transfer_ccc` is always blocking and is not affected by DMA and asynchronous configuration. Users need to query the I3C protocol to know the specific format of CCC commands, and fill :cpp:member:`i3c_master_ccc_transfer_config_t::direction` as ``I3C_MASTER_TRANSFER_DIRECTION_READ`` or ``I3C_MASTER_TRANSFER_DIRECTION_WRITE`` and fill :cpp:member:`i3c_master_ccc_transfer_config_t::data` and :cpp:member:`i3c_master_ccc_transfer_config_t::data_size` according to the format of sending commands or obtaining values.
Resource Cleanup
----------------
When the previously installed I3C bus or device is no longer needed, call :cpp:func:`i3c_master_bus_rm_i3c_device` or :cpp:func:`i3c_master_bus_rm_i2c_device` to remove the device, then call :cpp:func:`i3c_del_master_bus` to reclaim resources and release the underlying hardware.
.. code:: c
ESP_ERROR_CHECK(i3c_master_bus_rm_i3c_device(i3c_dev_handle));
ESP_ERROR_CHECK(i3c_del_master_bus(bus_handle));
Advanced Features
=================
Clock Source and Timing Parameter Fine-tuning
---------------------------------------------
Clock Source Selection
^^^^^^^^^^^^^^^^^^^^^^
The clock source of the I3C bus can be selected through :cpp:member:`i3c_master_bus_config_t::clock_source`.
.. code:: c
i3c_master_bus_config_t i3c_mst_config = {
// ... other configurations ...
.clock_source = I3C_MASTER_CLK_SRC_DEFAULT, // Default clock source
};
.. note::
When the I3C push-pull output frequency is greater than 3 MHz, please set the clock source to :cpp:enumerator:`i3c_master_clock_source_t::I3C_MASTER_CLK_SRC_PLL_F120M` or :cpp:enumerator:`i3c_master_clock_source_t::I3C_MASTER_CLK_SRC_PLL_F160M`.
The I3C driver provides rich timing parameter configuration options. You can adjust these parameters according to the actual hardware situation to optimize performance or solve timing issues.
Duty Cycle and Hold Time
^^^^^^^^^^^^^^^^^^^^^^^^^
Some I3C slave devices have strict timing requirements for their acknowledgment mechanism (ACK/NACK), such as requirements for SCL waveform duty cycle and SDA hold time. These parameters can be configured through the following configuration items.
.. code:: c
i3c_master_bus_config_t i3c_mst_config = {
// ... other configurations ...
.i3c_scl_pp_duty_cycle = 0.5, // Push-Pull mode duty cycle, usually 0.5 (default value 0 also means 0.5)
.i3c_scl_od_duty_cycle = 0.5, // Open-Drain mode duty cycle, usually 0.5 (default value 0 also means 0.5)
.i3c_sda_od_hold_time_ns = 25, // Open-Drain mode hold time, default 25 ns
.i3c_sda_pp_hold_time_ns = 0, // Push-Pull mode hold time, default 0 ns
};
The specific values of these parameters need to be determined according to the device manual and actual testing.
Event Callbacks
---------------
The I3C driver supports an event callback mechanism that can notify the application when a transfer is complete or when an IBI interrupt is received.
When the I3C controller generates events such as send or receive completion, it notifies the CPU through interrupts. If you need to call a function when a specific event occurs, you can register event callbacks with the I3C driver's interrupt service routine (ISR) by calling :cpp:func:`i3c_master_i3c_device_register_event_callbacks` and :cpp:func:`i3c_master_i2c_device_register_event_callbacks` for I3C and I2C slaves respectively. Since these callback functions are called in the ISR, they should not involve blocking operations. You can check the suffix of the called API to ensure that only FreeRTOS APIs with the ISR suffix are called in the function. The callback function has a boolean return value indicating whether the callback unblocked a higher priority task.
For event callbacks of I2C slaves, please refer to i2c_master_i2c_event_callbacks_t.
* :cpp:member:`i3c_master_i2c_event_callbacks_t::on_trans_done` can be set to a callback function for the master "transfer done" event. The function prototype is declared in :cpp:type:`i3c_master_i2c_callback_t`. Note that this callback function can only be used when the I2C slave device has DMA enabled and uses asynchronous transfer. For details, please refer to :ref:`dma-support`.
For event callbacks of I3C slaves, please refer to i3c_master_i3c_event_callbacks_t.
* :cpp:member:`i3c_master_i3c_event_callbacks_t::on_trans_done` can be set to a callback function for the master "transfer done" event. The function prototype is declared in :cpp:type:`i3c_master_i3c_callback_t`. Note that this callback function can only be used when the I3C slave device has DMA enabled and uses asynchronous transfer. For details, please refer to :ref:`dma-support`.
* :cpp:member:`i3c_master_i3c_event_callbacks_t::on_ibi` can be set to a callback function for IBI events. The function prototype is declared in :cpp:type:`i3c_master_ibi_callback_t`. For detailed information about IBI events, please refer to :ref:`in-band-interrupt`
.. note::
Callback functions are executed in the ISR context, therefore:
- Cannot perform blocking operations
- Can only call FreeRTOS APIs with the ISR suffix
- If ``CONFIG_I3C_MASTER_ISR_CACHE_SAFE`` is enabled, callback functions must be placed in IRAM
.. _in-band-interrupt:
In-Band Interrupt (IBI)
-----------------------
The I3C protocol supports In-Band Interrupt (IBI), allowing slave devices to send interrupt requests through the I3C bus without additional interrupt lines.
Configure IBI
^^^^^^^^^^^^^
The I3C bus configuration structure :cpp:type:`i3c_master_bus_config_t` contains IBI-related global configuration items:
- :cpp:member:`i3c_master_bus_config_t::ibi_rstart_trans_en` enables restart transaction on IBI. The I3C controller continues to execute the command that was interrupted by IBI after IBI completion. If IBI occurs during bus idle and the I3C transfer task is not empty, the I3C controller will continue to execute that task. If IBI conflicts with I3C controller transfer and wins arbitration, the interrupted task will continue to execute after IBI processing is complete.
- :cpp:member:`i3c_master_bus_config_t::ibi_silent_sir_rejected` when written as 0, does not notify the application layer when a slave interrupt request (SIR) is rejected. When written as 1, the IBI status is still written to the IBI FIFO and the application layer is notified.
- :cpp:member:`i3c_master_bus_config_t::ibi_no_auto_disable` if set, does not automatically disable IBI after the controller NACKs an In-Band interrupt, keeping in-band interrupt enabled.
You can use the :cpp:func:`i3c_master_i3c_device_ibi_config` function to configure IBI for a specific device:
.. code:: c
i3c_ibi_config_t ibi_cfg = {
.enable_ibi = true,
.enable_ibi_payload = true, // Allow IBI to carry payload
};
ESP_ERROR_CHECK(i3c_master_i3c_device_ibi_config(dev_handle, &ibi_cfg));
Handle IBI Events
^^^^^^^^^^^^^^^^^
Detailed information about IBI events will be provided from the callback through :cpp:type:`i3c_master_ibi_info_t`:
:cpp:member:`i3c_master_ibi_info_t::ibi_id` is the raw identifier of the IBI, usually encoded from the slave device's dynamic address; it is the raw value, i.e., dynamic address + read/write bit. :cpp:member:`i3c_master_ibi_info_t::ibi_sts` is the IBI status field reported by the controller. :cpp:member:`i3c_master_ibi_info_t::data_length` is the number of valid bytes in the payload buffer :cpp:member:`i3c_master_ibi_info_t::ibi_data`. :cpp:member:`i3c_master_ibi_info_t::ibi_data` is the optional payload bytes associated with the IBI. Only the first :cpp:member:`i3c_master_ibi_info_t::data_length` bytes are valid.
.. code:: c
static bool i3c_ibi_callback(i3c_master_i3c_device_handle_t dev_handle, const i3c_master_ibi_info_t *ibi_info, void *user_ctx)
{
// Can copy IBI event data to user-provided context and do further processing in task
// i3c_master_ibi_info_t is a user-defined structure, here including ibi_id and ibi_data_len, members can be added or removed according to actual needs
i3c_master_ibi_info_t evt = {
.ibi_id = ibi_info->ibi_id,
.ibi_data_len = ibi_info->data_length,
};
return false;
}
i3c_master_i3c_event_callbacks_t cbs = {
.on_ibi = i3c_ibi_callback,
};
ESP_ERROR_CHECK(i3c_master_i3c_device_register_event_callbacks(dev_handle, &cbs, NULL));
.. _dma-support:
DMA and Asynchronous Transfer
-----------------------------
The I3C driver supports DMA for large-capacity data transfers and asynchronous transfers, which can improve transfer efficiency and reduce CPU usage.
Enable DMA
^^^^^^^^^^
You can configure DMA for the bus through the :cpp:func:`i3c_master_bus_decorate_dma` function:
.. code:: c
i3c_master_dma_config_t dma_config = {
.max_transfer_size = 4096, // Maximum transfer size (bytes)
.dma_burst_size = 16, // DMA burst size (bytes)
};
ESP_ERROR_CHECK(i3c_master_bus_decorate_dma(bus_handle, &dma_config));
Enable Asynchronous Transfer
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
When DMA is enabled, you can further enable asynchronous transfer to improve performance:
.. code:: c
i3c_master_bus_config_t i3c_mst_config = {
// ... other configurations ...
.trans_queue_depth = 5, // Set the depth of internal transfer queue
.flags = {
.enable_async_trans = 1, // Enable asynchronous transfer
}
};
At this point, the I3C master transfer functions will return immediately after being called. When each transfer is complete, the :cpp:member:`i3c_master_i3c_event_callbacks_t::on_trans_done` callback function will be called to indicate the completion of a transfer. If you need to wait for the transfer to complete, you can call the :cpp:func:`i3c_master_bus_wait_all_done` function to wait for all transfers to complete:
.. code:: c
// Start multiple asynchronous transfers
ESP_ERROR_CHECK(i3c_master_i3c_device_transmit(dev_handle1, data1, size1, -1));
ESP_ERROR_CHECK(i3c_master_i3c_device_transmit(dev_handle2, data2, size2, -1));
ESP_ERROR_CHECK(i3c_master_i3c_device_transmit(dev_handle3, data3, size3, -1));
// Wait for all transfers to complete
ESP_ERROR_CHECK(i3c_master_bus_wait_all_done(bus_handle, -1));
Thread Safety
-------------
The following functions of the I3C driver are thread-safe and can be called from different RTOS tasks without additional lock protection:
Factory functions:
- :cpp:func:`i3c_new_master_bus`
- :cpp:func:`i3c_del_master_bus`
I3C master operation functions (thread safety guaranteed through bus operation signals):
- :cpp:func:`i3c_master_bus_add_i3c_static_device`
- :cpp:func:`i3c_master_bus_rm_i3c_device`
- :cpp:func:`i3c_master_i3c_device_transmit`
- :cpp:func:`i3c_master_i3c_device_receive`
- :cpp:func:`i3c_master_i3c_device_transmit_receive`
- :cpp:func:`i3c_master_i2c_device_transmit`
- :cpp:func:`i3c_master_i2c_device_receive`
- :cpp:func:`i3c_master_i2c_device_transmit_receive`
- :cpp:func:`i3c_master_transfer_ccc`
Cache Safety
------------
By default, when the cache is disabled (e.g., during SPI Flash write), I3C interrupts will be delayed, and event callback functions will not be able to execute on time, which will affect the system response of real-time applications.
This can be avoided by enabling the Kconfig option ``CONFIG_I3C_MASTER_ISR_CACHE_SAFE``. After enabling:
1. Interrupts can continue to run even when the cache is disabled
2. All functions used by the ISR are placed in IRAM
3. Driver objects are placed in DRAM (to prevent them from being accidentally mapped to PSRAM)
Enabling this option ensures interrupt operation when the cache is disabled, but will consume more IRAM.
.. note::
After enabling this option, when the cache is disabled, ISR callback functions will continue to run. Therefore, you must ensure that the callback functions and their context are also IRAM-safe. At the same time, data transfer buffers must also be placed in DRAM.
About Low Power Consumption
----------------------------
When power management :ref:`CONFIG_PM_ENABLE` is enabled, the system may adjust or disable clock sources before entering sleep mode, which can cause I3C transfer errors.
To prevent this from happening, the I3C driver internally creates a power management lock. After calling a transfer function, this lock will be activated to ensure the system does not enter sleep mode, thus maintaining the correct operation of the timer. After the transfer is complete, the driver automatically releases the lock, allowing the system to enter sleep mode.
Kconfig Options
---------------
The following Kconfig options can be used to configure the I3C driver:
- :ref:`CONFIG_I3C_MASTER_ISR_CACHE_SAFE`: Ensure I3C interrupts work properly when cache is disabled (e.g., during SPI Flash write)
- :ref:`CONFIG_I3C_MASTER_ISR_HANDLER_IN_IRAM`: Place I3C master ISR handler in IRAM to improve performance and reduce cache misses
- :ref:`CONFIG_I3C_MASTER_ENABLE_DEBUG_LOG`: Enable I3C debug logging
About Resource Consumption
---------------------------
You can use the :doc:`/api-guides/tools/idf-size` tool to view the code and data consumption of the I3C driver. The following are the test prerequisites (using ESP32-P4 as an example):
- Compiler optimization level is set to ``-Os`` to ensure minimal code size.
- Default log level is set to ``ESP_LOG_INFO`` to balance debug information and performance.
- The following driver optimization options are disabled:
- :ref:`CONFIG_I3C_MASTER_ISR_HANDLER_IN_IRAM` - ISR handler is not placed in IRAM.
- :ref:`CONFIG_I3C_MASTER_ISR_CACHE_SAFE` - Cache safety option is not enabled.
**Note: The following data is not precise and is for reference only. Data may vary on different chip models.**
+------------------+------------+-------+------+-------+-------+------------+-------+------------+---------+
| Component Layer | Total Size | DIRAM | .bss | .data | .text | Flash Code | .text | Flash Data | .rodata |
+==================+============+=======+======+=======+=======+============+=======+============+=========+
| hal | 30 | 0 | 0 | 0 | 0 | 30 | 18 | 0 | 12 |
+------------------+------------+-------+------+-------+-------+------------+-------+------------+---------+
| driver | 9249 | 12 | 12 | 0 | 0 | 9237 | 8666 | 571 | 571 |
+------------------+------------+-------+------+-------+-------+------------+-------+------------+---------+
Application Examples
====================
- :example:`peripherals/i3c/i3c_i2c_basic` demonstrates the basic steps of initializing the I3C master driver and reading data from the ICM42688 sensor using the I2C interface.
- :example:`peripherals/i3c/i3c_lsm6dscx` demonstrates how to read and write data from a connected LSM6DSOX sensor using I3C master mode, and supports in-band interrupt (IBI) event handling.
API Reference
=============
I3C Driver API
--------------
.. include-build-file:: inc/i3c_master.inc
I3C Driver I2C Slave API
------------------------
.. include-build-file:: inc/i3c_master_i2c.inc
I3C Driver Types
----------------
.. include-build-file:: inc/components/esp_hal_i3c/include/hal/i3c_master_types.inc
I3C HAL Types
-------------
.. include-build-file:: inc/components/esp_driver_i3c/include/driver/i3c_master_types.inc

View File

@@ -21,6 +21,7 @@ Peripherals API
:SOC_DIG_SIGN_SUPPORTED: ds
:SOC_I2C_SUPPORTED: i2c
:SOC_I2S_SUPPORTED: i2s
:SOC_I3C_MASTER_SUPPORTED: i3c_master
:SOC_ISP_SUPPORTED: isp
:SOC_JPEG_CODEC_SUPPORTED: jpeg
lcd/index

View File

@@ -0,0 +1,603 @@
========================
I3C 主机接口
========================
:link_to_translation:`en:[English]`
{IDF_TARGET_I3C_INTERNAL_PULLUP_PIN:default="未更新!", esp32p4="GPIO32/GPIO33"}
本文介绍了 ESP-IDF 的 I3C 主机驱动功能,章节目录如下:
.. contents::
:local:
:depth: 2
概述
====
I3C (Improved InterIntegrated Circuit) 是一种串行同步半双工通信协议,是 I2C 协议的增强版本。I3C 在保持与 I2C 大部分功能兼容的同时,提供了更高的速度、更低的功耗和更丰富的功能。
关于 I3C 的硬件相关信息,请参考 I3C 技术参考手册。
I3C 协议的主要特性包括:
- **向后兼容 I2C**I3C 总线可以同时支持 I2C 设备和 I3C 设备
- **更高的速度**I3C 速度最高可达 12.5 MHzI2C 最高速度可达 1 MHz
- **静态地址分配**:通过 SETDASA 过程手动根据静态地址分配动态地址
- **动态地址分配**:通过 ENTDAA 过程自动分配动态地址,避免地址冲突
- **带内中断IBI**:支持从机通过 I3C 总线发送中断请求,无需额外的中断线
- **公共命令码CCC**:支持广播和直接 CCC 命令,用于总线管理和设备配置
.. important::
1. 当兼容 I2C 设备时,请确保在 I3C 总线上挂载的 I2C 设备 **不启用** 时钟拉伸功能,否则当 I2C 从机拉伸时钟时会导致硬件状态机卡死。
2. I3C 的频率取决于电路设计和时序调整,具体请参考使用的 I3C 设备手册。
3. 部分 I3C 从机设备的应答机制ACK/NACK有严格的时序限制请参考所使用的 I3C 从机设备手册。
快速入门
========
本节将带你快速了解如何使用 I3C 主机驱动。通过实际的使用场景,展示如何创建总线、添加设备并进行数据传输。一般的使用流程如下:
.. blockdiag::
:scale: 100%
:caption: I3C 驱动的一般使用流程(点击图片查看大图)
:align: center
blockdiag {
default_fontsize = 14;
node_width = 250;
node_height = 80;
class emphasis [color = pink, style = dashed];
create_bus [label="创建 I3C 总线\n(i3c_new_master_bus)"];
add_device [label="添加设备\n(add_i2c_device / \nadd_i3c_static_device / \nscan_devices_by_entdaa)"];
transfer [label="数据传输\n(transmit / receive / \ntransmit_receive)", class="emphasis"];
cleanup [label="删除设备和总线\n(rm_device / del_master_bus)"];
create_bus -> add_device -> transfer -> cleanup;
}
创建 I3C 总线
----------------
I3C 主机总线在驱动程序中使用 :cpp:type:`i3c_master_bus_handle_t` 来表示。驱动内部维护了一个资源池,可管理多条总线,并在有请求时分配空闲的总线端口。
.. figure:: ../../../_static/diagrams/i3c/i3c_bus_structure.svg
:align: center
:alt: I3C 总线结构
I3C 总线结构
当创建 I3C 总线实例时,我们需要通过 :cpp:type:`i3c_master_bus_config_t` 配置 GPIO 引脚、时钟源、频率等参数。这些参数将决定总线的工作方式。以下代码展示了如何创建一个基本的 I3C 总线:
.. code:: c
#include "driver/i3c_master.h"
i3c_master_bus_config_t i3c_mst_config = {
.sda_io_num = I3C_MASTER_SDA_IO, // SDA 信号线的 GPIO 编号
.scl_io_num = I3C_MASTER_SCL_IO, // SCL 信号线的 GPIO 编号
.i3c_scl_freq_hz_od = 600 * 1000, // Open-Drain 模式下的 SCL 时钟频率,请参考设备手册获取合适的值
.i3c_scl_freq_hz_pp = 2 * 1000 * 1000, // Push-Pull 模式下的 SCL 时钟频率,请参考设备手册获取合适的值
.i3c_sda_od_hold_time_ns = 25, // Open-Drain 模式下 SDA 在 SCL 下降沿后的保持时间(纳秒),建议设置为 25请参考设备手册获取合适的值
.i3c_sda_pp_hold_time_ns = 0, // Push-Pull 模式下 SDA 在 SCL 下降沿后的保持时间(纳秒),默认值为 0请参考设备手册获取合适的值
.entdaa_device_num = 0, // 最大允许通过 ENTDAA 动态发现的设备数量,范围从 [0x0, 0x7F]0x0 表示不使用动态设备发现
};
i3c_master_bus_handle_t bus_handle;
ESP_ERROR_CHECK(i3c_new_master_bus(&i3c_mst_config, &bus_handle));
.. note::
I3C 协议需要在每次传输过程中在寻址过程与数据传输过程之间自动切换开漏和推挽模式。在 ESP32-P4 上,只有 {IDF_TARGET_I3C_INTERNAL_PULLUP_PIN} 支持自动打开/关闭内部上拉开关且支持用户调整内部上拉阻值。当使用其他 GPIO 时内部上拉不足建议额外添加外部上拉电阻,但此时在推挽模式下该上拉不可取消,可能会增加额外功耗。
添加并驱动传统 I2C 设备
----------------------------
.. figure:: ../../../_static/diagrams/i3c/i3c_i2c_write.svg
:align: center
:alt: 写入传统 I2C 设备
写入传统 I2C 设备
.. figure:: ../../../_static/diagrams/i3c/i3c_i2c_read.svg
:align: center
:alt: 从传统 I2C 设备读取
从传统 I2C 设备读取
I3C 总线支持和传统的 I2C 设备兼容,如果你需要在 I3C 总线上连接一个传统的 I2C 设备(例如 EEPROM、传感器等请务必注意I2C 从机不可在与 I3C 通信时发生时钟拉伸,具体流程可以使用以下方式:
.. code:: c
// 1. 创建 I3C 总线(参考上面的代码)
i3c_master_bus_handle_t bus_handle;
ESP_ERROR_CHECK(i3c_new_master_bus(&i3c_mst_config, &bus_handle));
// 2. 添加 I2C 设备
i3c_device_i2c_config_t i2c_dev_cfg = {
.device_address = 0x50, // I2C 设备的 7 位地址
.scl_freq_hz = 100 * 1000, // I2C 设备的时钟频率100 kHz
};
i3c_master_i2c_device_handle_t i2c_dev_handle;
ESP_ERROR_CHECK(i3c_master_bus_add_i2c_device(bus_handle, &i2c_dev_cfg, &i2c_dev_handle));
// 3. 写入数据到 I2C 设备
uint8_t write_data[10] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A};
ESP_ERROR_CHECK(i3c_master_i2c_device_transmit(i2c_dev_handle, write_data, sizeof(write_data), -1)); // -1 表示传输超时时间为无限长
// 4. 从 I2C 设备读取数据
uint8_t read_data[10] = {0};
ESP_ERROR_CHECK(i3c_master_i2c_device_receive(i2c_dev_handle, read_data, sizeof(read_data), -1));
// 5. 写入-读取组合事务(先写寄存器地址,再读数据,中间没有 STOP
uint8_t reg_addr = 0x00;
uint8_t read_buffer[5] = {0};
ESP_ERROR_CHECK(i3c_master_i2c_device_transmit_receive(i2c_dev_handle, &reg_addr, 1, read_buffer, sizeof(read_buffer), -1));
// 6. 清理资源
ESP_ERROR_CHECK(i3c_master_bus_rm_i2c_device(i2c_dev_handle));
ESP_ERROR_CHECK(i3c_del_master_bus(bus_handle));
在这个场景中,我们:
1. 通过 :cpp:func:`i3c_new_master_bus` 创建了一个 I3C 总线实例
2. 通过 :cpp:func:`i3c_master_bus_add_i2c_device` 添加了一个 I2C 设备,需要指定设备的静态地址和时钟频率
3. 使用 :cpp:func:`i3c_master_i2c_device_transmit` 写入数据,默认为阻塞模式,在非阻塞模式下工作请参考 :ref:`dma-support`,其它传输函数同理。
4. 使用 :cpp:func:`i3c_master_i2c_device_receive` 读取数据
5. 使用 :cpp:func:`i3c_master_i2c_device_transmit_receive` 执行写入-读取组合事务(常用于先写寄存器地址再读数据,中间没有停止位)
6. 最后清理资源
通过 SETDASA 添加并驱动 I3C 设备
----------------------------------------
对 I3C 传输过程中可能出现的具体行为,请参考标准 I3C 协议。该图例用作简要解释 I3C 传输中的行为以理解本文档中出现的 I3C 传输示意图:
.. figure:: ../../../_static/diagrams/i3c/i3c_icon.svg
:align: center
:alt: I3C 传输图例
I3C 传输图例
如果你知道 I3C 设备的静态地址,可以使用 SETDASA 方式添加设备:
.. figure:: ../../../_static/diagrams/i3c/i3c_setdasa.svg
:align: center
:alt: I3C 定向动态地址分配
I3C 定向动态地址分配
.. code:: c
// 1. 创建 I3C 总线
i3c_master_bus_handle_t bus_handle;
ESP_ERROR_CHECK(i3c_new_master_bus(&i3c_mst_config, &bus_handle));
// 2. 添加 I3C 设备(使用 SETDASA
i3c_device_i3c_config_t i3c_dev_cfg = {
.dynamic_addr = 0x08, // 为设备分配的动态地址,可以是除了 I3C 协议中规定为保留地址外的任意值,也可以通过 `i3c_master_get_valid_address_slot` 获取一个可用的动态地址
.static_addr = 0x74, // 设备的静态地址(从设备手册获取)
};
i3c_master_i3c_device_handle_t i3c_dev_handle;
ESP_ERROR_CHECK(i3c_master_bus_add_i3c_static_device(bus_handle, &i3c_dev_cfg, &i3c_dev_handle));
// 3. 写入数据到 I3C 设备
uint8_t write_data[100] = {0};
ESP_ERROR_CHECK(i3c_master_i3c_device_transmit(i3c_dev_handle, write_data, sizeof(write_data), -1));
// 4. 从 I3C 设备读取数据
uint8_t read_data[100] = {0};
ESP_ERROR_CHECK(i3c_master_i3c_device_receive(i3c_dev_handle, read_data, sizeof(read_data), -1));
// 5. 写入-读取组合事务
uint8_t reg_addr = 0x12;
uint8_t read_buffer[10] = {0};
ESP_ERROR_CHECK(i3c_master_i3c_device_transmit_receive(i3c_dev_handle, &reg_addr, 1, read_buffer, sizeof(read_buffer), -1));
// 6. 清理资源
ESP_ERROR_CHECK(i3c_master_bus_rm_i3c_device(i3c_dev_handle));
ESP_ERROR_CHECK(i3c_del_master_bus(bus_handle));
在这个场景中:
1. 我们使用 :cpp:func:`i3c_master_bus_add_i3c_static_device` 添加 I3C 设备
2. 需要提供设备的静态地址(从设备手册获取)和要分配的动态地址
3. 驱动程序会自动执行 SETDASA 过程,将动态地址分配给设备, 若地址冲突会返回 ``ESP_ERR_INVALID_STATE``
4. 之后可以使用动态地址 通过 :cpp:func:`i3c_master_i3c_device_transmit`:cpp:func:`i3c_master_i3c_device_receive`:cpp:func:`i3c_master_i3c_device_transmit_receive` 进行数据传输,默认为阻塞模式,在非阻塞模式下工作请参考 :ref:`dma-support`,其它传输函数同理。
5. 最后清理资源
通过 ENTDAA 添加并驱动 I3C 设备
--------------------------------
如果你不知道总线上有哪些 I3C 设备,或者想让系统自动发现和分配地址,可以使用 ENTDAA 方式:
.. figure:: ../../../_static/diagrams/i3c/i3c_entdaa.svg
:align: center
:alt: I3C 自动动态地址分配
I3C 自动动态地址分配
.. code:: c
// 1. 创建 I3C 总线(需要设置 entdaa_device_num
i3c_master_bus_config_t i3c_mst_config = {
// ... 其他配置 ...
.entdaa_device_num = 5, // 驱动最大可以动态发现的设备数量
};
i3c_master_bus_handle_t bus_handle;
ESP_ERROR_CHECK(i3c_new_master_bus(&i3c_mst_config, &bus_handle));
// 2. 扫描总线上的 I3C 设备
i3c_master_i3c_device_table_handle_t table_handle = NULL;
ESP_ERROR_CHECK(i3c_master_scan_devices_by_entdaa(bus_handle, &table_handle));
// 3. 获取发现的设备数量
size_t device_count = 0;
ESP_ERROR_CHECK(i3c_master_get_device_count(table_handle, &device_count));
printf("Found %zu I3C devices\n", device_count);
// 4. 遍历所有设备并获取设备信息
i3c_master_i3c_device_handle_t dev = NULL;
for (size_t i = 0; i < device_count; i++) {
i3c_master_i3c_device_handle_t dev_handle = NULL;
ESP_ERROR_CHECK(i3c_master_get_device_handle(table_handle, i, &dev_handle));
// 获取设备信息
i3c_device_information_t info;
ESP_ERROR_CHECK(i3c_master_i3c_device_get_info(dev_handle, &info));
printf("Device %d: Dynamic Addr=0x%02X, BCR=0x%02X, DCR=0x%02X, PID=0x%016llX\n",
i, info.dynamic_addr, info.bcr, info.dcr, info.pid);
if (info.pid == /* 设备 PID从设备手册获取 */) {
dev = dev_handle;
break;
}
}
// 释放设备句柄表,不再需要时调用
ESP_ERROR_CHECK(i3c_master_free_device_handle_table(table_handle));
// 5. 通过 transmit 或 receive 进行数据传输
ESP_ERROR_CHECK(i3c_master_i3c_device_transmit(dev, data, sizeof(data), -1));
ESP_ERROR_CHECK(i3c_master_i3c_device_receive(dev, data, sizeof(data), -1));
在这个场景中:
1. 创建总线时需要设置 `entdaa_device_num`,表示预期发现的设备数量
2. 使用 :cpp:func:`i3c_master_scan_devices_by_entdaa` 扫描总线上的所有 I3C 设备
3. 系统会自动为每个设备分配动态地址
4. 可以通过 :cpp:func:`i3c_master_get_device_count` 获取设备数量
5. 通过 :cpp:func:`i3c_master_get_device_handle` 获取每个设备的句柄
6. 使用 :cpp:func:`i3c_master_i3c_device_get_info` 可以获取设备的详细信息动态地址、BCR、DCR、PID
7. 根据获得的设备信息通过 :cpp:func:`i3c_master_i3c_device_transmit`:cpp:func:`i3c_master_i3c_device_receive` 进行数据传输
.. note::
:cpp:func:`i3c_master_scan_devices_by_entdaa` 是线程安全的,不会有两个线程同时寻址的情况。根据协议,当从机被 :cpp:func:`i3c_master_scan_devices_by_entdaa` 寻址发现后,不再具有响应第二次寻址的能力。因此不会出现因为在不同线程寻址而导致的地址变化的情况。该接口支持在初始化后新增设备。若要重新扫描,请用 CCC 机制重置 I3C 总线上的地址,或通过重新上电清除总线上的地址信息。
公共命令码CCC传输
-----------------------
I3C 协议使用公共命令码CCC进行总线管理和设备配置。可以使用 :cpp:func:`i3c_master_transfer_ccc` 函数来发送 CCC 命令。
CCC 传输可以是广播的(发送给所有设备)或直接的(发送给特定设备):
.. figure:: ../../../_static/diagrams/i3c/i3c_broadcast_ccc.svg
:align: center
:alt: I3C 广播命令
I3C 广播命令
.. figure:: ../../../_static/diagrams/i3c/i3c_direct_ccc.svg
:align: center
:alt: I3C 直接命令
I3C 直接命令
.. code:: c
// 广播 CCC 命令示例:发送 RSTDAA重置所有动态地址
i3c_master_ccc_transfer_config_t ccc_trans = {
.ccc_command = I3C_CCC_RSTDAA,
.direction = I3C_MASTER_TRANSFER_DIRECTION_WRITE,
.device_address = 0, // 广播命令,此字段被忽略
.data = NULL,
.data_size = 0,
};
ESP_ERROR_CHECK(i3c_master_transfer_ccc(bus_handle, &ccc_trans));
// 直接 CCC 命令示例:读取设备的 GETPID获取设备 ID
uint8_t pid_data[6] = {0};
ccc_trans = (i3c_master_ccc_transfer_config_t) {
.ccc_command = I3C_CCC_GETPID,
.direction = I3C_MASTER_TRANSFER_DIRECTION_READ,
.device_address = 0x08, // 目标设备地址,为动态地址
.data = pid_data,
.data_size = sizeof(pid_data),
};
ESP_ERROR_CHECK(i3c_master_transfer_ccc(bus_handle, &ccc_trans));
.. note::
:cpp:func:`i3c_master_transfer_ccc` 永远是阻塞的,不受到 DMA 和异步配置的影响。用户需要通过查询 I3C 协议获知 CCC 命令的具体格式,根据下发命令或获取数值的格式填充 :cpp:member:`i3c_master_ccc_transfer_config_t::direction```I3C_MASTER_TRANSFER_DIRECTION_READ````I3C_MASTER_TRANSFER_DIRECTION_WRITE`` 并填充 :cpp:member:`i3c_master_ccc_transfer_config_t::data`:cpp:member:`i3c_master_ccc_transfer_config_t::data_size`
资源回收
---------
当不再需要之前安装的 I3C 总线或设备,请调用 :cpp:func:`i3c_master_bus_rm_i3c_device`:cpp:func:`i3c_master_bus_rm_i2c_device` 来删除设备,然后调用 :cpp:func:`i3c_del_master_bus` 来回收资源,以释放底层硬件。
.. code:: c
ESP_ERROR_CHECK(i3c_master_bus_rm_i3c_device(i3c_dev_handle));
ESP_ERROR_CHECK(i3c_del_master_bus(bus_handle));
高级功能
========
时钟及时序参数微调
-------------------
时钟源选择
^^^^^^^^^^^^^^^
I3C 总线的时钟源可以通过 :cpp:member:`i3c_master_bus_config_t::clock_source` 进行选择。
.. code:: c
i3c_master_bus_config_t i3c_mst_config = {
// ... 其他配置 ...
.clock_source = I3C_MASTER_CLK_SRC_DEFAULT, // 默认时钟源
};
.. note::
当 I3C 推挽输出频率大于 3 MHz 时,请将时钟源设置为 :cpp:enumerator:`i3c_master_clock_source_t::I3C_MASTER_CLK_SRC_PLL_F120M`:cpp:enumerator:`i3c_master_clock_source_t::I3C_MASTER_CLK_SRC_PLL_F160M`
I3C 驱动提供了丰富的时序参数配置选项,可以根据实际硬件情况调整这些参数以优化性能或解决时序问题。
占空比和保持时间
^^^^^^^^^^^^^^^^^
部分 I3C 从机设备的应答机制ACK/NACK有严格的时序限制比如对 SCL 波形的占空比的要求以及对 SDA 保持时间的要求,这些参数可通过以下配置项进行配置。
.. code:: c
i3c_master_bus_config_t i3c_mst_config = {
// ... 其他配置 ...
.i3c_scl_pp_duty_cycle = 0.5, // Push-Pull 模式占空比,通常为 0.5 (默认值 0 时占空比也为 0.5)
.i3c_scl_od_duty_cycle = 0.5, // Open-Drain 模式占空比,通常为 0.5 (默认值 0 时占空比也为 0.5)
.i3c_sda_od_hold_time_ns = 25, // Open-Drain 模式保持时间,默认 25 ns
.i3c_sda_pp_hold_time_ns = 0, // Push-Pull 模式保持时间,默认 0 ns
};
这些参数的具体值需要根据设备手册和实际测试来确定。
关于事件回调函数
------------------
I3C 驱动支持事件回调机制,可以在传输完成或收到 IBI 中断时通知应用程序。
当 I3C 控制器生成发送或接收完成等事件时,会通过中断告知 CPU。如果需要在发生特定事件时调用函数可以为 I3C 和 I2C 从机分别调用 :cpp:func:`i3c_master_i3c_device_register_event_callbacks`:cpp:func:`i3c_master_i2c_device_register_event_callbacks`,向 I3C 驱动程序的中断服务程序 (ISR) 注册事件回调。由于上述回调函数是在 ISR 中调用的,因此,这些函数不应涉及阻塞操作。可以检查调用 API 的后缀,确保在函数中只调用了后缀为 ISR 的 FreeRTOS API。回调函数具有布尔返回值指示回调是否解除了更高优先级任务的阻塞状态。
有关 I2C 从机的事件回调,请参阅 i2c_master_i2c_event_callbacks_t。
* :cpp:member:`i3c_master_i2c_event_callbacks_t::on_trans_done` 可设置用于主机“传输完成”事件的回调函数。该函数原型在 :cpp:type:`i3c_master_i2c_callback_t` 中声明。请注意,当 I2C 从机设备使能 DMA 且使用异步传输时,才能使用该回调函数,具体请参考 :ref:`dma-support`
有关 I3C 从机的事件回调,请参阅 i3c_master_i3c_event_callbacks_t。
* :cpp:member:`i3c_master_i3c_event_callbacks_t::on_trans_done` 可设置用于主机“传输完成”事件的回调函数。该函数原型在 :cpp:type:`i3c_master_i3c_callback_t` 中声明。请注意,当 I3C 从机设备使能 DMA 且使用异步传输时,才能使用该回调函数,具体请参考 :ref:`dma-support`
* :cpp:member:`i3c_master_i3c_event_callbacks_t::on_ibi` 可设置用于 IBI 事件的回调函数。该函数原型在 :cpp:type:`i3c_master_ibi_callback_t` 中声明, 关于 IBI 事件的详细信息请参阅 :ref:`in-band-interrupt`
.. note::
回调函数在 ISR 上下文中执行,因此:
- 不能执行阻塞操作
- 只能调用后缀为 ISR 的 FreeRTOS API
- 如果启用了 ``CONFIG_I3C_MASTER_ISR_CACHE_SAFE``,回调函数必须放在 IRAM 中
.. _in-band-interrupt:
关于带内中断IBI
---------------------
I3C 协议支持带内中断IBI允许从机设备通过 I3C 总线发送中断请求,无需额外的中断线。
配置 IBI
^^^^^^^^^^^^^^^
I3C 总线配置结构体 :cpp:type:`i3c_master_bus_config_t` 中包含 IBI 相关的全局配置项:
- :cpp:member:`i3c_master_bus_config_t::ibi_rstart_trans_en` 在 IBI 上启用重启事务I3C 控制器在 IBI 完成后继续执行之前被 IBI 中断的命令。若 IBI 发生在总线空闲期间,且 I3C 传输任务非空,则 I3C 控制器将继续执行该任务。若 IBI 与 I3C 控制器传输冲突,并赢得仲裁,则 IBI 处理完成后会继续执行被打断的任务。
- :cpp:member:`i3c_master_bus_config_t::ibi_silent_sir_rejected` 当写入为 0 时当从机中断请求SIR被拒绝时不通知应用层。当写入为 1 时,仍将 IBI 状态写入 IBI FIFO并通知应用层。
- :cpp:member:`i3c_master_bus_config_t::ibi_no_auto_disable` 如果设置,在控制器 NACK In-Band 中断后,不自动禁用 IBI保持带内中断使能。
可以使用 :cpp:func:`i3c_master_i3c_device_ibi_config` 函数为特定设备配置 IBI
.. code:: c
i3c_ibi_config_t ibi_cfg = {
.enable_ibi = true,
.enable_ibi_payload = true, // 允许 IBI 携带有效载荷
};
ESP_ERROR_CHECK(i3c_master_i3c_device_ibi_config(dev_handle, &ibi_cfg));
处理 IBI 事件
^^^^^^^^^^^^^^^^
IBI 事件的详细信息将通过 :cpp:type:`i3c_master_ibi_info_t` 从回调中给出:
:cpp:member:`i3c_master_ibi_info_t::ibi_id` 是 IBI 的原始标识符,通常编码自从机设备的动态地址;它是原始值,即 动态地址 + 读/写位。 :cpp:member:`i3c_master_ibi_info_t::ibi_sts` 是 IBI 状态字段,由控制器报告。:cpp:member:`i3c_master_ibi_info_t::data_length` 是有效载荷缓冲区 :cpp:member:`i3c_master_ibi_info_t::ibi_data` 中的有效字节数。 :cpp:member:`i3c_master_ibi_info_t::ibi_data` 是与 IBI 关联的可选有效载荷。只有前 :cpp:member:`i3c_master_ibi_info_t::data_length` 字节是有效的。
.. code:: c
static bool i3c_ibi_callback(i3c_master_i3c_device_handle_t dev_handle, const i3c_master_ibi_info_t *ibi_info, void *user_ctx)
{
// 可以将 IBI 事件数据复制到用户提供的上下文中, 并在任务中做进一步处理
// i3c_master_ibi_event_t 是用户自定定义的结构体在这里包括了ibi_id 和 ibi_data_len可根据实际需求增删成员
i3c_master_ibi_event_t evt = {
.ibi_id = ibi_info->ibi_id,
.ibi_data_len = ibi_info->data_length,
};
return false;
}
i3c_master_i3c_event_callbacks_t cbs = {
.on_ibi = i3c_ibi_callback,
};
ESP_ERROR_CHECK(i3c_master_i3c_device_register_event_callbacks(dev_handle, &cbs, NULL));
.. _dma-support:
关于 DMA 和异步传输
-------------------
I3C 驱动程序支持 DMA 来支持大容量数据传输和异步传输,可以提高传输效率并减少 CPU 占用。
启用 DMA
^^^^^^^^^^^^
可以通过 :cpp:func:`i3c_master_bus_decorate_dma` 函数为总线配置 DMA
.. code:: c
i3c_master_dma_config_t dma_config = {
.max_transfer_size = 4096, // 最大传输大小(字节)
.dma_burst_size = 16, // DMA 突发大小(字节)
};
ESP_ERROR_CHECK(i3c_master_bus_decorate_dma(bus_handle, &dma_config));
启用异步传输
^^^^^^^^^^^^^^^
当 DMA 被启用时,可以进一步启用异步传输以提高性能:
.. code:: c
i3c_master_bus_config_t i3c_mst_config = {
// ... 其他配置 ...
.trans_queue_depth = 5, // 设置内部传输队列的深度
.flags = {
.enable_async_trans = 1, // 启用异步传输
}
};
此时I3C 主机的传输类函数被调用后会立即返回,当每一次传输完成后会调用 :cpp:member:`i3c_master_i3c_event_callbacks_t::on_trans_done` 回调函数表示一次传输的完成。如果需要等待传输完成,可以调用 :cpp:func:`i3c_master_bus_wait_all_done` 函数来等待所有传输完成:
.. code:: c
// 启动多个异步传输
ESP_ERROR_CHECK(i3c_master_i3c_device_transmit(dev_handle1, data1, size1, -1));
ESP_ERROR_CHECK(i3c_master_i3c_device_transmit(dev_handle2, data2, size2, -1));
ESP_ERROR_CHECK(i3c_master_i3c_device_transmit(dev_handle3, data3, size3, -1));
// 等待所有传输完成
ESP_ERROR_CHECK(i3c_master_bus_wait_all_done(bus_handle, -1));
线程安全
--------
I3C 驱动程序的以下函数是线程安全的,可以从不同的 RTOS 任务调用,无需额外的锁保护:
工厂函数:
- :cpp:func:`i3c_new_master_bus`
- :cpp:func:`i3c_del_master_bus`
I3C 主机操作函数(通过总线操作信号保证线程安全):
- :cpp:func:`i3c_master_bus_add_i3c_static_device`
- :cpp:func:`i3c_master_bus_rm_i3c_device`
- :cpp:func:`i3c_master_i3c_device_transmit`
- :cpp:func:`i3c_master_i3c_device_receive`
- :cpp:func:`i3c_master_i3c_device_transmit_receive`
- :cpp:func:`i3c_master_i2c_device_transmit`
- :cpp:func:`i3c_master_i2c_device_receive`
- :cpp:func:`i3c_master_i2c_device_transmit_receive`
- :cpp:func:`i3c_master_transfer_ccc`
Cache 安全
----------
默认情况下,当 cache 被禁用时(例如 SPI Flash 写入时I3C 中断会被延迟,事件回调函数将无法按时执行,会影响实时应用的系统响应。
可以通过启用 Kconfig 选项 ``CONFIG_I3C_MASTER_ISR_CACHE_SAFE`` 来避免此种情况发生,启用后:
1. 即使在 cache 被禁用的情况下,中断仍可继续运行
2. 将 ISR 使用的所有函数放入 IRAM 中
3. 将驱动程序对象放入 DRAM 中(以防它被意外映射到 PSRAM 中)
启用该选项可以保证 cache 禁用时的中断运行,但会占用更多的 IRAM。
.. note::
启用此选项后,当 cache 被禁用时ISR 回调函数将继续运行。因此,必须确保回调函数及其上下文也是 IRAM 安全的。同时,数据传输的 buffer 也必须放在 DRAM 中。
关于低功耗
------------
当启用电源管理 :ref:`CONFIG_PM_ENABLE` 时,系统在进入睡眠模式前可能会调整或禁用时钟源,从而导致 I3C 传输出错。
为了防止这种情况发生, I3C 驱动内部创建了一个电源管理锁。当调用传输函数后,该锁将被激活,确保系统不会进入睡眠模式,从而保持定时器的正确工作,直至传输完成后,驱动自动释放该锁。使系统能够进入睡眠模式。
Kconfig 选项
------------
以下 Kconfig 选项可用于配置 I3C 驱动程序:
- :ref:`CONFIG_I3C_MASTER_ISR_CACHE_SAFE`:确保 I3C 中断在缓存被禁用时也能正常工作(例如 SPI Flash 写入时)
- :ref:`CONFIG_I3C_MASTER_ISR_HANDLER_IN_IRAM`:将 I3C 主机 ISR 处理程序放入 IRAM 以提高性能并减少缓存未命中
- :ref:`CONFIG_I3C_MASTER_ENABLE_DEBUG_LOG`:启用 I3C 调试日志
关于资源消耗
------------
使用 :doc:`/api-guides/tools/idf-size` 工具可以查看 I3C 驱动的代码和数据消耗。以下是测试前提条件(以 ESP32-P4 为例):
- 编译器优化等级设置为 ``-Os``,以确保代码尺寸最小化。
- 默认日志等级设置为 ``ESP_LOG_INFO``,以平衡调试信息和性能。
- 关闭以下驱动优化选项:
- :ref:`CONFIG_I3C_MASTER_ISR_HANDLER_IN_IRAM` - 中断处理程序不放入 IRAM。
- :ref:`CONFIG_I3C_MASTER_ISR_CACHE_SAFE` - 不启用 Cache 安全选项。
**注意,以下数据不是精确值,仅供参考,在不同型号的芯片上,数据会有所出入。**
+------------------+------------+-------+------+-------+-------+------------+-------+------------+---------+
| Component Layer | Total Size | DIRAM | .bss | .data | .text | Flash Code | .text | Flash Data | .rodata |
+==================+============+=======+======+=======+=======+============+=======+============+=========+
| hal | 30 | 0 | 0 | 0 | 0 | 30 | 18 | 0 | 12 |
+------------------+------------+-------+------+-------+-------+------------+-------+------------+---------+
| driver | 9249 | 12 | 12 | 0 | 0 | 9237 | 8666 | 571 | 571 |
+------------------+------------+-------+------+-------+-------+------------+-------+------------+---------+
应用示例
=============
- :example:`peripherals/i3c/i3c_i2c_basic` 演示了初始化 I3C 主机驱动程序并用 I2C 接口从 ICM42688 传感器读取数据的基本步骤。
- :example:`peripherals/i3c/i3c_lsm6dscx` 演示了如何使用 I3C 主机模式从连接的 LSM6DSOX 传感器读取和写入数据并支持带内中断IBI事件处理。
API 参考
============
I3C 驱动 API
-----------------
.. include-build-file:: inc/i3c_master.inc
I3C 驱动 I2C 从机 API
------------------------
.. include-build-file:: inc/i3c_master_i2c.inc
I3C 驱动类型
---------------------
.. include-build-file:: inc/components/esp_hal_i3c/include/hal/i3c_master_types.inc
I3C HAL 类型
----------------
.. include-build-file:: inc/components/esp_driver_i3c/include/driver/i3c_master_types.inc

View File

@@ -20,6 +20,7 @@
:SOC_DIG_SIGN_SUPPORTED: ds
:SOC_I2C_SUPPORTED: i2c
:SOC_I2S_SUPPORTED: i2s
:SOC_I3C_MASTER_SUPPORTED: i3c_master
:SOC_ISP_SUPPORTED: isp
lcd/index
:SOC_GP_LDO_SUPPORTED: ldo_regulator

View File

@@ -1 +0,0 @@
CONFIG_IDF_EXPERIMENTAL_FEATURES=y

View File

@@ -1 +0,0 @@
CONFIG_IDF_EXPERIMENTAL_FEATURES=y