最近的折腾,汇总到一个帖子里,算是给2024年的交的一个作业吧。
分三个内容:
一、星河电子的XH-M240锂电池容量测试仪的拆并分析
二、ZB2L3电池容量测试仪的拆并画电路原理图
三、用Arduino验证测试
由于手头好几个充电宝使用的是18650锂电池,几年下来,容量有所减少。想着都拆开测量一下单节容量,重组一下。于是买了两种:
一、星河电子的XH-M240
整体情况 放电电流500mA左右,测量一节电池时间较长。
电路功能
电路
各功能电路分析如下:
系统的电源有两种供电方式,一是使用左边的电池仓,放入18650电池供电;二是使用外接5V电源供电。 两路供电使用了隔离二极管,防止倒灌。之后通过开关控制系统供电。供电经过C1滤波后,由U1时行3.3V稳压,整个系统使用的是3.3V电源的。
核心是一块标有FMD的MCU,16脚封装,外围电路有:LED指示、按键采样、电流电压采样、LED数码显示部分。 MCU的1、16脚是电源和地。 MCU的5、6、7脚是数码管正在显示毫瓦时、毫安时和正在测量的LED指示灯。 MCU的10脚是接收按键按下信号的。 MCU的11是被测电池的电压采样输入。 MCU的9脚是控制MOS管开关的,以控制是否进行放电测量。 MCU的12脚是被测电池放电电流的采样输入。 MCU的2、3脚是数码管显示“数字”的串行输出信号。 MCU的4、13、14、15脚是控制数码管位显示的信号。 另外,MCU的9、11脚应该是串行端口。 从引脚功能推断应该是辉芒微的FT64F0A3。
按键部分使用了MCU的一个端口,采用的是R19与R20、R21、R22不同的分压值,从而表示不同的按键被按下。
显示部分使用了一块无字MCU,用以将串行的信号进行“译码”,从而8段的数码显示。
电池电压的采样是由电阻R6、R7分压(衰减了0.118倍)后进行采样的,当电池为4.2V时,采样电压为4.2*6.8/(6.8+51)=0.5V。ADC内部参考电压可以为0.5V、2V、3V。不知配置为哪一档。 上图中的放电开关及电流采样可以简化为下图
图中,NMOS管U2完成被测电池放电回路的通断控制,MOS管的S极接地,参考电压为0V。电流采样电阻R2一端接地,另一端接被测电池的负极,当U2接通放电回路后,电流经过R8、U2、R2形成放电回路。此时,R2下端的电位相对于参考地的电位是负电位,而采样是从R2下端采集电压的,所以电流采样电压为负电压。当电池放电电流为3A时,采样电压为-60mV,百毫伏以下级的负电压一般要经过进一步处理,才能与后续电路使用。
电流采样放大部分如下图:
简化如下图:
输入与输出的关系如图,
在电路中,输入Vin不会出现正值,所以,输出Vout的在66.79mV以上,并随着输入Vin越负输出越高。 如果放电电流为3A时,采样放大输出为: Vout =30.3*3*20+66.79=1884.79(mV)=1.88V。 可能使用了内部2V参考。
运算放大器的输入输出关系如下
公式推导
相对误差
使用一节锂电池供电的这个电池仓,只能供电,不能外接充电,利用这个电池仓,加了一块4056的充电板,这样就可以电池充电、电池供电、USB供电了。
USB供电状态
整体电路图
二、另一种常见的是ZB2L3电池容量测试仪
外接负载放电,最大测试电流是3A,可测电1.2到12V的电池,也就是可以测试单块铅酸电池。配有两个7.5欧姆(5W)的放电电阻,如下图
我买到的是下面这样的,关键(运放周边的电阻)信息不一样的
为了看清楚,拆下部分元件
背面
标上元件代号
部分元件
电路如下
与上面的锂电池容量测试仪的原理一样,不做分析。 注意电流采样放大器周边电阻阻值,与上面的锂电池容量测试仪有一点不一样,其实,这个ZB2L3电池容量测试仪与上面的应该是一样的,我这个可能是山(修)寨(改)的,所以运放的放大倍数不一样。 这个输入与输入关系如下图 不知MCU中的算法是什么样的
相对误差如下:
三、自己献个丑
先仿真一个看看,
搭一个实际电路试试。
用8欧姆电阻放电,其中的4欧姆电阻做电流采样,现场情况。
使用Arduino板载5V做为参考电位,
用的是U盾上拆下来的ST7565的屏,放电开关使用NMOS管A09T。
电路如下:
改用0.5欧姆电阻进行采样,放电为(5.6+0.5=)6.1欧姆电阻。与NMOS管搭有一块洞洞板上
测量结果的查对误差如下
换了一个参考表,测量结果
两块参考表一起用,在500mAh时的情况
在1000mAH时的情况
看来两个参考表也是有误差的,而且很明显。自己做的肯定是有误差,想做修正,又没有标准源,只好找这两个参考表的中间值了。在算法中做个小修正
最后的结果如下
两个参考表在前后的位置也表现不同。 先不管了。误差产生:
一是参考电压的选择,是否准确;可用板载的电压,不同USB设备供电电压是不同的,所以可以选用外部参考电压。当然要求不高也可使用Arduino nano板的内部1.1V参考电压。
二是所用的电阻(采样及分压)精度是否高;电池电压测量使用的分压电阻是为了使用低参考电压而设置的,有误差只会影响放电终止电压。放电电阻中的电流采样电阻不精确,就会影响电池容量的测量。
三是系统布线是否合理,大电流线要粗且单独走线。这个就不用说了。其实这个测量精度对于本人而言没有多大意义,就是对18650电池衰减情况的一个判定,不做分容,足够了,打板就不做了,几块电池测完也是吃灰。
最后,贴出丑陋的原代码。
#include <Arduino.h> #include <U8g2lib.h> /* * 实际使用的是ST7565的12864屏,仿真时使用的是ST7920的12864屏。 * 所有串行输出均为调试时使用,若不用串行监视器,可以删除。 * 引脚A0采集电池电压值。 * 引脚A1采集取样电阻上的电压,并根据欧姆定律换算为电流值。 * 引脚D2为按键引脚,对地接一个按键开关。在测量结束后长按可复位。 * 引脚D4为MOSFET控制,测量结束可停止放电。 * 引脚D6为蜂鸣器引脚,对地接蜂鸣器,测试结后提示用。 * 引脚D7、D8、D9、D11、D13按照U8G2库要求接LCD屏。 * */
//实物使用 U8G2_ST7567_JLX12864_1_4W_SW_SPI u8g2(U8G2_R2, /* clock=*/ 13, /* data=*/ 11, /* cs=*/ 7, /* dc=*/ 9, /* reset=*/ 8); // 实际是ST7565的屏 //仿真使用 //U8G2_ST7920_128X64_1_SW_SPI u8g2(U8G2_R0, /* clock=*/ 13, /* data=*/ 11, /* CS=*/ 7, /* reset=*/ 8); // 仿真用 // 构造函数列表结束
const float BAT_LOW = 3.0; //定义锂离子电池的最低电压 const float BAT_HIGH = 5.3; //定义电池的最高电压,现为测试,可根据实际修改 const int Vref = 5.0; // 定义芯片的参考电压,用于电压采样的C/A转换!根据实际修改 const int BEE_Pin = 6; // 蜂鸣输出引脚 (pin5 pin6 频率为976.5625Hz) const int BUTTON = 2; // 按键引脚 const int SW = 4; // MOSFET控制引脚 unsigned long previousMillis = 0; // 上次时间(毫秒) unsigned long millisPassed = 0; // 经过时间(毫秒) unsigned long millisNow = 0; // 当前时间(毫秒) float Capacity = 0; // 定义电池容量的变量 const float Resistor = 0.48; // 定义采样电阻值为4欧姆,根据实际修改 float mA; float voltage; // 电压值 float current; // 电流值 int hours; int minutes; int seconds;
void setup(void) { pinMode(BUTTON, INPUT_PULLUP); //按键设为输入模式,内部上拉 analogReference(DEFAULT) ; // 参考电压使用默认参考值5v // 以9600bit的速度初始化串行通信: Serial.begin(9600); // 在串行监视器上输出相关信息,调试用 pinMode(SW, OUTPUT); digitalWrite(SW, LOW); // 关闭MOSFET,无放电 digitalWrite(BEE_Pin, LOW);
u8g2.begin(); u8g2.setContrast(100); // 设置对比度 u8g2.clearDisplay(); u8g2.setFont(u8g2_font_ncenB08_tr); // 选择字体 // 47 63 Serial.println( " BAT capacity tester >_< "); u8g2.firstPage(); do { u8g2.drawFrame(0, 0, 128, 64); u8g2.drawStr(10, 15, "BAT capacity test"); // 在坐标(10,15)处开始写入文本 u8g2.drawStr(10, 35, "Bat termination"); u8g2.drawStr(10, 50, "voltage: 3.0V"); } while ( u8g2.nextPage() );
delay(3000); }
void loop(void) { //u8g2.clearDisplay(); //u8g2.setFontDirection(0); while (digitalRead(BUTTON) == HIGH) { ReadV(); // 读取电压值 u8g2.firstPage(); do { u8g2.drawFrame(0, 0, 128, 64); u8g2.drawStr(10, 15, "BAT voltage : V"); // 在坐标(10,15)处开始写入文本 u8g2.setCursor(85, 15); u8g2.print(voltage); u8g2.drawStr(10, 35, "Press the BUTTON"); u8g2.drawStr(10, 50, "To start ! "); } while ( u8g2.nextPage() ); delay(200); } // 等待按下按键 delay(80); ReadV(); if ( voltage > BAT_HIGH) { digitalWrite(SW, LOW); // 关闭MOSFET,无放电 Serial.println( " Warning High-V! "); // Voltage alarm ! u8g2.firstPage(); do { Dframe_B(); u8g2.drawStr(10, 55, "HIGH Voltage!!"); // 在坐标(0,15)处开始写入文本 } while ( u8g2.nextPage() ); delay(2000); } else if (voltage < BAT_LOW) { digitalWrite(SW, LOW); // 关闭MOSFET,无放电 Serial.println( " Warning Low-V! "); u8g2.firstPage(); do { Dframe_B(); u8g2.drawStr(10, 55, "Low Voltage!!"); } while ( u8g2.nextPage() ); delay(2000); } else if (voltage >= BAT_LOW && voltage < BAT_HIGH ) { // 检查电池电压如果在安全范围内 previousMillis = millis(); while (1) { GaugeC(); // 测量放电容量 Serial.println( " During testing ... "); //u8g2.clearDisplay(); u8g2.firstPage(); do { Dframe(); Dframe_T(); u8g2.drawStr(10, 13, "During testing . . ."); } while ( u8g2.nextPage() ); millisToTime(); delay(1000); if (voltage <= BAT_LOW ) { EndGauge(); // 结束 } } } } //**********主程序结束*********
void Dframe_B() { // 电压告警输出画面 u8g2.drawFrame(0, 0, 128, 64); u8g2.drawHLine(0, 20, 128); u8g2.drawHLine(0, 42, 128); // Replace the BAT u8g2.drawStr(15, 15, "Voltage alarm !!"); u8g2.drawStr(5, 35, "The BAT voltage :"); u8g2.setCursor(105, 35); u8g2.print(voltage); //u8g2.drawStr(10, 55, "Replace the BAT"); }
void Dframe() { // 放电画面 u8g2.drawFrame(0, 0, 128, 64); u8g2.drawHLine(0, 20, 128); u8g2.drawVLine(45, 20, 64); //u8g2.drawStr(10, 13, "During testing ……"); u8g2.drawStr(5, 35, "V:"); u8g2.setCursor(20, 35); u8g2.print(voltage); u8g2.drawStr(5, 55, "I:"); u8g2.setCursor(20, 55); u8g2.print(current); u8g2.drawStr(52, 35, "C:"); u8g2.setCursor(68, 35); u8g2.print(Capacity); //u8g2.drawStr(100, 35,"mAH"); u8g2.drawStr(52, 55, "T:"); u8g2.setCursor(68, 55); }
void Dframe_T() { // LCD上显示放电时间 u8g2.setCursor(68, 55); if (hours < 10) u8g2.print('0'); // 补零 u8g2.print(hours); u8g2.setCursor(82, 55); u8g2.print(':'); u8g2.setCursor(86, 55); if (minutes < 10) u8g2.print('0'); // 补零 u8g2.print(minutes); u8g2.setCursor(100, 55); u8g2.print(':'); u8g2.setCursor(105, 55); if (seconds < 10) u8g2.print('0'); // 补零 u8g2.print(seconds); }
void ReadV() { // 模拟引脚A0:读入电池电压值 int sensorValue_voltage_Bat = analogRead(A0); // 将模拟读数(从0到1023)转换为电压(0到Vref的值) voltage = sensorValue_voltage_Bat * (Vref / 1023.0) * 5.0; // 根据分压比计算 // voltage = sensorValue_voltage_Bat * (Vref / 1023.0) * 5.05; // 带修正值 Serial.print("Voltage: "); // 串行口输出 Serial.print(voltage); // 串行监视器上显示电压值 delay(200); }
void ReadI() { // 模拟引脚A1:读入电阻上的电压值 int sensorValue_Shunt_Resistor = analogRead(A1); float voltage1 = sensorValue_Shunt_Resistor * (Vref / 1023.0); // 转换为电压值 //float voltage1 = sensorValue_Shunt_Resistor * (Vref / 1023.0) * 1.03; // 加入修正算法 current = voltage1 / Resistor; // 用欧姆定律计算 Serial.print(" Current: "); Serial.println(current); delay(200); }
void GaugeC() { // 通过电流值和时间计算一秒时间的放电容量 // analogWrite(MOSFET_Pin, PWM_VALUE); // D5输出PWM信号 digitalWrite(SW, HIGH); delay(30); ReadV(); ReadI(); millisPassed = millis() - previousMillis; mA = current * 1000.0 ; Capacity = Capacity + (mA * (millisPassed / 3600000.0)); // 1 Hour = 3600000ms 将其转换为毫安时单位 previousMillis = millis(); millisNow = millisPassed + millisNow; float TimePass = millisNow / 1000; Serial.print("Capacity , Time, "); Serial.print(Capacity); Serial.print(" , "); Serial.println(TimePass); Serial.println( ); delay(700); }
void EndGauge() { // 进入死循环,显示测量结束 digitalWrite(SW, LOW); // 关闭MOSFET,无放电 while (1) { Serial.println(); Serial.println( " TEST FINISHED @-@ "); millisToTime(); Serial.print("Capacity : "); Serial.print(Capacity); Serial.println(" mAH"); u8g2.firstPage(); do { Dframe(); Dframe_T(); u8g2.drawStr(10, 15, "FINISHED"); // 在坐标(10,15)处开始写入文本 } while ( u8g2.nextPage() ); analogWrite(BEE_Pin, 100); delay(900); if ( digitalRead(BUTTON) == LOW) { delay(2000); if ( digitalRead(BUTTON) == LOW) { restartArduino(); } } ReadV(); ReadI(); u8g2.firstPage(); do { Dframe(); Dframe_T(); u8g2.drawStr(10, 15, " "); } while ( u8g2.nextPage() ); digitalWrite(BEE_Pin, LOW); delay(600); } }
void millisToTime() { // 将时间转换为时分秒形式 hours = millisNow / 3600000; // 1小时 = 3600000毫秒 minutes = (millisNow % 3600000) / 60000; // 取余数后,1分钟 = 60000毫秒 seconds = (millisNow % 60000) / 1000; // 取余数后,1秒 = 1000毫秒 Serial.print("Passing time: "); if (hours < 10) Serial.print('0'); // 补零 Serial.print(hours); Serial.print(':'); if (minutes < 10) Serial.print('0'); // 补零 Serial.print(minutes); Serial.print(':'); if (seconds < 10) Serial.print('0'); // 补零 Serial.println(seconds); }
void restartArduino() { // 复位Arduino delay(10); asm volatile("jmp 0");
}
以上2024年不论是好过还是难过,都即将过去。展现在面前的2025年是全新的,需要努力拼搏。
祝各位坛友们及数码之家的工作人员平安健康!2025快乐!!
谢谢观赏!
|