## 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 项目中获得更可靠的电压测量数据。