## 一、先理解STM32 ADC多通道的几种工作模式 STM32的ADC支持**规则组**和**注入组**两类通道。规则组类似于常规轮询,最多16个通道;注入组类似中断优先,可插入到规则转换中。多通道采样最常用的是**规则组+扫描模式**,配合DMA可以无CPU干预地完成所有通道的循环采集。 关键概念: - **扫描模式(Scan Mode)**:自动按顺序转换规则组中的所有通道。 - **不连续模式(Discontinuous Mode)**:每次触发只转换指定数量的通道,常用于节省功耗或配合定时器分批次采样。 - **采样时间**:影响ADC采样电容的充电时间,对高阻抗信号源尤其重要。 - **校准(Calibration)**:STM32片内ADC自带校准功能,用于消除电容匹配误差。 ## 二、配置步骤(基于STM32CubeMX + HAL库) 1. **选择ADC实例**:在CubeMX中启用ADC1,将需要的通道(如CH0、CH1、CH2)配置为规则通道,并使能扫描模式。 2. **设置转换长度**:Number Of Conversion = 3,表示规则组中有3个通道。 3. **配置采样时间**:根据信号源内阻调整,通常设置55.5或84周期(即ADC时钟下)。若信号源高阻,建议加大到239.5周期。 4. **开启DMA**:将ADC1的DMA请求添加到循环模式,外设地址为ADC->DR,内存地址为自定义数组。 5. **ADC校准**:在初始化后调用HAL_ADCEx_Calibration_Start()。注意此函数会阻塞一段时间,建议放在启动后主循环前。 ## 三、完整代码示例(STM32F1 / F4系列通用) 以下代码适用于STM32F4系列,使用ADC1的CH0~CH2采样三个模拟信号,DMA循环传输到数组,主循环中计算滑动平均滤波结果。 ```c // adc_config.h #define ADC_CH_NUM 3 extern uint16_t adc_raw[ADC_CH_NUM]; extern float adc_filtered[ADC_CH_NUM]; void ADC_Init(void); void ADC_Filter(void); // adc_config.c #include "adc_config.h" #include "main.h" static ADC_HandleTypeDef hadc1; static DMA_HandleTypeDef hdma_adc1; uint16_t adc_raw[ADC_CH_NUM]; float adc_filtered[ADC_CH_NUM]; #define FILTER_WINDOW 8 static uint16_t filter_buf[ADC_CH_NUM][FILTER_WINDOW]; static uint8_t filter_index = 0; void ADC_Init(void) { __HAL_RCC_ADC1_CLK_ENABLE(); __HAL_RCC_DMA2_CLK_ENABLE(); // 配置DMA(ADC1的DMA请求映射到DMA2 Stream0) hdma_adc1.Instance = DMA2_Stream0; hdma_adc1.Init.Channel = DMA_CHANNEL_0; hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY; hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE; hdma_adc1.Init.MemInc = DMA_MINC_ENABLE; hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; hdma_adc1.Init.Mode = DMA_CIRCULAR; hdma_adc1.Init.Priority = DMA_PRIORITY_HIGH; HAL_DMA_Init(&hdma_adc1); __HAL_LINKDMA(&hadc1, DMA_Handle, hdma_adc1); // ADC1配置 hadc1.Instance = ADC1; hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4; hadc1.Init.Resolution = ADC_RESOLUTION_12B; hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE; hadc1.Init.ContinuousConvMode = ENABLE; // 连续采样 hadc1.Init.DiscontinuousConvMode = DISABLE; hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE; hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; hadc1.Init.NbrOfConversion = ADC_CH_NUM; HAL_ADC_Init(&hadc1); // 配置三个规则通道 ADC_ChannelConfTypeDef sConfig = {0}; sConfig.SamplingTime = ADC_SAMPLETIME_84CYCLES; // 根据信号源内阻调整 sConfig.Channel = ADC_CHANNEL_0; sConfig.Rank = 1; HAL_ADC_ConfigChannel(&hadc1, &sConfig); sConfig.Channel = ADC_CHANNEL_1; sConfig.Rank = 2; HAL_ADC_ConfigChannel(&hadc1, &sConfig); sConfig.Channel = ADC_CHANNEL_2; sConfig.Rank = 3; HAL_ADC_ConfigChannel(&hadc1, &sConfig); // 校准 HAL_ADCEx_Calibration_Start(&hadc1); // 启动ADC并开启DMA HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_raw, ADC_CH_NUM); } // 滑动平均滤波(每通道) void ADC_Filter(void) { filter_index = (filter_index + 1) % FILTER_WINDOW; for (int i = 0; i < ADC_CH_NUM; i++) { filter_buf[i][filter_index] = adc_raw[i]; uint32_t sum = 0; for (int j = 0; j < FILTER_WINDOW; j++) { sum += filter_buf[i][j]; } adc_filtered[i] = (float)sum / FILTER_WINDOW; } } ``` ## 四、校准注意事项 - STM32的ADC校准适用于所有转换模式,但**在连续扫描+DMA模式下,校准不会影响实时数据流**,需在启动转换前完成。 - 有些系列(如F4)校准前需要先将ADC使能,且等待稳定时间。HAL库函数已封装,直接调用即可。 - 若使用不同参考电压或环境温度变化大,建议定期重新校准。 ## 五、滤波策略:硬件过采样 vs 软件滤波 - **硬件过采样**:部分STM32支持过采样(oversampling),通过硬件累加+移位提高有效分辨率,适合低噪声信号。但每个通道会增加转换时间,且需要精确配置。 - **软件滤波**:灵活多样,滑动平均、中值滤波、IIR低通滤波等。对于工频干扰(50/60Hz),可结合定时器触发与整数周期采样。 - 推荐做法:先使用硬件过采样(如果支持)或增加采样时间降低噪声,再叠加简单的滑动平均或移动平均,计算代价小。 ## 六、常见问题与注意事项 - **规则组转换序列溢出**:如果NbrOfConversion设置小于实际配置的Rank数,会漏掉通道。务必保证两者相等。 - **DMA数据错位**:当ADC通道转换顺序与DMA缓冲写入不匹配时,会让数据张冠李戴。必须保持通道顺序和数组索引一致。 - **注入组冲突**:如果同时使用注入组且优先级高于规则组,会导致规则组转换被中断,时间轴抖动。若非必要,建议关闭注入组。 - **高阻抗信号**:采样时间过短会导致充电不足,产生非线性误差。可使用运放跟随器或延长采样周期。 - **参考电压噪声**:VREF+引脚上必须接高质量去耦电容,否则会影响所有通道的转换精度。 - **多ADC同步**:如果需要同时采样多个通道,可考虑多ADC同步模式(如使用ADC1和ADC2),但要注意外设映射冲突。 ## 七、总结 STM32 ADC多通道采样的核心在于正确配置扫描模式、通道顺序和采样时间,并利用DMA卸载CPU。校准能消除固定偏移误差,而滤波则是抑制噪声的有效手段。实际项目中应根据信号特性和实时性要求,灵活组合硬件过采样、软件滤波和触发方式。希望本文的示例与经验能帮助你在开发中少走弯路。