
1. 硬件资源配置与模块选型在GD32F303上实现精准数据采集首先要搞定硬件资源的合理分配。我最近在一个工业传感器项目上就用到了这套方案实测下来稳定性相当不错。先说说ADC的选择——这里用的是ADC2模块原因很简单它和定时器4的通道0有硬件级联动查看数据手册可以看到TIMER4_CH0正好能触发ADC2的常规通道转换。DMA通道的选择也有讲究。GD32F303的DMA1有多个通道经过实际测试DMA1_Channel4最适合这个场景。它不仅能自动搬运ADC数据到内存还支持循环模式。这里有个坑要注意DMA传输宽度必须和外设数据宽度匹配ADC数据是12位的但DMA要配置为16位传输否则会丢数据。时钟配置是很多人容易忽略的关键点。我的经验是先把时钟树理清楚系统时钟120MHzAPB2总线时钟60MHzADC时钟设为APB2的6分频10MHz定时器时钟用系统时钟的1分频120MHzstatic void rcu_config(void) { rcu_periph_clock_enable(RCU_GPIOA); rcu_periph_clock_enable(RCU_DMA1); rcu_periph_clock_enable(ADC_PWM_TMER_RCU); rcu_periph_clock_enable(ADC_RCU); rcu_adc_clock_config(RCU_CKADC_CKAPB2_DIV6); }2. 定时器精准触发配置定时器是这里的时间基准要产生精确的10ms周期信号。我用TIMER4实现这个功能配置步骤其实挺有技巧的预分频设为11999这样定时器时钟变成120MHz/(119991)10kHz自动重载值设为100-1产生10kHz/100100Hz的PWM周期10ms通道输出模式选PWM0占空比50%timer_parameter_struct timer_initpara; timer_initpara.prescaler 11999; timer_initpara.period 100-1; timer_init(ADC_PWM_TMER, timer_initpara); timer_channel_output_mode_config(ADC_PWM_TMER, ADC_PWM_CH, TIMER_OC_MODE_PWM0); timer_channel_output_pulse_value_config(ADC_PWM_TMER, ADC_PWM_CH, 50);实际调试时发现个问题如果直接用更新事件触发ADC会引入约1us的抖动。后来改用通道比较事件触发就稳定多了这个细节在手册里可不容易注意到。3. ADC与DMA的协同工作ADC配置有几个关键点必须掌握要开启扫描模式因为我们用到了多通道采样时间设置为55.5个周期这对高阻抗信号源很重要必须使能外部触发和DMA模式adc_special_function_config(ADC_NUM_CH, ADC_SCAN_MODE, ENABLE); adc_regular_channel_config(ADC_NUM_CH, 0, ADC_CHANNEL_0, ADC_SAMPLETIME_55POINT5); adc_external_trigger_config(ADC_NUM_CH, ADC_REGULAR_CHANNEL, ENABLE); adc_dma_mode_enable(ADC_NUM_CH);DMA配置要特别注意内存地址递增外设地址固定为ADC数据寄存器内存地址指向二维数组laser_fre[1024][2]传输数量是1024*2双通道一定要开循环模式不然采集一次就停了dma_init_struct.direction DMA_PERIPHERAL_TO_MEMORY; dma_init_struct.memory_addr (uint32_t)laser_fre; dma_init_struct.memory_inc DMA_MEMORY_INCREASE_ENABLE; dma_init_struct.number LASWER_FRE_NUME*ADC_CHANNEL_NUMER; dma_circulation_enable(DMA1, DMA_CH4);4. 中断处理与数据滤波当DMA传输完1024个数据后约10秒会触发中断。这里有个重要技巧在中断里不要直接处理数据而是设置标志位在主循环里处理。因为中断服务函数应该尽可能短。void DMA1_Channel3_4_IRQHandler(void) { if(dma_interrupt_flag_get(DMA1,DMA_CH4, DMA_INT_FLAG_FTF)) { timer_channel_output_mode_config(ADC_PWM_TMER, ADC_PWM_CH, TIMER_OC_MODE_HIGH); dma_interrupt_flag_clear(DMA1,DMA_CH4, DMA_INT_FLAG_G); Lcd_flag.battery_adc_flag true; } }数据滤波我用的是中位值平均滤波法效果比简单平均好很多。具体实现是对1024个数据排序去掉最高和最低的3个值对剩下的1018个数据求平均uint16_t filter_bat(uint16_t barray[LASWER_FRE_NUME][ADC_CHANNEL_NUMER], uint8_t ifilterlen) { // 排序代码... for(i 3FILER_MID; i ifilterlen-3FILER_MID; i) { sum sum barray[i][0]; } return (sum2); }5. 实际调试中的经验分享在实验室测试时一切正常但现场部署后发现有偶发数据异常。后来发现是电源噪声导致的解决方法有三个ADC电源引脚加10uF0.1uF去耦电容采样时间从55.5周期增加到71.5周期在软件滤波前先做异常值剔除还有个时序问题值得注意定时器、ADC、DMA的启动顺序很重要。正确的顺序应该是初始化所有外设开启DMA开启ADC最后启动定时器如果顺序不对可能会丢失前几个采样数据。我在初期调试时就踩过这个坑后来用逻辑分析仪抓信号才发现问题。对于需要更高精度的场合可以校准ADC的偏移误差。GD32F303提供了自校准功能上电后执行一次校准能显著提高精度adc_enable(ADC_NUM_CH); delay_ms(1); // 等待ADC稳定 adc_calibration_enable(ADC_NUM_CH);6. 性能优化技巧当采样通道增加到4个时发现10ms周期不够用了。通过以下优化解决了问题将ADC时钟从10MHz提升到14MHzAPB2/4使用定时器主从模式用TIMER4触发TIMER5产生第二组PWM在DMA中断中使用内存池管理数据对于需要实时处理数据的场景可以双缓冲技术配置两个1024点的缓冲区DMA在半满和全满时各触发一次中断主程序交替处理两个半缓冲区// 双缓冲配置 dma_init_struct.memory0_addr (uint32_t)buffer1; dma_init_struct.memory1_addr (uint32_t)buffer2; dma_init_struct.memory_burst_width DMA_MEMORY_BURST_SINGLE; dma_init_struct.periph_burst_width DMA_PERIPH_BURST_SINGLE; dma_dual_buffer_mode_enable(DMA1, DMA_CH4);7. 低功耗设计考虑在电池供电设备中使用时我对方案做了低功耗优化采样间隔从10ms调整为可配置1ms-1s不采样时关闭ADC和定时器时钟使用DMA传输完成中断唤醒MCU采样期间将系统时钟降到48MHzvoid enter_low_power_mode(void) { timer_disable(ADC_PWM_TMER); adc_disable(ADC_NUM_CH); rcu_system_clock_config(RCU_CKSYSSRC_PLL48M); __WFI(); // 等待中断唤醒 }这套方案经过半年多的现场运行验证稳定性非常好。最关键的是要确保三点定时器触发信号的稳定性、DMA配置的正确性以及滤波算法的有效性。对于不同的应用场景可以灵活调整采样周期、通道数量和滤波参数。