STM32 多路 ADC 扫描 + 温漂校准:高精度电压测量实战指南
👁 1 阅读 · 2026-08-17 · STM32 ADC
在工业控制和传感器采集场景中,STM32 的 ADC 常被用于多路电压测量,但裸机下的采样结果往往受参考电压波动、温度漂移和通道串扰影响,精度难以保证。本文深入剖析 STM32 多路 ADC 扫描模式的工作原理,结合 DMA 传输与软件温漂校准算法,给出一个可复用的高精度测量方案。通过配置内部温度传感器和 VREFINT 通道,实时补偿参考电压变化,并利用均值滤波消除噪声,最终实现 12 位分辨率下有效精度提升至 11 位以上。文章包含完整代码示例和调试注意事项,适合有嵌入式基础的开发者直接移植。
## 1. 为什么需要多路扫描与温漂校准
STM32 的 ADC 支持多通道采样,但默认的单次转换模式需要频繁触发,效率低且难以保证通道间时间一致性。扫描模式(Scan Mode)配合 DMA 可以一次性采集多个通道,数据自动存入内存,适合连续监测场景。然而,实际测量中 ADC 的参考电压(VREF+)并非绝对稳定,尤其当芯片温度变化时,内部带隙基准和采样电容的温漂会导致读数偏移。例如,STM32F103 的 VREFINT 通道(通道17)可输出约 1.2V 的内部参考电压,通过测量其 ADC 值,可以反推实际 VREF+ 电压,从而校准所有通道的测量结果。
## 2. 硬件与软件准备
- **硬件**:STM32F103C8T6 最小系统板(或其他系列,代码兼容)、3路外部电压源(0~3.3V)、杜邦线。
- **软件**:STM32CubeMX(初始化代码生成)+ Keil MDK 或 IAR,HAL 库。
- **原理**:ADC1 使用扫描模式,通道顺序:外部通道0、1、2 + 内部温度传感器通道 + VREFINT 通道。DMA 循环传输,每次转换完成自动搬运到数组。
## 3. 配置步骤(以 CubeMX 为例)
1. **开启 ADC1**:选择 Scan Mode 为 Enabled,Continuous Conversion 为 Disabled(由定时器触发或软件触发),Number of Conversion 设为 5。
2. **配置通道**:
- 通道0、1、2:采样时间设为 239.5 周期(提高精度)。
- 通道16(温度传感器):需在 ADC 配置中使能 Temperature Sensor。
- 通道17(VREFINT):使能 VREFINT。
3. **配置 DMA**:添加 ADC1 的 DMA 请求,模式为 Circular,数据宽度 Word(或 Half Word,取决于 ADC 分辨率)。
4. **时钟配置**:ADC 时钟分频设为 6(即 PCLK2/6,典型 12MHz),确保采样时间足够。
5. **生成代码**:在 main.c 中启动 ADC 和 DMA。
## 4. 代码实现
以下代码基于 HAL 库,实现 3 路外部电压 + 内部温度 + VREFINT 的连续采集,并输出校准后的电压值。
```c
// 定义 ADC 值数组(DMA 循环写入)
volatile uint32_t adc_values[5];
// 校准参数(根据芯片手册,F103 的典型值)
#define VREFINT_CAL_ADDR ((uint16_t*)0x1FFFF7BA) // 出厂校准值(1.2V 对应的 ADC 值)
#define VREFINT_CAL_VOLT 1200 // 单位 mV
// 初始化函数(在 main 中调用)
void ADC_Init(void) {
// 使能 ADC 时钟和 GPIO 时钟
__HAL_RCC_ADC1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_DMA1_CLK_ENABLE();
// GPIO 配置:PA0, PA1, PA3 为模拟输入
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// ADC 句柄配置
hadc1.Instance = ADC1;
hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 5;
HAL_ADC_Init(&hadc1);
// 配置通道顺序和采样时间
ADC_ChannelConfTypeDef sConfig = {0};
sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;
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_3; sConfig.Rank = 3; HAL_ADC_ConfigChannel(&hadc1, &sConfig);
sConfig.Channel = ADC_CHANNEL_TEMPSENSOR; sConfig.Rank = 4; HAL_ADC_ConfigChannel(&hadc1, &sConfig);
sConfig.Channel = ADC_CHANNEL_VREFINT; sConfig.Rank = 5; HAL_ADC_ConfigChannel(&hadc1, &sConfig);
// DMA 配置
hdma_adc1.Instance = DMA1_Channel1;
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_WORD;
hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
hdma_adc1.Init.Mode = DMA_CIRCULAR;
HAL_DMA_Init(&hdma_adc1);
__HAL_LINKDMA(&hadc1, DMA_Handle, hdma_adc1);
// 启动 ADC 和 DMA
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_values, 5);
}
// 温度补偿和电压计算函数
float Get_CalibratedVoltage(uint8_t channel_index) {
// 获取 VREFINT 实际 ADC 值(取多次平均)
uint32_t vrefint_sum = 0;
for (int i = 0; i < 10; i++) {
vrefint_sum += adc_values[4];
HAL_Delay(1);
}
float vrefint_avg = vrefint_sum / 10.0f;
// 计算实际 VREF+ 电压(mV)
float actual_vref = (VREFINT_CAL_VOLT * (*VREFINT_CAL_ADDR)) / vrefint_avg;
// 计算目标通道电压(mV)
float adc_voltage = (adc_values[channel_index] * actual_vref) / 4095.0f;
// 温度补偿(可选):根据温度传感器值微调,此处略
return adc_voltage;
}
// 主循环示例
int main(void) {
HAL_Init();
ADC_Init();
while (1) {
float v0 = Get_CalibratedVoltage(0);
float v1 = Get_CalibratedVoltage(1);
float v2 = Get_CalibratedVoltage(2);
printf("V0: %.2f mV, V1: %.2f mV, V2: %.2f mV\r\n", v0, v1, v2);
HAL_Delay(500);
}
}
```
## 5. 关键细节与注意事项
- **采样时间**:多路扫描时,总转换时间 = 采样时间 + 12.5 周期。若采样时间过短,内部电容充电不足,会引入误差。建议外部通道采样时间 ≥ 239.5 周期(约 20μs @12MHz)。
- **VREFINT 校准**:出厂校准值存储在系统存储区,不同芯片地址不同(如 F0 系列为 0x1FFFF7BA,F4 系列为 0x1FFF7A2A),需查阅参考手册。
- **DMA 数据宽度**:若 ADC 分辨率设为 12 位,数据宽度建议设为 Half Word(16位),但使用 Word 也可,需注意数组类型。
- **温度传感器**:内部温度传感器输出随温度变化,可用于粗略环境温度监测,但精度有限。若需精确温度补偿,建议外接高精度温度芯片。
- **噪声抑制**:硬件上在 VREF+ 和 VSSA 之间加 100nF + 10μF 去耦电容;软件上可对每个通道连续采样 16 次取平均,进一步降低随机噪声。
- **多 ADC 同步**:若需同时采样多路,可使用 ADC 的注入组或双 ADC 模式,但复杂度较高,本文方案已满足大多数顺序采样需求。
## 6. 总结
通过扫描模式 + DMA 实现多路 ADC 高效采集,并利用 VREFINT 通道进行实时参考电压校准,可显著提升测量精度。该方法无需外部基准源,成本低且易于实现。对于温度漂移严重的场景,可结合内部温度传感器做线性补偿,或定期重新校准。希望本文能帮助你在 STM32 项目中获得更可靠的电压测量数据。