当前位置:首页 > 技术 > 正文内容

STM32数模转换器实现任意波形输出设计

访客 技术 2026年7月8日 1

本文详细介绍基于STM32微控制器的数模转换器实现任意波形输出的完整方案,涵盖DMA传输机制、定时器触发方式、多类型波形生成算法、频率/幅值/直流偏移动态调节功能,并支持波形数据的实时更新操作。

一、STM32 DAC模块关键特性分析

微控制器系列 DAC通道数 位分辨率 输出缓冲 DMA支持 触发来源
STM32F103 2 12位 集成 支持 TIMx_TRGO
STM32F407 2 12位 集成 支持 TIMx_TRGO
STM32G474 2 12位 集成 支持 HRTIM
STM32H743 2 12位 集成 支持 TIMx_TRGO

推荐技术路线

  • 触发机制:定时器更新事件作为触发源
  • 数据搬运:DMA直接存储器访问
  • 精度配置:12位数字量(0-4095对应0-3.3V模拟电压)

二、硬件电路设计

STM32 DAC输出引脚分配:
PA4 → 第一通道模拟输出
PA5 → 第二通道模拟输出(复用)

信号调理链路:
DAC输出端口 → 电压跟随器(运算放大器) → 最终输出端

重要提示:STM32内部DAC输出阻抗较高(约15kΩ),无法直接驱动低阻抗负载,必须通过运算放大器进行阻抗匹配和信号调理。

三、CubeMX图形化配置步骤

1、数模转换器配置

  • 工作模式:常规工作模式
  • 触发信号:定时器6触发输出
  • 输出缓冲:使能
  • DMA传输:启用DMA1或DMA2

2、定时器6配置

  • 预分频器:0
  • 计数器周期:根据目标频率计算确定
  • 触发事件:更新事件

3、DMA传输配置

  • 传输方向:存储器到外设
  • 地址递增:使能
  • 数据宽度:半字(16位)

四、驱动程序实现方案

1、头文件结构定义

// signal_generator.h
#ifndef __SIGNAL_GENERATOR_H
#define __SIGNAL_GENERATOR_H

#include "stm32f4xx_hal.h"

// 波形类型枚举定义
typedef enum {
    SIGNAL_SINUSOID = 0,     // 正弦波形
    SIGNAL_RECTANGULAR,      // 矩形波形
    SIGNAL_LINEAR,           // 线性三角波形
    SIGNAL_RAMP,             // 斜坡锯齿波形
    SIGNAL_CUSTOM,           // 自定义波形数据
    SIGNAL_CONSTANT          // 直流电平
} Signal_TypeDef;

// 波形参数数据结构
typedef struct {
    Signal_TypeDef type;     // 波形类型标识
    float frequency;         // 输出频率 (Hz)
    float amplitude;         // 峰峰值幅值 (V)
    float dc_offset;         // 直流偏移量 (V)
    float pulse_ratio;       // 脉宽比 (0-1)
    uint16_t sample_count;   // 每周期采样点数
} Signal_ConfigStruct;

// 全局状态变量
extern Signal_ConfigStruct sig_config;
extern uint16_t output_buffer[512];    // DAC输出数据缓冲
extern uint16_t waveform_lut[512];     // 波形查找表

// 接口函数声明
void Signal_Generator_Init(void);
void Calculate_Waveform_Data(Signal_ConfigDef *config);
void Modify_Signal_Parameters(float freq, float amp, float offset, float duty);
void Signal_Output_Start(void);
void Signal_Output_Stop(void);
void Refresh_Waveform_Buffer(uint16_t *data_array, uint32_t length);

#endif

2、DAC模块初始化流程

// signal_generator.c
#include "signal_generator.h"
#include "math.h"

Signal_ConfigStruct sig_config = {0};
uint16_t output_buffer[512];
uint16_t waveform_lut[512];

// DAC与定时器统一初始化
void Signal_Generator_Init(void)
{
    DAC_HandleTypeDef hdac;
    TIM_HandleTypeDef htim6;
    
    // 步骤1:配置DAC外设
    hdac.Instance = DAC;
    HAL_DAC_Init(&hdac);
    
    DAC_ChannelConfTypeDef chConfig = {0};
    chConfig.DAC_Trigger = DAC_TRIGGER_T6_TRGO;  // 定时器6触发
    chConfig.DAC_OutputBuffer = DAC_OUTPUTBUFFER_ENABLE;
    HAL_DAC_ConfigChannel(&hdac, &chConfig, DAC_CHANNEL_1);
    
    // 步骤2:配置定时器6(触发源)
    htim6.Instance = TIM6;
    htim6.Init.Prescaler = 0;
    htim6.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim6.Init.Period = 168;  // 168MHz/168 = 1MHz更新率
    htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
    HAL_TIM_Base_Init(&htim6);
    
    // 步骤3:启动DMA传输
    HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1, 
                     (uint32_t*)output_buffer, 512, DAC_ALIGN_12B_R);
    
    // 步骤4:启动定时器
    HAL_TIM_Base_Start(&htim6);
    
    // 步骤5:设置初始参数
    sig_config.type = SIGNAL_SINUSOID;
    sig_config.frequency = 1000;   // 1kHz
    sig_config.amplitude = 1.0;    // 1V
    sig_config.dc_offset = 1.65;   // 1.65V中心电平
    sig_config.pulse_ratio = 0.5;  // 50%占空比
    sig_config.sample_count = 100; // 每周期100个采样点
    
    // 步骤6:生成初始波形
    Calculate_Waveform_Data(&sig_config);
}

3、波形数据生成算法(核心部分)

// 模拟电压转换为DAC数值 (0-3.3V → 0-4095)
uint16_t Convert_Voltage_To_DAC(float voltage_value)
{
    if (voltage_value < 0.0f) voltage_value = 0.0f;
    if (voltage_value > 3.3f) voltage_value = 3.3f;
    return (uint16_t)(voltage_value * 4095.0f / 3.3f);
}

// 正弦波形生成算法
void Compute_Sinusoid_Data(Signal_ConfigStruct *cfg)
{
    float phase_angle, voltage_level;
    
    for (uint16_t i = 0; i < cfg->sample_count; i++) {
        phase_angle = 2.0f * 3.1415926f * i / cfg->sample_count;
        voltage_level = cfg->amplitude * sinf(phase_angle) + cfg->dc_offset;
        waveform_lut[i] = Convert_Voltage_To_DAC(voltage_level);
    }
}

// 矩形波形生成算法
void Compute_Rectangular_Data(Signal_ConfigStruct *cfg)
{
    uint16_t high_count = (uint16_t)(cfg->sample_count * cfg->pulse_ratio);
    
    for (uint16_t i = 0; i < cfg->sample_count; i++) {
        if (i < high_count) {
            waveform_lut[i] = Convert_Voltage_To_DAC(cfg->dc_offset + cfg->amplitude);
        } else {
            waveform_lut[i] = Convert_Voltage_To_DAC(cfg->dc_offset - cfg->amplitude);
        }
    }
}

// 线性三角波形生成算法
void Compute_Linear_Data(Signal_ConfigStruct *cfg)
{
    float slope_value, voltage_level;
    
    for (uint16_t i = 0; i < cfg->sample_count; i++) {
        if (i < cfg->sample_count / 2) {
            // 上升段
            slope_value = 2.0f * cfg->amplitude * 2.0f / cfg->sample_count;
            voltage_level = cfg->dc_offset - cfg->amplitude + slope_value * i;
        } else {
            // 下降段
            slope_value = 2.0f * cfg->amplitude * 2.0f / cfg->sample_count;
            voltage_level = cfg->dc_offset + cfg->amplitude - slope_value * (i - cfg->sample_count/2);
        }
        waveform_lut[i] = Convert_Voltage_To_DAC(voltage_level);
    }
}

// 斜坡波形生成算法
void Compute_Ramp_Data(Signal_ConfigStruct *cfg)
{
    float slope_value, voltage_level;
    
    for (uint16_t i = 0; i < cfg->sample_count; i++) {
        slope_value = 2.0f * cfg->amplitude / cfg->sample_count;
        voltage_level = cfg->dc_offset - cfg->amplitude + slope_value * i;
        waveform_lut[i] = Convert_Voltage_To_DAC(voltage_level);
    }
}

// 直流电平生成算法
void Compute_Constant_Data(Signal_ConfigStruct *cfg)
{
    uint16_t dac_val = Convert_Voltage_To_DAC(cfg->dc_offset);
    
    for (uint16_t i = 0; i < cfg->sample_count; i++) {
        waveform_lut[i] = dac_val;
    }
}

// 统一波形生成调度函数
void Calculate_Waveform_Data(Signal_ConfigStruct *cfg)
{
    switch (cfg->type) {
        case SIGNAL_SINUSOID:
            Compute_Sinusoid_Data(cfg);
            break;
        case SIGNAL_RECTANGULAR:
            Compute_Rectangular_Data(cfg);
            break;
        case SIGNAL_LINEAR:
            Compute_Linear_Data(cfg);
            break;
        case SIGNAL_RAMP:
            Compute_Ramp_Data(cfg);
            break;
        case SIGNAL_CONSTANT:
            Compute_Constant_Data(cfg);
            break;
        case SIGNAL_CUSTOM:
            // 自定义波形由用户提供数据
            break;
    }
    
    // 计算DMA缓冲区有效长度
    uint32_t buffer_size = 512;
    
    // 循环填充DMA输出缓冲区
    for (uint32_t i = 0; i < buffer_size; i++) {
        output_buffer[i] = waveform_lut[i % cfg->sample_count];
    }
}

4、频率动态调节实现

// 调节输出信号频率
void Adjust_Signal_Frequency(float target_freq_hz)
{
    // 触发频率计算公式:
    // f_trigger = f_sys / (PSC+1) / (ARR+1)
    // 输出频率 = f_trigger / sample_count
    
    uint32_t system_clock = 168000000;  // 系统时钟168MHz
    uint16_t sample_pts = sig_config.sample_count;
    float f_trigger = target_freq_hz * sample_pts;
    
    // 自动计算分频系数和重装载值
    uint32_t prescaler = 0;
    uint32_t reload_val = (uint32_t)(system_clock / f_trigger) - 1;
    
    if (reload_val > 65535) {
        prescaler = reload_val / 65536;
        reload_val = 65535;
    }
    
    // 更新定时器参数
    __HAL_TIM_SET_PRESCALER(&htim6, prescaler);
    __HAL_TIM_SET_AUTORELOAD(&htim6, reload_val);
    
    // 复位计数器并重启定时器
    __HAL_TIM_SET_COUNTER(&htim6, 0);
    HAL_TIM_Base_Start(&htim6);
    
    sig_config.frequency = target_freq_hz;
}

5、双缓冲DMA模式(高级应用)

// 配置DMA双缓冲工作模式
void DAC_DoubleBuffer_Setup(void)
{
    // 使能双缓冲功能
    HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1,
                     (uint32_t*)buffer_bank1, 512, DAC_ALIGN_12B_R);
    
    HAL_DACEx_DualStartDMA(&hdac,
                          (uint32_t*)buffer_bank1, 
                          (uint32_t*)buffer_bank2,
                          512, DAC_ALIGN_12B_R);
}

// DMA传输完成中断处理(第一缓冲区)
void HAL_DAC_ConvCpltCallbackCh1(DAC_HandleTypeDef *hdac)
{
    // 第一缓冲区传输完毕,刷新该缓冲区数据
    Refresh_Buffer_Region(buffer_bank1, 512);
}

// DMA半传输完成中断处理(第二缓冲区)
void HAL_DAC_ConvHalfCpltCallbackCh1(DAC_HandleTypeDef *hdac)
{
    // 第二缓冲区传输完毕,刷新该缓冲区数据
    Refresh_Buffer_Region(buffer_bank2, 512);
}

6、自定义波形数据加载

// 从浮点数组加载自定义波形
void Load_Custom_Waveform(float *voltage_array, uint16_t data_length)
{
    if (data_length > 512) data_length = 512;
    
    sig_config.type = SIGNAL_CUSTOM;
    sig_config.sample_count = data_length;
    
    for (int i = 0; i < data_length; i++) {
        waveform_lut[i] = Convert_Voltage_To_DAC(voltage_array[i]);
    }
    
    // 更新DMA输出缓冲区
    for (int i = 0; i < 512; i++) {
        output_buffer[i] = waveform_lut[i % data_length];
    }
}

// 从无符号整型数组直接加载
void Load_Data_From_Array(uint16_t *raw_data, uint16_t data_length)
{
    if (data_length > 512) data_length = 512;
    
    memcpy(waveform_lut, raw_data, data_length * sizeof(uint16_t));
    sig_config.type = SIGNAL_CUSTOM;
    sig_config.sample_count = data_length;
}

7、主程序调用示例

// main.c
#include "signal_generator.h"

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    
    // 初始化信号发生器
    Signal_Generator_Init();
    
    // 用例1:产生1kHz正弦波,幅值1V,中心电平1.65V
    sig_config.type = SIGNAL_SINUSOID;
    sig_config.frequency = 1000;
    sig_config.amplitude = 1.0f;
    sig_config.dc_offset = 1.65f;
    sig_config.sample_count = 100;
    
    Calculate_Waveform_Data(&sig_config);
    Adjust_Signal_Frequency(1000);
    
    // 用例2:产生10kHz矩形波,脉宽比30%
    sig_config.type = SIGNAL_RECTANGULAR;
    sig_config.frequency = 10000;
    sig_config.pulse_ratio = 0.3f;
    
    Calculate_Waveform_Data(&sig_config);
    Adjust_Signal_Frequency(10000);
    
    // 用例3:加载自定义波形数据
    float user_defined[50];
    for (int i = 0; i < 50; i++) {
        user_defined[i] = 1.65f + 1.0f * sinf(2*3.1416*i/50);
    }
    Load_Custom_Waveform(user_defined, 50);
    Adjust_Signal_Frequency(2000);
    
    while (1) {
        // 运行时动态调整参数
        // 可通过串口通信接收新的参数值
        if (command_flag_new_freq) {
            Adjust_Signal_Frequency(received_frequency);
        }
        
        if (command_flag_new_amp) {
            sig_config.amplitude = received_amplitude;
            Calculate_Waveform_Data(&sig_config);
        }
        
        HAL_Delay(10);
    }
}

8、串口指令控制系统

// 串口指令解析处理
void Process_UART_Command(uint8_t *command_string)
{
    if (strncmp(command_string, "FREQ ", 5) == 0) {
        float freq = atof(command_string + 5);
        Adjust_Signal_Frequency(freq);
        printf("Output frequency: %.1f Hz\r\n", freq);
    }
    else if (strncmp(command_string, "AMP ", 4) == 0) {
        sig_config.amplitude = atof(command_string + 4);
        Calculate_Waveform_Data(&sig_config);
        printf("Amplitude: %.2f V\r\n", sig_config.amplitude);
    }
    else if (strncmp(command_string, "TYPE ", 5) == 0) {
        uint8_t wave_type = atoi(command_string + 5);
        sig_config.type = (Signal_TypeDef)wave_type;
        Calculate_Waveform_Data(&sig_config);
        printf("Waveform type: %d\r\n", wave_type);
    }
    else if (strcmp(command_string, "STOP") == 0) {
        Signal_Output_Stop();
        printf("Output stopped\r\n");
    }
    else if (strcmp(command_string, "START") == 0) {
        Signal_Output_Start();
        printf("Output started\r\n");
    }
}

9、执行效率优化策略(高频场景)

// 优化版正弦波生成(查表法)
void Compute_Sinusoid_Fast(Signal_ConfigStruct *cfg)
{
    // 预先生成正弦查找表
    static float sin_lut[1024];
    static uint8_t table_ready = 0;
    
    if (!table_ready) {
        for (int i = 0; i < 1024; i++) {
            sin_lut[i] = sinf(2.0f * 3.1415926f * i / 1024.0f);
        }
        table_ready = 1;
    }
    
    // 通过查表快速生成
    uint32_t step_size = 1024 / cfg->sample_count;
    
    for (uint16_t i = 0; i < cfg->sample_count; i++) {
        float voltage = cfg->amplitude * sin_lut[(i * step_size) % 1024] + cfg->dc_offset;
        waveform_lut[i] = Convert_Voltage_To_DAC(voltage);
    }
}

10、双通道同步输出配置

// 双通道信号输出配置
void DAC_DualChannel_Setup(void)
{
    // 配置两路DAC通道
    DAC_ChannelConfTypeDef chConfig = {0};
    
    // 通道1配置
    chConfig.DAC_Trigger = DAC_TRIGGER_T6_TRGO;
    chConfig.DAC_OutputBuffer = DAC_OUTPUTBUFFER_ENABLE;
    HAL_DAC_ConfigChannel(&hdac, &chConfig, DAC_CHANNEL_1);
    
    // 通道2配置
    chConfig.DAC_Trigger = DAC_TRIGGER_T6_TRGO;
    chConfig.DAC_OutputBuffer = DAC_OUTPUTBUFFER_ENABLE;
    HAL_DAC_ConfigChannel(&hdac, &chConfig, DAC_CHANNEL_2);
    
    // 双通道DMA同时传输
    uint32_t dual_buffer[2][512];  // 二维数组结构
    
    HAL_DACEx_DualStartDMA(&hdac,
                          (uint32_t*)dual_buffer[0],
                          (uint32_t*)dual_buffer[1],
                          512, DAC_ALIGN_12B_R);
}

五、输出频率精确计算

核心计算公式:

实际输出频率 = 定时器触发频率 / 每周期采样点数
定时器触发频率 = 系统时钟频率 / (预分频值+1) / (自动重装值+1)

计算实例(STM32F407系统频率168MHz):

  • 定时器参数:预分频=0,重装值=167
  • 触发频率 = 168MHz / 1 / 168 = 1MHz
  • 采样点数 = 100
  • 输出频率 = 1MHz / 100 = 10kHz

六、技术性能指标

波形类型 最高输出频率 分辨率精度 谐波特性
正弦波 100kHz 12位 失真度<1%
矩形波 1MHz 12位 上升沿≈100ns
自定义波 50kHz 12位 取决于采样点数

七、常见问题分析与解决

故障现象 产生原因 解决措施
输出波形有毛刺 DMA缓冲区更新冲突 采用双缓冲机制
频率偏差较大 定时器参数计算有误 启用自动重载模式
输出幅度不足 放大电路增益配置不当 增加后级放大倍数
高频谐波分量多 采样点数设置过低 提高采样密度
标签: STM32DAC

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。