ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

电池电量检测系统实战:ADC采样、分压电路与容量校准全解析

电池电量检测系统实战:ADC采样、分压电路与容量校准全解析 简介一套围绕电池电量检测系统展开的单片机课程设计资料包适合电子信息类学生与嵌入式入门者参考覆盖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大多数情况下就是 VDD3.3V 或 5V。如果电池是 3.7V 锂电池充满电时电压能到 4.2V已经超出了 3.3V ADC 的量程。直接接上去的结果不只是读数钳位在最大值长期超出规格还可能损坏引脚。所以第一步就是把电压按比例缩到 ADC 量程以内。常见做法是在电池正极和 GND 之间接两个电阻中间抽头接单片机 ADC 引脚。以 STM32F103C8T6 为例VDD 是 3.3VADC 是 12 位满量程对应 4095。如果我们希望 4.2V 对应 3.3V 的输出分压比就是 3.3 / 4.2 ≈ 0.786。选择 R1上臂10kΩ、R2下臂36kΩ实际分压比是 36 / (1036) ≈ 0.7834.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(f4.2V 时分压输出: {v_adc_max:.3f}V) # 约 3.287V留有余量 # 最低工作电压 3.0V 时 ADC 输入还有多少 v_adc_min divider(10_000, 36_000, 3.0) print(f3.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 电池电量检测系统原理图中的参考电压选择参考来源精度温度漂移成本适用场景VDD3.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/(R1R2) 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[i1][0]) { // 线性插值两段电压之间按比例估算容量 int v_high battery_curve[i][0]; int v_low battery_curve[i1][0]; int p_high battery_curve[i][1]; int p_low battery_curve[i1][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 一个 LSB20mV 的电压误差会直接出现在最终容量上查表前建议至少采 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.8kADC 引脚是否和其他外设共用例如 STM32 的 PA0 同时接了按键按键按下时会把引脚拉到不同电位锂电池本身在放电中电压是否稳定测量时不要用手同时捏住表笔两端人体电阻会改变分压回路。5.2 一个可靠的低电量提示阈值把 3.3V 作为最后 5%不同电池、不同负载下曲线末段的抖动最大。3.0V 以下区域的电压下降速度非常快可能几分钟内从 3.3V 掉到 2.8V用户根本没有反应时间。所以在系统里设置两级阈值一级告警电压低于 3.5VOLED 或 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 的读数、什么时候要停下来测一下硬件把这些过程放进报告里比你堆再多的参数都有说服力。本文还有配套的精品资源点击获取
返回列表