chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:04:25 +08:00
commit 548b49ebc0
20937 changed files with 5455372 additions and 0 deletions
@@ -0,0 +1,214 @@
Analog to Digital Converter (ADC) Calibration Driver
====================================================
:link_to_translation:`zh_CN:[中文]`
Introduction
------------
In {IDF_TARGET_NAME}, the analog-to-digital converter (ADC) compares the input analog voltage to the reference, and determines each bit of the output digital result. By design, the ADC reference voltage for {IDF_TARGET_NAME} is 1100 mV. However, the true reference voltage can range from 1000 mV to 1200 mV among different chips. This guide introduces the ADC calibration driver to minimize the effect of different reference voltages, and get more accurate output results.
Functional Overview
-------------------
The following sections of this document cover the typical steps to install and use the ADC calibration driver:
.. list::
- :ref:`adc-calibration-scheme-creation` - covers how to create a calibration scheme handle and delete the calibration scheme handle.
- :ref:`adc-result-conversion` - covers how to convert ADC raw result to calibrated result.
- :ref:`adc-thread-safety` - lists which APIs are guaranteed to be thread-safe by the driver.
- :ref:`Minimize Noise <adc-minimize-noise>` - describes a general way to minimize the noise.
:esp32: - :ref:`adc-kconfig-options` - lists the supported Kconfig options that can be used to make a different effect on driver behavior.
.. _adc-calibration-scheme-creation:
Calibration Scheme Creation
^^^^^^^^^^^^^^^^^^^^^^^^^^^
The ADC calibration driver provides ADC calibration scheme(s). From the calibration driver's point of view, an ADC calibration scheme is created for an ADC calibration handle :cpp:type:`adc_cali_handle_t`.
:cpp:func:`adc_cali_check_scheme` can be used to know which calibration scheme is supported on the chip. If you already know the supported schemes, this step can be skipped. Just call the corresponding function to create the scheme handle.
If you use your custom ADC calibration schemes, you could either modify this function :cpp:func:`adc_cali_check_scheme`, or just skip this step and call your custom creation function.
.. only:: esp32 or esp32s2 or esp32c2
ADC Calibration Line Fitting Scheme
```````````````````````````````````
{IDF_TARGET_NAME} supports :c:macro:`ADC_CALI_SCHEME_VER_LINE_FITTING` scheme. To create this scheme, set up :cpp:type:`adc_cali_line_fitting_config_t` first.
- :cpp:member:`adc_cali_line_fitting_config_t::unit_id`, the ADC that your ADC raw results are from.
- :cpp:member:`adc_cali_line_fitting_config_t::atten`, ADC attenuation that your ADC raw results use.
- :cpp:member:`adc_cali_line_fitting_config_t::bitwidth`, bit width of ADC raw result.
.. only:: esp32
There is also a configuration :cpp:member:`adc_cali_line_fitting_config_t::default_vref`. Normally this can be simply set to 0. Line Fitting scheme does not rely on this value. However, if the Line Fitting scheme required eFuse bits are not burned on your board, the driver will rely on this value to do the calibration.
You can use :cpp:func:`adc_cali_scheme_line_fitting_check_efuse` to check the eFuse bits. Normally the Line Fitting scheme eFuse value is :c:macro:`ADC_CALI_LINE_FITTING_EFUSE_VAL_EFUSE_TP` or :c:macro:`ADC_CALI_LINE_FITTING_EFUSE_VAL_EFUSE_VREF`. This means the Line Fitting scheme uses calibration parameters burned in the eFuse to do the calibration.
When the Line Fitting scheme eFuse value is :c:macro:`ADC_CALI_LINE_FITTING_EFUSE_VAL_DEFAULT_VREF`, you need to set the :cpp:member:`esp_adc_cali_line_fitting_init::default_vref`. Default vref is an estimate of the ADC reference voltage provided as a parameter during calibration.
After setting up the configuration structure, call :cpp:func:`adc_cali_create_scheme_line_fitting` to create a Line Fitting calibration scheme handle.
.. only:: esp32s2
This function may fail due to reasons such as :c:macro:`ESP_ERR_INVALID_ARG` or :c:macro:`ESP_ERR_NO_MEM`. Especially, when the function returns :c:macro:`ESP_ERR_NOT_SUPPORTED`, this means the calibration scheme required eFuse bits are not burned on your board.
.. code:: c
ESP_LOGI(TAG, "calibration scheme version is %s", "Line Fitting");
adc_cali_line_fitting_config_t cali_config = {
.unit_id = unit,
.atten = atten,
.bitwidth = ADC_BITWIDTH_DEFAULT,
};
ESP_ERROR_CHECK(adc_cali_create_scheme_line_fitting(&cali_config, &handle));
When the ADC calibration is no longer used, please delete the calibration scheme handle by calling :cpp:func:`adc_cali_delete_scheme_line_fitting`.
Delete Line Fitting Scheme
~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code:: c
ESP_LOGI(TAG, "delete %s calibration scheme", "Line Fitting");
ESP_ERROR_CHECK(adc_cali_delete_scheme_line_fitting(handle));
.. only:: esp32c3 or esp32s3 or esp32c6 or esp32h2 or esp32c5 or esp32p4
ADC Calibration Curve Fitting Scheme
````````````````````````````````````
{IDF_TARGET_NAME} supports :c:macro:`ADC_CALI_SCHEME_VER_CURVE_FITTING` scheme. To create this scheme, set up :cpp:type:`adc_cali_curve_fitting_config_t` first.
.. only:: esp32c3 or esp32s3
- :cpp:member:`adc_cali_curve_fitting_config_t::unit_id`, the ADC that your ADC raw results are from.
- :cpp:member:`adc_cali_curve_fitting_config_t::chan`, this member is kept here for extensibility. The calibration scheme only differs by attenuation, there is no difference among different channels.
- :cpp:member:`adc_cali_curve_fitting_config_t::atten`, ADC attenuation that your ADC raw results use.
- :cpp:member:`adc_cali_curve_fitting_config_t::bitwidth`, bit width of ADC raw result.
.. only:: esp32c6 or esp32h2 or esp32c5 or esp32p4
- :cpp:member:`adc_cali_curve_fitting_config_t::unit_id`, the ADC that your ADC raw results are from.
- :cpp:member:`adc_cali_curve_fitting_config_t::chan`, the ADC channel that your ADC raw results are from. The calibration scheme not only differs by attenuation but is also related to the channels.
- :cpp:member:`adc_cali_curve_fitting_config_t::atten`, ADC attenuation that your ADC raw results use.
- :cpp:member:`adc_cali_curve_fitting_config_t::bitwidth`, bit width of ADC raw result.
After setting up the configuration structure, call :cpp:func:`adc_cali_create_scheme_curve_fitting` to create a Curve Fitting calibration scheme handle. This function may fail due to reasons such as :c:macro:`ESP_ERR_INVALID_ARG` or :c:macro:`ESP_ERR_NO_MEM`.
ADC Calibration eFuse Related Failures
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When the function :cpp:func:`adc_cali_create_scheme_curve_fitting` returns :c:macro:`ESP_ERR_NOT_SUPPORTED`, this means the calibration scheme required eFuse bits are not correct on your board.
The ADC calibration scheme provided by ESP-IDF is based on the values in certain ADC calibration related on-chip eFuse bits. Espressif guarantees that these bits are burned during module manufacturing, so you don't have to burn these eFuses bits yourself.
If you see such an error, please contact us at `Technical Inquiries <https://www.espressif.com/en/contact-us/technical-inquiries>`__ website.
Create Curve Fitting Scheme
~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code:: c
ESP_LOGI(TAG, "calibration scheme version is %s", "Curve Fitting");
adc_cali_curve_fitting_config_t cali_config = {
.unit_id = unit,
.atten = atten,
.bitwidth = ADC_BITWIDTH_DEFAULT,
};
ESP_ERROR_CHECK(adc_cali_create_scheme_curve_fitting(&cali_config, &handle));
When the ADC calibration is no longer used, please delete the calibration scheme driver from the calibration handle by calling :cpp:func:`adc_cali_delete_scheme_curve_fitting`.
Delete Curve Fitting Scheme
~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code:: c
ESP_LOGI(TAG, "delete %s calibration scheme", "Curve Fitting");
ESP_ERROR_CHECK(adc_cali_delete_scheme_curve_fitting(handle));
.. note::
If you want to use your custom calibration schemes, you could provide a creation function to create your calibration scheme handle. Check the function table ``adc_cali_scheme_t`` in ``components/esp_adc/interface/adc_cali_interface.h`` to know the ESP ADC calibration interface.
.. _adc-result-conversion:
Result Conversion
^^^^^^^^^^^^^^^^^
After setting up the calibration characteristics, you can call :cpp:func:`adc_cali_raw_to_voltage` to convert the ADC raw result into calibrated result. The calibrated result is in the unit of mV. This function may fail due to an invalid argument. Especially, if this function returns :c:macro:`ESP_ERR_INVALID_STATE`, this means the calibration scheme is not created. You need to create a calibration scheme handle, use :cpp:func:`adc_cali_check_scheme` to know the supported calibration scheme. On the other hand, you could also provide a custom calibration scheme and create the handle.
.. only:: esp32c2
.. note::
ADC calibration is only supported under :c:macro:`ADC_ATTEN_DB_0` and :c:macro:`ADC_ATTEN_DB_12`. Under :c:macro:`ADC_ATTEN_DB_0`, the attenuation of ADC is set to 0 dB, and input voltage higher than 950 mV is not supported. Under :c:macro:`ADC_ATTEN_DB_12`, the attenuation of ADC is set to 11 dB, and input voltage higher than 2800 mV is not supported.
Get Voltage
~~~~~~~~~~~
.. code:: c
ESP_ERROR_CHECK(adc_cali_raw_to_voltage(adc_cali_handle, adc_raw[0][0], &voltage[0][0]));
ESP_LOGI(TAG, "ADC%d Channel[%d] Cali Voltage: %d mV", ADC_UNIT_1 + 1, EXAMPLE_ADC1_CHAN0, voltage[0][0]);
.. _adc-thread-safety:
Thread Safety
^^^^^^^^^^^^^
The factory function :cpp:func:`esp_adc_cali_new_scheme` is guaranteed to be thread-safe by the driver. Therefore, you can call them from different RTOS tasks without protection by extra locks.
Other functions that take the :cpp:type:`adc_cali_handle_t` as the first positional parameter are not thread-safe, you should avoid calling them from multiple tasks.
.. only:: esp32
.. _adc-kconfig-options:
Kconfig Options
^^^^^^^^^^^^^^^
- :ref:`CONFIG_ADC_CALI_EFUSE_TP_ENABLE` - disable this to decrease the code size, if the calibration eFuse value is not set to :cpp:type:`ADC_CALI_LINE_FITTING_EFUSE_VAL_EFUSE_TP`.
- :ref:`CONFIG_ADC_CALI_EFUSE_VREF_ENABLE` - disable this to decrease the code size, if the calibration eFuse value is not set to :cpp:type:`ADC_CALI_LINE_FITTING_EFUSE_VAL_EFUSE_VREF`.
- :ref:`CONFIG_ADC_CALI_LUT_ENABLE` - disable this to decrease the code size, if you do not calibrate the ADC raw results under :c:macro:`ADC_ATTEN_DB_12`.
.. _adc-minimize-noise:
Minimize Noise
^^^^^^^^^^^^^^
The {IDF_TARGET_NAME} ADC is sensitive to noise, leading to large discrepancies in ADC readings. Depending on the usage scenario, you may need to connect a bypass capacitor (e.g., a 100 nF ceramic capacitor) to the ADC input pad in use, to minimize noise. Besides, multisampling may also be used to further mitigate the effects of noise.
.. only:: esp32
.. figure:: ../../../../_static/diagrams/adc/adc-noise-graph.jpg
:align: center
:alt: ADC noise mitigation
Graph illustrating noise mitigation using capacitor and multisampling of 64 samples.
API Reference
-------------
.. include-build-file:: inc/adc_cali.inc
.. include-build-file:: inc/adc_cali_scheme.inc
@@ -0,0 +1,436 @@
Analog to Digital Converter (ADC) Continuous Mode Driver
========================================================
:link_to_translation:`zh_CN:[中文]`
Introduction
------------
This document describes the ADC continuous mode driver on {IDF_TARGET_NAME}.
In continuous mode, the ADC performs automatic, high-speed sampling on one or more analog input channels, with results efficiently transferred to memory via DMA. This mode is suitable for applications that require periodic or high-frequency data acquisition.
To perform single, on-demand ADC conversions, check :doc:`ADC Oneshot Mode Driver <adc_oneshot>`.
Driver Concepts
---------------
ADC continuous mode organizes data into conversion frames, which consist of multiple conversion results.
- Conversion Frame: A frame contains multiple conversion results. Conversion frame size is configured with :cpp:func:`adc_continuous_new_handle` in bytes.
- Conversion Result: A result consists of multiple bytes, as defined by :c:macro:`SOC_ADC_DIGI_RESULT_BYTES`. Its structure is :cpp:type:`adc_digi_output_data_t`, including the ADC unit, ADC channel, and raw data.
.. image:: /../_static/diagrams/adc/adc_conversion_frame.png
:scale: 100 %
:align: center
Functional Overview
-------------------
The following sections of this document cover the typical steps to install the ADC continuous mode driver, and read ADC conversion results from a group of ADC channels continuously:
- :ref:`adc-continuous-resource-allocation`: covers which parameters should be set up to initialize the ADC continuous mode driver and how to deinitialize it.
- :ref:`adc-continuous-adc-configurations`: describes how to configure the ADC(s) to make it work under continuous mode.
- :ref:`adc-continuous-adc-control`: describes ADC control functions.
- :ref:`adc-continuous-register-event-callbacks`: describes how to hook user-specific code to an ADC continuous mode event callback function.
- :ref:`adc-continuous-read-conversion-result`: covers how to get ADC conversion result.
- :ref:`adc-continuous-hardware-limitations`: describes the ADC-related hardware limitations.
- :ref:`adc-continuous-power-management`: covers power management-related information.
- :ref:`adc-continuous-iram-safe`: covers the IRAM safe functions.
- :ref:`adc-continuous-thread-safety`: lists which APIs are guaranteed to be thread-safe by the driver.
.. _adc-continuous-resource-allocation:
Resource Allocation
^^^^^^^^^^^^^^^^^^^
The ADC continuous mode driver is implemented based on {IDF_TARGET_NAME} SAR ADC module. Different ESP targets might have different numbers of independent ADCs.
To create an ADC continuous mode driver handle, set up the required configuration structure :cpp:type:`adc_continuous_handle_cfg_t`:
- :cpp:member:`adc_continuous_handle_cfg_t::max_store_buf_size`: set the maximum size of the pool in bytes, and the driver saves ADC conversion result into the pool.
- :cpp:member:`adc_continuous_handle_cfg_t::conv_frame_size`: set the size of the ADC conversion frame, in bytes.
- :cpp:member:`adc_continuous_handle_cfg_t::flags`: set the flags that can change the driver's behavior.
- ``flush_pool``: When the pool is full, the old data in the buffer pool will be automatically flushed and new data will be written. Otherwise, when the pool is full, new conversion results will be lost.
After setting up the above configurations for the ADC, call :cpp:func:`adc_continuous_new_handle` with the prepared :cpp:type:`adc_continuous_handle_cfg_t`. This function may fail due to various errors such as invalid arguments, insufficient memory, etc.
.. only:: esp32
Especially, when this function returns :c:macro:`ESP_ERR_NOT_FOUND`, this means the I2S0 peripheral is in use. See :ref:`adc-continuous-hardware-limitations` for more information.
.. only:: esp32s2
Especially, when this function returns :c:macro:`ESP_ERR_NOT_FOUND`, this means the SPI3 peripheral is in use. See :ref:`adc-continuous-hardware-limitations` for more information.
.. only:: SOC_GDMA_SUPPORTED
Especially, when this function returns :c:macro:`ESP_ERR_NOT_FOUND`, this means there is no free GDMA channel.
If the ADC continuous mode driver is no longer used, you should deinitialize the driver by calling :cpp:func:`adc_continuous_deinit`.
.. only:: SOC_ADC_DIG_IIR_FILTER_SUPPORTED
IIR filter
~~~~~~~~~~
Two IIR filters are available when ADC is working in continuous mode. To create an ADC IIR filter, you should set up :cpp:type:`adc_continuous_iir_filter_config_t` and call :cpp:func:`adc_new_continuous_iir_filter`.
- :cpp:member:`adc_digi_filter_config_t::unit`: ADC unit.
- :cpp:member:`adc_digi_filter_config_t::channel`: ADC channel to be filtered.
- :cpp:member:`adc_digi_filter_config_t::coeff`: Filter coefficient.
.. only:: SOC_ADC_DIG_IIR_FILTER_UNIT_BINDED
On {IDF_TARGET_NAME}, the filter is per ADC unit. Once a filter is enabled, all the enabled ADC channels in this ADC unit will be filtered. However, we suggest only enabling one ADC channel per unit, when using the filter feature. Because the filtered results depend on the previous filtered result. So you should not enable multiple ADC channels, to avoid mixing the filtered results.
To recycle a filter, you should call :cpp:func:`adc_del_continuous_iir_filter`.
.. only:: not SOC_ADC_DIG_IIR_FILTER_UNIT_BINDED
.. note::
If you use both filters on the same ADC channel, then only the first one will take effect.
.. only:: SOC_ADC_MONITOR_SUPPORTED
Monitor
~~~~~~~
{IDF_TARGET_SOC_ADC_DIGI_MONITOR_NUM} monitors are available when ADC is working under continuous mode, you can set one or two threshold(s) of a monitor on a working ADC channel, then the monitor will invoke interrupts every sample loop if conversion result outranges of the threshold. To create an ADC monitor, you need to set up the :cpp:type:`adc_monitor_config_t` and call :cpp:func:`adc_new_continuous_monitor`.
- :cpp:member:`adc_monitor_config_t::adc_unit`: Configures which ADC unit the channel you want to monitor belongs to.
- :cpp:member:`adc_monitor_config_t::channel`: The channel you want to monitor.
- :cpp:member:`adc_monitor_config_t::h_threshold`: The high threshold, conversion result larger than this value invokes interrupt, set to -1 if do not use.
- :cpp:member:`adc_monitor_config_t::l_threshold`: The low threshold, conversion result less than this value invokes interrupt, set to -1 if do not use.
Once a monitor is created, you can operate it by following APIs to construct your apps.
- :cpp:func:`adc_continuous_monitor_enable`: Enable a monitor.
- :cpp:func:`adc_continuous_monitor_disable`: Disable a monitor.
- :cpp:func:`adc_continuous_monitor_register_event_callbacks`: register user callbacks to take action when the ADC value exceeds of the thresholds.
- :cpp:func:`adc_del_continuous_monitor`: Delete a created monitor and free resources.
.. only:: esp32s2
.. NOTE::
There are some hardware limitations on {IDF_TARGET_NAME}:
1. Only one threshold is supported for one monitor.
2. Only one monitor is supported for one ADC unit.
3. All enabled channel(s) of a certain ADC unit in ADC continuous mode driver will be monitored. The :cpp:member:`adc_monitor_config_t::channel` parameter will not be used.
Specifically, the monitor function can be used to implement zero-crossing detection. As ADC cannot directly process negative input signals, an extra **DC bias** should be applied to the original signal before measurement.
First, add a DC bias to the input signal through a circuit to "shift" the negative signal into the ADC's measurement range. For the measurement range, please refer to the On-Chip Sensor and Analog Signal Processing chapter in `TRM <{IDF_TARGET_TRM_EN_URL}>`__. For example, adding a 1 V bias would transform a signal from -1 V to +1 V into 0 V to 2 V range. Then by setting the appropriate high and low thresholds, the ADC can detect if the input signal approaches zero, allowing for the identification of phase changes in the signal. Refer to the example code below for details.
.. code:: c
// Initialize the ADC monitor handle
adc_monitor_handle_t adc_monitor_handle = NULL;
// Configure the ADC monitor
adc_monitor_config_t zero_crossing_config = {
.adc_unit = EXAMPLE_ADC_UNIT_1, // Specify the ADC unit to monitor
.channel = EXAMPLE_ADC_CHANNEL_0, // Specify the ADC channel to monitor
.h_threshold = 1100, // Set the high threshold close to the DC bias and adjust it as needed
.l_threshold = 900, // Set the low threshold close to the DC bias and adjust it as needed
};
// Create the ADC monitor
ESP_ERROR_CHECK(adc_new_continuous_monitor(&zero_crossing_config, &adc_monitor_handle));
// Register the callback function
adc_monitor_evt_cbs_t zero_crossing_cbs = {
.on_over_high_thresh = example_on_exceed_high_thresh,
.on_below_low_thresh = example_on_below_low_thresh,
};
ESP_ERROR_CHECK(adc_continuous_monitor_register_event_callbacks(adc_monitor_handle, &zero_crossing_cbs, NULL));
// Enable the ADC monitor
ESP_ERROR_CHECK(adc_continuous_monitor_enable(adc_monitor_handle));
// Disable and delete the ADC monitor
ESP_ERROR_CHECK(adc_continuous_monitor_disable(adc_monitor_handle));
ESP_ERROR_CHECK(adc_del_continuous_monitor(adc_monitor_handle));
Initialize the ADC Continuous Mode Driver
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code:: c
adc_continuous_handle_t handle = NULL;
adc_continuous_handle_cfg_t adc_config = {
.max_store_buf_size = 1024,
.conv_frame_size = 256,
};
ESP_ERROR_CHECK(adc_continuous_new_handle(&adc_config, &handle));
Recycle the ADC Unit
~~~~~~~~~~~~~~~~~~~~
.. code:: c
ESP_ERROR_CHECK(adc_continuous_deinit(handle));
.. _adc-continuous-adc-configurations:
ADC Configurations
^^^^^^^^^^^^^^^^^^
After the ADC continuous mode driver is initialized, set up the :cpp:type:`adc_continuous_config_t` to configure ADC IOs to measure analog signal:
- :cpp:member:`adc_continuous_config_t::pattern_num`: number of ADC channels that will be used.
- :cpp:member:`adc_continuous_config_t::adc_pattern`: list of configs for each ADC channel that will be used, see the description below.
- :cpp:member:`adc_continuous_config_t::sample_freq_hz`: expected ADC sampling frequency in Hz.
- :cpp:member:`adc_continuous_config_t::conv_mode`: continuous conversion mode.
- :cpp:member:`adc_continuous_config_t::format`: conversion output format.
Set :cpp:type:`adc_digi_pattern_config_t` with the following process:
- :cpp:member:`adc_digi_pattern_config_t::atten`: ADC attenuation. Refer to the On-Chip Sensor and Analog Signal Processing chapter in `TRM <{IDF_TARGET_TRM_EN_URL}>`__.
- :cpp:member:`adc_digi_pattern_config_t::channel`: the IO corresponding ADC channel number. See the note below.
- :cpp:member:`adc_digi_pattern_config_t::unit`: the ADC that the IO is subordinate to.
- :cpp:member:`adc_digi_pattern_config_t::bit_width`: the bitwidth of the raw conversion result.
.. note::
For the IO corresponding ADC channel number, check `TRM <{IDF_TARGET_TRM_EN_URL}#sensor>`__ to acquire the ADC IOs. Besides, :cpp:func:`adc_continuous_io_to_channel` and :cpp:func:`adc_continuous_channel_to_io` can be used to acquire the ADC channels and ADC IOs.
To make these settings take effect, call :cpp:func:`adc_continuous_config` with the configuration structure above. This API may fail due to reasons like :c:macro:`ESP_ERR_INVALID_ARG`. When it returns :c:macro:`ESP_ERR_INVALID_STATE`, this means the ADC continuous mode driver is started, you should not call this API at this moment.
See ADC continuous mode example :example:`peripherals/adc/continuous_read` to see configuration codes.
.. only:: SOC_ADC_DIG_IIR_FILTER_SUPPORTED
To enable/disable the ADC IIR filter, you should call :cpp:func:`adc_continuous_iir_filter_enable` / :cpp:func:`adc_continuous_iir_filter_disable`.
.. only:: SOC_ADC_MONITOR_SUPPORTED
To enable/disable the ADC monitor, you should call :cpp:func:`adc_continuous_monitor_enable` / :cpp:func:`adc_continuous_monitor_disable`.
.. _adc-continuous-adc-control:
ADC Control
^^^^^^^^^^^
Start and Stop
~~~~~~~~~~~~~~
Calling :cpp:func:`adc_continuous_start` makes the ADC start to measure analog signals from the configured ADC channels, and generate the conversion results.
On the contrary, calling :cpp:func:`adc_continuous_stop` stops the ADC conversion.
.. code::c
ESP_ERROR_CHECK(adc_continuous_start(handle));
.. code:: c
ESP_ERROR_CHECK(adc_continuous_stop(handle));
.. _adc-continuous-register-event-callbacks:
Register Event Callbacks
^^^^^^^^^^^^^^^^^^^^^^^^
By calling :cpp:func:`adc_continuous_register_event_callbacks`, you can hook your own function to the driver ISR. Supported event callbacks are listed in :cpp:type:`adc_continuous_evt_cbs_t`.
- :cpp:member:`adc_continuous_evt_cbs_t::on_conv_done`: this is invoked when one conversion frame finishes.
- :cpp:member:`adc_continuous_evt_cbs_t::on_pool_ovf`: this is invoked when the internal pool is full. Newer conversion results will be discarded.
As the above callbacks are called in an ISR context, you should always ensure the callback function is suitable for an ISR context. Blocking logic should not appear in these callbacks. The callback function prototype is declared in :cpp:type:`adc_continuous_callback_t`.
You can also register your own context when calling :cpp:func:`adc_continuous_register_event_callbacks` by the parameter ``user_data``. This user data will be passed to the callback functions directly.
This function may fail due to reasons like :c:macro:`ESP_ERR_INVALID_ARG`. Especially, when :ref:`CONFIG_ADC_CONTINUOUS_ISR_IRAM_SAFE` is enabled, this error may indicate that the callback functions are not in the internal RAM. Check the error log for more details. Besides, when it fails due to :c:macro:`ESP_ERR_INVALID_STATE`, it indicates that the ADC continuous mode driver is started, and you should not add a callback at this moment.
Conversion Done Event
~~~~~~~~~~~~~~~~~~~~~
When the driver completes a conversion, it triggers the :cpp:member:`adc_continuous_evt_cbs_t::on_conv_done` event and fills the event data. Event data contains a buffer pointer to a conversion frame buffer, together with the size. Refer to :cpp:type:`adc_continuous_evt_data_t` to know the event data structure.
.. note::
It is worth noting that, the data buffer :cpp:member:`adc_continuous_evt_data_t::conv_frame_buffer` is maintained by the driver itself. Therefore, never free this piece of memory.
.. note::
When the Kconfig option :ref:`CONFIG_ADC_CONTINUOUS_ISR_IRAM_SAFE` is enabled, the registered callbacks and the functions called by the callbacks should be placed in IRAM. The involved variables should be placed in internal RAM as well.
Pool Overflow Event
~~~~~~~~~~~~~~~~~~~
The ADC continuous mode driver has an internal pool to save the conversion results. When the pool is full, a pool overflow event will emerge. Under this condition, the driver will not fill in the event data. This usually happens because the speed to read data from the pool by calling :cpp:func:`adc_continuous_read` is much slower than the ADC conversion speed.
.. _adc-continuous-read-conversion-result:
Read Conversion Result
^^^^^^^^^^^^^^^^^^^^^^
After calling :cpp:func:`adc_continuous_start`, the ADC continuous conversion starts. Call :cpp:func:`adc_continuous_read` to get the conversion results of the ADC channels. You need to provide a buffer to get the raw results.
Function :cpp:func:`adc_continuous_read` tries to read the expected length of conversion results each time.
- When calling :cpp:func:`adc_continuous_read`, you can request to read a conversion result of the specified length. Sometimes, however, the actual available conversion results may be less than the requested length, in which case the function still moves the data from the internal pool into the buffer you provided. Therefore, to learn the number of conversion results actually moved into the buffer, please check the value of ``out_length``.
- If there is no conversion result generated in the internal pool, the function will block for ``timeout_ms`` until at least one conversion result is generated. If there are still no generated results, the function will return :c:macro:`ESP_ERR_TIMEOUT`.
- If the generated results fill up the internal pool, newly generated results will be lost. Next time when :cpp:func:`adc_continuous_read` is called, this function will return :c:macro:`ESP_ERR_INVALID_STATE` to indicate this situation.
This API aims to give you a chance to read all the ADC continuous conversion results.
The ADC conversion results read from the above function are raw data. To calculate the voltage based on the ADC raw results, this formula can be used:
.. parsed-literal::
Vout = Dout * Vmax / Dmax (1)
where:
.. list-table::
:header-rows: 1
:widths: 20 80
:align: center
* - Vout
- Digital output result, standing for the voltage.
* - Dout
- ADC raw digital reading result.
* - Vmax
- Maximum measurable input analog voltage, this is related to the ADC attenuation, please refer to the On-Chip Sensor and Analog Signal Processing chapter in `TRM <{IDF_TARGET_TRM_EN_URL}#sensor>`__.
* - Dmax
- Maximum of the output ADC raw digital reading result, which is 2^bitwidth, where the bitwidth is the :cpp:member:`adc_digi_pattern_config_t::bit_width` configured before.
To do further calibration to convert the ADC raw result to voltage in mV, please refer to :doc:`adc_calibration`.
Parse ADC Raw Data
~~~~~~~~~~~~~~~~~~~~~
The raw data read from ADC continuous mode needs to be further parsed to obtain usable ADC conversion results. The function :cpp:func:`adc_continuous_parse_data` provides the functionality to parse raw data into structured ADC data.
.. note::
Input buffer requirements:
- **Length alignment**: `raw_data_size` must be a multiple of :c:macro:`SOC_ADC_DIGI_RESULT_BYTES`
- **Buffer size**: Ensure the `raw_data` buffer is large enough to hold `raw_data_size` bytes of data
.. code:: c
// Read raw data
uint32_t ret_num = 0;
esp_err_t ret = adc_continuous_read(handle, result, EXAMPLE_READ_LEN, &ret_num, 0);
if (ret == ESP_OK) {
// Parse raw data
adc_continuous_data_t parsed_data[ret_num / SOC_ADC_DIGI_RESULT_BYTES];
uint32_t num_parsed_samples = 0;
esp_err_t parse_ret = adc_continuous_parse_data(handle, result, ret_num, parsed_data, &num_parsed_samples);
if (parse_ret == ESP_OK) {
for (int i = 0; i < num_parsed_samples; i++) {
if (parsed_data[i].valid) {
ESP_LOGI(TAG, "ADC%d, Channel: %d, Value: %"PRIu32,
parsed_data[i].unit + 1,
parsed_data[i].channel,
parsed_data[i].raw_data);
}
}
}
}
The parsed data structure :cpp:type:`adc_continuous_data_t` contains the following information:
- :cpp:member:`adc_continuous_data_t::unit`ADC unit (ADC_UNIT_1 or ADC_UNIT_2)
- :cpp:member:`adc_continuous_data_t::channel`ADC channel number (0-9)
- :cpp:member:`adc_continuous_data_t::raw_data`ADC raw data value
- :cpp:member:`adc_continuous_data_t::valid`Whether the data is valid
Read and Parse ADC Data
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To simplify the usage flow, the function :cpp:func:`adc_continuous_read_parse` is provided, which merges the read and parse operations into a single function call.
.. code:: c
// Using the read and parse function
adc_continuous_data_t parsed_data[64]; // User specifies maximum number of samples
uint32_t num_samples = 0;
esp_err_t ret = adc_continuous_read_parse(handle, parsed_data, 64, &num_samples, 1000);
if (ret == ESP_OK) {
for (int i = 0; i < num_samples; i++) {
if (parsed_data[i].valid) {
ESP_LOGI(TAG, "ADC%d, Channel: %d, Value: %"PRIu32,
parsed_data[i].unit + 1,
parsed_data[i].channel,
parsed_data[i].raw_data);
}
}
}
.. _adc-continuous-hardware-limitations:
.. _hardware_limitations_adc_continuous:
Hardware Limitations
^^^^^^^^^^^^^^^^^^^^
.. list::
- A specific ADC unit can only work under one operating mode at any one time, either continuous mode or one-shot mode. :cpp:func:`adc_continuous_start` has provided the protection.
:not esp32s31: - Random Number Generator (RNG) uses ADC as an input source. When ADC continuous mode driver works, the random number generated from RNG will be less random.
:SOC_ADC_DIFF_SUPPORTED: - In single-ended mode, if the N-side channel is used as the input interface, its raw-data polarity is inverted. For an input range of -2 V to 2 V, the N-side raw code is 4393 to 0. Please use the ADC calibration APIs.
:esp32 or esp32s2: - ADC2 is also used by Wi-Fi. :cpp:func:`adc_continuous_start` has provided the protection between Wi-Fi driver and ADC continuous mode driver.
:esp32: - ADC continuous mode driver uses I2S0 peripheral as hardware DMA FIFO. Therefore, if I2S0 is in use already, the :cpp:func:`adc_continuous_new_handle` will return :c:macro:`ESP_ERR_NOT_FOUND`.
:esp32: - ESP32 DevKitC: GPIO 0 cannot be used due to external auto program circuits.
:esp32: - ESP-WROVER-KIT: GPIO 0, 2, 4, and 15 cannot be used due to external connections for different purposes.
:esp32s2: - ADC continuous mode driver uses SPI3 peripheral as hardware DMA FIFO. Therefore, if SPI3 is in use already, the :cpp:func:`adc_continuous_new_handle` will return :c:macro:`ESP_ERR_NOT_FOUND`.
:esp32c3: - ADC2 DMA functionality is no longer supported to retrieve ADC conversion results due to hardware limitations, as unstable results have been observed. This issue can be found in `ESP32C3 Errata <https://www.espressif.com/sites/default/files/documentation/esp32-c3_errata_en.pdf>`_. For compatibility, you can enable :ref:`CONFIG_ADC_CONTINUOUS_FORCE_USE_ADC2_ON_C3_S3` to force use ADC2.
:esp32s3: - ADC2 DMA functionality is no longer supported to retrieve ADC conversion results due to hardware limitations, as unstable results have been observed. This issue can be found in `ESP32S3 Errata <https://www.espressif.com/sites/default/files/documentation/esp32-s3_errata_en.pdf>`_. For compatibility, you can enable :ref:`CONFIG_ADC_CONTINUOUS_FORCE_USE_ADC2_ON_C3_S3` to force use ADC2.
.. _adc-continuous-power-management:
Power Management
^^^^^^^^^^^^^^^^
When power management is enabled, i.e., :ref:`CONFIG_PM_ENABLE` is on, the APB clock frequency may be adjusted when the system is in an idle state, thus potentially changing the behavior of ADC continuous conversion.
However, the continuous mode driver can prevent this change by acquiring a power management lock of type :cpp:enumerator:`ESP_PM_APB_FREQ_MAX`. The lock is acquired after the continuous conversion is started by :cpp:func:`adc_continuous_start`. Similarly, the lock will be released after :cpp:func:`adc_continuous_stop`. Therefore, :cpp:func:`adc_continuous_start` and :cpp:func:`adc_continuous_stop` should appear in pairs, otherwise, the power management will be out of action.
.. _adc-continuous-iram-safe:
IRAM Safe
^^^^^^^^^
All the ADC continuous mode driver APIs are not IRAM-safe. They are not supposed to be run when the Cache is disabled. By enabling the Kconfig option :ref:`CONFIG_ADC_CONTINUOUS_ISR_IRAM_SAFE`, the driver's internal ISR handler is IRAM-safe, which means even when the Cache is disabled, the driver will still save the conversion results into its internal pool.
.. _adc-continuous-thread-safety:
Thread Safety
^^^^^^^^^^^^^
ADC continuous mode driver APIs are not guaranteed to be thread-safe. However, the share hardware mutual exclusion is provided by the driver. See :ref:`adc-continuous-hardware-limitations` for more details.
Application Examples
--------------------
* :example:`peripherals/adc/continuous_read` demonstrates how to use the ADC Continuous Read Mode (DMA Mode) on {IDF_TARGET_NAME} development boards to read from GPIO pins via on-chip ADC modules.
API Reference
-------------
.. include-build-file:: inc/adc_continuous.inc
@@ -0,0 +1,216 @@
Analog to Digital Converter (ADC) Oneshot Mode Driver
=====================================================
:link_to_translation:`zh_CN:[中文]`
Introduction
------------
This document describes the ADC oneshot mode driver on {IDF_TARGET_NAME}.
Oneshot mode allows you to perform single, on-demand ADC conversions on selected analog input channels. It is suitable for applications that require infrequent or triggered sampling, as opposed to continuous data acquisition.
.. only:: SOC_ADC_DMA_SUPPORTED
To perform continuous data acquisition, {IDF_TARGET_NAME} provides :doc:`ADC Continuous Mode Driver <adc_continuous>`.
Functional Overview
-------------------
The following sections of this document cover the typical steps to install and operate an ADC:
- :ref:`adc-oneshot-resource-allocation` - covers which parameters should be set up to get an ADC handle and how to recycle the resources when ADC finishes working.
- :ref:`adc-oneshot-unit-configuration` - covers the parameters that should be set up to configure the ADC unit, so as to get ADC conversion raw result.
- :ref:`adc-oneshot-read-conversion-result` - covers how to get ADC conversion raw result.
- :ref:`hardware_limitations_adc_oneshot` - describes the ADC-related hardware limitations.
- :ref:`adc-oneshot-power-management` - covers power management-related information.
- :ref:`adc-oneshot-iram-safe` - describes tips on how to read ADC conversion raw results when the cache is disabled.
- :ref:`adc-oneshot-thread-safety` - lists which APIs are guaranteed to be thread-safe by the driver.
- :ref:`adc-oneshot-kconfig-options` - lists the supported Kconfig options that can be used to make a different effect on driver behavior.
.. _adc-oneshot-resource-allocation:
Resource Allocation
^^^^^^^^^^^^^^^^^^^
The ADC oneshot mode driver is implemented based on {IDF_TARGET_NAME} SAR ADC module. Different ESP chips might have different numbers of independent ADCs. From the oneshot mode driver's point of view, an ADC instance is represented by :cpp:type:`adc_oneshot_unit_handle_t`.
To install an ADC instance, set up the required initial configuration structure :cpp:type:`adc_oneshot_unit_init_cfg_t`:
- :cpp:member:`adc_oneshot_unit_init_cfg_t::unit_id` selects the ADC. Please refer to the `datasheet <{IDF_TARGET_TRM_EN_URL}>`__ to know dedicated analog IOs for this ADC.
- :cpp:member:`adc_oneshot_unit_init_cfg_t::clk_src` selects the source clock of the ADC. If set to 0, the driver will fall back to using a default clock source, see :cpp:type:`adc_oneshot_clk_src_t` to know the details.
- :cpp:member:`adc_oneshot_unit_init_cfg_t::ulp_mode` sets if the ADC will be working under ULP mode.
.. todo::
Add ULP ADC-related docs here.
After setting up the initial configurations for the ADC, call :cpp:func:`adc_oneshot_new_unit` with the prepared :cpp:type:`adc_oneshot_unit_init_cfg_t`. This function will return an ADC unit handle if the allocation is successful.
This function may fail due to various errors such as invalid arguments, insufficient memory, etc. Specifically, when the to-be-allocated ADC instance is registered already, this function will return :c:macro:`ESP_ERR_NOT_FOUND` error. Number of available ADC(s) is recorded by :c:macro:`SOC_ADC_PERIPH_NUM`.
If a previously created ADC instance is no longer required, you should recycle the ADC instance by calling :cpp:func:`adc_oneshot_del_unit`, related hardware and software resources will be recycled as well.
Create an ADC Unit Handle Under Normal Oneshot Mode
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code:: c
adc_oneshot_unit_handle_t adc1_handle;
adc_oneshot_unit_init_cfg_t init_config1 = {
.unit_id = ADC_UNIT_1,
.ulp_mode = ADC_ULP_MODE_DISABLE,
};
ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config1, &adc1_handle));
Recycle the ADC Unit
~~~~~~~~~~~~~~~~~~~~
.. code:: c
ESP_ERROR_CHECK(adc_oneshot_del_unit(adc1_handle));
.. _adc-oneshot-unit-configuration:
Unit Configuration
^^^^^^^^^^^^^^^^^^
After an ADC instance is created, set up the :cpp:type:`adc_oneshot_chan_cfg_t` to configure ADC IOs to measure analog signal:
- :cpp:member:`adc_oneshot_chan_cfg_t::atten`, ADC attenuation. Refer to `Datasheet <{IDF_TARGET_DATASHEET_EN_URL}>`__ > ``ADC Characteristics``.
- :cpp:member:`adc_oneshot_chan_cfg_t::bitwidth`, the bitwidth of the raw conversion result.
.. note::
For the IO corresponding ADC channel number, check `datasheet <{IDF_TARGET_TRM_EN_URL}>`__ to know the ADC IOs.
Additionally, :cpp:func:`adc_continuous_io_to_channel` and :cpp:func:`adc_continuous_channel_to_io` can be used to know the ADC channels and ADC IOs.
To make these settings take effect, call :cpp:func:`adc_oneshot_config_channel` with the above configuration structure. You should specify an ADC channel to be configured as well. Function :cpp:func:`adc_oneshot_config_channel` can be called multiple times to configure different ADC channels. The Driver will save each of these channel configurations internally.
Configure Two ADC Channels
~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code:: c
adc_oneshot_chan_cfg_t config = {
.bitwidth = ADC_BITWIDTH_DEFAULT,
.atten = ADC_ATTEN_DB_12,
};
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle, EXAMPLE_ADC1_CHAN0, &config));
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle, EXAMPLE_ADC1_CHAN1, &config));
.. _adc-oneshot-read-conversion-result:
Read Conversion Result
^^^^^^^^^^^^^^^^^^^^^^
After above configurations, the ADC is ready to measure the analog signal(s) from the configured ADC channel(s). Call :cpp:func:`adc_oneshot_read` to get the conversion raw result of an ADC channel.
- :cpp:func:`adc_oneshot_read` is safe to use. ADC(s) are shared by some other drivers/peripherals, see :ref:`hardware_limitations_adc_oneshot`. This function uses mutexes to avoid concurrent hardware usage. Therefore, this function should not be used in an ISR context. This function may fail when the ADC is in use by other drivers/peripherals, and return :c:macro:`ESP_ERR_TIMEOUT`. Under this condition, the ADC raw result is invalid.
This function will fail due to invalid arguments.
The ADC conversion results read from this function are raw data. To calculate the voltage based on the ADC raw results, this formula can be used:
.. parsed-literal::
Vout = Dout * Vmax / Dmax (1)
where:
.. list-table::
:header-rows: 1
:widths: 20 80
:align: center
* - Vout
- Digital output result, standing for the voltage.
* - Dout
- ADC raw digital reading result.
* - Vmax
- Maximum measurable input analog voltage, this is related to the ADC attenuation, please refer to `TRM <{IDF_TARGET_TRM_EN_URL}>`__ > ``On-Chip Sensor and Analog Signal Processing``.
* - Dmax
- Maximum of the output ADC raw digital reading result, which is 2^bitwidth, where bitwidth is the :cpp:member:`adc_oneshot_chan_cfg_t::bitwidth` configured before.
For further calibration to convert the ADC raw result to voltage in mV, please refer to :doc:`adc_calibration`.
Read Raw Result
~~~~~~~~~~~~~~~
.. code:: c
ESP_ERROR_CHECK(adc_oneshot_read(adc1_handle, EXAMPLE_ADC1_CHAN0, &adc_raw[0][0]));
ESP_LOGI(TAG, "ADC%d Channel[%d] Raw Data: %d", ADC_UNIT_1 + 1, EXAMPLE_ADC1_CHAN0, adc_raw[0][0]);
ESP_ERROR_CHECK(adc_oneshot_read(adc1_handle, EXAMPLE_ADC1_CHAN1, &adc_raw[0][1]));
ESP_LOGI(TAG, "ADC%d Channel[%d] Raw Data: %d", ADC_UNIT_1 + 1, EXAMPLE_ADC1_CHAN1, adc_raw[0][1]);
.. _hardware_limitations_adc_oneshot:
Hardware Limitations
^^^^^^^^^^^^^^^^^^^^
.. list::
:not esp32s31: - Random Number Generator (RNG) uses ADC as an input source. When ADC :cpp:func:`adc_oneshot_read` works, the random number generated from RNG will be less random.
:SOC_ADC_DMA_SUPPORTED: - A specific ADC unit can only work under one operating mode at any one time, either continuous mode or oneshot mode. :cpp:func:`adc_oneshot_read` has provided the protection.
:SOC_ADC_DIFF_SUPPORTED: - In single-ended mode, if the N-side channel is used as the input interface, its raw-data polarity is inverted. For an input range of -2 to 2, the N-side raw code is 4393 to 0. Please use the ADC calibration APIs.
:esp32 or esp32s2 or esp32s3: - ADC2 is also used by Wi-Fi. :cpp:func:`adc_oneshot_read` has provided protection between the Wi-Fi driver and ADC oneshot mode driver.
:esp32c3: - ADC2 oneshot mode is no longer supported, due to hardware limitations. The results are not stable. This issue can be found in `ESP32-C3 Series SoC Errata <https://www.espressif.com/sites/default/files/documentation/esp32-c3_errata_en.pdf>`_. For compatibility, you can enable :ref:`CONFIG_ADC_ONESHOT_FORCE_USE_ADC2_ON_C3` to force use ADC2.
:esp32: - ESP32-DevKitC: GPIO0 cannot be used in oneshot mode, because the DevKit has used it for auto-flash.
:esp32: - ESP-WROVER-KIT: GPIO 0, 2, 4, and 15 cannot be used due to external connections for different purposes.
.. _adc-oneshot-power-management:
Power Management
^^^^^^^^^^^^^^^^
When power management is enabled, i.e., :ref:`CONFIG_PM_ENABLE` is on, the system clock frequency may be adjusted when the system is in an idle state. However, the ADC oneshot mode driver works in a polling routine, the :cpp:func:`adc_oneshot_read` will poll the CPU until the function returns. During this period of time, the task in which ADC oneshot mode driver resides will not be blocked. Therefore the clock frequency is stable when reading.
.. _adc-oneshot-iram-safe:
IRAM Safe
^^^^^^^^^
By default, all the ADC oneshot mode driver APIs are not supposed to be run when the Cache is disabled. Cache may be disabled due to many reasons, such as Flash writing/erasing, OTA, etc. If these APIs execute when the Cache is disabled, you will probably see errors like ``Illegal Instruction`` or ``Load/Store Prohibited``.
.. _adc-oneshot-thread-safety:
Thread Safety
^^^^^^^^^^^^^
- :cpp:func:`adc_oneshot_new_unit`
- :cpp:func:`adc_oneshot_config_channel`
- :cpp:func:`adc_oneshot_read`
- :cpp:func:`adc_oneshot_del_unit`
Above functions are guaranteed to be thread-safe. Therefore, you can call them from different RTOS tasks without protection by extra locks.
.. _adc-oneshot-kconfig-options:
Kconfig Options
^^^^^^^^^^^^^^^
- :ref:`CONFIG_ADC_ONESHOT_CTRL_FUNC_IN_IRAM` controls where to place the ADC fast read function (IRAM or Flash), see :ref:`adc-oneshot-iram-safe` for more details.
Application Examples
--------------------
* :example:`peripherals/adc/oneshot_read` demonstrates how to obtain a one-shot ADC reading from a GPIO pin using the ADC one-shot mode driver and how to use the ADC Calibration functions to obtain a calibrated result in mV on {IDF_TARGET_NAME}.
API Reference
-------------
.. include-build-file:: inc/adc_oneshot.inc
@@ -0,0 +1,102 @@
Analog to Digital Converter (ADC)
=================================
:link_to_translation:`zh_CN:[中文]`
Overview
--------
This guide provides a comprehensive overview of the ADC (Analog to Digital Converter) controller on {IDF_TARGET_NAME}. It begins by introducing core ADC concepts such as conversion principles, raw data resolution, reference voltage, and attenuation. Then it walks through the two supported ADC driver modes — oneshot mode and continuous mode — along with ADC calibration, which helps improve accuracy.
{IDF_TARGET_NAME} integrates {IDF_TARGET_SOC_ADC_PERIPH_NUM} ADC(s) for measuring analog signals from multiple input channels. For details about the number of measurement channels (analog-enabled pins), voltage ranges, and other ADC characteristics, please refer to the `datasheet <{IDF_TARGET_DATASHEET_EN_URL}>`__.
ADC Conversion
---------------
ADC conversion is the process of converting an input analog voltage to a digital value. The results provided by the ADC driver APIs are raw data values that represent the analog input in digital form.
By default, the bit width of these raw ADC results is 12 bits. This means the input voltage range is divided into 4096 (2\ :sup:`12`) discrete levels, which defines the minimum detectable change in input signal.
The voltage ``Vdata`` corresponding to a raw ADC result ``data`` is calculated as:
.. math::
V_{data} = \frac{data}{2^{bitwidth} - 1} \times V_{ref}
Where:
- ``data`` is the raw ADC result.
- ``bitwidth`` is the resolution of the ADC result (e.g., 12 bits).
- ``Vref`` is the ADCs reference voltage.
By design, ``Vref`` is set to 1100 mV. However, due to manufacturing variations, the actual value may range between 1000 mV and 1200 mV depending on the chip.
To obtain calibrated and accurate voltage values, refer to the section :doc:`adc_calibration`, which explains how to use the ADC calibration driver to adjust the raw results based on the actual ``Vref`` value.
ADC Attenuation
---------------
The ADC can measure analog voltages from 0 V to ``Vref``. To measure higher voltages, input signals can be attenuated before being passed to the ADC.
The supported attenuation levels are:
- 0 dB (k≈100%)
- 2.5 dB (k≈75%)
- 6 dB (k≈50%)
- 12 dB (k≈25%)
Higher attenuation levels allow the ADC to measure higher input voltages. The voltage ``Vdata`` after applying attenuation can be calculated using:
.. math::
V_{data} = \frac{V_{ref}}{k}\times{\frac{data}{2^{bitwidth} - 1}}
Where:
- ``k`` is the ratio value corresponding to the attenuation level.
- Other variables are as defined above.
.. only:: not esp32
For detailed input voltage ranges associated with each attenuation setting, refer to the `datasheet <{IDF_TARGET_DATASHEET_EN_URL}>`__ > Electrical Characteristics > ADC Characteristics.
.. only:: esp32
For detailed input voltage ranges associated with each attenuation setting, refer to the `datasheet <{IDF_TARGET_DATASHEET_EN_URL}>`__ > Function Description > Analog Peripherals > Analog-to-Digital Converter (ADC).
Driver Usage
------------
.. list::
- ADC unit supports **oneshot mode**. Oneshot mode is suitable for oneshot sampling: ADC samples one channel at a time.
:SOC_ADC_DMA_SUPPORTED: - Each ADC unit supports **continuous mode**. Continuous mode is designed for continuous sampling: ADC sequentially samples a group of channels or continuously samples a single channel.
See the guide below for implementation details:
.. toctree::
:maxdepth: 2
adc_oneshot
:SOC_ADC_DMA_SUPPORTED: adc_continuous
ADC Calibration
----------------
The ADC calibration driver corrects deviations through software to obtain more accurate output results.
For more information, refer to the following guide:
.. toctree::
:maxdepth: 2
adc_calibration
API Reference
-------------
.. include-build-file:: inc/adc_channel.inc
.. include-build-file:: inc/adc_types.inc
@@ -0,0 +1,598 @@
=================
Analog Comparator
=================
:link_to_translation:`zh_CN:[中文]`
.. contents::
:local:
:depth: 2
Overview
========
An analog comparator tells you whether a source signal is above or below a reference signal. The reference can come from either an internal generator or an external input.
This makes the analog comparator useful for tasks such as:
- Detecting whether a waveform crosses a fixed threshold
- Turning a sine wave into a digital square wave around a chosen reference level
- Comparing one analog signal against another analog signal
- Generating interrupts or ETM events when the comparison result changes
- Building simple hardware signal-processing pipelines without continuous CPU involvement
.. note::
The analog comparator peripheral is not a continuous-time comparator in the same sense as a discrete analog comparator IC or an op-amp based analog front end. It is driven by a source clock, so comparison results are sampled in hardware time. In practice, clock source, scan timing, debounce, and ETM configuration all affect what you observe on real signals.
Quick Start
===========
If you are new to this peripheral, start with the simplest workflow:
1. Create a comparator unit
2. Select the source signal and reference source
3. Configure the internal reference or external reference input
4. Optionally configure debounce
5. Register a callback or create ETM connections
6. Enable the comparator
The following example uses an internal reference and an interrupt callback to detect when a source signal crosses 50% VDD:
.. code:: c
ana_cmpr_handle_t cmpr = NULL;
ana_cmpr_config_t config = {
.unit = 0, // Pick a comparator unit that is available on your target
.clk_src = ANA_CMPR_CLK_SRC_DEFAULT, // Most applications can use the default clock source
.ref_src = ANA_CMPR_REF_SRC_INTERNAL, // Use the internal reference for a simple fixed threshold
.cross_type = ANA_CMPR_CROSS_ANY, // Trigger on both positive and negative crossings
.src_chan0_gpio = 0, // Replace with the analog comparator source GPIO wired on your board
};
ESP_ERROR_CHECK(ana_cmpr_new_unit(&config, &cmpr));
ana_cmpr_internal_ref_config_t ref_cfg = {
.ref_volt = ANA_CMPR_REF_VOLT_50_PCT_VDD, // Set the internal reference voltage to 50% of VDD, i.e., compare whether the source signal is above or below half of the supply voltage
};
ESP_ERROR_CHECK(ana_cmpr_set_internal_reference(cmpr, &ref_cfg));
ana_cmpr_debounce_config_t dbc_cfg = {
.wait_us = 10, // Set a debounce time of 10 microseconds to suppress duplicate interrupts from noisy signals around the threshold
};
ESP_ERROR_CHECK(ana_cmpr_set_debounce(cmpr, &dbc_cfg));
ana_cmpr_event_callbacks_t cbs = {
.on_cross = example_ana_cmpr_on_cross_callback, // Register a callback to be called when a crossing event occurs; this callback should be defined elsewhere in your code
};
ESP_ERROR_CHECK(ana_cmpr_register_event_callbacks(cmpr, &cbs, NULL));
ESP_ERROR_CHECK(ana_cmpr_enable(cmpr));
This flow introduces the most important ideas:
- :cpp:func:`ana_cmpr_new_unit` creates a comparator instance and selects the source GPIO and reference source.
- :cpp:func:`ana_cmpr_set_internal_reference` chooses the threshold when using the internal reference.
- :cpp:func:`ana_cmpr_set_debounce` can suppress duplicate crossing interrupts caused by noisy or slow-moving signals.
- :cpp:func:`ana_cmpr_register_event_callbacks` lets your code react when the comparison result changes.
- :cpp:func:`ana_cmpr_enable` starts the hardware after configuration is complete.
The rest of this document expands this flow into typical usage scenarios.
Lifecycle and Valid API States
------------------------------
The following diagram shows the analog comparator lifecycle and the states in which each API is intended to be used:
.. mermaid::
flowchart TD
NC([Not created]) -->|ana_cmpr_new_unit| INIT[Init / Disabled]
INIT -->|ana_cmpr_enable| EN[Enabled]
EN -->|ana_cmpr_disable| INIT
INIT -->|ana_cmpr_del_unit| NC
subgraph INIT_APIS [Init-state APIs]
SCAN[ana_cmpr_set_scan_config]
ADD[ana_cmpr_add_src_chan<br/>ana_cmpr_remove_src_chan]
CB[ana_cmpr_register_event_callbacks]
end
subgraph BOTH_APIS [APIs usable in both states]
IR[ana_cmpr_set_internal_reference]
DBC[ana_cmpr_set_debounce]
end
subgraph EN_APIS [Enabled-only API]
TRIG[ana_cmpr_trigger_scan]
end
INIT -. can call .-> SCAN
INIT -. can call .-> ADD
INIT -. can call .-> CB
INIT -. can call .-> IR
INIT -. can call .-> DBC
EN -. can call .-> IR
EN -. can call .-> DBC
EN -. can call .-> TRIG
classDef stateNeutral fill:#f8fafc,stroke:#64748b,stroke-width:1.5px,color:#111827;
classDef stateInit fill:#eff6ff,stroke:#2563eb,stroke-width:1.5px,color:#111827;
classDef stateEnabled fill:#fdf2f8,stroke:#db2777,stroke-width:1.5px,color:#111827;
classDef initApi fill:#eff6ff,stroke:#60a5fa,color:#111827;
classDef bothApi fill:#ecfdf5,stroke:#10b981,color:#111827;
classDef enabledApi fill:#fdf2f8,stroke:#f472b6,color:#111827;
class NC stateNeutral;
class INIT stateInit;
class EN stateEnabled;
class SCAN,ADD,CB initApi;
class IR,DBC bothApi;
class TRIG enabledApi;
The main constraints to remember are:
- :cpp:func:`ana_cmpr_register_event_callbacks`, :cpp:func:`ana_cmpr_add_src_chan`, :cpp:func:`ana_cmpr_remove_src_chan`, and :cpp:func:`ana_cmpr_set_scan_config` are intended to be used while the comparator is still in init state.
- :cpp:func:`ana_cmpr_trigger_scan` only works after :cpp:func:`ana_cmpr_enable`.
- :cpp:func:`ana_cmpr_del_unit` requires the comparator to be disabled first.
Scenario 1: Detect When a Signal Crosses a Fixed Threshold
==========================================================
Use this setup when you have one analog input and you only care whether it is above or below a threshold.
Typical examples:
- Detect whether a sensor output rises above a limit
- Convert a sine wave into a digital waveform around a fixed threshold
- Trigger software when a signal changes polarity around mid-supply
The easiest way to build this is to compare the source signal against the internal reference.
Example: Internal Reference with Callback
-----------------------------------------
.. code:: c
static bool IRAM_ATTR example_ana_cmpr_on_cross_callback(ana_cmpr_handle_t cmpr,
const ana_cmpr_cross_event_data_t *edata,
void *user_ctx)
{
if (edata->cross_type == ANA_CMPR_CROSS_POS) {
gpio_set_level(EXAMPLE_MONITOR_GPIO_NUM, 1);
} else if (edata->cross_type == ANA_CMPR_CROSS_NEG) {
gpio_set_level(EXAMPLE_MONITOR_GPIO_NUM, 0);
}
return false;
}
ana_cmpr_handle_t cmpr = NULL;
ana_cmpr_config_t config = {
.unit = 0, // Use a valid unit index for the selected target
.clk_src = ANA_CMPR_CLK_SRC_DEFAULT, // Start with default unless you need specific timing
.ref_src = ANA_CMPR_REF_SRC_INTERNAL, // Compare source against internal threshold
.cross_type = ANA_CMPR_CROSS_ANY, // Report both positive and negative crossings
.src_chan0_gpio = 0, // Replace with the source channel GPIO connected on your board
};
ESP_ERROR_CHECK(ana_cmpr_new_unit(&config, &cmpr));
ana_cmpr_internal_ref_config_t ref_cfg = {
.ref_volt = ANA_CMPR_REF_VOLT_50_PCT_VDD,
};
ESP_ERROR_CHECK(ana_cmpr_set_internal_reference(cmpr, &ref_cfg));
ana_cmpr_debounce_config_t dbc_cfg = {
.wait_us = 10,
};
ESP_ERROR_CHECK(ana_cmpr_set_debounce(cmpr, &dbc_cfg));
ana_cmpr_event_callbacks_t cbs = {
.on_cross = example_ana_cmpr_on_cross_callback,
};
ESP_ERROR_CHECK(ana_cmpr_register_event_callbacks(cmpr, &cbs, NULL));
ESP_ERROR_CHECK(ana_cmpr_enable(cmpr));
How to Read the Configuration
-----------------------------
The :cpp:type:`ana_cmpr_config_t` structure describes the basic shape of the comparator instance:
- :cpp:member:`ana_cmpr_config_t::unit` selects which comparator unit to use.
- :cpp:member:`ana_cmpr_config_t::clk_src` selects the comparator source clock. For most applications, :cpp:enumerator:`ANA_CMPR_CLK_SRC_DEFAULT` is the right starting point.
- :cpp:member:`ana_cmpr_config_t::ref_src` selects whether the reference comes from the internal threshold generator or an external analog input.
- :cpp:member:`ana_cmpr_config_t::cross_type` selects which crossings should trigger the interrupt callback: positive, negative, or both.
- :cpp:member:`ana_cmpr_config_t::src_chan0_gpio` selects the source GPIO on targets whose analog comparator pads are configurable.
- :cpp:member:`ana_cmpr_config_t::resample_limit` sets the unit-wide number of consecutive consistent samples required before a channel state update is accepted.
When using the internal reference, :cpp:func:`ana_cmpr_set_internal_reference` sets the threshold with :cpp:member:`ana_cmpr_internal_ref_config_t::ref_volt`. The available values are fixed percentages of VDD. For example, :cpp:enumerator:`ANA_CMPR_REF_VOLT_50_PCT_VDD` means the comparator reports whether the source signal is above or below half of the supply voltage.
Debounce and Signal Stability
-----------------------------
Real analog signals are often noisy near the threshold. Without debounce, a signal that slowly moves across the threshold or jitters around it may trigger several crossing interrupts in a short time.
Use :cpp:func:`ana_cmpr_set_debounce` to suppress these duplicate crossings:
- :cpp:member:`ana_cmpr_debounce_config_t::wait_us` is the time window during which new crossing interrupts are temporarily ignored after one crossing has already been reported.
As a rule of thumb:
- Increase ``wait_us`` if your signal is noisy and you see too many interrupts.
- Decrease ``wait_us`` if your signal changes quickly and you start missing real crossings.
Callback Behavior
-----------------
Register callbacks with :cpp:func:`ana_cmpr_register_event_callbacks` before enabling the unit.
The comparator currently provides one callback group member:
- :cpp:member:`ana_cmpr_event_callbacks_t::on_cross`
The callback receives :cpp:type:`ana_cmpr_cross_event_data_t`, which tells you:
- :cpp:member:`ana_cmpr_cross_event_data_t::cross_type` - whether the source crossed upward or downward
- :cpp:member:`ana_cmpr_cross_event_data_t::src_chan_id` - which source channel triggered the event on targets that support multiple source channels
On targets that do not support independent positive and negative interrupt reporting, :cpp:member:`ana_cmpr_cross_event_data_t::cross_type` can always be reported as :cpp:enumerator:`ANA_CMPR_CROSS_ANY`.
The callback runs in interrupt context, so keep it short and non-blocking.
.. note::
If :ref:`CONFIG_ANA_CMPR_ISR_CACHE_SAFE` is enabled, place the callback and any accessed data in internal RAM.
Scenario 2: Compare a Signal Against an External Reference
==========================================================
Use this setup when the reference level is not a fixed percentage of VDD.
Typical examples:
- Compare a sensor signal against a voltage divider output
- Compare a waveform against another waveform
- Generate a digital result from a source/reference pair that both come from analog front-end circuitry
In this case, the comparator source is still your input signal, but the reference comes from another analog input.
Example: External Reference Input
---------------------------------
.. code:: c
ana_cmpr_handle_t cmpr = NULL;
ana_cmpr_config_t config = {
.unit = 0, // Use a valid unit index for the selected target
.clk_src = ANA_CMPR_CLK_SRC_DEFAULT, // Default clock is usually enough
.ref_src = ANA_CMPR_REF_SRC_EXTERNAL, // Select external reference mode
.cross_type = ANA_CMPR_CROSS_ANY, // Report both crossing directions
.src_chan0_gpio = 0, // Source signal GPIO from your board design
.ext_ref_gpio = 1, // Reference signal GPIO from your board design
};
ESP_ERROR_CHECK(ana_cmpr_new_unit(&config, &cmpr));
ana_cmpr_debounce_config_t dbc_cfg = {
.wait_us = 10,
};
ESP_ERROR_CHECK(ana_cmpr_set_debounce(cmpr, &dbc_cfg));
ana_cmpr_event_callbacks_t cbs = {
.on_cross = example_ana_cmpr_on_cross_callback,
};
ESP_ERROR_CHECK(ana_cmpr_register_event_callbacks(cmpr, &cbs, NULL));
ESP_ERROR_CHECK(ana_cmpr_enable(cmpr));
What Changes Compared with Scenario 1
-------------------------------------
The main difference is :cpp:member:`ana_cmpr_config_t::ref_src`:
- :cpp:enumerator:`ANA_CMPR_REF_SRC_INTERNAL` means the reference is generated inside the comparator block.
- :cpp:enumerator:`ANA_CMPR_REF_SRC_EXTERNAL` means the reference comes from a dedicated analog input.
When using an external reference:
- :cpp:member:`ana_cmpr_config_t::ext_ref_gpio` becomes meaningful on targets with configurable analog comparator pads.
- :cpp:func:`ana_cmpr_set_internal_reference` is not used.
You can query the actual GPIOs in use with :cpp:func:`ana_cmpr_get_channel_gpio`:
.. code:: c
gpio_num_t src_gpio = -1;
gpio_num_t ext_ref_gpio = -1;
// Read back actual channel mapping after initialization.
ESP_ERROR_CHECK(ana_cmpr_get_channel_gpio(cmpr, ANA_CMPR_SOURCE_CHAN, 0, &src_gpio));
ESP_ERROR_CHECK(ana_cmpr_get_channel_gpio(cmpr, ANA_CMPR_EXT_REF_CHAN, 0, &ext_ref_gpio));
.. only:: SOC_ANA_CMPR_SUPPORT_ETM_SCAN
Scenario 3: Build a Low-CPU Hardware Signal Path with ETM
=========================================================
Use this setup when you want the hardware to react to crossings directly, without routing every event through the CPU.
Typical examples:
- Turn a sampled analog input into a GPIO waveform with very low CPU load
- Use a timer to schedule comparator scans periodically
- Connect comparator crossings to other peripherals through ETM
Example: GPTimer Periodic Scan with ETM
---------------------------------------
:example:`peripherals/analog_comparator/etm_periodic_scan` example builds the following hardware chain:
- GPTimer alarm event -> GPTimer enable-alarm task
- GPTimer alarm event -> Analog comparator start task
- Analog comparator positive cross event -> GPIO set task
- Analog comparator negative cross event -> GPIO clear task
In other words, the timer schedules scans, the comparator samples the source signal, and the crossing result drives a monitor GPIO directly through ETM.
The key comparator-side configuration looks like this:
.. code:: c
ana_cmpr_handle_t cmpr = NULL;
ana_cmpr_config_t config = {
.unit = 0, // Use a valid unit index for your target
.clk_src = ANA_CMPR_CLK_SRC_DEFAULT, // Keep default unless ETM timing requires otherwise
.ref_src = ANA_CMPR_REF_SRC_INTERNAL, // Use internal reference for threshold
.cross_type = ANA_CMPR_CROSS_ANY, // Keep both edge directions for ETM output waveform
.src_chan0_gpio = 0, // Set according to your board's source signal wiring
.resample_limit = 3, // Improve noise immunity before reporting channel state
};
ESP_ERROR_CHECK(ana_cmpr_new_unit(&config, &cmpr));
ana_cmpr_internal_ref_config_t ref_cfg = {
.ref_volt = ANA_CMPR_REF_VOLT_50_PCT_VDD,
};
ESP_ERROR_CHECK(ana_cmpr_set_internal_reference(cmpr, &ref_cfg));
ana_cmpr_scan_config_t scan_cfg = {
.scan_mode = ANA_CMPR_SCAN_MODE_FULL, // Scan all configured channels each cycle
.poll_period_us = 2, // Tune this for update rate vs. timing margin
};
ESP_ERROR_CHECK(ana_cmpr_set_scan_config(cmpr, &scan_cfg));
ana_cmpr_etm_task_config_t cmpr_task_cfg = {
.task_type = ANA_CMPR_TASK_START,
};
ESP_ERROR_CHECK(ana_cmpr_new_etm_task(cmpr, &cmpr_task_cfg, &cmpr_start_task));
ana_cmpr_etm_event_config_t pos_evt_cfg = {
.event_type = ANA_CMPR_EVENT_POS_CROSS, // Rising crossing event
.src_chan_id = 0, // Monitor source channel 0 in this example
};
ESP_ERROR_CHECK(ana_cmpr_new_etm_event(cmpr, &pos_evt_cfg, &cmpr_pos_evt));
ana_cmpr_etm_event_config_t neg_evt_cfg = {
.event_type = ANA_CMPR_EVENT_NEG_CROSS, // Falling crossing event
.src_chan_id = 0, // Same source channel as positive crossing
};
ESP_ERROR_CHECK(ana_cmpr_new_etm_event(cmpr, &neg_evt_cfg, &cmpr_neg_evt));
Understanding Scan Configuration
--------------------------------
When the target supports source-channel scan, :cpp:func:`ana_cmpr_set_scan_config` controls how the comparator walks through source channels:
- :cpp:member:`ana_cmpr_scan_config_t::scan_mode` selects the scanning behavior.
- :cpp:member:`ana_cmpr_scan_config_t::poll_period_us` sets the wait time between channel comparisons.
These fields matter most when:
- you use multiple source channels
- the source signal is noisy
- you want periodic sampling instead of interrupting on every hardware edge
In practice:
- Shorter ``poll_period_us`` means faster scan response but tighter timing margins.
ETM Event and Task Handles
--------------------------
The analog comparator provides ETM handles through two APIs:
- :cpp:func:`ana_cmpr_new_etm_event` creates an event handle from a comparator crossing.
- :cpp:func:`ana_cmpr_new_etm_task` creates a task handle that can be triggered by another ETM event.
In the current analog comparator driver:
- ETM event type is selected by :cpp:member:`ana_cmpr_etm_event_config_t::event_type`
- The source channel is selected by :cpp:member:`ana_cmpr_etm_event_config_t::src_chan_id`
- Optional event delay is configured by :cpp:member:`ana_cmpr_etm_event_config_t::event_delay`
- Task type is selected by :cpp:member:`ana_cmpr_etm_task_config_t::task_type`
For how to connect the comparator events and tasks to ETM channels, refer to :doc:`ETM </api-reference/peripherals/etm>`.
Working with Multiple Source Channels
=====================================
Some targets support more than one source channel per comparator unit. If your application needs to sample several analog inputs against the same reference, you can configure extra source channels while the comparator is in init state.
The relevant APIs are:
- :cpp:func:`ana_cmpr_add_src_chan`
- :cpp:func:`ana_cmpr_remove_src_chan`
- :cpp:func:`ana_cmpr_set_src_chan_cross_type`
Example:
.. code:: c
ana_cmpr_src_chan_config_t src_cfg = {
.gpio_num = EXAMPLE_SRC_CHAN1_GPIO,
.cross_type = ANA_CMPR_CROSS_ANY,
};
ESP_ERROR_CHECK(ana_cmpr_add_src_chan(cmpr, 1, &src_cfg));
When using multiple source channels:
- Add or remove channels only before :cpp:func:`ana_cmpr_enable`.
- If you remove every configured source channel, :cpp:func:`ana_cmpr_enable` fails with ``ESP_ERR_INVALID_STATE``.
- :cpp:func:`ana_cmpr_set_src_chan_cross_type` is only runtime-configurable on ESP32-H2.
Runtime Observation and Timing Helpers
======================================
The following APIs are general runtime helpers and are not tied to multi-source configuration:
- :cpp:func:`ana_cmpr_trigger_scan` forces one software-triggered scan sequence (on targets that support software-triggered scan).
- :cpp:func:`ana_cmpr_get_output_level` reads the latest sampled comparator output of a source channel (on targets that support output-level reporting).
- :cpp:func:`ana_cmpr_get_clock_resolution_hz` returns PAD_COMP_CLK resolution in Hz.
- :cpp:func:`ana_cmpr_get_capture_timestamps` reads current/previous crossing timestamps (on capture-capable targets).
Use :cpp:func:`ana_cmpr_get_output_level` when you need current digital comparison state from software. Use :cpp:func:`ana_cmpr_get_clock_resolution_hz` when converting PAD_COMP_CLK ticks (for example, capture timestamps) to time units. Use :cpp:func:`ana_cmpr_trigger_scan` when you want deterministic software-controlled refresh timing; it requires :cpp:func:`ana_cmpr_enable` and returns ``ESP_ERR_INVALID_STATE`` in init state.
To get meaningful capture timestamps, enable the capture timer when creating the unit:
.. code:: c
ana_cmpr_config_t config = {
.unit = 0, // Use a valid unit index for your target
.clk_src = ANA_CMPR_CLK_SRC_DEFAULT, // PAD_COMP_CLK source
.ref_src = ANA_CMPR_REF_SRC_INTERNAL, // Reference mode used by your capture scenario
.cross_type = ANA_CMPR_CROSS_ANY, // Keep both crossing directions by default
.src_chan0_gpio = 0, // Must match your board's source signal wiring
.en_capture_timer = true, // Required for capture timestamp APIs
};
Then convert ticks to time with:
.. code:: c
uint32_t resolution_hz = 0;
uint32_t current_ticks = 0;
ESP_ERROR_CHECK(ana_cmpr_get_clock_resolution_hz(cmpr, &resolution_hz));
ESP_ERROR_CHECK(ana_cmpr_get_capture_timestamps(cmpr, 0, &current_ticks, NULL));
uint32_t time_us = (uint32_t)(((uint64_t)current_ticks * 1000000U) / resolution_hz);
Practical Configuration Notes
=============================
Choosing the Clock Source
-------------------------
The comparator clock affects sampling behavior. In most applications, use :cpp:enumerator:`ANA_CMPR_CLK_SRC_DEFAULT` unless you have a specific clocking requirement.
Note that the analog comparator clock source can be shared with other GPIO-extension style peripherals on some targets. If different peripherals request incompatible clock sources, initialization can fail.
Choosing the Internal Reference Level
-------------------------------------
The internal reference is defined as a percentage of VDD, not as an arbitrary voltage in volts.
This is convenient when:
- the input signal naturally swings with the supply range
- you want a simple mid-supply or supply-relative threshold
This is less suitable when:
- you need a precise threshold independent of VDD variation
- the threshold must change continuously instead of in coarse steps
Tuning Debounce
---------------
Debounce is one of the first parameters to tune on real hardware.
- Too little debounce: duplicate interrupts or unstable event behavior near the threshold
- Too much debounce: missed crossings on fast-changing signals
There is no single good value for every application. Start small and adjust based on observed signal behavior.
Tuning Scan Timing
------------------
On scan-capable targets, scan timing and resampling should be chosen together:
- Reduce ``poll_period_us`` when you need quicker updates
- Increase :cpp:member:`ana_cmpr_config_t::resample_limit` when you need better noise rejection
- Avoid choosing a scan rate that is much slower than the effective input dynamics you want to detect
Operational Notes
=================
Enable and Disable Lifecycle
----------------------------
The comparator follows a simple lifecycle:
1. Create and configure it
2. Enable it
3. Run your application
4. Disable it before deleting it
Use :cpp:func:`ana_cmpr_enable` and :cpp:func:`ana_cmpr_disable` as a pair.
While enabled, only a limited set of runtime control functions are intended to be adjusted. Configuration functions such as source channel management and scan configuration must be done before enabling the unit.
Power Management
----------------
When :ref:`CONFIG_PM_ENABLE` is enabled, sleep and clock changes can affect the comparator. The driver uses a power management lock internally when needed, so enabling the comparator can prevent light sleep while the unit is active.
If power consumption matters, structure your application so the comparator is only enabled when needed.
IRAM Safe
---------
If you need comparator interrupts to keep working while the cache is disabled, enable :ref:`CONFIG_ANA_CMPR_ISR_CACHE_SAFE`.
If you also need the runtime control functions to remain callable in that situation, enable :ref:`CONFIG_ANA_CMPR_CTRL_FUNC_IN_IRAM`.
The following control APIs can be placed in IRAM:
- :cpp:func:`ana_cmpr_set_internal_reference`
- :cpp:func:`ana_cmpr_set_debounce`
- :cpp:func:`ana_cmpr_set_src_chan_cross_type`
- :cpp:func:`ana_cmpr_trigger_scan`
Thread Safety
-------------
The driver is designed to be thread-safe for concurrent API calls on the same :cpp:type:`ana_cmpr_handle_t`, by combining:
- A per-handle FSM (INIT/ENABLE/WAIT) with atomic state transitions to serialize incompatible operations
- Short critical sections to keep shared software state and hardware registers updated atomically
- Slot lifecycle protection for create/delete windows
The main lifecycle contract still applies: call :cpp:func:`ana_cmpr_disable` before :cpp:func:`ana_cmpr_del_unit`, and make sure no active dependent objects (for example ETM handles) remain when deleting the unit.
Kconfig Options
---------------
The most relevant Kconfig options are:
- :ref:`CONFIG_ANA_CMPR_ISR_CACHE_SAFE`
Keeps the default ISR path available when cache is disabled. Enable this if comparator interrupts must work in cache-off windows.
- :ref:`CONFIG_ANA_CMPR_CTRL_FUNC_IN_IRAM`
Places ISR-callable runtime control APIs in IRAM, so they remain callable when cache is disabled.
- :ref:`CONFIG_ANA_CMPR_ENABLE_DEBUG_LOG`
Enables driver debug logs for troubleshooting and bring-up. This increases binary size and log verbosity.
Examples
========
.. list::
:SOC_ANA_CMPR_SUPPORT_AUTO_SCAN: - :example:`peripherals/analog_comparator/auto_scan` shows auto-scan based threshold detection. After enabling the comparator, hardware scans continuously and updates output in real time, while monitor GPIO is driven via interrupt or ETM depending on target capabilities.
:SOC_ANA_CMPR_SUPPORT_ETM_SCAN: - :example:`peripherals/analog_comparator/etm_periodic_scan` shows how to use GPTimer and ETM to trigger periodic comparator scans and drive a monitor GPIO from comparator crossing events.
API Reference
=============
Driver APIs
-----------
.. include-build-file:: inc/ana_cmpr.inc
Driver Types
------------
.. include-build-file:: inc/ana_cmpr_types.inc
@@ -0,0 +1,317 @@
=============================
Asynchronous Color Conversion
=============================
:link_to_translation:`zh_CN:[中文]`
This document introduces the Async Color Convert driver in ESP-IDF. The table of contents is as follows:
.. contents::
:local:
:depth: 2
Overview
========
{IDF_TARGET_NAME} provides a DMA2D engine that can offload 2D copy and color conversion work from the CPU.
This driver is useful when your application needs to:
- convert an image from one pixel format to another
- copy only a window of a larger image
- queue multiple conversions without doing the work on the CPU
- move between RGB and UYVY formats while selecting the RGB/YUV conversion standard
The Async Color Convert driver wraps DMA2D request preparation, queueing, and completion handling into a small API that supports both:
- asynchronous submission with an ISR callback
- a simpler blocking API built on top of the same request path
Quick Start
===========
If you are new to this driver, start with the simplest workflow:
1. Install the driver
2. Prepare one :cpp:type:`async_color_convert_request_t`
3. Submit the conversion through either the blocking or non-blocking API
4. Consume the converted output buffer after the conversion completes
5. Either submit another request or uninstall the driver when finished
The typical usage flow is:
.. mermaid::
flowchart TD
install["Install driver<br/>esp_async_color_convert_install_dma2d"] --> request["Prepare request<br/>async_color_convert_request_t"]
request --> blocking["Blocking path<br/>esp_color_convert_blocking"]
request --> nonBlocking["Non-blocking path<br/>esp_async_color_convert"]
nonBlocking --> callback["Wait for callback or task notification"]
blocking --> result["Use converted buffer"]
callback --> result
result --> request
result --> uninstall["Optional cleanup<br/>esp_async_color_convert_uninstall"]
Scenario 1: Start with One Blocking Conversion
==============================================
The easiest way to learn the API is to convert one image and wait until the conversion is complete.
The following flow mirrors the :example:`peripherals/dma/async_color_convert` example. It converts one embedded UYVY422 image into BGR24 and then lets the application consume the converted output:
.. code:: c
async_color_convert_handle_t conv_hdl = NULL; // Driver handle returned by the install API
async_color_convert_config_t config = {
.backlog = 1, // One in-flight request is enough for this simple blocking example
.dma_burst_size = 16, // Start with the default burst size used by the example
};
// Create one Async Color Convert driver instance backed by DMA2D.
ESP_ERROR_CHECK(esp_async_color_convert_install_dma2d(&config, &conv_hdl));
async_color_convert_request_t req = {
.src_buffer = sample_96x64_uyvy_yuv_start, // Source image can be in flash or RAM, as long as DMA can access it
.src_stride = 96, // Source image row stride, in pixels
.src_height = 64, // Source image height, in pixels
.src_x = 0, // Start from the left edge of the source image
.src_y = 0,
.dst_buffer = dst_bgr, // Destination buffer in DMA-capable RAM
.dst_stride = 96, // Destination image row stride, in pixels
.dst_height = 64, // Destination image height, in pixels
.dst_x = 0, // Write the converted output from the top-left corner
.dst_y = 0,
.copy_width = 96, // Convert the full image width, in pixels
.copy_height = 64, // Convert the full image height, in pixels
.src_color_format = ESP_COLOR_FOURCC_UYVY, // Source pixels are UYVY422
.dst_color_format = ESP_COLOR_FOURCC_BGR24, // Destination pixels are BGR24 (used as RGB888 in this driver)
.color_conv_std = COLOR_CONV_STD_RGB_YUV_BT601, // RGB/YUV standard used for this conversion pair
};
// Wait until DMA2D finishes the conversion. -1 means wait forever.
ESP_ERROR_CHECK(esp_color_convert_blocking(conv_hdl, &req, -1));
// Release the driver after all conversions are done.
ESP_ERROR_CHECK(esp_async_color_convert_uninstall(conv_hdl));
This flow introduces the most important ideas:
- :cpp:func:`esp_async_color_convert_install_dma2d` creates the driver instance
- :cpp:type:`async_color_convert_request_t` describes the source image, destination image, and conversion window
- :cpp:func:`esp_color_convert_blocking` waits until the hardware finishes the conversion
- :cpp:func:`esp_async_color_convert_uninstall` releases the driver resources
For the blocking API, ``timeout_ms = -1`` means wait forever. Other timeout values are currently unsupported and return ``ESP_ERR_INVALID_ARG``.
Understanding ``async_color_convert_request_t``
-----------------------------------------------
Most application issues come from building the request incorrectly, so it is worth understanding the structure carefully.
.. important::
In :cpp:type:`async_color_convert_request_t`, all geometry fields are measured in **pixels**, not bytes. This includes ``src_stride``, ``src_height``, ``src_x``, ``src_y``, ``dst_stride``, ``dst_height``, ``dst_x``, ``dst_y``, ``copy_width``, and ``copy_height``.
``src_stride`` and ``dst_stride`` are row strides, not conversion widths. They describe how many pixels each full image row spans in memory, so they can be larger than ``copy_width`` when converting a window inside a larger image.
The structure describes two things at the same time:
- the full source and destination images in memory
- the rectangular window that should be converted
The key fields are:
- :cpp:member:`async_color_convert_request_t::src_buffer`
Base address of the source image
- :cpp:member:`async_color_convert_request_t::src_stride`
Source image row stride in pixels
- :cpp:member:`async_color_convert_request_t::src_height`
Source image height in pixels
- :cpp:member:`async_color_convert_request_t::src_x` and :cpp:member:`async_color_convert_request_t::src_y`
Top-left corner of the source window
- :cpp:member:`async_color_convert_request_t::dst_buffer`
Base address of the destination image
- :cpp:member:`async_color_convert_request_t::dst_stride`
Destination image row stride in pixels
- :cpp:member:`async_color_convert_request_t::dst_height`
Destination image height in pixels
- :cpp:member:`async_color_convert_request_t::dst_x` and :cpp:member:`async_color_convert_request_t::dst_y`
Top-left corner of where the converted window should be written
- :cpp:member:`async_color_convert_request_t::copy_width` and :cpp:member:`async_color_convert_request_t::copy_height`
Size of the rectangle to convert
- :cpp:member:`async_color_convert_request_t::src_color_format` and :cpp:member:`async_color_convert_request_t::dst_color_format`
Source and destination pixel formats
- :cpp:member:`async_color_convert_request_t::color_conv_std`
RGB/YUV conversion standard, used for RGB <-> YUV conversions
Both the source window and destination window must stay within the bounds of their corresponding images.
Supported Conversions
---------------------
The following format pairs are currently supported by this driver:
.. list-table::
:header-rows: 1
* - Source format
- Destination format
- Conversion standard
* - ``ESP_COLOR_FOURCC_RGB16``
- ``ESP_COLOR_FOURCC_RGB16``
- N/A
* - ``ESP_COLOR_FOURCC_BGR24``
- ``ESP_COLOR_FOURCC_BGR24``
- N/A
* - ``ESP_COLOR_FOURCC_RGB24``
- ``ESP_COLOR_FOURCC_RGB24``
- N/A
* - ``ESP_COLOR_FOURCC_UYVY``
- ``ESP_COLOR_FOURCC_UYVY``
- N/A
* - ``ESP_COLOR_FOURCC_BGR24``
- ``ESP_COLOR_FOURCC_RGB24``
- N/A
* - ``ESP_COLOR_FOURCC_RGB24``
- ``ESP_COLOR_FOURCC_BGR24``
- N/A
* - ``ESP_COLOR_FOURCC_RGB16``
- ``ESP_COLOR_FOURCC_BGR24``
- N/A
* - ``ESP_COLOR_FOURCC_BGR24``
- ``ESP_COLOR_FOURCC_RGB16``
- N/A
* - ``ESP_COLOR_FOURCC_RGB24``
- ``ESP_COLOR_FOURCC_RGB16``
- N/A
* - ``ESP_COLOR_FOURCC_BGR24``
- ``ESP_COLOR_FOURCC_UYVY``
- BT.601
* - ``ESP_COLOR_FOURCC_BGR24``
- ``ESP_COLOR_FOURCC_UYVY``
- BT.709
* - ``ESP_COLOR_FOURCC_RGB24``
- ``ESP_COLOR_FOURCC_UYVY``
- BT.601
* - ``ESP_COLOR_FOURCC_RGB24``
- ``ESP_COLOR_FOURCC_UYVY``
- BT.709
* - ``ESP_COLOR_FOURCC_UYVY``
- ``ESP_COLOR_FOURCC_BGR24``
- BT.601
* - ``ESP_COLOR_FOURCC_UYVY``
- ``ESP_COLOR_FOURCC_BGR24``
- BT.709
.. note::
Always set :cpp:member:`async_color_convert_request_t::src_color_format` and
:cpp:member:`async_color_convert_request_t::dst_color_format`.
Set :cpp:member:`async_color_convert_request_t::color_conv_std` when converting between RGB and YUV.
Scenario 2: Use the Asynchronous API with a Callback
====================================================
Once the blocking flow is clear, the next step is to queue a request and let the driver notify you from interrupt context when it is finished.
.. code:: c
static bool color_conv_done_cb(async_color_convert_handle_t conv_hdl,
async_color_convert_event_data_t *edata,
void *cb_args)
{
BaseType_t high_task_wakeup = pdFALSE; // Required by FreeRTOS when an ISR wakes a task
SemaphoreHandle_t sem = (SemaphoreHandle_t)cb_args; // User context passed at submit time
// Notify a waiting task that the conversion has finished.
xSemaphoreGiveFromISR(sem, &high_task_wakeup);
// Return true when the unblocked task should run immediately after the ISR.
return high_task_wakeup == pdTRUE;
}
async_color_convert_request_t req = {
.src_buffer = src_buf, // Source image base address
.src_stride = src_width, // Source image row stride, in pixels
.src_height = src_height,
.src_x = 0,
.src_y = 0,
.dst_buffer = dst_buf, // Destination image base address
.dst_stride = dst_width, // Destination image row stride, in pixels
.dst_height = dst_height,
.dst_x = 0,
.dst_y = 0,
.copy_width = copy_width,
.copy_height = copy_height,
.src_color_format = ESP_COLOR_FOURCC_RGB16,
.dst_color_format = ESP_COLOR_FOURCC_BGR24,
};
// Queue one asynchronous request. The callback runs later in ISR context.
ESP_ERROR_CHECK(esp_async_color_convert(conv_hdl, &req, color_conv_done_cb, sem));
// Wait in task context until the callback gives the semaphore.
xSemaphoreTake(sem, portMAX_DELAY);
The callback runs in ISR context, so keep it short and only use ISR-safe APIs such as ``xSemaphoreGiveFromISR`` or ``xQueueSendFromISR``.
Operational Notes
=================
Driver Configuration
--------------------
The driver configuration fields are:
- :cpp:member:`async_color_convert_config_t::backlog`
Maximum number of in-flight or pending requests. ``0`` uses a driver default.
- :cpp:member:`async_color_convert_config_t::dma_burst_size`
DMA burst size in bytes. ``0`` uses a driver default.
- :cpp:member:`async_color_convert_config_t::intr_priority`
DMA2D interrupt priority. ``0`` uses the default low/medium priority.
DMA Burst Size
--------------
The ``dma_burst_size`` affects DMA transfer efficiency:
- Larger burst sizes may improve throughput
- Larger burst sizes can also increase bus occupancy, so they are not always best for every workload
- Common starting values are 16, 32, and 64 bytes
The best value depends on the chip's DMA controller capabilities and how much memory bandwidth is shared with other active components in the system.
Thread Safety and ISR Rules
---------------------------
- The driver is thread-safe. Requests from different tasks are serialized through the internal queue.
- :cpp:func:`esp_async_color_convert` can be called from tasks to enqueue requests.
- The callback type :cpp:type:`async_color_convert_isr_cb_t` runs in ISR context.
- Do not call blocking APIs from the callback.
- :cpp:func:`esp_color_convert_blocking` must not be called from ISR context.
Uninstalling the Driver
-----------------------
When the driver is no longer needed:
.. code:: c
// Uninstall only after all queued conversions have completed.
ESP_ERROR_CHECK(esp_async_color_convert_uninstall(conv_hdl));
If requests are still pending, :cpp:func:`esp_async_color_convert_uninstall` returns :c:macro:`ESP_ERR_INVALID_STATE`.
Application Example
===================
- :example:`peripherals/dma/async_color_convert` shows a beginner-friendly blocking conversion flow:
- an embedded ``.yuv`` image is read directly from mapped flash
- DMA2D converts the image from UYVY422 to BGR24
- the converted output is base64-encoded and printed to the console
- pytest reconstructs the image as a PNG artifact and compares it against a golden reference image
API Reference
=============
Async Color Convert Driver Functions
------------------------------------
.. include-build-file:: inc/esp_async_color_convert.inc
@@ -0,0 +1,344 @@
============================
Asynchronous CRC Calculation
============================
:link_to_translation:`zh_CN:[中文]`
This document introduces the features of the Asynchronous CRC (Async CRC) driver in ESP-IDF. The table of contents is as follows:
.. contents::
:local:
:depth: 2
Overview
========
The Async CRC driver provides hardware-accelerated CRC calculation using the General DMA peripheral. It supports both AHB-GDMA and AXI-GDMA backends, offering flexible CRC computation with configurable polynomial, initial value, bit reversal options, and final XOR processing.
Key capabilities include:
- Hardware-accelerated CRC calculation using General DMA
- Support for 8-bit, 16-bit, and 32-bit CRC algorithms
- Asynchronous API with callback notifications
- Blocking API with timeout support
- Request queuing with configurable backlog size
- Support for both AHB and AXI DMA backends
Application scenarios include:
- Data integrity verification for communication protocols
- File and firmware checksum calculation
- Network packet validation
- Storage data verification
Quick Start
===========
This section provides a concise overview of how to use the Async CRC driver. Through practical examples, it demonstrates how to initialize the driver, configure CRC parameters, and perform both asynchronous and blocking CRC calculations.
The typical usage flow for async CRC is as follows:
.. blockdiag::
:scale: 100%
:caption: Async CRC driver's general usage flow (click to enlarge)
:align: center
blockdiag {
default_fontsize = 14;
node_width = 250;
node_height = 80;
class emphasis [color = pink, style = dashed];
install [label="esp_async_crc_install_gdma_*"];
calc [label="esp_async_crc_calc"];
wait [label="Wait for callback"];
process [label="Process result"];
uninstall [label="esp_async_crc_uninstall"];
install -> calc -> wait -> process;
process -> calc [folded];
calc -> uninstall [folded];
}
Creating and Installing the Driver
----------------------------------
First, you need to install the Async CRC driver. The driver supports both AHB-GDMA and AXI-GDMA backends, depending on your chip's capabilities:
.. code:: c
async_crc_handle_t crc_hdl = NULL;
async_crc_config_t config = {
.backlog = 8, // Maximum pending requests in queue
.dma_burst_size = 16, // DMA burst size in bytes
};
// Install with AHB-GDMA backend (if available)
ESP_ERROR_CHECK(esp_async_crc_install_gdma_ahb(&config, &crc_hdl));
// Or install with AXI-GDMA backend (if available)
// ESP_ERROR_CHECK(esp_async_crc_install_gdma_axi(&config, &crc_hdl));
.. note::
**Choosing Between AHB-GDMA and AXI-GDMA Backends**
The backend choice depends on your chip's capabilities and performance requirements:
- **AHB-GDMA**: Available on most ESP chips. Connects to the AHB bus, suitable for general-purpose DMA operations. Best for:
- Standard performance requirements
- Compatibility across most ESP chip variants
- **AXI-GDMA**: Available on higher-end ESP chips with AXI bus support. Provides higher bandwidth and better performance for memory-intensive operations. Best for:
- High-throughput CRC calculations
- Processing large amounts of data
- Applications requiring maximum performance
- Accessing external memory (PSRAM) with better efficiency
When creating a driver instance, you need to configure:
- **backlog**: Maximum number of pending CRC requests that can be queued. Higher values use more memory but provide better throughput for bursty workloads.
- **intr_priority**: DMA interrupt priority. Set to ``0`` to use the default low/medium priority, or set a non-zero value to request a specific interrupt priority.
- **dma_burst_size**: DMA transfer burst size in bytes.
The driver handle ``crc_hdl`` is an opaque pointer that you use for all subsequent operations.
Performing Asynchronous CRC Calculation
---------------------------------------
The async API allows you to queue CRC calculations without blocking:
.. code:: c
static bool crc_complete_callback(async_crc_handle_t crc_hdl, async_crc_event_data_t *edata, void *cb_args)
{
uint32_t result = edata->crc_result;
// Further process the CRC result
// e.g., send to a task via queue, log it, etc.
return false;
}
// Configure CRC parameters for CRC-32
async_crc_params_t params = {
.width = 32,
.polynomial = 0x04C11DB7,
.init_value = 0xFFFFFFFF,
.final_xor_value = 0xFFFFFFFF,
.reverse_input = true,
.reverse_output = true,
};
// Start async CRC calculation
const char *data = "Hello, World!";
size_t data_len = strlen(data);
ESP_ERROR_CHECK(esp_async_crc_calc(crc_hdl, data, data_len, &params, crc_complete_callback, NULL));
The callback function is invoked in interrupt context when the CRC calculation completes. The callback receives:
- **crc_hdl**: The driver handle
- **edata**: Event data containing the CRC result
- **cb_args**: User-defined argument passed during ``esp_async_crc_calc``
Performing Blocking CRC Calculation
-----------------------------------
For simpler use cases or when async operations are not required, use the blocking API:
.. code:: c
uint32_t crc_result = 0;
async_crc_params_t params = {
.width = 32,
.polynomial = 0x04C11DB7,
.init_value = 0xFFFFFFFF,
.final_xor_value = 0xFFFFFFFF,
.reverse_input = true,
.reverse_output = true,
};
const char *data = "Hello, World!";
size_t data_len = strlen(data);
// Blocking CRC, wait indefinitely
ESP_ERROR_CHECK(esp_crc_calc_blocking(crc_hdl, data, data_len, &params, -1, &crc_result));
printf("CRC result: 0x%08X\n", crc_result);
The blocking API only supports ``timeout_ms = -1``, which means waiting indefinitely until the CRC calculation completes.
Uninstalling the Driver
------------------------
When the driver is no longer needed:
.. code:: c
ESP_ERROR_CHECK(esp_async_crc_uninstall(crc_hdl));
The uninstall function returns :c:macro:`ESP_ERR_INVALID_STATE` if there are pending operations or if the CRC engine is busy. Ensure all operations complete before uninstalling.
CRC Parameter Configuration
============================
The Async CRC driver supports flexible CRC algorithm configuration through the :cpp:type:`async_crc_params_t` structure.
CRC Width
---------
The :cpp:member:`async_crc_params_t::width` field specifies the CRC bit width:
- **8**: 8-bit CRC (e.g., CRC-8, CRC-8/MAXIM)
- **16**: 16-bit CRC (e.g., CRC-16/CCITT, CRC-16/IBM)
- **32**: 32-bit CRC (e.g., CRC-32, CRC-32/BZIP2)
Polynomial
----------
The :cpp:member:`async_crc_params_t::polynomial` field specifies the CRC polynomial in hexadecimal format. Common polynomial values include:
- CRC-32: ``0x04C11DB7``
- CRC-16/CCITT: ``0x1021``
- CRC-16/IBM: ``0x8005``
- CRC-8/MAXIM: ``0x31``
Initial Value
-------------
The :cpp:member:`async_crc_params_t::init_value` field sets the initial CRC value before processing. Common initial values:
- ``0xFFFFFFFF`` for CRC-32
- ``0x0000`` for many CRC-16 variants
- ``0x00`` for many CRC-8 variants
Final XOR Value
---------------
The :cpp:member:`async_crc_params_t::final_xor_value` field specifies a value to XOR with the final CRC result. This is commonly ``0xFFFFFFFF`` for CRC-32 but can be ``0x0000`` for some variants.
Bit Reversal Options
--------------------
- :cpp:member:`async_crc_params_t::reverse_input` If true, reverses the bit order of each input byte before processing
- :cpp:member:`async_crc_params_t::reverse_output` If true, reverses the bit order of the final CRC result before applying the final XOR
These options affect the reflection settings for different CRC algorithms.
Common CRC Configurations
-------------------------
The following table lists common CRC configurations:
+----------------+----------+---------------+---------------+------------------+---------------+---------------+
| CRC Algorithm | Width | Polynomial | Initial Value | Final XOR | Reverse Input | Reverse Output|
+================+==========+===============+===============+==================+===============+===============+
| CRC-32 | 32 | 0x04C11DB7 | 0xFFFFFFFF | 0xFFFFFFFF | true | true |
+----------------+----------+---------------+---------------+------------------+---------------+---------------+
| CRC-16/CCITT | 16 | 0x1021 | 0x0000 | 0x0000 | false | false |
+----------------+----------+---------------+---------------+------------------+---------------+---------------+
| CRC-16/IBM | 16 | 0x8005 | 0x0000 | 0x0000 | true | true |
+----------------+----------+---------------+---------------+------------------+---------------+---------------+
| CRC-8/MAXIM | 8 | 0x31 | 0x00 | 0x00 | true | true |
+----------------+----------+---------------+---------------+------------------+---------------+---------------+
Thread Safety
=============
The Async CRC driver is designed to be thread-safe and can be used from multiple tasks. The driver features a **race-free Finite State Machine (FSM)** architecture that ensures thread safety and proper handling of concurrent CRC requests.
Thread Safety Guarantees
------------------------
- All public APIs can be called from different tasks simultaneously
- The driver uses atomic operations and critical sections for internal state protection
- Request queuing ensures that concurrent calls are properly serialized
ISR Context Restrictions
------------------------
Neither the async API nor the blocking API can be called from interrupt context. Specifically:
- :cpp:func:`esp_async_crc_calc`: Involves memory allocation/free, DMA preparations, and logging functions that are not ISR-safe
- :cpp:func:`esp_crc_calc_blocking`: Uses synchronization primitives that may block
Callback Restrictions
---------------------
The callback function (:cpp:type:`async_crc_isr_cb_t`) is executed in interrupt context. Therefore:
- Do **not** perform blocking operations (e.g., ``vTaskDelay``, ``xQueueSend`` with timeout)
- Keep execution time minimal to avoid impacting system interrupt latency
- Do **not** allocate memory using ``malloc`` or similar functions
- Use only ISR-safe FreeRTOS APIs (e.g., ``xQueueSendFromISR``, ``xSemaphoreGiveFromISR``)
- Return ``true`` if the callback wakes a high-priority task
Example of callback using queue:
.. code:: c
static bool crc_callback(async_crc_handle_t crc_hdl, async_crc_event_data_t *edata, void *cb_args)
{
QueueHandle_t queue = (QueueHandle_t)cb_args;
BaseType_t high_task_awoken = pdFALSE;
// Send result to a task via ISR-safe queue
xQueueSendFromISR(queue, &edata->crc_result, &high_task_awoken);
return high_task_awoken == pdTRUE;
}
Buffer Requirements
===================
The Async CRC driver has specific requirements for data buffers.
Memory Type
-----------
Data buffers can be in internal memory (DRAM/IRAM) or external memory (PSRAM, Flash). The driver automatically handles both:
.. code:: c
// Internal RAM
static char internal_data[] = "Data in internal RAM";
esp_async_crc_calc(crc_hdl, internal_data, strlen(internal_data), &params, callback, NULL);
// External Flash
static const char *flash_data = "Data in external Flash";
esp_async_crc_calc(crc_hdl, flash_data, strlen(flash_data), &params, callback, NULL);
Performance Considerations
==========================
Backlog Configuration
---------------------
The ``backlog`` configuration affects performance:
- **Small backlog** (4-8): Lower memory usage, may cause backpressure under high load
- **Large backlog** (16+): Better throughput for bursty workloads, higher memory usage
Choose based on your application's memory constraints and workload pattern.
DMA Burst Size
--------------
The ``dma_burst_size`` affects DMA transfer efficiency:
- Larger burst sizes can improve throughput
- Typical values: 16, 32, 64 bytes
The optimal value depends on your chip's DMA controller capabilities.
Application Examples
====================
- :example:`peripherals/dma/async_crc` demonstrates how to use the Async CRC driver through an interactive console CLI.
API Reference
=============
Async CRC Driver Functions
--------------------------
.. include-build-file:: inc/esp_async_crc.inc
@@ -0,0 +1,96 @@
Asynchronous Memory Copy
========================
:link_to_translation:`zh_CN:[中文]`
Overview
--------
{IDF_TARGET_NAME} has a DMA engine which can help to offload internal memory copy operations from the CPU in an asynchronous way.
The async memcpy API wraps all DMA configurations and operations. The signature of :cpp:func:`esp_async_memcpy` is almost the same as the standard libc ``memcpy`` function.
The DMA allows multiple memory copy requests to be queued up before the first one is completed, which allows overlap of computation and memory copy. Moreover, it is still possible to know the exact time when a memory copy request is completed by registering an event callback.
Configure and Install Driver
----------------------------
There are several ways to install the async memcpy driver, depending on the underlying DMA engine:
.. list::
:SOC_CP_DMA_SUPPORTED: - :cpp:func:`esp_async_memcpy_install_cpdma` is used to install the async memcpy driver based on the CP DMA engine.
:SOC_AHB_GDMA_SUPPORTED: - :cpp:func:`esp_async_memcpy_install_gdma_ahb` is used to install the async memcpy driver based on the AHB GDMA engine.
:SOC_AXI_GDMA_SUPPORTED: - :cpp:func:`esp_async_memcpy_install_gdma_axi` is used to install the async memcpy driver based on the AXI GDMA engine.
- :cpp:func:`esp_async_memcpy_install` is a generic API to install the async memcpy driver with a default DMA engine. If the SoC has the CP DMA engine, the default DMA engine is CP DMA. Otherwise, the default DMA engine is AHB GDMA.
Driver configuration is described in :cpp:type:`async_memcpy_config_t`:
* :cpp:member:`backlog`: This is used to configure the maximum number of memory copy transactions that can be queued up before the first one is completed. If this field is set to zero, then the default value 4 will be applied.
* :cpp:member:`dma_burst_size`: Set the burst size in a DMA burst transfer.
* :cpp:member:`flags`: This is used to enable some special driver features.
.. code-block:: c
async_memcpy_config_t config = ASYNC_MEMCPY_DEFAULT_CONFIG();
// update the maximum data stream supported by underlying DMA engine
config.backlog = 8;
async_memcpy_handle_t driver = NULL;
ESP_ERROR_CHECK(esp_async_memcpy_install(&config, &driver)); // install driver with default DMA engine
Send Memory Copy Request
------------------------
:cpp:func:`esp_async_memcpy` is the API to send memory copy request to DMA engine. It must be called after driver is installed successfully. This API is thread safe, so it can be called from different tasks.
Different from the libc version of ``memcpy``, you can optionally pass a callback to :cpp:func:`esp_async_memcpy`, so that you can be notified when the memory copy is finished. Note that the callback is executed in the ISR context, please make sure you will not call any blocking functions in the callback.
The prototype of the callback function is :cpp:type:`async_memcpy_isr_cb_t`. The callback function should only return true if it wakes up a high priority task by RTOS APIs like :cpp:func:`xSemaphoreGiveFromISR`.
.. code-block:: c
// Callback implementation, running in ISR context
static bool my_async_memcpy_cb(async_memcpy_handle_t mcp_hdl, async_memcpy_event_t *event, void *cb_args)
{
SemaphoreHandle_t sem = (SemaphoreHandle_t)cb_args;
BaseType_t high_task_wakeup = pdFALSE;
xSemaphoreGiveFromISR(semphr, &high_task_wakeup); // high_task_wakeup set to pdTRUE if some high priority task unblocked
return high_task_wakeup == pdTRUE;
}
// Create a semaphore used to report the completion of async memcpy
SemaphoreHandle_t semphr = xSemaphoreCreateBinary();
// Called from user's context
ESP_ERROR_CHECK(esp_async_memcpy(driver_handle, to, from, copy_len, my_async_memcpy_cb, my_semaphore));
// Do something else here
xSemaphoreTake(my_semaphore, portMAX_DELAY); // Wait until the buffer copy is done
For simpler use cases where the task only needs to wait until one copy finishes, use :cpp:func:`esp_memcpy_blocking`. This API is built on top of the async request path and waits internally for the completion callback.
.. code-block:: c
ESP_ERROR_CHECK(esp_memcpy_blocking(driver_handle, to, from, copy_len, -1));
The blocking API must not be called from ISR context. Currently, it only supports ``timeout_ms = -1``, which means waiting indefinitely until the memory copy completes.
Uninstall Driver
----------------
:cpp:func:`esp_async_memcpy_uninstall` is used to uninstall asynchronous memcpy driver. It is not necessary to uninstall the driver after each memcpy operation. If you know your application will not use this driver anymore, then this API can recycle the memory and other hardware resources for you.
.. only:: SOC_ETM_SUPPORTED and SOC_GDMA_SUPPORT_ETM
ETM Event
---------
Async memory copy is able to generate an event when one async memcpy operation is done. This event can be used to interact with the :doc:`ETM </api-reference/peripherals/etm>` module. You can call :cpp:func:`esp_async_memcpy_new_etm_event` to get the ETM event handle.
For how to connect the event to an ETM channel, please refer to the :doc:`ETM </api-reference/peripherals/etm>` documentation.
API Reference
-------------
.. include-build-file:: inc/esp_async_memcpy.inc
@@ -0,0 +1,245 @@
BitScrambler Driver
========================
:link_to_translation:`zh_CN:[中文]`
Introduction
------------
The BitScrambler is a peripheral that applies various data transformation to a DMA stream using an user-supplied program. ESP-IDF provides an assembler, build system, and driver support for BitScrambler programs. The BitScrambler peripheral in the {IDF_TARGET_NAME} has independent TX and RX channels; both can be linked to the same or different peripherals.
Functional Overview
-------------------
.. list::
- :ref:`bitscrambler-assembly` covers how a BitScrambler assembly program is structured.
- :ref:`bitscrambler-build` covers how BitScrambler programs are integrated in the ESP-IDF build system.
- :ref:`bitscrambler-load` covers how to allocate BitScrambler instances and how to load a program into them.
- :ref:`bitscrambler-loopback` covers how to use the BitScrambler in loopback mode.
.. _bitscrambler-assembly:
BitScrambler Assembly
^^^^^^^^^^^^^^^^^^^^^
The operations a BitScrambler performs on the data in the DMA stream are defined in a BitScrambler program. As a BitScrambler program is a binary blob that is hard to write by hand, ESP-IDF includes an assembler. This assembler converts easier-to-write text files into BitScrambler binary programs.
BitScrambler assembly files consist of comments, labels, instruction bundles, meta-instructions. The assembler ignores comments; Labels define a location in the program; Instructions can, e.g., jump to the location indicated by a label. Instruction bundles are a collection of sub-instructions that get assembled into one 257-bit binary BitScrambler instruction. Meta-instructions define global BitScrambler configuration, like the amount of trailing bytes, the prefetch mode, or the contents of the LUT RAM.
BitScrambler assembly files are case-insensitive as well as not sensitive to indentation. This documentation uses upper and lower caps for readability, but the assembler itself doesn't care. Fields that can contain an integer normally are given in a base-10 number, but can also contain a hex value if prepended with ``0x`` or a binary value if prepended with ``0b``.
Comments
~~~~~~~~
A comment starts with a ``#`` and extends to the end of the line. It can appear anywhere a space is allowed, including within instruction bundles between sub-instructions.
Labels
~~~~~~
Any string of (non-whitespace) characters followed by a colon is a label. The label is a symbolic reference to the next instruction bundle in the assembly file. Note that a label cannot be part of an instruction bundle; it needs to be located before the start of the instruction bundle.
Example:
.. code:: asm
loop_back:
set 0..3 4..7,
set 4..7 0..3,
read 8,
write 8,
jmp loop_back
The ``jmp`` opcode in the instruction bundle will jump back to the start of itself, meaning that this instruction bundle will be executed over and over again in a tight loop.
Instruction Bundle
~~~~~~~~~~~~~~~~~~
An instruction bundle consists of sub-instructions separated by commas. The entire bundle is assembled into one 257-bit instruction that the BitScrambler will execute in a single clock cycle. This means all sub-instructions within the bundle run in parallel, regardless of their order in the assembly source. The bundle ends with the last sub-instruction that is not followed by a comma.
The specific details of the BitScrambler can be found in the *{IDF_TARGET_NAME} Technical Reference Manual* > *BitScrambler (BITSCRM)* [`PDF <{IDF_TARGET_TRM_EN_URL}#bitscrm>`__].
In summary, BitScrambler contains a 32-bit output register, where each bit can take the value from any bit in any of the input sources. The sources are:
- a 64-bit input register fed by the incoming DMA stream
- two 16-bit counters
- A 30 bits register that contains the output of various comparisons
- a fixed high and low bit
- the output of a look-up table (LUT) RAM
- the value of the output register in the previous cycle
Sub-instructions
""""""""""""""""
``set [output] [source_bits]``: Routes one or more source bits to output bits. Note that it's possible to route multiple bits using the ``..`` operator: for instance ``set 0..3 O4..O7`` will have the same effect as ``set 0 O4, set 1 O5, set 2 O6, set 3 O7``. The first argument is the output bit or output bit range; output bits are numbered from 0 to 31. The second argument is one or a range of `source bits`_. Note that any output bits that do not have a corresponding ``set`` sub-instruction in an instruction bundle will be set to a low logic level.
``write [n]``: After routing all output bits, take the least significant ``n`` output register bits and push them into the output DMA pipeline. ``n`` can be one of 0, 8, 16 or 32. If an instruction bundle does not have a ``write`` sub-instruction, it will be equivalent to a ``write 0``.
``read [n]``: After routing all output bits and writing to the output register, take ``n`` bits from the input DMA pipeline and push them into the 64-bit input register. ``n`` can be one of 0, 8, 16 or 32. These bits will be shifted into the input FIFO starting from the MSB. As an example, a ``read 16`` shifts bits 6316 of the input register down to bits 470 and the new 16 bits read from the input DMA pipeline will occupy bits 6348 in the input register. If an instruction bundle does not have a ``read`` sub-instruction, it will be equivalent to a ``read 0``.
opcode
""""""
.. only:: esp32p4
- ``LOOP(A|B) end_val ctr_add tgt``: If the selected counter (A or B) is smaller than end_val, add ``ctr_add`` to the selected counter (A or B) and jump to the label ``tgt``. If not, continue execution.
- ``ADD(A|B)[H|L] val``: Add ``val`` to the selected counter. If ``H`` or ``L`` is appended, only the high or low 8-bit, respectively, of the counter is written back.
- ``IF[N] source_bit tgt``: If the source bit `source_bit` is 1 (for IF) or zero (for IFN), jump to the label ``tgt``.
- ``LDCTD(A|B)[H|L] val``: Load ``val`` into the indicated counter. If H or L is appended, only the high or low 8-bit, respectively, will be updated.
- ``LDCTI(A|B)[H|L]``: Load the indicated counter (A or B) with bits 16-31 from the output register. If ``H`` or ``L`` is appended, only the high or low 8-bit, respectively, will be updated.
- ``JMP tgt``: Unconditional jump to label ``tgt``. This is equal to ``IF h tgt``.
- ``NOP``: No operation. This is equal to ``ADDA 0``.
.. only:: esp32c5
- ``LOOP(A|B) end_val ctr_add tgt``: If the selected counter (A or B) is smaller than end_val, add ``ctr_add`` to the selected counter (A or B) and jump to the label ``tgt``. If not, continue execution.
- ``ADD(A|B)[H|L] val``: Add ``val`` to the selected counter. If ``H`` or ``L`` is appended, only the high or low 8-bit, respectively, of the counter is written back.
- ``IF[N] source_bit tgt``: If the source bit `source_bit` is one (for IF) or zero (for IFN), jump to the label ``tgt``.
- ``LDCTD(A|B)[H|L] val``: Load ``val`` into the indicated counter. If H or L is appended, only the high or low 8-bit, respectively, will be updated.
- ``LDCTI(A|B)[H|L]``: Load the indicated counter (A or B) with bits 16-31 sent to the output register. If ``H`` or ``L`` is appended, only the high or low 8-bit, respectively, will be updated.
- ``ADDCTI(A|B)[H|L]``: Add bits 1631 sent to the output register to the indicated counter (A or B). If ``H`` or ``L`` is appended, only the high or low 8-bit, respectively, will be evaluated and updated.
- ``JMP tgt``: Unconditional jump to label ``tgt``. This is equal to ``IF h tgt``.
- ``NOP``: No operation. This is equal to ``ADDA 0``.
.. note::
Note that an instruction bundle can only contain one opcode, one ``read``, and one ``write``. It can contain multiple ``set`` instructions, although multiple ``set`` instruction cannot assign a value to the same output bits.
Source bits
"""""""""""
The ``set`` and ``if`` / ``ifn`` instructions have a ``source_bit`` field. The following values can be put there:
- ``0`` ``63``: The bit selected is sourced from the selected bit in the input register.
- ``O0`` ``O31``: The bit selected is sourced from the value the output register was assigned in the previous cycle.
- ``A0`` ``A15``: The bit selected is sourced from the selected bit in the A counter register.
- ``B0`` ``B15``: The bit selected is sourced from the selected bit in the B counter register.
- ``L0`` ``L31``: The bit selected is sourced from the output from the LUT RAM. As described in the Technical Reference Manual, the LUT RAM output is the LUT item at the position indicated by the most significant N bits of the bits routed to the output register in the previous cycle, with N being 9, 10 or 11 for a LUT width of 32, 16 or 8-bit respectively.
- A condition compares (a portion of) counter B with bits that were routed to the output register in the previous cycle. These conditions consist of three parts:
1. The first part specifies whether compare all bits of counter B or only the high or low 8 bits:
- ``B``: Compare the entire B register
- ``BH``: Compare the high 8 bits of the B register
- ``BL``: Compare the low 8 bits of the B register
2. The second part is the comparison operator, which supports ``<=``, ``>``, or ``=``.
3. The third part specifies the offset in the output register for comparison with the selected part of the B register:
- For 16-bit comparisons, the offset can be ``O0`` or ``O16``
- For 8-bit comparisons, the offset can be ``O0``, ``O8``, ``O16`` or ``O24``
- ``H`` or ``L``. These sources are fixed-high or fixed-low.
.. note::
Note that not all sources can be used together in the same instruction. For instance, it is not possible to use a bit from one of the two counters as well as a bit from the upper 32 bits of the input FIFO in the same instruction bundle. The assembler will generate an error if an instruction bundle tries to do this anyway.
Example
"""""""
An example BitScrambler program might look like this:
.. code:: asm
loop_back:
set 0..3 4..7,
set 4..7 0..3,
read 8,
write 8,
jmp loop_back
This program only has one instruction (as only the line with the ``jmp`` does not end in a comma). It takes the lower 4 bits of the data read from memory and sends it to the upper 4 bits of the first byte of the output register. It also takes the next 4 bits of the input register and sends it to the lower 4 bits of the output register. It then writes 8 bits (one byte) to the output, while reading 8 bits from the input. Finally, the program continues by jumping back to the start of the instruction. Note that this all is executed in one BitScrambler cycle, and as the sub-instructions all are part of the same instruction, they could be specified in any order within the instruction. The end result of this small BitScrambler program is that it takes in data, e.g., ``01 23 45 67`` and swaps the high and low nibble of every bytes, resulting in an output of ``10 32 54 76``.
Meta-instructions
~~~~~~~~~~~~~~~~~
Meta-instructions set global BitScrambler configuration. Meta-instructions are allowed anywhere in the assembly file (except within an instruction bundle) and may also affect the preceding assembly code due to their nature. Currently, two meta-instructions are defined: ``cfg`` sets a global BitScrambler setting, and ``lut`` defines lookup table RAM content.
Global configuration meta-instructions
""""""""""""""""""""""""""""""""""""""
- ``cfg prefetch true|false``: If prefetch is set to ``true``, the BitScrambler will read 64 bits from the input DMA stream into the input register at startup. If set to ``false``, the input register is initialized to zero. This setting defaults to ``true`` if not specified. Please note, if the prefetch is enabled while the input stream can't provide at least 64 bits of data, the BitScrambler will hang.
- ``cfg eof_on upstream|downstream``: After the input stream ends, the BitScrambler will still process a certain amount of 'trailing' dummy bytes so it can flush any data contained in its registers. This setting indicates from where the data will be counted: ``upstream`` makes the bitscrambler count the bytes being read, ``downstream`` makes it count the bytes being written. This defaults to ``upstream`` if not specified.
- ``cfg trailing_bytes N``: This indicates how many dummy bytes will be read or written (depending on the ``eof_on`` setting) before the BitScrambler indicates an end-of-stream on its output. This defaults to ``0`` if not specified.
- ``cfg lut_width_bits 8|16|32``: This selects the bus width of the LUT output RAM, in bits. The LUT can be 2048x8 bits, 1024x16 bits or 512x32 bits in size. This defaults to ``32`` if not specified.
LUT content meta-instructions
"""""""""""""""""""""""""""""
``lut`` instructions are used to specify the contents of the LUT RAM. This meta-instruction is followed by one or more numerical values, separated by spaces or commas. LUT RAM locations are defined in the order they're encountered in the assembly program; the first value is always stored at location 0, the second value encountered is always stored at location 1, etc. The amount of arguments to a LUT meta-instruction is arbitrary as LUT meta-instructions can always be broken up or merged. For instance, ``lut 1,2,3,4`` is the same as ``lut 1,2`` on one line and ``lut 3,4`` on the next line. Note that LUT values must be within range with respect to the value given to the ``cfg lut_width_bits`` configuration meta-statement.
.. _bitscrambler-build:
Build System Integration
^^^^^^^^^^^^^^^^^^^^^^^^
The BitScrambler has full ESP-IDF build system support. A component (including the main component) can have BitScrambler assembly source files in its source directories. These files generally have the suffix ``.bsasm``. To assemble and link such a file into the main application, the CMakeLists.txt file for the component can call ``target_bitscrambler_add_src("assembly_file.bsasm")``. For instance, for an assembly file called ``my_program.bsasm``, a CMakeLists.txt file may look like this:
.. code:: cmake
idf_component_register(SRCS "main.c" "some-file.c"
INCLUDE_DIRS "./include")
target_bitscrambler_add_src("my_program.bsasm")
To use the assembled BitScrambler program, you would refer to it as such:
.. code:: c
// Create a variable 'my_bitscrambler_program' that resolves to
// the binary bitscrambler program.
// 2nd arg is same as name of assembly file without ".bsasm"
BITSCRAMBLER_PROGRAM(my_bitscrambler_program, "my_program");
[...]
bitscrambler_handle_t bs;
[...create bitscrambler instance]
bitscrambler_enable(bs);
bitscrambler_load_program(bs, my_bitscrambler_program);
[...]
bitscrambler_disable(bs);
.. _bitscrambler-loopback:
Loopback Mode
^^^^^^^^^^^^^
The BitScrambler supports a loopback mode which is useful for data transformations that do not involve a peripheral. The loopback mode occupies both the TX and RX channels of the BitScrambler, although only the TX BitScrambler actually executes code. Note that even if loopback mode does not involve a peripheral, one still needs to be selected; the peripheral does not need to be initialized or used, but if it is, its DMA features will be unavailable.
.. _bitscrambler-load:
Resource Allocation and Program Loading
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
In loopback mode, a BitScrambler object is created using :cpp:func:`bitscrambler_loopback_create`. If there is a BitScrambler peripheral matching the requested characteristics, this function will return a handle to it. You can then use :cpp:func:`bitscrambler_load_program` to load a program into it, then call :cpp:func:`bitscrambler_loopback_run` to transform a memory buffer using the loaded program. You can call :cpp:func:`bitscrambler_loopback_run` any number of times; it's also permissible to use :cpp:func:`bitscrambler_load_program` to change programs between calls. Finally, to free the hardware resources and clean up memory, call :cpp:func:`bitscrambler_free`.
Integrating BitScrambler with Peripheral Drivers
------------------------------------------------
The BitScrambler can be used alongside other peripheral modules (with GDMA interface support) to perform data transformation and transmission tasks. Currently, the following peripheral modules are integrated with the BitScrambler:
.. list::
:SOC_PARLIO_SUPPORTED: - **Parlio TX Driver**: The BitScrambler acts as a transmission layer decorator function, which can be dynamically enabled during runtime. For details, refer to :ref:`parlio-tx-bitscrambler-decorator`.
:SOC_RMT_SUPPORT_DMA: - **RMT TX Driver**: The BitScrambler functions similarly to an RMT encoder. A dedicated encoder has been designed using the BitScrambler. For details, refer to :ref:`rmt-bitscrambler-encoder`.
Application Example
-------------------
* :example:`peripherals/bitscrambler` demonstrates how to use BitScrambler loopback mode to transform a buffer of data into a different format.
API Reference
-------------
.. include-build-file:: inc/bitscrambler.inc
.. include-build-file:: inc/bitscrambler_loopback.inc
.. include-build-file:: inc/bitscrambler_peri_select.inc
@@ -0,0 +1,366 @@
Camera Controller Driver
========================
:link_to_translation:`zh_CN:[中文]`
Introduction
------------
{IDF_TARGET_NAME} has the following hardware designed for communication with external camera sensors:
.. list::
:SOC_MIPI_CSI_SUPPORTED: - MIPI Camera Serial Interface (MIPI CSI)
:SOC_ISP_DVP_SUPPORTED: - Digital Video Port via ISP module (ISP DVP)
:SOC_LCDCAM_CAM_SUPPORTED: - Digital Video Port via LCD_CAM module (LCD_CAM DVP)
The camera controller driver provides an interface for these hardware peripherals.
Functional Overview
-------------------
.. list::
- :ref:`cam-resource-allocation` Describes how to allocate and configure camera controller instances, and how to release resources when no longer needed.
- :ref:`cam-enable-disable` Explains how to enable and disable a camera controller.
- :ref:`cam-start-stop` Details how to start and stop a camera controller.
- :ref:`cam-receive` Explains how to receive signals from a camera sensor.
- :ref:`cam-callback` Describes how to register user-defined event callback functions.
- :ref:`cam-thread-safety` Lists APIs that are thread-safe.
- :ref:`cam-kconfig-options` Lists supported Kconfig options that affect driver behavior.
- :ref:`cam-iram-safe` Provides guidance on making CSI interrupt and control functions work reliably when the cache is disabled.
.. _cam-resource-allocation:
Resource Allocation
^^^^^^^^^^^^^^^^^^^
Install the Camera Controller Driver
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can implement the camera controller driver using one of the following methods:
.. list::
:SOC_MIPI_CSI_SUPPORTED: - :cpp:func:`esp_cam_new_csi_ctlr`
:SOC_ISP_DVP_SUPPORTED: - :cpp:func:`esp_cam_new_isp_dvp_ctlr`
:SOC_LCDCAM_CAM_SUPPORTED: - :cpp:func:`esp_cam_new_lcd_cam_ctlr`
.. only:: SOC_MIPI_CSI_SUPPORTED
You can implement a camera controller driver using the CSI peripheral. This requires configuration via :cpp:type:`esp_cam_ctlr_csi_config_t`.
After specifying :cpp:type:`esp_cam_ctlr_csi_config_t`, call :cpp:func:`esp_cam_new_csi_ctlr` to allocate and initialize a CSI camera controller handle. If successful, this function returns a CSI camera controller handle. See the example below.
.. code:: c
#include "esp_cam_ctlr.h"
#include "esp_cam_ctlr_types.h"
#include "esp_cam_ctlr_csi.h"
void app_main(void)
{
esp_cam_ctlr_csi_config_t csi_config = {
.ctlr_id = 0,
.h_res = MIPI_CSI_DISP_HSIZE,
.v_res = MIPI_CSI_DISP_VSIZE_640P,
.lane_bit_rate_mbps = MIPI_CSI_LANE_BITRATE_MBPS,
.input_data_color_type = CAM_CTLR_COLOR_RAW8,
.output_data_color_type = CAM_CTLR_COLOR_RGB565,
.data_lane_num = 2,
.byte_swap_en = false,
.queue_items = 1,
};
esp_cam_ctlr_handle_t handle = NULL;
ESP_ERROR_CHECK(esp_cam_new_csi_ctlr(&csi_config, &handle));
}
The CSI peripheral in {IDF_TARGET_NAME} requires a stable 2.5 V power supply. Refer to the schematic diagram to ensure the power supply pins are connected to 2.5 V before using the MIPI CSI driver.
.. only:: SOC_GP_LDO_SUPPORTED
In {IDF_TARGET_NAME}, you can power the CSI using the internal adjustable LDO. Connect the LDO channel output pin to the CSI power supply pin. Before initializing the CSI driver, use the API in :doc:`/api-reference/peripherals/ldo_regulator` to configure the LDO to output 2.5 V.
.. only:: SOC_ISP_DVP_SUPPORTED
You can implement a camera controller driver using the ISP DVP peripheral. This requires configuration via :cpp:type:`esp_cam_ctlr_isp_dvp_cfg_t`.
After specifying :cpp:type:`esp_cam_ctlr_isp_dvp_cfg_t`, call :cpp:func:`esp_cam_new_isp_dvp_ctlr` to allocate and initialize an ISP DVP camera controller handle. If successful, this function returns an ISP DVP camera controller handle. See the example below.
Before calling :cpp:func:`esp_cam_new_isp_dvp_ctlr`, create an ISP handle using :cpp:func:`esp_isp_new_processor`.
.. code:: c
#include "esp_err.h"
#include "esp_cam_ctlr.h"
#include "esp_cam_ctlr_isp_dvp.h"
#include "driver/isp.h"
#define MIPI_CSI_DISP_HSIZE 800 // example value, replace with actual resolution
#define MIPI_CSI_DISP_VSIZE 600 // example value, replace with actual resolution
void app_main(void)
{
isp_proc_handle_t isp_proc = NULL;
esp_isp_processor_cfg_t isp_config = {
.clk_hz = 120 * 1000 * 1000,
.input_data_source = ISP_INPUT_DATA_SOURCE_DVP,
.input_data_color_type = ISP_COLOR_RAW8,
.output_data_color_type = ISP_COLOR_RGB565,
.has_line_start_packet = false,
.has_line_end_packet = false,
.h_res = MIPI_CSI_DISP_HSIZE,
.v_res = MIPI_CSI_DISP_VSIZE,
};
ESP_ERROR_CHECK(esp_isp_new_processor(&isp_config, &isp_proc));
esp_cam_ctlr_handle_t cam_handle = NULL;
esp_cam_ctlr_isp_dvp_cfg_t dvp_ctlr_config = {
.data_width = 8,
.data_io = {53, 54, 52, 0, 1, 45, 46, 47, -1, -1, -1, -1, -1, -1, -1, -1},
.pclk_io = 21,
.hsync_io = 5,
.vsync_io = 23,
.de_io = 22,
.io_flags.vsync_invert = 1,
.queue_items = 10,
};
ESP_ERROR_CHECK(esp_cam_new_isp_dvp_ctlr(isp_proc, &dvp_ctlr_config, &cam_handle));
}
.. only:: SOC_LCDCAM_CAM_SUPPORTED
You can implement a camera controller driver using the DVP port of LCD_CAM. This requires configuration via :cpp:type:`esp_cam_ctlr_dvp_config_t`.
:cpp:member:`esp_cam_ctlr_dvp_config_t::exexternal_xtal`: Set this to use an externally generated xclk. Otherwise, the camera driver generates it internally.
After specifying :cpp:type:`esp_cam_ctlr_lcd_cam_cfg_t`, call :cpp:func:`esp_cam_new_lcd_cam_ctlr` to allocate and initialize a DVP camera controller handle. If successful, this function returns a DVP camera controller handle. See the example below.
After calling :cpp:func:`esp_cam_new_dvp_ctlr`, allocate a camera buffer that meets alignment constraints, or call :cpp:func:`esp_cam_ctlr_alloc_buffer` to allocate automatically.
To configure format conversion, call :cpp:func:`esp_cam_ctlr_format_conversion`. The driver supports the following conversion types:
* YUV to RGB
* RGB to YUV
* YUV to YUV
Supported color ranges:
* Full range: 0-255 for both RGB and YUV
* Limited range: RGB 16-240, YUV Y:16-240, U-V:16-235
.. note::
- When :cpp:member:`esp_cam_ctlr_dvp_config_t::cam_data_width` is set to 8:
- The CAM_PCLK frequency is recommended to be less than 80 MHz.
- If YUV-RGB format conversion is also configured via :cpp:func:`esp_cam_ctlr_format_conversion`, the CAM_PCLK frequency is recommended to be less than 60 MHz.
- When :cpp:member:`esp_cam_ctlr_dvp_config_t::cam_data_width` is set to 16:
- The CAM_PCLK frequency is recommended to be less than 40 MHz.
- If YUV-RGB format conversion is also configured via :cpp:func:`esp_cam_ctlr_format_conversion`, the CAM_PCLK frequency is recommended to be less than 30 MHz.
.. code:: c
#include "esp_err.h"
#include "esp_cam_ctlr.h"
#include "esp_cam_ctlr_types.h"
#include "esp_cam_ctlr_isp_dvp.h"
void app_main(void)
{
esp_cam_ctlr_handle_t cam_handle = NULL;
esp_cam_ctlr_dvp_pin_config_t pin_cfg = {
.data_width = EXAMPLE_DVP_CAM_DATA_WIDTH,
.data_io = {
EXAMPLE_DVP_CAM_D0_IO,
EXAMPLE_DVP_CAM_D1_IO,
EXAMPLE_DVP_CAM_D2_IO,
EXAMPLE_DVP_CAM_D3_IO,
EXAMPLE_DVP_CAM_D4_IO,
EXAMPLE_DVP_CAM_D5_IO,
EXAMPLE_DVP_CAM_D6_IO,
EXAMPLE_DVP_CAM_D7_IO,
},
.vsync_io = EXAMPLE_DVP_CAM_VSYNC_IO,
.de_io = EXAMPLE_DVP_CAM_DE_IO,
.pclk_io = EXAMPLE_DVP_CAM_PCLK_IO,
.xclk_io = EXAMPLE_DVP_CAM_XCLK_IO, // Set XCLK pin to generate XCLK signal
};
esp_cam_ctlr_dvp_config_t dvp_config = {
.ctlr_id = 0,
.clk_src = CAM_CLK_SRC_DEFAULT,
.h_res = CONFIG_EXAMPLE_CAM_HRES,
.v_res = CONFIG_EXAMPLE_CAM_VRES,
.input_data_color_type = CAM_CTLR_COLOR_RGB565,
.output_data_color_type = CAM_CTLR_COLOR_RGB565,
.dma_burst_size = 128,
.pin = &pin_cfg,
.bk_buffer_dis = 1,
.xclk_freq = EXAMPLE_DVP_CAM_XCLK_FREQ_HZ,
};
ESP_ERROR_CHECK(esp_cam_new_dvp_ctlr(&dvp_config, &cam_handle));
}
Uninstall the Camera Controller Driver
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To release resources for a camera controller driver that is no longer needed, call :cpp:func:`esp_cam_ctlr_del`. This releases the underlying hardware.
.. _cam-enable-disable:
Enable and Disable the Camera Controller Driver
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Before operating the camera controller, enable the driver by calling :cpp:func:`esp_cam_ctlr_enable`. This switches the driver state from **init** to **enable**.
.. code:: c
#include "esp_cam_ctlr.h"
#include "esp_cam_ctlr_types.h"
#include "esp_err.h"
void app_main(void)
{
esp_cam_ctlr_handle_t handle;
ESP_ERROR_CHECK(esp_cam_ctlr_enable(handle));
}
To disable the driver and return to the **init** state, call :cpp:func:`esp_cam_ctlr_disable`.
.. code:: c
#include "esp_err.h"
#include "esp_cam_ctlr.h"
#include "esp_cam_ctlr_types.h"
void app_main(void)
{
esp_cam_ctlr_handle_t handle;
ESP_ERROR_CHECK(esp_cam_ctlr_disable(handle));
}
.. _cam-start-stop:
Start and Stop the Camera Controller Driver
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Before receiving signals from a camera sensor, start the driver by calling :cpp:func:`esp_cam_ctlr_start`. This switches the driver state from **enable** to **start**.
.. code:: c
#include "esp_err.h"
#include "esp_log.h"
#include "esp_cam_ctlr.h"
#include "esp_cam_ctlr_types.h"
void app_main(void)
{
esp_cam_ctlr_handle_t handle = NULL;
ESP_ERROR_CHECK(esp_cam_ctlr_start(handle));
ESP_LOGI("CAM", "Camera controller started successfully");
}
To stop the driver and return to the **enable** state, call :cpp:func:`esp_cam_ctlr_stop`.
.. code:: c
#include "esp_err.h"
#include "esp_cam_ctlr.h"
#include "esp_cam_ctlr_types.h"
void app_main(void)
{
esp_cam_ctlr_handle_t handle = NULL;
ESP_ERROR_CHECK(esp_cam_ctlr_stop(handle));
}
.. _cam-receive:
Receive Data from a Camera Sensor
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
To receive data from a camera sensor, call :cpp:func:`esp_cam_ctlr_receive`.
.. code:: c
#include "esp_err.h"
#include "esp_cam_ctlr.h"
#include "esp_cam_ctlr_types.h"
ESP_ERROR_CHECK(esp_cam_ctlr_receive(handle, &my_trans, ESP_CAM_CTLR_MAX_DELAY));
.. _cam-callback:
Register Event Callbacks
^^^^^^^^^^^^^^^^^^^^^^^^
After the camera controller driver starts receiving data, it can generate events dynamically. To execute user-defined functions when events occur, register your callback function using :cpp:func:`esp_cam_ctlr_register_event_callbacks`. Supported event callbacks are listed in :cpp:type:`esp_cam_ctlr_evt_cbs_t`:
- :cpp:member:`esp_cam_ctlr_evt_cbs_t::on_get_new_trans` Called after the driver finishes a transaction and attempts to get a new transaction descriptor. Also called in :cpp:func:`s_ctlr_csi_start`. If this callback does not provide a new transaction descriptor, the driver uses the internal backup buffer if the ``bk_buffer_dis`` flag is set.
- :cpp:member:`esp_cam_ctlr_evt_cbs_t::on_trans_finished` Called when the driver finishes a transaction. This function runs in the ISR context. Ensure that it does not block (for example, only use FreeRTOS APIs with the ``ISR`` suffix).
.. _cam-thread-safety:
Thread Safety
^^^^^^^^^^^^^
The following factory functions are thread-safe and can be called from different RTOS tasks without additional locking:
.. list::
:SOC_MIPI_CSI_SUPPORTED: - :cpp:func:`esp_cam_new_csi_ctlr`
:SOC_ISP_DVP_SUPPORTED: - :cpp:func:`esp_cam_new_isp_dvp_ctlr`
- :cpp:func:`esp_cam_ctlr_del`
.. _cam-kconfig-options:
Kconfig Options
^^^^^^^^^^^^^^^
The following Kconfig options affect interrupt handler behavior when the cache is disabled:
.. list::
:SOC_MIPI_CSI_SUPPORTED: - :ref:`CONFIG_CAM_CTLR_MIPI_CSI_ISR_CACHE_SAFE`, see :ref:`cam-thread-safety` for details.
:SOC_ISP_DVP_SUPPORTED: - :ref:`CONFIG_CAM_CTLR_ISP_DVP_ISR_CACHE_SAFE`, see :ref:`cam-thread-safety` for details.
.. _cam-iram-safe:
IRAM Safety
^^^^^^^^^^^^^^
By default, CSI interrupts are delayed when the cache is disabled during flash write or erase operations. They are handled after the cache is enabled again.
The following Kconfig options:
.. list::
:SOC_MIPI_CSI_SUPPORTED: - :ref:`CONFIG_CAM_CTLR_MIPI_CSI_ISR_CACHE_SAFE`
:SOC_ISP_DVP_SUPPORTED: - :ref:`CONFIG_CAM_CTLR_ISP_DVP_ISR_CACHE_SAFE`
- Enable the interrupt being serviced even when the cache is disabled.
- Place all functions used by the ISR into IRAM.
- Place driver object into DRAM (to avoid mapping to PSRAM).
This allows interrupts to run while the cache is disabled, but increases IRAM usage. Ensure that user callbacks and related code/data are IRAM-safe or DRAM-safe when the cache is disabled.
Application Examples
--------------------
* :example:`peripherals/camera/mipi_isp_dsi` Demonstrates how to use the ``esp_driver_cam`` component to capture signals from a MIPI CSI camera sensor via the ISP module and display them on an LCD screen via a DSI interface.
* :example:`peripherals/camera/dvp_isp_dsi` Demonstrates how to use the ``esp_driver_cam`` component to capture signals from a DVP camera sensor via the ISP module and display them on an LCD screen via a DSI interface.
* :example:`peripherals/camera/dvp_dsi` Demonstrates how to use the ``esp_driver_cam`` component to capture DVP camera sensor data and display it on a MIPI DSI LCD.
API Reference
-------------
.. include-build-file:: inc/esp_cam_ctlr.inc
.. include-build-file:: inc/esp_cam_ctlr_types.inc
.. include-build-file:: inc/esp_cam_ctlr_csi.inc
.. include-build-file:: inc/esp_cam_ctlr_isp_dvp.inc
@@ -0,0 +1,508 @@
Capacitive Touch Sensor
=========================
:link_to_translation:`zh_CN:[中文]`
{IDF_TARGET_TOUCH_SENSOR_VERSION:default="NOT_UPDATED", esp32="v1", esp32s2="v2", esp32s3="v2", esp32p4="v3", esp32h4="v3", esp32s31="v3"}
Introduction
---------------
A touch sensor system is built on a substrate which carries electrodes and relevant connections under a protective flat surface. When the surface is touched, the capacitance variation is used to evaluate if the touch was valid.
The sensing pads can be arranged in different combinations (e.g., matrix, slider), so that a larger area or more points can be detected. The touch pad sensing process is under the control of a hardware-implemented finite-state machine (FSM) which is initiated by software or a dedicated hardware timer.
For design, operation, and control registers of a touch sensor, see **{IDF_TARGET_NAME} Technical Reference Manual** > **On-Chip Sensors and Analog Signal Processing** [`PDF <{IDF_TARGET_TRM_EN_URL}#sensor>`__].
In-depth design details of touch sensors and firmware development guidelines for the {IDF_TARGET_NAME} are available in `Touch Sensor Application Note <https://github.com/espressif/esp-iot-solution/blob/release/v1.0/documents/touch_pad_solution/touch_sensor_design_en.md>`_.
Overview of Capacitive Touch Sensor Versions
-----------------------------------------------
+------------------+-----------+---------------------------------------------------------------------------------------+
| Hardware Version | Chip | Main Features |
+==================+===========+=======================================================================================+
| V1 | ESP32 | Version 1, the channel value decreases when it is touched |
+------------------+-----------+---------------------------------------------------------------------------------------+
| V2 | ESP32-S2 | Version 2, the channel value increases when it is touched |
| | | Supports hardware filter, benchmark, waterproof, proximity sensing and sleep wake-up |
| +-----------+---------------------------------------------------------------------------------------+
| | ESP32-S3 | Version 2, support proximity measurement done interrupt |
+------------------+-----------+---------------------------------------------------------------------------------------+
| V3 | ESP32-P4 | Version 3, support frequency hopping |
| | ESP32-H4 | |
| | ESP32-S31 | |
+------------------+-----------+---------------------------------------------------------------------------------------+
Measurement Principle
---------------------
The touch sensor will charge and discharge the touch channel by the internal current or voltage bias. Due to the internal capacitance and the stray capacitance in the circuit, the signals on the touch pins will present as a sawtooth wave. When the finger is approaching or touched on the touch pad, the capacitance of the touch channel increases, which leads to a slower charge and discharge, and a longer sawtooth period.
.. only:: esp32
The touch sensor charges and discharges the touch channel within a fixed time and counts the number of charge-discharge cycles, and the count result serves as the raw data. The duration of a single charge-discharge measurement can be specified by :cpp:member:`touch_sensor_sample_config_t::charge_times`, and the interval between two measurements can be specified by :cpp:member:`touch_sensor_config_t::meas_interval_us`.
.. figure:: ../../../_static/touch_pad-measurement-parameters.jpg
:align: center
:alt: Touch Pad - relationship between measurement parameters
:figclass: align-center
Touch Sensor Working Principle
.. only:: not esp32
The touch sensor counts the number of clock cycles spent for a fixed number of charge-discharge cycles, and the count result serves as the raw data. The number of charge-discharge cycles for a single measurement can be specified by :cpp:member:`touch_sensor_sample_config_t::charge_duration_ms`, and the interval between two measurements can be specified by :cpp:member:`touch_sensor_config_t::meas_interval_us`.
.. figure:: ../../../_static/touch_pad-measurement-parameters-version2.png
:align: center
:alt: Touch Pad - relationship between measurement parameters
:figclass: align-center
Touch Sensor Working Principle
Overview of Touch Sensor Channels
------------------------------------
.. include:: cap_touch_sens/{IDF_TARGET_PATH_NAME}.inc
:start-after: touch-chan-mapping
:end-before: ---
Terminology in the Driver
----------------------------
- **Touch Sensor Controller**: The controller of the touch sensor, responsible for configuring and managing the touch sensor.
- **Touch Sensor Channel**: A specific touch sensor sampling channel. A touch sensor module has multiple touch channels, which are usually connected to the touch pad for measuring the capacitance change. In the driver, sampling of **one** channel is called one ``measurement`` and the scanning of **all** registered channels is called one ``scan``.
.. only:: not esp32 and not esp32s2 and not esp32s3
- **Touch Sensor Sampling Configuration**: Touch sensor sampling configuration refers to all the hardware configurations that related to the sampling. It can determine how the touch channels sample by setting the number of charging times, charging frequency, measurement interval, etc. The {IDF_TARGET_NAME} supports multiple sets of sample configuration, which means it can support frequency hopping.
.. only:: esp32 or esp32s2 or esp32s3
- **Touch Sensor Sampling Configuration**: Touch sensor sampling configuration refers to all the hardware configurations that related to the sampling. It can determine how the touch channels sample by setting the number of charging times, charging frequency, measurement interval, etc. The {IDF_TARGET_NAME} only support one set of sample configuration, so it doesn't support frequency hopping.
File Structure
-----------------
.. figure:: ../../../_static/diagrams/cap_touch_sens/touch_file_structure.svg
:align: center
:alt: File Structure of Touch Sensor Driver
File Structure of Touch Sensor Driver
Finite-state Machine
---------------------
The following diagram shows the state machine of the touch sensor driver, which describes the driver state after calling a function, and the constraint of the state transition.
.. figure:: ../../../_static/diagrams/cap_touch_sens/touch_state_machine.svg
:align: center
:alt: Finite-state Machine of Touch Sensor Driver
Finite-state Machine of Touch Sensor Driver
The diagram above is the finite-state machine of the touch sensor driver, which describes how the state transferred by invoking different APIs. ``<other_configurations>`` in the diagram stands for the other optional configurations, like reconfigurations to the touch sensor controller or channels, callback registration, filter, and so on.
.. note::
:cpp:func:`touch_channel_read_data` can be called at any time after the channel is registered (i.e., since ``INIT`` state), but please take care of the validation of the data.
Functionality Introduction
------------------------------
Categorized by functionality, the APIs of Capacitive Touch Sensor mainly include:
.. list::
- :ref:`touch-ctrl`
- :ref:`touch-chan`
- :ref:`touch-filter`
- :ref:`touch-callback`
- :ref:`touch-enable`
- :ref:`touch-conti-scan`
- :ref:`touch-oneshot-scan`
- :ref:`touch-read`
:SOC_TOUCH_SUPPORT_BENCHMARK: - :ref:`touch-benchmark`
:SOC_TOUCH_SUPPORT_WATERPROOF: - :ref:`touch-waterproof`
:SOC_TOUCH_SUPPORT_PROX_SENSING: - :ref:`touch-prox-sensing`
:SOC_TOUCH_SUPPORT_SLEEP_WAKEUP: - :ref:`touch-sleep-wakeup`
:SOC_TOUCH_SUPPORT_DENOISE_CHAN: - :ref:`touch-denoise-chan`
.. _touch-ctrl:
Touch Sensor Controller Management
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Touch Sensor is controlled by controller handle :cpp:type:`touch_sensor_handle_t`, it can be initialized and allocated by :cpp:func:`touch_sensor_new_controller`.
.. code-block:: c
// Some target has multiple sets of sample configuration can be set, here take one for example
#define SAMPLE_NUM 1
touch_sensor_handle_t sens_handle = NULL;
// sample configuration
touch_sensor_sample_config_t sample_cfg[SAMPLE_NUM] = {
// Specify sample configuration or apply the default sample configuration via `TOUCH_SENSOR_Vn_DEFAULT_SAMPLE_CONFIG`
// ...
};
// Use the default touch controller configuration
touch_sensor_config_t touch_cfg = TOUCH_SENSOR_DEFAULT_BASIC_CONFIG(SAMPLE_NUM, sample_cfg);
// Allocate a new touch sensor controller handle
ESP_ERROR_CHECK(touch_sensor_new_controller(&touch_cfg, &sens_handle));
To delete the controller handle and free the software and hardware resources, please call :cpp:func:`touch_sensor_del_controller`. But note that you need to delete the other resources that based on the controller first, like the registered touch channels, otherwise it can't be deleted directly.
.. code-block:: c
ESP_ERROR_CHECK(touch_sensor_del_controller(sens_handle));
You can also update the configurations via :cpp:func:`touch_sensor_reconfig_controller` before the controller is enabled.
.. code-block:: c
touch_sensor_config_t touch_cfg = {
// New controller configurations
// ...
};
ESP_ERROR_CHECK(touch_sensor_reconfig_controller(sens_handle, &touch_cfg));
.. _touch-chan:
Touch Sensor Channel Management
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
There are multiple touch channels in the touch sensor module, the touch sensor channel is controlled by the channel handle :cpp:type:`touch_channel_handle_t`. It can be initialized and allocated by :cpp:func:`touch_sensor_new_channel`.
.. code-block:: c
// ...
touch_channel_config_t chan_cfg = {
// Touch channel configurations
// ...
};
touch_channel_handle_t chan_handle = NULL;
int chan_id = 0;
// Allocate a new touch sensor controller handle
ESP_ERROR_CHECK(touch_sensor_new_channel(sens_handle, chan_id, &chan_cfg, &chan_handle));
To delete the touch channel handle and free the software and hardware resources, please call :cpp:func:`touch_sensor_del_channel`.
.. code-block:: c
ESP_ERROR_CHECK(touch_sensor_del_channel(chan_handle));
You can also update the configurations via :cpp:func:`touch_sensor_reconfig_channel` before the controller is enabled.
.. code-block:: c
touch_channel_config_t chan_cfg = {
// New touch channel configurations
// ...
};
ESP_ERROR_CHECK(touch_sensor_reconfig_channel(chan_handle, &chan_cfg));
.. _touch-filter:
Filter Configuration
^^^^^^^^^^^^^^^^^^^^^^
The filter can help to increase the stability in different use cases. The filter can be registered by calling :cpp:func:`touch_sensor_config_filter` and specify the configurations :cpp:type:`touch_sensor_filter_config_t`. These configurations mainly determine how to filter and update the benchmark and read data. Please note that all touch channels will share this filter.
To deregister the filter, you can call :cpp:func:`touch_sensor_config_filter` again, and set the second parameter (i.e. :cpp:type:`touch_sensor_filter_config_t` pointer) to ``NULL``.
.. only:: esp32
The touch sensor version {IDF_TARGET_TOUCH_SENSOR_VERSION} does not natively support the hardware filter, but the driver can set up a periodically triggered software filter based on ``esp_timer``. The interval for the software filter can be specified by :cpp:member:`touch_sensor_filter_config_t::interval_ms`. Additionally, the :cpp:member:`touch_sensor_filter_config_t::data_filter_fn` supports to specify a custom filtering function. If there are no special filter requirements, this interface can be set to ``NULL`` to use the default filter in the driver.
.. only:: not esp32
The touch sensor version {IDF_TARGET_TOUCH_SENSOR_VERSION} supports the hardware filter. The filtering and updating strategy for the benchmark can be configured by :cpp:member:`touch_sensor_filter_config_t::benchmark`, while the filtering of read values can be configured by :cpp:member:`touch_sensor_filter_config_t::data`.
.. code-block:: c
// ...
touch_sensor_filter_config_t filter_config = {
// Filter configurations
// ...
};
// Register the filter
ESP_ERROR_CHECK(touch_sensor_config_filter(sens_handle, &filter_config));
// ...
// Deregister the filter
ESP_ERROR_CHECK(touch_sensor_config_filter(sens_handle, NULL));
.. _touch-callback:
Callback
^^^^^^^^^^^^^
Calling :cpp:func:`touch_sensor_register_callbacks` to register the touch sensor event callbacks. Once the touch sensor events (like ``on_active``, ``on_inactive``) trigger, the corresponding callbacks will be invoked, so that to deal with the event in the upper application.
For the general example, when the measured data of the current touch channel exceed the ``benchmark`` + ``active_threshold``, this channel is activated, and the driver will call ``on_active`` callback to inform the application layer. Similar, when the active channel measured a lower data than ``benchmark`` + ``active_threshold``, then this channel will be inactivated, and ``on_inactive`` will be called to inform this channel is released.
.. note::
To ensure the stability of the triggering and releasing, ``active_hysteresis`` and ``debounce_cnt`` can be configured to avoid the frequent triggering that caused by jitter and noise.
Please refer to :cpp:type:`touch_event_callbacks_t` for the details about the supported callbacks.
.. code-block:: c
touch_event_callbacks_t callbacks = {
.on_active = example_touch_on_active_cb,
// Other callbacks
// ...
};
// Register callbacks
ESP_ERROR_CHECK(touch_sensor_register_callbacks(sens_handle, &callbacks, NULL));
// To deregister callbacks, set the corresponding callback to NULL
callbacks.on_active = NULL;
// Other callbacks to deregister
// ...
ESP_ERROR_CHECK(touch_sensor_register_callbacks(sens_handle, &callbacks, NULL));
.. _touch-enable:
Enable and Disable
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
After finished the configuration of the touch controller and touch channels, :cpp:func:`touch_sensor_enable` can be called to enable the touch sensor controller. It will enter ``READY`` status and power on the registered channels, then you can start scanning and sampling the touch data. Note that you can only do scanning and reading operation once the controller is enabled. If you want to update the controller or channel configurations, you need to call :cpp:func:`touch_sensor_disable` first.
.. code-block:: c
// Enable touch sensor
ESP_ERROR_CHECK(touch_sensor_enable(sens_handle));
// ...
// Disable touch sensor
ESP_ERROR_CHECK(touch_sensor_disable(sens_handle));
.. _touch-conti-scan:
Continuous Scan
^^^^^^^^^^^^^^^^^^
With the touch controller enabled, :cpp:func:`touch_sensor_start_continuous_scanning` can be called to start the continuous scanning to all the registered touch channels. The read data of these touch channels will be updated automatically in each scan. Calling :cpp:func:`touch_sensor_stop_continuous_scanning` can stop the continuous scan.
.. code-block:: c
// Start continuous scan
ESP_ERROR_CHECK(touch_sensor_start_continuous_scanning(sens_handle));
// ...
// Stop continuous scan
ESP_ERROR_CHECK(touch_sensor_stop_continuous_scanning(sens_handle));
.. _touch-oneshot-scan:
Oneshot Scan
^^^^^^^^^^^^^^^
With the touch controller enabled, :cpp:func:`touch_sensor_trigger_oneshot_scanning` can be called to trigger an one-time scan to all the registered touch channels. Note that oneshot scan is a blocking function, it will keep blocking and only return when the scan is finished. Moreover, you can't trigger an oneshot scan after the continuous scan has started.
.. code-block:: c
// Trigger an oneshot scan with timeout 1000 ms
ESP_ERROR_CHECK(touch_sensor_trigger_oneshot_scanning(sens_handle, 1000));
.. _touch-read:
Read Measurement Data
^^^^^^^^^^^^^^^^^^^^^^^^
Call :cpp:func:`touch_channel_read_data` to read the data with different types. Like, benchmark, smooth data, etc. You can refer to :cpp:type:`touch_chan_data_type_t` for the supported data types.
.. only:: not esp32 and not esp32s2 and not esp32s3
The {IDF_TARGET_NAME} supports frequency hopping by configuring multiple set of sample configurations, :cpp:func:`touch_channel_read_data` can read out the data of each sample configuration in a single call, you can determine the sample configuration number by :cpp:member:`touch_sensor_config_t::sample_cfg_num`, and pass an array (which length is not smaller than the configuration number) to the third parameter ``*data``, so that all the measured data of this channel will be stored in the array.
.. code-block:: c
#define SAMPLE_NUM 1 // Take one sample configuration set for example
uint32_t smooth_data[SAMPLE_NUM] = {};
// Read the smooth data
ESP_ERROR_CHECK(touch_channel_read_data(chan_handle, TOUCH_CHAN_DATA_TYPE_SMOOTH, smooth_data));
.. only:: SOC_TOUCH_SUPPORT_BENCHMARK
.. _touch-benchmark:
Benchmark Configuration
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Normally, you don't have to set the benchmark manually, but you can force reset the benchmark to the current smooth value by calling :cpp:func:`touch_channel_config_benchmark` when necessary.
.. code-block:: c
touch_chan_benchmark_config_t benchmark_cfg = {
// Benchmark operations
// ...
};
ESP_ERROR_CHECK(touch_channel_config_benchmark(chan_handle, &benchmark_cfg));
.. only:: SOC_TOUCH_SUPPORT_WATERPROOF
.. _touch-waterproof:
Waterproof Configuration
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The {IDF_TARGET_NAME} supports waterproof. Waterproof can be registered by calling :cpp:func:`touch_sensor_config_waterproof` and specify the configurations :cpp:type:`touch_waterproof_config_t`. There are two parts of the waterproof function:
- Immersion (in-water) proof: :cpp:member:`touch_waterproof_config_t::guard_chan` can be specified for detecting immersion. It is usually designed as a ring on the PCB, which surrounds all the other touch pads. When this guard ring channel is triggered, that means the touch panel is immersed by water, all the touch channels will stop measuring to avoid falsely triggering.
- Moisture (water-drop) proof: :cpp:member:`touch_waterproof_config_t::shield_chan` can be specified for detecting moisture. It usually uses the grid layout on the PCB, which covers the whole touch panel. The shield channel will charge and discharge synchronously with the current touch channel, when there is a water droplet covers both shield channel and normal touch channel, :cpp:member:`touch_waterproof_config_t::shield_drv` can strengthen the electrical coupling caused by the water droplets, and then reconfigure the active threshold based on the disturbance to eliminate the influence that introduced by the water droplet.
To deregister the waterproof function, you can call :cpp:func:`touch_sensor_config_waterproof` again, and set the second parameter (i.e. :cpp:type:`touch_waterproof_config_t` pointer) to ``NULL``.
.. code-block:: c
touch_waterproof_config_t waterproof_cfg = {
// Waterproof configurations
// ...
};
// Register waterproof function
ESP_ERROR_CHECK(touch_sensor_config_waterproof(sens_handle, &waterproof_cfg));
// ...
// Deregister waterproof function
ESP_ERROR_CHECK(touch_sensor_config_waterproof(sens_handle, NULL));
.. only:: SOC_TOUCH_SUPPORT_PROX_SENSING
.. _touch-prox-sensing:
Proximity Sensing Configuration
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The {IDF_TARGET_NAME} supports proximity sensing. Proximity sensing can be registered by calling :cpp:func:`touch_sensor_config_proximity_sensing` and specify the configurations :cpp:type:`touch_proximity_config_t`.
.. only:: not esp32s2 and not esp32s3
Since the capacitance change caused by proximity sensing is far less than that caused by physical touch, large area of copper foil is often used on PCB to increase the sensing area. In addition, multiple rounds of scans are needed and the result of each scan will be accumulated in the driver to improve the measurement sensitivity. The scan times (rounds) can be determined by :cpp:member:`touch_proximity_config_t::scan_times` and the charging times of the proximity channel in one scan can be determined by :cpp:member:`touch_proximity_config_t::charge_times`. Generally, the larger the scan times and charging times is, the higher the sensitivity will be, however, the read data will be unstable if the sensitivity is too high. Proper parameters should be determined regarding the application.
.. only:: esp32s2 or esp32s3
Since the capacitance change caused by proximity sensing is far less than that caused by physical touch, large area of copper foil is often used on PCB to increase the sensing area. In addition, multiple rounds of scans are needed and the result of each scan will be accumulated in the driver to improve the measurement sensitivity. The scan times (rounds) can be determined by :cpp:member:`touch_proximity_config_t::scan_times`. Generally, the larger the scan times and charging times is, the higher the sensitivity will be, however, the read data will be unstable if the sensitivity is too high. Proper parameters should be determined regarding the application.
The accumulated proximity data can be read by :cpp:func:`touch_channel_read_data` with the data type :cpp:enumerator:`TOUCH_CHAN_DATA_TYPE_PROXIMITY`
To deregister the proximity sensing, you can call :cpp:func:`touch_sensor_config_proximity_sensing` again, and set the second parameter (i.e. :cpp:type:`touch_proximity_config_t` pointer) to ``NULL``.
.. code-block:: c
touch_proximity_config_t prox_cfg = {
// Proximity sensing configuration
// ...
};
// Register the proximity sensing
ESP_ERROR_CHECK(touch_sensor_config_proximity_sensing(sens_handle, &prox_cfg));
// ...
// Deregister the proximity sensing
ESP_ERROR_CHECK(touch_sensor_config_proximity_sensing(sens_handle, NULL));
.. only:: SOC_TOUCH_SUPPORT_SLEEP_WAKEUP
.. _touch-sleep-wakeup:
Sleep Wake-up Configuration
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The {IDF_TARGET_NAME} supports waking-up the chip from light sleep or deep sleep with the touch sensor as a wake-up source. The wake-up functionality can be registered by calling :cpp:func:`touch_sensor_config_sleep_wakeup` and specifying the configurations :cpp:type:`touch_sleep_config_t`.
After registering the touch sensor sleep wake-up, the chip will continue to sample the touch channels after sleep, which will increase the power consumption during the sleep. To reduce the sleep power consumption, you can reduce the number of charging and discharging times, increase the sampling interval, etc.
Moreover, please note that the operations like sampling, wake-up are all done by hardware when the main core is sleeping. Since this driver runs on the main core, it cannot provide functions such as reading or configuring during the sleep.
.. only:: SOC_RISCV_COPROC_SUPPORTED
If you want to read or configure the touch sensor during the sleep, you can turn to the driver ``components/ulp/ulp_riscv/ulp_core/include/ulp_riscv_touch_ulp_core.h`` which based on the :doc:`Ultra Low Power (ULP) Coprocessor <../system/ulp>`.
.. list::
- Light sleep wake-up: you need to set :cpp:member:`slp_wakeup_lvl` to :cpp:enumerator:`TOUCH_LIGHT_SLEEP_WAKEUP` to enable the light sleep wake-up by touch sensor. Note that any registered touch channel can wake-up the chip from light sleep.
:esp32: - Deep sleep wake-up: you need to set :cpp:member:`slp_wakeup_lvl` to :cpp:enumerator:`TOUCH_DEEP_SLEEP_WAKEUP` to enable the deep sleep wake-up by touch sensor. Note that in version {IDF_TARGET_TOUCH_SENSOR_VERSION}, enabling Deep-sleep wake-up will keep the RTC domain power on during the deep-sleep to maintain the operation of the touch sensor. At this time, any registered touch sensor channels can continue sampling and support waking up from Deep-sleep.
:not esp32: - Deep sleep wake-up: beside setting :cpp:member:`slp_wakeup_lvl` to :cpp:enumerator:`TOUCH_DEEP_SLEEP_WAKEUP`, you need to specify :cpp:member:`deep_slp_chan` additionally. In order to reduce the power consumption, only the specified channel can wake-up the chip from the deep sleep when RTC_PREI power domain off. And also, the driver supports to store another set of configurations for the deep sleep via :cpp:member:`deep_slp_sens_cfg`, this set of configurations only takes effect during the deep sleep, you can customize the configurations to save more power. The configurations will be reset to the previous set after waking-up from the deep sleep. Please be aware that, not only deep sleep wake-up, but also light sleep wake-up will be enabled when the :cpp:member:`slp_wakeup_lvl` is :cpp:enumerator:`TOUCH_DEEP_SLEEP_WAKEUP`.
.. only:: not esp32
You can decide whether allow to power down RTC_PERIPH domain during the Deep-sleep by :cpp:member:`touch_sleep_config_t::deep_slp_allow_pd`. If allowed, the RTC_PERIPH domain will be powered down after the chip enters Deep-sleep, and only the specified :cpp:member:`touch_sleep_config_t::deep_slp_chan` can wake-up the chip from Deep-sleep. If not allowed, all enabled touch channels can wake-up the chip from Deep-sleep.
To deregister the sleep wake-up function, you can call :cpp:func:`touch_sensor_config_sleep_wakeup` again, and set the second parameter (i.e. :cpp:type:`touch_sleep_config_t` pointer) to ``NULL``.
.. code-block:: c
touch_sleep_config_t light_slp_cfg = TOUCH_SENSOR_DEFAULT_LSLP_CONFIG();
// Register the light sleep wake-up
ESP_ERROR_CHECK(touch_sensor_config_sleep_wakeup(sens_handle, &light_slp_cfg));
// ...
// Deregister the light sleep wake-up
ESP_ERROR_CHECK(touch_sensor_config_sleep_wakeup(sens_handle, NULL));
// Default Deep-sleep wake-up configurations: RTC_PERIPH will keep power on during the Deep-sleep,
// All enabled touch channel can wake-up the chip from Deep-sleep
touch_sleep_config_t deep_slp_cfg = TOUCH_SENSOR_DEFAULT_DSLP_CONFIG();
// Default Deep-sleep wake-up power down configurations: RTC_PERIPH will be powered down during the Deep-sleep,
// only the specified sleep pad can wake-up the chip from Deep-sleep
// touch_sleep_config_t deep_slp_cfg = TOUCH_SENSOR_DEFAULT_DSLP_PD_CONFIG(sleep_channel, slp_chan_thresh1, ...);
// Register the deep sleep wake-up
ESP_ERROR_CHECK(touch_sensor_config_sleep_wakeup(sens_handle, &deep_slp_cfg));
.. only:: SOC_TOUCH_SUPPORT_DENOISE_CHAN
.. _touch-denoise-chan:
Denoise Channel Configuration
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The {IDF_TARGET_NAME} supports the internal background noise suppression by the denoise channel. Denoise channel can be registered by calling :cpp:func:`touch_sensor_config_denoise_channel` and specify the configurations :cpp:type:`touch_denoise_chan_config_t`.
Denoise channel is an internal channel that not fanned out. After the denoise channel is enabled, the sampled data of the other touch channels will minus the data of the denoise channel automatically, so the final measurement result of the touch channels will be attenuated compare to the original data.
Aside of the common touch channel configuration, the reference capacitance that attached to the denoise channel can be set by :cpp:member:`touch_denoise_chan_config_t::ref_cap`. And the noise suppression resolution can be set by :cpp:member:`touch_denoise_chan_config_t::resolution`. The higher the resolution, the greater and more accuracy the denoise channel sample data will be, and the better suppression effect it takes. But at the same time, the attenuation of other touch channel sampling values also increases.
For example, the denoise channel resolution is :cpp:enumerator:`touch_denoise_chan_resolution_t::TOUCH_DENOISE_CHAN_RESOLUTION_BIT8`, i.e., maximum sample data is ``255``. Assuming the actual sample data of a normal touch channel is ``10000``, and the denoise channel sample data is ``100``, then the final measurement result of the touch channel will be ``10000 - 100 = 9900``; If we increase the resolution to :cpp:enumerator:`touch_denoise_chan_resolution_t::TOUCH_DENOISE_CHAN_RESOLUTION_BIT12`, i.e., maximum sample data is ``4095``, the resolution is ``16`` times greater. So the denoise channel sample data will be about ``100 * 16 = 1600``, then the final measurement result of this touch channel will be ``10000 - 1600 = 8400.``
To deregister the denoise channel, you can call :cpp:func:`touch_sensor_config_denoise_channel` again, and set the second parameter (i.e. :cpp:type:`touch_denoise_chan_config_t` pointer) to ``NULL``.
.. code-block:: c
touch_denoise_chan_config_t denoise_cfg = {
// Denoise channel configurations
// ...
}
// Register the denoise channel
ESP_ERROR_CHECK(touch_sensor_config_denoise_channel(sens_handle, &denoise_cfg));
// ...
// Deregister the denoise channel
ESP_ERROR_CHECK(touch_sensor_config_denoise_channel(sens_handle, NULL));
Application Examples
------------------------
- :example:`peripherals/touch_sensor/touch_sens_basic` demonstrates how to register touch channels and read the data, including hardware requirements and project configuration instructions.
- :example:`peripherals/touch_sensor/touch_sens_sleep` demonstrates how to wake up the chip from the light or deep sleep by the touch sensor.
Application Notes
-----------------
Touch Sensor Power Consumption Issues
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Due to the capacitive charging and discharging operation in the touch sensor measurements, it is a relatively high power-consuming peripheral. In applications with high power consumption requirements, the following methods can be helpful to reduce the power consumption.
.. list::
- Reduce the number of touch channels: Multiple functions (such as single-press, double-press, long press, etc.) can be multiplexed on the same channel, thereby reducing the number of touch sensors.
- Increase the measurement interval: By increasing the measurement interval :cpp:member:`touch_sensor_config_t::meas_interval_us`, the measurement frequency will be reduced, thereby lowering down the power consumption.
:esp32: - Reduce single measurement duration: By decreasing the single measurement duration :cpp:member:`touch_sensor_sample_config_t::charge_duration_ms`, the number of charging and discharging cycles is reduced, resulting in lower power consumption.
:not esp32: - Reduce single measurement charging and discharging cycles: By lowering down the single measurement charging and discharging cycles :cpp:member:`touch_sensor_sample_config_t::charge_times`, power consumption will be decreased.
:esp32s2 or esp32s3: - Set the current bias type to self-bias: By configuring :cpp:member:`touch_sensor_sample_config_t::bias_type` to :cpp:enumerator:`touch_bias_type_t::TOUCH_BIAS_TYPE_SELF` to use self-bias, which is more power-saving but less stable.
:esp32 or esp32s2 or esp32s3: - Lower down the charging and discharging amplitudes: :cpp:member:`touch_sensor_sample_config_t::charge_volt_lim_l` and :cpp:member:`touch_sensor_sample_config_t::charge_volt_lim_h` can specify the lower voltage limit during the discharging and the upper voltage limit during the charging. Reducing both voltage limitations and the voltage error between them can also decrease the power consumption.
:esp32 or esp32s2 or esp32s3: - Reduce the current bias intensity: Lowering down :cpp:member:`touch_channel_config_t::charge_speed` (i.e., the current magnitude of the current bias) can help to reduce the power consumption.
:not esp32 and not esp32s2 and not esp32s3: - Lower down the LDO voltage biasing intensity: Decreasing :cpp:member:`touch_channel_config_t::bias_volt` can reduce the power consumption.
API Reference
-------------
.. include-build-file:: inc/touch_sens.inc
.. include-build-file:: inc/touch_sens_types.inc
.. include-build-file:: inc/touch_version_types.inc
@@ -0,0 +1,49 @@
.. This file gets included from other .rst files in this folder.
.. It contains target-specific snippets.
.. Comments and '---' lines act as delimiters.
..
.. This is necessary mainly because RST doesn't support substitutions
.. (defined in RST, not in Python) inside code blocks. If that is ever implemented,
.. These code blocks can be moved back to the main .rst files, with target-specific
.. file names being replaced by substitutions.
.. touch-chan-mapping
.. list-table::
:header-rows: 1
:widths: 20 20
* - Channel
- GPIO
* - CH0
- IO4
* - CH1
- IO0
* - CH2
- IO2
* - CH3
- IO15
* - CH4
- IO13
* - CH5
- IO12
* - CH6
- IO14
* - CH7
- IO27
* - CH8
- IO33
* - CH9
- IO32
---
@@ -0,0 +1,64 @@
.. This file gets included from other .rst files in this folder.
.. It contains target-specific snippets.
.. Comments and '---' lines act as delimiters.
..
.. This is necessary mainly because RST doesn't support substitutions
.. (defined in RST, not in Python) inside code blocks. If that is ever implemented,
.. These code blocks can be moved back to the main .rst files, with target-specific
.. file names being replaced by substitutions.
.. touch-chan-mapping
.. list-table::
:header-rows: 1
:widths: 20 20
* - Channel
- GPIO
* - CH0
- IO0
* - CH1
- IO1
* - CH2
- IO2
* - CH3
- IO3
* - CH4
- IO29
* - CH5
- IO30
* - CH6
- IO31
* - CH7
- IO32
* - CH8
- IO33
* - CH9
- IO34
* - CH10
- IO35
* - CH11
- IO36
* - CH12
- IO37
* - CH13
- IO38
* - CH14
- IO39
---
@@ -0,0 +1,64 @@
.. This file gets included from other .rst files in this folder.
.. It contains target-specific snippets.
.. Comments and '---' lines act as delimiters.
..
.. This is necessary mainly because RST doesn't support substitutions
.. (defined in RST, not in Python) inside code blocks. If that is ever implemented,
.. These code blocks can be moved back to the main .rst files, with target-specific
.. file names being replaced by substitutions.
.. touch-chan-mapping
.. list-table::
:header-rows: 1
:widths: 20 20
* - Channel
- GPIO
* - CH1
- IO2
* - CH2
- IO3
* - CH3
- IO4
* - CH4
- IO5
* - CH5
- IO6
* - CH6
- IO7
* - CH7
- IO8
* - CH8
- IO9
* - CH9
- IO10
* - CH10
- IO11
* - CH11
- IO12
* - CH12
- IO13
* - CH13
- IO14
* - CH14
- IO15
* - CH15
- Internal
---
@@ -0,0 +1,64 @@
.. This file gets included from other .rst files in this folder.
.. It contains target-specific snippets.
.. Comments and '---' lines act as delimiters.
..
.. This is necessary mainly because RST doesn't support substitutions
.. (defined in RST, not in Python) inside code blocks. If that is ever implemented,
.. These code blocks can be moved back to the main .rst files, with target-specific
.. file names being replaced by substitutions.
.. touch-chan-mapping
.. list-table::
:header-rows: 1
:widths: 20 20
* - Channel
- GPIO
* - CH0
- Internal
* - CH1
- IO1
* - CH2
- IO2
* - CH3
- IO3
* - CH4
- IO4
* - CH5
- IO5
* - CH6
- IO6
* - CH7
- IO7
* - CH8
- IO8
* - CH9
- IO9
* - CH10
- IO10
* - CH11
- IO11
* - CH12
- IO12
* - CH13
- IO13
* - CH14
- IO14
---
@@ -0,0 +1,64 @@
.. This file gets included from other .rst files in this folder.
.. It contains target-specific snippets.
.. Comments and '---' lines act as delimiters.
..
.. This is necessary mainly because RST doesn't support substitutions
.. (defined in RST, not in Python) inside code blocks. If that is ever implemented,
.. These code blocks can be moved back to the main .rst files, with target-specific
.. file names being replaced by substitutions.
.. touch-chan-mapping
.. list-table::
:header-rows: 1
:widths: 20 20
* - Channel
- GPIO
* - CH0
- Internal
* - CH1
- IO1
* - CH2
- IO2
* - CH3
- IO3
* - CH4
- IO4
* - CH5
- IO5
* - CH6
- IO6
* - CH7
- IO7
* - CH8
- IO8
* - CH9
- IO9
* - CH10
- IO10
* - CH11
- IO11
* - CH12
- IO12
* - CH13
- IO13
* - CH14
- IO14
---
@@ -0,0 +1,61 @@
.. This file gets included from other .rst files in this folder.
.. It contains target-specific snippets.
.. Comments and '---' lines act as delimiters.
..
.. This is necessary mainly because RST doesn't support substitutions
.. (defined in RST, not in Python) inside code blocks. If that is ever implemented,
.. These code blocks can be moved back to the main .rst files, with target-specific
.. file names being replaced by substitutions.
.. touch-chan-mapping
.. list-table::
:header-rows: 1
:widths: 20 20
* - Channel
- GPIO
* - CH0
- IO6
* - CH1
- IO7
* - CH2
- IO8
* - CH3
- IO9
* - CH4
- IO10
* - CH5
- IO11
* - CH6
- IO12
* - CH7
- IO13
* - CH8
- IO14
* - CH9
- IO15
* - CH10
- IO16
* - CH11
- IO17
* - CH12
- IO18
* - CH13
- IO19
---
@@ -0,0 +1,98 @@
Clock Tree
==========
:link_to_translation:`zh_CN:[中文]`
{IDF_TARGET_RC_FAST_VAGUE_FREQ: default="17.5", esp32="8", esp32s2="8", esp32h2="8", esp32h21="20", esp32h4="20"}
{IDF_TARGET_RC_FAST_ADJUSTED_FREQ: default="17.5", esp32="8.5", esp32s2="8.5", esp32h2="8.5", esp32h21="20", esp32h4="20"}
{IDF_TARGET_XTAL_FREQ: default="40", esp32="2 ~ 40", esp32c2="40/26", esp32h2="32", esp32c5="48", esp32h21="32", esp32h4="32"}
{IDF_TARGET_RC_SLOW_VAGUE_FREQ: default="136", esp32="150", esp32s2="90", esp32h21="600", esp32h4="600"}
{IDF_TARGET_OSC_SLOW_PIN: default="GPIO0", esp32c2="pin0 (when its frequency is no more than 136 kHz)", "esp32c6="GPIO0", esp32h2="GPIO13", esp32h21="GPIO11", esp32h4="GPIO5"}
The clock subsystem of {IDF_TARGET_NAME} is used to source and distribute system/module clocks from a range of root clocks. The clock tree driver maintains the basic functionality of the system clock and the intricate relationship among module clocks.
This document starts with the introduction to root and module clocks. Then it covers the clock tree APIs that can be called to monitor the status of the module clocks at runtime.
Introduction
------------
This section lists definitions of {IDF_TARGET_NAME}'s supported root clocks and module clocks. These definitions are commonly used in the driver configuration, to help select a proper source clock for the peripheral.
Root Clocks
^^^^^^^^^^^
Root clocks generate reliable clock signals. These clock signals then pass through various gates, muxes, dividers, or multipliers to become the clock sources for every functional module: the CPU core(s), Wi-Fi, Bluetooth, the RTC, and the peripherals.
{IDF_TARGET_NAME}'s root clocks are listed in :cpp:type:`soc_root_clk_t`:
.. list::
- Internal {IDF_TARGET_RC_FAST_VAGUE_FREQ} MHz RC Oscillator (RC_FAST)
This RC oscillator generates a about {IDF_TARGET_RC_FAST_ADJUSTED_FREQ} MHz clock signal output as the ``RC_FAST_CLK``.
.. only:: SOC_CLK_RC_FAST_D256_SUPPORTED
The about {IDF_TARGET_RC_FAST_ADJUSTED_FREQ} MHz signal output is also passed into a configurable divider, which by default divides the input clock frequency by 256, to generate a ``RC_FAST_D256_CLK``.
The exact frequency of ``RC_FAST_CLK`` can be computed in runtime through calibration on the ``RC_FAST_D256_CLK``.
.. only:: not SOC_CLK_RC_FAST_D256_SUPPORTED and SOC_CLK_RC_FAST_SUPPORT_CALIBRATION
The exact frequency of ``RC_FAST_CLK`` can be computed in runtime through calibration.
.. only:: not SOC_CLK_RC_FAST_SUPPORT_CALIBRATION
The exact frequency of ``RC_FAST_CLK`` cannot be computed in runtime through calibration, but it is still possible to get its frequency through an oscilloscope or a logic analyzer by routing the clock signal to a GPIO pin.
- External {IDF_TARGET_XTAL_FREQ} MHz Crystal (XTAL)
- Internal {IDF_TARGET_RC_SLOW_VAGUE_FREQ} kHz RC Oscillator (RC_SLOW)
This RC oscillator generates a about {IDF_TARGET_RC_SLOW_VAGUE_FREQ}kHz clock signal output as the ``RC_SLOW_CLK``. The exact frequency of this clock can be computed in runtime through calibration.
.. only:: SOC_CLK_XTAL32K_SUPPORTED
- External 32 kHz Crystal - optional (XTAL32K)
.. only:: esp32
The clock source for this ``XTAL32K_CLK`` can be either a 32 kHz crystal connecting to the ``32K_XP`` and ``32K_XN`` pins or a 32 kHz clock signal generated by an external circuit. The external signal must be connected to the ``32K_XN`` pin. Additionally, a 1 nF capacitor must be placed between the ``32K_XP`` pin and ground. In this case, the ``32K_XP`` pin cannot be used as a GPIO pin.
.. only:: esp32p4 or esp32s31
The clock source for this ``XTAL32K_CLK`` is a 32 kHz crystal connecting to the ``XTAL_32K_P`` and ``XTAL_32K_N`` pins.
.. only:: not esp32 and not esp32p4 and not esp32s31
The clock source for this ``XTAL32K_CLK`` can be either a 32 kHz crystal connecting to the ``XTAL_32K_P`` and ``XTAL_32K_N`` pins or a 32 kHz clock signal generated by an external circuit. The external signal must be connected to the ``XTAL_32K_P`` pin.
``XTAL32K_CLK`` can also be calibrated to get its exact frequency.
.. only:: SOC_CLK_OSC_SLOW_SUPPORTED
- External Slow Clock - optional (OSC_SLOW)
A clock signal generated by an external circuit can be connected to {IDF_TARGET_OSC_SLOW_PIN} to be the clock source for the ``RTC_SLOW_CLK``. This clock can also be calibrated to get its exact frequency.
Typically, the frequency of the signal generated from an RC oscillator circuit is less accurate and more sensitive to the environment compared to the signal generated from a crystal. {IDF_TARGET_NAME} provides several clock source options for the ``RTC_SLOW_CLK``, and it is possible to make the choice based on the requirements for system time accuracy and power consumption. For more details, please refer to :ref:`rtc-clock-source-choice`.
Module Clocks
^^^^^^^^^^^^^
{IDF_TARGET_NAME}'s available module clocks are listed in :cpp:type:`soc_module_clk_t`. Each module clock has a unique ID. You can get more information on each clock by checking the documented enum value.
API Usage
---------
The clock tree driver provides an all-in-one API to get the frequency of the module clocks, :cpp:func:`esp_clk_tree_src_get_freq_hz`. This function allows you to obtain the clock frequency at any time by providing the clock name :cpp:enum:`soc_module_clk_t` and specifying the desired precision level for the returned frequency value :cpp:enum:`esp_clk_tree_src_freq_precision_t`.
API Reference
-------------
.. include-build-file:: inc/clk_tree_defs.inc
.. include-build-file:: inc/esp_clk_tree.inc
@@ -0,0 +1,320 @@
=============================================
Coordinate Rotation Digital Computer (CORDIC)
=============================================
:link_to_translation:`zh_CN:[中文]`
.. contents::
:local:
:depth: 2
Overview
========
CORDIC (Coordinate Rotation Digital Computer) is an iterative method for computing trigonometric, hyperbolic, and arctangent functions. The algorithm converts these operations into simple additions and shifts through iterative rotations and scaling, enabling efficient hardware-based computation.
CORDIC is primarily implemented in hardware with the following characteristics:
- High computation speed
- High precision
- Lower resource usage
The table below compares the performance of computing the cosine of a random value using a CORDIC driver and the software IQmath library, measured in CPU cycles.
+------------------+----------------+----------------+
| Function | IQmath | CORDIC |
+==================+================+================+
| cos | 29640 | 8080 |
+------------------+----------------+----------------+
Driver Introduction
===================
Before using the CORDIC driver, you should understand its calculation methods and parameter meanings.
Calculation Methods
-------------------
The CORDIC function is selected via :cpp:member:`cordic_calculate_config_t::function`. Supported functions are listed in the table below. Some functions take two arguments, others one; some produce two results, others one.
The input scaling factor can be set with :cpp:member:`cordic_calculate_config_t::scale_exp` (effective scaling is :math:`2^n`). In the table below, :math:`n` denotes this parameter.
:math:`arg_{1}` and :math:`arg_{2}` are the inputs, corresponding to :cpp:member:`cordic_input_buffer_desc_t::p_data_arg1` and :cpp:member:`cordic_input_buffer_desc_t::p_data_arg2`. :math:`res_{1}` and :math:`res_{2}` are the outputs, corresponding to :cpp:member:`cordic_output_buffer_desc_t::p_data_res1` and :cpp:member:`cordic_output_buffer_desc_t::p_data_res2`. The mapping is summarized in the following table.
.. list-table:: CORDIC Supported Functions and Formulas
:header-rows: 1
:widths: 18 22 32 11 24
:stub-columns: 0
* - Function
- Input
- Output
- Scale exponent :math:`n`
- Input/Output range (scale = 1)
* - :cpp:enumerator:`ESP_CORDIC_FUNC_COS` Cosine
- :math:`\frac{\theta}{\pi}, \quad \theta \text{ in radians}`
- :math:`res_1 = \cos(\theta)`, :math:`res_2 = \sin(\theta)`
- 0
- :math:`arg_1, res_1, res_2 \in \left[-1, 1\right)`
* - :cpp:enumerator:`ESP_CORDIC_FUNC_SIN` Sine
- :math:`\frac{\theta}{\pi}, \quad \theta \text{ in radians}`
- :math:`res_1 = \sin(\theta)`, :math:`res_2 = \cos(\theta)`
- 0
- :math:`arg_1, res_1, res_2 \in \left[-1, 1\right)`
* - :cpp:enumerator:`ESP_CORDIC_FUNC_PHASE` Phase
- :math:`arg_1 = x \cdot 2^{-n}`, :math:`arg_2 = y \cdot 2^{-n}`
- :math:`res_1 = \arctan(y/x) \cdot 2^{-n}/\pi`, :math:`res_2 = \sqrt{x^2+y^2} \cdot 2^{-n}`
- [0, 15]
- :math:`arg_{1,2} \in \left[-1, 1\right)`, :math:`res_1 \in \left[-1, 1\right)`, :math:`res_2 \in \left[0, 1\right)`
* - :cpp:enumerator:`ESP_CORDIC_FUNC_MODULUS` Modulus
- :math:`arg_1 = x \cdot 2^{-n}`, :math:`arg_2 = y \cdot 2^{-n}`
- :math:`res_1 = \sqrt{x^2+y^2} \cdot 2^{-n}`, :math:`res_2 = \arctan(y/x) \cdot 2^{-n}/\pi`
- [0, 15]
- :math:`arg_{1,2} \in \left[-1, 1\right)`, :math:`res_1 \in \left[0, 1\right)`, :math:`res_2 \in \left[-1, 1\right)`
* - :cpp:enumerator:`ESP_CORDIC_FUNC_ARCTAN` Arctangent
- :math:`arg_1 = x \cdot 2^{-n}`
- :math:`res_1 = \arctan(x) \cdot 2^{-n}/\pi`
- [0, 15]
- :math:`arg_1, res_1 \in \left[-1, 1\right)`
* - :cpp:enumerator:`ESP_CORDIC_FUNC_COSH` Hyperbolic cosine
- :math:`arg_1 = x \cdot 2^{-n}`
- :math:`res_1 = \cosh(x) \cdot 2^{-n}`, :math:`res_2 = \sinh(x) \cdot 2^{-n}`
- 1
- :math:`arg_1 \in \left[-0.559, 0.559\right]`, :math:`res_1 \in \left[0.5, 0.846\right]`, :math:`res_2 \in \left[-0.683, 0.683\right]`
* - :cpp:enumerator:`ESP_CORDIC_FUNC_SINH` Hyperbolic sine
- :math:`arg_1 = x \cdot 2^{-n}`
- :math:`res_1 = \sinh(x) \cdot 2^{-n}`, :math:`res_2 = \cosh(x) \cdot 2^{-n}`
- 1
- :math:`arg_1 \in \left[-0.559, 0.559\right]`, :math:`res_1 \in \left[-0.683, 0.683\right]`, :math:`res_2 \in \left[0.5, 0.846\right]`
* - :cpp:enumerator:`ESP_CORDIC_FUNC_ARCHTANH` Inverse hyperbolic tangent
- :math:`arg_1 = x \cdot 2^{-n}`
- :math:`res_1 = \mathrm{arctanh}(x) \cdot 2^{-n}`
- 1
- :math:`arg_1 \in \left[-0.403, 0.403\right]`, :math:`res_1 \in \left[-0.559, 0.559\right]`
* - :cpp:enumerator:`ESP_CORDIC_FUNC_LOGE` Natural logarithm
- :math:`arg_1 = (x-1)/(x+1) \cdot 2^{-1}`
- :math:`res_1 = \ln(x) \cdot 2^{-2}`
- 1
- :math:`arg_1 \in \left[-0.403, 0.403\right]`, :math:`res_1 \in \left[-0.559, 0.559\right]`
* - :cpp:enumerator:`ESP_CORDIC_FUNC_SQUARE_ROOT` Square root
- :math:`arg_1 = x \cdot 2^{-n}`
- :math:`res_1 = \sqrt{x} \cdot 2^{-n}`
- [0, 4]
- :math:`arg_1 \in \left[0.1069, 1\right)`, :math:`res_1 \in \left[0.177, 1\right)`
Data Format
-----------
Input and output data format is selected via :cpp:member:`cordic_calculate_config_t::iq_format`. Supported formats:
- :cpp:enumerator:`ESP_CORDIC_FORMAT_Q15` Q15 format
In Q15 format, data has one sign bit plus 15 fractional bits. Range: [-1 (0x8000), 1 - 2^-15 (0x7FFF)].
.. figure:: ../../../_static/diagrams/cordic/cordic_q15.svg
:align: center
:alt: Q15 format
Q15 format
.. math::
value = \sum_{i=0}^{14} \frac{1}{2^{(15-i)}} \times B_i
- :cpp:enumerator:`ESP_CORDIC_FORMAT_Q31` Q31 format
In Q31 format, data has one sign bit plus 31 fractional bits. Range: [-1 (0x80000000), 1 - 2^-31 (0x7FFFFFFF)].
.. figure:: ../../../_static/diagrams/cordic/cordic_q31.svg
:align: center
:alt: Q31 format
Q31 format
.. math::
value = \sum_{i=0}^{30} \frac{1}{2^{(31-i)}} \times B_i
Precision
---------
The number of iterations (precision) is set via :cpp:member:`cordic_calculate_config_t::iteration_count`. Supported values are given below.
**Phase, Modulus, Arctan functions**
+------------------+----------------+----------------+--------------------------+--------------------------+
| Function | Iterations | Cycles | Max residual (q1.31) | Max residual (q1.15) |
+==================+================+================+==========================+==========================+
| Phase, Modulus, | 4 | 1 | :math:`2^{-4}` | :math:`2^{-4}` |
| Arctan +----------------+----------------+--------------------------+--------------------------+
| | 8 | 2 | :math:`2^{-8}` | :math:`2^{-8}` |
| +----------------+----------------+--------------------------+--------------------------+
| | 12 | 3 | :math:`2^{-12}` | :math:`2^{-12}` |
| +----------------+----------------+--------------------------+--------------------------+
| | 16 | 4 | :math:`2^{-16}` | :math:`2^{-15}` |
| +----------------+----------------+--------------------------+--------------------------+
| | 20 | 5 | :math:`2^{-20}` | :math:`2^{-15}` |
| +----------------+----------------+--------------------------+--------------------------+
| | 24 | 6 | :math:`2^{-24}` | :math:`2^{-15}` |
+------------------+----------------+----------------+--------------------------+--------------------------+
**Sin, Cos, Sinh, Cosh, Arctanh, Ln functions**
+------------------+----------------+----------------+--------------------------+--------------------------+
| Function | Iterations | Cycles | Max residual (q1.31) | Max residual (q1.15) |
+==================+================+================+==========================+==========================+
| Sin, Cos, | 4 | 1 | :math:`2^{-3}` | :math:`2^{-3}` |
| Sinh, Cosh, +----------------+----------------+--------------------------+--------------------------+
| Arctanh, Ln | 8 | 2 | :math:`2^{-7}` | :math:`2^{-7}` |
| +----------------+----------------+--------------------------+--------------------------+
| | 12 | 3 | :math:`2^{-11}` | :math:`2^{-11}` |
| +----------------+----------------+--------------------------+--------------------------+
| | 16 | 4 | :math:`2^{-14}` | :math:`2^{-14}` |
| +----------------+----------------+--------------------------+--------------------------+
| | 20 | 5 | :math:`2^{-18}` | :math:`2^{-15}` |
| +----------------+----------------+--------------------------+--------------------------+
| | 24 | 6 | :math:`2^{-22}` | :math:`2^{-15}` |
+------------------+----------------+----------------+--------------------------+--------------------------+
**Sqrt function**
+------------------+----------------+----------------+--------------------------+--------------------------+
| Function | Iterations | Cycles | Max residual (q1.31) | Max residual (q1.15) |
+==================+================+================+==========================+==========================+
| Sqrt | 4 | 1 | :math:`2^{-7}` | :math:`2^{-7}` |
| +----------------+----------------+--------------------------+--------------------------+
| | 8 | 2 | :math:`2^{-14}` | :math:`2^{-14}` |
| +----------------+----------------+--------------------------+--------------------------+
| | 12 | 3 | :math:`2^{-22}` | :math:`2^{-15}` |
+------------------+----------------+----------------+--------------------------+--------------------------+
.. note::
More iterations give higher precision but longer computation time. Once precision reaches the limit of the data format, further increasing iterations does not improve results and only increases latency.
Quick Start
===========
This section shows how to create a CORDIC engine and run calculations. Typical usage flow:
Create CORDIC Engine
--------------------
Create a CORDIC engine as follows:
.. code:: c
cordic_engine_handle_t engine = NULL;
cordic_engine_config_t config = {
.clock_source = CORDIC_CLK_SRC_DEFAULT, // Select clock source
};
ESP_ERROR_CHECK(cordic_new_engine(&config, &engine));
Run CORDIC Calculation
-----------------------
Use the engine to perform a calculation:
.. code:: c
cordic_calculate_config_t calc_config = {
.function = ESP_CORDIC_FUNC_COS, // Select function
.iq_format = ESP_CORDIC_FORMAT_Q15, // Input/output data format
.iteration_count = 4, // Iteration count (precision)
.scale_exp = 0, // Input scale exponent n (effective scale 2^n)
};
size_t buffer_depth = 60;
uint32_t data_x[buffer_depth] = {}; // Input 1, length 60
uint32_t data_y[buffer_depth] = {}; // Input 2, length 60
uint32_t res1[buffer_depth] = {}; // Output 1, length 60
uint32_t res2[buffer_depth] = {}; // Output 2, length 60
// Configure input
cordic_input_buffer_desc_t input_buffer = {
.p_data_arg1 = data_x, // Input 1
.p_data_arg2 = data_y, // Input 2
};
// Configure output
cordic_output_buffer_desc_t output_buffer = {
.p_data_res1 = res1, // Output 1
.p_data_res2 = res2, // Output 2
};
// Run calculation
ESP_ERROR_CHECK(cordic_calculate_polling(engine, &calc_config, &input_buffer, &output_buffer, buffer_depth));
Format Conversion
-----------------
The driver provides conversion between fixed-point and floating-point via :cpp:func:`cordic_convert_fixed_to_float` and :cpp:func:`cordic_convert_float_to_fixed`.
.. code:: c
// Fixed-point to float
// res1[0], res2[0] are CORDIC fixed-point outputs
float res1_float = cordic_convert_fixed_to_float(res1[0], ESP_CORDIC_FORMAT_Q15);
float res2_float = cordic_convert_fixed_to_float(res2[0], ESP_CORDIC_FORMAT_Q15);
// Float to fixed-point
uint32_t res1_fixed = cordic_convert_float_to_fixed(res1_float, ESP_CORDIC_FORMAT_Q15);
uint32_t res2_fixed = cordic_convert_float_to_fixed(res2_float, ESP_CORDIC_FORMAT_Q15);
Resource Cleanup
----------------
When the CORDIC engine is no longer needed, call :cpp:func:`cordic_delete_engine` to release the underlying hardware.
.. code:: c
ESP_ERROR_CHECK(cordic_delete_engine(engine));
Advanced
========
Thread Safety
-------------
The following CORDIC APIs are thread-safe and can be called from different RTOS tasks without extra locking:
Factory functions:
- :cpp:func:`cordic_new_engine`
- :cpp:func:`cordic_delete_engine`
.. note::
:cpp:func:`cordic_calculate_polling` is **not** thread-safe. Do not call it from multiple tasks without mutex protection.
Cache Safety
------------
:cpp:func:`cordic_calculate_polling` can be used safely in an ISR. For correct behavior when cache is disabled (e.g., in cache-safe ISRs), enable the Kconfig option `CONFIG_CORDIC_ONESHOT_CTRL_FUNC_IN_IRAM` to place the control functions in IRAM.
Kconfig Options
---------------
The following Kconfig option configures the CORDIC driver:
- :ref:`CONFIG_CORDIC_ONESHOT_CTRL_FUNC_IN_IRAM` - Place calculation control functions in IRAM.
API Reference
=============
CORDIC Driver API
-----------------
.. include-build-file:: inc/cordic.inc
CORDIC HAL API
--------------
.. include-build-file:: inc/components/esp_hal_cordic/include/hal/cordic_types.inc
CORDIC Types
------------
.. include-build-file:: inc/components/esp_driver_cordic/include/driver/cordic_types.inc
+162
View File
@@ -0,0 +1,162 @@
Digital To Analog Converter (DAC)
=================================
:link_to_translation:`zh_CN:[中文]`
{IDF_TARGET_DAC_CH_1: default = "Not Updated!", esp32 = "GPIO25", esp32s2 = "GPIO17"}
{IDF_TARGET_DAC_CH_2: default = "Not Updated!", esp32 = "GPIO26", esp32s2 = "GPIO18"}
{IDF_TARGET_DAC_REF_PIN: default = "Not Updated!", esp32 = "VDD3P3_RTC", esp32s2 = "VDD3P3_RTC_IO"}
Overview
--------
{IDF_TARGET_NAME} has two 8-bit DAC (digital to analog converter) channels respectively connected to {IDF_TARGET_DAC_CH_1} (Channel 1) and {IDF_TARGET_DAC_CH_2} (Channel 2). Each DAC channel can convert the digital value 0~255 to the analog voltage 0~Vref (The reference voltage 'Vref' here is input from the pin {IDF_TARGET_DAC_REF_PIN}, which ideally equals to the power supply VDD). The output voltage can be calculated as the following::
out_voltage = Vref * digi_val / 255
The DAC peripheral supports outputting analog signal in the following ways:
1. Outputting a voltage directly. The DAC channel keeps outputting a specified voltage.
2. Outputting continuous analog signal by DMA. The DAC converts the data in a buffer at a specified frequency.
3. Outputting a cosine wave by the cosine wave generator. The DAC channel can output a cosine wave with specified frequency and amplitude.
For other analog output options, see :doc:`Sigma-Delta Modulation <sdm>` and :doc:`LED Control <ledc>`. Both modules produce high-frequency PWM/PDM output, which can be hardware low-pass filtered in order to generate a lower frequency analog output.
Functional Overview
-------------------
Resources Management
^^^^^^^^^^^^^^^^^^^^
The DAC on {IDF_TARGET_NAME} has two channels. The channels have separate software resources and can be managed by :cpp:type:`dac_oneshot_handle_t`, :cpp:type:`dac_cosine_handle_t`, or :cpp:type:`dac_continuous_handle_t` according to the usage. Registering different modes on a same DAC channel is not allowed.
Direct Voltage Output (One-shot/Direct Mode)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The DAC channels in the group can convert an 8-bit digital value into the analog when :cpp:func:`dac_oneshot_output_voltage` is called (it can be called in ISR). The analog voltage is kept on the DAC channel until the next conversion starts. To start the voltage conversion, the DAC channels need to be enabled first through registering by :cpp:func:`dac_oneshot_new_channel`.
Continuous Wave Output (Continuous/DMA Mode)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
DAC channels can convert digital data continuously via the DMA. There are three ways to write the DAC data:
1. Normal writing (synchronous): Data can be transmitted at one time and kept blocked until all the data has been loaded into the DMA buffer, and the voltage is kept as the last conversion value while no more data is inputted. It is usually used to transport a long signal like an audio. To convert data continuously, the continuous channel handle need to be allocated by calling :cpp:func:`dac_continuous_new_channels` and the DMA conversion should be enabled by calling :cpp:func:`dac_continuous_enable`. Then data can be written by :cpp:func:`dac_continuous_write` synchronously. Refer to :example:`peripherals/dac/dac_continuous/dac_audio` for examples.
2. Cyclical writing: A piece of data can be converted cyclically without blocking, and no more operation is needed after the data are loaded into the DMA buffer. But note that the inputted buffer size is limited by the number of descriptors and the DMA buffer size. It is usually used to transport short signals that need to be repeated, e.g., a sine wave. To achieve cyclical writing, call :cpp:func:`dac_continuous_write_cyclically` after the DAC continuous mode is enabled. Refer to :example:`peripherals/dac/dac_continuous/signal_generator` for examples.
3. Asynchronous writing: Data can be transmitted asynchronously based on the event callback. :cpp:member:`dac_event_callbacks_t::on_convert_done` must be registered to use asynchronous mode. Users can get the :cpp:type:`dac_event_data_t` in the callback which contains the DMA buffer address and length, allowing them to load the data into the buffer directly. To use the asynchronous writing, call :cpp:func:`dac_continuous_register_event_callback` to register the :cpp:member:`dac_event_callbacks_t::on_convert_done` before enabling, and then :cpp:func:`dac_continuous_start_async_writing` to start the asynchronous writing. Note that once the asynchronous writing is started, the callback function will be triggered continuously. Call :cpp:func:`dac_continuous_write_asynchronously` to load the data either in a separate task or in the callback directly. Refer to :example:`peripherals/dac/dac_continuous/dac_audio` for examples.
The following diagram illustrates the life cycle of the DAC continuous driver and the state transitions associated with each API:
.. mermaid::
flowchart TD
NC(["Idle (Initial State)"]) -->|"new_channels()"| REG[Registered]
REG -->|"del_channels()"| NC
REG -->|"enable()"| EN[Enabled]
EN -->|"disable()"| REG
EN -->|"start_async_writing()"| ASYNC[Async Writing]
ASYNC -->|"stop_async_writing()"| EN
EN -->|"write_cyclically()"| CYCLIC[Cyclic Writing]
CYCLIC -->|"stop_cyclically()"| EN
EN -->|"write()"| SYNC[Sync Writing]
SYNC -->|"write()"| SYNC
SYNC -->|"on transmission complete"| EN
subgraph REG_APIS [Registered State APIs]
REGCB["register_event_callbacks()"]
end
subgraph ASYNC_APIS [Async Writing State APIs]
AWRITE["write_asynchronously()"]
end
REG -. can call .-> REGCB
ASYNC -. when receiving a callback .-> AWRITE
.. note::
- For brevity, the prefix ``dac_continuous_`` is omitted from all function names in the diagram.
- For backward compatibility, calling :cpp:func:`dac_continuous_stop_cyclically` to exit cyclic writing is optional — any API that transitions away from the Enabled state will automatically stop an ongoing cyclic conversion. However, explicitly calling :cpp:func:`dac_continuous_stop_cyclically` is recommended.
- Sync writing requires no explicit exit — any API that transitions away from the Enabled state will automatically stop the ongoing sync writing immediately (the data not yet converted is discarded).
.. only:: esp32
On ESP32, the DAC digital controller can be connected internally to the I2S0 and use its DMA for continuous conversion. Although the DAC only needs 8-bit data for conversion, it has to be the left-shifted 8 bits (i.e., the high 8 bits in a 16-bit slot) to satisfy the I2S communication format. By default, the driver helps to expand the data to 16-bit wide automatically. To expand manually, please disable :ref:`CONFIG_DAC_DMA_AUTO_16BIT_ALIGN` in the menuconfig.
The clock of the DAC digital controller comes from I2S0 as well, so there are two clock sources for selection:
- :cpp:enumerator:`dac_continuous_digi_clk_src_t::DAC_DIGI_CLK_SRC_PLL_D2` supports frequency between 19.6 KHz to several MHz. It is the default clock which can also be selected by :cpp:enumerator:`dac_continuous_digi_clk_src_t::DAC_DIGI_CLK_SRC_DEFAULT`.
- :cpp:enumerator:`dac_continuous_digi_clk_src_t::DAC_DIGI_CLK_SRC_APLL` supports frequency between 648 Hz to several MHz. However, it might be occupied by other peripherals, thus not providing the required frequency. In such case, this clock source is available only if APLL still can be correctly divided into the target DAC DMA frequency.
.. only:: esp32s2
On ESP32-S2, the DAC digital controller can be connected internally to the SPI3 and use its DMA for continuous conversion.
The clock sources of the DAC digital controller include:
- :cpp:enumerator:`dac_continuous_digi_clk_src_t::DAC_DIGI_CLK_SRC_APB` supports frequency between 77 Hz to several MHz. It is the default clock which can also be selected by :cpp:enumerator:`dac_continuous_digi_clk_src_t::DAC_DIGI_CLK_SRC_DEFAULT`.
- :cpp:enumerator:`dac_continuous_digi_clk_src_t::DAC_DIGI_CLK_SRC_APLL` supports frequency between 6 Hz to several MHz. However, it might be occupied by other peripherals, thus not providing the required frequency. In such case, this clock source is available only if APLL still can be correctly divided into the target DAC DMA frequency.
Cosine Wave Output (Cosine Mode)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The DAC peripheral has a cosine wave generator, which can generate cosine wave on the channels. Users can specify the frequency, amplitude, and phase of the cosine wave. To output the cosine wave, please acquire the DAC to cosine mode using :cpp:func:`dac_cosine_new_channel`, and then start the cosine wave generator by :cpp:func:`dac_cosine_start`.
Currently, the clock source of the cosine wave generator only comes from ``RTC_FAST`` which can be selected by :cpp:enumerator:`dac_cosine_clk_src_t::DAC_COSINE_CLK_SRC_RTC_FAST`. It is also the default clock source which is the same as :cpp:enumerator:`dac_cosine_clk_src_t::DAC_COSINE_CLK_SRC_RTC_DEFAULT`.
Power Management
^^^^^^^^^^^^^^^^
When the power management is enabled (i.e., :ref:`CONFIG_PM_ENABLE` is on), the system will adjust or stop the clock source of DAC before entering Light-sleep mode, thus potential influence to the DAC signals may lead to false data conversion.
When using DAC driver in continuous mode, it can prevent the system from changing or stopping the clock source in DMA or cosine mode by acquiring a power management lock. When the clock source is generated from APB, the lock type will be set to :cpp:enumerator:`esp_pm_lock_type_t::ESP_PM_APB_FREQ_MAX`. When the clock source is APLL (only in DMA mode), it will be set to :cpp:enumerator:`esp_pm_lock_type_t::ESP_PM_NO_LIGHT_SLEEP`. Whenever the DAC is converting (i.e., DMA or cosine wave generator is working), the driver guarantees that the power management lock is acquired after calling :cpp:func:`dac_continuous_enable`. Likewise, the driver will release the lock when :cpp:func:`dac_continuous_disable` is called.
.. _dac-iram-safe:
IRAM Safe
^^^^^^^^^
By default, the DAC DMA interrupt will be deferred when the cache is disabled for reasons like writing/erasing Flash. Thus the DMA EOF interrupt will not get executed in time.
To avoid such case in real-time applications, you can enable the Kconfig option :ref:`CONFIG_DAC_ISR_IRAM_SAFE` which:
1. Enables the interrupt being serviced even when cache is disabled;
2. Places driver object into DRAM (in case it is linked to PSRAM by accident).
This allows the interrupt to run while the cache is disabled but comes at the cost of increased IRAM consumption.
Thread Safety
^^^^^^^^^^^^^
All the public DAC APIs are guaranteed to be thread safe by the driver, which means users can call them from different RTOS tasks without protection by extra locks. Notice that the DAC driver uses mutex lock to ensure the thread safety, thus the APIs except :cpp:func:`dac_oneshot_output_voltage` are not allowed to be used in ISR.
Kconfig Options
^^^^^^^^^^^^^^^
- :ref:`CONFIG_DAC_ISR_IRAM_SAFE` controls whether the default ISR handler can work when cache is disabled. See :ref:`dac-iram-safe` for more information.
- :ref:`CONFIG_DAC_ENABLE_DEBUG_LOG` is used to enable the debug log output. Enable this option increases the firmware binary size.
.. only:: esp32
- :ref:`CONFIG_DAC_DMA_AUTO_16BIT_ALIGN` auto expands the 8-bit data to 16-bit data in the driver to satisfy the I2S DMA format.
Application Example
-------------------
- :example:`peripherals/dac/dac_continuous/signal_generator` demonstrates how to use the DAC driver on {IDF_TARGET_NAME} to output continuous voltage in two ways: by DMA transmission and by timer interrupt, generating different waveforms such as sine, triangle, saw tooth and square wave.
- :example:`peripherals/dac/dac_continuous/dac_audio` demonstrates how to use the DAC driver on {IDF_TARGET_NAME} to play a piece of audio stored in a buffer, with the audio being played every one second from a speaker or earphone.
- :example:`peripherals/dac/dac_cosine_wave` demonstrates how to use the DAC driver on an {IDF_TARGET_NAME} board to output a cosine wave on both channels, which can be monitored using an oscilloscope or the ADC channels internally.
- :example:`peripherals/dac/dac_oneshot` demonstrates how to use the DAC driver on {IDF_TARGET_NAME} to output a voltage that increases stepwise every 500 ms and resets to 0 periodically, with the output monitored via ADC or an optional oscilloscope.
API Reference
-------------
.. include-build-file:: inc/dac_oneshot.inc
.. include-build-file:: inc/dac_cosine.inc
.. include-build-file:: inc/dac_continuous.inc
.. include-build-file:: inc/components/esp_driver_dac/include/driver/dac_types.inc
.. include-build-file:: inc/components/esp_hal_ana_conv/include/hal/dac_types.inc
@@ -0,0 +1,157 @@
Dedicated GPIO
==============
:link_to_translation:`zh_CN:[中文]`
Overview
--------
The dedicated GPIO is designed for CPU interaction with GPIO matrix and IO MUX. Any GPIO that is configured as "dedicated" can be access by CPU instructions directly, which makes it easy to achieve a high GPIO flip speed, and simulate serial/parallel interface in a bit-banging way. As toggling a GPIO in this "CPU Dedicated" way costs few overhead, it would be great for cases like performance measurement using an oscilloscope.
Create/Destroy GPIO Bundle
--------------------------
A GPIO bundle is a group of GPIOs, which can be manipulated at the same time in one CPU cycle. The maximal number of GPIOs that a bundle can contain is limited by each CPU. What's more, the GPIO bundle has a strong relevance to the CPU which it derives from. **Any operations on the GPIO bundle should be put inside a task which is running on the same CPU core to the GPIO bundle belongs to.** Likewise, only those ISRs who are installed on the same CPU core are allowed to do operations on that GPIO bundle.
.. note::
Dedicated GPIO is more like a CPU peripheral, it has a strong relationship with CPU core. It's highly recommended to install and operate GPIO bundle in the same task, and the task should be pined to a CPU core. For example, if GPIO_A is connected to CPU_0, but the dedicated GPIO instruction is issued from CPU_1, then it's impossible to control GPIO_A.
To install a GPIO bundle, one needs to call :cpp:func:`dedic_gpio_new_bundle` to allocate the software resources and connect the dedicated channels to user selected GPIOs. Configurations for a GPIO bundle are covered in :cpp:type:`dedic_gpio_bundle_config_t` structure:
- :cpp:member:`dedic_gpio_bundle_config_t::gpio_array`: An array that contains GPIO number.
- :cpp:member:`dedic_gpio_bundle_config_t::array_size`: Element number of :cpp:member:`dedic_gpio_bundle_config_t::gpio_array`.
- :cpp:member:`dedic_gpio_bundle_config_t::in_en` and :cpp:member:`dedic_gpio_bundle_config_t::out_en` are used to configure whether to enable the input and output ability of the GPIO(s).
- :cpp:member:`dedic_gpio_bundle_config_t::in_invert` and :cpp:member:`dedic_gpio_bundle_config_t::out_invert` are used to configure whether to invert the GPIO signal.
The following code shows how to install an output only GPIO bundle:
.. highlight:: c
::
// Create bundleA, output only
dedic_gpio_bundle_handle_t bundleA = NULL;
dedic_gpio_bundle_config_t bundleA_config = {
.gpio_array = bundleA_gpios,
.array_size = sizeof(bundleA_gpios) / sizeof(bundleA_gpios[0]),
.flags = {
.out_en = 1,
},
};
ESP_ERROR_CHECK(dedic_gpio_new_bundle(&bundleA_config, &bundleA));
To uninstall the GPIO bundle, you should call :cpp:func:`dedic_gpio_del_bundle`.
GPIO Bundle Operations
----------------------
.. list-table::
:widths: 50 50
:header-rows: 1
* - Operations
- Functions
* - Write to GPIOs in the bundle by mask
- :cpp:func:`dedic_gpio_bundle_write`
* - Read the value that output from the given GPIO bundle
- :cpp:func:`dedic_gpio_bundle_read_out`
* - Read the value that input to the given GPIO bundle
- :cpp:func:`dedic_gpio_bundle_read_in`
.. note::
Using the above functions might not get a high GPIO flip speed because of the overhead of function calls and the bit operations involved inside. Users can try :ref:`manipulate_gpios_by_writing_assembly_code` instead to reduce the overhead but should take care of the thread safety by themselves.
.. _manipulate_gpios_by_writing_assembly_code:
Manipulate GPIOs by Writing Assembly Code
------------------------------------------
For advanced users, they can always manipulate the GPIOs by writing assembly code or invoking CPU Low Level APIs. The usual procedure could be:
1. Allocate a GPIO bundle: :cpp:func:`dedic_gpio_new_bundle`
2. Query the mask occupied by that bundle: :cpp:func:`dedic_gpio_get_out_mask` or/and :cpp:func:`dedic_gpio_get_in_mask`
3. Call CPU LL apis (e.g., `dedic_gpio_cpu_ll_write_mask`) or write assembly code with that mask
4. The fastest way of toggling IO is to use the dedicated "set/clear" instructions:
.. only:: CONFIG_IDF_TARGET_ARCH_XTENSA
- Set bits of GPIO: ``set_bit_gpio_out imm[7:0]``
- Clear bits of GPIO: ``clr_bit_gpio_out imm[7:0]``
- Note: Immediate value width depends on the number of dedicated GPIO channels
.. only:: CONFIG_IDF_TARGET_ARCH_RISCV
- Set bits of GPIO: ``csrrsi rd, csr, imm[4:0]``
- Clear bits of GPIO: ``csrrci rd, csr, imm[4:0]``
- Note: Can only control the lowest 4 GPIO channels
.. only:: esp32s2
For details of supported dedicated GPIO instructions, please refer to **{IDF_TARGET_NAME} Technical Reference Manual** > **IO MUX and GPIO Matrix (GPIO, IO_MUX)** [`PDF <{IDF_TARGET_TRM_EN_URL}#iomuxgpio>`__].
.. only:: esp32s3
For details of supported dedicated GPIO instructions, please refer to **{IDF_TARGET_NAME} Technical Reference Manual** > **Processor Instruction Extensions (PIE) (to be added later)** [`PDF <{IDF_TARGET_TRM_EN_URL}#pie>`__].
.. only:: not (esp32s2 or esp32s3)
For details of supported dedicated GPIO instructions, please refer to **{IDF_TARGET_NAME} Technical Reference Manual** > **ESP-RISC-V CPU** [`PDF <{IDF_TARGET_TRM_EN_URL}#riscvcpu>`__].
Some of the dedicated CPU instructions are also wrapped inside ``hal/dedic_gpio_cpu_ll.h`` as helper inline functions.
.. note::
Writing assembly code in application could make your code hard to port between targets, because those customized instructions are not guaranteed to remain the same format on different targets.
.. only:: SOC_DEDIC_GPIO_HAS_INTERRUPT
Interrupt Handling
------------------
Dedicated GPIO can also trigger interrupt on specific input event. All supported events are defined in :cpp:type:`dedic_gpio_intr_type_t`.
One can enable and register interrupt callback by calling :cpp:func:`dedic_gpio_bundle_set_interrupt_and_callback`. The prototype of the callback function is defined in :cpp:type:`dedic_gpio_isr_callback_t`. Keep in mind, the callback should return true if there's some high priority task woken up.
.. highlight:: c
::
// user defined ISR callback
IRAM_ATTR bool dedic_gpio_isr_callback(dedic_gpio_bundle_handle_t bundle, uint32_t index, void *args)
{
SemaphoreHandle_t sem = (SemaphoreHandle_t)args;
BaseType_t high_task_wakeup = pdFALSE;
xSemaphoreGiveFromISR(sem, &high_task_wakeup);
return high_task_wakeup == pdTRUE;
}
// enable positive edge interrupt on the second GPIO in the bundle (i.e., index 1)
ESP_ERROR_CHECK(dedic_gpio_bundle_set_interrupt_and_callback(bundle, BIT(1), DEDIC_GPIO_INTR_POS_EDGE, dedic_gpio_isr_callback, sem));
// wait for done semaphore
xSemaphoreTake(sem, portMAX_DELAY);
Application Example
-------------------
.. list::
* Software emulation (bit banging) of the UART/I2C/SPI protocols in assembly using the dedicated GPIOs and their associated CPU instructions: :example:`peripherals/dedicated_gpio`.
:SOC_DEDIC_GPIO_HAS_INTERRUPT: * :example:`peripherals/gpio/matrix_keyboard` demonstrates how to drive a matrix keyboard using the dedicated GPIO APIs, including manipulating the level on a group of GPIOs, triggering edge interrupt, and reading level on a group of GPIOs.
* :example:`peripherals/dedicated_gpio/soft_i2c` demonstrates how to configure and use dedicated/fast GPIOs to emulate an I2C master, perform write-read transactions on the bus, and handle strict timing requirements by placing certain functions in IRAM.
* :example:`peripherals/dedicated_gpio/soft_uart` demonstrates how to emulate a UART bus using dedicated/fast GPIOs on {IDF_TARGET_NAME}, which can send and receive characters on the UART bus using a TX pin and an RX pin, with the baud rate and other configurations adjustable via `menuconfig`.
.. only:: CONFIG_IDF_TARGET_ARCH_RISCV
* :example:`peripherals/dedicated_gpio/soft_spi` demonstrates how to configure and use dedicated/fast GPIOs to emulate a full-duplex SPI bus on {IDF_TARGET_NAME}.
API Reference
-------------
.. include-build-file:: inc/dedic_gpio.inc
+137
View File
@@ -0,0 +1,137 @@
RSA Digital Signature Peripheral (RSA_DS)
==========================================
:link_to_translation:`zh_CN:[中文]`
The RSA Digital Signature Peripheral (RSA_DS) provides hardware acceleration of signing messages based on RSA. It uses pre-encrypted parameters to calculate a signature. The parameters are encrypted using HMAC as a key-derivation function. In turn, the HMAC uses eFuses as the input key.
.. only:: SOC_KEY_MANAGER_SUPPORTED
On {IDF_TARGET_NAME}, the RSA Digital Signature Peripheral (RSA_DS) can also use a key stored in the Key Manager instead of an eFuse key block. The AES encryption key can be directly deployed in the Key Manager with the type :cpp:enumerator:`ESP_KEY_MGR_DS_KEY`. Refer to :ref:`key-manager` for more details.
The whole process happens in hardware so that neither the decryption key for the RSA parameters nor the input key for the HMAC key derivation function can be seen by the software while calculating the signature.
For more detailed information on the hardware involved in the signature calculation and the registers used, see **{IDF_TARGET_NAME} Technical Reference Manual** > **RSA Digital Signature Peripheral (RSA_DS)** [`PDF <{IDF_TARGET_TRM_EN_URL}#digsig>`__].
Private Key Parameters
----------------------
The private key parameters for the RSA signature are stored in flash. To prevent unauthorized access, they are AES-encrypted. The HMAC module is used as a key-derivation function to calculate the AES encryption key for the private key parameters. In turn, the HMAC module uses a key from the eFuses key block which can be read-protected to prevent unauthorized access as well.
Upon signature calculation invocation, the software only specifies which eFuse key to use, the corresponding eFuse key purpose, the location of the encrypted RSA parameters, and the message.
Key Generation
--------------
Both the HMAC key and the RSA private key have to be created and stored before the RSA_DS peripheral can be used. This needs to be done in software on the {IDF_TARGET_NAME} or alternatively on a host. For this context, ESP-IDF provides :cpp:func:`esp_efuse_write_block` to set the HMAC key and :cpp:func:`esp_hmac_calculate` to encrypt the private RSA key parameters.
You can find instructions on how to calculate and assemble the private key parameters in **{IDF_TARGET_NAME} Technical Reference Manual** > **RSA Digital Signature Peripheral (RSA_DS)** [`PDF <{IDF_TARGET_TRM_EN_URL}#digsig>`__].
Signature Calculation with ESP-IDF
----------------------------------
For more detailed information on the workflow and the registers used, see **{IDF_TARGET_NAME} Technical Reference Manual** > **RSA Digital Signature Peripheral (RSA_DS)** [`PDF <{IDF_TARGET_TRM_EN_URL}#digsig>`__].
Three parameters need to be prepared to calculate the digital signature:
#. The eFuse key block ID which is used as the key for the HMAC
#. The location of the encrypted private key parameters
#. The message to be signed
**Low-level API (plain RSA)**
Since the signature calculation takes some time, there are two possible API versions to use in ESP-IDF. The first one is :cpp:func:`esp_ds_sign` and simply blocks until the calculation is finished. If software needs to do something else during the calculation, :cpp:func:`esp_ds_start_sign` can be called, followed by periodic calls to :cpp:func:`esp_ds_is_busy` to check when the calculation has finished. Once the calculation has finished, :cpp:func:`esp_ds_finish_sign` can be called to get the resulting signature.
The APIs :cpp:func:`esp_ds_sign` and :cpp:func:`esp_ds_start_sign` calculate a plain RSA signature with the help of the RSA_DS peripheral. This signature must be converted to an appropriate format (e.g., PKCS#1 v1.5 or PSS) for use in TLS or other protocols.
.. note::
This is only the basic RSA_DS building block; the message length is fixed. To create signatures of arbitrary messages, the input is normally a hash of the actual message, padded up to the required length.
**PSA Crypto driver**
The RSA_DS peripheral is also exposed via the **PSA Crypto RSA_DS driver**, so you can use standard PSA APIs for signing (PKCS#1 v1.5 or PSS) and RSA decryption (PKCS#1 v1.5 or OAEP). Enable ``CONFIG_MBEDTLS_HARDWARE_RSA_DS_PERIPHERAL`` in ``Component config`` > ``mbedTLS``. For using the RSA_DS peripheral with ESP-TLS (e.g. TLS client authentication), see :ref:`digital-signature-with-esp-tls` in the ESP-TLS documentation.
.. _configure-the-ds-peripheral:
Configure the RSA_DS Peripheral for a TLS Connection
----------------------------------------------------
The RSA_DS peripheral on {IDF_TARGET_NAME} chip must be configured before it can be used for a TLS connection. The configuration involves the following steps:
1) Randomly generate a 256-bit value called the ``Initialization Vector`` (IV).
2) Randomly generate a 256-bit value called the ``HMAC_KEY``.
3) Calculate the encrypted private key parameters from the client private key (RSA) and the parameters generated in the above steps.
4) Then burn the 256-bit ``HMAC_KEY`` on the eFuse, which can only be read by the RSA_DS peripheral.
For more details, see **{IDF_TARGET_NAME} Technical Reference Manual** > **RSA Digital Signature Peripheral (RSA_DS)** [`PDF <{IDF_TARGET_TRM_EN_URL}#digsig>`__].
To configure the RSA_DS peripheral for development purposes, you can use the `esp-secure-cert-tool <https://pypi.org/project/esp-secure-cert-tool>`_.
The encrypted private key parameters obtained after the RSA_DS peripheral configuration should be stored in flash. The application needs to read the RSA_DS data from flash (e.g., through the APIs provided by the `esp_secure_cert_mgr <https://github.com/espressif/esp_secure_cert_mgr>`_ component; see the `component/README <https://github.com/espressif/esp_secure_cert_mgr#readme>`_ for more details). For using the RSA_DS peripheral with ESP-TLS, see :ref:`digital-signature-with-esp-tls`.
Using RSA_DS with PSA Crypto
-----------------------------
To use the RSA_DS peripheral for signing or decryption in application code (outside of ESP-TLS), enable ``CONFIG_MBEDTLS_HARDWARE_RSA_DS_PERIPHERAL``. Populate an ``esp_ds_data_ctx_t`` with the encrypted key data (:cpp:type:`esp_ds_data_t`), the eFuse key block ID, and the RSA key length in bits. Ensure the ``rsa_length`` field of :cpp:type:`esp_ds_data_t` is set when the key is created (e.g. via :cpp:func:`esp_ds_encrypt_params` or the RSA_DS provisioning tool). Then wrap the context in an ``esp_rsa_ds_opaque_key_t``, import it as a PSA opaque key using ``PSA_KEY_LIFETIME_ESP_RSA_DS_VOLATILE``, and call ``psa_sign_hash()`` or ``psa_asymmetric_decrypt()``:
.. code-block:: c
#include "psa/crypto.h"
#include "psa_crypto_driver_esp_rsa_ds.h"
// ds_ctx points to esp_ds_data_ctx_t (e.g. from secure cert or NVS)
esp_ds_data_ctx_t *ds_ctx = ...;
esp_rsa_ds_opaque_key_t rsa_ds_opaque_key = {
.ds_data_ctx = ds_ctx,
};
psa_key_attributes_t attrs = PSA_KEY_ATTRIBUTES_INIT;
psa_set_key_type(&attrs, PSA_KEY_TYPE_RSA_KEY_PAIR);
psa_set_key_bits(&attrs, ds_ctx->rsa_length_bits);
psa_set_key_usage_flags(&attrs, PSA_KEY_USAGE_SIGN_HASH);
psa_set_key_algorithm(&attrs, PSA_ALG_RSA_PKCS1V15_SIGN(PSA_ALG_SHA_256));
psa_set_key_lifetime(&attrs, PSA_KEY_LIFETIME_ESP_RSA_DS_VOLATILE);
psa_key_id_t key_id;
psa_status_t status = psa_import_key(&attrs,
(const uint8_t *)&rsa_ds_opaque_key,
sizeof(rsa_ds_opaque_key),
&key_id);
psa_reset_key_attributes(&attrs);
if (status != PSA_SUCCESS) {
// handle error
}
// Sign a hash (e.g. SHA-256 of your message)
uint8_t hash[32] = { ... };
uint8_t signature[256];
size_t sig_len;
status = psa_sign_hash(key_id, PSA_ALG_RSA_PKCS1V15_SIGN(PSA_ALG_SHA_256),
hash, sizeof(hash), signature, sizeof(signature), &sig_len);
psa_destroy_key(key_id);
Persistent vs. Volatile RSA_DS PSA Keys
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The driver supports two PSA key lifetimes for RSA_DS keys:
- ``PSA_KEY_LIFETIME_ESP_RSA_DS_VOLATILE`` (used in the example above) stores only pointers to the caller-supplied ``esp_ds_data_ctx_t`` and any Key Manager recovery info in the PSA key slot. The referenced buffers must remain valid until :cpp:func:`psa_destroy_key` is called. This avoids deep-copying large blobs such as :cpp:type:`esp_ds_data_t` (≈1200-1600 bytes, chip-dependent) when they already live in mmap'd flash via ``esp_secure_cert_mgr``.
- ``PSA_KEY_LIFETIME_ESP_RSA_DS`` (persistent) deep-copies the encrypted key material into the PSA key slot at :cpp:func:`psa_import_key` time and PSA persists it to NVS together with the rest of the key attributes. The caller is free to release the import-time buffers once :cpp:func:`psa_import_key` returns; subsequent :cpp:func:`psa_sign_hash` / :cpp:func:`psa_asymmetric_decrypt` calls retrieve the bytes back from NVS automatically. Use this lifetime when the application wants the key to survive reboots without having to reload the ``esp_ds_data_ctx_t`` from external storage on every boot.
Example for SSL Mutual Authentication Using RSA_DS
---------------------------------------------------
The SSL mutual authentication example that previously lived under ``examples/protocols/mqtt/ssl_ds`` is now shipped with the standalone `espressif/mqtt <https://components.espressif.com/components/espressif/mqtt>`__ component. Follow the component documentation to fetch the SSL RSA_DS example and build it together with ESP-MQTT. The example continues to use ``mqtt_client`` (implemented by ESP-MQTT) to connect to ``test.mosquitto.org`` over mutual-authenticated TLS, with the TLS portion handled by ESP-TLS.
.. only:: SOC_KEY_MANAGER_SUPPORTED
In case both the :cpp:member:`esp_ds_data_ctx_t::efuse_key_id` and :cpp:member:`esp_rsa_ds_opaque_key_t::key_recovery_info` are set, the ESP-DS PSA driver prefers using the Key Manager-based DS key over the eFuse-based DS key.
API Reference
-------------
.. include-build-file:: inc/psa_crypto_driver_esp_rsa_ds_contexts.inc
+158
View File
@@ -0,0 +1,158 @@
ECDSA Digital Signature Peripheral (ECDSA_DS)
==============================================
:link_to_translation:`zh_CN:[中文]`
The ECDSA Digital Signature Peripheral (ECDSA_DS) implements the Elliptic Curve Digital Signature Algorithm (ECDSA), a variant of the Digital Signature Algorithm (DSA) that uses elliptic-curve cryptography.
{IDF_TARGET_NAME}'s ECDSA_DS peripheral provides a secure and efficient environment for computing ECDSA signatures. It offers fast computations while ensuring the confidentiality of the signing process to prevent information leakage. ECDSA private key used in the signing process is accessible only to the hardware peripheral, and it is not readable by software.
The ECDSA_DS peripheral can help to establish **Secure Device Identity** for TLS mutual authentication and similar use-cases.
Supported Features
------------------
.. list::
- ECDSA digital signature generation and verification
:SOC_ECDSA_SUPPORT_CURVE_P384: - Three different elliptic curves, namely P-192, P-256 and P-384 (FIPS 186-3 specification)
:not SOC_ECDSA_SUPPORT_CURVE_P384: - Two different elliptic curves, namely P-192 and P-256 (FIPS 186-3 specification)
:SOC_ECDSA_SUPPORT_CURVE_P384: - Three hash algorithms for message hash in the ECDSA operation, namely SHA-224, SHA-256 and SHA-384 (FIPS PUB 180-4 specification)
:not SOC_ECDSA_SUPPORT_CURVE_P384: - Two hash algorithms for message hash in the ECDSA operation, namely SHA-224 and SHA-256 (FIPS PUB 180-4 specification)
ECDSA_DS on {IDF_TARGET_NAME}
-----------------------------
On {IDF_TARGET_NAME}, the ECDSA_DS module works with a secret key burnt into an eFuse block.
.. only:: SOC_KEY_MANAGER_SUPPORTED
On {IDF_TARGET_NAME}, the ECDSA_DS module also supports storing a secret key in the Key Manager. Refer to :ref:`key-manager` for more details.
This key is made completely inaccessible (default mode) for any resources outside the cryptographic modules, thus avoiding key leakage.
ECDSA Key Storage
^^^^^^^^^^^^^^^^^
.. only:: SOC_ECDSA_SUPPORT_CURVE_P384
ECDSA private keys are stored in eFuse key blocks. The number of key blocks required depends on the curve size:
- **P-256 curve**: Require one eFuse key block (256 bits)
- **P-384 curve**: Requires two eFuse key blocks (512 bits total)
For curves requiring two key blocks (like P-384), configure the following fields:
- Set :cpp:member:`esp_tls_cfg_t::ecdsa_key_efuse_blk` to the low block number
- Set :cpp:member:`esp_tls_cfg_t::ecdsa_key_efuse_blk_high` to the high block number
For single-block curves (like P-256), only set :cpp:member:`esp_tls_cfg_t::ecdsa_key_efuse_blk` and leave :cpp:member:`esp_tls_cfg_t::ecdsa_key_efuse_blk_high` as 0 or unassigned.
.. only:: not SOC_ECDSA_SUPPORT_CURVE_P384
ECDSA private keys are stored in eFuse key blocks. One eFuse key block (256 bits) is required for P-256 curve.
Configure the following field:
- Set :cpp:member:`esp_tls_cfg_t::ecdsa_key_efuse_blk` to the block number and leave :cpp:member:`esp_tls_cfg_t::ecdsa_key_efuse_blk_high` as 0 or unassigned.
ECDSA key can be programmed externally through ``idf.py`` script. Here is an example of how to program the ECDSA key:
Using eFuses to store the ECDSA key:
.. code:: bash
idf.py efuse-burn-key <BLOCK_NUM> </path/to/ecdsa_private_key.pem> ECDSA_KEY
.. only:: SOC_EFUSE_BLOCK9_KEY_PURPOSE_QUIRK
.. note::
Five physical eFuse blocks can be used as keys for the ECDSA_DS module: block 4 ~ block 8. E.g., for block 4 (which is the first key block) , the argument should be ``BLOCK_KEY0``.
.. only:: not SOC_EFUSE_BLOCK9_KEY_PURPOSE_QUIRK
.. note::
Six physical eFuse blocks can be used as keys for the ECDSA_DS module: block 4 ~ block 9. E.g., for block 4 (which is the first key block) , the argument should be ``BLOCK_KEY0``.
.. only:: SOC_KEY_MANAGER_SUPPORTED
Using the Key Manager to store the ECDSA key:
ECDSA private keys can be stored in the Key Manager. Refer to :ref:`key-manager` for more details.
Deploy an ECDSA key into the Key Manager and store the generated Key Recovery info in the flash memory for persistent keys.
Alternatively the ECDSA key can also be programmed through the application running on the target.
Using eFuses to store the ECDSA key:
Following code snippet uses :cpp:func:`esp_efuse_write_key` to set physical key block 0 in the eFuse with key purpose as :cpp:enumerator:`esp_efuse_purpose_t::ESP_EFUSE_KEY_PURPOSE_ECDSA_KEY`:
.. code-block:: c
#include "esp_efuse.h"
const uint8_t key_data[32] = { ... };
esp_err_t status = esp_efuse_write_key(EFUSE_BLK_KEY0,
ESP_EFUSE_KEY_PURPOSE_ECDSA_KEY,
key_data, sizeof(key_data));
if (status == ESP_OK) {
// written key
} else {
// writing key failed, maybe written already
}
.. only:: SOC_ECDSA_P192_CURVE_DEFAULT_DISABLED
ECDSA Curve Configuration
-------------------------
.. only:: esp32h2
The ECDSA_DS peripheral of the ESP32-H2 supports both ECDSA-P192 and ECDSA-P256 operations. However, starting with ESP32-H2 revision 1.2, only ECDSA-P256 operations are enabled by default. You can enable ECDSA-P192 operations using the following configuration options:
.. only:: not esp32h2
The ECDSA_DS peripheral of {IDF_TARGET_NAME} supports both ECDSA-P192 and ECDSA-P256 operations, but only ECDSA-P256 operations are enabled by default. You can enable ECDSA-P192 operations through the following configuration options:
- :ref:`CONFIG_ESP_ECDSA_ENABLE_P192_CURVE` enables support for ECDSA-P192 curve operations, allowing the device to perform ECDSA operations with both 192-bit and 256-bit curves. However, if ECDSA-P192 operations have already been permanently disabled during eFuse write protection, enabling this option can not re-enable ECDSA-P192 curve operations.
- :cpp:func:`esp_efuse_enable_ecdsa_p192_curve_mode()` enables ECDSA-P192 curve operations programmatically by writing the appropriate value to the eFuse, allowing both P-192 and P-256 curve operations. Note that this API will fail if the eFuse is already write-protected.
.. only:: SOC_ECDSA_SUPPORT_DETERMINISTIC_MODE
Deterministic Signature Generation
-----------------------------------
The ECDSA_DS peripheral of {IDF_TARGET_NAME} also supports generation of deterministic signatures using deterministic derivation of the parameter K as specified in the `RFC 6979 <https://tools.ietf.org/html/rfc6979>`_ section 3.2.
Non-Determinisitic Signature Generation
---------------------------------------
Dependency on TRNG
^^^^^^^^^^^^^^^^^^
The ECDSA_DS peripheral relies on the hardware True Random Number Generator (TRNG) for its internal entropy requirement for generating non-deterministic signatures. During ECDSA signature creation, the algorithm requires a random integer to be generated as specified in the `RFC 6090 <https://tools.ietf.org/html/rfc6090>`_ section 5.3.2.
Please ensure that hardware :doc:`RNG <../system/random>` is enabled before starting ECDSA computations (primarily signing) in the application.
Application Outline
-------------------
Please refer to the :ref:`ecdsa-peri-with-esp-tls` guide for details on how-to use the ECDSA_DS peripheral for establishing a mutually authenticated TLS connection.
The ECDSA_DS peripheral in Mbed TLS stack is integrated by overriding the ECDSA signing and verifying APIs. Please note that, the ECDSA_DS peripheral does not support all curves or hash algorithms, and hence for cases where the hardware requirements are not met, the implementation falls back to the software.
For a particular TLS context, additional APIs have been supplied to populate certain fields (e.g., private key ctx) to differentiate routing to hardware. ESP-TLS layer integrates these APIs internally and hence no additional work is required at the application layer. However, for custom use-cases please refer to API details below.
API Reference
-------------
.. include-build-file:: inc/psa_crypto_driver_esp_ecdsa_contexts.inc
+174
View File
@@ -0,0 +1,174 @@
Event Task Matrix (ETM)
=======================
:link_to_translation:`zh_CN:[中文]`
Introduction
------------
Normally, if a peripheral X needs to notify peripheral Y of a particular event, this could only be done via a CPU interrupt from peripheral X, where the CPU notifies peripheral Y on behalf of peripheral X. However, in time-critical applications, the latency introduced by CPU interrupts is non-negligible.
With the help of the Event Task Matrix (ETM) module, some peripherals can directly notify other peripherals of events through pre-set connections without the intervention of CPU interrupts. This allows precise and low latency synchronization between peripherals, and lessens the CPU's workload as the CPU no longer needs to handle these events.
.. blockdiag:: /../_static/diagrams/etm/etm_channel.diag
:caption: ETM channels Overview
:align: center
The ETM module has multiple programmable channels, they are used to connect a particular **Event** to a particular **Task**. When an event is activated, the ETM channel will trigger the corresponding task automatically.
Peripherals that support ETM functionality provide their or unique set of events and tasks to be connected by the ETM. An ETM channel can connect any event to any task, even looping back an event to a task on the same peripheral. However, an ETM channel can only connect one event to one task at a time (i.e., 1 to 1 relation). If you want to use different events to trigger the same task, you can set up more ETM channels.
Typically, with the help of the ETM module, you can implement features like:
- Toggle the GPIO when a timer alarm event happens
- Start an ADC conversion when a pulse edge is detected on a GPIO
Functional Overview
-------------------
The following sections of this document cover the typical steps to configure and use the ETM module.
- :ref:`etm-channel-allocation` - describes how to install and uninstall the ETM channel.
- :ref:`etm-event` - describes how to allocate a new ETM event handle or fetch an existing handle from various peripherals.
- :ref:`etm-task` - describes how to allocate a new ETM task handle or fetch an existing handle from various peripherals.
- :ref:`etm-channel-control` - describes common ETM channel control functions.
- :ref:`etm-power-management` - describes the options and strategies provided by the driver in order to save power.
- :ref:`etm-thread-safety` - lists which APIs are guaranteed to be thread-safe by the driver.
- :ref:`etm-kconfig-options` - lists the supported Kconfig options that can be used to make a different effect on driver behavior.
.. _etm-channel-allocation:
ETM Channel Allocation
^^^^^^^^^^^^^^^^^^^^^^
There are many identical ETM channels in {IDF_TARGET_NAME} [1]_, and each channel is represented by :cpp:type:`esp_etm_channel_handle_t` in the software. The ETM core driver manages all available hardware resources in a pool so that you do not need to care about which channel is in use and which is not. The ETM core driver will allocate a channel for you when you call :cpp:func:`esp_etm_new_channel` and delete it when you call :cpp:func:`esp_etm_del_channel`. All requirements needed for allocating a channel are provided in :cpp:type:`esp_etm_channel_config_t`.
Before deleting an ETM channel, please disable it by :cpp:func:`esp_etm_channel_disable` in advance or make sure it has not been enabled yet by :cpp:func:`esp_etm_channel_enable`.
.. _etm-event:
ETM Event
^^^^^^^^^
ETM Event abstracts the event source, masking the details of specific event sources, and is represented by :cpp:type:`esp_etm_event_handle_t` in the software, allowing applications to handle different types of events more easily. ETM events can be generated from a variety of peripherals, thus the way to get the event handle differs from peripherals. When an ETM event is no longer used, you should call :cpp:func:`esp_etm_channel_connect` with a ``NULL`` event handle to disconnect it and then call :cpp:func:`esp_etm_del_event` to free the event resource.
GPIO Events
~~~~~~~~~~~
GPIO **edge** event is the most common event type, it can be generated by any GPIO pin. You can call :cpp:func:`gpio_new_etm_event` to create a GPIO event handle, with the configurations provided in :cpp:type:`gpio_etm_event_config_t`:
- :cpp:member:`gpio_etm_event_config_t::edge` or :cpp:member:`gpio_etm_event_config_t::edges` decides which edge(s) to trigger the event(s), supported edge types are listed in the :cpp:type:`gpio_etm_event_edge_t`.
You need to build a connection between the GPIO ETM event handle and the GPIO number. So you should call :cpp:func:`gpio_etm_event_bind_gpio` afterwards. Please note, only the ETM event handle that created by :cpp:func:`gpio_new_etm_event` can set a GPIO number. Calling this function with other kinds of ETM events returns :c:macro:`ESP_ERR_INVALID_ARG` error. Needless to say, this function does not help with the GPIO initialization, you still need to call :cpp:func:`gpio_config` to set the property like direction, pull up/down mode separately.
Other Peripheral Events
~~~~~~~~~~~~~~~~~~~~~~~
.. list::
:SOC_SYSTIMER_SUPPORT_ETM: - You can call :cpp:func:`esp_systick_new_etm_alarm_event` to get the ETM event from RTOS Systick, one per CPU core.
:SOC_SYSTIMER_SUPPORT_ETM: - Refer to :doc:`/api-reference/system/esp_timer` for how to get the ETM event handle from esp_timer.
:SOC_TIMER_SUPPORT_ETM: - Refer to :ref:`gptimer-etm-event-and-task` for how to get the ETM event handle from GPTimer.
:SOC_GDMA_SUPPORT_ETM: - Refer to :doc:`/api-reference/peripherals/async_memcpy` for how to get the ETM event handle from async memcpy.
:SOC_MCPWM_SUPPORT_ETM: - Refer to :doc:`/api-reference/peripherals/mcpwm` for how to get the ETM event handle from MCPWM.
:SOC_ANA_CMPR_SUPPORT_ETM: - Refer to :doc:`/api-reference/peripherals/ana_cmpr` for how to get the ETM event handle from analog comparator.
:SOC_TEMPERATURE_SENSOR_SUPPORT_ETM: - Refer to :doc:`/api-reference/peripherals/temp_sensor` for how to get the ETM event handle from temperature sensor.
:SOC_I2S_SUPPORTS_ETM: - Refer to :doc:`/api-reference/peripherals/i2s` for how to get the ETM event handle from I2S.
:SOC_LEDC_SUPPORT_ETM: - Refer to :doc:`/api-reference/peripherals/ledc` for how to get the ETM event handle from LEDC.
.. _etm-task:
ETM Task
^^^^^^^^
ETM Task abstracts the task action and is represented by :cpp:type:`esp_etm_task_handle_t` in the software, allowing tasks to be managed and represented in the same way. ETM tasks can be assigned to a variety of peripherals, thus the way to get the task handle differs from peripherals. When an ETM task is no longer used, you should call :cpp:func:`esp_etm_channel_connect` with a ``NULL`` task handle to disconnect it and then call :cpp:func:`esp_etm_del_task` to free the task resource.
GPIO Tasks
~~~~~~~~~~
GPIO task is the most common task type. One GPIO can take one or more GPIO ETM task actions, and one GPIO ETM task action can even manage multiple GPIOs. When the task gets activated by the ETM channel, all managed GPIOs can set/clear/toggle at the same time. You can call :cpp:func:`gpio_new_etm_task` to create a GPIO task handle, with the configurations provided in :cpp:type:`gpio_etm_task_config_t`:
- :cpp:member:`gpio_etm_task_config_t::action` or :cpp:member:`gpio_etm_task_config_t::actions` decides what GPIO action(s) would be taken by the ETM task. Supported actions are listed in the :cpp:type:`gpio_etm_task_action_t`. If one GPIO needs to take more than one actions, the action tasks have to be created in one :cpp:func:`gpio_new_etm_task` call with filling the actions into the array of :cpp:member:`gpio_etm_task_config_t::actions`.
To build a connection between the GPIO ETM task and the GPIO number, you should call :cpp:func:`gpio_etm_task_add_gpio`. You can call this function by several times if you want the task handle to manage more GPIOs. Please note, only the ETM task handle that created by :cpp:func:`gpio_new_etm_task` can manage a GPIO. Calling this function with other kinds of ETM tasks returns :c:macro:`ESP_ERR_INVALID_ARG` error. Needless to say, this function does not help with the GPIO initialization, you still need to call :cpp:func:`gpio_config` to set the property like direction, pull up/down mode separately.
Before you call :cpp:func:`esp_etm_del_task` to delete the GPIO ETM task, make sure that all previously added GPIOs are removed by :cpp:func:`gpio_etm_task_rm_gpio` in advance.
Other Peripheral Tasks
~~~~~~~~~~~~~~~~~~~~~~
.. list::
:SOC_TIMER_SUPPORT_ETM: - Refer to :ref:`gptimer-etm-event-and-task` for how to get the ETM task handle from GPTimer.
:SOC_TEMPERATURE_SENSOR_SUPPORT_ETM: - Refer to :doc:`/api-reference/peripherals/temp_sensor` for how to get the ETM task handle from temperature sensor.
:SOC_I2S_SUPPORTS_ETM: - Refer to :doc:`/api-reference/peripherals/i2s` for how to get the ETM task handle from I2S.
:SOC_LEDC_SUPPORT_ETM: - Refer to :doc:`/api-reference/peripherals/ledc` for how to get the ETM task handle from LEDC.
.. _etm-channel-control:
ETM Channel Control
^^^^^^^^^^^^^^^^^^^
Connect Event and Task
~~~~~~~~~~~~~~~~~~~~~~
An ETM event has no association with an ETM task, until they are connected to the same ETM channel by calling :cpp:func:`esp_etm_channel_connect`. Especially, calling the function with a ``NULL`` task/event handle means disconnecting the channel from any task or event. Note that, this function can be called either before or after the channel is enabled. But calling this function at runtime to change the connection can be dangerous, because the channel may be in the middle of a cycle, and the new connection may not take effect immediately.
Enable and Disable Channel
~~~~~~~~~~~~~~~~~~~~~~~~~~
You can call :cpp:func:`esp_etm_channel_enable` and :cpp:func:`esp_etm_channel_disable` to enable and disable the ETM channel from working.
ETM Channel Profiling
~~~~~~~~~~~~~~~~~~~~~
To check if the ETM channels are set with proper events and tasks, you can call :cpp:func:`esp_etm_dump` to dump all working ETM channels with their associated events and tasks. The dumping format is like:
::
===========ETM Dump Start==========
channel 0: event 48 ==> task 17
channel 1: event 48 ==> task 90
channel 2: event 48 ==> task 94
===========ETM Dump End============
The digital ID printed in the dump information is defined in the ``soc/soc_etm_source.h`` file.
.. _etm-power-management:
Power Management
^^^^^^^^^^^^^^^^
When power management is enabled, i.e., :ref:`CONFIG_PM_ENABLE` is on, the system may adjust or disable the clock source, and power off the ETM peripheral before going to sleep. As a result, the existing connection between events and tasks will be lost, and the ETM channels can't work correctly after wake up. So by default, the driver will acquire a power management lock internally to forbid the system from powering off the ETM peripheral.
.. only:: SOC_ETM_SUPPORT_SLEEP_RETENTION
If you want to save more power, you can set :cpp:member:`esp_etm_channel_config_t::etm_chan_flags::allow_pd` to ``true``. Then ETM registers will be backed up before sleep and restored after wake up. Please note, enabling this option will increase the memory consumption for saving the register context.
.. _etm-thread-safety:
Thread Safety
^^^^^^^^^^^^^
The ETM core driver is thread-safe.
- Factory functions (for example, :cpp:func:`esp_etm_new_channel`, :cpp:func:`gpio_new_etm_task`, and other ``*_new_etm_*`` creators) are thread-safe and can be called from different RTOS tasks concurrently.
- Channel control APIs (for example, :cpp:func:`esp_etm_channel_connect`, :cpp:func:`esp_etm_channel_enable`, :cpp:func:`esp_etm_channel_disable`, :cpp:func:`esp_etm_del_channel`) are also thread-safe. Concurrent operations on different channels are safe. Concurrent operations on the same channel are serialized internally; if an operation is incompatible with the current channel state, it will return :c:macro:`ESP_ERR_INVALID_STATE`.
- No functions are allowed to run within the ISR environment.
.. _etm-kconfig-options:
Kconfig Options
^^^^^^^^^^^^^^^
- :ref:`CONFIG_ETM_ENABLE_DEBUG_LOG` is used to enable the debug log output. Enabling this option increases the firmware binary size as well.
API Reference
-------------
.. include-build-file:: inc/esp_etm.inc
.. include-build-file:: inc/gpio_etm.inc
.. include-build-file:: inc/esp_systick_etm.inc
.. [1]
Different ESP chip series might have different numbers of ETM channels. For more details, please refer to *{IDF_TARGET_NAME} Technical Reference Manual* > Chapter **Event Task Matrix (ETM)** [`PDF <{IDF_TARGET_TRM_EN_URL}#evntaskmatrix>`__]. The driver does not forbid you from applying for more channels, but it will return an error when all available hardware resources are used up. Please always check the return value when doing channel allocation (i.e., :cpp:func:`esp_etm_new_channel`).
+169
View File
@@ -0,0 +1,169 @@
GPIO & RTC GPIO
===============
:link_to_translation:`zh_CN:[中文]`
GPIO Summary
------------
.. include:: gpio/{IDF_TARGET_PATH_NAME}.inc
:start-after: gpio-summary
:end-before: ---
.. only:: SOC_RTCIO_INPUT_OUTPUT_SUPPORTED
.. only:: not SOC_LP_PERIPHERALS_SUPPORTED
There is also separate "RTC GPIO" support, which functions when GPIOs are routed to the "RTC" low-power and analog subsystem. These pin functions can be used when:
.. only:: SOC_LP_PERIPHERALS_SUPPORTED
There is also separate "RTC GPIO" support, which functions when GPIOs are routed to the "RTC" low-power, analog subsystem, and Low-Power(LP) peripherals. These pin functions can be used when:
.. list::
- In Deep-sleep mode
:SOC_ULP_FSM_SUPPORTED: - The :doc:`Ultra Low Power FSM co-processor <../../api-reference/system/ulp>` is running
:SOC_RISCV_COPROC_SUPPORTED: - The :doc:`Ultra Low Power RISC-V co-processor <../../api-reference/system/ulp-risc-v>` is running
:SOC_LP_CORE_SUPPORTED: - The :doc:`Ultra Low Power LP-Core co-processor <../../api-reference/system/ulp-lp-core>` is running
- Analog functions such as ADC/DAC/etc are in use
:SOC_LP_PERIPHERALS_SUPPORTED: - LP peripherals, such as LP_UART, LP_I2C, are in use
IO Configuration
----------------
An IO can be used in two ways:
- As a simple GPIO input to read the level on the pin, or as a simple GPIO output to output the desired level on the pin.
- As a peripheral signal input/output.
IDF peripheral drivers always take care of the necessary IO configurations that need to be applied onto the pins, so that they can be used as the peripheral signal inputs or outputs. This means the users usually only need to be responsible for configuring the IOs as simple inputs or outputs. :cpp:func:`gpio_config` is an all-in-one API that can be used to configure the I/O mode, internal pull-up/pull-down resistors, etc. for pins, including the ones reused by the USB PHY.
In some applications, an IO pin can serve dual purposes. For example, the IO, which outputs a LEDC PWM signal, can also act as a GPIO input to generate interrupts or GPIO ETM events. Careful handling on the configuration step is necessary for such dual use of IO pins cases. :cpp:func:`gpio_config` is an API that overwrites all the current configurations, so it must be called to set the pin mode to :cpp:enumerator:`gpio_mode_t::GPIO_MODE_INPUT` before calling the LEDC driver API which connects the output signal to the pin. As an alternative, if no other configuration is needed other than making the pin input enabled, :cpp:func:`gpio_input_enable` can be the one to call at any time to achieve the same purpose.
Check Current Configuration of IOs
----------------------------------
GPIO driver offers a dump function :cpp:func:`gpio_dump_io_configuration` to show the current configurations of IOs, such as pull-up/pull-down, input/output enable, pin mapping, etc. Below is an example of how to dump the configuration of GPIO4, GPIO18, and GPIO26:
::
gpio_dump_io_configuration(stdout, (1ULL << 4) | (1ULL << 18) | (1ULL << 26));
The dump will be like this:
::
================IO DUMP Start================
IO[4] -
Pullup: 1, Pulldown: 0, DriveCap: 2
InputEn: 1, OutputEn: 0, OpenDrain: 0
FuncSel: 1 (GPIO)
GPIO Matrix SigIn ID: (simple GPIO input)
SleepSelEn: 1
IO[18] -
Pullup: 0, Pulldown: 0, DriveCap: 2
InputEn: 0, OutputEn: 1, OpenDrain: 0
FuncSel: 1 (GPIO)
GPIO Matrix SigOut ID: 256 (simple GPIO output)
SleepSelEn: 1
IO[26] **RESERVED** -
Pullup: 1, Pulldown: 0, DriveCap: 2
InputEn: 1, OutputEn: 0, OpenDrain: 0
FuncSel: 0 (IOMUX)
SleepSelEn: 1
=================IO DUMP End==================
In addition, if you would like to dump the configurations of all IOs, you can use:
::
gpio_dump_io_configuration(stdout, SOC_GPIO_VALID_GPIO_MASK);
If an IO pin is routed to a peripheral signal through the GPIO matrix, the signal ID printed in the dump information is defined in the :component_file:`soc/{IDF_TARGET_PATH_NAME}/include/soc/gpio_sig_map.h` header file. The word ``**RESERVED**`` indicates the IO is occupied by either SPI flash or PSRAM. It is strongly not recommended to reconfigure them for other application purposes.
Do not rely on the default configurations values in the Technical Reference Manual, because it may be changed in the bootloader or application startup code before app_main.
.. only:: SOC_GPIO_SUPPORT_PIN_GLITCH_FILTER or SOC_GPIO_FLEX_GLITCH_FILTER_NUM
GPIO Glitch Filter
------------------
The {IDF_TARGET_NAME} chip features hardware filters to remove unwanted glitch pulses from the input GPIO, which can help reduce false triggering of the interrupt and prevent a noise being routed to the peripheral side.
.. only:: SOC_GPIO_SUPPORT_PIN_GLITCH_FILTER
Each GPIO can be configured with a glitch filter, which can be used to filter out pulses shorter than **two** sample clock cycles. The duration of the filter is not configurable. The sample clock is the clock source of the IO_MUX. In the driver, we call this kind of filter as ``pin glitch filter``. You can create the filter handle by calling :cpp:func:`gpio_new_pin_glitch_filter`. All the configurations for a pin glitch filter are listed in the :cpp:type:`gpio_pin_glitch_filter_config_t` structure.
- :cpp:member:`gpio_pin_glitch_filter_config_t::gpio_num` sets the GPIO number to enable the glitch filter.
.. only:: SOC_GPIO_FLEX_GLITCH_FILTER_NUM
{IDF_TARGET_FLEX_GLITCH_FILTER_NUM:default="8"}
{IDF_TARGET_NAME} provides {IDF_TARGET_FLEX_GLITCH_FILTER_NUM} flexible glitch filters, whose duration is configurable. We refer to this kind of filter as ``flex flitch filter``. Each of them can be applied to any input GPIO. However, applying multiple filters to the same GPIO does not make difference from one. You can create the filter handle by calling :cpp:func:`gpio_new_flex_glitch_filter`. All the configurations for a flexible glitch filter are listed in the :cpp:type:`gpio_flex_glitch_filter_config_t` structure.
- :cpp:member:`gpio_flex_glitch_filter_config_t::gpio_num` sets the GPIO that will be applied to the flex glitch filter.
- :cpp:member:`gpio_flex_glitch_filter_config_t::window_width_ns` and :cpp:member:`gpio_flex_glitch_filter_config_t::window_thres_ns` are the key parameters of the glitch filter. During :cpp:member:`gpio_flex_glitch_filter_config_t::window_width_ns`, any pulse whose width is shorter than :cpp:member:`gpio_flex_glitch_filter_config_t::window_thres_ns` will be discarded. Please note that, you can not set :cpp:member:`gpio_flex_glitch_filter_config_t::window_thres_ns` bigger than :cpp:member:`gpio_flex_glitch_filter_config_t::window_width_ns`.
.. only:: SOC_GPIO_SUPPORT_PIN_GLITCH_FILTER and SOC_GPIO_FLEX_GLITCH_FILTER_NUM
Please note, the ``pin glitch filter`` and ``flex glitch filter`` are independent. You can enable both of them for the same GPIO.
The glitch filter is disabled by default, and can be enabled by calling :cpp:func:`gpio_glitch_filter_enable`. To recycle the filter, you can call :cpp:func:`gpio_del_glitch_filter`. Please note, before deleting the filter, you should disable it first by calling :cpp:func:`gpio_glitch_filter_disable`.
.. only:: SOC_GPIO_SUPPORT_PIN_HYS_FILTER
GPIO Hysteresis Filter
----------------------
{IDF_TARGET_NAME} support the hardware hysteresis of the input pin, which can reduce the GPIO interrupt shoot by accident due to unstable sampling when the input voltage is near the criteria of logic 0 and 1, especially when the input logic level conversion is slow or the voltage setup time is too long.
.. only:: SOC_GPIO_SUPPORT_PIN_HYS_CTRL_BY_EFUSE
Each pin can enable hysteresis function independently. By default, it controlled by eFuse and been closed, but it can also be enabled or disabled by software manually. You can select the hysteresis control mode by configuring :cpp:member:`gpio_config_t::hys_ctrl_mode`. Hysteresis control mode is set along with all the other GPIO configurations in :cpp:func:`gpio_config`.
.. note::
When the hysteresis function is controlled by eFuse, this feature can still be controlled independently for each pin, you need to `burn the eFuse <https://docs.espressif.com/projects/esptool/en/latest/esp32/espefuse/index.html>`_ to enable the hysteresis function on specific GPIO additionally.
.. only:: not SOC_GPIO_SUPPORT_PIN_HYS_CTRL_BY_EFUSE
Each pin can enable hysteresis function independently. By default, the function is not enabled. You can select the hysteresis control mode by configuring :cpp:member:`gpio_config_t::hys_ctrl_mode`. Hysteresis control mode is set along with all the other GPIO configurations in :cpp:func:`gpio_config`.
Application Example
-------------------
.. list::
* :example:`peripherals/gpio/generic_gpio` demonstrates how to configure GPIO to generate pulses and use it with interruption.
:esp32s2: * :example:`peripherals/gpio/matrix_keyboard` demonstrates how to drive a common matrix keyboard using the dedicated GPIO APIs, including manipulating the level on a group of GPIOs, triggering edge interrupt, and reading level on a group of GPIOs.
API Reference - Normal GPIO
---------------------------
.. include-build-file:: inc/gpio.inc
.. include-build-file:: inc/gpio_types.inc
.. only:: SOC_RTCIO_INPUT_OUTPUT_SUPPORTED
API Reference - RTC GPIO
------------------------
.. include-build-file:: inc/rtc_io.inc
.. include-build-file:: inc/lp_io.inc
.. include-build-file:: inc/rtc_io_types.inc
.. only:: SOC_GPIO_SUPPORT_PIN_GLITCH_FILTER or SOC_GPIO_FLEX_GLITCH_FILTER_NUM
API Reference - GPIO Glitch Filter
----------------------------------
.. include-build-file:: inc/gpio_filter.inc
@@ -0,0 +1,205 @@
.. This file gets included from other .rst files in this folder.
.. It contains target-specific snippets.
.. Comments and '---' lines act as delimiters.
..
.. This is necessary mainly because RST doesn't support substitutions
.. (defined in RST, not in Python) inside code blocks. If that is ever implemented,
.. These code blocks can be moved back to the main .rst files, with target-specific
.. file names being replaced by substitutions.
.. gpio-summary
The {IDF_TARGET_NAME} chip features 34 physical GPIO pins (GPIO0 ~ GPIO19, GPIO21 ~ GPIO23, GPIO25 ~ GPIO27, and GPIO32 ~ GPIO39). Each pin can be used as a general-purpose I/O, or be connected to an internal peripheral signal. Through IO MUX, RTC IO MUX and the GPIO matrix, peripheral input signals can be from any IO pins, and peripheral output signals can be routed to any IO pins. Together these modules provide highly configurable I/O. For more details, see *{IDF_TARGET_NAME} Technical Reference Manual* > *IO MUX and GPIO Matrix (GPIO, IO_MUX)* [`PDF <{IDF_TARGET_TRM_EN_URL}#iomuxgpio>`__].
The table below provides more information on pin usage, and please note the comments in the table for GPIOs with restrictions.
.. list-table::
:header-rows: 1
:widths: 8 12 12 20
* - GPIO
- Analog Function
- RTC GPIO
- Comments
* - GPIO0
- ADC2_CH1
- RTC_GPIO11
- Strapping pin
* - GPIO1
-
-
- TXD
* - GPIO2
- ADC2_CH2
- RTC_GPIO12
- Strapping pin
* - GPIO3
-
-
- RXD
* - GPIO4
- ADC2_CH0
- RTC_GPIO10
-
* - GPIO5
-
-
- Strapping pin
* - GPIO6
-
-
- SPI0/1
* - GPIO7
-
-
- SPI0/1
* - GPIO8
-
-
- SPI0/1
* - GPIO9
-
-
- SPI0/1
* - GPIO10
-
-
- SPI0/1
* - GPIO11
-
-
- SPI0/1
* - GPIO12
- ADC2_CH5
- RTC_GPIO15
- Strapping pin; JTAG
* - GPIO13
- ADC2_CH4
- RTC_GPIO14
- JTAG
* - GPIO14
- ADC2_CH6
- RTC_GPIO16
- JTAG
* - GPIO15
- ADC2_CH3
- RTC_GPIO13
- Strapping pin; JTAG
* - GPIO16
-
-
- SPI0/1
* - GPIO17
-
-
- SPI0/1
* - GPIO18
-
-
-
* - GPIO19
-
-
-
* - GPIO21
-
-
-
* - GPIO22
-
-
-
* - GPIO23
-
-
-
* - GPIO25
- ADC2_CH8
- RTC_GPIO6
-
* - GPIO26
- ADC2_CH9
- RTC_GPIO7
-
* - GPIO27
- ADC2_CH7
- RTC_GPIO17
-
* - GPIO32
- ADC1_CH4
- RTC_GPIO9
-
* - GPIO33
- ADC1_CH5
- RTC_GPIO8
-
* - GPIO34
- ADC1_CH6
- RTC_GPIO4
- GPI
* - GPIO35
- ADC1_CH7
- RTC_GPIO5
- GPI
* - GPIO36
- ADC1_CH0
- RTC_GPIO0
- GPI
* - GPIO37
- ADC1_CH1
- RTC_GPIO1
- GPI
* - GPIO38
- ADC1_CH2
- RTC_GPIO2
- GPI
* - GPIO39
- ADC1_CH3
- RTC_GPIO3
- GPI
.. note::
- Strapping pin: GPIO0, GPIO2, GPIO5, GPIO12 (MTDI), and GPIO15 (MTDO) are strapping pins. For more information, please refer to `ESP32 datasheet <{IDF_TARGET_DATASHEET_EN_URL}>`_.
- SPI0/1: GPIO6-11 and GPIO16-17 are usually connected to the SPI flash and PSRAM integrated on the module and therefore should not be used for other purposes.
- JTAG: GPIO12-15 are usually used for inline debug.
- GPI: GPIO34-39 can only be set as input mode and do not have software-enabled pullup or pulldown functions.
- TXD & RXD are usually used for flashing and debugging.
- ADC2: ADC2 pins cannot be used when Wi-Fi is used. So, if you are having trouble getting the value from an ADC2 GPIO while using Wi-Fi, you may consider using an ADC1 GPIO instead, which should solve your problem. For more details, please refer to :ref:`Hardware Limitations of ADC Continuous Mode <hardware_limitations_adc_continuous>` and :ref:`Hardware Limitations of ADC Oneshot Mode <hardware_limitations_adc_oneshot>`.
- Please do not use the interrupt of GPIO36 and GPIO39 when using ADC or Wi-Fi and Bluetooth with sleep mode enabled. Please refer to `ESP32 ECO and Workarounds for Bugs <https://espressif.com/sites/default/files/documentation/eco_and_workarounds_for_bugs_in_esp32_en.pdf>`_ > GPIO-3.11 for the detailed description of the issue.
---
@@ -0,0 +1,116 @@
.. This file gets included from other .rst files in this folder.
.. It contains target-specific snippets.
.. Comments and '---' lines act as delimiters.
..
.. This is necessary mainly because RST doesn't support substitutions
.. (defined in RST, not in Python) inside code blocks. If that is ever implemented,
.. These code blocks can be moved back to the main .rst files, with target-specific
.. file names being replaced by substitutions.
.. gpio-summary
The {IDF_TARGET_NAME} chip features 21 physical GPIO pins (GPIO0 ~ GPIO20). For chip variants with an SiP flash built in, GPIO11 ~ GPIO17 are dedicated to connecting the SiP flash; therefore, only 14 GPIO pins are available.
Each pin can be used as a general-purpose I/O, or to be connected to an internal peripheral signal. Through GPIO matrix and IO MUX, peripheral input signals can be from any IO pins, and peripheral output signals can be routed to any IO pins. Together these modules provide highly configurable I/O. For more details, see *{IDF_TARGET_NAME} Technical Reference Manual* > *IO MUX and GPIO Matrix (GPIO, IO_MUX)* [`PDF <{IDF_TARGET_TRM_EN_URL}#iomuxgpio>`__].
The table below provides more information on pin usage, and please note the comments in the table for GPIOs with restrictions.
.. list-table::
:header-rows: 1
:widths: 12 12 22
* - GPIO
- Analog Function
- Comments
* - GPIO0
- ADC1_CH0
- RTC
* - GPIO1
- ADC1_CH1
- RTC
* - GPIO2
- ADC1_CH2
- RTC
* - GPIO3
- ADC1_CH3
- RTC
* - GPIO4
- ADC1_CH4
- RTC
* - GPIO5
-
- RTC
* - GPIO6
-
-
* - GPIO7
-
-
* - GPIO8
-
- Strapping pin
* - GPIO9
-
- Strapping pin
* - GPIO10
-
-
* - GPIO11
-
-
* - GPIO12
-
- SPI0/1
* - GPIO13
-
- SPI0/1
* - GPIO14
-
- SPI0/1
* - GPIO15
-
- SPI0/1
* - GPIO16
-
- SPI0/1
* - GPIO17
-
- SPI0/1
* - GPIO18
-
-
* - GPIO19
-
-
* - GPIO20
-
-
.. note::
- Strapping pin: GPIO8 and GPIO9 are strapping pins. For more information, please refer to `ESP8684 datasheet <https://www.espressif.com/sites/default/files/documentation/esp8684_datasheet_en.pdf>`_.
- SPI0/1: GPIO12-17 are usually used for SPI flash and not recommended for other uses.
- RTC: GPIO0-5 can be used to wake up the chip from Deep-sleep mode. Other GPIOs can only wake up the chip from Light-sleep mode. For more information, please refer to Section :ref:`Wakeup Sources<api-reference-wakeup-source>`.
---
@@ -0,0 +1,119 @@
.. This file gets included from other .rst files in this folder.
.. It contains target-specific snippets.
.. Comments and '---' lines act as delimiters.
..
.. This is necessary mainly because RST doesn't support substitutions
.. (defined in RST, not in Python) inside code blocks. If that is ever implemented,
.. These code blocks can be moved back to the main .rst files, with target-specific
.. file names being replaced by substitutions.
.. gpio-summary
The {IDF_TARGET_NAME} chip features 22 physical GPIO pins (GPIO0 ~ GPIO21). Each pin can be used as a general-purpose I/O, or be connected to an internal peripheral signal. Through GPIO matrix and IO MUX, peripheral input signals can be from any IO pins, and peripheral output signals can be routed to any IO pins. Together these modules provide highly configurable I/O. For more details, see *{IDF_TARGET_NAME} Technical Reference Manual* > *IO MUX and GPIO Matrix (GPIO, IO_MUX)* [`PDF <{IDF_TARGET_TRM_EN_URL}#iomuxgpio>`__].
The table below provides more information on pin usage, and please note the comments in the table for GPIOs with restrictions.
.. list-table::
:header-rows: 1
:widths: 12 12 22
* - GPIO
- Analog Function
- Comment
* - GPIO0
- ADC1_CH0
- RTC
* - GPIO1
- ADC1_CH1
- RTC
* - GPIO2
- ADC1_CH2
- Strapping pinRTC
* - GPIO3
- ADC1_CH3
- RTC
* - GPIO4
- ADC1_CH4
- RTC
* - GPIO5
- ADC2_CH0
- RTC
* - GPIO6
-
-
* - GPIO7
-
-
* - GPIO8
-
- Strapping pin
* - GPIO9
-
- Strapping pin
* - GPIO10
-
-
* - GPIO11
-
-
* - GPIO12
-
- SPI0/1
* - GPIO13
-
- SPI0/1
* - GPIO14
-
- SPI0/1
* - GPIO15
-
- SPI0/1
* - GPIO16
-
- SPI0/1
* - GPIO17
-
- SPI0/1
* - GPIO18
-
- USB-JTAG
* - GPIO19
-
- USB-JTAG
* - GPIO20
-
-
* - GPIO21
-
-
.. note::
- Strapping pin: GPIO2, GPIO8 and GPIO9 are strapping pins. For more information, please refer to `ESP32-C3 Datasheet <{IDF_TARGET_DATASHEET_EN_URL}>`_.
- SPI0/1: GPIO12 ~ GPIO17 are usually used for SPI flash and are not recommended for other uses.
- USB-JTAG: GPIO18 and GPIO19 are used by USB-JTAG by default. If they are reconfigured to operate as normal GPIOs, USB-JTAG functionality will be disabled.
- RTC: GPIO0 ~ GPIO5 can be used to wake up the chip from Deep-sleep mode. Other GPIOs can only wake up the chip from Light-sleep mode. For more information, please refer to Section :ref:`Wakeup Sources<api-reference-wakeup-source>`.
---
@@ -0,0 +1,176 @@
.. This file gets included from other .rst files in this folder.
.. It contains target-specific snippets.
.. Comments and '---' lines act as delimiters.
..
.. This is necessary mainly because RST doesn't support substitutions
.. (defined in RST, not in Python) inside code blocks. If that is ever implemented,
.. These code blocks can be moved back to the main .rst files, with target-specific
.. file names being replaced by substitutions.
.. gpio-summary
The {IDF_TARGET_NAME} chip features 29 physical GPIO pins (GPIO0 ~ GPIO28). Each pin can be used as a general-purpose I/O, or to be connected to an internal peripheral signal. Through GPIO matrix and IO MUX, peripheral input signals can be from any IO pins, and peripheral output signals can be routed to any IO pins. Together these modules provide highly configurable I/O. For more details, see *{IDF_TARGET_NAME} Technical Reference Manual* > *IO MUX and GPIO Matrix (GPIO, IO_MUX)* [`PDF <{IDF_TARGET_TRM_EN_URL}#iomuxgpio>`__].
The table below provides more information on pin usage, and please note the comments in the table for GPIOs with restrictions.
.. list-table::
:header-rows: 1
:widths: 8 12 12 20
* - GPIO
- Analog Function
- LP GPIO
- Comments
* - GPIO0
-
- LP_GPIO0
-
* - GPIO1
- ADC1_CH0
- LP_GPIO1
-
* - GPIO2
- ADC1_CH1
- LP_GPIO2
- Strapping pin
* - GPIO3
- ADC1_CH2
- LP_GPIO3
-
* - GPIO4
- ADC1_CH3
- LP_GPIO4
-
* - GPIO5
- ADC1_CH4
- LP_GPIO5
-
* - GPIO6
- ADC1_CH5
- LP_GPIO6
-
* - GPIO7
-
-
- Strapping pin
* - GPIO8
-
-
-
* - GPIO9
-
-
-
* - GPIO10
-
-
-
* - GPIO11
-
-
-
* - GPIO12
-
-
-
* - GPIO13
-
-
- USB-JTAG
* - GPIO14
-
-
- USB-JTAG
* - GPIO15
-
-
-
* - GPIO16
-
-
- SPI0/1
* - GPIO17
-
-
- SPI0/1
* - GPIO18
-
-
- SPI0/1
* - GPIO19
-
-
- SPI0/1
* - GPIO20
-
-
- SPI0/1
* - GPIO21
-
-
- SPI0/1
* - GPIO22
-
-
- SPI0/1
* - GPIO23
-
-
-
* - GPIO24
-
-
-
* - GPIO25
-
-
- Strapping pin
* - GPIO26
-
-
-
* - GPIO27
-
-
- Strapping pin
* - GPIO28
-
-
- Strapping pin
.. note::
- Strapping pin: GPIO2, GPIO7, GPIO25, GPIO27, and GPIO28 are strapping pins. For more information, please refer to `datasheet <{IDF_TARGET_DATASHEET_EN_URL}>`__.
- SPI0/1: GPIO16 ~ GPIO22 are usually used for SPI flash and PSRAM, they're not recommended for other uses.
- USB-JTAG: GPIO13 and GPIO14 are used by USB-JTAG by default. If they are reconfigured to operate as normal GPIOs, USB-JTAG functionality will be disabled.
---
@@ -0,0 +1,188 @@
.. This file gets included from other .rst files in this folder.
.. It contains target-specific snippets.
.. Comments and '---' lines act as delimiters.
..
.. This is necessary mainly because RST doesn't support substitutions
.. (defined in RST, not in Python) inside code blocks. If that is ever implemented,
.. These code blocks can be moved back to the main .rst files, with target-specific
.. file names being replaced by substitutions.
.. gpio-summary
The {IDF_TARGET_NAME} chip features 31 physical GPIO pins (GPIO0 ~ GPIO30). Each pin can be used as a general-purpose I/O, or to be connected to an internal peripheral signal. Through GPIO matrix and IO MUX, peripheral input signals can be from any IO pins, and peripheral output signals can be routed to any IO pins. Together these modules provide highly configurable I/O. For more details, see *{IDF_TARGET_NAME} Technical Reference Manual* > *IO MUX and GPIO Matrix (GPIO, IO_MUX)* [`PDF <{IDF_TARGET_TRM_EN_URL}#iomuxgpio>`__].
The table below provides more information on pin usage, and please note the comments in the table for GPIOs with restrictions.
.. list-table::
:header-rows: 1
:widths: 8 12 12 20
* - GPIO
- Analog Function
- LP GPIO
- Comments
* - GPIO0
- ADC1_CH0
- LP_GPIO0
-
* - GPIO1
- ADC1_CH1
- LP_GPIO1
-
* - GPIO2
- ADC1_CH2
- LP_GPIO2
-
* - GPIO3
- ADC1_CH3
- LP_GPIO3
-
* - GPIO4
- ADC1_CH4
- LP_GPIO4
- Strapping pin
* - GPIO5
- ADC1_CH5
- LP_GPIO5
- Strapping pin
* - GPIO6
- ADC1_CH6
- LP_GPIO6
-
* - GPIO7
-
- LP_GPIO7
-
* - GPIO8
-
-
- Strapping pin
* - GPIO9
-
-
- Strapping pin
* - GPIO10
-
-
-
* - GPIO11
-
-
-
* - GPIO12
-
-
- USB-JTAG
* - GPIO13
-
-
- USB-JTAG
* - GPIO14
-
-
-
* - GPIO15
-
-
- Strapping pin
* - GPIO16
-
-
-
* - GPIO17
-
-
-
* - GPIO18
-
-
-
* - GPIO19
-
-
-
* - GPIO20
-
-
-
* - GPIO21
-
-
-
* - GPIO22
-
-
-
* - GPIO23
-
-
-
* - GPIO24
-
-
- SPI0/1
* - GPIO25
-
-
- SPI0/1
* - GPIO26
-
-
- SPI0/1
* - GPIO27
-
-
- SPI0/1
* - GPIO28
-
-
- SPI0/1
* - GPIO29
-
-
- SPI0/1
* - GPIO30
-
-
- SPI0/1
.. note::
- Strapping pin: GPIO4, GPIO5, GPIO8, GPIO9, and GPIO15 are strapping pins. For more information, please refer to `datasheet <{IDF_TARGET_DATASHEET_EN_URL}>`__.
- SPI0/1: GPIO24 ~ GPIO30 are usually used for SPI flash and not recommended for other uses.
- USB-JTAG: GPIO12 and GPIO13 are used by USB-JTAG by default. If they are reconfigured to operate as normal GPIOs, USB-JTAG functionality will be disabled.
- For chip variants with an SiP flash built in, GPIO24 ~ GPIO30 are dedicated to connecting the SiP flash; GPIO10 ~ GPIO11 are not led out to any chip pins; therefore, only the remaining 22 GPIO pins are available.
- For chip variants without an in-package flash, GPIO14 is not led out to any chip pins.
---
@@ -0,0 +1,181 @@
.. This file gets included from other .rst files in this folder.
.. It contains target-specific snippets.
.. Comments and '---' lines act as delimiters.
..
.. This is necessary mainly because RST doesn't support substitutions
.. (defined in RST, not in Python) inside code blocks. If that is ever implemented,
.. These code blocks can be moved back to the main .rst files, with target-specific
.. file names being replaced by substitutions.
.. gpio-summary
The {IDF_TARGET_NAME} chip features 30 physical GPIO pins (GPIO0 ~ GPIO29). Each pin can be used as a general-purpose I/O, or to be connected to an internal peripheral signal. Through GPIO matrix and IO MUX, peripheral input signals can be from any IO pins, and peripheral output signals can be routed to any IO pins. Together these modules provide highly configurable I/O. For more details, see *{IDF_TARGET_NAME} Technical Reference Manual* > *IO MUX and GPIO Matrix (GPIO, IO_MUX)* [`PDF <{IDF_TARGET_TRM_EN_URL}#iomuxgpio>`__].
The table below provides more information on pin usage, and please note the comments in the table for GPIOs with restrictions.
.. list-table::
:header-rows: 1
:widths: 8 12 12 20
* - GPIO
- Analog Function
- LP GPIO
- Comments
* - GPIO0
-
- LP_GPIO0
-
* - GPIO1
- ADC1_CH0
- LP_GPIO1
-
* - GPIO2
-
- LP_GPIO2
-
* - GPIO3
- ADC1_CH1
- LP_GPIO3
-
* - GPIO4
- ADC1_CH2
- LP_GPIO4
-
* - GPIO5
- ADC1_CH3
- LP_GPIO5
-
* - GPIO6
-
- LP_GPIO6
-
* - GPIO7
-
-
-
* - GPIO8
-
-
-
* - GPIO9
-
-
-
* - GPIO10
-
-
-
* - GPIO11
-
-
-
* - GPIO12
-
-
- USB-JTAG
* - GPIO13
-
-
- USB-JTAG
* - GPIO14
-
-
- SPI0/1
* - GPIO15
-
-
- SPI0/1
* - GPIO16
-
-
- SPI0/1
* - GPIO17
-
-
- SPI0/1
* - GPIO18
-
-
-
* - GPIO19
-
-
- SPI0/1
* - GPIO20
-
-
- SPI0/1
* - GPIO21
-
-
- SPI0/1
* - GPIO22
-
-
-
* - GPIO23
-
-
-
* - GPIO24
-
-
-
* - GPIO25
-
-
-
* - GPIO26
-
-
-
* - GPIO27
-
-
-
* - GPIO28
-
-
-
* - GPIO29
-
-
-
.. note::
- Some pins are used as strapping pins, which can be used to select in which boot mode to load the chip, etc.. The details can be found in `datasheet <{IDF_TARGET_DATASHEET_EN_URL}>`_ > ``Strapping Pins``.
- SPI0/1: GPIO14 ~ GPIO17 and GPIO19 ~ GPIO21 are usually used for SPI flash and PSRAM, they're not recommended for other uses.
- USB-JTAG: GPIO12 and GPIO13 are used by USB-JTAG by default. If they are reconfigured to operate as normal GPIOs, USB-JTAG functionality will be disabled.
---
@@ -0,0 +1,144 @@
.. This file gets included from other .rst files in this folder.
.. It contains target-specific snippets.
.. Comments and '---' lines act as delimiters.
..
.. This is necessary mainly because RST doesn't support substitutions
.. (defined in RST, not in Python) inside code blocks. If that is ever implemented,
.. These code blocks can be moved back to the main .rst files, with target-specific
.. file names being replaced by substitutions.
.. gpio-summary
The {IDF_TARGET_NAME} chip features 28 physical GPIO pins (GPIO0 ~ GPIO27). Each pin can be used as a general-purpose I/O, or to be connected to an internal peripheral signal. Through GPIO matrix and IO MUX, peripheral input signals can be from any IO pins, and peripheral output signals can be routed to any IO pins. Together these modules provide highly configurable I/O. For more details, see *{IDF_TARGET_NAME} Technical Reference Manual* > *IO MUX and GPIO Matrix (GPIO, IO_MUX)* [`PDF <{IDF_TARGET_TRM_EN_URL}#iomuxgpio>`__].
The table below provides more information on pin usage, and please note the comments in the table for GPIOs with restrictions.
.. list-table::
:header-rows: 1
:widths: 8 12 20
* - GPIO
- Analog Function
- Comments
* - GPIO0
-
-
* - GPIO1
- ADC1_CH0
-
* - GPIO2
- ADC1_CH1
- Strapping pin
* - GPIO3
- ADC1_CH2
- Strapping pin
* - GPIO4
- ADC1_CH3
-
* - GPIO5
- ADC1_CH4
-
* - GPIO6
-
-
* - GPIO7
-
- RTC
* - GPIO8
-
- Strapping pin, RTC
* - GPIO9
-
- Strapping pin, RTC
* - GPIO10
- ANA_CMPR_CH0 reference voltage
- RTC
* - GPIO11
- ANA_CMPR_CH0 input (non-inverting)
- RTC
* - GPIO12
-
- RTC
* - GPIO13
-
- RTC
* - GPIO14
-
- RTC
* - GPIO15
-
- SPI0/1
* - GPIO16
-
- SPI0/1
* - GPIO17
-
- SPI0/1
* - GPIO18
-
- SPI0/1
* - GPIO19
-
- SPI0/1
* - GPIO20
-
- SPI0/1
* - GPIO21
-
- SPI0/1
* - GPIO22
-
-
* - GPIO23
-
-
* - GPIO24
-
-
* - GPIO25
-
- Strapping pin
* - GPIO26
-
- USB-JTAG
* - GPIO27
-
- USB-JTAG
.. note::
- Strapping pin: GPIO2, GPIO3, GPIO8, GPIO9, and GPIO25 are strapping pins. For more information, please refer to `ESP32H2 datasheet <{IDF_TARGET_DATASHEET_EN_URL}>`_.
- SPI0/1: GPIO15-21 are usually used for SPI flash and not recommended for other uses.
- USB-Serial-JTAG: GPIO 26 and 27 are used by USB-Serial-JTAG by default. In order to use them as GPIOs, USB-Serial-JTAG will be disabled by the drivers.
- For chip variants with an SiP flash built in, GPIO15GPIO21 are dedicated to connecting the SiP flash and are not fan-out to the external pins. In addition, GPIO6GPIO7 are also not fan-out to the external pins. In conclusion, only GPIO0GPIO5, GPIO8GPIO14, GPIO22GPIO27 are available to users.
- RTC: GPIO7GPIO14 can be used to wake up the chip from Deep-sleep mode. Note that although GPIO7 is an RTC GPIO, it cannot be used for external wake-up since it is not led out. Other GPIOs can only wake up the chip from Light-sleep mode. For more information, please refer to Section :ref:`Wakeup Sources <api-reference-wakeup-source>`.
---
@@ -0,0 +1,161 @@
.. This file gets included from other .rst files in this folder.
.. It contains target-specific snippets.
.. Comments and '---' lines act as delimiters.
..
.. This is necessary mainly because RST doesn't support substitutions
.. (defined in RST, not in Python) inside code blocks. If that is ever implemented,
.. These code blocks can be moved back to the main .rst files, with target-specific
.. file names being replaced by substitutions.
.. gpio-summary
The {IDF_TARGET_NAME} chip features 26 physical GPIO pins (GPIO0 ~ GPIO25). Each pin can be used as a general-purpose I/O, or to be connected to an internal peripheral signal. Through GPIO matrix and IO MUX, peripheral input signals can be from any IO pins, and peripheral output signals can be routed to any IO pins. Together these modules provide highly configurable I/O. For more details, see *{IDF_TARGET_NAME} Technical Reference Manual* > *IO MUX and GPIO Matrix (GPIO, IO_MUX)* [`PDF <{IDF_TARGET_TRM_EN_URL}#iomuxgpio>`__].
The table below provides more information on pin usage, and please note the comments in the table for GPIOs with restrictions.
.. list-table::
:header-rows: 1
:widths: 8 12 12 20
* - GPIO
- Analog Function
- LP GPIO
- Comments
* - GPIO0
-
-
-
* - GPIO1
- ADC1_CH0
-
-
* - GPIO2
- ADC1_CH1
-
-
* - GPIO3
- ADC1_CH2
-
-
* - GPIO4
- ADC1_CH3
-
-
* - GPIO5
- ADC1_CH4
- LP_GPIO0
-
* - GPIO6
-
- LP_GPIO1
-
* - GPIO7
-
- LP_GPIO2
-
* - GPIO8
-
- LP_GPIO3
- Strapping pin
* - GPIO9
-
- LP_GPIO4
-
* - GPIO10
-
- LP_GPIO5
-
* - GPIO11
-
- LP_GPIO6
-
* - GPIO12
-
-
-
* - GPIO13
-
-
- Strapping pin
* - GPIO14
-
-
- Strapping pin
* - GPIO15
-
-
-
* - GPIO16
-
-
-
* - GPIO17
-
-
- USB-JTAG
* - GPIO18
-
-
- USB-JTAG
* - GPIO19
-
-
- SPI0/1
* - GPIO20
-
-
- SPI0/1
* - GPIO21
-
-
- SPI0/1
* - GPIO22
-
-
- SPI0/1
* - GPIO23
-
-
- SPI0/1
* - GPIO24
-
-
- SPI0/1
* - GPIO25
-
-
- SPI0/1
.. note::
- Strapping pin: GPIO8, GPIO13, and GPIO14 are strapping pins. For more information, please refer to `datasheet <{IDF_TARGET_DATASHEET_EN_URL}>`__.
- SPI0/1: GPIO19 ~ GPIO25 are usually used for SPI flash and not recommended for other uses.
- USB-JTAG: GPIO17 and GPIO18 are used by USB-JTAG by default. If they are reconfigured to operate as normal GPIOs, USB-JTAG functionality will be disabled.
---
@@ -0,0 +1,231 @@
.. This file gets included from other .rst files in this folder.
.. It contains target-specific snippets.
.. Comments and '---' lines act as delimiters.
..
.. This is necessary mainly because RST doesn't support substitutions
.. (defined in RST, not in Python) inside code blocks. If that is ever implemented,
.. These code blocks can be moved back to the main .rst files, with target-specific
.. file names being replaced by substitutions.
.. gpio-summary
The {IDF_TARGET_NAME} chip features 40 physical GPIO pins (GPIO0 ~ GPIO39). Each pin can be used as a general-purpose I/O, or to be connected to an internal peripheral signal. Through GPIO matrix and IO MUX, peripheral input signals can be from any IO pins, and peripheral output signals can be routed to any IO pins. Together these modules provide highly configurable I/O. For more details, see *{IDF_TARGET_NAME} Technical Reference Manual* > *IO MUX and GPIO Matrix (GPIO, IO_MUX)* [`PDF <{IDF_TARGET_TRM_EN_URL}#iomuxgpio>`__].
The table below provides more information on pin usage, and please note the comments in the table for GPIOs with restrictions.
.. list-table::
:header-rows: 1
:widths: 8 12 12 20
* - GPIO
- Analog Function
- LP GPIO
- Comments
* - GPIO0
-
- LP_GPIO0
-
* - GPIO1
-
- LP_GPIO1
-
* - GPIO2
-
- LP_GPIO2
-
* - GPIO3
-
- LP_GPIO3
-
* - GPIO4
-
- LP_GPIO4
-
* - GPIO5
-
- LP_GPIO5
-
* - GPIO6
-
-
- SPI0/1
* - GPIO7
-
-
- SPI0/1
* - GPIO8
-
-
- SPI0/1
* - GPIO9
-
-
- SPI0/1
* - GPIO10
-
-
- SPI0/1
* - GPIO11
-
-
- SPI0/1
* - GPIO12
-
-
- SPI0/1
* - GPIO13
-
-
- USB-JTAG
* - GPIO14
-
-
- USB-JTAG
* - GPIO15
-
-
-
* - GPIO16
-
-
-
* - GPIO17
-
-
- Strapping pin
* - GPIO18
-
-
-
* - GPIO19
-
-
-
* - GPIO20
-
-
-
* - GPIO21
-
-
-
* - GPIO22
-
-
-
* - GPIO23
-
-
-
* - GPIO24
-
-
-
* - GPIO25
-
-
-
* - GPIO26
-
-
-
* - GPIO27
-
-
-
* - GPIO28
- ADC1_CH0
-
-
* - GPIO29
- ADC1_CH1
-
-
* - GPIO30
- ADC1_CH2
-
-
* - GPIO31
- ADC1_CH3
-
-
* - GPIO32
- ADC1_CH4
-
-
* - GPIO33
-
-
-
* - GPIO34
-
-
-
* - GPIO35
-
-
-
* - GPIO36
-
-
- Strapping pin
* - GPIO37
-
-
- Strapping pin
* - GPIO38
-
-
-
* - GPIO39
-
-
-
.. note::
- Strapping pin: GPIO17, GPIO36, and GPIO37 are strapping pins. For more information, please refer to `datasheet <{IDF_TARGET_DATASHEET_EN_URL}>`__.
- SPI0/1: GPIO6 ~ GPIO12 are usually used for SPI flash and PSRAM. These pins are not recommended for other uses.
- USB-JTAG: GPIO13 and GPIO14 are used by USB-JTAG by default. If they are reconfigured to operate as normal GPIOs, USB-JTAG functionality will be disabled.
---
@@ -0,0 +1,305 @@
.. This file gets included from other .rst files in this folder.
.. It contains target-specific snippets.
.. Comments and '---' lines act as delimiters.
..
.. This is necessary mainly because RST doesn't support substitutions
.. (defined in RST, not in Python) inside code blocks. If that is ever implemented,
.. These code blocks can be moved back to the main .rst files, with target-specific
.. file names being replaced by substitutions.
.. gpio-summary
The {IDF_TARGET_NAME} chip features 55 physical GPIO pins (GPIO0 ~ GPIO54). Each pin can be used as a general-purpose I/O, or to be connected to an internal peripheral signal. Through GPIO matrix and IO MUX, peripheral input signals can be from any IO pins, and peripheral output signals can be routed to any IO pins. Together these modules provide highly configurable I/O. For more details, see *{IDF_TARGET_NAME} Technical Reference Manual* > *IO MUX and GPIO Matrix (GPIO, IO_MUX)* [`PDF <{IDF_TARGET_TRM_EN_URL}#iomuxgpio>`__].
The table below provides more information on pin usage, and please note the comments in the table for GPIOs with restrictions.
.. list-table::
:header-rows: 1
:widths: 8 12 12 20
* - GPIO
- Analog Function
- LP GPIO
- Comments
* - GPIO0
-
- LP_GPIO0
-
* - GPIO1
-
- LP_GPIO1
-
* - GPIO2
- TOUCH0
- LP_GPIO2
-
* - GPIO3
- TOUCH1
- LP_GPIO3
-
* - GPIO4
- TOUCH2
- LP_GPIO4
-
* - GPIO5
- TOUCH3
- LP_GPIO5
-
* - GPIO6
- TOUCH4
- LP_GPIO6
-
* - GPIO7
- TOUCH5
- LP_GPIO7
-
* - GPIO8
- TOUCH6
- LP_GPIO8
-
* - GPIO9
- TOUCH7
- LP_GPIO9
-
* - GPIO10
- TOUCH8
- LP_GPIO10
-
* - GPIO11
- TOUCH9
- LP_GPIO11
-
* - GPIO12
- TOUCH10
- LP_GPIO12
-
* - GPIO13
- TOUCH11
- LP_GPIO13
-
* - GPIO14
- TOUCH12
- LP_GPIO14
-
* - GPIO15
- TOUCH13
- LP_GPIO15
-
* - GPIO16
- ADC1_CH0
-
-
* - GPIO17
- ADC1_CH1
-
-
* - GPIO18
- ADC1_CH2
-
-
* - GPIO19
- ADC1_CH3
-
-
* - GPIO20
- ADC1_CH4
-
-
* - GPIO21
- ADC1_CH5
-
-
* - GPIO22
- ADC1_CH6
-
-
* - GPIO23
- ADC1_CH7
-
-
* - GPIO24
-
-
-
* - GPIO25
-
-
-
* - GPIO26
-
-
-
* - GPIO27
-
-
-
* - GPIO28
-
-
-
* - GPIO29
-
-
-
* - GPIO30
-
-
-
* - GPIO31
-
-
-
* - GPIO32
-
-
-
* - GPIO33
-
-
-
* - GPIO34
-
-
- Strapping pin
* - GPIO35
-
-
- Strapping pin
* - GPIO36
-
-
- Strapping pin
* - GPIO37
-
-
- Strapping pin
* - GPIO38
-
-
- Strapping pin
* - GPIO39
-
-
-
* - GPIO40
-
-
-
* - GPIO41
-
-
-
* - GPIO42
-
-
-
* - GPIO43
-
-
-
* - GPIO44
-
-
-
* - GPIO45
-
-
-
* - GPIO46
-
-
-
* - GPIO47
-
-
-
* - GPIO48
-
-
-
* - GPIO49
- ADC2_CH0
-
-
* - GPIO50
- ADC2_CH1
-
-
* - GPIO51
- ADC2_CH2, ANA_CMPR_CH0 reference voltage
-
-
* - GPIO52
- ADC2_CH3, ANA_CMPR_CH0 input (non-inverting)
-
-
* - GPIO53
- ADC2_CH4, ANA_CMPR_CH1 reference voltage
-
-
* - GPIO54
- ADC2_CH5, ANA_CMPR_CH1 input (non-inverting)
-
-
.. note::
- Strapping pin: GPIO34, GPIO35, GPIO36, GPIO37, and GPIO38 are strapping pins. For more information, please refer to `datasheet <{IDF_TARGET_DATASHEET_EN_URL}>`__.
- USB-JTAG: GPIO24 and GPIO25 are used by USB-JTAG by default. If they are reconfigured to operate as normal GPIOs, USB-JTAG functionality will be disabled.
---
@@ -0,0 +1,247 @@
.. This file gets included from other .rst files in this folder.
.. It contains target-specific snippets.
.. Comments and '---' lines act as delimiters.
..
.. This is necessary mainly because RST doesn't support substitutions
.. (defined in RST, not in Python) inside code blocks. If that is ever implemented,
.. These code blocks can be moved back to the main .rst files, with target-specific
.. file names being replaced by substitutions.
.. gpio-summary
The {IDF_TARGET_NAME} chip features 43 physical GPIO pins (GPIO0 ~ GPIO21 and GPIO26 ~ GPIO46). Each pin can be used as a general-purpose I/O, or be connected to an internal peripheral signal. Through IO MUX, RTC IO MUX and the GPIO matrix, peripheral input signals can be from any IO pins, and peripheral output signals can be routed to any IO pins. Together these modules provide highly configurable I/O. For more details, see *{IDF_TARGET_NAME} Technical Reference Manual* > *IO MUX and GPIO Matrix (GPIO, IO_MUX)* [`PDF <{IDF_TARGET_TRM_EN_URL}#iomuxgpio>`__].
The table below provides more information on pin usage, and please note the comments in the table for GPIOs with restrictions.
.. list-table::
:header-rows: 1
:widths: 8 12 12 20
* - GPIO
- Analog Function
- RTC GPIO
- Comment
* - GPIO0
-
- RTC_GPIO0
- Strapping pin
* - GPIO1
- ADC1_CH0
- RTC_GPIO1
-
* - GPIO2
- ADC1_CH1
- RTC_GPIO2
-
* - GPIO3
- ADC1_CH2
- RTC_GPIO3
-
* - GPIO4
- ADC1_CH3
- RTC_GPIO4
-
* - GPIO5
- ADC1_CH4
- RTC_GPIO5
-
* - GPIO6
- ADC1_CH5
- RTC_GPIO6
-
* - GPIO7
- ADC1_CH6
- RTC_GPIO7
-
* - GPIO8
- ADC1_CH7
- RTC_GPIO8
-
* - GPIO9
- ADC1_CH8
- RTC_GPIO9
-
* - GPIO10
- ADC1_CH9
- RTC_GPIO10
-
* - GPIO11
- ADC2_CH0
- RTC_GPIO11
-
* - GPIO12
- ADC2_CH1
- RTC_GPIO12
-
* - GPIO13
- ADC2_CH2
- RTC_GPIO13
-
* - GPIO14
- ADC2_CH3
- RTC_GPIO14
-
* - GPIO15
- ADC2_CH4
- RTC_GPIO15
-
* - GPIO16
- ADC2_CH5
- RTC_GPIO16
-
* - GPIO17
- ADC2_CH6
- RTC_GPIO17
-
* - GPIO18
- ADC2_CH7
- RTC_GPIO18
-
* - GPIO19
- ADC2_CH8
- RTC_GPIO19
-
* - GPIO20
- ADC2_CH9
- RTC_GPIO20
-
* - GPIO21
-
- RTC_GPIO21
-
* - GPIO26
-
-
- SPI0/1
* - GPIO27
-
-
- SPI0/1
* - GPIO28
-
-
- SPI0/1
* - GPIO29
-
-
- SPI0/1
* - GPIO30
-
-
- SPI0/1
* - GPIO31
-
-
- SPI0/1
* - GPIO32
-
-
- SPI0/1
* - GPIO33
-
-
-
* - GPIO34
-
-
-
* - GPIO35
-
-
-
* - GPIO36
-
-
-
* - GPIO37
-
-
-
* - GPIO38
-
-
-
* - GPIO39
-
-
- JTAG
* - GPIO40
-
-
- JTAG
* - GPIO41
-
-
- JTAG
* - GPIO42
-
-
- JTAG
* - GPIO43
-
-
-
* - GPIO44
-
-
-
* - GPIO45
-
-
- Strapping pin
* - GPIO46
-
-
- GPIStrapping pin
.. note::
- Strapping pin: GPIO0, GPIO45 and GPIO46 are strapping pins. For more information, please refer to `ESP32-S2 datasheet <{IDF_TARGET_DATASHEET_EN_URL}>`_
- SPI0/1: GPIO26-32 are usually used for SPI flash and PSRAM and not recommended for other uses.
- JTAG: GPIO39-42 are usually used for inline debug.
- GPI: GPIO46 is fixed to pull-down and is input only.
---
@@ -0,0 +1,256 @@
.. This file gets included from other .rst files in this folder.
.. It contains target-specific snippets.
.. Comments and '---' lines act as delimiters.
..
.. This is necessary mainly because RST doesn't support substitutions
.. (defined in RST, not in Python) inside code blocks. If that is ever implemented,
.. These code blocks can be moved back to the main .rst files, with target-specific
.. file names being replaced by substitutions.
.. gpio-summary
The {IDF_TARGET_NAME} chip features 45 physical GPIO pins (GPIO0 ~ GPIO21 and GPIO26 ~ GPIO48). Each pin can be used as a general-purpose I/O, or be connected to an internal peripheral signal. Through GPIO matrix, IO MUX, and RTC IO MUX, peripheral input signals can be from any GPIO pin, and peripheral output signals can be routed to any GPIO pin. Together these modules provide highly configurable I/O. For more details, see *{IDF_TARGET_NAME} Technical Reference Manual* > *IO MUX and GPIO Matrix (GPIO, IO_MUX)* [`PDF <{IDF_TARGET_TRM_EN_URL}#iomuxgpio>`__].
The table below provides more information on pin usage, and please note the comments in the table for GPIOs with restrictions.
.. list-table::
:header-rows: 1
:widths: 8 12 12 20
* - GPIO
- Analog Function
- RTC GPIO
- Comment
* - GPIO0
-
- RTC_GPIO0
- Strapping pin
* - GPIO1
- ADC1_CH0
- RTC_GPIO1
-
* - GPIO2
- ADC1_CH1
- RTC_GPIO2
-
* - GPIO3
- ADC1_CH2
- RTC_GPIO3
- Strapping pin
* - GPIO4
- ADC1_CH3
- RTC_GPIO4
-
* - GPIO5
- ADC1_CH4
- RTC_GPIO5
-
* - GPIO6
- ADC1_CH5
- RTC_GPIO6
-
* - GPIO7
- ADC1_CH6
- RTC_GPIO7
-
* - GPIO8
- ADC1_CH7
- RTC_GPIO8
-
* - GPIO9
- ADC1_CH8
- RTC_GPIO9
-
* - GPIO10
- ADC1_CH9
- RTC_GPIO10
-
* - GPIO11
- ADC2_CH0
- RTC_GPIO11
-
* - GPIO12
- ADC2_CH1
- RTC_GPIO12
-
* - GPIO13
- ADC2_CH2
- RTC_GPIO13
-
* - GPIO14
- ADC2_CH3
- RTC_GPIO14
-
* - GPIO15
- ADC2_CH4
- RTC_GPIO15
-
* - GPIO16
- ADC2_CH5
- RTC_GPIO16
-
* - GPIO17
- ADC2_CH6
- RTC_GPIO17
-
* - GPIO18
- ADC2_CH7
- RTC_GPIO18
-
* - GPIO19
- ADC2_CH8
- RTC_GPIO19
- USB-JTAG
* - GPIO20
- ADC2_CH9
- RTC_GPIO20
- USB-JTAG
* - GPIO21
-
- RTC_GPIO21
-
* - GPIO26
-
-
- SPI0/1
* - GPIO27
-
-
- SPI0/1
* - GPIO28
-
-
- SPI0/1
* - GPIO29
-
-
- SPI0/1
* - GPIO30
-
-
- SPI0/1
* - GPIO31
-
-
- SPI0/1
* - GPIO32
-
-
- SPI0/1
* - GPIO33
-
-
- SPI0/1
* - GPIO34
-
-
- SPI0/1
* - GPIO35
-
-
- SPI0/1
* - GPIO36
-
-
- SPI0/1
* - GPIO37
-
-
- SPI0/1
* - GPIO38
-
-
-
* - GPIO39
-
-
-
* - GPIO40
-
-
-
* - GPIO41
-
-
-
* - GPIO42
-
-
-
* - GPIO43
-
-
-
* - GPIO44
-
-
-
* - GPIO45
-
-
- Strapping pin
* - GPIO46
-
-
- Strapping pin
* - GPIO47
-
-
-
* - GPIO48
-
-
-
.. Note::
- Strapping pin: GPIO0, GPIO3, GPIO45 and GPIO46 are strapping pins. For more information, please refer to `ESP32-S3 datasheet <{IDF_TARGET_DATASHEET_EN_URL}>`_.
- SPI0/1: GPIO26 ~ GPIO32 are usually used for SPI flash and PSRAM and not recommended for other uses. When using Octal flash or Octal PSRAM or both, GPIO33 ~ GPIO37 are connected to SPIIO4 ~ SPIIO7 and SPIDQS. Therefore, on boards embedded with ESP32-S3R8 / ESP32-S3R8V chip, GPIO33 ~ GPIO37 are also not recommended for other uses.
- USB-JTAG: GPIO19 and GPIO20 are used by USB-JTAG by default. If they are reconfigured to operate as normal GPIOs, USB-JTAG functionality will be disabled.
---
@@ -0,0 +1,330 @@
.. This file gets included from other .rst files in this folder.
.. It contains target-specific snippets.
.. Comments and '---' lines act as delimiters.
..
.. This is necessary mainly because RST doesn't support substitutions
.. (defined in RST, not in Python) inside code blocks. If that is ever implemented,
.. These code blocks can be moved back to the main .rst files, with target-specific
.. file names being replaced by substitutions.
.. gpio-summary
The {IDF_TARGET_NAME} chip features 60 physical GPIO pins (GPIO0 ~ GPIO61, except GPIO29, GPIO41). Each pin can be used as a general-purpose I/O, or to be connected to an internal peripheral signal. Through GPIO matrix and IO MUX, peripheral input signals can be from any IO pins, and peripheral output signals can be routed to any IO pins. Together these modules provide highly configurable I/O. For more details, see *{IDF_TARGET_NAME} Technical Reference Manual* > *IO MUX and GPIO Matrix (GPIO, IO_MUX)* [`PDF <{IDF_TARGET_TRM_EN_URL}#iomuxgpio>`__].
The table below provides more information on pin usage, and please note the comments in the table for GPIOs with restrictions.
.. list-table::
:header-rows: 1
:widths: 8 12 12 20
* - GPIO
- Analog Function
- LP GPIO
- Comment
* - GPIO0
-
- LP_GPIO0
-
* - GPIO1
-
- LP_GPIO1
-
* - GPIO2
-
- LP_GPIO2
-
* - GPIO3
-
- LP_GPIO3
-
* - GPIO4
- DAC0
- LP_GPIO4
-
* - GPIO5
- DAC1
- LP_GPIO5
-
* - GPIO6
- TOUCH0
- LP_GPIO6
-
* - GPIO7
- TOUCH1
- LP_GPIO7
-
* - GPIO8
- TOUCH2
-
-
* - GPIO9
- TOUCH3
-
-
* - GPIO10
- TOUCH4
-
-
* - GPIO11
- TOUCH5
-
-
* - GPIO12
- TOUCH6
-
-
* - GPIO13
- TOUCH7
-
-
* - GPIO14
- TOUCH8
-
-
* - GPIO15
- TOUCH9
-
-
* - GPIO16
- TOUCH10
-
-
* - GPIO17
- TOUCH11
-
-
* - GPIO18
- TOUCH12
-
-
* - GPIO19
- TOUCH13
-
-
* - GPIO20
-
-
-
* - GPIO21
-
-
-
* - GPIO22
-
-
-
* - GPIO23
-
-
-
* - GPIO24
-
-
-
* - GPIO25
-
-
-
* - GPIO26
-
-
- SPI0/1
* - GPIO27
-
-
- SPI0/1
* - GPIO28
-
-
- SPI0/1
* - GPIO30
-
-
- SPI0/1
* - GPIO31
-
-
- SPI0/1
* - GPIO32
-
-
- SPI0/1
* - GPIO33
-
-
-
* - GPIO34
-
-
-
* - GPIO35
-
-
-
* - GPIO36
-
-
- Strapping pin
* - GPIO37
-
-
- Strapping pin
* - GPIO38
-
-
- Strapping pin
* - GPIO39
-
-
- Strapping pin
* - GPIO40
-
-
- Strapping pin
* - GPIO42
- ADC1_CH0_N
-
-
* - GPIO43
- ADC1_CH0_P
-
-
* - GPIO44
- ADC1_CH1_N
-
-
* - GPIO45
- ADC1_CH1_P
-
-
* - GPIO46
- ADC1_CH2_N
-
-
* - GPIO47
- ADC1_CH2_P
-
-
* - GPIO48
- ADC1_CH3_N
-
-
* - GPIO49
- ADC1_CH3_P
-
-
* - GPIO50
- ADC2_CH0_N
-
-
* - GPIO51
- ADC2_CH0_P
-
-
* - GPIO52
- ADC2_CH1_N
-
-
* - GPIO53
- ADC2_CH1_P
-
-
* - GPIO54
- ADC2_CH2_N
-
-
* - GPIO55
- ADC2_CH2_P
-
-
* - GPIO56
- ADC2_CH3_N
-
-
* - GPIO57
- ADC2_CH3_P
-
-
* - GPIO58
-
-
- UART0
* - GPIO59
-
-
- UART0
* - GPIO60
-
-
- Strapping pin
* - GPIO61
-
-
- Strapping pin
.. note::
- Strapping pin: GPIO36 ~ GPIO40, GPIO60 ~ GPIO61 are strapping pins. For more information, please refer to `{IDF_TARGET_NAME} datasheet <{IDF_TARGET_DATASHEET_EN_URL}>`_.
- SPI0/1: GPIO26 ~ GPIO32 are usually used for SPI flash, they're not recommended for other uses.
---
@@ -0,0 +1,421 @@
===============================
General Purpose Timer (GPTimer)
===============================
:link_to_translation:`zh_CN:[中文]`
This document introduces the features of the General Purpose Timer (GPTimer) driver in ESP-IDF. The table of contents is as follows:
.. contents::
:local:
:depth: 2
Overview
========
GPTimer is a dedicated driver for the {IDF_TARGET_NAME} [`Timer Group peripheral <{IDF_TARGET_TRM_EN_URL}#timg>`__]. This timer can select different clock sources and prescalers to meet the requirements of nanosecond-level resolution. Additionally, it has flexible timeout alarm functions and allows automatic updating of the count value at the alarm moment, achieving very precise timing cycles.
Based on the **high resolution, high count range, and high response** capabilities of the hardware timer, the main application scenarios of this driver include:
- Running freely as a calendar clock to provide timestamp services for other modules
- Generating periodic alarms to complete periodic tasks
- Generating one-shot alarms, which can be used to implement a monotonic software timer list with asynchronous updates of alarm values
- Working with the GPIO module to achieve PWM signal output and input capture
- etc.
Quick Start
===========
This section provides a concise overview of how to use the GPTimer driver. Through practical examples, it demonstrates how to initialize and start a timer, configure alarm events, and register callback functions. The typical usage flow is as follows:
.. blockdiag::
:scale: 100%
:caption: GPTimer driver's general usage flow (click to enlarge)
:align: center
blockdiag {
default_fontsize = 14;
node_width = 250;
node_height = 80;
class emphasis [color = pink, style = dashed];
create [label="gptimer_new_timer"];
config [label="gptimer_set_alarm_action \n gptimer_register_event_callbacks"];
enable [label="gptimer_enable"];
start [label="gptimer_start"];
running [label="Timer Running", class="emphasis"]
stop [label="gptimer_stop"];
disable [label="gptimer_disable"];
cleanup [label="gptimer_delete_timer"];
create -> config -> enable -> start -> running -> stop -> disable -> cleanup;
enable -> start [folded];
stop -> disable [folded];
}
Creating and Starting a Timer
-----------------------------
First, we need to create a timer instance. The following code shows how to create a timer with a resolution of 1 MHz:
.. code:: c
gptimer_handle_t gptimer = NULL;
gptimer_config_t timer_config = {
.clk_src = GPTIMER_CLK_SRC_DEFAULT, // Select the default clock source
.direction = GPTIMER_COUNT_UP, // Counting direction is up
.resolution_hz = 1 * 1000 * 1000, // Resolution is 1 MHz, i.e., 1 tick equals 1 microsecond
};
// Create a timer instance
ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &gptimer));
// Enable the timer
ESP_ERROR_CHECK(gptimer_enable(gptimer));
// Start the timer
ESP_ERROR_CHECK(gptimer_start(gptimer));
When creating a timer instance, we need to configure parameters such as the clock source, counting direction, and resolution through :cpp:type:`gptimer_config_t`. These parameters determine how the timer works. Then, call the :cpp:func:`gptimer_new_timer` function to create a new timer instance, which returns a handle pointing to the new instance. The timer handle is essentially a pointer to the timer memory object, of type :cpp:type:`gptimer_handle_t`.
Here are the other configuration parameters of the :cpp:type:`gptimer_config_t` structure and their explanations:
- :cpp:member:`gptimer_config_t::clk_src` selects the clock source for the timer. Available clock sources are listed in :cpp:type:`gptimer_clock_source_t`, and only one can be selected. Different clock sources vary in resolution, accuracy, and power consumption.
- :cpp:member:`gptimer_config_t::direction` sets the counting direction of the timer. Supported directions are listed in :cpp:type:`gptimer_count_direction_t`, and only one can be selected.
- :cpp:member:`gptimer_config_t::resolution_hz` sets the resolution of the internal counter. Each tick is equivalent to **1 / resolution_hz** seconds.
- :cpp:member:`gptimer_config_t::intr_priority` sets the interrupt priority. If set to ``0``, a default priority interrupt will be allocated; otherwise, the specified priority will be used.
- :cpp:member:`gptimer_config_t::flags` is used to fine-tune some behaviors of the driver, including the following options:
- :cpp:member:`gptimer_config_t::flags::allow_pd` configures whether the driver allows the system to power down the peripheral in sleep mode. Before entering sleep, the system will back up the GPTimer register context, which will be restored when the system wakes up. Note that powering down the peripheral can save power but will consume more memory to save the register context. You need to balance power consumption and memory usage. This configuration option depends on specific hardware features. If enabled on an unsupported chip, you will see an error message like ``not able to power down in light sleep``.
.. note::
Note that if all hardware timers in the current chip have been allocated, :cpp:func:`gptimer_new_timer` will return the :c:macro:`ESP_ERR_NOT_FOUND` error.
Before starting the timer, it must be enabled. The enable function :cpp:func:`gptimer_enable` can switch the internal state machine of the driver to the active state, which includes some system service requests/registrations, such as applying for a power management lock. The corresponding disable function is :cpp:func:`gptimer_disable`, which releases all system services.
.. note::
When calling the :cpp:func:`gptimer_enable` and :cpp:func:`gptimer_disable` functions, they need to be used in pairs. This means you cannot call :cpp:func:`gptimer_enable` or :cpp:func:`gptimer_disable` twice in a row. This pairing principle ensures the correct management and release of resources.
The :cpp:func:`gptimer_start` function is used to start the timer. After starting, the timer will begin counting and will automatically overflow and restart from 0 when it reaches the maximum or minimum value (depending on the counting direction).
The :cpp:func:`gptimer_stop` function is used to stop the timer. Note that stopping a timer does not clear the current value of the counter. To clear the counter, use the :cpp:func:`gptimer_set_raw_count` function introduced later.
The :cpp:func:`gptimer_start` and :cpp:func:`gptimer_stop` functions follow the idempotent principle. This means that if the timer is already started, calling the :cpp:func:`gptimer_start` function again will have no effect. Similarly, if the timer is already stopped, calling the :cpp:func:`gptimer_stop` function again will have no effect.
.. note::
However, note that when the timer is in the **intermediate state** of starting (the start has begun but not yet completed), if another thread calls the :cpp:func:`gptimer_start` or :cpp:func:`gptimer_stop` function, it will return the :c:macro:`ESP_ERR_INVALID_STATE` error to avoid triggering uncertain behavior.
Setting and Getting the Count Value
-----------------------------------
When a timer is newly created, its internal counter value defaults to zero. You can set other count values using the :cpp:func:`gptimer_set_raw_count` function. The maximum count value depends on the bit width of the hardware timer (usually no less than ``54 bits``).
.. note::
If the timer is already running, :cpp:func:`gptimer_set_raw_count` will make the timer immediately jump to the new value and start counting from the newly set value.
The :cpp:func:`gptimer_get_raw_count` function is used to get the current count value of the timer. This count value is the accumulated count since the timer started (assuming it started from 0). Note that the returned value has not been converted to any unit; it is a pure count value. You need to convert the count value to time units based on the actual resolution of the timer. The timer's resolution can be obtained using the :cpp:func:`gptimer_get_resolution` function.
.. code:: c
// Check the timer's resolution
uint32_t resolution_hz;
ESP_ERROR_CHECK(gptimer_get_resolution(gptimer, &resolution_hz));
// Read the current count value
uint64_t count;
ESP_ERROR_CHECK(gptimer_get_raw_count(gptimer, &count));
// (Optional) Convert the count value to time units (seconds)
double time = (double)count / resolution_hz;
Triggering Periodic Alarm Events
--------------------------------
In addition to the timestamp function, the general-purpose timer also supports alarm functions. The following code shows how to set a periodic alarm that triggers once per second:
.. code-block:: c
:emphasize-lines: 10-32
gptimer_handle_t gptimer = NULL;
gptimer_config_t timer_config = {
.clk_src = GPTIMER_CLK_SRC_DEFAULT, // Select the default clock source
.direction = GPTIMER_COUNT_UP, // Counting direction is up
.resolution_hz = 1 * 1000 * 1000, // Resolution is 1 MHz, i.e., 1 tick equals 1 microsecond
};
// Create a timer instance
ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &gptimer));
static bool example_timer_on_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
{
// General process for handling event callbacks:
// 1. Retrieve user context data from user_ctx (passed in from gptimer_register_event_callbacks)
// 2. Get alarm event data from edata, such as edata->count_value
// 3. Perform user-defined operations
// 4. Return whether a high-priority task was awakened during the above operations to notify the scheduler to switch tasks
return false;
}
gptimer_alarm_config_t alarm_config = {
.reload_count = 0, // When the alarm event occurs, the timer will automatically reload to 0
.alarm_count = 1000000, // Set the actual alarm period, since the resolution is 1us, 1000000 represents 1s
.flags.auto_reload_on_alarm = true, // Enable auto-reload function
};
// Set the timer's alarm action
ESP_ERROR_CHECK(gptimer_set_alarm_action(gptimer, &alarm_config));
gptimer_event_callbacks_t cbs = {
.on_alarm = example_timer_on_alarm_cb, // Call the user callback function when the alarm event occurs
};
// Register timer event callback functions, allowing user context to be carried
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, NULL));
// Enable the timer
ESP_ERROR_CHECK(gptimer_enable(gptimer));
// Start the timer
ESP_ERROR_CHECK(gptimer_start(gptimer));
The :cpp:func:`gptimer_set_alarm_action` function is used to configure the timer's alarm action. When the timer count value reaches the specified alarm value, an alarm event will be triggered. Users can choose to automatically reload the preset count value when the alarm event occurs, thereby achieving periodic alarms.
Here are the necessary members of the :cpp:type:`gptimer_alarm_config_t` structure and their functions. By configuring these parameters, users can flexibly control the timer's alarm behavior to meet different application needs.
- :cpp:member:`gptimer_alarm_config_t::alarm_count` sets the target count value that triggers the alarm event. When the timer count value reaches this value, an alarm event will be triggered. When setting the alarm value, consider the counting direction of the timer. If the current count value has **exceeded** the alarm value, the alarm event will be triggered immediately.
- :cpp:member:`gptimer_alarm_config_t::reload_count` sets the count value to be reloaded when the alarm event occurs. This configuration only takes effect when the :cpp:member:`gptimer_alarm_config_t::flags::auto_reload_on_alarm` flag is ``true``. The actual alarm period will be determined by ``|alarm_count - reload_count|``. From a practical application perspective, it is not recommended to set the alarm period to less than 5us.
.. note::
Specifically, ``gptimer_set_alarm_action(gptimer, NULL);`` means disabling the timer's alarm function.
The :cpp:func:`gptimer_register_event_callbacks` function is used to register the timer event callback functions. When the timer triggers a specific event (such as an alarm event), the user-defined callback function will be called. Users can perform custom operations in the callback function, such as sending signals, to achieve more flexible event handling mechanisms. Since the callback function is executed in the interrupt context, avoid performing complex operations (including any operations that may cause blocking) in the callback function to avoid affecting the system's real-time performance. The :cpp:func:`gptimer_register_event_callbacks` function also allows users to pass a context pointer to access user-defined data in the callback function.
The supported event callback functions for GPTimer are as follows:
- :cpp:type:`gptimer_alarm_cb_t` alarm event callback function, which has a corresponding data structure :cpp:type:`gptimer_alarm_event_data_t` for passing alarm event-related data:
- :cpp:member:`gptimer_alarm_event_data_t::alarm_value` stores the alarm value, which is the target count value that triggers the alarm event.
- :cpp:member:`gptimer_alarm_event_data_t::count_value` stores the count value when entering the interrupt handler after the alarm occurs. This value may differ from the alarm value due to interrupt handler delays, and the count value may have been automatically reloaded when the alarm occurred.
.. note::
Be sure to register the callback function before calling :cpp:func:`gptimer_enable`, otherwise the timer event will not correctly trigger the interrupt service.
Triggering One-Shot Alarm Events
--------------------------------
Some application scenarios only require triggering a one-shot alarm interrupt. The following code shows how to set a one-shot alarm that triggers after 1 second:
.. code-block:: c
:emphasize-lines: 12-13,24
gptimer_handle_t gptimer = NULL;
gptimer_config_t timer_config = {
.clk_src = GPTIMER_CLK_SRC_DEFAULT, // Select the default clock source
.direction = GPTIMER_COUNT_UP, // Counting direction is up
.resolution_hz = 1 * 1000 * 1000, // Resolution is 1 MHz, i.e., 1 tick equals 1 microsecond
};
// Create a timer instance
ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &gptimer));
static bool example_timer_on_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
{
// This is just a demonstration of how to stop the timer when the alarm occurs for the first time
gptimer_stop(timer);
// General process for handling event callbacks:
// 1. Retrieve user context data from user_ctx (passed in from gptimer_register_event_callbacks)
// 2. Get alarm event data from edata, such as edata->count_value
// 3. Perform user-defined operations
// 4. Return whether a high-priority task was awakened during the above operations to notify the scheduler to switch tasks
return false;
}
gptimer_alarm_config_t alarm_config = {
.alarm_count = 1000000, // Set the actual alarm period, since the resolution is 1us, 1000000 represents 1s
.flags.auto_reload_on_alarm = false; // Disable auto-reload function
};
// Set the timer's alarm action
ESP_ERROR_CHECK(gptimer_set_alarm_action(gptimer, &alarm_config));
gptimer_event_callbacks_t cbs = {
.on_alarm = example_timer_on_alarm_cb, // Call the user callback function when the alarm event occurs
};
// Register timer event callback functions, allowing user context to be carried
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, NULL));
// Enable the timer
ESP_ERROR_CHECK(gptimer_enable(gptimer));
// Start the timer
ESP_ERROR_CHECK(gptimer_start(gptimer));
Unlike periodic alarms, the above code disables the auto-reload function when configuring the alarm behavior. This means that after the alarm event occurs, the timer will not automatically reload to the preset count value but will continue counting until it overflows. If you want the timer to stop immediately after the alarm, you can call :cpp:func:`gptimer_stop` in the callback function.
Resource Recycling
------------------
When the timer is no longer needed, you should call the :cpp:func:`gptimer_delete_timer` function to release software and hardware resources. Before deleting, ensure that the timer is already stopped.
Advanced Features
=================
After understanding the basic usage, we can further explore more features of the GPTimer driver.
Dynamic Alarm Value Update
--------------------------
The GPTimer driver supports dynamically updating the alarm value in the interrupt callback function by calling the :cpp:func:`gptimer_set_alarm_action` function, thereby implementing a monotonic software timer list. The following code shows how to reset the next alarm trigger time when the alarm event occurs:
.. code-block:: c
:emphasize-lines: 12-16
gptimer_handle_t gptimer = NULL;
gptimer_config_t timer_config = {
.clk_src = GPTIMER_CLK_SRC_DEFAULT, // Select the default clock source
.direction = GPTIMER_COUNT_UP, // Counting direction is up
.resolution_hz = 1 * 1000 * 1000, // Resolution is 1 MHz, i.e., 1 tick equals 1 microsecond
};
// Create a timer instance
ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &gptimer));
static bool example_timer_on_alarm_cb(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
{
gptimer_alarm_config_t alarm_config = {
.alarm_count = edata->alarm_value + 1000000, // Next alarm in 1s from the current alarm
};
// Update the alarm value
gptimer_set_alarm_action(timer, &alarm_config);
return false;
}
gptimer_alarm_config_t alarm_config = {
.alarm_count = 1000000, // Set the actual alarm period, since the resolution is 1us, 1000000 represents 1s
.flags.auto_reload_on_alarm = false, // Disable auto-reload function
};
// Set the timer's alarm action
ESP_ERROR_CHECK(gptimer_set_alarm_action(gptimer, &alarm_config));
gptimer_event_callbacks_t cbs = {
.on_alarm = example_timer_on_alarm_cb, // Call the user callback function when the alarm event occurs
};
// Register timer event callback functions, allowing user context to be carried
ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer, &cbs, NULL));
// Enable the timer
ESP_ERROR_CHECK(gptimer_enable(gptimer));
// Start the timer
ESP_ERROR_CHECK(gptimer_start(gptimer));
.. only:: SOC_TIMER_SUPPORT_ETM and SOC_ETM_SUPPORTED
.. _gptimer-etm-event-and-task:
GPTimer's ETM Events and Tasks
------------------------------
GPTimer can generate various events that can be connected to the :doc:`ETM </api-reference/peripherals/etm>` module. The event types are listed in :cpp:type:`gptimer_etm_event_type_t`. Users can create an ``ETM event`` handle by calling :cpp:func:`gptimer_new_etm_event`.
GPTimer also supports some tasks that can be triggered by other events and executed automatically. The task types are listed in :cpp:type:`gptimer_etm_task_type_t`. Users can create an ``ETM task`` handle by calling :cpp:func:`gptimer_new_etm_task`.
For how to connect the timer events and tasks to the ETM channel, please refer to the :doc:`ETM </api-reference/peripherals/etm>` documentation.
Power Management
----------------
When power management :ref:`CONFIG_PM_ENABLE` is enabled, the system may adjust or disable the clock source before entering sleep mode, causing the GPTimer to lose accuracy.
To prevent this, the GPTimer driver creates a power management lock internally. When the :cpp:func:`gptimer_enable` function is called, the lock is activated to ensure the system does not enter sleep mode, thus maintaining the timer's accuracy. To reduce power consumption, you can call the :cpp:func:`gptimer_disable` function to release the power management lock, allowing the system to enter sleep mode. However, this will stop the timer, so you need to restart the timer after waking up.
.. only:: SOC_TIMER_SUPPORT_SLEEP_RETENTION
Besides disabling the clock source, the system can also power down the GPTimer before entering sleep mode to further reduce power consumption. To achieve this, set :cpp:member:`gptimer_config_t::allow_pd` to ``true``. Before the system enters sleep mode, the GPTimer register context will be backed up to memory and restored after the system wakes up. Note that enabling this option reduces power consumption but increases memory usage. Therefore, you need to balance power consumption and memory usage when using this feature.
Thread Safety
-------------
The driver uses critical sections to ensure atomic operations on registers. Key members in the driver handle are also protected by critical sections. The driver's internal state machine uses atomic instructions to ensure thread safety, with state checks preventing certain invalid concurrent operations (e.g., conflicts between `start` and `stop`). Therefore, GPTimer driver APIs can be used in a multi-threaded environment without extra locking.
The following functions can also be used in an interrupt context:
.. list::
- :cpp:func:`gptimer_start`
- :cpp:func:`gptimer_stop`
- :cpp:func:`gptimer_get_raw_count`
- :cpp:func:`gptimer_set_raw_count`
- :cpp:func:`gptimer_get_captured_count`
- :cpp:func:`gptimer_set_alarm_action`
Cache Safety
------------
When the file system performs Flash read/write operations, the system temporarily disables the Cache function to avoid errors when loading instructions and data from Flash. This causes the GPTimer interrupt handler to be unresponsive during this period, preventing the user callback function from executing in time. If you want the interrupt handler to run normally when the Cache is disabled, you can enable the :ref:`CONFIG_GPTIMER_ISR_CACHE_SAFE` option.
.. note::
Note that when this option is enabled, all interrupt callback functions and their context data **must be placed in internal storage**. This is because the system cannot load data and instructions from Flash when the Cache is disabled.
Performance
-----------
To improve the real-time responsiveness of interrupt handling, the GPTimer driver provides the :ref:`CONFIG_GPTIMER_ISR_HANDLER_IN_IRAM` option. Once enabled, the interrupt handler is placed in internal RAM, reducing delays caused by potential cache misses when loading instructions from Flash.
.. note::
However, the user callback function and its context data called by the interrupt handler may still reside in Flash. Cache misses are still possible, so users must manually place the callback function and data in internal RAM, for example by using :c:macro:`IRAM_ATTR` and :c:macro:`DRAM_ATTR`.
As mentioned above, the GPTimer driver allows some functions to be called in an interrupt context. By enabling the :ref:`CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM` option, these functions can also be placed in IRAM, which helps avoid performance loss caused by cache misses and allows them to be used when the Cache is disabled.
Other Kconfig Options
---------------------
- The :ref:`CONFIG_GPTIMER_ENABLE_DEBUG_LOG` option forces the GPTimer driver to enable all debug logs, regardless of the global log level settings. Enabling this option helps developers obtain more detailed log information during debugging, making it easier to locate and solve problems.
Resource Consumption
--------------------
Use the :doc:`/api-guides/tools/idf-size` tool to check the code and data consumption of the GPTimer driver. The following are the test conditions (using ESP32-C2 as an example):
- Compiler optimization level set to ``-Os`` to ensure minimal code size.
- Default log level set to ``ESP_LOG_INFO`` to balance debug information and performance.
- Disable the following driver optimization options:
- :ref:`CONFIG_GPTIMER_ISR_HANDLER_IN_IRAM` - Do not place the interrupt handler in IRAM.
- :ref:`CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM` - Do not place control functions in IRAM.
- :ref:`CONFIG_GPTIMER_ISR_CACHE_SAFE` - Do not enable Cache safety options.
**Note that the following data are not exact values and are for reference only; they may differ on different chip models.**
+------------------+------------+-------+------+-------+-------+------------+-------+------------+---------+
| Component Layer | Total Size | DIRAM | .bss | .data | .text | Flash Code | .text | Flash Data | .rodata |
+==================+============+=======+======+=======+=======+============+=======+============+=========+
| soc | 8 | 0 | 0 | 0 | 0 | 0 | 0 | 8 | 8 |
+------------------+------------+-------+------+-------+-------+------------+-------+------------+---------+
| hal | 206 | 0 | 0 | 0 | 0 | 206 | 206 | 0 | 0 |
+------------------+------------+-------+------+-------+-------+------------+-------+------------+---------+
| driver | 4251 | 12 | 12 | 0 | 0 | 4046 | 4046 | 193 | 193 |
+------------------+------------+-------+------+-------+-------+------------+-------+------------+---------+
Additionally, each GPTimer handle dynamically allocates about ``100`` bytes of memory from the heap. If the :cpp:member:`gptimer_config_t::flags::allow_pd` option is enabled, each timer will also consume approximately ``30`` extra bytes of memory during sleep to store the register context.
Application Examples
====================
.. list::
- :example:`peripherals/timer_group/gptimer` demonstrates how to use the general-purpose timer APIs on ESP SOC chips to generate periodic alarm events and trigger different alarm actions.
- :example:`peripherals/timer_group/wiegand_interface` uses two timers (one in one-shot alarm mode and the other in periodic alarm mode) to trigger interrupts and change the GPIO output state in the alarm event callback function, simulating the output waveform of the Wiegand protocol.
:SOC_TIMER_SUPPORT_ETM and SOC_ETM_SUPPORTED: - :example:`peripherals/timer_group/gptimer_capture_hc_sr04` demonstrates how to use the general-purpose timer and Event Task Matrix (ETM) to accurately capture timestamps of ultrasonic sensor events and convert them into distance information.
API Reference
=============
GPTimer Driver APIs
-------------------
.. include-build-file:: inc/gptimer.inc
GPTimer Driver Types
--------------------
.. include-build-file:: inc/gptimer_types.inc
GPTimer HAL Types
-----------------
.. include-build-file:: inc/timer_types.inc
GPTimer ETM APIs
----------------
.. only:: SOC_TIMER_SUPPORT_ETM and SOC_ETM_SUPPORTED
.. include-build-file:: inc/gptimer_etm.inc
+259
View File
@@ -0,0 +1,259 @@
Hash-Based Message Authentication Code (HMAC)
=============================================
:link_to_translation:`zh_CN:[中文]`
Hash-based Message Authentication Code (HMAC) is a secure authentication technique that verifies the authenticity and integrity of a message with a pre-shared key. This module provides hardware acceleration for SHA256-HMAC generation using a key burned into an eFuse block.
For more detailed information on the application workflow and the HMAC calculation process, see **{IDF_TARGET_NAME} Technical Reference Manual** > **HMAC Accelerator (HMAC)** [`PDF <{IDF_TARGET_TRM_EN_URL}#hmac>`__].
Generalized Application Scheme
------------------------------
Let there be two parties, A and B. They want to verify the authenticity and integrity of messages sent between each other. Before they can start sending messages, they need to exchange the secret key via a secure channel.
To verify A's messages, B can do the following:
- A calculates the HMAC of the message it wants to send.
- A sends the message and the HMAC to B.
- B calculates the HMAC of the received message itself.
- B checks whether the received and calculated HMACs match.
If they do match, the message is authentic.
However, the HMAC itself is not bound to this use case. It can also be used for challenge-response protocols supporting HMAC or as a key input for further security modules (see below), etc.
HMAC on {IDF_TARGET_NAME}
-----------------------------
On {IDF_TARGET_NAME}, the HMAC module works with a secret key burnt into the eFuses.
.. only:: SOC_KEY_MANAGER_SUPPORTED
On {IDF_TARGET_NAME}, the HMAC module also supports storing a secret key in the Key Manager. Refer to :ref:`key-manager` for more details.
This key can be made completely inaccessible for any resources outside the cryptographic modules, thus avoiding key leakage.
.. only:: SOC_DIG_SIGN_SUPPORTED
Furthermore, {IDF_TARGET_NAME} has three different application scenarios for its HMAC module:
#. HMAC is generated for software use
#. HMAC is used as a key for the RSA Digital Signature Peripheral (RSA_DS)
#. HMAC is used for enabling the soft-disabled JTAG interface
The first mode is called **Upstream** mode, while the last two modes are called **Downstream** modes.
.. only:: not SOC_DIG_SIGN_SUPPORTED
Furthermore, {IDF_TARGET_NAME} has two different application scenarios for its HMAC module:
#. HMAC is generated for software use
#. HMAC is used for enabling the soft-disabled JTAG interface
The first mode is called **Upstream** mode, while the second mode is called **Downstream** mode.
eFuse Keys for HMAC
^^^^^^^^^^^^^^^^^^^
Six physical eFuse blocks can be used as keys for the HMAC module: block 4 ~ block 9. The enum :cpp:enum:`hmac_key_id_t` in the API maps them to ``HMAC_KEY0`` ~ ``HMAC_KEY5``.
Each key has a corresponding eFuse parameter **key purpose** determining for which of the HMAC application scenarios (see below) the key may be used:
.. only:: SOC_DIG_SIGN_SUPPORTED
.. list-table::
:widths: 15 70
:header-rows: 1
* - Key Purpose
- Application Scenario
* - 8
- HMAC generated for software use
* - 7
- HMAC used as a key for the RSA Digital Signature Peripheral (RSA_DS)
* - 6
- HMAC used for enabling the soft-disabled JTAG interface
* - 5
- HMAC both as a key for the RSA_DS module and for enabling JTAG
.. only:: not SOC_DIG_SIGN_SUPPORTED
.. list-table::
:widths: 15 70
:header-rows: 1
* - Key Purpose
- Application Scenario
* - 8
- HMAC generated for software use
* - 5, 6
- HMAC used for enabling the soft-disabled JTAG interface (HMAC Downstream mode)
This is to prevent the usage of a key for a different function than originally intended.
To calculate an HMAC, the software has to provide the ID of the key block containing the secret key as well as the **key purpose** (see **{IDF_TARGET_NAME} Technical Reference Manual** > **eFuse Controller (eFuse)** [`PDF <{IDF_TARGET_TRM_EN_URL}#efuse>`__]).
Before the HMAC key calculation, the HMAC module looks up the purpose of the provided key block. The calculation only proceeds if the purpose of the provided key block matches the purpose stored in the eFuses of the key block provided by the ID.
HMAC Generation for Software
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Key purpose value: 8
In this case, the HMAC is given out to the software, e.g., to authenticate a message.
The API to calculate the HMAC is :cpp:func:`psa_mac_compute`, which takes an opaque PSA key referencing an eFuse key block that contains the secret and has its purpose set to Upstream mode.
.. only:: SOC_DIG_SIGN_SUPPORTED
HMAC for RSA Digital Signature
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Key purpose values: 7, 5
The HMAC can be used as a key derivation function to decrypt private key parameters which are used by the RSA Digital Signature module. A standard message is used by the hardware in that case. You only need to provide the eFuse key block and purpose on the HMAC side, additional parameters are required for the RSA Digital Signature component in that case.
Neither the key nor the actual HMAC is ever exposed outside the HMAC module and RSA_DS component. The calculation of the HMAC and its handover to the RSA_DS component happen internally.
For more details, see **{IDF_TARGET_NAME} Technical Reference Manual** > **RSA Digital Signature Peripheral (RSA_DS)** [`PDF <{IDF_TARGET_TRM_EN_URL}#digsig>`__].
.. _hmac_for_enabling_jtag:
HMAC for Enabling JTAG
^^^^^^^^^^^^^^^^^^^^^^
Key purpose values: 6, 5
The third application is using the HMAC as a key to enable JTAG if it was soft-disabled before.
Following is the procedure to re-enable the JTAG:
**Stage 1: Setup**
1. Generate a 256-bit HMAC secret key to use for JTAG re-enable.
2. Write the key to an eFuse block with key purpose HMAC_DOWN_ALL (5) or HMAC_DOWN_JTAG (6). This can be done using the ``esp_efuse_write_key()`` function in the firmware or using ``idf.py efuse-burn-key`` from the host.
3. Configure the eFuse key block to be read-protected using the ``esp_efuse_set_read_protect()``, so that software cannot read back the value.
4. Burn the ``soft JTAG disable`` bit/bits on {IDF_TARGET_NAME}. This will permanently disable JTAG unless the correct key value is provided by the software.
.. only:: esp32s2
.. note::
The API **esp_efuse_write_field_bit(ESP_EFUSE_SOFT_DIS_JTAG)** can be used to burn ``soft JTAG disable`` bit on {IDF_TARGET_NAME}.
.. only:: not esp32s2
.. note::
The API **esp_efuse_write_field_cnt(ESP_EFUSE_SOFT_DIS_JTAG, ESP_EFUSE_SOFT_DIS_JTAG[0]->bit_count)** can be used to burn ``soft JTAG disable`` bits on {IDF_TARGET_NAME}.
.. only:: esp32s2 or esp32s3
.. note::
If ``HARD_DIS_JTAG`` eFuse is set, then ``SOFT_DIS_JTAG`` functionality does not work because JTAG is permanently disabled.
.. only:: not esp32s2 and not esp32s3
.. note::
If ``DIS_PAD_JTAG`` eFuse is set, then ``SOFT_DIS_JTAG`` functionality does not work because JTAG is permanently disabled.
JTAG enables
1. The key to re-enable JTAG is the output of the HMAC-SHA256 function using the secret key in eFuse and 32 ``0x00`` bytes as the message.
2. Pass this key value when calling the :cpp:func:`esp_hmac_jtag_enable` function from the firmware.
3. To re-disable JTAG in the firmware, reset the system or call :cpp:func:`esp_hmac_jtag_disable`.
For a complete workflow of soft-disabling and re-enabling JTAG, refer to :example:`security/hmac_soft_jtag`. This example demonstrates how to use HMAC to re-enable a soft-disabled JTAG interface, covering steps like generating an HMAC key, burning it to eFuse, and creating token data from the key.
For more details, see **{IDF_TARGET_NAME} Technical Reference Manual** > **HMAC Accelerator (HMAC)** [`PDF <{IDF_TARGET_TRM_EN_URL}#hmac>`__].
Application Outline
-------------------
The following code is an outline of how to set an eFuse key and then use it to calculate an HMAC for software usage.
Using eFuses to store the HMAC key:
We use ``esp_efuse_write_key`` to set physical key block 4 in the eFuse for the HMAC module together with its purpose. ``ESP_EFUSE_KEY_PURPOSE_HMAC_UP`` (8) means that this key can only be used for HMAC generation for software usage:
.. code-block:: c
#include "esp_efuse.h"
const uint8_t key_data[32] = { ... };
esp_err_t status = esp_efuse_write_key(EFUSE_BLK_KEY4,
ESP_EFUSE_KEY_PURPOSE_HMAC_UP,
key_data, sizeof(key_data));
if (status == ESP_OK) {
// written key
} else {
// writing key failed, maybe written already
}
Now we can calculate an HMAC for software usage with the saved key through the PSA Crypto API.
Using an eFuse-based HMAC key:
.. code-block:: c
#include "psa/crypto.h"
#include "psa_crypto_driver_esp_hmac_opaque.h"
uint8_t hmac[32];
size_t hmac_length = 0;
const char *message = "Hello, HMAC!";
const size_t msg_len = 12;
// Setup key attributes for ESP-HMAC opaque driver
psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT;
psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_SIGN_MESSAGE);
psa_set_key_algorithm(&attributes, PSA_ALG_HMAC(PSA_ALG_SHA_256));
psa_set_key_type(&attributes, PSA_KEY_TYPE_HMAC);
psa_set_key_bits(&attributes, 256);
psa_set_key_lifetime(&attributes, PSA_KEY_LIFETIME_ESP_HMAC_VOLATILE);
// Create opaque key reference for eFuse-based key
esp_hmac_opaque_key_t opaque_key = {
.efuse_key_id = HMAC_KEY4,
};
// Import the opaque key
psa_key_id_t key_id = 0;
psa_status_t status = psa_import_key(&attributes, (uint8_t *)&opaque_key,
sizeof(opaque_key), &key_id);
if (status != PSA_SUCCESS) {
// Failed to import key
psa_reset_key_attributes(&attributes);
return;
}
// Compute HMAC
status = psa_mac_compute(key_id, PSA_ALG_HMAC(PSA_ALG_SHA_256),
(uint8_t *)message, msg_len,
hmac, sizeof(hmac), &hmac_length);
// Clean up
psa_destroy_key(key_id);
psa_reset_key_attributes(&attributes);
if (status == PSA_SUCCESS) {
// HMAC written to hmac now
} else {
// failure calculating HMAC
}
.. note::
The ESP-HMAC opaque PSA driver is backed by the one-shot hardware HMAC peripheral, which computes the MAC over the whole message in a single operation and cannot save or restore intermediate state between calls. Multipart streaming is therefore not supported: supply the entire message in a single :cpp:func:`psa_mac_compute` call (as shown above) or in a single multipart update. A second non-empty update on the same operation returns ``PSA_ERROR_BAD_STATE``; the operation fails closed and never produces a MAC computed over only part of the message.
API Reference
-------------
.. include-build-file:: inc/psa_crypto_driver_esp_hmac_opaque_contexts.inc
+707
View File
@@ -0,0 +1,707 @@
Inter-Integrated Circuit (I2C)
==============================
:link_to_translation:`zh_CN:[中文]`
Introduction
------------
I2C is a serial, synchronous, multi-device, half-duplex communication protocol that allows co-existence of multiple masters and slaves on the same bus. I2C uses two bidirectional open-drain lines: serial data line (SDA) and serial clock line (SCL), pulled up by resistors.
{IDF_TARGET_NAME} has {IDF_TARGET_SOC_HP_I2C_NUM} I2C controller(s) (also called port), responsible for handling communication on the I2C bus.
.. only:: not esp32c2
A single I2C controller can be a master or a slave.
.. only:: esp32c2
The I2C controller can only be a master.
.. only:: SOC_LP_I2C_SUPPORTED
Additionally, the {IDF_TARGET_NAME} chip has 1 low-power (LP) I2C controller. It is the cut-down version of regular I2C. Usually, the LP I2C controller only support basic I2C functionality with a much smaller RAM size, and does not support slave mode. For a full list of difference between HP I2C and LP I2C, please refer to the *{IDF_TARGET_NAME} Technical Reference Manual* > *I2C Controller (I2C)* > *Features* [`PDF <{IDF_TARGET_TRM_EN_URL}#i2c>`__].
You can use LP I2C peripheral when HP I2C is not sufficient for users' usage. But please note again the LP I2C does not support all HP I2C functions. Please read documentation before you use it.
Typically, an I2C slave device has a 7-bit address or 10-bit address. {IDF_TARGET_NAME} supports both I2C Standard-mode (Sm) and Fast-mode (Fm) which can go up to 100 kHz and 400 kHz respectively.
.. warning::
The clock frequency of SCL in master mode should not be larger than 400 kHz.
.. note::
The frequency of SCL is influenced by both the pull-up resistor and the wire capacitance. Therefore, it is strongly recommended to choose appropriate pull-up resistors to make the frequency accurate. The recommended value for pull-up resistors usually ranges from 1 kΩ to 10 kΩ.
Keep in mind that the higher the frequency, the smaller the pull-up resistor should be (but not less than 1 kΩ). Indeed, large resistors will decline the current, which will increase the clock switching time and reduce the frequency. A range of 2 kΩ to 5 kΩ is recommended, but adjustments may also be necessary depending on their current draw requirements.
.. only:: esp32
.. note::
The ESP32 I2C controller does not support clock stretching when operating as a slave. Therefore, in addition to driver configuration, the application layer should pay attention to speed matching and synchronization between master and slave devices: if the master processes too fast while the slave responds slowly, communication errors or data loss may occur. It is recommended to use application-layer data verification, GPIO signal synchronization, or other methods to achieve data synchronization between master and slave. Please confirm whether the ESP32 slave mode meets your project requirements according to your use case before use.
I2C Clock Configuration
-----------------------
.. list::
- :cpp:enumerator:`i2c_clock_source_t::I2C_CLK_SRC_DEFAULT`: Default I2C source clock.
:SOC_I2C_SUPPORT_XTAL: - :cpp:enumerator:`i2c_clock_source_t::I2C_CLK_SRC_XTAL`: External crystal for I2C clock source.
:SOC_I2C_SUPPORT_RTC: - :cpp:enumerator:`i2c_clock_source_t::I2C_CLK_SRC_RC_FAST`: Internal 20 MHz RC oscillator for I2C clock source.
:SOC_I2C_SUPPORT_APB: - :cpp:enumerator:`i2c_clock_source_t::I2C_CLK_SRC_APB`: APB clock as I2C clock source.
:SOC_I2C_SUPPORT_REF_TICK: - :cpp:enumerator:`i2c_clock_source_t::I2C_CLK_SRC_REF_TICK`: 1 MHZ clock.
Functional Overview
-------------------
The I2C driver offers following services:
- :ref:`i2c-resource-allocation` - covers how to allocate I2C bus with properly set of configurations. It also covers how to recycle the resources when they finished working.
- :ref:`i2c-master-controller` - covers behavior of I2C master controller. Introduce data transmit, data receive, and data transmit and receive.
- :ref:`i2c-slave-controller` - covers behavior of I2C slave controller. Involve data transmit and data receive.
- :ref:`i2c-power-management` - describes how different source clock will affect power consumption.
- :ref:`i2c-iram-safe` - describes tips on how to make the I2C interrupt work better along with a disabled cache.
- :ref:`i2c-thread-safety` - lists which APIs are guaranteed to be thread safe by the driver.
- :ref:`i2c-kconfig-options` - lists the supported Kconfig options that can bring different effects to the driver.
.. _i2c-resource-allocation:
Resource Allocation
^^^^^^^^^^^^^^^^^^^
The I2C master bus is represented by :cpp:type:`i2c_master_bus_handle_t` in the driver. The available ports are managed in a resource pool that allocates a free port on request.
Install I2C master bus and device
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The I2C master bus is designed based on bus-device model. So :cpp:type:`i2c_master_bus_config_t` and :cpp:type:`i2c_device_config_t` are required separately to allocate the I2C master bus instance and I2C device instance.
.. figure:: ../../../_static/diagrams/i2c/i2c_master_module.png
:align: center
:alt: I2C master bus-device module
I2C master bus-device module
I2C master bus requires the configuration that specified by :cpp:type:`i2c_master_bus_config_t`:
- :cpp:member:`i2c_master_bus_config_t::i2c_port` sets the I2C port used by the controller.
- :cpp:member:`i2c_master_bus_config_t::sda_io_num` sets the GPIO number for the serial data bus (SDA).
- :cpp:member:`i2c_master_bus_config_t::scl_io_num` sets the GPIO number for the serial clock bus (SCL).
- :cpp:member:`i2c_master_bus_config_t::clk_source` selects the source clock for I2C bus. The available clocks are listed in :cpp:type:`i2c_clock_source_t`. For the effect on power consumption of different clock source, please refer to :ref:`i2c-power-management` section.
- :cpp:member:`i2c_master_bus_config_t::glitch_ignore_cnt` sets the glitch period of master bus, if the glitch period on the line is less than this value, it can be filtered out, typically value is 7.
- :cpp:member:`i2c_master_bus_config_t::intr_priority` sets the priority of the interrupt. If set to ``0`` , then the driver will use a interrupt with low or medium priority (priority level may be one of 1, 2 or 3), otherwise use the priority indicated by :cpp:member:`i2c_master_bus_config_t::intr_priority`. Please use the number form (1, 2, 3) , not the bitmask form ((1<<1), (1<<2), (1<<3)).
- :cpp:member:`i2c_master_bus_config_t::trans_queue_depth` sets the depth of internal transfer queue. Only valid in asynchronous transaction.
- :cpp:member:`i2c_master_bus_config_t::enable_internal_pullup` enables internal pullups. Note: This is not strong enough to pullup buses under high-speed frequency. A suitable external pullup is recommended.
- :cpp:member:`i2c_master_bus_config_t::allow_pd` configures if the driver allows the system to power down the peripheral in light sleep mode. Before entering sleep, the system will backup the I2C register context, which will be restored later when the system exit the sleep mode. Powering down the peripheral can save more power, but at the cost of more memory consumed to save the register context. It's a tradeoff between power consumption and memory consumption. This configuration option relies on specific hardware feature, if you enable it on an unsupported chip, you will see error message like ``not able to power down in light sleep``.
If the configurations in :cpp:type:`i2c_master_bus_config_t` is specified, then :cpp:func:`i2c_new_master_bus` can be called to allocate and initialize an I2C master bus. This function will return an I2C bus handle if it runs correctly. Specifically, when there are no more I2C port available, this function will return :c:macro:`ESP_ERR_NOT_FOUND` error.
I2C master device requires the configuration that specified by :cpp:type:`i2c_device_config_t`:
- :cpp:member:`i2c_device_config_t::dev_addr_length` configure the address bit length of the slave device. It can be chosen from enumerator :cpp:enumerator:`I2C_ADDR_BIT_LEN_7` or :cpp:enumerator:`I2C_ADDR_BIT_LEN_10` (if supported).
- :cpp:member:`i2c_device_config_t::device_address` sets the I2C device raw address. Please parse the device address to this member directly. For example, the device address is 0x28, then parse 0x28 to :cpp:member:`i2c_device_config_t::device_address`, don't carry a write or read bit.
- :cpp:member:`i2c_device_config_t::scl_speed_hz` sets the SCL line frequency of this device.
- :cpp:member:`i2c_device_config_t::scl_wait_us` sets the SCL await time (in μs). Usually this value should not be very small because slave stretch will happen in pretty long time (It's possible even stretch for 12 ms). Set ``0`` means use default register value.
Once the :cpp:type:`i2c_device_config_t` structure is populated with mandatory parameters, :cpp:func:`i2c_master_bus_add_device` can be called to allocate an I2C device instance and mounted to the master bus then. This function will return an I2C device handle if it runs correctly. Specifically, when the I2C bus is not initialized properly, calling this function will result in a :c:macro:`ESP_ERR_INVALID_ARG` error.
.. code:: c
#include "driver/i2c_master.h"
i2c_master_bus_config_t i2c_mst_config = {
.clk_source = I2C_CLK_SRC_DEFAULT,
.i2c_port = TEST_I2C_PORT,
.scl_io_num = I2C_MASTER_SCL_IO,
.sda_io_num = I2C_MASTER_SDA_IO,
.glitch_ignore_cnt = 7,
.flags.enable_internal_pullup = true,
};
i2c_master_bus_handle_t bus_handle;
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_mst_config, &bus_handle));
i2c_device_config_t dev_cfg = {
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
.device_address = 0x58,
.scl_speed_hz = 100000,
};
i2c_master_dev_handle_t dev_handle;
ESP_ERROR_CHECK(i2c_master_bus_add_device(bus_handle, &dev_cfg, &dev_handle));
Get I2C master handle via port
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When the I2C master handle has been initialized in one module (e.g. the audio module), but it is not convenient to acquire this handle in another module (e.g. the video module). You can use the helper function, :cpp:func:`i2c_master_get_bus_handle` to retrieve the initialized handle via port. Ensure that the handle has already been initialized beforehand to avoid potential errors.
.. code:: c
// Source File 1
#include "driver/i2c_master.h"
i2c_master_bus_handle_t bus_handle;
i2c_master_bus_config_t i2c_mst_config = {
... // same as others
};
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_mst_config, &bus_handle));
// Source File 2
#include "driver/i2c_master.h"
i2c_master_bus_handle_t handle;
ESP_ERROR_CHECK(i2c_master_get_bus_handle(0, &handle));
.. only:: SOC_LP_I2C_SUPPORTED
Install I2C master bus with LP I2C Peripheral
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Install I2C master bus with LP I2C peripheral is almost as same as how HP I2C peripheral is installed. However, there are still some difference should be taken focus on, including IOs, clock sources, I2C port number, etc. Following code will show how to install I2C master bus with LP_I2C.
.. code:: c
#include "driver/i2c_master.h"
i2c_master_bus_config_t i2c_mst_config = {
.clk_source = LP_I2C_SCLK_DEFAULT, // clock source for LP I2C, might different from HP I2C
.i2c_port = LP_I2C_NUM_0, // Assign to LP I2C port
.scl_io_num = 7, // SCL IO number. Please refer to technical reference manual
.sda_io_num = 6, // SDA IO number. Please refer to technical reference manual
.glitch_ignore_cnt = 7,
.flags.enable_internal_pullup = true,
};
i2c_master_bus_handle_t bus_handle;
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_mst_config, &bus_handle));
i2c_device_config_t dev_cfg = {
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
.device_address = 0x58,
.scl_speed_hz = 100000,
};
i2c_master_dev_handle_t dev_handle;
ESP_ERROR_CHECK(i2c_master_bus_add_device(bus_handle, &dev_cfg, &dev_handle));
Uninstall I2C master bus and device
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If a previously installed I2C bus or device is no longer needed, it's recommended to recycle the resource by calling :cpp:func:`i2c_master_bus_rm_device` or :cpp:func:`i2c_del_master_bus`, so as to release the underlying hardware.
Please note that removing all devices attached to bus before delete the master bus.
Install I2C slave device
~~~~~~~~~~~~~~~~~~~~~~~~
I2C slave requires the configuration specified by :cpp:type:`i2c_slave_config_t`:
.. list::
- :cpp:member:`i2c_slave_config_t::i2c_port` sets the I2C port used by the controller.
- :cpp:member:`i2c_slave_config_t::sda_io_num` sets the GPIO number for serial data bus (SDA).
- :cpp:member:`i2c_slave_config_t::scl_io_num` sets the GPIO number for serial clock bus (SCL).
- :cpp:member:`i2c_slave_config_t::clk_source` selects the source clock for I2C bus. The available clocks are listed in :cpp:type:`i2c_clock_source_t`. For the effect on power consumption of different clock source, please refer to :ref:`i2c-power-management` section.
- :cpp:member:`i2c_slave_config_t::send_buf_depth` sets the sending software buffer length.
- :cpp:member:`i2c_slave_config_t::receive_buf_depth` sets the receiving software buffer length.
- :cpp:member:`i2c_slave_config_t::intr_priority` sets the priority of the interrupt. If set to ``0`` , then the driver will use a interrupt with low or medium priority (priority level may be one of 1, 2 or 3), otherwise use the priority indicated by :cpp:member:`i2c_slave_config_t::intr_priority`. Please use the number form (1, 2, 3), instead of the bitmask form ((1<<1), (1<<2), (1<<3)). Please pay attention that once the interrupt priority is set, it cannot be changed until :cpp:func:`i2c_del_slave_device` is called.
- :cpp:member:`i2c_slave_config_t::addr_bit_len` Set this variable to ``I2C_ADDR_BIT_LEN_10`` if the slave should have a 10-bit address.
- :cpp:member:`i2c_slave_config_t::allow_pd` If set, the driver will backup/restore the I2C registers before/after entering/exist sleep mode. By this approach, the system can power off I2C's power domain. This can save power, but at the expense of more RAM being consumed.
:SOC_I2C_SLAVE_SUPPORT_BROADCAST: - :cpp:member:`i2c_slave_config_t::broadcast_en` Set this to true to enable the slave broadcast. When the slave receives the general call address 0x00 from the master and the R/W bit followed is 0, it responds to the master regardless of its own address.
- :cpp:member:`i2c_slave_config_t::enable_internal_pullup` Set this to enable internal pull-up. Even though, an output pull-up resistance is strongly recommended.
Once the :cpp:type:`i2c_slave_config_t` structure is populated with mandatory parameters, :cpp:func:`i2c_new_slave_device` can be called to allocate and initialize an I2C master bus. This function will return an I2C bus handle if it runs correctly. Specifically, when there are no more I2C port available, this function will return :c:macro:`ESP_ERR_NOT_FOUND` error.
.. code:: c
i2c_slave_config_t i2c_slv_config = {
.i2c_port = I2C_SLAVE_NUM,
.clk_source = I2C_CLK_SRC_DEFAULT,
.scl_io_num = I2C_SLAVE_SCL_IO,
.sda_io_num = I2C_SLAVE_SDA_IO,
.slave_addr = ESP_SLAVE_ADDR,
.send_buf_depth = 100,
.receive_buf_depth = 100,
};
i2c_slave_dev_handle_t slave_handle;
ESP_ERROR_CHECK(i2c_new_slave_device(&i2c_slv_config, &slave_handle));
Uninstall I2C slave device
~~~~~~~~~~~~~~~~~~~~~~~~~~
If a previously installed I2C bus is no longer needed, it's recommended to recycle the resource by calling :cpp:func:`i2c_del_slave_device`, so that to release the underlying hardware.
.. _i2c-master-controller:
I2C Master Controller
^^^^^^^^^^^^^^^^^^^^^
After installing the I2C master driver by :cpp:func:`i2c_new_master_bus`, {IDF_TARGET_NAME} is ready to communicate with other I2C devices. I2C APIs allow the standard transactions. Like the wave as follows:
.. wavedrom:: /../_static/diagrams/i2c/i2c_trans_wave.json
I2C Master Write
~~~~~~~~~~~~~~~~
After installing I2C master bus successfully, you can simply call :cpp:func:`i2c_master_transmit` to write data to the slave device. The principle of this function can be explained by following chart.
In order to organize the process, the driver uses a command link, that should be populated with a sequence of commands and then passed to I2C controller for execution.
.. figure:: ../../../_static/diagrams/i2c/i2c_master_write_slave.png
:align: center
:alt: I2C master write to slave
I2C master write to slave
Simple example for writing data to slave:
.. code:: c
#define DATA_LENGTH 100
i2c_master_bus_config_t i2c_mst_config = {
.clk_source = I2C_CLK_SRC_DEFAULT,
.i2c_port = I2C_PORT_NUM_0,
.scl_io_num = I2C_MASTER_SCL_IO,
.sda_io_num = I2C_MASTER_SDA_IO,
.glitch_ignore_cnt = 7,
};
i2c_master_bus_handle_t bus_handle;
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_mst_config, &bus_handle));
i2c_device_config_t dev_cfg = {
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
.device_address = 0x58,
.scl_speed_hz = 100000,
};
i2c_master_dev_handle_t dev_handle;
ESP_ERROR_CHECK(i2c_master_bus_add_device(bus_handle, &dev_cfg, &dev_handle));
ESP_ERROR_CHECK(i2c_master_transmit(dev_handle, data_wr, DATA_LENGTH, -1));
I2C master write also supports transmit multi-buffer in one transaction. Take following transaction as a simple example:
.. code:: c
uint8_t control_phase_byte = 0;
size_t control_phase_size = 0;
if (/*condition*/) {
control_phase_byte = 1;
control_phase_size = 1;
}
uint8_t *cmd_buffer = NULL;
size_t cmd_buffer_size = 0;
if (/*condition*/) {
uint8_t cmds[4] = {BYTESHIFT(lcd_cmd, 3), BYTESHIFT(lcd_cmd, 2), BYTESHIFT(lcd_cmd, 1), BYTESHIFT(lcd_cmd, 0)};
cmd_buffer = cmds;
cmd_buffer_size = 4;
}
uint8_t *lcd_buffer = NULL;
size_t lcd_buffer_size = 0;
if (buffer) {
lcd_buffer = (uint8_t*)buffer;
lcd_buffer_size = buffer_size;
}
i2c_master_transmit_multi_buffer_info_t lcd_i2c_buffer[3] = {
{.write_buffer = &control_phase_byte, .buffer_size = control_phase_size},
{.write_buffer = cmd_buffer, .buffer_size = cmd_buffer_size},
{.write_buffer = lcd_buffer, .buffer_size = lcd_buffer_size},
};
i2c_master_multi_buffer_transmit(handle, lcd_i2c_buffer, sizeof(lcd_i2c_buffer) / sizeof(i2c_master_transmit_multi_buffer_info_t), -1);
I2C Master Read
~~~~~~~~~~~~~~~
After installing I2C master bus successfully, you can simply call :cpp:func:`i2c_master_receive` to read data from the slave device. The principle of this function can be explained by following chart.
.. figure:: ../../../_static/diagrams/i2c/i2c_master_read_slave.png
:align: center
:alt: I2C master read from slave
I2C master read from slave
Simple example for reading data from slave:
.. code:: c
#define DATA_LENGTH 100
i2c_master_bus_config_t i2c_mst_config = {
.clk_source = I2C_CLK_SRC_DEFAULT,
.i2c_port = I2C_PORT_NUM_0,
.scl_io_num = I2C_MASTER_SCL_IO,
.sda_io_num = I2C_MASTER_SDA_IO,
.glitch_ignore_cnt = 7,
};
i2c_master_bus_handle_t bus_handle;
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_mst_config, &bus_handle));
i2c_device_config_t dev_cfg = {
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
.device_address = 0x58,
.scl_speed_hz = 100000,
};
i2c_master_dev_handle_t dev_handle;
ESP_ERROR_CHECK(i2c_master_bus_add_device(bus_handle, &dev_cfg, &dev_handle));
i2c_master_receive(dev_handle, data_rd, DATA_LENGTH, -1);
I2C Master Write and Read
~~~~~~~~~~~~~~~~~~~~~~~~~
Some I2C device needs write configurations before reading data from it. Therefore, an interface called :cpp:func:`i2c_master_transmit_receive` can help. The principle of this function can be explained by following chart.
.. figure:: ../../../_static/diagrams/i2c/i2c_master_write_read_slave.png
:align: center
:alt: I2C master write to slave and read from slave
I2C master write to slave and read from slave
Please note that no STOP condition bit is inserted between the write and read operations; therefore, this function is suited to read a register from an I2C device. A simple example for writing and reading from a slave device:
.. code:: c
i2c_device_config_t dev_cfg = {
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
.device_address = 0x58,
.scl_speed_hz = 100000,
};
i2c_master_dev_handle_t dev_handle;
ESP_ERROR_CHECK(i2c_master_bus_add_device(bus_handle, &dev_cfg, &dev_handle));
uint8_t buf[20] = {0x20};
uint8_t buffer[2];
ESP_ERROR_CHECK(i2c_master_transmit_receive(dev_handle, buf, sizeof(buf), buffer, 2, -1));
I2C Master Probe
~~~~~~~~~~~~~~~~
I2C driver can use :cpp:func:`i2c_master_probe` to detect whether the specific device has been connected on I2C bus. If this function return ``ESP_OK``, that means the device has been detected.
.. important::
Pull-ups must be connected to the SCL and SDA pins when this function is called. If you get `ESP_ERR_TIMEOUT` while `xfer_timeout_ms` was parsed correctly, you should check the pull-up resistors. If you do not have proper resistors nearby, setting `flags.enable_internal_pullup` as true is also acceptable.
.. figure:: ../../../_static/diagrams/i2c/i2c_master_probe.png
:align: center
:alt: I2C master probe
I2C master probe
Simple example for probing an I2C device:
.. code:: c
i2c_master_bus_config_t i2c_mst_config_1 = {
.clk_source = I2C_CLK_SRC_DEFAULT,
.i2c_port = TEST_I2C_PORT,
.scl_io_num = I2C_MASTER_SCL_IO,
.sda_io_num = I2C_MASTER_SDA_IO,
.glitch_ignore_cnt = 7,
.flags.enable_internal_pullup = true,
};
i2c_master_bus_handle_t bus_handle;
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_mst_config_1, &bus_handle));
ESP_ERROR_CHECK(i2c_master_probe(bus_handle, 0x22, -1));
ESP_ERROR_CHECK(i2c_del_master_bus(bus_handle));
I2C Master Execute Customized Transactions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Not all I2C devices strictly adhere to the standard I2C protocol, as different manufacturers may implement custom variations. For example, some devices require the address to be shifted, while others do not. Similarly, certain devices mandate acknowledgment (ACK) checks for specific operations, whereas others might not. To accommodate these variations, :cpp:func:`i2c_master_execute_defined_operations` function allow developers to define and execute fully customized I2C transactions. This flexibility ensures seamless communication with non-standard devices by tailoring the transaction sequence, addressing, and acknowledgment behavior to the device's specific requirements.
.. note::
If you want to define your address in :cpp:type:`i2c_operation_job_t`, please set :cpp:member:`i2c_device_config_t::device_address` as ``I2C_DEVICE_ADDRESS_NOT_USED`` to skip internal address configuration in driver.
For address configuration of user defined transactions, given that the device address is ``0x20``, there are two situations. See following example:
.. code:: c
i2c_device_config_t i2c_device = {
.device_address = I2C_DEVICE_ADDRESS_NOT_USED,
.scl_speed_hz = 100 * 1000,
.scl_wait_us = 20000,
};
i2c_master_dev_handle_t dev_handle;
i2c_master_bus_add_device(bus_handle, &i2c_device, &dev_handle);
// Situation one: The device does not allow device address shift
uint8_t address1 = 0x20;
i2c_operation_job_t i2c_ops1[] = {
{ .command = I2C_MASTER_CMD_START },
{ .command = I2C_MASTER_CMD_WRITE, .write = { .ack_check = false, .data = (uint8_t *) &address1, .total_bytes = 1 } },
{ .command = I2C_MASTER_CMD_STOP },
};
// Situation one: The device address should be left shifted by one byte to include a write bit or a read bit (official protocol)
uint8_t address2 = (0x20 << 1 | 0); // (0x20 << 1 | 1)
i2c_operation_job_t i2c_ops2[] = {
{ .command = I2C_MASTER_CMD_START },
{ .command = I2C_MASTER_CMD_WRITE, .write = { .ack_check = false, .data = (uint8_t *) &address2, .total_bytes = 1 } },
{ .command = I2C_MASTER_CMD_STOP },
};
Some devices do not require an address, and allow direct transaction with data:
.. code:: c
uint8_t data[8] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
i2c_operation_job_t i2c_ops[] = {
{ .command = I2C_MASTER_CMD_START },
{ .command = I2C_MASTER_CMD_WRITE, .write = { .ack_check = false, .data = (uint8_t *)data, .total_bytes = 8 } },
{ .command = I2C_MASTER_CMD_STOP },
};
i2c_master_execute_defined_operations(dev_handle, i2c_ops, sizeof(i2c_ops) / sizeof(i2c_operation_job_t), -1);
The principle of read operations is the same as that of write operations. Note to always ensure the last byte read before the stop condition is a ``NACK``. An example is as follows:
.. code:: c
uint8_t address = (0x20 << 1 | 1);
uint8_t rcv_data[10] = {};
i2c_operation_job_t i2c_ops[] = {
{ .command = I2C_MASTER_CMD_START },
{ .command = I2C_MASTER_CMD_WRITE, .write = { .ack_check = false, .data = (uint8_t *) &address, .total_bytes = 1 } },
{ .command = I2C_MASTER_CMD_READ, .read = { .ack_value = I2C_ACK_VAL, .data = (uint8_t *)rcv_data, .total_bytes = 9 } },
{ .command = I2C_MASTER_CMD_READ, .read = { .ack_value = I2C_NACK_VAL, .data = (uint8_t *)(rcv_data + 9), .total_bytes = 1 } }, // This must be NACK
{ .command = I2C_MASTER_CMD_STOP },
};
i2c_master_execute_defined_operations(dev_handle, i2c_ops, sizeof(i2c_ops) / sizeof(i2c_operation_job_t), -1);
.. _i2c-slave-controller:
I2C Slave Controller
^^^^^^^^^^^^^^^^^^^^
After installing the I2C slave driver by :cpp:func:`i2c_new_slave_device`, {IDF_TARGET_NAME} is ready to communicate with other I2C masters as a slave.
The I2C slave is not as active as the I2C master, which knows when to send data and when to receive it. The I2C slave is very passive in most cases, meaning the I2C slave's ability to send and receive data is largely dependent on the master's actions. Therefore, we implement two callback functions in the driver to handle read and write requests from the I2C master.
I2C Slave Write
~~~~~~~~~~~~~~~
You can get I2C slave write event by registering :cpp:member:`i2c_slave_event_callbacks_t::on_request` callback. Then, in a task where the request event is triggered, you can call ``i2c_slave_write`` to send data.
A simple example for transmitting data:
.. code:: c
// Prepare a callback function
static bool i2c_slave_request_cb(i2c_slave_dev_handle_t i2c_slave, const i2c_slave_request_event_data_t *evt_data, void *arg)
{
i2c_slave_event_t evt = I2C_SLAVE_EVT_TX;
BaseType_t xTaskWoken = 0;
xQueueSendFromISR(context->event_queue, &evt, &xTaskWoken);
return xTaskWoken;
}
// Register callback in a task
i2c_slave_event_callbacks_t cbs = {
.on_request = i2c_slave_request_cb,
};
ESP_ERROR_CHECK(i2c_slave_register_event_callbacks(context.handle, &cbs, &context));
// Wait for request event and send data in a task
static void i2c_slave_task(void *arg)
{
uint8_t buffer_size = 64;
uint32_t write_len;
uint8_t *data_buffer;
while (true) {
i2c_slave_event_t evt;
if (xQueueReceive(context->event_queue, &evt, 10) == pdTRUE) {
ESP_ERROR_CHECK(i2c_slave_write(handle, data_buffer, buffer_size, &write_len, 1000));
}
}
vTaskDelete(NULL);
}
.. only:: not esp32
I2C Slave Reset TX FIFO
~~~~~~~~~~~~~~~~~~~~~~~~
In some scenarios, the slave may prepare more data than the master actually reads. For example, if the slave prepares 16 bytes of data but the master only reads 8 bytes, the remaining 8 bytes will stay in the TX FIFO. To prepare fresh data for the next transaction, you can use :cpp:func:`i2c_slave_reset_tx_fifo` to clear the TX FIFO.
.. note::
It is recommended to call this function after the master has completed the read transaction to ensure data integrity.
Simple example:
.. code:: c
// First write, data may not be completely read by master
ESP_ERROR_CHECK(i2c_slave_write(handle, data_buffer_1, buffer_size_1, &write_len_1, 1000));
// Wait for the next master read transaction, here we simply use a delay for demonstration
vTaskDelay(pdMS_TO_TICKS(10));
// Clear remaining data in TX FIFO
ESP_ERROR_CHECK(i2c_slave_reset_tx_fifo(handle));
// Second write, new data will be sent normally
ESP_ERROR_CHECK(i2c_slave_write(handle, data_buffer_2, buffer_size_2, &write_len_2, 1000));
I2C Slave Read
~~~~~~~~~~~~~~
Same as write event, you can get I2C slave read event by registering :cpp:member:`i2c_slave_event_callbacks_t::on_receive` callback. Then, in a task where the request event is triggered, you can save the data and do what you want.
A simple example for receiving data:
.. code:: c
// Prepare a callback function
static bool i2c_slave_receive_cb(i2c_slave_dev_handle_t i2c_slave, const i2c_slave_rx_done_event_data_t *evt_data, void *arg)
{
i2c_slave_event_t evt = I2C_SLAVE_EVT_RX;
BaseType_t xTaskWoken = 0;
// You can get data and length via i2c_slave_rx_done_event_data_t
xQueueSendFromISR(context->event_queue, &evt, &xTaskWoken);
return xTaskWoken;
}
// Register callback in a task
i2c_slave_event_callbacks_t cbs = {
.on_receive = i2c_slave_receive_cb,
};
ESP_ERROR_CHECK(i2c_slave_register_event_callbacks(context.handle, &cbs, &context));
Register Event Callbacks
^^^^^^^^^^^^^^^^^^^^^^^^
I2C master callbacks
~~~~~~~~~~~~~~~~~~~~
When an I2C master bus triggers an interrupt, a specific event will be generated and notify the CPU. If you have some functions that need to be called when those events occurred, you can hook your functions to the ISR (Interrupt Service Routine) by calling :cpp:func:`i2c_master_register_event_callbacks`. Since the registered callback functions are called in the interrupt context, users should ensure the callback function doesn't attempt to block (e.g. by making sure that only FreeRTOS APIs with ``ISR`` suffix are called from the function). The callback functions are required to return a boolean value, to tell the ISR whether a high priority task is woken up by it.
I2C master event callbacks are listed in the :cpp:type:`i2c_master_event_callbacks_t`.
Although I2C is a synchronous communication protocol, asynchronous behavior is supported by registering above callbacks. In this way, I2C APIs will be non-blocking interface. But note that on the same bus, only one device can adopt asynchronous operation.
.. important::
I2C master asynchronous transaction is still an experimental feature (The issue is that when asynchronous transaction is very large, it will cause memory problem).
- :cpp:member:`i2c_master_event_callbacks_t::on_recv_done` sets a callback function for master "transaction-done" event. The function prototype is declared in :cpp:type:`i2c_master_callback_t`.
I2C slave callbacks
~~~~~~~~~~~~~~~~~~~
When an I2C slave bus triggers an interrupt, a specific event will be generated and notify the CPU. If you have some function that needs to be called when those events occurred, you can hook your function to the ISR (Interrupt Service Routine) by calling :cpp:func:`i2c_slave_register_event_callbacks`. Since the registered callback functions are called in the interrupt context, users should ensure the callback function doesn't attempt to block (e.g. by making sure that only FreeRTOS APIs with ``ISR`` suffix are called from the function). The callback function has a boolean return value, to tell the caller whether a high priority task is woken up by it.
I2C slave event callbacks are listed in the :cpp:type:`i2c_slave_event_callbacks_t`.
.. list::
- :cpp:member:`i2c_slave_event_callbacks_t::on_request` sets a callback function for request event.
- :cpp:member:`i2c_slave_event_callbacks_t::on_receive` sets a callback function for receive event. The function prototype is declared in :cpp:type:`i2c_slave_received_callback_t`.
.. _i2c-power-management:
Power Management
^^^^^^^^^^^^^^^^
.. only:: SOC_I2C_SUPPORT_APB
When the power management is enabled (i.e. :ref:`CONFIG_PM_ENABLE` is on), the system will adjust or stop the source clock of I2C FIFO before going into Light-sleep mode, thus potentially changing the I2C signals and leading to transmitting or receiving invalid data.
However, the driver can prevent the system from changing APB frequency by acquiring a power management lock of type :cpp:enumerator:`ESP_PM_APB_FREQ_MAX`. Whenever user creates an I2C bus that has selected :cpp:enumerator:`I2C_CLK_SRC_APB` as the clock source, the driver will guarantee that the power management lock is acquired when I2C operations begin and the lock will be released automatically when I2C operations finish.
.. only:: SOC_I2C_SUPPORT_REF_TICK
If the controller clock source is selected to :cpp:enumerator:`I2C_CLK_SRC_REF_TICK`, then the driver won't install power management lock for it, which is more suitable for a low power application as long as the source clock can still provide sufficient resolution.
.. only:: SOC_I2C_SUPPORT_XTAL
If the controller clock source is selected to :cpp:enumerator:`I2C_CLK_SRC_XTAL`, then the driver won't install power management lock for it, which is more suitable for a low power application as long as the source clock can still provide sufficient resolution.
.. _i2c-iram-safe:
IRAM Safe
^^^^^^^^^
By default, the I2C interrupt will be deferred when the cache is disabled for reasons like writing or erasing flash. Thus the event callback functions will not get executed in time, which is not expected in a real-time application.
There's a Kconfig option :ref:`CONFIG_I2C_ISR_IRAM_SAFE` that will:
1. Enable the interrupt being serviced even when cache is disabled.
2. Place all functions that used by the ISR into IRAM.
3. Place driver object into DRAM (in case it's mapped to PSRAM by accident).
This will allow the interrupt to run while the cache is disabled but will come at the cost of increased IRAM consumption.
.. _i2c-thread-safety:
Thread Safety
^^^^^^^^^^^^^
The factory function :cpp:func:`i2c_new_master_bus` and :cpp:func:`i2c_new_slave_device` are guaranteed to be thread safe by the driver, which means that the functions can be called from different RTOS tasks without protection by extra locks.
I2C master operation functions are also guaranteed to be thread safe by bus operation semaphore.
- :cpp:func:`i2c_master_transmit`
- :cpp:func:`i2c_master_multi_buffer_transmit`
- :cpp:func:`i2c_master_transmit_receive`
- :cpp:func:`i2c_master_receive`
- :cpp:func:`i2c_master_probe`
I2C slave operation functions are also guaranteed to be thread safe by bus operation semaphore.
- :cpp:func:`i2c_slave_write`
Other functions are not guaranteed to be thread-safe. Thus, you should avoid calling them in different tasks without mutex protection.
.. _i2c-kconfig-options:
Kconfig Options
^^^^^^^^^^^^^^^
- :ref:`CONFIG_I2C_ISR_IRAM_SAFE` controls whether the default ISR handler can work when cache is disabled, see also :ref:`i2c-iram-safe` for more information.
- :ref:`CONFIG_I2C_ENABLE_DEBUG_LOG` is used to enable the debug log at the cost of increased firmware binary size.
Application Examples
--------------------
- :example:`peripherals/i2c/i2c_basic` demonstrates the basic steps to initialize the I2C master driver and read data from a MPU9250 sensor.
- :example:`peripherals/i2c/i2c_eeprom` demonstrates how to use the I2C master mode to read and write data from a connected EEPROM.
- :example:`peripherals/i2c/i2c_tools` demonstrates how to use the I2C Tools for developing I2C related applications, providing command-line tools for configuring the I2C bus, scanning for devices, reading and setting registers, and examining registers.
- :example:`peripherals/i2c/i2c_slave_network_sensor` demonstrates how to use the I2C slave for developing I2C related applications, providing how I2C slave can behave as a network sensor, and use event callbacks to receive and send data.
- :example:`peripherals/i2c/i2c_u8g2` demonstrates how to use the I2C master mode to interface with U8G2 library for controlling OLED displays.
API Reference
-------------
.. include-build-file:: inc/i2c_master.inc
.. only:: SOC_I2C_SUPPORT_SLAVE
.. include-build-file:: inc/i2c_slave.inc
.. include-build-file:: inc/components/esp_driver_i2c/include/driver/i2c_types.inc
.. include-build-file:: inc/components/esp_hal_i2c/include/hal/i2c_types.inc
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,603 @@
====================
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
@@ -0,0 +1,58 @@
Peripherals API
****************
:link_to_translation:`zh_CN:[中文]`
.. toctree::
:maxdepth: 1
:SOC_ADC_SUPPORTED: adc/index
:SOC_ANA_CMPR_SUPPORTED: ana_cmpr
:SOC_GDMA_SUPPORT_CRC: async_crc
:SOC_DMA2D_SUPPORTED: async_color_convert
:SOC_ASYNC_MEMCPY_SUPPORTED: async_memcpy
:SOC_BITSCRAMBLER_SUPPORTED: bitscrambler
:SOC_MIPI_CSI_SUPPORTED: camera_driver
:SOC_CLK_TREE_SUPPORTED: clk_tree
:SOC_CORDIC_SUPPORTED: cordic
:SOC_DAC_SUPPORTED: dac
:SOC_ECDSA_SUPPORTED: ecdsa
:SOC_ETM_SUPPORTED: etm
gpio
:SOC_GPTIMER_SUPPORTED: gptimer
:SOC_DEDICATED_GPIO_SUPPORTED: dedic_gpio
:SOC_HMAC_SUPPORTED: hmac
: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
:SOC_KEY_MANAGER_SUPPORTED: key_manager
lcd/index
:SOC_GP_LDO_SUPPORTED: ldo_regulator
:SOC_LEDC_SUPPORTED: ledc
:SOC_MCPWM_SUPPORTED: mcpwm
:SOC_PARLIO_SUPPORTED: parlio/index
:SOC_PCNT_SUPPORTED: pcnt
:SOC_PPA_SUPPORTED: ppa
:SOC_RMT_SUPPORTED: rmt
:SOC_SDMMC_HOST_SUPPORTED or SOC_SDIO_SLAVE_SUPPORTED: sd_pullup_requirements
:SOC_SDMMC_HOST_SUPPORTED: sdmmc_host
:SOC_GPSPI_SUPPORTED: sdspi_host
:SOC_SDIO_SLAVE_SUPPORTED: sdio_slave
:SOC_SDM_SUPPORTED: sdm
:SOC_SPI_FLASH_SUPPORTED: spi_flash/index
:SOC_GPSPI_SUPPORTED: spi_master
:SOC_GPSPI_SUPPORTED: spi_slave
:SOC_SPI_SUPPORT_SLAVE_HD_VER2: spi_slave_hd
:SOC_LP_I2S_SUPPORTED: lp_i2s
:SOC_LP_VAD_SUPPORTED: vad
:SOC_TEMP_SENSOR_SUPPORTED: temp_sensor
:SOC_TOUCH_SENSOR_SUPPORTED: cap_touch_sens
:SOC_TWAI_SUPPORTED: twai
:SOC_UART_SUPPORTED: uart
:SOC_USB_OTG_SUPPORTED: USB Device Stack <https://docs.espressif.com/projects/esp-usb/en/latest/{IDF_TARGET_PATH_NAME}/usb_device.html>
:SOC_USB_OTG_SUPPORTED: USB Host <https://docs.espressif.com/projects/esp-usb/en/latest/{IDF_TARGET_PATH_NAME}/usb_host.html>
Code examples for this API section are provided in the :example:`peripherals` directory of ESP-IDF examples.
+982
View File
@@ -0,0 +1,982 @@
Image Signal Processor (ISP)
============================
:link_to_translation:`zh_CN:[中文]`
Introduction
------------
{IDF_TARGET_NAME} includes an Image Signal Processor (ISP), which is a feature pipeline that consists of many image processing algorithms. ISP receives image data from the DVP camera or MIPI-CSI camera, or system memory, and writes the processed image data to the system memory through DMA. The ISP is designed to work with other camera controller modules and can not operate independently.
Terminology
-----------
.. list::
- MIPI-CSI: Camera serial interface, a high-speed serial interface for cameras compliant with MIPI specifications
- DVP: Digital video parallel interface, generally composed of vsync, hsync, de, and data signals
- RAW: Unprocessed data directly output from an image sensor, typically divided into R, Gr, Gb, and B four channels classified into RAW8, RAW10, RAW12, etc., based on bit width
- RGB: Colored image format composed of red, green, and blue colors classified into RGB888, RGB565, etc., based on the bit width of each color
- YUV: Colored image format composed of luminance and chrominance classified into YUV444, YUV422, YUV420, etc., based on the data arrangement
- AF: Auto focus
- AWB: Auto white balance
- AE: Auto exposure
- HIST: Histogram
- BF: Bayer noise filter
- BLC: Black Level Correction
- LSC: Lens Shading Correction
- CCM: Color correction matrix
ISP Pipeline
------------
.. blockdiag::
:scale: 100%
:caption: ISP Pipeline
:align: center
blockdiag isp_pipeline {
orientation = portrait;
node_height = 30;
node_width = 120;
span_width = 100;
default_fontsize = 16;
isp_header [label = "ISP Header"];
isp_tail [label = "ISP Tail"];
isp_chs [label = "Contrast &\n Hue & Saturation", width = 150, height = 70];
isp_yuv [label = "YUV Limit\n YUB2RGB", width = 120, height = 70];
isp_header -> BLC -> BF -> LSC -> Demosaic -> WBG -> CCM -> Gamma -> RGB2YUV -> SHARP -> isp_chs -> isp_yuv -> CROP -> isp_tail;
LSC -> HIST
Demosaic -> WBG
Demosaic -> AWB
Demosaic -> AE
Demosaic -> HIST
WBG -> AWB
Gamma -> AE
RGB2YUV -> HIST
RGB2YUV -> AF
}
Functional Overview
-------------------
The ISP driver offers following services:
- :ref:`isp-resource-allocation` - covers how to allocate ISP resources with properly set of configurations. It also covers how to recycle the resources when they finished working.
- :ref:`isp-enable-disable` - covers how to enable and disable an ISP processor.
- :ref:`isp-af-statistics` - covers how to get AF statistics one-shot or continuously.
- :ref:`isp-awb-statistics` - covers how to get AWB white patches statistics one-shot or continuously.
- :ref:`isp-ae-statistics` - covers how to get AE statistics one-shot or continuously.
- :ref:`isp-hist-statistics` - covers how to get histogram statistics one-shot or continuously.
- :ref:`isp-bf` - covers how to enable and configure BF function.
- :ref:`isp-blc` - covers how to enable and configure BLC function.
- :ref:`isp-lsc` - covers how to enable and configure LSC function.
- :ref:`isp-ccm-config` - covers how to configure the CCM.
- :ref:`isp-demosaic` - covers how to configure the Demosaic function.
- :ref:`isp-gamma-correction` - covers how to enable and configure gamma correction.
- :ref:`isp-sharpen` - covers how to configure the sharpening function.
- :ref:`isp-crop` - covers how to enable and configure image cropping function.
- :ref:`isp-callback` - covers how to hook user specific code to ISP driver event callback function.
- :ref:`isp-thread-safety` - lists which APIs are guaranteed to be thread safe by the driver.
- :ref:`isp-kconfig-options` - lists the supported Kconfig options that can bring different effects to the driver.
- :ref:`isp-iram-safe` - describes tips on how to make the ISP interrupt and control functions work better along with a disabled cache.
.. _isp-resource-allocation:
Resource Allocation
^^^^^^^^^^^^^^^^^^^
Install ISP Driver
~~~~~~~~~~~~~~~~~~
ISP driver requires the configuration that specified by :cpp:type:`esp_isp_processor_cfg_t`.
If the configurations in :cpp:type:`esp_isp_processor_cfg_t` is specified, users can call :cpp:func:`esp_isp_new_processor` to allocate and initialize an ISP processor. This function will return an ISP processor handle if it runs correctly. You can take following code as reference:
.. code-block:: c
esp_isp_processor_cfg_t isp_config = {
.clk_src = ISP_CLK_SRC_DEFAULT,
...
};
isp_proc_handle_t isp_proc = NULL;
ESP_ERROR_CHECK(esp_isp_new_processor(&isp_config, &isp_proc));
You can use the created handle to enable/disable the ISP driver and do other ISP module installation.
.. note::
ISP peripheral is necessary if MIPI CSI or ISP_DVP is used as camera controller. This means that even if ISP functions are not needed, you still need to install the ISP driver by calling :cpp:func:`esp_isp_new_processor`.
If ISP functions are not needed, ISP driver supports bypassing ISP pipelines and enabling only the necessary functions. This can be achieved by setting :cpp:member:`esp_isp_processor_cfg_t::bypass_isp`.
Install ISP Auto Focus (AF) Driver
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ISP auto focus (AF) driver requires the configuration that specified by :cpp:type:`esp_isp_af_config_t`.
If the configurations in :cpp:type:`esp_isp_af_config_t` is specified, users can call :cpp:func:`esp_isp_new_af_controller` to allocate and initialize an ISP AF controller. This function will return an ISP AF controller handle if it runs correctly. You can take following code as reference:
.. code-block:: c
esp_isp_af_config_t af_config = {
.edge_thresh = 128,
};
isp_af_ctlr_t af_ctrlr = NULL;
ESP_ERROR_CHECK(esp_isp_new_af_controller(isp_proc, &af_config, &af_ctrlr));
You can use the created handle to enable/disable the ISP AF driver and install ISP AF environment detector module.
Install ISP Auto White Balance (AWB) Driver
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ISP auto white balance (AWB) driver requires the configuration specified by :cpp:type:`esp_isp_awb_config_t`.
If an :cpp:type:`esp_isp_awb_config_t` configuration is specified, you can call :cpp:func:`esp_isp_new_awb_controller` to allocate and initialize an ISP AWB controller. This function will return an ISP AWB controller handle on success. You can take following code as reference:
.. code-block:: c
isp_awb_ctlr_t awb_ctlr = NULL;
uint32_t image_width = 800;
uint32_t image_height = 600;
/* The AWB configuration, please refer to the API comment for how to tune these parameters */
esp_isp_awb_config_t awb_config = {
.sample_point = ISP_AWB_SAMPLE_POINT_1,
...
};
ESP_ERROR_CHECK(esp_isp_new_awb_controller(isp_proc, &awb_config, &awb_ctlr));
The AWB handle created in this step is required by other AWB APIs and AWB scheme.
Install ISP Auto Exposure (AE) Driver
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ISP auto exposure (AE) driver requires the configuration that specified by :cpp:type:`esp_isp_ae_config_t`.
If the configurations in :cpp:type:`esp_isp_ae_config_t` is specified, call :cpp:func:`esp_isp_new_ae_controller` to allocate and initialize an ISP AE controller. This function will return an ISP AE controller handle if it runs correctly. You can take following code as reference.
.. code-block:: c
esp_isp_ae_config_t ae_config = {
.sample_point = ISP_AE_SAMPLE_POINT_0,
...
};
isp_ae_ctlr_t ae_ctlr = NULL;
ESP_ERROR_CHECK(esp_isp_new_ae_controller(isp_proc, &ae_config, &ae_ctlr));
You can use the created handle to enable/disable the ISP AE driver and do ISP AE environment detector setup.
Install ISP Histogram (HIST) Driver
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ISP histogram (HIST) driver requires the configuration that specified by :cpp:type:`esp_isp_hist_config_t`.
If the configurations in :cpp:type:`esp_isp_hist_config_t` is specified, users can call :cpp:func:`esp_isp_new_hist_controller` to allocate and initialize an ISP Histogram controller. This function will return an ISP HIST controller handle if it runs correctly. You can take following code as reference.
.. list::
- The sum of all subwindow weights' decimal values should be 256; otherwise, the statistics will be small. The integer value should be 0.
- The sum of all RGB coefficients' decimal values should be 256; otherwise, the statistics will be small. The integer value should be 0.
- The segment_threshold must be 0255 and in order.
.. code:: c
esp_isp_hist_config_t hist_cfg = {
.segment_threshold = {16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240},
.hist_mode = ISP_HIST_SAMPLING_RGB,
.rgb_coefficient.coeff_r = {
.integer = 0,
.decimal = 86,
},
.rgb_coefficient.coeff_g = {
.integer = 0,
.decimal = 85,
},
.rgb_coefficient.coeff_b = {
.integer = 0,
.decimal = 85,
},
.window_weight = {
{{16, 0}}, {{10, 0}}, {{10, 0}}, {{10, 0}}, {{10, 0}},
{{10, 0}}, {{10, 0}}, {{10, 0}}, {{10, 0}}, {{10, 0}},
{{10, 0}}, {{10, 0}}, {{10, 0}}, {{10, 0}}, {{10, 0}},
{{10, 0}}, {{10, 0}}, {{10, 0}}, {{10, 0}}, {{10, 0}},
{{10, 0}}, {{10, 0}}, {{10, 0}}, {{10, 0}}, {{10, 0}},
},
};
isp_hist_ctlr_t hist_ctlr_ctlr = NULL;
ESP_ERROR_CHECK(esp_isp_new_hist_controller(isp_proc, &hist_config, &hist_ctlr));
You can use the created handle to enable/disable the ISP HIST driver setup.
Uninstall ISP Drivers
~~~~~~~~~~~~~~~~~~~~~~~
If previously installed ISP drivers are no longer needed, it's recommended to recycle the resource by following APIs to release the underlying hardware:
.. list::
- :cpp:func:`esp_isp_del_processor`, for ISP processor.
- :cpp:func:`esp_isp_del_af_controller`, for ISP AF controller.
- :cpp:func:`esp_isp_del_awb_controller`, for ISP AWB controller.
- :cpp:func:`esp_isp_del_ae_controller`, for ISP AE controller.
- :cpp:func:`esp_isp_del_hist_controller`, for ISP Histogram controller.
.. _isp-enable-disable:
Enable and Disable ISP
^^^^^^^^^^^^^^^^^^^^^^
ISP
~~~
Before doing ISP pipeline, you need to enable the ISP processor first, by calling :cpp:func:`esp_isp_enable`. This function:
* Switches the driver state from **init** to **enable**.
Calling :cpp:func:`esp_isp_disable` does the opposite, that is, put the driver back to the **init** state.
ISP AF Controller
~~~~~~~~~~~~~~~~~
Before doing ISP AF, you need to enable the ISP AF controller first, by calling :cpp:func:`esp_isp_af_controller_enable`. This function:
* Switches the driver state from **init** to **enable**.
Calling :cpp:func:`esp_isp_af_controller_disable` does the opposite, that is, put the driver back to the **init** state.
.. _isp-af-statistics:
AF One-shot and Continuous Statistics
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Calling :cpp:func:`esp_isp_af_controller_get_oneshot_statistics` to get one-shot AF statistics result. You can take following code as reference.
Aside from the above one-shot API, the ISP AF driver also provides a way to start AF statistics continuously. Calling :cpp:func:`esp_isp_af_controller_start_continuous_statistics` to start the continuous statistics and :cpp:func:`esp_isp_af_controller_stop_continuous_statistics` to stop it.
Note that if you want to use the continuous statistics, you need to register the :cpp:member:`esp_isp_af_env_detector_evt_cbs_t::on_env_statistics_done` or :cpp:member:`esp_isp_af_env_detector_evt_cbs_t::on_env_change` callbacks to get the statistics result. See how to register in :ref:`isp-callback`.
.. note::
When you use the continuous statistics, AF Environment Detector will be invalid.
.. code:: c
esp_isp_af_config_t af_config = {
.edge_thresh = 128,
};
isp_af_ctlr_t af_ctrlr = NULL;
ESP_ERROR_CHECK(esp_isp_new_af_controller(isp_proc, &af_config, &af_ctrlr));
ESP_ERROR_CHECK(esp_isp_af_controller_enable(af_ctrlr));
isp_af_result_t result = {};
/* Trigger the AF statistics and get its result for one time with timeout value 2000 ms */
ESP_ERROR_CHECK(esp_isp_af_controller_get_oneshot_statistics(af_ctrlr, 2000, &result));
/* Start continuous AF statistics */
ESP_ERROR_CHECK(esp_isp_af_controller_start_continuous_statistics(af_ctrlr));
// You can do other stuffs here, the statistics result can be obtained in the callback
// ......
// vTaskDelay(pdMS_TO_TICKS(1000));
/* Stop continuous AF statistics */
ESP_ERROR_CHECK(esp_isp_af_controller_stop_continuous_statistics(af_ctrlr));
/* Disable the AF controller */
ESP_ERROR_CHECK(esp_isp_af_controller_disable(af_ctrlr));
/* Delete the AF controller and free the resources */
ESP_ERROR_CHECK(esp_isp_del_af_controller(af_ctrlr));
Set AF Environment Detector
^^^^^^^^^^^^^^^^^^^^^^^^^^^
Calling :cpp:func:`esp_isp_af_controller_set_env_detector` to set an ISP AF environment detector. You can take following code as reference:
.. code-block:: c
esp_isp_af_env_config_t env_config = {
.interval = 10,
};
isp_af_ctlr_t af_ctrlr = NULL;
ESP_ERROR_CHECK(esp_isp_new_af_controller(isp_proc, &af_config, &af_ctrlr));
ESP_ERROR_CHECK(esp_isp_af_controller_set_env_detector(af_ctrlr, &env_config));
Set AF Environment Detector Threshold
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Calling :cpp:func:`esp_isp_af_controller_set_env_detector_threshold` to set the threshold of an ISP AF environment detector.
.. code-block:: c
int definition_thresh = 0;
int luminance_thresh = 0;
ESP_ERROR_CHECK(esp_isp_af_env_detector_set_threshold(env_detector, definition_thresh, luminance_thresh));
ISP AWB Controller
~~~~~~~~~~~~~~~~~~
Before doing ISP AWB, you need to enable the ISP AWB controller first, by calling :cpp:func:`esp_isp_awb_controller_enable`. This function:
* Switches the driver state from **init** to **enable**.
Calling :cpp:func:`esp_isp_awb_controller_disable` does the opposite, that is, put the driver back to the **init** state.
.. _isp-awb-statistics:
AWB One-shot and Continuous Statistics
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Calling :cpp:func:`esp_isp_awb_controller_get_oneshot_statistics` to get oneshot AWB statistics result of white patches. You can take following code as reference.
Aside from the above one-shot API, the ISP AWB driver also provides a way to start AWB statistics continuously. Calling :cpp:func:`esp_isp_awb_controller_start_continuous_statistics` starts the continuous statistics and :cpp:func:`esp_isp_awb_controller_stop_continuous_statistics` stops it.
Note that if you want to use the continuous statistics, you need to register the :cpp:member:`esp_isp_awb_cbs_t::on_statistics_done` callback to get the statistics result. See how to register it in :ref:`isp-callback`.
.. code-block:: c
bool example_isp_awb_on_statistics_done_cb(isp_awb_ctlr_t awb_ctlr, const esp_isp_awb_evt_data_t *edata, void *user_data);
// ...
isp_awb_ctlr_t awb_ctlr = NULL;
uint32_t image_width = 800;
uint32_t image_height = 600;
/* The AWB configuration, please refer to the API comment for how to tune these parameters */
esp_isp_awb_config_t awb_config = {
.sample_point = ISP_AWB_SAMPLE_POINT_1,
...
};
isp_awb_stat_result_t stat_res = {};
/* Create the AWB controller */
ESP_ERROR_CHECK(esp_isp_new_awb_controller(isp_proc, &awb_config, &awb_ctlr));
/* Register the AWB callback */
esp_isp_awb_cbs_t awb_cb = {
.on_statistics_done = example_isp_awb_on_statistics_done_cb,
};
ESP_ERROR_CHECK(esp_isp_awb_register_event_callbacks(awb_ctlr, &awb_cb, NULL));
/* Enable the AWB controller */
ESP_ERROR_CHECK(esp_isp_awb_controller_enable(awb_ctlr));
/* Get one-shot AWB statistics result */
ESP_ERROR_CHECK(esp_isp_awb_controller_get_oneshot_statistics(awb_ctlr, -1, &stat_res));
/* Start continuous AWB statistics, note that continuous statistics requires `on_statistics_done` callback */
ESP_ERROR_CHECK(esp_isp_awb_controller_start_continuous_statistics(awb_ctlr));
// You can do other stuffs here, the statistics result can be obtained in the callback
// ......
// vTaskDelay(pdMS_TO_TICKS(1000));
/* Stop continuous AWB statistics */
ESP_ERROR_CHECK(esp_isp_awb_controller_stop_continuous_statistics(awb_ctlr));
/* Disable the AWB controller */
ESP_ERROR_CHECK(esp_isp_awb_controller_disable(awb_ctlr));
/* Delete the AWB controller and free the resources */
ESP_ERROR_CHECK(esp_isp_del_awb_controller(awb_ctlr));
ISP AE Controller
~~~~~~~~~~~~~~~~~
Before doing ISP AE, you need to enable the ISP AE controller first, by calling :cpp:func:`esp_isp_ae_controller_enable`. This function:
* Switches the driver state from **init** to **enable**.
Calling :cpp:func:`esp_isp_ae_controller_disable` does the opposite, that is, put the driver back to the **init** state.
.. _isp-ae-statistics:
AE One-shot and Continuous Statistics
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Calling :cpp:func:`esp_isp_ae_controller_get_oneshot_statistics` to get oneshot AE statistics result. You can take following code as reference.
When using AE oneshot statistics, the AE continuous mode need to be disabled otherwise the result may be overwritten by the environment detector. After oneshot operation finishes, you need to restart continuous mode again.
Aside from the above oneshot API, the ISP AE driver also provides a way to start AE statistics continuously. Calling :cpp:func:`esp_isp_ae_controller_start_continuous_statistics` to start the continuous statistics and :cpp:func:`esp_isp_ae_controller_stop_continuous_statistics` to stop it.
Note that if you want to use the continuous statistics, you need to register the :cpp:member:`esp_isp_ae_env_detector_evt_cbs_t::on_env_statistics_done` or :cpp:member:`esp_isp_ae_env_detector_evt_cbs_t::on_env_change` callback to get the statistics result. See how to register in :ref:`isp-callback`.
.. note::
When using oneshot statistics, the AE environment detector will be temporarily disabled and will automatically recover once the oneshot is completed.
.. code-block:: c
esp_isp_ae_config_t ae_config = {
.sample_point = ISP_AE_SAMPLE_POINT_0,
};
isp_ae_ctlr_t ae_ctlr = NULL;
ESP_ERROR_CHECK(esp_isp_new_ae_controller(isp_proc, &ae_config, &ae_ctlr));
ESP_ERROR_CHECK(esp_isp_ae_controller_enable(ae_ctlr));
isp_ae_result_t result = {};
/* Trigger the AE statistics and get its result for one time with timeout value 2000 ms. */
ESP_ERROR_CHECK(esp_isp_ae_controller_get_oneshot_statistics(ae_ctlr, 2000, &result));
/* Start continuous AE statistics */
ESP_ERROR_CHECK(esp_isp_ae_controller_start_continuous_statistics(ae_ctlr));
// You can do other stuffs here, the statistics result can be obtained in the callback
// ......
// vTaskDelay(pdMS_TO_TICKS(1000));
/* Stop continuous AE statistics */
ESP_ERROR_CHECK(esp_isp_ae_controller_stop_continuous_statistics(ae_ctlr));
/* Disable the AE controller */
ESP_ERROR_CHECK(esp_isp_ae_controller_disable(ae_ctlr));
/* Delete the AE controller and free the resources */
ESP_ERROR_CHECK(esp_isp_del_ae_controller(ae_ctlr));
Set AE Environment Detector
^^^^^^^^^^^^^^^^^^^^^^^^^^^
Calling :cpp:func:`esp_isp_ae_controller_set_env_detector` to set an ISP AE environment detector. You can take following code as reference.
.. code:: c
esp_isp_ae_env_config_t env_config = {
.interval = 10,
};
ESP_ERROR_CHECK(esp_isp_ae_controller_set_env_detector(ae_ctlr, &env_config));
Set AE Environment Detector Threshold
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Calling :cpp:func:`esp_isp_ae_controller_set_env_detector_threshold` to set the thresholds (1-255) of an ISP AE environment detector.
.. code:: c
esp_isp_ae_env_thresh_t env_thresh = {
.low_thresh = 110,
.high_thresh = 130,
};
ESP_ERROR_CHECK(esp_isp_ae_controller_set_env_detector_threshold(ae_ctlr, env_thresh));
.. _isp-hist:
ISP Histogram Controller
~~~~~~~~~~~~~~~~~~~~~~~~
Before doing ISP histogram statistics, you need to enable the ISP histogram controller first, by calling :cpp:func:`esp_isp_hist_controller_enable`. This function:
* Switches the driver state from **init** to **enable**.
Calling :cpp:func:`esp_isp_hist_controller_disable` does the opposite, that is, put the driver back to the **init** state.
.. _isp-hist-statistics:
Histogram One-shot and Continuous Statistics
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Calling :cpp:func:`esp_isp_hist_controller_get_oneshot_statistics` to get oneshot histogram statistics result. You can take following code as reference.
Aside from the above oneshot API, the ISP histogram driver also provides a way to start histogram statistics continuously. Calling :cpp:func:`esp_isp_hist_controller_start_continuous_statistics` starts the continuous statistics and :cpp:func:`esp_isp_hist_controller_stop_continuous_statistics` stops it.
Note that if you want to use the continuous statistics, you need to register the :cpp:member:`esp_isp_hist_cbs_t::on_statistics_done` callback to get the statistics result. See how to register it in :ref:`isp-callback`.
.. code:: c
static bool s_hist_scheme_on_statistics_done_callback(isp_hist_ctlr_t awb_ctrlr, const esp_isp_hist_evt_data_t *edata, void *user_data)
{
for(int i = 0; i < 16; i++) {
esp_rom_printf(DRAM_STR("val %d is %x\n"), i, edata->hist_result.hist_value[i]); // get the histogram statistic value
}
return true;
}
esp_isp_hist_cbs_t hist_cbs = {
.on_statistics_done = s_hist_scheme_on_statistics_done_callback,
};
esp_isp_hist_register_event_callbacks(hist_ctlr, &hist_cbs, hist_ctlr);
esp_isp_hist_controller_enable(hist_ctlr);
.. _isp-bf:
ISP BF Controller
~~~~~~~~~~~~~~~~~
This pipeline is used for doing image input denoising under bayer mode.
Calling :cpp:func:`esp_isp_bf_configure` to configure BF function, you can take following code as reference.
.. code-block:: c
esp_isp_bf_config_t bf_config = {
.denoising_level = 5,
.bf_template = {
{1, 2, 1},
{2, 4, 2},
{1, 2, 1},
},
...
};
ESP_ERROR_CHECK(esp_isp_bf_configure(isp_proc, &bf_config));
ESP_ERROR_CHECK(esp_isp_bf_enable(isp_proc));
:cpp:member:`esp_isp_bf_config_t::bf_template` is used for bayer denoise. You can set the :cpp:member:`esp_isp_bf_config_t::bf_template` with a Gaussian filter template or an average filter template.
After calling :cpp:func:`esp_isp_bf_configure`, you need to enable the ISP BF controller, by calling :cpp:func:`esp_isp_bf_enable`. This function:
* Switches the driver state from **init** to **enable**.
Calling :cpp:func:`esp_isp_bf_disable` does the opposite, that is, put the driver back to the **init** state.
.. _isp-blc:
ISP BLC Controller
^^^^^^^^^^^^^^^^^^
Black Level Correction (BLC) aims for the issues caused by the uneven black level of the image.
Calling :cpp:func:`esp_isp_blc_configure` to configure the BLC module to do the correction.
.. code-block:: c
esp_isp_blc_config_t blc_config = {
.window = {
.top_left = {
.x = 0,
.y = 0,
},
.btm_right = {
.x = CONFIG_EXAMPLE_MIPI_CSI_DISP_HRES,
.y = CONFIG_EXAMPLE_MIPI_CSI_DISP_VRES,
},
},
.filter_enable = true,
.filter_threshold = {
.top_left_chan_thresh = 128,
.top_right_chan_thresh = 128,
.bottom_left_chan_thresh = 128,
.bottom_right_chan_thresh = 128,
},
.stretch = {
.top_left_chan_stretch_en = true,
.top_right_chan_stretch_en = true,
.bottom_left_chan_stretch_en = true,
.bottom_right_chan_stretch_en = true,
},
};
ESP_ERROR_CHECK(esp_isp_blc_configure(isp_proc, &blc_config));
ESP_ERROR_CHECK(esp_isp_blc_enable(isp_proc));
After calling :cpp:func:`esp_isp_blc_configure`, you need to enable the ISP BLC controller by calling :cpp:func:`esp_isp_blc_enable`. This function:
* Switches the driver state from **init** to **enable**.
Calling :cpp:func:`esp_isp_blc_disable` does the opposite, that is, put the driver back to the **init** state.
Calling :cpp:func:`esp_isp_blc_set_correction_offset` to set the BLC correction offset.
.. code-block:: c
esp_isp_blc_offset_t blc_offset = {
.top_left_chan_offset = 20,
.top_right_chan_offset = 20,
.bottom_left_chan_offset = 20,
.bottom_right_chan_offset = 20,
};
ESP_ERROR_CHECK(esp_isp_blc_set_correction_offset(isp_proc, &blc_offset));
.. _isp-lsc:
ISP LSC Controller
~~~~~~~~~~~~~~~~~~
Lens Shading Correction (LSC) aims for the issues caused by the uneven refraction of light through the camera lens.
Calling :cpp:func:`esp_isp_lsc_configure` to configure the LSC module to do the correction. The :cpp:type:`esp_isp_lsc_gain_array_t` is necessary for the hardware to do the correction related calculation. :cpp:func:`esp_isp_lsc_allocate_gain_array` is a helper function to help allocate proper size of memory for the gains.
.. code-block:: c
esp_isp_lsc_gain_array_t gain_array = {};
size_t gain_size = 0;
ESP_ERROR_CHECK(esp_isp_lsc_allocate_gain_array(isp_proc, &gain_array, &gain_size));
esp_isp_lsc_config_t lsc_config = {
.gain_array = &gain_array,
};
isp_lsc_gain_t gain_val = {
.decimal = 204,
.integer = 0,
};
for (int i = 0; i < gain_size; i++) {
gain_array.gain_r[i].val = gain_val.val;
gain_array.gain_gr[i].val = gain_val.val;
gain_array.gain_gb[i].val = gain_val.val;
gain_array.gain_b[i].val = gain_val.val;
}
ESP_ERROR_CHECK(esp_isp_lsc_configure(isp_proc, &lsc_config));
After calling :cpp:func:`esp_isp_lsc_configure`, you need to enable the ISP LSC controller by calling :cpp:func:`esp_isp_lsc_enable`. The LSC can be disabled by calling :cpp:func:`esp_isp_lsc_disable`. It is allowed to call :cpp:func:`esp_isp_lsc_configure` when the LSC is not enabled, but the LSC function will only take effect when it is enabled.
.. _isp-color:
ISP Color Controller
~~~~~~~~~~~~~~~~~~~~
This pipeline is used to adjust the image contrast, saturation, hue and brightness.
Calling :cpp:func:`esp_isp_color_configure` to configure color function, you can take following code as reference.
{IDF_TARGET_SOC_ISP_COLOR_CONTRAST_MAX:default="1.0", esp32p4="1.0"}
{IDF_TARGET_SOC_ISP_COLOR_CONTRAST_DEFAULT:default="1.0", esp32p4="1.0"}
{IDF_TARGET_SOC_ISP_COLOR_SATURATION_MAX:default="1.0", esp32p4="1.0"}
{IDF_TARGET_SOC_ISP_COLOR_SATURATION_DEFAULT:default="1.0", esp32p4="1.0"}
{IDF_TARGET_SOC_ISP_COLOR_HUE_MAX:default="359", esp32p4="359"}
{IDF_TARGET_SOC_ISP_COLOR_HUE_DEFAULT:default="0", esp32p4="0"}
{IDF_TARGET_SOC_ISP_COLOR_BRIGHTNESS_MIN:default="-127", esp32p4="-127"}
{IDF_TARGET_SOC_ISP_COLOR_BRIGHTNESS_MAX:default="128", esp32p4="128"}
{IDF_TARGET_SOC_ISP_COLOR_BRIGHTNESS_DEFAULT:default="0", esp32p4="0"}
.. list::
- Contrast value should be 0 ~ {IDF_TARGET_SOC_ISP_COLOR_CONTRAST_MAX}, default {IDF_TARGET_SOC_ISP_COLOR_CONTRAST_DEFAULT}
- Saturation value should be 0 ~ {IDF_TARGET_SOC_ISP_COLOR_SATURATION_MAX}, default {IDF_TARGET_SOC_ISP_COLOR_SATURATION_DEFAULT}
- Hue value should be 0 ~ {IDF_TARGET_SOC_ISP_COLOR_HUE_MAX}, default {IDF_TARGET_SOC_ISP_COLOR_HUE_DEFAULT}
- Brightness value should be {IDF_TARGET_SOC_ISP_COLOR_BRIGHTNESS_MIN} ~ {IDF_TARGET_SOC_ISP_COLOR_BRIGHTNESS_MAX}, default {IDF_TARGET_SOC_ISP_COLOR_BRIGHTNESS_DEFAULT}
.. code:: c
esp_isp_color_config_t color_config = {
.color_contrast = {
.integer = 1,
.decimal = 0,
},
.color_saturation = {
.integer = 1,
.decimal = 0,
},
.color_hue = 0,
.color_brightness = 0,
};
ESP_ERROR_CHECK(esp_isp_color_configure(isp_proc, &color_config));
ESP_ERROR_CHECK(esp_isp_color_enable(isp_proc));
After calling :cpp:func:`esp_isp_color_configure`, you need to enable the ISP color controller, by calling :cpp:func:`esp_isp_color_enable`. This function:
* Switches the driver state from **init** to **enable**.
Calling :cpp:func:`esp_isp_color_disable` does the opposite, that is, put the driver back to the **init** state.
.. note::
When the ISP DVP peripheral is used with the output color format set to the RGB color space, :ref:`isp-color` is automatically enabled in the camera driver to ensure correct data output. The function :cpp:func:`esp_isp_color_disable` should never be called in this case, otherwise it may result in disarrayed camera data.
.. _isp-ccm-config:
Configure CCM
^^^^^^^^^^^^^
Color correction matrix can scale the color ratio of RGB888 pixels. It can be used for adjusting the image color via some algorithms, for example, used for white balance by inputting the AWB computed result, or used as a filter with some filter algorithms.
To adjust the color correction matrix, here is the formula:
.. code-block:: none
[ R' ] [ RR RG RB ] [ R ]
[ G' ] = [ GR GG GB ] * [ G ]
[ B' ] [ BR BG BB ] [ B ]
, and you can refer to the following code:
.. code-block:: c
// ...
// Configure CCM
esp_isp_ccm_config_t ccm_cfg = {
.matrix = {
1.0, 0.0, 0.0,
0.0, 1.0, 0.0,
0.0, 0.0, 1.0
},
.saturation = false,
...
};
ESP_ERROR_CHECK(esp_isp_ccm_configure(isp_proc, &ccm_cfg));
// The configured CCM will be applied to the image once the CCM module is enabled
ESP_ERROR_CHECK(esp_isp_ccm_enable(isp_proc));
// CCM can also be configured after it is enabled
ccm_cfg.matrix[0][0] = 2.0;
ESP_ERROR_CHECK(esp_isp_ccm_configure(isp_proc, &ccm_cfg));
// Disable CCM if no longer needed
ESP_ERROR_CHECK(esp_isp_ccm_disable(isp_proc));
.. _isp-demosaic:
ISP Demosaic Controller
~~~~~~~~~~~~~~~~~~~~~~~
This pipeline is used for doing image demosaic algorithm to convert RAW image to RGB mode.
Calling :cpp:func:`esp_isp_demosaic_configure` to configure Demosaic function, you can take following code as reference.
.. code:: c
esp_isp_demosaic_config_t demosaic_config = {
.grad_ratio = {
.integer = 2,
.decimal = 5,
},
...
};
ESP_ERROR_CHECK(esp_isp_demosaic_configure(isp_proc, &demosaic_config));
ESP_ERROR_CHECK(esp_isp_demosaic_enable(isp_proc));
After calling :cpp:func:`esp_isp_demosaic_configure`, you need to enable the ISP Demosaic controller, by calling :cpp:func:`esp_isp_demosaic_enable`. This function:
* Switches the driver state from **init** to **enable**.
Calling :cpp:func:`esp_isp_demosaic_disable` does the opposite, that is, put the driver back to the **init** state.
:cpp:func:`esp_isp_demosaic_configure` is allowed to be called even if the driver is in **init** state, but the demosaic configurations will only be taken into effect when in **enable** state.
.. _isp-gamma-correction:
Enable Gamma Correction
^^^^^^^^^^^^^^^^^^^^^^^
The human visual system is non-linearly sensitive to the physical luminance. Adding gamma correction to the ISP pipeline to transform RGB coordinates into a space in which coordinates are proportional to subjective brightness.
The driver provides a helper API :cpp:func:`esp_isp_gamma_fill_curve_points` to fill :cpp:type:`isp_gamma_curve_points_t`, which is a group of points used to describe the gamma correction curve. Or you can manually declare the points as your desired gamma correction curve. Each R/G/B component can have its own gamma correction curve, you can set the configuration by calling :cpp:func:`esp_isp_gamma_configure`.
A typical code example is:
.. code:: c
#include <math.h>
// Set the camera gamma to be 0.7, so the gamma correction curve is y = 256 * (x / 256) ^ 0.7
static uint32_t s_gamma_curve(uint32_t x)
{
return pow((double)x / 256, 0.7) * 256;
}
isp_gamma_curve_points_t pts = {};
ESP_ERROR_CHECK(esp_isp_gamma_fill_curve_points(s_gamma_curve, &pts));
ESP_ERROR_CHECK(esp_isp_gamma_configure(isp_proc, COLOR_COMPONENT_R, &pts));
ESP_ERROR_CHECK(esp_isp_gamma_configure(isp_proc, COLOR_COMPONENT_G, &pts));
ESP_ERROR_CHECK(esp_isp_gamma_configure(isp_proc, COLOR_COMPONENT_B, &pts));
// Enable gamma module after curve parameters configured
ESP_ERROR_CHECK(esp_isp_gamma_enable(isp_proc));
// Disable gamma if no longer needed
ESP_ERROR_CHECK(esp_isp_gamma_disable(isp_proc));
.. _isp-sharpen:
ISP Sharpen Controller
~~~~~~~~~~~~~~~~~~~~~~
This pipeline is used for doing image input sharpening under YUV mode.
Calling :cpp:func:`esp_isp_sharpen_configure` to configure Sharpen function, you can take following code as reference.
.. code:: c
esp_isp_sharpen_config_t sharpen_config = {
.h_thresh = 255,
.sharpen_template = {
{1, 2, 1},
{2, 4, 2},
{1, 2, 1},
},
...
};
ESP_ERROR_CHECK(esp_isp_sharpen_configure(isp_proc, &sharpen_config));
ESP_ERROR_CHECK(esp_isp_sharpen_enable(isp_proc));
:cpp:member:`esp_isp_sharpen_config_t::sharpen_template` is used for sharpening. You can set the :cpp:member:`esp_isp_sharpen_config_t::sharpen_template` with a Gaussian filter template or an average filter template.
After calling :cpp:func:`esp_isp_sharpen_configure`, you need to enable the ISP Sharpen controller, by calling :cpp:func:`esp_isp_sharpen_enable`. This function:
* Switches the driver state from **init** to **enable**.
Calling :cpp:func:`esp_isp_sharpen_disable` does the opposite, that is, put the driver back to the **init** state.
:cpp:func:`esp_isp_sharpen_configure` is allowed to be called even if the driver is in **init** state, but the sharpen configurations will only be taken into effect when in **enable** state.
.. _isp-crop:
ISP Image Crop Controller
~~~~~~~~~~~~~~~~~~~~~~~~~
The ISP image crop function can extract a specified region from the original image, reducing the amount of data for subsequent processing and improving processing efficiency. The crop function is executed at the end of the ISP pipeline and can output a smaller region than the input image.
.. note::
The ISP image crop function is only available on ESP32-P4 revision 3.0 and above.
Calling :cpp:func:`esp_isp_crop_configure` to configure the image crop function, you can take the following code as reference:
.. code-block:: c
esp_isp_crop_config_t crop_config = {
.window = {
.top_left = {
.x = 100, // Top-left X coordinate of crop region
.y = 100, // Top-left Y coordinate of crop region
},
.btm_right = {
.x = 699, // Bottom-right X coordinate of crop region
.y = 499, // Bottom-right Y coordinate of crop region
}
}
};
ESP_ERROR_CHECK(esp_isp_crop_configure(isp_proc, &crop_config));
ESP_ERROR_CHECK(esp_isp_crop_enable(isp_proc));
After calling :cpp:func:`esp_isp_crop_configure`, you need to enable the ISP image crop controller by calling :cpp:func:`esp_isp_crop_enable`. This function:
* Switches the driver state from **init** to **enable**.
Calling :cpp:func:`esp_isp_crop_disable` does the opposite, that is, put the driver back to the **init** state.
:cpp:func:`esp_isp_crop_configure` is allowed to be called even if the driver is in **init** state, but the crop configurations will only be taken into effect when in **enable** state.
.. note::
- The top-left coordinates (top_left) of the crop region must be smaller than the bottom-right coordinates (btm_right)
- The top-left coordinates (top_left) of the crop region must be even, and the bottom-right coordinates (btm_right) must be odd
- The crop region cannot exceed the boundaries of the original image
- Adjust the display medium (such as LCD) size according to the cropped resolution to ensure complete display and avoid black borders or stretching.
.. _isp-callback:
Register Event Callbacks
^^^^^^^^^^^^^^^^^^^^^^^^
After an ISP module starts up, it can generate a specific event dynamically.
You can save your own context to callback function as well, via the parameter ``user_data``. The user data will be directly passed to the callback function.
.. note::
The below-mentioned callback functions are called within an ISR context. You must ensure that the functions do not attempt to block (e.g., by making sure that only FreeRTOS APIs with ``ISR`` suffix are called from within the function).
Register ISP Processor Event Callbacks
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
After the ISP processor is enabled, it can generate multiple events of multiple ISP submodules dynamically. You can hook your functions to the interrupt service routine by calling :cpp:func:`esp_isp_register_event_callbacks`. All supported event callbacks are listed in :cpp:type:`esp_isp_evt_cbs_t`:
- :cpp:member:`esp_isp_evt_cbs_t::on_sharpen_frame_done` sets a callback function for sharpen frame done. It will be called after the ISP sharpen submodule finishes its operation for one frame. The function prototype is declared in :cpp:type:`esp_isp_sharpen_callback_t`.
Register ISP AF Environment Detector Event Callbacks
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
After the ISP AF environment detector starts up, it can generate a specific event dynamically. If you have some functions that should be called when the event happens, please hook your function to the interrupt service routine by calling :cpp:func:`esp_isp_af_env_detector_register_event_callbacks`. All supported event callbacks are listed in :cpp:type:`esp_isp_af_env_detector_evt_cbs_t`:
- :cpp:member:`esp_isp_af_env_detector_evt_cbs_t::on_env_statistics_done` sets a callback function for environment statistics done. The function prototype is declared in :cpp:type:`esp_isp_af_env_detector_callback_t`.
- :cpp:member:`esp_isp_af_env_detector_evt_cbs_t::on_env_change` sets a callback function for environment change. The function prototype is declared in :cpp:type:`esp_isp_af_env_detector_callback_t`.
Register ISP AWB Statistics Done Event Callbacks
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
After the ISP AWB controller finished statistics of white patches, it can generate a specific event dynamically. If you want to be informed when the statistics done event takes place, please hook your function to the interrupt service routine by calling :cpp:func:`esp_isp_awb_register_event_callbacks`. All supported event callbacks are listed in :cpp:type:`esp_isp_awb_cbs_t`:
- :cpp:member:`esp_isp_awb_cbs_t::on_statistics_done` sets a callback function when finishing statistics of the white patches. The function prototype is declared in :cpp:type:`esp_isp_awb_callback_t`.
Register ISP AE Environment Detector Event Callbacks
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
After the ISP AE environment detector starts up, it can generate a specific event dynamically. If you have some functions that should be called when the event happens, please hook your function to the interrupt service routine by calling :cpp:func:`esp_isp_ae_env_detector_register_event_callbacks`. All supported event callbacks are listed in :cpp:type:`esp_isp_ae_env_detector_evt_cbs_t`:
- :cpp:member:`esp_isp_ae_env_detector_evt_cbs_t::on_env_statistics_done` sets a callback function for environment statistics done. The function prototype is declared in :cpp:type:`esp_isp_ae_env_detector_callback_t`.
- :cpp:member:`esp_isp_ae_env_detector_evt_cbs_t::on_env_change` sets a callback function for environment change. The function prototype is declared in :cpp:type:`esp_isp_ae_env_detector_callback_t`.
Register ISP HIST Statistics Done Event Callbacks
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
After the ISP HIST controller finished statistics of brightness, it can generate a specific event dynamically. If you want to be informed when the statistics done event takes place, please hook your function to the interrupt service routine by calling :cpp:func:`esp_isp_hist_register_event_callbacks`. All supported event callbacks are listed in :cpp:type:`esp_isp_hist_cbs_t`:
- :cpp:member:`esp_isp_hist_cbs_t::on_statistics_done` sets a callback function when finishing statistics of the brightness. The function prototype is declared in :cpp:type:`esp_isp_hist_callback_t`.
.. _isp-thread-safety:
Thread Safety
^^^^^^^^^^^^^
The following factory function are guaranteed to be thread safe by the driver:
.. list::
- :cpp:func:`esp_isp_new_processor`
- :cpp:func:`esp_isp_del_processor`
- :cpp:func:`esp_isp_new_af_controller`
- :cpp:func:`esp_isp_del_af_controller`
- :cpp:func:`esp_isp_new_awb_controller`
- :cpp:func:`esp_isp_del_awb_controller`
- :cpp:func:`esp_isp_new_ae_controller`
- :cpp:func:`esp_isp_del_ae_controller`
- :cpp:func:`esp_isp_new_hist_controller`
- :cpp:func:`esp_isp_del_hist_controller`
These functions can be called from different RTOS tasks without protection by extra locks. Other APIs are not guaranteed to be thread-safe.
.. _isp-kconfig-options:
Kconfig Options
^^^^^^^^^^^^^^^
- :ref:`CONFIG_ISP_ISR_IRAM_SAFE` controls whether the default ISR handler should be masked when the cache is disabled.
.. _isp-iram-safe:
IRAM Safe
^^^^^^^^^
By default, the ISP interrupt will be deferred when the cache is disabled because of writing or erasing the flash.
Kconfig option :ref:`CONFIG_ISP_ISR_IRAM_SAFE` will:
- Enable the interrupt being serviced even when the cache is disabled
- Place all functions that used by the ISR into IRAM
- Place driver object into DRAM (in case it is mapped to PSRAM by accident)
This allows the interrupt to run while the cache is disabled, but comes at the cost of increased IRAM consumption. With this option enabled, the ISR callbacks will be running when cache is disabled. Therefore you should make sure the callbacks and its involved context are IRAM-safe as well.
Kconfig option :ref:`CONFIG_ISP_CTRL_FUNC_IN_IRAM` will:
- Place some of the ISP control functions into IRAM, including:
.. list::
- :cpp:func:`esp_isp_sharpen_configure`
- :cpp:func:`esp_isp_demosaic_configure`
Application Examples
--------------------
* :example:`peripherals/isp/multi_pipelines` demonstrates how to use the ISP pipelines to process the image signals from camera sensors and display the video on LCD screen via DSI peripheral.
* `esp_video/examples <https://github.com/espressif/esp-video-components/tree/master/esp_video/examples>`_ provides some examples of enabling ISP control algorithms.
API Reference
-------------
.. include-build-file:: inc/isp.inc
.. include-build-file:: inc/isp_af.inc
.. include-build-file:: inc/isp_ae.inc
.. include-build-file:: inc/isp_awb.inc
.. include-build-file:: inc/isp_bf.inc
.. include-build-file:: inc/isp_blc.inc
.. include-build-file:: inc/isp_lsc.inc
.. include-build-file:: inc/isp_ccm.inc
.. include-build-file:: inc/isp_demosaic.inc
.. include-build-file:: inc/isp_sharpen.inc
.. include-build-file:: inc/isp_gamma.inc
.. include-build-file:: inc/isp_hist.inc
.. include-build-file:: inc/isp_color.inc
.. include-build-file:: inc/isp_crop.inc
.. include-build-file:: inc/isp_core.inc
.. include-build-file:: inc/components/esp_driver_isp/include/driver/isp_types.inc
.. include-build-file:: inc/components/esp_hal_cam/include/hal/isp_types.inc
+628
View File
@@ -0,0 +1,628 @@
JPEG Encoder and Decoder
========================
:link_to_translation:`zh_CN:[中文]`
Introduction
------------
JPEG is a commonly used method of lossy compression for digital images, particularly for those images produced by digital photography. The compression level varies with changes in image size and compression quality. JPEG typically achieves 10:1 compression with little perceptible loss in image quality.
JPEG codec on {IDF_TARGET_NAME} is an image codec, which is based on the JPEG baseline standard, for compressing and decompressing images to reduce the bandwidth required to transmit images or the space required to store images, making it possible to process large-resolution images. But please note, at one time, the codec engine can only work as either encoder or decoder.
For more hardware features of JPEG codec, please refer to the `JPEG codec <{IDF_TARGET_TRM_EN_URL}#jpegcodec>`__ section in {IDF_TARGET_NAME} Technical Reference Manual, for more details.
Functional Overview
-------------------
This document covers the following sections:
- :ref:`jpeg-resource-allocation` - covers how to allocate JPEG resources with properly set of configurations. It also covers how to recycle the resources when they finished working.
- :ref:`jpeg-finite-state-machine` - covers JPEG workflow. Introduce how jpeg driver uses internal resources and its software process.
- :ref:`jpeg-decoder-engine` - covers behavior of JPEG decoder engine. Introduce how to use decoder engine functions to decode an image (from jpg format to raw format).
- :ref:`jpeg-encoder-engine` - covers behavior of JPEG encoder engine. Introduce how to use encoder engine functions to encode an image (from raw format to jpg format).
- :ref:`jpeg-performance-overview` - covers encoder and decoder performance.
- :ref:`jpeg-pixel-storage-layout` - covers color space order overview required in this JPEG decoder and encoder.
- :ref:`jpeg-thread-safety` - lists which APIs are guaranteed to be thread safe by the driver.
- :ref:`jpeg-power-management` - describes how JPEG driver would be affected by power consumption.
- :ref:`jpeg-flash-encryption` - describes how to use the JPEG codec correctly when flash/PSRAM encryption is enabled.
- :ref:`jpeg-kconfig-options` - lists the supported Kconfig options that can bring different effects to the driver.
.. _jpeg-resource-allocation:
Resource Allocation
^^^^^^^^^^^^^^^^^^^
Install JPEG decoder engine
~~~~~~~~~~~~~~~~~~~~~~~~~~~
JPEG decoder engine requires the configuration that specified by :cpp:type:`jpeg_decode_engine_cfg_t`.
If the configurations in :cpp:type:`jpeg_decode_engine_cfg_t` is specified, users can call :cpp:func:`jpeg_new_decoder_engine` to allocate and initialize a JPEG decoder engine. This function will return an JPEG decoder handle if it runs correctly. You can take following code as reference.
.. code:: c
jpeg_decoder_handle_t decoder_engine;
jpeg_decode_engine_cfg_t decode_eng_cfg = {
.intr_priority = 0,
.timeout_ms = 40,
};
ESP_ERROR_CHECK(jpeg_new_decoder_engine(&decode_eng_cfg, &decoder_engine));
Uninstall JPEG decoder engine
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If a previously installed JPEG engine is no longer needed, it's recommended to recycle the resource by calling :cpp:func:`jpeg_del_decoder_engine`, so that the underlying hardware is released.
.. code:: c
ESP_ERROR_CHECK(jpeg_del_decoder_engine(decoder_engine));
Install JPEG encoder engine
~~~~~~~~~~~~~~~~~~~~~~~~~~~
The JPEG encoder engine requires the configuration specified by :cpp:type:`jpeg_encode_engine_cfg_t`.
If the configurations in :cpp:type:`jpeg_encode_engine_cfg_t` is specified, users can call :cpp:func:`jpeg_new_encoder_engine` to allocate and initialize a JPEG encoder engine. This function will return an JPEG encoder handle if it runs correctly. You can take following code as reference.
.. code:: c
jpeg_encoder_handle_t encoder_engine;
jpeg_encode_engine_cfg_t encode_eng_cfg = {
.intr_priority = 0,
.timeout_ms = 40,
};
ESP_ERROR_CHECK(jpeg_new_encoder_engine(&encode_eng_cfg, &encoder_engine));
Uninstall JPEG encoder engine
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If a previously installed JPEG engine is no longer needed, it's recommended to recycle the resource by calling :cpp:func:`jpeg_del_encoder_engine`, so that the underlying hardware is released.
.. code:: c
ESP_ERROR_CHECK(jpeg_del_encoder_engine(encoder_engine));
.. _jpeg-finite-state-machine:
Finite State Machine
^^^^^^^^^^^^^^^^^^^^
The JPEG driver usage of hardware resources and its process workflow are shown in the following graph:
.. figure:: ../../../_static/diagrams/jpeg/jpeg_workflow.png
:align: center
:alt: JPEG finite state machine
JPEG finite state machine
.. _jpeg-decoder-engine:
JPEG Decoder Engine
^^^^^^^^^^^^^^^^^^^
After installing the JPEG decoder driver by :cpp:func:`jpeg_new_decoder_engine`, {IDF_TARGET_NAME} is ready to decode JPEG pictures by :cpp:func:`jpeg_decoder_process`. :cpp:func:`jpeg_decoder_process` is flexible for decoding different types of pictures by a configurable parameter called :cpp:type:`jpeg_decode_cfg_t`.
Moreover, our JPEG decoder API provides a helper function which helps you get the basic information of your given image. Calling :cpp:func:`jpeg_decoder_get_info` would return the picture information structure called :cpp:func:`jpeg_decoder_get_info`. If you already know the picture basic information, this functions is unnecessary to be called.
The format conversions supported by this driver are listed in the table below:
.. list-table::
:header-rows: 1
:widths: 50 50
:align: center
* - Format of the already compressed image
- Format after decompressing
* - YUV444
- RGB565/RGB888
* - YUV422
- RGB565/RGB888
* - YUV420
- RGB565/RGB888
* - GRAY
- GRAY
Overall, You can take following code as reference, the code is going to decode a 1080*1920 picture.
.. code:: c
jpeg_decode_cfg_t decode_cfg_rgb = {
.output_format = JPEG_DECODE_OUT_FORMAT_RGB888,
.rgb_order = JPEG_DEC_RGB_ELEMENT_ORDER_BGR,
};
size_t rx_buffer_size;
jpeg_decode_memory_alloc_cfg_t rx_mem_cfg = {
.buffer_direction = JPEG_DEC_ALLOC_OUTPUT_BUFFER,
};
const uint8_t *bit_stream = embedded_jpeg_start;
uint8_t *out_buf = (uint8_t*)jpeg_alloc_decoder_mem(1920 * 1088 * 3, &rx_mem_cfg, &rx_buffer_size);
jpeg_decode_picture_info_t header_info;
ESP_ERROR_CHECK(jpeg_decoder_get_info(bit_stream, bit_stream_size, &header_info));
uint32_t out_size = 0;
ESP_ERROR_CHECK(jpeg_decoder_process(decoder_engine, &decode_cfg_rgb, bit_stream, bit_stream_size, out_buf, &out_size));
There are some tips that can help you use this driver more accurately:
1. In above code, you should make sure the output buffer `out_buf` follows the driver's alignment requirements. We provide a helper function :cpp:func:`jpeg_alloc_decoder_mem` to help you allocate a buffer with aligned size and address.
2. The content of `bit_stream` should not be changed until :cpp:func:`jpeg_decoder_process` returns. This input buffer can come directly from flash-mapped embedded data or any other memory region that stays readable for the full call.
3. If the source JPEG uses YUV420 or YUV422 sampling, the decoded output dimensions can be padded up to 16-pixel boundaries. For example, if the visible image size is 1080*1920, the decoder may require an output buffer sized for 1088*1920 pixels. This comes from the JPEG block layout, so please provide enough output buffer memory for the padded image, not only for the visible width and height.
.. _jpeg-encoder-engine:
JPEG Encoder Engine
^^^^^^^^^^^^^^^^^^^
After installing the JPEG encoder driver by :cpp:func:`jpeg_new_encoder_engine`, {IDF_TARGET_NAME} is ready to encode JPEG pictures by :cpp:func:`jpeg_encoder_process`. :cpp:func:`jpeg_encoder_process` is flexible for decoding different types of pictures by a configurable parameter called :cpp:type:`jpeg_encode_cfg_t`.
The format conversions supported by this driver are listed in the table below:
.. list-table::
:header-rows: 1
:widths: 50 50
:align: center
* - Format of Original Image
- Down sampling method
* - RGB565/RGB888
- YUV444/YUV422/YUV420
* - GRAY
- GRAY
Below is the example of code that encodes a 1280x720 picture from an embedded raw buffer:
.. code:: c
size_t raw_size_720p = EXAMPLE_WIDTH * EXAMPLE_HEIGHT * 3; /* 1280x720 bgr24 frame */
jpeg_encode_cfg_t enc_config = {
.src_type = JPEG_ENCODE_IN_FORMAT_RGB888,
.sub_sample = JPEG_DOWN_SAMPLING_YUV422,
.image_quality = 80,
.width = 1280,
.height = 720,
.pixel_reverse = false, // Whether to reverse the pixel order of the input image, or pixel order detail please refer to technical reference manual
};
jpeg_encode_memory_alloc_cfg_t rx_mem_cfg = {
.buffer_direction = JPEG_ENC_ALLOC_OUTPUT_BUFFER,
};
size_t jpg_buffer_size = 0;
uint8_t *jpg_buf_720p = (uint8_t*)jpeg_alloc_encoder_mem(raw_size_720p / 10, &rx_mem_cfg, &jpg_buffer_size);
if (jpg_buf_720p == NULL) {
ESP_LOGE(TAG, "alloc jpg_buf_720p error");
return;
}
/* The current JPEG encoder input path expects BGR24-style raw bytes for
* JPEG_ENCODE_IN_FORMAT_RGB888. The embedded asset can be read directly
* from flash as long as it remains valid until this call returns. */
ESP_ERROR_CHECK(jpeg_encoder_process(jpeg_handle, &enc_config, embedded_bgr24_start, raw_size_720p, jpg_buf_720p, jpg_buffer_size, &jpg_size_720p));
There are some tips that can help you use this driver more accurately:
1. In the above code, the output buffer `jpg_buf_720p` should be allocated by calling :cpp:func:`jpeg_alloc_encoder_mem`, because the JPEG bitstream buffer must satisfy the driver's alignment requirements.
2. The content pointed to by `embedded_bgr24_start` should not be changed until :cpp:func:`jpeg_encoder_process` returns. This input buffer can come from flash-mapped embedded data or another memory region that stays readable for the full call.
3. For :cpp:enumerator:`JPEG_ENCODE_IN_FORMAT_RGB888`, the current driver expects the raw input bytes in a BGR24-style layout. Supplying RGB24 raw data would swap the red and blue channels in the encoded JPEG.
4. The compression ratio depends on the chosen `image_quality` and the content of the image itself. Generally, a higher `image_quality` value obviously results in better image quality but a smaller compression ratio. As for the image content, it is hard to give any specific guidelines, so this question is out of the scope of this document. Generally, the baseline JPEG compression ratio can vary from 40:1 to 10:1. Please take the actual situation into account.
.. _jpeg-performance-overview:
Performance Overview
^^^^^^^^^^^^^^^^^^^^
This section provides some measurements of the decoder and encoder performance. The data presented in the tables below gives the average values of decoding or encoding a randomly chosen picture fragments for 50 times. All tests were performed at a CPU frequency of 360MHz and a SPI RAM clock frequency of 200MHz. Only JPEG related code is run in this test, no other modules are involved (e.g. USB Camera, etc.).
Both decoder and encoder are not cause too much CPU involvement. Only header parse causes CPU source. Calculations related to JPEG compression, such as DCT, quantization, huffman encoding/decoding, etc., are done entirely in hardware.
JPEG decoder performance
~~~~~~~~~~~~~~~~~~~~~~~~
.. only:: esp32p4
.. list-table::
:header-rows: 1
:widths: 25 25 25 25 25
:align: center
* - JPEG Height
- JPEG Width
- Pixel Format in [#]_
- Pixel Format out [#]_
- Performance (fps)
* - 1080
- 1920
- YUV422
- RGB888/RGB565
- 48
* - 720
- 1280
- YUV422
- RGB888/RGB565
- 109
* - 480
- 800
- YUV422
- RGB888/RGB565
- 253
* - 480
- 640
- YUV422
- RGB888/RGB565
- 307
* - 480
- 320
- YUV422
- RGB888/RGB565
- 571
* - 720
- 1280
- GRAY
- GRAY
- 161
.. [#] Format of the already compressed image
.. [#] Format after decompressing
.. only:: esp32s31
.. list-table::
:header-rows: 1
:widths: 25 25 25 25 25
:align: center
* - JPEG Height
- JPEG Width
- Pixel Format in [#]_
- Pixel Format out [#]_
- Performance (fps)
* - 1080
- 1920
- YUV422
- RGB888/RGB565
- 28
* - 720
- 1280
- YUV422
- RGB888/RGB565
- 62
* - 480
- 800
- YUV422
- RGB888/RGB565
- 138
* - 480
- 320
- YUV422
- RGB888/RGB565
- 286
* - 720
- 1280
- GRAY
- GRAY
- 96
.. [#] Format of the already compressed image
.. [#] Format after decompressing
JPEG encoder performance
~~~~~~~~~~~~~~~~~~~~~~~~
.. only:: esp32p4
.. list-table::
:header-rows: 1
:widths: 25 25 25 25 25
:align: center
* - JPEG Height
- JPEG Width
- Pixel Format in [#]_
- Pixel Format out [#]_
- Performance (fps)
* - 1080
- 1920
- RGB888
- YUV422
- 26
* - 1080
- 1920
- RGB565
- YUV422
- 36
* - 1080
- 1920
- RGB565
- YUV420
- 40
* - 1080
- 1920
- RGB565
- YUV444
- 24
* - 1080
- 1920
- RGB888
- YUV422
- 26
* - 720
- 1280
- RGB565
- YUV420
- 88
* - 720
- 1280
- RGB565
- YUV444
- 55
* - 720
- 1280
- RGB565
- YUV422
- 81
* - 480
- 800
- RGB888
- YUV420
- 142
* - 640
- 800
- RGB888
- YUV420
- 174
* - 480
- 320
- RGB888
- YUV420
- 315
* - 720
- 1280
- GRAY
- GRAY
- 163
.. [#] Format of Original Image
.. [#] Down sampling method
.. only:: esp32s31
.. list-table::
:header-rows: 1
:widths: 25 25 25 25 25
:align: center
* - JPEG Height
- JPEG Width
- Pixel Format in [#]_
- Pixel Format out [#]_
- Performance (fps)
* - 1080
- 1920
- RGB888
- YUV422
- 21
* - 1080
- 1920
- RGB565
- YUV422
- 21
* - 1080
- 1920
- RGB565
- YUV420
- 25
* - 720
- 1280
- RGB565
- YUV420
- 62
* - 720
- 1280
- RGB565
- YUV422
- 47
* - 480
- 800
- RGB888
- YUV444
- 74
* - 480
- 800
- RGB888
- YUV422
- 108
* - 480
- 800
- RGB888
- YUV420
- 126
* - 480
- 320
- RGB888
- YUV444
- 173
* - 480
- 320
- RGB888
- YUV422
- 241
* - 480
- 320
- RGB888
- YUV420
- 273
* - 720
- 1280
- GRAY
- GRAY
- 92
.. [#] Format of Original Image
.. [#] Down sampling method
.. _jpeg-pixel-storage-layout:
Pixel Storage Layout for Different Color Formats
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The encoder and decoder described in this guide use the same uncompressed raw image formats (RGB, YUV). Therefore, the encoder and decoder are not discussed separately in this section. The pixel layout of the following formats applies to the input direction of the encoder and the output direction of the decoder (if supported). The specific pixel layout is shown in the following figure:
RGB888
~~~~~~
In the following picture, each small block means one bit.
.. figure:: ../../../_static/diagrams/jpeg/rgb888.png
:align: center
:alt: RGB888 pixel order
RGB888 pixel order
For RGB888, the order can be changed via :cpp:member:`jpeg_decode_cfg_t::rgb_order` sets the pixel to `RGB` order.
.. figure:: ../../../_static/diagrams/jpeg/rgb888_bigendian.png
:align: center
:alt: RGB888 pixel big endian order
RGB888 pixel big endian order
RGB565
~~~~~~
In the following picture, each small block means one bit.
.. figure:: ../../../_static/diagrams/jpeg/rgb565.png
:align: center
:alt: RGB565 pixel order
RGB565 pixel order
For RGB565, the order can be changed via :cpp:member:`jpeg_decode_cfg_t::rgb_order` sets the pixel to `RGB` order.
.. figure:: ../../../_static/diagrams/jpeg/rgb565_bigendian.png
:align: center
:alt: RGB565 pixel big endian order
RGB565 pixel big endian order
YUV444
~~~~~~
In the following picture, each small block means one byte.
.. figure:: ../../../_static/diagrams/jpeg/yuv444.png
:align: center
:alt: YUV444 pixel order
YUV444 pixel order
YUV422
~~~~~~
In the following picture, each small block means one byte.
.. figure:: ../../../_static/diagrams/jpeg/yuv422.png
:align: center
:alt: YUV422 pixel order
YUV422 pixel order
YUV420
~~~~~~
In the following picture, each small block means one byte.
.. figure:: ../../../_static/diagrams/jpeg/yuv420.png
:align: center
:alt: YUV420 pixel order
YUV420 pixel order
.. _jpeg-thread-safety:
Thread Safety
^^^^^^^^^^^^^
The factory function :cpp:func:`jpeg_new_decoder_engine`, :cpp:func:`jpeg_decoder_get_info`, :cpp:func:`jpeg_decoder_process`, and :cpp:func:`jpeg_del_decoder_engine` are guaranteed to be thread safe by the driver, which means, user can call them from different RTOS tasks without protection by extra locks.
.. _jpeg-power-management:
Power Management
^^^^^^^^^^^^^^^^
When power management is enabled (i.e., :ref:`CONFIG_PM_ENABLE` is set), the system needs to adjust or stop the source clock of JPEG to enter Light-sleep, thus potentially changing the JPEG decoder or encoder process. This might lead to unexpected behavior in hardware calculation. To prevent such issues, entering Light-sleep is disabled for the time when JPEG encoder or decoder is working.
Whenever the user is decoding or encoding via JPEG (i.e., calling :cpp:func:`jpeg_encoder_process` or :cpp:func:`jpeg_decoder_process`), the driver guarantees that the power management lock is acquired by setting it to :cpp:enumerator:`esp_pm_lock_type_t::ESP_PM_CPU_FREQ_MAX`. Once the encoding or decoding is finished, the driver releases the lock and the system can enter Light-sleep.
.. _jpeg-flash-encryption:
Usage Under Encryption
^^^^^^^^^^^^^^^^^^^^^^
The JPEG codec moves data via the 2D-DMA, and the JPEG codec **cannot process encrypted data**. Therefore, when PSRAM encryption is enabled, the JPEG input/output buffers must reside in an unencrypted memory region, otherwise encoding/decoding fails.
To support the encrypted scenario, the driver does the following:
- When ``CONFIG_SPIRAM_ENC_EXEMPT`` is enabled, :cpp:func:`jpeg_alloc_decoder_mem` and :cpp:func:`jpeg_alloc_encoder_mem` allocate buffers from the unencrypted PSRAM region (``MALLOC_CAP_SPIRAM_NO_ENC``) automatically.
- The allocated buffers satisfy both the cache line alignment and the byte alignment required by the 2D-DMA.
Please note the following when using it:
1. It is recommended to always allocate buffers via :cpp:func:`jpeg_alloc_encoder_mem` / :cpp:func:`jpeg_alloc_decoder_mem` to ensure correct alignment and memory region.
2. The size of the unencrypted region is determined by ``CONFIG_SPIRAM_ENC_EXEMPT_SIZE``. Since the JPEG buffer size depends on the image resolution and cannot be predicted automatically, configure it according to the largest image you actually process. If the region is insufficient, the allocation fails and an error log is printed, suggesting to enlarge ``CONFIG_SPIRAM_ENC_EXEMPT_SIZE``. Also note that this value must not be greater than or equal to the actual PSRAM size, otherwise the unencrypted region is disabled.
.. _jpeg-kconfig-options:
Kconfig Options
^^^^^^^^^^^^^^^
- :ref:`CONFIG_JPEG_ENABLE_DEBUG_LOG` is used to enable the debug log at the cost of increased firmware binary size.
Maintainers' Notes
------------------
The JPEG driver usage of hardware resources and its dependency status are shown in the following graph:
.. figure:: ../../../_static/diagrams/jpeg/jpeg_drv_file_structure.png
:align: center
:alt: JPEG driver files structure
JPEG driver file structure
Application Examples
--------------------
- :example:`peripherals/jpeg/jpeg_decode` demonstrates how to use the JPEG hardware decoder to parse one embedded JPEG, decode it into RGB888, stream the raw output as base64 over UART, and validate the result with pytest.
- :example:`peripherals/jpeg/jpeg_encode` demonstrates how to use the JPEG hardware encoder to encode an embedded 720p raw picture, stream the JPEG as base64 over UART, and validate the result with pytest.
API Reference
-------------
.. only:: SOC_JPEG_DECODE_SUPPORTED
.. include-build-file:: inc/jpeg_decode.inc
.. only:: SOC_JPEG_ENCODE_SUPPORTED
.. include-build-file:: inc/jpeg_encode.inc
.. include-build-file:: inc/components/esp_driver_jpeg/include/driver/jpeg_types.inc
.. include-build-file:: inc/components/esp_hal_jpeg/include/hal/jpeg_types.inc
@@ -0,0 +1,171 @@
.. _key-manager:
Key Manager
===========
:link_to_translation:`zh_CN:[中文]`
The {IDF_TARGET_NAME}'s Key Manager peripheral provides hardware-assisted **key deployment and recovery** for cryptographic keys. It allows cryptographic keys to be provisioned and used without storing plaintext key material in flash, RAM, or eFuses.
The Key Manager is intended for applications that require secure handling of long-term cryptographic keys.
.. only:: esp32p4
.. note::
The Key Manager peripheral is only supported on ESP32-P4 chip revision >= v3.0.
.. only:: esp32c5
.. note::
The Key Manager peripheral is only supported on ESP32-C5 chip revision >= v1.2.
Key Manager provides the following properties:
- **Device uniqueness**
Keys are cryptographically bound to a Hardware Unique Key (HUK) that is unique to each chip.
- **No plaintext key storage**
Key material is never exposed to software accessible memory.
- **Flexible key lifecycle**
Keys can be deployed, recovered, or replaced by a newer key without reprogramming the eFuses for each key.
- **Resistance to physical extraction**
Reading flash or eFuses contents would not reveal usable key material.
Hardware Unique Key (HUK)
-------------------------
The Hardware Unique Key (HUK) is a device-specific unique key generated entirely in hardware HUK peripheral. It is generated using SRAM Physical Unclonable Function (PUF) and is reconstructed using the HUK recovery info stored in the key recovery info of a Key Manager deployed key. See **{IDF_TARGET_NAME} Technical Reference Manual** > **Chapter Key Manager** [`PDF <{IDF_TARGET_TRM_EN_URL}>`__] > **HUK Generator** for more details about the HUK peripheral.
The HUK acts as the root of trust for all keys deployed through the Key Manager.
Key Deployment and Key Recovery
-------------------------------
The Key Manager operates in two distinct phases:
- **Key deployment**
A cryptographic key is generated or securely introduced into the chip, and it gets bound to the HUK. This step is usually performed during manufacturing, first boot up or when generating transient or persistent keys during the application runtime.
- **Key recovery**
On subsequent boots, a Key Manager-deployed persistent key is restored using the previously generated key recovery information, without exposing the key value.
During deployment, the Key Manager generates a data structure referred to as :cpp:type:`esp_key_mgr_key_recovery_info_t`. In case of persistent keys, the applications must store this data in non-volatile storage (for example, flash) in order to recover the key on later boots.
Supported Key Types
-------------------
The Key Manager can manage keys for the following key types:
.. list::
:SOC_KEY_MANAGER_ECDSA_KEY_DEPLOY: - ECDSA
:SOC_KEY_MANAGER_FE_KEY_DEPLOY: - Flash Encryption (XTS-AES)
:SOC_KEY_MANAGER_HMAC_KEY_DEPLOY: - HMAC
:SOC_KEY_MANAGER_DS_KEY_DEPLOY: - Digital Signature peripherals
:SOC_KEY_MANAGER_FE_KEY_DEPLOY: - PSRAM Encryption
Each key is associated with a :cpp:type:`esp_key_mgr_key_purpose_t`, which defines how the key can be used by hardware peripherals.
Key Deployment Modes
--------------------
The Key Manager provides multiple key deployment modes to support different provisioning and security requirements.
Random Deploy Mode
^^^^^^^^^^^^^^^^^^
In this mode, the Key Manager generates a random private key internally.
- The key value is never known to the application software.
- No external key material is required.
- Intended for use cases where the key does not need to be backed up or exported.
AES Deploy Mode
^^^^^^^^^^^^^^^
In this mode, a user-specified private key is securely deployed.
- The key is encrypted before being transmitted to the chip.
- Auxiliary key material is used to protect the deployment process.
- Intended for factory provisioning scenarios where the key value must be predefined.
ECDH0 Deploy Mode
^^^^^^^^^^^^^^^^^
In this mode, a private key is negotiated using Elliptic Curve Diffie-Hellman (ECDH).
- The final private key is never transmitted.
- The deployment process can occur over an untrusted channel.
- Intended for high-security provisioning environments.
ECDH1 Deploy Mode
^^^^^^^^^^^^^^^^^
This mode is similar to ECDH0 Deploy Mode, with additional flexibility for manufacturing workflows.
- Supports negotiated key deployment using auxiliary recovery data
- Allows updating deployed keys by replacing auxiliary information
- Intended for large-scale manufacturing with controlled trust assumptions
For detailed information various deployment modes, see **{IDF_TARGET_NAME} Technical Reference Manual** > **Chapter Key Manager** [`PDF <{IDF_TARGET_TRM_EN_URL}>`__] > **Section Key Manager**.
Typical Workflows
-----------------
First Boot or Manufacturing
^^^^^^^^^^^^^^^^^^^^^^^^^^^
A typical provisioning flow includes:
1. Generating the Hardware Unique Key (HUK)
2. Deploying required cryptographic keys using an appropriate deployment mode
3. Storing the generated ``key_recovery_info`` in non-volatile storage
4. Locking relevant security configuration eFuses, if required
This process is usually performed once per device.
Normal Boot
^^^^^^^^^^^
During a normal boot:
1. The application provides the previously generated and stored ``key_recovery_info`` of a Key Manager-deployed key.
2. The HUK is reconstructed automatically by hardware.
3. The Key Manager recovers the deployed key internally.
4. Cryptographic peripherals can use the recovered key.
Security Considerations
-----------------------
Applications using the Key Manager should consider the following:
- Protect the ``key_recovery_info`` of a Key Manager-deployed key against unauthorized modification or loss.
- Lock Key Manager's security-related eFuses after successful key deployment to prevent re-deployment of a key of the same type.
- Avoid deploying new XTS-AES keys when Flash Encryption is already enabled unless explicitly intended.
API Reference
-------------
.. include-build-file:: inc/esp_key_mgr.inc
.. include-build-file:: inc/key_mgr_types.inc
Examples
--------
See :example:`security/key_manager` for an example demonstrating key deployment using the Key Manager and using the deployed key to perform signing operations.
This example shows how to:
- Initialize the Key Manager
- Deploy keys using the AES deployment mode
- Use the PSA interface to perform signing operations using the Key Manager deployed key
@@ -0,0 +1,120 @@
MIPI DSI Interfaced LCD
=======================
:link_to_translation:`zh_CN:[中文]`
#. Create a DSI bus, and it will initialize the D-PHY as well.
.. code-block:: c
esp_lcd_dsi_bus_handle_t mipi_dsi_bus = NULL;
esp_lcd_dsi_bus_config_t bus_config = {
.bus_id = 0, // index from 0, specify the DSI host to use
.num_data_lanes = 2, // Number of data lanes to use, can't set a value that exceeds the chip's capability
.lane_bit_rate_mbps = EXAMPLE_MIPI_DSI_LANE_BITRATE_MBPS, // Bit rate of the data lanes, in Mbps
};
ESP_ERROR_CHECK(esp_lcd_new_dsi_bus(&bus_config, &mipi_dsi_bus));
#. Derive the DBI interface from the DSI bus. The DBI interface mostly is used as the control IO layer in the esp_lcd component. This interface provides the functions to read or write the configuration registers inside the LCD device. In this step, you need to provide the following information:
- :cpp:member:`esp_lcd_dbi_io_config_t::virtual_channel` sets the virtual channel number to use. The virtual channel is a logical channel that is used to multiplex the data from different sources. If you only have one LCD connected, you can set this to ``0``.
- :cpp:member:`esp_lcd_dbi_io_config_t::lcd_cmd_bits` and :cpp:member:`esp_lcd_dbi_io_config_t::lcd_param_bits` set the bit width of the command and parameter that recognized by the LCD controller chip. This is chip specific, you should refer to your LCD spec in advance.
.. code-block:: c
esp_lcd_panel_io_handle_t mipi_dbi_io = NULL;
esp_lcd_dbi_io_config_t dbi_config = {
.virtual_channel = 0,
.lcd_cmd_bits = 8, // according to the LCD spec
.lcd_param_bits = 8, // according to the LCD spec
};
ESP_ERROR_CHECK(esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &dbi_config, &mipi_dbi_io));
#. Install the LCD controller driver. The LCD controller driver is responsible for sending the commands and parameters to the LCD controller chip. In this step, you need to specify the MIPI DBI IO handle that allocated in the last step, and some panel specific configurations:
- :cpp:member:`esp_lcd_panel_dev_config_t::bits_per_pixel` sets the bit width of each pixel. The LCD driver uses this value to calculate the number of bytes to send to the LCD controller chip.
- :cpp:member:`esp_lcd_panel_dev_config_t::reset_gpio_num` sets the GPIO number of the reset pin. If the LCD controller chip does not have a reset pin, you can set this value to ``-1``.
- :cpp:member:`esp_lcd_panel_dev_config_t::rgb_ele_order` sets the RGB element order of the pixel data, it can be **RGB** or **BGR**.
.. code-block:: c
esp_lcd_panel_handle_t ili9881c_ctrl_panel = NULL;
esp_lcd_panel_dev_config_t lcd_dev_config = {
.bits_per_pixel = 24, // MIPI LCD usually uses 24 bit (i.e., RGB888) per pixel
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
.reset_gpio_num = EXAMPLE_PIN_NUM_LCD_RST,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_ili9881c(mipi_dbi_io, &lcd_dev_config, &ili9881c_ctrl_panel));
#. With the LCD control panel that returned in the last step, you can reset the LCD device followed by a basic initialization. After that, you can turn on the display.
.. code-block:: c
ESP_ERROR_CHECK(esp_lcd_panel_reset(ili9881c_ctrl_panel));
ESP_ERROR_CHECK(esp_lcd_panel_init(ili9881c_ctrl_panel));
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(ili9881c_ctrl_panel, true));
#. However, you still can't send pixel data to the MIPI LCD with the control panel, because MIPI LCD has a high resolution and there's no GRAM in the LCD controller. We need to maintain the LCD frame buffer and flush it to the LCD via the MIPI DSI DPI interface. To allocate a DPI data panel, you need to provide many essential parameters, including the DPI clock frequency, the pixel format, the video timing, and so on.
- :cpp:member:`esp_lcd_dpi_panel_config_t::virtual_channel` sets the virtual channel number to use. Like the DBI interface, we also need to set the virtual channel for the DPI interface. If you only have one LCD connected, you can set this to ``0``.
- :cpp:member:`esp_lcd_dpi_panel_config_t::dpi_clk_src` sets the clock source for the DPI interface. The available clock sources are listed in :cpp:type:`mipi_dsi_dpi_clock_source_t`.
- :cpp:member:`esp_lcd_dpi_panel_config_t::dpi_clock_freq_mhz` sets the DPI clock frequency in MHz. Higher pixel clock frequency results in higher refresh rate, but may cause flickering if the DMA bandwidth is not sufficient or the LCD controller chip does not support high pixel clock frequency.
- :cpp:member:`esp_lcd_dpi_panel_config_t::in_color_format` sets the pixel format of the input pixel data. The available pixel formats are listed in :cpp:type:`lcd_color_format_t`. We usually use **RGB888** for MIPI LCD to get the best color depth.
- :cpp:member:`esp_lcd_dpi_panel_config_t::video_timing` sets the LCD panel specific timing parameters. All required parameters are listed in the :cpp:type:`esp_lcd_video_timing_t`, including the LCD resolution and blanking porches. Please fill them according to the datasheet of your LCD.
.. code-block:: c
esp_lcd_panel_handle_t mipi_dpi_panel = NULL;
esp_lcd_dpi_panel_config_t dpi_config = {
.virtual_channel = 0,
.dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT,
.dpi_clock_freq_mhz = 1 * 1000,
.in_color_format = LCD_COLOR_FMT_RGB888,
.video_timing = {
.h_size = EXAMPLE_MIPI_DSI_LCD_H_RES,
.v_size = EXAMPLE_MIPI_DSI_LCD_V_RES,
.hsync_back_porch = EXAMPLE_MIPI_DSI_LCD_HBP,
.hsync_pulse_width = EXAMPLE_MIPI_DSI_LCD_HSYNC,
.hsync_front_porch = EXAMPLE_MIPI_DSI_LCD_HFP,
.vsync_back_porch = EXAMPLE_MIPI_DSI_LCD_VBP,
.vsync_pulse_width = EXAMPLE_MIPI_DSI_LCD_VSYNC,
.vsync_front_porch = EXAMPLE_MIPI_DSI_LCD_VFP,
},
};
ESP_ERROR_CHECK(esp_lcd_new_panel_dpi(mipi_dsi_bus, &dpi_config, &mipi_dpi_panel));
ESP_ERROR_CHECK(esp_lcd_panel_init(mipi_dpi_panel));
#. Configure draw bitmap hook function (optional)
If you want to use DMA2D to implement draw bitmap, the driver has already implemented the DMA2D draw bitmap hook function, you only need to call :func:`esp_lcd_dpi_panel_enable_dma2d` to enable it.
.. code-block:: c
ESP_ERROR_CHECK(esp_lcd_dpi_panel_enable_dma2d(mipi_dpi_panel));
.. note::
Due to hardware limitation, if external memory encryption is enabled, DMA2D can only access address and length that are aligned to {IDF_TARGET_SOC_MEMSPI_ENCRYPTION_ALIGNMENT} bytes. You need to ensure that your draw buffer's address and length are aligned to {IDF_TARGET_SOC_MEMSPI_ENCRYPTION_ALIGNMENT} bytes. :example:`peripherals/lcd/mipi_dsi` shows how to use LVGL to constrain the redrawn area to ensure alignment.
If you need more advanced applications, you can add a custom hook for draw bitmap, such as using PPA to implement rotation, scaling, etc.
.. code-block:: c
esp_lcd_panel_hooks_t hooks = {
.draw_bitmap_hook = custom_draw_bitmap_hook,
};
ESP_ERROR_CHECK(esp_lcd_dpi_panel_register_hooks(mipi_dpi_panel, &hooks, &user_ctx));
Power Supply for MIPI DPHY
--------------------------
The MIPI DPHY on {IDF_TARGET_NAME} requires a dedicated 2.5V power supply. Please refer to your schematic and ensure that the power pin (often labeled ``VDD_MIPI_DPHY``) is properly connected to a 2.5V power source before using the MIPI DSI driver.
.. only:: SOC_GP_LDO_SUPPORTED
On {IDF_TARGET_NAME}, the MIPI DPHY can be powered by the internal adjustable LDO. Connect the output pin of the LDO channel to the MIPI DPHY power pin. Before initializing the DSI driver, use the API provided in :doc:`/api-reference/peripherals/ldo_regulator` to configure the LDO output voltage to 2.5V.
API Reference
-------------
.. include-build-file:: inc/esp_lcd_mipi_dsi.inc
@@ -0,0 +1,58 @@
I2C Interfaced LCD
==================
:link_to_translation:`zh_CN:[中文]`
#. Create I2C bus. Please refer to :doc:`I2C API doc </api-reference/peripherals/i2c>` for more details.
.. code-block:: c
i2c_master_bus_handle_t i2c_bus = NULL;
i2c_master_bus_config_t bus_config = {
.clk_source = I2C_CLK_SRC_DEFAULT,
.glitch_ignore_cnt = 7,
.i2c_port = I2C_BUS_PORT,
.sda_io_num = EXAMPLE_PIN_NUM_SDA,
.scl_io_num = EXAMPLE_PIN_NUM_SCL,
.flags.enable_internal_pullup = true,
};
ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &i2c_bus));
#. Allocate an LCD IO device handle from the I2C bus. In this step, you need to provide the following information:
- :cpp:member:`esp_lcd_panel_io_i2c_config_t::dev_addr` sets the I2C device address of the LCD controller chip. The LCD driver uses this address to communicate with the LCD controller chip.
- :cpp:member:`esp_lcd_panel_io_i2c_config_t::scl_speed_hz` sets the I2C clock frequency in Hz. The value should not exceed the range recommended in the LCD spec.
- :cpp:member:`esp_lcd_panel_io_i2c_config_t::lcd_cmd_bits` and :cpp:member:`esp_lcd_panel_io_i2c_config_t::lcd_param_bits` set the bit width of the command and parameter recognized by the LCD controller chip. This is chip specific, you should refer to your LCD spec in advance.
- :cpp:member:`esp_lcd_panel_io_i2c_config_t::transaction_timeout_ms` sets the timeout (in milliseconds) for each underlying I2C transfer. Setting this to 0 or -1 means to wait indefinitely. If a positive value is specified, panel IO calls will return ``ESP_ERR_TIMEOUT`` when the timeout is reached. This is useful for cases like shared buses or when a slave device could potentially hang the bus.
.. code-block:: c
esp_lcd_panel_io_handle_t io_handle = NULL;
esp_lcd_panel_io_i2c_config_t io_config = {
.dev_addr = EXAMPLE_I2C_HW_ADDR,
.scl_speed_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ,
.control_phase_bytes = 1, // refer to LCD spec
.dc_bit_offset = 6, // refer to LCD spec
.lcd_cmd_bits = EXAMPLE_LCD_CMD_BITS,
.lcd_param_bits = EXAMPLE_LCD_CMD_BITS,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(i2c_bus, &io_config, &io_handle));
#. Install the LCD controller driver. The LCD controller driver is responsible for sending the commands and parameters to the LCD controller chip. In this step, you need to specify the I2C IO device handle that allocated in the last step, and some panel specific configurations:
- :cpp:member:`esp_lcd_panel_dev_config_t::reset_gpio_num` sets the LCD's hardware reset GPIO number. If the LCD does not have a hardware reset pin, set this to ``-1``.
- :cpp:member:`esp_lcd_panel_dev_config_t::bits_per_pixel` sets the bit width of the pixel color data. The LCD driver uses this value to calculate the number of bytes to send to the LCD controller chip.
.. code-block:: c
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_panel_dev_config_t panel_config = {
.bits_per_pixel = 1,
.reset_gpio_num = EXAMPLE_PIN_NUM_RST,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(io_handle, &panel_config, &panel_handle));
API Reference
-------------
.. include-build-file:: inc/esp_lcd_io_i2c.inc
@@ -0,0 +1,88 @@
I80 Interfaced LCD
==================
:link_to_translation:`zh_CN:[中文]`
#. Create I80 bus by :cpp:func:`esp_lcd_new_i80_bus`. You need to set up the following parameters for an Intel 8080 parallel bus:
- :cpp:member:`esp_lcd_i80_bus_config_t::clk_src` sets the clock source of the I80 bus. Note, the default clock source may be different between ESP targets.
- :cpp:member:`esp_lcd_i80_bus_config_t::wr_gpio_num` sets the GPIO number of the pixel clock (also referred as ``WR`` in some LCD spec)
- :cpp:member:`esp_lcd_i80_bus_config_t::dc_gpio_num` sets the GPIO number of the data or command select pin (also referred as ``RS`` in some LCD spec)
- :cpp:member:`esp_lcd_i80_bus_config_t::bus_width` sets the bit width of the data bus (only support ``8`` or ``16``)
- :cpp:member:`esp_lcd_i80_bus_config_t::data_gpio_nums` is the array of the GPIO number of the data bus. The number of GPIOs should be equal to the :cpp:member:`esp_lcd_i80_bus_config_t::bus_width` value.
- :cpp:member:`esp_lcd_i80_bus_config_t::max_transfer_bytes` sets the maximum number of bytes that can be transferred in one transaction.
.. code-block:: c
esp_lcd_i80_bus_handle_t i80_bus = NULL;
esp_lcd_i80_bus_config_t bus_config = {
.clk_src = LCD_CLK_SRC_DEFAULT,
.dc_gpio_num = EXAMPLE_PIN_NUM_DC,
.wr_gpio_num = EXAMPLE_PIN_NUM_PCLK,
.data_gpio_nums = {
EXAMPLE_PIN_NUM_DATA0,
EXAMPLE_PIN_NUM_DATA1,
EXAMPLE_PIN_NUM_DATA2,
EXAMPLE_PIN_NUM_DATA3,
EXAMPLE_PIN_NUM_DATA4,
EXAMPLE_PIN_NUM_DATA5,
EXAMPLE_PIN_NUM_DATA6,
EXAMPLE_PIN_NUM_DATA7,
},
.bus_width = 8,
.max_transfer_bytes = EXAMPLE_LCD_H_RES * 100 * sizeof(uint16_t), // transfer 100 lines of pixels (assume pixel is RGB565) at most in one transaction
.dma_burst_size = EXAMPLE_DMA_BURST_SIZE,
};
ESP_ERROR_CHECK(esp_lcd_new_i80_bus(&bus_config, &i80_bus));
#. Allocate an LCD IO device handle from the I80 bus. In this step, you need to provide the following information:
- :cpp:member:`esp_lcd_panel_io_i80_config_t::cs_gpio_num` sets the GPIO number of the chip select pin.
- :cpp:member:`esp_lcd_panel_io_i80_config_t::pclk_hz` sets the pixel clock frequency in Hz. Higher pixel clock frequency results in higher refresh rate, but may cause flickering if the DMA bandwidth is not sufficient or the LCD controller chip does not support high pixel clock frequency.
- :cpp:member:`esp_lcd_panel_io_i80_config_t::lcd_cmd_bits` and :cpp:member:`esp_lcd_panel_io_i80_config_t::lcd_param_bits` set the bit width of the command and parameter that recognized by the LCD controller chip. This is chip specific, you should refer to your LCD spec in advance.
- :cpp:member:`esp_lcd_panel_io_i80_config_t::trans_queue_depth` sets the maximum number of transactions that can be queued in the LCD IO device. A bigger value means more transactions can be queued up, but it also consumes more memory.
.. note::
The output pixel clock PCLK frequency has an upper limit that depends on the data bus width:
- When :cpp:member:`esp_lcd_i80_bus_config_t::bus_width` is 8, the PCLK frequency is recommended to be less than 80 MHz.
- When :cpp:member:`esp_lcd_i80_bus_config_t::bus_width` is 16, the PCLK frequency is recommended to be less than 40 MHz.
.. code-block:: c
esp_lcd_panel_io_handle_t io_handle = NULL;
esp_lcd_panel_io_i80_config_t io_config = {
.cs_gpio_num = EXAMPLE_PIN_NUM_CS,
.pclk_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ,
.trans_queue_depth = 10,
.dc_levels = {
.dc_idle_level = 0,
.dc_cmd_level = 0,
.dc_dummy_level = 0,
.dc_data_level = 1,
},
.lcd_cmd_bits = EXAMPLE_LCD_CMD_BITS,
.lcd_param_bits = EXAMPLE_LCD_PARAM_BITS,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &io_handle));
#. Install the LCD controller driver. The LCD controller driver is responsible for sending the commands and parameters to the LCD controller chip. In this step, you need to specify the I80 IO device handle that allocated in the last step, and some panel specific configurations:
- :cpp:member:`esp_lcd_panel_dev_config_t::bits_per_pixel` sets the bit width of the pixel color data. The LCD driver uses this value to calculate the number of bytes to send to the LCD controller chip.
- :cpp:member:`esp_lcd_panel_dev_config_t::reset_gpio_num` sets the GPIO number of the reset pin. If the LCD controller chip does not have a reset pin, you can set this value to ``-1``.
- :cpp:member:`esp_lcd_panel_dev_config_t::rgb_ele_order` sets the color order the pixel color data.
.. code-block:: c
esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = EXAMPLE_PIN_NUM_RST,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
.bits_per_pixel = 16,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle));
API Reference
-------------
.. include-build-file:: inc/esp_lcd_io_i80.inc
@@ -0,0 +1,108 @@
LCD
===
:link_to_translation:`zh_CN:[中文]`
Introduction
------------
ESP chips can generate various kinds of timings needed by common LCDs on the market, like SPI LCD, I2C LCD, Parallel LCD (Intel 8080), RGB/SRGB LCD, MIPI DSI LCD, etc. The ``esp_lcd`` component offers an abstracted driver framework to support them in an unified way.
An LCD typically consists of two main planes:
* **Control Plane**: This plane allows us to read and write to the internal registers of the LCD device controller. Host typically uses this plane for tasks such as initializing the LCD power supply and performing gamma calibration.
* **Data Plane**: The data plane is responsible for transmitting pixel data to the LCD device.
Functional Overview
-------------------
In the context of ``esp_lcd``, both the data plane and the control plane are represented by the :cpp:type:`esp_lcd_panel_handle_t` type.
On some LCDs, these two planes may be combined into a single plane. In this configuration, pixel data is transmitted through the control plane, achieving functionality similar to that of the data plane. This merging is common in SPI LCDs and I2C LCDs.
Additionally, there are LCDs that do not require a separate control plane. For instance, certain RGB LCDs automatically execute necessary initialization procedures after power-up. Host devices only need to continuously refresh pixel data through the data plane. However, it's essential to note that not all RGB LCDs eliminate the control plane entirely. Some LCD devices can simultaneously support multiple interfaces, requiring the Host to send specific commands via the control plane (such as those based on the SPI interface) to enable the RGB mode.
This document will discuss how to create the control plane and data plane, as mentioned earlier, based on different types of LCDs.
.. toctree::
:maxdepth: 1
:SOC_GPSPI_SUPPORTED: spi_lcd
:SOC_I2C_SUPPORTED: i2c_lcd
:SOC_LCD_I80_SUPPORTED: i80_lcd
:SOC_LCD_RGB_SUPPORTED: rgb_lcd
:SOC_MIPI_DSI_SUPPORTED: dsi_lcd
:SOC_PARLIO_LCD_SUPPORTED: parl_lcd
.. note::
ESP-IDF provides only a limited number of LCD device controller drivers out of the box (e.g., ST7789). More drivers are available in the `ESP Component Registry <https://components.espressif.com/components?q=esp_lcd>`__.
LCD Control Panel Operations
----------------------------
* :cpp:func:`esp_lcd_panel_reset` can reset the LCD control panel.
* :cpp:func:`esp_lcd_panel_init` performs a basic initialization of the control panel. To perform more manufacturer specific initialization, please refer to :ref:`steps_add_manufacture_init`.
* By combining using :cpp:func:`esp_lcd_panel_swap_xy` and :cpp:func:`esp_lcd_panel_mirror`, you can achieve the functionality of rotating or mirroring the LCD screen.
* :cpp:func:`esp_lcd_panel_disp_on_off` can turn on or off the LCD screen by cutting down the output path from the frame buffer to the LCD screen. Please note, this is not controlling the LCD backlight. Backlight control is not covered by the ``esp_lcd`` driver.
* :cpp:func:`esp_lcd_panel_disp_sleep` can reduce the power consumption of the LCD screen by entering the sleep mode. The internal frame buffer is still retained.
LCD Data Panel Operations
-------------------------
* :cpp:func:`esp_lcd_panel_reset` can reset the LCD data panel.
* :cpp:func:`esp_lcd_panel_init` performs a basic initialization of the data panel.
* :cpp:func:`esp_lcd_panel_draw_bitmap` is the function which does the magic to flush the user draw buffer to the LCD screen, where the target draw window is configurable. Please note, this function expects that the draw buffer is a 1-D array and there's no stride in between each lines.
* :cpp:func:`esp_lcd_panel_draw_bitmap_2d` is the function which does the magic to flush the user draw buffer to the LCD screen, where the source and target draw windows are configurable. Please note, the draw buffer can be a 2-D array or a 1-D array with no stride in between each lines.
Advanced Frame Buffer Debugging
-------------------------------
For advanced debugging, ESP-IDF can load the ``idf-drivers-gdb`` Python package in a GDB session. This package provides the ``framebuffer_display`` command, which reads LCD frame buffer memory from the target and renders it on the host side.
This is useful when the panel does not show the expected image and you need to check whether the frame buffer content is already wrong, or whether the problem is in the LCD interface timing, GPIO routing, panel initialization, or color format configuration.
For installation steps, supported pixel formats, command syntax, examples, and troubleshooting tips, see the `idf-drivers-gdb package documentation <https://pypi.org/project/idf-drivers-gdb/>`__.
.. _steps_add_manufacture_init:
Steps to Add Manufacturer Specific Initialization
-------------------------------------------------
The LCD controller drivers (e.g., st7789) in ESP-IDF only provide basic initialization in the :cpp:func:`esp_lcd_panel_init`, leaving the vast majority of settings to the default values. Some LCD modules need to set a bunch of manufacturer specific configurations before it can display normally. These configurations usually include gamma, power voltage and so on. If you want to add manufacturer specific initialization, please follow the steps below:
.. code:: c
esp_lcd_panel_reset(panel_handle);
esp_lcd_panel_init(panel_handle);
// set extra configurations e.g., gamma control
// with the underlying IO handle
// please consult your manufacturer for special commands and corresponding values
esp_lcd_panel_io_tx_param(io_handle, GAMMA_CMD, (uint8_t[]) {
GAMMA_ARRAY
}, N);
// turn on the display
esp_lcd_panel_disp_on_off(panel_handle, true);
Application Example
-------------------
.. list::
* :example:`peripherals/lcd/tjpgd` shows how to decode a JPEG image and display it on an SPI-interfaced LCD, and rotate the image periodically.
:SOC_GPSPI_SUPPORTED: * :example:`peripherals/lcd/spi_lcd_touch` demonstrates how to drive the LCD and touch panel on the same SPI bus, and display a simple GUI using the LVGL library.
:SOC_LCD_I80_SUPPORTED: * :example:`peripherals/lcd/i80_controller` demonstrates how to port the LVGL library onto the `esp_lcd` driver layer to create GUIs.
:SOC_LCD_RGB_SUPPORTED: * :example:`peripherals/lcd/rgb_panel` demonstrates how to install an RGB panel driver, display a scatter chart on the screen based on the LVGL library.
:SOC_I2C_SUPPORTED: * :example:`peripherals/lcd/i2c_oled` demonstrates how to use the SSD1306 panel driver from the `esp_lcd` component to facilitate the porting of LVGL library and display a scrolling text on the OLED screen.
:SOC_MIPI_DSI_SUPPORTED: * :example:`peripherals/lcd/mipi_dsi` demonstrates the general process of installing a MIPI DSI LCD driver, and displays a LVGL widget on the screen.
:SOC_PARLIO_LCD_SUPPORTED: * :example:`peripherals/lcd/parlio_simulate` demonstrates how to use Parallel IO peripheral to drive an SPI or I80 Interfaced LCD.
API Reference
-------------
.. include-build-file:: inc/lcd_types.inc
.. include-build-file:: inc/esp_lcd_types.inc
.. include-build-file:: inc/esp_lcd_panel_io.inc
.. include-build-file:: inc/esp_lcd_panel_ops.inc
.. include-build-file:: inc/esp_lcd_panel_vendor.inc
@@ -0,0 +1,76 @@
Parallel IO simulation of SPI or I80 Interfaced LCD
===================================================
:link_to_translation:`zh_CN:[中文]`
Parallel IO is not a bus-type peripheral. The driver directly creates a Parallel IO device for the LCD. Currently the driver supports SPI (1 bit data width) and I80 (8 bit data width) modes.
#. Create Parallel IO device by :cpp:func:`esp_lcd_new_panel_io_parl`. You need to set up the following parameters for a Parallel IO device:
- :cpp:member:`esp_lcd_panel_io_parl_config_t::clk_src` sets the clock source of the Parallel IO device. Note, the default clock source may be different between ESP targets.
- :cpp:member:`esp_lcd_panel_io_parl_config_t::clk_gpio_num` sets the GPIO number of the pixel clock (also referred as ``WR`` or ``SCLK`` in some LCD spec)
- :cpp:member:`esp_lcd_panel_io_parl_config_t::dc_gpio_num` sets the GPIO number of the data or command select pin (also referred as ``RS`` in some LCD spec)
- :cpp:member:`esp_lcd_panel_io_parl_config_t::cs_gpio_num` sets the GPIO number of the chip select pin. (Note that the Parallel IO LCD driver only supports a single LCD device).
- :cpp:member:`esp_lcd_panel_io_parl_config_t::data_width` sets the bit width of the data bus (only support ``1`` or ``8``)
- :cpp:member:`esp_lcd_panel_io_parl_config_t::data_gpio_nums` is the array of the GPIO number of the data bus. The number of GPIOs should be equal to the :cpp:member:`esp_lcd_panel_io_parl_config_t::data_width` value.
- :cpp:member:`esp_lcd_panel_io_parl_config_t::max_transfer_bytes` sets the maximum number of bytes that can be transferred in one transaction.
- :cpp:member:`esp_lcd_panel_io_parl_config_t::dma_burst_size` sets the number of bytes transferred by dma burst.
- :cpp:member:`esp_lcd_panel_io_parl_config_t::pclk_hz` sets the pixel clock frequency in Hz. Higher pixel clock frequency results in higher refresh rate, but may cause display abnormalities if the DMA bandwidth is not sufficient or the LCD controller chip does not support high pixel clock frequency.
- :cpp:member:`esp_lcd_panel_io_parl_config_t::dc_levels` sets the effective level for DC data selection and command selection.
- :cpp:member:`esp_lcd_panel_io_parl_config_t::lcd_cmd_bits` and :cpp:member:`esp_lcd_panel_io_parl_config_t::lcd_param_bits` set the bit width of the command and parameter that recognized by the LCD controller chip. This is chip specific, you should refer to your LCD spec in advance.
- :cpp:member:`esp_lcd_panel_io_parl_config_t::trans_queue_depth` sets the maximum number of transactions that can be queued in the Parallel IO device. A bigger value means more transactions can be queued up, but it also consumes more memory.
.. code-block:: c
esp_lcd_panel_io_handle_t io_handle = NULL;
esp_lcd_panel_io_parl_config_t io_config = {
.clk_src = PARLIO_CLK_SRC_DEFAULT,
.dc_gpio_num = EXAMPLE_PIN_NUM_DC,
.clk_gpio_num = EXAMPLE_PIN_NUM_PCLK,
.data_gpio_nums = {
EXAMPLE_PIN_NUM_DATA0, // set DATA0 to drive SPI interfaced LCD or set DATA0~7 to drive I80 interfaced LCD
},
.data_width = 1, // set 1 to drive SPI interfaced LCD or set 8 to drive I80 interfaced LCD
.max_transfer_bytes = EXAMPLE_LCD_H_RES * 100 * sizeof(uint16_t), // transfer 100 lines of pixels (assume pixel is RGB565) at most in one transaction
.dma_burst_size = EXAMPLE_DMA_BURST_SIZE,
.cs_gpio_num = EXAMPLE_PIN_NUM_CS,
.pclk_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ,
.trans_queue_depth = 10,
.dc_levels = {
.dc_cmd_level = 0,
.dc_data_level = 1,
},
.lcd_cmd_bits = EXAMPLE_LCD_CMD_BITS,
.lcd_param_bits = EXAMPLE_LCD_PARAM_BITS,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_io_parl(&io_config, io_handle));
.. only:: not SOC_PARLIO_SUPPORT_I80_LCD
.. note::
Due to hardware limitations, {IDF_TARGET_NAME} can not drive I80 interfaced LCD by Parallel IO.
#. Install the LCD controller driver. The LCD controller driver is responsible for sending the commands and parameters to the LCD controller chip. In this step, you need to specify the Parallel IO device handle that allocated in the last step, and some panel specific configurations:
- :cpp:member:`esp_lcd_panel_dev_config_t::reset_gpio_num` sets the LCD's hardware reset GPIO number. If the LCD does not have a hardware reset pin, set this to ``-1``.
- :cpp:member:`esp_lcd_panel_dev_config_t::rgb_ele_order` sets the RGB element order of each color data.
- :cpp:member:`esp_lcd_panel_dev_config_t::bits_per_pixel` sets the bit width of the pixel color data. The LCD driver uses this value to calculate the number of bytes to send to the LCD controller chip.
- :cpp:member:`esp_lcd_panel_dev_config_t::data_endian` specifies the data endian to be transmitted to the screen. No need to specify for color data within one byte, like RGB232. For drivers that do not support specifying data endian, this field would be ignored.
.. code-block:: c
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = EXAMPLE_PIN_NUM_RST,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR,
.bits_per_pixel = 16,
};
// Create LCD panel handle for ST7789, with the Parallel IO device handle
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle));
API Reference
-------------
.. include-build-file:: inc/esp_lcd_io_parl.inc
@@ -0,0 +1,316 @@
RGB Interfaced LCD
==================
:link_to_translation:`zh_CN:[中文]`
RGB LCD panel is created by :cpp:func:`esp_lcd_new_rgb_panel`, with various configurations specified in :cpp:type:`esp_lcd_rgb_panel_config_t`.
- :cpp:member:`esp_lcd_rgb_panel_config_t::clk_src` selects the clock source of the RGB LCD controller. The available clock sources are listed in :cpp:type:`lcd_clock_source_t`.
- :cpp:member:`esp_lcd_rgb_panel_config_t::data_width` sets number of data lines consumed by the RGB interface. It can be 8/16/24.
- :cpp:member:`esp_lcd_rgb_panel_config_t::bits_per_pixel` specifies the number of bits per pixel. This differs from :cpp:member:`esp_lcd_rgb_panel_config_t::data_width`. By default, if this field is set to 0, the driver will automatically match the bpp to the value set in :cpp:member:`esp_lcd_rgb_panel_config_t::data_width`. However, in some scenarios, these values need to be different. For instance, a serial RGB interfaced LCD might only require ``8`` data lines, but the color depth could be ``RGB888``, meaning :cpp:member:`esp_lcd_rgb_panel_config_t::bits_per_pixel` should be set to ``24``.
- :cpp:member:`esp_lcd_rgb_panel_config_t::hsync_gpio_num`, :cpp:member:`esp_lcd_rgb_panel_config_t::vsync_gpio_num`, :cpp:member:`esp_lcd_rgb_panel_config_t::de_gpio_num`, :cpp:member:`esp_lcd_rgb_panel_config_t::pclk_gpio_num`, :cpp:member:`esp_lcd_rgb_panel_config_t::disp_gpio_num` and :cpp:member:`esp_lcd_rgb_panel_config_t::data_gpio_nums` are GPIO pins consumed by the RGB LCD controller. If any of them are not used, please set them to ``-1``.
- :cpp:member:`esp_lcd_rgb_panel_config_t::dma_burst_size` specifies the size of the DMA transfer burst. Ensure this value is a power of 2.
- :cpp:member:`esp_lcd_rgb_panel_config_t::bounce_buffer_size_px` specifies the size of the bounce buffer. This is required only for the "bounce buffer" mode. For more details, see :ref:`bounce_buffer_with_single_psram_frame_buffer`.
- :cpp:member:`esp_lcd_rgb_panel_config_t::timings` specifies the timing parameters unique to the LCD panel. These parameters, detailed in :cpp:type:`esp_lcd_rgb_timing_t`, include the LCD resolution and blanking porches. Ensure they are set according to your LCD's datasheet.
- :cpp:member:`esp_lcd_rgb_panel_config_t::fb_in_psram` determines if the frame buffer should be allocated from PSRAM. For further details, see :ref:`single_frame_buffer_in_psram`.
- :cpp:member:`esp_lcd_rgb_panel_config_t::num_fbs` specifies how many frame buffers the driver should allocate. For backward compatibility, setting this to ``0`` will allocate a single frame buffer. If you don't want to allocate any frame buffer, use :cpp:member:`esp_lcd_rgb_panel_config_t::no_fb` instead.
- :cpp:member:`esp_lcd_rgb_panel_config_t::no_fb` determines whether frame buffer will be allocated. When it is set, no frame buffer will be allocated. This is also called the :ref:`bounce_buffer_only` mode.
.. note::
- When :cpp:member:`esp_lcd_rgb_panel_config_t::data_width` is 8:
- The PCLK frequency is recommended to be less than 80 MHz.
- If YUV-RGB format conversion is also configured via :cpp:func:`esp_lcd_rgb_panel_set_yuv_conversion`, the PCLK frequency is recommended to be less than 60 MHz.
- When :cpp:member:`esp_lcd_rgb_panel_config_t::data_width` is 16:
- The PCLK frequency is recommended to be less than 40 MHz.
- If YUV-RGB format conversion is also configured, the PCLK frequency is recommended to be less than 30 MHz.
GPIO Matrix and IOMUX Pins
--------------------------
.. only:: esp32s31
On {IDF_TARGET_NAME}, the RGB LCD driver can use IOMUX automatically when all required signals are mapped to dedicated pins:
- If ``data_gpio_nums``, ``hsync_gpio_num``, ``vsync_gpio_num``, ``pclk_gpio_num``, and ``de_gpio_num`` all match their dedicated IOMUX pins, the driver bypasses the GPIO matrix automatically.
- If any one of these signals does not match the dedicated IOMUX pin, the driver falls back to GPIO matrix routing automatically.
For higher pixel clock configurations, using IOMUX pins is recommended for better timing robustness.
Dedicated RGB IOMUX pins on {IDF_TARGET_NAME} are listed below:
- When ``data_width = 8``, ``DATA[0:7]`` is used
- When ``data_width = 16``, ``DATA[0:15]`` is used
- When ``data_width = 24``, ``DATA[0:23]`` is used
.. list-table::
:widths: 35 65
:header-rows: 1
* - Signal
- GPIO
* - DATA[0:7]
- 8, 9, 10, 11, 12, 13, 14, 15
* - DATA[8:15]
- 16, 17, 18, 19, 33, 34, 35, 36
* - DATA[16:23]
- 37, 38, 39, 2, 3, 4, 5, 7
* - HSYNC
- 44
* - VSYNC
- 45
* - PCLK
- 40
* - DE
- 43
.. only:: not esp32s31
On {IDF_TARGET_NAME}, RGB signals are routed through the GPIO matrix.
RGB LCD Frame Buffer Operation Modes
------------------------------------
Most of the time, the RGB LCD driver should maintain at least one screen sized frame buffer. According to the number and location of the frame buffer, the driver provides several different buffer modes.
Single Frame Buffer in Internal Memory
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This is the default and simplest and you do not have to specify flags or bounce buffer options. A frame buffer is allocated from the internal memory. The frame data is read out by DMA to the LCD verbatim. It needs no CPU intervention to function, but it has the downside that it uses up a fair bit of the limited amount of internal memory.
.. code:: c
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_rgb_panel_config_t panel_config = {
.data_width = 16, // RGB565 in parallel mode, thus 16 bits in width
.clk_src = LCD_CLK_SRC_DEFAULT,
.disp_gpio_num = EXAMPLE_PIN_NUM_DISP_EN,
.pclk_gpio_num = EXAMPLE_PIN_NUM_PCLK,
.vsync_gpio_num = EXAMPLE_PIN_NUM_VSYNC,
.hsync_gpio_num = EXAMPLE_PIN_NUM_HSYNC,
.de_gpio_num = EXAMPLE_PIN_NUM_DE,
.data_gpio_nums = {
EXAMPLE_PIN_NUM_DATA0,
EXAMPLE_PIN_NUM_DATA1,
EXAMPLE_PIN_NUM_DATA2,
// other GPIOs
// The number of GPIOs here should be the same to the value of "data_width" above
...
},
// The timing parameters should refer to your LCD spec
.timings = {
.pclk_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ,
.h_res = EXAMPLE_LCD_H_RES,
.v_res = EXAMPLE_LCD_V_RES,
.hsync_back_porch = 40,
.hsync_front_porch = 20,
.hsync_pulse_width = 1,
.vsync_back_porch = 8,
.vsync_front_porch = 4,
.vsync_pulse_width = 1,
},
};
ESP_ERROR_CHECK(esp_lcd_new_rgb_panel(&panel_config, &panel_handle));
.. _single_frame_buffer_in_psram:
Single Frame Buffer in PSRAM
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If you have PSRAM and prefer to store the frame buffer there instead of using the limited internal memory, the LCD peripheral can utilize EDMA to fetch frame data directly from PSRAM, bypassing the internal cache. This can be enabled by setting :cpp:member:`esp_lcd_rgb_panel_config_t::fb_in_psram` to ``true``. The trade-off is that when both the CPU and EDMA need access to PSRAM, the bandwidth is **shared** between them, meaning EDMA and the CPU each get half. If other peripherals are also using EDMA, a high pixel clock might cause LCD peripheral starvation, leading to display corruption. However, with a sufficiently low pixel clock, this approach minimizes CPU intervention.
.. only:: esp32s3
The PSRAM shares the same SPI bus with the main flash (the one stores your firmware binary). At any given time, there can only be one consumer of the SPI bus. When you also use the main flash to serve your file system (e.g., :doc:`SPIFFS </api-reference/storage/spiffs>`), the bandwidth of the underlying SPI bus will also be shared, leading to display corruption. You can use :cpp:func:`esp_lcd_rgb_panel_set_pclk` to update the pixel clock frequency to a lower value.
.. code:: c
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_rgb_panel_config_t panel_config = {
.data_width = 16, // RGB565 in parallel mode, thus 16 bits in width
.clk_src = LCD_CLK_SRC_DEFAULT,
.disp_gpio_num = EXAMPLE_PIN_NUM_DISP_EN,
.pclk_gpio_num = EXAMPLE_PIN_NUM_PCLK,
.vsync_gpio_num = EXAMPLE_PIN_NUM_VSYNC,
.hsync_gpio_num = EXAMPLE_PIN_NUM_HSYNC,
.de_gpio_num = EXAMPLE_PIN_NUM_DE,
.data_gpio_nums = {
EXAMPLE_PIN_NUM_DATA0,
EXAMPLE_PIN_NUM_DATA1,
EXAMPLE_PIN_NUM_DATA2,
// other GPIOs
// The number of GPIOs here should be the same to the value of "data_width" above
...
},
// The timing parameters should refer to your LCD spec
.timings = {
.pclk_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ,
.h_res = EXAMPLE_LCD_H_RES,
.v_res = EXAMPLE_LCD_V_RES,
.hsync_back_porch = 40,
.hsync_front_porch = 20,
.hsync_pulse_width = 1,
.vsync_back_porch = 8,
.vsync_front_porch = 4,
.vsync_pulse_width = 1,
},
.flags.fb_in_psram = true, // allocate frame buffer from PSRAM
};
ESP_ERROR_CHECK(esp_lcd_new_rgb_panel(&panel_config, &panel_handle));
.. _double_frame_buffer_in_psram:
Double Frame Buffer in PSRAM
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
To prevent tearing effects, the simplest method is to use two screen-sized frame buffers. Given the limited internal memory, these buffers must be allocated from PSRAM. This ensures that the frame buffer being written to by the CPU and the one being read by the EDMA are always distinct and independent. The EDMA will only switch between the two buffers once the current write operation is complete and the frame has been fully transmitted to the LCD. The main drawback of this approach is the need to maintain synchronization between the two frame buffers.
.. code:: c
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_rgb_panel_config_t panel_config = {
.data_width = 16, // RGB565 in parallel mode, thus 16 bits in width
.num_fbs = 2, // allocate double frame buffer
.clk_src = LCD_CLK_SRC_DEFAULT,
.disp_gpio_num = EXAMPLE_PIN_NUM_DISP_EN,
.pclk_gpio_num = EXAMPLE_PIN_NUM_PCLK,
.vsync_gpio_num = EXAMPLE_PIN_NUM_VSYNC,
.hsync_gpio_num = EXAMPLE_PIN_NUM_HSYNC,
.de_gpio_num = EXAMPLE_PIN_NUM_DE,
.data_gpio_nums = {
EXAMPLE_PIN_NUM_DATA0,
EXAMPLE_PIN_NUM_DATA1,
EXAMPLE_PIN_NUM_DATA2,
// other GPIOs
// The number of GPIOs here should be the same to the value of "data_width" above
...
},
// The timing parameters should refer to your LCD spec
.timings = {
.pclk_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ,
.h_res = EXAMPLE_LCD_H_RES,
.v_res = EXAMPLE_LCD_V_RES,
.hsync_back_porch = 40,
.hsync_front_porch = 20,
.hsync_pulse_width = 1,
.vsync_back_porch = 8,
.vsync_front_porch = 4,
.vsync_pulse_width = 1,
},
.flags.fb_in_psram = true, // allocate frame buffer from PSRAM
};
ESP_ERROR_CHECK(esp_lcd_new_rgb_panel(&panel_config, &panel_handle));
.. _user_custom_frame_buffer:
User Custom Frame Buffer
^^^^^^^^^^^^^^^^^^^^^^^^
User can provide their own frame buffer instead of letting the driver allocate it. In this mode, user needs to manage the lifecycle of the frame buffer by themselves.
.. code:: c
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_rgb_panel_config_t panel_config = {
.data_width = 16, // RGB565 in parallel mode, thus 16 bits in width
.clk_src = LCD_CLK_SRC_DEFAULT,
.disp_gpio_num = EXAMPLE_PIN_NUM_DISP_EN,
.pclk_gpio_num = EXAMPLE_PIN_NUM_PCLK,
.vsync_gpio_num = EXAMPLE_PIN_NUM_VSYNC,
.hsync_gpio_num = EXAMPLE_PIN_NUM_HSYNC,
.de_gpio_num = EXAMPLE_PIN_NUM_DE,
.data_gpio_nums = {
EXAMPLE_PIN_NUM_DATA0,
EXAMPLE_PIN_NUM_DATA1,
EXAMPLE_PIN_NUM_DATA2,
// other GPIOs
// The number of GPIOs here should be the same to the value of "data_width" above
...
},
// The timing parameters should refer to your LCD spec
.timings = {
.pclk_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ,
.h_res = EXAMPLE_LCD_H_RES,
.v_res = EXAMPLE_LCD_V_RES,
.hsync_back_porch = 40,
.hsync_front_porch = 20,
.hsync_pulse_width = 1,
.vsync_back_porch = 8,
.vsync_front_porch = 4,
.vsync_pulse_width = 1,
},
.user_fbs[0] = user_frame_buffer, // use user custom frame buffer
};
ESP_ERROR_CHECK(esp_lcd_new_rgb_panel(&panel_config, &panel_handle));
.. _bounce_buffer_with_single_psram_frame_buffer:
Bounce Buffer with Single PSRAM Frame Buffer
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This mode allocates two "bounce buffers" from internal memory and a main frame buffer in PSRAM. To enable this mode, set the :cpp:member:`esp_lcd_rgb_panel_config_t::fb_in_psram` flag and specify a non-zero value for :cpp:member:`esp_lcd_rgb_panel_config_t::bounce_buffer_size_px`. The bounce buffers only need to hold a few lines of display data, which is much smaller than the main frame buffer. The LCD peripheral uses DMA to read data from one bounce buffer while an interrupt routine uses the CPU DCache to copy data from the main PSRAM frame buffer into the other bounce buffer. Once the LCD peripheral finishes reading from the bounce buffer, the buffers swap roles, allowing the CPU to fill the other one. The advantage of this mode is achieving a higher pixel clock frequency. Since the bounce buffers are larger than the FIFOs in the EDMA path, this method is also more robust against short bandwidth spikes. The downside is a significant increase in CPU usage, and the LCD **CANNOT** function if the external memory cache is disabled, such as during OTA or NVS writes to the main flash.
.. note::
For optimal performance in this mode, it is highly recommended to enable the "PSRAM XIP (Execute In Place)" feature by turning on the Kconfig option: :ref:`CONFIG_SPIRAM_XIP_FROM_PSRAM`. This allows the CPU to fetch instructions and read-only data directly from PSRAM instead of the main flash. Additionally, the external memory cache remains active even when writing to the main flash via SPI 1, making it feasible to display an OTA progress bar during your application updates.
.. note::
This mode also faces issues due to limited PSRAM bandwidth. For instance, if your draw buffers are in PSRAM and their contents are copied to the internal frame buffer by CPU Core 1, while CPU Core 0 is performing another memory copy in the DMA EOF ISR, both CPUs will be accessing PSRAM via cache, sharing its bandwidth. This significantly increases the memory copy time in the DMA EOF ISR, causing the driver to fail in switching the bounce buffer promptly, resulting in a screen shift. Although the driver can detect this condition and restart in the LCD's VSYNC interrupt handler, you may still notice flickering on the screen.
.. code:: c
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_rgb_panel_config_t panel_config = {
.data_width = 16, // RGB565 in parallel mode, thus 16 bits in width
.clk_src = LCD_CLK_SRC_DEFAULT,
.bounce_buffer_size_px = 10 * EXAMPLE_LCD_H_RES, // allocate 10 lines data as bounce buffer from internal memory
.disp_gpio_num = EXAMPLE_PIN_NUM_DISP_EN,
.pclk_gpio_num = EXAMPLE_PIN_NUM_PCLK,
.vsync_gpio_num = EXAMPLE_PIN_NUM_VSYNC,
.hsync_gpio_num = EXAMPLE_PIN_NUM_HSYNC,
.de_gpio_num = EXAMPLE_PIN_NUM_DE,
.data_gpio_nums = {
EXAMPLE_PIN_NUM_DATA0,
EXAMPLE_PIN_NUM_DATA1,
EXAMPLE_PIN_NUM_DATA2,
// other GPIOs
// The number of GPIOs here should be the same to the value of "data_width" above
...
},
// The timing parameters should refer to your LCD spec
.timings = {
.pclk_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ,
.h_res = EXAMPLE_LCD_H_RES,
.v_res = EXAMPLE_LCD_V_RES,
.hsync_back_porch = 40,
.hsync_front_porch = 20,
.hsync_pulse_width = 1,
.vsync_back_porch = 8,
.vsync_front_porch = 4,
.vsync_pulse_width = 1,
},
.flags.fb_in_psram = true, // allocate frame buffer from PSRAM
};
ESP_ERROR_CHECK(esp_lcd_new_rgb_panel(&panel_config, &panel_handle));
.. _bounce_buffer_only:
Bounce Buffer Only
^^^^^^^^^^^^^^^^^^
This mode is similar to :ref:`bounce_buffer_with_single_psram_frame_buffer`, but there is no PSRAM frame buffer initialized by the LCD driver. Instead, the user supplies a callback function that is responsible for filling the bounce buffers. As this driver does not care where the written pixels come from, this allows for the callback doing e.g., on-the-fly conversion from a smaller, 8-bit-per-pixel PSRAM frame buffer to a 16-bit LCD, or even procedurally generated frame-buffer-less graphics. This option is selected by setting the :cpp:member:`esp_lcd_rgb_panel_config_t::no_fb` flag and supplying a :cpp:member:`esp_lcd_rgb_panel_config_t::bounce_buffer_size_px` value. And then register the :cpp:member:`esp_lcd_rgb_panel_event_callbacks_t::on_bounce_empty` callback by calling :cpp:func:`esp_lcd_rgb_panel_register_event_callbacks`.
.. note::
In a well-designed embedded application, situations where the DMA cannot deliver data as fast as the LCD consumes it should be avoided. However, such scenarios can theoretically occur. In the {IDF_TARGET_NAME} hardware, this results in the LCD outputting dummy bytes while the DMA waits for data. If the DMA were to run in a continuous stream, it could cause a desynchronization between the LCD address from which the DMA reads data and the address from which the LCD peripheral outputs data, leading to a **permanently** shifted image.
To prevent this, you can either enable the :ref:`CONFIG_LCD_RGB_RESTART_IN_VSYNC` option, allowing the driver to automatically restart the DMA during the VBlank interrupt, or call :cpp:func:`esp_lcd_rgb_panel_restart` to manually restart the DMA. Note that :cpp:func:`esp_lcd_rgb_panel_restart` does not restart the DMA immediately; instead, the DMA will be restarted at the next VSYNC event.
API Reference
-------------
.. include-build-file:: inc/esp_lcd_panel_rgb.inc
@@ -0,0 +1,69 @@
SPI Interfaced LCD
==================
:link_to_translation:`zh_CN:[中文]`
#. Create an SPI bus. Please refer to :doc:`SPI Master API doc </api-reference/peripherals/spi_master>` for more details.
Currently the driver supports SPI, Quad SPI and Octal SPI (simulate Intel 8080 timing) modes.
.. code-block:: c
spi_bus_config_t buscfg = {
.sclk_io_num = EXAMPLE_PIN_NUM_SCLK,
.mosi_io_num = EXAMPLE_PIN_NUM_MOSI,
.miso_io_num = EXAMPLE_PIN_NUM_MISO,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = EXAMPLE_LCD_H_RES * 80 * sizeof(uint16_t), // transfer 80 lines of pixels (assume pixel is RGB565) at most in one SPI transaction
};
ESP_ERROR_CHECK(spi_bus_initialize(LCD_HOST, &buscfg, SPI_DMA_CH_AUTO)); // Enable the DMA feature
#. Allocate an LCD IO device handle from the SPI bus. In this step, you need to provide the following information:
- :cpp:member:`esp_lcd_panel_io_spi_config_t::dc_gpio_num` sets the GPIO number for the DC signal line (some LCD calls this ``RS`` line). The LCD driver uses this GPIO to switch between sending command and sending data.
- :cpp:member:`esp_lcd_panel_io_spi_config_t::cs_gpio_num` sets the GPIO number for the CS signal line. The LCD driver uses this GPIO to select the LCD chip. If the SPI bus only has one device attached (i.e., this LCD), you can set the GPIO number to ``-1`` to occupy the bus exclusively.
- :cpp:member:`esp_lcd_panel_io_spi_config_t::pclk_hz` sets the frequency of the pixel clock, in Hz. The value should not exceed the range recommended in the LCD spec.
- :cpp:member:`esp_lcd_panel_io_spi_config_t::spi_mode` sets the SPI mode. The LCD driver uses this mode to communicate with the LCD. For the meaning of the SPI mode, please refer to the :doc:`SPI Master API doc </api-reference/peripherals/spi_master>`.
- :cpp:member:`esp_lcd_panel_io_spi_config_t::lcd_cmd_bits` and :cpp:member:`esp_lcd_panel_io_spi_config_t::lcd_param_bits` set the bit width of the command and parameter that recognized by the LCD controller chip. This is chip specific, you should refer to your LCD spec in advance.
- :cpp:member:`esp_lcd_panel_io_spi_config_t::trans_queue_depth` sets the depth of the SPI transaction queue. A bigger value means more transactions can be queued up, but it also consumes more memory.
- :cpp:member:`esp_lcd_panel_io_spi_config_t::cs_ena_pretrans` sets the amount of SPI bit-cycles which the cs should be activated before the transmission (0-16).
- :cpp:member:`esp_lcd_panel_io_spi_config_t::cs_ena_posttrans` sets the amount of SPI bit-cycles which the cs should stay active after the transmission (0-16).
.. code-block:: c
esp_lcd_panel_io_handle_t io_handle = NULL;
esp_lcd_panel_io_spi_config_t io_config = {
.dc_gpio_num = EXAMPLE_PIN_NUM_LCD_DC,
.cs_gpio_num = EXAMPLE_PIN_NUM_LCD_CS,
.pclk_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ,
.lcd_cmd_bits = EXAMPLE_LCD_CMD_BITS,
.lcd_param_bits = EXAMPLE_LCD_PARAM_BITS,
.spi_mode = 0,
.trans_queue_depth = 10,
};
// Attach the LCD to the SPI bus
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_HOST, &io_config, &io_handle));
#. Install the LCD controller driver. The LCD controller driver is responsible for sending the commands and parameters to the LCD controller chip. In this step, you need to specify the SPI IO device handle that allocated in the last step, and some panel specific configurations:
- :cpp:member:`esp_lcd_panel_dev_config_t::reset_gpio_num` sets the LCD's hardware reset GPIO number. If the LCD does not have a hardware reset pin, set this to ``-1``.
- :cpp:member:`esp_lcd_panel_dev_config_t::rgb_ele_order` sets the RGB element order of each color data.
- :cpp:member:`esp_lcd_panel_dev_config_t::bits_per_pixel` sets the bit width of the pixel color data. The LCD driver uses this value to calculate the number of bytes to send to the LCD controller chip.
- :cpp:member:`esp_lcd_panel_dev_config_t::data_endian` specifies the data endian to be transmitted to the screen. No need to specify for color data within one byte, like RGB232. For drivers that do not support specifying data endian, this field would be ignored.
.. code-block:: c
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = EXAMPLE_PIN_NUM_RST,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR,
.bits_per_pixel = 16,
};
// Create LCD panel handle for ST7789, with the SPI IO device handle
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle));
API Reference
-------------
.. include-build-file:: inc/esp_lcd_io_spi.inc
@@ -0,0 +1,57 @@
Low Dropout Voltage Regulator (LDO)
===================================
:link_to_translation:`zh_CN:[中文]`
Introduction
------------
The {IDF_TARGET_NAME} chip internally integrates {IDF_TARGET_SOC_GP_LDO_NUM_UNITS} channels of low-dropout voltage regulators (LDOs). Each channel's voltage is programmable. In our hardware reference designs, some of these LDO outputs are typically used to power the internal Flash and PSRAM, while the remaining LDOs can be used to supply external devices.
.. note::
It's essential to read the manual first and ensure that the required current does not exceed the chip's specifications.
Functional Overview
-------------------
The description of the LDO driver is divided into the following sections:
- :ref:`ldo-channel-acquisition` - Introduces the types of LDO channels and how to apply for LDO channel resources.
- :ref:`ldo-adjust-voltage` - Describes how to adjust the voltage of the LDO channel.
.. _ldo-channel-acquisition:
LDO Channel Acquisition
^^^^^^^^^^^^^^^^^^^^^^^
LDO channels can be classified into two types: fixed voltage and adjustable voltage. For a fixed voltage channel, it allows multiple users to simultaneously use it (in software, we allow a variable to have multiple immutable references ). However, for an adjustable voltage channel, only one user is allowed to use it at a time (in software, we don't allow a variable to have multiple mutable references or coexistence of mutable and immutable references).
In the driver, the LDO channel is represented by the :cpp:type:`esp_ldo_channel_handle_t`. You can use the :cpp:func:`esp_ldo_acquire_channel` function to request LDO channel resources. Upon successful acquisition, a handle for the LDO channel will be returned, which can be used for subsequent voltage adjustment operations. When applying for a channel, the :cpp:type:`esp_ldo_channel_config_t` structure is used to specify the basic information of the LDO channel, including the channel ID, the desired output voltage, and whether the voltage can be dynamically adjusted.
- :cpp:member:`esp_ldo_channel_config_t::chan_id` - LDO channels are uniquely identified by a label, which is used to distinguish different LDO channels. Please note that this information needs to be determined based on the circuit schematic and chip datasheet. For example, an LDO channel labeled as ``LDO_VO3`` corresponds to an ID of ``3``.
- :cpp:member:`esp_ldo_channel_config_t::voltage_mv` - The desired output voltage of the LDO channel, in millivolts.
- :cpp:member:`esp_ldo_channel_config_t::ldo_extra_flags::adjustable` - Whether the LDO channel's output voltage can be dynamically adjusted. Only when it is set to `true`, can the :cpp:func:`esp_ldo_channel_adjust_voltage` function be used to dynamically adjust the output voltage.
Since multiple users are allowed to use a fixed voltage LDO channel simultaneously, the driver internally maintains a reference counter. The LDO channel will be automatically closed when the last user releases the LDO channel resources. The function to release LDO channel resources is :cpp:func:`esp_ldo_release_channel`. Additionally, it is important to note that the acquisition and release of LDO channels should appear in pairs during usage.
.. _ldo-adjust-voltage:
LDO Voltage Adjustment
^^^^^^^^^^^^^^^^^^^^^^
:cpp:func:`esp_ldo_channel_adjust_voltage` function is used to adjust the output voltage of an LDO channel at runtime. However, please note that this function can only be used for LDO channels with adjustable voltage. Attempting to use this function on a fixed voltage LDO channel will result in an error.
Also, it is important to keep in mind that due to hardware limitations, the LDO channel voltage may have a deviation of approximately 50-100mV. Therefore, it is not advisable to rely on the LDO channel's output voltage for precise analog control.
Application Examples
--------------------
.. list::
:SOC_MIPI_DSI_SUPPORTED: * Use the internal LDO channel to power up the MIPI DPHY: :example:`peripherals/lcd/mipi_dsi`
API Reference
-------------
.. include-build-file:: inc/esp_ldo_regulator.inc
+445
View File
@@ -0,0 +1,445 @@
LED Control (LEDC)
==================
:link_to_translation:`zh_CN:[中文]`
Introduction
------------
The LED control (LEDC) peripheral is primarily designed to control the intensity of LEDs, although it can also be used to generate PWM signals for other purposes. It has {IDF_TARGET_SOC_LEDC_CHANNEL_NUM} channels which can generate independent waveforms that can be used, for example, to drive RGB LED devices.
.. only:: esp32
LEDC channels are divided into two groups of 8 channels each. One group of LEDC channels operates in high speed mode. This mode is implemented in hardware and offers automatic and glitch-free changing of the PWM duty cycle. The other group of channels operate in low speed mode, the PWM duty cycle must be changed by the driver in software. Each group of channels is also able to use different clock sources.
The PWM controller can automatically increase or decrease the duty cycle gradually, allowing for fades without any processor interference.
Functionality Overview
----------------------
.. only:: esp32
Setting up a channel of the LEDC in either :ref:`high or low speed mode <ledc-api-high_low_speed_mode>` is done in three steps:
.. only:: not esp32
Setting up a channel of the LEDC is done in three steps. Note that unlike ESP32, {IDF_TARGET_NAME} only supports configuring channels in "low speed" mode.
1. :ref:`ledc-api-configure-timer` by specifying the PWM signal's frequency and duty cycle resolution.
2. :ref:`ledc-api-configure-channel` by associating it with the timer and GPIO to output the PWM signal.
3. :ref:`ledc-api-change-pwm-signal` that drives the output in order to change LED's intensity. This can be done under the full control of software or with hardware fading functions.
As an optional step, it is also possible to set up an interrupt on fade end.
.. figure:: ../../../_static/ledc-api-settings.jpg
:align: center
:alt: Key Settings of LED PWM Controller's API
:figclass: align-center
Key Settings of LED PWM Controller's API
.. note::
For an initial setup, it is recommended to configure for the timers first (by calling :cpp:func:`ledc_timer_config`), and then for the channels (by calling :cpp:func:`ledc_channel_config`). This ensures the PWM frequency is at the desired value since the appearance of the PWM signal from the IO pad.
.. _ledc-api-configure-timer:
Timer Configuration
^^^^^^^^^^^^^^^^^^^
Setting the timer is done by calling the function :cpp:func:`ledc_timer_config` and passing the data structure :cpp:type:`ledc_timer_config_t` that contains the following configuration settings:
.. list::
:esp32: - Speed mode :cpp:type:`ledc_mode_t`
:not esp32: - Speed mode (value must be ``LEDC_LOW_SPEED_MODE``)
- Timer number :cpp:type:`ledc_timer_t`
- PWM signal frequency in Hz
- Resolution of PWM duty
- Source clock :cpp:type:`ledc_clk_cfg_t`
The frequency and the duty resolution are interdependent. The higher the PWM frequency, the lower the duty resolution which is available, and vice versa. This relationship might be important if you are planning to use this API for purposes other than changing the intensity of LEDs. For more details, see Section :ref:`ledc-api-supported-range-frequency-duty-resolution`.
The source clock can also limit the PWM frequency. The higher the source clock frequency, the higher the maximum PWM frequency can be configured.
.. only:: esp32
.. list-table:: Characteristics of {IDF_TARGET_NAME} LEDC source clocks
:widths: 5 5 5 20
:header-rows: 1
* - Clock name
- Clock freq
- Speed mode
- Clock capabilities
* - APB_CLK
- 80 MHz
- High / Low
- /
* - REF_TICK
- 1 MHz
- High / Low
- Dynamic Frequency Scaling compatible
* - RC_FAST_CLK
- ~ 8 MHz
- Low
- Dynamic Frequency Scaling compatible, Light-sleep compatible
.. only:: esp32s2
.. list-table:: Characteristics of {IDF_TARGET_NAME} LEDC source clocks
:widths: 15 15 30
:header-rows: 1
* - Clock name
- Clock freq
- Clock capabilities
* - APB_CLK
- 80 MHz
- /
* - REF_TICK
- 1 MHz
- Dynamic Frequency Scaling compatible
* - RC_FAST_CLK
- ~ 8 MHz
- Dynamic Frequency Scaling compatible, Light-sleep compatible
* - XTAL_CLK
- 40 MHz
- Dynamic Frequency Scaling compatible
.. only:: esp32s3 or esp32c3
.. list-table:: Characteristics of {IDF_TARGET_NAME} LEDC source clocks
:widths: 15 15 30
:header-rows: 1
* - Clock name
- Clock freq
- Clock capabilities
* - APB_CLK
- 80 MHz
- /
* - RC_FAST_CLK
- ~ 20 MHz
- Dynamic Frequency Scaling compatible, Light-sleep compatible
* - XTAL_CLK
- 40 MHz
- Dynamic Frequency Scaling compatible
.. only:: esp32c2
.. list-table:: Characteristics of {IDF_TARGET_NAME} LEDC source clocks
:widths: 15 15 30
:header-rows: 1
* - Clock name
- Clock freq
- Clock capabilities
* - PLL_60M_CLK
- 60 MHz
- /
* - RC_FAST_CLK
- ~ 20 MHz
- Dynamic Frequency Scaling compatible, Light-sleep compatible
* - XTAL_CLK
- 40/26 MHz
- Dynamic Frequency Scaling compatible
.. only:: esp32c5
.. list-table:: Characteristics of {IDF_TARGET_NAME} LEDC source clocks
:widths: 15 15 30
:header-rows: 1
* - Clock name
- Clock freq
- Clock capabilities
* - PLL_80M_CLK
- 80 MHz
- /
* - RC_FAST_CLK
- ~ 17.5 MHz
- Dynamic Frequency Scaling compatible, Light-sleep compatible
* - XTAL_CLK
- 48 MHz
- Dynamic Frequency Scaling compatible
.. only:: esp32c6 or esp32c61 or esp32p4 or esp32s31
.. list-table:: Characteristics of {IDF_TARGET_NAME} LEDC source clocks
:widths: 15 15 30
:header-rows: 1
* - Clock name
- Clock freq
- Clock capabilities
* - PLL_80M_CLK
- 80 MHz
- /
* - RC_FAST_CLK
- ~ 17.5 MHz
- Dynamic Frequency Scaling compatible, Light-sleep compatible
* - XTAL_CLK
- 40 MHz
- Dynamic Frequency Scaling compatible
.. only:: esp32h2
.. list-table:: Characteristics of {IDF_TARGET_NAME} LEDC source clocks
:widths: 15 15 30
:header-rows: 1
* - Clock name
- Clock freq
- Clock capabilities
* - PLL_96M_CLK
- 96 MHz
- /
* - RC_FAST_CLK
- ~ 8 MHz
- Dynamic Frequency Scaling compatible, Light-sleep compatible
* - XTAL_CLK
- 32 MHz
- Dynamic Frequency Scaling compatible
.. only:: esp32h21 or esp32h4
.. list-table:: Characteristics of {IDF_TARGET_NAME} LEDC source clocks
:widths: 15 15 30
:header-rows: 1
* - Clock name
- Clock freq
- Clock capabilities
* - PLL_96M_CLK
- 96 MHz
- /
* - RC_FAST_CLK
- ~ 20 MHz
- Dynamic Frequency Scaling compatible, Light-sleep compatible
* - XTAL_CLK
- 32 MHz
- Dynamic Frequency Scaling compatible
.. note::
.. only:: SOC_CLK_RC_FAST_SUPPORT_CALIBRATION
1. On {IDF_TARGET_NAME}, if RC_FAST_CLK is chosen as the LEDC clock source, an internal calibration will be performed to get the exact frequency of the clock. This ensures the accuracy of output PWM signal frequency.
.. only:: not SOC_CLK_RC_FAST_SUPPORT_CALIBRATION
1. On {IDF_TARGET_NAME}, if RC_FAST_CLK is chosen as the LEDC clock source, you may see the frequency of output PWM signal is not very accurate. This is because no internal calibration is performed to get the exact frequency of the clock due to hardware limitation, a theoretic frequency value is used.
.. only:: not SOC_LEDC_HAS_TIMER_SPECIFIC_MUX
2. For {IDF_TARGET_NAME}, all timers share one clock source. In other words, it is impossible to use different clock sources for different timers.
The LEDC driver offers a helper function :cpp:func:`ledc_find_suitable_duty_resolution` to find the maximum possible resolution for the timer, given the source clock frequency and the desired PWM signal frequency.
When a timer is no longer needed by any channel, it can be deconfigured by calling the same function :cpp:func:`ledc_timer_config`. The configuration structure :cpp:type:`ledc_timer_config_t` passes in should be:
- :cpp:member:`ledc_timer_config_t::speed_mode` The speed mode of the timer which wants to be deconfigured belongs to (:cpp:type:`ledc_mode_t`)
- :cpp:member:`ledc_timer_config_t::timer_num` The ID of the timers which wants to be deconfigured (:cpp:type:`ledc_timer_t`)
- :cpp:member:`ledc_timer_config_t::deconfigure` Set this to true so that the timer specified can be deconfigured
.. _ledc-api-configure-channel:
Channel Configuration
^^^^^^^^^^^^^^^^^^^^^
When the timer is set up, configure the desired channel (one out of :cpp:type:`ledc_channel_t`). This is done by calling the function :cpp:func:`ledc_channel_config`.
Similar to the timer configuration, the channel setup function should be passed a structure :cpp:type:`ledc_channel_config_t` that contains the channel's configuration parameters.
At this point, the channel should start operating and generating the PWM signal on the selected GPIO, as configured in :cpp:type:`ledc_channel_config_t`, with the frequency specified in the timer settings and the given duty cycle. The channel operation (signal generation) can be suspended at any time by calling the function :cpp:func:`ledc_stop`.
.. _ledc-api-change-pwm-signal:
Change PWM Signal
^^^^^^^^^^^^^^^^^
Once the channel starts operating and generating the PWM signal with the constant duty cycle and frequency, there are a couple of ways to change this signal. When driving LEDs, primarily the duty cycle is changed to vary the light intensity.
The following two sections describe how to change the duty cycle using software and hardware fading. If required, the signal's frequency can also be changed; it is covered in Section :ref:`ledc-api-change-pwm-frequency`.
.. only:: not esp32
.. note::
All the timers and channels in the {IDF_TARGET_NAME}'s LED PWM Controller only support low speed mode. Any change of PWM settings must be explicitly triggered by software (see below).
Change PWM Duty Cycle Using Software
""""""""""""""""""""""""""""""""""""
To set the duty cycle, use the dedicated function :cpp:func:`ledc_set_duty`. After that, call :cpp:func:`ledc_update_duty` to activate the changes. To check the currently set value, use the corresponding ``_get_`` function :cpp:func:`ledc_get_duty`.
Another way to set the duty cycle, as well as some other channel parameters, is by calling :cpp:func:`ledc_channel_config` covered in Section :ref:`ledc-api-configure-channel`.
The range of the duty cycle values passed to functions depends on selected ``duty_resolution`` and should be from ``0`` to ``(2 ** duty_resolution)``. For example, if the selected duty resolution is 10, then the duty cycle values can range from 0 to 1024. This provides the resolution of ~ 0.1%.
.. only:: esp32 or esp32s2 or esp32s3 or esp32c3 or esp32c2 or esp32c6 or esp32h2 or esp32p4
.. warning::
On {IDF_TARGET_NAME}, when channel's binded timer selects its maximum duty resolution, the duty cycle value cannot be set to ``(2 ** duty_resolution)``. Otherwise, the internal duty counter in the hardware will overflow and be messed up.
.. only:: esp32h2
The hardware limitation above only applies to chip revision before v1.2.
.. only:: esp32p4
The hardware limitation above only applies to chip revision before v3.0.
Change PWM Duty Cycle Using Hardware
""""""""""""""""""""""""""""""""""""
The LEDC hardware provides the means to gradually transition from one duty cycle value to another. To use this functionality, enable fading with :cpp:func:`ledc_fade_func_install` and then configure it by calling one of the available fading functions:
* :cpp:func:`ledc_set_fade_with_time`
* :cpp:func:`ledc_set_fade_with_step`
* :cpp:func:`ledc_set_fade`
.. only:: SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED
On {IDF_TARGET_NAME}, the hardware additionally allows to perform consecutive linear fades without CPU intervention. This feature can be useful if you want to do a fade with gamma correction.
The luminance perceived by human eyes does not have a linear relationship with the PWM duty cycle. In order to make human feel the LED is dimming or lighting linearly, the change in duty cycle should be non-linear, which is the so-called gamma correction. The LED controller can simulate a gamma curve fading by piecewise linear approximation. :cpp:func:`ledc_fill_multi_fade_param_list` is a function that can help to construct the parameters for the piecewise linear fades. First, you need to allocate a memory block for saving the fade parameters, then by providing start/end PWM duty cycle values, gamma correction function, and the total number of desired linear segments to the helper function, it will fill the calculation results into the allocated space. You can also construct the array of :cpp:type:`ledc_fade_param_config_t` manually. Once the fade parameter structs are prepared, a consecutive fading can be configured by passing the pointer to the prepared :cpp:type:`ledc_fade_param_config_t` list and the total number of fade ranges to :cpp:func:`ledc_set_multi_fade`.
.. only:: esp32
Start fading with :cpp:func:`ledc_fade_start`. A fade can be operated in blocking or non-blocking mode, please check :cpp:enum:`ledc_fade_mode_t` for the difference between the two available fade modes. Note that with either fade mode, the next fade or fixed-duty update will not take effect until the last fade finishes. Due to hardware limitations, there is no way to stop a fade before it reaches its target duty.
.. only:: not esp32
Start fading with :cpp:func:`ledc_fade_start`. A fade can be operated in blocking or non-blocking mode, please check :cpp:enum:`ledc_fade_mode_t` for the difference between the two available fade modes. Note that with either fade mode, the next fade or fixed-duty update will not take effect until the last fade finishes or is stopped. :cpp:func:`ledc_fade_stop` has to be called to stop a fade that is in progress.
To get a notification about the completion of a fade operation, a fade end callback function can be registered for each channel by calling :cpp:func:`ledc_cb_register` after the fade service being installed. The fade end callback prototype is defined in :cpp:type:`ledc_cb_t`, where you should return a boolean value from the callback function, indicating whether a high priority task is woken up by this callback function. It is worth mentioning, the callback and the function invoked by itself should be placed in IRAM, as the interrupt service routine is in IRAM. :cpp:func:`ledc_cb_register` will print a warning message if it finds the addresses of callback and user context are incorrect.
If not required anymore, fading and an associated interrupt can be disabled with :cpp:func:`ledc_fade_func_uninstall`.
.. _ledc-api-change-pwm-frequency:
Change PWM Frequency
""""""""""""""""""""
The LEDC API provides several ways to change the PWM frequency "on the fly":
* Set the frequency by calling :cpp:func:`ledc_set_freq`. There is a corresponding function :cpp:func:`ledc_get_freq` to check the current frequency.
* Change the frequency and the duty resolution by calling :cpp:func:`ledc_bind_channel_timer` to bind some other timer to the channel.
* Change the channel's timer by calling :cpp:func:`ledc_channel_config`.
More Control Over PWM
"""""""""""""""""""""
There are several individual timer-specific functions that can be used to change PWM output:
* :cpp:func:`ledc_timer_rst`
* :cpp:func:`ledc_timer_pause`
* :cpp:func:`ledc_timer_resume`
The first function is called "behind the scenes" by :cpp:func:`ledc_timer_config` to provide a startup of a timer after it is configured.
.. only:: SOC_LEDC_SUPPORT_ETM and SOC_ETM_SUPPORTED
LEDC's ETM Events and Tasks
---------------------------
LEDC can generate various events that can be connected to the :doc:`ETM </api-reference/peripherals/etm>` module. Timer's supported events are listed in :cpp:type:`ledc_timer_etm_event_type_t`, and channel's supported events are listed in :cpp:type:`ledc_channel_etm_event_type_t`. Users can create an ``ETM event`` handle by calling :cpp:func:`ledc_timer_new_etm_event` or :cpp:func:`ledc_channel_new_etm_event` respectively.
LEDC also supports some tasks that can be triggered by other events and executed automatically. Timer's supported tasks are listed in :cpp:type:`ledc_timer_etm_task_type_t`, and channel's supported tasks are listed in :cpp:type:`ledc_channel_etm_task_type_t`. Users can create an ``ETM task`` handle by calling :cpp:func:`ledc_timer_new_etm_task` or :cpp:func:`ledc_channel_new_etm_task` respectively.
Some useful applications of ETM with LEDC are:
* To generate a PWM signal with certain number of pulses
* To synchronize the PWM period with an external signal
* To start / stop the PWM signal output or a fading without CPU intervention
For how to connect the LEDC events and tasks to the ETM channel, please refer to the :doc:`ETM </api-reference/peripherals/etm>` documentation.
Power Management
----------------
LEDC driver does not utilize power management lock to prevent the system from going into Light-sleep. Instead, the LEDC peripheral power domain state and the PWM signal output behavior during sleep can be chosen by configuring :cpp:member:`ledc_channel_config_t::sleep_mode`. The default mode is :cpp:enumerator:`LEDC_SLEEP_MODE_NO_ALIVE_NO_PD`, which stands for no signal output and LEDC power domain will not be powered down during sleep.
If signal output needs to be maintained in Light-sleep, then select :cpp:enumerator:`LEDC_SLEEP_MODE_KEEP_ALIVE`. As long as the binded LEDC timer clock source is Light-sleep compatible, the PWM signal can continue its output even the system enters Light-sleep. The cost is a higher power consumption in sleep, since the clock source and the power domain where LEDC belongs to cannot be powered down. Note that, if there is an unfinished fade before entering sleep, the fade can also continue during sleep, but the target duty might not be reached exactly. It will adjust to the target duty after wake-up.
.. only:: SOC_LEDC_SUPPORT_SLEEP_RETENTION
There is another sleep mode, :cpp:enumerator:`LEDC_SLEEP_MODE_NO_ALIVE_ALLOW_PD`, can save some power consumption in sleep, but at the expense of more memory being consumed. The system retains LEDC register context before entering Light-sleep and restores them after waking up, so that the LEDC power domain can be powered down during sleep. Any unfinished fade will not resume upon waking up from sleep, instead, it will output a PWM signal with a fixed duty cycle that matches the duty cycle just before entering sleep.
.. only:: esp32
.. _ledc-api-high_low_speed_mode:
LEDC High and Low Speed Mode
----------------------------
High speed mode enables a glitch-free changeover of timer settings. This means that if the timer settings are modified, the changes will be applied automatically on the next overflow interrupt of the timer. In contrast, when updating the low-speed timer, the change of settings should be explicitly triggered by software. The LEDC driver handles it in the background, e.g., when :cpp:func:`ledc_timer_config` is called.
For additional details regarding speed modes, see **{IDF_TARGET_NAME} Technical Reference Manual** > **LED PWM Controller (LEDC)** [`PDF <{IDF_TARGET_TRM_EN_URL}#ledpwm>`__].
.. _ledc-api-supported-range-frequency-duty-resolution:
.. only:: not esp32
.. _ledc-api-supported-range-frequency-duty-resolution:
Supported Range of Frequency and Duty Resolutions
-------------------------------------------------
The LED PWM Controller is designed primarily to drive LEDs. It provides a large flexibility of PWM duty cycle settings. For instance, the PWM frequency of 5 kHz can have the maximum duty resolution of 13 bits. This means that the duty can be set anywhere from 0 to 100% with a resolution of ~ 0.012% (2 ** 13 = 8192 discrete levels of the LED intensity). Note, however, that these parameters depend on the clock signal clocking the LED PWM Controller timer which in turn clocks the channel (see :ref:`timer configuration <ledc-api-configure-timer>` and the **{IDF_TARGET_NAME} Technical Reference Manual** > **LED PWM Controller (LEDC)** [`PDF <{IDF_TARGET_TRM_EN_URL}#ledpwm>`__]).
The LEDC can be used for generating signals at much higher frequencies that are sufficient enough to clock other devices, e.g., a digital camera module. In this case, the maximum available frequency is 40 MHz with duty resolution of 1 bit. This means that the duty cycle is fixed at 50% and cannot be adjusted.
The LEDC API is designed to report an error when trying to set a frequency and a duty resolution that exceed the range of LEDC's hardware. For example, an attempt to set the frequency to 20 MHz and the duty resolution to 3 bits results in the following error reported on a serial monitor:
.. highlight:: none
::
E (196) ledc: requested frequency and duty resolution cannot be achieved, try reducing freq_hz or duty_resolution. div_param=128
In such a situation, either the duty resolution or the frequency must be reduced. For example, setting the duty resolution to 2 resolves this issue and makes it possible to set the duty cycle at 25% steps, i.e., at 25%, 50% or 75%.
The LEDC driver also captures and reports attempts to configure frequency/duty resolution combinations that are below the supported minimum, e.g.,:
::
E (196) ledc: requested frequency and duty resolution cannot be achieved, try increasing freq_hz or duty_resolution. div_param=128000000
The duty resolution is normally set using :cpp:type:`ledc_timer_bit_t`. This enumeration covers the range from 10 to 15 bits. If a smaller duty resolution is required (from 10 down to 1), enter the equivalent numeric values directly.
Application Example
-------------------
.. list::
* :example:`peripherals/ledc/ledc_basic` demonstrates how to use the LEDC to generate a PWM signal in LOW SPEED mode.
* :example:`peripherals/ledc/ledc_fade` demonstrates how to control the intensity of LEDs using the LEDC fade functionality.
:SOC_LEDC_GAMMA_CURVE_FADE_SUPPORTED: * :example:`peripherals/ledc/ledc_gamma_curve_fade` demonstrates how to use the LEDC for color control of RGB LEDs with gamma correction.
:SOC_LEDC_SUPPORT_ETM and SOC_ETM_SUPPORTED: * :example:`peripherals/ledc/ledc_dimmer` demonstrates how to use the LEDC and ETM to generate TRIAC gate trigger pulses that are synchronized to the mains zerocross.
API Reference
-------------
.. include-build-file:: inc/ledc.inc
.. include-build-file:: inc/ledc_types.inc
.. only:: SOC_LEDC_SUPPORT_ETM and SOC_ETM_SUPPORTED
.. include-build-file:: inc/ledc_etm.inc
@@ -0,0 +1,134 @@
Low Power Inter-IC Sound (LP I2S)
=================================
Introduction
------------
LP I2S (Low Power Inter-IC Sound) is a synchronous protocol which can be used for audio data transmission. It also provides a data reception communication interface for Voice Activity Detection (VAD) and some digital audio applications in low power mode. For more details about VAD, see :doc:`Voice Activity Detection <./vad>`.
.. only:: SOC_I2S_SUPPORTED
.. note::
For I2S documentation, see :doc:`Inter-IC Sound <./i2s>`.
The I2S standard bus defines three signals,
- BCK: bit clock
- WS: word select
- SD: serial data
A basic I2S data bus has one master and one slave. The roles remain unchanged throughout the communication.
.. only:: esp32p4
LP I2S on {IDF_TARGET_NAME} only supports working as an I2S Slave.
The LP I2S module on {IDF_TARGET_NAME} provides an independent RX unit, which supports receiving data when the chip is running under sleep modes. Compared to HP I2S, LP I2S does not support DMA access. Instead, it uses a piece of separate internal memory to store data.
I2S Communication Mode
----------------------
Standard Mode
^^^^^^^^^^^^^
In standard mode, there are always two sound channels, i.e., the left and right channels, which are called "slots". These slots support 16-bit-width sample data. The communication format for the slots can be found in this :ref:`i2s-communication-mode` section.
PDM Mode (RX)
^^^^^^^^^^^^^
PDM (Pulse-density Modulation) mode for RX channel can receive PDM-format data. Only 16-bit-width sample data are supported. The communication format for the slots can be found in this :ref:`i2s-communication-mode` section.
Functional Overview
-------------------
Resource Allocation
^^^^^^^^^^^^^^^^^^^
To create a LP I2S channel handle, you should set up the LP I2S channel configuration structure :cpp:type:`lp_i2s_chan_config_t`, and call :cpp:func:`lp_i2s_new_channel` with the prepared configuration structure.
If the LP I2S channel is no longer used, you should recycle the allocated resource by calling :cpp:func:`lp_i2s_del_channel`.
.. code:: c
//initialization
lp_i2s_chan_handle_t rx_handle = NULL;
lp_i2s_chan_config_t config = {
.id = 0,
.role = I2S_ROLE_SLAVE,
.threshold = 512,
};
ESP_ERROR_CHECK(lp_i2s_new_channel(&config, NULL, &rx_handle));
//deinitialization
ESP_ERROR_CHECK(lp_i2s_del_channel(rx_chan));
Register Event Callbacks
^^^^^^^^^^^^^^^^^^^^^^^^
By calling :cpp:func:`lp_i2s_register_event_callbacks`, you can hook your own function to the driver ISR. Supported event callbacks are listed in :cpp:type:`lp_i2s_evt_cbs_t`.
As the above callbacks are called in an ISR context, you should always ensure the callback function is suitable for an ISR context. Blocking logic should not appear in these callbacks. The callback function prototype is declared in :cpp:type:`lp_i2s_callback_t`.
You can also register your own context when calling :cpp:func:`lp_i2s_register_event_callbacks` by the parameter ``user_data``. This user data will be passed to the callback functions directly.
This function may fail due to reasons like :c:macro:`ESP_ERR_INVALID_ARG`, especially, this error may indicate that the callback functions are not in the internal RAM. Callbacks should be placed in IRAM since the default ISR handler is allocated with the `ESP_INTR_FLAG_IRAM` flag.
Please check the error log for more details. If it fails due to :c:macro:`ESP_ERR_INVALID_STATE`, it indicates that the LP I2S channel is enabled, and you cannot add a callback at this moment.
.. code:: c
lp_i2s_evt_cbs_t cbs = {
.on_thresh_met = s_lp_i2s_on_thresh_met,
.on_request_new_trans = s_lp_i2s_on_request_new_trans,
};
ESP_ERROR_CHECK(lp_i2s_register_event_callbacks(rx_chan, &cbs, &trans));
Enable and Disable LP I2S
^^^^^^^^^^^^^^^^^^^^^^^^^
Before using LP I2S to receive data, you need to enable the LP I2S channel by calling :cpp:func:`lp_i2s_channel_enable`, this function switches the driver state from **init** to **enable**. Calling :cpp:func:`lp_i2s_channel_disable` does the opposite, that is, puts the driver back to the **init** state.
Communication Mode
^^^^^^^^^^^^^^^^^^
.. list::
- Calling :cpp:func:`lp_i2s_channel_init_std_mode` can help you initialize the LP I2S channel to STD mode. Some initialization helpers are listed below:
- :c:macro:`LP_I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG`
- :c:macro:`LP_I2S_STD_MSB_SLOT_DEFAULT_CONFIG`
- :c:macro:`LP_I2S_STD_PCM_SHORT_SLOT_DEFAULT_CONFIG`
- Calling :cpp:func:`lp_i2s_channel_init_pdm_rx_mode` can help you initialize the LP I2S channel to PDM mode. :c:macro:`LP_I2S_PDM_RX_SLOT_DEFAULT_CONFIG` is an initialization helper.
Read Data via LP I2S
^^^^^^^^^^^^^^^^^^^^
After the LP I2S channel is enabled, :cpp:func:`lp_i2s_channel_read` and :cpp:func:`lp_i2s_channel_read_until_bytes` will be available.
.. list::
- For :cpp:func:`lp_i2s_channel_read`, if there are new data received by the LP I2S channel, this API will move the received data to the ``buffer`` you specified in :cpp:type:`lp_i2s_trans_t`. The API will try to receive the data as the ``buflen`` you specified. Check the ``received_size`` to know how many bytes you received, in case there are no enough received data. If no new received data, the API will block until ``timeout_ms``.
- For :cpp:func:`lp_i2s_channel_read_until_bytes`, this API is a wrapper of the :cpp:func:`lp_i2s_channel_read`. The difference is, the :cpp:func:`lp_i2s_channel_read_until_bytes` will block until ``buflen`` bytes are received.
- For both of the two APIs, if :cpp:member:`lp_i2s_evt_cbs_t::on_request_new_trans` is set, the driver will each time requesting a new LP I2S transaction descriptor (:cpp:type:`lp_i2s_trans_t`) from the callback event data structure (:cpp:type:`lp_i2s_evt_data_t`). This also means, the ``buffer`` in the (:cpp:type:`lp_i2s_trans_t`) needs to be ready for receiving data.
Thread Safety
^^^^^^^^^^^^^
All the APIs are guaranteed to be thread safe by the driver, which means, you can call them from different RTOS tasks without protection by extra locks.
All the APIs are not allowed to be used in ISR context.
API Reference
-------------
.. include-build-file:: inc/lp_i2s.inc
.. include-build-file:: inc/lp_i2s_std.inc
.. include-build-file:: inc/lp_i2s_pdm.inc
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,23 @@
Parallel IO
===========
:link_to_translation:`zh_CN:[中文]`
Introduction
------------
[`Parallel IO Peripheral <{IDF_TARGET_TRM_EN_URL}#parlio>`__] is a general parallel interface that can be used to connect external devices such as LED matrices, LCD displays, printers, and cameras. This peripheral has independent TX and RX units. Each unit can have up to 8 or 16 data signals and 1 or 2 clock signals.
The TX and RX drivers of the Parallel IO peripheral are independently designed and can be used by including the header files ``driver/parlio_tx.h`` or ``driver/parlio_rx.h``.
.. toctree::
:maxdepth: 1
parlio_tx
parlio_rx
API Reference
-------------
.. include-build-file:: inc/components/esp_driver_parlio/include/driver/parlio_types.inc
.. include-build-file:: inc/components/esp_hal_parlio/include/hal/parlio_types.inc
@@ -0,0 +1,420 @@
Parallel IO RX Driver
=====================
:link_to_translation:`zh_CN:[中文]`
This document describes the functionality of the Parallel IO RX driver in ESP-IDF. The table of contents is as follows:
.. contents::
:local:
:depth: 2
Introduction
------------
The Parallel IO RX unit is part of the general parallel interface, hereinafter referred to as the RX unit. It supports data reception from external devices via GDMA on a parallel bus. Given the flexibility of IO data, the RX unit can be used as a general interface to connect various peripherals. The main application scenarios of this driver include:
- High-speed data acquisition, such as camera and sensor data reading
- High-speed parallel communication with other hosts as a slave device
- Logic analyzer and signal monitoring applications
Quick Start
-----------
This section will quickly guide you on how to use the RX unit driver. Through a simple example demonstrating data reception with a soft delimiter, it shows how to create and start an RX unit, initiate a receive transaction, and register event callback functions. The general usage process is as follows:
Creating and Enabling the RX Unit
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
First, we need to create an RX unit instance. The following code shows how to create an RX unit instance:
.. code:: c
parlio_rx_unit_handle_t rx_unit = NULL;
parlio_rx_unit_config_t config = {
.clk_src = PARLIO_CLK_SRC_DEFAULT, // Select the default clock source
.data_width = 4, // Data width is 4 bits
.clk_in_gpio_num = -1, // Do not use an external clock source
.clk_out_gpio_num = EXAMPLE_PIN_CLK, // Output clock pin
.valid_gpio_num = EXAMPLE_PIN_VALID, // Valid signal pin
.data_gpio_nums = {
EXAMPLE_PIN_DATA0,
EXAMPLE_PIN_DATA1,
EXAMPLE_PIN_DATA2,
EXAMPLE_PIN_DATA3,
[4 ... (PARLIO_RX_UNIT_MAX_DATA_WIDTH - 1)] = -1,
},
.exp_clk_freq_hz = 1 * 1000 * 1000, // Expected clock frequency is 1 MHz
.trans_queue_depth = 10, // Transaction queue depth is 10
.max_recv_size = 1024, // Maximum receive size is 1024 bytes
};
// Create RX unit instance
ESP_ERROR_CHECK(parlio_new_rx_unit(&config, &rx_unit));
// Enable RX unit and reset transaction queue
ESP_ERROR_CHECK(parlio_rx_unit_enable(rx_unit, true));
When creating an RX unit instance, we need to configure parameters such as the clock source, data width, and expected clock frequency through :cpp:type:`parlio_rx_unit_config_t`. Then call the :cpp:func:`parlio_new_rx_unit` function to create a new RX unit instance, which will return a handle pointing to the new instance. The instance handle is essentially a pointer to the RX unit memory object, of type :cpp:type:`parlio_rx_unit_handle_t`.
The following are the configuration parameters of the :cpp:type:`parlio_rx_unit_config_t` structure and their explanations:
.. list::
- :cpp:member:`parlio_rx_unit_config_t::clk_src` Sets the clock source of the RX unit. Available clock sources are listed in :cpp:type:`parlio_clock_source_t`, and only one can be selected. Different clock sources vary in resolution, accuracy, and power consumption.
- :cpp:member:`parlio_rx_unit_config_t::clk_in_gpio_num` Uses an external clock as the clock source, setting the corresponding GPIO number for clock input. Otherwise, set to -1, and the driver will use the internal :cpp:member:`parlio_rx_unit_config_t::clk_src` as the clock source.
- :cpp:member:`parlio_rx_unit_config_t::ext_clk_freq_hz` The frequency of the external input clock source, valid only when :cpp:member:`parlio_rx_unit_config_t::clk_in_gpio_num` is not -1.
- :cpp:member:`parlio_rx_unit_config_t::exp_clk_freq_hz` Sets the expected sample/bit clock frequency, which is divided from the internal or external clock regarding the clock source.
- :cpp:member:`parlio_rx_unit_config_t::clk_out_gpio_num` The GPIO number for the output clock signal (if supported). Set to -1 if not needed.
- :cpp:member:`parlio_rx_unit_config_t::data_width` The data bus width of the RX unit, must be a power of 2 and not greater than {IDF_TARGET_SOC_PARLIO_RX_UNIT_MAX_DATA_WIDTH}.
- :cpp:member:`parlio_rx_unit_config_t::data_gpio_nums` The GPIO numbers for RX data, unused GPIOs should be set to -1.
- :cpp:member:`parlio_rx_unit_config_t::valid_gpio_num` The GPIO number for the valid signal, set to -1 if not used. The valid signal indicates whether the data on the data lines are valid.
- :cpp:member:`parlio_rx_unit_config_t::trans_queue_depth` The depth of the internal transaction queue. The deeper the queue, the more transactions can be prepared in the pending queue.
- :cpp:member:`parlio_rx_unit_config_t::max_recv_size` The maximum receive size per transaction (in bytes). This decides the number of DMA nodes will be used for each transaction.
- :cpp:member:`parlio_rx_unit_config_t::flags` Usually used to fine-tune some behaviors of the driver, including the following options
- :cpp:member:`parlio_rx_unit_config_t::flags::free_clk` Whether the input external clock is a free-running clock. A free-running clock will always keep running (e.g. I2S bclk), a non-free-running clock will start when there are data transporting and stop when the bus idle (e.g. SPI).
:SOC_PARLIO_RX_CLK_SUPPORT_GATING: - :cpp:member:`parlio_rx_unit_config_t::flags::clk_gate_en` Enable RX clock gating, the output clock will be controlled by the valid gpio.
:SOC_PARLIO_SUPPORT_SLEEP_RETENTION: - :cpp:member:`parlio_rx_unit_config_t::flags::allow_pd` Set to allow power down. When this flag set, the driver will backup/restore the PARLIO registers before/after entering/exist sleep mode.
.. note::
If all RX units in the current chip have been requested, the :cpp:func:`parlio_new_rx_unit` function will return the :c:macro:`ESP_ERR_NOT_FOUND` error.
The RX unit must be enabled before use. The enable function :cpp:func:`parlio_rx_unit_enable` can switch the internal state machine of the driver to the active state, which also includes some system service requests/registrations, such as requesting a power management lock and resetting the transaction queue. The corresponding disable function is :cpp:func:`parlio_rx_unit_disable`, which will release all system services.
Creating Delimiters
^^^^^^^^^^^^^^^^^^^
Before initiating receive transactions, we need to create delimiters that define when a frame starts and ends. The RX unit supports three types of delimiters:
**Level Delimiter**: Uses a level signal to split valid data into frames.
.. code:: c
parlio_rx_delimiter_handle_t level_delimiter = NULL;
parlio_rx_level_delimiter_config_t level_config = {
.valid_sig_line_id = 4, // Use data line 4 as valid signal input
.sample_edge = PARLIO_SAMPLE_EDGE_POS, // Sample on positive edge
.bit_pack_order = PARLIO_BIT_PACK_ORDER_MSB, // Pack bits from MSB
.eof_data_len = 256, // End of frame interrupt triggers after 256 bytes, if set to 0, the EOF will be triggered when the valid signal is disabled
.timeout_ticks = 1000, // Timeout interrupt triggers after 1000 clock ticks since the valid signal is disabled but no enough data for EOF. If set to 0, the timeout interrupt will not be triggered
.flags = {
.active_low_en = false, // Active high level
},
};
ESP_ERROR_CHECK(parlio_new_rx_level_delimiter(&level_config, &level_delimiter));
**Pulse Delimiter**: Uses pulse signals to split valid data into frames.
.. code:: c
parlio_rx_delimiter_handle_t pulse_delimiter = NULL;
parlio_rx_pulse_delimiter_config_t pulse_config = {
.valid_sig_line_id = 4, // Use data line 4 as valid signal input
.sample_edge = PARLIO_SAMPLE_EDGE_NEG, // Sample on negative edge
.bit_pack_order = PARLIO_BIT_PACK_ORDER_MSB, // Pack bits from MSB
.eof_data_len = 128, // End of frame interrupt triggers after 128 bytes, if set to 0, the EOF will be triggered when the valid signal is disabled
.timeout_ticks = 500, // Timeout interrupt triggers after 500 clock ticks since the valid signal is disabled but no enough data for EOF. If set to 0, the timeout interrupt will not be triggered
.flags = {
.start_bit_included = false, // Start bit not included in data
.end_bit_included = false, // End bit not included in data
.has_end_pulse = true, // Has end pulse to terminate
.pulse_invert = false, // Do not invert pulse
},
};
ESP_ERROR_CHECK(parlio_new_rx_pulse_delimiter(&pulse_config, &pulse_delimiter));
**Soft Delimiter**: Uses software-defined data length to split valid data into frames.
.. code:: c
parlio_rx_delimiter_handle_t soft_delimiter = NULL;
parlio_rx_soft_delimiter_config_t soft_config = {
.sample_edge = PARLIO_SAMPLE_EDGE_POS, // Sample on positive edge
.bit_pack_order = PARLIO_BIT_PACK_ORDER_MSB, // Pack bits from MSB
.eof_data_len = 512, // End of frame after 512 bytes, since there is no other end condition, the soft delimiter must set this field
.timeout_ticks = 0, // No timeout
};
ESP_ERROR_CHECK(parlio_new_rx_soft_delimiter(&soft_config, &soft_delimiter));
Initiating RX Receive Transactions
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
After enabling the RX unit and creating delimiters, we can configure receive parameters and call :cpp:func:`parlio_rx_unit_receive` to start the RX transaction. The following code shows how to initiate an RX unit receive transaction:
.. code:: c
#define PAYLOAD_SIZE 512
// Allocate DMA compatible buffer
uint8_t *payload = heap_caps_calloc(1, PAYLOAD_SIZE, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT | MALLOC_CAP_DMA);
// Configure RX unit receive parameters
parlio_receive_config_t receive_config = {
.delimiter = soft_delimiter, // Use the soft delimiter created above
.flags = {
.partial_rx_en = false, // Disable partial receive mode
.indirect_mount = false, // Direct mount to user buffer to DMA
},
};
// Start soft delimiter (required for soft delimiter only)
ESP_ERROR_CHECK(parlio_rx_soft_delimiter_start_stop(rx_unit, soft_delimiter, true));
// Start receive transaction
ESP_ERROR_CHECK(parlio_rx_unit_receive(rx_unit, payload, PAYLOAD_SIZE, &receive_config));
// Wait for receive transaction to complete
ESP_ERROR_CHECK(parlio_rx_unit_wait_all_done(rx_unit, 5000)); // Wait up to 5 seconds
// Stop soft delimiter
ESP_ERROR_CHECK(parlio_rx_soft_delimiter_start_stop(rx_unit, soft_delimiter, false));
The RX unit receives data in bytes, and the received data length depends on the delimiter configuration. Calling :cpp:func:`parlio_rx_unit_receive` starts the RX transaction, which requires parameters such as the unit handle, payload buffer, and payload size (in **bytes**). Additionally, specific configurations for the reception should be provided in :cpp:type:`parlio_receive_config_t`.
The following are the configuration parameters of the :cpp:type:`parlio_receive_config_t` structure and their explanations:
.. list::
- :cpp:member:`parlio_receive_config_t::delimiter` The delimiter to be used for this receive transaction.
- :cpp:member:`parlio_receive_config_t::flags` Usually used to fine-tune some behaviors of the reception, including the following options
- :cpp:member:`parlio_receive_config_t::flags::partial_rx_en` Whether this is an infinite transaction that supposed to receive continuously and partially.
- :cpp:member:`parlio_receive_config_t::flags::indirect_mount` Enable this flag to use an INTERNAL DMA buffer instead of the user payload buffer. The data will be copied to the payload in every interrupt.
:cpp:func:`parlio_rx_unit_receive` internally constructs a transaction descriptor and sends it to the work queue, which is usually scheduled in the ISR context. Therefore, when :cpp:func:`parlio_rx_unit_receive` returns, the transaction may not have started yet. Note that you cannot recycle or modify the contents of the payload before the transaction ends. By registering event callbacks through :cpp:func:`parlio_rx_unit_register_event_callbacks`, you can be notified when the transaction is complete. To ensure all pending transactions are completed, you can also call :cpp:func:`parlio_rx_unit_wait_all_done`, providing a blocking receive function.
Registering Event Callbacks
^^^^^^^^^^^^^^^^^^^^^^^^^^^
Since :cpp:func:`parlio_rx_unit_receive` is an asynchronous interface, we may want to know when the receive transaction is complete or when partial data is received. The following code shows how to register event callbacks:
.. code:: c
static bool on_partial_receive_callback(parlio_rx_unit_handle_t rx_unit, const parlio_rx_event_data_t *edata, void *user_ctx)
{
// Called when partial data is received (for infinite transactions). You can do simple processing in the callback, such as queueing, task operations, or copying the received data to the user buffer.
return false; // Return true if high priority task should be woken up
}
static bool on_receive_done_callback(parlio_rx_unit_handle_t rx_unit, const parlio_rx_event_data_t *edata, void *user_ctx)
{
// Called when receive transaction is complete
BaseType_t high_task_wakeup = pdFalse;
TaskHandle_t task = (TaskHandle_t)user_ctx;
// Notify the waiting task
vTaskNotifyGiveFromISR(task, &high_task_wakeup);
return (high_task_wakeup == pdTRUE);
}
static bool on_timeout_callback(parlio_rx_unit_handle_t rx_unit, const parlio_rx_event_data_t *edata, void *user_ctx)
{
// Called when receive timeout occurs
return false;
}
parlio_rx_event_callbacks_t cbs = {
.on_partial_receive = on_partial_receive_callback,
.on_receive_done = on_receive_done_callback,
.on_timeout = on_timeout_callback,
};
ESP_ERROR_CHECK(parlio_rx_unit_register_event_callbacks(rx_unit, &cbs, xTaskGetCurrentTaskHandle()));
When the RX unit generates events such as receive done or timeout, it will notify the CPU via interrupts. If you need to call a function when a specific event occurs, you can call :cpp:func:`parlio_rx_unit_register_event_callbacks` to register event callbacks to the RX unit driver's interrupt service routine (ISR). Since the callback function is called in the ISR, complex operations (including any operations that may cause blocking) should be avoided in the callback function to avoid affecting the system's real-time performance.
For the event callbacks supported by the RX unit, refer to :cpp:type:`parlio_rx_event_callbacks_t`:
- :cpp:member:`parlio_rx_event_callbacks_t::on_partial_receive` Sets the callback function for the "partial data received" event, with the function prototype declared as :cpp:type:`parlio_rx_callback_t`.
- :cpp:member:`parlio_rx_event_callbacks_t::on_receive_done` Sets the callback function for the "receive complete" event, with the function prototype declared as :cpp:type:`parlio_rx_callback_t`.
- :cpp:member:`parlio_rx_event_callbacks_t::on_timeout` Sets the callback function for the "receive timeout" event, with the function prototype declared as :cpp:type:`parlio_rx_callback_t`. The timeout ticks is determined by the :cpp:member:`parlio_rx_level_delimiter_config_t::timeout_ticks`, :cpp:member:`parlio_rx_pulse_delimiter_config_t::timeout_ticks` or :cpp:member:`parlio_rx_soft_delimiter_config_t::timeout_ticks`.
Resource Recycling
^^^^^^^^^^^^^^^^^^
When the RX unit is no longer needed, the :cpp:func:`parlio_del_rx_unit` function should be called to release software and hardware resources. Ensure the RX unit is disabled before deletion. Also remember to delete the delimiters.
.. code:: c
ESP_ERROR_CHECK(parlio_rx_unit_disable(rx_unit));
ESP_ERROR_CHECK(parlio_del_rx_unit(rx_unit));
ESP_ERROR_CHECK(parlio_del_rx_delimiter(soft_delimiter));
free(payload);
Advanced Features
-----------------
After understanding the basic usage, we can further explore more advanced features of the RX unit driver.
Using an External Clock as the RX Unit Clock Source
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The RX unit can choose various clock sources, among which the external clock source is special. We enable the external clock source input by configuring :cpp:member:`parlio_rx_unit_config_t::clk_src`, :cpp:member:`parlio_rx_unit_config_t::clk_in_gpio_num`, and :cpp:member:`parlio_rx_unit_config_t::ext_clk_freq_hz`:
.. code-block:: c
:emphasize-lines: 3,5,6
parlio_rx_unit_handle_t rx_unit = NULL;
parlio_rx_unit_config_t config = {
.clk_src = PARLIO_CLK_SRC_EXTERNAL, // Select external clock source
.data_width = 4, // Data width is 4 bits
.clk_in_gpio_num = EXAMPLE_PIN_CLK_IN, // Set external clock source input pin
.ext_clk_freq_hz = 10 * 1000 * 1000, // External clock source frequency is 10 MHz
.exp_clk_freq_hz = 10 * 1000 * 1000, // Expected clock frequency matches external
.valid_gpio_num = EXAMPLE_PIN_VALID, // Valid signal pin
.data_gpio_nums = {
EXAMPLE_PIN_DATA0,
EXAMPLE_PIN_DATA1,
EXAMPLE_PIN_DATA2,
EXAMPLE_PIN_DATA3,
[4 ... (PARLIO_RX_UNIT_MAX_DATA_WIDTH - 1)] = -1,
},
.trans_queue_depth = 10,
.max_recv_size = 1024,
.flags = {
.free_clk = true, // External clock is free-running
},
};
// Create RX unit instance
ESP_ERROR_CHECK(parlio_new_rx_unit(&config, &rx_unit));
// Enable RX unit
ESP_ERROR_CHECK(parlio_rx_unit_enable(rx_unit, true));
.. note::
When using an external clock source, ensure that :cpp:member:`parlio_rx_unit_config_t::ext_clk_freq_hz` matches the actual frequency of the external clock for proper operation.
Infinite Receive Transactions
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The RX unit supports infinite receive transactions where it continuously receives data in a streaming fashion. This is useful for applications like logic analyzers or continuous data monitoring:
.. code:: c
// Configure infinite receive transaction
parlio_receive_config_t receive_config = {
.delimiter = soft_delimiter,
.flags = {
.partial_rx_en = true, // Enable infinite/partial receive mode
.indirect_mount = true, // Use internal buffer to avoid data corruption
},
};
// Start soft delimiter
ESP_ERROR_CHECK(parlio_rx_soft_delimiter_start_stop(rx_unit, soft_delimiter, true));
// Start infinite receive transaction
ESP_ERROR_CHECK(parlio_rx_unit_receive(rx_unit, payload, PAYLOAD_SIZE, &receive_config));
// The transaction will continue indefinitely, with partial receive callbacks being triggered as data is received.
// Use parlio_rx_soft_delimiter_start_stop to stop the transaction when needed.
vTaskDelay(pdMS_TO_TICKS(5000)); // Let it run for 5 seconds
// Stop the infinite transaction
ESP_ERROR_CHECK(parlio_rx_soft_delimiter_start_stop(rx_unit, soft_delimiter, false));
In infinite receive mode, the :cpp:member:`parlio_rx_event_callbacks_t::on_partial_receive` callback will be triggered each time the internal buffer is filled, and the data will be copied to the user buffer if :cpp:member:`parlio_receive_config_t::flags::indirect_mount` is enabled.
ISR Context Receive
^^^^^^^^^^^^^^^^^^^
For applications requiring very low latency, the RX unit driver provides :cpp:func:`parlio_rx_unit_receive_from_isr` which can be called from ISR context, such as within event callbacks:
.. code:: c
static bool on_receive_done_isr_callback(parlio_rx_unit_handle_t rx_unit, const parlio_rx_event_data_t *edata, void *user_ctx)
{
// Queue another receive transaction immediately from ISR context
parlio_receive_config_t *config = (parlio_receive_config_t *)user_ctx;
uint8_t *next_buffer = get_next_buffer(); // User-defined function
bool hp_task_woken = false;
esp_err_t ret = parlio_rx_unit_receive_from_isr(rx_unit, next_buffer, BUFFER_SIZE, config, &hp_task_woken);
if (ret != ESP_OK) {
// Handle error
}
return hp_task_woken;
}
Power Management
^^^^^^^^^^^^^^^^
When power management :ref:`CONFIG_PM_ENABLE` is enabled, the system may adjust or disable the clock source before entering sleep, causing the RX unit's internal time base to not work as expected.
To prevent this, the RX unit driver internally creates a power management lock. The type of lock is set according to different clock sources. The driver will acquire the lock in :cpp:func:`parlio_rx_unit_enable` and release the lock in :cpp:func:`parlio_rx_unit_disable`. This means that regardless of the power management policy, the system will not enter sleep mode, and the clock source will not be disabled or adjusted between these two functions, ensuring that any RX transaction can work normally.
.. only:: SOC_PARLIO_SUPPORT_SLEEP_RETENTION
In addition to turning off the clock source, the system can also turn off the RX unit's power to further reduce power consumption when entering sleep mode. To achieve this, set :cpp:member:`parlio_rx_unit_config_t::flags::allow_pd` to ``true``. Before the system enters sleep mode, the RX unit's register context will be backed up to memory and restored when the system wakes up. Note that enabling this option can reduce power consumption but will increase memory usage.
Thread Safety
^^^^^^^^^^^^^
The driver uses critical sections to ensure atomic operations on registers. Key members in the driver handle are also protected by critical sections. The driver's internal state machine uses atomic instructions to ensure thread safety, and uses thread-safe FreeRTOS queues to manage receive transactions. Therefore, RX unit driver APIs can be used in a multi-threaded environment without extra locking.
Cache Safety
^^^^^^^^^^^^
When the file system performs Flash read/write operations, the system temporarily disables the Cache function to avoid errors when loading instructions and data from Flash. This will cause the RX unit's interrupt handler to be unresponsive during this period, preventing user callback functions from being executed in time. If you want the interrupt handler to run normally while the Cache is disabled, you can enable the :ref:`CONFIG_PARLIO_RX_ISR_CACHE_SAFE` option.
.. note::
Note that after enabling this option, all interrupt callback functions and their context data **must reside in internal memory**. Because when the Cache is disabled, the system cannot load data and instructions from external memory.
.. only:: SOC_SPI_MEM_SUPPORT_AUTO_SUSPEND or SOC_SPIRAM_XIP_SUPPORTED
.. note::
When the following options are enabled, the Cache will not be disabled automatically during Flash read/write operations. You don't have to enable the :ref:`CONFIG_PARLIO_RX_ISR_CACHE_SAFE`.
.. list::
:SOC_SPI_MEM_SUPPORT_AUTO_SUSPEND: - :ref:`CONFIG_SPI_FLASH_AUTO_SUSPEND`
:SOC_SPIRAM_XIP_SUPPORTED: - :ref:`CONFIG_SPIRAM_XIP_FROM_PSRAM`
Performance
^^^^^^^^^^^
To improve the real-time response capability of interrupt handling, the RX unit driver provides the :ref:`CONFIG_PARLIO_RX_ISR_HANDLER_IN_IRAM` option. Enabling this option will place the interrupt handler in internal RAM, reducing the latency caused by cache misses when loading instructions from Flash.
.. note::
However, user callback functions and context data called by the interrupt handler may still be located in Flash, and cache miss issues will still exist. Users need to place callback functions and data in internal RAM, for example, using :c:macro:`IRAM_ATTR` and :c:macro:`DRAM_ATTR`.
And please also take care that, when the :cpp:member:`parlio_receive_config_t::flags::indirect_mount` option is enabled, the driver will use an internal DMA buffer instead of the user payload buffer. The data will be copied to the payload in the interrupt. Therefore, using this option will slightly reduce the data throughput efficiency.
Other Kconfig Options
^^^^^^^^^^^^^^^^^^^^^
- :ref:`CONFIG_PARLIO_ENABLE_DEBUG_LOG` option allows forcing the enablement of all debug logs of the RX unit driver, regardless of the global log level setting. Enabling this option can help developers obtain more detailed log information during debugging, making it easier to locate and solve problems. This option is shared with the TX unit driver.
Resource Consumption
^^^^^^^^^^^^^^^^^^^^
Use the :doc:`/api-guides/tools/idf-size` tool to view the code and data consumption of the RX unit driver. The following are the test conditions (taking ESP32-H2 as an example):
- The compiler optimization level is set to ``-Os`` to ensure the minimum code size.
- The default log level is set to ``ESP_LOG_INFO`` to balance debugging information and performance.
- The following driver optimization options are disabled:
- :ref:`CONFIG_PARLIO_RX_ISR_HANDLER_IN_IRAM` - The interrupt handler is not placed in IRAM.
- :ref:`CONFIG_PARLIO_RX_ISR_CACHE_SAFE` - The Cache safety option is not enabled.
**Note that the following data is not precise and is for reference only. The data may vary on different chip models and different versions of IDF.**
+-----------------+------------+-------+------+-------+-------+------------+---------+-------+
| Component Layer | Total Size | DIRAM | .bss | .data | .text | Flash Code | .rodata | .text |
+=================+============+=======+======+=======+=======+============+=========+=======+
| soc | 100 | 0 | 0 | 0 | 0 | 100 | 0 | 100 |
+-----------------+------------+-------+------+-------+-------+------------+---------+-------+
| hal | 18 | 0 | 0 | 0 | 0 | 18 | 0 | 18 |
+-----------------+------------+-------+------+-------+-------+------------+---------+-------+
| driver | 9666 | 0 | 0 | 0 | 0 | 9666 | 618 | 9048 |
+-----------------+------------+-------+------+-------+-------+------------+---------+-------+
In addition, each RX unit handle dynamically allocates about ``700`` bytes of memory from the heap (transaction queue depth is 10). If the :cpp:member:`parlio_rx_unit_config_t::flags::allow_pd` option is enabled, each RX unit will consume an additional ``32`` bytes of memory during sleep to save the register context.
Application Examples
---------------------
* :example:`peripherals/parlio/parlio_rx/logic_analyzer` demonstrates how to use the Parallel IO RX peripheral to implement a logic analyzer. This analyzer can sample data on multiple GPIOs at high frequency, monitor internal or external signals, and save the raw sampled data to Flash or output it through a TCP stream.
API Reference
-------------
.. include-build-file:: inc/parlio_rx.inc
@@ -0,0 +1,418 @@
Parallel IO TX Driver
=====================
:link_to_translation:`zh_CN:[中文]`
This document describes the functionality of the Parallel IO TX driver in ESP-IDF. The table of contents is as follows:
.. contents::
:local:
:depth: 2
Introduction
------------
The Parallel IO TX unit is part of the general parallel interface, hereinafter referred to as the TX unit. It supports data communication between external devices and internal memory via GDMA on a parallel bus. Given the flexibility of IO data, the TX unit can be used as a general interface to connect various peripherals. The main application scenarios of this driver include:
- Driving LCD, LED displays
- High-speed parallel communication with other devices
- Simulating the timing of other peripherals when the number of peripherals is insufficient.
Quick Start
-----------
This section will quickly guide you on how to use the TX unit driver. Through a simple example simulating QPI (Quad Peripheral Interface) transmission timing, it demonstrates how to create and start a TX unit, initiate a transmission transaction, and register event callback functions. The general usage process is as follows:
Creating and Enabling the TX Unit
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
First, we need to create a TX unit instance. The following code shows how to create a TX unit instance to simulate QPI:
.. code:: c
parlio_tx_unit_handle_t tx_unit = NULL;
parlio_tx_unit_config_t config = {
.clk_src = PARLIO_CLK_SRC_DEFAULT, // Select the default clock source
.data_width = 4, // Data width is 4 bits
.clk_in_gpio_num = -1, // Do not use an external clock source
.valid_gpio_num = EXAMPLE_PIN_CS, // Use the valid signal as chip select
.clk_out_gpio_num = EXAMPLE_PIN_CLK,
.data_gpio_nums = {
EXAMPLE_PIN_DATA0,
EXAMPLE_PIN_DATA1,
EXAMPLE_PIN_DATA2,
EXAMPLE_PIN_DATA3,
},
.output_clk_freq_hz = 10 * 1000 * 1000, // Output clock frequency is 10 MHz
.trans_queue_depth = 32, // Transaction queue depth is 32
.max_transfer_size = 256, // Maximum transfer size is 256 bytes
.shift_edge = PARLIO_SHIFT_EDGE_NEG, // Shift data on the falling edge of the clock
.flags = {
.invert_valid_out = true, // The valid signal is high by default, inverted to simulate the chip select signal CS in QPI timing
}
};
// Create TX unit instance
ESP_ERROR_CHECK(parlio_new_tx_unit(&config, &tx_unit));
// Enable TX unit
ESP_ERROR_CHECK(parlio_tx_unit_enable(tx_unit));
When creating a TX unit instance, we need to configure parameters such as the clock source, data width, and output clock frequency through :cpp:type:`parlio_tx_unit_config_t`. Then call the :cpp:func:`parlio_new_tx_unit` function to create a new TX unit instance, which will return a handle pointing to the new instance. The instance handle is essentially a pointer to the TX unit memory object, of type :cpp:type:`parlio_tx_unit_handle_t`.
The following are the configuration parameters of the :cpp:type:`parlio_tx_unit_config_t` structure and their explanations:
.. list::
- :cpp:member:`parlio_tx_unit_config_t::clk_src` Sets the clock source of the TX unit. Available clock sources are listed in :cpp:type:`parlio_clock_source_t`, and only one can be selected. Different clock sources vary in resolution, accuracy, and power consumption.
- :cpp:member:`parlio_tx_unit_config_t::clk_in_gpio_num` Uses an external clock as the clock source, setting the corresponding GPIO number for clock input. Otherwise, set to -1, and the driver will use the internal :cpp:member:`parlio_tx_unit_config_t::clk_src` as the clock source. This option has higher priority than :cpp:member:`parlio_tx_unit_config_t::clk_src`.
- :cpp:member:`parlio_tx_unit_config_t::input_clk_src_freq_hz` The frequency of the external input clock source, valid only when :cpp:member:`parlio_tx_unit_config_t::clk_in_gpio_num` is not -1.
- :cpp:member:`parlio_tx_unit_config_t::output_clk_freq_hz` Sets the frequency of the output clock, derived from the internal or external clock source. Note that not all frequencies can be achieved, and the driver will automatically adjust to the nearest frequency when the set frequency cannot be achieved.
- :cpp:member:`parlio_tx_unit_config_t::clk_out_gpio_num` The GPIO number for the output clock signal.
- :cpp:member:`parlio_tx_unit_config_t::data_width` The data bus width of the TX unit, must be a power of 2 and not greater than {IDF_TARGET_SOC_PARLIO_TX_UNIT_MAX_DATA_WIDTH}.
- :cpp:member:`parlio_tx_unit_config_t::data_gpio_nums` The GPIO numbers for TX data, unused GPIOs should be set to -1.
- :cpp:member:`parlio_tx_unit_config_t::valid_gpio_num` The GPIO number for the valid signal, set to -1 if not used. The valid signal stays high level when the TX unit is transmitting data. Note that enabling the valid signal in some specific chips will occupy the MSB data bit, reducing the maximum data width of the TX unit by 1 bit. In this case, the maximum configurable data bus width is :c:macro:`SOC_PARLIO_TX_UNIT_MAX_DATA_WIDTH` / 2. Please check the return value of :cpp:func:`parlio_new_tx_unit`.
- :cpp:member:`parlio_tx_unit_config_t::valid_start_delay` The number of clock cycles the valid signal will stay high level before the TX unit starts transmitting data. This configuration option depends on specific hardware features, and if enabled on unsupported chips or configured with invalid values, you will see an error message like ``invalid valid delay``.
- :cpp:member:`parlio_tx_unit_config_t::valid_stop_delay` The number of clock cycles the valid signal will stay high level after the TX unit finishes transmitting data. This configuration option depends on specific hardware features, and if enabled on unsupported chips or configured with invalid values, you will see an error message like ``invalid valid delay``.
- :cpp:member:`parlio_tx_unit_config_t::trans_queue_depth` The depth of the internal transaction queue. The deeper the queue, the more transactions can be prepared in the pending queue.
- :cpp:member:`parlio_tx_unit_config_t::max_transfer_size` The maximum transfer size per transaction (in bytes).
- :cpp:member:`parlio_tx_unit_config_t::dma_burst_size` The DMA burst transfer size (in bytes), must be a power of 2.
- :cpp:member:`parlio_tx_unit_config_t::shift_edge` The data shift edge of the TX unit.
- :cpp:member:`parlio_tx_unit_config_t::bit_pack_order` Sets the order of data bits within a byte (valid only when data width < 8).
- :cpp:member:`parlio_tx_unit_config_t::flags` Usually used to fine-tune some behaviors of the driver, including the following options
- :cpp:member:`parlio_tx_unit_config_t::flags::invert_valid_out` Determines whether to invert the valid signal before sending it to the GPIO pin.
:SOC_PARLIO_TX_CLK_SUPPORT_GATING: - :cpp:member:`parlio_tx_unit_config_t::flags::clk_gate_en` Enables TX unit clock gating, the output clock will be controlled by the MSB bit of the data bus, i.e., by writing a high level to :cpp:member:`parlio_tx_unit_config_t::data_gpio_nums` [:c:macro:`SOC_PARLIO_TX_UNIT_MAX_DATA_WIDTH` - 1] to enable clock output, and a low level to disable it. In this case, the data bus width needs to be configured as :c:macro:`SOC_PARLIO_TX_UNIT_MAX_DATA_WIDTH`. Note that if both the valid signal output and clock gating are enabled, clock gating can come from the valid signal. there is no limit on the data bus width. (Note that in some chips, the valid signal occupies the MSB data bit, so the maximum configurable data bus width is :c:macro:`SOC_PARLIO_TX_UNIT_MAX_DATA_WIDTH` / 2)
:SOC_PARLIO_SUPPORT_SLEEP_RETENTION: - :cpp:member:`parlio_tx_unit_config_t::flags::allow_pd` Configures whether the driver allows the system to turn off the peripheral power in sleep mode. Before entering sleep, the system will back up the TX unit register context, and these contexts will be restored when the system exits sleep mode. Turning off the peripheral can save more power, but at the cost of consuming more memory to save the register context. You need to balance power consumption and memory usage. This configuration option depends on specific hardware features, and if enabled on unsupported chips, you will see an error message like ``register back up is not supported``.
.. note::
If all TX units in the current chip have been requested, the :cpp:func:`parlio_new_tx_unit` function will return the :c:macro:`ESP_ERR_NOT_FOUND` error.
The TX unit must be enabled before use. The enable function :cpp:func:`parlio_tx_unit_enable` can switch the internal state machine of the driver to the active state, which also includes some system service requests/registrations, such as requesting a power management lock. The corresponding disable function is :cpp:func:`parlio_tx_unit_disable`, which will release all system services.
.. note::
When calling the :cpp:func:`parlio_tx_unit_enable` and :cpp:func:`parlio_tx_unit_disable` functions, they need to be used in pairs. This means you cannot call the :cpp:func:`parlio_tx_unit_enable` or :cpp:func:`parlio_tx_unit_disable` function twice in a row. This paired calling principle ensures the correct management and release of resources.
.. note::
Please note that after the TX unit is enabled, it will check the current work queue. If there are pending transmission transactions in the queue, the driver will immediately initiate a transmission.
Initiating TX Transmission Transactions
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
After enabling the TX unit, we can configure some parameters for the transmission and call the :cpp:func:`parlio_tx_unit_transmit` to start the TX transaction. The following code shows how to initiate a TX unit transmission transaction:
.. code:: c
#define PAYLOAD_SIZE 128
// Configure TX unit transmission parameters
parlio_transmit_config_t transmit_config = {
.idle_value = 0x00, // All data lines are low in idle state
};
// Prepare the data to be sent
uint8_t payload[PAYLOAD_SIZE] = {0};
for (int i = 0; i < PAYLOAD_SIZE; i++) {
payload[i] = i;
}
// The first call to parlio_tx_unit_transmit will start the transmission immediately as there is no ongoing transaction
ESP_ERROR_CHECK(parlio_tx_unit_transmit(tx_unit, payload, PAYLOAD_SIZE * sizeof(uint8_t) * 8, &transmit_config));
// The second call to parlio_tx_unit_transmit may queue the transaction if the previous one is not completed, and it will be scheduled in the ISR context after the previous transaction is completed
ESP_ERROR_CHECK(parlio_tx_unit_transmit(tx_unit, payload, PAYLOAD_SIZE * sizeof(uint8_t) * 8, &transmit_config));
// (Optional) Wait for the TX unit to complete all transactions
ESP_ERROR_CHECK(parlio_tx_unit_wait_all_done(tx_unit, -1));
The TX unit transmits data in bits, and the transmission bit length must be a multiple of the corresponding bus width. Calling :cpp:func:`parlio_tx_unit_transmit` to start the TX transaction, which requires parameters such as the unit handle, payload buffer, and payload size (in **bits**). Additionally, specific configurations for the transmission should be provided in :cpp:type:`parlio_transmit_config_t`.
The following are the configuration parameters of the :cpp:type:`parlio_transmit_config_t` structure and their explanations:
.. list::
- :cpp:member:`parlio_transmit_config_t::idle_value` Sets the value on the data lines when the TX unit is idle after transmission. This value will remain even after calling :cpp:func:`parlio_tx_unit_disable` to disable the TX unit.
:SOC_BITSCRAMBLER_SUPPORTED: - :cpp:member:`parlio_transmit_config_t::bitscrambler_program` The pointer to the bitscrambler program binary file. Set to ``NULL`` if the bitscrambler is not used in this transmission.
- :cpp:member:`parlio_transmit_config_t::flags` Usually used to fine-tune some behaviors of the transmission, including the following options
- :cpp:member:`parlio_transmit_config_t::flags::queue_nonblocking` Sets whether the function needs to wait when the transmission queue is full. If this value is set to ``true``, the function will immediately return the error code :c:macro:`ESP_ERR_INVALID_STATE` when the queue is full. Otherwise, the function will block the current thread until there is space in the transmission queue.
:SOC_PARLIO_TX_SUPPORT_LOOP_TRANSMISSION: - :cpp:member:`parlio_transmit_config_t::flags::loop_transmission` Setting this to ``true`` enables infinite loop transmission. In this case, the transmission will not stop unless manually calling :cpp:func:`parlio_tx_unit_disable`, and no "trans_done" event will be generated. Since the loop is controlled by DMA, the TX unit can generate periodic sequences with minimal CPU intervention.
:cpp:func:`parlio_tx_unit_transmit` internally constructs a transaction descriptor and sends it to the work queue, which is usually scheduled in the ISR context. Therefore, when :cpp:func:`parlio_tx_unit_transmit` returns, the transaction may not have started yet. Note that you cannot recycle or modify the contents of the payload before the transaction ends. By registering event callbacks through :cpp:func:`parlio_tx_unit_register_event_callbacks`, you can be notified when the transaction is complete. To ensure all pending transactions are completed, you can also call :cpp:func:`parlio_tx_unit_wait_all_done`, providing a blocking send function.
With simple configuration, we can send data in QPI format, as shown in the waveform below:
.. wavedrom:: /../_static/diagrams/parlio/parlio_tx/sim_qpi_waveform.json
Registering Event Callbacks
^^^^^^^^^^^^^^^^^^^^^^^^^^^
Since :cpp:func:`parlio_tx_unit_transmit` is an asynchronous interface, we may want to know when the transmission transaction is complete. The following code shows how to register an event callback for the transmission transaction done:
.. code:: c
static bool test_parlio_tx_done_callback(parlio_tx_unit_handle_t tx_unit, const parlio_tx_done_event_data_t *edata, void *user_ctx)
{
// General process for handling event callbacks:
// 1. Retrieve user context data from user_ctx (passed in from test_parlio_tx_done_callback)
// 2. Perform user-defined operations
// 3. Return whether a high-priority task was woken up during the above operations to notify the scheduler to switch tasks
BaseType_t high_task_wakeup = pdFalse;
// Use FreeRTOS task handle as user context
TaskHandle_t task = (TaskHandle_t)user_ctx;
// Send task notification to the specified task upon transmission done
vTaskNotifyGiveFromISR(task, &high_task_wakeup);
// Return whether a high-priority task was woken up by this function
return (high_task_wakeup == pdTRUE);
}
parlio_tx_event_callbacks_t cbs = {
// Set test_parlio_tx_done_callback as the event callback function for transmission done
.on_trans_done = test_parlio_tx_done_callback,
};
ESP_ERROR_CHECK(parlio_tx_unit_register_event_callbacks(tx_unit, &cbs, xTaskGetCurrentTaskHandle()));
When the TX unit generates events such as transmission done, it will notify the CPU via interrupts. If you need to call a function when a specific event occurs, you can call :cpp:func:`parlio_tx_unit_register_event_callbacks` to register event callbacks to the TX unit driver's interrupt service routine (ISR). Since the callback function is called in the ISR, complex operations (including any operations that may cause blocking) should be avoided in the callback function to avoid affecting the system's real-time performance. :cpp:func:`parlio_tx_unit_register_event_callbacks` also allows users to pass a context pointer to access user-defined data in the callback function.
For the event callbacks supported by the TX unit, refer to :cpp:type:`parlio_tx_event_callbacks_t`:
- :cpp:member:`parlio_tx_event_callbacks_t::on_trans_done` Sets the callback function for the "transmission complete" event, with the function prototype declared as :cpp:type:`parlio_tx_done_callback_t`.
- :cpp:member:`parlio_tx_event_callbacks_t::on_buffer_switched` Sets the callback function for the "buffer switch" event, with the function prototype declared as :cpp:type:`parlio_tx_buffer_switched_callback_t`.
Resource Recycling
^^^^^^^^^^^^^^^^^^
When the TX unit is no longer needed, the :cpp:func:`parlio_del_tx_unit` function should be called to release software and hardware resources. Ensure the TX unit is disabled before deletion.
.. code:: c
ESP_ERROR_CHECK(parlio_tx_unit_disable(tx_unit));
ESP_ERROR_CHECK(parlio_del_tx_unit(tx_unit));
Advanced Features
-----------------
After understanding the basic usage, we can further explore more advanced features of the TX unit driver.
Using an External Clock as the TX Unit Clock Source
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The TX unit can choose various clock sources, among which the external clock source is special. We enable the external clock source input by configuring :cpp:member:`parlio_tx_unit_config_t::clk_src`, :cpp:member:`parlio_tx_unit_config_t::clk_in_gpio_num`, and :cpp:member:`parlio_tx_unit_config_t::input_clk_src_freq_hz`:
.. code-block:: c
:emphasize-lines: 3,5,6
parlio_tx_unit_handle_t tx_unit = NULL;
parlio_tx_unit_config_t config = {
.clk_src = PARLIO_CLK_SRC_EXTERNAL, // Select external clock source
.data_width = 4, // Data width is 4 bits
.clk_in_gpio_num = EXAMPLE_PIN_CLK_IN, // Set external clock source input pin
.input_clk_src_freq_hz = 10 * 1000 * 1000, // External clock source frequency is 10 MHz
.valid_gpio_num = -1, // Do not use valid signal
.clk_out_gpio_num = EXAMPLE_PIN_CLK_OUT,
.data_gpio_nums = {
EXAMPLE_PIN_DATA0,
EXAMPLE_PIN_DATA1,
EXAMPLE_PIN_DATA2,
EXAMPLE_PIN_DATA3,
},
.output_clk_freq_hz = 5 * 1000 * 1000, // Output clock frequency is 5 MHz. Note that it cannot exceed the input clock frequency
.trans_queue_depth = 32,
.max_transfer_size = 256,
.shift_edge = PARLIO_SHIFT_EDGE_NEG, // Shift data on the falling edge of the clock
};
// Create TX unit instance
ESP_ERROR_CHECK(parlio_new_tx_unit(&config, &tx_unit));
// Enable TX unit
ESP_ERROR_CHECK(parlio_tx_unit_enable(tx_unit));
#define PAYLOAD_SIZE 64
// Configure TX unit transmission parameters
parlio_transmit_config_t transmit_config = {
.idle_value = 0x00, // All data lines are low in idle state
};
// Prepare the data to be sent
uint8_t payload[PAYLOAD_SIZE] = {0};
for (int i = 0; i < PAYLOAD_SIZE; i++) {
payload[i] = i;
}
// Start transmission transaction
ESP_ERROR_CHECK(parlio_tx_unit_transmit(tx_unit, payload, PAYLOAD_SIZE * sizeof(uint8_t) * 8, &transmit_config));
The waveform of the external clock input is shown below:
.. wavedrom:: /../_static/diagrams/parlio/parlio_tx/external_clock_input_waveform.json
.. note::
The ratio of :cpp:member:`parlio_tx_unit_config_t::input_clk_src_freq_hz` to :cpp:member:`parlio_tx_unit_config_t::output_clk_freq_hz` determines the internal clock division factor of the TX unit.
When the actual frequency of the external clock differs from :cpp:member:`parlio_tx_unit_config_t::input_clk_src_freq_hz`, the actual output clock frequency generated by the TX unit will also change accordingly.
.. only:: SOC_PARLIO_TX_SUPPORT_LOOP_TRANSMISSION
Infinite Loop Transmission
^^^^^^^^^^^^^^^^^^^^^^^^^^
{IDF_TARGET_NAME} supports infinite loop transmission, where the TX unit can generate periodic sequences without CPU intervention. By configuring :cpp:member:`parlio_transmit_config_t::flags::loop_transmission`, we can enable infinite loop transmission
.. code-block:: c
:emphasize-lines: 32
parlio_tx_unit_handle_t tx_unit = NULL;
parlio_tx_unit_config_t config = {
.clk_src = PARLIO_CLK_SRC_DEFAULT, // Select the default clock source
.data_width = 4, // Data width is 4 bits
.clk_in_gpio_num = -1, // Do not use an external clock source
.valid_gpio_num = -1, // Do not use valid signal
.clk_out_gpio_num = EXAMPLE_PIN_CLK,
.data_gpio_nums = {
EXAMPLE_PIN_DATA0,
EXAMPLE_PIN_DATA1,
EXAMPLE_PIN_DATA2,
EXAMPLE_PIN_DATA3,
},
.output_clk_freq_hz = 10 * 1000 * 1000, // Output clock frequency is 10 MHz
.trans_queue_depth = 32,
.max_transfer_size = 256,
.shift_edge = PARLIO_SHIFT_EDGE_NEG, // Shift data on the falling edge of the clock
.flags = {
.invert_valid_out = true, // The valid signal is high by default, inverted to simulate the chip select signal CS in QPI timing
}
};
// Create TX unit instance
ESP_ERROR_CHECK(parlio_new_tx_unit(&config, &tx_unit));
// Enable TX unit
ESP_ERROR_CHECK(parlio_tx_unit_enable(tx_unit));
#define PAYLOAD_SIZE 64
// Configure TX unit transmission parameters
parlio_transmit_config_t transmit_config = {
.idle_value = 0x00, // All data lines are low in idle state
.loop_transmission = true, // Enable infinite loop transmission
};
// Prepare the data to be sent
uint8_t payload[PAYLOAD_SIZE] = {0};
for (int i = 0; i < PAYLOAD_SIZE; i++) {
payload[i] = i;
}
// Start loop transmission transaction
ESP_ERROR_CHECK(parlio_tx_unit_transmit(tx_unit, payload, PAYLOAD_SIZE * sizeof(uint8_t) * 8, &transmit_config));
The waveform of the loop transmission is shown below:
.. wavedrom:: /../_static/diagrams/parlio/parlio_tx/loop_transmission_waveform.json
In this case, the transmission will not stop unless manually calling :cpp:func:`parlio_tx_unit_disable`, and no "trans_done" event will be generated.
.. note::
If you need to modify the transmission payload after enabling infinite loop transmission, you can configure :cpp:member:`parlio_transmit_config_t::flags::loop_transmission` and call :cpp:func:`parlio_tx_unit_transmit` again with a new payload buffer. The driver will switch to the new buffer after the old buffer is completely transmitted. You can register :cpp:member:`parlio_tx_event_callbacks_t::on_buffer_switched` to set the callback function for the "buffer switch" event, and need to maintain two buffers to avoid data inconsistency caused by premature modification or recycling of the old buffer.
.. only:: SOC_BITSCRAMBLER_SUPPORTED
.. _parlio-tx-bitscrambler-decorator:
Custom Bitstream Generation with BitScrambler
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
We can use the :doc:`BitScrambler </api-reference/peripherals/bitscrambler>` assembly code to control the data on the DMA path, thereby implementing some simple encoding work. Compared to using the CPU for encoding, the BitScrambler has higher performance and does not consume CPU resources, but is limited by the limited instruction memory of the BitScrambler, so it cannot implement complex encoding work.
After writing the BitScrambler program, we can enable it by calling :cpp:func:`parlio_tx_unit_decorate_bitscrambler`. And configure the :cpp:member:`parlio_transmit_config_t::bitscrambler_program` to point to the binary file of the BitScrambler program. Different transmission transactions can use different BitScrambler programs. The binary file must conform to the BitScrambler assembly language specification, and will be loaded into the BitScrambler's instruction memory at runtime. For details on how to write and compile the BitScrambler program, please refer to :doc:`BitScrambler Programming Guide </api-reference/peripherals/bitscrambler>`.
.. only:: esp32p4
.. note::
Due to hardware limitations, on chip versions prior to rev3.0, the bitstream generated by the bitscrambler must have the same length as the original bitstream, otherwise transmission blocking or data loss may occur.
:cpp:func:`parlio_tx_unit_decorate_bitscrambler` and :cpp:func:`parlio_tx_unit_undecorate_bitscrambler` need to be used in pairs. When deleting the TX unit, you need to call :cpp:func:`parlio_tx_unit_undecorate_bitscrambler` first to remove the BitScrambler.
Power Management
^^^^^^^^^^^^^^^^
When power management :ref:`CONFIG_PM_ENABLE` is enabled, the system may adjust or disable the clock source before entering sleep, causing the TX unit's internal time base to not work as expected.
To prevent this, the TX unit driver internally creates a power management lock. The type of lock is set according to different clock sources. The driver will acquire the lock in :cpp:func:`parlio_tx_unit_enable` and release the lock in :cpp:func:`parlio_tx_unit_disable`. This means that regardless of the power management policy, the system will not enter sleep mode, and the clock source will not be disabled or adjusted between these two functions, ensuring that any TX transaction can work normally.
.. only:: SOC_PARLIO_SUPPORT_SLEEP_RETENTION
In addition to turning off the clock source, the system can also turn off the TX unit's power to further reduce power consumption when entering sleep mode. To achieve this, set :cpp:member:`parlio_tx_unit_config_t::flags::allow_pd` to ``true``. Before the system enters sleep mode, the TX unit's register context will be backed up to memory and restored when the system wakes up. Note that enabling this option can reduce power consumption but will increase memory usage. Therefore, when using this feature, you need to balance power consumption and memory usage.
Thread Safety
^^^^^^^^^^^^^
The driver uses critical sections to ensure atomic operations on registers. Key members in the driver handle are also protected by critical sections. The driver's internal state machine uses atomic instructions to ensure thread safety, and use thread-safe FreeRTOS queues to manage transmit transactions. Therefore, TX unit driver APIs can be used in a multi-threaded environment without extra locking.
Cache Safety
^^^^^^^^^^^^
When the file system performs Flash read/write operations, the system temporarily disables the Cache function to avoid errors when loading instructions and data from Flash. This will cause the TX unit's interrupt handler to be unresponsive during this period, preventing user callback functions from being executed in time. If you want the interrupt handler to run normally while the Cache is disabled, you can enable the :ref:`CONFIG_PARLIO_TX_ISR_CACHE_SAFE` option.
.. note::
Note that after enabling this option, all interrupt callback functions and their context data **must reside in internal memory**. Because when the Cache is disabled, the system cannot load data and instructions from external memory.
.. only:: SOC_SPI_MEM_SUPPORT_AUTO_SUSPEND or SOC_SPIRAM_XIP_SUPPORTED
.. note::
When the following options are enabled, the Cache will not be disabled automatically during Flash read/write operations. You don't have to enable the :ref:`CONFIG_PARLIO_TX_ISR_CACHE_SAFE`.
.. list::
:SOC_SPI_MEM_SUPPORT_AUTO_SUSPEND: - :ref:`CONFIG_SPI_FLASH_AUTO_SUSPEND`
:SOC_SPIRAM_XIP_SUPPORTED: - :ref:`CONFIG_SPIRAM_XIP_FROM_PSRAM`
Performance
^^^^^^^^^^^
To improve the real-time response capability of interrupt handling, the TX unit driver provides the :ref:`CONFIG_PARLIO_TX_ISR_HANDLER_IN_IRAM` option. Enabling this option will place the interrupt handler in internal RAM, reducing the latency caused by cache misses when loading instructions from Flash.
.. note::
However, user callback functions and context data called by the interrupt handler may still be located in Flash, and cache miss issues will still exist. Users need to place callback functions and data in internal RAM, for example, using :c:macro:`IRAM_ATTR` and :c:macro:`DRAM_ATTR`.
Other Kconfig Options
^^^^^^^^^^^^^^^^^^^^^
- :ref:`CONFIG_PARLIO_ENABLE_DEBUG_LOG` option allows forcing the enablement of all debug logs of the TX unit driver, regardless of the global log level setting. Enabling this option can help developers obtain more detailed log information during debugging, making it easier to locate and solve problems. This option is shared with the RX unit driver.
Resource Consumption
^^^^^^^^^^^^^^^^^^^^
Use the :doc:`/api-guides/tools/idf-size` tool to view the code and data consumption of the TX unit driver. The following are the test conditions (taking ESP32-H2 as an example):
- The compiler optimization level is set to ``-Os`` to ensure the minimum code size.
- The default log level is set to ``ESP_LOG_INFO`` to balance debugging information and performance.
- The following driver optimization options are disabled:
- :ref:`CONFIG_PARLIO_TX_ISR_HANDLER_IN_IRAM` - The interrupt handler is not placed in IRAM.
- :ref:`CONFIG_PARLIO_TX_ISR_CACHE_SAFE` - The Cache safety option is not enabled.
**Note that the following data is not precise and is for reference only. The data may vary on different chip models and different versions of IDF.**
+-----------------+------------+-------+------+-------+-------+------------+---------+-------+
| Component Layer | Total Size | DIRAM | .bss | .data | .text | Flash Code | .rodata | .text |
+=================+============+=======+======+=======+=======+============+=========+=======+
| soc | 92 | 0 | 0 | 0 | 0 | 92 | 0 | 92 |
+-----------------+------------+-------+------+-------+-------+------------+---------+-------+
| hal | 18 | 0 | 0 | 0 | 0 | 18 | 0 | 18 |
+-----------------+------------+-------+------+-------+-------+------------+---------+-------+
| driver | 6478 | 12 | 12 | 0 | 0 | 6466 | 586 | 5880 |
+-----------------+------------+-------+------+-------+-------+------------+---------+-------+
In addition, each TX unit handle dynamically allocates about ``800`` bytes of memory from the heap (transmission queue depth is 4). If the :cpp:member:`parlio_tx_unit_config_t::flags::allow_pd` option is enabled, each TX unit will consume an additional ``32`` bytes of memory during sleep to save the register context.
Application Examples
---------------------
.. list::
- :example:`peripherals/parlio/parlio_tx/simple_rgb_led_matrix` demonstrates how to use the TX unit driver of {IDF_TARGET_NAME} to support HUB75 interface RGB LED matrix panels and use the LVGL library to display simple UI elements.
:SOC_PARLIO_TX_SUPPORT_LOOP_TRANSMISSION: - :example:`peripherals/parlio/parlio_tx/advanced_rgb_led_matrix` demonstrates how to use the infinite loop transmission feature of the TX unit of {IDF_TARGET_NAME} to support HUB75 interface RGB LED matrix panels. Compared to the simple_rgb_led_matrix example, it does not require manual loop scanning and is more flexible.
:SOC_PARLIO_LCD_SUPPORTED: - :example:`peripherals/lcd/parlio_simulate` demonstrates how to use the TX unit driver of the parallel IO peripheral to drive screens with SPI or I80 interfaces.
API Reference
-------------
.. include-build-file:: inc/parlio_tx.inc
+412
View File
@@ -0,0 +1,412 @@
Pulse Counter (PCNT)
====================
Introduction
------------
The PCNT (Pulse Counter) module is designed to count the number of rising and/or falling edges of input signals. The {IDF_TARGET_NAME} contains multiple pulse counter units in the module. [1]_ Each unit is in effect an independent counter with multiple channels, where each channel can increment/decrement the counter on a rising/falling edge. Furthermore, each channel can be configured separately.
PCNT channels can react to signals of **edge** type and **level** type, however for simple applications, detecting the edge signal is usually sufficient. PCNT channels can be configured react to both pulse edges (i.e., rising and falling edge), and can be configured to increase, decrease or do nothing to the unit's counter on each edge. The level signal is the so-called **control signal**, which is used to control the counting mode of the edge signals that are attached to the same channel. By combining the usage of both edge and level signals, a PCNT unit can act as a **quadrature decoder**.
Besides that, PCNT unit is equipped with a separate glitch filter, which is helpful to remove noise from the signal.
Typically, a PCNT module can be used in scenarios like:
- Calculate periodic signal's frequency by counting the pulse numbers within a time slice
- Decode quadrature signals into speed and direction
Functional Overview
-------------------
Description of the PCNT functionality is divided into the following sections:
.. list::
- :ref:`pcnt-resource-allocation` - covers how to allocate PCNT units and channels with properly set of configurations. It also covers how to recycle the resources when they finished working.
- :ref:`pcnt-setup-channel-actions` - covers how to configure the PCNT channel to behave on different signal edges and levels.
- :ref:`pcnt-watch-points` - describes how to configure PCNT watch points (i.e., tell PCNT unit to trigger an event when the count reaches a certain value).
:SOC_PCNT_SUPPORT_STEP_NOTIFY: - :ref:`pcnt-step-notify` - describes how to configure PCNT watch step (i.e., tell PCNT unit to trigger an event when the count increment reaches a certain value).
- :ref:`pcnt-register-event-callbacks` - describes how to hook your specific code to the watch point event callback function.
- :ref:`pcnt-set-glitch-filter` - describes how to enable and set the timing parameters for the internal glitch filter.
:SOC_PCNT_SUPPORT_CLEAR_SIGNAL: - :ref:`pcnt-set-clear-signal` - describes how to set the parameters for the external clear signal.
- :ref:`pcnt-enable-disable-unit` - describes how to enable and disable the PCNT unit.
- :ref:`pcnt-unit-io-control` - describes IO control functions of PCNT unit, like enable glitch filter, start and stop unit, get and clear count value.
- :ref:`pcnt-power-management` - describes what functionality will prevent the chip from going into low power mode.
- :ref:`pcnt-iram-safe` - describes tips on how to make the PCNT interrupt and IO control functions work better along with a disabled cache.
- :ref:`pcnt-thread-safe` - lists which APIs are guaranteed to be thread safe by the driver.
- :ref:`pcnt-kconfig-options` - lists the supported Kconfig options that can be used to make a different effect on driver behavior.
.. _pcnt-resource-allocation:
Resource Allocation
^^^^^^^^^^^^^^^^^^^
The PCNT unit and channel are represented by :cpp:type:`pcnt_unit_handle_t` and :cpp:type:`pcnt_channel_handle_t` respectively. All available units and channels are maintained by the driver in a resource pool, so you do not need to know the exact underlying instance ID.
Install PCNT Unit
~~~~~~~~~~~~~~~~~
To install a PCNT unit, there is a configuration structure that needs to be given in advance: :cpp:type:`pcnt_unit_config_t`:
.. list::
- :cpp:member:`pcnt_unit_config_t::group_id` specifies which PCNT peripheral group (hardware instance) to allocate the unit from. Must be a valid group index in range ``[0, PCNT_LL_INST_NUM)``. When you have multiple independent PCNT groups on a chip (e.g., ESP32-S31), this field allows you to pin a unit to a specific group.
- :cpp:member:`pcnt_unit_config_t::clk_src` selects the clock source for the PCNT unit.
- :cpp:member:`pcnt_unit_config_t::low_limit` and :cpp:member:`pcnt_unit_config_t::high_limit` specify the range for the internal hardware counter. The counter will reset to zero automatically when it crosses either the high or low limit.
- :cpp:member:`pcnt_unit_config_t::intr_priority` sets the interrupt priority. If it is set to ``0``, the driver will allocate an interrupt with a default priority. Otherwise, the driver will use the given priority.
- :cpp:member:`pcnt_unit_config_t::flags::accum_count` sets whether to create an internal accumulator for the counter. This is helpful when you want to extend the counter's width, which by default is 16 bit at most, defined in the hardware. See also :ref:`pcnt-compensate-overflow-loss` for how to use this feature to compensate the overflow loss.
:SOC_PCNT_SUPPORT_STEP_NOTIFY: - :cpp:member:`pcnt_unit_config_t::flags::en_step_notify_up` Configure whether to enable watch step to count in the positive direction.
:SOC_PCNT_SUPPORT_STEP_NOTIFY: - :cpp:member:`pcnt_unit_config_t::flags::en_step_notify_down` Configure whether to enable watch step to count in the negative direction.
.. note::
All units within the **same PCNT group** share the same hardware interrupt source and the same peripheral clock. Therefore, when installing multiple PCNT units in the same group, the following constraints apply:
- Both :cpp:member:`pcnt_unit_config_t::intr_priority` and :cpp:member:`pcnt_unit_config_t::clk_src` must be the same for all units within the same group. If there is a mismatch, :cpp:func:`pcnt_new_unit` will return :c:macro:`ESP_ERR_INVALID_ARG`.
Unit allocation and initialization is done by calling a function :cpp:func:`pcnt_new_unit` with :cpp:type:`pcnt_unit_config_t` as an input parameter. The function will return a PCNT unit handle only when it runs correctly. Specifically, when there are no more free PCNT units in the pool (i.e., unit resources have been used up), then this function will return :c:macro:`ESP_ERR_NOT_FOUND` error.
If a previously created PCNT unit is no longer needed, it is recommended to recycle the resource by calling :cpp:func:`pcnt_del_unit`. Which in return allows the underlying unit hardware to be used for other purposes. Before deleting a PCNT unit, one should ensure the following prerequisites:
- The unit is in the init state, in other words, the unit is either disabled by :cpp:func:`pcnt_unit_disable` or not enabled yet.
- The attached PCNT channels are all removed by :cpp:func:`pcnt_del_channel`.
.. code:: c
#define EXAMPLE_PCNT_HIGH_LIMIT 100
#define EXAMPLE_PCNT_LOW_LIMIT -100
pcnt_unit_config_t unit_config = {
.high_limit = EXAMPLE_PCNT_HIGH_LIMIT,
.low_limit = EXAMPLE_PCNT_LOW_LIMIT,
};
pcnt_unit_handle_t pcnt_unit = NULL;
ESP_ERROR_CHECK(pcnt_new_unit(&unit_config, &pcnt_unit));
Install PCNT Channel
~~~~~~~~~~~~~~~~~~~~
To install a PCNT channel, you must initialize a :cpp:type:`pcnt_chan_config_t` structure in advance, and then call :cpp:func:`pcnt_new_channel`. The configuration fields of the :cpp:type:`pcnt_chan_config_t` structure are described below:
- :cpp:member:`pcnt_chan_config_t::edge_gpio_num` and :cpp:member:`pcnt_chan_config_t::level_gpio_num` specify the GPIO numbers used by **edge** type signal and **level** type signal. Please note, either of them can be assigned to ``-1`` if it is not actually used, and thus it will become a **virtual IO**. For some simple pulse counting applications where one of the level/edge signals is fixed (i.e., never changes), you can reclaim a GPIO by setting the signal as a virtual IO on channel allocation. Setting the level/edge signal as a virtual IO causes that signal to be internally routed to a fixed High/Low logic level, thus allowing you to save a GPIO for other purposes.
- :cpp:member:`pcnt_chan_config_t::flags::virt_edge_io_level` and :cpp:member:`pcnt_chan_config_t::flags::virt_level_io_level` specify the virtual IO level for **edge** and **level** input signal, to ensure a deterministic state for such control signal. Please note, they are only valid when either :cpp:member:`pcnt_chan_config_t::edge_gpio_num` or :cpp:member:`pcnt_chan_config_t::level_gpio_num` is assigned to ``-1``.
- :cpp:member:`pcnt_chan_config_t::flags::invert_edge_input` and :cpp:member:`pcnt_chan_config_t::flags::invert_level_input` are used to decide whether to invert the input signals before they going into PCNT hardware. The invert is done by GPIO matrix instead of PCNT hardware.
Channel allocating and initialization is done by calling a function :cpp:func:`pcnt_new_channel` with the above :cpp:type:`pcnt_chan_config_t` as an input parameter plus a PCNT unit handle returned from :cpp:func:`pcnt_new_unit`. This function will return a PCNT channel handle if it runs correctly. Specifically, when there are no more free PCNT channel within the unit (i.e., channel resources have been used up), then this function will return :c:macro:`ESP_ERR_NOT_FOUND` error. Note that, when install a PCNT channel for a specific unit, one should ensure the unit is in the init state, otherwise this function will return :c:macro:`ESP_ERR_INVALID_STATE` error.
If a previously created PCNT channel is no longer needed, it is recommended to recycle the resources by calling :cpp:func:`pcnt_del_channel`. Which in return allows the underlying channel hardware to be used for other purposes.
.. code:: c
#define EXAMPLE_CHAN_GPIO_A 0
#define EXAMPLE_CHAN_GPIO_B 2
pcnt_chan_config_t chan_config = {
.edge_gpio_num = EXAMPLE_CHAN_GPIO_A,
.level_gpio_num = EXAMPLE_CHAN_GPIO_B,
};
pcnt_channel_handle_t pcnt_chan = NULL;
ESP_ERROR_CHECK(pcnt_new_channel(pcnt_unit, &chan_config, &pcnt_chan));
.. note::
In PCNT, the GPIOs involved can be reconfigured for pull-up or pull-down after initializing PCNT using functions such as :cpp:func:`gpio_pullup_en` and :cpp:func:`gpio_pullup_dis`.
.. _pcnt-setup-channel-actions:
Set Up Channel Actions
^^^^^^^^^^^^^^^^^^^^^^
The PCNT will increase/decrease/hold its internal count value when the input pulse signal toggles. You can set different actions for edge signal and/or level signal.
- :cpp:func:`pcnt_channel_set_edge_action` function is to set specific actions for rising and falling edge of the signal attached to the :cpp:member:`pcnt_chan_config_t::edge_gpio_num`. Supported actions are listed in :cpp:type:`pcnt_channel_edge_action_t`.
- :cpp:func:`pcnt_channel_set_level_action` function is to set specific actions for high and low level of the signal attached to the :cpp:member:`pcnt_chan_config_t::level_gpio_num`. Supported actions are listed in :cpp:type:`pcnt_channel_level_action_t`. This function is not mandatory if the :cpp:member:`pcnt_chan_config_t::level_gpio_num` is set to ``-1`` when allocating PCNT channel by :cpp:func:`pcnt_new_channel`.
.. code:: c
// decrease the counter on rising edge, increase the counter on falling edge
ESP_ERROR_CHECK(pcnt_channel_set_edge_action(pcnt_chan, PCNT_CHANNEL_EDGE_ACTION_DECREASE, PCNT_CHANNEL_EDGE_ACTION_INCREASE));
// keep the counting mode when the control signal is high level, and reverse the counting mode when the control signal is low level
ESP_ERROR_CHECK(pcnt_channel_set_level_action(pcnt_chan, PCNT_CHANNEL_LEVEL_ACTION_KEEP, PCNT_CHANNEL_LEVEL_ACTION_INVERSE));
.. _pcnt-watch-points:
Watch Points
^^^^^^^^^^^^
Each PCNT unit can be configured to watch several different values that you are interested in. The value to be watched is also called **Watch Point**. The watch point itself can not exceed the range set in :cpp:type:`pcnt_unit_config_t` by :cpp:member:`pcnt_unit_config_t::low_limit` and :cpp:member:`pcnt_unit_config_t::high_limit`. When the counter reaches either watch point, a watch event will be triggered and notify you by interrupt if any watch event callback has ever registered in :cpp:func:`pcnt_unit_register_event_callbacks`. See :ref:`pcnt-register-event-callbacks` for how to register event callbacks.
The watch point can be added and removed by :cpp:func:`pcnt_unit_add_watch_point` and :cpp:func:`pcnt_unit_remove_watch_point`. The commonly-used watch points are: **zero cross**, **maximum/minimum count** and other threshold values. The number of available watch point is limited, :cpp:func:`pcnt_unit_add_watch_point` will return error :c:macro:`ESP_ERR_NOT_FOUND` if it can not find any free hardware resource to save the watch point. You can not add the same watch point for multiple times, otherwise it will return error :c:macro:`ESP_ERR_INVALID_STATE`.
It is recommended to remove the unused watch point by :cpp:func:`pcnt_unit_remove_watch_point` to recycle the watch point resources.
.. code:: c
// add zero across watch point
ESP_ERROR_CHECK(pcnt_unit_add_watch_point(pcnt_unit, 0));
// add high limit watch point
ESP_ERROR_CHECK(pcnt_unit_add_watch_point(pcnt_unit, EXAMPLE_PCNT_HIGH_LIMIT));
.. only:: not SOC_PCNT_SUPPORT_RUNTIME_THRES_UPDATE
.. note::
Due to the hardware limitation, after adding a watch point, you should call :cpp:func:`pcnt_unit_clear_count` to make it take effect.
.. only:: SOC_PCNT_SUPPORT_STEP_NOTIFY
.. _pcnt-step-notify:
Watch Step
^^^^^^^^^^^
PCNT unit can be configured to watch a specific value increment (can be positive or negative) that you are interested in. The function of watching value increment is also called **Watch Step**. To install watch step requires enabling :cpp:member:`pcnt_unit_config_t::flags::en_step_notify_up` or :cpp:member:`pcnt_unit_config_t::flags::en_step_notify_down`. The step interval itself can not exceed the range set in :cpp:type:`pcnt_unit_config_t` by :cpp:member:`pcnt_unit_config_t::low_limit` and :cpp:member:`pcnt_unit_config_t::high_limit`.When the counter increment reaches step interval, a watch event will be triggered and notify you by interrupt if any watch event callback has ever registered in :cpp:func:`pcnt_unit_register_event_callbacks`. See :ref:`pcnt-register-event-callbacks` for how to register event callbacks.
The watch step can be added and removed by :cpp:func:`pcnt_unit_add_watch_step` and :cpp:func:`pcnt_unit_remove_watch_step`. The parameter ``step_interval`` can be positive(step forward, e.g., [N]->[N+1]->[N+2]->...) or negative(step backward, e.g., [N]->[N-1]->[N-2]->...). The same direction can only add one watch step, otherwise it will return error :c:macro:`ESP_ERR_INVALID_STATE`.
.. note::
Due to hardware limitations, some chips may only support adding one direction of watch step. Please check the return value of :cpp:func:`pcnt_unit_add_watch_step` for more details.
It is recommended to remove the unused watch step by :cpp:func:`pcnt_unit_remove_watch_step` to recycle the watch step resources.
.. note::
When a watch step and a watch point are triggered at the same time (i.e. at the same absolute point), the callback function only gets called by once.
The step increment will be reset to 0 when the count reaches the high/low limit value. Please do not rely too much on the exact step interval.
.. code:: c
// add positive direction watch step with 100 step intervals
ESP_ERROR_CHECK(pcnt_unit_add_watch_step(pcnt_unit, 100));
.. _pcnt-register-event-callbacks:
.. only:: not SOC_PCNT_SUPPORT_STEP_NOTIFY
.. _pcnt-register-event-callbacks:
Register Event Callbacks
^^^^^^^^^^^^^^^^^^^^^^^^
When PCNT unit reaches any enabled watch point, specific event will be generated and notify the CPU by interrupt. If you have some function that want to get executed when event happens, you should hook your function to the interrupt service routine by calling :cpp:func:`pcnt_unit_register_event_callbacks`. All supported event callbacks are listed in the :cpp:type:`pcnt_event_callbacks_t`:
- :cpp:member:`pcnt_event_callbacks_t::on_reach` sets a callback function for watch point event. As this function is called within the ISR context, you must ensure that the function does not attempt to block (e.g., by making sure that only FreeRTOS APIs with ``ISR`` suffix are called from within the function). The function prototype is declared in :cpp:type:`pcnt_watch_cb_t`.
You can save their own context to :cpp:func:`pcnt_unit_register_event_callbacks` as well, via the parameter ``user_ctx``. This user data will be directly passed to the callback functions.
In the callback function, the driver will fill in the event data of specific event. For example, the watch point event or watch step event data is declared as :cpp:type:`pcnt_watch_event_data_t`:
- :cpp:member:`pcnt_watch_event_data_t::watch_point_value` saves the count value when the event triggered.
- :cpp:member:`pcnt_watch_event_data_t::zero_cross_mode` saves how the PCNT unit crosses the zero point in the latest time. The possible zero cross modes are listed in the :cpp:type:`pcnt_unit_zero_cross_mode_t`. Usually different zero cross mode means different **counting direction** and **counting step size**.
Registering callback function results in lazy installation of interrupt service, thus this function should only be called before the unit is enabled by :cpp:func:`pcnt_unit_enable`. Otherwise, it can return :c:macro:`ESP_ERR_INVALID_STATE` error.
.. code:: c
static bool example_pcnt_on_reach(pcnt_unit_handle_t unit, const pcnt_watch_event_data_t *edata, void *user_ctx)
{
BaseType_t high_task_wakeup;
QueueHandle_t queue = (QueueHandle_t)user_ctx;
// send watch point to queue, from this interrupt callback
xQueueSendFromISR(queue, &(edata->watch_point_value), &high_task_wakeup);
// return whether a high priority task has been waken up by this function
return (high_task_wakeup == pdTRUE);
}
pcnt_event_callbacks_t cbs = {
.on_reach = example_pcnt_on_reach,
};
QueueHandle_t queue = xQueueCreate(10, sizeof(int));
ESP_ERROR_CHECK(pcnt_unit_register_event_callbacks(pcnt_unit, &cbs, queue));
.. _pcnt-set-glitch-filter:
Set Glitch Filter
^^^^^^^^^^^^^^^^^
The PCNT unit features filters to ignore possible short glitches in the signals. The parameters that can be configured for the glitch filter are listed in :cpp:type:`pcnt_glitch_filter_config_t`:
- :cpp:member:`pcnt_glitch_filter_config_t::max_glitch_ns` sets the maximum glitch width, in nano seconds. If a signal pulse's width is smaller than this value, then it will be treated as noise and will not increase/decrease the internal counter.
You can enable the glitch filter for PCNT unit by calling :cpp:func:`pcnt_unit_set_glitch_filter` with the filter configuration provided above. Particularly, you can disable the glitch filter later by calling :cpp:func:`pcnt_unit_set_glitch_filter` with a ``NULL`` filter configuration.
This function should be called when the unit is in the init state. Otherwise, it will return :c:macro:`ESP_ERR_INVALID_STATE` error.
.. note::
The glitch filter operates using the APB clock. To ensure the counter does not miss any pulses, the maximum glitch width should be longer than one APB_CLK cycle (typically 12.5 ns if APB is 80 MHz). Since the APB frequency can change with Dynamic Frequency Scaling (DFS), the filter may not function as expected in such cases. Therefore, the driver installs a power management lock for each PCNT unit. For more details on the power management strategy used in the PCNT driver, please refer to :ref:`pcnt-power-management`.
.. code:: c
pcnt_glitch_filter_config_t filter_config = {
.max_glitch_ns = 1000,
};
ESP_ERROR_CHECK(pcnt_unit_set_glitch_filter(pcnt_unit, &filter_config));
.. only:: SOC_PCNT_SUPPORT_CLEAR_SIGNAL
.. _pcnt-set-clear-signal:
Use External Clear Signal
^^^^^^^^^^^^^^^^^^^^^^^^^
The PCNT unit can receive a clear signal from the GPIO. The parameters that can be configured for the clear signal are listed in :cpp:type:`pcnt_clear_signal_config_t`:
- :cpp:member:`pcnt_clear_signal_config_t::clear_signal_gpio_num` specify the GPIO numbers used by **clear** signal. The default active level is high, and the input mode is pull-down enabled.
- :cpp:member:`pcnt_clear_signal_config_t::flags::invert_clear_signal` is used to decide whether to invert the input signal before it going into PCNT hardware. The invert is done by GPIO matrix instead of PCNT hardware. The input mode is pull-up enabled when the input signal is inverted.
This signal acts in the same way as calling :cpp:func:`pcnt_unit_clear_count`, but is not subject to software latency, and is suitable for use in situations with low latency requirements. Also please note, the flip frequency of this signal can not be too high.
.. code:: c
pcnt_clear_signal_config_t clear_signal_config = {
.clear_signal_gpio_num = PCNT_CLEAR_SIGNAL_GPIO,
};
ESP_ERROR_CHECK(pcnt_unit_set_clear_signal(pcnt_unit, &clear_signal_config));
.. _pcnt-enable-disable-unit:
.. only:: not SOC_PCNT_SUPPORT_CLEAR_SIGNAL
.. _pcnt-enable-disable-unit:
Enable and Disable Unit
^^^^^^^^^^^^^^^^^^^^^^^
Before doing IO control to the PCNT unit, you need to enable it first, by calling :cpp:func:`pcnt_unit_enable`. Internally, this function:
* switches the PCNT driver state from **init** to **enable**.
* enables the interrupt service if it has been lazy installed in :cpp:func:`pcnt_unit_register_event_callbacks`.
* acquires a proper power management lock if it has been installed. See also :ref:`pcnt-power-management` for more information.
On the contrary, calling :cpp:func:`pcnt_unit_disable` will do the opposite, that is, put the PCNT driver back to the **init** state, disable the interrupts service and release the power management lock.
.. code::c
ESP_ERROR_CHECK(pcnt_unit_enable(pcnt_unit));
.. _pcnt-unit-io-control:
Unit IO Control
^^^^^^^^^^^^^^^
Start/Stop and Clear
~~~~~~~~~~~~~~~~~~~~
Calling :cpp:func:`pcnt_unit_start` makes the PCNT unit start to work, increase or decrease counter according to pulse signals. On the contrary, calling :cpp:func:`pcnt_unit_stop` will stop the PCNT unit but retain current count value. Instead, clearing counter can only be done by calling :cpp:func:`pcnt_unit_clear_count`.
Note, :cpp:func:`pcnt_unit_start` and :cpp:func:`pcnt_unit_stop` should be called when the unit has been enabled by :cpp:func:`pcnt_unit_enable`. Otherwise, it will return :c:macro:`ESP_ERR_INVALID_STATE` error.
.. code::c
ESP_ERROR_CHECK(pcnt_unit_clear_count(pcnt_unit));
ESP_ERROR_CHECK(pcnt_unit_start(pcnt_unit));
Get Count Value
~~~~~~~~~~~~~~~
You can read current count value at any time by calling :cpp:func:`pcnt_unit_get_count`. The returned count value is a **signed** integer, where the sign can be used to reflect the direction.
.. code:: c
int pulse_count = 0;
ESP_ERROR_CHECK(pcnt_unit_get_count(pcnt_unit, &pulse_count));
.. _pcnt-compensate-overflow-loss:
Compensate Overflow Loss
~~~~~~~~~~~~~~~~~~~~~~~~
The internal hardware counter will be cleared to zero automatically when it reaches high or low limit. If you want to compensate for that count loss and extend the counter's bit-width, you can:
.. list::
1. Enable :cpp:member:`pcnt_unit_config_t::flags::accum_count` when installing the PCNT unit.
2. Add the high/low limit as the :ref:`pcnt-watch-points`.
3. Now, the returned count value from the :cpp:func:`pcnt_unit_get_count` function not only reflects the hardware's count value, but also accumulates the high/low overflow loss to it.
.. note::
:cpp:func:`pcnt_unit_clear_count` resets the accumulated count value as well.
.. note::
When enabling the count overflow compensation, it is recommended to use as large a high/low count limit as possible, as it can avoid frequent interrupt triggering, improve system performance, and avoid compensation failure due to multiple overflows.
.. _pcnt-power-management:
Power Management
^^^^^^^^^^^^^^^^
When power management is enabled (i.e., :ref:`CONFIG_PM_ENABLE` is on), the system adjusts the APB frequency before entering light sleep, which can cause the PCNT glitch filter to misinterpret valid signals as noise.
To prevent this, the driver can acquire a power management lock of type :cpp:enumerator:`ESP_PM_APB_FREQ_MAX`, ensuring the APB frequency remains constant. This lock is acquired when the PCNT unit is enabled via :cpp:func:`pcnt_unit_enable` and released when the unit is disabled via :cpp:func:`pcnt_unit_disable`.
.. _pcnt-iram-safe:
IRAM Safe
^^^^^^^^^
By default, the PCNT interrupt will be deferred when the Cache is disabled for reasons like writing/erasing Flash. Thus the alarm interrupt will not get executed in time, which is not expected in a real-time application.
There is a Kconfig option :ref:`CONFIG_PCNT_ISR_IRAM_SAFE` that:
1. Enables the interrupt being serviced even when cache is disabled
2. Places all functions that used by the ISR into IRAM [2]_
3. Places driver object into DRAM (in case it is mapped to PSRAM by accident)
This allows the interrupt to run while the cache is disabled but comes at the cost of increased IRAM consumption.
There is another Kconfig option :ref:`CONFIG_PCNT_CTRL_FUNC_IN_IRAM` that can put commonly used IO control functions into IRAM as well. So that these functions can also be executable when the cache is disabled. These IO control functions are as follows:
- :cpp:func:`pcnt_unit_start`
- :cpp:func:`pcnt_unit_stop`
- :cpp:func:`pcnt_unit_clear_count`
- :cpp:func:`pcnt_unit_get_count`
.. _pcnt-thread-safe:
Thread Safety
^^^^^^^^^^^^^
The factory functions :cpp:func:`pcnt_new_unit` and :cpp:func:`pcnt_new_channel` are guaranteed to be thread safe by the driver, which means, you can call them from different RTOS tasks without protection by extra locks.
The following functions are allowed to run under ISR context, the driver uses a critical section to prevent them being called concurrently in both task and ISR.
- :cpp:func:`pcnt_unit_start`
- :cpp:func:`pcnt_unit_stop`
- :cpp:func:`pcnt_unit_clear_count`
- :cpp:func:`pcnt_unit_get_count`
Other functions that take the :cpp:type:`pcnt_unit_handle_t` and :cpp:type:`pcnt_channel_handle_t` as the first positional parameter, are not treated as thread safe. This means you should avoid calling them from multiple tasks.
.. _pcnt-kconfig-options:
Kconfig Options
^^^^^^^^^^^^^^^
- :ref:`CONFIG_PCNT_CTRL_FUNC_IN_IRAM` controls where to place the PCNT control functions (IRAM or Flash), see :ref:`pcnt-iram-safe` for more information.
- :ref:`CONFIG_PCNT_ISR_IRAM_SAFE` controls whether the default ISR handler can work when cache is disabled, see :ref:`pcnt-iram-safe` for more information.
- :ref:`CONFIG_PCNT_ENABLE_DEBUG_LOG` is used to enabled the debug log output. Enabling this option increases the firmware binary size.
Application Examples
--------------------
* :example:`peripherals/pcnt/rotary_encoder` demonstrates how to use the PCNT peripheral to decode the differential signals generated from a common rotary encoder, EC11, and how to configure the rotary encoder to wake the system from light-sleep.
API Reference
-------------
.. include-build-file:: inc/pulse_cnt.inc
.. include-build-file:: inc/pcnt_types.inc
.. [1]
Different ESP chip series might have different number of PCNT units and channels. Please refer to the [`TRM <{IDF_TARGET_TRM_EN_URL}#pcnt>`__] for details. The driver does not forbid you from applying for more PCNT units and channels, but it returns error when all available hardware resources are used up. Please always check the return value when doing resource allocation (e.g., :cpp:func:`pcnt_new_unit`).
.. [2]
:cpp:member:`pcnt_event_callbacks_t::on_reach` callback and the functions invoked by itself should also be placed in IRAM, you need to take care of them by themselves.
+185
View File
@@ -0,0 +1,185 @@
Pixel-Processing Accelerator (PPA)
==================================
Introduction
------------
{IDF_TARGET_NAME} includes a pixel-processing accelerator (PPA) module, to realize hardware-level acceleration of image algorithms, such as image rotation, scaling, mirroring, and blending.
Terminology
-----------
The terms used in relation to the PPA driver are given in the table and the diagram below.
.. list-table::
:widths: 25 75
:header-rows: 1
* - Term
- Definition
* - Picture (pic)
- A complete image stored in the system memory.
* - Block
- A portion cropped from a picture at a certain size, with the maximum size equivalent to the entire picture.
* - Pixel
- The unit to be used in the PPA context.
* - PPA Operation
- Types of image algorithm accelerations, includes scale-rotate-mirror (SRM), blend, and fill.
* - PPA Client
- Who wants to do the PPA operations. Typically, every PPA client is hold by a specific task.
* - PPA Transaction
- One request from a PPA client to do a PPA operation is one PPA transaction.
.. figure:: ../../../_static/diagrams/ppa/pic_blk_concept.png
:align: center
:alt: PPA picture/block terminology
PPA picture/block terminology
Functional Overview
-------------------
The following sections detail the design of the PPA driver:
- :ref:`ppa-client-registration` - Covers how to register a PPA client to perform any PPA operations.
- :ref:`ppa-register-callback` - Covers how to hook user specific code to PPA driver event callback function.
- :ref:`ppa-perform-operation` - Covers how to perform a PPA operation.
- :ref:`ppa-buffer-alignment` - Covers the buffer alignment requirements for PPA input and output buffers.
- :ref:`ppa-thread-safety` - Covers the usage of the PPA operation APIs in thread safety aspect.
- :ref:`ppa-performance-overview` - Covers the performance of PPA operations.
.. _ppa-client-registration:
Register PPA Client
^^^^^^^^^^^^^^^^^^^
Requests to perform PPA operations are made by PPA clients. Therefore, PPA clients need to be registered first before doing any PPA operations. Call :cpp:func:`ppa_register_client` function to register a new client. :cpp:type:`ppa_client_config_t` structure is used to specify the properties of the client.
- :cpp:member:`ppa_client_config_t::oper_type` - Each PPA operation type corresponds to one PPA client type, a registered PPA client can only request one specific type of PPA operations.
- :cpp:member:`ppa_client_config_t::max_pending_trans_num` - Decides the maximum number of pending PPA transactions the client can hold.
It is recommended that every task to register its own PPA clients. For example, an application contains two tasks: Task A requires both the PPA SRM and the PPA fill functionalities, so one PPA SRM client and one PPA fill client should be registered in Task A; While Task B also requires the PPA SRM functionality, then another PPA SRM client should be registered in Task B.
If the task no longer needs to do PPA operations, the corresponding PPA clients can be deregistered with :cpp:func:`ppa_unregister_client` function.
.. _ppa-register-callback:
Register PPA Event Callbacks
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
When an event occurs (e.g., a PPA transaction is completed), the CPU is notified of this event via an interrupt. If some specific functions need to be called when a particular event occurs, a callback can be registered for that event by calling :cpp:func:`ppa_client_register_event_callbacks`. This can be specifically useful when ``PPA_TRANS_MODE_NON_BLOCKING`` mode is selected to perform the PPA operations. It is worth noticing that the event callbacks are bound to PPA clients, but the user context is provided per transaction in the call to the PPA operation APIs. This allows the maximum flexibility in utilizing the event callbacks.
The registered callback functions are called in the interrupt context, therefore, the callback functions should follow common ISR (Interrupt Service Routine) rules.
.. _ppa-perform-operation:
Perform PPA Operations
^^^^^^^^^^^^^^^^^^^^^^
Once the PPA client is registered, a PPA operation can be requested with the returned :cpp:type:`ppa_client_handle_t`.
PPA operations includes:
Scale, Rotate, Mirror (SRM)
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Call :cpp:func:`ppa_do_scale_rotate_mirror` to apply one or more of the scaling, rotation, mirroring operations to the target block inside a picture.
Some notes to avoid confusion in configuring :cpp:type:`ppa_srm_oper_config_t`:
.. list::
- :cpp:member:`ppa_in_pic_blk_config_t::buffer` and :cpp:member:`ppa_out_pic_blk_config_t::buffer` have to be the pointers to different picture buffers for a SRM operation.
- The precision of :cpp:member:`ppa_srm_oper_config_t::scale_x` and :cpp:member:`ppa_srm_oper_config_t::scale_y` will be truncated to a step size of 1/16.
- Output block's width/height is totally determined by the input block's width/height, scaling factor, and rotation angle, so output block's width/height does not need to be configured. However, please make sure the output block can fit at the offset location in the output picture.
- If the color mode of the input or output picture is ``PPA_SRM_COLOR_MODE_YUV420``, then its ``pic_w``, ``pic_h``, ``block_w``, ``block_h``, ``block_offset_x``, ``block_offset_y`` fields must be even.
.. note::
The PPA SRM internally uses bilinear scaling algorithm to process. Therefore, it may cause chromatic aberration and loss of contrast at the edges in a scaled picture.
Blend
~~~~~
Call :cpp:func:`ppa_do_blend` to blend the two target blocks of two so-called foreground (FG) and background (BG) pictures.
Blend follows the normal Alpha Blending formula:
:math:`A_{out} = A_b + A_f - A_b \times A_f`
:math:`C_{out} = (C_b \times A_b \times (1 - A_f) + C_f \times A_f) / (A_b + A_f - A_b \times A_f)`
where :math:`A_b` is the Alpha channel of the background layer, :math:`A_f` is the Alpha channel of the foreground layer, :math:`C_b` corresponds to the R, G, B components of the background layer, and :math:`C_f` corresponds to the R, G, B components of the foreground layer.
Note that this formula is not symmetric to FG and BG. When :math:`A_f = 1`, it calculates :math:`C_{out} = C_f`, :math:`A_{out} = 1`, which means if the color mode of the FG picture is ``PPA_BLEND_COLOR_MODE_RGB565`` or ``PPA_BLEND_COLOR_MODE_RGB888``, since a Alpha value of 255 will be filled by the PPA hardware (i.e. :math:`A_f = 1`), the blended result will be identical to the FG block.
If :cpp:member:`ppa_blend_oper_config_t::bg_ck_en` or :cpp:member:`ppa_blend_oper_config_t::fg_ck_en` is set to ``true``, the pixels that fall into the color-key (also known as Chroma-key) range do not follow the Alpha Blending process. Please check **{IDF_TARGET_NAME} Technical Reference Manual** > **Pixel-Processing Accelerator (PPA)** > **Functional Description** > **Layer Blending (BLEND)** [`PDF <{IDF_TARGET_TRM_EN_URL}#ppa>`__] for the detailed rules.
Similarly, some notes to avoid confusion in configuring :cpp:type:`ppa_blend_oper_config_t`:
.. list::
- :cpp:member:`ppa_out_pic_blk_config_t::buffer` can be the same pointer to one of the input's :cpp:member:`ppa_in_pic_blk_config_t::buffer` for a blend operation.
- The blocks' width/height of FG and BG should be identical, and are the width/height values for the output block.
- If the color mode of the input picture is ``PPA_BLEND_COLOR_MODE_A4``, then its ``block_w`` and ``block_offset_x`` fields must be even.
Fill
~~~~
Call :cpp:func:`ppa_do_fill` to fill a target block inside a picture.
:cpp:type:`ppa_trans_mode_t` is a field configurable to all the PPA operation APIs. It decides whether you want the call to the PPA operation API to block until the transaction finishes or to return immediately after the transaction is pushed to the internal queue.
.. _ppa-buffer-alignment:
Buffer Alignment
^^^^^^^^^^^^^^^^
The PPA requires certain buffer alignment rules to ensure memory access correctness:
- Cache line alignment (output buffer only):
- The ``out.buffer`` address and ``out.buffer_size`` must be aligned to the cache line size, if the buffer is located in a memory region that is cacheable.
- Flash encryption alignment (when flash encryption is enabled and the buffers are located in external memory):
- The byte width of the block must be aligned to {IDF_TARGET_SOC_MEMSPI_ENCRYPTION_ALIGNMENT} bytes.
- The starting address of each row of the block must be aligned to {IDF_TARGET_SOC_MEMSPI_ENCRYPTION_ALIGNMENT} bytes.
Additionally, when accessing encrypted external memory, the PPA's DMA data burst length must be greater than or equal to {IDF_TARGET_SOC_MEMSPI_ENCRYPTION_ALIGNMENT} bytes. If a smaller burst length is configured, the driver will automatically increase it to meet this requirement.
.. warning::
SRM engine processes the picture by macro blocks, where the alignment of the macro blocks is uncontrollable, thus if the buffer is in external memory, SRM operation is unable to work with flash encrypted.
.. _ppa-thread-safety:
Thread Safety
^^^^^^^^^^^^^
The PPA driver has guaranteed the thread safety of calling the PPA operation APIs in all following situations:
.. list::
- Among clients of different types in one task
- Among clients of same type in different tasks
- Among clients of different types in different tasks
.. _ppa-performance-overview:
Performance Overview
^^^^^^^^^^^^^^^^^^^^
The PPA operations are acted on the target block of an input picture. Therefore, the time it takes to complete a PPA transaction is proportional to the amount of the data in the block. The size of the entire picture has no influence on the performance. More importantly, the PPA performance highly relies on the PSRAM bandwidth if the pictures are located in the PSRAM section. When there are quite a few peripherals reading and writing to the PSRAM at the same time, the performance of PPA operation will be greatly reduced.
Application Examples
^^^^^^^^^^^^^^^^^^^^
* :example:`peripherals/ppa/ppa_transform` - PPA transform image processing example. The embedded RGB565 image is transformed by SRM, highlighted with blend, framed with fill, and emitted as base64 for host-side PPM reconstruction and golden-image comparison.
* :example:`peripherals/ppa/ppa_color_key` - PPA blend color-keying example. The example generates a centered RGB888 glow foreground in software, then demonstrates two blend effects on the embedded RGB565 image: replacing the keyed red `ESP32` text with the glow, and preserving the keyed text while blending the glow into the non-key area. Both results are emitted as base64 for host-side PPM reconstruction and golden-image comparison.
API Reference
-------------
.. include-build-file:: inc/ppa.inc
.. include-build-file:: inc/ppa_types.inc
+663
View File
@@ -0,0 +1,663 @@
Remote Control Transceiver (RMT)
================================
:link_to_translation:`zh_CN:[中文]`
Introduction
------------
The RMT (Remote Control Transceiver) peripheral was designed to act as an infrared transceiver. However, due to the flexibility of its data format, RMT can be extended to a versatile and general-purpose transceiver, transmitting or receiving many other types of signals. From the perspective of network layering, the RMT hardware contains both physical and data link layers. The physical layer defines the communication media and bit signal representation. The data link layer defines the format of an RMT frame. The minimal data unit in the frame is called the **RMT symbol**, which is represented by :cpp:type:`rmt_symbol_word_t` in the driver.
{IDF_TARGET_NAME} contains multiple channels in the RMT peripheral [1]_. Each channel can be independently configured as either transmitter or receiver.
Typically, the RMT peripheral can be used in the following scenarios:
- Transmit or receive infrared signals, with any IR protocols, e.g., NEC
- General-purpose sequence generator
- Transmit signals in a hardware-controlled loop, with a finite or infinite number of times
- Multi-channel simultaneous transmission
- Modulate the carrier to the output signal or demodulate the carrier from the input signal
Layout of RMT Symbols
^^^^^^^^^^^^^^^^^^^^^
The RMT hardware defines data in its own pattern -- the **RMT symbol**. The diagram below illustrates the bit fields of an RMT symbol. Each symbol consists of two pairs of two values. The first value in the pair is a 15-bit value representing the signal's duration in units of RMT ticks. The second in the pair is a 1-bit value representing the signal's logic level, i.e., high or low.
.. packetdiag:: /../_static/diagrams/rmt/rmt_symbols.diag
:caption: Structure of RMT symbols (L - signal level)
:align: center
RMT Transmitter Overview
^^^^^^^^^^^^^^^^^^^^^^^^
The data path and control path of an RMT TX channel is illustrated in the figure below:
.. blockdiag:: /../_static/diagrams/rmt/rmt_tx.diag
:caption: RMT Transmitter Overview
:align: center
The driver encodes the user's data into RMT data format, then the RMT transmitter can generate the waveforms according to the encoding artifacts. It is also possible to modulate a high-frequency carrier signal before being routed to a GPIO pad.
RMT Receiver Overview
^^^^^^^^^^^^^^^^^^^^^
The data path and control path of an RMT RX channel is illustrated in the figure below:
.. blockdiag:: /../_static/diagrams/rmt/rmt_rx.diag
:caption: RMT Receiver Overview
:align: center
The RMT receiver can sample incoming signals into RMT data format, and store the data in memory. It is also possible to tell the receiver the basic characteristics of the incoming signal, so that the signal's stop condition can be recognized, and signal glitches and noise can be filtered out. The RMT peripheral also supports demodulating the high-frequency carrier from the base signal.
Functional Overview
-------------------
The description of the RMT functionality is divided into the following sections:
- :ref:`rmt-resource-allocation` - covers how to allocate and properly configure RMT channels. It also covers how to recycle channels and other resources when they are no longer used.
- :ref:`rmt-carrier-modulation-and demodulation` - describes how to modulate and demodulate the carrier signals for TX and RX channels respectively.
- :ref:`rmt-register-event-callbacks` - covers how to register user-provided event callbacks to receive RMT channel events.
- :ref:`rmt-enable-and-disable-channel` - shows how to enable and disable the RMT channel.
- :ref:`rmt-initiate-tx-transaction` - describes the steps to initiate a transaction for a TX channel.
- :ref:`rmt-initiate-rx-transaction` - describes the steps to initiate a transaction for an RX channel.
- :ref:`rmt-multiple-channels-simultaneous-transmission` - describes how to collect multiple channels into a sync group so that their transmissions can be started simultaneously.
- :ref:`rmt-rmt-encoder` - focuses on how to write a customized encoder by combining multiple primitive encoders that are provided by the driver.
- :ref:`rmt-power-management` - describes how different clock sources affects power consumption.
- :ref:`rmt-cache-safe` - describes how disabling the cache affects the RMT driver, and tips to mitigate it.
- :ref:`rmt-thread-safety` - lists which APIs are guaranteed to be thread-safe by the driver.
- :ref:`rmt-kconfig-options` - describes the various Kconfig options supported by the RMT driver.
.. _rmt-resource-allocation:
Resource Allocation
^^^^^^^^^^^^^^^^^^^
Both RMT TX and RX channels are represented by :cpp:type:`rmt_channel_handle_t` in the driver. The driver internally manages which channels are available and hands out a free channel on request.
Install RMT TX Channel
~~~~~~~~~~~~~~~~~~~~~~
To install an RMT TX channel, there is a configuration structure that needs to be given in advance :cpp:type:`rmt_tx_channel_config_t`. The following list describes each member of the configuration structure.
- :cpp:member:`rmt_tx_channel_config_t::gpio_num` sets the GPIO number used by the transmitter.
- :cpp:member:`rmt_tx_channel_config_t::clk_src` selects the source clock for the RMT channel. The available clocks are listed in :cpp:type:`rmt_clock_source_t`. Note that, the selected clock is also used by other channels, which means the user should ensure this configuration is the same when allocating other channels, regardless of TX or RX. For the effect on the power consumption of different clock sources, please refer to the :ref:`rmt-power-management` section.
- :cpp:member:`rmt_tx_channel_config_t::resolution_hz` sets the resolution of the internal tick counter. The timing parameter of the RMT signal is calculated based on this **tick**.
- :cpp:member:`rmt_tx_channel_config_t::mem_block_symbols` has a slightly different meaning based on if the DMA backend is enabled or not.
- If the DMA is enabled via :cpp:member:`rmt_tx_channel_config_t::with_dma`, then this field controls the size of the internal DMA buffer. To achieve a better throughput and smaller CPU overhead, you can set a larger value, e.g., ``1024``.
- If DMA is not used, this field controls the size of the dedicated memory block owned by the channel, which should be at least {IDF_TARGET_SOC_RMT_MEM_WORDS_PER_CHANNEL}.
- :cpp:member:`rmt_tx_channel_config_t::trans_queue_depth` sets the depth of the internal transaction queue, the deeper the queue, the more transactions can be prepared in the backlog.
- :cpp:member:`rmt_tx_channel_config_t::invert_out` is used to decide whether to invert the RMT signal before sending it to the GPIO pad.
- :cpp:member:`rmt_tx_channel_config_t::with_dma` enables the DMA backend for the channel. Using the DMA allows a significant amount of the channel's workload to be offloaded from the CPU. However, the DMA backend is not available on all ESP chips, please refer to [`TRM <{IDF_TARGET_TRM_EN_URL}#rmt>`__] before you enable this option. Or you might encounter a :c:macro:`ESP_ERR_NOT_SUPPORTED` error.
- :cpp:member:`rmt_tx_channel_config_t::intr_priority` Set the priority of the interrupt. If set to ``0`` , then the driver will use a interrupt with low or medium priority (priority level may be one of 1,2 or 3), otherwise use the priority indicated by :cpp:member:`rmt_tx_channel_config_t::intr_priority`. Please use the number form (1,2,3) , not the bitmask form ((1<<1),(1<<2),(1<<3)). Please pay attention that once the interrupt priority is set, it cannot be changed until :cpp:func:`rmt_del_channel` is called.
- :cpp:member:`rmt_tx_channel_config_t::allow_pd` configures if the driver allows the system to power down the peripheral in light sleep mode. Before entering sleep, the system will backup the RMT register context, which will be restored later when the system exit the sleep mode. Powering down the peripheral can save more power, but at the cost of more memory consumed to save the register context. It's a tradeoff between power consumption and memory consumption. This configuration option relies on specific hardware feature, if you enable it on an unsupported chip, you will see error message like ``not able to power down in light sleep``.
Once the :cpp:type:`rmt_tx_channel_config_t` structure is populated with mandatory parameters, users can call :cpp:func:`rmt_new_tx_channel` to allocate and initialize a TX channel. This function returns an RMT channel handle if it runs correctly. Specifically, when there are no more free channels in the RMT resource pool, this function returns :c:macro:`ESP_ERR_NOT_FOUND` error. If some feature (e.g., DMA backend) is not supported by the hardware, it returns :c:macro:`ESP_ERR_NOT_SUPPORTED` error.
.. code-block:: c
rmt_channel_handle_t tx_chan = NULL;
rmt_tx_channel_config_t tx_chan_config = {
.clk_src = RMT_CLK_SRC_DEFAULT, // select source clock
.gpio_num = 0, // GPIO number
.mem_block_symbols = 64, // memory block size, 64 * 4 = 256 Bytes
.resolution_hz = 1 * 1000 * 1000, // 1 MHz tick resolution, i.e., 1 tick = 1 µs
.trans_queue_depth = 4, // set the number of transactions that can pend in the background
.flags.invert_out = false, // do not invert output signal
.flags.with_dma = false, // do not need DMA backend
};
ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &tx_chan));
Install RMT RX Channel
~~~~~~~~~~~~~~~~~~~~~~
To install an RMT RX channel, there is a configuration structure that needs to be given in advance :cpp:type:`rmt_rx_channel_config_t`. The following list describes each member of the configuration structure.
- :cpp:member:`rmt_rx_channel_config_t::gpio_num` sets the GPIO number used by the receiver.
- :cpp:member:`rmt_rx_channel_config_t::clk_src` selects the source clock for the RMT channel. The available clocks are listed in :cpp:type:`rmt_clock_source_t`. Note that, the selected clock is also used by other channels, which means the user should ensure this configuration is the same when allocating other channels, regardless of TX or RX. For the effect on the power consumption of different clock sources, please refer to the :ref:`rmt-power-management` section.
- :cpp:member:`rmt_rx_channel_config_t::resolution_hz` sets the resolution of the internal tick counter. The timing parameter of the RMT signal is calculated based on this **tick**.
- :cpp:member:`rmt_rx_channel_config_t::mem_block_symbols` has a slightly different meaning based on whether the DMA backend is enabled.
- If the DMA is enabled via :cpp:member:`rmt_rx_channel_config_t::with_dma`, this field controls the maximum size of the DMA buffer.
- If DMA is not used, this field controls the size of the dedicated memory block owned by the channel, which should be at least {IDF_TARGET_SOC_RMT_MEM_WORDS_PER_CHANNEL}.
- :cpp:member:`rmt_rx_channel_config_t::invert_in` is used to invert the input signals before it is passed to the RMT receiver. The inversion is done by the GPIO matrix instead of by the RMT peripheral.
- :cpp:member:`rmt_rx_channel_config_t::with_dma` enables the DMA backend for the channel. Using the DMA allows a significant amount of the channel's workload to be offloaded from the CPU. However, the DMA backend is not available on all ESP chips, please refer to [`TRM <{IDF_TARGET_TRM_EN_URL}#rmt>`__] before you enable this option. Or you might encounter a :c:macro:`ESP_ERR_NOT_SUPPORTED` error.
- :cpp:member:`rmt_rx_channel_config_t::intr_priority` Set the priority of the interrupt. If set to ``0`` , then the driver will use a interrupt with low or medium priority (priority level may be one of 1,2 or 3), otherwise use the priority indicated by :cpp:member:`rmt_rx_channel_config_t::intr_priority`. Please use the number form (1,2,3) , not the bitmask form ((1<<1),(1<<2),(1<<3)). Please pay attention that once the interrupt priority is set, it cannot be changed until :cpp:func:`rmt_del_channel` is called.
- :cpp:member:`rmt_rx_channel_config_t::allow_pd` configures if the driver allows the system to power down the peripheral in light sleep mode. Before entering sleep, the system will backup the RMT register context, which will be restored later when the system exit the sleep mode. Powering down the peripheral can save more power, but at the cost of more memory consumed to save the register context. It's a tradeoff between power consumption and memory consumption. This configuration option relies on specific hardware feature, if you enable it on an unsupported chip, you will see error message like ``not able to power down in light sleep``.
Once the :cpp:type:`rmt_rx_channel_config_t` structure is populated with mandatory parameters, users can call :cpp:func:`rmt_new_rx_channel` to allocate and initialize an RX channel. This function returns an RMT channel handle if it runs correctly. Specifically, when there are no more free channels in the RMT resource pool, this function returns :c:macro:`ESP_ERR_NOT_FOUND` error. If some feature (e.g., DMA backend) is not supported by the hardware, it returns :c:macro:`ESP_ERR_NOT_SUPPORTED` error.
.. code-block:: c
rmt_channel_handle_t rx_chan = NULL;
rmt_rx_channel_config_t rx_chan_config = {
.clk_src = RMT_CLK_SRC_DEFAULT, // select source clock
.resolution_hz = 1 * 1000 * 1000, // 1 MHz tick resolution, i.e., 1 tick = 1 µs
.mem_block_symbols = 64, // memory block size, 64 * 4 = 256 Bytes
.gpio_num = 2, // GPIO number
.flags.invert_in = false, // do not invert input signal
.flags.with_dma = false, // do not need DMA backend
};
ESP_ERROR_CHECK(rmt_new_rx_channel(&rx_chan_config, &rx_chan));
.. note::
When multiple RMT channels are allocated at the same time, the groups prescale is determined based on the resolution of the first channel. The driver then selects the appropriate prescale from low to high. To avoid prescale conflicts when allocating multiple channels, allocate channels in order of their target resolution, either from highest to lowest or lowest to highest.
Uninstall RMT Channel
~~~~~~~~~~~~~~~~~~~~~
If a previously installed RMT channel is no longer needed, it is recommended to recycle the resources by calling :cpp:func:`rmt_del_channel`, which in return allows the underlying software and hardware resources to be reused for other purposes.
.. _rmt-carrier-modulation-and demodulation:
Carrier Modulation and Demodulation
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The RMT transmitter can generate a carrier wave and modulate it onto the message signal. Compared to the message signal, the carrier signal's frequency is significantly higher. In addition, the user can only set the frequency and duty cycle for the carrier signal. The RMT receiver can demodulate the carrier signal from the incoming signal. Note that, carrier modulation and demodulation are not supported on all ESP chips, please refer to [`TRM <{IDF_TARGET_TRM_EN_URL}#rmt>`__] before configuring the carrier, or you might encounter a :c:macro:`ESP_ERR_NOT_SUPPORTED` error.
Carrier-related configurations lie in :cpp:type:`rmt_carrier_config_t`:
- :cpp:member:`rmt_carrier_config_t::frequency_hz` sets the carrier frequency, in Hz.
- :cpp:member:`rmt_carrier_config_t::duty_cycle` sets the carrier duty cycle.
- :cpp:member:`rmt_carrier_config_t::extra_rmt_carrier_config_flags::polarity_active_low` sets the carrier polarity, i.e., on which level the carrier is applied.
- :cpp:member:`rmt_carrier_config_t::extra_rmt_carrier_config_flags::always_on` sets whether to output the carrier even when the data transmission has finished. This configuration is only valid for the TX channel.
.. note::
For the RX channel, we should not set the carrier frequency exactly to the theoretical value. It is recommended to leave a tolerance for the carrier frequency. For example, in the snippet below, we set the frequency to 25 KHz, instead of the 38 KHz configured on the TX side. The reason is that reflection and refraction occur when a signal travels through the air, leading to distortion on the receiver side.
.. code-block:: c
rmt_carrier_config_t tx_carrier_cfg = {
.duty_cycle = 0.33, // duty cycle 33%
.frequency_hz = 38000, // 38 KHz
.flags.polarity_active_low = false, // carrier should be modulated to high level
};
// modulate carrier to TX channel
ESP_ERROR_CHECK(rmt_apply_carrier(tx_chan, &tx_carrier_cfg));
rmt_carrier_config_t rx_carrier_cfg = {
.duty_cycle = 0.33, // duty cycle 33%
.frequency_hz = 25000, // 25 KHz carrier, should be smaller than the transmitter's carrier frequency
.flags.polarity_active_low = false, // the carrier is modulated to high level
};
// demodulate carrier from RX channel
ESP_ERROR_CHECK(rmt_apply_carrier(rx_chan, &rx_carrier_cfg));
.. _rmt-register-event-callbacks:
Register Event Callbacks
^^^^^^^^^^^^^^^^^^^^^^^^
When an event occurs on an RMT channel (e.g., transmission or receiving is completed), the CPU is notified of this event via an interrupt. If you have some function that needs to be called when a particular events occur, you can register a callback for that event to the RMT driver's ISR (Interrupt Service Routine) by calling :cpp:func:`rmt_tx_register_event_callbacks` and :cpp:func:`rmt_rx_register_event_callbacks` for TX and RX channel respectively. Since the registered callback functions are called in the interrupt context, the user should ensure the callback function does not block, e.g., by making sure that only FreeRTOS APIs with the ``FromISR`` suffix are called from within the function. The callback function has a boolean return value used to indicate whether a higher priority task has been unblocked by the callback.
The TX channel-supported event callbacks are listed in the :cpp:type:`rmt_tx_event_callbacks_t`:
- :cpp:member:`rmt_tx_event_callbacks_t::on_trans_done` sets a callback function for the "trans-done" event. The function prototype is declared in :cpp:type:`rmt_tx_done_callback_t`.
The RX channel-supported event callbacks are listed in the :cpp:type:`rmt_rx_event_callbacks_t`:
- :cpp:member:`rmt_rx_event_callbacks_t::on_recv_done` sets a callback function for "receive-done" event. The function prototype is declared in :cpp:type:`rmt_rx_done_callback_t`.
.. note::
The "receive-done" is not equivalent to "receive-finished". This callback can also be called at a "partial-receive-done" time, for many times during one receive transaction.
Users can save their own context in :cpp:func:`rmt_tx_register_event_callbacks` and :cpp:func:`rmt_rx_register_event_callbacks` as well, via the parameter ``user_data``. The user data is directly passed to each callback function.
In the callback function, users can fetch the event-specific data that is filled by the driver in the ``edata``. Note that the ``edata`` pointer is **only** valid during the callback, please do not try to save this pointer and use that outside of the callback function.
The TX-done event data is defined in :cpp:type:`rmt_tx_done_event_data_t`:
- :cpp:member:`rmt_tx_done_event_data_t::num_symbols` indicates the number of transmitted RMT symbols. This also reflects the size of the encoding artifacts. Please note, this value accounts for the ``EOF`` symbol as well, which is appended by the driver to mark the end of one transaction.
The RX-complete event data is defined in :cpp:type:`rmt_rx_done_event_data_t`:
- :cpp:member:`rmt_rx_done_event_data_t::received_symbols` points to the received RMT symbols. These symbols are saved in the ``buffer`` parameter of the :cpp:func:`rmt_receive` function. Users should not free this receive buffer before the callback returns. If you also enabled the partial receive feature, then the user buffer will be used as a "second level buffer", where its content can be overwritten by data comes in afterwards. In this case, you should copy the received data to another place if you want to keep it or process it later.
- :cpp:member:`rmt_rx_done_event_data_t::num_symbols` indicates the number of received RMT symbols. This value is not larger than the ``buffer_size`` parameter of :cpp:func:`rmt_receive` function. If the ``buffer_size`` is not sufficient to accommodate all the received RMT symbols, the driver only keeps the maximum number of symbols that the buffer can hold, and excess symbols are discarded or ignored.
- :cpp:member:`rmt_rx_done_event_data_t::extra_rmt_rx_done_event_flags::is_last` indicates whether the current received buffer is the last one in the transaction. This is useful when you enable the partial reception feature by :cpp:member:`rmt_receive_config_t::extra_rmt_receive_flags::en_partial_rx`.
.. _rmt-enable-and-disable-channel:
Enable and Disable Channel
^^^^^^^^^^^^^^^^^^^^^^^^^^
:cpp:func:`rmt_enable` must be called in advance before transmitting or receiving RMT symbols. For TX channels, enabling a channel enables a specific interrupt and prepares the hardware to dispatch transactions. For RX channels, enabling a channel enables an interrupt, but the receiver is not started during this time, as the characteristics of the incoming signal have yet to be specified. The receiver is started in :cpp:func:`rmt_receive`.
:cpp:func:`rmt_disable` does the opposite by disabling the interrupt and clearing any pending interrupts. The transmitter and receiver are disabled as well.
.. code:: c
ESP_ERROR_CHECK(rmt_enable(tx_chan));
ESP_ERROR_CHECK(rmt_enable(rx_chan));
.. _rmt-initiate-tx-transaction:
Initiate TX Transaction
^^^^^^^^^^^^^^^^^^^^^^^
RMT is a special communication peripheral, as it is unable to transmit raw byte streams like SPI and I2C. RMT can only send data in its own format :cpp:type:`rmt_symbol_word_t`. However, the hardware does not help to convert the user data into RMT symbols, this can only be done in software by the so-called **RMT Encoder**. The encoder is responsible for encoding user data into RMT symbols and then writing to the RMT memory block or the DMA buffer. For how to create an RMT encoder, please refer to :ref:`rmt-rmt-encoder`.
Once you created an encoder, you can initiate a TX transaction by calling :cpp:func:`rmt_transmit`. This function takes several positional parameters like channel handle, encoder handle, and payload buffer. Besides, you also need to provide a transmission-specific configuration in :cpp:type:`rmt_transmit_config_t`:
- :cpp:member:`rmt_transmit_config_t::loop_count` sets the number of transmission loops. After the transmitter has finished one round of transmission, it can restart the same transmission again if this value is not set to zero. As the loop is controlled by hardware, the RMT channel can be used to generate many periodic sequences with minimal CPU intervention.
- Setting :cpp:member:`rmt_transmit_config_t::loop_count` to `-1` means an infinite loop transmission. In this case, the channel does not stop until :cpp:func:`rmt_disable` is called. The "trans-done" event is not generated as well.
.. only:: not esp32
- Setting :cpp:member:`rmt_transmit_config_t::loop_count` to a positive number means finite number of iterations. In this case, the "trans-done" event is when the specified number of iterations have completed.
- :cpp:member:`rmt_transmit_config_t::eot_level` sets the output level when the transmitter finishes working or stops working by calling :cpp:func:`rmt_disable`.
- :cpp:member:`rmt_transmit_config_t::queue_nonblocking` sets whether to wait for a free slot in the transaction queue when it is full. If this value is set to ``true``, then the function will return with an error code :c:macro:`ESP_ERR_INVALID_STATE` when the queue is full. Otherwise, the function will block until a free slot is available in the queue.
.. note::
There is a limitation in the transmission size if the :cpp:member:`rmt_transmit_config_t::loop_count` is set to non-zero, i.e., to enable the loop feature. The total amount of symbols returned by the encoder should not exceed the capacity of :c:macro:`SOC_RMT_MEM_WORDS_PER_CHANNEL`, or you might see an error message like ``encoding artifacts can't exceed hw memory block for loop transmission``. If you have to start a large transaction by loop, you can try either of the following methods.
- Increase the :cpp:member:`rmt_tx_channel_config_t::mem_block_symbols`. This approach does not work if the DMA backend is also enabled.
- Customize an encoder and construct an infinite loop in the encoding function. See also :ref:`rmt-rmt-encoder`.
Internally, :cpp:func:`rmt_transmit` constructs a transaction descriptor and sends it to a job queue, which is dispatched in the ISR. So it is possible that the transaction is not started yet when :cpp:func:`rmt_transmit` returns. You cannot recycle or modify the payload buffer until the transaction is finished. You can get the transaction completion event by registering a callback function via :cpp:func:`rmt_tx_register_event_callbacks`. To ensure all pending transactions to complete, you can also use :cpp:func:`rmt_tx_wait_all_done`.
.. _rmt-multiple-channels-simultaneous-transmission:
Multiple Channels Simultaneous Transmission
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
In some real-time control applications (e.g., to make two robotic arms move simultaneously), you do not want any time drift between different channels. The RMT driver can help to manage this by creating a so-called **Sync Manager**. The sync manager is represented by :cpp:type:`rmt_sync_manager_handle_t` in the driver. The procedure of RMT sync transmission is shown as follows:
.. figure:: /../_static/rmt_tx_sync.png
:align: center
:alt: RMT TX Sync
RMT TX Sync
Install RMT Sync Manager
~~~~~~~~~~~~~~~~~~~~~~~~
To create a sync manager, the user needs to tell which channels are going to be managed in the :cpp:type:`rmt_sync_manager_config_t`:
- :cpp:member:`rmt_sync_manager_config_t::tx_channel_array` points to the array of TX channels to be managed.
- :cpp:member:`rmt_sync_manager_config_t::array_size` sets the number of channels to be managed.
:cpp:func:`rmt_new_sync_manager` can return a manager handle on success. This function could also fail due to various errors such as invalid arguments, etc. Especially, when the sync manager has been installed before, and there are no hardware resources to create another manager, this function reports :c:macro:`ESP_ERR_NOT_FOUND` error. In addition, if the sync manager is not supported by the hardware, it reports a :c:macro:`ESP_ERR_NOT_SUPPORTED` error. Please refer to [`TRM <{IDF_TARGET_TRM_EN_URL}#rmt>`__] before using the sync manager feature.
Start Transmission Simultaneously
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
For any managed TX channel, it does not start the machine until :cpp:func:`rmt_transmit` has been called on all channels in :cpp:member:`rmt_sync_manager_config_t::tx_channel_array`. Before that, the channel is just put in a waiting state. TX channels will usually complete their transactions at different times due to differing transactions, thus resulting in a loss of sync. So before restarting a simultaneous transmission, the user needs to call :cpp:func:`rmt_sync_reset` to synchronize all channels again.
Calling :cpp:func:`rmt_del_sync_manager` can recycle the sync manager and enable the channels to initiate transactions independently afterward.
.. code:: c
rmt_channel_handle_t tx_channels[2] = {NULL}; // declare two channels
int tx_gpio_number[2] = {0, 2};
// install channels one by one
for (int i = 0; i < 2; i++) {
rmt_tx_channel_config_t tx_chan_config = {
.clk_src = RMT_CLK_SRC_DEFAULT, // select source clock
.gpio_num = tx_gpio_number[i], // GPIO number
.mem_block_symbols = 64, // memory block size, 64 * 4 = 256 Bytes
.resolution_hz = 1 * 1000 * 1000, // 1 MHz resolution
.trans_queue_depth = 1, // set the number of transactions that can pend in the background
};
ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &tx_channels[i]));
}
// enable the channels
for (int i = 0; i < 2; i++) {
ESP_ERROR_CHECK(rmt_enable(tx_channels[i]));
}
// install sync manager
rmt_sync_manager_handle_t synchro = NULL;
rmt_sync_manager_config_t synchro_config = {
.tx_channel_array = tx_channels,
.array_size = sizeof(tx_channels) / sizeof(tx_channels[0]),
};
ESP_ERROR_CHECK(rmt_new_sync_manager(&synchro_config, &synchro));
ESP_ERROR_CHECK(rmt_transmit(tx_channels[0], led_strip_encoders[0], led_data, led_num * 3, &transmit_config));
// tx_channels[0] does not start transmission until call of `rmt_transmit()` for tx_channels[1] returns
ESP_ERROR_CHECK(rmt_transmit(tx_channels[1], led_strip_encoders[1], led_data, led_num * 3, &transmit_config));
.. _rmt-initiate-rx-transaction:
Initiate RX Transaction
^^^^^^^^^^^^^^^^^^^^^^^
As also discussed in the :ref:`rmt-enable-and-disable-channel`, calling :cpp:func:`rmt_enable` does not prepare an RX to receive RMT symbols. The user needs to specify the basic characteristics of the incoming signals in :cpp:type:`rmt_receive_config_t`:
- :cpp:member:`rmt_receive_config_t::signal_range_min_ns` specifies the minimal valid pulse duration in either high or low logic levels. A pulse width that is smaller than this value is treated as a glitch, and ignored by the hardware.
- :cpp:member:`rmt_receive_config_t::signal_range_max_ns` specifies the maximum valid pulse duration in either high or low logic levels. A pulse width that is bigger than this value is treated as **Stop Signal**, and the receiver generates receive-complete event immediately.
- If the incoming packet is long, that they cannot be stored in the user buffer at once, you can enable the partial reception feature by setting :cpp:member:`rmt_receive_config_t::extra_rmt_receive_flags::en_partial_rx` to ``true``. In this case, the driver invokes :cpp:member:`rmt_rx_event_callbacks_t::on_recv_done` callback multiple times during one transaction, when the user buffer is **almost full**. You can check the value of :cpp:member:`rmt_rx_done_event_data_t::extra_rmt_rx_done_event_flags::is_last` to know if the transaction is about to finish. Please note this features is not supported on all ESP series chips because it relies on hardware abilities like "ping-pong receive" or "DMA receive".
The RMT receiver starts the RX machine after the user calls :cpp:func:`rmt_receive` with the provided configuration above. Note that, this configuration is transaction specific, which means, to start a new round of reception, the user needs to set the :cpp:type:`rmt_receive_config_t` again. The receiver saves the incoming signals into its internal memory block or DMA buffer, in the format of :cpp:type:`rmt_symbol_word_t`.
.. note::
After calling :cpp:func:`rmt_receive` function, the actual reception starts at the first level change of the received signal. If you need to get the start time of the reception, you can register a GPIO interrupt on the GPIO of the RMT RX channel, please refer to :doc:`GPIO doc </api-reference/peripherals/gpio>` for details.
.. only:: SOC_RMT_SUPPORT_RX_PINGPONG
Due to the limited size of the memory block, the RMT receiver notifies the driver to copy away the accumulated symbols in a ping-pong way.
.. only:: not SOC_RMT_SUPPORT_RX_PINGPONG
Due to the limited size of the memory block, the RMT receiver can only save short frames whose length is not longer than the memory block capacity. Long frames are truncated by the hardware, and the driver reports an error message: ``hw buffer too small, received symbols truncated``.
The copy destination should be provided in the ``buffer`` parameter of :cpp:func:`rmt_receive` function. If this buffer overflows due to an insufficient buffer size, the receiver can continue to work, but overflowed symbols are dropped and the following error message is reported: ``user buffer too small, received symbols truncated``. Please take care of the lifecycle of the ``buffer`` parameter, ensuring that the buffer is not recycled before the receiver is finished or stopped.
The receiver is stopped by the driver when it finishes working, i.e., receive a signal whose duration is bigger than :cpp:member:`rmt_receive_config_t::signal_range_max_ns`. The user needs to call :cpp:func:`rmt_receive` again to restart the receiver, if necessary. The user can get the received data in the :cpp:member:`rmt_rx_event_callbacks_t::on_recv_done` callback. See also :ref:`rmt-register-event-callbacks` for more information.
.. code:: c
static bool example_rmt_rx_done_callback(rmt_channel_handle_t channel, const rmt_rx_done_event_data_t *edata, void *user_data)
{
BaseType_t high_task_wakeup = pdFALSE;
QueueHandle_t receive_queue = (QueueHandle_t)user_data;
// send the received RMT symbols to the parser task
xQueueSendFromISR(receive_queue, edata, &high_task_wakeup);
// return whether any task is woken up
return high_task_wakeup == pdTRUE;
}
QueueHandle_t receive_queue = xQueueCreate(1, sizeof(rmt_rx_done_event_data_t));
rmt_rx_event_callbacks_t cbs = {
.on_recv_done = example_rmt_rx_done_callback,
};
ESP_ERROR_CHECK(rmt_rx_register_event_callbacks(rx_channel, &cbs, receive_queue));
// the following timing requirement is based on NEC protocol
rmt_receive_config_t receive_config = {
.signal_range_min_ns = 1250, // the shortest duration for NEC signal is 560 µs, 1250 ns < 560 µs, valid signal is not treated as noise
.signal_range_max_ns = 12000000, // the longest duration for NEC signal is 9000 µs, 12000000 ns > 9000 µs, the receive does not stop early
};
rmt_symbol_word_t raw_symbols[64]; // 64 symbols should be sufficient for a standard NEC frame
// ready to receive
ESP_ERROR_CHECK(rmt_receive(rx_channel, raw_symbols, sizeof(raw_symbols), &receive_config));
// wait for the RX-done signal
rmt_rx_done_event_data_t rx_data;
xQueueReceive(receive_queue, &rx_data, portMAX_DELAY);
// parse the received symbols
example_parse_nec_frame(rx_data.received_symbols, rx_data.num_symbols);
.. _rmt-rmt-encoder:
RMT Encoder
^^^^^^^^^^^
An RMT encoder is part of the RMT TX transaction, whose responsibility is to generate and write the correct RMT symbols into hardware memory or DMA buffer at a specific time. There are some special restrictions for an encoding function:
- During a single transaction, the encoding function may be called multiple times. This is necessary because the target RMT memory block cannot hold all the artifacts at once. To overcome this limitation, the driver utilizes a **ping-pong** approach, where the encoding session is divided into multiple parts. This means that the encoder needs to **keep track of its state** to continue encoding from where it left off in the previous part.
- The encoding function is running in the ISR context. To speed up the encoding session, it is highly recommended to put the encoding function into IRAM. This can also avoid the cache miss during encoding.
To help get started with the RMT driver faster, some commonly used encoders are provided out-of-the-box. They can either work alone or be chained together into a new encoder. See also `Composite Pattern <https://en.wikipedia.org/wiki/Composite_pattern>`__ for the principle behind it. The driver has defined the encoder interface in :cpp:type:`rmt_encoder_t`, it contains the following functions:
- :cpp:member:`rmt_encoder_t::encode` is the fundamental function of an encoder. This is where the encoding session happens.
- The function might be called multiple times within a single transaction. The encode function should return the state of the current encoding session.
- The supported states are listed in the :cpp:type:`rmt_encode_state_t`. If the result contains :cpp:enumerator:`RMT_ENCODING_COMPLETE`, it means the current encoder has finished work.
- If the result contains :cpp:enumerator:`RMT_ENCODING_MEM_FULL`, the program needs to yield from the current session, as there is no space to save more encoding artifacts.
- :cpp:member:`rmt_encoder_t::reset` should reset the encoder state back to the initial state (the RMT encoder is stateful).
- If the RMT transmitter is manually stopped without resetting its corresponding encoder, subsequent encoding session can be erroneous.
- This function is also called implicitly in :cpp:func:`rmt_disable`.
- :cpp:member:`rmt_encoder_t::del` should free the resources allocated by the encoder.
Copy Encoder
~~~~~~~~~~~~
A copy encoder is created by calling :cpp:func:`rmt_new_copy_encoder`. A copy encoder's main functionality is to copy the RMT symbols from user space into the driver layer. It is usually used to encode ``const`` data, i.e., data does not change at runtime after initialization such as the leading code in the IR protocol.
A configuration structure :cpp:type:`rmt_copy_encoder_config_t` should be provided in advance before calling :cpp:func:`rmt_new_copy_encoder`. Currently, this configuration is reserved for future expansion, and has no specific use or setting items for now.
Bytes Encoder
~~~~~~~~~~~~~
A bytes encoder is created by calling :cpp:func:`rmt_new_bytes_encoder`. The bytes encoder's main functionality is to convert the user space byte stream into RMT symbols dynamically. It is usually used to encode dynamic data, e.g., the address and command fields in the IR protocol.
A configuration structure :cpp:type:`rmt_bytes_encoder_config_t` should be provided in advance before calling :cpp:func:`rmt_new_bytes_encoder`:
- :cpp:member:`rmt_bytes_encoder_config_t::bit0` and :cpp:member:`rmt_bytes_encoder_config_t::bit1` are necessary to specify the encoder how to represent bit zero and bit one in the format of :cpp:type:`rmt_symbol_word_t`.
- :cpp:member:`rmt_bytes_encoder_config_t::msb_first` sets the bit endianness of each byte. If it is set to true, the encoder encodes the **Most Significant Bit** first. Otherwise, it encodes the **Least Significant Bit** first.
Besides the primitive encoders provided by the driver, the user can implement his own encoder by chaining the existing encoders together. A common encoder chain is shown as follows:
.. blockdiag:: /../_static/diagrams/rmt/rmt_encoder_chain.diag
:caption: RMT Encoder Chain
:align: center
Simple Callback Encoder
~~~~~~~~~~~~~~~~~~~~~~~
A simple callback encoder is created by calling :cpp:func:`rmt_new_simple_encoder`. The simple callback encoder allows you to provide a callback that reads data from userspace and writes symbols to the output stream without having to chain other encoders. The callback itself gets a pointer to the data passed to :cpp:func:`rmt_transmit`, a counter indicating the amount of symbols already written by the callback in this transmission, and a pointer where to write the encoded RMT symbols as well as the free space there. If the space is not enough for the callback to encode something, it can return 0 and the RMT will wait for previous symbols to be transmitted and call the callback again, now with more space available. If the callback successfully writes RMT symbols, it should return the number of symbols written.
A configuration structure :cpp:type:`rmt_simple_encoder_config_t` should be provided in advance before calling :cpp:func:`rmt_new_simple_encoder`:
- :cpp:member:`rmt_simple_encoder_config_t::callback` and :cpp:member:`rmt_simple_encoder_config_t::arg` provide the callback function and an opaque argument that will be passed to that function.
- :cpp:member:`rmt_simple_encoder_config_t::min_chunk_size` specifies the minimum amount of free space, in symbols, the encoder will be always be able to write some data to. In other words, when this amount of free space is passed to the encoder, it should never return 0 (except when the encoder is done encoding symbols).
While the functionality of an encoding process using the simple callback encoder can usually also realized by chaining other encoders, the simple callback can be more easy to understand and maintain than an encoder chain.
.. only:: SOC_BITSCRAMBLER_SUPPORTED and SOC_RMT_SUPPORT_DMA
.. _rmt-bitscrambler-encoder:
BitScrambler Encoder
~~~~~~~~~~~~~~~~~~~~
When the RMT transmit channel has DMA enabled, we can control the data on the DMA path by writing :doc:`BitScrambler </api-reference/peripherals/bitscrambler>` assembly code, which implements simple encoding operations. Compared to CPU-based encoding, the BitScrambler offers higher performance without consuming CPU resources. However, due to the limited instruction memory space of the BitScrambler, it cannot implement complex encoding operations. Additionally, the output data format from the BitScrambler program must conform to the :cpp:type:`rmt_symbol_word_t` structure.
A BitScrambler encoder can be created by calling :cpp:func:`rmt_new_bitscrambler_encoder`. This function takes a configuration parameter of type :cpp:type:`rmt_bs_encoder_config_t`, which includes the following configuration items:
- :cpp:member:`rmt_bs_encoder_config_t::program_bin` points to the binary file of the BitScrambler program. This binary file must comply with the BitScrambler assembly language specification and will be loaded into the BitScrambler's instruction memory at runtime. For information on how to write and compile BitScrambler programs, please refer to the :doc:`BitScrambler Programming Guide </api-reference/peripherals/bitscrambler>`.
.. note::
The BitScrambler encoder **must** be used with an RMT channel that has DMA enabled.
Customize RMT Encoder for NEC Protocol
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This section demonstrates how to write an NEC encoder. The NEC IR protocol uses pulse distance encoding of the message bits. Each pulse burst is ``562.5 µs`` in length, logical bits are transmitted as follows. It is worth mentioning that the least significant bit of each byte is sent first.
- Logical ``0``: a ``562.5 µs`` pulse burst followed by a ``562.5 µs`` space, with a total transmit time of ``1.125 ms``
- Logical ``1``: a ``562.5 µs`` pulse burst followed by a ``1.6875 ms`` space, with a total transmit time of ``2.25 ms``
When a key is pressed on the remote controller, the transmitted message includes the following elements in the specified order:
.. figure:: /../_static/ir_nec.png
:align: center
:alt: IR NEC Frame
IR NEC Frame
- ``9 ms`` leading pulse burst, also called the "AGC pulse"
- ``4.5 ms`` space
- 8-bit address for the receiving device
- 8-bit logical inverse of the address
- 8-bit command
- 8-bit logical inverse of the command
- a final ``562.5 µs`` pulse burst to signify the end of message transmission
Then you can construct the NEC :cpp:member:`rmt_encoder_t::encode` function in the same order, for example:
.. code:: c
// IR NEC scan code representation
typedef struct {
uint16_t address;
uint16_t command;
} ir_nec_scan_code_t;
// construct an encoder by combining primitive encoders
typedef struct {
rmt_encoder_t base; // the base "class" declares the standard encoder interface
rmt_encoder_t *copy_encoder; // use the copy_encoder to encode the leading and ending pulse
rmt_encoder_t *bytes_encoder; // use the bytes_encoder to encode the address and command data
rmt_symbol_word_t nec_leading_symbol; // NEC leading code with RMT representation
rmt_symbol_word_t nec_ending_symbol; // NEC ending code with RMT representation
int state; // record the current encoding state, i.e., we are in which encoding phase
} rmt_ir_nec_encoder_t;
static size_t rmt_encode_ir_nec(rmt_encoder_t *encoder, rmt_channel_handle_t channel, const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state)
{
rmt_ir_nec_encoder_t *nec_encoder = __containerof(encoder, rmt_ir_nec_encoder_t, base);
rmt_encode_state_t session_state = RMT_ENCODING_RESET;
rmt_encode_state_t state = RMT_ENCODING_RESET;
size_t encoded_symbols = 0;
ir_nec_scan_code_t *scan_code = (ir_nec_scan_code_t *)primary_data;
rmt_encoder_handle_t copy_encoder = nec_encoder->copy_encoder;
rmt_encoder_handle_t bytes_encoder = nec_encoder->bytes_encoder;
switch (nec_encoder->state) {
case 0: // send leading code
encoded_symbols += copy_encoder->encode(copy_encoder, channel, &nec_encoder->nec_leading_symbol,
sizeof(rmt_symbol_word_t), &session_state);
if (session_state & RMT_ENCODING_COMPLETE) {
nec_encoder->state = 1; // we can only switch to the next state when the current encoder finished
}
if (session_state & RMT_ENCODING_MEM_FULL) {
state |= RMT_ENCODING_MEM_FULL;
goto out; // yield if there is no free space to put other encoding artifacts
}
// fall-through
case 1: // send address
encoded_symbols += bytes_encoder->encode(bytes_encoder, channel, &scan_code->address, sizeof(uint16_t), &session_state);
if (session_state & RMT_ENCODING_COMPLETE) {
nec_encoder->state = 2; // we can only switch to the next state when the current encoder finished
}
if (session_state & RMT_ENCODING_MEM_FULL) {
state |= RMT_ENCODING_MEM_FULL;
goto out; // yield if there is no free space to put other encoding artifacts
}
// fall-through
case 2: // send command
encoded_symbols += bytes_encoder->encode(bytes_encoder, channel, &scan_code->command, sizeof(uint16_t), &session_state);
if (session_state & RMT_ENCODING_COMPLETE) {
nec_encoder->state = 3; // we can only switch to the next state when the current encoder finished
}
if (session_state & RMT_ENCODING_MEM_FULL) {
state |= RMT_ENCODING_MEM_FULL;
goto out; // yield if there is no free space to put other encoding artifacts
}
// fall-through
case 3: // send ending code
encoded_symbols += copy_encoder->encode(copy_encoder, channel, &nec_encoder->nec_ending_symbol,
sizeof(rmt_symbol_word_t), &session_state);
if (session_state & RMT_ENCODING_COMPLETE) {
nec_encoder->state = RMT_ENCODING_RESET; // back to the initial encoding session
state |= RMT_ENCODING_COMPLETE; // telling the caller the NEC encoding has finished
}
if (session_state & RMT_ENCODING_MEM_FULL) {
state |= RMT_ENCODING_MEM_FULL;
goto out; // yield if there is no free space to put other encoding artifacts
}
}
out:
*ret_state = state;
return encoded_symbols;
}
A full sample code can be found in :example:`peripherals/rmt/ir_nec_transceiver`. In the above snippet, we use a ``switch-case`` and several ``goto`` statements to implement a `Finite-state machine <https://en.wikipedia.org/wiki/Finite-state_machine>`__ . With this pattern, users can construct much more complex IR protocols.
.. _rmt-power-management:
Power Management
^^^^^^^^^^^^^^^^
When power management is enabled, i.e., :ref:`CONFIG_PM_ENABLE` is on, the system may adjust or disable the clock source before going to sleep. As a result, the time base inside the RMT can't work as expected.
The driver can prevent the above issue by creating a power management lock. The lock type is set based on different clock sources. The driver will acquire the lock in :cpp:func:`rmt_enable`, and release it in :cpp:func:`rmt_disable`. That means, any RMT transactions in between these two functions are guaranteed to work correctly and stable.
.. only:: SOC_RMT_SUPPORT_SLEEP_RETENTION
Besides the potential changes to the clock source, when the power management is enabled, the system can also power down the RMT hardware before sleep. Set :cpp:member:`rmt_tx_channel_config_t::allow_pd` and :cpp:member:`rmt_rx_channel_config_t::allow_pd` to ``true`` to enable the power down feature. RMT registers will be backed up before sleep and restored after wake up. Please note, enabling this option will increase the memory consumption.
.. _rmt-cache-safe:
Cache Safe
^^^^^^^^^^
By default, the RMT interrupt is deferred when the Cache is disabled for reasons like writing or erasing the main Flash. Thus the transaction-done interrupt does not get handled in time, which is not acceptable in a real-time application. What is worse, when the RMT transaction relies on **ping-pong** interrupt to successively encode or copy RMT symbols, a delayed interrupt can lead to an unpredictable result.
There is a Kconfig option :ref:`CONFIG_RMT_TX_ISR_CACHE_SAFE` and :ref:`CONFIG_RMT_RX_ISR_CACHE_SAFE` that has the following features:
1. Enable the interrupt being serviced even when the cache is disabled
2. Place all functions used by the ISR into IRAM [2]_
3. Place the driver object into DRAM in case it is mapped to PSRAM by accident
This Kconfig option allows the interrupt handler to run while the cache is disabled but comes at the cost of increased IRAM consumption.
Please note, when :ref:`CONFIG_RMT_TX_ISR_CACHE_SAFE` is enabled, you must also place the encoder functions (mainly the :cpp:member:`rmt_encoder_t::encode` and :cpp:member:`rmt_encoder_t::reset`) into IRAM. You can use :c:macro:`RMT_ENCODER_FUNC_ATTR` to decorate your encoder functions.
Another Kconfig option :ref:`CONFIG_RMT_RECV_FUNC_IN_IRAM` can place :cpp:func:`rmt_receive` into the IRAM as well. So that the receive function can be used even when the flash cache is disabled.
.. _rmt-thread-safety:
Thread Safety
^^^^^^^^^^^^^
The factory function :cpp:func:`rmt_new_tx_channel`, :cpp:func:`rmt_new_rx_channel` and :cpp:func:`rmt_new_sync_manager` are guaranteed to be thread-safe by the driver, which means, user can call them from different RTOS tasks without protection by extra locks.
Other functions that take the :cpp:type:`rmt_channel_handle_t` and :cpp:type:`rmt_sync_manager_handle_t` as the first positional parameter, are not thread-safe. which means the user should avoid calling them from multiple tasks.
The following functions are allowed to use under ISR context as well.
- :cpp:func:`rmt_receive`
.. _rmt-kconfig-options:
Kconfig Options
^^^^^^^^^^^^^^^
- :ref:`CONFIG_RMT_TX_ISR_CACHE_SAFE` and :ref:`CONFIG_RMT_RX_ISR_CACHE_SAFE` control whether the default ISR handler can work when cache is disabled, see also :ref:`rmt-cache-safe` for more information.
- :ref:`CONFIG_RMT_ENABLE_DEBUG_LOG` is used to enable the debug log at the cost of increased firmware binary size.
- :ref:`CONFIG_RMT_RECV_FUNC_IN_IRAM` controls where to place the RMT receive function (IRAM or Flash), see :ref:`rmt-cache-safe` for more information.
Application Examples
--------------------
.. list::
- :example:`peripherals/rmt/led_strip` demonstrates how to use the RMT peripheral to drive a WS2812 LED strip, which is able to change the number of LEDs and the chasing effect.
- :example:`peripherals/rmt/led_strip_simple_encoder` demonstrates how to use the RMT peripheral to drive a WS2812 LED strip by implementing a callback that converts RGB pixels into a format recognized by the hardware.
- :example:`peripherals/rmt/ir_nec_transceiver` demonstrates how to use the RMT peripheral to implement the encoding and decoding of remote IR NEC protocol.
- :example:`peripherals/rmt/dshot_esc` demonstrates how to use the RMT TX channel to achieve infinite loop transmission. It constructs an RMT encoder for the DShot digital protocol. This protocol is primarily used for communication between flight controllers and electronic speed controllers, offering greater resistance to electrical noise compared to traditional analog protocols.
- :example:`peripherals/rmt/onewire` demonstrates how to simulate the 1-wire hardware protocol by using the `onewire_bus` library, and read data from multiple DS18B20 temperature sensors on the bus. This library is built upon a pair of transmit and receive channels of the RMT peripheral.
:SOC_RMT_SUPPORT_TX_LOOP_COUNT: - :example:`peripherals/rmt/musical_buzzer` demonstrates how to use the RMT TX channel to drive a passive buzzer to play simple music. Each musical note is represented by a constant frequency of PWM signal with a fixed duration.
:SOC_RMT_SUPPORT_TX_LOOP_AUTO_STOP: - :example:`peripherals/rmt/stepper_motor` demonstrates how to use the RMT peripheral to drive a STEP/DIR interfaced stepper motor controller (such as DRV8825). After programming the RMT encoder, S-curve profile can be implemented for desired acceleration, constant speed, and deceleration phases, thereby smoothly driving the stepper motor.
FAQ
---
* Why the RMT transmits more data than expected?
The encoding for the RMT transmission is carried out within the ISR context. Should the RMT encoding process be prolonged (for example, through logging or tracing the procedure) or if it is delayed due to interrupt latency and preemptive interrupts, the hardware transmitter might read from the memory before the encoder has written to it. Consequently, the transmitter would end up sending outdated data. Although it's not possible to instruct the transmitter to pause and wait, this issue can be mitigated by employing a combination of the following strategies:
- Increase the :cpp:member:`rmt_tx_channel_config_t::mem_block_symbols`, in steps of {IDF_TARGET_SOC_RMT_MEM_WORDS_PER_CHANNEL}.
- Place the encoding function in the IRAM with ``IRAM_ATTR`` attribute.
- Enable the :cpp:member:`rmt_tx_channel_config_t::with_dma` if DMA is available.
- Install the RMT driver on a separate CPU core to avoid competing for the same CPU resources with other interrupt heavy peripherals (e.g. WiFi, Bluetooth).
API Reference
-------------
.. include-build-file:: inc/rmt_tx.inc
.. include-build-file:: inc/rmt_rx.inc
.. include-build-file:: inc/rmt_common.inc
.. include-build-file:: inc/rmt_encoder.inc
.. include-build-file:: inc/components/esp_driver_rmt/include/driver/rmt_types.inc
.. include-build-file:: inc/components/esp_hal_rmt/include/hal/rmt_types.inc
.. [1]
Different ESP chip series might have different numbers of RMT channels. Please refer to [`TRM <{IDF_TARGET_TRM_EN_URL}#rmt>`__] for details. The driver does not forbid you from applying for more RMT channels, but it returns an error when there are no hardware resources available. Please always check the return value when doing :ref:`rmt-resource-allocation`.
.. [2]
The callback function, e.g., :cpp:member:`rmt_tx_event_callbacks_t::on_trans_done`, and the functions invoked by itself should also reside in IRAM, users need to take care of this by themselves.
@@ -0,0 +1,377 @@
SD Pull-up Requirements
=======================
:link_to_translation:`zh_CN:[中文]`
Espressif hardware products are designed for multiple use cases which may require different pull states on pins. For this reason, the pull state of particular pins on certain products needs to be adjusted to provide the pull-ups required in the SD bus.
SD pull-up requirements apply to cases where {IDF_TARGET_NAME} uses the SPI or SDMMC controller to communicate with SD cards. When an SD card is operating in SPI mode or 1-bit SD mode, the CMD and DATA (DAT0 - DAT3) lines of the SD bus must be pulled up by 10 kΩ resistors. SD cards and SDIO devices should also have pull-ups on all above-mentioned lines (regardless of whether these lines are connected to the host) in order to prevent them from entering a wrong state.
.. only:: esp32
By default, the MTDI bootstrapping pin is incompatible with the DAT2 line pull-up if the flash voltage is 3.3 V. For more information, see :ref:`mtdi_strapping_pin` below.
.. todo::
Add a diagram of the bus lines and pullups
This document has the following structure:
- :ref:`compatibility_overview_espressif_hw_sdio` between the default pull states on pins of Espressif's products and the states required by the SD bus
- :ref:`sdio_solutions` - ideas on how to resolve compatibility issues
- :ref:`related_info_sdio` - other relevant information
.. _compatibility_overview_espressif_hw_sdio:
Overview of Compatibility
-------------------------
This section provides an overview of compatibility issues that might occur when using SDIO (secure digital input output). Since the SD bus needs to be connected to pull-ups, these issues should be resolved regardless of whether they are related to master (host) or slave (device). Each issue has links to its respective solution. A solution for a host and device may differ.
.. only:: esp32
Systems on a Chip (SoCs)
^^^^^^^^^^^^^^^^^^^^^^^^
- ESP32 (except for D2WD versions, see `ESP32 datasheet <https://www.espressif.com/sites/default/files/documentation/esp32_datasheet_en.pdf>`_):
- :ref:`sd_pull-up_no_pull-ups`
- :ref:`strapping_conflicts_dat2` for models with 3.3 V flash chip
- ESP32-D2WD:
- :ref:`sd_pull-up_no_pull-ups`
- :ref:`no_pull-up_on_gpio12`
.. only:: SOC_SDMMC_USE_GPIO_MATRIX
Systems on a Chip (SoCs)
^^^^^^^^^^^^^^^^^^^^^^^^
{IDF_TARGET_NAME} SDMMC host controller allows using any of GPIOs for any of SD interface signals. However, it is recommended to avoid using strapping GPIOs, GPIOs with internal weak pull-downs and GPIOs commonly used for other purposes to prevent conflicts.
.. only:: esp32s3
- GPIO0 (strapping pin)
- GPIO45, GPIO46 (strapping pins, internal weak pulldown)
- GPIO26 - GPIO32 (commonly used for SPI flash and PSRAM)
- GPIO33 - GPIO37 (when using chips and modules with octal SPI flash or octal PSRAM)
- GPIO43, GPIO44 (GPIOs used for UART0 by default)
- GPIO19, GPIO20 (GPIOs used for USB by default)
.. only:: esp32
Systems in Packages (SIP)
^^^^^^^^^^^^^^^^^^^^^^^^^
- ESP32-PICO-D4:
- :ref:`sd_pull-up_no_pull-ups`
- :ref:`strapping_conflicts_dat2`
Modules
^^^^^^^
- ESP32-WROOM-32 Series, including ESP32-WROOM-32, ESP32-WROOM-32D, ESP32-WROOM-32U, and ESP32-SOLO-1
- :ref:`sd_pull-up_no_pull-ups`
- :ref:`strapping_conflicts_dat2`
- ESP32-WROVER Series, including ESP32-WROVER and ESP32-WROVER-I
- :ref:`sd_pull-up_no_pull-ups`
- ESP32-WROVER-B Series, including ESP32-WROVER-B and ESP32-WROVER-IB
- :ref:`sd_pull-up_no_pull-ups`
- :ref:`strapping_conflicts_dat2`
- ESP32-WROVER-E Series, including ESP32-WROVER-E and ESP32-WROVER-IE
- :ref:`sd_pull-up_no_pull-ups`
- :ref:`strapping_conflicts_dat2`
.. only:: esp32
.. _sdio_dev_kits:
Development Boards
^^^^^^^^^^^^^^^^^^
- ESP32-PICO-KIT, including PICO-KIT v4.1, v4.0, and v3
- :ref:`sd_pull-up_no_pull-ups`
- :ref:`strapping_conflicts_dat2`
- :ref:`gpio2_strapping_pin`
- ESP32-DevKitC, including ESP32-DevKitC v4 and v2
- :ref:`sd_pull-up_no_pull-ups`
- :ref:`strapping_conflicts_dat2`
- :ref:`gpio2_strapping_pin`
- ESP-WROVER-KIT
- Required pull-ups are provided
- :ref:`pull-up_conflicts_on_gpio13` (v4.1, v3, v2, and v1)
- :ref:`strapping_conflicts_dat2` (v4.1, v2, and v1)
- :ref:`gpio2_strapping_pin` (v2, v1)
You can determine the version of your ESP23-WROVER-KIT by checking which module is mounted on it:
- ESP32-WROVER-B on v4.1
- ESP32-WROVER on v3
- ESP32-WROOM-32 on v1 and v2
- ESP32-LyraTD-MSC
- Required pull-ups are provided
- :ref:`strapping_conflicts_dat2`
- ESP32-LyraT
- Required pull-ups are provided
- :ref:`pull-up_conflicts_on_gpio13`
.. only:: esp32s3
.. _sdio_dev_kits:
Development Boards
^^^^^^^^^^^^^^^^^^
- ESP32-S3-DevKitC-1
- :ref:`sd_pull-up_no_pull-ups`
- ESP32-S3-USB-OTG
- The board may be used in 1-line and 4-line SD mode or SPI mode.
- Required pull-ups are provided on GPIOs 33-38.
- ESP32-S3-EYE
- The board is limited to 1-line SD mode.
- Required pull-ups are provided on GPIOs 38-40.
.. only:: SOC_SDIO_SLAVE_SUPPORTED
Non-Espressif Hosts
^^^^^^^^^^^^^^^^^^^
Please make sure that your SDIO host provides necessary pull-ups for all SD bus signals.
.. _sdio_solutions:
Solutions
---------
.. _sd_pull-up_no_pull-ups:
No Pull-ups
^^^^^^^^^^^
.. only:: esp32 or esp32s3
When using a development board without pull-ups:
- If your host and slave device are on separate boards, replace one of them with a board that has pull-ups. For the list of Espressif's development boards with pull-ups, go to :ref:`sdio_dev_kits`.
- Attach external pull-ups by connecting each pin which requires a pull-up to VDD via a 10 kΩ resistor.
.. only:: not esp32 and not esp32s3
When using a development board without pull-ups:
- If your host and slave device are on separate boards, replace one of them with a board that has pull-ups.
- Attach external pull-ups by connecting each pin which requires a pull-up to VDD via a 10 kΩ resistor.
.. only:: esp32
.. _pull-up_conflicts_on_gpio13:
Pull-up Conflicts on GPIO13
^^^^^^^^^^^^^^^^^^^^^^^^^^^
If DAT3 of your device is not properly pulled up, you have the following options:
- Use 1-bit SD mode and tie the device's DAT3 to VDD
- Use SPI mode
- Perform one of the following actions on the GPIO13 pin:
- Remove the pull-down resistors
- Attach a pull-up resistor of less than 5 kΩ (2 kΩ suggested)
- Pull it up or drive it high either by using the host or with 3.3 V on VDD in 1-bit SD mode
.. _strapping_conflicts_dat2:
Conflicts Between Bootstrap and SDIO on DAT2
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
There is a conflict between the boot strapping requirements of the ESP32 and the SDIO protocol. For details, see :ref:`mtdi_strapping_pin`.
To resolve the conflict, you have the following options:
1. (Recommended) Burn the flash voltage selection eFuses. This permanently configures the internal regulator's output voltage to 3.3 V, and GPIO12 will not be used as a bootstrapping pin. After that, connect a pull-up resistor to GPIO12.
.. warning::
Burning eFuses is irreversible! The issue list above might be out of date, so please make sure that the module you are burning has a 3.3 V flash chip by checking the information on https://www.espressif.com/. If you burn the 3.3 V eFuses on a module with a 1.8 V flash chip, the module will stop functioning.
If you are sure that you need to irreversibly burn eFuses, go to your ESP-IDF directory and run the following command using ``espefuse`` tool:
.. code-block:: bash
espefuse set-flash-voltage 3.3V
This command burns the ``XPD_SDIO_TIEH``, ``XPD_SDIO_FORCE``, and ``XPD_SDIO_REG`` eFuses. After all the three eFuses are burned to value 1, the internal VDD_SDIO flash voltage regulator is permanently set to 3.3 V. You will see the following log if the burning succeeds:
.. code-block:: bash
espefuse v5.0.2
Connecting....
=== Run "set-flash-voltage" command ===
Enable internal flash voltage regulator (VDD_SDIO) to 3.3V.
The following eFuses are burned: XPD_SDIO_FORCE, XPD_SDIO_REG, XPD_SDIO_TIEH.
VDD_SDIO setting complete.
Check all blocks for burn...
idx, BLOCK_NAME, Conclusion
[00] BLOCK0 is not empty
(written ): 0x0000000400182226000004320000a8b0002bc8f09e47e69800000000
(to write): 0x00000000000000000001c00000000000000000000000000000000000
(coding scheme = NONE)
.
This is an irreversible operation!
Type 'BURN' (all capitals) to continue.
BURN
BURN BLOCK0 - OK (write block == read block)
Reading updated eFuses...
Successful.
To check the status of the eFuses, run:
.. code-block:: none
idf.py efuse-summary
If running from an automated flashing script, it is better to use standalone eFuse tool ``espefuse``. This tool also has an option ``--do-not-confirm`` to burn eFuses without confirmation.
For more details, see **{IDF_TARGET_NAME} Technical Reference Manual** [`PDF <{IDF_TARGET_TRM_EN_URL}#efuse>`__].
2. **If using 1-bit SD mode or SPI mode**, disconnect the DAT2 pin and make sure it is pulled high. For this, you have the following options:
- Leave the host's DAT2 floating and directly connect the slave's DAT2 to VDD.
- For a slave device, build a firmware with the option ``SDIO_SLAVE_FLAG_DAT2_DISABLED`` and re-flash your device. This option helps avoid slave detecting on the DAT2 line. Note that 4-bit SD mode is no longer supported by the standard Card Common Control Register (CCCR); however, the host is not aware of that. The use of 4-bit SD mode has to be disabled on the host's side.
.. _no_pull-up_on_gpio12:
No Pull-up on GPIO12
^^^^^^^^^^^^^^^^^^^^
Your module is compatible with the SDIO protocol. Just connect GPIO12 to VDD via a 10 kΩ resistor.
.. _gpio2_strapping_pin:
Download Mode Not Working (minor issue)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
When the GPIO2 pin is pulled high in accordance with the SD pull-up requirements, you can not enter download mode because GPIO2 is a bootstrapping pin which in this case must be pulled low.
There are following solutions:
- For boards that require shorting the GPIO0 and GPIO2 pins with a jumper, put the jumper in place, and the auto-reset circuit pulls GPIO2 low along with GPIO0 before entering download mode.
- For boards with components attached to their GPIO2 pin (such as pull-down resistors and/or LEDs), check the schematic of your development board for anything connected to GPIO2.
- **LEDs** would not affect operation in most cases.
- **Pull-down resistors** can interfere with DAT0 signals and must be removed.
If above solutions do not work for you, please determine if it is the host or slave device that has pull-ups affecting their GPIO2, then locate these pull-ups and remove them.
.. only:: esp32
.. _related_info_sdio:
Related Information
-------------------
.. _mtdi_strapping_pin:
MTDI Strapping Pin
^^^^^^^^^^^^^^^^^^
MTDI (GPIO12) is used as a bootstrapping pin to select the output voltage of an internal regulator (VDD_SDIO) which powers the flash chip. This pin has an internal pull-down, so, if left unconnected, it will read low level at startup, which leads to selecting the default 3.3 V operation.
For ESP32 modules that use 1.8 V flash (such as ESP32-WROVER and ESP32-WROVER-I), they have pull-up resistors on GPIO12. For ESP32 modules that use 3.3 V flash, they have no pull-up resistors on GPIO12, and this pin is slightly pulled down internally.
When adding a pull-up to GPIO12 for SD card operation, consider the following:
- For boards that do not use the internal regulator (VDD_SDIO) to power flash, GPIO12 can be pulled high.
- For boards using 1.8 V flash chips, GPIO12 needs to be pulled high at reset. This is fully compatible with the SD card operation.
- On boards using the internal regulator and a 3.3 V flash chip, GPIO12 must be pulled low at reset. This is incompatible with the SD card operation. For reference information on compatibility of Espressif's boards with the SD card operation, see :ref:`compatibility_overview_espressif_hw_sdio`.
Internal Pull-ups and Strapping Requirements
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Using external resistors is always preferable. However, Espressif's products have internal weak pull-up and pull-down resistors which can be enabled and used instead of external ones. Please keep in mind that this solution can not guarantee reliable SDIO communication.
With that said, the information about these internal pull-ups and strapping requirements can still be useful. Espressif hardware products have different weak internal pull-ups and pull-downs connected to CMD and DATA pins. The table below shows the default pull-up and pull-down states of the CMD and DATA pins.
The following abbreviations are used in the table:
- **WPU**: Weak pull-up inside the SoC
- **WPD**: Weak pull-down inside the SoC
- **PU**: Pull-up inside Espressif modules but outside the SoC
.. list-table:: Default pull-up and pull-down states of the CMD and DATA pins
:widths: 25 25 25 25
:header-rows: 1
* - GPIO number
- Pin Name
- Startup State
- Strapping Requirement
* - **15**
- CMD
- WPU
-
* - **2**
- DAT0
- WPD
- Low for Download mode
* - **4**
- DAT1
- WPD
-
* - **12**
- DAT2
- PU for 1.8 V flash; WPD for 3.3 V flash
- High for 1.8 V flash; Low for 3.3 V flash
* - **13**
- DAT3
- WPU
-
.. only:: not esp32
.. _related_info_sdio:
Related Information
-------------------
Internal Pull-ups and Strapping Requirements
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Using external resistors is always preferable. However, Espressif's products have internal weak pull-up and pull-down resistors which can be enabled and used instead of external ones. Please keep in mind that this solution can not guarantee reliable SDIO communication.
Generally it's not recommended to reuse strapping pins for SDIO purposes. The pullup and pulldown requirements of SD and strapping may conflict with each other. See datasheet for the strapping pins of {IDF_TARGET}.
@@ -0,0 +1,299 @@
SDIO Card Slave Driver
======================
:link_to_translation:`zh_CN:[中文]`
Overview
--------
.. only:: esp32
The ESP32 SDIO Card host and slave peripherals share two sets of pins, as shown in the table below. The first set is usually occupied by the SPI0 bus, which is responsible for the SPI flash holding the code to run. This means the SDIO slave driver can only run on the second set of pins while the SDIO host is not using it.
The SDIO slave can run under three modes: SPI, 1-bit SD, and 4-bit SD modes. Based on the signals on the interface, the device can determine the current mode and configure itself to adapt to that mode. Later, the slave driver can communicate with the slave device to properly handle commands and data transfers. According to the SDIO specification, the CMD and DAT0-3 signal lines should be pulled up whether in 1-bit SD, 4-bit SD or SPI mode.
Connections
^^^^^^^^^^^
.. only:: esp32
.. list-table::
:header-rows: 1
:widths: 25 25 25 25
:align: center
* - Pin Name
- Corresponding Pins in SPI Mode
- GPIO Number (Slot 1)
- GPIO Number (Slot 2)
* - CLK
- SCLK
- 6
- 14
* - CMD
- MOSI
- 11
- 15
* - DAT0
- MISO
- 7
- 2
* - DAT1
- Interrupt
- 8
- 4
* - DAT2
- N.C. (pullup)
- 9
- 12
* - DAT3
- #CS
- 10
- 13
.. only:: esp32c6
.. list-table::
:header-rows: 1
:widths: 30 40 30
:align: center
* - Pin Name
- Corresponding Pins in SPI Mode
- GPIO Number
* - CLK
- SCLK
- 19
* - CMD
- MOSI
- 18
* - DAT0
- MISO
- 20
* - DAT1
- Interrupt
- 21
* - DAT2
- N.C. (pullup)
- 22
* - DAT3
- #CS
- 23
- 1-bit SD mode: Connect CLK, CMD, DAT0, DAT1 pins, and the ground.
- 4-bit SD mode: Connect all pins, and the ground.
- SPI mode: Connect SCLK, MOSI, MISO, Interrupt, #CS pins, and the ground.
.. note::
Please check if CMD and DATA lines DAT0-DAT3 of the card are properly pulled up by 10 KOhm - 90 KOhm resistors, which should be ensured even in 1-bit mode or SPI mode. Most official modules do not offer these pullups internally. If you are using official development boards, check :ref:`compatibility_overview_espressif_hw_sdio` to see whether your development boards have such pullups.
.. only:: esp32
.. note::
Most official modules have conflicts on strapping pins with the SDIO slave function. If you are using an ESP32 module with 3.3 V flash inside, when you are developing on the module for the first time, you will need to perform an eFuse burn-in prior to development. This will adjust the pin configuration of the module to make the module compatible with SDIO functionality. See :ref:`compatibility_overview_espressif_hw_sdio` for details on how to configure this.
Here is a list of modules/kits with 3.3 V flash:
- Modules: All modules except ESP32-WROVER, ESP32-WROVER-I, ESP32-S3-WROOM-2, and please check `Modules Overview <https://www.espressif.com/en/products/modules>`__ for module list
- Kits: ESP32-PICO-KIT, ESP32-DevKitC (up to v4), ESP32-WROVER-KIT (v4.1 [also known as ESP32-WROVER-KIT-VB], v2, v1 [also known as DevKitJ v1])
You can tell the version of your ESP23-WROVER-KIT version from the module on it: v4.1 are with ESP32-WROVER-B modules, v3 are with ESP32-WROVER modules, while v2 and v1 are with ESP32-WROOM-32 modules.
Refer to :doc:`sd_pullup_requirements` for more technical details of the pullups.
.. toctree::
:hidden:
sd_pullup_requirements
The host initializes the slave into SD mode by sending the CMD0 command with the DAT3 pin set to a high level. Alternatively, the host initializes the SPI mode by sending CMD0 with CS pin low, which is the same pin as DAT3.
After the initialization, the host can enable the 4-bit SD mode by writing CCCR register 0x07 by CMD52. All the bus detection processes are handled by the slave peripheral.
The host has to communicate with the slave by an ESP-slave-specific protocol.
The slave driver offers three services over Function 1 access by CMD52 and CMD53:
(1) sending and receiving FIFO
(2) 52 R/W registers (8-bit) shared by host and slave
(3) 16 interrupt sources (8 from host to slave, and 8 from slave to host)
Terminology
^^^^^^^^^^^
The SDIO slave driver uses the following terms:
- A transfer is initiated by a command token from the host and may consist of a response and multiple data blocks. The core mechanism of the {IDF_TARGET_NAME} SDIO slave driver involves data exchange and communication through transfers.
- Sending: slave to host transfers.
- Receiving: host to slave transfers.
.. note::
The register names in **{IDF_TARGET_NAME} Technical Reference Manual** > **SDIO Slave Controller** [`PDF <{IDF_TARGET_TRM_EN_URL}#sdioslave>`__] are organized from the host's perspective. For instance, ``RX`` registers indicate sending, while ``TX`` registers denote receiving. In our driver implementation, we've chosen not to utilize the terms ``TX`` or ``RX`` to prevent any potential ambiguities.
- FIFO: A designated address within Function 1 that can be accessed using CMD53 commands for reading or writing substantial volumes of data. The address corresponds to the length intended for reading from or writing to the slave in a single transfer: **requested length** = 0x1F800 address.
- Ownership: When the driver assumes ownership of a buffer, it means that the driver has the capability to perform random read/write operations on the buffer (often via DMA). The application should not read/write the buffer until the ownership is returned to the application. If the application reads from a buffer owned by a receiving driver, the data read can be random; similarly, if the application writes to a buffer owned by a sending driver, the data sent may be corrupted.
- Requested length: The length requested in one transfer determined by the FIFO address.
- Transfer length: The length requested in one transfer determined by the CMD53 byte/block count field.
.. note::
Requested length is different from the transfer length. In the context of {IDF_TARGET_NAME} SDIO slave DMA, the operation is based on the **requested length** rather than the **transfer length**. This means the DMA controller will process the data transfer according to the **requested length**, ensuring that only data within the **requested length** is transferred. The **transfer length** should be no shorter than the **requested length**, and the rest part is filled with 0 during sending or discard during receiving.
- Receiving buffer size: The buffer size is pre-defined between the host and the slave before communication starts. The slave application has to set the buffer size during initialization by the ``recv_buffer_size`` parameter in the ``sdio_slave_config_t`` structure.
- Interrupts: The {IDF_TARGET_NAME} SDIO slave supports interrupts in two directions: from host to slave (referred to as slave interrupts) and from slave to host (referred to as host interrupts). For more details, refer to :ref:`interrupts`.
- Registers: Specific addresses in Function 1 accessed by CMD52 or CMD53.
Communication with ESP SDIO Slave
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The host should initialize the {IDF_TARGET_NAME} SDIO slave according to the standard SDIO initialization process (Sector 3.1.2 of `SDIO Simplified Specification <https://www.sdcard.org/downloads/pls/pdf/?p=PartE1_SDIO_Simplified_Specification_Ver3.00.jpg&f=PartE1_SDIO_Simplified_Specification_Ver3.00.pdf&e=EN_SSE1>`_), which is described briefly in `ESP SDIO Slave Initialization <https://espressif.github.io/idf-extra-components/latest/esp_serial_slave_link/sdio_slave_protocol.html#esp-sdio-slave-initialization>`_.
Furthermore, there is an {IDF_TARGET_NAME}-specific upper-level communication protocol built upon the foundation of CMD52/CMD53 to Function 1. Within this particular communication protocol, the master and slave engage in data exchange and communication through the utilization of CMD52/CMD53 commands. For more detailed information, please consult the `ESP SDIO Slave Protocol <https://espressif.github.io/idf-extra-components/latest/esp_serial_slave_link/sdio_slave_protocol.html#esp-sdio-slave-protocol>`_ section.
There is also a component `ESSL <https://components.espressif.com/components/espressif/esp_serial_slave_link>`_ designed for {IDF_TARGET_NAME} master to communicate with {IDF_TARGET_NAME} SDIO slave. See example :example:`peripherals/sdio` when programming your host.
.. _interrupts:
Interrupts
^^^^^^^^^^
There are interrupts from host to slave, and from slave to host to help communicating conveniently.
Slave Interrupts
""""""""""""""""
The host can trigger an interruption in the slave by writing a single bit to the 0x08D register. As soon as any bit within the register is set, an interrupt is generated, prompting the SDIO slave driver to invoke the callback function specified in the ``slave_intr_cb`` member of the ``sdio_slave_config_t`` structure.
.. note::
The callback function is called in the ISR. Do not use any delay, loop or blocking function in the callback, e.g., mutex.
Similar to the previous information, there's an alternative set of functions available. You can call ``sdio_slave_wait_int`` to wait for an interrupt within a certain time, or call ``sdio_slave_clear_int`` to clear interrupts from host. The callback function can work with the wait functions perfectly.
Host Interrupts
"""""""""""""""
The slave can interrupt the host by an interrupt line at certain time, which is level-sensitive, i.e., the interrupt signal can be triggered by detecting the level change of the interrupt line. When the host see the interrupt line pulled down, it may read the slave interrupt status register, to see the interrupt source. Host can clear interrupt bits, or choose to disable a interrupt source. The interrupt line holds active until all the sources are cleared or disabled.
There are several dedicated interrupt sources as well as general-purpose sources. see ``sdio_slave_hostint_t`` for more information.
Shared Registers
^^^^^^^^^^^^^^^^
There are 52 R/W shared registers (8-bit) to share information between host and slave. The slave can write or read the registers at any time by ``sdio_slave_read_reg`` and ``sdio_slave_write_reg``. The host can access (R/W) the register by CMD52 or CMD53.
Receiving FIFO
^^^^^^^^^^^^^^
When the host is going to send the slave some packets, it has to check whether the slave is ready to receive by reading the buffer number of slave.
To allow the host sending data to the slave, the application has to load buffers to the slave driver by the following steps:
1. Register the buffer by calling ``sdio_slave_recv_register_buf``, and get the handle of the registered buffer. The driver allocates memory for the linked-list descriptor needed to link the buffer onto the hardware. The size of these buffers should equal to the Receiving buffer size.
2. Load buffers onto the driver by passing the buffer handle to ``sdio_slave_recv_load_buf``.
3. Get the received data by calling ``sdio_slave_recv`` or ``sdio_slave_recv_packet``. If a non-blocking call is needed, set ``wait=0``.
The difference between two APIs is that, ``sdio_slave_recv_packet`` gives more information about packet, which can consist of several buffers.
When ``ESP_ERR_NOT_FINISHED`` is returned by this API, you should call this API iteratively until the return value is ``ESP_OK``. All the continuous buffers returned with ``ESP_ERR_NOT_FINISHED``, together with the last buffer returned with ``ESP_OK``, belong to one packet from the host.
Call ``sdio_slave_recv_get_buf`` to get the address of the received data, and the actual length received in each buffer. The packet length is the sum of received length of all the buffers in the packet.
If the host never send data longer than the Receiving buffer size, or you do not care about the packet boundary (e.g., the data is only a byte stream), you can call the simpler version ``sdio_slave_recv`` instead.
4. Pass the handle of processed buffer back to the driver by ``sdio_recv_load_buf`` again.
.. note::
To minimize data copying overhead, the driver itself does not maintain any internal buffer; it is the responsibility of the application to promptly provide new buffers. The DMA system automatically stores received data into these buffers.
Sending FIFO
^^^^^^^^^^^^
Each time the slave has data to send, it raises an interrupt, and the host requests the packet length. There are two sending modes:
- Stream Mode: When a buffer is loaded into the driver, the buffer length is included into the packet length requested by host in the incoming communications. This is irrespective of whether previous packets have been sent or not. In other words, the length of the newly loaded buffer is included into the length of the packet requested by the host, even if there are previously unsent packets. This enables the host to receive data from several buffers in a single transfer.
- Packet Mode: The packet length is updated packet by packet, and only when previous packet is sent. This means that the host can only get data of one buffer in one transfer.
.. note::
To avoid overhead from copying data, the driver itself does not have any buffer inside. Namely, the DMA takes data directly from the buffer provided by the application. The application should not touch the buffer until the sending is finished, so as to ensure that the data is transferred correctly.
The sending mode can be set in the ``sending_mode`` member of ``sdio_slave_config_t``, and the buffer numbers can be set in the ``send_queue_size``. Each buffer is restricted by the maximum size supported by a single SDIO slave DMA descriptor, which is chip-dependent. Though in the stream mode, several buffers can be sent in one transfer, each buffer is still counted as one in the queue.
The application can call ``sdio_slave_transmit`` to send packets. In this case, the function returns when the transfer is successfully done, so the queue is not fully used. When higher efficiency is required, the application can use the following functions instead:
1. Pass buffer information (address, length, as well as an ``arg`` indicating the buffer) to ``sdio_slave_send_queue``.
- If non-blocking call is needed, set ``wait=0``.
- If the ``wait`` is not ``portMAX_DELAY`` (wait until success), application has to check the result to know whether the data is put in to the queue or discard.
2. Call ``sdio_slave_send_get_finished`` to get and deal with a finished transfer. A buffer should be kept unmodified until returned from ``sdio_slave_send_get_finished``. This means the buffer is actually sent to the host, rather than just staying in the queue.
There are several ways to use the ``arg`` in the queue parameter:
1. Directly point ``arg`` to a dynamic-allocated buffer, and use the ``arg`` to free it when transfer finished.
2. Wrap transfer information in a transfer structure, and point ``arg`` to the structure. You can use the structure to do more things like::
typedef struct {
uint8_t* buffer;
size_t size;
int id;
}sdio_transfer_t;
//and send as:
sdio_transfer_t trans = {
.buffer = ADDRESS_TO_SEND,
.size = 8,
.id = 3, //the 3rd transfer so far
};
sdio_slave_send_queue(trans.buffer, trans.size, &trans, portMAX_DELAY);
//... maybe more transfers are sent here
//and deal with finished transfer as:
sdio_transfer_t* arg = NULL;
sdio_slave_send_get_finished((void**)&arg, portMAX_DELAY);
ESP_LOGI("tag", "(%d) successfully send %d bytes of %p", arg->id, arg->size, arg->buffer);
some_post_callback(arg); //do more things
3. Work with the receiving part of this driver, and point ``arg`` to the receive buffer handle of this buffer, so that we can directly use the buffer to receive data when it is sent::
uint8_t buffer[256]={1,2,3,4,5,6,7,8};
sdio_slave_buf_handle_t handle = sdio_slave_recv_register_buf(buffer);
sdio_slave_send_queue(buffer, 8, handle, portMAX_DELAY);
//... maybe more transfers are sent here
//and load finished buffer to receive as
sdio_slave_buf_handle_t handle = NULL;
sdio_slave_send_get_finished((void**)&handle, portMAX_DELAY);
sdio_slave_recv_load_buf(handle);
For more about this, see :example:`peripherals/sdio`.
Reset SDIO
^^^^^^^^^^^^
Calling ``sdio_slave_reset`` can reset PKT_LEN (Packet length accumulator value) and TOKEN1 (Receiving buffers accumulated number) at the SDIO slave driver software level to resynchronize the transmit and receive counts with the host.
If there is a usage scenario where the ESP chip remains powered on but the HOST is powered off. During the power-off period of the HOST, some unknown signals may be generated on the SDIO signal line, causing the SDIO hardware state machine to be abnormal. The HOST restarts and executes the card identification process, and the ESP will not respond normally. In this case, consider calling ``sdio_slave_reset_hw`` to reset the SDIO hardware.
.. note::
Reset the SDIO hardware. The interrupt enable status and shared register values will be lost. You may need to call ``sdio_slave_set_host_intena`` and ``sdio_slave_write_reg`` to set them.
Application Example
-------------------
- :example:`peripherals/sdio/host` and :example:`peripherals/sdio/slave` demonstrate how to use a host to communicate with an ESP SDIO slave device.
API Reference
-------------
.. include-build-file:: inc/sdio_slave_types.inc
.. include-build-file:: inc/sdio_slave.inc
+157
View File
@@ -0,0 +1,157 @@
Sigma-Delta Modulation (SDM)
============================
:link_to_translation:`zh_CN:[中文]`
Introduction
------------
{IDF_TARGET_NAME} has a second-order sigma-delta modulator, which can generate independent PDM pulses to multiple channels. Please refer to the TRM to check how many hardware channels are available. [1]_
Delta-sigma modulation converts an analog voltage signal into a pulse frequency, or pulse density, which can be understood as pulse-density modulation (PDM) (refer to |wiki_ref|_).
Typically, a Sigma-Delta modulated channel can be used in scenarios like:
- LED dimming
- Simple DAC (8-bit), with the help of an active RC low-pass filter
- Class D amplifier, with the help of a half-bridge or full-bridge circuit plus an LC low-pass filter
Functional Overview
-------------------
The following sections of this document cover the typical steps to install and operate an SDM channel:
- :ref:`sdm-resource-allocation` - covers how to initialize and configure an SDM channel and how to recycle the resources when it finishes working.
- :ref:`sdm-enable-and-disable-channel` - covers how to enable and disable the channel.
- :ref:`sdm-set-equivalent-duty-cycle` - describes how to set the equivalent duty cycle of the PDM pulses.
- :ref:`sdm-power-management` - describes how different source clock selections can affect power consumption.
- :ref:`sdm-iram-safe` - lists which functions are supposed to work even when the cache is disabled.
- :ref:`sdm-thread-safety` - lists which APIs are guaranteed to be thread-safe by the driver.
- :ref:`sdm-kconfig-options` - lists the supported Kconfig options that can be used to make a different effect on driver behavior.
.. _sdm-resource-allocation:
Resource Allocation
^^^^^^^^^^^^^^^^^^^
In ESP-IDF, the information and attributes of SDM channels are managed and accessed through specific data structures, where the data structure is called :cpp:type:`sdm_channel_handle_t`. Each channel is capable to output the binary, hardware-generated signal with the sigma-delta modulation. The driver manages all available channels in a pool so that there is no need to manually assign a fixed channel to a GPIO.
To install an SDM channel, you should call :cpp:func:`sdm_new_channel` to get a channel handle. Channel-specific configurations are passed in the :cpp:type:`sdm_config_t` structure:
- :cpp:member:`sdm_config_t::gpio_num` sets the GPIO that the PDM pulses output from.
- :cpp:member:`sdm_config_t::clk_src` selects the source clock for the SDM module. Note that, all channels should select the same clock source.
- :cpp:member:`sdm_config_t::sample_rate_hz` sets the sample rate of the SDM module. A higher sample rate can help to output signals with higher SNR (Signal to Noise Ratio), and easier to restore the original signal after the filter.
- :cpp:member:`sdm_config_t::invert_out` sets whether to invert the output signal.
The function :cpp:func:`sdm_new_channel` can fail due to various errors such as insufficient memory, invalid arguments, etc. Specifically, when there are no more free channels (i.e., all hardware SDM channels have been used up), :c:macro:`ESP_ERR_NOT_FOUND` will be returned.
If a previously created SDM channel is no longer required, you should recycle it by calling :cpp:func:`sdm_del_channel`. It allows the underlying HW channel to be used for other purposes. Before deleting an SDM channel handle, you should disable it by :cpp:func:`sdm_channel_disable` in advance or make sure it has not been enabled yet by :cpp:func:`sdm_channel_enable`.
Creating an SDM Channel with a Sample Rate of 1 MHz
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code:: c
sdm_channel_handle_t chan = NULL;
sdm_config_t config = {
.clk_src = SDM_CLK_SRC_DEFAULT,
.sample_rate_hz = 1 * 1000 * 1000,
.gpio_num = 0,
};
ESP_ERROR_CHECK(sdm_new_channel(&config, &chan));
.. _sdm-enable-and-disable-channel:
Enable and Disable Channel
^^^^^^^^^^^^^^^^^^^^^^^^^^
Before doing further IO control to the SDM channel, you should enable it first, by calling :cpp:func:`sdm_channel_enable`. Internally, this function:
* switches the channel state from **init** to **enable**
* acquires a proper power management lock if a specific clock source (e.g., APB clock) is selected. See also :ref:`sdm-power-management` for more information.
On the contrary, calling :cpp:func:`sdm_channel_disable` does the opposite, that is, put the channel back to the **init** state and releases the power management lock.
.. _sdm-set-equivalent-duty-cycle:
Set Pulse Density
^^^^^^^^^^^^^^^^^
For the output PDM signals, the pulse density decides the output analog voltage that is restored by a low-pass filter. The restored analog voltage from the channel is calculated by ``Vout = VDD_IO / 256 * duty + VDD_IO / 2``. The range of the quantized ``density`` input parameter of :cpp:func:`sdm_channel_set_pulse_density` is from -128 to 127 (8-bit signed integer). Depending on the value of the ``density`` parameter, the duty cycle of the output signal will be changed accordingly. For example, if a zero value is set, then the output signal's duty will be around 50%.
.. _sdm-power-management:
Power Management
^^^^^^^^^^^^^^^^
When power management is enabled (i.e., :ref:`CONFIG_PM_ENABLE` is on), the system will adjust the APB frequency before going into Light-sleep, thus potentially changing the sample rate of the sigma-delta modulator.
However, the driver can prevent the system from changing APB frequency by acquiring a power management lock of type :cpp:enumerator:`ESP_PM_APB_FREQ_MAX`. Whenever the driver creates an SDM channel instance that has selected :cpp:enumerator:`SDM_CLK_SRC_APB` as its clock source, the driver guarantees that the power management lock is acquired when enabling the channel by :cpp:func:`sdm_channel_enable`. Likewise, the driver releases the lock when :cpp:func:`sdm_channel_disable` is called for that channel.
.. _sdm-iram-safe:
IRAM Safe
^^^^^^^^^
There is a Kconfig option :ref:`CONFIG_SDM_CTRL_FUNC_IN_IRAM` that can put commonly-used IO control functions into IRAM as well. So that these functions can also be executable when the cache is disabled. These IO control functions are listed as follows:
- :cpp:func:`sdm_channel_set_pulse_density`
.. _sdm-thread-safety:
Thread Safety
^^^^^^^^^^^^^
The driver uses critical sections to ensure atomic operations on registers. Key members in the driver handle are also protected by critical sections. The driver's internal state machine uses atomic instructions to ensure thread safety, with state checks preventing certain invalid concurrent operations (e.g., conflicts between `enable` and `delete`). Therefore, SDM driver APIs can be used in a multi-threaded environment without extra locking.
The following functions can also be used in an interrupt context:
.. list::
- :cpp:func:`sdm_channel_set_pulse_density`
.. _sdm-kconfig-options:
Kconfig Options
^^^^^^^^^^^^^^^
- :ref:`CONFIG_SDM_CTRL_FUNC_IN_IRAM` controls where to place the SDM channel control functions (IRAM or Flash), see :ref:`sdm-iram-safe` for more information.
- :ref:`CONFIG_SDM_ENABLE_DEBUG_LOG` is used to enable the debug log output. Enabling this option increases the firmware binary size.
.. _convert_to_analog_signal:
Convert to an Analog Signal (Optional)
--------------------------------------
Typically, if a Sigma-Delta signal is connected to an LED to adjust the brightness, you do not have to add any filter between them, because our eyes have their own low-pass filters for changes in light intensity. However, if you want to check the real voltage or watch the analog waveform, you need to design an analog low-pass filter. Also, it is recommended to use an active filter instead of a passive filter to gain better isolation and not lose too much voltage.
For example, you can take the following `Sallen-Key topology Low Pass Filter`_ as a reference.
.. figure:: ../../../_static/typical_sallenkey_LP_filter.png
:align: center
:alt: Sallen-Key Low Pass Filter
:figclass: align-center
Sallen-Key Low Pass Filter
(Refer to :example_file:`peripherals/sigma_delta/sdm_dac/README.md` to see the waveforms before and after filtering.)
Application Examples
--------------------
* :example:`peripherals/sigma_delta/sdm_dac` demonstrates how to use the sigma-delta driver to act as an 8-bit DAC, and output a 100 Hz sine wave.
* :example:`peripherals/sigma_delta/sdm_led` demonstrates how to use the sigma-delta driver to control the brightness of an LED or LCD backlight.
API Reference
-------------
.. include-build-file:: inc/sdm.inc
.. include-build-file:: inc/sdm_types.inc
.. [1]
Different ESP chip series might have different numbers of SDM channels. Please refer to Chapter `GPIO and IOMUX <{IDF_TARGET_TRM_EN_URL}#iomuxgpio>`__ in {IDF_TARGET_NAME} Technical Reference Manual for more details. The driver does not forbid you from applying for more channels, but it will return an error when all available hardware resources are used up. Please always check the return value when doing resource allocation (e.g., :cpp:func:`sdm_new_channel`).
.. _Sallen-Key topology Low Pass Filter: https://en.wikipedia.org/wiki/Sallen%E2%80%93Key_topology
.. |wiki_ref| replace:: Delta-sigma modulation on Wikipedia
.. _wiki_ref: https://en.wikipedia.org/wiki/Delta-sigma_modulation
@@ -0,0 +1,221 @@
SDMMC Host Driver
=================
:link_to_translation:`zh_CN:[中文]`
Overview
--------
{IDF_TARGET_NAME}'s SDMMC host peripheral has two slots. Each slot can be used independently to connect to an SD card, SDIO device, or eMMC chip.
.. only:: esp32
- Slot 0 (:c:macro:`SDMMC_HOST_SLOT_0`) is an 8-bit slot. It uses ``HS1_*`` signals in the PIN MUX.
- Slot 1 (:c:macro:`SDMMC_HOST_SLOT_1`) is a 4-bit slot. It uses ``HS2_*`` signals in the PIN MUX.
The slots are connected to {IDF_TARGET_NAME} GPIOs using IO MUX. Pin mappings of these slots are given in the table below.
.. list-table::
:header-rows: 1
:widths: 20 40 40
:align: center
* - Signal
- Slot 0
- Slot 1
* - CMD
- GPIO11
- GPIO15
* - CLK
- GPIO6
- GPIO14
* - D0
- GPIO7
- GPIO2
* - D1
- GPIO8
- GPIO4
* - D2
- GPIO9
- GPIO12
* - D3
- GPIO10
- GPIO13
* - D4
- GPIO16
-
* - D5
- GPIO17
-
* - D6
- GPIO5
-
* - D7
- GPIO18
-
* - CD
- any input via GPIO matrix
- any input via GPIO matrix
* - WP
- any input via GPIO matrix
- any input via GPIO matrix
The Card Detect (CD) and Write Protect (WP) signals can be routed to arbitrary pins using the GPIO matrix. To reserve the pins, set the ``cd`` and ``wp`` members of the :cpp:class:`sdmmc_slot_config_t` structure before calling :cpp:func:`sdmmc_host_init_slot`. Please note that it is not advised to specify a CD pin when working with SDIO cards, because the CD signal in ESP32 can also trigger SDIO slave interrupt.
.. warning::
Pins used by Slot 0 (``HS1_*``) are also used to connect the SPI flash chip in ESP32-WROOM and ESP32-WROVER modules. These pins cannot be concurrently shared between an SD card and an SPI flash. If you need to use Slot 0, establish an alternative connection for the SPI flash using different pins and configure the necessary eFuses accordingly.
.. only:: esp32s3
Both slots :c:macro:`SDMMC_HOST_SLOT_0` and :c:macro:`SDMMC_HOST_SLOT_1` support 1-, 4- and 8-line SD interfaces. The slots are connected to {IDF_TARGET_NAME} GPIOs using the GPIO matrix. This means that any GPIO may be used for each of the SD card signals.
.. only:: esp32p4
- :c:macro:`SDMMC_HOST_SLOT_1` is routed via GPIO Matrix. This means that any GPIO may be used for each of the SD card signals. It is for non UHS-I usage.
- :c:macro:`SDMMC_HOST_SLOT_0` is dedicated to UHS-I mode.
On {IDF_TARGET_NAME}, SDMMC host requires an external power supply for the IO voltage. Please refer to :ref:`pwr-ctrl` for details.
.. only:: esp32s31
- Both slots :c:macro:`SDMMC_HOST_SLOT_0` and :c:macro:`SDMMC_HOST_SLOT_1` are dedicated IOs.
- :c:macro:`SDMMC_HOST_SLOT_0` supports UHS-I mode.
Supported Speed Modes
---------------------
SDMMC Host driver supports the following speed modes:
.. list::
- Default Speed (20 MHz): 1-line or 4-line with SD cards, and 1-line, 4-line, or 8-line with 3.3 V eMMC
- High Speed (40 MHz): 1-line or 4-line with SD cards, and 1-line, 4-line, or 8-line with 3.3 V eMMC
:SOC_SDMMC_UHS_I_SUPPORTED: - UHS-I 1.8 V, SDR104 (200 MHz): 4-line with SD cards
:SOC_SDMMC_UHS_I_SUPPORTED: - UHS-I 1.8 V, SDR50 (100 MHz): 4-line with SD cards
:SOC_SDMMC_UHS_I_SUPPORTED: - UHS-I 1.8 V, DDR50 (50 MHz): 4-line with SD cards
- High Speed DDR (40 MHz): 4-line with 3.3 V eMMC
Speed modes not supported at present:
- High Speed DDR mode: 8-line eMMC
Using the SDMMC Host Driver
---------------------------
Of all the functions listed below, only the following ones will be used directly by most applications:
- :cpp:func:`sdmmc_host_init`
- :cpp:func:`sdmmc_host_init_slot`
- :cpp:func:`sdmmc_host_deinit`
Other functions, such as the ones given below, will be called by the SD/MMC protocol layer via function pointers in the :cpp:class:`sdmmc_host_t` structure:
- :cpp:func:`sdmmc_host_set_bus_width`
- :cpp:func:`sdmmc_host_set_card_clk`
- :cpp:func:`sdmmc_host_do_transaction`
Configuring Bus Width and Frequency
-----------------------------------
With the default initializers for :cpp:class:`sdmmc_host_t` and :cpp:class:`sdmmc_slot_config_t`, i.e., :c:macro:`SDMMC_HOST_DEFAULT` and :c:macro:`SDMMC_SLOT_CONFIG_DEFAULT`, SDMMC Host driver will attempt to use the widest bus supported by the card (4 lines for SD, 8 lines for eMMC) and the frequency of 20 MHz.
In the designs where communication at 40 MHz frequency can be achieved, it is possible to increase the bus frequency by changing the ``max_freq_khz`` field of :cpp:class:`sdmmc_host_t`:
.. code-block::
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
host.max_freq_khz = SDMMC_FREQ_HIGHSPEED;
If you need a specific frequency other than standard speeds, you are free to use any value from within an appropriate range of the SD interface given (SDMMC or SDSPI). However, the real clock frequency shall be calculated by the underlying driver and the value can be different from the one required.
For the SDMMC, ``max_freq_khz`` works as the upper limit so the final frequency value shall be always lower or equal. For the SDSPI, the nearest fitting frequency is supplied and thus the value can be greater than/equal to/lower than ``max_freq_khz``.
To configure the bus width, set the ``width`` field of :cpp:class:`sdmmc_slot_config_t`. For example, to set 1-line mode:
.. code-block::
sdmmc_slot_config_t slot = SDMMC_SLOT_CONFIG_DEFAULT();
slot.width = 1;
.. only:: SOC_SDMMC_USE_GPIO_MATRIX
Configuring GPIOs
-----------------
{IDF_TARGET_NAME} SDMMC Host can be configured to use arbitrary GPIOs for each of the signals. Configuration is performed by setting members of :cpp:class:`sdmmc_slot_config_t` structure.
For example, to use GPIOs 1-6 for CLK, CMD, and D0-D3 signals respectively:
.. code-block::
sdmmc_slot_config_t slot = SDMMC_SLOT_CONFIG_DEFAULT();
slot.clk = GPIO_NUM_1;
slot.cmd = GPIO_NUM_2;
slot.d0 = GPIO_NUM_3;
slot.d1 = GPIO_NUM_4;
slot.d2 = GPIO_NUM_5;
slot.d3 = GPIO_NUM_6;
It is also possible to configure Card Detect and Write Protect pins. Similar to other signals, set ``cd`` and ``wp`` members of the same structure:
.. code-block::
slot.cd = GPIO_NUM_7;
slot.wp = GPIO_NUM_8;
``SDMMC_SLOT_CONFIG_DEFAULT`` sets both to ``GPIO_NUM_NC``, meaning that by default the signals are not used.
Once :cpp:class:`sdmmc_slot_config_t` structure is initialized this way, you can use it when calling :cpp:func:`sdmmc_host_init_slot` or one of the higher level functions (such as :cpp:func:`esp_vfs_fat_sdmmc_mount`).
.. only:: SOC_SDMMC_IO_POWER_EXTERNAL
.. _pwr-ctrl:
Configuring Voltage Level
-------------------------
{IDF_TARGET_NAME} SDMMC Host requires the IO voltage to be supplied externally via the VDDPST_5 (SD_VREF) pin. If the design doesn't require the higher speed SD modes, this pin can be simply connected to the 3.3V supply.
If the design does require higher speed SD modes (which only work at 1.8V IO levels), there are two options available:
- Use the on-chip programmable LDO. In this case, connect the desired LDO output channel to VDDPST_5 (SD_VREF) pin. Call :cpp:func:`sd_pwr_ctrl_new_on_chip_ldo` to initialize the SD power control driver, then set :cpp:member:`sdmmc_host_t::pwr_ctrl_handle` to the resulting handle.
- Use an external programmable LDO. Likewise, connect the LDO output to the VDDPST_5 (SD_VREF) pin. Then implement a custom `sd_pwr_ctrl` driver to control your LDO. Finally, assign :cpp:member:`sdmmc_host_t::pwr_ctrl_handle` to the handle of your driver instance.
DDR Mode for eMMC Chips
-----------------------
By default, DDR mode will be used if:
- SDMMC host frequency is set to :c:macro:`SDMMC_FREQ_HIGHSPEED` in :cpp:class:`sdmmc_host_t` structure, and
- eMMC chip reports DDR mode support in its CSD register
DDR mode places higher requirements for signal integrity. To disable DDR mode while keeping the :c:macro:`SDMMC_FREQ_HIGHSPEED` frequency, clear the :c:macro:`SDMMC_HOST_FLAG_DDR` bit in :cpp:member:`sdmmc_host_t::flags` field of the :cpp:class:`sdmmc_host_t`:
.. code-block::
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
host.max_freq_khz = SDMMC_FREQ_HIGHSPEED;
host.flags &= ~SDMMC_HOST_FLAG_DDR;
See also
--------
- :doc:`../storage/sdmmc`: introduces the higher-level driver which implements the protocol layer.
- :doc:`sdspi_host`: introduces a similar driver that uses the SPI controller and is limited to SD protocol's SPI mode.
- :doc:`sd_pullup_requirements`: introduces pull-up support and compatibility of modules and development kits.
API Reference
-------------
.. include-build-file:: inc/sdmmc_host.inc
.. include-build-file:: inc/sd_pwr_ctrl.inc
.. include-build-file:: inc/sd_pwr_ctrl_by_on_chip_ldo.inc
@@ -0,0 +1,69 @@
SD SPI Host Driver
==================
:link_to_translation:`zh_CN:[中文]`
Overview
--------
The SD SPI host driver allows communication with one or more SD cards using the SPI Master driver, which utilizes the SPI host. Each card is accessed through an SD SPI device, represented by an SD SPI handle :cpp:type:`sdspi_dev_handle_t`, which returns when the device is attached to an SPI bus by calling :cpp:func:`sdspi_host_init_device`. It is important to note that the SPI bus should be initialized beforehand by :cpp:func:`spi_bus_initialize`.
.. only:: esp32
This driver's naming pattern was adopted from the :doc:`sdmmc_host` due to their similarity. Likewise, the APIs of both drivers are also very similar.
SD SPI driver that accesses the SD card in SPI mode offers lower throughput but makes pin selection more flexible. With the help of the GPIO matrix, an SPI peripheral's signals can be routed to any {IDF_TARGET_NAME} pin. Otherwise, if an SDMMC host driver is used (see :doc:`sdmmc_host`) to access the card in SD 1-bit/4-bit mode, higher throughput can be reached while requiring routing the signals through their dedicated IO_MUX pins only.
With the help of :doc:`spi_master` the SD SPI host driver based on, the SPI bus can be shared among SD cards and other SPI devices. The SPI Master driver will handle exclusive access from different tasks.
The SD SPI driver uses software-controlled CS signal.
How to Use
----------
Firstly, use the macro :c:macro:`SDSPI_DEVICE_CONFIG_DEFAULT` to initialize the structure :cpp:type:`sdspi_device_config_t`, which is used to initialize an SD SPI device. This macro will also fill in the default pin mappings, which are the same as the pin mappings of the SDMMC host driver. Modify the host and pins of the structure to desired value. Then call ``sdspi_host_init_device`` to initialize the SD SPI device and attach to its bus.
Then use the :c:macro:`SDSPI_HOST_DEFAULT` macro to initialize the :cpp:type:`sdmmc_host_t` structure, which is used to store the state and configurations of the upper layer (SD/SDIO/MMC driver). Modify the ``slot`` parameter of the structure to the SD SPI device SD SPI handle just returned from ``sdspi_host_init_device``. Call ``sdmmc_card_init`` with the :cpp:type:`sdmmc_host_t` to probe and initialize the SD card.
Now you can use SD/SDIO/MMC driver functions to access your card!
Other Details
-------------
Only the following driver's API functions are normally used by most applications:
- :cpp:func:`sdspi_host_init`
- :cpp:func:`sdspi_host_init_device`
- :cpp:func:`sdspi_host_remove_device`
- :cpp:func:`sdspi_host_deinit`
Other functions are mostly used by the protocol level SD/SDIO/MMC driver via function pointers in the :cpp:type:`sdmmc_host_t` structure. For more details, see :doc:`../storage/sdmmc`.
.. note::
SD over SPI does not support speeds above :c:macro:`SDMMC_FREQ_DEFAULT` due to the limitations of the SPI driver.
.. warning::
If you want to share the SPI bus among SD card and other SPI devices, there are some restrictions, see :doc:`sdspi_share`.
.. todo
.. The SD SPI API reference could use more detail such as:
.. - Configuration. What are some key points of concern regarding slot configuration.
.. - Which function/how is a transaction done?
.. - Are there code snippets or corresponding application examples?
Related Docs
--------------
.. toctree::
:maxdepth: 1
sdspi_share
API Reference
-------------
.. include-build-file:: inc/sdspi_host.inc
@@ -0,0 +1,76 @@
Sharing the SPI Bus Among SD Cards and Other SPI Devices
========================================================
:link_to_translation:`zh_CN:[中文]`
The SD card has an SPI mode, enabling it to function as an SPI device, but there are some restrictions that we need to pay attention to.
Pin Loading of Other Devices
----------------------------
When adding more devices onto the same bus, the overall pin loading increases. The loading consists of AC loading (pin capacitor) and DC loading (pull-ups).
AC Loading
^^^^^^^^^^
SD cards, designed for high-speed communications, have small pin capacitors (AC loading) to work until 50 MHz. However, the other attached devices will increase the pin's AC loading.
Heavy AC loading of a pin may prevent the pin from being toggled quickly. By using an oscilloscope, you will see the edges of the pin become smoother, i.e., the gradient of the edge is smaller. The setup timing requirements of an SD card may be violated when the card is connected to a bus with a high AC load. Even worse, high AC loads may cause the SD card and other SPI devices to fail to properly resolve clock signals from the host, affecting communication stability.
This issue may be more obvious if other attached devices are not designed to work at the same frequency as the SD card, because they may have larger pin capacitors. The larger the pin capacity, the greater the pin response time, the smaller the max frequency the SD bus can work.
To see if your pin AC loading is too heavy, you can try the following tests:
Terminology:
- **launch edge**: at which clock edge the data starts to toggle;
- **latch edge**: at which clock edge the data is supposed to be sampled by the receiver. For SD card, it is the rising edge.
1. Use an oscilloscope to see the clock and compare the data line to the clock.
- If you see the clock is not fast enough, e.g., the rising/falling edge is longer than 1/4 of the clock cycle, it means the clock is skewed too much.
- If you see the data line unstable before the latch edge of the clock, it means the load of the data line is too large.
You may also observe the corresponding phenomenon that data delayed largely from the launching edge of the clock with logic analyzers. But it is not as obvious as with an oscilloscope.
2. Try to use a slower clock frequency.
If the lower frequency can work while the higher frequency cannot, it is an indication that the AC loading on the pins is too large.
If the AC loading of the pins is too large, you can either use other faster devices with lower pin load or slow down the clock speed.
DC Loading
^^^^^^^^^^
The pull-ups required by SD cards are usually around 10 kOhm to 50 kOhm, which may be too strong for some other SPI devices.
Check the specification of your device about its DC output current, it should be larger than 700 μA, otherwise, the device output may not be read correctly.
Initialization Sequence
-----------------------
.. note::
If you see any problem in the following steps, please make sure the timing is correct first. You can try to slow down the clock speed, such as setting ``SDMMC_FREQ_PROBING`` to 400 kHz for SD card, to avoid the influence of pin AC loading, as discussed in the previous section.
When using an SD card with other SPI devices on the same SPI bus, due to the restrictions of the SD card startup flow, the following initialization sequence should be followed. Refer to :example:`storage/sd_card` for further details.
1. Initialize the SPI bus properly by :cpp:func:`spi_bus_initialize`.
2. Tie the CS lines of all other devices than the SD card to idle state (by default it's high). This is to avoid conflicts with the SD card in the following step.
You can do this by either:
1. Attach devices to the SPI bus by calling :cpp:func:`spi_bus_add_device`. This function will by default initialize the GPIO that is used as CS to the idle level: high.
2. Before adding a new device, initialize the GPIO for the CS pin that must remain high.
3. Rely on the internal/external pull-up (**not recommended**) to pull up all the CS pins when the GPIOs of ESP are not initialized yet. You need to check carefully the pull-up is strong enough and there are no other pull-downs that will influence the pull-up. For example, internal pull-down should be enabled.
3. Mount the card to the filesystem by calling :cpp:func:`esp_vfs_fat_sdspi_mount`.
This step will put the SD card into the SPI mode, which **should** be done before all other SPI communications on the same bus. Otherwise, the card will stay in the SD mode, in which mode it may randomly respond to any SPI communications on the bus, even when its CS line is not addressed.
If you want to test this behavior, please also note that, once the card is put into SPI mode, it will not return to SD mode before the next power cycle, i.e., powered down and powered up again.
4. Now you can talk to other SPI devices freely!
@@ -0,0 +1,28 @@
SPI Features
============
.. _spi_master_features:
SPI Master
----------
.. _spi_bus_lock:
SPI Bus Lock
^^^^^^^^^^^^
To realize the multiplexing of different devices from different drivers, including SPI Master, SPI Flash, etc., an SPI bus lock is applied on each SPI bus. Drivers can attach their devices to the bus with the arbitration of the lock.
Each bus lock is initialized with a BG (background) service registered. All devices that request transactions on the bus should wait until the BG is successfully disabled.
- For the SPI1 bus, the BG is the cache. The bus lock disables the cache before device operations start, and enables it again after the device releases the lock. No devices on SPI1 are allowed to use ISR, since it is meaningless for the task to yield to other tasks when the cache is disabled.
.. only:: esp32
There are quite a few limitations when using the SPI Master driver on the SPI1 bus. See :ref:`spi_master_on_spi1_bus`.
.. only:: not esp32
The SPI Master driver has not supported SPI1 bus. Only the SPI Flash driver can attach to the bus.
- For other buses, the driver can register the ISR as a BG. If a device task requests exclusive bus access, the bus lock will block the task, disable the ISR, and then unblock the task. After the task releases the lock, the lock will try to re-enable the ISR if there are still pending transactions in the ISR.
@@ -0,0 +1,79 @@
.. _auto-suspend:
Flash Auto Suspend Feature
--------------------------
.. important::
1. The flash chip you are using should have a suspend/resume feature.
2. The MSPI hardware should support the auto-suspend feature, i.e., hardware can send suspend command automatically.
If you use suspend feature on an unsupported chip, it may cause a severe crash. Therefore, we strongly suggest you reading the flash chip datasheets first. Ensure the flash chip satisfies the following conditions at minimum.
1. With the current software implementation, SUS bit in status registers should in SR2 bit7 or SR bit15.
2. With the current software implementation, suspend command should be 75H, with resume command being 7AH.
3. When the flash is successfully suspended, all address of the flash, except from the section/block being erased, can be read correctly. At this stateresume can be sent immediately as well.
4. When the flash is successfully resumed, another suspend can be sent immediately at this state.
When :ref:`CONFIG_SPI_FLASH_AUTO_SUSPEND` is enabled, the caches will be kept enabled. They would be disabled if :ref:`CONFIG_SPI_FLASH_AUTO_SUSPEND` is disabled. The hardware handles the arbitration between SPI0 and SPI1. If the SPI1 operation is short, such as a reading operation, the CPU and the cache will wait until the SPI1 operation is completed. However, during processes like erasing, page programming, or status register writing (e.g., ``SE``, ``PP``, and ``WRSR``), an auto suspend will happen, interrupting the ongoing flash operation. This allows the CPU to access data from the cache and flash within limited time.
This approach allows certain code/variables to be stored in flash/PSRAM instead of IRAM/DRAM, while still being executable during flash erasing. This reduces the usage of IRAM/DRAM.
Please note that this feature comes with the overhead of flash suspend/resume. Frequent interruptions during flash erasing can significantly prolong the erasing process. To avoid this, you may use FreeRTOS task priorities to ensure that only real-time critical tasks are executed at a higher priority than flash erasing, allowing the flash erasing to complete in reasonable time.
There are three kinds of code:
1. Critical code: inside IRAM/DRAM. This kind of code usually has high performance requirements, related to cache/flash/PSRAM, or is called very often.
2. Cached code: inside flash/PSRAM. This kind of code has lower performance requirements or is called less often. They will execute during erasing, with some overhead.
3. Low-priority code: inside flash/PSRAM and disabled during erasing. This kind of code should be forbidden from being executed to avoid affecting the flash erasing, by setting a lower task priority than the erasing task.
Regarding the flash suspend feature usage and corresponding response time delay, please also see the :example:`system/flash_suspend` example.
.. note::
The flash auto suspend feature relies heavily on strict timing. You can see it as a kind of optimization plan, which means that you cannot use it in every situation, like high requirement of real-time system or triggering interrupt very frequently (e.g., LCD flush, bluetooth, Wi-Fi, etc.). You should take following steps to evaluate what kind of ISR can be used together with flash suspend.
.. wavedrom:: /../_static/diagrams/spi_flash/flash_auto_suspend_timing.json
As you can see from the diagram, two key values should be noted:
1. ISR time: The ISR time cannot be very long, at least not larger than the value you set in ``IWDT``. But it will significantly lengthen the erasing/writing completion time.
2. ISR interval: ISR cannot be triggered very often. The most important time is the **ISR interval minus ISR time** (from point b to point c in the diagram). During this time, SPI1 will send resume command to restart the operation. However, it needs a time ``tsus`` for preparation, and the typical value of ``tsus`` is about **40 us**. If SPI1 cannot resume the operation but another suspend command comes, it will cause CPU starve and ``TWDT`` may be triggered.
The ``tsus`` time mentioned in point 2 can be found by looking through the flash datasheets, usually in the AC CHARACTERISTICS section. Users needs to make sure that the ``tsus`` value obtained from the datasheets is not greater than the :ref:`CONFIG_SPI_FLASH_SUSPEND_TSUS_VAL_US` value in Kconfig.
Furthermore, the flash suspend might be delayed. If both the CPU and the cache access the flash via SPI0 frequently and SPI1 sends the suspend command frequently as well, the efficiency of MSPI data transfer will be influenced. So, we have a **lock** inside to prevent this. When SPI1 sends the suspend command, SPI0 will take over memory SPI bus and take the lock. After SPI0 finishes sending data, it will retain control of the memory SPI bus until the lock delay period time finishes. During this lock delay period, if there is any other SPI0 transaction, then the SPI0 transaction will be proceeded and a new lock delay period will start. Otherwise, SPI0 will release the memory bus and start SPI0/1 arbitration.
Advanced Suspend-resume Usage
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
In the default configuration, flash suspend is issued automatically by hardware, and the resume is also handled by hardware at an appropriate moment, while a fixed time delay is used to wait for the suspend command to take effect. This implementation already covers the majority of use cases. However, in scenarios that have stricter requirements on real-time response, performance, or suspend granularity, ESP-IDF also provides additional advanced configuration options for fine-tuning the suspend-resume behavior.
1. Detect Whether Suspend Has Taken Effect via Flash Register
Normally, after issuing the suspend command, the hardware waits for a fixed period of time defined by ``tsus`` before handing the memory bus back to SPI0/CPU. Because this delay must be configured according to the worst case given in the datasheet, it is conservative in most situations.
By enabling :ref:`CONFIG_SPI_FLASH_AUTO_CHECK_SUSPEND_STATUS`, the hardware will instead poll the ``WIP`` bit in the flash status register to determine whether the suspend command has actually taken effect, rather than waiting for the fixed time given by :ref:`CONFIG_SPI_FLASH_SUSPEND_TSUS_VAL_US`. Since the actual suspend setup time is usually much shorter than the maximum value listed in the datasheet, this approach can significantly reduce the overhead during suspend and improve overall performance.
.. important::
Before enabling this option, please confirm that the behavior of the ``WIP`` bit after suspend on your flash chip matches the expectation. Most flash chips that support suspend/resume satisfy this requirement, but it is still recommended to consult the corresponding datasheet to verify it.
2. Software-controlled Flash Resume
Normally, after the flash is suspended, the hardware will automatically arrange an appropriate moment to send the resume command, allowing the flash to continue its original erase/write operation. This implementation is transparent to software, but it has a side effect: while a high-priority task or interrupt is still running, the hardware may again initiate a suspend/resume sequence, which interrupts those tasks and affects the continuity and timing of their execution.
By enabling :ref:`CONFIG_SPI_FLASH_SOFTWARE_RESUME`, the hardware auto-resume feature is disabled, and the flash resume operation is instead issued by software at an appropriate point. With this option enabled, once the flash is suspended it will stay suspended until software explicitly resumes it. In the SPI1 wait-idle flow, software actively checks the flash's suspend state, and if it finds the flash in a suspended state, it calls the driver's resume interface to let the flash continue the original operation. This means that the resume command is only issued after the high-priority task or interrupt has truly finished and software has returned to the SPI1 operation context, avoiding repeated bus preemption caused by suspend-resume on high-priority paths.
Because this mechanism relies on the software layer to perform resume at well-defined execution points, and the current implementation does not provide corresponding protection for multi-core scenarios, this option has the following limitations:
- It is only supported on single-core scenarios. :ref:`CONFIG_FREERTOS_UNICORE` must be enabled.
- It is an experimental feature and is only visible after :ref:`CONFIG_IDF_EXPERIMENTAL_FEATURES` is enabled.
- This feature improves the continuity of interrupt response and the overall application performance. However, it also increases the time needed to complete a single flash operation.
@@ -0,0 +1,414 @@
SPI Flash API
=============
:link_to_translation:`zh_CN:[中文]`
Overview
--------
The spi_flash component contains API functions related to reading, writing, erasing, and memory mapping for data in the external flash.
For higher-level API functions which work with partitions defined in the :doc:`partition table </api-guides/partition-tables>`, see :doc:`/api-reference/storage/partition`
.. note::
``esp_partition_*`` APIs are recommended to be used instead of the lower level ``esp_flash_*`` API functions when accessing the main SPI flash chip, since they conduct bounds checking and are guaranteed to calculate correct offsets in flash based on the information in the partition table. ``esp_flash_*`` functions can still be used directly when accessing an external (secondary) SPI flash chip.
Different from the API before ESP-IDF v4.0, the functionality of ``esp_flash_*`` APIs is not limited to the "main" SPI flash chip (the same SPI flash chip from which program runs). With different chip pointers, you can access external flash chips connected to not only SPI0/1 but also other SPI buses like SPI2.
.. note::
Instead of going through the cache connected to the SPI0 peripheral, most ``esp_flash_*`` APIs go through other SPI peripherals like SPI1, SPI2, etc. This makes them able to access not only the main flash, but also external (secondary) flash.
However, due to the limitations of the cache, operations through the cache are limited to the main flash. The address range limitation for these operations is also on the cache side. The cache is not able to access external flash chips or address range above its capabilities. These cache operations include: mmap, encrypted read/write, executing code or access to variables in the flash.
.. note::
Flash APIs after ESP-IDF v4.0 are no longer **atomic**. If a write operation occurs during another on-going read operation, and the flash addresses of both operations overlap, the data returned from the read operation may contain both old data and new data (that was updated written by the write operation).
.. note::
Encrypted flash operations are only supported with the main flash chip (and not with other flash chips, that is on SPI1 with different CS, or on other SPI buses). Reading through cache is only supported on the main flash, which is determined by the HW.
Support for Features of Flash Chips
-----------------------------------
Quad/Dual Mode Chips
^^^^^^^^^^^^^^^^^^^^
Features of different flashes are implemented in different ways and thus need special support. The fast/slow read and Dual mode (DOUT/DIO) of almost all flashes with 24-bit address are supported, because they do not need any vendor-specific commands.
Quad mode (QIO/QOUT) is supported on the following chip types:
1. ISSI
2. GD
3. MXIC
4. FM
5. Winbond
6. XMC
7. BOYA
.. note::
Only when one flash series listed above is supported by {IDF_TARGET_NAME}, this flash series is supported by the chip driver by default. You can use ``Component config`` > ``SPI Flash driver`` > ``Auto-detect flash chips`` in menuconfig to enable/disable a flash series.
Optional Features
^^^^^^^^^^^^^^^^^
.. toctree::
:hidden:
spi_flash_optional_feature
There are some features that are not supported by all flash chips, or not supported by all Espressif chips. These features include:
.. only:: esp32s3
- OPI flash - means that flash supports octal mode.
- 32-bit address flash - usually means that the flash has higher capacity (equal to or larger than 16 MB) that needs longer addresses.
.. only:: esp32s3
- High performance mode (HPM) - means that flash works under high frequency which is higher than 80 MHz.
- Flash unique ID - means that flash supports its unique 64-bit ID.
.. only:: SOC_SPI_MEM_SUPPORT_AUTO_SUSPEND
- Suspend & Resume - means that flash can accept suspend/resume command during its writing/erasing. The {IDF_TARGET_NAME} may keep the cache on when the flash is being written/erased and suspend it to read its contents randomly.
If you want to use these features, please ensure both {IDF_TARGET_NAME} and ALL flash chips in your product support these features. For more details, refer to :doc:`spi_flash_optional_feature`.
You may also customise your own flash chip driver. See :doc:`spi_flash_override_driver` for more details.
.. toctree::
:hidden:
Custom Flash Driver <spi_flash_override_driver>
Initializing a Flash Device
---------------------------
To use the ``esp_flash_*`` APIs, you need to initialise a flash chip on a certain SPI bus, as shown below:
1. Call :cpp:func:`spi_bus_initialize` to properly initialize an SPI bus. This function initializes the resources (I/O, DMA, interrupts) shared among devices attached to this bus.
2. Call :cpp:func:`spi_bus_add_flash_device` to attach the flash device to the bus. This function allocates memory and fills the members for the ``esp_flash_t`` structure. The CS I/O is also initialized here.
3. Call :cpp:func:`esp_flash_init` to actually communicate with the chip. This also detects the chip type, and influence the following operations.
.. note:: Multiple flash chips can be attached to the same bus now.
SPI Flash Access API
--------------------
This is the set of API functions for working with data in flash:
- :cpp:func:`esp_flash_read` reads data from flash to RAM
- :cpp:func:`esp_flash_write` writes data from RAM to flash
- :cpp:func:`esp_flash_erase_region` erases specific region of flash
- :cpp:func:`esp_flash_erase_chip` erases the whole flash
- :cpp:func:`esp_flash_get_chip_size` returns flash chip size, in bytes, as configured in menuconfig
Generally, try to avoid using the raw SPI flash functions to the "main" SPI flash chip in favour of :ref:`partition-specific functions <flash-partition-apis>`.
SPI Flash Size
--------------
The SPI flash size is configured by writing a field in the ESP-IDF second stage bootloader image header, flashed at offset 0x1000.
By default, the SPI flash size is detected by ``esptool`` when this bootloader is written to flash, and the header is updated with the correct size. Alternatively, it is possible to generate a fixed flash size by setting :ref:`CONFIG_ESPTOOLPY_FLASHSIZE` in the project configuration.
If it is necessary to override the configured flash size at runtime, it is possible to set the ``chip_size`` member of the ``g_rom_flashchip`` structure. This size is used by ``esp_flash_*`` functions (in both software & ROM) to check the bounds.
Concurrency Constraints for Flash on SPI1
-----------------------------------------
.. toctree::
:hidden:
spi_flash_concurrency
.. attention::
The SPI0/1 bus is shared between the instruction & data cache (for firmware execution) and the SPI1 peripheral (controlled by the drivers including this SPI flash driver). Hence, calling SPI Flash API on SPI1 bus (including the main flash) causes significant influence to the whole system. See :doc:`spi_flash_concurrency` for more details.
SPI Flash Encryption
--------------------
It is possible to encrypt the contents of SPI flash and have it transparently decrypted by hardware.
Refer to the :doc:`Flash Encryption documentation </security/flash-encryption>` for more details.
Memory Mapping API
------------------
{IDF_TARGET_CACHE_SIZE:default="64 KB", esp32c2=16~64 KB}
{IDF_TARGET_NAME} features memory hardware which allows regions of flash memory to be mapped into instruction and data address spaces. This mapping works only for read operations. It is not possible to modify contents of flash memory by writing to a mapped memory region.
Mapping happens in {IDF_TARGET_CACHE_SIZE} pages. Memory mapping hardware can map flash into the data address space and the instruction address space. See the technical reference manual for more details and limitations about memory mapping hardware.
Note that some pages are used to map the application itself into memory, so the actual number of available pages may be less than the capability of the hardware.
Reading data from flash using a memory mapped region is the only way to decrypt contents of flash when :doc:`flash encryption </security/flash-encryption>` is enabled. Decryption is performed at the hardware level.
Memory mapping API are declared in ``spi_flash_mmap.h`` and ``esp_partition.h``:
- :cpp:func:`spi_flash_mmap` maps a region of physical flash addresses into instruction space or data space of the CPU.
- :cpp:func:`spi_flash_munmap` unmaps previously mapped region.
- :cpp:func:`esp_partition_mmap` maps part of a partition into the instruction space or data space of the CPU.
Differences between :cpp:func:`spi_flash_mmap` and :cpp:func:`esp_partition_mmap` are as follows:
- :cpp:func:`spi_flash_mmap` must be given a {IDF_TARGET_CACHE_SIZE} aligned physical address.
- :cpp:func:`esp_partition_mmap` may be given any arbitrary offset within the partition. It adjusts the returned pointer to mapped memory as necessary.
Note that since memory mapping happens in pages, it may be possible to read data outside of the partition provided to ``esp_partition_mmap``, regardless of the partition boundary.
.. note::
mmap is supported by cache, so it can only be used on main flash.
.. only:: not esp32
.. _blocks_write_flag:
About the :cpp:enumerator:`SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE` flag
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
When flash erasing/writing happen while cache mapping exists, it often causes some cache region to be invalidated and reloaded again. To improve the performance, it is suggested to specify this flag when you are sure:
1. This mapping will end in a short time, and
2. Before the mapping ends, you don't need to erase or write the flash.
This flag will prevent all writes until the corresponding munmap is called. Most ESP-IDF APIs that rely on the mapping to flash internally use this flag.
.. only:: SOC_SPI_MEM_SUPPORT_AUTO_SUSPEND or SOC_SPIRAM_XIP_SUPPORTED
This flag also helps to prevent the cache being disabled when you are using following modes. See their documentation for more details.
.. list::
:SOC_SPIRAM_XIP_SUPPORTED: - :ref:`xip_from_psram`
:SOC_SPI_MEM_SUPPORT_AUTO_SUSPEND: - :ref:`auto-suspend`
This flag is implemented by a lock in the SPI Flash driver. The lock is taken when :cpp:func:`spi_flash_mmap` (or mmap-like APIs) is called with the flag and released until corresponding unmap is called. There is a reference counter internally allowing concurrent mapping to the flash. Only after the last mapping to Flash with the flag is revoked (counter equals 0) can the flash erasing/writing APIs start execution.
SPI Flash Implementation
------------------------
.. note::
The header files in ``components/spi_flash/include/esp_flash_chips/`` directory are **semi-public** - they are intended for expert users who need to implement custom chip drivers for unsupported flash chips, but they are **not considered stable API** and may change without notice. For most use cases, you should use the public APIs in ``esp_flash.h`` instead.
The ``esp_flash_t`` structure holds chip data as well as three important parts of this API:
1. The host driver, which provides the hardware support to access the chip;
2. The chip driver, which provides compatibility service to different chips;
3. The OS functions, provide support of some OS functions (e.g., lock, delay) in different stages (1st/2nd boot, or the app).
Host Driver
^^^^^^^^^^^
The host driver relies on an interface (``spi_flash_host_driver_t``) defined in the ``spi_flash_types.h`` (in the ``esp_hal_mspi/include/hal`` folder). This interface provides some common functions to communicate with the chip.
In other files of the SPI HAL, some of these functions are implemented with existing {IDF_TARGET_NAME} memory-spi functionalities. However, due to the speed limitations of {IDF_TARGET_NAME}, the HAL layer cannot provide high-speed implementations to some reading commands (so the support for it was dropped). The files (``memspi_host_driver.h`` and ``.c``) implement the high-speed version of these commands with the ``common_command`` function provided in the HAL, and wrap these functions as ``spi_flash_host_driver_t`` for upper layer to use.
You can also implement your own host driver, even with the GPIO. As long as all the functions in the ``spi_flash_host_driver_t`` are implemented, the esp_flash API can access the flash regardless of the low-level hardware.
Chip Driver
^^^^^^^^^^^
The chip driver, defined in ``esp_flash_chips/spi_flash_chip_driver.h``, wraps basic functions provided by the host driver for the API layer to use.
Some operations need some commands to be sent first, or read some status afterwards. Some chips need different commands or values, or need special communication ways.
There is a type of chip called ``generic chip`` which stands for common chips. Other special chip drivers can be developed on the base of the generic chip.
The chip driver relies on the host driver.
.. _esp_flash_os_func:
OS Functions
^^^^^^^^^^^^
.. toctree::
:hidden:
../spi_features
Currently the OS function layer provides entries of a lock and delay.
The lock (see :ref:`spi_bus_lock`) is used to resolve the conflicts among the access of devices on the same SPI bus, and the SPI Flash chip access. E.g.
1. On SPI1 bus, the cache (used to fetch the data (code) in the Flash and PSRAM) should be disabled when the flash chip on the SPI0/1 is being accessed.
2. On the other buses, the flash driver needs to disable the ISR registered by SPI Master driver, to avoid conflicts.
3. Some devices of SPI Master driver may require to use the bus monopolized during a period (especially when the device does not have a CS wire, or the wire is controlled by software like SDSPI driver).
The delay is used by some long operations which requires the master to wait or polling periodically.
The top API wraps these the chip driver and OS functions into an entire component, and also provides some argument checking.
OS functions can also help to avoid a watchdog timeout when erasing large flash areas. During this time, the CPU is occupied with the flash erasing task. This stops other tasks from being executed. Among these tasks is the idle task to feed the watchdog timer (WDT). If the configuration option :ref:`CONFIG_ESP_TASK_WDT_PANIC` is selected and the flash operation time is longer than the watchdog timeout period, the system will reboot.
It is pretty hard to totally eliminate this risk, because the erasing time varies with different flash chips, making it hard to be compatible in flash drivers. Therefore, users need to pay attention to it. Please use the following guidelines:
1. It is recommended to enable the :ref:`CONFIG_SPI_FLASH_YIELD_DURING_ERASE` option to allow the scheduler to re-schedule during erasing flash memory. Besides, following parameters can also be used.
- Increase :ref:`CONFIG_SPI_FLASH_ERASE_YIELD_TICKS` or decrease :ref:`CONFIG_SPI_FLASH_ERASE_YIELD_DURATION_MS` in menuconfig.
- You can also increase :ref:`CONFIG_ESP_TASK_WDT_TIMEOUT_S` in menuconfig for a larger watchdog timeout period. However, with larger watchdog timeout period, previously detected timeouts may no longer be detected.
2. Please be aware of the consequences of enabling the :ref:`CONFIG_ESP_TASK_WDT_PANIC` option when doing long-running SPI flash operations which triggers the panic handler when it times out. However, this option can also help dealing with unexpected exceptions in your application. Please decide whether this is needed to be enabled according to actual condition.
3. During your development, please carefully review the actual flash operation according to the specific requirements and time limits on erasing flash memory of your projects. Always allow reasonable redundancy based on your specific product requirements when configuring the flash erasing timeout threshold, thus improving the reliability of your product.
.. _spi-flash-implementation-details:
Implementation Details
----------------------
In order to perform some flash operations, it is necessary to make sure that both CPUs are not running any code from flash for the duration of the flash operation:
- In a single-core setup, the SDK needs to disable interrupts or scheduler before performing the flash operation.
- In a dual-core setup, the SDK needs to make sure that both CPUs are not running any code from flash.
When SPI flash API is called on CPU A (can be PRO or APP), start the ``spi_flash_op_block_func`` function on CPU B using the ``esp_ipc_call`` API. This API wakes up a high priority task on CPU B and tells it to execute a given function, in this case, ``spi_flash_op_block_func``. This function disables cache on CPU B and signals that the cache is disabled by setting the ``s_flash_op_can_start`` flag. Then the task on CPU A disables cache as well and proceeds to execute flash operation.
While a flash operation is running, interrupts can still run on CPUs A and B. It is assumed that all interrupt code is placed into RAM. Once the interrupt allocation API is added, a flag should be added to request the interrupt to be disabled for the duration of a flash operations.
Once the flash operation is complete, the function on CPU A sets another flag, ``s_flash_op_complete``, to let the task on CPU B know that it can re-enable cache and release the CPU. Then the function on CPU A re-enables the cache on CPU A as well and returns control to the calling code.
Additionally, all API functions are protected with a mutex (``s_flash_op_mutex``).
In a single core environment (:ref:`CONFIG_FREERTOS_UNICORE` enabled), you need to disable both caches, so that no inter-CPU communication can take place.
.. only:: SOC_SPI_MEM_SUPPORT_AUTO_SUSPEND
.. _internal_memory_saving_for_flash_driver:
Internal Memory Saving For Flash Driver
---------------------------------------
The ESP-IDF provides options to optimize the usage of IRAM by selectively placing certain functions into flash memory via turning off :ref:`CONFIG_SPI_FLASH_PLACE_FUNCTIONS_IN_IRAM`. It allows SPI flash operation functions to be executed from flash memory instead of IRAM. Thus it saves IRAM memory for other significant time-critical functions or tasks.
However, this has some implications for flash itself. Functions placed into flash memory may have slightly increased execution times compared to those placed in IRAM. Applications with strict timing requirements or those heavily reliant on SPI flash operations may need to evaluate the trade-offs before enabling this option.
.. note::
:ref:`CONFIG_SPI_FLASH_PLACE_FUNCTIONS_IN_IRAM` should not be turned off when :ref:`CONFIG_SPI_FLASH_AUTO_SUSPEND` is not enabled, otherwise it will cause critical crash. As for flash suspend feature, please refer to :ref:`auto-suspend` for more information.
Resource Consumption
^^^^^^^^^^^^^^^^^^^^
Use the :doc:`/api-guides/tools/idf-size` tool to check the code and data consumption of the SPI flash driver. The following are the test results under 2 different conditions (using ESP32-C2 as an example):
**Note that the following data are not exact values and are for reference only; they may differ on different chip models.**
Resource consumption when :ref:`CONFIG_SPI_FLASH_PLACE_FUNCTIONS_IN_IRAM` is enabled:
.. list-table:: Resource Consumption
:widths: 20 10 10 10 10 10 10 10 10 10
:header-rows: 1
* - Component Layer
- Total Size
- DIRAM
- .bss
- .data
- .text
- Flash Code
- .text
- Flash Data
- .rodata
* - hal
- 4624
- 4038
- 0
- 0
- 4038
- 586
- 586
- 0
- 0
* - spi_flash
- 14074
- 11597
- 82
- 1589
- 9926
- 2230
- 2230
- 247
- 247
Resource consumption when :ref:`CONFIG_SPI_FLASH_PLACE_FUNCTIONS_IN_IRAM` is disabled:
.. list-table:: Resource Consumption
:widths: 20 10 10 10 10 10 10 10 10 10
:header-rows: 1
* - Component Layer
- Total Size
- DIRAM
- .bss
- .data
- .text
- Flash Code
- .text
- Flash Data
- .rodata
* - hal
- 4632
- 0
- 0
- 0
- 0
- 4632
- 4632
- 0
- 0
* - spi_flash
- 14569
- 1399
- 22
- 429
- 948
- 11648
- 11648
- 1522
- 1522
Related Documents
------------------
.. list::
- :doc:`spi_flash_optional_feature`
- :doc:`spi_flash_concurrency`
:CONFIG_ESP_ROM_HAS_SPI_FLASH: - :doc:`spi_flash_idf_vs_rom`
.. toctree::
:hidden:
spi_flash_idf_vs_rom
API Reference - SPI Flash
-------------------------
.. include-build-file:: inc/esp_flash_spi_init.inc
.. include-build-file:: inc/esp_flash.inc
.. include-build-file:: inc/spi_flash_mmap.inc
.. include-build-file:: inc/spi_flash_types.inc
.. include-build-file:: inc/esp_flash_err.inc
.. include-build-file:: inc/esp_spi_flash_counters.inc
API Reference - Flash Encrypt
-----------------------------
.. include-build-file:: inc/esp_flash_encrypt.inc
@@ -0,0 +1,125 @@
.. _concurrency-constraints-flash:
Concurrency Constraints for Flash on SPI0/1
===========================================
:link_to_translation:`zh_CN:[中文]`
The SPI0/1 bus is shared between the cache and the SPI1 peripheral (controlled by the drivers including this SPI Flash driver). Operations to SPI1 may cause significant influence to the cache and hence the whole system. There are no such constraints and impacts for flash chips connected to other SPI buses, which are not covered in this document.
There are three kinds of activities that can happen on SPI0/1 bus:
- Flash writing operations (via SPI1). For example, erasing, page programming, or status register writing commands (e.g., ``SE``, ``PP``, and ``WRSR``). During these commands, the flash is in a unreadable state. The CPU and the cache have to wait until the writing command is completed. APIs below can trigger writing commands:
- Calling non_encrypted SPI flash write API (:cpp:func:`esp_flash_write`, :cpp:func:`esp_flash_erase_region`, etc.)
- Calling :cpp:func:`esp_flash_write_encrypted`
- Short operations (via SPI1, includes non-writing flash commands). APIs below can trigger short operations:
.. list::
- Calling non_encrypted SPI flash read API (:cpp:func:`esp_flash_read`, etc.)
:esp32: - Or other drivers on SPI1 bus for user defined SPI operations (enable experimental feature :ref:`CONFIG_SPI_FLASH_SHARE_SPI1_BUS`)
- Cache read (via SPI0). Following API and operations can trigger cache read:
- Code execution from SPI Flash or PSRAM
- Fetch static data of .data/.rodata/.bss segment from SPI Flash or PSRAM
- All other read/write operation to the PSRAM via the heap or `esp_himem`
- Read from area mapped to SPI Flash, includes:
- mmap-like functions: :cpp:func:`spi_flash_mmap`, :cpp:func:`spi_flash_mmap_pages`, :cpp:func:`esp_mmu_map`, :cpp:func:`bootloader_mmap`, and :cpp:func:`esp_partition_mmap`.
- Functions relying on :cpp:func:`spi_flash_mmap`: :cpp:func:`esp_partition_find`, :cpp:func:`esp_partition_register_external`.
- Encrypted flash read/write APIs :cpp:func:`esp_flash_read_encrypted` and :cpp:func:`esp_flash_write_encrypted` (on esp32, or for data validation).
.. only:: esp32
Caches are disabled during all SPI1 operations. Most tasks will be disabled, and access to Flash/PSRAM is forbidden. See :ref:`cache_disabled` for more details.
.. only:: not esp32
All SPI flash APIs are exclusive to each other by some internal mutex provided by the driver.
For all SPI1 operations (read/write), caches are disabled during these operations by default. Most tasks will be disabled, and access to Flash/PSRAM is forbidden. See :ref:`cache_disabled` for more details.
.. only:: SOC_SPI_MEM_SUPPORT_AUTO_SUSPEND or SOC_SPIRAM_XIP_SUPPORTED
Some options help reduce the impact of cache disabling. The impact of write operations differs between modes.
.. only:: SOC_SPIRAM_XIP_SUPPORTED
- **XIP from PSRAM**: In this mode, all segments that were previously executed from Flash are loaded and executed from PSRAM instead. As a result, the cache can remain enabled while the flash is being erased or written, and code execution is not affected by write operations in most cases. See :ref:`xip_from_psram` for more details.
.. only:: SOC_SPI_MEM_SUPPORT_AUTO_SUSPEND
- **Auto Suspend**: In this mode, when cache access to flash misses during flash erase/write operations, it is allowed to suspend the flash writing to read from it transparently with some latency. As a result, caches are kept enabled and code execution won't be affected so much during writing operations.
This is an optional feature that depends on special SPI Flash models, hence disabled by default. See :doc:`spi_flash_optional_feature` and :ref:`auto-suspend` for more details.
See :ref:`esp_flash_os_func` and :ref:`spi_bus_lock` for the detailed information of software implementation.
.. _cache_disabled:
Cache Disabled (Default)
------------------------
.. only:: esp32
Caches are disabled during SPI1 operations. All SPI1 operations will automatically and transparently disable the caches.
.. only:: not esp32
By default, caches are disabled during SPI1 operations (read/write). All SPI1 operations will automatically and transparently disable the caches.
.. only:: SOC_HP_CPU_HAS_MULTIPLE_CORES
When the caches are disabled, all non-IRAM-safe interrupts will be disabled, and all other tasks are suspended. The other core will be polling in a busy loop. Only IRAM-safe interrupt handlers will be executed. These will be restored when the Flash operation completes.
.. only:: not SOC_HP_CPU_HAS_MULTIPLE_CORES
When the caches are disabled, all non-IRAM-safe interrupts will be disabled, and all other tasks are suspended. Only IRAM-safe interrupt handlers will be executed. These will be restored when the Flash operation completes.
See :ref:`iram-safe-interrupt-handlers` for information on how to prevent an interrupt handler from being disabled when the cache is disabled.
When the cache is disabled, all CPUs should execute code and access data only from internal RAM. For differences between internal RAM (e.g., IRAM, DRAM) and flash cache, please refer to the :ref:`application memory layout <memory-layout>` documentation.
.. _iram-safe-interrupt-handlers:
IRAM-Safe Interrupt Handlers
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
For interrupt handlers which need to execute when the cache is disabled (e.g., for low latency operations), set the ``ESP_INTR_FLAG_IRAM`` flag when the :doc:`interrupt handler is registered </api-reference/system/intr_alloc>`.
You must ensure that all data and functions accessed by these interrupt handlers, including the ones that handlers call, are located in IRAM or DRAM. See :ref:`how-to-place-code-in-iram`.
If a function or symbol is not correctly put into IRAM/DRAM, and the interrupt handler reads from the flash cache during a flash operation, it will cause a crash. This may be due to an Illegal Instruction exception (for code which should be in IRAM) or garbage data being read (for constant data which should be in DRAM).
.. note::
When working with strings in ISRs, it is not advised to use ``printf`` and other output functions. For debugging purposes, use :cpp:func:`ESP_DRAM_LOGE` and similar macros when logging from ISRs. Make sure that both ``TAG`` and format string are placed into ``DRAM`` in that case.
Non-IRAM-Safe Interrupt Handlers
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If the ``ESP_INTR_FLAG_IRAM`` flag is not set when registering, the interrupt handler will not be executed when the caches are disabled. Once the caches are restored, the non-IRAM-safe interrupts will be re-enabled. After this moment, the interrupt handler will run normally again. This means that as long as caches are disabled, the corresponding hardware events will not occur.
.. only:: SOC_DMA_CAN_ACCESS_FLASH
When DMA Read Data from Flash
-----------------------------
The Flash device doesn't allow reading while it is being erased/programmed, even when the data is not in the region being erased/programmed.
When the flash is being erased/programmed, the Flash data read by DMA is unpredictable. It is recommended to stop DMA access to Flash before erasing or writing to it. If DMA cannot be stopped (for example, the LCD needs to continuously refresh image data stored in Flash), it is advisable to copy such data to PSRAM or internal SRAM.
.. only:: SOC_SPI_MEM_SUPPORT_AUTO_SUSPEND
.. include:: auto_suspend.inc
.. only:: SOC_SPIRAM_XIP_SUPPORTED
.. include:: xip_from_psram.inc
@@ -0,0 +1,44 @@
SPI Flash API ESP-IDF Version vs Chip-ROM Version
===================================================
:link_to_translation:`zh_CN:[中文]`
.. toctree::
:maxdepth: 1
There is a set of SPI flash drivers in Chip-ROM which you can use by enabling :ref:`CONFIG_SPI_FLASH_ROM_IMPL`. Most of the ESP-IDF SPI flash driver code are in internal RAM, therefore enabling this option frees some internal RAM usage. Note that if you enable this option, this means some SPI flash driver features and bugfixes that are done in ESP-IDF might not be included in the Chip-ROM version.
Feature Supported by ESP-IDF but Not in Chip-ROM
------------------------------------------------
.. list::
- Octal flash chip support. See :ref:`oct-flash-doc` for details.
- 32-bit-address support on flash chips. Note that this feature is an optional feature, please do read :ref:`32-bit-flash-doc` for details.
- TH flash chip support.
- Kconfig option :ref:`CONFIG_SPI_FLASH_CHECK_ERASE_TIMEOUT_DISABLED`.
- :ref:`CONFIG_SPI_FLASH_VERIFY_WRITE`, enabling this option helps you detect bad writing.
- :ref:`CONFIG_SPI_FLASH_LOG_FAILED_WRITE`, enabling this option prints the bad writing.
- :ref:`CONFIG_SPI_FLASH_WARN_SETTING_ZERO_TO_ONE`, enabling this option checks if you are writing zero to one.
- :ref:`CONFIG_SPI_FLASH_DANGEROUS_WRITE`, enabling this option checks for flash programming to certain protected regions like bootloader, partition table or application itself.
- :ref:`CONFIG_SPI_FLASH_ENABLE_COUNTERS`, enabling this option to collect performance data for ESP-IDF SPI flash driver APIs.
- :ref:`CONFIG_SPI_FLASH_AUTO_SUSPEND`, enabling this option to automatically suspend or resume a long flash operation when short flash operation happens. Note that this feature is an optional feature, please do read :ref:`auto-suspend-intro` for more limitations.
- :ref:`CONFIG_ESP_SLEEP_SET_FLASH_DPD`, enabling this option allows the flash to enter deep power-down (DPD) mode during sleep for lower power consumption. Note that this feature is an optional feature, please do read :ref:`deep-power-down-mode` for more limitations.
:ESP_ROM_HAS_SPI_FLASH_MMAP and SOC_SPIRAM_XIP_SUPPORTED and not esp32s3: - :ref:`CONFIG_SPIRAM_XIP_FROM_PSRAM`, enabling this option allows you to use external PSRAM as instruction cache and read-only data cache. Some functions in the ROM don't support this usage, and an ESP-IDF version of these functions is provided.
:esp32s3: - :ref:`CONFIG_SPIRAM_FETCH_INSTRUCTIONS` and :ref:`CONFIG_SPIRAM_RODATA`, enabling these options allows you to use external PSRAM as instruction cache and read-only data cache. Some functions in the ROM don't support this usage, and an ESP-IDF version of these functions is provided.
Bugfixes Introduced in ESP-IDF but Not in Chip-ROM
--------------------------------------------------
.. list::
- Detected flash physical size correctly, for larger than 256 MBit flash chips. (Commit ID: b4964279d44f73cce7cfd5cf684567fbdfd6fd9e)
:esp32c3: - Improved SPI1 CS setup timing, otherwise issue may happen on ZB32Q128. (Commit ID: 08f1bbe0c75382f1702e40c941e93314285105d4)
:esp32s3: - Fixed issue that 4-line flash encryption can not work normally when 8-line PSRAM enabled. (Commit ID: 683d92bc884e0f2a7eebea40a551cf05f0c28256)
:esp32s2: - Fixed issue that only 4 MB virtual address ranges can be mapped to read-only data on flash.
:esp32s3: - Fixed issue that only 128 KB virtual address ranges can be mapped to instructions on flash.
:esp32s3: - Fixed issue that only 16 MB virtual address ranges can be mapped to read-only data on flash.
:esp32c3: - Fixed issue that only 128 KB virtual address ranges can be mapped to instructions on flash.
:esp32c2: - Fixed issue that only at most 128 KB virtual address ranges can be mapped to instructions on flash.
- Fixed issue that address range may escape from checking for erasing and writing function when their sum overflows 32-bit boundary.
@@ -0,0 +1,213 @@
Optional Features for Flash
===========================
:link_to_translation:`zh_CN:[中文]`
Some features are not supported on all ESP chips and flash chips. You can check the list below for more information:
- :ref:`auto-suspend-intro`
- :ref:`flash-unique-id`
- :ref:`high-performance-mode`
- :ref:`deep-power-down-mode`
- :ref:`32-bit-flash-doc`
- :ref:`oct-flash-doc`
.. note::
When Flash optional features listed in this page are used, aside from the capability of ESP chips and ESP-IDF version you are using, you will also need to make sure these features are supported by flash chips used:
- If you are using an official Espressif modules/SiP, please make sure that they support the above features by referring to the `datasheet <https://www.espressif.com/en/support/download/documents/modules>`__. Otherwise, please contact `Espressif's business team <https://www.espressif.com/en/contact-us/sales-questions>`_ to know if we can supply such products for you.
- If you are making your own modules with your own bought flash chips and need features listed above, please contact your vendor to see if they support those features, and make sure that the chips can be supplied continuously.
.. attention::
This document only shows that ESP-IDF code has supported the features of those flash chips. It is not a list of stable flash chips certified by Espressif. If you build your own hardware with your own brought flash chips (even with features listed in this page), you need to validate the reliability of flash chips yourself.
.. _auto-suspend-intro:
Auto Suspend & Resume
---------------------
This feature is supported on all Espressif chips except ESP32 and ESP32-S2.
.. only:: SOC_SPI_MEM_SUPPORT_AUTO_SUSPEND
List of flash chips that support this feature:
1. XM25xxD series
2. GD25QxxE series
3. FM25Q32
.. attention::
There are multiple limitations about the auto-suspend feature, please do read :ref:`auto-suspend` for more information before you enable this feature.
.. _flash-unique-id:
Flash Unique ID
---------------
This feature is supported on all Espressif chips.
Unique ID is not flash id, which means flash has 64-bit unique ID for each device. The instruction to read the unique ID (4Bh) accesses a factory-set read-only 64-bit number that is unique to each flash device. This ID number helps you to recognize each device. Not all flash vendors support this feature. If you try to read the unique ID on a chip which does not have this feature, the behavior is not determined.
List of flash chips that support this feature:
1. ISSI
2. GD
3. TH
4. FM
5. Winbond
6. XMC
7. BOYA
.. _high-performance-mode:
High Performance Mode of QSPI Flash Chips
-----------------------------------------
This feature is only supported on ESP32-S3 for now.
The support for ESP32-S2, ESP32-C3, ESP32-C6, ESP32-H2, and ESP32-P4 may be added in the future.
.. note::
This section is provided for QSPI flash chips. Octal flash used on ESP-chips supports High Performance mode by default so far, please refer to :ref:`oct-flash-doc` for the list of supported octal flash chips.
.. only:: esp32s3
High Performance mode (HPM) means that the SPI1 and flash chip works under high frequency. Usually, when the operating frequency of the flash is greater than 80 MHz, it is considered that the flash works under HPM.
As far as we acknowledged, there are more than three strategies for HPM in typical SPI flash parts. For some flash chips, HPM is controlled by dummy cycle (DC) bit in the registers, while for other chips, it can be controlled by other bits (like HPM bit) in the register, or some special command. The difference in strategies requires the driver to explicitly add support for each chip.
.. attention::
It is hard to create several strategies to cover all situations, so all flash chips using HPM need to be supported explicitly. Therefore, if you try to use a flash not listed in :ref:`hpm_dc_support_list`, it might cause some error. So, when you try to use the flash chip beyond supported list, please test properly.
Moreover, when the `DC adjustment` strategy is adopted by the flash chip, the flash remains in a state in which DC is different from the default value after a software reset. The sub mode of HPM that adjusts the DC to run at higher frequency in the application is called `HPM-DC`. `HPM-DC` feature needs a feature `DC Aware` to be enabled in the bootloader. Otherwise different DC value will forbid the 2nd bootloader from being boot up after reset.
To enable High Performance mode:
1. De-select :ref:`CONFIG_ESPTOOLPY_OCT_FLASH` and :ref:`CONFIG_ESPTOOLPY_FLASH_MODE_AUTO_DETECT`. HPM is not used for Octal flash, enabling related options may bypass HPM functions.
2. Enable ``CONFIG_SPI_FLASH_HPM_ENA`` option.
3. Switch flash frequency to HPM ones. For example, ``CONFIG_ESPTOOLPY_FLASHFREQ_120M``.
4. Make sure the config option for `HPM-DC` feature (under ``CONFIG_SPI_FLASH_HPM_DC`` choices) is selected correctly according to whether the bootloader supports `DC Aware`.
- If bootloader supports `DC Aware`, select ``CONFIG_SPI_FLASH_HPM_DC_AUTO``. This allows the usage of flash chips that adopted `DC adjustment` strategy.
- If bootloader doesn't support `DC Aware`, select ``CONFIG_SPI_FLASH_HPM_DC_DISABLE``. It avoids consequences caused by running `HPM-DC` with non-DC-aware bootloaders. But please avoid using flash chips that adopts `DC adjustment` strategy if ``CONFIG_SPI_FLASH_HPM_DC_DISABLE`` is selected. See list of flash models that adpot DC strategy below.
Check whether the bootloader supports `DC Aware` in the following way:
- If you are starting a new project, it's suggested to enable `DC Aware` by selecting :ref:`CONFIG_BOOTLOADER_FLASH_DC_AWARE` option in the bootloader menu. Please note that, you won't be able to modify this option via OTA, because the support is in the bootloader.
- If you are working on an existing project and want to update `HPM-DC` config option in the app via OTA, check the sdkconfig file used to build your bootloader (upgrading ESP-IDF version may make this file different from the one used by bootloader to build):
- For latest version (v4.4.7+, v5.0.7+, v5.1.4+, v5.2 and above), if :ref:`CONFIG_BOOTLOADER_FLASH_DC_AWARE` is selected, the bootloader supports `DC Aware`.
- For other versions (v4.4.4-v4.4.6, v5.0-v5.0.6, and v5.1-v5.1.3), if ``CONFIG_ESPTOOLPY_FLASHFREQ_120M`` is selected, the bootloader supports `DC Aware`. In this case, enable :ref:`CONFIG_BOOTLOADER_FLASH_DC_AWARE` to confirm this (though it will not affect bootloader in devices in the field).
- For versions below v4.4.4, the bootloader doesn't support `DC Aware`.
.. _hpm_dc_support_list:
Quad Flash HPM support list
^^^^^^^^^^^^^^^^^^^^^^^^^^^
Flash chips that don't need HPM-DC:
1. GD25Q64C (ID: 0xC84017)
2. GD25Q32C (ID: 0xC84016)
3. ZB25VQ32B (ID: 0x5E4016)
4. GD25LQ255E (ID: 0xC86019)
Following flash chips also have HPM feature, but requires the bootloader to support `DC Aware`:
1. GD25Q64E (ID: 0xC84017)
2. GD25Q128E (ID: 0xC84018)
3. XM25QH64C (ID: 0x204017)
4. XM25QH128C (ID: 0x204018)
.. _deep-power-down-mode:
SPI Flash Deep Power-Down Mode
-------------------------------
Currently, only ESP32-H21, ESP32-H4 and ESP32-P4(version less v3) support this feature.
This feature is not yet supported on other ESP32 series chips. If you have this requirement, you may submit a request to Espressif Systems officially.
Deep Power-Down (DPD) mode is a power-saving mode supported by most SPI flash chips. Compared to the standby mode (where the chip select signal remains asserted), SPI flash consumes significantly less power in DPD mode—typically reducing current by 10 µA or more.
If you plan to use a customized SPI flash model, or if you use other ESP32-series chips, please make sure to consult the corresponding SPI flash datasheet or contact Espressif directly.
.. _32-bit-flash-doc:
32-bit Address Support of QSPI Flash Chips
------------------------------------------
This feature is supported on all Espressif chips (see restrictions to application below).
.. note::
This section is provided for QSPI flash chips. The 32-bit address support of Octal flash chips are considered as part of the Octal flash support. Please refer to :ref:`oct-flash-doc` for the list of supported octal flash chips.
Most NOR flash chips used by Espressif chips use 24-bits address, which can cover 16 MB memory. However, for larger memory (usually equal to or larger than 32 MB), flash uses a 32-bits address to address memory region higher than 16 MB. Unfortunately, 32-bits address chips have vendor-specific commands, so we need to support the chips one by one.
List of Flash chips that support this feature:
1. W25Q256
2. GD25Q256
3. XM25QH256D
Restrictions
^^^^^^^^^^^^
.. only:: not SOC_SPI_MEM_SUPPORT_CACHE_32BIT_ADDR_MAP
.. important::
The part of flash above 16 MB can be used for data storage, for example using a file system.
Mapping data/instructions to 32-bit physical address space (so as to be accessed by the CPU) needs the support of MMU. However {IDF_TARGET_NAME} doesn't support this feature. Only ESP32-S3 and ESP32-P4 supports this up to now.
.. only:: SOC_SPI_MEM_SUPPORT_CACHE_32BIT_ADDR_MAP
For Quad flash chips, by default, the part of flash above 16 MB can be used for data storage, for example using a file system.
*Experimental Feature*: To enable full support for Quad flash addresses above 16MB (for both code execution and data access), enable this experimental feature by setting below options to ``y``:
- :ref:`CONFIG_IDF_EXPERIMENTAL_FEATURES`
- :ref:`CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH`
Please note that, this option is experimental, which means that it can not be used on all Quad flash chips stably. For more information, please contact `Espressif's business team <https://www.espressif.com/en/contact-us/sales-questions>`_.
For Octal flash chips, this feature is enabled by default if :ref:`CONFIG_ESPTOOLPY_OCT_FLASH` is enabled.
.. _oct-flash-doc:
OPI Flash Support
-----------------
This feature is only supported on ESP32-S3 for now.
OPI flash means that the flash chip supports octal peripheral interface, which has octal I/O pins. Different octal flash has different configurations and different commands. Hence, it is necessary to carefully check the support list.
.. only:: esp32s3
.. note::
To know how to configure menuconfig for a board with different flash and PSRAM, please refer to :ref:`flash-psram-configuration`.
List of flash chips that support this feature:
1. MX25UM25645G
2. MX25UM12345G
@@ -0,0 +1,251 @@
Overriding Default Chip Drivers
===============================
:link_to_translation:`zh_CN:[中文]`
.. warning::
Customizing SPI Flash Chip Drivers is considered an "expert" feature. The user should only do so at their own risk (see the notes below).
During the SPI Flash driver's initialization (i.e., :cpp:func:`esp_flash_init`), there is a chip detection step during which the driver iterates through a Default Chip Driver List and determine which chip driver can properly support the currently connected flash chip. The Default Chip Drivers are provided by the ESP-IDF, thus are updated in together with each ESP-IDF version. However ESP-IDF also allows to customize their own chip drivers.
Note the following points when customizing chip drivers:
1. You may need to rely on some non-public ESP-IDF functions, which have slight possibility to change between ESP-IDF versions. On the one hand, these changes may be useful bug fixes for your driver, on the other hand, they may also be breaking changes (i.e., breaks your code).
2. Some ESP-IDF bug fixes to other chip drivers are not automatically applied to your own custom chip drivers.
3. If the protection of flash is not handled properly, there may be some random reliability issues.
4. If you update to a newer ESP-IDF version that has support for more chips, you will have to manually add those new chip drivers into your custom chip driver list. Otherwise the driver will only search for the drivers in custom list you provided.
Steps For Creating Custom Chip Drivers and Overriding the ESP-IDF Default Driver List
-------------------------------------------------------------------------------------
Bootloader Flash Driver
^^^^^^^^^^^^^^^^^^^^^^^
To implement the bootloader driver so that the ESP chip can boot successfully, please read this part carefully. Wrong code will make ESP chip fail to boot or introduce random issues. Issues in the bootloader cannot be fixed via OTA. Please fully comprehend below parts before making any changes.
Currently, there are two parts to override in bootloader: ``bootloader_flash_unlock`` and ``bootloader_flash_qe_support_list`` (and its size). See sections below.
Ensure that all commands are sent and recognized successfully. If the flash receives an unknown command, it may silently ignore it and continue operating without re-enabling protection. This could lead to mis-writing, erasing, or locking issues.
All bootloader flash driver functions reside in a component for the bootloader, which should be placed under ``bootloader_components``. For example, :example:`storage/custom_flash_driver/bootloader_components/`. Only the components under ``bootloader_components`` will be added into the component list of bootloader automatically.
There is a slight difference in the ``CMakeLists.txt`` of the component. The customized flash functions take effect by overriding existing weak ones. In order to link them into the bootloader, use the linker argument `-u` against the overriding functions in the ``CMakeLists.txt``. See :example_file:`storage/custom_flash_driver/bootloader_components/bootloader_flash/CMakeLists.txt`.
Bootloader flash unlock
~~~~~~~~~~~~~~~~~~~~~~~
``bootloader_flash_unlock`` function is used to unlock flash write protection, which means once the flash is locked by accident, IDF is able to unlock it. By default, the unlock function will clear all bits in status registers and configuration registers except QE bit.
Please don not modify the QE bit; otherwise, your chip will not be able to run under QUAD modes.
Please check the default case below. If your flash have different behavior from that, some modifications are required.
You can start from copying ``bootloader_flash_unlock_default`` from ``components/bootloader_support/bootloader_flash/src/bootloader_flash.c`` into a file in your customized component folder before modifying it. The function should be renamed to ``bootloader_flash_unlock`` to override the IDF bootloader function. For example, :example_file:`storage/custom_flash_driver/bootloader_components/bootloader_flash/bootloader_flash_unlock_custom.c`.
After that, your implementation of ``bootloader_flash_unlock`` will be linked instead of IDF default one, which has been declared as a weak symbol. So when IDF second stage bootloader boots, your implementation of ``bootloader_flash_unlock`` will be executed. If you want to make sure your function is truly linked, you can enable ``CONFIG_BOOTLOADER_LOG_LEVEL_DEBUG`` and you will see a ``Using overridden bootloader_flash_unlock`` log.
The default function includes three common behaviors, please check first:
- Case 1 (**Default**): Check if the QE bit in your flash chip is located at bit 1 in status register-2, written by the second byte after command 01H (WRSR). If so, this matches the default behavior, and no further action is needed.
- Case 2: Check if the QE bit in your flash chip is located at bit 1 in status register-2, written by command 31H (and your flash does not support Case 1, which uses 01H + 2 bytes to write it). If this is the case, add your chip ID to the function :cpp:func:`is_qe_bit_in_bit1_at_reg2`. For example:
.. code-block:: c
IRAM_ATTR bool is_qe_bit_in_bit1_at_reg2(const esp_rom_spiflash_chip_t* chip)
{
bool ret = true;
switch (chip->device_id) {
/****GD series***/
case 0xC84016:
case 0xC84017:
case 0xC84018:
break;
/**** your flash series ****/
case /*your flash ID*/:
break;
default:
ret = false;
}
return ret;
}
- Case 3: Check whether the QE bit in your flash chip is located at bit 6 in status register-1, which can be written by command 01H. If so, you need to add your chip ID in function :cpp:func:`is_qe_bit_in_bit6_at_reg1`. For example:
.. code-block:: c
IRAM_ATTR bool is_qe_bit_in_bit6_at_reg1(const esp_rom_spiflash_chip_t* chip)
{
bool ret = true;
switch (chip->device_id) {
/***ISSI series***/
case 0x9D4016:
case 0x9D4017:
break;
/***MXIC series***/
case 0xC22016:
case 0xC22017:
break;
/****your flash series***/
case /*your flash ID*/:
break;
default:
ret = false;
}
return ret;
}
- Case 4: If the three cases mentioned above cannot cover your usage, please add another `if` block and the corresponding behavior function in function ``bootloader_flash_unlock``. The determination function in the `if` block is suggested to be named after ``is_qe_bit_in_bit_x_at_reg_x_`` (x stands for behavior). Refer to example :example_file:`storage/custom_flash_driver/bootloader_components/bootloader_flash/bootloader_flash_unlock_custom.c`.
Bootloader Flash Quad Mode Support
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Pointer ``bootloader_flash_qe_support_list`` is used for iteration in bootloader for selecting the correct behavior to enable flash chip work under QUAD mode. To operate the flash in QUAD mode, enabling the QE bit in the flash status register is necessary. If you want to use your flash chip under QUAD mode, please read this part and implement the necessary changes.
* Case 1: If QE bit is placed at bit 1 in status register-2 to be written by command 31H, nothing needs to be done because this is the default behavior.
* Case 2: If QE bit on your chip is placed at different places, or need to use different command. Please add your own support.
To add your own support, you can start from copying the ``bootloader_flash_qe_support_list_user`` function from `flash_qio_mode.c <https://github.com/espressif/esp-idf/blob/master/components/bootloader_support/bootloader_flash/src/flash_qio_mode.c>`_ into your file, renaming to ``bootloader_flash_qe_support_list``. Please also define a corresponding ``bootloader_flash_qe_list_count``.
Add the details of your flash chip, including the chip's name, ID, and the functions to write registers, into ``bootloader_flash_qio_support_list``. You can also reuse the existing functions like ``bootloader_read_status_8b_rdsr``.
If the existing functions do not fully meet your needs, you can define your own functions using ``bootloader_execute_flash_command``, such as ``bootloader_read_status_otp_mode_8b`` and ``bootloader_write_status_otp_mode_8b``. For example, see `bootloader_flash_custom.c <https://github.com/espressif/esp-flash-drivers/tree/main/esp_flash_nor/bootloader_flash_driver/bootloader_flash_custom.c>`_.
Put everything together:
.. code-block:: c
const DRAM_ATTR bootloader_qio_info_t bootloader_flash_qe_support_list_user[] = {
/* Manufacturer, mfg_id, flash_id, id mask, Read Status, Write Status, QIE Bit */
{ "MXIC", 0xC2, 0x2000, 0xFF00, bootloader_read_status_8b_rdsr, bootloader_write_status_8b_wrsr, 6 },
{ "ISSI", 0x9D, 0x4000, 0xCF00, bootloader_read_status_8b_rdsr, bootloader_write_status_8b_wrsr, 6 },
{ "WinBond", 0xEF, 0x4000, 0xFF00, bootloader_read_status_16b_rdsr_rdsr2, bootloader_write_status_16b_wrsr, 9 },
{ "GD", 0xC8, 0x4000, 0xFFFF, bootloader_read_status_16b_rdsr_rdsr2, bootloader_write_status_16b_wrsr, 9 },
{ "XM25QU64A", 0x20, 0x3817, 0xFFFF, bootloader_read_status_8b_xmc25qu64a, bootloader_write_status_8b_xmc25qu64a, 6 },
{ "TH", 0xCD, 0x6000, 0xFF00, bootloader_read_status_16b_rdsr_rdsr2, bootloader_write_status_16b_wrsr, 9 },
{ "EON", 0x1C, 0x7000, 0xFF00, bootloader_read_status_otp_mode_8b, bootloader_write_status_otp_mode_8b, 6 },
/* Final entry is default entry, if no other IDs have matched
This approach works for chips including:
GigaDevice (mfg ID 0xC8, flash IDs including 4016),
FM25Q32 (QOUT mode only, mfg ID 0xA1, flash IDs including 4016)
BY25Q32 (mfg ID 0x68, flash IDs including 4016)
*/
{ NULL, 0xFF, 0xFFFF, 0xFFFF, bootloader_read_status_8b_rdsr2, bootloader_write_status_8b_wrsr2, 1 },
};
const DRAM_ATTR bootloader_qio_info_t* bootloader_flash_qe_support_list = bootloader_flash_qe_support_list_user;
uint8_t DRAM_ATTR bootloader_flash_qe_list_count = (sizeof(bootloader_flash_qe_support_list_user) / sizeof(bootloader_qio_info_t));
App Flash Driver
^^^^^^^^^^^^^^^^
Generic Flash Driver
~~~~~~~~~~~~~~~~~~~~
The flash driver in the application is used to read, write, erase, and save data. It also supports some advanced features like OTA. Below is a guide on how to customize the driver for your specific flash model.
- Step 1: The last item of `default_registered_chips` should be the `generic chip driver <https://github.com/espressif/esp-idf/blob/master/components/spi_flash/spi_flash_chip_generic.c>`_. If your flash chip does not match any of the chip drivers listed above, it will use the generic driver. Check for any differences in behavior between your flash and the generic driver, including but not limited to different commands, dummy cycles, data bytes, and status registers.
- Step 2: If you have found something different from the generic driver, you need to implement your own chip driver. Create a new file named ``spi_flash_chip_<vendor>.c`` to implement the specific behavior, and copy the ``esp_flash_chip_generic`` structure into it as a starting point. Remember to include ``esp_flash_chips/spi_flash_chip_generic.h``. Here is an example `esp_flash_nor <https://github.com/espressif/esp-flash-drivers/tree/main/esp_flash_nor/>`_.
.. note::
The chip driver header files are located in the ``esp_flash_chips/`` directory (e.g., ``components/spi_flash/include/esp_flash_chips/``). These headers are **semi-public** - they are intended for expert users who need to implement custom chip drivers, but they are **not considered stable API** and may change without notice.
- Step 3: Implement the functions with difference and point to them from the ``spi_flash_chip_t``. Note: if some behavior of your flash is the same as the generic one, retain the generic driver functions without customization. Only implement the parts that differ. Here is an example:
.. important::
Flash work for suspend (i.e., enabling :ref:`CONFIG_SPI_FLASH_AUTO_SUSPEND`) should be tested carefully and systematically due to different flash hardware design. If you want to use the suspend feature for mass production, please contact `Espressif's business team <https://www.espressif.com/en/contact-us/sales-questions>`_.
.. code-block:: c
const DRAM_ATTR spi_flash_chip_t esp_flash_chip_eon = {
.name = chip_name,
.timeout = &spi_flash_chip_generic_timeout, /*<! default behavior*/
.probe = spi_flash_chip_eon_probe, /*<! EON specific */
.reset = spi_flash_chip_generic_reset,
.detect_size = spi_flash_chip_generic_detect_size,
.erase_chip = spi_flash_chip_generic_erase_chip,
.erase_sector = spi_flash_chip_generic_erase_sector,
.erase_block = spi_flash_chip_generic_erase_block,
.sector_size = 4 * 1024,
.block_erase_size = 64 * 1024,
.get_chip_write_protect = spi_flash_chip_generic_get_write_protect,
.set_chip_write_protect = spi_flash_chip_generic_set_write_protect,
.num_protectable_regions = 0,
.protectable_regions = NULL,
.get_protected_regions = NULL,
.set_protected_regions = NULL,
.read = spi_flash_chip_generic_read,
.write = spi_flash_chip_generic_write,
.program_page = spi_flash_chip_generic_page_program,
.page_size = 256,
.write_encrypted = spi_flash_chip_generic_write_encrypted,
.wait_idle = spi_flash_chip_generic_wait_idle,
.set_io_mode = spi_flash_chip_eon_set_io_mode,
.get_io_mode = spi_flash_chip_eon_get_io_mode,
.read_reg = spi_flash_chip_generic_read_reg,
.yield = spi_flash_chip_generic_yield,
.sus_setup = spi_flash_chip_eon_suspend_cmd_conf,
.get_chip_caps = spi_flash_chip_eon_get_caps,
};
.. note::
- When writing your own flash chip driver, you can set your flash chip capabilities through ``spi_flash_chip_***(vendor)_get_caps`` and point the function pointer ``get_chip_caps`` to the ``spi_flash_chip_***_get_caps`` function for protection. The steps are as follows.
1. Please check whether your flash chip has the capabilities listed in ``spi_flash_caps_t`` by checking the flash datasheet.
2. Write a function named ``spi_flash_chip_***(vendor)_get_caps``. Take the example below as a reference (if the flash supports ``suspend`` and ``read unique id``).
3. Point the pointer ``get_chip_caps`` (in ``spi_flash_chip_t``) to ``spi_flash_chip_***_get_caps``.
.. code-block:: c
spi_flash_caps_t spi_flash_chip_***(vendor)_get_caps(esp_flash_t *chip)
{
spi_flash_caps_t caps_flags = 0;
// 32-bit address flash is not supported
caps_flags |= SPI_FLAHS_CHIP_CAP_SUSPEND;
// Read flash unique id
caps_flags |= SPI_FLASH_CHIP_CAP_UNIQUE_ID;
return caps_flags;
}
.. code-block:: c
const spi_flash_chip_t esp_flash_chip_eon = {
// Other function pointers
.get_chip_caps = spi_flash_chip_eon_get_caps,
};
- You can also see how to implement this in the example :example:`storage/custom_flash_driver`. This example demonstrates how to override the default chip driver list.
- Step 4: Create a header file (e.g., ``spi_flash_chip_<vendor>.h``) and declare the structure using `extern`, so that other components or source code can reuse this structure. Wrap all your chip drivers (including source files and their headers) into a chip-driver component. Add the include path and the source file to the component ``CMakeLists.txt``.
- Step 5: The ``linker.lf`` is used to put every chip driver that you are going to use whilst cache is disabled into internal RAM. See :doc:`/api-guides/linker-script-generation` for more details. Make sure this file covers all the source files that you add.
- Step 6: Add a new component in your project, e.g., ``custom_chip_driver``. List your chip object under ``default_registered_chips`` in ``custom_chip_driver/chip_drivers.c``. Then enable the :ref:`CONFIG_SPI_FLASH_OVERRIDE_CHIP_DRIVER_LIST` configuration option. This prevents compilation and linking of the default chip driver list (``default_registered_chips``) provided by ESP-IDF. Instead, the linker searches for the structure of the same name (``default_registered_chips``) that must be provided by you. You can refer to :example_file:`storage/custom_flash_driver/components/custom_chip_driver/chip_drivers.c`.
- Step 7: Build your project, and you will see the new flash driver in use.
High Performance Flash Implementation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The high performance mode operates at frequencies higher than 80 MHz. Please check the datasheet for your flash and to determine which approach can reach to frequencies higher than 80 MHz, as listed in *DC Characteristics* section. Some behavior is already defined in the `high performance file <https://github.com/espressif/esp-idf/blob/master/components/spi_flash/spi_flash_hpm_enable.c>`_ . If your flash meets the specified behavior, extend the list as introduced in the ``bootloader_flash_unlock`` section. If your flash has different behavior, please add the new behavior and override the behavior table ``spi_flash_hpm_enable_list``.
.. important::
Flash with a frequency set above 80 MHz should be tested carefully due to its strict timing requirements. If you want to use the high performance mode feature for mass production, please contact `Espressif's business team <https://www.espressif.com/en/contact-us/sales-questions>`_.
Example
-------
See :example:`storage/custom_flash_driver`, which uses an `external component <https://github.com/espressif/esp-flash-drivers/tree/main/esp_flash_nor>`_ to add support for custom flash and implement customized drivers, while this document mainly focuses on illustrating how to override the IDF driver.
@@ -0,0 +1,17 @@
.. _xip_from_psram:
Executing Code from PSRAM
-------------------------
Select :ref:`CONFIG_SPIRAM_XIP_FROM_PSRAM` config to enable this mode. In this mode, code is executed from PSRAM, and the cache will not be disabled during write APIs in most cases.
In this mode, the flash ``.text`` sections (used for instructions) and the flash ``.rodata`` sections (used for read-only data) will be loaded into PSRAM at startup. The corresponding virtual addresses will be mapped to PSRAM. You do not need to ensure that code and data executed while the flash is being erased or programmed reside in IRAM.
Exception: Cache-Mapped Regions in Flash
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Due to the restriction from SPI Nor Flash parts, access to cache mapped regions in flash (mapped via APIs like spi_flash_mmap) is still not allowed while the flash is being erased/written, regardless of whether the erase/write region and the mapped region overlap. In this case, cache should still be disabled to prevent reading corrupted data from the cache.
To prevent cache disabling, a lock is implemented inside the SPI Flash driver to ensure mutual exclusion between cache mapping and flash writing, and most ESP-IDF APIs that perform flash mapping use this flag. If mmap-like APIs are called by yourself, you can specify this flag :cpp:enumerator:`SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE` to prevent cache disabling. You cannot use this flag in a task that uses ``esp_flash_erase_*`` or ``esp_flash_write`` between ``spi_flash_mmap`` and ``spi_flash_munmap`` (regardless of whether the write region and mapped region overlap), otherwise it will cause a deadlock. See :ref:`blocks_write_flag` for more details about the flag.
If mmap-like APIs are called without this flag, the cache will still be disabled when flash erasing or writing happens.
@@ -0,0 +1,878 @@
SPI Master Driver
=================
:link_to_translation:`zh_CN:[中文]`
SPI Master driver is a program that controls {IDF_TARGET_NAME}'s General Purpose SPI (GP-SPI) peripheral(s) when it functions as a master.
.. only:: esp32
.. note::
SPI1 is not a GP-SPI. SPI Master driver also supports SPI1 but with quite a few limitations, see :ref:`spi_master_on_spi1_bus`.
For more hardware information about the GP-SPI peripheral(s), see **{IDF_TARGET_NAME} Technical Reference Manual** > **SPI Controller** [`PDF <{IDF_TARGET_TRM_EN_URL}#spi>`__].
Terminology
-----------
The terms used in relation to the SPI Master driver are given in the table below.
.. list-table::
:widths: 30 70
:header-rows: 1
* - Term
- Definition
* - Host
- The SPI controller peripheral inside {IDF_TARGET_NAME} initiates SPI transmissions over the bus and acts as an SPI Master.
* - Device
- SPI slave Device. An SPI bus may be connected to one or more Devices. Each Device shares the MOSI, MISO, and SCLK signals but is only active on the bus when the Host asserts the Device's individual CS line.
* - Bus
- A signal bus, common to all Devices connected to one Host. In general, a bus includes the following lines: MISO, MOSI, SCLK, one or more CS lines, and, optionally, QUADWP and QUADHD. So Devices are connected to the same lines, with the exception that each Device has its own CS line. Several Devices can also share one CS line if connected in a daisy-chain manner.
* - MOSI
- Master Out, Slave In, a.k.a. D. Data transmission from a Host to Device. Also data0 signal in Octal/OPI mode.
* - MISO
- Master In, Slave Out, a.k.a. Q. Data transmission from a Device to Host. Also data1 signal in Octal/OPI mode.
* - SCLK
- Serial Clock. The oscillating signal generated by a Host keeps the transmission of data bits in sync.
* - CS
- Chip Select. Allows a Host to select individual Device(s) connected to the bus in order to send or receive data.
* - QUADWP
- Write Protect signal. Used for 4-bit (qio/qout) transactions. Also for the data2 signal in Octal/OPI mode.
* - QUADHD
- Hold signal. Used for 4-bit (qio/qout) transactions. Also for the data3 signal in Octal/OPI mode.
* - DATA4
- Data4 signal in Octal/OPI mode.
* - DATA5
- Data5 signal in Octal/OPI mode.
* - DATA6
- Data6 signal in Octal/OPI mode.
* - DATA7
- Data7 signal in Octal/OPI mode.
* - Assertion
- The action of activating a line.
* - De-assertion
- The action of returning the line back to inactive (back to idle) status.
* - Transaction
- One instance of a Host asserting a CS line, transferring data to and from a Device, and de-asserting the CS line. Transactions are atomic, which means they can never be interrupted by another transaction.
* - Launch Edge
- Edge of the clock at which the source register **launches** the signal onto the line.
* - Latch Edge
- Edge of the clock at which the destination register **latches in** the signal.
Driver Features
---------------
The SPI Master driver governs the communications between Hosts and Devices. The driver supports the following features:
- Multi-threaded environments
- Transparent handling of DMA transfers while reading and writing data
- Automatic time-division multiplexing of data coming from different Devices on the same signal bus, see :ref:`spi_bus_lock`.
.. warning::
The SPI Master driver allows multiple Devices to be connected on a same SPI bus (sharing a single {IDF_TARGET_NAME} SPI peripheral). As long as each Device is accessed by only one task, the driver is thread-safe. However, if multiple tasks try to access the same SPI Device, the driver is **not thread-safe**. In this case, it is recommended to either:
- Refactor your application so that each SPI peripheral is only accessed by a single task at a time. You can use :cpp:member:`spi_bus_config_t::isr_cpu_id` to register the SPI ISR to the same core as SPI peripheral-related tasks to ensure thread safety.
- Add a mutex lock around the shared Device using :c:macro:`xSemaphoreCreateMutex`.
.. toctree::
:hidden:
SPI Features <spi_features>
SPI Transactions
----------------
An SPI bus transaction consists of five phases which can be found in the table below. Any of these phases can be skipped.
{IDF_TARGET_ADDR_LEN:default="32", esp32="64"}
.. list-table::
:widths: 30 70
:header-rows: 1
* - Phase
- Description
* - Command
- In this phase, a command (0-16 bit) is written to the bus by the Host.
* - Address
- In this phase, an address (0-{IDF_TARGET_ADDR_LEN} bit) is transmitted over the bus by the Host.
* - Dummy
- This phase is configurable and is used to meet the timing requirements.
* - Write
- Host sends data to a Device. This data follows the optional command and address phases and is indistinguishable from them at the electrical level.
* - Read
- Device sends data to its Host.
.. todo::
Add a package diagram.
The attributes of a transaction are determined by the bus configuration structure :cpp:type:`spi_bus_config_t`, Device configuration structure :cpp:type:`spi_device_interface_config_t`, and transaction configuration structure :cpp:type:`spi_transaction_t`.
An SPI Host can send full-duplex transactions, during which the Read and Write phases occur simultaneously. The total transaction length is determined by the sum of the following members:
- :cpp:member:`spi_device_interface_config_t::command_bits`
- :cpp:member:`spi_device_interface_config_t::address_bits`
- :cpp:member:`spi_transaction_t::length`
While the member :cpp:member:`spi_transaction_t::rxlength` only determines the length of data received into the buffer.
In half-duplex transactions, the Read and Write phases are not simultaneous (one direction at a time). The lengths of the Write and Read phases are determined by :cpp:member:`spi_transaction_t::length` and :cpp:member:`spi_transaction_t::rxlength` respectively.
The Command and Address phases are optional, as not every SPI Device requires a command and/or address. This is reflected in the Device's configuration: if :cpp:member:`spi_device_interface_config_t::command_bits` and/or :cpp:member:`spi_device_interface_config_t::address_bits` are set to zero, no Command or Address phase will occur.
The Read and Write phases can also be optional, as not every transaction requires both writing and reading data. If :cpp:member:`spi_transaction_t::rx_buffer` is ``NULL`` and :c:macro:`SPI_TRANS_USE_RXDATA` is not set, the Read phase is skipped. If :cpp:member:`spi_transaction_t::tx_buffer` is ``NULL`` and :c:macro:`SPI_TRANS_USE_TXDATA` is not set, the Write phase is skipped.
The driver supports two types of transactions: interrupt transactions and polling transactions. The programmer can choose to use a different transaction type per Device. If your Device requires both transaction types, see :ref:`mixed_transactions`.
.. _interrupt_transactions:
Interrupt Transactions
^^^^^^^^^^^^^^^^^^^^^^
Interrupt transactions blocks the transaction routine until the transaction completes, thus allowing the CPU to run other tasks.
An application task can queue multiple transactions, and the driver automatically handles them one by one in the interrupt service routine (ISR). It allows the task to switch to other procedures until all the transactions are complete.
.. _polling_transactions:
Polling Transactions
^^^^^^^^^^^^^^^^^^^^
Polling transactions do not use interrupts. The routine keeps polling the SPI Host's status bit until the transaction is finished.
All the tasks that use interrupt transactions can be blocked by the queue. At this point, they need to wait for the ISR to run twice before the transaction is finished. Polling transactions save time otherwise spent on queue handling and context switching, which results in smaller transaction duration. The disadvantage is that the CPU is busy while these transactions are in progress.
The :cpp:func:`spi_device_polling_end` routine needs an overhead of at least 1 µs to unblock other tasks when the transaction is finished. It is strongly recommended to wrap a series of polling transactions using the functions :cpp:func:`spi_device_acquire_bus` and :cpp:func:`spi_device_release_bus` to avoid the overhead. For more information, see :ref:`bus_acquiring`.
.. _transaction-line-mode:
Transaction Line Mode
^^^^^^^^^^^^^^^^^^^^^
Supported line modes for {IDF_TARGET_NAME} are listed as follows, to make use of these modes, set the member ``flags`` in the struct :cpp:type:`spi_transaction_t` as shown in the ``Transaction Flag`` column. If you want to check if corresponding IO pins are set or not, set the member ``flags`` in the :cpp:type:`spi_bus_config_t` as shown in the ``Bus IO setting Flag`` column.
.. only:: not SOC_SPI_SUPPORT_OCT
.. list-table::
:widths: 30 40 40 40 50 50
:header-rows: 1
* - Mode name
- Command Line Width
- Address Line Width
- Data Line Width
- Transaction Flag
- Bus IO Setting Flag
* - Normal SPI
- 1
- 1
- 1
- 0
- 0
* - Dual Output
- 1
- 1
- 2
- SPI_TRANS_MODE_DIO
- SPICOMMON_BUSFLAG_DUAL
* - Dual I/O
- 1
- 2
- 2
- SPI_TRANS_MODE_DIO
SPI_TRANS_MULTILINE_ADDR
- SPICOMMON_BUSFLAG_DUAL
* - Quad Output
- 1
- 1
- 4
- SPI_TRANS_MODE_QIO
- SPICOMMON_BUSFLAG_QUAD
* - Quad I/O
- 1
- 4
- 4
- SPI_TRANS_MODE_QIO
SPI_TRANS_MULTILINE_ADDR
- SPICOMMON_BUSFLAG_QUAD
.. only:: SOC_SPI_SUPPORT_OCT
.. list-table::
:widths: 30 40 40 40 50 50
:header-rows: 1
* - Mode name
- Command Line Width
- Address Line Width
- Data Line Width
- Transaction Flag
- Bus IO Setting Flag
* - Normal SPI
- 1
- 1
- 1
- 0
- 0
* - Dual Output
- 1
- 1
- 2
- SPI_TRANS_MODE_DIO
- SPICOMMON_BUSFLAG_DUAL
* - Dual I/O
- 1
- 2
- 2
- SPI_TRANS_MODE_DIO
SPI_TRANS_MULTILINE_ADDR
- SPICOMMON_BUSFLAG_DUAL
* - Quad Output
- 1
- 1
- 4
- SPI_TRANS_MODE_QIO
- SPICOMMON_BUSFLAG_QUAD
* - Quad I/O
- 1
- 4
- 4
- SPI_TRANS_MODE_QIO
SPI_TRANS_MULTILINE_ADDR
- SPICOMMON_BUSFLAG_QUAD
* - Octal Output
- 1
- 1
- 8
- SPI_TRANS_MODE_OCT
- SPICOMMON_BUSFLAG_OCTAL
* - OPI
- 8
- 8
- 8
- SPI_TRANS_MODE_OCT
SPI_TRANS_MULTILINE_ADDR
SPI_TRANS_MULTILINE_CMD
- SPICOMMON_BUSFLAG_OCTAL
Command and Address Phases
^^^^^^^^^^^^^^^^^^^^^^^^^^
During the Command and Address phases, the members :cpp:member:`spi_transaction_t::cmd` and :cpp:member:`spi_transaction_t::addr` are sent to the bus, nothing is read at this time. The default lengths of the Command and Address phases are set in :cpp:type:`spi_device_interface_config_t` by calling :cpp:func:`spi_bus_add_device`. If the flags :c:macro:`SPI_TRANS_VARIABLE_CMD` and :c:macro:`SPI_TRANS_VARIABLE_ADDR` in the member :cpp:member:`spi_transaction_t::flags` are not set, the driver automatically sets the length of these phases to default values during Device initialization.
If the lengths of the Command and Address phases need to be variable, declare the struct :cpp:type:`spi_transaction_ext_t`, set the flags :c:macro:`SPI_TRANS_VARIABLE_CMD` and/or :c:macro:`SPI_TRANS_VARIABLE_ADDR` in the member :cpp:member:`spi_transaction_ext_t::base` and configure the rest of base as usual. Then the length of each phase will be equal to :cpp:member:`spi_transaction_ext_t::command_bits` and :cpp:member:`spi_transaction_ext_t::address_bits` set in the struct :cpp:type:`spi_transaction_ext_t`.
If the Command and Address phase need to have the same number of lines as the data phase, you need to set ``SPI_TRANS_MULTILINE_CMD`` and/or ``SPI_TRANS_MULTILINE_ADDR`` to the ``flags`` member in the struct :cpp:type:`spi_transaction_t`. Also see :ref:`transaction-line-mode`.
Write and Read Phases
^^^^^^^^^^^^^^^^^^^^^
Normally, the data that needs to be transferred to or from a Device is read from or written to a chunk of memory indicated by the members :cpp:member:`spi_transaction_t::rx_buffer` and :cpp:member:`spi_transaction_t::tx_buffer`. If DMA is enabled for transfers, the buffers are required to be:
1. Allocated in DMA-capable internal memory (MALLOC_CAP_DMA), see :ref:`DMA-Capable Memory <dma-capable-memory>`.
2. 32-bit aligned (starting from a 32-bit boundary and having a length of multiples of 4 bytes).
If these requirements are not satisfied, the transaction efficiency will be affected due to the allocation and copying of temporary buffers.
If using more than one data line to transmit, please set ``SPI_DEVICE_HALFDUPLEX`` flag for the member ``flags`` in the struct :cpp:type:`spi_device_interface_config_t`. And the member ``flags`` in the struct :cpp:type:`spi_transaction_t` should be set as described in :ref:`transaction-line-mode`.
.. only:: esp32
.. note::
Half-duplex transactions with both Read and Write phases are not supported when using DMA. For details and workarounds, see :ref:`spi_known_issues`.
.. only:: not SOC_SPI_HD_BOTH_INOUT_SUPPORTED
.. note::
Half-duplex transactions with both Read and Write phases are not supported. Please use full duplex mode.
.. _bus_acquiring:
Bus Acquiring
^^^^^^^^^^^^^
Sometimes you might want to send SPI transactions exclusively and continuously so that it takes as little time as possible. For this, you can use bus acquiring, which helps to suspend transactions (both polling or interrupt) to other Devices until the bus is released. To acquire and release a bus, use the functions :cpp:func:`spi_device_acquire_bus` and :cpp:func:`spi_device_release_bus`.
.. only:: SOC_SPI_SUPPORT_SLEEP_RETENTION
Sleep Retention
^^^^^^^^^^^^^^^
{IDF_TARGET_NAME} supports to retain the SPI register context before entering **light sleep** and restore them after waking up. This means you don't have to re-init the SPI driver after the light sleep.
This feature can be enabled by setting the flag :c:macro:`SPICOMMON_BUSFLAG_SLP_ALLOW_PD`. It will allow the system to power down the SPI in light sleep, meanwhile save the register context. It can help to save more power consumption with some extra cost of the memory.
Driver Usage
------------
.. todo::
Organize the Driver Usage into subsections that will reflect the general user experience of the users, e.g.,
Configuration
Add stuff about the configuration API here, the various options in configuration (e.g., configure for interrupt vs. polling), and optional configuration
Transactions
Describe how to execute a normal transaction (i.e., where data is larger than 32 bits). Describe how to configure between big and little-endian.
- Add a sub-sub section on how to optimize when transmitting less than 32 bits
- Add a sub-sub section on how to transmit mixed transactions to the same Device
- Initialize an SPI bus by calling the function :cpp:func:`spi_bus_initialize`. Make sure to set the correct I/O pins in the struct :cpp:type:`spi_bus_config_t`. Set the signals that are not needed to ``-1``.
- Register a Device connected to the bus with the driver by calling the function :cpp:func:`spi_bus_add_device`. Make sure to configure any timing requirements the Device might need with the parameter ``dev_config``. You should now have obtained the Device's handle which will be used when sending a transaction to it.
- To interact with the Device, fill one or more :cpp:type:`spi_transaction_t` structs with any transaction parameters required. Then send the structs either using a polling transaction or an interrupt transaction:
- :ref:`Interrupt <interrupt_transactions>`
Either queue all transactions by calling the function :cpp:func:`spi_device_queue_trans` and, at a later time, query the result using the function :cpp:func:`spi_device_get_trans_result`, or handle all requests synchronously by feeding them into :cpp:func:`spi_device_transmit`.
- :ref:`Polling <polling_transactions>`
Call the function :cpp:func:`spi_device_polling_transmit` to send polling transactions. Alternatively, if you want to insert something in between, send the transactions by using :cpp:func:`spi_device_polling_start` and :cpp:func:`spi_device_polling_end`.
- (Optional) To perform back-to-back transactions with a Device, call the function :cpp:func:`spi_device_acquire_bus` before sending transactions and :cpp:func:`spi_device_release_bus` after the transactions have been sent.
- (Optional) To remove a certain Device from the bus, call :cpp:func:`spi_bus_remove_device` with the Device handle as an argument.
- (Optional) To remove the driver from the bus, make sure no more devices are attached and call :cpp:func:`spi_bus_free`.
The example code for the SPI Master driver can be found in the :example:`peripherals/spi_master` directory of ESP-IDF examples.
.. only:: SOC_PSRAM_DMA_CAPABLE
Transactions with Data on PSRAM
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
{IDF_TARGET_NAME} supports GPSPI Master with DMA transferring data from/to PSRAM directly without extra internal copy process, which saves memory, by adding :c:macro:`SPI_TRANS_DMA_USE_PSRAM` flag to the transaction.
Note that this feature shares bandwidth (bus frequency * bus bits width) with MSPI bus, so GPSPI transfer bandwidth should be less than PSRAM bandwidth, **otherwise transmission data may be lost**. You can check the return value or :c:macro:`SPI_TRANS_DMA_RX_FAIL` and :c:macro:`SPI_TRANS_DMA_TX_FAIL` flags after the transaction is finished to check if error occurs during the transmission. If the transaction returns :c:macro:`ESP_ERR_INVALID_STATE` error, the transaction fails.
Transactions with Data Not Exceeding 32 Bits
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
When the transaction data size is equal to or less than 32 bits, it will be sub-optimal to allocate a buffer for the data. The data can be directly stored in the transaction struct instead. For transmitted data, it can be achieved by using the :cpp:member:`spi_transaction_t::tx_data` member and setting the :c:macro:`SPI_TRANS_USE_TXDATA` flag on the transmission. For received data, use :cpp:member:`spi_transaction_t::rx_data` and set :c:macro:`SPI_TRANS_USE_RXDATA`. In both cases, do not touch the :cpp:member:`spi_transaction_t::tx_buffer` or :cpp:member:`spi_transaction_t::rx_buffer` members, because they use the same memory locations as :cpp:member:`spi_transaction_t::tx_data` and :cpp:member:`spi_transaction_t::rx_data`.
Transactions with Integers Other than ``uint8_t``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
An SPI Host reads and writes data into memory byte by byte. By default, data is sent with the most significant bit (MSB) first, as LSB is first used in rare cases. If a value of fewer than 8 bits needs to be sent, the bits should be written into memory in the MSB first manner.
For example, if ``0b00010`` needs to be sent, it should be written into a ``uint8_t`` variable, and the length for reading should be set to 5 bits. The Device will still receive 8 bits with 3 additional "random" bits, so the reading must be performed correctly.
Not all chips support data transmission with any bit lengths. Sending or receiving unsupported bit lengths will return :c:macro:`ESP_ERR_NOT_SUPPORTED` error. The supported lengths are shown in the table below (**YES** means support any bits length, **NO** means bytes (8 bits) only):
+------+--------+-------+----------+--------------------+---------------------+
| | ESP32 | ESP32-S2 | ESP32-S3/C2/C3/C6 | ESP32-H2/P4/C5/C61 |
+======+========+=======+==========+====================+=====================+
| TX | DMA | YES | YES | (bit_len % 8) != 1 | YES |
+ +--------+-------+----------+--------------------+---------------------+
| | NO DMA | YES | YES | (bit_len % 8) != 1 | YES |
+------+--------+-------+----------+--------------------+---------------------+
| RX | DMA | NO | NO | YES | YES |
+ +--------+-------+----------+--------------------+---------------------+
| | NO DMA | YES | NO | YES | YES |
+------+--------+-------+----------+--------------------+---------------------+
If you still need to use unsupported bit lengths, you can use the following alternatives:
1. Use :cpp:member:`spi_transaction_t::cmd` and :cpp:member:`spi_transaction_t::addr` and data phase combination. The drawback is that the command and address phases do not receive data from the slave device.
2. Use two supported length transmissions combination, like ``2+7`` to implement ``9 bit`` transmission, while keeping the CS line valid. The drawback is that there is a minimum time interval between two transmissions (see :ref:`transaction_time_cost`), and the overall transfer rate is lower.
On top of that, {IDF_TARGET_NAME} is a little-endian chip, which means that the least significant byte of ``uint16_t`` and ``uint32_t`` variables is stored at the smallest address. Hence, if ``uint16_t`` is stored in memory, bits [7:0] are sent first, followed by bits [15:8].
For cases when the data to be transmitted has a size differing from ``uint8_t`` arrays, the following macros can be used to transform data to the format that can be sent by the SPI driver directly:
- :c:macro:`SPI_SWAP_DATA_TX` for data to be transmitted
- :c:macro:`SPI_SWAP_DATA_RX` for data received
.. _mixed_transactions:
Notes on Sending Mixed Transactions to the Same Device
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
To reduce coding complexity, send only one type of transaction (interrupt or polling) to one Device. However, you still can send both interrupt and polling transactions alternately. The notes below explain how to do this.
The polling transactions should be initiated only after all the polling and interrupt transactions are finished.
Since an unfinished polling transaction blocks other transactions, please do not forget to call the function :cpp:func:`spi_device_polling_end` after :cpp:func:`spi_device_polling_start` to allow other transactions or to allow other Devices to use the bus. Remember that if there is no need to switch to other tasks during your polling transaction, you can initiate a transaction with :cpp:func:`spi_device_polling_transmit` so that it will be ended automatically.
In-flight polling transactions are disturbed by the ISR operation to accommodate interrupt transactions. Always make sure that all the interrupt transactions sent to the ISR are finished before you call :cpp:func:`spi_device_polling_start`. To do that, you can keep calling :cpp:func:`spi_device_get_trans_result` until all the transactions are returned.
To have better control of the calling sequence of functions, send mixed transactions to the same Device only within a single task.
.. only:: esp32
.. _spi_master_on_spi1_bus:
Notes on Using the SPI Master Driver on SPI1 Bus
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. note::
Though the :ref:`spi_bus_lock` feature makes it possible to use SPI Master driver on the SPI1 bus, it is still tricky and needs a lot of special treatment. It is a feature for advanced developers.
To use SPI Master driver on SPI1 bus, you have to take care of two problems:
1. The code and data should be in the internal memory when the driver is operating on SPI1 bus.
SPI1 bus is shared among Devices and the cache for data (code) in the flash as well as the PSRAM. The cache should be disabled when other drivers are operating on the SPI1 bus. Hence the data (code) in the flash as well as the PSRAM cannot be fetched while the driver acquires the SPI1 bus by:
- Explicit bus acquiring between :cpp:func:`spi_device_acquire_bus` and :cpp:func:`spi_device_release_bus`.
- Implicit bus acquiring between :cpp:func:`spi_device_polling_start` and :cpp:func:`spi_device_polling_end` (or inside :cpp:func:`spi_device_polling_transmit`).
During the time above, all other tasks and most ISRs will be disabled (see :ref:`iram-safe-interrupt-handlers`). Application code and data used by the current task should be placed in internal memory (DRAM or IRAM), or already in the ROM. Access to external memory (flash code, const data in the flash, and static/heap data in the PSRAM) will cause a ``Cache disabled but cached memory region accessed`` exception. For differences between IRAM, DRAM, and flash cache, please refer to the :ref:`application memory layout <memory-layout>` documentation.
To place functions into the IRAM, you can either:
1. Add ``IRAM_ATTR`` (include ``esp_attr.h``) to the function like:
IRAM_ATTR void foo(void) { }
Please note that when a function is inlined, it will follow its caller's segment, and the attribute will not take effect. You may need to use ``NOLINE_ATTR`` to avoid this. Please also note that the compiler may transform some code into a lookup table in the const data, so ``noflash_text`` is not safe.
2. Use the ``noflash`` placement in the ``linker.lf``. See more in :doc:`../../api-guides/linker-script-generation`. Please note that the compiler may transform some code into a lookup table in the const data, so ``noflash_text`` is not safe.
Please do take care that the optimization level may affect the compiler behavior of inline, or transform some code into a lookup table in the const data, etc.
To place data into the DRAM, you can either:
1. Add ``DRAM_ATTR`` (include ``esp_attr.h``) to the data definition like:
DRAM_ATTR int g_foo = 3;
2. Use the ``noflash`` placement in the linker.lf. See more in :doc:`../../api-guides/linker-script-generation`.
Please also see the example :example:`peripherals/spi_master/hd_eeprom`.
GPIO Matrix and IO_MUX
^^^^^^^^^^^^^^^^^^^^^^
.. only:: esp32
Most of ESP32's peripheral signals have a direct connection to their dedicated IO_MUX pins. However, the signals can also be routed to any other available pins using the less direct GPIO matrix. If at least one signal is routed through the GPIO matrix, then all signals will be routed through it.
The GPIO matrix introduces flexibility of routing but also brings the following disadvantages:
- Increases the input delay of the MISO signal, which makes MISO setup time violations more likely. If SPI needs to operate at high speeds, use dedicated IO_MUX pins.
- Allows signals with clock frequencies only up to 40 MHz, as opposed to 80 MHz if IO_MUX pins are used.
.. note::
For more details about the influence of the MISO input delay on the maximum clock frequency, see :ref:`timing_considerations`.
The IO_MUX pins for SPI buses are given below.
.. list-table::
:widths: 40 20 20
:header-rows: 1
* - Pin Name
- SPI 2 (GPIO Number)
- SPI 3 (GPIO Number)
* - CS0 [1]_
- 15
- 5
* - SCLK
- 14
- 18
* - MISO
- 12
- 19
* - MOSI
- 13
- 23
* - QUADWP
- 2
- 22
* - QUADHD
- 4
- 21
.. only:: not esp32
{IDF_TARGET_SPI2_IOMUX_PIN_CS:default="N/A", esp32s2="10", esp32s3="10", esp32c2="10", esp32c3="10", esp32c6="16", esp32h2="1", esp32p4="7" , esp32c5="10", esp32c61="8", esp32h21="12", esp32h4="20"}
{IDF_TARGET_SPI2_IOMUX_PIN_CLK:default="N/A", esp32s2="12", esp32s3="12", esp32c2="6", esp32c3="6", esp32c6="6", esp32h2="4", esp32p4="9" , esp32c5="6", esp32c61="6", esp32h21="2", esp32h4="16"}
{IDF_TARGET_SPI2_IOMUX_PIN_MOSI:default="N/A", esp32s2="11" esp32s3="11", esp32c2="7" esp32c3="7", esp32c6="7", esp32h2="5", esp32p4="8" , esp32c5="7", esp32c61="7", esp32h21="3", esp32h4="17"}
{IDF_TARGET_SPI2_IOMUX_PIN_MISO:default="N/A", esp32s2="13" esp32s3="13", esp32c2="2" esp32c3="2", esp32c6="2", esp32h2="0", esp32p4="10", esp32c5="2", esp32c61="2", esp32h21="4", esp32h4="15"}
{IDF_TARGET_SPI2_IOMUX_PIN_HD:default="N/A", esp32s2="9" esp32s3="9", esp32c2="4" esp32c3="4", esp32c6="4", esp32h2="3", esp32p4="6" , esp32c5="4", esp32c61="3", esp32h21="1", esp32h4="19"}
{IDF_TARGET_SPI2_IOMUX_PIN_WP:default="N/A", esp32s2="14" esp32s3="14", esp32c2="5" esp32c3="5", esp32c6="5", esp32h2="2", esp32p4="11", esp32c5="5", esp32c61="4", esp32h21="0", esp32h4="18"}
Most of the chip's peripheral signals have a direct connection to their dedicated IO_MUX pins. However, the signals can also be routed to any other available pins using the less direct GPIO matrix. If at least one signal is routed through the GPIO matrix, then all signals will be routed through it.
When an SPI Host is set to 40 MHz or lower frequencies, routing SPI pins via the GPIO matrix will behave the same compared to routing them via IOMUX.
The IO_MUX pins for SPI buses are given below.
.. list-table::
:widths: 40 30
:header-rows: 1
* - Pin Name
- GPIO Number (SPI2)
* - CS0 [1]_
- {IDF_TARGET_SPI2_IOMUX_PIN_CS}
* - SCLK
- {IDF_TARGET_SPI2_IOMUX_PIN_CLK}
* - MISO
- {IDF_TARGET_SPI2_IOMUX_PIN_MISO}
* - MOSI
- {IDF_TARGET_SPI2_IOMUX_PIN_MOSI}
* - QUADWP
- {IDF_TARGET_SPI2_IOMUX_PIN_WP}
* - QUADHD
- {IDF_TARGET_SPI2_IOMUX_PIN_HD}
.. [1] Only the first Device attached to the bus can use the CS0 pin.
.. _speed_considerations:
Transfer Speed Considerations
-----------------------------
There are three factors limiting the transfer speed:
- Transaction interval
- SPI clock frequency
- Cache miss of SPI functions, including callbacks
The main parameter that determines the transfer speed for large transactions is clock frequency. For multiple small transactions, the transfer speed is mostly determined by the length of transaction intervals.
.. _transaction_time_cost:
Transaction Duration
^^^^^^^^^^^^^^^^^^^^
{IDF_TARGET_MAX_TRANS_TIME_INTR_DMA:default="N/A", esp32="28", esp32s2="23", esp32c3="28", esp32s3="26", esp32c2="42", esp32c6="34", esp32h2="58", esp32p4="44", esp32c5="24", esp32c61="32", esp32h21="60", esp32h4="70"}
{IDF_TARGET_MAX_TRANS_TIME_POLL_DMA:default="N/A", esp32="10", esp32s2="9", esp32c3="10", esp32s3="11", esp32c2="17", esp32c6="17", esp32h2="28", esp32p4="27", esp32c5="15", esp32c61="17", esp32h21="32", esp32h4="35"}
{IDF_TARGET_MAX_TRANS_TIME_INTR_CPU:default="N/A", esp32="25", esp32s2="22", esp32c3="27", esp32s3="24", esp32c2="40", esp32c6="32", esp32h2="54", esp32p4="26", esp32c5="22", esp32c61="29", esp32h21="55", esp32h4="60"}
{IDF_TARGET_MAX_TRANS_TIME_POLL_CPU:default="N/A", esp32="8", esp32s2="8", esp32c3="9", esp32s3="9", esp32c2="15", esp32c6="15", esp32h2="24", esp32p4="12", esp32c5="12", esp32c61="14", esp32h21="26", esp32h4="25"}
Transaction duration includes setting up SPI peripheral registers, copying data to FIFOs or setting up DMA links, and the time for SPI transactions.
Interrupt transactions allow appending extra overhead to accommodate the cost of FreeRTOS queues and the time needed for switching between tasks and the ISR.
For **interrupt transactions**, the CPU can switch to other tasks when a transaction is in progress. This saves CPU time but increases the transaction duration. See :ref:`interrupt_transactions`. For **polling transactions**, it does not block the task but allows to do polling when the transaction is in progress. For more information, see :ref:`polling_transactions`.
If DMA is enabled, setting up the linked list requires about 2 µs per transaction. When a master is transferring data, it automatically reads the data from the linked list. If DMA is not enabled, the CPU has to write and read each byte from the FIFO by itself. Usually, this is faster than 2 µs, but the transaction length is limited to 64 bytes for both write and read.
The typical transaction duration for one byte of data is given below.
- Interrupt Transaction via DMA: {IDF_TARGET_MAX_TRANS_TIME_INTR_DMA} µs.
- Interrupt Transaction via CPU: {IDF_TARGET_MAX_TRANS_TIME_INTR_CPU} µs.
- Polling Transaction via DMA: {IDF_TARGET_MAX_TRANS_TIME_POLL_DMA} µs.
- Polling Transaction via CPU: {IDF_TARGET_MAX_TRANS_TIME_POLL_CPU} µs.
Note that these data are tested with :ref:`CONFIG_SPI_MASTER_ISR_IN_IRAM` enabled. SPI transaction related code are placed in the internal memory. If this option is turned off (for example, for internal memory optimization), the transaction duration may be affected.
SPI Clock Frequency
^^^^^^^^^^^^^^^^^^^
The clock source of the GPSPI peripherals can be selected by setting :cpp:member:`spi_device_interface_config_t::clock_source`. You can refer to :cpp:type:`spi_clock_source_t` to know the supported clock sources.
By default driver sets clock source to ``SPI_CLK_SRC_DEFAULT``. This usually stands for the highest frequency among GPSPI supported clock sources. Its value is different among chips.
The actual clock frequency of a device may not be exactly equal to the number you set, it is re-calculated by the driver to the nearest hardware-compatible number, and no more than the frequency of selected clock source. You can call :cpp:func:`spi_device_get_actual_freq` to know the actual frequency computed by the driver.
The clock frequency of the device can be changed during transmission by setting :cpp:member:`spi_transaction_t::override_freq_hz`. This operation will use new clock frequency for the device's current and later transmissions. If the expected clock frequency cannot be achieved, the driver will print an warning and continue to use the previous clock frequency for transmission.
The theoretical maximum transfer speed of the Write or Read phase can be calculated according to the table below:
.. only:: not SOC_SPI_SUPPORT_OCT
.. list-table::
:widths: 40 30
:header-rows: 1
* - Line Width of Write/Read phase
- Speed (Bps)
* - 1-Line
- *SPI Frequency / 8*
* - 2-Line
- *SPI Frequency / 4*
* - 4-Line
- *SPI Frequency / 2*
.. only:: SOC_SPI_SUPPORT_OCT
.. list-table::
:widths: 40 30
:header-rows: 1
* - Line Width of Write/Read phase
- Speed (Bps)
* - 1-Line
- *SPI Frequency / 8*
* - 2-Line
- *SPI Frequency / 4*
* - 4-Line
- *SPI Frequency / 2*
* - 8-Line
- *SPI Frequency*
The transfer speed calculation of other phases (Command, Address, Dummy) is similar.
.. only:: esp32
If the clock frequency is too high, the use of some functions might be limited. See :ref:`timing_considerations`.
Cache Missing
^^^^^^^^^^^^^
The default config puts only the ISR into the IRAM. Other SPI-related functions, including the driver itself and the callback, might suffer from cache misses and need to wait until the code is read from flash. Select :ref:`CONFIG_SPI_MASTER_IN_IRAM` to put the whole SPI driver into IRAM and put the entire callback(s) and its callee functions into IRAM to prevent cache missing.
.. note::
SPI driver implementation is based on FreeRTOS APIs, to use :ref:`CONFIG_SPI_MASTER_IN_IRAM`, you should enable :ref:`CONFIG_FREERTOS_IN_IRAM`.
For an interrupt transaction, the overall cost is **20+8n/Fspi[MHz]** [µs] for n bytes transferred in one transaction. Hence, the transferring speed is: **n/(20+8n/Fspi)**. An example of transferring speed at 8 MHz clock speed is given in the following table.
.. list-table::
:widths: 30 45 40 30 30
:header-rows: 1
* - Frequency (MHz)
- Transaction Interval (µs)
- Transaction Length (bytes)
- Total Time (µs)
- Total Speed (KBps)
* - 8
- 25
- 1
- 26
- 38.5
* - 8
- 25
- 8
- 33
- 242.4
* - 8
- 25
- 16
- 41
- 490.2
* - 8
- 25
- 64
- 89
- 719.1
* - 8
- 25
- 128
- 153
- 836.6
When a transaction length is short, the cost of the transaction interval is high. If possible, try to squash several short transactions into one transaction to achieve a higher transfer speed.
Please note that the ISR is disabled during flash operation by default. To keep sending transactions during flash operations, enable :ref:`CONFIG_SPI_MASTER_ISR_IN_IRAM` and set :c:macro:`ESP_INTR_FLAG_IRAM` in the member :cpp:member:`spi_bus_config_t::intr_flags`. In this case, all the transactions queued before starting flash operations are handled by the ISR in parallel. Also note that the callback of each Device and their ``callee`` functions should be in IRAM, or your callback will crash due to cache missing. For more details, see :ref:`iram-safe-interrupt-handlers`.
.. only:: esp32h2
Timing Tuning
-------------
.. only:: esp32h2
This feature is supported only on chip revision v1.2 or later.
To accommodate the timing requirements of different slave devices and improve signal stability, GP-SPI controllers support two sampling modes when receiving data: Sample Phase 0 and Sample Phase 1. These can be configured via :cpp:member:`spi_device_interface_config_t::sample_point`.
Sample Phase 0 (SPI mode 0):
.. wavedrom:: /../_static/diagrams/spi/spi_mode0_delay.json
Sample Phase 1 (SPI mode 0):
.. wavedrom:: /../_static/diagrams/spi/spi_mode0_std.json
By default, the driver uses sample phase 0, when the slave device adheres to standard SPI timing specifications, sample phase 0 provides more stable data reception at high clock frequencies.
.. only:: esp32
.. _timing_considerations:
Timing Considerations
---------------------
As shown in the figure below, there is a delay on the MISO line after the SCLK launch edge and before the signal is latched by the internal register. As a result, the MISO pin setup time is the limiting factor for the SPI clock speed. When the delay is too long, the setup slack is < 0, which means the setup timing requirement is violated and the reading might be incorrect.
.. image:: /../_static/spi_miso.png
:scale: 40 %
:align: center
.. wavedrom:: /../_static/diagrams/spi/miso_timing_waveform.json
The maximum allowed frequency is dependent on:
- :cpp:member:`spi_device_interface_config_t::input_delay_ns` - maximum data valid time on the MISO bus after a clock cycle on SCLK starts
- If the IO_MUX pin or the GPIO Matrix is used
When the GPIO matrix is used, the maximum allowed frequency is reduced to about 33 ~ 77% in comparison to the existing **input delay**. To retain a higher frequency, you have to use the IO_MUX pins or the **dummy bit workaround**. You can obtain the maximum reading frequency of the master by using the function :cpp:func:`spi_get_freq_limit`.
.. _dummy_bit_workaround:
**Dummy bit workaround**: Dummy clocks, during which the Host does not read data, can be inserted before the Read phase begins. The Device still sees the dummy clocks and sends out data, but the Host does not read until the Read phase comes. This compensates for the lack of the MISO setup time required by the Host and allows the Host to do reading at a higher frequency.
In the ideal case, if the Device is so fast that the input delay is shorter than an APB clock cycle - 12.5 ns - the maximum frequency at which the Host can read (or read and write) in different conditions is as follows:
.. list-table::
:widths: 30 45 40 30
:header-rows: 1
* - Frequency Limit (MHz)
- Frequency Limit (MHz)
- Dummy Bits Used by Driver
- Comments
* - GPIO Matrix
- IO_MUX Pins
-
-
* - 26.6
- 80
- No
-
* - 40
- --
- Yes
- Half-duplex, no DMA allowed
If the Host only writes data, the **dummy bit workaround** and the frequency check can be disabled by setting the bit ``SPI_DEVICE_NO_DUMMY`` in the member :cpp:member:`spi_device_interface_config_t::flags`. When disabled, the output frequency can be 80 MHz, even if the GPIO matrix is used.
:cpp:member:`spi_device_interface_config_t::flags`
The SPI Master driver still works even if the :cpp:member:`spi_device_interface_config_t::input_delay_ns` in the structure :cpp:type:`spi_device_interface_config_t` is set to 0. However, setting an accurate value helps to:
- Calculate the frequency limit for full-duplex transactions
- Compensate the timing correctly with dummy bits for half-duplex transactions
You can approximate the maximum data valid time after the launch edge of SPI clocks by checking the statistics in the AC characteristics chapter of your Device's specification or measure the time using an oscilloscope or logic analyzer.
Please note that the actual PCB layout design and excessive loads may increase the input delay. It means that non-optimal wiring and/or a load capacitor on the bus will most likely lead to input delay values exceeding the values given in the Device specification or measured while the bus is floating.
Some typical delay values are shown in the following table. These data are retrieved when the slave Device is on a different physical chip.
.. list-table::
:widths: 40 20
:header-rows: 1
* - Device
- Input Delay (ns)
* - Ideal Device
- 0
* - ESP32 slave using IO_MUX
- 50
* - ESP32 slave using GPIO_MATRIX
- 75
The MISO path delay (valid time) consists of a slave's **input delay** plus the master's **GPIO matrix delay**. The delay determines the above frequency limit for full-duplex transfers. Once exceeding, full-duplex transfers will not work as well as the half-duplex transactions that use dummy bits. The frequency limit is:
*Freq limit [MHz] = 80 / (floor(MISO delay[ns]/12.5) + 1)*
The figure below shows the relationship between frequency limit and input delay. Two extra APB clock cycle periods should be added to the MISO delay if the master uses the GPIO matrix.
.. image:: /../_static/spi_master_freq_tv.png
Corresponding frequency limits for different Devices with different **input delay** times are shown in the table below.
When the master is IO_MUX (0 ns):
.. list-table::
:widths: 20 40 40
:header-rows: 1
* - Input Delay (ns)
- MISO Path Delay (ns)
- Freq. Limit (MHz)
* - 0
- 0
- 80
* - 50
- 50
- 16
* - 75
- 75
- 11.43
When the master is GPIO_MATRIX (25 ns):
.. list-table::
:widths: 20 40 40
:header-rows: 1
* - Input Delay (ns)
- MISO Path Delay (ns)
- Freq. Limit (MHz)
* - 0
- 25
- 26.67
* - 50
- 75
- 11.43
* - 75
- 100
- 8.89
.. _spi_known_issues:
Known Issues
------------
1. Half-duplex transactions are not compatible with DMA when both the Write and Read phases are used.
If such transactions are required, you have to use one of the alternative solutions:
1. Use full-duplex transactions instead.
2. Disable DMA by setting the bus initialization function's last parameter to 0 as follows:
``ret=spi_bus_initialize(SPI3_HOST, &buscfg, 0);``
This can prohibit you from transmitting and receiving data longer than 64 bytes.
3. Try using the command and address fields to replace the Write phase.
2. Full-duplex transactions are not compatible with the **dummy bit workaround**, hence the frequency is limited. See :ref:`dummy bit speed-up workaround <dummy_bit_workaround>`.
3. ``dummy_bits`` in :cpp:type:`spi_device_interface_config_t` and :cpp:type:`spi_transaction_ext_t` are not available when SPI Read and Write phases are both enabled (regardless of full duplex or half duplex mode).
4. ``cs_ena_pretrans`` is not compatible with the Command and Address phases of full-duplex transactions.
.. only:: esp32
5. If DMA is enabled, the RX buffer should be word-aligned (starting from a 32-bit boundary and having a length of multiples of 4 bytes). Otherwise, DMA may overwrite the data in the unaligned part.
Application Examples
--------------------
- :example:`peripherals/spi_master/hd_eeprom` demonstrates how to use the SPI master half duplex mode to read/write an AT93C46D EEPROM (8-bit mode) on {IDF_TARGET_NAME}.
- :example:`peripherals/spi_master/lcd` demonstrates how to use the SPI master driver to display an animation on the LCD. With the help of the DMA, we can do render and flush in parallel. This example also illustrates using the SPI transaction hook function to drive the D/C signal level.
API Reference - SPI Common
--------------------------
.. include-build-file:: inc/spi_types.inc
.. include-build-file:: inc/spi_common.inc
API Reference - SPI Master
--------------------------
.. include-build-file:: inc/spi_master.inc
@@ -0,0 +1,289 @@
SPI Slave Driver
================
:link_to_translation:`zh_CN:[中文]`
SPI Slave driver is a program that controls {IDF_TARGET_NAME}'s General Purpose SPI (GP-SPI) peripheral(s) when it functions as a slave.
For more hardware information about the GP-SPI peripheral(s), see **{IDF_TARGET_NAME} Technical Reference Manual** > **SPI Controller** [`PDF <{IDF_TARGET_TRM_EN_URL}#spi>`__].
Terminology
-----------
The terms used in relation to the SPI slave driver are given in the table below.
.. list-table::
:widths: 30 70
:header-rows: 1
* - Term
- Definition
* - Host
- The SPI controller peripheral external to {IDF_TARGET_NAME} that initiates SPI transmissions over the bus, and acts as an SPI Master.
* - Device
- SPI slave device (general purpose SPI controller). Each Device shares the MOSI, MISO and SCLK signals but is only active on the bus when the Host asserts the Device's individual CS line.
* - Bus
- A signal bus, common to all Devices connected to one Host. In general, a bus includes the following lines: MISO, MOSI, SCLK, one or more CS lines, and, optionally, QUADWP and QUADHD. So Devices are connected to the same lines, with the exception that each Device has its own CS line. Several Devices can also share one CS line if connected in the daisy-chain manner.
* - MISO
- Master In, Slave Out, a.k.a. Q. Data transmission from a Device to Host.
* - MOSI
- Master Out, Slave In, a.k.a. D. Data transmission from a Host to Device.
* - SCLK
- Serial Clock. Oscillating signal generated by a Host that keeps the transmission of data bits in sync.
* - CS
- Chip Select. Allows a Host to select individual Device(s) connected to the bus in order to send or receive data.
* - QUADWP
- Write Protect signal. Only used for 4-bit (qio/qout) transactions.
* - QUADHD
- Hold signal. Only used for 4-bit (qio/qout) transactions.
* - Assertion
- The action of activating a line. The opposite action of returning the line back to inactive (back to idle) is called **de-assertion**.
* - Transaction
- One instance of a Host asserting a CS line, transferring data to and from a Device, and de-asserting the CS line. Transactions are atomic, which means they can never be interrupted by another transaction.
* - Launch Edge
- Edge of the clock at which the source register **launches** the signal onto the line.
* - Latch Edge
- Edge of the clock at which the destination register **latches in** the signal.
Driver Features
---------------
{IDF_TARGET_MAX_DATA_BUF:default="64", esp32s2="72"}
The SPI slave driver allows using the SPI peripherals as full-duplex Devices. The driver can send/receive transactions up to {IDF_TARGET_MAX_DATA_BUF} bytes in length, or utilize DMA to send/receive longer transactions. However, there are some :ref:`known issues <spi_dma_known_issues>` related to DMA.
The SPI slave driver supports registering the SPI ISR to a certain CPU core. If multiple tasks try to access the same SPI Device simultaneously, it is recommended that your application be refactored so that each SPI peripheral is only accessed by a single task at a time. Please also use :cpp:member:`spi_bus_config_t::isr_cpu_id` to register the SPI ISR to the same core as SPI peripheral related tasks to ensure thread safety.
.. only:: SOC_SPI_SUPPORT_SLEEP_RETENTION
Sleep Retention
^^^^^^^^^^^^^^^
{IDF_TARGET_NAME} supports to retain the SPI register context before entering **light sleep** and restore them after waking up. This means you don't have to re-init the SPI driver after the light sleep.
This feature can be enabled by setting the flag :c:macro:`SPICOMMON_BUSFLAG_SLP_ALLOW_PD`. It will allow the system to power down the SPI in light sleep, meanwhile save the register context. It can help to save more power consumption with some extra cost of the memory.
Notice that when GPSPI is working as a slave, it is **not** support to enter sleep when any transaction (including TX and RX) is not finished.
SPI Transactions
----------------
A full-duplex SPI transaction begins when the Host asserts the CS line and starts sending out clock pulses on the SCLK line. Every clock pulse, a data bit is shifted from the Host to the Device on the MOSI line and back on the MISO line at the same time. At the end of the transaction, the Host de-asserts the CS line.
The attributes of a transaction are determined by the configuration structure for an SPI peripheral acting as a slave device :cpp:type:`spi_slave_interface_config_t`, and transaction configuration structure :cpp:type:`spi_slave_transaction_t`.
As not every transaction requires both writing and reading data, you can choose to configure the :cpp:type:`spi_transaction_t` structure for TX only, RX only, or TX and RX transactions. If :cpp:member:`spi_slave_transaction_t::rx_buffer` is set to ``NULL``, the read phase will be skipped. Similarly, if :cpp:member:`spi_slave_transaction_t::tx_buffer` is set to ``NULL``, the write phase will be skipped.
.. note::
A Host should not start a transaction before its Device is ready for receiving data. It is recommended to use another GPIO pin for a handshake signal to sync the Devices. For more details, see :ref:`transaction_interval`.
.. only:: SOC_PSRAM_DMA_CAPABLE
Using PSRAM for DMA transfer
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
SPI Slave driver supports using PSRAM for DMA transfer. Directly passing a PSRAM address as :cpp:member:`spi_slave_transaction_t::tx_buffer` or :cpp:member:`spi_slave_transaction_t::rx_buffer` is supported. For the rx_buffer, it has alignment requirements, using :cpp:func:`heap_caps_malloc` to allocate memory can automatically handle the alignment requirements. For the buffers that you can not control, you can also use the :c:macro:`SPI_SLAVE_TRANS_DMA_BUFFER_ALIGN_AUTO` flag to enable driver to automatically align the buffer from PSRAM.
Note that this feature shares the MSPI bus bandwidth (bus frequency * bus width), so the transmission bandwidth of the host to this device should be less than the PSRAM bandwidth, otherwise **data may be lost**, and the ``spi_slave_transmit`` function will return the :c:macro:`ESP_ERR_INVALID_STATE` error.
Driver Usage
------------
- Initialize an SPI peripheral as a Device by calling the function :cpp:func:`spi_slave_initialize`. Make sure to set the correct I/O pins in the struct `bus_config`. Set the unused signals to ``-1``.
.. only:: esp32
If transactions are expected to be longer than 32 bytes, set the parameter ``dma_chan`` to ``1`` or ``2`` to allow a DMA channel 1 or 2 respectively. Otherwise, set ``dma_chan`` to ``0``.
.. only:: esp32s2
If transactions will be longer than 32 bytes, allow a DMA channel by setting the parameter ``dma_chan`` to the host device. Otherwise, set ``dma_chan`` to ``0``.
- Before initiating transactions, fill one or more :cpp:type:`spi_slave_transaction_t` structs with the transaction parameters required. Either queue all transactions by calling the function :cpp:func:`spi_slave_queue_trans` and, at a later time, query the result by using the function :cpp:func:`spi_slave_get_trans_result`, or handle all requests individually by feeding them into :cpp:func:`spi_slave_transmit`. The latter two functions will be blocked until the Host has initiated and finished a transaction, causing the queued data to be sent and received, or until ``ticks_to_wait`` expires.
- (Optional) Enable/Disable driver functions: Slave driver supports disabling / enabling driver after it is initialized by calling to :cpp:func:`spi_slave_disable` / :cpp:func:`spi_slave_enable`, to be able to change clock or power config or sleep to save power. By default, the driver state is `enabled` after initialized.
- (Optional) To unload the SPI slave driver, call :cpp:func:`spi_slave_free`.
Transaction Data and Master/Slave Length Mismatches
---------------------------------------------------
Normally, the data that needs to be transferred to or from a Device is read or written to a chunk of memory indicated by the :cpp:member:`spi_slave_transaction_t::rx_buffer` and :cpp:member:`spi_slave_transaction_t::tx_buffer`. The SPI driver can be configured to use DMA for transfers, in which case these buffers must be allocated in DMA-capable memory using ``pvPortMallocCaps(size, MALLOC_CAP_DMA)``.
The amount of data that the driver can read or write to the buffers is limited by :cpp:member:`spi_slave_transaction_t::length`. However, this member does not define the actual length of an SPI transaction. A transaction's length is determined by the clock and CS lines driven by the Host. The actual length of the transmission can be read only after a transaction is finished from the member :cpp:member:`spi_slave_transaction_t::trans_len`.
If the length of the transmission is greater than the buffer length, only the initial number of bits specified in the :cpp:member:`spi_slave_transaction_t::length` member will be sent and received. In this case, :cpp:member:`spi_slave_transaction_t::trans_len` is set to :cpp:member:`spi_slave_transaction_t::length` instead of the actual transaction length. To meet the actual transaction length requirements, set :cpp:member:`spi_slave_transaction_t::length` to a value greater than the maximum :cpp:member:`spi_slave_transaction_t::trans_len` expected. If the transmission length is shorter than the buffer length, only the data equal to the length of the buffer will be transmitted.
When you need to specify different TX/RX lengths in a single transaction, you can use the :cpp:member:`spi_slave_transaction_t::tx_length` and :cpp:member:`spi_slave_transaction_t::rx_length` members to specify the lengths of TX and RX respectively. This configuration is mutually exclusive with :cpp:member:`spi_slave_transaction_t::length`, and cannot be used simultaneously.
GPIO Matrix and IO_MUX
^^^^^^^^^^^^^^^^^^^^^^
.. only:: esp32
Most of {IDF_TARGET_NAME}'s peripheral signals have direct connection to their dedicated IO_MUX pins. However, the signals can also be routed to any other available pins using the less direct GPIO matrix.
If at least one signal is routed through the GPIO matrix, then all signals will be routed through it. If the driver is configured so that all SPI signals are either routed to their dedicated IO_MUX pins or are not connected at all, the GPIO matrix will be bypassed.
The GPIO matrix introduces flexibility of routing but also increases the input delay of the MISO signal, which makes MISO setup time violations more likely. If SPI needs to operate at high speeds, use dedicated IO_MUX pins.
.. note::
For more details about the influence of the MISO input delay on the maximum clock frequency, see :ref:`timing_considerations`.
The IO_MUX pins for SPI buses are given below.
.. list-table::
:widths: 40 30 30
:header-rows: 1
* - Pin Name
- GPIO Number (SPI2)
- GPIO Number (SPI3)
* - CS0
- 15
- 5
* - SCLK
- 14
- 18
* - MISO
- 12
- 19
* - MOSI
- 13
- 23
* - QUADWP
- 2
- 22
* - QUADHD
- 4
- 21
.. only:: not esp32
{IDF_TARGET_SPI2_IOMUX_PIN_CS:default="N/A", esp32s2="10", esp32s3="10", esp32c2="10", esp32c3="10", esp32c6="16", esp32h2="1", esp32p4="7" , esp32c5="10", esp32c61="8", esp32h21="12"}
{IDF_TARGET_SPI2_IOMUX_PIN_CLK:default="N/A", esp32s2="12", esp32s3="12", esp32c2="6", esp32c3="6", esp32c6="6", esp32h2="4", esp32p4="9" , esp32c5="6", esp32c61="6", esp32h21="2"}
{IDF_TARGET_SPI2_IOMUX_PIN_MOSI:default="N/A", esp32s2="11" esp32s3="11", esp32c2="7" esp32c3="7", esp32c6="7", esp32h2="5", esp32p4="8" , esp32c5="7", esp32c61="7", esp32h21="3"}
{IDF_TARGET_SPI2_IOMUX_PIN_MISO:default="N/A", esp32s2="13" esp32s3="13", esp32c2="2" esp32c3="2", esp32c6="2", esp32h2="0", esp32p4="10", esp32c5="2", esp32c61="2", esp32h21="4"}
{IDF_TARGET_SPI2_IOMUX_PIN_HD:default="N/A", esp32s2="9" esp32s3="9", esp32c2="4" esp32c3="4", esp32c6="4", esp32h2="3", esp32p4="6" , esp32c5="4", esp32c61="3", esp32h21="1"}
{IDF_TARGET_SPI2_IOMUX_PIN_WP:default="N/A", esp32s2="14" esp32s3="14", esp32c2="5" esp32c3="5", esp32c6="5", esp32h2="2", esp32p4="11", esp32c5="5", esp32c61="4", esp32h21="0"}
Most of chip's peripheral signals have direct connection to their dedicated IO_MUX pins. However, the signals can also be routed to any other available pins using the less direct GPIO matrix. If at least one signal is routed through the GPIO matrix, then all signals will be routed through it.
When an SPI Host is set to 80 MHz or lower frequencies, routing SPI pins via GPIO matrix will behave the same compared to routing them via IO_MUX.
The IO_MUX pins for SPI buses are given below.
.. list-table::
:widths: 40 30
:header-rows: 1
* - Pin Name
- GPIO Number (SPI2)
* - CS0
- {IDF_TARGET_SPI2_IOMUX_PIN_CS}
* - SCLK
- {IDF_TARGET_SPI2_IOMUX_PIN_CLK}
* - MISO
- {IDF_TARGET_SPI2_IOMUX_PIN_MISO}
* - MOSI
- {IDF_TARGET_SPI2_IOMUX_PIN_MOSI}
* - QUADWP
- {IDF_TARGET_SPI2_IOMUX_PIN_WP}
* - QUADHD
- {IDF_TARGET_SPI2_IOMUX_PIN_HD}
Speed and Timing Considerations
-------------------------------
.. _transaction_interval:
Transaction Interval
^^^^^^^^^^^^^^^^^^^^
The {IDF_TARGET_NAME} SPI slave peripherals are designed as general purpose Devices controlled by a CPU. As opposed to dedicated slaves, CPU-based SPI Devices have a limited number of pre-defined registers. All transactions must be handled by the CPU, which means that the transfers and responses are not real-time, and there might be noticeable latency.
As a solution, a Device's response rate can be doubled by using the functions :cpp:func:`spi_slave_queue_trans` and then :cpp:func:`spi_slave_get_trans_result` instead of using :cpp:func:`spi_slave_transmit`.
You can also configure a GPIO pin through which the Device will signal to the Host when it is ready for a new transaction. A code example of this can be found in :example:`peripherals/spi_slave`.
SCLK Frequency Requirements
^^^^^^^^^^^^^^^^^^^^^^^^^^^
{IDF_TARGET_MAX_FREQ:default="60", esp32="10", esp32s2="40", esp32c6="40", esp32h2="32"}
The SPI slaves are designed to operate at up to {IDF_TARGET_MAX_FREQ} MHz. The data cannot be recognized or received correctly if the clock is too fast or does not have a 50% duty cycle.
.. only:: esp32
On top of that, there are additional requirements for the data to meet the timing constraints:
- Read (MOSI):
The Device can read data correctly only if the data is already set at the launch edge. Although it is usually the case for most masters.
- Write (MISO):
The output delay of the MISO signal needs to be shorter than half of a clock cycle period so that the MISO line is stable before the next latch edge. Given that the clock is balanced, the output delay and frequency limitations in different cases are given below.
.. list-table::
:widths: 30 40 40
:header-rows: 1
* - /
- Output delay of MISO (ns)
- Freq. limit (MHz)
* - IO_MUX
- 43.75
- < 11.4
* - GPIO matrix
- 68.75
- < 7.2
Note:
1. If the frequency reaches the maximum limitation, random errors may occur.
2. The clock uncertainty between the Host and the Device (12.5 ns) is included.
3. The output delay is measured under ideal circumstances (no load). If the MISO pin is heavily loaded, the output delay will be longer, and the maximum allowed frequency will be lower.
Exception: The frequency is allowed to be higher if the master has more tolerance for the MISO setup time, e.g., latch data at the next edge, or configurable latching time.
.. _spi_dma_known_issues:
Restrictions and Known Issues
-----------------------------
1. If DMA is enabled, the rx buffer should be word-aligned (starting from a 32-bit boundary and having a length of multiples of 4 bytes). Otherwise, DMA may write incorrectly or not in a boundary aligned manner. The driver reports an error if this condition is not satisfied.
Also, a Host should write lengths that are multiples of 4 bytes. The data with inappropriate lengths will be discarded.
.. only:: esp32
2. Furthermore, DMA requires SPI modes 1 and 3. For SPI modes 0 and 2, the MISO signal has to be launched half a clock cycle earlier to meet the timing. The new timing is as follows:
.. wavedrom:: /../_static/diagrams/spi/spi_slave_miso_dma.json
If DMA is enabled, a Device's launch edge is half of an SPI clock cycle ahead of the normal time, shifting to the Master's actual latch edge. In this case, if the GPIO matrix is bypassed, the hold time for data sampling is 68.75 ns and no longer a half of an SPI clock cycle. If the GPIO matrix is used, the hold time will increase to 93.75 ns. The Host should sample the data immediately at the latch edge or communicate in SPI modes 1 or 3. If your Host cannot meet these timing requirements, initialize your Device without DMA.
3. ESP32 SPI Slave **still** outputs the level 0/1 on the MISO pin even when the CS line is not asserted, which may cause other devices on the bus to output incorrect data. The solution is:
1) Use a separate bus for the ESP32 SPI Slave, not sharing it with other devices.
2) Add a buffer chip between the ESP32 SPI MISO pin and the bus, such as 74HC125.
Application Examples
--------------------
The code example for Device/Host communication can be found in the :example:`peripherals/spi_slave` directory of ESP-IDF examples.
- :example: `peripherals/spi_slave/receiver` demonstrates how to configure an SPI slave to receive data from an SPI master and implement handshaking to manage data transfer readiness.
- :example: `peripherals/spi_slave/sender` demonstrate how to configure an SPI master to send data to an SPI slave and use handshaking to ensure proper timing for data transmission.
API Reference
-------------
.. include-build-file:: inc/spi_slave.inc
@@ -0,0 +1,163 @@
SPI Slave Half Duplex
=====================
:link_to_translation:`zh_CN:[中文]`
Introduction
------------
The Half Duplex (HD) Mode is a special mode provided by ESP SPI Slave peripheral. Under this mode, the hardware provides more services than the Full Duplex (FD) Mode (the mode for general-purpose SPI transactions, see :doc:`spi_slave`). These services reduce the CPU load and the response time of SPI Slave. However, it is important to note that the communication format is determined by the hardware and is always in a half-duplex configuration, allowing only one-way data transfer at any given time. Hence, the mode is named Half Duplex Mode due to this characteristic.
When conducting an SPI transaction, transactions can be classified into several types based on the **command** phase of the transaction. Each transaction may consist of the following phases: command, address, dummy, and data. The command phase is mandatory, while the other phases may be determined by the command field. During the command, address, and dummy phases, the bus is always controlled by the master (usually the host), while the direction of the data phase depends on the command. The data phase can be either an input phase, where the master writes data to the slave (e.g., the host sends data to the slave), or an output phase, where the master reads data from the slave (e.g., the host receives data from the slave).
Protocol
^^^^^^^^
About the details of how master should communicate with the SPI Slave, see `ESP SPI Slave HD Protocol <https://espressif.github.io/idf-extra-components/latest/esp_serial_slave_link/spi_slave_hd_protocol.html#spi-slave-hd-half-duplex-protocol>`_.
Through these different transactions, the slave provides these services to the master:
- A DMA channel for the master to write a great amount of data to the slave.
- A DMA channel for the master to read a great amount of data from the slave.
- Several general purpose registers, shared between the master and the slave.
- Several general purpose interrupts, for the master to interrupt the SW of the slave.
Terminology
-----------
- Transaction
- Channel
- Sending
- Receiving
- Data Descriptor
Driver Feature
--------------
- Transaction read/write by master in segments
- Queues for data to send and received
Driver Usage
------------
Slave Initialization
^^^^^^^^^^^^^^^^^^^^
Call :cpp:func:`spi_slave_hd_init` to initialize the SPI bus as well as the peripheral and the driver. The SPI Slave exclusively uses the SPI peripheral, pins of the bus before it is deinitialized, which means other devices are unable to use the above resources during initialization. Thus, to ensure SPI resources are correctly occupied and the connections work properly, most configurations of the slave should be done as soon as the slave is initialized.
The :cpp:type:`spi_bus_config_t` specifies how the bus should be initialized, while :cpp:type:`spi_slave_hd_slot_config_t` specifies how the SPI Slave driver should work.
To use 3-wire mode, also known as single I/O (SIO) mode, set :c:macro:`SPI_SLAVE_HD_3WIRE_MODE` in :cpp:member:`spi_slave_hd_slot_config_t::flags`. In this mode, MOSI is used for both input and output data, so :cpp:member:`spi_bus_config_t::mosi_io_num` must be set to an output-capable GPIO. The MISO line is not used and :cpp:member:`spi_bus_config_t::miso_io_num` can be set to ``-1``. The master should use the 1-bit SPI Slave HD commands in this mode. Commands with DIO/QIO masks will select 2-line or 4-line data phases in hardware and are not compatible with 3-wire mode, resulting in data errors.
Enable/Disable Driver (Optional)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Slave driver supports disabling / enabling driver after it is initialized by calling to :cpp:func:`spi_slave_hd_disable` / :cpp:func:`spi_slave_hd_enable`, to be able to change clock or power config or sleep to save power. By default, the driver state is `enabled` after initialized.
Deinitialization (Optional)
^^^^^^^^^^^^^^^^^^^^^^^^^^^
Call :cpp:func:`spi_slave_hd_deinit` to uninstall the driver. The resources, including the pins, SPI peripheral, internal memory used by the driver, and interrupt sources, are released by the ``deinit()`` function.
Send/Receive Data by DMA Channels
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
To send data to the master through the sending DMA channel, the application should properly wrap the data in an :cpp:type:`spi_slave_hd_data_t` descriptor structure before calling :cpp:func:`spi_slave_hd_queue_trans` with the data descriptor and the channel argument of :cpp:enumerator:`SPI_SLAVE_CHAN_TX`. The pointers to descriptors are stored in the queue, and the data is sent to the master in the same order they are enqueued using :cpp:func:`spi_slave_hd_queue_trans`, upon receiving the master's ``Rd_DMA`` command.
.. only:: SOC_PSRAM_DMA_CAPABLE
The driver supports using PSRAM for DMA transfer. Directly passing a PSRAM address as :cpp:member:`spi_slave_hd_data_t::data` is supported. For the DMA receive channel, its memory address and transfer length have alignment requirements, using :cpp:func:`heap_caps_malloc` to allocate memory can automatically handle the alignment requirements. For the buffers that you can not control, you can also use the :c:macro:`SPI_SLAVE_HD_TRANS_DMA_BUFFER_ALIGN_AUTO` flag to enable driver to automatically align the buffer from PSRAM.
Note that this feature shares the MSPI bus bandwidth (bus frequency * bus width), so the transmission bandwidth of the host to this device should be less than the PSRAM bandwidth, otherwise **data may be lost**, and then getting the transmission result will return the :c:macro:`ESP_ERR_INVALID_STATE` error.
The application should check the result of data sending by calling :cpp:func:`spi_slave_hd_get_trans_res` with the channel set as :cpp:enumerator:`SPI_SLAVE_CHAN_TX`. This function blocks until the transaction with the command ``Rd_DMA`` from the master successfully completes (or timeout). The ``out_trans`` argument of the function outputs the pointer of the data descriptor which is just finished, providing information about the sending.
Receiving data from the master through the receiving DMA channel is quite similar. The application calls :cpp:func:`spi_slave_hd_queue_trans` with proper data descriptor and the channel argument of :cpp:enumerator:`SPI_SLAVE_CHAN_RX`. And the application calls the :cpp:func:`spi_slave_hd_get_trans_res` later to get the descriptor to the receiving buffer before it handles the data in the receiving buffer.
.. note::
This driver itself does not have an internal buffer for the data to send or just received. The application should provide data buffer for driver via data descriptors to send to the master, or to receive data from the master.
The application has to properly keep the data descriptor as well as the buffer it points, after the descriptor is successfully sent into the driver internal queue by :cpp:func:`spi_slave_hd_queue_trans`, and before returned by :cpp:func:`spi_slave_hd_get_trans_res`. During this period, the hardware as well as the driver may read or write to the buffer and the descriptor when required at any time.
Please note that, when using this driver for data transfer, the buffer does not have to be fully sent or filled before it is terminated. For example, in the segment transaction mode, the master has to send ``CMD7`` to terminate a ``Wr_DMA`` transaction or send ``CMD8`` to terminate an ``Rd_DMA`` transaction (in segments), no matter whether the send (receive) buffer is used up (full) or not.
.. _spi_slave_hd_data_arguments:
Using Data Descriptor with Customized User Arguments
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Sometimes you may have initiator (sending data descriptor) and closure (handling returned descriptors) functions in different places. When you get the returned data descriptor in the closure, you may need some extra information when handling the finished data descriptor. For example, you may want to know which round it is for the returned descriptor when you send the same piece of data several times.
Set the ``arg`` member in the data descriptor to a variable indicating the transaction by force casting, or point it to a structure that wraps all the information you may need when handling the sending/receiving data. Then you can get what you need in your closure.
.. _spi_slave_hd_callbacks:
Using Callbacks
^^^^^^^^^^^^^^^
.. note::
These callbacks are called in the ISR, so the required operations need to be processed quickly and returned as soon as possible to ensure that the system is functioning properly. You may need to be very careful to write the code in the ISR.
Since the interrupt handling is executed concurrently with the application, long delays or blocking may cause the system to respond slower or lead to unpredictable behavior. Therefore, when writing callback functions, avoid using operations that may cause delays or blocking, e.g., waiting, sleeping, resource locking, etc.
The :cpp:type:`spi_slave_hd_callback_config_t` member in the :cpp:type:`spi_slave_hd_slot_config_t` configuration structure passed when initializing the SPI Slave HD driver, allows you to have callbacks for each event you may concern.
The corresponding interrupt for each callback that is not **NULL** is enabled, so that the callbacks can be called immediately when the events happen. You do not need to provide callbacks for the unconcerned events.
The ``arg`` member in the configuration structure can help you pass some context to the callback or indicate the specific SPI Slave instance when using the same callbacks for multiple SPI Slave peripherals. You can set the arg member to a variable that indicates the SPI Slave instance by performing a forced type casting or point it to a context structure. All the callbacks are called with this ``arg`` argument you set when the callbacks are initialized.
There are two other arguments: the ``event`` and the ``awoken``.
- The ``event`` passes the information of the current event to the callback. The :cpp:type:`spi_slave_hd_event_t` type contains the information of the event, for example, event type, the data descriptor just finished (The :ref:`data argument <spi_slave_hd_data_arguments>` is very useful in this case!).
- The ``awoken`` argument serves as an output parameter. It informs the ISR that tasks have been awakened after the callback function, and the ISR should call `portYIELD_FROM_ISR()` to schedule these tasks. Simply pass the ``awoken`` argument to all FreeRTOS APIs that may unblock tasks, and the value of ``awoken`` will be returned to the ISR.
Writing/Reading Shared Registers
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Call :cpp:func:`spi_slave_hd_write_buffer` to write the shared buffer, and :cpp:func:`spi_slave_hd_read_buffer` to read the shared buffer.
.. note::
On {IDF_TARGET_NAME}, the shared registers are read/written in words by the application but read/written in bytes by the master. There is no guarantee four continuous bytes read from the master are from the same word written by the slave's application. It is also possible that if the slave reads a word while the master is writing bytes of the word, the slave may get one word with half of them just written by the master, and the other half has not been written into.
The master can confirm that the word is not in transition by reading the word twice and comparing the values.
For the slave, it is more difficult to ensure the word is not in transition because the process of master writing four bytes can be very long (32 SPI clocks). You can put some CRC in the last (largest address) byte of a word so that when the byte is written, the word is sure to be all written.
Due to the conflicts that may be among read/write from SW (worse if there are multi-cores) and master, it is suggested that a word is only used in one direction (only written by the master or only written by the slave).
Receiving General Purpose Interrupts from the Master
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
When the master sends ``CMD8``, ``CMD9`` or ``CMDA``, the slave corresponding is triggered. Currently the ``CMD8`` is permanently used to indicate the termination of ``Rd_DMA`` segments. To receive general-purpose interrupts, register callbacks for ``CMD9`` and ``CMDA`` when the slave is initialized, see :ref:`spi_slave_hd_callbacks`.
.. only:: SOC_SPI_SUPPORT_SLEEP_RETENTION
Sleep Retention
^^^^^^^^^^^^^^^
{IDF_TARGET_NAME} supports to retain the SPI register context before entering **light sleep** and restore them after waking up. This means you don't have to re-init the SPI driver after the light sleep.
This feature can be enabled by setting the flag :c:macro:`SPICOMMON_BUSFLAG_SLP_ALLOW_PD`. It will allow the system to power down the SPI in light sleep, meanwhile save the register context. It can help to save more power consumption with some extra cost of the memory.
Notice that when GPSPI is working as a slave, it is **not** support to enter sleep when any transaction (including TX and RX) is not finished.
.. only:: not esp32
Application Examples
--------------------
The code example for Device/Host communication can be found in the :example:`peripherals/spi_slave_hd` directory of ESP-IDF examples.
- :example: `peripherals/spi_slave_hd/append_mode` demonstrates how to use the SPI Slave HD driver and ESSL driver to communicate (ESSL driver is an encapsulated layer based on SPI Master driver to communicate with halfduplex mode SPI Slave).
- :example: `peripherals/spi_slave_hd/segment_mode` demonstrate two ways to use the SPI Slave Halfduplex Segment Mode: Using the SPI Slave Halfduplex driver with two tasks repeating transactions with the SPI Master, and using the ESP Serial Slave Link APIs for multiple exchanges with the slave.
API Reference
-------------
.. include-build-file:: inc/spi_slave_hd.inc
@@ -0,0 +1,205 @@
Temperature Sensor
==================
:link_to_translation:`zh_CN:[中文]`
Introduction
------------
The {IDF_TARGET_NAME} has a built-in sensor used to measure the chip's internal temperature. The temperature sensor module contains an 8-bit Sigma-Delta analog-to-digital converter (ADC) and a digital-to-analog converter (DAC) to compensate for the temperature measurement.
.. note::
The temperature sensor is designed primarily to measure the temperature **inside** the silicon. The sensor can reflect the temperature changes very well but it can't give a precise measurement value. So it's not recommended to use it for ambient temperature measurement.
Functional Overview
-------------------
The description of the temperature sensor functionality is divided into the following sections:
.. list::
- :ref:`temp-resource-allocation` - covers which parameters should be set up to get a temperature sensor handle and how to recycle the resources when the temperature sensor finishes working.
- :ref:`temp-enable-and-disable-temperature-sensor` - covers how to enable and disable the temperature sensor.
- :ref:`temp-get-temperature-value` - covers how to get the real-time temperature value.
:SOC_TEMPERATURE_SENSOR_INTR_SUPPORT: - :ref:`temp-install-temperature-threshold-callback` - describes how to register a temperature threshold callback.
- :ref:`temp-power-management` - covers how the temperature sensor is affected when changing power mode (e.g., Light-sleep mode).
:SOC_TEMPERATURE_SENSOR_INTR_SUPPORT: - :ref:`temp-iram-safe` - describes tips on how to make the temperature sensor interrupt work better along with a disabled cache.
- :ref:`temp-thread-safety` - covers how to make the driver to be thread-safe.
:SOC_TEMPERATURE_SENSOR_SUPPORT_ETM: - :ref:`temperature-sensor-etm-event-and-task` - describes what the events and tasks can be connected to the ETM channel.
.. _temp-resource-allocation:
Resource Allocation
^^^^^^^^^^^^^^^^^^^
The {IDF_TARGET_NAME} has just one built-in temperature sensor hardware. The temperature sensor instance is represented by :cpp:type:`temperature_sensor_handle_t`, which is also the bond of the context. By using :cpp:type:`temperature_sensor_handle_t`, the temperature sensor properties can be accessed and modified in different function calls to control and manage the temperature sensor. The variable would always be the parameter of the temperature APIs with the information of hardware and configurations, so you can just create a pointer of type :cpp:type:`temperature_sensor_handle_t` and passing to APIs as needed.
In order to install a built-in temperature sensor instance, the first thing is to evaluate the temperature range in your detection environment. For example, if the testing environment is in a room, the range you evaluate might be 10 °C ~ 30 °C; if the testing in a lamp bulb, the range you evaluate might be 60 °C ~ 110 °C. Based on that, configuration structure :cpp:type:`temperature_sensor_config_t` should be defined in advance:
- :cpp:member:`range_min`: The minimum value of the testing range you have evaluated.
- :cpp:member:`range_max`: The maximum value of the testing range you have evaluated.
- :cpp:member:`allow_pd` configures if the driver allows the system to power down the peripheral in light sleep mode. Before entering sleep, the system will backup the temperature sensor register context, which will be restored later when the system exit the sleep mode. Powering down the peripheral can save more power, but at the cost of more memory consumed to save the register context. It's a tradeoff between power consumption and memory consumption. This configuration option relies on specific hardware feature, if you enable it on an unsupported chip, you will see error message like ``not able to power down in light sleep``.
After the ranges are set, the structure could be passed to :cpp:func:`temperature_sensor_install`, which will instantiate the temperature sensor instance and return a handle.
As mentioned above, different measure ranges have different measurement errors. You do not need to care about the measurement error because we have an internal mechanism to choose the minimum error according to the given range.
If the temperature sensor is no longer needed, you need to call :cpp:func:`temperature_sensor_uninstall` to free the temperature sensor resource.
Creating a Temperature Sensor Handle
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Step 1: Evaluate the testing range. In this example, the range is 20 °C ~ 50 °C.
* Step 2: Configure the range and obtain a handle.
.. code:: c
temperature_sensor_handle_t temp_handle = NULL;
temperature_sensor_config_t temp_sensor_config = TEMPERATURE_SENSOR_CONFIG_DEFAULT(20, 50);
ESP_ERROR_CHECK(temperature_sensor_install(&temp_sensor_config, &temp_handle));
.. _temp-enable-and-disable-temperature-sensor:
Enable and Disable Temperature Sensor
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1. Enable the temperature sensor by calling :cpp:func:`temperature_sensor_enable`. The internal temperature sensor circuit will start to work. The driver state will transit from init to enable.
2. To Disable the temperature sensor, please call :cpp:func:`temperature_sensor_disable`.
.. _temp-get-temperature-value:
Get Temperature Value
^^^^^^^^^^^^^^^^^^^^^
After the temperature sensor is enabled by :cpp:func:`temperature_sensor_enable`, you can get the current temperature by calling :cpp:func:`temperature_sensor_get_celsius`.
.. code:: c
// Enable temperature sensor
ESP_ERROR_CHECK(temperature_sensor_enable(temp_handle));
// Get converted sensor data
float tsens_out;
ESP_ERROR_CHECK(temperature_sensor_get_celsius(temp_handle, &tsens_out));
printf("Temperature in %f °C\n", tsens_out);
// Disable the temperature sensor if it is not needed and save the power
ESP_ERROR_CHECK(temperature_sensor_disable(temp_handle));
.. only:: SOC_TEMPERATURE_SENSOR_INTR_SUPPORT
.. _temp-install-temperature-threshold-callback:
Install Temperature Threshold Callback
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
{IDF_TARGET_NAME} supports automatically triggering to monitor the temperature value continuously. When the temperature value reaches a given threshold, an interrupt will happen. Thus you can install your own interrupt callback functions to do what they want, e.g., alarm, restart, etc. The following information indicates how to prepare a threshold callback.
- :cpp:member:`temperature_sensor_event_callbacks_t::on_threshold`: As this function is called within the ISR context, you must ensure that the function does not attempt to block, e.g., by making sure that only FreeRTOS APIs with the ``ISR`` suffix are called from within the function, etc. The function prototype is declared in :cpp:type:`temperature_thres_cb_t`.
You can save your own context to :cpp:func:`temperature_sensor_register_callbacks` as well, via the parameter ``user_arg``. The user data will be directly passed to the callback function.
.. code:: c
IRAM_ATTR static bool temp_sensor_monitor_cbs(temperature_sensor_handle_t tsens, const temperature_sensor_threshold_event_data_t *edata, void *user_data)
{
ESP_DRAM_LOGI("tsens", "Temperature value is higher or lower than threshold, value is %d\n...\n\n", edata->celsius_value);
return false;
}
// Callback configurations
temperature_sensor_abs_threshold_config_t threshold_cfg = {
.high_threshold = 50,
.low_threshold = -10,
};
// Set absolute value monitor threshold.
temperature_sensor_set_absolute_threshold(temp_sensor, &threshold_cfg);
// Register interrupt callback
temperature_sensor_event_callbacks_t cbs = {
.on_threshold = temp_sensor_monitor_cbs,
};
// Install temperature callback.
temperature_sensor_register_callbacks(temp_sensor, &cbs, NULL);
.. _temp-power-management:
.. only:: not SOC_TEMPERATURE_SENSOR_INTR_SUPPORT
.. _temp-power-management:
Power Management
^^^^^^^^^^^^^^^^
As the temperature sensor does not use the APB clock, it will keep working no matter if the power management is enabled with ``CONFIG_PM_ENABLE``.
.. only:: SOC_TEMPERATURE_SENSOR_INTR_SUPPORT
.. _temp-iram-safe:
IRAM Safe
^^^^^^^^^
By default, the temperature sensor interrupt will be deferred when the cache is disabled for reasons like writing/erasing flash. Thus the event callback functions will not get executed in time, which is not expected in a real-time application.
There is a Kconfig option :ref:`CONFIG_TEMP_SENSOR_ISR_IRAM_SAFE` that will:
1. Enable the interrupt that is being serviced even when the cache is disabled.
2. Place all functions that are used by the ISR into IRAM.
This allows the interrupt to run while the cache is disabled but comes at the cost of increased IRAM consumption.
.. _temp-thread-safety:
.. only:: not SOC_TEMPERATURE_SENSOR_INTR_SUPPORT
.. _temp-thread-safety:
Thread Safety
^^^^^^^^^^^^^
In the temperature sensor driver, we do not add any protection to ensure the thread safety, because typically this driver is only supposed to be used in one task. If you have to use this driver in different tasks, please add extra locks to protect it.
.. only:: SOC_TEMPERATURE_SENSOR_SUPPORT_ETM
.. _temperature-sensor-etm-event-and-task:
ETM Event and Task
^^^^^^^^^^^^^^^^^^
Temperature Sensor is able to generate events that can interact with the :doc:`ETM </api-reference/peripherals/etm>` module. The supported events are listed in the :cpp:type:`temperature_sensor_etm_event_type_t`. You can call :cpp:func:`temperature_sensor_new_etm_event` to get the corresponding ETM event handle. The supported tasks are listed in the :cpp:type:`temperature_sensor_etm_task_type_t`. You can call :cpp:func:`temperature_sensor_new_etm_task` to get the corresponding ETM task handle.
.. note::
- :cpp:enumerator:`TEMPERATURE_SENSOR_EVENT_OVER_LIMIT` for :cpp:member:`temperature_sensor_etm_event_type_t::event_type` depends on what kind of threshold you set first. If you set the absolute threshold by :cpp:func:`temperature_sensor_set_absolute_threshold`, then the :cpp:enumerator:`TEMPERATURE_SENSOR_EVENT_OVER_LIMIT` refers to absolute threshold. Likewise, if you set the delta threshold by :cpp:func:`temperature_sensor_set_delta_threshold`, then the :cpp:enumerator:`TEMPERATURE_SENSOR_EVENT_OVER_LIMIT` refers to delta threshold.
For how to connect the event and task to an ETM channel, please refer to the :doc:`ETM </api-reference/peripherals/etm>` documentation.
Unexpected Behaviors
--------------------
1. The value you get from the chip is usually different from the ambient temperature. It is because the temperature sensor is built inside the chip. To some extent, it measures the temperature of the chip.
2. When installing the temperature sensor, the driver may print ``the boundary you gave cannot meet the range of internal temperature sensor``. It is because the built-in temperature sensor has a testing limit. The error comes from the incorrect configuration of :cpp:type:`temperature_sensor_config_t` as follow:
(1) Totally out of range, like 200 °C ~ 300 °C.
(2) Cross the boundary of each predefined measurement. like 40 °C ~ 110 °C.
Application Examples
--------------------
* :example:`peripherals/temperature_sensor/temp_sensor` demonstrates how to use the built-in temperature sensor, showcasing the measurement range and error based on different DAC levels and offsets.
.. only:: SOC_TEMPERATURE_SENSOR_INTR_SUPPORT
* :example:`peripherals/temperature_sensor/temp_sensor_monitor` demonstrates how to use the temperature sensor to automatically monitor temperature values continuously, triggering an interrupt when a specific value is reached or when the change between two consecutive samplings is larger/smaller than the settings.
API Reference
----------------------------------
.. include-build-file:: inc/temperature_sensor.inc
.. include-build-file:: inc/temperature_sensor_types.inc
.. only:: SOC_TEMPERATURE_SENSOR_SUPPORT_ETM
.. include-build-file:: inc/temperature_sensor_etm.inc
+482
View File
@@ -0,0 +1,482 @@
====================================
Two-Wire Automotive Interface (TWAI)
====================================
:link_to_translation:`zh_CN:[中文]`
This document introduces the features of the Two-Wire Automotive Interface (TWAI) controller driver in ESP-IDF. The chapter structure is as follows:
.. contents::
:local:
:depth: 2
Overview
========
TWAI is a highly reliable, multi-master, real-time, serial asynchronous communication protocol designed for automotive and industrial applications. It is compatible with the frame structure defined in the ISO 11898-1 standard and supports both standard frames with 11-bit identifiers and extended frames with 29-bit identifiers. The protocol supports message prioritization with lossless arbitration, automatic retransmission, and fault confinement mechanisms. The {IDF_TARGET_NAME} includes {IDF_TARGET_CONFIG_SOC_TWAI_CONTROLLER_NUM} TWAI controllers, allowing for the creation of {IDF_TARGET_CONFIG_SOC_TWAI_CONTROLLER_NUM} driver instances.
.. only:: SOC_TWAI_FD_SUPPORTED
The TWAI controllers on the {IDF_TARGET_NAME} also compatible with FD format (a.k.a. CAN FD) frames defined in ISO 11898-1, and can transmit and receive both classic and FD format frames.
.. only:: not SOC_TWAI_FD_SUPPORTED
The TWAI controllers on the {IDF_TARGET_NAME} are **not compatible with FD format frames and will interpret such frames as errors.**
Thanks to its hardware-based fault tolerance and multi-master architecture, the TWAI driver is ideal for scenarios such as:
- Serving as a robust communication bus in environments with significant electrical noise
- Enabling long-distance communication across multiple sensors/actuators with resilience to single-node failures
- Building decentralized distributed local networks that avoid the unpredictability of single-master designs
- Acting as a bridging node alongside other communication protocols
Getting Started
===============
This section provides a quick overview of how to use the TWAI driver. Through simple examples, it demonstrates how to create a TWAI node instance, transmit and receive messages on the bus, and safely stop and uninstall the driver. The general usage flow is as follows:
.. image:: ../../../_static/diagrams/twai/base_flow.drawio.svg
:align: center
Hardware Connection
-------------------
The {IDF_TARGET_NAME} does not integrate an internal TWAI transceiver. Therefore, an external transceiver is required to connect to a TWAI bus. The model of the external transceiver depends on the physical layer standard used in your specific application. For example, a TJA105x transceiver can be used to comply with the ISO 11898-2 standard.
.. image:: ../../../_static/diagrams/twai/hw_connection.svg
:alt: ESP32 to Transceiver Wiring
:align: center
Specifically:
- For single-node testing, you can directly short the TX and RX pins to omit the transceiver.
- BUS_OFF (optional): Outputs a low logic level (0V) when the TWAI controller enters the bus-off state. Otherwise, it remains at a high logic level (3.3V).
- CLK_OUT (optional): Outputs the time quantum clock of the controller, which is a divided version of the source clock.
Creating and Starting a TWAI Node
---------------------------------
First, we need to create a TWAI instance. The following code demonstrates how to create a TWAI node with a baud rate of 200kHz:
.. code:: c
#include "esp_twai.h"
#include "esp_twai_onchip.h"
twai_node_handle_t node_hdl = NULL;
twai_onchip_node_config_t node_config = {
.io_cfg.tx = 4, // TWAI TX GPIO pin
.io_cfg.rx = 5, // TWAI RX GPIO pin
.bit_timing.bitrate = 200000, // 200 kbps bitrate
.tx_queue_depth = 5, // Transmit queue depth set to 5
};
// Create a new TWAI controller driver instance
ESP_ERROR_CHECK(twai_new_node_onchip(&node_config, &node_hdl));
// Start the TWAI controller
ESP_ERROR_CHECK(twai_node_enable(node_hdl));
When creating a TWAI instance, you must configure parameters such as GPIO pins and baud rate using the :cpp:type:`twai_onchip_node_config_t` structure. These parameters determine how the TWAI node operates. Then, you can call the :cpp:func:`twai_new_node_onchip` function to create a new TWAI instance. This function returns a handle to the newly created instance. A TWAI handle is essentially a pointer to an internal TWAI memory object of type :cpp:type:`twai_node_handle_t`.
Below are additional configuration fields of the :cpp:type:`twai_onchip_node_config_t` structure along with their descriptions:
- :cpp:member:`twai_onchip_node_config_t::clk_src`: Specifies the clock source used by the controller. Supported sources are listed in :cpp:type:`twai_clock_source_t`.
- :cpp:member:`twai_onchip_node_config_t::bit_timing::sp_permill`: Specifies the location of the sample point. ssp_permill sets the location of the secondary sample point and can be used to fine-tune timing in low SNR conditions.
- :cpp:member:`twai_onchip_node_config_t::data_timing`: Specifies the baud rate and sample point for the data phase in FD frames. This field is ignored if the controller does not support FD format.
- :cpp:member:`twai_onchip_node_config_t::fail_retry_cnt`: Sets the number of retry attempts on transmission failure. -1 indicates infinite retries until success or bus-off; 0 disables retries (single-shot mode); 1 retries once, and so on.
- :cpp:member:`twai_onchip_node_config_t::intr_priority`: Interrupt priority in the range [0:3], where higher values indicate higher priority.
- :cpp:member:`twai_onchip_node_config_t::flags`: A set of flags for fine-tuning driver behavior. Options include:
- :cpp:member:`twai_onchip_node_config_t::flags::enable_self_test`: Enables self-test mode. In this mode, ACK is not checked during transmission, which is useful for single-node testing.
- :cpp:member:`twai_onchip_node_config_t::flags::enable_loopback`: Enables loopback mode. The node will receive its own transmitted messages (subject to filter configuration), while also transmitting them to the bus.
- :cpp:member:`twai_onchip_node_config_t::flags::enable_listen_only`: Configures the node in listen-only mode. In this mode, the node only receives and does not transmit any dominant bits, including ACK and error frames.
- :cpp:member:`twai_onchip_node_config_t::flags::no_receive_rtr`: When using filters, determines whether remote frames matching the ID pattern should be filtered out.
- :cpp:member:`twai_onchip_node_config_t::flags::enable_scheduled_tx`: Enables scheduled transmission. This option requires a non-zero :cpp:member:`twai_onchip_node_config_t::timestamp_resolution_hz`.
The :cpp:func:`twai_node_enable` function starts the TWAI controller. Once enabled, the controller is connected to the bus and can transmit messages. It also generates events upon receiving messages from other nodes on the bus or when bus errors are detected.
The corresponding function, :cpp:func:`twai_node_disable`, immediately stops the node and disconnects it from the bus. Any ongoing transmissions will be aborted. When the node is re-enabled later, if there are pending transmissions in the queue, the driver will immediately initiate a new transmission attempt.
Transmitting Messages
---------------------
TWAI messages come in various types, which are specified by their headers. A typical data frame consists primarily of a header and data payload, with a structure similar to the following:
.. image:: ../../../_static/diagrams/twai/frame_struct.svg
:align: center
To reduce performance overhead caused by memory copying, the TWAI driver uses pointers to pass messages. The driver is designed to operate in asynchronous mode, so the :cpp:type:`twai_frame_t` structure and the memory pointed to by :cpp:member:`twai_frame_t::buffer` must remain valid until the transmission is actually complete. You can determine when transmission is complete in the following ways:
- Call the :cpp:func:`twai_node_transmit_wait_all_done` function to wait for all transmissions to complete.
- Register the :cpp:member:`twai_event_callbacks_t::on_tx_done` event callback function to receive a notification when transmission is complete.
The following code demonstrates how to transmit a typical data frame:
.. code:: c
uint8_t send_buff[8] = {0};
twai_frame_t tx_msg = {
.header.id = 0x1, // Message ID
.header.ide = true, // Use 29-bit extended ID format
.buffer = send_buff, // Pointer to data to transmit
.buffer_len = sizeof(send_buff), // Length of data to transmit
};
ESP_ERROR_CHECK(twai_node_transmit(node_hdl, &tx_msg, 0)); // Timeout = 0: returns immediately if queue is full
ESP_ERROR_CHECK(twai_node_transmit_wait_all_done(node_hdl, -1)); // Wait for transmission to finish
In this example, :cpp:member:`twai_frame_t::header::id` specifies the ID of the message as 0x01. Message IDs are typically used to indicate the type of message in an application and also play a role in bus arbitration during transmission—lower values indicate higher priority on the bus. :cpp:member:`twai_frame_t::buffer` points to the memory address where the data to be transmitted is stored, and :cpp:member:`twai_frame_t::buffer_len` specifies the length of that data. The :cpp:func:`twai_node_transmit` function is thread-safe and can also be called from an ISR. When called from an ISR, the ``timeout`` parameter is ignored, and the function will not block.
Note that :cpp:member:`twai_frame_t::header::dlc` can also specify the length of the data in the frame. The DLC (Data Length Code) is mapped to the actual data length as defined in ISO 11898-1. You can use either :cpp:func:`twaifd_dlc2len` or :cpp:func:`twaifd_len2dlc` for conversion. If both dlc and buffer_len are non-zero, they must represent the same length.
The :cpp:type:`twai_frame_t` message structure also includes other configuration fields:
- :cpp:member:`twai_frame_t::dlc`: Data Length Code. For classic frames, values [0:8] represent lengths [0:8]; for FD format, values [0:15] represent lengths up to 64 bytes.
- :cpp:member:`twai_frame_t::header::ide`: Indicates use of a 29-bit extended ID format.
- :cpp:member:`twai_frame_t::header::rtr`: Indicates the frame is a remote frame, which contains no data payload.
- :cpp:member:`twai_frame_t::header::fdf`: Marks the frame as an FD format frame, supporting up to 64 bytes of data.
- :cpp:member:`twai_frame_t::header::brs`: Enables use of a separate data-phase baud rate when transmitting.
- :cpp:member:`twai_frame_t::header::esi`: For received frames, indicates the error state of the transmitting node.
- :cpp:member:`twai_frame_t::tx_queue_priority`: Local transmit queue priority. See `Transmit Queue Priority`_ for details.
Receiving Messages
------------------
Receiving messages must be done within a receive event callback. Therefore, to receive messages, you need to register a receive event callback via :cpp:member:`twai_event_callbacks_t::on_rx_done` before starting the controller. This enables the controller to deliver received messages via the callback when events occur. The following code snippets demonstrate how to register the receive event callback and how to handle message reception inside the callback:
Registering the receive event callback (before starting the controller):
.. code:: c
twai_event_callbacks_t user_cbs = {
.on_rx_done = twai_rx_cb,
};
ESP_ERROR_CHECK(twai_node_register_event_callbacks(node_hdl, &user_cbs, NULL));
Receiving messages inside the callback:
.. code:: c
static bool twai_rx_cb(twai_node_handle_t handle, const twai_rx_done_event_data_t *edata, void *user_ctx)
{
uint8_t recv_buff[8];
twai_frame_t rx_frame = {
.buffer = recv_buff,
.buffer_len = sizeof(recv_buff),
};
if (ESP_OK == twai_node_receive_from_isr(handle, &rx_frame)) {
// receive ok, do something here
}
return false;
}
Similarly, since the driver uses pointers for message passing, you must configure the pointer :cpp:member:`twai_frame_t::buffer` and its memory length :cpp:member:`twai_frame_t::buffer_len` before receiving.
Frame Timestamp
---------------
The TWAI driver supports creating a 64-bit timestamp for each successfully received frame, enabling this feature by configuring the :cpp:member:`twai_onchip_node_config_t::timestamp_resolution_hz` field when creating the node. The timestamp is stored in the :cpp:member:`twai_frame_t::header::timestamp` field of the received frame.
The node time inherits from the system time, i.e. the time starts from the power-on of the chip, and is not affected by the stop/restart/BUS_OFF state during the node's lifetime.
.. only:: SOC_TWAI_FD_SUPPORTED
Scheduled Transmission
----------------------
The {IDF_TARGET_NAME} TWAI supports schedule a transmitted frame by trigger time. Enable :cpp:member:`twai_onchip_node_config_t::flags::enable_scheduled_tx` and set :cpp:member:`twai_onchip_node_config_t::timestamp_resolution_hz` when creating the node, then fill :cpp:member:`twai_frame_t::header::trigger_time` before calling :cpp:func:`twai_node_transmit`. The trigger time uses the same timebase as received frame timestamps.
.. code:: c
twai_onchip_node_config_t node_config = {
.io_cfg.tx = 4,
.io_cfg.rx = 5,
.bit_timing.bitrate = 500000,
.timestamp_resolution_hz = 1000, // 1 tick = 1 ms
.tx_queue_depth = 4,
.flags.enable_scheduled_tx = true,
};
twai_frame_t tx_msg = {
.header.id = 0x10,
.header.trigger_time = 2000, // transmit when node timestamp reaches 2000 ticks
};
ESP_ERROR_CHECK(twai_node_transmit(node_hdl, &tx_msg, 0));
.. note::
If the frame's trigger time has already been reached when the frame is ready to transmit, the driver starts transmitting it immediately. When multiple scheduled frames are queued, the driver processes them in software submission order. Frames are not reordered by :cpp:member:`twai_frame_t::header::trigger_time`, so a later-submitted frame with an earlier trigger time cannot overtake frames submitted before it.
Stopping and Deleting the Node
------------------------------
When the TWAI node is no longer needed, you should call :cpp:func:`twai_node_delete` to release software and hardware resources. Make sure the TWAI controller is stopped before deleting the node.
Advanced Features
=================
After understanding the basic usage, you can further explore more advanced capabilities of the TWAI driver. The driver supports more detailed controller configuration and error feedback features. The complete driver feature diagram is shown below:
.. image:: ../../../_static/diagrams/twai/full_flow.drawio.svg
:align: center
Transmit from ISR
-----------------
The TWAI driver supports transmitting messages from an Interrupt Service Routine (ISR). This is particularly useful for applications requiring low-latency responses or periodic transmissions triggered by hardware timers. For example, you can trigger a new transmission from within the ``on_tx_done`` callback, which is executed in an ISR context.
.. code:: c
static bool twai_tx_done_cb(twai_node_handle_t handle, const twai_tx_done_event_data_t *edata, void *user_ctx)
{
// A frame has been successfully transmitted. Queue another one.
// The frame and its data buffer must be valid until transmission is complete.
static const uint8_t data_buffer[] = {1, 2, 3, 4};
static const twai_frame_t tx_frame = {
.header.id = 0x2,
.buffer = (uint8_t *)data_buffer,
.buffer_len = sizeof(data_buffer),
};
// The `twai_node_transmit` is safe to be called in an ISR context
twai_node_transmit(handle, &tx_frame, 0);
return false;
}
.. note::
When calling :cpp:func:`twai_node_transmit` from an ISR, the ``timeout`` parameter is ignored, and the function will not block. If the transmit queue is full, the function will return immediately with an error. It is the application's responsibility to handle cases where the queue is full. Similarly, the ``twai_frame_t`` structure and the memory pointed to by ``buffer`` must remain valid until the transmission is complete. You can get the completed frame by the :cpp:member:`twai_tx_done_event_data_t::done_tx_frame` pointer.
Transmit Queue Priority
-----------------------
The TWAI driver supports local transmit queue prioritization through :cpp:member:`twai_frame_t::tx_queue_priority`. When multiple frames are pending in the driver's transmit queue, frames with a higher ``tx_queue_priority`` value are dequeued and started transmitting first. Frames with the same priority keep their enqueue order.
This priority only affects the driver's local transmit queue. It is not transmitted on the TWAI bus and does not replace TWAI bus arbitration. If the controller has multiple hardware transmit buffers (for example, 4 hardware transmit buffers for esp32c5), the already cached frames will not be preempted by newly queued higher-priority frames. Once a frame reaches the bus, arbitration is still determined by the frame ID, where lower IDs have higher bus priority.
Bit Timing Customization
------------------------
Unlike other asynchronous communication protocols, the TWAI controller performs counting and sampling within one bit time in units of **Time Quanta (Tq)**. The number of time quanta per bit determines the final baud rate and the sample point position. When signal quality is poor, you can manually fine-tune these timing segments to meet specific requirements. The time quanta within a bit time are divided into different segments, as illustrated below:
.. image:: ../../../_static/diagrams/twai/bit_timing.svg
:alt: Bit timing configuration
:align: center
The synchronization segment (sync) is fixed at 1 Tq. The sample point lies between time segments tseg1 and tseg2. The Synchronization Jump Width (SJW) defines the maximum number of time quanta by which a bit time can be lengthened or shortened for synchronization purposes, ranging from [1 : tseg2]. The clock source divided by the baud rate prescaler (BRP) equals the time quantum. The total sum of all segments equals one bit time. Therefore, the following formula applies:
- Baud rate (bitrate):
.. math::
\text{bitrate} = \frac{f_{\text{src}}}{\text{brp} \cdot (1 + \text{prop_seg} + \text{tseg}_1 + \text{tseg}_2)}
- Sample point:
.. math::
\text{sample_point} = \frac{1 + \text{prop_seg} + \text{tseg}_1}{1 + \text{prop_seg} + \text{tseg}_1 + \text{tseg}_2}
The following code demonstrates how to configure a baud rate of 500 Kbit/s with a sample point at 75% when using an 80 MHz clock source:
.. code:: c
twai_timing_advanced_config_t timing_cfg = {
.brp = 8, // Prescaler set to 8, time quantum = 80M / 8 = 10 MHz (10M Tq)
.prop_seg = 10, // Propagation segment
.tseg_1 = 4, // Phase segment 1
.tseg_2 = 5, // Phase segment 2
.sjw = 3, // Synchronization Jump Width
};
ESP_ERROR_CHECK(twai_node_reconfig_timing(node_hdl, &timing_cfg, NULL)); // Configure arbitration phase timing; NULL means FD data phase timing is not configured
When manually configuring these timing segments, it is important to pay attention to the supported range of each segment according to the specific hardware. The timing configuration function :cpp:func:`twai_node_reconfig_timing` can configure the timing parameters for both the arbitration phase and the FD data phase either simultaneously or separately. When the controller does not support FD format, the data phase configuration is ignored. The timing parameter struct :cpp:type:`twai_timing_advanced_config_t` also includes the following additional configuration fields:
- :cpp:member:`twai_timing_advanced_config_t::clk_src` — The clock source.
- :cpp:member:`twai_timing_advanced_config_t::ssp_offset` — The number of time quanta by which the secondary sample point (SSP) is offset relative to the synchronization segment.
.. note::
Different combinations of ``brp``, ``prop_seg``, ``tseg_1``, ``tseg_2``, and ``sjw`` can achieve the same baud rate. Users should consider factors such as **propagation delay, node processing time, and phase errors**, and adjust the timing parameters based on the physical characteristics of the bus.
Filter Configuration
---------------------
Mask Filters
^^^^^^^^^^^^
The TWAI controller hardware can filter messages based on their ID to reduce software and hardware overhead, thereby improving node efficiency. Nodes that filter out certain messages will **not receive those messages, but will still send acknowledgments (ACKs)**.
{IDF_TARGET_NAME} includes {IDF_TARGET_CONFIG_SOC_TWAI_MASK_FILTER_NUM} mask filters. A message passing through any one of these filters will be received by the node. A typical TWAI mask filter is configured with an ID and a MASK, where:
- ID: represents the expected message ID, either the standard 11-bit or extended 29-bit format.
- MASK: defines the filtering rules for each bit of the ID:
- '0' means the corresponding bit is ignored (any value passes).
- '1' means the corresponding bit must match exactly to pass.
- When both ID and MASK are `0`, the filter ignores all bits and accepts all frames.
- When both ID and MASK are set to the maximum `0xFFFFFFFF`, the filter accepts no frames.
The following code demonstrates how to calculate the MASK and configure a filter:
.. code:: c
twai_mask_filter_config_t mfilter_cfg = {
.id = 0x10, // 0b 000 0001 0000
.mask = 0x7f0, // 0b 111 1111 0000 — the upper 7 bits must match strictly, the lower 4 bits are ignored, accepts IDs of the form
// 0b 000 0001 xxxx (hex 0x01x)
.is_ext = false, // Accept only standard IDs, not extended IDs
};
ESP_ERROR_CHECK(twai_node_config_mask_filter(node_hdl, 0, &mfilter_cfg)); // Configure on filter 0
.. only:: not SOC_TWAI_FD_SUPPORTED
Dual Filter Mode
^^^^^^^^^^^^^^^^
{IDF_TARGET_NAME} supports dual filter mode, which allows the hardware to be configured as two parallel independent 16-bit mask filters. By enabling this, more IDs can be received. Note that using dual filter mode to filter 29-bit extended IDs, each filter can only filter the upper 16 bits of the ID, while the remaining 13 bits are not filtered. The following code demonstrates how to configure dual filter mode using the function :cpp:func:`twai_make_dual_filter`:
.. code:: c
// filter 1 id/mask 0x020, 0x7f0, receive only std id 0x02x
// filter 2 id/mask 0x013, 0x7f8, receive only std id 0x010~0x017
twai_mask_filter_config_t dual_config = twai_make_dual_filter(0x020, 0x7f0, 0x013, 0x7f8, false); // id1, mask1, id2, mask2, no extend ID
ESP_ERROR_CHECK(twai_node_config_mask_filter(node_hdl, 0, &dual_config));
.. only:: SOC_TWAI_FD_SUPPORTED
Range Filter
^^^^^^^^^^^^
{IDF_TARGET_NAME} also includes 1 range filter, which exists alongside the mask filters. You can configure the desired ID reception range directly using the function :cpp:func:`twai_node_config_range_filter`. The details are as follows:
- Setting :cpp:member:twai_range_filter_config_t::range_low to the minimum value 0, and :cpp:member:twai_range_filter_config_t::range_high to the maximum value 0xFFFFFFFF means receiving all messages.
- Configuring an invalid range means no messages will be received.
Bus Errors and Recovery
-----------------------
The TWAI controller can detect errors caused by bus interference or corrupted frames that do not conform to the frame format. It implements a fault isolation mechanism using transmit and receive error counters (TEC and REC). The values of these counters determine the node's error state: Error Active, Error Warning, Error Passive, and Bus Off. This mechanism ensures that nodes with persistent errors eventually disconnect themselves from the bus.
- **Error Active**: When both TEC and REC are less than 96, the node is in the active error state, meaning normal operation. The node participates in bus communication and sends **active error flags** when errors are detected to actively report them.
- **Error Warning**: When either TEC or REC is greater than or equal to 96 but both are less than 128, the node is in the warning error state. Errors may exist but the node behavior remains unchanged.
- **Error Passive**: When either TEC or REC is greater than or equal to 128, the node enters the passive error state. It can still communicate on the bus but sends only one **passive error flag** when detecting errors.
- **Bus Off**: When **TEC** is greater than or equal to 256, the node enters the bus off (offline) state. The node is effectively disconnected and does not affect the bus. It remains offline until recovery is triggered by software.
Software can retrieve the node status from tasks via the function :cpp:func:`twai_node_get_info`. When the controller detects errors, it triggers the :cpp:member:`twai_event_callbacks_t::on_error` callback, where the error data provides detailed information.
When the nodes error state changes, the :cpp:member:`twai_event_callbacks_t::on_state_change` callback is triggered, allowing the application to respond to the state transition. If the node is offline and needs recovery, call :cpp:func:`twai_node_recover` from a task context. **Note that recovery is not immediate; the controller will automatically reconnect to the bus only after detecting 129 consecutive recessive bits (11 bits each).**
When recovery completes, the :cpp:member:`twai_event_callbacks_t::on_state_change` callback will be triggered again, the node changes its state from :cpp:enumerator:`TWAI_ERROR_BUS_OFF` to :cpp:enumerator:`TWAI_ERROR_ACTIVE`. A recovered node can immediately resume transmissions; if there are pending tasks in the transmit queue, the driver will start transmitting them right away.
Power Management
----------------
When power management is enabled via :ref:`CONFIG_PM_ENABLE`, the system may adjust or disable clock sources before entering sleep mode, which could cause TWAI to malfunction. To prevent this, the driver manages a power management lock internally. This lock is acquired when calling :cpp:func:`twai_node_enable`, ensuring the system does not enter sleep mode and TWAI remains functional. To allow the system to enter a low-power state, call :cpp:func:`twai_node_disable` to release the lock. During sleep, the TWAI controller will also stop functioning.
.. only:: SOC_TWAI_SUPPORT_SLEEP_RETENTION
About Sleep Retention
^^^^^^^^^^^^^^^^^^^^^
{IDF_TARGET_NAME} supports powering down the TWAI controller during **Light Sleep** to further reduce power consumption and automatically restore after waking up. This means the application does not need to reconfigure TWAI after **Light Sleep** wake up.
Enable the option :ref:`CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP`, and set :cpp:member:`twai_onchip_node_config_t::flags::sleep_allow_pd` to ``true`` when initializing the TWAI node to enable this feature. Otherwise, the TWAI controller will remain powered during **Light Sleep**. This feature helps reduce power consumption during light sleep but requires additional storage to save register configurations.
Cache Safety
------------
During Flash write operations, the system temporarily disables cache to prevent instruction and data fetch errors from Flash. This can cause interrupt handlers stored in Flash to become unresponsive. If you want interrupt routines to remain operational during cache-disabled periods, enable the :ref:`CONFIG_TWAI_ISR_CACHE_SAFE` option.
.. note::
When this option is enabled, **all interrupt callback functions and their context data must reside in internal memory**, because the system cannot fetch instructions or data from Flash while the cache is disabled.
Thread Safety
-------------
The driver guarantees thread safety for all public TWAI APIs. You can safely call these APIs from different RTOS tasks without requiring additional synchronization or locking mechanisms.
Performance
-----------
To improve the real-time performance of interrupt handling, the driver provides the :ref:`CONFIG_TWAI_ISR_IN_IRAM` option. When enabled, the TWAI ISR (Interrupt Service Routine) and receive operations are placed in internal RAM, reducing latency caused by instruction fetching from Flash.
For applications that require high-performance transmit operations, the driver provides the :ref:`CONFIG_TWAI_IO_FUNC_IN_IRAM` option to place transmit functions in IRAM. This is particularly beneficial for time-critical applications that frequently call :cpp:func:`twai_node_transmit` from user tasks.
.. note::
However, user-defined callback functions and context data invoked by the ISR may still reside in Flash. To fully eliminate Flash latency, users must place these functions and data into internal RAM using macros such as :c:macro:`IRAM_ATTR` for functions and :c:macro:`DRAM_ATTR` for data.
Resource Usage
--------------
You can inspect the Flash and memory usage of the TWAI driver using the :doc:`/api-guides/tools/idf-size` tool. Below are the test conditions (based on the ESP32-C6 as an example):
- Compiler optimization level is set to ``-Os`` to minimize code size.
- Default log level is set to ``ESP_LOG_INFO`` to balance debugging information and performance.
- The following driver optimization options are disabled:
- :ref:`CONFIG_TWAI_ISR_IN_IRAM` ISR is not placed in IRAM.
- :ref:`CONFIG_TWAI_ISR_CACHE_SAFE` Cache safety option is disabled.
**The following resource usage data is for reference only. Actual values may vary across different target chips.**
+-----------------+------------+-------+------+-------+-------+-------+---------+-------+
| Component Layer | Total Size | DIRAM | .bss | .data | .text | Flash | .rodata | .text |
+=================+============+=======+======+=======+=======+=======+=========+=======+
| driver | 7262 | 12 | 12 | 0 | 0 | 7250 | 506 | 6744 |
+-----------------+------------+-------+------+-------+-------+-------+---------+-------+
| hal | 1952 | 0 | 0 | 0 | 0 | 0 | 0 | 1952 |
+-----------------+------------+-------+------+-------+-------+-------+---------+-------+
| soc | 64 | 0 | 0 | 0 | 0 | 64 | 64 | 0 |
+-----------------+------------+-------+------+-------+-------+-------+---------+-------+
Resource Usage with :ref:`CONFIG_TWAI_ISR_IN_IRAM` Enabled:
+-----------------+------------+-------+------+-------+-------+-------+---------+-------+
| Component Layer | Total Size | DIRAM | .bss | .data | .text | Flash | .rodata | .text |
+=================+============+=======+======+=======+=======+=======+=========+=======+
| driver | 7248 | 692 | 12 | 0 | 680 | 6556 | 506 | 6050 |
+-----------------+------------+-------+------+-------+-------+-------+---------+-------+
| hal | 1952 | 1030 | 0 | 0 | 1030 | 922 | 0 | 922 |
+-----------------+------------+-------+------+-------+-------+-------+---------+-------+
| soc | 64 | 0 | 0 | 0 | 0 | 0 | 64 | 0 |
+-----------------+------------+-------+------+-------+-------+-------+---------+-------+
Additionally, each TWAI handle dynamically allocates approximately ``168`` + 4 * :cpp:member:`twai_onchip_node_config_t::tx_queue_depth` bytes of memory from the heap.
Other Kconfig Options
---------------------
- :ref:`CONFIG_TWAI_ENABLE_DEBUG_LOG`: This option forces all debug logs of the TWAI driver to be enabled regardless of the global log level settings. Enabling this can help developers obtain more detailed log information during debugging, making it easier to locate and resolve issues.
Application Examples
====================
.. list::
- :example:`peripherals/twai/twai_utils` demonstrates how to use the TWAI (Two-Wire Automotive Interface) APIs to create a command-line interface for TWAI bus communication, supporting frame transmission/reception, filtering, monitoring, and both classic and FD formats for testing and debugging TWAI networks.
- :example:`peripherals/twai/twai_error_recovery` demonstrates how to recover nodes from the bus-off state and resume communication, as well as bus error reporting, node state changes, and other event information.
- :example:`peripherals/twai/twai_network` using 2 nodes with different roles: transmitting and listening, demonstrates how to use the driver for single and bulk data transmission, as well as configure filters to receive these data.
- :example:`peripherals/twai/cybergear` demonstrates how to control XiaoMi CyberGear motors via TWAI interface.
API Reference
=============
On-Chip TWAI APIs
-----------------
.. include-build-file:: inc/esp_twai_onchip.inc
TWAI Driver APIs
----------------
.. include-build-file:: inc/esp_twai.inc
TWAI Driver Types
-----------------
.. include-build-file:: inc/esp_twai_types.inc
TWAI HAL Types
--------------
.. include-build-file:: inc/twai_types.inc
+452
View File
@@ -0,0 +1,452 @@
Universal Asynchronous Receiver/Transmitter (UART)
==================================================
:link_to_translation:`zh_CN:[中文]`
{IDF_TARGET_UART_EXAMPLE_PORT:default = "UART_NUM_1", esp32 = "UART_NUM_2", esp32s3 = "UART_NUM_2"}
Introduction
------------
A Universal Asynchronous Receiver/Transmitter (UART) is a hardware feature that handles communication (i.e., timing requirements and data framing) using widely-adopted asynchronous serial communication interfaces, such as RS232, RS422, and RS485. A UART provides a widely adopted and cheap method to realize full-duplex or half-duplex data exchange among different devices.
The {IDF_TARGET_NAME} chip has {IDF_TARGET_SOC_UART_HP_NUM} UART controllers (also referred to as port), each featuring an identical set of registers to simplify programming and for more flexibility.
Each UART controller is independently configurable with parameters such as baud rate, data bit length, bit ordering, number of stop bits, parity bit, etc. All the regular UART controllers are compatible with UART-enabled devices from various manufacturers and can also support Infrared Data Association (IrDA) protocols.
.. only:: SOC_UART_HAS_LP_UART
Additionally, the {IDF_TARGET_NAME} chip has one low-power (LP) UART controller. It is the cut-down version of regular UART. Usually, the LP UART controller only support basic UART functionality with a much smaller RAM size, and does not support IrDA or RS485 protocols. For a full list of difference between UART and LP UART, please refer to the **{IDF_TARGET_NAME} Technical Reference Manual** > **UART Controller (UART)** > **Features** [`PDF <{IDF_TARGET_TRM_EN_URL}#uart>`__]).
.. only:: SOC_UHCI_SUPPORTED
.. toctree::
:hidden:
uhci
The {IDF_TARGET_NAME} chip also supports using DMA with UART. For details, see to :doc:`uhci`.
Functional Overview
-------------------
The overview describes how to establish communication between an {IDF_TARGET_NAME} and other UART devices using the functions and data types of the UART driver. A typical programming workflow is broken down into the sections provided below:
1. :ref:`uart-api-driver-installation` - Allocating {IDF_TARGET_NAME}'s resources for the UART driver
2. :ref:`uart-api-setting-communication-parameters` - Setting baud rate, data bits, stop bits, etc.
3. :ref:`uart-api-setting-communication-pins` - Assigning pins for connection to a device
4. :ref:`uart-api-running-uart-communication` - Sending/receiving data
5. :ref:`uart-api-using-interrupts` - Triggering interrupts on specific communication events
6. :ref:`uart-api-deleting-driver` - Freeing allocated resources if a UART communication is no longer required
Steps 1 to 3 comprise the configuration stage. Step 4 is where the UART starts operating. Steps 5 and 6 are optional.
.. only:: SOC_UART_HAS_LP_UART
Additionally, when using the LP UART Controller you need to pay attention to :ref:`uart-api-lp-uart-driver`.
The UART driver's functions identify each of the UART controllers using :cpp:type:`uart_port_t`. This identification is needed for all the following function calls.
.. _uart-api-driver-installation:
Install Drivers
^^^^^^^^^^^^^^^^^^^
First of all, install the driver by calling :cpp:func:`uart_driver_install` and specify the following parameters:
- UART port number
- Size of RX ring buffer
- Size of TX ring buffer
- Event queue size
- Pointer to store the event queue handle
- Flags to allocate an interrupt
.. _driver-code-snippet:
The function allocates the required internal resources for the UART driver.
.. code-block:: c
// Setup UART buffered IO with event queue
const int uart_buffer_size = (1024 * 2);
QueueHandle_t uart_queue;
// Install UART driver using an event queue here
ESP_ERROR_CHECK(uart_driver_install({IDF_TARGET_UART_EXAMPLE_PORT}, uart_buffer_size, uart_buffer_size, 10, &uart_queue, 0));
.. _uart-api-setting-communication-parameters:
Set Communication Parameters
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
As the next step, UART communication parameters can be configured all in a single step or individually in multiple steps.
Single Step
"""""""""""
Call the function :cpp:func:`uart_param_config` and pass to it a :cpp:type:`uart_config_t` structure. The :cpp:type:`uart_config_t` structure should contain all the required parameters. See the example below.
.. code-block:: c
const uart_port_t uart_num = {IDF_TARGET_UART_EXAMPLE_PORT};
uart_config_t uart_config = {
.baud_rate = 115200,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_CTS_RTS,
.rx_flow_ctrl_thresh = 122,
};
// Configure UART parameters
ESP_ERROR_CHECK(uart_param_config(uart_num, &uart_config));
For more information on how to configure the hardware flow control options, please refer to :example:`peripherals/uart/uart_echo`.
.. only:: SOC_UART_SUPPORT_SLEEP_RETENTION
Additionally, :cpp:member:`uart_config_t::allow_pd` can be set to enable the backup of the UART configuration registers before entering sleep and restore these registers after exiting sleep. This allows the UART to continue working properly after waking up even when the UART module power domain is entirely off during sleep. This option implies an balance between power consumption and memory usage. If the power consumption is not a concern, you can disable this option to save memory.
If glitches may occur on the RX signal, :cpp:member:`uart_config_t::rx_glitch_filt_thresh` can be set to filter the glitches to ensure the correct data is received (note that this feature is not supported on ESP32 and ESP32-S2). The unit of the :cpp:member:`uart_config_t::rx_glitch_filt_thresh` is nanoseconds. The default value is 0, which means no filtering.
Multiple Steps
""""""""""""""
Configure specific parameters individually by calling a dedicated function from the table given below. These functions are also useful if re-configuring a single parameter.
.. list-table:: Functions for Configuring specific parameters individually
:widths: 30 70
:header-rows: 1
* - Parameter to Configure
- Function
* - Baud rate
- :cpp:func:`uart_set_baudrate`
* - Number of transmitted bits
- :cpp:func:`uart_set_word_length` selected out of :cpp:type:`uart_word_length_t`
* - Parity control
- :cpp:func:`uart_set_parity` selected out of :cpp:type:`uart_parity_t`
* - Number of stop bits
- :cpp:func:`uart_set_stop_bits` selected out of :cpp:type:`uart_stop_bits_t`
* - Hardware flow control mode
- :cpp:func:`uart_set_hw_flow_ctrl` selected out of :cpp:type:`uart_hw_flowcontrol_t`
* - Communication mode
- :cpp:func:`uart_set_mode` selected out of :cpp:type:`uart_mode_t`
Each of the above functions has a ``_get_`` counterpart to check the currently set value. For example, to check the current baud rate value, call :cpp:func:`uart_get_baudrate`.
.. _uart-api-setting-communication-pins:
Set Communication Pins
^^^^^^^^^^^^^^^^^^^^^^^^^^
After setting communication parameters, configure the physical GPIO pins to which the other UART device will be connected. For this, call the function :cpp:func:`uart_set_pin` and specify the GPIO pin numbers to which the driver should route the TX, RX, RTS, CTS, DTR, and DSR signals. If you want to keep a currently allocated pin number for a specific signal, pass the macro :c:macro:`UART_PIN_NO_CHANGE`.
The same macro :c:macro:`UART_PIN_NO_CHANGE` should be specified for pins that will not be used.
.. code-block:: c
// Set UART pins(TX: IO4, RX: IO5, RTS: IO18, CTS: IO19, DTR: UNUSED, DSR: UNUSED)
ESP_ERROR_CHECK(uart_set_pin({IDF_TARGET_UART_EXAMPLE_PORT}, 4, 5, 18, 19, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE));
.. _uart-api-running-uart-communication:
Run UART Communication
^^^^^^^^^^^^^^^^^^^^^^^^^^
Serial communication is controlled by each UART controller's finite state machine (FSM).
The process of sending data involves the following steps:
1. Write data into TX FIFO buffer
2. FSM serializes the data
3. FSM sends the data out
The process of receiving data is similar, but the steps are reversed:
1. FSM processes an incoming serial stream and parallelizes it
2. FSM writes the data into RX FIFO buffer
3. Read the data from RX FIFO buffer
Therefore, an application only writes and reads data from a specific buffer using :cpp:func:`uart_write_bytes` and :cpp:func:`uart_read_bytes` respectively, and the FSM does the rest.
Transmit Data
"""""""""""""
After preparing the data for transmission, call the function :cpp:func:`uart_write_bytes` and pass the data buffer's address and data length to it. The function copies the data to the TX ring buffer (either immediately or after enough space is available), and then exit. When there is free space in the TX FIFO buffer, an interrupt service routine (ISR) moves the data from the TX ring buffer to the TX FIFO buffer in the background. The code below demonstrates the use of this function.
.. code-block:: c
// Write data to UART.
char* test_str = "This is a test string.\n";
uart_write_bytes(uart_num, (const char*)test_str, strlen(test_str));
The function :cpp:func:`uart_write_bytes_with_break` is similar to :cpp:func:`uart_write_bytes` but adds a serial break signal at the end of the transmission. A 'serial break signal' means holding the TX line low for a period longer than one data frame.
.. code-block:: c
// Write data to UART, end with a break signal.
uart_write_bytes_with_break(uart_num, "test break\n",strlen("test break\n"), 100);
Another function for writing data to the TX FIFO buffer is :cpp:func:`uart_tx_chars`. Unlike :cpp:func:`uart_write_bytes`, this function does not block until space is available. Instead, it writes all data which can immediately fit into the hardware TX FIFO, and then return the number of bytes that were written.
There is a 'companion' function :cpp:func:`uart_wait_tx_done` that monitors the status of the TX FIFO buffer and returns once it is empty.
.. code-block:: c
// Wait for packet to be sent
const uart_port_t uart_num = {IDF_TARGET_UART_EXAMPLE_PORT};
ESP_ERROR_CHECK(uart_wait_tx_done(uart_num, 100)); // wait timeout is 100 RTOS ticks (TickType_t)
Receive Data
""""""""""""
Once the data is received by the UART and saved in the RX FIFO buffer, it needs to be retrieved using the function :cpp:func:`uart_read_bytes`. Before reading data, you can check the number of bytes available in the RX FIFO buffer by calling :cpp:func:`uart_get_buffered_data_len`. An example of using these functions is given below.
.. code-block:: c
// Read data from UART.
const uart_port_t uart_num = {IDF_TARGET_UART_EXAMPLE_PORT};
uint8_t data[128];
int length = 0;
ESP_ERROR_CHECK(uart_get_buffered_data_len(uart_num, (size_t*)&length));
length = uart_read_bytes(uart_num, data, length, 100);
If the data in the RX FIFO buffer is no longer needed, you can clear the buffer by calling :cpp:func:`uart_flush`.
Software Flow Control
"""""""""""""""""""""
If the hardware flow control is disabled, you can manually set the RTS and DTR signal levels by using the functions :cpp:func:`uart_set_rts` and :cpp:func:`uart_set_dtr` respectively.
Communication Mode Selection
""""""""""""""""""""""""""""
The UART controller supports a number of communication modes. A mode can be selected using the function :cpp:func:`uart_set_mode`. Once a specific mode is selected, the UART driver handles the behavior of a connected UART device accordingly. As an example, it can control the RS485 driver chip using the RTS line to allow half-duplex RS485 communication.
.. code-block:: bash
// Setup UART in rs485 half duplex mode
ESP_ERROR_CHECK(uart_set_mode(uart_num, UART_MODE_RS485_HALF_DUPLEX));
.. _uart-api-using-interrupts:
Use Interrupts
^^^^^^^^^^^^^^^^
There are many interrupts that can be generated depending on specific UART states or detected errors. The full list of available interrupts is provided in *{IDF_TARGET_NAME} Technical Reference Manual* > *UART Controller (UART)* > *UART Interrupts* [`PDF <{IDF_TARGET_TRM_EN_URL}#uart>`__]. You can enable or disable specific interrupts by calling :cpp:func:`uart_enable_intr_mask` or :cpp:func:`uart_disable_intr_mask` respectively.
The UART driver provides a convenient way to handle specific interrupts by wrapping them into corresponding events. Events defined in :cpp:type:`uart_event_type_t` can be reported to a user application using the FreeRTOS queue functionality.
To receive the events that have happened, call :cpp:func:`uart_driver_install` and get the event queue handle returned from the function. Please see the above :ref:`code snippet <driver-code-snippet>` as an example.
The processed events include the following:
- **FIFO overflow** (:cpp:enumerator:`UART_FIFO_OVF`): The RX FIFO can trigger an interrupt when it receives more data than the FIFO can store.
- (Optional) Configure the full threshold of the FIFO space by entering it in the structure :cpp:type:`uart_intr_config_t` and call :cpp:func:`uart_intr_config` to set the configuration. This can help the data stored in the RX FIFO can be processed timely in the driver to avoid FIFO overflow.
- Enable the interrupts using the functions :cpp:func:`uart_enable_rx_intr`.
- Disable these interrupts using the corresponding functions :cpp:func:`uart_disable_rx_intr`.
.. code-block:: c
const uart_port_t uart_num = {IDF_TARGET_UART_EXAMPLE_PORT};
// Configure a UART interrupt threshold and timeout
uart_intr_config_t uart_intr = {
.intr_enable_mask = UART_INTR_RXFIFO_FULL | UART_INTR_RXFIFO_TOUT,
.rxfifo_full_thresh = 100,
.rx_timeout_thresh = 10,
};
ESP_ERROR_CHECK(uart_intr_config(uart_num, &uart_intr));
// Enable UART RX FIFO full threshold and timeout interrupts
ESP_ERROR_CHECK(uart_enable_rx_intr(uart_num));
- **Pattern detection** (:cpp:enumerator:`UART_PATTERN_DET`): An interrupt triggered on detecting a 'pattern' of the same character being received/sent repeatedly. It can be used, e.g., to detect a command string with a specific number of identical characters (the 'pattern') at the end. The following functions are available:
- Configure and enable this interrupt using :cpp:func:`uart_enable_pattern_det_baud_intr`
- Disable the interrupt using :cpp:func:`uart_disable_pattern_det_intr`
.. code-block:: c
//Set UART pattern detect function
uart_enable_pattern_det_baud_intr(EX_UART_NUM, '+', PATTERN_CHR_NUM, 9, 0, 0);
- **Other events**: The UART driver can report other events such as data receiving (:cpp:enumerator:`UART_DATA`), ring buffer full (:cpp:enumerator:`UART_BUFFER_FULL`), detecting NULL after the stop bit (:cpp:enumerator:`UART_BREAK`), parity check error (:cpp:enumerator:`UART_PARITY_ERR`), and frame error (:cpp:enumerator:`UART_FRAME_ERR`).
The strings inside of brackets indicate corresponding event names. An example of how to handle various UART events can be found in :example:`peripherals/uart/uart_events`.
.. _uart-api-deleting-driver:
Deleting a Driver
^^^^^^^^^^^^^^^^^
If the communication established with :cpp:func:`uart_driver_install` is no longer required, the driver can be removed to free allocated resources by calling :cpp:func:`uart_driver_delete`.
Macros
^^^^^^
The API also defines several macros. For example, :c:macro:`UART_HW_FIFO_LEN` defines the length of hardware FIFO buffers; :c:macro:`UART_BITRATE_MAX` gives the maximum baud rate supported by the UART controllers, etc.
.. only:: SOC_UART_HAS_LP_UART
.. _uart-api-lp-uart-driver:
Use LP UART Controller with HP Core
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The UART driver also supports to control the LP UART controller when the chip is in active mode. The configuration steps for the LP UART are the same as the steps for a normal UART controller, except:
.. list::
- The port number for the LP UART controller is defined by :c:macro:`LP_UART_NUM_0`.
- The available clock sources for the LP UART controller can be found in :cpp:type:`lp_uart_sclk_t`.
- The size of the hardware FIFO for the LP UART controller is much smaller, which is defined in :c:macro:`SOC_LP_UART_FIFO_LEN`.
:SOC_LP_GPIO_MATRIX_SUPPORTED: - The GPIO pins for the LP UART controller can only be selected from the LP GPIO pins.
:not SOC_LP_GPIO_MATRIX_SUPPORTED: - The GPIO pins for the LP UART controller are unalterable, because there is no LP GPIO matrix on the target. Please see **{IDF_TARGET_NAME} Technical Reference Manual** > **IO MUX and GPIO Matrix (GPIO, IO MUX)** > **LP IO MUX Functions List** [`PDF <{IDF_TARGET_TRM_EN_URL}#lp-io-mux-func-list>`__] for the specific pin numbers.
Overview of RS485 Specific Communication 0ptions
------------------------------------------------
.. note::
The following section uses ``[UART_REGISTER_NAME].[UART_FIELD_BIT]`` to refer to UART register fields/bits. For more information on a specific option bit, see **{IDF_TARGET_NAME} Technical Reference Manual** > **UART Controller (UART)** > **Register Summary** [`PDF <{IDF_TARGET_TRM_EN_URL}#uart-reg-summ>`__]. Use the register name to navigate to the register description and then find the field/bit.
- ``UART_RS485_CONF_REG.UART_RS485_EN``: setting this bit enables RS485 communication mode support.
- ``UART_RS485_CONF_REG.UART_RS485TX_RX_EN``: if this bit is set, the transmitter's output signal loops back to the receiver's input signal.
- ``UART_RS485_CONF_REG.UART_RS485RXBY_TX_EN``: if this bit is set, the transmitter will still be sending data if the receiver is busy (remove collisions automatically by hardware).
The {IDF_TARGET_NAME}'s RS485 UART hardware can detect signal collisions during transmission of a datagram and generate the interrupt ``UART_RS485_CLASH_INT`` if this interrupt is enabled. The term collision means that a transmitted datagram is not equal to the one received on the other end. Data collisions are usually associated with the presence of other active devices on the bus or might occur due to bus errors.
The collision detection feature allows handling collisions when their interrupts are activated and triggered. The interrupts ``UART_RS485_FRM_ERR_INT`` and ``UART_RS485_PARITY_ERR_INT`` can be used with the collision detection feature to control frame errors and parity bit errors accordingly in RS485 mode. This functionality is supported in the UART driver and can be used by selecting the :cpp:enumerator:`UART_MODE_RS485_APP_CTRL` mode (see the function :cpp:func:`uart_set_mode`).
The collision detection feature can work with circuit A and circuit C (see Section `Interface Connection Options`_). Use the function :cpp:func:`uart_get_collision_flag` to check if the collision detection flag has been raised. In the case of using circuit A or B, either DTR or RTS pin can be connected to the DE/~RE pin of the transceiver module to achieve half-duplex communication.
The RS485 half-duplex communication mode is supported by the UART driver and can be activated by selecting the :cpp:enumerator:`UART_MODE_RS485_HALF_DUPLEX` mode calling :cpp:func:`uart_set_mode`. The DTR line is automatically controlled by the hardware directly under RS485 half-duplex mode, while the RTS line is software-controlled by the UART driver. Once the host starts writing data to the TX FIFO buffer, the UART driver automatically asserts the RTS pin (logic 1); once the last bit of the data has been transmitted, the driver de-asserts the RTS pin (logic 0). To use this mode, the software would have to disable the hardware flow control function. Since the switching is made in the interrupt handler, comparing to DTR line, some latency is expected on RTS line.
.. only:: esp32
.. note::
On {IDF_TARGET_NAME}, DTR signal is only available on UART0. For other UART ports, you can only connect RTS signal to the DE/~RE pin of the transceiver module.
Interface Connection Options
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This section provides example schematics to demonstrate the basic aspects of {IDF_TARGET_NAME}'s RS485 interface connection.
.. note::
- The schematics below do **not** necessarily contain **all required elements**.
- The **analog devices** ADM483 & ADM2483 are examples of common RS485 transceivers and **can be replaced** with other similar transceivers.
Circuit A: Collision Detection Circuit
""""""""""""""""""""""""""""""""""""""
.. code-block:: none
VCC ---------------+
|
+-------x-------+
RXD <------| R |
| B|----------<> B
TXD ------>| D ADM483 |
ESP | | RS485 bus side
DTR/RTS ------>| DE |
| A|----------<> A
+----| /RE |
| +-------x-------+
| |
GND GND
This circuit is preferable because it allows for collision detection and is quite simple at the same time. The receiver in the line driver is constantly enabled, which allows the UART to monitor the RS485 bus. Echo suppression is performed by the UART peripheral when the bit ``UART_RS485_CONF_REG.UART_RS485TX_RX_EN`` is enabled.
Circuit B: Manual Switching Transmitter/Receiver Without Collision Detection
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
.. code-block:: none
VCC ---------------+
|
+-------x-------+
RXD <------| R |
| B|-----------<> B
TXD ------>| D ADM483 |
ESP | | RS485 bus side
DTR/RTS --+--->| DE |
| | A|-----------<> A
+----| /RE |
+-------x-------+
|
GND
This circuit does not allow for collision detection. It suppresses the null bytes that the hardware receives when the bit ``UART_RS485_CONF_REG.UART_RS485TX_RX_EN`` is set. The bit ``UART_RS485_CONF_REG.UART_RS485RXBY_TX_EN`` is not applicable in this case.
Circuit C: Auto Switching Transmitter/Receiver
""""""""""""""""""""""""""""""""""""""""""""""
.. code-block:: none
VCC1 <-------------------+-----------+ +-------------------+----> VCC2
10K ____ | | | |
+---|____|--+ +---x-----------x---+ 10K ____ |
| | | +---|____|--+
RX <----------+-------------------| RXD | |
10K ____ | A|---+---------------<> A (+)
+-------|____|------| PV ADM2483 | | ____ 120
| ____ | | +---|____|---+ RS485 bus side
VCC1 <--+--|____|--+------->| DE | |
10K | | B|---+------------+--<> B (-)
---+ +-->| /RE | | ____
10K | | | | +---|____|---+
____ | /-C +---| TXD | 10K |
TX >---|____|--+_B_|/ NPN | | | |
|\ | +---x-----------x---+ |
| \-E | | | |
| | | | |
GND1 GND1 GND1 GND2 GND2
This galvanically isolated circuit does not require RTS pin control by a software application or driver because it controls the transceiver direction automatically. However, it requires suppressing null bytes during transmission by setting ``UART_RS485_CONF_REG.UART_RS485RXBY_TX_EN`` to 1 and ``UART_RS485_CONF_REG.UART_RS485TX_RX_EN`` to 0. This setup can work in any RS485 UART mode or even in :cpp:enumerator:`UART_MODE_UART`.
Application Examples
--------------------
* :example:`peripherals/uart/uart_async_rxtxtasks` demonstrates how to use two asynchronous tasks for communication via the same UART interface, with one task transmitting "Hello world" periodically and the other task receiving and printing data from the UART.
* :example:`peripherals/uart/uart_echo` demonstrates how to use the UART interfaces to echo back any data received on the configured UART.
* :example:`peripherals/uart/uart_echo_rs485` demonstrates how to use the ESP32's UART software driver in RS485 half duplex transmission mode to echo any data it receives on UART port back to the sender in the RS485 network, requiring external connection of bus drivers.
* :example:`peripherals/uart/uart_events` demonstrates how to use the UART driver to handle special UART events, read data from UART0, and echo it back to the monitoring console.
* :example:`peripherals/uart/uart_repl` demonstrates how to use and connect two UARTs, allowing the UART used for stdout to send commands and receive replies from another console UART without human interaction.
* :example:`peripherals/uart/uart_select` demonstrates the use of ``select()`` for synchronous I/O multiplexing on the UART interface, allowing for non-blocking read and write from/to various sources such as UART and sockets, where a ready resource can be served without being blocked by a busy resource.
* :example:`peripherals/uart/nmea0183_parser` demonstrates how to parse NMEA-0183 data streams from GPS/BDS/GLONASS modules using the ESP UART Event driver and ESP event loop library, and output common information such as UTC time, latitude, longitude, altitude, and speed.
API Reference
-------------
.. include-build-file:: inc/uart.inc
.. include-build-file:: inc/uart_wakeup.inc
.. include-build-file:: inc/uart_types.inc
GPIO Lookup Macros
^^^^^^^^^^^^^^^^^^
Some UART ports have dedicated IO_MUX pins to which they are connected directly. These can be useful if you need very high UART baud rates, which means you will have to use IO_MUX pins only. In other cases, any GPIO pin can be used for UART communication by routing the signals through the GPIO matrix. If the UART port has dedicated IO_MUX pins, :c:macro:`UxTXD_GPIO_NUM` and :c:macro:`UxRXD_GPIO_NUM` can be used to find the corresponding IO_MUX pin numbers.
.. include-build-file:: inc/uart_pins.inc
+303
View File
@@ -0,0 +1,303 @@
UART DMA (UHCI)
===============
:link_to_translation:`zh_CN:[中文]`
This document describes the functionality of the UART DMA(UHCI) driver in ESP-IDF. The table of contents is as follows:
.. contents::
:local:
:depth: 2
Introduction
------------
This document shows how to use UART and DMA together for transmitting or receiving large data volumes using high baud rates. {IDF_TARGET_SOC_UART_HP_NUM} HP UART controllers on {IDF_TARGET_NAME} share one group of DMA TX/RX channels via host controller interface (HCI). This document assumes that UART DMA is controlled by UHCI entity.
.. note::
The UART DMA shares the HCI hardware with Bluetooth, so please don't use BT HCI together with UART DMA, even if they use different UART ports.
Quick Start
-----------
This section will quickly guide you on how to use the UHCI driver. Through a simple example including transmitting and receiving, it demonstrates how to create and start a UHCI, initiate a transmit and receive transactions, and register event callback functions. The general usage process is as follows:
Creating and Enabling the UHCI controller
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
UHCI controller requires the configuration specified by :cpp:type:`uhci_controller_config_t`.
If the configurations in :cpp:type:`uhci_controller_config_t` is specified, users can call :cpp:func:`uhci_new_controller` to allocate and initialize a uhci controller. This function will return a uhci controller handle if it runs correctly. Besides, UHCI must work with the installed UART driver. As a reference, see the code below.
.. code:: c
#define EX_UART_NUM 1 // Define UART port number
// For uart port configuration, please refer to UART programming guide.
// Please double-check as the baud rate might be limited by serial port chips.
uart_config_t uart_config = {
.baud_rate = 1 * 1000 * 1000,
.data_bits = UART_DATA_8_BITS,
.parity = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.source_clk = UART_SCLK_DEFAULT,
};
//UART parameter config
ESP_ERROR_CHECK(uart_param_config(EX_UART_NUM, &uart_config));
ESP_ERROR_CHECK(uart_set_pin(EX_UART_NUM, UART_TX_IO, UART_RX_IO, -1, -1));
uhci_controller_config_t uhci_cfg = {
.uart_port = EX_UART_NUM, // Connect uart port to UHCI hardware.
.tx_trans_queue_depth = 30, // Queue depth of transaction queue.
.max_receive_internal_mem = 10 * 1024, // internal memory usage, for more information, please refer to API reference.
.max_transmit_size = 10 * 1024, // Maximum transfer size in one transaction, in bytes.
.dma_burst_size = 32, // Burst size.
.rx_eof_flags.idle_eof = 1, // When to trigger a end of frame event, you can choose `idle_eof`, `rx_brk_eof`, `length_eof`, for more information, please refer to API reference.
};
uhci_controller_handle_t uhci_ctrl;
ESP_ERROR_CHECK(uhci_new_controller(&uhci_cfg, &uhci_ctrl));
Register Event Callbacks
^^^^^^^^^^^^^^^^^^^^^^^^
When an event occurs on the UHCI controller (e.g., transmission or receiving is completed), the CPU is notified of this event via an interrupt. If there is a function that needs to be called when a particular events occur, you can register a callback for that event with the ISR for UHCI (Interrupt Service Routine) by calling :cpp:func:`uhci_register_event_callbacks` for both TX and RX respectively. Since the registered callback functions are called in the interrupt context, the user should ensure that the callback function is non-blocking, e.g., by making sure that only FreeRTOS APIs with the ``FromISR`` suffix are called from within the function. The callback function has a boolean return value used to indicate whether a higher priority task has been unblocked by the callback.
The UHCI event callbacks are listed in the :cpp:type:`uhci_event_callbacks_t`:
- :cpp:member:`uhci_event_callbacks_t::on_tx_trans_done` sets a callback function for the "trans-done" event. The function prototype is declared in :cpp:type:`uhci_tx_done_callback_t`.
- :cpp:member:`uhci_event_callbacks_t::on_rx_trans_event` sets a callback function for "receive" event. The function prototype is declared in :cpp:type:`uhci_rx_event_callback_t`.
.. note::
The "rx-trans-event" is not equivalent to "receive-finished". This callback can also be called at a "partial-received" time, for many times during one receive transaction, which can be notified by :cpp:member:`uhci_rx_event_data_t::flags::totally_received`.
Users can save their own context in :cpp:func:`uhci_register_event_callbacks` as well, via the parameter ``user_data``. The user data is directly passed to each callback function.
In the callback function, users can fetch the event-specific data that is filled by the driver in the ``edata``. Note that the ``edata`` pointer is **only** valid during the callback, please do not try to save this pointer and use that outside of the callback function.
The TX event data is defined in :cpp:type:`uhci_tx_done_event_data_t`:
- :cpp:member:`uhci_tx_done_event_data_t::buffer` indicates the buffer has been sent out.
The RX event data is defined in :cpp:type:`uhci_rx_event_data_t`:
- :cpp:member:`uhci_rx_event_data_t::data` points to the received data. The data is stored in the buffer specified by the ``buffer`` parameter of :cpp:func:`uhci_receive`, so users should not free this receive buffer before the callback returns. Data pointed to by ``edata->data`` is typically only guaranteed to be readable during the callback. If application code needs to use the received data after callback returns, copy it to a external buffer first.
- :cpp:member:`uhci_rx_event_data_t::recv_size` indicates the number of received data. This value is not larger than the ``buffer_size`` parameter of :cpp:func:`uhci_receive` function.
- :cpp:member:`uhci_rx_event_data_t::flags::totally_received` indicates whether the current received buffer is the last one in the transaction.
.. note::
Forwarding ``edata->data`` pointer to another task without copying is an advanced zero-copy usage. To keep it safe, user code must understand the chunking and overwrite behavior of the underlying circular DMA buffer, and guarantee the consumer can process data before it gets overwritten.
Initiating UHCI Transmission
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
:cpp:func:`uhci_transmit` is a non-blocking function, which means this function will immediately return after you call it. The related callback can be obtained via :cpp:member:`uhci_event_callbacks_t::on_tx_trans_done` to indicate that the transaction is done. The function :cpp:func:`uhci_wait_all_tx_transaction_done` can be used to block the thread until all transactions are finished.
Data can be transmitted via UHCI as follows:
.. code:: c
uint8_t data_wr[DATA_LENGTH];
for (int i = 0; i < DATA_LENGTH; i++) {
data_wr[i] = i;
}
ESP_ERROR_CHECK(uhci_transmit(uhci_ctrl, data_wr, DATA_LENGTH));
// Wait all transaction finishes
ESP_ERROR_CHECK(uhci_wait_all_tx_transaction_done(uhci_ctrl, -1));
Initiating UHCI Reception
^^^^^^^^^^^^^^^^^^^^^^^^^
:cpp:func:`uhci_receive` is a non-blocking function, which means this function will immediately return after it is called. The related callback can be obtained via :cpp:member:`uhci_event_callbacks_t::on_rx_trans_event` to indicate the receive event. It can be useful to determine if a transaction has been finished.
Data can be received via UHCI as follows:
.. code:: c
// global variable: handle of queue.
QueueHandle_t uhci_queue;
IRAM_ATTR static bool s_uhci_rx_event_cbs(uhci_controller_handle_t uhci_ctrl, const uhci_rx_event_data_t *edata, void *user_ctx)
{
// parameter `user_ctx` is parsed by the third parameter of function `uhci_register_event_callbacks`
uhci_context_t *ctx = (uhci_context_t *)user_ctx;
BaseType_t xTaskWoken = 0;
uhci_event_t evt = 0;
if (edata->flags.totally_received) {
evt = UHCI_EVT_EOF;
ctx->receive_size += edata->recv_size;
memcpy(ctx->p_receive_data, edata->data, edata->recv_size);
} else {
evt = UHCI_EVT_PARTIAL_DATA;
ctx->receive_size += edata->recv_size;
memcpy(ctx->p_receive_data, edata->data, edata->recv_size);
ctx->p_receive_data += edata->recv_size;
}
xQueueSendFromISR(uhci_queue, &evt, &xTaskWoken);
return xTaskWoken;
}
// In task
uhci_event_callbacks_t uhci_cbs = {
.on_rx_trans_event = s_uhci_rx_event_cbs,
};
// Register callback and start reception.
ESP_ERROR_CHECK(uhci_register_event_callbacks(uhci_ctrl, &uhci_cbs, ctx));
ESP_ERROR_CHECK(uhci_receive(uhci_ctrl, pdata, 100));
uhci_event_t evt;
while (1) {
// A queue in task for receiving event triggered by UHCI.
if (xQueueReceive(uhci_queue, &evt, portMAX_DELAY) == pdTRUE) {
if (evt == UHCI_EVT_EOF) {
printf("Received size: %d\n", ctx->receive_size);
break;
}
}
}
In the API :cpp:func:`uhci_receive` interface, the parameter `read_buffer` is a buffer that must be provided by the user, and parameter `buffer_size` represents the size of the buffer supplied by the user. In the configuration structure of the UHCI controller, the parameter :cpp:member:`uhci_controller_config_t::max_receive_internal_mem` specifies the desired size of the internal DMA working space. The software allocates a certain number of DMA nodes based on this working space size. These nodes form a circular linked list.
When a node is filled, but the reception has not yet completed, the event :cpp:member:`uhci_event_callbacks_t::on_rx_trans_event` will be triggered, accompanied by :cpp:member:`uhci_rx_event_data_t::flags::totally_received` set to 0. When all the data has been fully received, the :cpp:member:`uhci_event_callbacks_t::on_rx_trans_event` event will be triggered again with :cpp:member:`uhci_rx_event_data_t::flags::totally_received` set to 1.
This mechanism allows the user to achieve continuous and fast reception using a relatively small buffer, without needing to allocate a buffer the same size as the total data being received.
.. note::
The parameter `read_buffer` of :cpp:func:`uhci_receive` cannot be freed until receive finishes.
Uninstall UHCI controller
^^^^^^^^^^^^^^^^^^^^^^^^^
If a previously installed UHCI controller is no longer needed, it's recommended to recycle the resource by calling :cpp:func:`uhci_del_controller`, so that the underlying hardware is released.
.. code:: c
ESP_ERROR_CHECK(uhci_del_controller(uhci_ctrl));
Advanced Features
-----------------
As the basic usage has been covered, it's time to explore more advanced features of the UHCI driver.
Power Management
^^^^^^^^^^^^^^^^
When power management is enabled, i.e., :ref:`CONFIG_PM_ENABLE` is on, the system may adjust or disable the clock source before going to sleep. As a result, the FIFO inside the UHCI can't work as expected.
The driver can prevent the above issue by creating a power management lock. The lock type is set based on different clock sources. The driver will acquire the lock in :cpp:func:`uhci_receive` or :cpp:func:`uhci_transmit`, and release it in the transaction-done interrupt. That means, any UHCI transactions between these two functions are guaranteed to work correctly and stably.
Cache Safe
^^^^^^^^^^
By default, the interrupt on which UHCI relies is deferred when the Cache is disabled for reasons such as writing or erasing the main flash. Thus, the transaction-done interrupt fails to be handled in time, which is unacceptable in a real-time application. What is worse, when the UHCI transaction relies on **ping-pong** interrupt to successively encode or copy the UHCI buffer, a delayed interrupt can lead to an unpredictable result.
There is a Kconfig option :ref:`CONFIG_UHCI_ISR_CACHE_SAFE` that has the following features:
1. Enable the interrupt being serviced even when the cache is disabled
2. Place all functions used by the ISR into IRAM [1]_
3. Place the driver object into DRAM in case it is mapped to PSRAM by accident
This Kconfig option allows the interrupt handler to run while the cache is disabled but comes at the cost of increased IRAM consumption.
Resource Consumption
^^^^^^^^^^^^^^^^^^^^
Use the :doc:`/api-guides/tools/idf-size` tool to check the code and data consumption of the UHCI driver. The following are the test results under 2 different conditions (using ESP32-C3 as an example):
**Note that the following data are not exact values and are for reference only; they may differ on different chip models.**
Resource consumption when :ref:`CONFIG_UHCI_ISR_CACHE_SAFE` is enabled:
.. list-table:: Resource Consumption
:widths: 10 10 10 10 10 10 10 10 10
:header-rows: 1
* - Component Layer
- Total Size
- DIRAM
- .bss
- .data
- .text
- Flash Code
- Flash Data
- .rodata
* - UHCI
- 5733
- 680
- 8
- 34
- 638
- 4878
- 175
- 175
Resource consumption when :ref:`CONFIG_UHCI_ISR_CACHE_SAFE` is disabled:
.. list-table:: Resource Consumption
:widths: 10 10 10 10 10 10 10 10 10 10
:header-rows: 1
* - Component Layer
- Total Size
- DIRAM
- .bss
- .data
- .text
- Flash Code
- .text
- Flash Data
- .rodata
* - UHCI
- 5479
- 42
- 8
- 34
- 0
- 5262
- 5262
- 175
- 175
Performance
^^^^^^^^^^^
To improve the real-time response capability of interrupt handling, the UHCI driver provides the :ref:`CONFIG_UHCI_ISR_HANDLER_IN_IRAM` option. Enabling this option will place the interrupt handler in internal RAM, reducing the latency caused by cache misses when loading instructions from Flash.
.. note::
However, user callback functions and context data called by the interrupt handler may still be located in Flash, and cache miss issues will still exist. Users need to place callback functions and data in internal RAM, for example, using :c:macro:`IRAM_ATTR` and :c:macro:`DRAM_ATTR`.
Thread Safety
^^^^^^^^^^^^^
The factory function :cpp:func:`uhci_new_controller`, :cpp:func:`uhci_register_event_callbacks` and :cpp:func:`uhci_del_controller` are guaranteed to be thread safe by the driver, which means, user can call them from different RTOS tasks without protection by extra locks.
Other Kconfig Options
^^^^^^^^^^^^^^^^^^^^^
- :ref:`CONFIG_UHCI_ENABLE_DEBUG_LOG` is allowed for the forced enabling of all debug logs for the UHCI driver, regardless of the global log level setting. Enabling this option can help developers obtain more detailed log information during the debugging process, making it easier to locate and resolve issues, but it will increase the size of the firmware binary.
Application Examples
--------------------
- :example:`peripherals/uart/uart_dma_ota` demonstrates how to use the uart dma for fast OTA the chip firmware with 1M baud rate speed.
API Reference
-------------
.. include-build-file:: inc/uhci.inc
.. include-build-file:: inc/components/esp_driver_uart/include/driver/uhci_types.inc
.. include-build-file:: inc/components/esp_hal_uart/include/hal/uhci_types.inc
.. [1]
The callback function, e.g., :cpp:member:`uhci_event_callbacks_t::on_tx_trans_done`, :cpp:member:`uhci_event_callbacks_t::on_rx_trans_event` and the functions invoked by itself should also reside in IRAM, users need to take care of this by themselves.
+181
View File
@@ -0,0 +1,181 @@
Voice Activity Detection (VAD)
==============================
:link_to_translation:`zh_CN:[中文]`
Introduction
------------
Voice Activity Detection (VAD) module facilitates the hardware implementation of the first-stage algorithm for voice wake-up and other multimedia functions.
Additionally, it provides hardware support for low-power voice wake-up solutions.
.. only:: SOC_LP_I2S_SUPPORTED
For LP I2S documentation, see :doc:`Low Power Inter-IC Sound <./lp_i2s>`.
Hardware State Machine
----------------------
LP VAD driver provides a structure :cpp:type:`lp_vad_config_t` to configure the LP VAD module:
- :cpp:member:`lp_vad_config_t::init_frame_num`, number of init frames that are used for VAD to denoise, this helps the VAD to decrease the accidental trigger ratio. Note too big values may lead to voice activity miss.
- :cpp:member:`lp_vad_config_t::min_energy_thresh`, minimum energy threshold, voice activities with energy higher than this value will be detected.
- :cpp:member:`lp_vad_config_t::skip_band_energy_thresh`, skip band energy threshold or not, the passband energy check determines whether the proportion of passband energy within the total frequency domain meets the required threshold. Note in different environments, enabling the passband energy check may reduce false trigger rates but could also increase the rate of missed detections.
- :cpp:member:`lp_vad_config_t::speak_activity_thresh`, when in speak-activity-listening-state, if number of the detected speak activity is higher than this value, VAD runs into speak-activity-detected-state.
- :cpp:member:`lp_vad_config_t::non_speak_activity_thresh`, when in speak-activity-detected-state, if the number of the detected speak activity is higher than this value, but lower than :cpp:member:`lp_vad_config_t::max_speak_activity_thresh`,
* if the number of the detected non-speak activity is higher than this value, VAD runs into speak-activity-listening-state
* if the number of the detected non-speak activity is lower than this value, VAD keeps in speak-activity-detected-state
- :cpp:member:`lp_vad_config_t::min_speak_activity_thresh`, when in speak-activity-detected-state, if the number of the detected speak activity is higher than this value, but lower than :cpp:member:`lp_vad_config_t::max_speak_activity_thresh`, then the VAD state machine will depends on the value of :cpp:member:`lp_vad_config_t::non_speak_activity_thresh`
- :cpp:member:`lp_vad_config_t::max_speak_activity_thresh`, when in speak-activity-detected-state, if the number of the detected speak activity is higher than this value, VAD runs into speak-activity-listening-state
Above configurations can change the VAD state machine shown below:
.. code-block:: text
┌──────────────────────────────────┐
│ │
┌─────────────┤ speak-activity-listening-state │ ◄───────────────┐
│ │ │ │
│ └──────────────────────────────────┘ │
│ ▲ │
│ │ │
│ │ │
│ │ │
│ │ │
detected speak activity │ │ detected speak activity │ detected speak activity
>= │ │ >= │ >=
'speak_activity_thresh' │ │ 'min_speak_activity_thresh' │ 'max_speak_activity_thresh'
│ │ │
│ │ && │
│ │ │
│ │ detected non-speak activity │
│ │ < │
│ │ 'non_speak_activity_thresh' │
│ │ │
│ │ │
│ │ │
│ │ │
│ │ │
│ ┌───────────┴─────────────────────┐ │
│ │ │ │
└───────────► │ speak-activity-detected-state ├─────────────────┘
│ │
└─┬───────────────────────────────┘
│ ▲
│ │
│ │
│ │ detected speak activity
│ │ >=
│ │ 'min_speak_activity_thresh'
│ │
│ │ &&
│ │
│ │ detected non-speak activity
│ │ <
└─────────────────────┘ 'non_speak_activity_thresh'
HP Driver Functional Overview
-----------------------------
The VAD HP driver is used for configure the LP VAD to be working under the control of the HP core. The HP core can also be woken up by the VAD when voice activity is detected.
Resource Allocation
^^^^^^^^^^^^^^^^^^^
.. only:: SOC_LP_I2S_SUPPORT_VAD
:cpp:type:`lp_vad_init_config_t` is the configuration structure that is needed to create a LP I2S VAD unit handle. To create a LP I2S VAD unit handle, you will need to first create a LP I2S channel handle. see :doc:`Low Power Inter-IC Sound <./lp_i2s>`.
You can call :cpp:func:`lp_i2s_vad_new_unit` to create the handle. If the VAD unit is no longer used, you should recycle the allocated resource by calling :cpp:func:`lp_i2s_vad_del_unit`.
.. code:: c
vad_unit_handle_t vad_handle = NULL;
lp_vad_init_config_t init_config = {
.lp_i2s_chan = rx_handle,
.vad_config = {
.init_frame_num = 100,
.min_energy_thresh = 100,
.speak_activity_thresh = 10,
.non_speak_activity_thresh = 30,
.min_speak_activity_thresh = 3,
.max_speak_activity_thresh = 100,
},
};
ESP_ERROR_CHECK(lp_i2s_vad_new_unit(vad_id, init_config, &vad_handle));
ESP_ERROR_CHECK(lp_i2s_vad_del_unit(vad_handle));
Enable and Disable the VAD
^^^^^^^^^^^^^^^^^^^^^^^^^^
.. only:: SOC_LP_I2S_SUPPORT_VAD
Before using a VAD unit to detect voice activity, you need to enable the VAD unit by calling :cpp:func:`lp_i2s_vad_enable`, this function switches the driver state from **init** to **enable**, and also enables the VAD hardware. Calling :cpp:func:`lp_i2s_vad_disable` does the opposite, that is, put the driver back to the **init** state, the hardware will stop as well.
HP Core Wake-up
^^^^^^^^^^^^^^^
.. only:: SOC_LP_I2S_SUPPORT_VAD
:cpp:func:`esp_sleep_enable_vad_wakeup` can help you to set the VAD to be working as the HP core wake-up source. To make VAD work during sleep, you should let the system maintain the RTC domain and XTAL power. See code example below:
.. code:: c
ESP_ERROR_CHECK(esp_sleep_enable_vad_wakeup());
LP Driver Functional Overview
-----------------------------
The VAD LP driver is mainly for LP core wake-up. The VAD can be configured under HP core control, then it can wakeup the LP core when voice activities are detected.
Resource Allocation
^^^^^^^^^^^^^^^^^^^
.. only:: SOC_LP_I2S_SUPPORT_VAD
:cpp:type:`lp_core_lp_vad_cfg_t` and :cpp:func:`lp_core_lp_vad_init` are used to initialize the VAD LP driver.
:cpp:func:`lp_core_lp_vad_deinit` is used to recycle the allocated resources.
Enable and Disable the VAD
^^^^^^^^^^^^^^^^^^^^^^^^^^
.. only:: SOC_LP_I2S_SUPPORT_VAD
:cpp:func:`lp_core_lp_vad_enable` and :cpp:func:`lp_core_lp_vad_disable` are used for enabling / disabling the hardware.
LP Core Wake-up
^^^^^^^^^^^^^^^
.. only:: SOC_LP_I2S_SUPPORT_VAD
Set :c:macro:`ULP_LP_CORE_WAKEUP_SOURCE_LP_VAD` in :cpp:type:`ulp_lp_core_cfg_t` to enable the VAD to be working as the LP core wake-up source.
.. code:: c
static void load_and_start_lp_core_firmware(ulp_lp_core_cfg_t* cfg, const uint8_t* firmware_start, const uint8_t* firmware_end)
{
TEST_ASSERT(ulp_lp_core_load_binary(firmware_start,
(firmware_end - firmware_start)) == ESP_OK);
TEST_ASSERT(ulp_lp_core_run(cfg) == ESP_OK);
}
ulp_lp_core_cfg_t cfg = {
.wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_LP_VAD,
};
load_and_start_lp_core_firmware(&cfg, lp_core_main_vad_bin_start, lp_core_main_vad_bin_end);
API Reference
-------------
.. include-build-file:: inc/lp_i2s_vad.inc
.. include-build-file:: inc/ulp_lp_core_lp_vad_shared.inc