手把手教你用STM32F103的DAC和DMA生成正弦波(附完整代码和波形数组生成方法)
从零构建STM32F103正弦波发生器:DAC与DMA的黄金组合实战指南
当我们需要在嵌入式系统中生成精确的模拟信号时,数字模拟转换器(DAC)是不可或缺的硬件资源。而STM32F103系列微控制器内置的12位DAC,配合直接内存访问(DMA)技术,能够实现高效、精确的波形生成。本文将带你深入理解这一技术组合,并手把手实现一个可调频率的正弦波发生器。
1. 硬件基础与设计思路
STM32F103的DAC模块具有两个独立的12位输出通道,每个通道都能在不需要CPU干预的情况下,通过DMA自动更新输出值。这种架构特别适合周期性波形生成,比如我们要实现的正弦波。
为什么选择DMA+DAC方案?
- CPU资源零占用 :一旦初始化完成,波形生成完全由硬件自动处理
- 高精度定时 :利用定时器触发确保采样间隔绝对精确
- 低抖动输出 :DMA直接搬运数据,避免了软件延迟带来的时序不确定性
关键硬件资源配置:
DAC通道1 -> PA4
DAC通道2 -> PA5
定时器8 -> 触发源
DMA2通道4 -> 数据传输
2. 正弦波表生成方法论
由于STM32的DAC没有内置正弦波发生器,我们需要预先计算一个周期的正弦采样值数组。这个数组的质量直接决定了输出波形的精度和平滑度。
Python生成正弦数组的实用代码:
import numpy as np
def generate_sine_table(length=32, bits=12, vref=3.3):
amplitude = (2**bits - 1) / 2
offset = amplitude
return [int(round(offset + amplitude * np.sin(2*np.pi*i/length)))
for i in range(length)]
# 生成12位精度的32点正弦表
sine_table = generate_sine_table(32, 12)
print(sine_table)
关键参数解析:
| 参数 | 说明 | 典型值 |
|---|---|---|
| 表长度 | 一个周期的采样点数 | 32-256 |
| 分辨率 | DAC的位数 | 12位 |
| 幅度 | 输出电压范围 | 0-3.3V |
提示:表长度越长波形越平滑,但会占用更多内存。32点对于大多数应用已经足够。
3. 完整工程配置详解
让我们拆解初始化代码的每个关键部分,理解各个配置项的作用。
GPIO初始化:
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; // 模拟输入模式
GPIO_Init(GPIOA, &GPIO_InitStructure);
注意:DAC输出引脚必须配置为模拟输入模式,即使它们是输出。
定时器配置:
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_Period = 0x19; // 自动重装载值
TIM_TimeBaseStructure.TIM_Prescaler = 0x00; // 无分频
TIM_TimeBaseInit(TIM8, &TIM_TimeBaseStructure);
TIM_SelectOutputTrigger(TIM8, TIM_TRGOSource_Update);
定时器频率计算公式: $$ f_{TIM} = \frac{f_{CLK}}{(Prescaler + 1) \times (Period + 1)} $$
DMA关键配置解析:
DMA_InitStructure.DMA_PeripheralBaseAddr = DAC_DHR12RD_Address;
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)&DualSine12bit;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_InitStructure.DMA_BufferSize = 32;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
- 循环模式确保波形连续输出
- 内存地址递增实现自动遍历数组
- 外设地址固定为DAC数据寄存器
4. 频率调节与性能优化
输出波形频率由三个因素决定:
- 定时器触发频率
- 正弦表长度
- DMA缓冲区大小
频率计算公式: $$ f_{out} = \frac{f_{TIM}}{N} $$ 其中N为正弦表长度。
实际调节技巧:
- 需要更高频率?减小定时器周期或降低表长度
- 需要更好波形质量?增加表长度但保持相同频率
- 动态调节示例:
// 动态改变定时器周期来调整频率
void set_sine_frequency(uint32_t freq) {
TIM8->ARR = SystemCoreClock / (32 * freq) - 1;
TIM_SetAutoreload(TIM8, TIM8->ARR);
}
常见问题排查指南:
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 无输出 | DMA未启动 | 检查DMA和DAC使能顺序 |
| 波形畸变 | 定时器配置错误 | 验证定时器时钟和分频 |
| 幅度不对 | 数组值范围错误 | 确认数组值在0-4095范围内 |
| 波形不连续 | DMA未循环模式 | 检查DMA_Mode配置 |
5. 双通道同步输出进阶技巧
利用STM32的双DAC特性,我们可以实现相位可调的双通道输出:
// 生成相位差90度的双通道数据
for(int i=0; i<32; i++) {
DualSine12bit[i] = (Sine12bit[i] << 16) | Sine12bit[(i+8)%32];
}
应用场景:
- 正交信号生成
- 三相电机驱动
- 差分信号模拟
6. 实测对比与性能评估
使用示波器观察输出时,重点关注三个指标:
- 频率精度(与计算值偏差)
- 总谐波失真(THD)
- 电压稳定性
典型性能参数:
| 指标 | 32点波形 | 64点波形 |
|---|---|---|
| THD | <5% | <2% |
| 最大频率 | ~100kHz | ~50kHz |
| 内存占用 | 128字节 | 256字节 |
通过合理配置,这个方案可以实现优于1%的频率精度和小于3%的THD,满足大多数测试和测量应用的需求。
更多推荐


所有评论(0)