赞
踩
MTK方案的电池充电过程分为预充、恒流充电(CC模式)、恒压充电(CV模式)三种模式,整个充电过程如下充电状态图所示:
从充电状态图看出来,刚开始充电的时候,代码先判断是插USB充电还是插ac充电,电池在进入充电阶段分为快速充电、CC(恒流充电)、CV(恒压充电)。而从CC模式切换到CV模式在代码中的alps/mediatek/kernel/drivers/power/linear_charging.c和alps/mediatek/kernel/drivers/power/ switch_charging.c。
MTK Battery系统驱动的大致流程主要是通过系统platform总线注册device和driver,然后在probe函数里面创建了一个线程,然后创建一个hrtimer定时器,定时器没10s运行一次,同时在probe函数里面会创建一些设备节点,通过这些设备节点,系统将每10s更新的数据上传给上层供上层调用显示。
首先先介绍几个电压值:
VCHG:USB正极
VCDT:充电电压检测脚
ISENSE:充电电流检测电阻的正极
BATSNS:充电电流检测电阻的负极
BAT:电池正极引脚
BAT_ON:电池NTC(热敏电阻)引脚
通过三极管可以开启,关闭充电功能,开启充电的时候调节三极管的基级电流可以控制流过三极管CE端的电流从而实现充电电流大小的设置。
Rsense采样电阻:对于软件来说可以测量充电电流的大小:电流计算方法:(ISENSE – VBAT)/Rsense。对于PMU来说通过Rsense可以实现电流控制。比如要实现1A的充电电流,Rsense为0.2欧。PMU实现该电流的方法就是设法一直保证Rsense两端的电压是0.2V。
BAT_ON在充电中的作用:
1,检测电池是否存在。电压必须小于1.062V,否则认为电池不存在不能充电,硬件行为,软件无法关闭此功能
2,高温检测,电池电压必须大于0.2V,否则认为电池温度过高不能充电,硬件行为,软件可以关闭此功能
3,软件检测电池温度。
上图中NTC电阻会串联一个24k的电阻,这个会在后面源代码分析时起作用。MTK电池显示的具体过程为:硬件ADC读取Battery的各路信息:包括温度,电压等。然后利用MTK开发的电量算法分析得到的数据。Kernel层将电量信息通过写文件节点的方式更新,并通过UEVENT通知上层。上层Service开启UEVENT LISTENER,监听到UEVENT后,读取battery相关文件节点,获取电量信息。Service更新数据后,通过Broadcast通知所有开启了相关listener的activities。
根据不同的电量读取和计算的策略,第一步的读取和第二步的算法部分会有比较大的差异,而后面的数据更新和事件通知部分一致性较高。在分析源代码前,先来看看PMU_ChargerStruct这个结构体。
- 146 typedef struct
- 147 {
- 148 kal_bool bat_exist; // 判断电池是否存在
- 149 kal_bool bat_full; //判断电池是否充满
- 150 INT32 bat_charging_state; //判断充电状态
- 151 UINT32 bat_vol; //电池平均电压
- 152 kal_bool bat_in_recharging_state; //电池是否在回充
- 153 kal_uint32 Vsense; // 电池瞬间电压
- 154 kal_bool charger_exist; // Charger是否存在Charger电压
- 155 UINT32 charger_vol; // Charger电压
- 156 INT32 charger_protect_status; //充电保护状态,过流或者过压保护状态
- 157 INT32 ICharging; // 充电电流
- 158 INT32 IBattery;
- 159 INT32 temperature; // 电池温度
- 160 INT32 temperatureR;
- 161 INT32 temperatureV;
- 162 UINT32 total_charging_time; //总的充电时间
- 163 UINT32 PRE_charging_time; // Pre cc充电时间
- 164 UINT32 CC_charging_time; //cc充电时间
- 165 UINT32 TOPOFF_charging_time; //TOPOFF充电时间
- 166 UINT32 POSTFULL_charging_time; //Postfull充电时间
- 167 UINT32 charger_type; //充电器类型
- 168 INT32 SOC; //底层的电量
- 169 INT32 UI_SOC; // 上层的电量
- 170 UINT32 nPercent_ZCV;
- 171 UINT32 nPrecent_UI_SOC_check_point; //N%同步点对应的开路电压以及UI电量
- 172 UINT32 ZCV; //电池当前开路电压
- 173 } PMU_ChargerStruct;
结构体中个变量定义可参考注释,PMU_ChargerStruct记录了整个充电代码的所有充电的变化情况,包括上报给上层的电量值,可以说整个充电的过程就是围绕这这个结构体进行数值的变化,所以这个结构体在整个充电过程中起着非常重要的作用。
下面来看看代码的具体实现。
MTK的battery的注册是通过platform总线实现的,在mediatek/kernel/drivers/power/battery_common.c中,我们会看到对battery的device和driver进行注册的函数,device和driver通过name进行匹配,device和driver注册后,就是执行battery_driver结构体里面的函数,执行battery_driver里面的函数首先运行的是battery_probe函数,在battery_probe函数里面,先对battery进行字符设备的注册,注册的字符设备名为MT_pmic_adc_cali。在注册完字符设备后,probe会执行get_charging_control();这个函数,这个函数在整个充电过程中起着很重要的作用,该函数就一行代码:
- static void get_charging_control(void)
- {
- battery_charging_control = chr_control_interface;
- }
- kal_int32 chr_control_interface(CHARGING_CTRL_CMD cmd, void *data)
- {
- kal_int32 status;
- if(cmd < CHARGING_CMD_NUMBER)
- status = charging_func[cmd](data);
- else
- return STATUS_UNSUPPORTED;
- return status;
- }
在get_charging_control函数里面,就是将chr_control_interface函数指向battery_charging_control,在后面会有很多对battery_charging_control函数的调用,而所有的调用都是传递一个参数进来,然后对比charging_func数组里面的函数指正,在对其他函数进行调用。下面看看battery_charging_control(CHARGING_CMD_GET_PLATFORM_BOOT_MODE,&g_platform_boot_mode);这个语句的调用,就可以知道battery_charging_control函数是怎样运行的。
- typedef enum
- {
- CHARGING_CMD_INIT,
- CHARGING_CMD_DUMP_REGISTER,
- CHARGING_CMD_ENABLE,
- CHARGING_CMD_SET_CV_VOLTAGE,
- CHARGING_CMD_GET_CURRENT,
- CHARGING_CMD_SET_CURRENT,
- CHARGING_CMD_SET_INPUT_CURRENT,
- CHARGING_CMD_GET_CHARGING_STATUS,
- CHARGING_CMD_RESET_WATCH_DOG_TIMER,
- CHARGING_CMD_SET_HV_THRESHOLD,
- CHARGING_CMD_GET_HV_STATUS,
- CHARGING_CMD_GET_BATTERY_STATUS,
- CHARGING_CMD_GET_CHARGER_DET_STATUS,
- CHARGING_CMD_GET_CHARGER_TYPE,
- CHARGING_CMD_GET_IS_PCM_TIMER_TRIGGER,
- CHARGING_CMD_SET_PLATFORM_RESET,
- CHARGING_CMD_GET_PLATFORM_BOOT_MODE,
- CHARGING_CMD_SET_POWER_OFF,
- CHARGING_CMD_NUMBER
- } CHARGING_CTRL_CMD;
CHARGING_CMD_GET_PLATFORM_BOOT_MODE定义在一个枚举型变量中,并且在枚举类型中的是第十七个元素,而枚举类型如果没有初始化第一参数时第一个参数就为0,所以CHARGING_CMD_GET_PLATFORM_BOOT_MODE的值就为16,而battery_charging_control函数是通过chr_control_interface函数指向相同的地址的,所以,调用battery_charging_control函数并传递参数实际就是对chr_control_interface函数进行操作,在chr_control_interface函数中,会直接将cmd参数直接给charging_func结构体,而charging_func结构体的定义为:
- static kal_uint32 (*charging_func[CHARGING_CMD_NUMBER])(void *data)=
- {
- charging_hw_init
- ,charging_dump_register
- ,charging_enable
- ,charging_set_cv_voltage
- ,charging_get_current
- ,charging_set_current
- ,charging_set_input_current // not support, empty function
- ,charging_get_charging_status // not support, empty function
- ,charging_reset_watch_dog_timer
- ,charging_set_hv_threshold
- ,charging_get_hv_status
- ,charging_get_battery_status
- ,charging_get_charger_det_status
- ,charging_get_charger_type
- ,charging_get_is_pcm_timer_trigger
- ,charging_set_platform_reset
- ,charging_get_platfrom_boot_mode
- ,charging_set_power_off
- };
CHARGING_CMD_GET_PLATFORM_BOOT_MODE在CHARGING_CTRL_CMD枚举类型中的值为17,所以执行status = charging_func[cmd](data)语句的时候就直接调用到charging_func中的第十七个参数,并且返回值直接跟data指向相同的地址,所以返回值会在data。所以,对于battery_charging_control(CHARGING_CMD_GET_PLATFORM_BOOT_MODE,&g_platform_boot_mode)这类函数的调用,首先就是确定CHARGING_CMD_GET_PLATFORM_BOOT_MODE在CHARGING_CTRL_CMD枚举中的位置,然后到charging_func结构体中相对应的位置查询相对应的函数执行,最后将执行函数后的返回值通过data这个指针变量传给调用函数。
当probe函数注册完了字符设备后,函数进行的随后的进行的操作是在sys下面建立设备节点,总共建立了四个设备节点,分别为ac_main、usb_main、wireless_main和battery_main,这四个节点分别为使用适配器、USB、无线充电以及使用电池供电。电池电量发生变化的时候,会通过这些节点将数据上报给上层,也就是说上层是通过这些节点来读取底层电池电量变化的数据的。
当初始化完成后,probe函数会创建一个hrtimer定时器,定时器启动bat_thread_kthread函数,bat_thread_kthread函数中的while(1)里面包含了BAT_thread()函数,BAT_thread()就是充电的核心函数。
- int bat_thread_kthread(void *x)
- {
- ktime_t ktime = ktime_set(3, 0); // 10s, 10* 1000 ms
-
- /* Run on a process content */
- while (1) {
- mutex_lock(&bat_mutex);
- if((chargin_hw_init_done == KAL_TRUE) && (battery_suspended == KAL_FALSE))
- BAT_thread();
- mutex_unlock(&bat_mutex);
- battery_xlog_printk(BAT_LOG_FULL, "wait event \n" );
- wait_event(bat_thread_wq, (bat_thread_timeout == KAL_TRUE));
- bat_thread_timeout = KAL_FALSE;
- hrtimer_start(&battery_kthread_timer, ktime, HRTIMER_MODE_REL);
- ktime = ktime_set(BAT_TASK_PERIOD, 0); // 10s, 10* 1000 ms
- if( chr_wake_up_bat == KAL_TRUE && g_smartbook_update != 1) // for charger plug in/ out
- {
- g_smartbook_update = 0;
- battery_meter_reset();
- chr_wake_up_bat = KAL_FALSE;
- battery_xlog_printk(BAT_LOG_CRTI, "[BATTERY] Charger plug in/out, Call battery_meter_reset. (%d)\n", BMT_status.UI_SOC);
- }
- }
- return 0;
- }
bat_thread_kthread函数作为定时器的触发函数,在函数中通过ktime =ktime_set(BAT_TASK_PERIOD, 0);将定时触发时间设置为10s,所以每10s就将会对该函数进行触发,该函数的核心函数为BAT_thread(),基本上整个充电过程都在这个函数里面实现。
- void BAT_thread(void)
- {
- static kal_bool battery_meter_initilized = KAL_FALSE;
- _g_bat_sleep_total_time = 0;
- if(battery_meter_initilized == KAL_FALSE)
- {
- battery_meter_initial(); //move from battery_probe() to decrease booting time
- BMT_status.nPercent_ZCV = battery_meter_get_battery_nPercent_zcv();
- battery_meter_initilized = KAL_TRUE;
- }
- mt_battery_charger_detect_check();
- mt_battery_GetBatteryData();
- mt_battery_thermal_check();
- mt_battery_notify_check();
- if( BMT_status.charger_exist == KAL_TRUE )
- {
- mt_battery_CheckBatteryStatus();
- mt_battery_charging_algorithm();
- }
- mt_battery_update_status();
- }
当系统第一次启动的时候battery_meter_initilized为KAL_FALSE,所以BAT_thread会调用battery_meter_initial函数。
battery_meter_initial函数为系统启动时运行的,也电池充电做一些初始化操作。MTK有AUXADC、SW_FG、HW_FG三种不同的电池算法方案,这三种方案分别初始化,因为82平台采用的SW_FG,所以接下去先主要分析SW_FG的流程。而SW_FG中主要是利用线性插值算法来重构zcv表格,利用积分算法求电池的当前电量。
先来看看系统是如何利用线性插值的方法来求电池的温度,重构zcv表格,在table_init函数中:
- void table_init(void)
- {
- ..........
- int temperature = force_get_tbat(); //求电池的温度
-
- // Re-constructure r-table profile according to current temperature
- profile_p_r_table = fgauge_get_profile_r_table(TEMPERATURE_T);
- if (profile_p_r_table == NULL)
- {
- bm_print(BM_LOG_CRTI, "[FGADC] fgauge_get_profile_r_table : create table fail !\r\n");
- }
- fgauge_construct_r_table_profile(temperature, profile_p_r_table);
-
- // Re-constructure battery profile according to current temperature
- profile_p = fgauge_get_profile(TEMPERATURE_T);
- if (profile_p == NULL)
- {
- bm_print(BM_LOG_CRTI, "[FGADC] fgauge_get_profile : create table fail !\r\n");
- }
- fgauge_construct_battery_profile(temperature, profile_p);
- }
table_init首先获取系统的温度int temperature =force_get_tbat(),这里是通过读NTC电阻的电压,然后通过查表来求电阻的。
- int force_get_tbat(void)
- {
- ……………
- bat_temperature_volt = 2;
- //求的NTC电阻的电压,也就是原理图中的BAT_ON
- ret = battery_meter_ctrl(BATTERY_METER_CMD_GET_ADC_V_BAT_TEMP, &bat_temperature_volt);
- if(bat_temperature_volt != 0)
- {
- ……………
- bat_temperature_val = BattVoltToTemp(bat_temperature_volt);
- }
- return bat_temperature_val;
- #endif
- }
BattVoltToTemp函数就是任何将ADC读出的电压值转换为温度值,该函数其实就是做了两个运算,运算的原理如下图所示:
NTC电阻就是通过与电阻的串联跟并联并且通过电压值来得到的。计算出系统当前NTC电阻的电阻值后,然后就调用BattThermistorConverTemp函数进行查表,对比出当前系统的温度。而BattThermistorConverTemp函数是通过alps/mediatek/custom/mt6582/kernel/battery/battery/cust_battery_meter_table.h中的Batt_Temperature_Table结构体,然后根据电阻值落在哪个区间,根据线性插值的方法求出当前电池的温度。 然后在回到table_init函数,MTK的zcv电池参数表格会预先测得的在-100 25 50 摄氏度开路电量跟放电深度之间的关系。结合真实的温度值,系统会自己构建一张当前温度值的ZCV电池曲线表格。
- // Re-constructure r-table profile according to current temperature
- profile_p_r_table = fgauge_get_profile_r_table(TEMPERATURE_T); //返回NTC电阻跟电压表格
- if (profile_p_r_table == NULL)
- {
- bm_print(BM_LOG_CRTI, "[FGADC] fgauge_get_profile_r_table : create table fail !\r\n");
- }
- //动态构建一个NTC电阻跟电压关系的表格
- fgauge_construct_r_table_profile(temperature, profile_p_r_table);
-
- R_PROFILE_STRUC_P fgauge_get_profile_r_table(kal_uint32 temperature)
- {
- switch (temperature)
- {
- case TEMPERATURE_T0:
- return &r_profile_t0[g_fg_battery_id][0];
- break;
- case TEMPERATURE_T1:
- return &r_profile_t1[g_fg_battery_id][0];
- break;
- case TEMPERATURE_T2:
- return &r_profile_t2[g_fg_battery_id][0];
- break;
- case TEMPERATURE_T3:
- return &r_profile_t3[g_fg_battery_id][0];
- break;
- case TEMPERATURE_T:
- return &r_profile_temperature[0];
- break;
- default:
- return NULL;
- break;
- }
- }
调用fgauge_get_profile_r_table函数会根据上面读取到的温度来返回相对应的r_profile_t数组,r_profile_t数组在alps/mediatek/custom/mt6582/kernel/battery/battery/cust_battery_meter_table.h中,而随后调用的fgauge_construct_r_table_profile函数也是先根据当前电池的温度确定是落入哪个温度范围,假如当前电池温度是落入打0到25度之间,然后根据开路电压值和NTC电阻值的不同采用线性平均法分别构建出两个数值,并以这两个值在构造出一个新的开路电压跟NTC电阻的动态的结构体。
- for (i = 0; i < saddles; i++) // 构建表格中的电压值
- {
- if( ((high_profile_p + i)->voltage) > ((low_profile_p + i)->voltage) )
- {
- temp_v_1 = (high_profile_p + i)->voltage;
- temp_v_2 = (low_profile_p + i)->voltage;
- (temp_profile_p + i)->voltage = temp_v_2 + //采用线性平均法求电压
- (
- (
- (temperature - low_temperature) *
- (temp_v_1 - temp_v_2)
- ) /
- (high_temperature - low_temperature)
- );
- }
- else
- {
- temp_v_1 = (low_profile_p + i)->voltage;
- temp_v_2 = (high_profile_p + i)->voltage;
- (temp_profile_p + i)->voltage = temp_v_2 +
- (
- (
- (high_temperature - temperature) *
- (temp_v_1 - temp_v_2)
- ) /
- (high_temperature - low_temperature)
- );
- }
- }
- /* Interpolation for R_BAT */
- for (i = 0; i < saddles; i++) // 构建表格中的NTC电阻值
- {
- if( ((high_profile_p + i)->resistance) > ((low_profile_p + i)->resistance) )
- {
- temp_r_1 = (high_profile_p + i)->resistance;
- temp_r_2 = (low_profile_p + i)->resistance;
- (temp_profile_p + i)->resistance = temp_r_2 + //采用线性平均法求电阻
- (
- (
- (temperature - low_temperature) *
- (temp_r_1 - temp_r_2)
- ) /
- (high_temperature - low_temperature)
- );
- }
- else
- {
- temp_r_1 = (low_profile_p + i)->resistance;
- temp_r_2 = (high_profile_p + i)->resistance;
- (temp_profile_p + i)->resistance = temp_r_2 +
- (
- (
- (high_temperature - temperature) *
- (temp_r_1 - temp_r_2)
- ) /
- (high_temperature - low_temperature)
- );
- }
- }
重构ZCV表格的方法是通过线性插值的方法重构的,具体原理如下图:
table_init后面利用同样的原理,动态生成了一个开路电压与放电深度关系的结构体,这里就不介绍了。
MTK系统是通过battery_meter_get_battery_percentage函数来读取当前电池的电量的,然后在通过设备结点,通过给上层调用。battery_meter_get_battery_percentage函数主要就是调用oam_run函数来实现电流的库伦算法。积分法是利用电流计算公式 I = Q/t来求的,它的优点时适用于各种电池,但缺点是初始电量无法获取。
而整个积分过程可参考下图:
- void oam_run(void)
- {
- ……
- //now_time = rtc_read_hw_time();
- getrawmonotonic(&now_time); //获取系统当前时间
- delta_time = now_time.tv_sec - last_oam_run_time.tv_sec;
-
- last_oam_run_time = now_time;
-
- // Reconstruct table if temp changed;
- fgauge_construct_table_by_temp(); // 当电压表发生改变了的时候,重构电压表
-
- vol_bat = 15; //set avg times
- ret = battery_meter_ctrl(BATTERY_METER_CMD_GET_ADC_V_BAT_SENSE, &vol_bat); //得到闭路电压
-
- oam_i_1 = (((oam_v_ocv_1-vol_bat)*1000)*10) / oam_r_1; //0.1mA
- oam_i_2 = (((oam_v_ocv_2-vol_bat)*1000)*10) / oam_r_2; //0.1mA
-
- oam_car_1 = (oam_i_1*delta_time/3600) + oam_car_1; //0.1mAh
- oam_car_2 = (oam_i_2*delta_time/3600) + oam_car_2; //0.1mAh
-
- oam_d_1 = oam_d0 + (oam_car_1*100/10)/gFG_BATT_CAPACITY_aging; //gFG_BATT_CAPACITY_aging is Q_MAX
- if(oam_d_1 < 0) oam_d_1 = 0;
- if(oam_d_1 > 100) oam_d_1 = 100;
-
- oam_d_2 = oam_d0 + (oam_car_2*100/10)/gFG_BATT_CAPACITY_aging;
- if(oam_d_2 < 0) oam_d_2 = 0;
- if(oam_d_2 > 100) oam_d_2 = 100;
-
- oam_v_ocv_1 = vol_bat + mtk_imp_tracking(vol_bat, oam_i_2, 5);
-
- oam_d_3 = fgauge_read_d_by_v(oam_v_ocv_1);
- if(oam_d_3 < 0) oam_d_3 = 0;
- if(oam_d_3 > 100) oam_d_3 = 100;
-
- oam_r_1 = fgauge_read_r_bat_by_v(oam_v_ocv_1);
-
- oam_v_ocv_2 = fgauge_read_v_by_d(oam_d_2);
- oam_r_2 = fgauge_read_r_bat_by_v(oam_v_ocv_2);
oam_run函数中算法的大致思路为:系统当前的电量通过最终的开路电压oam_v_ocv_1查ZCV表得到当前的电量值,而最终开路电压需要通过闭路电压v_bat和闭路电流oam_i_2 去回溯电池内阻,逐次逼近,而oam_i_2 通过另一种方式即电量积分更新的电压oam_v_ocv_2来得到。
下面看看具体代码的实现。
- vol_bat = 15; //set avg times
- ret = battery_meter_ctrl(BATTERY_METER_CMD_GET_ADC_V_BAT_SENSE, &vol_bat);
首先是闭路电压的更新,这里求的是V_BAT的电压,不需要算法支持直接通过读寄存器实现,注意vol_bat这个参数被复用,返回的平均次数时为最终的v_bat电压值。
- oam_i_1 = (((oam_v_ocv_1-vol_bat)*1000)*10) / oam_r_1; //0.1mA
- oam_i_2 = (((oam_v_ocv_2-vol_bat)*1000)*10) / oam_r_2; //0.1mA
-
- oam_car_1 = (oam_i_1*delta_time/3600) + oam_car_1; //0.1mAh
- oam_car_2 = (oam_i_2*delta_time/3600) + oam_car_2; //0.1mAh
oam_v_ocv_1的值会在oam_init函数里面赋值,oam_init里给的值是hw_ocv的电压值,即系统刚起来时电流很小的时候的电压值。ocv_1和ocv_2是通过两种不同的方式来更新电压。这里是通过内阻压降即IR drop来求电流。如下图所示,图中R为电池内阻。
这里的关键是oam_i_2,这边的I2 有几个作用:
1、因为电流是通过上图的内阻IR drop得到的,而方式一内阻回溯逼近开路电压本质也是IR drop,如果使用oam_i_1 则没有意义,只能使用不同体系的I2.
2、方式二 电流积分求电量查表 同样依赖 oam_i_2 这个体系是累积积分不需要引用其他体系的参数
3、I2的方向作为充电还是放电的依据
而oam_i_1只有作用3。这里还要注意的是oam_i_1和oam_i_2的单位是0.1mA。而oam_car_1和oam_car_2 是累积电量,同样对应的单位是0.1mAh,显然oam_car_2是算法的有效参数。
oam_v_ocv_1 = vol_bat + mtk_imp_tracking(vol_bat, oam_i_2, 5);
求oam_v_ocv_1电压的思路是闭路电压加上电池内阻消耗的电压,而mtk_imp_tracking函数是利用内阻回溯电池内阻压降,即回溯IR drop,然后逐渐比较开路电压。
- kal_int32 mtk_imp_tracking(kal_int32 ori_voltage, kal_int32 ori_current, kal_int32 recursion_time)
- {
- kal_int32 ret_compensate_value = 0;
- kal_int32 temp_voltage_1 = ori_voltage;
- kal_int32 temp_voltage_2 = temp_voltage_1;
- int i = 0;
-
- for(i=0 ; i < recursion_time ; i++)
- {
- gFG_resistance_bat = fgauge_read_r_bat_by_v(temp_voltage_2);
- ret_compensate_value = ( (ori_current) * (gFG_resistance_bat + R_FG_VALUE)) / 1000;
- ret_compensate_value = (ret_compensate_value+(10/2)) / 10;
- temp_voltage_2 = temp_voltage_1 + ret_compensate_value;
-
- bm_print(BM_LOG_FULL, "[mtk_imp_tracking] temp_voltage_2=%d,temp_voltage_1=%d,ret_compensate_value=%d,gFG_resistance_bat=%d\n",
- temp_voltage_2,temp_voltage_1,ret_compensate_value,gFG_resistance_bat);
- }
-
- gFG_resistance_bat = fgauge_read_r_bat_by_v(temp_voltage_2);
- ret_compensate_value = ( (ori_current) * (gFG_resistance_bat + R_FG_VALUE + FG_METER_RESISTANCE)) / 1000;
- ret_compensate_value = (ret_compensate_value+(10/2)) / 10;
-
- gFG_compensate_value = ret_compensate_value;
-
- bm_print(BM_LOG_FULL, "[mtk_imp_tracking] temp_voltage_2=%d,temp_voltage_1=%d,ret_compensate_value=%d,gFG_resistance_bat=%d\n",
- temp_voltage_2,temp_voltage_1,ret_compensate_value,gFG_resistance_bat);
-
- return ret_compensate_value;
- }
mtk_imp_tracking函数主要是在for循环实现逼近。首先通过闭路电压vol_bat去查表得到电池的内阻gFG_resistance_bat,然后通过U=I*R来算出内阻分去的电压ret_compensate_value,这里要注意的是调用mtk_imp_tracking函数时第二个参数传递的参数为oam_i_2,为oam_i_2的单位为0.1mA,所以这里算出来的内阻分去的电压值ret_compensate_value的单位为0.1mV。然后利用闭路电压加上内阻分去的电压值来获取开路电压。这里求开路电压不是一次进行一次直接求出,而是通过for循环进行5次逼近,这样反复就能更真实的逼近电池的开路电压。这里还需注意两点,首先是这条语句ret_compensate_value = (ret_compensate_value+(10/2)) / 10;因为ret_compensate_value为int型变量,而int型变量在进行整除的时候往往小数点后面的会被去掉,会引入较大误差,这里做的目的就是起到四舍五入的目的。另外一点需要注意的是R_FG_VALUE,R_FG_VALUE是指硬件FG使用的FG电阻(一般是20毫欧)这边是SW_FG所以为0。
- oam_r_1 = fgauge_read_r_bat_by_v(oam_v_ocv_1);
- oam_v_ocv_2 = fgauge_read_v_by_d(oam_d_2);
- oam_r_2 = fgauge_read_r_bat_by_v(oam_v_ocv_2);
然后函数会根据之前读出来的oam_v_ocv_1和oam_d_2在去求出oam_r_1和oam_r_2这也是为下次积分做准备,因为系统每10s会调用这个函数。MTK为了增加用户的体验感,在D3的问题上针对电量跳变的情况 又做了步优化得到D5;
- if(d5_count >= d5_count_time)
- {
- if(gFG_Is_Charging == KAL_FALSE)
- {
- if( oam_d_3 > oam_d_5 )
- {
- oam_d_5 = oam_d_5 + 1;
- }
- else
- {
- if(oam_d_4 > oam_d_5)
- {
- oam_d_5 = oam_d_5 + 1;
- }
- }
- }
- else
- {
- if( oam_d_5 > oam_d_3 )
- {
- oam_d_5 = oam_d_5 - 1;
- }
- else
- {
- if(oam_d_4 < oam_d_5)
- {
- oam_d_5 = oam_d_5 - 1;
- }
- }
- }
- d5_count = 0;
- oam_d_3_pre = oam_d_3;
- oam_d_4_pre = oam_d_4;
- }
- else
- {
- d5_count = d5_count + 10;
- }
这部分代码比较简单,1分钟内电量值不会改变,且每分钟电量的变化不会大于1%,这样用户体验会比较好。防止因为低电压时陡峭的电量曲线,以及比较耗电的应用,或突然的大电流引起的电量跳变。当然特殊应用下应该取消这个功能,否则会带来电量变化延时等问题。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。