Merge branch 'feature/gpio_sigma_delta_ng' into 'master'

 SDM Driver-NG (Sigma-Delta channel allocator)

Closes IDF-5261 and IDF-2877

See merge request espressif/esp-idf!18626
This commit is contained in:
morris
2022-07-20 21:30:12 +08:00
122 changed files with 1887 additions and 1352 deletions
+1 -1
View File
@@ -22,7 +22,7 @@ The DAC driver allows these channels to be set to arbitrary voltages.
The DAC channels can also be driven with DMA-style written sample data by the digital controller, however the driver does not supported this yet.
For other analog output options, see the :doc:`Sigma-delta Modulation module <sigmadelta>` and the :doc:`LED Control module <ledc>`. Both these modules produce high frequency PWM output, which can be hardware low-pass filtered in order to generate a lower frequency analog output.
For other analog output options, see the :doc:`Sigma-delta Modulation module <sdm>` and the :doc:`LED Control module <ledc>`. Both these modules produce high frequency PDM/PWM output, which can be hardware low-pass filtered in order to generate a lower frequency analog output.
Application Example
+1 -1
View File
@@ -27,7 +27,7 @@ Peripherals API
:SOC_SDMMC_HOST_SUPPORTED: sdmmc_host
sdspi_host
:SOC_SDIO_SLAVE_SUPPORTED: sdio_slave
:SOC_SIGMADELTA_SUPPORTED: sigmadelta
:SOC_SDM_SUPPORTED: sdm
spi_master
spi_slave
:esp32: secure_element
+28 -28
View File
@@ -20,19 +20,19 @@ Functional Overview
Description of the PCNT functionality is divided into the following sections:
- :ref:`allocating-resource` - 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:`set-up-channel-actions` - covers how to configure the PCNT channel to behave on different signal edges and levels.
- :ref:`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).
- :ref:`callbacks-register-event` - describes how to hook your specific code to the watch point event callback function.
- :ref:`set-glitch-filter` - describes how to enable and set the timing parameters for the internal glitch filter.
- :ref:`enable-and-disable-unit` - describes how to enable and disable the PCNT unit.
- :ref:`unit-io-control` - describes IO control functions of PCNT unit, like enable glitch filter, start and stop unit, get and clear count value.
- :ref:`managing-power` - describes what functionality will prevent the chip from going into low power mode.
- :ref:`iram-safety` - describes tips on how to make the PCNT interrupt and IO control functions work better along with a disabled cache.
- :ref:`thread-safe` - lists which APIs are guaranteed to be thread safe by the driver.
- :ref:`kconfig-option` - lists the supported Kconfig options that can be used to make a different effect on driver behavior.
- :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).
- :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.
- :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.
.. _allocating-resource:
.. _pcnt-resource-allocation:
Resource Allocation
^^^^^^^^^^^^^^^^^^^
@@ -91,7 +91,7 @@ If a previously created PCNT channel is no longer needed, it's recommended to re
pcnt_channel_handle_t pcnt_chan = NULL;
ESP_ERROR_CHECK(pcnt_new_channel(pcnt_unit, &chan_config, &pcnt_chan));
.. _set-up-channel-actions:
.. _pcnt-setup-channel-actions:
Set Up Channel Actions
^^^^^^^^^^^^^^^^^^^^^^
@@ -108,12 +108,12 @@ The PCNT will increase/decrease/hold its internal count value when the input pul
// 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));
.. _watch-points:
.. _pcnt-watch-points:
Watch Points
^^^^^^^^^^^^
Each PCNT unit can be configured to watch several different values that you're interested in. The value to be watched is also called **Watch Point**. The watch point itself can't 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:`callbacks-register-event` for how to register event callbacks.
Each PCNT unit can be configured to watch several different values that you're interested in. The value to be watched is also called **Watch Point**. The watch point itself can't 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't find any free hardware resource to save the watch point. You can't add the same watch point for multiple times, otherwise it will return error :c:macro:`ESP_ERR_INVALID_STATE`.
@@ -126,7 +126,7 @@ It is recommended to remove the unused watch point by :cpp:func:`pcnt_unit_remov
// add high limit watch point
ESP_ERROR_CHECK(pcnt_unit_add_watch_point(pcnt_unit, EXAMPLE_PCNT_HIGH_LIMIT));
.. _callbacks-register-event:
.. _pcnt-register-event-callbacks:
Register Event Callbacks
^^^^^^^^^^^^^^^^^^^^^^^^
@@ -146,7 +146,7 @@ Registering callback function will result in lazy installation of interrupt serv
.. code:: c
static bool example_pcnt_on_reach(pcnt_unit_handle_t unit, pcnt_watch_event_data_t *edata, void *user_ctx)
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;
@@ -162,7 +162,7 @@ Registering callback function will result in lazy installation of interrupt serv
QueueHandle_t queue = xQueueCreate(10, sizeof(int));
ESP_ERROR_CHECK(pcnt_unit_register_event_callbacks(pcnt_unit, &cbs, queue));
.. _set-glitch-filter:
.. _pcnt-set-glitch-filter:
Set Glitch Filter
^^^^^^^^^^^^^^^^^
@@ -177,7 +177,7 @@ This function should be called when the the unit is in the init state. Otherwise
.. note::
The glitch filter is clocked from APB. For the counter not to miss any pulses, the maximum glitch width should be longer than one APB_CLK cycle (usually 12.5 ns if APB equals 80MHz). As the APB frequency would be changed after DFS (Dynamic Frequency Scaling) enabled, which means the filter won't work as expect in that case. So the driver will install a PM lock for PCNT unit during the first time you enable the glitch filter. For more information related to power management strategy used in PCNT driver, please see :ref:`managing-power`.
The glitch filter is clocked from APB. For the counter not to miss any pulses, the maximum glitch width should be longer than one APB_CLK cycle (usually 12.5 ns if APB equals 80MHz). As the APB frequency would be changed after DFS (Dynamic Frequency Scaling) enabled, which means the filter won't work as expect in that case. So the driver will install a PM lock for PCNT unit during the first time you enable the glitch filter. For more information related to power management strategy used in PCNT driver, please see :ref:`pcnt-power-management`.
.. code:: c
@@ -186,7 +186,7 @@ This function should be called when the the unit is in the init state. Otherwise
};
ESP_ERROR_CHECK(pcnt_unit_set_glitch_filter(pcnt_unit, &filter_config));
.. _enable-and-disable-unit:
.. _pcnt-enable-disable-unit:
Enable and Disable Unit
^^^^^^^^^^^^^^^^^^^^^^^
@@ -195,7 +195,7 @@ Before doing IO control to the PCNT unit, you need to enable it first, by callin
* switch the PCNT driver state from **init** to **enable**.
* enable the interrupt service if it has been lazy installed in :cpp:func:`pcnt_unit_register_event_callbacks`.
* acquire a proper power management lock if it has been lazy installed in :cpp:func:`pcnt_unit_set_glitch_filter`. See also :ref:`managing-power` for more information.
* acquire a proper power management lock if it has been lazy installed in :cpp:func:`pcnt_unit_set_glitch_filter`. 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.
@@ -203,7 +203,7 @@ On the contrary, calling :cpp:func:`pcnt_unit_disable` will do the opposite, tha
ESP_ERROR_CHECK(pcnt_unit_enable(pcnt_unit));
.. _unit-io-control:
.. _pcnt-unit-io-control:
Unit IO Control
^^^^^^^^^^^^^^^
@@ -234,7 +234,7 @@ You can check current count value at any time by calling :cpp:func:`pcnt_unit_ge
int pulse_count = 0;
ESP_ERROR_CHECK(pcnt_unit_get_count(pcnt_unit, &pulse_count));
.. _managing-power:
.. _pcnt-power-management:
Power Management
^^^^^^^^^^^^^^^^
@@ -243,7 +243,7 @@ When power management is enabled (i.e. :ref:`CONFIG_PM_ENABLE` is on), the syste
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 you enable the glitch filter by :cpp:func:`pcnt_unit_set_glitch_filter`, the driver will guarantee that the power management lock is acquired after the PCNT unit is enabled by :cpp:func:`pcnt_unit_enable`. Likewise, the driver releases the lock after :cpp:func:`pcnt_unit_disable` is called.
.. _iram-safety:
.. _pcnt-iram-safe:
IRAM Safe
^^^^^^^^^
@@ -265,7 +265,7 @@ There's another Kconfig option :ref:`CONFIG_PCNT_CTRL_FUNC_IN_IRAM` that can put
- :cpp:func:`pcnt_unit_clear_count`
- :cpp:func:`pcnt_unit_get_count`
.. _thread-safe:
.. _pcnt-thread-safe:
Thread Safety
^^^^^^^^^^^^^
@@ -280,13 +280,13 @@ The following functions are allowed to run under ISR context, the driver uses a
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.
.. _kconfig-option:
.. _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:`iram-safety` for more information.
- :ref:`CONFIG_PCNT_ISR_IRAM_SAFE` controls whether the default ISR handler can work when cache is disabled, see :ref:`iram-safety` for more information.
- :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. Enable this option will increase the firmware binary size.
Application Examples
+132
View File
@@ -0,0 +1,132 @@
Sigma-Delta Modulation (SDM)
============================
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]_
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 a SDM channel:
- `Resource Allocation <#resource-allocation>`__ - covers which parameters should be set up to get a channel handle and how to recycle the resources when it finishes working.
- `Enable and Disable Channel <#enable-and-disable-channel>`__ - covers how to enable and disable the channel.
- `Set Equivalent Duty Cycle <#set-equivalent-duty-cycle>`__ - describes how to set the equivalent duty cycle of the PDM pulses.
- `Power Management <#power-management>`__ - describes how different source clock selections can affect power consumption.
- `IRAM Safe <#iram-safe>`__ - lists which functions are supposed to work even when the cache is disabled.
- `Thread Safety <#thread-safety>`__ - lists which APIs are guaranteed to be thread safe by the driver.
- `Kconfig Options <#kconfig-options>`__ - lists the supported Kconfig options that can be used to make a different effect on driver behavior.
Resource Allocation
^^^^^^^^^^^^^^^^^^^
A SDM channel is represented by :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 users don't need to manually assign a fixed channel to a GPIO.
To install a 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 will 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.
- :cpp:member:`sdm_config_t::invert_out` sets whether to invert the output signal.
- :cpp:member:`sdm_config_t::io_loop_back` is for debugging purposes only. It enables both the GPIO's input and output ability through the GPIO matrix peripheral.
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), then :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 a SDM channel handle, you should disable it by :cpp:func:`sdm_channel_disable` in advance or make sure it has not enabled yet by :cpp:func:`sdm_channel_enable`.
Creating a SDM Channel with Sample Rate of 1MHz
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. 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));
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 will:
* switch the channel state from **init** to **enable**
* acquire a proper power management lock is a specific clock source (e.g. APB clock) is selected. See also `Power management <#power-management>`__ for more information.
On the contrary, calling :cpp:func:`sdm_channel_disable` will do the opposite, that is, put the channel back to the **init** state and release the power management lock.
Set Equivalent Duty Cycle
^^^^^^^^^^^^^^^^^^^^^^^^^
For the output PDM signals, the duty cycle refers to the percentage of high level cycles to the whole statistical period. The average output voltage from the channel is calculated by ``Vout = VDD_IO / 256 * duty + VDD_IO / 2``. Thus the range of the ``duty`` input parameter of :cpp:func:`sdm_channel_set_duty` is from -128 to 127 (eight bit signed integer). For example,if zero value is set, then the output signal's duty will be about 50%.
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 a SDM channel instance that has selected :cpp:enumerator:`SDM_CLK_SRC_APB` as its clock source, the driver will guarantee that the power management lock is acquired when enable 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.
IRAM Safe
^^^^^^^^^
There's 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_duty`
Thread Safety
^^^^^^^^^^^^^
The factory function :cpp:func:`sdm_new_channel` is guaranteed to be thread safe by the driver, which means, user can call it 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:`sdm_channel_set_duty`
Other functions that take the :cpp:type:`sdm_channel_handle_t` as the first positional parameter, are not treated as thread safe. Which means the user should avoid calling them from multiple tasks.
Kconfig Options
^^^^^^^^^^^^^^^
- :ref:`CONFIG_SDM_CTRL_FUNC_IN_IRAM` controls where to place the SDM channel control functions (IRAM or Flash), see `IRAM Safe <#iram-safe>`__ for more information.
- :ref:`CONFIG_SDM_ENABLE_DEBUG_LOG` is used to enabled the debug log output. Enable this option will increase the firmware binary size.
Convert to analog signal (Optional)
-----------------------------------
Typically, if the sigma-delta signal is connected to an LED, you don't have to add any filter between them (because our eyes are a low pass filter naturally). 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
Application Example
-------------------
* LED driven by a GPIO that is modulated with Sigma-Delta: :example:`peripherals/sigma_delta`.
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 won't forbid you from applying for more channels, but it will return 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
@@ -1,53 +0,0 @@
Sigma-delta Modulation
======================
{IDF_TARGET_SIGMA_DELTA_MODULATION_CHANNEL_NUM:default="8", esp32c3="4"}
Introduction
------------
{IDF_TARGET_NAME} has a second-order sigma-delta modulation module. This driver configures the channels of the sigma-delta module.
Functionality Overview
----------------------
There are {IDF_TARGET_SIGMA_DELTA_MODULATION_CHANNEL_NUM} independent sigma-delta modulation channels identified with :cpp:type:`sigmadelta_channel_t`. Each channel is capable to output the binary, hardware generated signal with the sigma-delta modulation.
Selected channel should be set up by providing configuration parameters in :cpp:type:`sigmadelta_config_t` and then applying this configuration with :cpp:func:`sigmadelta_config`.
Another option is to call individual functions, that will configure all required parameters one by one:
* **Prescaler** of the sigma-delta generator - :cpp:func:`sigmadelta_set_prescale`
* **Duty** of the output signal - :cpp:func:`sigmadelta_set_duty`
* **GPIO pin** to output modulated signal - :cpp:func:`sigmadelta_set_pin`
The range of the 'duty' input parameter of :cpp:func:`sigmadelta_set_duty` is from -128 to 127 (eight bit signed integer). If zero value is set, then the output signal's duty will be about 50%, see description of :cpp:func:`sigmadelta_set_duty`.
Convert to analog signal (Optional)
-----------------------------------
Typically, if the sigma-delta signal is connected to an LED, you don't have to add any filter between them (because our eyes are a low pass filter naturally). 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
Application Example
-------------------
Sigma-delta Modulation example: :example:`peripherals/sigmadelta`.
API Reference
-------------
.. include-build-file:: inc/sigmadelta.inc
.. include-build-file:: inc/sigmadelta_types.inc
.. _Sallen-Key topology Low Pass Filter: https://en.wikipedia.org/wiki/Sallen%E2%80%93Key_topology