系列文章目录

持续更新…



前言

在嵌入式系统开发中,模拟信号的转换是非常常见的操作,尤其在物联网设备和传感器应用中,如何通过 DAC(数字到模拟转换器)将数字信号转换为模拟信号,以便与外部模拟设备通信。ESP32 提供了内置的 DAC 功能,可以方便地将数字数据转化为模拟信号进行输出。

本篇博文将详细介绍 ESP32 的 DAC 功能,帮助初学者理解 DAC 的工作原理和常见的配置方式,并通过示例代码展示如何在 ESP32 上实现 DAC 控制。

参考文档:ESP32-S3技术参考手册ESP32-S3编程指南


一、DAC概述

DAC(数字到模拟转换器)是将数字信号转换为对应模拟信号的设备。在 ESP32 中,DAC 通常用于输出模拟信号,通过数字数据控制产生特定的电压值。ESP32 提供了两个 DAC 通道,可以分别通过 GPIO 25 和 GPIO 26 输出模拟信号。这两个 DAC 通道可以通过 DMA 功能进行高速数据传输。

ESP32 的 DAC 系统在多种应用中都能得到广泛使用,尤其是在需要输出音频信号、模拟电压波形等场景。其主要特点如下:
1.支持两通道 DAC 输出,分别映射到 GPIO 25 和 GPIO 26。
2.支持 DMA 功能,能够实现高速的数据传输和转换。
3.支持不同的输出频率和幅度调整,可通过配置参数灵活调节。

DAC 工作原理概述
ESP32 的 DAC 系统内部采用 SAR ADC(逐次逼近转换器),通过调节相关配置,可以生成不同幅度和频率的模拟信号。这些模拟信号可用于驱动外部硬件设备,如音响、传感器等。

二、DAC类型定义及相关API

需包含在 DAC 应用程序中的公共头文件包括:
dac_oneshot.h:新 DAC 驱动的最上层头文件,应包含在使用新驱动 API(单次模式)的应用程序中。
dac_cosine.h:新 DAC 驱动的最上层头文件,应包含在使用新驱动 API(余弦模式)的应用程序中。
dac_continuous.h:新 DAC 驱动的最上层头文件,应包含在使用新驱动 API(连续模式)的应用程序中。
DAC类型定义(新版)

// DAC 通道类型
typedef enum {
    DAC_CHAN_0 = 0,   // DAC 通道 0,GPIO25(ESP32)/GPIO17(ESP32-S2)
    DAC_CHAN_1 = 1,   // DAC 通道 1,GPIO26(ESP32)/GPIO18(ESP32-S2)
} dac_channel_t;

// DAC 连续模式时钟源类型
typedef enum {
    DAC_DIGI_CLK_SRC_DEFAULT = 0,   // 默认时钟源
    DAC_DIGI_CLK_SRC_PLL_D2 = 1,    // PLL D2 时钟源
    DAC_DIGI_CLK_SRC_APLL = 2,      // APLL 时钟源
} dac_continuous_digi_clk_src_t;

// DAC 连续模式通道工作模式
typedef enum {
    DAC_CHANNEL_MODE_SIMUL = 0,  // 同时输出模式
    DAC_CHANNEL_MODE_ALTER = 1,  // 交替输出模式
} dac_continuous_channel_mode_t;

// DAC 余弦波发生器幅度衰减
typedef enum {
    DAC_COSINE_ATTEN_DEFAULT = 0,   // 默认无衰减
    DAC_COSINE_ATTEN_DB_0 = 1,      // 无衰减
    DAC_COSINE_ATTEN_DB_6 = 2,      // 1/2 幅度
    DAC_COSINE_ATTEN_DB_12 = 3,     // 1/4 幅度
    DAC_COSINE_ATTEN_DB_18 = 4,     // 1/8 幅度
} dac_cosine_atten_t;

// DAC 余弦波发生器相位
typedef enum {
    DAC_COSINE_PHASE_0 = 0,  // 0° 相位
    DAC_COSINE_PHASE_180 = 1,  // 180° 相位
} dac_cosine_phase_t;

// DAC 连续模式配置结构体
typedef struct {
    uint32_t chan_mask;              // DAC 通道掩码,选择启用哪些 DAC 通道
    uint32_t desc_num;               // DMA 描述符数量
    size_t buf_size;                 // DMA 缓冲区大小
    uint32_t freq_hz;                // DAC 转换频率(单位:Hz)
    int8_t offset;                   // DAC 数据的偏移量
    dac_continuous_digi_clk_src_t clk_src;  // DAC 数字控制器时钟源
    dac_continuous_channel_mode_t chan_mode; // DAC 通道工作模式
} dac_continuous_config_t;

DAC相关API(新版)

// ========================== 单次模式(One-shot) ==========================

// 分配一个新的 DAC 单次通道
esp_err_t dac_oneshot_new_channel(
    const dac_oneshot_config_t *oneshot_cfg, // 输入参数:单次模式配置结构体指针
    dac_oneshot_handle_t *ret_handle         // 输出参数:返回的 DAC 单次通道句柄
);

// 删除一个 DAC 单次通道
esp_err_t dac_oneshot_del_channel(
    dac_oneshot_handle_t handle // 输入参数:待删除的 DAC 单次通道句柄
);

// 输出指定 DAC 通道的电压值
esp_err_t dac_oneshot_output_voltage(
    dac_oneshot_handle_t handle, // 输入参数:DAC 单次通道句柄
    uint8_t digi_value          // 输入参数:8 位数字值(0~255),对应模拟电压(0~Vref)
);

// ========================== 连续模式(Continuous/DMA) ==========================

// 分配新的 DAC 连续模式通道
esp_err_t dac_continuous_new_channels(
    const dac_continuous_config_t *cont_cfg, // 输入参数:连续模式配置结构体指针
    dac_continuous_handle_t *ret_handle     // 输出参数:返回的 DAC 连续模式通道句柄
);

// 删除 DAC 连续模式通道
esp_err_t dac_continuous_del_channels(
    dac_continuous_handle_t handle // 输入参数:待删除的 DAC 连续模式通道句柄
);

// 启用 DAC 连续模式
esp_err_t dac_continuous_enable(
    dac_continuous_handle_t handle // 输入参数:DAC 连续模式通道句柄
);

// 禁用 DAC 连续模式
esp_err_t dac_continuous_disable(
    dac_continuous_handle_t handle // 输入参数:DAC 连续模式通道句柄
);

// 向 DAC 连续模式写入数据
esp_err_t dac_continuous_write(
    dac_continuous_handle_t handle, // 输入参数:DAC 连续模式通道句柄
    uint8_t *buf,                   // 输入参数:待转换的数字数据缓冲区
    size_t buf_size,                // 输入参数:数字数据缓冲区大小
    size_t *bytes_loaded,           // 输出参数:已加载到 DMA 缓冲区的字节数(可为 NULL)
    int timeout_ms                  // 输入参数:超时时间(毫秒),-1 表示无限等待
);

// 启动 DAC 连续模式的异步写入
esp_err_t dac_continuous_start_async_writing(
    dac_continuous_handle_t handle // 输入参数:DAC 连续模式通道句柄
);

// 停止 DAC 连续模式的异步写入
esp_err_t dac_continuous_stop_async_writing(
    dac_continuous_handle_t handle // 输入参数:DAC 连续模式通道句柄
);

// ========================== 余弦波模式(Cosine) ==========================

// 配置并启用 DAC 余弦波发生器
esp_err_t dac_cosine_new_channel(
    const dac_cosine_config_t *cos_cfg, // 输入参数:余弦波配置结构体指针
    dac_cosine_handle_t *ret_handle     // 输出参数:返回的 DAC 余弦波通道句柄
);

// 启动 DAC 余弦波发生器
esp_err_t dac_cosine_start(
    dac_cosine_handle_t handle // 输入参数:DAC 余弦波通道句柄
);

// 停止 DAC 余弦波发生器
esp_err_t dac_cosine_stop(
    dac_cosine_handle_t handle // 输入参数:DAC 余弦波通道句柄
);

三、DAC示例程序

ESP32-S3 不支持内置 DAC 功能,参考代码使用的是 ESP32。
示例 1:单次输出模式(One-shot Mode)(IDF5.x新版驱动)

#include "driver/dac_oneshot.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

#define DAC_CHANNEL DAC_CHAN_0 // 使用通道 0(GPIO25)

void app_main(void)
{
    esp_err_t ret;
    dac_oneshot_handle_t dac_handle = NULL;
    dac_oneshot_config_t oneshot_cfg = {
        .chan_id = DAC_CHANNEL,
    };

    // 分配 DAC 单次通道
    ret = dac_oneshot_new_channel(&oneshot_cfg, &dac_handle);
    if (ret != ESP_OK)
    {
        ESP_LOGE("DAC", "Failed to allocate DAC channel");
        return;
    }
    else
    {
        ESP_LOGI("DAC", "DAC channel allocated successfully");  
    }

    // 输出电压值 128(约为 VDD3P3_RTC 的一半)
    ret = dac_oneshot_output_voltage(dac_handle, 128);
    if (ret != ESP_OK)
    {
        ESP_LOGE("DAC", "Failed to output voltage");
    }
    else
    {
        ESP_LOGI("DAC", "DAC output voltage set successfully");
    }

    // 删除 DAC 通道
    ret = dac_oneshot_del_channel(dac_handle);
    if (ret != ESP_OK)
    {
        ESP_LOGE("DAC", "Failed to delete DAC channel");
    }
    else
    {
        ESP_LOGI("DAC", "DAC channel deleted successfully");
    }

    while (1)
    {
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
    
}

示例 2:连续输出模式(Continuous Mode)(IDF5.x新版驱动)

#include "driver/dac_continuous.h"
#include "esp_log.h"           // 用于日志输出
#include "freertos/FreeRTOS.h" // FreeRTOS相关
#include "freertos/task.h"     // 任务延时函数
#include <math.h>              // 数学函数(sin等)

// DAC配置参数
#define DAC_CHANNEL_MASK DAC_CHANNEL_MASK_CH0 // 使用通道 0(GPIO25)
#define SAMPLE_RATE 8000                      // 采样率 8kHz(生成更流畅的波形)
#define BUFFER_SIZE 100                       // DMA缓冲区大小(一个周期的采样点数)
#define OUTPUT_FREQ 80                        // 输出信号频率 = SAMPLE_RATE / BUFFER_SIZE = 8000/100 = 80Hz

// 全局变量
uint8_t dma_buffer[BUFFER_SIZE];           // DMA缓冲区,存储波形数据
static const char *TAG = "DAC_CONTINUOUS"; // 日志标签

void generate_waveform(void)
{
    ESP_LOGI(TAG, "Generating sine wave data...");
    for (int i = 0; i < BUFFER_SIZE; i++)
    {
        // 生成正弦波:幅值范围0-255,中心值128
        dma_buffer[i] = (uint8_t)(128 * (1 + sin(2 * M_PI * i / BUFFER_SIZE)));
    }
    ESP_LOGI(TAG, "Waveform generation completed. Buffer size: %d samples", BUFFER_SIZE);
}

void app_main(void)
{
    esp_err_t ret;
    dac_continuous_handle_t dac_handle = NULL;

    // DAC连续模式配置结构体
    dac_continuous_config_t cont_cfg = {
        .chan_mask = DAC_CHANNEL_MASK,       // 通道掩码:选择DAC通道0 (GPIO25)
        .desc_num = 2,                       // DMA描述符数量:用于环形缓冲
        .buf_size = BUFFER_SIZE,             // DMA缓冲区大小:100个采样点
        .freq_hz = SAMPLE_RATE,              // 采样频率:8000Hz
        .offset = 0,                         // 偏移量:0
        .clk_src = DAC_DIGI_CLK_SRC_DEFAULT, // 时钟源:默认
        .chan_mode = DAC_CHANNEL_MODE_SIMUL, // 通道模式:同时模式
    };

    ESP_LOGI(TAG, "Starting DAC continuous output...");

    // 分配 DAC 连续模式通道
    ret = dac_continuous_new_channels(&cont_cfg, &dac_handle);
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "Failed to allocate DAC continuous channels: %s", esp_err_to_name(ret));
        return;
    }
    ESP_LOGI(TAG, "DAC channels allocated successfully");

    // 启用 DAC 连续模式
    ret = dac_continuous_enable(dac_handle);
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "Failed to enable DAC continuous mode: %s", esp_err_to_name(ret));
        // 清理资源
        dac_continuous_del_channels(dac_handle);
        return;
    }
    ESP_LOGI(TAG, "DAC continuous mode enabled");

    // 生成波形数据并写入DMA缓冲区
    generate_waveform();

    // 循环连续输出波形数据
    ESP_LOGI(TAG, "Starting continuous waveform output...");
    while (1)
    {
        // 向 DMA 缓冲区写入数据(阻塞模式,超时时间-1表示永久等待)
        ret = dac_continuous_write(dac_handle, dma_buffer, BUFFER_SIZE, NULL, -1);
        if (ret != ESP_OK)
        {
            ESP_LOGE(TAG, "Failed to write data to DAC: %s", esp_err_to_name(ret));
            break;
        }

        // 可选:添加少量延时以避免过于频繁的写入
        vTaskDelay(pdMS_TO_TICKS(10));
    }

    // 程序结束时的清理工作(这段代码在无限循环中不会被执行到)
    ESP_LOGI(TAG, "Stopping DAC continuous output...");

    // 停止 DAC 连续模式
    ret = dac_continuous_disable(dac_handle);
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "Failed to disable DAC continuous mode: %s", esp_err_to_name(ret));
    }

    // 删除 DAC 连续模式通道
    ret = dac_continuous_del_channels(dac_handle);
    if (ret != ESP_OK)
    {
        ESP_LOGE(TAG, "Failed to delete DAC continuous channels: %s", esp_err_to_name(ret));
    }

    ESP_LOGI(TAG, "DAC continuous output stopped and resources cleaned up");
}


总结

本篇博文详细介绍了 ESP32 的新驱动 DAC 功能,包括其硬件原理、常见配置及如何使用新驱动 API(oneshot 和 continuous 模式)进行 DAC 控制。通过 API 示例,展示了如何在 ESP32 上输出数字到模拟信号的转换。

希望通过本篇博文,您能掌握 ESP32 DAC 的基本配置与应用,能够灵活地实现模拟信号的输出,适用于各种项目需求。欢迎大家交流与反馈。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐