乐于分享
好东西不私藏

BQ40Z50调试记录:从硬件连接到软件实现的完整指南

BQ40Z50调试记录:从硬件连接到软件实现的完整指南

作为德州仪器(TI)推出的标杆级电量计芯片,BQ40Z50在1-4串锂电池管理领域凭借其高精度、高安全性和全集成特性,已成为笔记本、无人机、医疗设备及工业应用中智能电池方案的首选。然而,在实际调试过程中,工程师们常常会遇到各种连接、配置和通信问题。本文将基于实际项目经验,系统梳理BQ40Z50的调试流程,并分享实际项目代码及通信问题的解决方案。

调试过程中最宝贵的经验往往来自实际遇到的问题和解决方案。如果你在BQ40Z50调试中有其他独特的经验或疑问,欢迎在评论区分享交流。

对于BQ40Z50按常规的IIC协议修改部分代码可满足SMBUS协议,但是在读取数据时偶尔会出错,解决办法:

1.在发送完开始信号后增加2US延时

2.在发送完停止信号后增加200US延时

3.在等待应答时,改为5000US延时

4.等待应答结束后增加200US延时

5.发送完应答信号后增加1ms延时

做完以上5步即可让读数稳定。

另外读电池电压数据时两个字节,所以需要读两次,读其他数据只要一个字节只需要读一次,下面贴上代码:

/************************************

此程序基于STM32F0系列单片机,用于驱动BQ40Z50芯片,发送完命令在等待应答完之后必须要加50US以上的延时,后面发的命令才能正常响应,读取数据完成发送应答信号后,必须加50US以上延时,读下一个字节才会正常,硬件方面SDA,SCL两个脚在3.3V供电时必须外部上拉10K电阻,单片机配置时不需要上下拉。

读取一帧数据流程:发起始信号->发设备写地址->等待应答->发寄存器地址->等待应答->重发起始信号->发设备读地址(设备地址+1)->等待应答->读取低字

节->发送应信号->读取高字节->发送非应答信号(读完最后一个字节发送非应答信号)->发送停止信号。

作者:不忘初心

************************************/

#include "BQ40Z50.h" 

uint8_t DEVICE_NUMBER[5]={0x44,0x02,0x02,0x00,0x46};//返回15个数据,FW版本命令一样

uint8_t HW_VERSION[5]={0x44,0x02,0x03,0x00,0x53};//返回6个数据

uint8_t UNSEAL_FULL_ACCESS[5]={0x44,0x02,0x54,0x00,0x34};//返回8个数据

//初始化BQ40Z50    

void BQ40Z50_Init(void)

{

GPIO_InitTypeDef  GPIO_InitStructure;

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);   

GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_11|GPIO_Pin_10; 

GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;   

 GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_OUT;  

GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_NOPULL;

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_Level_1;

 GPIO_Init(GPIOB,&GPIO_InitStructure);

GPIO_SetBits(GPIOB,GPIO_Pin_10|GPIO_Pin_11);

delay_ms(10);

//配置SDA脚为输入

void SDA_In(void)

{

GPIO_InitTypeDef  GPIO_InitStructure;

GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_11;    

  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_Level_1;

  GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_IN;    

GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;

GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_NOPULL;

  GPIO_Init(GPIOB, &GPIO_InitStructure);

}

//配置SDA脚为输出

void SDA_Out(void)

{

GPIO_InitTypeDef  GPIO_InitStructure;

GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_11;    

  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_Level_1;

  GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_OUT;  

GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;

GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_NOPULL;

  GPIO_Init(GPIOB, &GPIO_InitStructure);

}

//IIC Start

void IIC_Start()

{

SDA_Out();

SDA_H;delay_us(2);

SCL_H;delay_us(2);

SDA_L;

}

//IIC Stop

void IIC_Stop()

{

SDA_Out();

SCL_H;

SDA_L;delay_us(2);

SDA_H;

delay_us(120);

}

/****************************************************************************

* 名    称:uint8_t IIC_Wait_Ack(void)

* 功    能:等待ACK应答

* 入口参数:无

* 出口参数:返回真值=应答,返回假值=无应答

* 说    明:收到应答信号后要加50US以上延时,发送下一个数据才正常

* 调用方法:无 

****************************************************************************/

uint8_t IIC_Wait_Ack(void)

{

uint16_t time = 0;

SDA_In();

while(SDA_Read)

{

time++;

if(time > 65000)

{

IIC_Stop();

return FALSE;

}

}

SCL_H;delay_us(10);

SCL_L;delay_us(500);//此值改成500读数据更稳定

return TRUE;

}

/****************************************************************************

* 名    称:void IIC_Ack(uint8_t Ack)

* 功    能:IIC ACK应答

* 入口参数: Ack 用于切换应答和非应答

* 出口参数:无

* 说    明:Ack=1 应答;Ack=0 非应答

* 调用方法:无 

****************************************************************************/

void IIC_Ack(uint8_t Ack)

{

SDA_Out();

if(Ack)SDA_L;

else SDA_H;

SCL_H;delay_us(10);

SCL_L;delay_us(500);//此处延时短了,读出的数据容易出错,此值修改成500更稳定

}

// IIC Write byte

void Write_IIC_Byte(uint8_t IIC_Byte)

{

uint8_t i;

delay_us(10);

SCL_L;

SDA_Out();

for(i=0;i<8;i++)

{

if(IIC_Byte&0x80)SDA_H;  

else SDA_L;

IIC_Byte<<=1;

delay_us(4);

SCL_H; 

delay_us(10);

SCL_L;

delay_us(6);

}

}

// Read_IIC_Byte

uint8_t Read_IIC_Byte(uint8_t Ack)  

uint8_t i,ReceiveByte=0;

SDA_In();

SCL_H;delay_us(10);

for(i=0;i<8;i++)

{

ReceiveByte<<=1;

SCL_H;

delay_us(8);

if(SDA_Read)ReceiveByte|=0x01;

delay_us(2);

SCL_L;

delay_us(10);

}

if(Ack)IIC_Ack(1);

else IIC_Ack(0);

return ReceiveByte;

}

// Read_BQ40Z50_Data

uint16_t Read_BQ40Z50_Data(uint8_t Reg,uint8_t len)

{

uint8_t bit=0;

uint16_t data=0,data1=0;

IIC_Start();                    

Write_IIC_Byte(BQ40Z50_ADDR);  

bit=IIC_Wait_Ack();

if(bit==0)return 0;

Write_IIC_Byte(Reg);      

bit=IIC_Wait_Ack();

if(bit==0)return 0;

IIC_Start();

Write_IIC_Byte(BQ40Z50_ADDR+1);

bit=IIC_Wait_Ack();

if(bit==0)return 0;

if(len==2)

{

data = Read_IIC_Byte(1);        

data1 = Read_IIC_Byte(0);

}

else if(len==1)data = Read_IIC_Byte(0);

IIC_Stop();

if(len==2)data=data|data1<<8;

return data&0x7fff;

}

void Read_BQ40Z50_Verison(uint8_t *Reg,uint8_t *buf,uint8_t len)

{

uint8_t i=0,bit=0;

IIC_Start();                    

Write_IIC_Byte(BQ40Z50_ADDR);  

bit=IIC_Wait_Ack();

if(bit==0)return;

for(i=0;i<5;i++)

{

Write_IIC_Byte(Reg[i]);

bit=IIC_Wait_Ack();

if(bit==0)return;

}

IIC_Stop();

delay_ms(20);

IIC_Start();                    

Write_IIC_Byte(BQ40Z50_ADDR);  

bit=IIC_Wait_Ack();

if(bit==0)return;

Write_IIC_Byte(0x44);      

bit=IIC_Wait_Ack();

if(bit==0)return;

IIC_Start();

Write_IIC_Byte(BQ40Z50_ADDR+1);

bit=IIC_Wait_Ack();

if(bit==0)return;

for(i=0;i<len-1;i++)

{

buf[i] = Read_IIC_Byte(1); 

if(i==(len-2))

{

buf[len-1]=Read_IIC_Byte(0);

}

}

IIC_Stop();

//DEBUG("buf=");

//for(i=0;i<15;i++)DEBUG("0x%02x ",buf[i]);

//DEBUG("\r\n");

}

void Reset_BQ40Z50(void)

{

uint8_t i=0,bit=0;

uint8_t buf[5]={0x44,0x02,0x41,0x00,0x22};

IIC_Start();                    

Write_IIC_Byte(BQ40Z50_ADDR);  

bit=IIC_Wait_Ack();

if(bit==0)return;

for(i=0;i<5;i++)

{

Write_IIC_Byte(buf[i]);

bit=IIC_Wait_Ack();

if(bit==0)return;

}

IIC_Stop();

}

void UNSEAL_BQ40Z50(void)

{

uint8_t i=0,bit=0;

uint8_t buf[4]={0x00,0x14,0x04,0x0c};

uint8_t buf2[4]={0x00,0x72,0x36,0x19};

IIC_Start();                    

Write_IIC_Byte(BQ40Z50_ADDR);  

bit=IIC_Wait_Ack();

if(bit==0)return;

for(i=0;i<4;i++)

{

Write_IIC_Byte(buf[i]);

bit=IIC_Wait_Ack();

if(bit==0)return;

}

delay_ms(12);

IIC_Start();                    

Write_IIC_Byte(BQ40Z50_ADDR);  

bit=IIC_Wait_Ack();

if(bit==0)return;

for(i=0;i<4;i++)

{

Write_IIC_Byte(buf2[i]);

bit=IIC_Wait_Ack();

if(bit==0)return;

}

IIC_Stop();

}

void ShutDown_BQ40Z50(void)

{

uint8_t i=0,bit=0;

uint8_t buf[5]={0x44,0x02,0x10,0x00,0x3b};

IIC_Start();                    

Write_IIC_Byte(BQ40Z50_ADDR);  

bit=IIC_Wait_Ack();

if(bit==0)return;

for(i=0;i<5;i++)

{

Write_IIC_Byte(buf[i]);

bit=IIC_Wait_Ack();

if(bit==0)return;

}

IIC_Stop();

}

// 单个字节命令,读数据函数

uint16_t BYTE1_Read_BQ40Z50(uint8_t Reg,uint8_t len)

{

 uint8_t i=0,j=0;

 uint16_t data=0,data1=0,buf[5],temp=0;

 for(i=0;i<5;i++)

 {

  IIC_Start();                    

  Write_IIC_Byte(BQ40Z50_ADDR);   

  IIC_Wait_Ack(); 

  Write_IIC_Byte(Reg);         

  IIC_Wait_Ack(); 

  IIC_Start();

  Write_IIC_Byte(BQ40Z50_ADDR+1);

  IIC_Wait_Ack(); 

  if(len==2)

  {

   data = Read_IIC_Byte(1);        

   data1 = Read_IIC_Byte(0);

  }  

  else if(len==1)data = Read_IIC_Byte(0);

  IIC_Stop();

  if(len==2)data=data|data1<<8;

  buf[i]=data;

 }

 for(j=0;j<4;j++)

 {

  for(i=0;i<4;i++)

  {

   if(buf[i]>buf[i+1])

   {

    temp=buf[i];

    buf[i]=buf[i+1];

    buf[i+1]=temp;

   } 

  }

 }

 return buf[3];

}

// 单个字节命令,读n个数据函数

void BYTEn_Read_BQ40Z50(uint8_t Reg,uint8_t *buf,uint8_t len)

{

 uint8_t i=0;

 IIC_Start();                    

 Write_IIC_Byte(BQ40Z50_ADDR);   

 IIC_Wait_Ack(); 

 Write_IIC_Byte(Reg);         

 IIC_Wait_Ack(); 

 IIC_Start();

 Write_IIC_Byte(BQ40Z50_ADDR+1);

 IIC_Wait_Ack(); 

 for(i=0;i<len-1;i++)

 {

  buf[i] = Read_IIC_Byte(1); 

  if(i==(len-2))

  {

   buf[i+1]=Read_IIC_Byte(0);

  }

 } 

 IIC_Stop();

}

//双字节读数据函数:*Reg要写的命令,*buf读出的数据,len要读的字节长度

void BYTE2_Read_BQ40Z50(uint16_t Reg,uint8_t *buf,uint8_t len)

{

 uint8_t i=0;

 Write_Command_BQ40Z50(Reg);

 delay_ms(20);

 IIC_Start();                    

 Write_IIC_Byte(BQ40Z50_ADDR);   

 IIC_Wait_Ack(); 

 Write_IIC_Byte(0x44);         

 IIC_Wait_Ack();

 IIC_Start();

 Write_IIC_Byte(BQ40Z50_ADDR+1);

 IIC_Wait_Ack(); 

 for(i=0;i<len-1;i++)

 {

  buf[i] = Read_IIC_Byte(1); 

  if(i==(len-2))

  {

   buf[i+1]=Read_IIC_Byte(0);

  }

 }  

 IIC_Stop();

}

//把数据从小到大排列

void Compare_data(uint16_t *buf,uint16_t len)

{

 uint16_t i=0,j=0,temp=0;

 for(j=0;j<len-1;j++)

 {

  for(i=0;i<len-1;i++)

  {

   if(buf[i]>buf[i+1])

   {

    temp=buf[i];

    buf[i]=buf[i+1];

    buf[i+1]=temp;

   } 

  }

 }

}

/******以下是头文件*****/

#ifndef __BQ40Z50_H__

#define __BQ40Z50_H__   

#include "head_config.h"

#define SCL_L   GPIO_ResetBits(GPIOB,GPIO_Pin_10)//CLK

#define SCL_H   GPIO_SetBits(GPIOB,GPIO_Pin_10)

#define SDA_L   GPIO_ResetBits(GPIOB,GPIO_Pin_11)//DIN

#define SDA_H   GPIO_SetBits(GPIOB,GPIO_Pin_11)

#define SDA_Read      GPIOB->IDR  & GPIO_Pin_11

#define BQ40Z50_ADDR   0x16  //BQ40Z50设备地址

extern uint8_t DEVICE_NUMBER[];

extern uint8_t HW_VERSION[];

extern uint8_t UNSEAL_FULL_ACCESS[];

void BQ40Z50_Init(void);

void IIC_Start(void);

void IIC_Stop(void);

uint8_t IIC_Wait_Ack(void);

void IIC_Ack(uint8_t Ack);

void Write_IIC_Byte(uint8_t IIC_Byte);

uint8_t Read_IIC_Byte(uint8_t Ack);

uint16_t Read_BQ40Z50_Data(uint8_t Reg,uint8_t len);

void Read_BQ40Z50_Verison(uint8_t *Reg,uint8_t *buf,uint8_t len);

void Reset_BQ40Z50(void);

void UNSEAL_BQ40Z50(void);

void ShutDown_BQ40Z50(void);

#endif