# STM32F4 ADC 内部基准电压校准在高速采样下的漂移补偿策略 ## 1. 漂移问题的根源 STM32F4 系列(如 STM32F407)内置 12 位 ADC,支持最高 2.4 MSPS 采样率。其内部基准电压 VREFINT 是一个带隙参考源,标称值为 1.21V,但实际值随温度(典型 ±5mV/°C)和 VDDA 电压(线性调整率约 0.1%/V)漂移。在高速采样时,ADC 内部开关电容网络动态功耗增大,导致芯片局部温升,进一步加剧 VREFINT 漂移。若不补偿,ADC 转换结果将产生系统性误差,尤其在测量小信号或需要高精度比例测量时,误差可达 ±10 LSB 以上。 ## 2. 补偿原理:比例测量与实时校准 ### 2.1 比例测量基础 ADC 转换结果 D 与输入电压 Vin 的关系为: ```c D = (Vin / VREFINT) * 4095 ``` 若 VREFINT 漂移,D 随之变化。但若同时测量 VREFINT 自身(通过内部通道),则可反推实际 VREFINT 值: ```c VREFINT_actual = (VREFINT_CAL / D_VREFINT) * 4095 ``` 其中 VREFINT_CAL 是出厂校准值(存储在地址 0x1FFF7A2A,12 位)。 ### 2.2 动态补偿策略 在高速采样中,我们无法频繁插入 VREFINT 测量(会中断采样流)。因此采用**分段补偿**: - 在采样序列中周期性插入 VREFINT 测量(例如每 1000 次采样插入一次)。 - 利用插值算法估算当前 VREFINT 值,并修正所有采样结果。 - 结合过采样技术(硬件或软件)降低噪声,提高修正精度。 ## 3. 硬件与软件配置 ### 3.1 硬件连接 - 使用 STM32F407 开发板,ADC1 通道 0(PA0)接模拟信号。 - 确保 VDDA 与 VREF+ 连接稳定,VREF- 接地。 - 建议在 VDDA 与 GND 间加 1μF 和 100nF 去耦电容。 ### 3.2 寄存器配置步骤 1. **使能时钟**:ADC1、GPIOA、DMA1 时钟。 2. **配置 GPIO**:PA0 为模拟输入。 3. **配置 ADC1**: - 分辨率 12 位,右对齐。 - 扫描模式关闭,连续转换模式开启。 - 采样时间设为 84 周期(高速采样下保证精度)。 - 使能 DMA 循环模式。 4. **配置 DMA**:传输数据到内存缓冲区。 5. **校准 ADC**:上电后执行一次标准校准。 6. **读取 VREFINT 校准值**:从系统存储器读取。 ## 4. 完整代码示例 以下代码基于 STM32 HAL 库,实现带漂移补偿的 ADC 高速采样。 ```c #include "stm32f4xx_hal.h" // 缓冲区定义 #define ADC_BUFFER_SIZE 1024 uint16_t adc_buffer[ADC_BUFFER_SIZE]; uint16_t vrefint_buffer[8]; // 存储 VREFINT 采样值 // 出厂校准值(12位) #define VREFINT_CAL_ADDR 0x1FFF7A2A uint16_t vrefint_cal = *(uint16_t*)VREFINT_CAL_ADDR; // 补偿系数 float vrefint_actual; float compensation_factor; // ADC 句柄 ADC_HandleTypeDef hadc1; DMA_HandleTypeDef hdma_adc1; void ADC_Init(void) { __HAL_RCC_ADC1_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_DMA1_CLK_ENABLE(); // GPIO 配置 GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = GPIO_PIN_0; GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); // ADC 配置 hadc1.Instance = ADC1; hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4; // 21MHz hadc1.Init.Resolution = ADC_RESOLUTION_12B; hadc1.Init.ScanConvMode = DISABLE; hadc1.Init.ContinuousConvMode = ENABLE; hadc1.Init.DiscontinuousConvMode = DISABLE; hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE; hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; hadc1.Init.NbrOfConversion = 1; hadc1.Init.DMAContinuousRequests = ENABLE; hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV; HAL_ADC_Init(&hadc1); // 配置通道0(PA0) ADC_ChannelConfTypeDef sConfig = {0}; sConfig.Channel = ADC_CHANNEL_0; sConfig.Rank = 1; sConfig.SamplingTime = ADC_SAMPLETIME_84CYCLES; HAL_ADC_ConfigChannel(&hadc1, &sConfig); // DMA 配置 hdma_adc1.Instance = DMA1_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); // ADC 校准 HAL_ADCEx_Calibration_Start(&hadc1); // 启动 ADC 和 DMA HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buffer, ADC_BUFFER_SIZE); } // 读取 VREFINT 通道(内部通道17) void Read_VREFINT(void) { ADC_ChannelConfTypeDef sConfig = {0}; sConfig.Channel = ADC_CHANNEL_VREFINT; sConfig.Rank = 1; sConfig.SamplingTime = ADC_SAMPLETIME_84CYCLES; HAL_ADC_ConfigChannel(&hadc1, &sConfig); // 停止当前 DMA 转换 HAL_ADC_Stop_DMA(&hadc1); // 连续采样 VREFINT 多次取平均 for (int i = 0; i < 8; i++) { HAL_ADC_Start(&hadc1); HAL_ADC_PollForConversion(&hadc1, 10); vrefint_buffer[i] = HAL_ADC_GetValue(&hadc1); HAL_ADC_Stop(&hadc1); } // 计算平均值 uint32_t sum = 0; for (int i = 0; i < 8; i++) sum += vrefint_buffer[i]; uint16_t vrefint_avg = sum / 8; // 计算实际 VREFINT 电压(假设 VDDA=3.3V,但这里用校准值反推) // 注意:VREFINT_CAL 是在 VDDA=3.3V 时测得的,因此实际 VDDA 可由下式得到 vrefint_actual = 3.3f * vrefint_cal / vrefint_avg; // 补偿因子:实际 VREFINT 与标称 1.21V 的比值 compensation_factor = 1.21f / vrefint_actual; // 恢复通道0配置 sConfig.Channel = ADC_CHANNEL_0; HAL_ADC_ConfigChannel(&hadc1, &sConfig); // 重新启动 DMA HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buffer, ADC_BUFFER_SIZE); } // 补偿采样值 float Get_Compensated_Value(uint16_t raw) { // 将原始值转换为电压(基于实际 VREFINT) float voltage = (float)raw * vrefint_actual / 4095.0f; // 应用补偿因子(可选,若需标称电压) return voltage * compensation_factor; } int main(void) { HAL_Init(); ADC_Init(); // 初始读取 VREFINT Read_VREFINT(); while (1) { // 每 1000 次采样重新校准(简单计数) static uint32_t counter = 0; if (counter++ >= 1000) { counter = 0; Read_VREFINT(); } // 处理最新 DMA 数据(例如取平均值) // 注意:DMA 循环写入,需防止数据竞争,可使用双缓冲或临界区 uint32_t sum = 0; for (int i = 0; i < ADC_BUFFER_SIZE; i++) { sum += adc_buffer[i]; } float avg_raw = (float)sum / ADC_BUFFER_SIZE; float compensated = Get_Compensated_Value((uint16_t)avg_raw); // 此处可输出或使用 compensated HAL_Delay(10); } } ``` ## 5. 注意事项 - **校准时机**:VREFINT 测量会中断采样流,建议在采样间隙或低优先级任务中执行。若需连续高速采样,可增加 DMA 缓冲并采用双缓冲机制,避免数据覆盖。 - **滤波**:VREFINT 采样值应多次平均(如 8 次)以降低噪声。 - **温度影响**:若环境温度变化剧烈,可提高校准频率;反之可降低。 - **VDDA 稳定性**:确保 VDDA 电源纹波小于 10mV,否则补偿效果有限。 - **过采样**:对于更高精度,可开启硬件过采样(若支持)或软件累加平均,但需注意采样速率下降。 - **校准值有效性**:出厂校准值仅适用于 3.3V 供电,若 VDDA 不同,需重新标定。 ## 6. 总结 通过周期性测量 VREFINT 并动态计算补偿因子,可有效抑制温度与电源波动引起的 ADC 漂移,尤其在高速采样场景下显著提升测量一致性。本策略实现简单、开销小,适用于工业控制、传感器采集等对精度要求较高的嵌入式系统。开发者可根据实际需求调整校准周期与滤波深度,达到性能与精度的平衡。