STM32数模转换器实现任意波形输出设计
本文详细介绍基于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缓冲区更新冲突 | 采用双缓冲机制 |
| 频率偏差较大 | 定时器参数计算有误 | 启用自动重载模式 |
| 输出幅度不足 | 放大电路增益配置不当 | 增加后级放大倍数 |
| 高频谐波分量多 | 采样点数设置过低 | 提高采样密度 |