Skip to content

Commit

Permalink
Merge branch 'feature/add_sdm_dac_example' into 'master'
Browse files Browse the repository at this point in the history
sdm: add wave output example

Closes IDF-6636

See merge request espressif/esp-idf!21892
  • Loading branch information
suda-morris committed Jan 9, 2023
2 parents dba66c1 + 6b631b6 commit 5fb9866
Show file tree
Hide file tree
Showing 27 changed files with 300 additions and 59 deletions.
2 changes: 1 addition & 1 deletion components/driver/deprecated/sigma_delta_legacy.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ static inline esp_err_t _sigmadelta_set_duty(sigmadelta_port_t sigmadelta_port,
{
SIGMADELTA_OBJ_CHECK(sigmadelta_port);

sdm_ll_set_duty(p_sigmadelta_obj[sigmadelta_port]->hal.dev, channel, duty);
sdm_ll_set_pulse_density(p_sigmadelta_obj[sigmadelta_port]->hal.dev, channel, duty);
return ESP_OK;
}

Expand Down
34 changes: 25 additions & 9 deletions components/driver/include/driver/sdm.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
Expand All @@ -25,7 +25,7 @@ typedef struct sdm_channel_t *sdm_channel_handle_t;
typedef struct {
int gpio_num; /*!< GPIO number */
sdm_clock_source_t clk_src; /*!< Clock source */
uint32_t sample_rate_hz; /*!< Sample rate in Hz, it determines how frequent the modulator outputs a pulse */
uint32_t sample_rate_hz; /*!< Over sample rate in Hz, it determines the frequency of the carrier pulses */
struct {
uint32_t invert_out: 1; /*!< Whether to invert the output signal */
uint32_t io_loop_back: 1; /*!< For debug/test, the signal output from the GPIO will be fed to the input path as well */
Expand Down Expand Up @@ -88,17 +88,33 @@ esp_err_t sdm_channel_enable(sdm_channel_handle_t chan);
esp_err_t sdm_channel_disable(sdm_channel_handle_t chan);

/**
* @brief Set the duty cycle of the PDM output signal.
* @brief Set the pulse density of the PDM output signal.
*
* @note For PDM signals, duty cycle refers to the percentage of high level cycles to the whole statistical period.
* The average output voltage could be Vout = VDD_IO / 256 * duty + VDD_IO / 2
* @note If the duty is set to zero, the output signal is like a 50% duty cycle square wave, with a frequency around (sample_rate_hz / 4).
* @note The duty is proportional to the equivalent output voltage after a low-pass-filter.
* @note The raw output signal requires a low-pass filter to restore it into analog voltage,
* the restored analog output voltage could be Vout = VDD_IO / 256 * density + VDD_IO / 2
* @note This function is allowed to run within ISR context
* @note This function will be placed into IRAM if `CONFIG_SDM_CTRL_FUNC_IN_IRAM` is on, so that it's allowed to be executed when Cache is disabled
* @note This function will be placed into IRAM if `CONFIG_SDM_CTRL_FUNC_IN_IRAM` is on,
* so that it's allowed to be executed when Cache is disabled
*
* @param[in] chan SDM channel created by `sdm_new_channel`
* @param[in] duty Equivalent duty cycle of the PDM output signal, ranges from -128 to 127. But the range of [-90, 90] can provide a better randomness.
* @param[in] density Quantized pulse density of the PDM output signal, ranges from -128 to 127.
* But the range of [-90, 90] can provide a better randomness.
* @return
* - ESP_OK: Set pulse density successfully
* - ESP_ERR_INVALID_ARG: Set pulse density failed because of invalid argument
* - ESP_FAIL: Set pulse density failed because of other error
*/
esp_err_t sdm_channel_set_pulse_density(sdm_channel_handle_t chan, int8_t density);

/**
* @brief The alias function of `sdm_channel_set_pulse_density`, it decides the pulse density of the output signal
*
* @note `sdm_channel_set_pulse_density` has a more appropriate name compare this
* alias function, suggest to turn to `sdm_channel_set_pulse_density` instead
*
* @param[in] chan SDM channel created by `sdm_new_channel`
* @param[in] duty Actually it's the quantized pulse density of the PDM output signal
*
* @return
* - ESP_OK: Set duty cycle successfully
* - ESP_ERR_INVALID_ARG: Set duty cycle failed because of invalid argument
Expand Down
2 changes: 1 addition & 1 deletion components/driver/linker.lf
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ entries:
gpio: gpio_set_level (noflash)
gpio: gpio_intr_disable (noflash)
if SDM_CTRL_FUNC_IN_IRAM = y:
sdm: sdm_channel_set_duty (noflash)
sdm: sdm_channel_set_pulse_density (noflash)
if DAC_CTRL_FUNC_IN_IRAM = y:
dac_oneshot: dac_oneshot_output_voltage (noflash)
dac_continuous: dac_continuous_write_asynchronously (noflash)
Expand Down
11 changes: 7 additions & 4 deletions components/driver/sdm.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
Expand Down Expand Up @@ -262,7 +262,7 @@ esp_err_t sdm_new_channel(const sdm_config_t *config, sdm_channel_handle_t *ret_
sdm_ll_set_prescale(group->hal.dev, chan_id, prescale);
chan->sample_rate_hz = src_clk_hz / prescale;
// preset the duty cycle to zero
sdm_ll_set_duty(group->hal.dev, chan_id, 0);
sdm_ll_set_pulse_density(group->hal.dev, chan_id, 0);

// initialize other members of timer
chan->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
Expand Down Expand Up @@ -317,16 +317,19 @@ esp_err_t sdm_channel_disable(sdm_channel_handle_t chan)
return ESP_OK;
}

esp_err_t sdm_channel_set_duty(sdm_channel_handle_t chan, int8_t duty)
esp_err_t sdm_channel_set_pulse_density(sdm_channel_handle_t chan, int8_t density)
{
ESP_RETURN_ON_FALSE_ISR(chan, ESP_ERR_INVALID_ARG, TAG, "invalid argument");

sdm_group_t *group = chan->group;
int chan_id = chan->chan_id;

portENTER_CRITICAL_SAFE(&chan->spinlock);
sdm_ll_set_duty(group->hal.dev, chan_id, duty);
sdm_ll_set_pulse_density(group->hal.dev, chan_id, density);
portEXIT_CRITICAL_SAFE(&chan->spinlock);

return ESP_OK;
}

esp_err_t sdm_channel_set_duty(sdm_channel_handle_t chan, int8_t duty)
__attribute__((alias("sdm_channel_set_pulse_density")));
10 changes: 5 additions & 5 deletions components/driver/test_apps/gpio_extensions/main/test_sdm.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
Expand Down Expand Up @@ -37,7 +37,7 @@ TEST_CASE("sdm_channel_install_uninstall", "[sdm]")
}
}

TEST_CASE("sdm_channel_set_duty", "[sdm]")
TEST_CASE("sdm_channel_set_pulse_density", "[sdm]")
{
const int sdm_chan_gpios[2] = {0, 2};
sdm_config_t config = {
Expand All @@ -49,16 +49,16 @@ TEST_CASE("sdm_channel_set_duty", "[sdm]")
config.gpio_num = sdm_chan_gpios[i];
TEST_ESP_OK(sdm_new_channel(&config, &chans[i]));
// should see a ~250KHz (sample_rate/4) square wave
TEST_ESP_OK(sdm_channel_set_duty(chans[i], 0));
TEST_ESP_OK(sdm_channel_set_pulse_density(chans[i], 0));
TEST_ESP_OK(sdm_channel_enable(chans[i]));
}
vTaskDelay(pdMS_TO_TICKS(500));

// can't delete the channel if the channel is in the enable state
TEST_ESP_ERR(ESP_ERR_INVALID_STATE, sdm_del_channel(chans[0]));

TEST_ESP_OK(sdm_channel_set_duty(chans[0], 127));
TEST_ESP_OK(sdm_channel_set_duty(chans[1], -128));
TEST_ESP_OK(sdm_channel_set_pulse_density(chans[0], 127));
TEST_ESP_OK(sdm_channel_set_pulse_density(chans[1], -128));
vTaskDelay(pdMS_TO_TICKS(500));

for (size_t i = 0; i < 2; i++) {
Expand Down
6 changes: 3 additions & 3 deletions components/hal/esp32/include/hal/sdm_ll.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ static inline void sdm_ll_enable_clock(gpio_sd_dev_t *hw, bool en)
*
* @param hw Peripheral SIGMADELTA hardware instance address.
* @param channel Sigma-delta channel number
* @param duty Sigma-delta duty of one channel, the value ranges from -128 to 127, recommended range is -90 ~ 90.
* @param density Sigma-delta quantized density of one channel, the value ranges from -128 to 127, recommended range is -90 ~ 90.
* The waveform is more like a random one in this range.
*/
__attribute__((always_inline))
static inline void sdm_ll_set_duty(gpio_sd_dev_t *hw, int channel, int8_t duty)
static inline void sdm_ll_set_pulse_density(gpio_sd_dev_t *hw, int channel, int8_t density)
{
HAL_FORCE_MODIFY_U32_REG_FIELD(hw->channel[channel], duty, (uint32_t)duty);
HAL_FORCE_MODIFY_U32_REG_FIELD(hw->channel[channel], duty, (uint32_t)density);
}

/**
Expand Down
6 changes: 3 additions & 3 deletions components/hal/esp32c3/include/hal/sdm_ll.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ static inline void sdm_ll_enable_clock(gpio_sd_dev_t *hw, bool en)
*
* @param hw Peripheral SIGMADELTA hardware instance address.
* @param channel Sigma-delta channel number
* @param duty Sigma-delta duty of one channel, the value ranges from -128 to 127, recommended range is -90 ~ 90.
* @param density Sigma-delta quantized density of one channel, the value ranges from -128 to 127, recommended range is -90 ~ 90.
* The waveform is more like a random one in this range.
*/
__attribute__((always_inline))
static inline void sdm_ll_set_duty(gpio_sd_dev_t *hw, int channel, int8_t duty)
static inline void sdm_ll_set_pulse_density(gpio_sd_dev_t *hw, int channel, int8_t density)
{
HAL_FORCE_MODIFY_U32_REG_FIELD(hw->channel[channel], duty, (uint32_t)duty);
HAL_FORCE_MODIFY_U32_REG_FIELD(hw->channel[channel], duty, (uint32_t)density);
}

/**
Expand Down
8 changes: 4 additions & 4 deletions components/hal/esp32c6/include/hal/sdm_ll.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
Expand Down Expand Up @@ -31,13 +31,13 @@ static inline void sdm_ll_enable_clock(gpio_sd_dev_t *hw, bool en)
*
* @param hw Peripheral SIGMADELTA hardware instance address.
* @param channel Sigma-delta channel number
* @param duty Sigma-delta duty of one channel, the value ranges from -128 to 127, recommended range is -90 ~ 90.
* @param density Sigma-delta quantized density of one channel, the value ranges from -128 to 127, recommended range is -90 ~ 90.
* The waveform is more like a random one in this range.
*/
__attribute__((always_inline))
static inline void sdm_ll_set_duty(gpio_sd_dev_t *hw, int channel, int8_t duty)
static inline void sdm_ll_set_pulse_density(gpio_sd_dev_t *hw, int channel, int8_t density)
{
HAL_FORCE_MODIFY_U32_REG_FIELD(hw->channel[channel], duty, (uint32_t)duty);
HAL_FORCE_MODIFY_U32_REG_FIELD(hw->channel[channel], duty, (uint32_t)density);
}

/**
Expand Down
6 changes: 3 additions & 3 deletions components/hal/esp32h4/include/hal/sdm_ll.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ static inline void sdm_ll_enable_clock(gpio_sd_dev_t *hw, bool en)
*
* @param hw Peripheral SIGMADELTA hardware instance address.
* @param channel Sigma-delta channel number
* @param duty Sigma-delta duty of one channel, the value ranges from -128 to 127, recommended range is -90 ~ 90.
* @param density Sigma-delta quantized density of one channel, the value ranges from -128 to 127, recommended range is -90 ~ 90.
* The waveform is more like a random one in this range.
*/
__attribute__((always_inline))
static inline void sdm_ll_set_duty(gpio_sd_dev_t *hw, int channel, int8_t duty)
static inline void sdm_ll_set_pulse_density(gpio_sd_dev_t *hw, int channel, int8_t density)
{
HAL_FORCE_MODIFY_U32_REG_FIELD(hw->channel[channel], duty, (uint32_t)duty);
HAL_FORCE_MODIFY_U32_REG_FIELD(hw->channel[channel], duty, (uint32_t)density);
}

/**
Expand Down
6 changes: 3 additions & 3 deletions components/hal/esp32s2/include/hal/sdm_ll.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ static inline void sdm_ll_enable_clock(gpio_sd_dev_t *hw, bool en)
*
* @param hw Peripheral SIGMADELTA hardware instance address.
* @param channel Sigma-delta channel number
* @param duty Sigma-delta duty of one channel, the value ranges from -128 to 127, recommended range is -90 ~ 90.
* @param density Sigma-delta quantized density of one channel, the value ranges from -128 to 127, recommended range is -90 ~ 90.
* The waveform is more like a random one in this range.
*/
__attribute__((always_inline))
static inline void sdm_ll_set_duty(gpio_sd_dev_t *hw, int channel, int8_t duty)
static inline void sdm_ll_set_pulse_density(gpio_sd_dev_t *hw, int channel, int8_t density)
{
HAL_FORCE_MODIFY_U32_REG_FIELD(hw->channel[channel], duty, (uint32_t)duty);
HAL_FORCE_MODIFY_U32_REG_FIELD(hw->channel[channel], duty, (uint32_t)density);
}

/**
Expand Down
6 changes: 3 additions & 3 deletions components/hal/esp32s3/include/hal/sdm_ll.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ static inline void sdm_ll_enable_clock(gpio_sd_dev_t *hw, bool en)
*
* @param hw Peripheral SIGMADELTA hardware instance address.
* @param channel Sigma-delta channel number
* @param duty Sigma-delta duty of one channel, the value ranges from -128 to 127, recommended range is -90 ~ 90.
* @param density Sigma-delta quantized density of one channel, the value ranges from -128 to 127, recommended range is -90 ~ 90.
* The waveform is more like a random one in this range.
*/
__attribute__((always_inline))
static inline void sdm_ll_set_duty(gpio_sd_dev_t *hw, int channel, int8_t duty)
static inline void sdm_ll_set_pulse_density(gpio_sd_dev_t *hw, int channel, int8_t density)
{
HAL_FORCE_MODIFY_U32_REG_FIELD(hw->channel[channel], duty, (uint32_t)duty);
HAL_FORCE_MODIFY_U32_REG_FIELD(hw->channel[channel], duty, (uint32_t)density);
}

/**
Expand Down
23 changes: 18 additions & 5 deletions docs/en/api-reference/peripherals/sdm.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ 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|_).

The main differences comparing to the PDM in I2S peripheral and DAC are:

1. SDM has no clock signal, it just like the DAC mode of PDM;
2. SDM has no DMA, and it can't change its output density continuously. If you have to, you can update the density in a timer's callback;
3. Base on the former two points, an external active or passive filter is required to restore the analog wave (See :ref:`convert_to_analog_signal`);

Typically, a Sigma-Delta modulated channel can be used in scenarios like:

- LED dimming
Expand Down Expand Up @@ -65,10 +73,10 @@ Before doing further IO control to the SDM channel, you should enable it first,

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
^^^^^^^^^^^^^^^^^^^^^^^^^
Set Pulse Density
^^^^^^^^^^^^^^^^^

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%.
For the output PDM signals, the pulse density decides the output analog voltage that 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 (eight-bit signed integer). For example, if a zero value is set, then the output signal's duty will be around 50%.

Power Management
^^^^^^^^^^^^^^^^
Expand All @@ -82,15 +90,15 @@ 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`
- :cpp:func:`sdm_channel_set_pulse_density`

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`
- :cpp:func:`sdm_channel_set_pulse_density`

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.

Expand All @@ -100,6 +108,8 @@ 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:

Convert to analog signal (Optional)
-----------------------------------

Expand Down Expand Up @@ -130,3 +140,6 @@ API Reference
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

.. |wiki_ref| replace:: Delta-sigma modulation on Wikipedia
.. _wiki_ref: https://en.wikipedia.org/wiki/Delta-sigma_modulation
5 changes: 3 additions & 2 deletions docs/en/migration-guides/release-5.x/5.0/peripherals.rst
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,13 @@ GPIO
- SDM channel representation has changed from ``sigmadelta_channel_t`` to :cpp:type:`sdm_channel_handle_t`, which is an opaque pointer.
- SDM channel configurations are stored in :cpp:type:`sdm_config_t` now, instead the previous ``sigmadelta_config_t``.
- In the legacy driver, users don't have to set the clock source for SDM channel. But in the new driver, users need to set a proper one in the :cpp:member:`sdm_config_t::clk_src`. The available clock sources are listed in the :cpp:type:`soc_periph_sdm_clk_src_t`.
- In the legacy driver, users need to set a ``prescale`` for the channel, which reflects the frequency in which the modulator outputs a pulse. In the new driver, users should use :cpp:member:`sdm_config_t::sample_rate_hz`.
- In the legacy driver, users need to set a ``prescale`` for the channel, which reflects the frequency in which the modulator outputs a pulse. In the new driver, users should use :cpp:member:`sdm_config_t::sample_rate_hz` to set the over sample rate.
- In the legacy driver, users set ``duty`` to decide the output analog value, it's now renamed to a more appropriate name ``density``.

Breaking Changes in Usage
^^^^^^^^^^^^^^^^^^^^^^^^^

- Channel configuration was done by channel allocation, in :cpp:func:`sdm_new_channel`. In the new driver, only the ``duty`` can be changed at runtime, by :cpp:func:`sdm_channel_set_duty`. Other parameters like ``gpio number`` and ``prescale`` are only allowed to set during channel allocation.
- Channel configuration was done by channel allocation, in :cpp:func:`sdm_new_channel`. In the new driver, only the ``density`` can be changed at runtime, by :cpp:func:`sdm_channel_set_pulse_density`. Other parameters like ``gpio number`` and ``prescale`` are only allowed to set during channel allocation.
- Before further channel operations, users should **enable** the channel in advance, by calling :cpp:func:`sdm_channel_enable`. This function will help to manage some system level services, like **Power Management**.

Timer Group Driver
Expand Down
Loading

0 comments on commit 5fb9866

Please sign in to comment.