# Sigma-Delta 调制器 (SDM)

[English]

## 简介

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

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

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_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()`

### 线程安全

驱动使用了临界区保证了对寄存器的原子操作。句柄内部的关键成员也受临界区保护。驱动内部的状态机使用了原子指令保证了线程安全，通过状态检查还能进一步防止一些不合法的并发操作（例如 enable 和 delete 冲突）。因此， SDM 驱动的 API 可以在多线程环境下使用，无需自行加锁。

同时，以下这些函数还允许在中断上下文中使用：

- `sdm_channel_set_pulse_density()`

### Kconfig 选项

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

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

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

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

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

Sallen-Key 拓扑低通滤波器

（滤波前后的波形请参阅文档 peripherals/sigma_delta/sdm_dac/README.md）

## 应用示例

- peripherals/sigma_delta/sdm_dac 演示了如何使用 sigma-delta 驱动器作为 8 位 DAC（数字模拟转换器），并输出 100 Hz 的正弦波。

- peripherals/sigma_delta/sdm_led 演示了如何使用 sigma-delta 驱动器来控制 LED 或 LCD 背光的亮度。

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

### Structures

struct sdm_config_t

  Sigma Delta channel configuration.

  Public Members

  gpio_num_t 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 allow_pd

    If set, driver allows the power domain to be powered off when system enters sleep mode. This can save power, but at the expense of more RAM being consumed to save register context.

  struct sdm_config_t flags

    Extra flags

### Type Definitions

typedef struct sdm_channel_t *sdm_channel_handle_t

  Type of Sigma Delta channel handle.

### Header File

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

- This header file can be included with:

  >     #include "hal/sdm_types.h"

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

  >     REQUIRES esp_hal_gpio

  > or

  >     PRIV_REQUIRES esp_hal_gpio

### Type Definitions

typedef soc_periph_sdm_clk_src_t sdm_clock_source_t

[1]

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

此文档对您有帮助吗？

- 反馈已收到，谢谢！
- 如果您有其他意见，欢迎填写乐鑫文档反馈表。

- 我们重视您的反馈。
- 您可以填写乐鑫文档反馈表告诉我们如何改进该文档。