ADC(模数转换器)是嵌入式系统中连接模拟世界与数字世界的关键桥梁。在工业控制、环境监测、医疗设备等场景中,经常需要同时采集多个传感器的模拟信号。STM32系列MCU内置的多通道ADC配合HAL库,为我们提供了高效可靠的解决方案。
这个项目将带你深入理解STM32 HAL库中多通道ADC的轮询采集实现。不同于单通道采集,多通道模式需要考虑通道切换时序、采样周期配置、数据对齐等关键问题。我会基于STM32F4系列芯片,分享一个经过生产环境验证的稳定方案。
典型的12位精度ADC多通道采集电路需要注意:
以采集4路0-3.3V模拟信号为例:
code复制CH0 -> PA0 (温度传感器输出)
CH1 -> PA1 (压力传感器输出)
CH2 -> PA2 (光照强度输出)
CH3 -> PA3 (电池电压检测)
ADC参数设置:
通道参数配置示例:
| Channel | Rank | Sampling Time |
|---|---|---|
| CH0 | 1 | 480 Cycles |
| CH1 | 2 | 480 Cycles |
| CH2 | 3 | 480 Cycles |
| CH3 | 4 | 480 Cycles |
注意:采样周期需要根据信号源阻抗调整。对于输出阻抗>10kΩ的信号源,建议使用480周期采样时间。
c复制ADC_HandleTypeDef hadc1;
void ADC_Init(void)
{
hadc1.Instance = ADC1;
hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
hadc1.Init.Resolution = ADC_RESOLUTION_12B;
hadc1.Init.ScanConvMode = ENABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 4;
hadc1.Init.DMAContinuousRequests = DISABLE;
hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV;
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
// 配置通道序列
ADC_ChannelConfTypeDef sConfig = {0};
sConfig.Channel = ADC_CHANNEL_0;
sConfig.Rank = 1;
sConfig.SamplingTime = ADC_SAMPLETIME_480CYCLES;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
// 重复上述步骤配置CH1-CH3...
}
多通道轮询采集的核心在于正确处理转换序列和状态标志:
c复制#define ADC_CHANNEL_COUNT 4
uint16_t adc_values[ADC_CHANNEL_COUNT];
void ADC_ReadValues(void)
{
// 启动ADC转换
if (HAL_ADC_Start(&hadc1) != HAL_OK) {
return;
}
// 等待所有通道转换完成
if (HAL_ADC_PollForConversion(&hadc1, 10) != HAL_OK) {
HAL_ADC_Stop(&hadc1);
return;
}
// 按顺序读取各通道数据
for(int i=0; i<ADC_CHANNEL_COUNT; i++) {
adc_values[i] = HAL_ADC_GetValue(&hadc1);
// 最后一个通道不需要再次等待
if(i < ADC_CHANNEL_COUNT-1) {
if(HAL_ADC_PollForConversion(&hadc1, 10) != HAL_OK) {
break;
}
}
}
HAL_ADC_Stop(&hadc1);
}
多通道ADC的采样间隔由以下因素决定:
计算公式:
code复制总采样时间 = Σ(各通道采样周期) + 通道数×15 + (通道数-1)×2
以4通道480周期采样为例:
code复制总时间 = (480+15)×4 + 3×2 = 1986 ADC时钟周期
当ADC时钟为30MHz时,完整扫描耗时约66.2μs。
参考电压滤波:
软件滤波方案:
c复制#define SAMPLE_TIMES 16
uint16_t ADC_GetAverage(uint8_t channel)
{
uint32_t sum = 0;
for(int i=0; i<SAMPLE_TIMES; i++) {
ADC_ReadValues();
sum += adc_values[channel];
HAL_Delay(1); // 降低采样相关性
}
return (sum + SAMPLE_TIMES/2) / SAMPLE_TIMES; // 四舍五入
}
c复制void ADC_ReadSafe(uint8_t channel)
{
if(channel >= ADC_CHANNEL_COUNT) {
return 0;
}
ADC_ReadValues();
// 检测异常值(超过参考电压范围)
if(adc_values[channel] > 0xFF0) {
HAL_ADC_Stop(&hadc1);
ADC_Init(); // 重新初始化ADC
return 0;
}
return adc_values[channel];
}
c复制void ADC_ReadWithTimeout(void)
{
__HAL_IWDG_RELOAD_COUNTER(&hiwdg);
if(HAL_ADC_Start(&hadc1) != HAL_OK) {
return;
}
uint32_t tickstart = HAL_GetTick();
while(HAL_ADC_PollForConversion(&hadc1, 10) != HAL_OK) {
if(HAL_GetTick() - tickstart > 100) {
HAL_ADC_Stop(&hadc1);
return;
}
}
// ...读取数据流程
}
c复制void ADC_LowPowerRead(void)
{
// 唤醒前配置IO状态
HAL_GPIO_WritePin(SENSOR_PWR_GPIO_Port, SENSOR_PWR_Pin, GPIO_PIN_SET);
HAL_Delay(10); // 等待传感器稳定
ADC_ReadValues();
// 读取后关闭外围电路
HAL_GPIO_WritePin(SENSOR_PWR_GPIO_Port, SENSOR_PWR_Pin, GPIO_PIN_RESET);
HAL_ADC_Stop(&hadc1);
}
c复制void ADC_AdjustSpeed(uint8_t needHighSpeed)
{
if(needHighSpeed) {
hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV2;
} else {
hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV8;
}
HAL_ADC_Init(&hadc1);
}
对于需要更高采样率或更多通道的场景,可以启用STM32的多ADC模式:
交替触发模式:
交织模式:
配置示例:
c复制// 在CubeMX中启用ADC1和ADC2的同步模式
// 使用TIM2触发转换
void MultiADC_Init(void)
{
// ADC1配置(主ADC)
hadc1.Instance = ADC1;
hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T2_TRGO;
// ADC2配置(从ADC)
hadc2.Instance = ADC2;
hadc2.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T2_TRGO;
// 定时器2配置
htim2.Instance = TIM2;
htim2.Init.Prescaler = 84-1; // 1MHz
htim2.Init.Period = 100-1; // 10kHz采样率
HAL_TIM_Base_Start(&htim2);
// 启动ADC
HAL_ADC_Start(&hadc1);
HAL_ADC_Start(&hadc2);
}