/* ****************************************************************************** * * @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表查表得到的SOC,(100-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_dsg,SOC_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