Chapter 160: SDM (Sigma-Delta Modulation) Overview

Chapter Objectives

By the end of this chapter, you will be able to:

  • Understand the fundamental principles of Sigma-Delta Modulation (SDM).
  • Explain concepts like oversampling, noise shaping, and 1-bit DAC operation.
  • Identify the ESP32 variants that feature the SDM peripheral.
  • Configure and utilize the SDM peripheral using ESP-IDF v5.x to generate analog-like output signals.
  • Implement practical applications such as controlling LED brightness or generating simple DC levels.
  • Recognize common issues and troubleshoot SDM configurations.

Introduction

In the realm of digital microcontrollers, generating true analog signals often requires dedicated Digital-to-Analog Converters (DACs). However, many applications need simple, relatively low-frequency analog-like outputs without the complexity or cost of a full DAC. Sigma-Delta Modulation (SDM) provides an ingenious method to achieve this using primarily digital means. By rapidly switching a digital output pin, and then applying a simple external low-pass filter, we can create a time-averaged analog voltage.

The ESP32 series includes an SDM peripheral (on specific variants) that allows for the generation of such pulse streams. This chapter delves into the theory behind SDM, how it’s implemented in ESP32, and how you can leverage it for tasks like precise LED dimming, generating control voltages, or even basic audio tone generation. Understanding SDM opens up possibilities for creating cost-effective analog interfaces with minimal external components.

Theory

Sigma-Delta Modulation is a technique for encoding analog signals into digital signals or, as in the case of the ESP32’s SDM peripheral, for representing a desired analog output level using a high-frequency stream of 1-bit digital pulses. The core idea is to represent the amplitude of the desired analog signal by the density of pulses in the digital output stream.

Key Concepts:

Sigma (Σ) and Delta (Δ):

  • Delta (Δ): Refers to the difference between the input signal (or target value) and the quantized output.

  • Sigma (Σ): Refers to the integration (summation) of this difference.The modulator continuously tries to minimize this integrated error by adjusting its 1-bit output.

graph TD
    subgraph Sigma-Delta Modulator
        direction LR
        
        Input["Target Value<br>(e.g., Desired Brightness)"]:::primary --> Sum;
        
        Sum(Σ) --> Integrator[∫<br>Integrator];
        Integrator --> Quantizer{"1-Bit<br>Quantizer<br>(Comparator)"}:::decision;
        Quantizer --> Output[1-Bit Digital Stream<br>Pulse Density Modulated];
        
        Quantizer --"Feedback Loop<br>(1-Bit DAC)"--> Sum;
    end

    Output --> ExternalFilter["External Low-Pass Filter<br>(e.g., RC Circuit)"]:::process;
    ExternalFilter --> AnalogOut[Smoothed Analog<br>Output Voltage]:::success;

    classDef primary fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6;
    classDef success fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46;
    classDef decision fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E;
    classDef process fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF;

Oversampling:

SDM operates by sampling the input signal (or the target digital value) at a much higher frequency than the Nyquist rate for the desired output bandwidth. This high sampling rate spreads the quantization noise over a wider frequency spectrum. For the ESP32’s SDM output, we are setting a target digital value that the modulator tries to represent. The “oversampling” here relates to the high-frequency clock used to generate the pulse stream compared to the effective bandwidth of the analog signal we want to produce after filtering.

Quantization (1-bit):

The core of the SDM is a simple 1-bit quantizer (a comparator). At each high-frequency clock cycle, it decides whether the output should be high (1) or low (0). This decision is based on the integrated error signal.

Noise Shaping:

This is the clever part of SDM. The feedback loop within the modulator is designed such that the quantization noise is “pushed” towards higher frequencies, away from the desired signal band. If you look at the noise spectrum, you’ll see low noise in the lower frequencies (where your signal of interest lies) and significantly increased noise at higher frequencies.

xychart-beta
    title "Quantization Noise Spectrum"
    x-axis "Frequency"
    y-axis "Noise Power"
    
    line [0, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8]
    bar  [0.1, 0.15, 0.2, 0.25, 0.3, 0.5, 0.8, 1.2, 1.8, 2.5, 3.3, 4.0]
    
    

Digital Output Stream (Pulse Density Modulation):

The output of the SDM is a stream of ‘1’s and ‘0’s. If you want to represent a higher analog voltage, the density of ‘1’s in the stream will be higher. For a lower voltage, the density of ‘0’s (or fewer ‘1’s) will be higher. For example, to represent 50% of the full scale, the output might be an alternating sequence like 101010…. To represent 75%, it might be 11101110….

External Low-Pass Filter:

To convert this high-frequency digital pulse stream back into a smooth analog voltage, an external low-pass filter (typically a simple RC filter) is required. This filter averages out the pulses, and because the noise has been pushed to high frequencies, the filter can effectively remove it, leaving the desired analog signal.

How ESP32 SDM Works (for Output Generation):

The ESP32’s SDM peripheral (where available) is designed to take a digital duty cycle value (typically 8-bit or 16-bit, representing the desired output level) and generate a corresponding high-frequency pulse-density modulated signal on a GPIO pin.

  • You configure a target “density” or “duty cycle” value.
  • The SDM hardware, driven by a clock (often APB_CLK or a prescaled version), generates the 1-bit pulse stream.
  • The higher the duty cycle value you set, the greater the proportion of ‘1’s in the output stream, leading to a higher average voltage after external filtering.
  • The effective resolution depends on the clock frequency of the SDM and the characteristics of the low-pass filter.

The SDM peripheral can be thought of as a sophisticated, high-frequency 1-bit Pulse Width Modulator (PWM) where the pulse density rather than fixed pulse width in a fixed period defines the output. This can lead to better linearity and noise performance in some cases, especially when properly filtered.

Practical Examples

Let’s explore how to use the SDM peripheral on an ESP32 using ESP-IDF v5.x. We’ll create a simple example to generate a specific DC voltage level, which can be observed by measuring the voltage after an RC filter or by controlling the brightness of an LED.

flowchart TD
    A[Start: Goal is to Generate Analog-like Voltage]:::primary
    B["Define Configuration <i>sdm_config_t</i>"]:::process
    C["Set GPIO Pin <i>.gpio_num</i>"]:::process
    D["Set Clock Source <i>.clk_src</i>"]:::process
    E["Set Sample Rate <i>.sample_rate_hz</i>"]:::process
    F{"Allocate SDM Channel<br><i>sdm_new_channel()</i>"}:::decision
    G["Enable Channel<br><i>sdm_channel_enable()</i>"]:::process
    H["Loop or Task<br>to Control Output"]:::process
    I["Set Pulse Density<br><i>sdm_channel_set_pulse_density()</i>"]:::process
    J["Hardware Generates<br>Pulse Stream on GPIO"]:::success
    K["Error Handling:<br>Log Error, Cleanup"]:::check
    L["Cleanup:<br><i>sdm_channel_disable()</i><br><i>sdm_del_channel()</i>"]:::check

    A --> B
    B --> C & D & E
    C --> F
    D --> F
    E --> F
    F --"Success"--> G
    F --"Fail"--> K
    G --> H
    H --> I
    I --> J
    I --"On change"--> H
    J --> I
    
    classDef primary fill:#EDE9FE,stroke:#5B21B6,stroke-width:2px,color:#5B21B6;
    classDef success fill:#D1FAE5,stroke:#059669,stroke-width:2px,color:#065F46;
    classDef decision fill:#FEF3C7,stroke:#D97706,stroke-width:1px,color:#92400E;
    classDef process fill:#DBEAFE,stroke:#2563EB,stroke-width:1px,color:#1E40AF;
    classDef check fill:#FEE2E2,stroke:#DC2626,stroke-width:1px,color:#991B1B;

Prerequisites:

  • ESP-IDF v5.x installed and configured with VS Code.
  • An ESP32 board that has the SDM peripheral (e.g., ESP32, ESP32-S2, ESP32-S3).
  • An LED and a current-limiting resistor (e.g., 220-330 Ohm).
  • A simple RC low-pass filter (e.g., 1k Ohm resistor and 1uF capacitor) if you want to measure a smoother analog voltage with a multimeter or oscilloscope.

Project Setup:

  1. Create a new ESP-IDF project in VS Code or navigate to an existing one.
  2. The SDM driver is part of the esp_driver_sdm component, which should be automatically available.

Example: Generating a Variable DC Output (LED Brightness Control)

This example will configure an SDM channel to output a signal whose duty cycle (and thus average voltage) can be varied. We’ll connect this to an LED to visually see the effect.

File: main/sdm_example_main.c

C
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/sdm.h"
#include "esp_log.h"

static const char *TAG = "SDM_EXAMPLE";

#define SDM_OUTPUT_GPIO  CONFIG_EXAMPLE_SDM_GPIO_NUM // Define in Kconfig or here
#define SDM_CHANNEL_HANDLE NULL // Will be assigned by sdm_new_channel

// Default GPIO if not set by Kconfig
#ifndef SDM_OUTPUT_GPIO
#define SDM_OUTPUT_GPIO  GPIO_NUM_4 // Example GPIO, change as needed
#endif

void app_main(void)
{
    ESP_LOGI(TAG, "Initializing SDM channel...");

    sdm_channel_handle_t sdm_chan = NULL; // Declare channel handle

    sdm_config_t config = {
        .gpio_num = SDM_OUTPUT_GPIO,
        .clk_src = SDM_CLK_SRC_DEFAULT, // Use default clock source (APB CLK)
        // .sample_rate_hz can be set for specific applications, default is usually fine for basic DC output
        // For ESP32, default sample_rate_hz is 8MHz if clk_src is APB_CLK (80MHz) and prescale is 10.
        // For ESP32-S2/S3, it depends on the specific clock configuration.
        // The driver calculates the internal prescaler based on this.
        // Let's use a common sample rate.
        // The prescaler will be clk_src_freq / sample_rate_hz.
        // e.g. 80MHz / 1MHz = 80.
        // The actual output frequency of the SDM bitstream is sample_rate_hz.
        .sample_rate_hz = 1 * 1000 * 1000, // 1 MHz sample rate for the sigma-delta modulator itself
    };

    // Allocate a new SDM channel
    esp_err_t ret = sdm_new_channel(&config, &sdm_chan);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to allocate SDM channel: %s", esp_err_to_name(ret));
        return;
    }

    ESP_LOGI(TAG, "SDM channel allocated successfully on GPIO %d", SDM_OUTPUT_GPIO);

    // Enable the channel
    ret = sdm_channel_enable(sdm_chan);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "Failed to enable SDM channel: %s", esp_err_to_name(ret));
        sdm_del_channel(sdm_chan); // Clean up
        return;
    }
    ESP_LOGI(TAG, "SDM channel enabled.");

    // The density value ranges from -128 to 127 for an 8-bit equivalent output.
    // 0 means roughly 50% duty cycle (if the modulator is perfectly balanced).
    // 127 means maximum density of '1's (approaching 100% VDD after filtering).
    // -128 means maximum density of '0's (approaching 0% GND after filtering).
    // The actual output voltage V_out = VDD * (density + 128) / 256
    // Or more generally, V_out = V_low + (V_high - V_low) * (density - DENSITY_MIN) / (DENSITY_MAX - DENSITY_MIN)

    int8_t density = 0; // Start with 50% duty cycle equivalent
    bool increasing = true;

    while (1) {
        // Set the duty cycle (density) for the SDM channel
        // The `density` parameter is an int, but for the default 8-bit like behavior,
        // it's interpreted as an int8_t internally by the driver for setting duty.
        // So, values from -128 to 127.
        ret = sdm_channel_set_pulse_density(sdm_chan, density);
        if (ret != ESP_OK) {
            ESP_LOGE(TAG, "Failed to set SDM density: %s", esp_err_to_name(ret));
        } else {
            // Calculate approximate output voltage percentage (assuming VDD = 3.3V, GND = 0V)
            // Effective duty cycle = (density + 128) / 256
            // For density = 0, effective duty = 128/256 = 0.5 (50%)
            // For density = 127, effective duty = 255/256 = ~1.0 (100%)
            // For density = -128, effective duty = 0/256 = 0.0 (0%)
            float effective_duty = (density + 128.0f) / 256.0f;
            ESP_LOGI(TAG, "Set SDM density to: %d (Effective Duty: %.2f%%)", density, effective_duty * 100.0f);
        }

        if (increasing) {
            density += 8; // Increase brightness
            if (density > 120) { // Stay a bit below max to see clear changes
                density = 127;
                increasing = false;
                vTaskDelay(pdMS_TO_TICKS(1000)); // Hold at max for a bit
            }
        } else {
            density -= 8; // Decrease brightness
            if (density < -120) { // Stay a bit above min
                density = -128;
                increasing = true;
                vTaskDelay(pdMS_TO_TICKS(1000)); // Hold at min for a bit
            }
        }
        vTaskDelay(pdMS_TO_TICKS(100)); // Update rate
    }

    // In a real application, you would disable and delete the channel when done.
    // sdm_channel_disable(sdm_chan);
    // sdm_del_channel(sdm_chan);
}

Hardware Connection:

  • Connect an LED’s anode (longer leg) to the GPIO pin specified by SDM_OUTPUT_GPIO (e.g., GPIO4).
  • Connect the LED’s cathode (shorter leg) to one end of a current-limiting resistor (e.g., 330 Ohms).
  • Connect the other end of the resistor to GND.

If you want to observe with a multimeter/oscilloscope:

  • Connect SDM_OUTPUT_GPIO to an RC filter input (e.g., R=1kOhm, C=1uF).
  • Connect the other end of the resistor to the capacitor and your measurement point.
  • Connect the other end of the capacitor to GND.
  • Measure the voltage at the junction of R and C.

Kconfig (Optional, for GPIO configuration):

You can add a Kconfig option to make the GPIO configurable via menuconfig.

Create main/Kconfig.projbuild:

Plaintext
menu "SDM Example Configuration"
    config EXAMPLE_SDM_GPIO_NUM
        int "SDM Output GPIO Number"
        default 4
        help
            GPIO number to use for SDM output.
endmenu

And add build_requires_kconfig to your CMakeLists.txt in the main directory:

Plaintext
# main/CMakeLists.txt
idf_component_register(...)
# ... other configurations ...

# Add Kconfig dependency
set(KCONFIG_projbuild ${CMAKE_CURRENT_SOURCE_DIR}/Kconfig.projbuild)
# This line ensures Kconfig is processed
build_requires_kconfig()

Build and Flash Instructions:

  1. Configure (Optional):
    • Open the ESP-IDF terminal in VS Code.
    • Run idf.py menuconfig.
    • Navigate to “Example Configuration” (if you added Kconfig) and set the GPIO pin if needed. Save and exit.
  2. Build:
    • Run idf.py build.
  3. Flash:
    • Connect your ESP32 board.
    • Run idf.py -p (PORT) flash (replace (PORT) with your device’s serial port, e.g., /dev/ttyUSB0 or COM3).
  4. Monitor:
    • Run idf.py -p (PORT) monitor.

Observe Results:

  • LED: You should see the LED connected to SDM_OUTPUT_GPIO gradually brighten and dim.
  • Multimeter/Oscilloscope (with RC filter): You should observe the DC voltage at the output of the RC filter changing smoothly, corresponding to the density values being set. For example, with a 3.3V VDD:
    • Density -128: ~0V
    • Density 0: ~1.65V
    • Density 127: ~3.3V

Tip: The quality of the analog output (smoothness, ripple) heavily depends on the SDM clock frequency (sample_rate_hz in config) and the characteristics of your external low-pass filter. A higher SDM clock frequency allows for a higher cutoff frequency for the low-pass filter, leading to a faster responding analog output. A lower cutoff frequency (larger R or C) will result in a smoother DC output but slower response to changes in density.

Variant Notes

The Sigma-Delta Modulator (SDM) peripheral, specifically the one accessible via the esp_driver_sdm API, is not universally available across all ESP32 variants.

  • ESP32 (Original):
    • Supported. The ESP32 has 8 SDM channels. The esp_driver_sdm is the recommended driver in ESP-IDF v5.x. Older IDF versions might have used a legacy sigmadelta_ driver, but the new component-based driver should be preferred.
    • The clock source is typically APB_CLK (80 MHz).
  • ESP32-S2:
    • Supported. The ESP32-S2 has 4 SDM channels. It uses the esp_driver_sdm API.
    • Clock source can be APB_CLK or XTAL_CLK.
  • ESP32-S3:
    • Supported. The ESP32-S3 has 4 SDM channels. It uses the esp_driver_sdm API.
    • Clock source can be APB_CLK, XTAL_CLK, or RTC_FAST_CLK.
  • ESP32-C3:
    • Not Supported. The ESP32-C3 does not have a dedicated SDM peripheral like the one described above.
    • Alternative: For analog-like output, you would typically use the LEDC peripheral (PWM) with a high frequency and an external RC filter. The LEDC can achieve similar results for many applications like LED dimming or generating DC levels.
  • ESP32-C6:
    • Not Supported. The ESP32-C6 does not have a dedicated SDM peripheral.
    • Alternative: Similar to ESP32-C3, use LEDC (PWM) with an external RC filter.
  • ESP32-H2:
    • Not Supported. The ESP32-H2 does not have a dedicated SDM peripheral.
    • Alternative: Use LEDC (PWM) with an external RC filter.

Key Differences Summary:

Feature ESP32 ESP32-S2 ESP32-S3 ESP32-C3 / C6 / H2
SDM Peripheral Yes Yes Yes No
Number of Channels 8 4 4 N/A
Recommended API esp_driver_sdm esp_driver_sdm esp_driver_sdm N/A (Use LEDC driver)
Typical Clock Sources APB_CLK APB_CLK, XTAL_CLK APB_CLK, XTAL_CLK, RTC_FAST_CLK N/A
Alternative Method LEDC (PWM) + External RC Filter

When working with ESP32-C3, C6, or H2, if you need to generate an analog-like voltage, you should refer to Chapter 145: LED PWM Controller (LEDC) and apply an external RC low-pass filter to its output.

Common Mistakes & Troubleshooting Tips

Mistake / Issue Symptom(s) Troubleshooting / Solution
No Output on GPIO Pin LED doesn’t light up at all. Multimeter reads 0V or a fixed low voltage. Scope shows no signal. 1. Check Channel Enable: Ensure sdm_channel_enable() is called after sdm_new_channel().
2. Verify GPIO Number: Double-check that SDM_OUTPUT_GPIO is set to a valid, free GPIO. Log the GPIO number to confirm.
3. Check for Errors: Add ESP_LOGE checks for the return values of all sdm_... functions.
Expecting Smooth DC Voltage Directly from GPIO Measuring the GPIO pin with a multimeter gives a seemingly random or unstable voltage. An oscilloscope shows a high-frequency square wave, not a DC level. Add a Low-Pass Filter: The SDM output is a digital pulse stream. A simple external RC (Resistor-Capacitor) low-pass filter is required to average these pulses into a smooth analog voltage.
Incorrect density Interpretation LED is always at half brightness, or brightness control seems inverted or doesn’t cover the full range. Understand the Range [-128, 127]:
density = 0 is ~50% duty cycle (VDD/2).
density = 127 is max ‘1’s (~100% VDD).
density = -128 is max ‘0’s (~0% GND).
Use the formula (density + 128) / 256 to map to a 0-1 duty cycle.
Using Strapping or Special GPIOs The ESP32 fails to boot, enters the wrong boot mode, or behaves erratically when the SDM pin is connected. Check Datasheet: Consult the datasheet for your specific ESP32 board/module. Avoid using strapping pins (e.g., GPIO0, 2, 4, 5, 12, 15 on some boards) unless you know what you’re doing. Choose a general-purpose GPIO.
Output Voltage is “Noisy” or has High Ripple On an oscilloscope, the filtered DC voltage is not smooth and has significant high-frequency ripple. 1. Adjust Filter Components: Increase the time constant of your RC filter (use a larger resistor or capacitor) for better smoothing. This will slow the response time.
2. Increase SDM Sample Rate: In sdm_config_t, set a higher sample_rate_hz. This pushes noise to even higher frequencies, making it easier for your filter to remove.

Warning: Always check the Technical Reference Manual (TRM) for your specific ESP32 variant for detailed information on the SDM peripheral’s capabilities, clocking options, and register descriptions.

Exercises

  1. Fixed DC Voltage Output:
    • Modify the example code to output a fixed DC voltage that is approximately 25% of VDD.
    • Calculate the required density value.
    • Use a multimeter (with an RC filter) to verify the output voltage. (Assume VDD = 3.3V).
  2. Interactive LED Brightness Control via UART:
    • Extend the example to read a number (0-100) from the UART console.
    • Map this input number to the SDM density range (-128 to 127) to control LED brightness.
    • 0 should correspond to LED off (density -128), and 100 to LED full brightness (density 127).
  3. Generating a Very Slow “Breathing” LED Effect:
    • Use the SDM peripheral to create a very slow sinusoidal “breathing” effect for an LED.
    • Calculate density values that approximate a sine wave over a period of several seconds (e.g., 5 seconds per cycle).
    • Hint: You can precompute sine values or calculate them on the fly. The density will change slowly. density = (int8_t)(sin(angle) * 127.0f);

Summary

  • Sigma-Delta Modulation (SDM) is a technique to represent analog values using a high-frequency 1-bit digital pulse stream. The density of pulses corresponds to the analog level.
  • Key SDM principles include oversampling, 1-bit quantization, and noise shaping, which pushes quantization noise to higher frequencies.
  • An external low-pass filter (e.g., RC filter) is essential to convert the SDM’s digital pulse stream into a smoothed analog voltage.
  • The ESP32, ESP32-S2, and ESP32-S3 variants include a dedicated SDM peripheral, configurable via the esp_driver_sdm API in ESP-IDF v5.x. ESP32-C3/C6/H2 do not have this specific peripheral; LEDC (PWM) is the alternative.
  • Configuration involves setting the GPIO, clock source, and sample rate using sdm_config_t and sdm_new_channel().
  • The output level is controlled by setting the density (typically -128 to 127) using sdm_channel_set_pulse_density().
  • Common applications include LED dimming, generating DC reference voltages, and simple tone generation.
  • Proper GPIO selection, external filtering, and understanding the density parameter are crucial for successful SDM implementation.

Further Reading

  • ESP-IDF SDM Driver Documentation:
  • ESP32 Series Technical Reference Manuals (TRM):
  • Application Notes and Examples:
    • Explore example projects provided by Espressif in the esp-idf/examples/peripherals/sdm directory.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top