基于STM32 DAC输出任意波形的方案
基于STM32 DAC输出任意波形的方案,包含DMA传输、定时器触发、多种波形生成、可调频率/幅度/偏置,并且支持实时更新波形数据。
一、STM32 DAC核心特性
| 型号 | DAC通道 | 分辨率 | 缓冲 | DMA | 触发源 |
|---|---|---|---|---|---|
| STM32F103 | 2 | 12位 | 有 | 支持 | TIMx_TRGO |
| STM32F407 | 2 | 12位 | 有 | 支持 | TIMx_TRGO |
| STM32G474 | 2 | 12位 | 有 | 支持 | HRTIM |
| STM32H743 | 2 | 12位 | 有 | 支持 | TIMx_TRGO |
推荐配置:
- DAC触发:定时器更新事件
- 数据传输:DMA
- 分辨率:12位(0-4095对应0-3.3V)
二、硬件连接
STM32 DAC引脚:
PA4 → DAC_OUT1
PA5 → DAC_OUT2 (可选)
输出调理电路:
DAC_OUT → 运放电压跟随器 → 输出
必须加运放:STM32 DAC输出阻抗大(约15kΩ),不能直接驱动低阻抗负载。
三、CubeMX配置
1、DAC配置
- Mode:Normal Mode
- Trigger:Timer 6 Trigger Out
- Output Buffer:Enable
- DMA:Enable DMA1/2
2、TIM6配置
- Prescaler:0
- Counter Period:根据频率计算
- Trigger Event:Update Event
3、DMA配置
- Direction:Memory to Peripheral
- Increment Address:Enable
- Data Width:Half Word (16-bit)
四、DAC驱动核心代码
1、头文件定义
// waveform.h
#ifndef __WAVEFORM_H
#define __WAVEFORM_H
#include "stm32f4xx_hal.h"
// 波形类型
typedef enum {
WAVE_SINE = 0, // 正弦波
WAVE_SQUARE, // 方波
WAVE_TRIANGLE, // 三角波
WAVE_SAWTOOTH, // 锯齿波
WAVE_ARBITRARY, // 任意波形
WAVE_DC // 直流
} Waveform_Type_t;
// 波形参数结构体
typedef struct {
Waveform_Type_t type; // 波形类型
float frequency; // 频率 (Hz)
float amplitude; // 幅值 (V)
float offset; // 偏置 (V)
float duty_cycle; // 占空比 (0-1)
uint16_t points; // 一个周期点数
} Waveform_Param_t;
// 全局参数
extern Waveform_Param_t wave_param;
extern uint16_t dac_buffer[1024]; // DAC输出缓冲区
extern uint16_t wave_table[1024]; // 波形数据表
// 函数声明
void DAC_Waveform_Init(void);
void Generate_Waveform(Waveform_Param_t *param);
void Set_Waveform_Params(float freq, float amp, float offset, float duty);
void DAC_Start_Waveform(void);
void DAC_Stop_Waveform(void);
void Update_Waveform_Data(uint16_t *data, uint32_t size);
#endif
2、DAC初始化
// waveform.c
#include "waveform.h"
#include "math.h"
Waveform_Param_t wave_param = {0};
uint16_t dac_buffer[1024];
uint16_t wave_table[1024];
// DAC和定时器初始化
void DAC_Waveform_Init(void)
{
DAC_HandleTypeDef hdac;
TIM_HandleTypeDef htim6;
// 1. 初始化DAC通道1
hdac.Instance = DAC;
HAL_DAC_Init(&hdac);
DAC_ChannelConfTypeDef sConfig = {0};
sConfig.DAC_Trigger = DAC_TRIGGER_T6_TRGO; // TIM6触发
sConfig.DAC_OutputBuffer = DAC_OUTPUTBUFFER_ENABLE;
HAL_DAC_ConfigChannel(&hdac, &sConfig, DAC_CHANNEL_1);
// 2. 初始化TIM6(触发定时器)
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. 启动DAC DMA
HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1,
(uint32_t*)dac_buffer, 1024, DAC_ALIGN_12B_R);
// 4. 启动定时器
HAL_TIM_Base_Start(&htim6);
// 5. 默认参数
wave_param.type = WAVE_SINE;
wave_param.frequency = 1000; // 1kHz
wave_param.amplitude = 1.0; // 1V
wave_param.offset = 1.65; // 1.65V(中点)
wave_param.duty_cycle = 0.5; // 50%
wave_param.points = 100; // 100点/周期
// 6. 生成默认波形
Generate_Waveform(&wave_param);
}
3、波形生成函数(核心)
// 电压转DAC值 (0-3.3V 转 0-4095)
uint16_t Voltage_To_DAC(float voltage)
{
if (voltage < 0) voltage = 0;
if (voltage > 3.3f) voltage = 3.3f;
return (uint16_t)(voltage * 4095.0f / 3.3f);
}
// 生成正弦波
void Generate_Sine_Wave(Waveform_Param_t *p)
{
float angle, voltage;
for (uint16_t i = 0; i < p->points; i++) {
angle = 2.0f * 3.1415926f * i / p->points;
voltage = p->amplitude * sinf(angle) + p->offset;
wave_table[i] = Voltage_To_DAC(voltage);
}
}
// 生成方波
void Generate_Square_Wave(Waveform_Param_t *p)
{
uint16_t high_points = (uint16_t)(p->points * p->duty_cycle);
for (uint16_t i = 0; i < p->points; i++) {
if (i < high_points) {
wave_table[i] = Voltage_To_DAC(p->offset + p->amplitude);
} else {
wave_table[i] = Voltage_To_DAC(p->offset - p->amplitude);
}
}
}
// 生成三角波
void Generate_Triangle_Wave(Waveform_Param_t *p)
{
float slope, voltage;
for (uint16_t i = 0; i < p->points; i++) {
if (i < p->points / 2) {
// 上升沿
slope = 2.0f * p->amplitude * 2.0f / p->points;
voltage = p->offset - p->amplitude + slope * i;
} else {
// 下降沿
slope = 2.0f * p->amplitude * 2.0f / p->points;
voltage = p->offset + p->amplitude - slope * (i - p->points/2);
}
wave_table[i] = Voltage_To_DAC(voltage);
}
}
// 生成锯齿波
void Generate_Sawtooth_Wave(Waveform_Param_t *p)
{
float slope, voltage;
for (uint16_t i = 0; i < p->points; i++) {
slope = 2.0f * p->amplitude / p->points;
voltage = p->offset - p->amplitude + slope * i;
wave_table[i] = Voltage_To_DAC(voltage);
}
}
// 生成直流
void Generate_DC_Wave(Waveform_Param_t *p)
{
uint16_t dac_value = Voltage_To_DAC(p->offset);
for (uint16_t i = 0; i < p->points; i++) {
wave_table[i] = dac_value;
}
}
// 主波形生成函数
void Generate_Waveform(Waveform_Param_t *p)
{
switch (p->type) {
case WAVE_SINE:
Generate_Sine_Wave(p);
break;
case WAVE_SQUARE:
Generate_Square_Wave(p);
break;
case WAVE_TRIANGLE:
Generate_Triangle_Wave(p);
break;
case WAVE_SAWTOOTH:
Generate_Sawtooth_Wave(p);
break;
case WAVE_DC:
Generate_DC_Wave(p);
break;
case WAVE_ARBITRARY:
// 用户自定义波形,不自动生成
break;
}
// 计算实际DMA缓冲区长度
uint32_t buffer_length = 1024;
// 填充DMA缓冲区(循环填充)
for (uint32_t i = 0; i < buffer_length; i++) {
dac_buffer[i] = wave_table[i % p->points];
}
}
4、频率控制(关键)
// 设置波形频率
void Set_Waveform_Frequency(float freq_hz)
{
// 计算定时器重载值
// 公式:f_trigger = f_sys / (PSC+1) / (ARR+1)
// 波形频率 = f_trigger / points
uint32_t sys_clk = 168000000; // 168MHz
uint16_t points = wave_param.points;
float f_trigger = freq_hz * points;
// 自动计算分频和重载值
uint32_t psc = 0;
uint32_t arr = (uint32_t)(sys_clk / f_trigger) - 1;
if (arr > 65535) {
psc = arr / 65536;
arr = 65535;
}
// 更新TIM6
__HAL_TIM_SET_PRESCALER(&htim6, psc);
__HAL_TIM_SET_AUTORELOAD(&htim6, arr);
// 重新启动定时器
__HAL_TIM_SET_COUNTER(&htim6, 0);
HAL_TIM_Base_Start(&htim6);
wave_param.frequency = freq_hz;
}
5、DMA双缓冲模式(进阶)
// 使用DMA双缓冲,避免波形更新时的毛刺
void DAC_DMA_DoubleBuffer_Init(void)
{
// 配置DMA为双缓冲模式
HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1,
(uint32_t*)dac_buffer1, 512, DAC_ALIGN_12B_R);
HAL_DACEx_DualStartDMA(&hdac,
(uint32_t*)dac_buffer1, (uint32_t*)dac_buffer2,
512, DAC_ALIGN_12B_R);
}
// DMA传输完成中断回调
void HAL_DAC_ConvCpltCallbackCh1(DAC_HandleTypeDef *hdac)
{
// 缓冲区0传输完成,更新缓冲区0
Update_Waveform_Buffer(dac_buffer1, 512);
}
void HAL_DAC_ConvHalfCpltCallbackCh1(DAC_HandleTypeDef *hdac)
{
// 缓冲区1传输完成一半,更新缓冲区1
Update_Waveform_Buffer(dac_buffer2, 512);
}
6、任意波形数据加载
// 从外部加载任意波形数据
void Load_Arbitrary_Waveform(float *voltage_data, uint16_t size)
{
if (size > 1024) size = 1024;
wave_param.type = WAVE_ARBITRARY;
wave_param.points = size;
for (int i = 0; i < size; i++) {
wave_table[i] = Voltage_To_DAC(voltage_data[i]);
}
// 更新DMA缓冲区
for (int i = 0; i < 1024; i++) {
dac_buffer[i] = wave_table[i % size];
}
}
// 从数组加载
void Load_Waveform_From_Array(uint16_t *dac_data, uint16_t size)
{
if (size > 1024) size = 1024;
memcpy(wave_table, dac_data, size * sizeof(uint16_t));
wave_param.type = WAVE_ARBITRARY;
wave_param.points = size;
}
7、主函数调用示例
// main.c
#include "waveform.h"
int main(void)
{
HAL_Init();
SystemClock_Config();
// 初始化DAC波形发生器
DAC_Waveform_Init();
// 示例1:生成1kHz正弦波,幅值1V,偏置1.65V
wave_param.type = WAVE_SINE;
wave_param.frequency = 1000;
wave_param.amplitude = 1.0f;
wave_param.offset = 1.65f;
wave_param.points = 100;
Generate_Waveform(&wave_param);
Set_Waveform_Frequency(1000);
// 示例2:生成10kHz方波,占空比30%
wave_param.type = WAVE_SQUARE;
wave_param.frequency = 10000;
wave_param.duty_cycle = 0.3f;
Generate_Waveform(&wave_param);
Set_Waveform_Frequency(10000);
// 示例3:加载自定义波形
float custom_wave[50];
for (int i = 0; i < 50; i++) {
custom_wave[i] = 1.65f + 1.0f * sinf(2*3.1416*i/50);
}
Load_Arbitrary_Waveform(custom_wave, 50);
Set_Waveform_Frequency(2000);
while (1) {
// 可以动态修改波形参数
// 例如通过串口命令修改
if (new_frequency_received) {
Set_Waveform_Frequency(new_freq);
}
if (new_amplitude_received) {
wave_param.amplitude = new_amp;
Generate_Waveform(&wave_param);
}
HAL_Delay(10);
}
}
8、串口命令控制
// 通过串口控制波形
void UART_Command_Handler(uint8_t *cmd)
{
if (strncmp(cmd, "FREQ ", 5) == 0) {
float freq = atof(cmd + 5);
Set_Waveform_Frequency(freq);
printf("频率设置为: %.1f Hz\r\n", freq);
}
else if (strncmp(cmd, "AMP ", 4) == 0) {
wave_param.amplitude = atof(cmd + 4);
Generate_Waveform(&wave_param);
printf("幅值设置为: %.2f V\r\n", wave_param.amplitude);
}
else if (strncmp(cmd, "TYPE ", 5) == 0) {
uint8_t type = atoi(cmd + 5);
wave_param.type = (Waveform_Type_t)type;
Generate_Waveform(&wave_param);
printf("波形类型设置为: %d\r\n", type);
}
else if (strcmp(cmd, "STOP") == 0) {
DAC_Stop_Waveform();
printf("波形输出停止\r\n");
}
else if (strcmp(cmd, "START") == 0) {
DAC_Start_Waveform();
printf("波形输出启动\r\n");
}
}
9、性能优化(高频率)
// 使用查表法提高计算速度
void Generate_Sine_Wave_Fast(Waveform_Param_t *p)
{
// 预计算正弦表
static float sin_table[1024];
static uint8_t table_initialized = 0;
if (!table_initialized) {
for (int i = 0; i < 1024; i++) {
sin_table[i] = sinf(2.0f * 3.1415926f * i / 1024.0f);
}
table_initialized = 1;
}
// 查表生成
uint32_t step = 1024 / p->points;
for (uint16_t i = 0; i < p->points; i++) {
float voltage = p->amplitude * sin_table[(i * step) % 1024] + p->offset;
wave_table[i] = Voltage_To_DAC(voltage);
}
}
10、双通道输出(立体声)
// 双通道波形输出
void DAC_Dual_Channel_Init(void)
{
// 配置DAC通道1和通道2
DAC_ChannelConfTypeDef sConfig = {0};
// 通道1
sConfig.DAC_Trigger = DAC_TRIGGER_T6_TRGO;
sConfig.DAC_OutputBuffer = DAC_OUTPUTBUFFER_ENABLE;
HAL_DAC_ConfigChannel(&hdac, &sConfig, DAC_CHANNEL_1);
// 通道2
sConfig.DAC_Trigger = DAC_TRIGGER_T6_TRGO;
sConfig.DAC_OutputBuffer = DAC_OUTPUTBUFFER_ENABLE;
HAL_DAC_ConfigChannel(&hdac, &sConfig, DAC_CHANNEL_2);
// 双通道DMA传输
uint32_t dac_buffer_dual[2][1024]; // 二维数组
HAL_DACEx_DualStartDMA(&hdac,
(uint32_t*)dac_buffer_dual[0],
(uint32_t*)dac_buffer_dual[1],
1024, DAC_ALIGN_12B_R);
}
参考代码 STM32 DAC输出任意波形 www.youwenfan.com/contentcnu/69991.html
五、输出频率计算
关键公式:
实际输出频率 = 定时器触发频率 / 每个周期点数
定时器触发频率 = 系统时钟 / (PSC+1) / (ARR+1)
示例计算(STM32F407 @168MHz):
- 定时器配置:PSC=0, ARR=167
- 触发频率 = 168MHz / 1 / 168 = 1MHz
- 波形点数 = 100
- 输出频率 = 1MHz / 100 = 10kHz
六、性能参数
| 波形类型 | 最高频率 | 分辨率 | 谐波失真 |
|---|---|---|---|
| 正弦波 | 100kHz | 12位 | <1% |
| 方波 | 1MHz | 12位 | 边沿时间≈100ns |
| 任意波 | 50kHz | 12位 | 取决于点数 |
七、常见问题
| 问题 | 原因 | 解决 |
|---|---|---|
| 波形毛刺 | DMA更新冲突 | 使用双缓冲 |
| 频率不准 | 定时器计算误差 | 使用自动重载 |
| 输出范围小 | 运放增益不够 | 增加后级放大 |
| 高次谐波 | 点数太少 | 增加采样点数 |

浙公网安备 33010602011771号