SY8835_For_Demo_Ourself/UsrSrc/bat/bat.c

604 lines
17 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
******************************************************************************
*
* @file bat.c
* @brief Voltage-based RC model gauge algorithm
* @ic sy8835
*
* @version 1.0
* @date 2024/11/01 17:35:40
* @author Alex Xu
*
* Copyright (c) 2013-2099,Tkplusemi Technology Co.,Ltd.
* All Rights Reserved
*
* History:
* Revision Date Author Desc
* 1.0.0 2024/11/01 Alex build this file
******************************************************************************
*/
#include "bat.h"
#include "adc.h"
#include "pmu.h"
idata uint8_t bat_level = 0;
idata uint8_t bat_level_Pec = 0;
bit F_batlevel_low = 0; //电池低压
#if 0
#if GAUGE_ENABLE
/******************************************************************************\
Macro definitions
\******************************************************************************/
/******************************************************************************\
Variables definitions
\******************************************************************************/
static idata const uint16 ocv_zero = 3200;
static idata const uint16 ocv_full = 4200;
static idata uint8 ocv_delta[20] = {255 ,93 ,24 ,48 ,38 ,41 ,34 ,30 ,33 ,40 ,48 ,66 ,84 ,78 ,74 ,79 ,82 ,85 ,86 ,85};
//带插值版的参数
static idata const uint16 kint_0deg[5] = { 25, 32, 31, 27, 24}; //初值当小于0度时采用此值
static idata const uint8 Kint_soc0[6] = { 1, 22, 160, 255, 25, 20 }; //SOC=0~10%不同温度Kint的delta值每8度一个delta值*8如第一个值代表0~7度第二个值代表8~15度
static idata const uint8 Kint_soc1[6] = { 6, 0, 12, 110, 0, 0 }; //SOC=10~25%不同温度Kint的delta值每8度一个delta值*8如第一个值代表0~7度第二个值代表8~15度
static idata const uint8 Kint_soc2[6] = { 0, 10, 15, 40, 10, 0 }; //SOC=25~50%不同温度Kint的delta值每8度一个delta值*8如第一个值代表0~7度第二个值代表8~15度
static idata const uint8 Kint_soc3[6] = { 38, 18, 48, 160, 38, 29 }; //SOC=50~80%不同温度Kint的delta值每8度一个delta值*8如第一个值代表0~7度第二个值代表8~15度
static idata const uint8 Kint_soc4[6] = { 14, 45, 80, 210, 88, 29 }; //SOC=80~100%不同温度Kint的delta值每8度一个delta值*8如第一个值代表0~7度第二个值代表8~15度
//uint8 k_ratio_chg_fast[5];
//uint8 k_ratio_chg_norm[5];
static idata const uint8 k_ratio_chg_temp[7] = { 128, 54, 42, 32, 32, 46, 32 }; //20210304 充电kint各温度区间补偿系数
static idata const uint8 k_ratio_chg_temprange[6] = { 5, 10, 16, 35, 44, 55 }; //将温度划分成7个区间默认为5度以下5~9,10~14,15~34,35~44,45~55,55以上
//int8 fastchgth; //快充阈值当充电电流大于这个阈值时选择k_ratio_chg_fast否则选择k_ratio_chg_norm
//高位开始代表从15度开始SOC值是否需要减1如15度相比25度容量衰减超过1%则bit15=1然后14度和15度一样则bit14=0如果13度比15度容量小1%则bit13=1依次类推
//小于0度固定和0度补偿值一样此方式最大补偿值为每1度补1%16度以上不补偿
static idata uint16 c_delta = 0;
//用户写入参数
uint8 c_ratio_customer = 0; //客户参数区写入的值,客户预存各种场景容量损失,可以灵活补偿负载/温度/老化等多种场景
//电量计中间变量
static uint32 K_int = 0;
static uint16 OCV_int16 = 0;
static uint32 OCV_int32 = 0;
static uint8 SOC_int = 0; //直接从OCV表查表得到的SOC100-SOC_int*Qmax为到充满需要的容量SOC_int由于未补偿负载除非常温和负载大小和建模一致否则放电使用此SOC会有误差
static int8 SOC_dsg = 0; //放电时使用的SOC在不同温度下会有跳变SOC_dsg=SOC_int-c_ratio_0deg/c_ratio_10deg
uint8 SOC_report = 0; //上报的SOC值结合库仑计信息和SOC_int/SOC_dsg之间
//20210219 更新基于OCV的平滑算法
static uint16 OCV_deltahalf = 0; //用于追赶SOC的OCV累计溢出值取各SOC 0.5%对应的OCV delta值
static uint8 OCV_count = 0; //计算当前和上次更新SOC_report的差值
static uint16 OCV_lastupdate = 0; //记录上次更新SOC_report的OCV值
/******************************************************************************\
Functions definitions
\******************************************************************************/
//20210304新增对于充电温度补偿
/*
*******************************************************************************
* static uint8 Kint_Rate_chg(int8 temperature_s)
*
* Description : 对充电温度进行补偿
* Arguments : NONE
* Returns : NONE
* Notes : NONE
*
*******************************************************************************
*/
static uint8 Kint_Rate_chg(int8 temperature_s)//查找不同温度下充电时Kint补偿系数
{
if(temperature_s > k_ratio_chg_temprange[5])
return k_ratio_chg_temp[6];
if(temperature_s > k_ratio_chg_temprange[4])
return k_ratio_chg_temp[5];
if(temperature_s > k_ratio_chg_temprange[3])
return k_ratio_chg_temp[4];
if(temperature_s > k_ratio_chg_temprange[2])
return k_ratio_chg_temp[3];
if(temperature_s > k_ratio_chg_temprange[1])
return k_ratio_chg_temp[2];
if(temperature_s > k_ratio_chg_temprange[0])
return k_ratio_chg_temp[1];
return k_ratio_chg_temp[0];
}
/*查表获得Kint值*/
/*
*******************************************************************************
* static uint16 Kint_Find_delta(int8 temperature_s)
*
* Description : 对充电温度进行补偿
* Arguments : NONE
* Returns : NONE
* Notes : NONE
*
*******************************************************************************
*/
static uint16 Kint_Find_delta(int8 temperature_s, uint8 current_s)
{
uint8 temprange,i,temperature_use,tempindex;
// uint16 Kint_chg_temp = 0;
uint32 Kintsum = 0;
// uint16 Kint_temp = 0;
if (temperature_s < 0)
temperature_use = 0;
else if (temperature_s > 47)
temperature_use = 47;
else
temperature_use = temperature_s; //温度限定在0~63度超过63度或者低于0度则用63度和0度 temperature_s = 35
temprange = temperature_use >> 3; //找出温度区间 temperature_use/8 = 35/8 = 4 为何要用8取模和取余Kinit表是以8度为一个区间取值
tempindex = temperature_use - (temprange << 3); //对温度取余,得到某个区间第几个温度值 temperature_use%8 = 35 % 8 = 3
if (SOC_int > 80)
{
for (i = temprange; i > 0; i--)
{
Kintsum = Kintsum + (((uint32)Kint_soc4[i-1])<<3); //温度大于某个区间就从0区间到此区间的所有的delta值*8并累加例如35℃所在温度区间为32~39℃第四区间。
}
for (i = tempindex; i > 0; i--)
{
Kintsum = Kintsum + ((uint32)Kint_soc4[temprange]); //将不足一个区间0-8只间的值数据按照加法累加计算温度余数对应的kint累加值例如35℃在温度区间余数为35-32=3℃。
}
Kintsum = Kintsum >> 3; //由于delta放大了8倍这里要除以8
Kintsum = Kintsum + kint_0deg[4]; //得到此温度下的放电Kint值
}
else if (SOC_int > 50)
{
for (i = temprange; i > 0; i--)
{
Kintsum = Kintsum + (((uint32)Kint_soc3[i-1]) << 3); //温度大于某个区间就将区间的delta值*8
}
for (i = tempindex; i > 0; i--)
{
Kintsum = Kintsum + ((uint32)Kint_soc3[temprange]); //将不足一个区间8个值数据按照加法累加
}
Kintsum = Kintsum >> 3; //由于delta放大了8倍这里要除以8
Kintsum = Kintsum + kint_0deg[3]; //得到此温度下的放电Kint值
}
else if (SOC_int > 25)
{
for (i = temprange; i > 0; i--)
{
Kintsum = Kintsum + (((uint32)Kint_soc2[i-1]) << 3); //温度大于某个区间就将区间的delta值*8
}
for (i = tempindex; i > 0; i--)
{
Kintsum = Kintsum + ((uint32)Kint_soc2[temprange]); //将不足一个区间8个值数据按照加法累加
}
Kintsum = Kintsum >> 3; //由于delta放大了8倍这里要除以8
Kintsum = Kintsum + kint_0deg[2]; //得到此温度下的放电Kint值
}
else if (SOC_int > 10)
{
for (i = temprange; i > 0; i--)
{
Kintsum = Kintsum + (((uint32)Kint_soc1[i-1] )<< 3); //温度大于某个区间就将区间的delta值*8
}
for (i = tempindex; i > 0; i--)
{
Kintsum = Kintsum + ((uint32)Kint_soc1[temprange]); //将不足一个区间8个值数据按照加法累加
}
Kintsum = Kintsum >> 3; //由于delta放大了8倍这里要除以8
Kintsum = Kintsum + kint_0deg[1]; //得到此温度下的放电Kint值
}
else
{
for (i = temprange; i > 0; i--)
{
Kintsum = Kintsum + (((uint32)Kint_soc0[i-1]) << 3); //温度大于某个区间就将区间的delta值*8
}
for (i = tempindex; i > 0; i--)
{
Kintsum = Kintsum + ((uint32)Kint_soc0[temprange]); //将不足一个区间8个值数据按照加法累加
}
Kintsum = Kintsum >> 3; //由于delta放大了8倍这里要除以8
Kintsum = Kintsum + kint_0deg[0]; //得到此温度下的放电Kint值
}
if(current_s > 0)
{
Kintsum = Kintsum * Kint_Rate_chg(temperature_s);
}
return (uint16)Kintsum;
}
//2023/2/17以上代码为通过查表找出随SOC温度和充放电倍率变化的Kint值可以根据需要适当减少数组数量增加常温下不同SOC区间的Kint值以提高常温精度
//2023/2/17下面代码引入current_s标志位主要是为了解决没有充放电时SOC出现反弹的问题
//2023/2/17 如果对低温性能要求不高可以删掉SOC_dsgSOC_Dsg的只是单纯在SOC_int基础上低温时直接减去一个随温度变化的值SOC_dsg = SOC_int - c_ratio_customer;
//如果不对SOC_int做低温补偿就不会出现SOC_dsg这个值造成差异。那么这个代码就可以精简掉SOC_dsg
//这里代码还做了一个基于OCV_count的平滑如果OCV差值只要超过0.5%OCV_deltahalf就允许快速平滑SOC。
//实际应用例子因为我们的OCV-SOC_int表是基于常温的假设某个OCV值下SOC_int=20%,但是目前是低温状态查表得到c_ratio_customer=10%那么SOC_dsg=10%。即实际当前是只有10%
//电量但是SOC_int=20%这时上报的SOC_report就要从初始的20%(=SOC_int)平滑到放电截止的时0%SOC_dsg=0%SOC_int=10%因此SOC_report就要以2倍的速度下降所以引入OCV_deltahalf
//但还有一种场景是在低温下SOC_report=SOC——dsg但是此时恢复到常温比如低温下SOC_report=SOC_dsg=10%,但是回到常温对应电量有20%因此显示10%要更满速度到0%才能使电量充分放完
static void SOC_report_cal(uint8 current_s) //current_s在具体实现上用charger存在标志位取代即可
{
if(OCV_int16 > OCV_lastupdate) //取绝对值
OCV_count = OCV_int16 - OCV_lastupdate;
else
OCV_count = OCV_lastupdate - OCV_int16;
if (current_s > 0) //充电时建议改成charger插入状态
{
if((SOC_int == 100) && (SOC_report < 100)) //如果SOC_int已经到达100%则SOC_report快速趋近100%
{
SOC_report++;
OCV_lastupdate = OCV_int16;
}
else if((OCV_count > OCV_deltahalf) && (SOC_int > SOC_report))
{
SOC_report++;
OCV_lastupdate = OCV_int16;
}
}
else //放电时
{
if((SOC_dsg == 0) && (SOC_report > 0)) //如果SOC_dsg已经到达0%则SOC_report快速趋近0%
{
SOC_report--;
OCV_lastupdate = OCV_int16;
}
else if((OCV_count > OCV_deltahalf) && (SOC_report > SOC_dsg))
{
SOC_report--;
OCV_lastupdate = OCV_int16;
}
else if((OCV_count > (OCV_deltahalf << 2)) && (SOC_report < SOC_dsg)) //OCV变化2%SOC对应的Delta允许变化1%
{
if(SOC_report > 0)
{
SOC_report--;
OCV_lastupdate = OCV_int16;
}
}
}
if(SOC_report == 100)
OCV_lastupdate = ocv_full;
if(SOC_report == 0)
OCV_lastupdate = ocv_zero;
bat_level = SOC_report;
}
//20210108更新版的SOC_dsg计算方法计算出温度或者客户写入的容量损失后的容量
//20210118修正代码中bug
static void SOC_dsg_cal(int8 temperature_s)
{
int8 i;
uint16 k;
SOC_dsg = SOC_int;//大于16度保持不变
if (temperature_s < 16)//低于16度做补偿,按位补偿相减
{
k = 0x8000;
for (i=16; i > temperature_s; i--)
{
if (c_delta & k)
SOC_dsg--;
k = k >> 1;
}
}
if (c_ratio_customer != 0)
{
SOC_dsg = SOC_int - c_ratio_customer;
}
if (SOC_dsg < 0)
SOC_dsg = 0;
}
//2023/2/17 查OCV表计算SOC_int
static uint8 SOCint_Calc(uint16 ocv)
{
uint8 i = 0;
uint8 k, j;
uint16 ocv_sum;
uint16 ocv_now;
ocv_now = (uint16)ocv << 3;
ocv_sum = ocv_zero << 3;
for (k = 0; k < 20; k++)
{
for (j = 0; j < 5; j++)
{
//2021/04/01 避免OCV=OCV_zero时SOC=1%而不是0%
if ((ocv_sum) >=(ocv_now))
goto Findsoc;
ocv_sum = ocv_sum + ocv_delta[k];
i++;
}
}
Findsoc:
//20210219
OCV_deltahalf = ocv_delta[k] >> 4;//OCV_deltahalf取0.5%对应的OCV差值由于ocv_delta[k]为8倍的1%SOC OCV差值
return i;
}
/*
*******************************************************************************
* void Gauge_Init(int vbat_init, int current_init, int temp_init)
*
* Description : Gauge Initialization
*
* Arguments :int vbat_init:Bat Adc Value
* int current_init:Charger Exist Flag
* int temp_init: Bat NTC Adc Value
*
* Returns :None
* Notes :
*
*******************************************************************************
*/
static void Gauge_Init(int vbat_init, uint8 current_init, int temp_init)
{
if (vbat_init < (ocv_zero))
{
OCV_int16 = (ocv_zero);
}
else if(vbat_init > (ocv_full))
{
OCV_int16 = (ocv_full);
}
else
OCV_int16 = vbat_init;
OCV_int32 = ((uint32)OCV_int16) << 16;
SOC_int = SOCint_Calc((uint16)(OCV_int32 >> 16));
SOC_dsg_cal((int8)temp_init);
//testdata = SOC_dsg;
SOC_report_cal(current_init);//平滑处理返回 平滑后的SOC值SOC_report
}
/*
*******************************************************************************
* void Gauge_Update(int vbat_new, int current_new, int temp_new)
*
* Description : Gauge Updata
*
* Arguments : int vbat_new:Bat Adc Value
int current_new:Charge Current
int temp_new:Bat NTC Value
* Returns : None
* Notes :
*
*******************************************************************************
*/
static void Gauge_Update(int vbat_new, uint8 current_new, int temp_new)
{
K_int = Kint_Find_delta((int8)temp_new,current_new);
OCV_int32 = OCV_int32 - (K_int * (OCV_int32 >> 16)) + (K_int * vbat_new);
OCV_int16 = (OCV_int32 >> 16);
if (OCV_int16 > (ocv_full))
{
OCV_int32 = ((uint32) ocv_full ) << 16;
}
if (OCV_int16 < (ocv_zero))
{
OCV_int32 = ((uint32) ocv_zero) << 16;
}
//20210401 增加 OCV_int16 = (OCV_int32 >> 16);避免没有及时更新OCV_int16
OCV_int16 = (OCV_int32 >> 16);
SOC_int = SOCint_Calc(OCV_int16);
SOC_dsg_cal((int8)temp_new);
SOC_report_cal(current_new);//平滑处理返回 平滑后的SOC值SOC_report
}
void Bat_Cal_Init(void)
{
uint8 Charger_On = 0;
uint8 Vbat_Init = 0;
uint16 Vbat_NTC_Value = 0;
Charger_On = CHIP_STA4 & 0x07;
#if ADC_ENABLE
Vbat_Init = Vbat_Adc; //获取Bat电压
Vbat_NTC_Value = Vntc_Adc;
#endif
Gauge_Init(Vbat_Init,Charger_On,Vbat_NTC_Value);
#ifdef _DEBUG_BAT
printf("Gauge Init!\r\n");
#endif
}
void Bat_Gauge_Handle(void)
{
uint8 Charger_On = 0;
uint8 Vbat_Adc_Value = 0;
uint16 Vbat_NTC_Value = 0;
Charger_On = CHIP_STA4 & 0x07;
#if ADC_ENABLE
Vbat_Adc_Value = Vbat_Adc; //获取Bat电压
Vbat_NTC_Value = Vntc_Adc;
#endif
Gauge_Update(Vbat_Adc_Value,Charger_On,Vbat_NTC_Value);
}
#endif
#else
//bit F_batlevel_low = 0; //电池低压
#if BAT_VALUE
#define C_offset_bat_level_MAX 150
#define C_offset_bat_level_MIN 10
#define C_bat_level_protect 0
#define C_bat_level_lowpower 1
bit F_batlevel_protect = 0; //低电保护
idata uint8_t offset_bat_level = ( C_offset_bat_level_MAX + C_offset_bat_level_MIN ) / 2;
#define C_batLevel_SetMax 20
/*充电电池电压会浮高,具体电压需要测试。*/
const uint16_t Boost_batlevel_Threshold[C_batLevel_SetMax] =
{
3000, //0%
3050, //5%
3100, //10%
3200, //15%
3250, //20%
3300, //25%
3350, //30%
3400, //4
3450,
3500, //5
3575,
3650, //6
3720,
3800, //7
3850,
3900, //8
3975,
4050, //9
4125,
4200 //10
};
#if 0
const uint16_t Charging_batlevel_Threshold1[C_batLevel_SetMax] =
{
3050, //1
3150, //2
3350, //3
3450, //4
3550, //5
3700, //6
3850, //7
3950, //8
4100, //9
4200 //10
};
#endif
void check_bat_level(void)
{
uint8_t i = 0;
//get bat level
#if ADC_ENABLE
for(i=0; i < C_batLevel_SetMax; i++)
{
#if 0
if( Vbat_Adc < Boost_batlevel_Threshold[i] )
{
break;
}
#else
if(ChgStatus == CHG_STA_ING) //充电
{
if( Vbat_Adc < Boost_batlevel_Threshold[i] + 50 )
break;
}
else //放电
{
if( Vbat_Adc < Boost_batlevel_Threshold[i] )
{
break;
}
}
#endif
}
#endif
if( i > bat_level )
{
offset_bat_level++;
}
else
{
offset_bat_level--;
}
if( pmu_Info.pmu_Chip_STA & INIT_OK )
{
bat_level = i;
bat_level_Pec = 5 * bat_level;
}
//debounce
if( (offset_bat_level > C_offset_bat_level_MAX) || (offset_bat_level < C_offset_bat_level_MIN) )
{
//update
bat_level = i;
bat_level_Pec = 5 * bat_level;
offset_bat_level = ( C_offset_bat_level_MAX + C_offset_bat_level_MIN ) / 2;
}
#if 1
if( bat_level <= C_bat_level_protect )
{
F_batlevel_protect = 1;
}
else if( bat_level > ( C_bat_level_protect + 1 ) )
{
F_batlevel_protect = 0;
}
#endif
if( bat_level <= C_bat_level_lowpower )
{
F_batlevel_low = 1;
}
else if( bat_level > (C_bat_level_lowpower + 1) )
{
F_batlevel_low = 0;
}
}
#endif
#endif