STM32 ADC 多通道扫描 + DMA:采样值跳变的三个常见坑
引言
STM32 的 ADC 多通道扫描配合 DMA 是嵌入式采集的经典组合:CPU 零干预,数据自动搬运。但很多人在第一次用这个组合时都会遇到同一个症状:采样值毫无规律地跳变,有时甚至通道之间的数据互相"串台"。这篇文章结合实际调试经验,总结三个最常见的坑及解决办法。
坑一:采样时间不足,通道之间互相串扰
现象:用扫描模式连续采集多个通道,比如 PA0 接电位器、PA1 接温度传感器,发现 PA1 的读数会跟着 PA0 的电压一起漂。
原因:ADC 内部的采样保持电容(C_ADC)在切换通道后,需要一定的采样时间(Sampling Time)对电容充电到新通道的电压。如果信号源内阻较大(比如 10k 以上的分压电阻),而采样时间只设了最短的几 cycles,电容还没充到目标电压就进入转换阶段,读到的就是上一个通道的"残留"。
解决办法:
1. 根据信号源内阻估算需要的采样时间。信号源内阻 R 越大,采样时间就要设得越长。参考公式:t_s ≥ (R_source + R_ADC) × C_ADC × ln(2^(N+1)),其中 N 是分辨率位数。
2. 实践中直接把 ADC_SampleTime 设大一档,比如从 ADC_SAMPLETIME_15CYCLES 改成 ADC_SAMPLETIME_480CYCLES,再观察跳变是否消失。
3. 信号源端加电压跟随器(运放 buffer)是治本的办法,把源内阻降到几十欧。
关键代码(HAL 配置采样时间):
ADC_ChannelConfTypeDef sConfig = {0};
sConfig.Channel = ADC_CHANNEL_1;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_480CYCLES; // 内阻大时不要吝啬采样时间
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
坑二:DMA 循环模式下,半传输中断与数据对齐没理顺
现象:开了 DMA 循环模式 + 半传输中断(HT)和传输完成中断(TC)做双缓冲,发现数据错位:通道 0 的数据跑到了通道 1 的槽位,或者每轮数据整体偏移。
原因有三类:
1. 外设/内存数据宽度没对齐。ADC 数据寄存器是 16 位,如果 DMA 设成了字节(Byte)传输,一个采样会被拆成两次搬运,通道顺序全乱。
2. 缓冲区长度不是"通道数 × 采样点数"的整数关系。比如 3 个通道、每次采 100 点,DMA 缓冲区应该是 uint16_t buf[300],NDTR 设 300;设错一个数,半传输中断的位置就错位。
3. 在中断里处理数据时读写了 DMA 正在写的那一半缓冲,造成撕裂。
解决办法:
- DMA 外设数据宽度和内存数据宽度都设为 HalfWord(16 位),与 ADC_DR 对齐。
- 严格按 通道数 × 每通道点数 计算缓冲区长度和 NDTR。
- 在 HT 中断里只处理前半缓冲,在 TC 中断里只处理后半缓冲,处理函数里不要做耗时操作,只做搬运/标记。
关键代码:
#define ADC_CH_NUM 3
#define ADC_SMP_NUM 100
uint16_t adcBuf[ADC_CH_NUM * ADC_SMP_NUM];
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adcBuf, ADC_CH_NUM * ADC_SMP_NUM);
void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc)
{
// 只处理前半:adcBuf[0 .. 149]
process_data(adcBuf, ADC_CH_NUM * ADC_SMP_NUM / 2);
}
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
// 只处理后半:adcBuf[150 .. 299]
process_data(&adcBuf[ADC_CH_NUM * ADC_SMP_NUM / 2], ADC_CH_NUM * ADC_SMP_NUM / 2);
}
坑三:参考电压不稳,采样值整体漂移
现象:输入电压明明是稳定的直流,采样值却在几十个 LSB 范围内来回跳;用手摸一下板子,数值变化更明显。
原因:ADC 的参考电压就是 VDDA(很多 STM32 型号没有独立 VREF+ 引脚,直接用 VDDA 做基准)。如果 VDDA 上叠加了数字电路的开关噪声,或者 VDDA 本身随负载波动,那么"尺子"在抖,量出来的数自然跳。
解决办法:
1. VDDA 引脚就近接 100nF + 1uF(或 4.7uF)去耦电容到地,走线尽量短,这是数据手册的硬性要求。
2. 模拟地和数字地单点连接,避免大电流地线流过模拟地。
3. 怀疑参考电压漂移时,可以打开内部参考电压通道(VREFINT)一起扫描采集,用 VREFINT 的理论值反推当前真实的 VDDA,做软件校准:
VDD_measured = 3.3 × VREFINT_CAL / VREFINT_DATA
其中 VREFINT_CAL 是出厂校准值(存在系统存储区),VREFINT_DATA 是当前采到的 VREFINT 原始值。
关键代码(软件校准思路):
// 使能内部参考电压通道并加入扫描序列后:
uint16_t vrefint_data = adcBuf[VREFINT_INDEX]; // 本轮采到的 VREFINT 原始值
uint16_t vrefint_cal = *(__IO uint16_t*)0x1FFF7A2A; // 以 F103 为例的校准值地址
float vdda = 3.3f * vrefint_cal / vrefint_data; // 当前真实 VDDA
// 再用 vdda 把其他通道的原始值换算成电压
float voltage_ch0 = (float)adcBuf[0] / 4095.0f * vdda;
总结
多通道扫描 + DMA 的采样跳变,九成问题出在这三处:采样时间给够、DMA 宽度与长度算对、参考电压保持干净。排查时建议按这个顺序逐一验证:先把采样时间拉到最大看跳变是否消失,再检查 DMA 配置,最后看电源。希望这篇文章能帮你少熬几个调试的夜晚。
- 点赞
- 收藏
- 关注作者
评论(0)