Sigma-Delta Modulation (SDM)

Introduction

ESP32-S2 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 - 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 - covers how to enable and disable the channel.

  • Set Equivalent Duty Cycle - describes how to set the equivalent duty cycle of the PDM pulses.

  • Power Management - describes how different source clock selections can affect power consumption.

  • IRAM Safe - lists which functions are supposed to work even when the cache is disabled.

  • Thread Safety - lists which APIs are guaranteed to be thread safe by the driver.

  • 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 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 sdm_new_channel() to get a channel handle. Channel specific configurations are passed in the sdm_config_t structure:

The function 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 ESP_ERR_NOT_FOUND will be returned.

If a previously created SDM channel is no longer required, you should recycle it by calling 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 sdm_channel_disable() in advance or make sure it has not enabled yet by sdm_channel_enable().

Creating a SDM Channel with Sample Rate of 1MHz

 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 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 for more information.

On the contrary, calling 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 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. 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 ESP_PM_APB_FREQ_MAX. Whenever the driver creates a SDM channel instance that has selected SDM_CLK_SRC_APB as its clock source, the driver will guarantee that the power management lock is acquired when enable the channel by sdm_channel_enable(). Likewise, the driver releases the lock when sdm_channel_disable() is called for that channel.

IRAM Safe

There’s a Kconfig option 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:

Thread Safety

The factory function 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.

Other functions that take the 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

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.

Sallen-Key Low Pass Filter

Sallen-Key Low Pass Filter

Application Example

API Reference

Header File

Functions

esp_err_t sdm_new_channel(const sdm_config_t *config, sdm_channel_handle_t *ret_chan)

Create a new Sigma Delta channel.

Parameters
  • config[in] SDM configuration

  • ret_chan[out] Returned SDM channel handle

Returns

  • ESP_OK: Create SDM channel successfully

  • ESP_ERR_INVALID_ARG: Create SDM channel failed because of invalid argument

  • ESP_ERR_NO_MEM: Create SDM channel failed because out of memory

  • ESP_ERR_NOT_FOUND: Create SDM channel failed because all channels are used up and no more free one

  • ESP_FAIL: Create SDM channel failed because of other error

esp_err_t sdm_del_channel(sdm_channel_handle_t chan)

Delete the Sigma Delta channel.

Parameters

chan[in] SDM channel created by sdm_new_channel

Returns

  • ESP_OK: Delete the SDM channel successfully

  • ESP_ERR_INVALID_ARG: Delete the SDM channel failed because of invalid argument

  • ESP_ERR_INVALID_STATE: Delete the SDM channel failed because the channel is not in init state

  • ESP_FAIL: Delete the SDM channel failed because of other error

esp_err_t sdm_channel_enable(sdm_channel_handle_t chan)

Enable the Sigma Delta channel.

Note

This function will transit the channel state from init to enable.

Note

This function will acquire a PM lock, if a specific source clock (e.g. APB) is selected in the sdm_config_t, while CONFIG_PM_ENABLE is enabled.

Parameters

chan[in] SDM channel created by sdm_new_channel

Returns

  • ESP_OK: Enable SDM channel successfully

  • ESP_ERR_INVALID_ARG: Enable SDM channel failed because of invalid argument

  • ESP_ERR_INVALID_STATE: Enable SDM channel failed because the channel is already enabled

  • ESP_FAIL: Enable SDM channel failed because of other error

esp_err_t sdm_channel_disable(sdm_channel_handle_t chan)

Disable the Sigma Delta channel.

Note

This function will do the opposite work to the sdm_channel_enable()

Parameters

chan[in] SDM channel created by sdm_new_channel

Returns

  • ESP_OK: Disable SDM channel successfully

  • ESP_ERR_INVALID_ARG: Disable SDM channel failed because of invalid argument

  • ESP_ERR_INVALID_STATE: Disable SDM channel failed because the channel is not enabled yet

  • ESP_FAIL: Disable SDM channel failed because of other error

esp_err_t sdm_channel_set_duty(sdm_channel_handle_t chan, int8_t duty)

Set the duty cycle 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

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

Parameters
  • chan[in] SDM channel created by sdm_new_channel

  • duty[in] Equivalent duty cycle of the PDM output signal, ranges from -128 to 127. But the range of [-90, 90] can provide a better randomness.

Returns

  • ESP_OK: Set duty cycle successfully

  • ESP_ERR_INVALID_ARG: Set duty cycle failed because of invalid argument

  • ESP_FAIL: Set duty cycle failed because of other error

Structures

struct sdm_config_t

Sigma Delta channel configuration.

Public Members

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 invert_out

Whether to invert the output signal

uint32_t io_loop_back

For debug/test, the signal output from the GPIO will be fed to the input path as well

struct sdm_config_t::[anonymous] flags

Extra flags

Type Definitions

typedef struct sdm_channel_t *sdm_channel_handle_t

Type of Sigma Delta channel handle.

Header File

Type Definitions

typedef soc_periph_sdm_clk_src_t sdm_clock_source_t
1

Different ESP chip series might have different numbers of SDM channels. Please refer to Chapter GPIO and IOMUX in ESP32-S2 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. sdm_new_channel()).