You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ESP32 DAC扫频问题:无需停止DAC修改频率的可行方法

ESP32 DAC扫频:动态改频与时序问题解决

问题分析

当前使用dac_cosine组件实现扫频时,存在两个核心问题:

  • 官方dac_cosineAPI不支持运行时动态修改输出频率,必须停止、销毁、重建通道才能更新参数;
  • 停止DAC后直接执行销毁操作,会因硬件未完成停止时序导致异常。

解决方案

1. 修复时序问题(基于原余弦波组件)

虽然无法免停止改频,但可以通过等待硬件完成停止操作来避免异常。修改set_frequency函数,在停止后轮询确认DAC已停止,再执行后续步骤:

#include <math.h>
#include <rom/ets_sys.h>
#include <esp_task_wdt.h>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/dac_cosine.h"
#include "esp_adc/adc_oneshot.h"
#include "esp_check.h"

dac_cosine_handle_t chan0_handle;

dac_cosine_config_t cos0_cfg = {
    .chan_id = DAC_CHAN_0,
    .freq_hz = 1000,
    .clk_src = DAC_COSINE_CLK_SRC_DEFAULT,
    .offset = 0,
    .phase = DAC_COSINE_PHASE_0,
    .atten = DAC_COSINE_ATTEN_DEFAULT,
    .flags.force_set_freq = false,
};

void set_frequency(uint32_t frequency) {
    cos0_cfg.freq_hz = frequency;

    // 停止DAC并等待硬件完成停止
    ESP_ERROR_CHECK(dac_cosine_stop(chan0_handle));
    // 轮询确认DAC已停止,超时时间设为100ms
    TickType_t start_tick = xTaskGetTickCount();
    while (dac_cosine_is_running(chan0_handle)) {
        if (xTaskGetTickCount() - start_tick > 100 / portTICK_PERIOD_MS) {
            ESP_LOGE("DAC", "Stop timeout");
            break;
        }
        vTaskDelay(1 / portTICK_PERIOD_MS);
    }

    // 销毁并重建通道
    ESP_ERROR_CHECK(dac_cosine_del_channel(&chan0_handle));
    ESP_ERROR_CHECK(dac_cosine_new_channel(&cos0_cfg, &chan0_handle));
    ESP_ERROR_CHECK(dac_cosine_start(chan0_handle));
}

void sweep() {
    int freq = 1000;
    while(true) {
        set_frequency(freq);
        freq += 1000;
        vTaskDelay(1000 / portTICK_PERIOD_MS);
        if(freq > 10000) {
            freq = 1000;
        }
    }
}

void app_main(void)
{
    ESP_ERROR_CHECK(dac_cosine_new_channel(&cos0_cfg, &chan0_handle));
    ESP_ERROR_CHECK(dac_cosine_start(chan0_handle));

    xTaskCreate(sweep, "sweeping", 4096, NULL, 5, NULL);
}

2. 无缝扫频替代方案:DMA驱动DAC

如果需要无中断的平滑扫频,建议使用DMA+DAC的方式,自行生成波形数据并通过DMA循环输出。这种方式可以在运行时动态调整采样率(实现频率变化),或者实时更新波形数据,完全不需要停止DAC输出。

示例代码思路(生成正弦波并动态调整频率):

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/dac.h"
#include "driver/dma.h"
#include "esp_log.h"
#include <math.h>

#define DAC_CHAN DAC_CHAN_0
#define WAVE_SAMPLES 256
#define MAX_FREQ 10000
#define MIN_FREQ 1000

static const char *TAG = "DAC_DMA";
uint8_t sine_wave[WAVE_SAMPLES];
dma_handle_t dma_chan;

// 生成正弦波数据
void generate_sine_wave() {
    for (int i = 0; i < WAVE_SAMPLES; i++) {
        sine_wave[i] = (uint8_t)(127 * sin(2 * M_PI * i / WAVE_SAMPLES) + 128);
    }
}

// 初始化DMA驱动DAC
void dac_dma_init() {
    // 初始化DAC
    dac_output_enable(DAC_CHAN);

    // 配置DMA
    dma_config_t dma_cfg = {
        .direction = DMA_MEMORY_TO_PERIPH,
        .src_addr = (uintptr_t)sine_wave,
        .dst_addr = (uintptr_t)&DAC->DATA[DAC_CHAN],
        .src_addr_inc = DMA_ADDR_INC_ENABLE,
        .dst_addr_inc = DMA_ADDR_INC_DISABLE,
        .src_inc_size = DMA_INC_SIZE_8BIT,
        .dst_inc_size = DMA_INC_SIZE_8BIT,
        .size = WAVE_SAMPLES,
        .num_blocks = 1,
        .block_size = WAVE_SAMPLES,
    };

    // 创建DMA通道
    dma_chan = dma_channel_alloc();
    dma_channel_config(dma_chan, &dma_cfg);
    // 开启循环模式
    dma_channel_set_loop_mode(dma_chan, true);
    // 启动DMA传输
    dma_channel_start(dma_chan);
}

// 动态调整频率:通过修改DMA传输速率实现
void set_dac_frequency(uint32_t freq) {
    // 计算所需的DMA时钟分频(假设APB时钟为80MHz,需根据实际硬件调整)
    uint32_t apb_clk = 80000000;
    // 每个采样的时钟周期 = APB时钟 / (频率 * 采样点数)
    uint32_t cycle_per_sample = apb_clk / (freq * WAVE_SAMPLES);
    // 设置DMA传输节拍(需匹配硬件寄存器要求,此处为示例逻辑)
    dma_channel_set_transfer_length(dma_chan, WAVE_SAMPLES, cycle_per_sample);
}

void sweep_task(void *arg) {
    int freq = MIN_FREQ;
    bool increasing = true;
    while (1) {
        set_dac_frequency(freq);
        ESP_LOGI(TAG, "Current frequency: %d Hz", freq);
        
        if (increasing) {
            freq += 1000;
            if (freq >= MAX_FREQ) {
                increasing = false;
            }
        } else {
            freq -= 1000;
            if (freq <= MIN_FREQ) {
                increasing = true;
            }
        }
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

void app_main(void) {
    generate_sine_wave();
    dac_dma_init();
    xTaskCreate(sweep_task, "sweep_task", 4096, NULL, 5, NULL);
}

注意事项

  • 余弦波组件的时序问题本质是硬件寄存器操作需要时间,轮询等待是最可靠的方式,避免固定延时的不确定性;
  • DMA方案需要注意APB时钟的实际值,不同ESP32型号可能有差异,需根据实际硬件调整分频参数;
  • DMA方式可以实现更灵活的波形输出(如任意波形、调频、调幅等),适合对输出连续性要求高的场景。

内容的提问来源于stack exchange,提问作者Mart

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 00:47:57