# Sigma-Delta 调制器 (SDM)

[English]

## 简介

ESP32 具备二阶 Sigma-Delta 调制器，可以为多个通道生成独立的脉冲密度调制 (PDM) 脉冲。请参阅技术参考手册，查看可用的硬件通道数量。[1]

Sigma-Delta 调制器可以将模拟电压信号转换为脉冲频率或脉冲密度，该过程称为脉冲密度调制 (PDM)（请参阅 维基百科上有关 Sigma-Delta 调制的介绍）。

与 I2S 外设中的 PDM 模式和和数模转换器 (DAC) 相比，SDM 中的 PDM 主要有以下特点：

1. SDM 没有时钟信号，类似于 PDM 的 DAC 模式；

2. SDM 没有 DMA 支持，无法持续改变其输出密度。如果需要改变 SDM 的输出密度，可以在定时器的回调函数中进行操作；

3. 基于以上两点，不同于 DAC，要还原模拟波形，还必须使用外部的有源或无源低通滤波器，详情请参阅 转换为模拟信号（可选）。

Sigma-Delta 调制通道通常应用于以下场景：

- LED 调光

- 使用有源 RC 低通滤波器，实现简单的数模转换（8 位分辨率）

- 结合半桥或全桥回路，以及 LC 低通滤波器，实现 D 级功率放大

## 功能概述

下文将分节概述安装和操作 SDM 通道的一般步骤：

- 资源分配 - 介绍如何初始化和配置 SDM 通道，以及在通道完成任务后如何回收相关资源。

- 启用和禁用通道 - 介绍如何启用和禁用 SDM 通道。

- 设置脉冲密度 - 介绍如何设置 PDM 脉冲的等效占空比。

- 电源管理 - 介绍不同时钟源对功耗的影响。

- IRAM 安全 - 介绍禁用 cache 时仍可使用的功能。

- 线程安全 - 介绍由驱动程序认证为线程安全的 API。

- Kconfig 选项 - 介绍 SDM 驱动程序支持的各种 Kconfig 选项，这些选项可以给驱动程序的行为造成不同影响。

### 资源分配

在 ESP-IDF 中，SDM 通道的信息和属性通过特定的数据结构进行管理和访问，该数据结构表示为 `sdm_channel_handle_t`。每个通道都可以输出由硬件生成的二进制信号，且这些信号都经过 Sigma-Delta 调制。所有可用通道均存放在资源池中，由驱动程序管理，无需手动将固定通道分配给 GPIO。

要安装 SDM 通道，请调用 `sdm_new_channel()` 获取通道句柄。通道的具体配置信息由结构体 `sdm_config_t` 传递。

- `sdm_config_t::gpio_num` 设置 PDM 脉冲输出的 GPIO 管脚号。

- `sdm_config_t::clk_src` 选择 SDM 模块的时钟源。注意，所有通道选择的时钟源应保持一致。

- `sdm_config_t::sample_rate_hz` 设置 SDM 模块的采样率。

- `sdm_config_t::invert_out` 设置是否反转输出信号。

- `sdm_config_t::io_loop_back` 通过 GPIO 矩阵外设，启用 GPIO 的输入和输出功能。注意，该字段仅供调试使用。

函数 `sdm_new_channel()` 可能因为各种原因失败，如内存不足、参数无效等。当缺少空闲通道（即所有的硬件 SDM 通道均在使用中）时，将返回 `ESP_ERR_NOT_FOUND`。

SDM 通道完成任务后，请调用 `sdm_del_channel()` 回收相应资源，以便底层硬件通道用于其他目的。在删除 SDM 通道句柄前，请通过 `sdm_channel_disable()` 禁用要删除的通道，或确保该通道尚未由 `sdm_channel_enable()` 启用，再继续删除操作。

#### 创建采样率为 1 MHz 的 SDM 通道

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

### 启用和禁用通道

在对 SDM 通道进行进一步的 IO 控制之前，需要先调用 `sdm_channel_enable()` 启用通道。在内部，该函数实现了以下操作：

- 将通道状态从 init 切换到 enable

- 如果选择了特定时钟源（如 APB 锁），则会获取合适的电源管理锁。要了解更多有关信息，请参阅 电源管理。

调用 `sdm_channel_disable()` 则执行相反操作，即将通道恢复到 init 状态，并释放电源管理锁。

### 设置脉冲密度

在 PDM 中，脉冲密度决定了低通滤波器转换后的输出模拟电压，该模拟电压可以通过公式 `Vout = VDD_IO / 256 * duty + VDD_IO / 2` 计算。使用函数 `sdm_channel_set_pulse_density()` 时，需要传入一个名为 `density` 的参数。这个参数是一个整数值，范围在 -128 到 127 之间，表示一个 8 位有符号整数。根据 `density` 参数的不同取值，输出信号的占空比也会相应改变。例如，如果将 `density` 参数设置为零，输出信号的占空比约为 50%。

### 电源管理

启用电源管理（即启用 CONFIG_PM_ENABLE）时，在进入 Light-sleep 模式前，系统会调整 APB 频率，这可能会改变 Sigma-Delta 调制器的采样率。

但是，通过获取类型为 `ESP_PM_APB_FREQ_MAX` 的电源管理锁，驱动程序可以防止系统改变 APB 频率。每当驱动程序创建 SDM 通道，且该通道选择 `SDM_CLK_SRC_APB` 作为其时钟源时，在通过 `sdm_channel_enable()` 启用通道的过程中，驱动程序会确保获取类型为 `ESP_PM_APB_FREQ_MAX` 的电源管理锁。反之，调用 `sdm_channel_disable()` 禁用通道时，驱动程序释放该锁。

### IRAM 安全

Kconfig 选项 CONFIG_SDM_CTRL_FUNC_IN_IRAM 支持将常用的 IO 控制函数存放在 IRAM 中，以保证在禁用 cache 时可以正常使用函数。IO 控制函数如下所示：

- `sdm_channel_set_pulse_density()`

### 线程安全

驱动程序会确保工厂函数 `sdm_new_channel()` 的线程安全，使用时，可以直接从不同的 RTOS 任务中调用此类函数，无需额外锁保护。

驱动程序设置了临界区，以防函数同时在任务和 ISR 中调用。因此，以下函数支持在 ISR 上下文运行：

- `sdm_channel_set_pulse_density()`

其他以 `sdm_channel_handle_t` 作为第一个位置参数的函数均非线程安全，因此应避免从多个任务中调用这类函数。

### Kconfig 选项

- CONFIG_SDM_CTRL_FUNC_IN_IRAM 控制 SDM 通道控制函数的存放位置（IRAM 或 flash）。更多信息请参阅 IRAM 安全。

- CONFIG_SDM_ENABLE_DEBUG_LOG 用于启用调试日志输出。启用此选项将增加固件的二进制文件大小。

## 转换为模拟信号（可选）

一般而言，Sigma-Delta 信号连接到 LED 用来调节明暗时，无需在信号和 LED 之间添加滤波器，因为人眼本身对光强变化有低通滤波作用。但是，如果你想测量实际电压，或观察模拟波形，就需要设计一个模拟低通滤波器。此外，建议使用有源滤波器，相较于无源滤波器，有源滤波器在处理信号时具有更强的抗干扰性，且损失的电压较少。

请参阅如下示例 Sallen-Key 拓扑低通滤波器，了解滤波器的相关知识。

Sallen-Key 拓扑低通滤波器

## 应用示例

- 使用 Sigma-Delta 调制的 100 Hz 正弦波：peripherals/sigma_delta/sdm_dac。

- 使用 Sigma-Delta 调制、并由 GPIO 驱动的 LED：peripherals/sigma_delta/sdm_led。

## API 参考

### Header File

- components/esp_driver_sdm/include/driver/sdm.h

- This header file can be included with:

  >     #include "driver/sdm.h"

- This header file is a part of the API provided by the `esp_driver_sdm` component. To declare that your component depends on `esp_driver_sdm`, add the following to your CMakeLists.txt:

  >     REQUIRES esp_driver_sdm

  > or

  >     PRIV_REQUIRES esp_driver_sdm

### Functions

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

  Create a new Sigma Delta channel.

  参数:

    - config -- [in] SDM configuration

    - ret_chan -- [out] Returned SDM channel handle

  返回:

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

  参数:

    chan -- [in] SDM channel created by `sdm_new_channel`

  返回:

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

  备注

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

  备注

  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.

  参数:

    chan -- [in] SDM channel created by `sdm_new_channel`

  返回:

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

  备注

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

  参数:

    chan -- [in] SDM channel created by `sdm_new_channel`

  返回:

    - 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_pulse_density(sdm_channel_handle_t chan, int8_t density)

  Set the pulse density of the PDM output signal.

  备注

  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

  备注

  This function is allowed to run within ISR context

  备注

  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

  参数:

    - chan -- [in] SDM channel created by `sdm_new_channel`

    - density -- [in] Quantized pulse density of the PDM output signal, ranges from -128 to 127. But the range of [-90, 90] can provide a better randomness.

  返回:

    - 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_duty(sdm_channel_handle_t chan, int8_t duty)

  The alias function of `sdm_channel_set_pulse_density`, it decides the pulse density of the output signal.

  备注

  `sdm_channel_set_pulse_density` has a more appropriate name compare this alias function, suggest to turn to `sdm_channel_set_pulse_density` instead

  参数:

    - chan -- [in] SDM channel created by `sdm_new_channel`

    - duty -- [in] Actually it's the quantized pulse density of the PDM output signal

  返回:

    - 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

    Over sample rate in Hz, it determines the frequency of the carrier pulses

  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

- components/hal/include/hal/sdm_types.h

- This header file can be included with:

  >     #include "hal/sdm_types.h"

### Type Definitions

typedef soc_periph_sdm_clk_src_t sdm_clock_source_t

[1]

不同的 ESP 芯片系列可能具有不同数量的 SDM 通道，请参阅 ESP32 技术参考手册中的 GPIO 和 IOMUX 章节，了解更多详情。驱动程序对通道申请数量不做限制，但当硬件资源用尽时，驱动程序将返回错误。因此，每次进行通道分配（如调用 `sdm_new_channel()`）时，请注意检查返回值。

提供有关此文档的反馈