深入解析GD32 DMA握手机制:DAC正弦波数据传输出错排查指南
当你在GD32平台上尝试用DAC+DMA+TIMER生成正弦波时,是否遇到过波形断裂、数据错位或频率异常的问题?这往往源于对DMA握手机制的理解偏差。本文将从一个实际调试案例切入,带你拆解DMA与DAC的交互细节。
1. 问题现象与根源定位
最近在调试GD32F103的DAC正弦波输出时,遇到了一个典型现象:示波器显示的波形周期性出现"台阶式"跳变,本该平滑的曲线在某些点突然跃升。通过逻辑分析仪抓取DMA传输时序后发现,DAC保持寄存器获取的数据与实际内存中的正弦波数组不符。
常见异常现象对照表:
| 现象描述 | 可能原因 | 验证方法 |
|---|---|---|
| 波形幅值正确但频率减半 | TIMER重载值计算错误 | 检查ARR寄存器配置 |
| 波形出现周期性畸变 | DMA传输地址未对齐 | 检查内存地址&数组类型 |
| 只有固定电平输出 | DMA通道映射错误 | 核对DMA1_Channel2配置 |
| 随机数据点跳变 | 外设地址计算错误 | 检查DAC_DHRx寄存器偏移量 |
提示:使用J-Link调试时,可在DMA传输完成中断设置断点,通过Memory窗口实时查看DAC_DHR寄存器的值。
2. DMA握手机制深度解析
2.1 从TIMER到DAC的触发链条
整个数据传输的触发源头是TIMER的更新事件。当计数器达到自动重装载值(ARR)时:
- TIMERx_CNT == TIMERx_ARR
- 产生更新事件(UEV)
- 触发主模式输出(TIMERx_TRGO)
- DAC检测到外部触发信号
- DAC使能DMA请求(DMAEN=1)
// 关键配置代码示例 timer_auto_reload_value_config(TIMER6, 72-1); // 设置ARR值 timer_master_output_trigger_source_select(TIMER6, TIMER_TRI_OUT_SRC_UPDATE); dac_trigger_source_config(DAC0, DAC_TRIGGER_T6_TRGO); dac_trigger_enable(DAC0);2.2 DMA1_Channel2的独占性
为什么必须是DMA1的Channel2?查看GD32参考手册的DMA映射表会发现:
- DAC0对应DMA1_Channel2
- DAC1对应DMA1_Channel3
通道配置要点:
- 外设地址设为DAC_DHRx寄存器地址(如0x40007408)
- 内存地址指向正弦波数组首地址
- 传输宽度需匹配数据格式(12位右对齐为16位)
dma_parameter_struct dma_init; dma_struct_para_init(&dma_init); dma_init.periph_addr = (uint32_t)&DAC_R12DH(DAC0); dma_init.memory_addr = (uint32_t)sin_wave; dma_init.direction = DMA_PERIPH; dma_init.number = 256; // 波形点数 dma_init.periph_inc = DMA_PERIPH_INCREASE_DISABLE; dma_init.memory_inc = DMA_MEMORY_INCREASE_ENABLE; dma_init.periph_width = DMA_PERIPHERAL_WIDTH_16BIT; dma_init.memory_width = DMA_MEMORY_WIDTH_16BIT; dma_init.priority = DMA_PRIORITY_HIGH; dma_init.circular_mode = DMA_CIRCULAR_MODE_ENABLE; // 循环模式 dma_init(DMA1, DMA_CH2, &dma_init);3. 典型错误案例分析
3.1 外设地址计算错误
曾遇到一个案例:工程师直接将0x40007400作为DMA目标地址,导致DAC获取的是控制寄存器值而非波形数据。正确的DHR寄存器地址计算:
DAC0_R12DH地址 = DAC0基地址 + 偏移量 = 0x40007400 + 0x08 = 0x40007408地址验证技巧:
- 在调试器中输入
(uint32_t)&DAC_R12DH(DAC0)查看实际值 - 对比手册中的寄存器映射表
- 通过Memory窗口观察写入值
3.2 传输宽度不匹配
当正弦波数组定义为uint8_t类型但DMA配置为16位传输时,会导致相邻数据合并传输。例如:
内存数据:[0x12, 0x34, 0x56, 0x78]实际传输:0x3412,0x7856(小端模式)
解决方案:
- 确保数组类型与DMA宽度一致
- 使用
__attribute__((aligned(4)))保证内存对齐
4. 高级调试技巧
4.1 使用断点验证传输时序
在DMA传输完成中断(TCIF)设置断点,检查:
- DMA_CNDTR寄存器剩余计数
- DAC_DOR寄存器当前输出值
- 逻辑分析仪抓取TIMER_TRGO和DAC_OUT时序
4.2 频率精度优化公式
实际输出频率计算公式:
f_out = f_timer / (ARR + 1) / N其中:
- f_timer:TIMER时钟源频率
- ARR:自动重装载值
- N:正弦波一个周期的点数
示例计算: 当f_timer=72MHz,ARR=71,N=256时:
f_out = 72,000,000 / 72 / 256 ≈ 3906.25Hz4.3 动态波形切换方案
通过双缓冲技术实现波形无缝切换:
- 配置两个内存区域:WaveA和WaveB
- 在DMA半传输中断(HTIF)和传输完成中断(TCIF)中切换内存地址
- 使用DMA_MemoryTargetConfig()函数动态修改目标地址
void DMA1_Channel2_IRQHandler(void) { if(dma_interrupt_flag_get(DMA1, DMA_CH2, DMA_INT_FLAG_HT)) { dma_memory_address_config(DMA1, DMA_CH2, (uint32_t)wave_b); } if(dma_interrupt_flag_get(DMA1, DMA_CH2, DMA_INT_FLAG_TC)) { dma_memory_address_config(DMA1, DMA_CH2, (uint32_t)wave_a); } dma_interrupt_flag_clear(DMA1, DMA_CH2, DMA_INT_FLAG_G); }调试时发现,在波形切换瞬间偶尔会出现毛刺,通过示波器触发模式捕获到这是由DMA地址重配置期间的短暂延迟导致的。解决方法是在切换前短暂关闭DMA使能:
dma_channel_disable(DMA1, DMA_CH2); dma_memory_address_config(DMA1, DMA_CH2, new_addr); dma_channel_enable(DMA1, DMA_CH2);