德州仪器BQ27220电量计驱动详解
最近在调试BQ27220电量计,记录一下,供大家参考。
一、先上图
图一图二为在“Battery Management Studio”上进行调试,也为后续驱动的顺序做准备。小白可以对比驱动手册,理解上面的意思,下文会详细描述。
图三图四图五自己用单片机成功驱动的读写例子。
下面是代码说明:
注意:如果确定硬件没问题,调不通的,用逻辑分析仪去抓,大概率是时序的问题,后面代码需要注意的地方也会标注出来,供大家参考
//补上自己控制io口的相关代码 最好是直接操作寄存器的方式
#define SDA_OUT() //配置sda为输出
#define IIC_SDA_H() //配置sda为输出高
#define IIC_SDA_L() //配置sda为输出低
#define IIC_SDA_IN() //配置sda为输入
#define IIC_SCL_H() //配置scl为输出高
#define IIC_SCL_L() //配置scl为输出低
#define READ_SDA() //sda为输入时 读取sda的状态
//产生IIC起始信号
static void IIC_Start(void)
{
SDA_OUT(); //sda线输出
IIC_SDA_H();
IIC_SCL_H();
delay1us(5);//注意这里的延时 一般情况下,这里不需要修改
IIC_SDA_L();//START:when CLK is high,DATA change form high to low
delay1us(5);
IIC_SCL_L();//钳住I2C总线,准备发送或接收数据
}
//产生IIC停止信号
static void IIC_Stop(void)
{
SDA_OUT();//sda线输出
IIC_SCL_L();
IIC_SDA_L();//STOP:when CLK is high DATA change form low to high
delay1us(5);//注意这里的延时 一般情况下,这里不需要修改
IIC_SCL_H();
delay1us(3);//注意这里的延时 一般情况下,这里不需要修改
IIC_SDA_H();//发送I2C总线结束信号
delay1us(5);//注意这里的延时 一般情况下,这里不需要修改
}
//等待应答信号 这个函数一定要注意
static uint8_t IIC_Wait_Ack(void)
{
uint8_t ucErrTime=0;
IIC_SDA_IN(); //SDA设置为输入
delay1us(3);
IIC_SCL_H();
delay1us(3);
while(READ_SDA())
{
ucErrTime++;
//delay(1);
if(ucErrTime>250)
{
IIC_Stop();
return 1;
}
}
IIC_SCL_L();//时钟输出0
return 0;
}
//产生ACK应答
static void IIC_Ack(void)
{
IIC_SCL_L();
SDA_OUT();
IIC_SDA_L();
delay1us(3);//注意这里的延时 一般情况下,这里不需要修改
IIC_SCL_H();
delay1us(5);//注意这里的延时 一般情况下,这里不需要修改
IIC_SCL_L();
}
//不产生ACK应答
static void IIC_NAck(void)
{
IIC_SCL_L();
SDA_OUT();
IIC_SDA_H();
delay1us(3);//注意这里的延时 一般情况下,这里不需要修改
IIC_SCL_H();
delay1us(3);//注意这里的延时 一般情况下,这里不需要修改
IIC_SCL_L();
}
//IIC发送一个字节
//注意这里的延时 一般情况下,这里不需要修改
static void IIC_Send_Byte(uint8_t txd)
{
uint8_t t;
SDA_OUT();
IIC_SCL_L();//拉低时钟开始数据传输
for(t=0;t<8;t++)
{
if((txd&0x80)>>7)
IIC_SDA_H();
else
IIC_SDA_L();
//hal_gpio_write(IIC_SDA,((txd&0x80)>>7));
txd<<=1;
delay1us(5); //对TEA5767这三个延时都是必须的
IIC_SCL_H();
delay1us(5);
IIC_SCL_L();
delay1us(5);
}
}
//读1个字节,ack=1时,发送ACK,ack=0,发送nACK
//注意这里的延时 一般情况下,这里不需要修改
static uint8_t IIC_Read_Byte(unsigned char ack)
{
unsigned char i,receive=0;
IIC_SDA_IN();//SDA设置为输入
for(i=0;i<8;i++ )
{
IIC_SCL_L();
delay1us(5);
IIC_SCL_H();
receive<<=1;
if(READ_SDA())receive++;
delay1us(5);
}
if (!ack)
IIC_NAck();//发送nACK
else
IIC_Ack(); //发送ACK
return receive;
}
//向BQ27220的寄存器发送字节 这里后续这里的len一直为2字节
//这个函数必须注意延时函数的延时时间和是否需要延时 不同的平台,可能都不一样
static uint8_t BQ27220_Write(uint8_t addr,uint8_t reg,uint8_t *data,uint8_t len)
{
IIC_Start();
IIC_Send_Byte(addr);//发送器件地址+写命令
if(IIC_Wait_Ack()) //等待应答
{
IIC_Stop();
return 0;
}
IIC_Send_Byte(reg); //写寄存器地址
delay1us(30);//用过两款单片机驱动,一款这里需要延时 一款不需要,请注意
if(IIC_Wait_Ack()) //等待应答
{
IIC_Stop();
return 0;
}
IIC_Send_Byte(data[0]);//发送数据
delay1us(30);//用过两款单片机驱动,一款这里需要延时 一款不需要,请注意
if(IIC_Wait_Ack()) //等待ACK
{
IIC_Stop();
return 0;
}
IIC_Send_Byte(data[1]);//发送数据
delay1us(30);//用过两款单片机驱动,一款这里需要延时 一款不需要,请注意
if(IIC_Wait_Ack()) //等待ACK
{
IIC_Stop();
return 0;
}
delay1us(20);//用过两款单片机驱动,一款这里需要延时 一款不需要,请注意
IIC_Stop();
mdelay(2);//这里最好加上延时,因为BQ27220手册里说了,结尾要加66uS的延时
return 1;
}
//读取BQ27220的寄存器字节 这里后续这里的len一直为2字节
//这个函数必须注意延时函数的延时时间和是否需要延时 不同的平台,可能都不一样
static uint8_t BQ27220_Read(uint8_t addr, uint8_t reg,uint8_t cmd,uint8_t *date,uint8_t len)
{
//uint8_t res;
IIC_Start();
IIC_Send_Byte(addr);//发送器件地址+写命令
if(IIC_Wait_Ack()) //等待ACK
{
IIC_Stop();
return 0;
}
IIC_Send_Byte(reg); //写寄存器地址
delay1us(30);//用过两款单片机驱动,一款这里需要延时 一款不需要,请注意
if(IIC_Wait_Ack()) //等待ACK
{
IIC_Stop();
return 0;
}
delay1us(10);//用过两款单片机驱动,一款这里需要延时 一款不需要,请注意
IIC_Start();
IIC_Send_Byte(addr|0x01);//发送器件地址+读命令
if(IIC_Wait_Ack()) //等待ACK
{
IIC_Stop();
return 0;
}
delay1us(50);//用过两款单片机驱动,两款需要延时的大小都不一样,建议是以150uA为延时
date[0]=IIC_Read_Byte(1);//读取数据,发送nACK
delay1us(50);/用过两款单片机驱动,两款需要延时的大小都不一样,建议是以150uA为延时
date[1]=IIC_Read_Byte(0);//读取数据,发送nACK
IIC_Stop(); //产生一个停止条件
return 1;
}
接下来是步骤说明,根据图一图二的步骤来的,本人也是按照这个步骤来的,实测基本上问题不大了。注意 注意 注意 一定要按照图一图二的顺序来。
(1):上电先判断电量计的容量配置是否为自己要的配置(举例而已(0x3C,因为我有改容量所以读取容量寄存器,判断是否需要修改),具体看自己,图一图二没有该步骤),如果需要修改,则开始进行图一图二的步骤,反之则执行读取电量等操作。
(2):执行图一图二的操作。有些说明地方可能不对,请结合配置手册来看,W表示写,R表示读。
我是写了一个定时器,每50ms发送一条配置指令,可以通过下面的方式,通过加延时去实现
W 0x00 0x1404 这里只说明一次 W +0x00,0x1404表示在寄存器00写入0x1404
delayms(50);
W 0x00 0x7236//这两个指令需要配置时都需要先发送
delayms(50);
说明:我的方法是通过一个标志位和一个定时器来实现的,如下,每个配置的指令的定时都大同小异,后面会按照延时的方式去写,这样方便大家理解。
W 0x4100 //重启操作,这里最好加上重启操作,因为有时候万一出了问题,导致配置丢失 ,这里重启,恢复初始状态
delayms(4000);//重启需要4秒钟 4秒后在操作27220
W 0x00 0xFFFF
delayms(50);
W 0x00 0xFFFF//两次发送FFFF让27220进入FULL ACCESS 模式
delayms(50);
W 0x00 0x9000//进入CFGUPDATE模式
delayms(1100);
R 0x3B //读取两字节 通过第一节判断是否成功进入CFGUPDATE模式 没有进入的话 需要重新开始,不可以往下进行 判断的依据为 第一字节&0x04==1表示进入成功.
W 0x3E 0x9B92 //通过3E寄存器配置接下来要操作的地址 这个寄存器很重要
delayms(50);
上面的0x9B92 其实是0x929B,但程序必须写成9B92,这个操作的是下面这个寄存器,也是很重要的一个寄存器 默认值为0x102A 没啥要求的话,就按照我的配置来吧 我测试过,确实能用。
W 0x40 0x0021 //上面的0x3E告诉27220要配置的地址,这里的0x40是告诉27220 要配置的值 这里说一下,0021表示启动平滑处理 就是电量可以从100 99…..往下掉 不会跳变,21表示使用FIXED_EDV0(即用自己配置的东西),请对照手册去理解
delayms(50);
W 0x60 0xCC24
//
上面的0x3E告诉27220要配置的地址,0x40是告诉27220 要配置的值,这里的0x60是告诉27220 配置好了 请根据这个值进行校验,非常注意 这里的校验很奇怪,请不要按照官方的文档进行配置,按照我这里的描述来,按照官方的文档很容易出错,只需要用到官方提供的两个公式即可,不要问为什么,我也很奇怪。
校验值说明:
公式如下:
Temp = mod(255 – Old_Chksum – OLD_DC_MSB – OLD_DC_LSB, 256);
New_Chksum = 255 – mod(temp + 0x04 + 0Xb0, 256);
开始说明:
(1)通过大量的实验表明,27220第一次读取“Old_Chksum,OLD_DC_MSB,OLD_DC_LSB”都是一样的,意思就是假如 上电进入配置模式,配置要操作的地址为0x9D92,读取0x40(就是这个地址原来的值)都为0x0BB8(记住就行,可能例子的0bb8写反了),读取0x60的值都为0x3D24(3D表示Old_Chksum,24表是长度)不要管3D24怎么来的 记住这两个值就行,记住只需要你读一次值记下来就行,千万不要第二次读取,因为第二次读取出来的值跟第一次读取出来的值不一样,这样会导致后续的配置不成功。
(2)通过步骤一读取到的“Old_Chksum,OLD_DC_MSB,OLD_DC_LSB”用带入上面的公式进行计算,得到新的校验值,上例子解析, Temp=(255-0x3D-0x0B-0xB8)%256=255(结果为负的话就用补码来实现,(这里建议在网页收搜一下mod的计算器,直接填数字就行)这里假设要配置的值为0x2710(10000mAh)
New_Chksum = 255 – (0x27+0x10+0x3D)%256=0xC9;记住这个(0xC924就行),把这两个值写入到0x60寄存器就行,同理,修改别的地址的值也是一样的。
delayms(50);
W 0x3E 0x9D92 //这个是配置电池可以的容量的 跟0x9F92是配合使用的
delayms(50);
W 0x40 0x2710 //配置的电池容量为10000mAh
delayms(50);
W 0x60 0xC924 //校验值按照上面的方法进行
delayms(50);
W 0x3E 0x9F92 //这个是配置电池可以的容量的 跟0x9F92是配合使用的
delayms(50);
W 0x40 0x2710 //配置的电池容量为10000mAh
delayms(50);
W 0x60 0x2D24 //校验值按照上面的方法进行
delayms(50);
W 0x3E 0x0692 //这个操作是进制27220进入休眠的 进入休眠后 所有数据都会丢失,所以禁止它休眠
delayms(50);
W 0x40 0x0084 //
delayms(50);
W 0x60 0x1524 //校验值按照上面的方法进行
delayms(50);
W 0x3E 0x7192 //这个操作是让27220平滑处理 尽量加上吧 每试过不加上会不会有影响
delayms(50);
W 0x40 0x0F0E //
delayms(50);
W 0x60 0xCF92 //校验值按照上面的方法进行
delayms(50);
W 0x3E 0xB792 //因为是用自己设定的值,所以这里配的3%的电压为3.100V 这个可以自己定,别定太高,免得出现电量跳变
delayms(50);
W 0x40 0x0C1C //
delayms(50);
W 0x60 0x2821 //校验值按照上面的方法进行 这里的21 不是24 因为通过上面的校验方法 读出来的长度就为0x21
delayms(50);
W 0x3E 0xBA92 //因为是用自己设定的值,所以这里配的7%的电压为3.200V 这个可以自己定,别定太高,免得出现电量跳变
delayms(50);
W 0x40 0x0C80 //
delayms(50);
W 0x60 0x7C1E //校验值按照上面的方法进行 这里的1E 不是24 因为通过上面的校验方法 读出来的长度就为0x1E
delayms(50);
//到此,已经把电池容量的3000mA配置到了10000mA 去掉休眠,加上平滑处理,自定义电量百分之七的电压(防止跳变,别定太高)接下来就是最后两条指令,重置计量。
W 0x00 0x9100//退出配置模式 重启计量器 开始生效配置
delayms(50);
W 0x00 0x3000//进入CFGUPDATE模式
对了 图一图二配置的是5000mAh的
作者:qq_37681280