一、说明
本文讲述锂电池在系统中进行充放电过程中进行电量百分比计算,仅供参考,电池电量百分比计算相对于来说是略微复杂,不仅需要分压电路并且还需要根据电池电压的大小来进行分级,因此除去最基础的ADC驱动(不同平台配置不同),整个充放电系统流程都有涉及到。
二、电路
1.电池电压检测电路
在电量百分比计算中,硬件电路尤为重要,简单来说就是对电池进行分压,运用串联电阻分压的特性,防止电压超过最高电压烧坏MCU的情况出现,检测电池电压。
2.充电电压检测电路
检测充电电压。
3.充电电流检测电路
检测充电电流电压。
三、软件计算
1ADC采集
建议使用DMA进行读取,这里从获取ADC的数据开始,再划出ADC缓存区,根据不同的采集通道来存放各自的数据。
//放置在.h文件
#define ADC_BUFFER_LEN 10 //ADC单个通道缓存区大小
#define ADC_CHANNEL_NUM 4 //ADC通道总数
#define ADC_BATTERY_VOLTAGE 0
#define ADC_BATTERY_NTC 1
#define ADC_CHARGE_VOLTAGE 2
#define ADC_CHARGE_CURRENT 3
/*定义ADC数据的结构体*/
struct ADC_Sample{
uint16_t buffer[ADC_BUFFER_LEN];
uint8_t size;
uint8_t index;
};
/*定义ADC通道枚举*/
enum{
BATTERY_VOLTAGE=0,
BATTERY_NTC,
CHARGE_VOLTAGE,
CHARGE_CURRENT,
};
uint16_t ADC_GetValue(uint8_t channel);
void Buffer_fill(struct ADC_Sample *channel,uint16_t data);
void Range_Updata(void);
//放置在.c文件
struct ADC_Sample ADC_buffer_fill[ADC_CHANNEL_NUM];
/*获取ADC相对应的通道数据*/
uint16_t ADC_GetValue(uint8_t channel){
if(channel >= ADC_CHANNEL_NUM){
return 0;
}
return ADC_Buffer[channel];
}
/*ADC缓存区循环填充*/
void Buffer_fill(struct ADC_Sample *channel,uint16_t data){
channel->buffer[channel->index] = data;
channel->index++;
if(channel->index >= CURRENT_BUFFER_LEN){
channel->index = 0;
}
}
/*更新ADC采样值*/
void Range_Updata(void)//需要在定时器进行中断更新
{
Buffer_fill(&ADC_buffer_fill[BATTERY_VOLTAGE],ADC_GetValue(ADC_BATTERY_VOLTAGE));
Buffer_fill(&ADC_buffer_fill[BATTERY_NTC],ADC_GetValue(ADC_BATTERY_NTC));
Buffer_fill(&ADC_buffer_fill[CHARGE_VOLTAGE],ADC_GetValue(ADC_CHARGE_VOLTAGE));
Buffer_fill(&ADC_buffer_fill[CHARGE_CURRENT],ADC_GetValue(ADC_CHARGE_CURRENT));
}
2.采集电池电压
电池采集的数据为电压类,因此采用电压类的ADC转换
/*获取电压类ADC*/
uint16_t Get_Voltage(uint8_t channle){
struct ADC_Sample *adc_dev = &vol_buffer[channle];
uint32_t temp = 0;
int i = 0;
for (; i < CURRENT_BUFFER_LEN; i++) {
temp += adc_dev->buffer[i];
}
return temp/CURRENT_BUFFER_LEN;
}
/*获取电池电压*/
float Get_BatteryVol(void)
{
return (float)Get_Voltage(BATTERY_VOLTAGE) / 4096.0f * 3.3f * xxx;//xxx为分压转换系数
}
3采集充电电压
同上
/*获取充电电压 */
float Get_ChargeVol(void)
{
return (float)Get_Vol(VOL_CHARGE) / 4096.0f * 3.3f * xxx;//xxx为分压转换系数
}
4.采集充电电流
电流类相较于电压类是需要进行转换的,因此充电电流采取电流类转换
/*充电电流电流类*/
uint16_t Get_Current(uint8_t channle){
struct ADC_Sample *adc_dev = ¤t_buffer[channle];
uint32_t temp = 0;
int i = 0;
uint16_t current;
for (; i < CURRENT_BUFFER_LEN; i++) {
temp += adc_dev->buffer[i];
}
temp/=CURRENT_BUFFER_LEN;
current = temp * 3300 / 4096 * 10;
return current;
}
/*当前充电电流*/
uint16_t Get_ChargeCurrent(void)
{
uint16_t current_I;
current_I = (uint16_t)(Get_Current(CHARGE_CURRENT)/10);
if(current_I > reference_current){
return (current_I-reference_current);
}else{
reference_current = current_I;
return 0;
}
}
5.判断是否接入适配器
通过判断充电电压是否在正常电压范围内进行判断有无适配器接入。
/*判断是否有外接电源适配器*/
uint8_t ChargeAccess(void){
charge_vol = ADC_GetValue(ADC_CHARGE_DET) / 4096.0f * 3.3f * xxx;//xxx为分压转换系数
if(charge_vol > 3.5f && charge_vol < 5.5f){
return 1;
}
return 0;
}
6.当前充电基准电流
基准电流在充电其实设置即可。
/*设置当前充电基准电流*/
void Set_ChargeRefCurrent(void)//在开启充电时进行设置
{
reference_current = (uint16_t)(Get_Current(CURRENT_CHARGE)/10);
}
6.充电过温保护
过温保护即为电池过温保护,使用NTC的查表法进行温度检测,达到设置温度停止充电,防止出现危险情况。
放置.h文件
#define NTC_HIGH_PRECISION 1//1=0.1℃ 0=1℃
#define NUM 141
//-20℃~120℃
static int16_t NTC3435_10K[NUM]=
{
3581,3559,3535,3511,3486,3461,3435,3408,3381,3353,
3324,3295,3266,3234,3203,3171,3138,3105,3072,3037,
3003, //0
2968,2932,2896,2859,2823,2785,2748,2710,2672,2633,
2595,2556,2517,2478,2438,2399,2360,2321,2281,2242,
2203,2164,2125,2086,2048,2009,1971,1933,1896,1858,
1822,1785,1749,1713,1678,1643,1896,1896,1540,1507,
1475,1436,1411,1380,1349,1319,1289,1260,1231,1203,
1176,1149,1122,1096,1070,1045,1021,997,974,951,
928,906,885,864,843,823,803,784,766,747,
729,712,695,678,662,646,631,616,601,587,
573,559,546,533,520,508,496,484,473,462,
451,440,430,420,410,400,391,382,373,365,
356,348,340,332,325,317,310,303,296,290,
283,277,271,265,259,253,247,242,237,231,
};
int16_t Read_NTC_Temperature(int16_t *list,u16 rADC,int16_t BaseValue);
//输入参数:ADC表 采集的ADC值 ADC表的起始温度值(-20℃=-200)
//返回值:温度值 单位0.1℃ 例如返回值是100,对应的就是100*0.1℃=10℃。
//该函数得到的结果还需要除以10得到的结果才是正确的结果
int16_t Read_NTC_Temperature(int16_t *list,uint16_t rADC,int16_t BaseValue)
{
uint16_t index=0;
uint16_t deta=0;
uint16_t t=0;
int16_t result=0;
if(rADC>=list[0])
return BaseValue;
if(rADC<=*(list+NUM-1))
{
result=((NUM-1)*10+BaseValue);
return result;
}
index=NTC_Lookup(list,rADC);
#if NTC_HIGH_PRECISION
deta=list[index]-list[index+1];
t=10*(list[index]-rADC)/deta;
#endif
result=(BaseValue+index*10+t);
return result;
}
7.电池充满判断
在充电电压到达一定数值后,电压变化不会太明显,为精确判断充电是否完成,因此根据最后充电电流的变化来确认充电是否完成,当电流降低达到一定值之后便认为是充电完成。
#define CHARGE_DONE_CURRENT 190 //充电电流小于190即判定为充满
uint8_t Is_Charge_Done(void)
{
return Charge_Finish;
}
void Reset_Charge_Done(void)
{
Charge_Finish = 0;
}
//判断语句,在合适的位置进行判断,如果充电电流小于190,并且电量不小于99%
if(Get_ChargeCurrent() < CHARGE_DONE_CURRENT && Get_Battery_Percent() >= 99){
if(Charge_Ctrl_Flag()){ //如果充电脚使能
if(!Is_Charge_Done()){
charge_time = Get_sysTime(); //充电完成时间
Charge_Finish = 1;
}
}
}
8.电量计算
最后是电量的计算,通过调用此函数就可以将电池电量百分比计算出来。
typedef enum{
NO_BATTERY = 0,
BATTERY_LEVEL_1,
BATTERY_LEVEL_2,
BATTERY_LEVEL_3,
BATTERY_LEVEL_4,
BATTERY_LEVEL_5,
}BatteryLevel; //五个等级
BatteryLevel batteryLevel;
//每个等级对应的电池电压
#define LOW_POWER_VAL (23.1f*6.0f/7.0f)//22.8
#define POWER_LEVEL1_VAL (24.9f*6.0f/7.0f)//24.2
#define POWER_LEVEL2_VAL (25.4f*6.0f/7.0f)//24.8
#define POWER_LEVEL3_VAL (26.0f*6.0f/7.0f)//25.6
#define POWER_LEVEL4_VAL (26.8f*6.0f/7.0f)//26.5
#define POWER_LEVEL5_VAL (27.8f*6.0f/7.0f)//27.8
/*延时充电等级*/
#define BATTERY_DELAY_LEVEL_1 1
#define BATTERY_DELAY_LEVEL_2 2
uint8_t battery_percent;//电池百分比
uint8_t battery_cur_count;//静态计数
uint32_t battery_refrece;//电池时间参考
static float battery_val;//获取电池电压
static const float battery_rate[6] ={LOW_POWER_VAL,POWER_LEVEL1_VAL,POWER_LEVEL2_VAL,POWER_LEVEL3_VAL,POWER_LEVEL4_VAL,POWER_LEVEL5_VAL};//每个等级对应的电池电压数组
bool is_first = true;//为第一次运行减少误差
static uint32_t battery_updata_rate;//更新速率等级
static uint8_t bat_diff_count = 0;//与上次的差异值
static uint8_t delay_charge = 0;//充电延时
void Delay_Charge_Set(uint8_t delay_level)
{
delay_charge = delay_level;
}
void Delay_Charge_Reset(void)
{
delay_charge = 0;
}
uint8_t Delay_Charge_Get(void)
{
return delay_charge;
}
void Charge_Ctrl_Enable(void)
{
GPIO_SetBits(CHARGE_CTRL_GPIO,CHARGE_CTRL_GPIO_PIN);
}
void Charge_Ctrl_Disable(void)
{
GPIO_ResetBits(CHARGE_CTRL_GPIO,CHARGE_CTRL_GPIO_PIN);
}
uint8_t Charge_Ctrl_Flag(void)
{
return GPIO_ReadOutputDataBit(CHARGE_CTRL_GPIO,CHARGE_CTRL_GPIO_PIN);
}
/*获取电池电量百分比*/
uint8_t Get_Battery_Percent(void)
{
int i = 0;
if(is_first){ //第一次跑机,延时获取
while(i++ < 20){
battery_val = Get_BatteryVal(); //获取电池电压
if(battery_val < 3.6f){//最小电压
}else if((battery_val > 4.3f) && ChargeAccess()){
}else{
break;
}
DELAY_WaitmS(100);//第一次获取数据延时一小段时间
}
}else if(delay_charge && ChargeAccess()){ //主要在充电完成之后
if((Get_sysTime() - Get_Charge_Start_Time() > 5*1000) && (Delay_Charge_Get() != BATTERY_DELAY_LEVEL_2)){ //延时级别1,正常延时5秒充电使能
battery_val = Get_BatteryVal();
if(battery_val <= battery_rate[BATTERY_LEVEL_5]){ //如果电池电压未到满压,立即开始充电
Charge_Ctrl_Enable();//开始充电
Delay_Charge_Reset();//清清除延时等级
//延时再次判断
DELAY_WaitmS(200);
battery_val = Get_BatteryVal();
if(battery_val > 4.8f || Read_NTC_Temperature(NTC3435_10K,Get_Battery_Temper(),-200)/10) > 40){ //充电过温保护,关闭充电
Charge_Ctrl_Disable();
Delay_Charge_Set(BATTERY_DELAY_LEVEL_2);
}
}
}
if(Get_sysTime() - Get_Charge_Start_Time() > 60*1000){ //延时级别2,过温保护延时1分钟充电使能
Charge_Ctrl_Enable();
Delay_Charge_Reset();
}
}
battery_val = Get_BatteryVal();
//动态调整电压值
if(battery_val ){//可要可不要
battery_val += 0.02f;
}else {
battery_val += 0;
}
//如果是充电脚,用于在充电的过程中电池电压相较于实际电压略高的问题
if(Charge_Ctrl_Flag()){
(battery_val > POWER_LEVEL5_VAL) ? (battery_val -= 0.05f) : ((battery_val > POWER_LEVEL4_VAL) ? (battery_val -= 0.15f) : (battery_val -= 0.2f));
}
//电池数据更新时间,根据不同电压调整电量衰减时间
if(battery_percent <= 20 ){
battery_updata_rate = 1;
}else if(battery_percent >= 80){
battery_updata_rate=16;
}else if(battery_percent >= 50){
battery_updata_rate = 8;
}else if(Charge_Ctrl_Flag()){ //充电速率
battery_updata_rate = 10;
}else{
battery_updata_rate = 4;
}
if((Get_sysTime() - battery_refrece > (uint32_t)(1000 * battery_updata_rate)) || is_first){
if (battery_val >= battery_rate[BATTERY_LEVEL_4]){
/* 充电时: 80%-90%电压判断,90%-100% 电流判断 */
if (Charge_Ctrl_Flag()) {
if (battery_val >= battery_rate[BATTERY_LEVEL_5]) {
battery_cur_count=(uint8_t)((11-(Get_ChargeCurrent()/100))+90);
if (Is_Charge_Done()) {
battery_cur_count = 100;
} else if (battery_cur_count > 99) {
if (Is_Charge_Done()) {
battery_cur_count = 100;
} else {
battery_cur_count = 100;
}
}
batteryLevel = BATTERY_LEVEL_5;
} else if (battery_val >= battery_rate[BATTERY_LEVEL_4]) {
battery_cur_count = (uint8_t) ((battery_val - battery_rate[BATTERY_LEVEL_4]) /(battery_rate[BATTERY_LEVEL_5] - battery_rate[BATTERY_LEVEL_4]) * 10 + 80);
batteryLevel = BATTERY_LEVEL_4;
}
} else {
/* 耗电时,全部按电压判断 */
if(battery_val >= battery_rate[BATTERY_LEVEL_4]){
battery_cur_count = (uint8_t)((battery_val - battery_rate[BATTERY_LEVEL_4])/(battery_rate[BATTERY_LEVEL_5] - battery_rate[BATTERY_LEVEL_4])*20 + 80);
batteryLevel = BATTERY_LEVEL_4;
if (Is_Charge_Done() && Charge_Ctrl_Flag()) { //充电完成并且有充电标志的情况下才显示100%
batteryLevel = BATTERY_LEVEL_5;
battery_cur_count = 100;
} else if (battery_cur_count > 99) {
if (Is_Charge_Done()) {
batteryLevel = BATTERY_LEVEL_5;
battery_cur_count = 100;
} else {
battery_cur_count = 100;
}
}
}
}
} else if(battery_val >= battery_rate[BATTERY_LEVEL_3]){
battery_cur_count = (uint8_t)((battery_val - battery_rate[BATTERY_LEVEL_3])/(battery_rate[BATTERY_LEVEL_4] - battery_rate[BATTERY_LEVEL_3])*20 + 60);
batteryLevel = BATTERY_LEVEL_3;
}else if(battery_val >= battery_rate[BATTERY_LEVEL_2]){
battery_cur_count = (uint8_t)((battery_val - battery_rate[BATTERY_LEVEL_2])/(battery_rate[BATTERY_LEVEL_3] - battery_rate[BATTERY_LEVEL_2])*20 + 40);
batteryLevel = BATTERY_LEVEL_2;
}else if(battery_val >= battery_rate[BATTERY_LEVEL_1]){
battery_cur_count = (uint8_t)((battery_val - battery_rate[BATTERY_LEVEL_1])/(battery_rate[BATTERY_LEVEL_2] - battery_rate[BATTERY_LEVEL_1])*20 + 20);
batteryLevel = BATTERY_LEVEL_1;
}else if(battery_val >= battery_rate[NO_BATTERY]){
battery_cur_count = (uint8_t)((battery_val - battery_rate[NO_BATTERY])/(battery_rate[BATTERY_LEVEL_1] - battery_rate[NO_BATTERY])*20 + 0);
batteryLevel = NO_BATTERY;
}else{
battery_cur_count = 1;
}
//如果跟上一次的百分比不一致
if (battery_cur_count != battery_percent){
bat_diff_count ++;
} else {
bat_diff_count = 0;
}
if (!is_first) { //非首次运行
//如果不同次数大于2次
if (bat_diff_count >= 2){
bat_diff_count = 0;
if (ChargeAccess() && battery_cur_count > battery_percent){
++battery_percent;
} else if (battery_cur_count < battery_percent){
--battery_percent;
}
}
} else {
is_first = false;
//如果需要做掉电保存还可以在此处进行Flash的电量读取
}
//限定范围在1~100
battery_percent = battery_percent>100? 100: battery_percent;
battery_percent = battery_percent<1 ? 1: battery_percent;
battery_refrece = Get_sysTime(); //获取系统时间为下一次电量计算做准备
}
return battery_percent;
}