ThinkNotes

Simple is not easy | 化繁为简,知易行难

0%

电量计 -- 问题和功能记录

电量计–问题和功能记录

切换CV相关问题

充电过充中切cv

需求描述:(NPC660项目)在充电过充中Host发送SBS73命令切换截止电压,从默认的4.35v切到4.1v,同时Charger充电器也设置截止电压4.15v以保证能充满到4.1v。期望充电到4.1v时rsoc能报100.

问题分析:在充电过充中切换截止电压,之前电量计累计的数据没有清0,导致充满到4.1v时电量还是按4.35v计算,只有70%

解决方案:收到SBS73就复位电量计数据(清0),注意清0会导致rsoc跳变。

image-20250408135441409

放电过充中切cv

需求描述:(NPC660项目)按4.35v充满后,如果长时间没拔下充电器,充电器会判断为长时间过充,Host发送SBS73命令切换截止电压,从默认的4.35v切到4.1v,之后放电到4.1v。期望放电到4.1v过充中rsoc能保持100,4.1v以下按正常电量消耗显示,电量无跳变。

问题分析:

(1)切换后Host有时读到soc是0值,可能是正好读到了复位数据。

(2)初始方案是判断电压在4.35~4.1时直接sbsif强制上报100,但4.1v时soc会有跳变。

(3)Host在4.35v时发送cv切换时GGMEM0看到soc是100,但4.35放电到4.1v过充中,soc一直下降,放电到4.1v按真实soc显示会跳变到89。

image-20250408140512307

解决方案:

(1)测试发现放电中每次发SBS73切cv到4.1v时,GGMEM0 soc都复位到100,因此解决方案是4.1v附近再复位一次电量计数据。

(2)不能在刚刚4.1v时复位,算法来不及刷新数据,会导致soc跳变。在4.1V + 20mV时提前复位。

(3)为了防止跳变,预设了平滑下降数据,平滑追赶跳变soc值。

image-20250408150400108

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
uint8_t cv_adjusted_flag = 0;
static int16_t adjusted_cv_value = 0;
#define FAKE_SOC_TABLE_SIZE 40
static uint8_t fake_soc[FAKE_SOC_TABLE_SIZE] = {
99, 99, 98, 98, 97, 97, 96, 96, 95, 95, 94, 94, 93, 93, 92, 92, 91, 91, 90, 90,
89, 88, 87, 86, 85, 84, 83, 82, 81, 80, 79, 78, 77, 76, 75, 74, 73, 72, 71, 70,
};

//main loop每秒调用一次
void check_and_force_soc_if_needed(void)
{
// 获取当前电池电压
int16_t current_voltage = ((SBSIF_T *)psbsifHandle)->sbs_buff[SBS09_BATTVOLT];

// 判断是否需要处理CV调整后的特殊情况
if (cv_adjusted_flag)
{
// 如果电压仍高于调整后的CV值,保持SOC为100%
if (current_voltage > adjusted_cv_value)
{
// 检查是否是放电状态
if (((SBSIF_T *)psbsifHandle)->sbs_buff[SBS0A_BATTCURR] < 0)
{
//4.1V以上时直接上报100,避免误读到SBS73的reset soc值0.
((SBSIF_T *)psbsifHandle)->sbs_buff[SBS0D_RSOC] = 100;
((SBSIF_T *)psbsifHandle)->sbs_buff[SBS0E_ASOC] = 100;

//接近4.1V时提前reset数据,这样降到4.1v时真实soc能接近100
//如果不reset,从4.35放电到4.1的电量会被计算到soc,造成4.1v的soc只有90%
if(current_voltage - adjusted_cv_value < 20)
reset_gauge_parameters();
}
}
else
{
// 电压已降至CV值以下,按真实soc报
if (((SBSIF_T *)psbsifHandle)->sbs_buff[SBS0A_BATTCURR] < 0)
{
// 不要在这里复位,不然客户会看到soc跳变
// reset_gauge_parameters();

// 备选: 如果用真实rsoc有跳变(5~15%左右),做平滑处理,每秒降低soc直到匹配真实soc
static int idrop = 0;
if((((SBSIF_T *)psbsifHandle)->sbs_buff[SBS0D_RSOC] < fake_soc[idrop]) &&
(idrop < FAKE_SOC_TABLE_SIZE))
{
((SBSIF_T *)psbsifHandle)->sbs_buff[SBS0D_RSOC] = fake_soc[idrop];
idrop++;
return; //这里退出,下次继续,直到匹配或者超出fake table
}
}

// 清除全局标志
cv_adjusted_flag = 0;
}
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void smart_charge_func(void)
{
// SBS73: 超长时间充电切CV值功能
int16_t host_update = (((SBSIF_T *)psbsifHandle)->sbs_buff[SBS73_UPDATE_CV] >> 16) & 0xFFFF;
int16_t update_cv = ((SBSIF_T *)psbsifHandle)->sbs_buff[SBS73_UPDATE_CV] & 0xFFFF
// 如果收到Host发送的CV调整命令
if (host_update && update_cv)
{
// 记录新的CV值
adjusted_cv_value = update_cv;

// 获取当前电池电压
int16_t current_voltage = ((SBSIF_T *)psbsifHandle)->sbs_buff[SBS09_BATTVOLT];

// 如果当前电压高于新设定的CV值,设置标志且只设置一次
if ((cv_adjusted_flag == 0) &&
(current_voltage > adjusted_cv_value))
{
cv_adjusted_flag = 1;
}
}
....

充电到99停充,自动报百

FW有优化处理,当Charger提前截止充电了,电池处于idle状态且当前电量在99,Host发送81命令0x3数据通知电量计,电量计idle_process检测此标志位,持续30s后电量计自动报100,避免充电器插着但长时间停充场景的电量到不了100。

image-20250424111349241

GGMEM数据使用SBS命令导出

客户环境下可能没有多的I2C接口能接Cobra上位机去读GGMEM,而问题调试必须依赖于GGMEM数据。

客户的Host代码可以使用SBS D0 ~ D8命令获取GGMEM0 ~8

image-20250424113644949

Host乱切cv导致跳0和满充75问题

image-20250425150103773

image-20250425150921622

Host侧代码可通过SBS D0~D8读GGMEM去分析这种跳0问题:

image-20250425151016548

注意Cobra轮询时间比较长最短1s,有时不一定能观测到soc跳0的值,但GGMEM0第一个DWORD复位为DESIGN FCC = 13ec是可以作为判断的。

最终原因就是Host Charger逻辑问题,一直在发SBS73,而FW没加防呆,频繁复位libfg数据,导致最终4.1V时接近OCV查表值的电量75,而不是CV追赶值100.

image-20250425154850336

Cycle-count放电循环次数

SBS17 cycle-count = 总放电量/电池容量,每秒更新

image-20250507101040214

不接i2c多次循环再读cycle count可以读到更新,因为sleep机制下会有timer每60s唤醒一次持续1s,更新libfg信息,包括累计discharge ccmah,计算cycle count

USB模式和AC Charger模式的eocma调整接口

USB口只有500mA电流,应该保证USB充电的eocma小于500mA,典型值是200mA。参数的满充截止电流如果和USB电流相等都是500mA,host必须发SBS74切eocma到200mA,否则充电尾端加速过快,容易读到电量跳变。

image-20250515162238231

NTC表的两种形式

SD77561原版使用电压-电阻查表;SD77428A3使用电阻-温度查表,支持6uA/48uA两种电流源切换,大电流源保证高温时R*I = V有更大的值,这样电压ADC有更高精度。现在需要将SD77561的电压-电阻查表改成电阻-温度查表,支持6uA/72uA两种电流源切换:

(1)首先将SD77428代码里已经有的R-T表,手动创建一个77561 PRJ的NTC.txt格式表:

x轴是16个采样点,total length是cobra解析此表的参数总和,即16*2 (x和y) + 6(header) = 38

image-20250630155253817

(2)修改ADC查表方式,并添加高温切换大电流源

代码分支:HaoPeng-I0917A

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Copy raw thm voltage to buffer
((SBSIF_T *)psbsifHandle)->sbs_val = sbs_cal_lsb_factor((int32_t)(dacq_p->raw_buff[((SBSIF_T *)psbsifHandle)->sbs_val]), ADC_EXTMPLSB, ADC_EXTMPLSB_FACTOR);
//暂存,后面切换电流源用到此raw电压作为条件
temp2 = ((SBSIF_T *)psbsifHandle)->sbs_val;
/* find Re, with 20uA source */
// we modify thermal table as Voltage V.S. Temperature, so don't need to multiple THM_OHM
//((SBSIF_T *)psbsifHandle)->sbs_val *= (THM_OHM); //Re = 1000uV / 20uA ==> Ohm

//转换电压成电阻:电压*1000精度/电流源 = NTC R,支持72ua和6ua电流源,72uA为了高温下高精度测量
if(EXTNTCSRC_72UA == Chip_ADC_GetNTCSrc())
((SBSIF_T *)psbsifHandle)->sbs_val *= (1000/72);
else
((SBSIF_T *)psbsifHandle)->sbs_val *= (1000/6);

//查表,改成电阻-温度表
lut_one_latitude(TEMPERATURE_DATA_NUM,
(one_latitude_data_t *)GDM_FLASH_THML_TABLE,
((SBSIF_T *)psbsifHandle)->sbs_val,
&temp1);
temp1 += DK_BASE;
//记录温度
((SBSIF_T *)psbsifHandle)->sbs_buff[SBS61_ETDK1] = temp1 - (int32_t)(param_board_cfg[PARM_BCFG_EXTTHMOFFSET]); //((SBSIF_T *)psbsifHandle)->sbs_val;


//根据ntc电压(温度状况)决定电流源切换提高精度:
//高温ntc电压小于60mV切换72uA,常温低温ntc电压大于900mV切换6uA
if((temp2 < 60) && (EXTNTCSRC_6UA == Chip_ADC_GetNTCSrc())){
Chip_ADC_SetNTCSrc(EXTNTCSRC_72UA);
}else if ((temp2 > 900) && (EXTNTCSRC_72UA == Chip_ADC_GetNTCSrc())){
Chip_ADC_SetNTCSrc(EXTNTCSRC_6UA);
}

在线升级bin和参数对照

终端在线升级出问题一般只会回传bin,需要对照bin的参数是怎么配置的,注意bin没有8K的bootloader offset即可找到参数

image-20250723203915801

缺失的参数定义在C:\git-834\COBRA\COBRA Documents\SD77226SBS_X_20250315\Project\Parameter查看

SOH更新原理

image-20250725114616713

77428 Host驱动使用硬件I2C判断通信问题

i2c sbs接口持续错误可用此方式判断是硬件问题还是软件问题

image-20250725114726725

注释函数大幅减少Code段占用

一般注释掉函数的调用处,但不注释函数体本身,只会减少运行时堆栈,并不会减少Code代码段,;但是561 KEIL项目注释main的libfg_update后,Code段从36K减到20K

原因是编译器优化:未被调用的函数被从Image中移除;只有被调用的函数体才存放在Image。

如下图分别是不调用和调用libfg_update时的.map信息:

image-20250729102546398

image-20250729102611276

SD77428 power supply框架获取电量信息

框架参考:https://www.kernel.org/doc/html/latest/power/power_supply_class.html

代码见<include/linux/power_supply.h>

基本原理是dev设备,注册power supply属性,其中的desc描述结构包含get_property回调函数接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static int32_t sd77428_power_supply_init(struct sd77428_data *chip)
{
chip->bat_cfg.drv_data = chip;
chip->bat_cfg.of_node = chip->client->dev.of_node;

chip->bat_desc.name = "sd77428";
chip->bat_desc.type = POWER_SUPPLY_TYPE_BATTERY;
chip->bat_desc.properties = sd77428_battery_props;
chip->bat_desc.num_properties = ARRAY_SIZE(sd77428_battery_props);
chip->bat_desc.get_property = sd77428_battery_get_property; //这里是get_property回调
chip->bat_desc.no_thermal = 1;
chip->bat_desc.external_power_changed = sd77428_external_power_changed;

chip->bat = devm_power_supply_register(chip->dev, &chip->bat_desc, &chip->bat_cfg);
...

驱动实现get_property回调,关联电量信息和上报的状态,有的信息需要映射,例如SOH不能直接上报值只能报状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
val->intval = chip->batt_info.batt_voltage; //mV
break;

case POWER_SUPPLY_PROP_CURRENT_NOW:
val->intval = chip->batt_info.batt_current; //mA
break;

case POWER_SUPPLY_PROP_CAPACITY: //capacity in percents(soc), from 0 to 100
val->intval = chip->batt_info.batt_rsoc;
break;

case POWER_SUPPLY_PROP_TEMP:
val->intval = chip->batt_info.batt_temp; //单位:摄氏度
break;

case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: //design charge capacity
val->intval = chip->batt_info.batt_capacity;
break;

case POWER_SUPPLY_PROP_CHARGE_FULL: //full-charge-capacity(FCC)
val->intval = chip->batt_info.batt_fcc;
break;

case POWER_SUPPLY_PROP_CHARGE_NOW: //charge capacity now
val->intval = chip->batt_info.batt_rc;
break;

case POWER_SUPPLY_PROP_HEALTH:
//val->intval = chip->batt_info.batt_soh; //不能直接返回SOH值
val->intval = POWER_SUPPLY_HEALTH_GOOD; //根据状态返回POWER_SUPPLY_HEALTH定义的状态
break;

然后linux应用层通过sysfs访问power_supply路径下的节点,设备名称是前面注册的.name名称(sd77428),路径下是各属性都像文件一样访问。

用uevent查看节点的所有实时信息,power_supply节点的任何信息的更新都会反映到默认属性uevent(power_supply属于uevent子类,基于uevent通知机制实现);如果只查看某个信息如capacity,charge_now,都可以分开cat查看

如下是终端输出,sysfs查看uevent和驱动打印的电池信息是一致的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
root@phytiumpi:~# ls /sys/class/power_supply
sd77428

root@phytiumpi:~# ls /sys/class/power_supply/sd77428
capacity charge_now device power status temp uevent
charge_full_design current_now health present subsystem type voltage_now

root@phytiumpi:~# cat /sys/class/power_supply/sd77428/uevent
POWER_SUPPLY_NAME=sd77428
POWER_SUPPLY_STATUS=Discharging
POWER_SUPPLY_PRESENT=1
POWER_SUPPLY_VOLTAGE_NOW=4089
POWER_SUPPLY_CURRENT_NOW=0
POWER_SUPPLY_CAPACITY=98
POWER_SUPPLY_TEMP=25
POWER_SUPPLY_CHARGE_FULL_DESIGN=3400
POWER_SUPPLY_CHARGE_FULL=3400
POWER_SUPPLY_CHARGE_NOW=3318
POWER_SUPPLY_HEALTH=Good

root@phytiumpi:~# [ 23.297385] [bmt]sd77428_get_batt_info: vbat:4089, ibat:00000, tbat:25, rsoc:098, fcc:3400, dcap:3400, soh:100, cycle:0, rc:3318, dfcc:3798, cc:8242, ext_chg -1

561 cobra导出EEPROM 64K数据

image-20251009163917427

从左到右依次read all,每次export一个bin,得到3个bin,然后用hex editor合并成一个64KB的bin,按EEPROM+Sytem+Information合并

561 SBS改成428格式,交叉测试

LK阶段问题怀疑是428使用轮询I2C造成slave持续拉低clock/data,561使用中断处理I2C,为了快速验证,不改host 428驱动情况下,使用561修改部分命令成428格式测试。

改两处即可:I2C slave地址,SBS命令的值和长度。注意Slave地址设置原代码有问题,应该直接赋值SADDR1,不能或等,否则COM通信失败

image-20251011144509147

image-20251011144420090

561 使用轮询方式处理I2C

需求同上,为了对比测试428问题,561使用轮询i2c方式

修改两处:

1.i2c slave handle加到while1 ms轮询

2.关闭NVIC I2C中断开关 (561基于ARM,428的riscv是另外一套PLIC关闭)

注意自定义的数字逻辑也有中断使能和中断状态,这个和NVIC中断是无关的,属于内部状态,所以即使关了外部NVIC中断开关,也只是切断了ISR回调处理,轮询方式调用i2c slave handle仍可以读内部i2c register中断状态判断是读是写是传输完成,只要保证清除状态,不要重复处理即可。

image-20251011145237741

77428A3不更新FCC问题

A3驱动下载参数后需要发SBS8F带非0参数,reset_lib去更新FCC=Design Capacity

image-20251028142924671