1. 为什么需要双ADC同步采样?
在嵌入式系统开发中,测量两路同频信号的相位差是个常见需求。比如做超声波流量计时,需要比较发射和接收信号的相位差来计算流速;开发功率分析仪时,要测量电压和电流波形的相位差来计算功率因数。这些场景下,采样同步性直接决定测量精度——如果两路信号采集时间有偏差,算出来的相位差就会包含额外误差。
STM32的ADC模块虽然性能强大,但有个先天限制:无法真正同步采样。因为芯片内部ADC转换器是共享的,即使配置了双ADC模式,本质上还是顺序工作。这就好比餐厅只有一个厨师,虽然接了两份订单(ADC1和ADC2),但只能一份一份做。实测发现,直接启动双ADC采样,两路信号采集时间可能相差数微秒,对于1kHz信号就会引入3~5度的相位误差。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 硬件设计与CubeMX配置
2.1 定时器触发原理
解决同步问题的核心在于用定时器统一触发采样。STM32的定时器可以生成精确的触发信号(TRGO),这个信号能同时送达多个ADC。相当于给两个ADC装了同步启动的"遥控开关",虽然ADC转换仍需时间,但至少启动时刻是严格对齐的。具体实现时要注意:
- 选择支持主模式输出的定时器(如TIM2/TIM3)
- 配置触发输出为更新事件(Update Event)
- ADC触发源选择定时器触发(Timer Trigger)
c复制// CubeMX配置关键步骤:
1. 在TIMx配置中开启"Master Mode" → "Update Event"
2. 在ADC1/ADC2的"Trigger Source"中选择对应定时器
3. 设置ADC的"External Trigger Edge"为上升沿触发
2.2 双ADC的DMA配置
采样数据要通过DMA传输以减少CPU干预。特别要注意:
- 两个ADC使用独立的DMA流(如ADC1用DMA2 Stream0,ADC2用DMA2 Stream2)
- 内存地址设置为32位数组(uint32_t buffer[2][N])
- 开启DMA循环模式避免数据覆盖
c复制// DMA配置示例(以STM32F407为例):
hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
hdma_adc1.Init.Mode = DMA_CIRCULAR;
3. 软件实现关键代码
3.1 启动顺序的玄机
代码执行顺序直接影响同步效果。必须严格遵循:
- 先启动ADC的DMA传输(HAL_ADC_Start_DMA)
- 再使能ADC等待触发(HAL_ADC_Start)
- 最后启动定时器(HAL_TIM_Base_Start)
c复制// 正确启动顺序示例:
HAL_ADC_Start_DMA(&hadc1, adc1_buf, SAMPLES);
HAL_ADC_Start_DMA(&hadc2, adc2_buf, SAMPLES);
HAL_ADC_Start(&hadc1);
HAL_ADC_Start(&hadc2);
HAL_TIM_Base_Start(&htim3); // 最后启动定时器
如果顺序颠倒,可能出现ADC已经启动但未收到触发信号,导致首组数据无效。我在调试超声波流量计时就踩过这个坑,相位差总是跳动,后来用逻辑分析仪抓信号才发现是启动顺序问题。
3.2 FFT相位差计算
采集到数据后,用实数FFT提取相位信息:
- 对两路信号分别做FFT变换
- 在频谱峰值处(信号频率)获取相位角
- 计算相位差并解模糊(unwrap)
c复制// 使用ARM CMSIS-DSP库示例:
arm_rfft_fast_instance_f32 fft_inst;
arm_rfft_fast_init_f32(&fft_inst, FFT_SIZE);
arm_rfft_fast_f32(&fft_inst, adc1_buf, fft1_out, 0);
arm_rfft_fast_f32(&fft_inst, adc2_buf, fft2_out, 0);
float phase1 = atan2f(fft1_out[2*peak_bin+1], fft1_out[2*peak_bin]);
float phase2 = atan2f(fft2_out[2*peak_bin+1], fft2_out[2*peak_bin]);
float phase_diff = (phase2 - phase1) * 180/M_PI;
4. 误差分析与优化技巧
4.1 主要误差来源
实测发现0.1~0.2度的误差主要来自:
- 时钟抖动:定时器和ADC时钟不同步
- 采样间隔偏差:虽然触发同步,但ADC转换时间存在ns级差异
- FFT频谱泄漏:非整周期采样导致
4.2 提升精度的5个技巧
- 时钟同步:配置定时器和ADC使用同一时钟源(如APB2)
- 过采样:提高采样率4倍以上再数字滤波
- 加窗处理:对信号加汉宁窗减少频谱泄漏
- 频率微调:调整采样率使信号周期正好是采样窗口的整数倍
- 温度补偿:高频环境下校准ADC的采样保持时间
c复制// 汉宁窗应用示例:
for(int i=0; i<SAMPLES; i++) {
float window = 0.5f * (1 - cosf(2*M_PI*i/(SAMPLES-1)));
adc1_buf[i] *= window;
adc2_buf[i] *= window;
}
5. 实战案例:超声波流量计
最近做的燃气表项目就用到这个技术。需要测量上下游超声波传感器的相位差来计算气流速度,关键参数:
- 信号频率:1MHz
- 采样率:4MHz(STM32H743的双ADC交替采样)
- 最终精度:±0.05度
硬件上特别注意了PCB布局:
- 两路ADC输入走线等长
- 模拟电源单独滤波
- 信号端接50Ω电阻匹配
调试时发现,当信号幅度低于1V时误差明显增大,后来在ADC前增加了可编程增益放大器(PGA),通过软件动态调整增益,最终在全量程范围内保持了稳定精度。
