电池电量检测系统实战:ADC采样、分压电路与容量校准全解析
2026/9/11 21:46:32 网站建设 项目流程

简介:一套围绕电池电量检测系统展开的单片机课程设计资料包,适合电子信息类学生与嵌入式入门者参考,覆盖AD采样、下位机控制、上位机显示与设计报告等完整环节。压缩包共37个文件,包含Keil工程源码、Proteus原理图、LabVIEW上位机工程、Word设计报告、hex烧录文件及若干过程图片,整体约1.91MB,目录按单片机程序设计、原理图、上位机设计和报告模块组织,便于快速定位和复用。资源详细记录了AD转换芯片的选型对比与仿真验证,如ADC0808/0809、AD1674、ADS7824等,最终选用16位ADS7825,仿真逻辑与数据手册基本一致;下位机以函数模块化组织代码,注释较全,并对电压小数点、电量百分比最高位为0等显示细节做了专门处理;上位机以回车换行作为帧结束标志,完成字符串解析和数据显示,全面还原了串口通信链路。设计报告还指出了ADC输入端负载效应可能引入误差,并提出了增加电压跟随器、提高输入阻抗的优化思路。全套资料已有780人浏览学习,对准备课程设计答辩或开展电池数据采集项目都有较好的参考价值。

1. 电池电量检测系统:一个把 ADC、分压和容量曲线串起来的课程设计

电池电量检测几乎是每个单片机课程设计必选的题目:成本低、可验证、原理清楚,而且能直接看到自己写的代码和硬件一起工作。这个 zip 包里既有源码、原理图又有报告,本质上你面对的是三件事:把电池电压变成单片机可以读的数字,把数字在程序里换算成剩余电量百分比,最后在报告里说清楚误差从哪里来、为什么可以接受。

这套系统最常见的方案是电阻分压加 ADC 采样加查表映射。相比库仑计或专用电量计芯片,它成本最低、代码量小,适合 51、STM32 这类入门级单片机。代价是精度有限,不能精确到 1%,但做一个课程设计完全够用。下面就把从原理图到代码、再到校准和报告整理的完整思路讲清楚,每一条照着搭就行。

2. 原理图设计:电池电量检测系统的分压、滤波与 ADC 输入电路

2.1 为什么分压电阻是电池电量检测系统的第一道坎

单片机的 ADC 输入范围通常被限制在 0 到 Vref,大多数情况下就是 VDD,3.3V 或 5V。如果电池是 3.7V 锂电池,充满电时电压能到 4.2V,已经超出了 3.3V ADC 的量程。直接接上去的结果不只是读数钳位在最大值,长期超出规格还可能损坏引脚。所以第一步就是把电压按比例缩到 ADC 量程以内。

常见做法是在电池正极和 GND 之间接两个电阻,中间抽头接单片机 ADC 引脚。以 STM32F103C8T6 为例,VDD 是 3.3V,ADC 是 12 位,满量程对应 4095。如果我们希望 4.2V 对应 3.3V 的输出,分压比就是 3.3 / 4.2 ≈ 0.786。选择 R1(上臂)10kΩ、R2(下臂)36kΩ,实际分压比是 36 / (10+36) ≈ 0.783,4.2V 对应约 3.29V,这个余量是合适的。

V_adc = V_bat × R2 / (R1 + R2)

电阻阻值不能取太大,否则 ADC 输入等效阻抗会拉低采样电压,造成非线性误差;也不能太小,否则分压回路本身耗电,长时间挂着会额外消耗电池。10k 到 100k 这个区间是折中方案,51 系统用 10k 级别比较多,STM32 可以放宽到 50k 以内。实际画原理图时,可以在抽头和 ADC 引脚之间串一个 1k 电阻,后面再接一个 100nF 电容到地,组成简单的 RC 低通滤波。

用一段 Python 脚本可以快速验算电阻组合是否合适:

# 计算分压后电压,确认不超出 ADC 量程 def divider(r_top, r_bottom, v_bat): return v_bat * r_bottom / (r_top + r_bottom) # 10k + 36k 组合,输入 4.2V 满电电压 v_adc_max = divider(10_000, 36_000, 4.2) print(f"4.2V 时分压输出: {v_adc_max:.3f}V") # 约 3.287V,留有余量 # 最低工作电压 3.0V 时 ADC 输入还有多少 v_adc_min = divider(10_000, 36_000, 3.0) print(f"3.0V 时分压输出: {v_adc_min:.3f}V") # 约 2.348V

这段脚本的作用是验证分压比是否同时满足最高电压不超量程、最低电压仍在 ADC 线性区间这两个约束。分压比一旦确定,后续固件里所有电压换算公式的分母就固定了。如果改电池类型,比如换成 12V 铅酸电池,直接改脚本里的v_bat重新算一遍电阻组合即可。

2.2 STM32F103C8T6 与 51 单片机在 ADC 电路上的差异

拿 stm32f103c8t6 原理图上最常见的接法说,它内置两个 12 位 ADC,采样速度最高 1MHz,输入阻抗在采样模式下大约是几 kΩ 量级。设计要点是:

  • 在 ADC 引脚和地之间加一个 100nF 电容,滤掉电池上的高频噪声和开关负载带来的纹波。
  • RC 滤波器的截止频率按 1 / (2πRC) 算。R 取 1kΩ、C 取 100nF 时,截止频率约 1.6kHz,对电池电压这种缓变信号足够了。
  • 不能把电池直接接 ADC 引脚,即使电压在量程内,电池内阻和负载变化引起的压降也会直接反映在采样值上。

如果是 STC 或传统的 51 单片机,很多型号没有内置 ADC,需要外接 ADC 芯片,课程设计里最常用的是 ADC0832 或 PCF8591。它们一个是 SPI,一个是 I2C,代码量都不大。但注意这两种芯片都是 8 位分辨率,满量程只有 256 个台阶,对应电池 3.0V 到 4.2V 的区间,1 个 LSB 大约 4.7mV,通过查表换算成容量后误差会明显变大。

还有一个容易忽略的点:51 单片机 IO 口默认是准双向模式,上拉能力很弱,但分压电路是持续供电的。如果单片机进入低功耗模式,分压电阻上的电流可能比单片机本身的待机电流还大。在这个系统里,比较好的做法是让分压电阻的接地端接一个 MOS 管或三极管,由单片机 GPIO 控制,只在测量时导通。

2.3 电池电量检测系统原理图中的参考电压选择

参考来源精度温度漂移成本适用场景
VDD(3.3V LDO 输出)一般,约 ±2%中等零成本课程设计、精度要求不高的设备
内部参考电压(如 STM32 的 VREFINT)稳定,约 ±1% 以内较低零成本需要在线校准 ADC
外部基准(如 TL431、REF3030)高,±0.5% 或更好几角到几元电池电量检测系统的精度验证阶段

这里要特别说下 STM32 的 VREFINT。它内部有一个约 1.2V 的基准电压,出厂时校准值存在芯片 Flash 里。运行时通过读取 VREFINT 通道的实际 ADC 值,可以反推出当前的 VDD,即使板子上的 3.3V LDO 实际输出是 3.28V 或 3.32V,也能修正 ADC 的满量程。这个技巧在课程设计报告里是一个不错的加分点,后面第 4 章会专门展开。

3. 源码实现:电池电量检测系统的 ADC 采样、滤波与容量计算

3.1 初始化 ADC 时的两个关键参数

在 STM32 上用 HAL 库初始化 ADC 非常快,但有几个参数不是默认值就能直接用的。下面是在 stm32f103c8t6 最小系统板上验证过的最小配置,对应 PA0 通道。

ADC_HandleTypeDef hadc1; void ADC_Init(void) { hadc1.Instance = ADC1; hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; // 右对齐,12 位数据低 12 位有效 hadc1.Init.ScanConvMode = DISABLE; // 单通道采集 hadc1.Init.ContinuousConvMode = DISABLE; // 单次转换 hadc1.Init.NbrOfConversion = 1; // 转换通道数 hadc1.Init.DiscontinuousConvMode = DISABLE; hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START; // 软件触发 hadc1.Init.Resolution = ADC_RESOLUTION_12B; // 12 位分辨率,4096 个台阶 HAL_ADC_Init(&hadc1); } void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc) { GPIO_InitTypeDef GPIO_InitStruct = {0}; __HAL_RCC_ADC1_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitStruct.Pin = GPIO_PIN_0; GPIO_InitStruct.Mode = GPIO_MODE_ANALOG; // 模拟输入,不要配成上拉或下拉 HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); HAL_NVIC_SetPriority(ADC1_IRQn, 0, 0); HAL_NVIC_EnableIRQ(ADC1_IRQn); }

两个容易踩的坑。第一,Resolution设置为 12 位后,满量程值就是 4095,后续算电压时除法分母要写对,很多人用 4096 做分母导致最终电压偏小。第二,GPIO 必须配置成GPIO_MODE_ANALOG,如果配成普通输入模式,引脚内部的上拉或下拉电阻会和分压电阻并联,直接改变分压比,导致实测电压系统性偏高或偏低。

3.2 采样多次取平均:电池电量检测系统的防抖逻辑

电池电压不是恒定的。电机启动、屏幕刷新、无线模块发射时都会让电压瞬时下跌。单次 ADC 采样结果如果直接用,显示的容量会跳来跳去。常见的做法是连续采样 16 到 32 次,去掉最大值和最小值后取平均。

#define SAMPLE_COUNT 16 uint32_t ReadBatteryVoltage_mV(void) { uint32_t sum = 0; uint16_t sample[SAMPLE_COUNT]; uint16_t max_val = 0, min_val = 4095; for (int i = 0; i < SAMPLE_COUNT; i++) { HAL_ADC_Start(&hadc1); HAL_ADC_PollForConversion(&hadc1, 10); sample[i] = HAL_ADC_GetValue(&hadc1); HAL_ADC_Stop(&hadc1); } // 剔除极值,避免瞬时噪声影响平均结果 for (int i = 0; i < SAMPLE_COUNT; i++) { if (sample[i] > max_val) max_val = sample[i]; if (sample[i] < min_val) min_val = sample[i]; sum += sample[i]; } sum -= max_val + min_val; // 转换到实际电压:ADC 值 × VREF / 4095,再除以分压比 // 假设 VREF = 3300mV,分压比 R2/(R1+R2) = 0.5 uint32_t vbat_mv = (sum / (SAMPLE_COUNT - 2)) * 3300UL / 4095UL * 2UL; return vbat_mv; }

几个参数的含义要说明白。SAMPLE_COUNT取 16 是性能与稳定性的折中。8 次太少,滤波效果不明显;32 次以上对电压变化剧烈的情况有帮助,但会让 ADC 占用更多时间。电池电压变化是慢信号,代码里两次读取间隔超过 100ms 时,16 次采样几乎没有耗时压力。去掉一个最大值一个最小值,是课程设计里最简单有效的抗脉冲干扰办法。如果电池电压本身波动不大,也可以不做剔除,直接平均,代码更简洁。

电压换算用的公式是 ADC 值除以 4095,不是 4096。12 位 ADC 的满量程读数是 4095,对应 VREF。除错会导致整体估算偏差约 0.02%,虽然不大,但报告里把这个细节写出来会显得严谨。分压比那一步用的是乘以 2 的写法,只适用于分压电阻上下臂相等的场景。如果你的电阻组合是 10k 和 36k,这里要改成乘以 1.278,或者干脆用浮点运算,STM32F103 有硬件浮点单元,不需要担心性能。

3.3 电压到容量的映射:电池电量检测系统不能只用线性比例

很多人第一次做这个题目时,直接用percent = (vbat - 3.0) / (4.2 - 3.0) * 100这种线性映射。锂电池的放电曲线不是直线,这会导致 3.8V 附近显示 70% 左右,但实际上容量已经消耗了大半。正确做法是一张按放电曲线生成的查找表,程序里根据电压做查表或插值。

映射方式实现复杂度典型误差适用场景
线性比例最低,一行公式8% ~ 15%,3.8V 附近偏差最明显临时验证、不考虑精度的演示
分段线性插值查表低,一张表加循环3% ~ 8%,节点越密误差越小课程设计主流方案,推荐
多项式拟合中,需要浮点运算2% ~ 5%有浮点单元的芯片
SOC 芯片(如库仑计)高,需要硬件配合1% 以内产品级方案,课程设计一般不用
// 以 3.7V 锂电池、0.2C 放电速率为例 // 电压单位 mV,容量单位 % const uint16_t battery_curve[][2] = { {4200, 100}, {4160, 95}, {4090, 90}, {4000, 80}, {3900, 60}, {3840, 50}, {3780, 40}, {3700, 25}, {3600, 10}, {3300, 2}, {3100, 0} }; int GetBatteryPercent(uint32_t vbat_mv) { int n = sizeof(battery_curve) / sizeof(battery_curve[0]); if (vbat_mv >= battery_curve[0][0]) return 100; if (vbat_mv <= battery_curve[n-1][0]) return 0; for (int i = 0; i < n - 1; i++) { if (vbat_mv <= battery_curve[i][0] && vbat_mv >= battery_curve[i+1][0]) { // 线性插值:两段电压之间按比例估算容量 int v_high = battery_curve[i][0]; int v_low = battery_curve[i+1][0]; int p_high = battery_curve[i][1]; int p_low = battery_curve[i+1][1]; return p_low + (vbat_mv - v_low) * (p_high - p_low) / (v_high - v_low); } } return 0; }

这张表怎么用、什么场合可以简化,说几点实际经验。表里的电压点是简化后的三段平台区。主流锂电池放电曲线是:4.2V 到 3.9V 下降略快,3.9V 到 3.7V 是一个较缓的平台,3.6V 以下迅速跌落。你手里的电池如果 datasheet 或实测曲线不同,直接替换表里的电压值即可。

如果只做 51 单片机且没有浮点运算单元,插值公式里的整数运算顺序有讲究。上面的写法先乘后除,最大中间值是百分比和电压的积,在 uint32_t 范围内不会溢出;如果反过来先除再乘,会丢掉大量小数精度。查表法默认电池在稳态电压下工作。停止放电一段时间后电压会回升 0.1V 左右,会导致显示容量偏低或偏高。比较常见的补偿方式:检测到负载断开时写一个 1 到 2 分钟的定时器,读数稳定后再更新显示。

如果是 STC89C52 这类不带 ADC 的 51 单片机,流程是一样的,只是把HAL_ADC_GetValue()换成从 ADC0832 读到的 8 位值。注意 8 位 ADC 的分辨率只有约 4mV 一个 LSB,20mV 的电压误差会直接出现在最终容量上,查表前建议至少采 32 次求平均。

4. 精度校准:电池电量检测系统的 VREFINT 修正与放电曲线实测

4.1 用 VREFINT 校准 ADC 满量程,电池电量检测系统误差减半

上一章代码里直接用了 3300mV 作为参考电压。实际上板载 LDO 的输出可能不是严格的 3.3V,而是 3.28V 或 3.31V。如果电源芯片本身误差是 ±2%,那 ADC 的满量程对应电压就会从 3.234V 漂到 3.366V,最终容量误差会被放大到接近 5%。

更稳的做法是每次上电时读一次内部的 VREFINT 通道,用它推算出真实的 VDD。STM32F103 出厂时在地址0x1FFFF7BA存了一个 16 位的校准值,对应的含义是当 VDD 等于 3.3V 时,VREFINT 的实际 ADC 读数。

uint16_t vrefint_cal; uint32_t vdd_mv; void CalibrateVDDSource(void) { // 读取出厂校准值,注意不同型号地址可能不同 vrefint_cal = *(uint16_t *)0x1FFFF7BA; ADC_ChannelConfTypeDef sConfig = {0}; sConfig.Channel = ADC_CHANNEL_VREFINT; sConfig.Rank = ADC_REGULAR_RANK_1; sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5; // 参考电压采样需要更长时间 HAL_ADC_ConfigChannel(&hadc1, &sConfig); HAL_ADC_Start(&hadc1); HAL_ADC_PollForConversion(&hadc1, 10); uint16_t adc_vrefint = HAL_ADC_GetValue(&hadc1); HAL_ADC_Stop(&hadc1); // VDD = 3.3V × 校准值 / 当前读数 vdd_mv = 3300UL * vrefint_cal / adc_vrefint; }

SamplingTime这一项值得注意。VREFINT 的内部阻抗比普通外部通道高,用默认的 1.5 周期采样值可能偏小。官方推荐是用最大采样时间,代码里写了 239.5 周期。如果你在调试时发现 VREFINT 读数忽高忽低,先检查的也是这个参数。

vdd_mv替换掉上一章代码里的固定3300UL后,整个系统就能自动适应不同批次板子的电源误差。这个技巧是很多产品级代码在用的,放在课程设计里属于性价比很高的加分项:代码改动不超过十行,报告里有理论依据也有测试数据可写。

4.2 放电实验:自己生成更贴合实际电池的电压-容量表

默认查表用的是通用曲线,不同厂家、不同放电倍率下曲线会有差异。如果你手上有一个可调电子负载,或者干脆用几个功率电阻做放电,就能以 5% 为步长实测自己的表。如果这份内容同时用于单片机毕业设计,这一步的实测数据会明显拉高评审印象分。

具体步骤:

  • 电池充满到 4.2V,静置 30 分钟。
  • 接上固定负载,例如 10Ω 电阻,对应约 0.4A 放电,每放出 5% 额定容量记录一次电压。
  • 记录时断开负载,等电压稳定 30 秒再读,避免负载内阻压降干扰。
  • 测试中不要只放到底,低于 3.0V 立即停止,低于 2.8V 会损伤电池。
  • 将电压、容量对应关系反向替换进battery_curve表。

理论上这个实验一小时左右可以做完,时间成本不高,但报告里能给出的数据维度和可信度完全不同。一篇评分较高的课程设计报告里,这种实证曲线的价值往往高于参数推导过程本身。

4.3 误差来源拆解:单片机课程设计报告中怎么把校准数据写实

报告里最好不要只写“误差小于 5%”这种一句话结论。给出这个数字,就要拆解它的构成:

误差来源典型幅度是否可通过软件补偿
分压电阻精度±1%(0603 电阻)不能,只能换更高精度电阻
ADC 参考电压漂移±2%可以,用 VREFINT 修正
ADC 量化误差0.024%(12 位)不能,但影响很小
查表法模型误差3% ~ 8%可以,细化表内节点
温度导致的电池容量变化5% ~ 15%(低温时)可以加温度补偿,但课程设计一般省略

这样拆之后,你一眼就能看到系统的瓶颈在分压电阻精度和查表模型误差上。如果你想在报告里把方案改进章节写实,方向就非常明确:把两只分压电阻换成 0.1% 精度的,放电曲线表节点从 11 个扩到 21 个,系统整体精度就能从 5% 提升到 2% 左右。

5. 硬件联调与验证:电池电量检测系统实测表现与低电量保护阈值

5.1 串口输出实测电压,对照万用表找偏差

代码写完、板子焊好后,不要直接看屏幕或串口助手显示的容量百分比,先看原始电压。把计算出来的vbat_mv通过串口每秒打印一次,同时用万用表测量电池两端电压,对比两者差值。

// 串口发送示例,使用 UART1,波特率 115200 printf("VBAT: %lu mV, Percent: %d%%\r\n", (unsigned long)vbat_mv, GetBatteryPercent(vbat_mv));

正常情况误差应该在 ±30mV 以内。如果偏大,优先检查:分压电阻实际阻值与标称值是否一致,很多课程设计买的电阻精度是 5%,实测 10k 可能只有 9.8k;ADC 引脚是否和其他外设共用,例如 STM32 的 PA0 同时接了按键,按键按下时会把引脚拉到不同电位;锂电池本身在放电中电压是否稳定,测量时不要用手同时捏住表笔两端,人体电阻会改变分压回路。

5.2 一个可靠的低电量提示阈值:把 3.3V 作为最后 5%

不同电池、不同负载下,曲线末段的抖动最大。3.0V 以下区域的电压下降速度非常快,可能几分钟内从 3.3V 掉到 2.8V,用户根本没有反应时间。所以在系统里设置两级阈值:

  • 一级告警:电压低于 3.5V,OLED 或 LED 显示黄色提示,提醒尽快充电。
  • 二级告警:电压低于 3.3V,进入低功耗模式或者强制关机,保护电池不过放。
#define LOW_PERCENT 5 // 5% 以下视为紧急 #define ALARM_MV 3500 // 一级报警 #define SHUTDOWN_MV 3300 // 二级关机,保护电池 void BatteryMonitorTask(uint32_t vbat_mv) { int percent = GetBatteryPercent(vbat_mv); if (vbat_mv <= SHUTDOWN_MV) { // 切断负载,进入 STOP 模式,GPIO 全部复位 HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI); } else if (vbat_mv <= ALARM_MV) { // 点亮告警 LED,串口输出低电量 HAL_GPIO_WritePin(LED_ALARM_GPIO_Port, LED_ALARM_Pin, GPIO_PIN_SET); } }

这段代码把阈值判断和动作分开写,目的就是让评审人一眼能看懂告警逻辑。实际产品里这一步往往对接电源管理芯片,课程设计里用 GPIO 控制一个 MOS 管或继电器足矣。

5.3 最后的进阶技巧:用 OLED 显示电量图标替代百分比数字

把电压转成百分比后,很多同学直接在 OLED 上打印数字。实际体验中,一个 10 格或 20 格的电池图标比精确数字更直观,且代码量差不多。用 20 格表示时,percent / 5就是点亮格数,直接画一个矩形框,从右往左填充。

这个显示逻辑给报告带来的好处是:你可以把查表法与人机交互两个知识点串起来,展示的不只是 ADC 采样,而是一套完整的小型监测系统。如果有富余时间,再加一个按键切换显示电压、百分比和剩余可用时间估算,整个课程设计的完成度会明显上一个台阶。真正把这套系统做扎实的,往往不是写代码的部分,而是知道什么时候应该去查曲线、什么时候应该信任 ADC 的读数、什么时候要停下来测一下硬件,把这些过程放进报告里,比你堆再多的参数都有说服力。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询