应用笔记 / 经验分享 · 2026年3月1日

STM32+CubeMX 配置ADC配合DMA多通道多次采样并自动取平均

一、推荐参数

先用这一组,比较稳:

  • Oversampling Ratio = 8x

  • Right Bit Shift = 3

这等价于:

  • 每个通道内部采 8 次

  • 累加后右移 3 位(即除以 8)

  • 输出结果仍保持接近 12 位量程

也就是说,你拿到的就是“8 次平均值”。STM32L4 的硬件过采样支持把输出当作 averaging function 来用。

如果你更想抑制噪声,可改成:

  • 16x + Shift 4

但总转换时间会进一步增加。


二、CubeMX 配置

下面按 “软件触发一次,采完 6 个通道(每个都做过采样),DMA 搬 6 个值” 来配。

1)ADC1 基本参数

ADC1 > Parameter Settings 里:

  • Scan Conversion Mode:Enable

  • Nbr Of Conversion:6

  • 把你的 6 个通道分别设成 Rank 1 ~ Rank 6

  • Resolution:12 bits

  • Data Alignment:Right

  • Continuous Conversion ModeDisable

  • Discontinuous Conversion Mode:Disable

  • External Trigger Conversion Source:Software Start

  • DMA Continuous RequestsDisable

  • End Of Conversion SelectionEnd of Sequence

说明:

  • 你要的是“触发一次,拿一组”,所以 Continuous 要关

  • DMA 用 Normal 模式,一次搬完 6 个结果就停

  • EOC 选 End of Sequence,这样一整组 6 通道完成后再作为一次完整结果处理,更省心。STM32L4 的 ADC 支持 single-shot 或 continuous 的单通道/扫描模式,规则组数据也可通过 DMA 获取。


2)每个通道的 Sampling Time

在 6 个通道各自的 Rank 设置里,给一个相对稳妥的采样时间:

  • 若信号源阻抗不高:24.5 cycles47.5 cycles

  • 若是分压电阻较大 / 传感器阻抗高:建议 92.5 cycles 或更长

STM32L4 允许每个通道单独设置采样时间;高阻信号需要更长采样时间。


3)打开 Oversampling

在 ADC1 的参数页里,找到 Oversampling / Regular Oversampling(不同 CubeMX 版本位置略有不同),设置:

  • Oversampling Mode:Enable

  • Oversampling Ratio8x

  • Right Bit Shift3 bits

  • Triggered Oversampling Mode / Regular Oversampling Mode

    • 若有这个选项,建议先用 Continued mode(连续过采样)

    • 也就是一次规则转换内,把 8 次子采样连续完成,再给出一个结果

说明:

  • L4 的硬件过采样支持 2/4/8/16/32/64/128/256 倍。

  • 8x + shift 3 是最像“硬件平均 8 次”的配置。


4)DMA 配置

ADC1 > DMA Settings 添加一个 DMA 通道:

  • Direction:Peripheral to Memory

  • ModeNormal

  • Peripheral Increment:Disable

  • Memory Increment:Enable

  • Peripheral Data Width:Half Word

  • Memory Data Width:Half Word

  • Priority:Low / Medium 都可以

因为 ADC 数据寄存器是 16 位,DMA 用 Half Word 最合适。STM32 的 ADC 结果寄存器是 16 位,数据可由 DMA 取走。


三、CubeMX 生成代码关键检查

不同 HAL 版本字段名略有差异,但核心会类似下面这样。

MX_ADC1_Init() 里关键部分(示例)

hadc1.Instance = ADC1;
hadc1.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV2; // 或按你的需求
hadc1.Init.Resolution = ADC_RESOLUTION_12B;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE;
hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV;
hadc1.Init.LowPowerAutoWait = DISABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.NbrOfConversion = 6;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
hadc1.Init.DMAContinuousRequests = DISABLE;
hadc1.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;
hadc1.Init.OversamplingMode = ENABLE;

然后还有 Oversampling 结构体(字段名按你当前 HAL 为准):

hadc1.Init.Oversampling.Ratio = ADC_OVERSAMPLING_RATIO_8;
hadc1.Init.Oversampling.RightBitShift = ADC_RIGHTBITSHIFT_3;
hadc1.Init.Oversampling.TriggeredMode = ADC_TRIGGEREDMODE_SINGLE_TRIGGER;
hadc1.Init.Oversampling.OversamplingStopReset = ADC_REGOVERSAMPLING_CONTINUED_MODE;

这里最后两个枚举名在不同 HAL 版本中可能略有差异;CubeMX 生成什么就以什么为准
你只需要保证逻辑上是:一次启动后,每个规则转换会连续完成 8 次子采样并输出平均值


四、6 通道配置示例(Rank 1~6)

这个你前面已经配好了,只确认保留:

ADC_ChannelConfTypeDef sConfig = {0};

sConfig.SamplingTime = ADC_SAMPLETIME_47CYCLES_5;
sConfig.SingleDiff = ADC_SINGLE_ENDED;
sConfig.OffsetNumber = ADC_OFFSET_NONE;
sConfig.Offset = 0;

/* Rank 1 */
sConfig.Channel = ADC_CHANNEL_1;
sConfig.Rank = ADC_REGULAR_RANK_1;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);

/* Rank 2 */
sConfig.Channel = ADC_CHANNEL_2;
sConfig.Rank = ADC_REGULAR_RANK_2;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);

/* … 一直到 Rank 6 */

STM32L4 的 sequencer 可按任意顺序转换多达 16 个通道;你这里只用 6 个没问题。


五、应用代码模板(一次触发,DMA 收 6 个“已平均值”)

1)全局变量

#include “main.h”

extern ADC_HandleTypeDef hadc1;

#define ADC_CH_NUM 6

uint16_t adc_avg_buf[ADC_CH_NUM];
volatile uint8_t adc_done = 0;


2)初始化时先做一次校准

STM32L4 官方建议在应用中做 ADC 校准;当参考电压变化超过 10%(如复位、某些低功耗恢复)时建议重新校准。

void ADC1_AppInit(void)
{
HAL_ADCEx_Calibration_Start(&hadc1, ADC_SINGLE_ENDED);
}

3)启动一次采样

HAL_StatusTypeDef ADC1_ScanOnce_Oversampled_Start(void)
{
adc_done = 0;
return HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_avg_buf, ADC_CH_NUM);
}

这里的 adc_avg_buf[0..5] 不是“原始单次值”,而是:

  • Rank1 通道:内部 8 次平均后的值

  • Rank2 通道:内部 8 次平均后的值

  • Rank6 通道:内部 8 次平均后的值


4)DMA 完成回调

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
if (hadc->Instance == ADC1)
{
adc_done = 1;
HAL_ADC_Stop_DMA(&hadc1);
}
}

5)使用方法

if (ADC1_ScanOnce_Oversampled_Start() == HAL_OK)
{
// 等待 adc_done 或在主循环里异步判断
}

主循环里:

if (adc_done)
{
adc_done = 0;

// adc_avg_buf[0] -> Rank1 通道的平均值
// adc_avg_buf[1] -> Rank2
// …
// adc_avg_buf[5] -> Rank6
}


六、这时“单次采样时间”会怎么变

这是你最容易忽略的点。

如果你原来单通道一次转换耗时近似是:

Tone=Tsample+TconvertT_{one} = T_{sample} + T_{convert}

那打开 8x oversampling 后,每个通道大致会变成:

Tone_os≈8×(Tsample+Tconvert)T_{one\_os} \approx 8 \times (T_{sample} + T_{convert})

所以 整组 6 通道 的总时间也大约乘以 8。STM32L4 的硬件过采样就是先做多次采样并累加,再输出给 DMA。

举例(粗略):

  • 若原来 6 通道一轮约 20µs

  • 开 8x 后,可能接近 160µs

所以如果你有“50µs 内完成”的硬限制,8x 可能就太慢了;那就改成:

  • 2x + shift1

  • 或 4x + shift2


七、最常见的坑

1)开了 Oversampling,却还按“原采样速度”估算

这是最常见误判。过采样会显著拉长转换时间。

2)DMA 长度还写成 6*N

模板 B 下,DMA 长度就是 6,不是 6×8
因为多次采样在 ADC 内部已经处理完了。

3)采样时间太短

如果前端源阻抗偏大(比如几十 kΩ 分压),哪怕开了过采样,采样电容本身还没充稳,结果照样漂。
这时应优先增加 Sampling Time,而不是盲目加大 Oversampling Ratio。

4)误以为分辨率一定“真实提高”

过采样确实能改善随机噪声、实现平均/基本滤波,L4 在一定条件下可把 12 位输出扩展到 16 位表现;但这依赖噪声特性和输入信号条件,不是“无条件白捡 4 位有效精度”。