STM32 ADC分压电路设计与锂电池监控实现

📅 2026/7/19 5:49:18
STM32 ADC分压电路设计与锂电池监控实现
1. 项目背景与需求分析在嵌入式系统开发中电源管理一直是个绕不开的话题。最近接手一个野外监测设备的项目设备采用锂电池供电需要实时监控电源状态。这让我想起三年前做过的类似项目当时因为电源检测不准确导致设备在关键时刻掉链子至今记忆犹新。STM32的ADC模块模数转换器理论上可以直接测量电压但有两个现实问题一是STM32的ADC输入电压范围通常是0-3.3V取决于具体型号而锂电池电压往往超过这个范围二是直接测量会带来安全隐患可能损坏MCU。这就引出了我们今天要讨论的核心技术方案——通过分压电路ADC采样实现安全可靠的电源监控。2. 硬件设计关键点2.1 分压电路设计典型的分压电路由两个电阻串联组成其核心公式Vout Vin * (R2 / (R1 R2))在我的项目中使用12V锂电池供电选择R1100kΩR233kΩ计算得Vout 12V * (33k / (100k 33k)) ≈ 2.98V这个值正好在STM32F103的ADC输入范围内0-3.3V。但实际选型时要注意电阻精度至少1%普通5%精度的电阻会导致显著误差电阻功率要足够按PU²/R计算100kΩ电阻功率P (12V)² / 100kΩ 1.44mW0402封装足够建议加入保护电阻和滤波电容我的实际电路如下[电池] -- [R0 1kΩ] -- [R1 100kΩ] -- [R2 33kΩ] -- [GND] | | [C1 100nF] [ADC输入]2.2 PCB布局注意事项分压电阻尽量靠近MCU的ADC引脚布局模拟走线要远离数字信号线必要时做包地处理在ADC输入引脚添加0.1μF去耦电容对于高精度应用考虑使用金属膜电阻和温度系数匹配3. 软件实现细节3.1 ADC初始化代码void ADC1_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; ADC_InitTypeDef ADC_InitStructure; // 使能GPIOA和ADC1时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE); // 配置PA1为模拟输入 GPIO_InitStructure.GPIO_Pin GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AIN; GPIO_Init(GPIOA, GPIO_InitStructure); // 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); // 使能ADC1 ADC_Cmd(ADC1, ENABLE); // ADC校准 ADC_ResetCalibration(ADC1); while(ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while(ADC_GetCalibrationStatus(ADC1)); }3.2 电压计算与滤波算法直接读取的ADC值会有波动我采用了移动平均滤波#define SAMPLE_COUNT 10 float Get_Battery_Voltage(void) { static uint16_t samples[SAMPLE_COUNT]; static uint8_t index 0; uint32_t sum 0; float voltage; // 获取ADC采样值 samples[index] ADC_GetConversionValue(ADC1); index (index 1) % SAMPLE_COUNT; // 计算平均值 for(int i0; iSAMPLE_COUNT; i) { sum samples[i]; } uint16_t avg sum / SAMPLE_COUNT; // 转换为实际电压 // 分压比 R2/(R1R2) 33k/(100k33k) ≈ 0.248 // Vbat Vadc * (R1R2)/R2 ≈ Vadc * 4.03 voltage avg * 3.3f / 4095 * 4.03f; return voltage; }4. 实际应用中的经验教训4.1 校准技巧硬件分压电路存在误差建议在实际项目中增加校准环节准备精确的已知电压源如5.00V稳压源测量并记录ADC读数计算实际比例系数// 实测发现当输入5.00V时ADC读数为2500 float scale_factor 5.00f / (2500 * 3.3f / 4095);4.2 电量估算策略从电压到电量百分比是个复杂转换锂电池放电曲线非线性。我的经验做法实测电池完整放电过程记录电压-电量对应表使用分段线性插值法float voltage_to_percent(float volt) { if(volt 4.2f) return 100.0f; if(volt 3.9f) return 80.0f (volt-3.9f)*66.67f; if(volt 3.7f) return 30.0f (volt-3.7f)*250.0f; if(volt 3.5f) return 10.0f (volt-3.5f)*100.0f; return 0.0f; }4.3 低功耗优化对于电池供电设备ADC采样频率需要权衡正常模式每秒采样1次低电量模式每10秒采样1次休眠模式关闭ADC通过定时器唤醒void Enter_Low_Power_Mode(void) { // 降低ADC采样频率 ADC_InitStructure.ADC_ContinuousConvMode DISABLE; ADC_Init(ADC1, ADC_InitStructure); // 设置唤醒定时器 RTC_SetAlarm(RTC_GetCounter() 10); // 10秒后唤醒 PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI); }5. 进阶应用多电池系统监控在更复杂的系统中可能需要监控多节电池。这里分享一个我设计的4节锂电池监控方案5.1 硬件设计使用模拟多路复用器如CD4051切换不同分压电路电池1 电池2 电池3 电池4 | | | | [分压电路][分压电路][分压电路][分压电路] | | | | --------------------------- | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | --------------------------- CD4051多路复用器 | ADC输入5.2 软件实现#define BATTERY_COUNT 4 float battery_voltages[BATTERY_COUNT]; void Monitor_Batteries(void) { for(int i0; iBATTERY_COUNT; i) { Set_Mux_Channel(i); // 切换多路复用器通道 delay_ms(1); // 稳定时间 battery_voltages[i] Get_ADC_Voltage() * 4.03f; } }6. 常见问题排查指南6.1 ADC读数不稳定可能原因及解决方案电源噪声增加滤波电容10μF电解0.1μF陶瓷参考电压不稳在VREF引脚添加4.7μF电容采样时间不足调整ADC采样周期ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_239Cycles5);6.2 测量值偏差大排查步骤用万用表测量实际分压点电压检查电阻实际阻值焊下来测量验证ADC参考电压是否准确// 测量内部参考电压需芯片支持 Vrefint ADC_Get_VREFINT() * 3.3f / 4095;6.3 低电压时精度下降这是正常现象因为电压分辨率 3.3V / 4095 ≈ 0.8mV 经过分压后 实际分辨率 0.8mV * (R1R2)/R2 ≈ 3.2mV对于12V系统这相当于约0.027%的分辨率通常足够使用。如果确实需要更高精度可以考虑使用16位ADC外设如ADS1115选择更大的分压比但要确保最高电压时不超过ADC量程7. 项目优化与扩展思路7.1 温度补偿电阻值会随温度变化精密应用需要补偿// 获取温度传感器数据STM32内置 float temp Get_Internal_Temp(); // 温度补偿系数根据电阻规格书 float R1_actual R1 * (1 0.0005*(temp - 25)); float R2_actual R2 * (1 0.0004*(temp - 25)); float scale_factor (R1_actual R2_actual) / R2_actual;7.2 无线传输监控结合ESP8266实现远程监控void Send_To_Server(float voltage) { char buffer[64]; sprintf(buffer, GET /update?field1%.2f HTTP/1.1\r\nHost: api.thingspeak.com\r\n\r\n, voltage); ESP8266_Send(buffer); }7.3 历史数据记录使用SPI Flash存储历史数据void Save_Voltage_Record(float volt) { uint32_t timestamp RTC_GetCounter(); SPI_Flash_Write(volt, current_addr, sizeof(float)); SPI_Flash_Write(×tamp, current_addr4, sizeof(uint32_t)); current_addr 8; }