保姆级教程:用GD32F103的DAC+TIMER+DMA生成正弦波,示波器实测波形稳如老狗
GD32F103实战:DAC+TIMER+DMA正弦波生成全解析
最近在调试一个音频信号发生器项目时,发现不少初学者在使用GD32的DAC功能时都会遇到波形不稳定、配置复杂的问题。今天我就以GD32103C-START开发板为例,手把手带大家实现一个零CPU占用的正弦波发生器。这个方案特别适合需要精确控制波形输出的场景,比如音频设备测试、工业控制信号模拟等。
1. 硬件准备与环境搭建
在开始编码前,我们需要确保硬件连接正确。将开发板的PA4引脚(DAC0输出通道)连接到示波器探头,这是默认的DAC输出引脚。GD32F103系列虽然与STM32F103引脚兼容,但在外设寄存器配置上存在关键差异,这也是很多开发者容易踩坑的地方。
开发环境建议使用Keil MDK或IAR,需要安装GD32的Device Family Pack。这里有个小技巧:务必确认安装的固件库版本与芯片型号匹配。我就曾因为用了旧版库导致DMA配置异常,浪费了半天时间排查。
提示:GD32F103的DAC输出电压范围是0~3.3V,如果发现示波器显示幅值不足,检查开发板的供电电压是否稳定。
2. 核心外设工作原理剖析
2.1 DAC触发机制详解
GD32的DAC支持多种触发方式,我们选择定时器触发以实现精确的波形周期控制。关键点在于:
- 触发源选择:必须配置TIMER6的TRGO输出作为DAC触发信号
- 双缓冲机制:DAC的数据保持寄存器(DHR)与输出寄存器(DOR)的同步时机直接影响波形稳定性
寄存器配置示例:
// 使能DAC时钟 rcu_periph_clock_enable(RCU_DAC); // 初始化DAC dac_deinit(); dac_trigger_disable(DAC0); dac_wave_mode_config(DAC0, DAC_WAVE_DISABLE); dac_output_buffer_enable(DAC0);2.2 定时器精准定时配置
TIMER6作为基础定时器,其更新事件将触发DAC转换。这里有个隐藏坑点:MMC位必须正确设置,否则无法产生稳定的TRGO信号。
关键配置参数计算:
- 定时器时钟:通常为APB1时钟(如108MHz)
- 波形频率 = 定时器频率 / (ARR+1) / 采样点数
配置代码片段:
timer_parameter_struct timer_initpara; // 定时器基础配置 timer_initpara.prescaler = 9; // 108MHz/(9+1)=10.8MHz timer_initpara.alignedmode = TIMER_COUNTER_EDGE; timer_initpara.counterdirection = TIMER_COUNTER_UP; timer_initpara.period = 99; // 10.8MHz/(99+1)=108kHz timer_initpara.clockdivision = TIMER_CKDIV_DIV1; timer_init(TIMER6, &timer_initpara); // 关键MMC位配置 timer_master_output_trigger_source_select(TIMER6, TIMER_TRI_OUT_SRC_UPDATE);2.3 DMA高效数据传输
DMA配置需要特别注意通道映射关系:DAC0只能使用DMA1的Channel2。这是芯片硬件决定的,选错通道会导致传输失败。
地址计算要点:
- 外设地址:DAC0_R12DH = 0x40007400 + 0x08
- 内存地址:正弦波数据数组首地址
- 传输宽度:半字(16位)
DMA初始化代码:
dma_parameter_struct dma_init_struct; dma_deinit(DMA1, DMA_CH2); dma_init_struct.direction = DMA_PERIPHERAL_TO_MEMORY; dma_init_struct.memory_addr = (uint32_t)sin_table; dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE; dma_init_struct.memory_width = DMA_MEMORY_WIDTH_16BIT; dma_init_struct.number = SIN_TABLE_SIZE; dma_init_struct.periph_addr = (uint32_t)&DAC_R12DH(DAC0); dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE; dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_16BIT; dma_init_struct.priority = DMA_PRIORITY_HIGH; dma_init(DMA1, DMA_CH2, &dma_init_struct);3. 正弦波数据生成优化
波形质量很大程度上取决于采样点的数量和精度。推荐使用MATLAB或Python预先生成采样数据:
import numpy as np # 生成12位精度的正弦波表 samples = 256 sin_table = np.sin(2*np.pi*np.arange(samples)/samples) sin_table = np.round((sin_table + 1) * 2047).astype(int) # 输出C语言数组格式 print("const uint16_t sin_table[%d] = {" % samples) for i in range(0, samples, 8): print(" " + ", ".join(f"0x{x:04X}" for x in sin_table[i:i+8]) + ",") print("};")实际项目中还需要考虑:
- 采样点数与波形周期的关系
- 量化误差对THD(总谐波失真)的影响
- 内存对齐对DMA性能的影响
4. 系统集成与调试技巧
将所有模块整合后,启动顺序非常关键:
- 初始化DAC但不使能触发
- 配置DMA但暂停传输
- 启动定时器
- 使能DAC DMA请求
- 最后使能DAC触发
示波器实测时常见问题排查:
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 无输出 | DMA未启动 | 检查DMA通道映射 |
| 波形畸变 | 定时器配置错误 | 确认MMC位设置 |
| 幅值不稳 | 电源噪声 | 添加滤波电容 |
| 周期不准 | ARR计算错误 | 重新计算定时参数 |
调试时建议逐步验证:
- 先用GPIO翻转测试定时器中断频率
- 然后测试DAC静态输出
- 最后启用DMA传输
5. 性能优化进阶技巧
要实现"稳如老狗"的波形输出,还需要注意:
- 内存优化:将sin_table放在CCM内存(如果可用)减少总线冲突
- 时序优化:调整DMA优先级避免被其他外设中断
- 电源优化:独立给模拟部分供电,添加LC滤波
- 抗干扰设计:在DAC输出端添加运放缓冲
对于需要更高精度的场景,可以考虑:
- 使用插值算法增加等效采样率
- 添加后级RC滤波平滑阶梯波形
- 采用双DAC输出差分信号
我在实际项目中发现,当输出频率超过10kHz时,GD32F103的DAC线性度会明显下降。这时可以采用PWM+DAC的组合方案,用PWM生成高频成分,DAC处理低频基准。
