STM32多通道ADC采集与DMA传输实战指南
📅 2026/7/21 4:01:53
👁️ 阅读次数
📝 编程学习
1. 项目背景与核心需求
对于刚接触STM32的开发者来说,ADC多通道采集是个既基础又关键的技能点。我在实际项目中经常遇到需要同时监测多个模拟信号的场景,比如工业控制中的温度、压力、流量三参数采集。传统单通道轮询方式不仅效率低,还会丢失关键数据。而使用扫描模式配合DMA传输,可以真正做到"一劳永逸"——配置完成后,CPU几乎不用干预就能持续获取稳定的采样数据。
本教程要解决的典型场景是:通过STM32的ADC同时采集三路PWM信号的平均电压值。这在电机控制、LED调光等应用中非常常见。比如用PWM控制电机转速时,我们需要实时监测驱动电压的波动情况。直接测量PWM的瞬时值没有意义,必须通过ADC采集多个周期计算平均电压。
2. 硬件设计要点
2.1 引脚分配策略
STM32的ADC通道与GPIO引脚是固定映射关系,以STM32F103C8T6为例:
- ADC1_IN0~ADC1_IN15分别对应PA0~PA7/PB0~PB1/PC0~PC5
- 同一个ADC的不同通道可以同时启用,但不可跨ADC使用(除非使用双ADC模式)
建议在CubeMX中按以下原则配置:
- 优先选择同一GPIO端口的引脚(如PA1,PA2,PA3),简化布线
- 避免与已用的定时器PWM输出引脚冲突
- 长距离传输时选择具有ADC模拟输入功能的引脚(带"ADC"标注)
2.2 外围电路设计
测量PWM平均电压需要RC低通滤波电路,典型参数:
PWM频率=1kHz时: R=10kΩ, C=1μF → 截止频率=1/(2πRC)≈16Hz滤波后的纹波电压应小于ADC的1LSB(3.3V/4096≈0.8mV)
注意:直接测量PWM会导致ADC采样值剧烈波动。我曾在一个无人机项目中犯过这个错误,导致油门信号检测完全失效。
3. 软件配置详解
3.1 CubeMX基础配置
- 在"Analog"标签下启用ADC1
- 勾选需要使用的通道(如IN1,IN2,IN3)
- 参数设置:
- Resolution: 12Bits
- Scan Conversion Mode: Enabled
- Continuous Conversion Mode: Enabled
- DMA Continuous Requests: Enabled
- Number Of Conversion: 3
- 在"DMA Settings"添加DMA通道,选择:
- Mode: Circular
- Data Width: Half Word
3.2 关键代码实现
3.2.1 DMA缓冲区定义
#define ADC_CHANNELS 3 uint16_t adcBuffer[ADC_CHANNELS]; // DMA传输目标地址 void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) { // 每次转换完成自动触发 // 此处可添加数据处理代码 }3.2.2 启动ADC采样
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adcBuffer, ADC_CHANNELS);3.2.3 电压计算与滤波
float getAverageVoltage(uint8_t channel) { static float sum[ADC_CHANNELS] = {0}; static uint16_t count = 0; sum[channel] += adcBuffer[channel] * 3.3f / 4095; count++; if(count >= 100) { // 每100次采样计算一次平均值 float avg = sum[channel] / 100; sum[channel] = 0; count = 0; return avg; } return -1; // 未完成足够采样 }4. 实测中的典型问题
4.1 采样值跳变问题
现象:即使输入电压稳定,ADC读数仍有±3LSB波动 解决方案:
- 在ADC引脚添加0.1μF去耦电容
- 适当增加采样时间(如239.5周期)
- 软件端采用移动平均滤波:
#define FILTER_SIZE 10 float filterBuffer[FILTER_SIZE]; float movingAverage(float newVal) { static uint8_t index = 0; filterBuffer[index++] = newVal; if(index >= FILTER_SIZE) index = 0; float sum = 0; for(uint8_t i=0; i<FILTER_SIZE; i++) { sum += filterBuffer[i]; } return sum / FILTER_SIZE; }4.2 多通道间串扰
现象:通道1的电压变化会影响通道2的读数 根本原因:ADC内部采样保持电容放电不完全 解决方法:
- 在CubeMX中增加通道间延迟(如ADC_TwoSamplingDelay_5Cycles)
- 降低采样频率
- 硬件上在信号源端加入电压跟随器
5. 性能优化技巧
5.1 定时触发采样
通过TIM触发ADC,可实现精确的采样间隔:
// CubeMX配置: // TIM2 -> Trigger Output Selection -> Update Event // ADC1 -> External Trigger Source -> Timer 2 TRGO HAL_TIM_Base_Start(&htim2); // 启动定时器5.2 过采样技术
通过16倍过采样可将有效分辨率提升至14位:
uint32_t sum = 0; for(int i=0; i<16; i++) { while(!HAL_ADC_PollForConversion(&hadc1, 10)); sum += HAL_ADC_GetValue(&hadc1); } uint16_t result = sum >> 2; // 除以16后再左移2位6. 完整项目示例
以STM32F103测量三路PWM占空比为例:
硬件连接:
- PWM1 -> PA0 (ADC1_IN0) + 10kΩ/1μF滤波
- PWM2 -> PA1 (ADC1_IN1) + 10kΩ/1μF滤波
- PWM3 -> PA2 (ADC1_IN2) + 10kΩ/1μF滤波
核心代码逻辑:
while(1) { float duty1 = getAverageVoltage(0) / 3.3 * 100; float duty2 = getAverageVoltage(1) / 3.3 * 100; float duty3 = getAverageVoltage(2) / 3.3 * 100; printf("Duty: %.1f%%, %.1f%%, %.1f%%\r\n", duty1, duty2, duty3); HAL_Delay(100); }实测发现,当PWM频率高于5kHz时,需要减小RC时间常数(如1kΩ+0.1μF),但会引入更多噪声。这时可以采用硬件PWM积分电路,或者使用STM32的HRTIM配合ADC实现硬件同步采样。
编程学习
技术分享
实战经验