Merge branch 'feature/add_sdm_dac_example' into 'master'

sdm: add wave output example

Closes IDF-6636

See merge request espressif/esp-idf!21892
This commit is contained in:
morris 2023-01-09 15:30:30 +08:00
commit 5fb9866f0d
27 changed files with 300 additions and 59 deletions

View File

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

View File

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

View File

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

View File

@ -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
*/
@ -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;
@ -317,7 +317,7 @@ 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");
@ -325,8 +325,11 @@ esp_err_t sdm_channel_set_duty(sdm_channel_handle_t chan, int8_t duty)
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")));

View File

@ -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
*/
@ -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 = {
@ -49,7 +49,7 @@ 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));
@ -57,8 +57,8 @@ TEST_CASE("sdm_channel_set_duty", "[sdm]")
// 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++) {

View File

@ -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);
}
/**

View File

@ -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);
}
/**

View File

@ -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
*/
@ -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);
}
/**

View File

@ -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);
}
/**

View File

@ -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);
}
/**

View File

@ -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);
}
/**

View File

@ -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
@ -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
^^^^^^^^^^^^^^^^
@ -82,7 +90,7 @@ 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
^^^^^^^^^^^^^
@ -90,7 +98,7 @@ 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.
@ -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)
-----------------------------------
@ -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

View File

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

View File

@ -107,11 +107,12 @@ GPIO
- SDM 通道配置原来存放于 ``sigmadelta_config_t``,现存放于 :cpp:type:`sdm_config_t`
- 旧版驱动中,用户无需为 SDM 通道设置时钟源。但是在新驱动中,用户需要在 :cpp:member:`sdm_config_t::clk_src` 为 SDM 通道设置合适的时钟源,:cpp:type:`soc_periph_sdm_clk_src_t` 中列出了可用的时钟源。
- 旧版驱动中,用户需要为通道设置 ``prescale``,该参数会影响调制器输出脉冲的频率。在新的驱动中,用户需要使用 :cpp:member:`sdm_config_t::sample_rate_hz` 实现该功能。
- 旧版驱动中,用户通过设置 ``duty`` 来改变输出的模拟量,现在换成了一个更贴切的名字 ``density``
主要使用方法更新
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- 更新前,通道配置由通道分配在 :cpp:func:`sdm_new_channel` 完成。在新驱动中,只有 ``duty`` 可在运行时由 :cpp:func:`sdm_channel_set_duty` 更新。其他参数如 ``gpio number````prescale`` 只能在通道分配时进行设置。
- 更新前,通道配置由通道分配在 :cpp:func:`sdm_new_channel` 完成。在新驱动中,只有 ``density`` 可在运行时由 :cpp:func:`sdm_channel_set_pulse_density` 更新。其他参数如 ``gpio number````prescale`` 只能在通道分配时进行设置。
- 在进行下一步通道操作前,用户应通过调用 :cpp:func:`sdm_channel_enable` 提前 **使能** 该通道。该函数有助于管理一些系统级服务,如 **电源管理**
定时器组驱动

View File

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

View File

@ -3,4 +3,4 @@
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(gpio_sigma_delta)
project(sdm_dac_example)

View File

@ -0,0 +1,69 @@
| Supported Targets | ESP32 | ESP32-C3 | ESP32-C6 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- |
# Sigma Delta Modulation DAC Example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
This example uses the sigma-delta driver to generate modulated output on a GPIO. If you filter the output signal with an active or passive filter, you can get a 1 KHz sine wave.
## How to use example
### Hardware Required
* A development board with any supported Espressif SoC (e.g., ESP32-DevKitC, ESP-WROVER-KIT, etc.)
* A USB cable for Power supply and programming
* An active or passive low-pass filter. Connect them as below:
```
┌──────────────────────────────┐
│ ESP │ ┌───────────────────┐
│ │ │ Active or Passive │ Sine Wave
│ EXAMPLE_SIGMA_DELTA_GPIO_NUM ├────►│ Low-pass Filter ├──────────────
└──────────────────────────────┘ └───────────────────┘
```
### Configure the project
The main configurations can be update by the macro that defined at the top of the [example source file](main/sdm_dac_example_main.c), including the output gpio, timer callback interval and the sine wave frequency and amplitude.
### Build and Flash
Build the project and flash it to the board, then run the monitor tool to view the serial output:
Run `idf.py -p PORT flash monitor` to build, flash and monitor the project.
(To exit the serial monitor, type ``Ctrl-]``.)
See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
## Example Output
Once the upload is complete and the board is reset, the program should start running. This is reported on the monitor as below:
```
...
I (299) main_task: Calling app_main()
I (309) gpio: GPIO[0]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (309) sdm_dac: Sigma-delta output is attached to GPIO 0
I (319) sdm_dac: Timer allocated with resolution 10000000 Hz
I (329) sdm_dac: Timer callback registered, interval 10 us
I (329) sdm_dac: Timer enabled
I (339) sdm_dac: Output start
```
After the output stated, you can monitor the output signal by an oscilloscope.
If you monitor on the GPIO directly, you can see the raw SDM output, it consists by square waves (i.e. pulse) with different density
![raw_sdm_output](raw_sdm_output.png)
If you monitor the signal after a low-pass filter, you can see the pulses are filtered into a sine wave already
![filtered_sine_wave](filtered_sine_wave.png)
## Troubleshooting
For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.
If a lot of glitches appear in the filtered sine wave, it might be the inappropriate GND that the probe connected, please try to choose the GND that nearby the filter.

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

View File

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

View File

@ -0,0 +1,111 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <math.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "driver/sdm.h"
#include "driver/gptimer.h"
#define EXAMPLE_SIGMA_DELTA_GPIO_NUM (0) // Select GPIO_NUM_0 as the sigma-delta output pin
#define EXAMPLE_OVER_SAMPLE_RATE (20 * 1000 * 1000) // 20 MHz over sample rate
#define EXAMPLE_TIMER_RESOLUTION (10 * 1000 * 1000) // 10 MHz timer counting resolution
#define EXAMPLE_CALLBACK_INTERVAL_US (10) // 10 us interval of each timer callback
#define EXAMPLE_ALARM_COUNT (EXAMPLE_CALLBACK_INTERVAL_US * (EXAMPLE_TIMER_RESOLUTION / 1000000))
#define EXAMPLE_SINE_WAVE_FREQ_HZ (1000) // 1 KHz wave, adjust this value to decide the sine wave frequency
#define EXAMPLE_SINE_WAVE_AMPLITUDE (127.0f) // 1 ~ 127, adjust this value to decide the sine wave amplitude
#define EXAMPLE_SINE_WAVE_POINT_NUM (1000000 / (EXAMPLE_CALLBACK_INTERVAL_US * EXAMPLE_SINE_WAVE_FREQ_HZ))
#define CONST_PI (3.1416f) // Constant of PI, used for calculating the sine wave
ESP_STATIC_ASSERT(EXAMPLE_SINE_WAVE_POINT_NUM > 1, "Sine wave frequency is too high");
ESP_STATIC_ASSERT(EXAMPLE_CALLBACK_INTERVAL_US >= 7, "Timer callback interval is too short");
static const char *TAG = "sdm_dac";
static int8_t sine_wave[EXAMPLE_SINE_WAVE_POINT_NUM]; // Sine wave data buffer
static bool IRAM_ATTR example_timer_callback(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx)
{
static uint32_t cnt = 0;
sdm_channel_handle_t sdm_chan = (sdm_channel_handle_t)user_ctx;
/* Set the pulse density */
sdm_channel_set_pulse_density(sdm_chan, sine_wave[cnt++]);
/* Loop the sine wave data buffer */
if (cnt >= EXAMPLE_SINE_WAVE_POINT_NUM) {
cnt = 0;
}
return false;
}
static gptimer_handle_t example_init_gptimer(void* args)
{
/* Allocate GPTimer handle */
gptimer_handle_t timer_handle;
gptimer_config_t timer_cfg = {
.clk_src = GPTIMER_CLK_SRC_DEFAULT,
.direction = GPTIMER_COUNT_UP,
.resolution_hz = EXAMPLE_TIMER_RESOLUTION,
};
ESP_ERROR_CHECK(gptimer_new_timer(&timer_cfg, &timer_handle));
ESP_LOGI(TAG, "Timer allocated with resolution %d Hz", EXAMPLE_TIMER_RESOLUTION);
/* Set the timer alarm configuration */
gptimer_alarm_config_t alarm_cfg = {
.alarm_count = EXAMPLE_ALARM_COUNT,
.reload_count = 0,
.flags.auto_reload_on_alarm = true,
};
ESP_ERROR_CHECK(gptimer_set_alarm_action(timer_handle, &alarm_cfg));
/* Register the alarm callback */
gptimer_event_callbacks_t cbs = {
.on_alarm = example_timer_callback,
};
ESP_ERROR_CHECK(gptimer_register_event_callbacks(timer_handle, &cbs, args));
ESP_LOGI(TAG, "Timer callback registered, interval %d us", EXAMPLE_CALLBACK_INTERVAL_US);
/* Clear the timer raw count value, make sure it'll count from 0 */
ESP_ERROR_CHECK(gptimer_set_raw_count(timer_handle, 0));
/* Enable the timer */
ESP_ERROR_CHECK(gptimer_enable(timer_handle));
ESP_LOGI(TAG, "Timer enabled");
return timer_handle;
}
static sdm_channel_handle_t example_init_sdm(void)
{
/* Allocate sdm channel handle */
sdm_channel_handle_t sdm_chan = NULL;
sdm_config_t config = {
.clk_src = SDM_CLK_SRC_DEFAULT,
.gpio_num = EXAMPLE_SIGMA_DELTA_GPIO_NUM,
.sample_rate_hz = EXAMPLE_OVER_SAMPLE_RATE,
};
ESP_ERROR_CHECK(sdm_new_channel(&config, &sdm_chan));
/* Enable the sdm channel */
ESP_ERROR_CHECK(sdm_channel_enable(sdm_chan));
ESP_LOGI(TAG, "Sigma-delta output is attached to GPIO %d", EXAMPLE_SIGMA_DELTA_GPIO_NUM);
return sdm_chan;
}
void app_main(void)
{
/* Assign sine wave data */
for (int i = 0; i < EXAMPLE_SINE_WAVE_POINT_NUM; i++) {
sine_wave[i] = (int8_t)((sin(2 * (float)i * CONST_PI / EXAMPLE_SINE_WAVE_POINT_NUM)) * EXAMPLE_SINE_WAVE_AMPLITUDE);
}
/* Initialize sigma-delta modulation on the specific GPIO */
sdm_channel_handle_t sdm_chan = example_init_sdm();
/* Initialize GPTimer and register the timer alarm callback */
gptimer_handle_t timer_handle = example_init_gptimer(sdm_chan);
/* Start the GPTimer */
ESP_LOGI(TAG, "Output start");
ESP_ERROR_CHECK(gptimer_start(timer_handle));
}

View File

@ -0,0 +1,19 @@
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import pytest
from pytest_embedded import Dut
@pytest.mark.esp32
@pytest.mark.esp32s2
@pytest.mark.esp32s3
@pytest.mark.esp32c3
@pytest.mark.esp32c6
@pytest.mark.generic
def test_sdm_dac_example(dut: Dut) -> None:
dut.expect(r'sdm_dac: Sigma-delta output is attached to GPIO \w+')
dut.expect(r'sdm_dac: Timer allocated with resolution \w+ Hz')
dut.expect(r'sdm_dac: Timer callback registered, interval \w+ us')
dut.expect_exact('sdm_dac: Timer enabled')
dut.expect_exact('sdm_dac: Output start')

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

View File

@ -0,0 +1,6 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(sdm_led_example)

View File

@ -1,7 +1,7 @@
| Supported Targets | ESP32 | ESP32-C3 | ESP32-C6 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- | -------- | -------- |
# Sigma Delta Modulation Example
# Sigma Delta Modulation LED Example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
@ -22,7 +22,7 @@ EXAMPLE_SIGMA_DELTA_GPIO_NUM +----/\/\/\----+------|>|-----+ GND
### Configure the project
You can change the GPIO number by [EXAMPLE_SIGMA_DELTA_GPIO_NUM](main/sdm_example_main.c).
You can change the GPIO number by [EXAMPLE_SIGMA_DELTA_GPIO_NUM](main/sdm_led_example_main.c).
### Build and Flash

View File

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

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
@ -16,7 +16,7 @@
#define EXAMPLE_LED_DIM_DUTY_MAX 90
#define EXAMPLE_LED_DIM_DUTY_MIN (EXAMPLE_LED_DIM_DUTY_MAX - EXAMPLE_LED_DIM_PERIOD_MS / EXAMPLE_LED_DIM_DELAY_MS * EXAMPLE_LED_DIM_DUTY_STEP)
static const char *TAG = "example";
static const char *TAG = "sdm_led";
void app_main(void)
{
@ -36,7 +36,7 @@ void app_main(void)
int8_t duty = 0;
int step = EXAMPLE_LED_DIM_DUTY_STEP;
while (1) {
ESP_ERROR_CHECK(sdm_channel_set_duty(sdm_chan, duty));
ESP_ERROR_CHECK(sdm_channel_set_pulse_density(sdm_chan, duty));
/* By changing delay time, you can change the blink frequency of LED */
vTaskDelay(pdMS_TO_TICKS(EXAMPLE_LED_DIM_DELAY_MS));

View File

@ -11,7 +11,7 @@ from pytest_embedded import Dut
@pytest.mark.esp32c3
@pytest.mark.esp32c6
@pytest.mark.generic
def test_sdm_example(dut: Dut) -> None:
dut.expect_exact('example: Install sigma delta channel')
dut.expect_exact('example: Enable sigma delta channel')
dut.expect_exact('example: Change duty cycle continuously')
def test_sdm_led_example(dut: Dut) -> None:
dut.expect_exact('sdm_led: Install sigma delta channel')
dut.expect_exact('sdm_led: Enable sigma delta channel')
dut.expect_exact('sdm_led: Change duty cycle continuously')