TI BQ27421电量计驱动的调试

TI BQ27421电量计驱动的调试

运行环境

  • 硬件环境:瑞芯微 rv1108
  • 操作系统: linux

TI 电量计技术资料

资料的下载

进入TI 电量计产品的官网 https://www.ti.com/product/BQ27421-G1 下载技术手册 Technical Reference
data sheet 的下载 https://www.ti.com/lit/ds/symlink/bq27421-g1.pdf?ts=1686216383022&ref_url=https%253A%252F%252Fwww.ti.com%252Fproduct%252FBQ27421-G1

参考手册讲解

  • 从数据手册(data sheet )上有二种比较重要的点,一是I2C的频率 ,这里是400Khz, 另一个是I2C的地址,这里是0x55

The 7-bit device address (ADDR) is the most significant 7 bits of the hex address and is fixed as
1010101.

  • 另一个很重要的文档是Technical Reference 技术参考手册,这里讲明了每个寄存器表示的意义,以及举例如何进行操作。

Standard Commands 标准命令; 标准命令了解就可以了,使用时知道查询哪个寄存器获取电量百分比等就行。
Extended Data Commands 扩展数据命令,主要是寄存器参数的一些设计,影响电量的功能,需要设置正确。

  • Technical Reference 技术参考手册中的 ** 3.1 Data Memory Parameter Update Example ** 是要重点读的,而且要读懂
    在这里插入图片描述电量计有几个参数必须是要设置的,如电池容量,充放电最高和截止电压等,举例中描述的是如何设置电池容量。

1.设置unseal
2.发送SET_CFGUPDATE
3.查询进入CFGUPDATE 的标志,大约需要1S
4.往0x61 BlockDataControl() 写0
5.往(using the DataBlockClass() command (0x3E) ) 0x3e写Subclass ID
6.往(Block data starts at 0x40) 0x40 写 Data Memory Summary Tables 中的offset
7.写data block, 或者把data block读出来,设置对应的寄存器
8.写chechsum.

退出CFGUPDATE ,查询CFGUPDATE 标志

  • 第六章 ** 6.3 Data Memory Summary Tables ** 这里的寄存器主要是查询
    在这里插入图片描述
    在这里插入图片描述注意这二个地方的寄存器

CFGUPMODE = Fuel gauge is in CONFIG UPDATE mode. True when set. Default is 0. Refer to Section 2.4.3 for details.
INITCOMP = Initialization completion bit indicating the initialization is complete. True when set

TI工具的使用

使用工具对电池进行一次充电放电的学习。再导出配置文件
请添加图片描述在这里插入图片描述

配置文件如下所示(部分配置)

;--------------------------------------------------------
;Verify Existing Firmware Version
;--------------------------------------------------------
; -- ????
W: AA 00 01 00
C: AA 00 21 04
; -- FWVERSION 0x0109
W: AA 00 02 00
C: AA 00 09 01
;--------------------------------------------------------
;SET_CFGUPDATE
;--------------------------------------------------------
W: AA 00 13 00
X: 1100
;----- DataClass 02 (safety)
W: AA 3E 02 00
; ---- write block data 0x40~0x60   ;550 - 0 - 50
W: AA 40 02 26 00 00 32 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
; ---- write blockdata 0xA5
W: AA 60 A5
X: 10
;  ---- DataClass 02
W: AA 3E 02 00
;  ---- Read Checksum 0xA5
C: AA 60 A5
;  -- write block 0x24(36) (Charge Termination)
W: AA 3E 24 00   
W: AA 40 00 19 28 63 5F FF 62 00 32 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
;  -- write blockdata checksum
W: AA 60 69

配置文件解析

W: 表示该行是写入一个或多个字节数据的命令。
C: 表示该行是读取和比较一个或多个字节数据的命令。
X: 表示该行是在继续之前等待给定毫秒数的命令。

W: AA 00 01 00 的意思
把01写到0x00地址
往00写到0x01地址
AA为I2C的地址,AA后面的00表示寄存器地址

驱动开发与调试

关于bq27421的驱动,网上有二种
第一种是linux 最新的内核中自带的
第二种是其它平台下的 下载地址
当然把BQ27421参考手册读懂后完全自己写,这里参考第种方式,在第二种方式 上进行修改。
这里有个地方要看懂

static struct dm_reg bq274xx_dm_regs[] = {
	{64, 2, 1, 0x0F},	/* Op Config B */
	{80, 78, 1, 10},	/* TermV Valid t 10s */
	{82, 0, 2, 17312},	/* Qmax */
	{82, 3, 2, 15},		/* Reserve capacity */
	{82, 10, 2, 300},	/* Design Capacity */
	{82, 12, 2, 1140},	/* Design Energy */
	{82, 16, 2, 3300},	/* Terminate Voltage */
	{82, 27, 2, 33},	/* Taper rate */
};
static void rom_mode_gauge_dm_init(struct bq27xxx_device_info *di)
{
	int i;
	int timeout = INITCOMP_TIMEOUT_MS;
	u8 subclass, offset;
	u32 blk_number;
	u32 blk_number_prev = 0;
	u8 buf[32];
	bool buf_valid = false;
	struct dm_reg *dm_reg;
	dev_dbg(di->dev, "%s:\n", __func__);
	while (!rom_mode_gauge_init_completed(di) && timeout > 0) {
		msleep(100);
		timeout -= 100;
	}
	if (timeout <= 0) {
		dev_err(di->dev, "%s: INITCOMP not set after %d seconds\n",
			__func__, INITCOMP_TIMEOUT_MS/100);
		return;
	}
	if (!di->dm_regs || !di->dm_regs_count) {
		dev_err(di->dev, "%s: Data not available for DM initialization\n",
			__func__);
		return;
	}
	dev_warn(di->dev, "start rom_mode_gauge_dm_init\n");
	enter_cfg_update_mode(di);
	for (i = 0; i < di->dm_regs_count; i++) {
		dm_reg = &di->dm_regs[i];
		subclass = dm_reg->subclass;
		offset = dm_reg->offset;
		/*
		 * Create a composite block number to see if the subsequent
		 * register also belongs to the same 32 btye block in the DM
		 */
		blk_number = subclass << 8;
		blk_number |= offset >> 5;
		if (blk_number == blk_number_prev) {
			copy_to_dm_buf_big_endian(di, buf, offset,
				dm_reg->len, dm_reg->data);
		} else {
			if (buf_valid)
				update_dm_block(di, blk_number_prev >> 8,
					(blk_number_prev << 5) & 0xFF , buf);
			else
				buf_valid = true;
			read_dm_block(di, dm_reg->subclass, dm_reg->offset,
				buf);
			copy_to_dm_buf_big_endian(di, buf, offset - ((blk_number << 5) & 0xFF),
				dm_reg->len, dm_reg->data);
		}
		blk_number_prev = blk_number;
	}
	/* Last buffer to be written */
	if (buf_valid)
		update_dm_block(di, subclass, offset, buf);
	exit_cfg_update_mode(di);
}

bq274xx_dm_regs 为电量计寄存器参数的配置,主要是配合关键重要的信息。 rom_mode_gauge_dm_init 函数中的流程与前面讲的 ** 3.1 Data Memory Parameter Update Example ** 基本一致

		if (blk_number == blk_number_prev) {
			copy_to_dm_buf_big_endian(di, buf, offset,
				dm_reg->len, dm_reg->data);
		} else {
			if (buf_valid)
				update_dm_block(di, blk_number_prev >> 8,
					(blk_number_prev << 5) & 0xFF , buf);
			else
				buf_valid = true;
			read_dm_block(di, dm_reg->subclass, dm_reg->offset,
				buf);
			copy_to_dm_buf_big_endian(di, buf, offset - ((blk_number << 5) & 0xFF),
				dm_reg->len, dm_reg->data);
		}
		blk_number_prev = blk_number;

这一段代码的意思是如果有相同的subclass 由组成32个字节的块写到电量计中(先读出块的内容,然后再更新要对应的字节)。

对于我们的项目,硬件通过TI 提供的工具,对电池进行了一个轮回的充电放电,导出了电量的配置文件,就是上在提到的类似下方的文件,所以不能用原来的方式。

W: AA 00 01 00
C: AA 00 21 04

结合上述ti工具导出的配置文件,软件修改如下

// 根据ti 的工具导出的学习数据
struct dm_block {
    u8 subclass;
    u8 offset;
    // 块默认长度为32
    u8 block_data[32];
};
static struct dm_block bq274xx_dm_block[] = {
    // safety
    {2, 0, .block_data = {0x02, 0x26, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
    // Charge Termination
    {36, 0, .block_data = {0x00, 0x19, 0x28, 0x63, 0x5F, 0xFF, 0x62, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
    // Data
    {48, 0, .block_data = {0x0E, 0x10, 0xFD, 0xFF, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
    // Discharge
    {49, 0, .block_data = {0x0A, 0x0F, 0x02, 0x05, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
    // Registers
    {64, 0, .block_data = {0x25, 0xF8, 0x0F, 0x48, 0x00, 0x14, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
    // Power
    {68, 0, .block_data = {0x05, 0x00, 0x32, 0x01, 0xC2, 0x14, 0x14, 0x00, 0x03, 0x08, 0x98, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
    // IT Cfg
    {80, 0, .block_data = {0x02, 0xBC, 0x01, 0x2C, 0x00, 0x1E, 0x00, 0xC8, 0xC8, 0x14, 0x08, 0x00, 0x3C, 0x0E, 0x10, 0x00, 0x0A, 0x46, 0x05, 0x14, 0x05, 0x0F, 0x03, 0x20, 0x00, 0x64, 0x46, 0x50, 0x0A, 0x01, 0x90, 0x00}},
    {80, 32, .block_data = {0x64, 0x19, 0xDC, 0x5C, 0x60, 0x00, 0x7D, 0x00, 0x04, 0x03, 0x19, 0x25, 0x0F, 0x14, 0x0A, 0x78, 0x60, 0x28, 0x01, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x80, 0x04, 0x01, 0x14, 0x00}},
    {80, 64, .block_data = {0x0B, 0x0B, 0xB8, 0x01, 0x2C, 0x0A, 0x01, 0x0A, 0x00, 0x00, 0x00, 0xC8, 0x00, 0x64, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
    // Current Thresholds
    {81, 0, .block_data = {0x00, 0xA7, 0x00, 0x64, 0x00, 0xFA, 0x00, 0x3C, 0x3C, 0x01, 0xB3, 0xB3, 0x01, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
    // State
    {82, 0, .block_data = {0x43, 0xF8, 0x00, 0x00, 0x00, 0x81, 0x0E, 0xDB, 0x0E, 0xA8, 0x03, 0xE8, 0x0E, 0x74, 0x05, 0x3C, 0x0C, 0x80, 0x00, 0xC8, 0x00, 0x32, 0x00, 0x14, 0x03, 0xE8, 0x01, 0x00, 0x64, 0x10, 0x04, 0x00}},
    {82, 32, .block_data = {0x0A, 0x10, 0x5E, 0xFF, 0xCD, 0xFF, 0xCD, 0x00, 0x02, 0x02, 0xBC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
    // R_a RAM
    {89, 0, .block_data = {0x00, 0x66, 0x00, 0x66, 0x00, 0x63, 0x00, 0x6B, 0x00, 0x48, 0x00, 0x3B, 0x00, 0x3E, 0x00, 0x3F, 0x00, 0x35, 0x00, 0x2F, 0x00, 0x3C, 0x00, 0x46, 0x00, 0x8C, 0x01, 0x71, 0x02, 0x4C, 0x00, 0x00}},
    // Security Class Codes  是默认值
    {112, 0, .block_data = {0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
};

static void dm_init(void)
{
    int i;
    int timeout = INITCOMP_TIMEOUT_MS;
    u8 subclass, offset;
    int size = ARRAY_SIZE(bq274xx_dm_block);
    struct dm_block *p_dm_block;
    while (!rom_mode_gauge_init_completed() && timeout > 0) {
        msleep(100);
        timeout -= 100;
    }
    if (timeout <= 0) {
        printk("%s: INITCOMP not set after %d seconds\n",
               __func__, INITCOMP_TIMEOUT_MS / 100);
        return;
    }
    enter_cfg_update_mode();
    for (i = 0; i < size; i++) {
        p_dm_block = &bq274xx_dm_block[i];
        subclass = p_dm_block->subclass;
        offset = p_dm_block->offset;
        update_dm_block(subclass, offset, p_dm_block->block_data);
    }
#if 0
	//读出数据
	    for (i = 0; i < size; i++) {
		u8 data[32]={0};
		int ret =0;
		int k=0;
        p_dm_block = &bq274xx_dm_block[i];
        subclass = p_dm_block->subclass;
        offset = p_dm_block->offset;
        ret = read_dm_block(subclass, offset, data);
		if(ret) {
			printk("subclass = %d  , offset = %d\n",subclass,offset);
			for(k=0;k<32;k++){
				printk("%02x ",data[k]);
			}
			printk("\n");
		}
    }
#endif 
    exit_cfg_update_mode();
}

调试时遇到的问题

在测试中发现电量百分比不准,调试发现 Full Charge Capacity 变回了默认值。检测发现写的配置已经到RAM中。Full Charge Capacity 的值是由写入的值再通过BQ27421计算出来的。不能直接修改。检测Data Memory 的值与写入的值一样,表示写入是正常的。
在这里插入图片描述
后面经过反复阅读TI bq27421的寄存器手册,对比发现如果CFGUPMODE 为1(表示没有退出配置模式), Full Charge Capacity 的值不会更新。说明退出的配置有问题。

static int exit_cfg_update_mode(void)

{
    int i = 0;
    u16 flags;
    /*
    W: AA 00 00 00
    W: AA 00 42 00
    */
    control_cmd_wr(0);
    control_cmd_wr(BQ274XX_SOFT_RESET); //退出配置模式
    while (i < CFG_UPDATE_POLLING_RETRY_LIMIT) {
        i++;
        flags = bq27xxx_read(BQ27XXX_REG_FLAGS, false);
        if (!(flags & (1 << 4)))
            break;
        msleep(100);
    }
    if (i == CFG_UPDATE_POLLING_RETRY_LIMIT) {
        printk("%s: failed %04x\n", __func__, flags);
        return 0;
    }
    if (seal())
        return 1;
    else
        return 0;
   return 1;
}

这里的退出配置模式由原来的 BQ274XX_EXIT_CFGUPDATE 改为BQ274XX_SOFT_RESET 。且为了保证每次都能退出设置模式,不报错,结合官方工具的文件

W: AA 00 00 00
W: AA 00 42 00

再在代码中增加 control_cmd_wr(0);

结论

在使用bq27421时,只进行了简单的配置(电池容量,充放电电压等等)然后让电量计自学习,测试时发现不同的机器电量表现不一样(关机时bq27421是断电的)。后面改为统一写入电量计量产配置文件的方式。所有就有了上面的工作。

最后记录一下i2ctool 工具的使用(用i2ctool 工具想验证写入电量计的数据是否正确,但未能成功,第三步返回error)

//进入UNSEALED模式 ,否则设置 BlockDataControl(): 0x61 无效
./i2cset -f -y 2 0x55 0x0 0x00
./i2cset -f -y 2 0x55 0x01 0x80
./i2cset -f -y 2 0x55 0x0 0x00
./i2cset -f -y 2 0x55 0x01 0x80


//查询sealed模式写入情况 ,w 表示读字
./i2cget -f -y 2 0x55 0x0 w

//进入 配置更新模式 
./i2cset -f -y 2 0x55 0x0 0x13

//BLOCK_DATA_CONTROL
./i2cset -f -y 2 0x55 0x61 0x0 

//BLOCK_DATA_CLASS
./i2cset -f -y 2 0x55 0x3e 0x52

//BLOCK_DATA
// -r 指定地址范围
./i2cdump -f -y -r 0x40-0x5f 2 0x55 
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值