别再只会用万用表了!手把手教你用STM32的ADC测量电池电压(附完整代码)

发布时间:2026/6/2 5:20:02

别再只会用万用表了!手把手教你用STM32的ADC测量电池电压(附完整代码) 实战指南用STM32的ADC精准测量电池电压附完整工程代码在嵌入式开发中电池电压监测是一个基础但至关重要的功能。无论是便携式设备、物联网节点还是工业传感器准确的电压测量都能为电量管理、系统保护和用户体验提供关键数据支持。本文将带你从硬件设计到软件实现完整构建一个基于STM32的电池电压监测系统。1. 硬件设计与分压电路1.1 分压电阻计算原理当测量高于3.3V的电池电压时必须使用分压电路将电压降至ADC的测量范围内。典型的分压电路由两个电阻串联组成其输出电压V_out与输入电压V_in的关系为V_out V_in * (R2 / (R1 R2))选择电阻值时需要考虑以下因素功耗平衡阻值过小会导致不必要的功耗过大则容易引入噪声精度要求1%精度的金属膜电阻是理想选择ADC输入阻抗STM32的ADC输入阻抗约为50kΩ分压电路的输出阻抗应远小于此值提示实际设计中建议R1R2的总阻值在10kΩ~100kΩ之间既能降低功耗又能保证测量精度。1.2 实际电路设计示例假设我们需要测量0-12V的锂电池电压选择R130kΩR210kΩ参数计算值说明分压比1:4V_out V_in / 4最大输出电压3.0V12V输入时输出3V电流消耗0.3mA12V12V/(30k10k)0.3mA测量分辨率2.93mV3.3V/4095≈0.8mV实际2.93mV// 电压换算公式 float battery_voltage adc_value * (3.3f / 4095) * ((R1 R2) / R2);2. STM32 ADC配置详解2.1 初始化流程关键步骤完整的ADC初始化包含以下核心步骤时钟配置确保ADC和GPIO时钟使能GPIO设置将对应引脚配置为模拟输入模式ADC参数配置分辨率12位数据对齐右对齐扫描模式单通道时禁用连续转换根据需求选择通道配置指定采样时间和规则通道顺序校准执行ADC校准流程// 示例ADC1单通道初始化代码 void ADC1_Init(void) { ADC_InitTypeDef ADC_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; // 1. 时钟使能 RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE); // 2. GPIO配置 GPIO_InitStructure.GPIO_Pin GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AIN; GPIO_Init(GPIOA, GPIO_InitStructure); // 3. ADC参数配置 ADC_InitStructure.ADC_Mode ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode DISABLE; ADC_InitStructure.ADC_ContinuousConvMode ENABLE; ADC_InitStructure.ADC_ExternalTrigConv ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel 1; ADC_Init(ADC1, ADC_InitStructure); // 4. 通道配置 ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); // 5. 使能ADC并校准 ADC_Cmd(ADC1, ENABLE); ADC_ResetCalibration(ADC1); while(ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while(ADC_GetCalibrationStatus(ADC1)); }2.2 采样时间优化技巧ADC的采样时间直接影响测量精度和转换速度。STM32允许配置不同的采样周期数采样周期数典型时间12MHz适用场景1.50.125μs低阻抗信号源7.50.625μs中等阻抗(10kΩ左右)13.51.125μs高阻抗信号源28.52.375μs极高阻抗或高精度要求41.53.458μs特殊高精度场景55.54.625μs分压电路典型配置71.55.958μs抗干扰需求强的环境239.519.96μs极端高阻抗电路注意对于典型的分压电路(总阻抗约40kΩ)建议选择55.5个周期的采样时间可在精度和速度间取得良好平衡。3. 软件校准与滤波算法3.1 参考电压校准技术STM32内部有一个1.2V的参考电压(VREFINT)其实际值在芯片出厂时已校准并存储在系统存储器中。利用这个特性我们可以实现高精度的电压测量// 获取内部参考电压校准值(位于0x1FFFF7BA地址) #define VREFINT_CAL ((uint16_t*) ((uint32_t) 0x1FFFF7BA)) // 带参考电压校准的ADC读取函数 float ADC_ReadWithVrefCal(ADC_TypeDef* ADCx, uint8_t channel) { uint16_t vref_adc, channel_adc; // 读取内部参考电压ADC值 ADC_RegularChannelConfig(ADCx, ADC_Channel_Vrefint, 1, ADC_SampleTime_239Cycles5); ADC_SoftwareStartConvCmd(ADCx, ENABLE); while(!ADC_GetFlagStatus(ADCx, ADC_FLAG_EOC)); vref_adc ADC_GetConversionValue(ADCx); // 读取目标通道ADC值 ADC_RegularChannelConfig(ADCx, channel, 1, ADC_SampleTime_239Cycles5); ADC_SoftwareStartConvCmd(ADCx, ENABLE); while(!ADC_GetFlagStatus(ADCx, ADC_FLAG_EOC)); channel_adc ADC_GetConversionValue(ADCx); // 计算实际电压 float vref_actual 1.2f * (*VREFINT_CAL) / vref_adc; return channel_adc * vref_actual / 4095; }3.2 数字滤波算法实现为消除噪声干扰可采用以下滤波算法组合移动平均滤波简单有效适合周期性采样#define FILTER_SIZE 8 float movingAverageFilter(float new_value) { static float buffer[FILTER_SIZE] {0}; static uint8_t index 0; static float sum 0; sum - buffer[index]; buffer[index] new_value; sum new_value; index (index 1) % FILTER_SIZE; return sum / FILTER_SIZE; }中值滤波有效消除脉冲干扰float medianFilter(float new_value) { static float buffer[5] {0}; static uint8_t index 0; float temp[5]; buffer[index] new_value; if(index 5) index 0; memcpy(temp, buffer, sizeof(temp)); // 简单冒泡排序 for(int i0; i4; i) { for(int ji1; j5; j) { if(temp[i] temp[j]) { float swap temp[i]; temp[i] temp[j]; temp[j] swap; } } } return temp[2]; // 返回中值 }卡尔曼滤波适合动态变化的电压测量typedef struct { float q; // 过程噪声协方差 float r; // 测量噪声协方差 float x; // 估计值 float p; // 估计误差协方差 float k; // 卡尔曼增益 } KalmanFilter; float kalmanFilter(KalmanFilter* kf, float measurement) { // 预测更新 kf-p kf-p kf-q; // 测量更新 kf-k kf-p / (kf-p kf-r); kf-x kf-x kf-k * (measurement - kf-x); kf-p (1 - kf-k) * kf-p; return kf-x; }4. 完整工程实现与优化4.1 工程架构设计一个健壮的电池监测系统应包含以下模块battery_monitor/ ├── inc/ │ ├── adc.h # ADC驱动接口 │ ├── filter.h # 滤波算法库 │ └── battery.h # 电池管理核心逻辑 ├── src/ │ ├── adc.c # ADC驱动实现 │ ├── filter.c # 滤波算法实现 │ └── battery.c # 电池逻辑实现 └── project/ ├── main.c # 主应用逻辑 └── stm32f10x_it.c # 中断处理4.2 电池电量估算算法基于电压的电量估算(SOC)需要考虑电池特性曲线// 锂电池典型放电曲线分段线性估算 float estimateLiBatterySOC(float voltage) { const float points[] { 3.00f, 0.00f, // 0% at 3.0V 3.70f, 10.00f, // 10% at 3.7V 3.90f, 50.00f, // 50% at 3.9V 4.10f, 90.00f, // 90% at 4.1V 4.20f, 100.00f // 100% at 4.2V }; if(voltage points[0]) return points[1]; if(voltage points[8]) return points[9]; for(int i0; i4; i) { if(voltage points[i*2] voltage points[(i1)*2]) { float x1 points[i*2]; float y1 points[i*21]; float x2 points[(i1)*2]; float y2 points[(i1)*21]; return y1 (voltage - x1) * (y2 - y1) / (x2 - x1); } } return 0; }4.3 低功耗优化策略对于电池供电设备功耗优化至关重要间歇采样模式void enterLowPowerMode(void) { // 配置唤醒定时器 RTC_SetAlarm(...); // 关闭外设时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_ALL, DISABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_ALL, DISABLE); // 进入STOP模式 PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI); // 唤醒后重新初始化系统时钟 SystemInit(); }动态采样频率void adjustSamplingRate(float voltage) { static uint32_t last_sample 0; uint32_t interval; if(voltage 3.8f) { interval 60000; // 高电量时每分钟采样一次 } else if(voltage 3.5f) { interval 30000; // 中等电量每30秒采样 } else { interval 5000; // 低电量时每5秒采样 } if(HAL_GetTick() - last_sample interval) { last_sample HAL_GetTick(); sampleBatteryVoltage(); } }硬件优化措施在分压电路输出端添加0.1μF电容滤除高频噪声使用低功耗运放缓冲高阻抗分压电路选择低功耗的基准电压源替代分压电阻在长时间不测量时通过MOS管切断分压电路供电在实际项目中我发现分压电阻的温漂对长期测量稳定性影响很大。采用金属膜电阻并保持电路远离热源后系统连续工作一个月的电压漂移从原来的±5%降低到了±1%以内。

相关新闻