BQ24195的使用:与MSP430G2553的I2C通信

前言

本文作为bq24195的I2C使用教程,主要涉及I2C通信代码的实现以及一些注意事项,硬件部分稍有涉及但不是主要内容。

正文

硬件连接图:

I2C的上拉电阻10K或4.7K都行,阻值影响的是跳变沿的时间,即使fast mode I2C通信的频率也才400k左右,所以影响不大。

软件例程

我们用的是G2553的硬件I2C,有中断法和查询法,不想用中断的可以用查询法。如果选择了低功耗,建议用中断法。

MSP430G2553硬件I2C驱动-中断法

IT已经给我们准备好了,直接照搬msp430g2xx3_usci_i2c_standard_master.c例程就行。稍微整理一下做成i2c.h和i2c.c文件,力求简洁美观。

/*
 * i2c.h
 */

#ifndef I2C_H_
#define I2C_H_

#include <stdint.h>

#define SLAVE_ADDR  0x6B    //BQ24195

#define MAX_BUFFER_SIZE     20

//******************************************************************************
// General I2C State Machine ***************************************************
//******************************************************************************

typedef enum I2C_ModeEnum{
    IDLE_MODE,
    NACK_MODE,
    TX_REG_ADDRESS_MODE,
    RX_REG_ADDRESS_MODE,
    TX_DATA_MODE,
    RX_DATA_MODE,
    SWITCH_TO_RX_MODE,
    SWITHC_TO_TX_MODE,
    TIMEOUT_MODE
} I2C_Mode;

void initClockTo16MHz();
void initI2C();
void CopyArray(uint8_t *source, uint8_t *dest, uint8_t count);
I2C_Mode I2C_Master_WriteReg(uint8_t dev_addr, uint8_t reg_addr, uint8_t *reg_data, uint8_t count);
I2C_Mode I2C_Master_ReadReg(uint8_t dev_addr, uint8_t reg_addr, uint8_t count);

#endif /* I2C_H_ */

下面是i2c.c文件:

/*
 * i2c.c
 */

#include <msp430.h>
#include <stdint.h>
#include <stdbool.h>
#include "i2c.h"

I2C_Mode MasterMode = IDLE_MODE;
uint8_t TransmitRegAddr = 0;


uint8_t ReceiveBuffer[MAX_BUFFER_SIZE] = {0};
uint8_t RXByteCtr = 0;
uint8_t ReceiveIndex = 0;
uint8_t TransmitBuffer[MAX_BUFFER_SIZE] = {0};
uint8_t TXByteCtr = 0;
uint8_t TransmitIndex = 0;


I2C_Mode I2C_Master_ReadReg(uint8_t dev_addr, uint8_t reg_addr, uint8_t count)
{
    /* Initialize state machine */
    MasterMode = TX_REG_ADDRESS_MODE;
    TransmitRegAddr = reg_addr;
    RXByteCtr = count;
    TXByteCtr = 0;
    ReceiveIndex = 0;
    TransmitIndex = 0;

    /* Initialize slave address and interrupts */
    UCB0I2CSA = dev_addr;
    IFG2 &= ~(UCB0TXIFG + UCB0RXIFG);       // Clear any pending interrupts
    IE2 &= ~UCB0RXIE;                       // Disable RX interrupt
    IE2 |= UCB0TXIE;                        // Enable TX interrupt

    UCB0CTL1 |= UCTR + UCTXSTT;             // I2C TX, start condition
    __bis_SR_register(CPUOFF + GIE);              // Enter LPM0 w/ interrupts

    return MasterMode;

}

I2C_Mode I2C_Master_WriteReg(uint8_t dev_addr, uint8_t reg_addr, uint8_t *reg_data, uint8_t count)
{
    /* Initialize state machine */
    MasterMode = TX_REG_ADDRESS_MODE;
    TransmitRegAddr = reg_addr;

    //Copy register data to TransmitBuffer
    CopyArray(reg_data, TransmitBuffer, count);

    TXByteCtr = count;
    RXByteCtr = 0;
    ReceiveIndex = 0;
    TransmitIndex = 0;

    /* Initialize slave address and interrupts */
    UCB0I2CSA = dev_addr;
    IFG2 &= ~(UCB0TXIFG + UCB0RXIFG);       // Clear any pending interrupts
    IE2 &= ~UCB0RXIE;                       // Disable RX interrupt
    IE2 |= UCB0TXIE;                        // Enable TX interrupt

    UCB0CTL1 |= UCTR + UCTXSTT;             // I2C TX, start condition
    __bis_SR_register(CPUOFF + GIE);      // Enter LPM0 w/ interrupts

    return MasterMode;
}


void CopyArray(uint8_t *source, uint8_t *dest, uint8_t count)
{
    uint8_t copyIndex = 0;
    for (copyIndex = 0; copyIndex < count; copyIndex++)
    {
        dest[copyIndex] = source[copyIndex];
    }
}

void initClockTo16MHz()
{
    if (CALBC1_16MHZ==0xFF)                  // If calibration constant erased
    {
        while(1);                               // do not load, trap CPU!!
    }
    DCOCTL = 0;                               // Select lowest DCOx and MODx settings
    BCSCTL1 = CALBC1_16MHZ;                    // Set DCO
    DCOCTL = CALDCO_16MHZ;
}


void initI2C()
{
    P1SEL |= BIT6 + BIT7;                     // Assign I2C pins to USCI_B0
    P1SEL2|= BIT6 + BIT7;                     // Assign I2C pins to USCI_B0

    UCB0CTL1 |= UCSWRST;                      // Enable SW reset
    UCB0CTL0 = UCMST + UCMODE_3 + UCSYNC;     // I2C Master, synchronous mode
    UCB0CTL1 = UCSSEL_2 + UCSWRST;            // Use SMCLK, keep SW reset
    UCB0BR0 = 160;                            // fSCL = SMCLK/160 = ~100kHz
    UCB0BR1 = 0;
    UCB0I2CSA = SLAVE_ADDR;                   // Slave Address
    UCB0CTL1 &= ~UCSWRST;                     // Clear SW reset, resume operation
    UCB0I2CIE |= UCNACKIE;
}


#pragma vector = USCIAB0TX_VECTOR
__interrupt void USCIAB0TX_ISR(void)
{
  if (IFG2 & UCB0RXIFG)                 // Receive Data Interrupt
  {
      //Must read from UCB0RXBUF
      uint8_t rx_val = UCB0RXBUF;

      if (RXByteCtr)
      {
          ReceiveBuffer[ReceiveIndex++] = rx_val;
          RXByteCtr--;
      }

      if (RXByteCtr == 1)        //特别注意
      {
          UCB0CTL1 |= UCTXSTP;
      }
      else if (RXByteCtr == 0)
      {
          IE2 &= ~UCB0RXIE;
          MasterMode = IDLE_MODE;
          __bic_SR_register_on_exit(CPUOFF);      // Exit LPM0
      }
  }
  else if (IFG2 & UCB0TXIFG)            // Transmit Data Interrupt
  {
      switch (MasterMode)
      {
          case TX_REG_ADDRESS_MODE:
              UCB0TXBUF = TransmitRegAddr;
              if (RXByteCtr)
                  MasterMode = SWITCH_TO_RX_MODE;   // Need to start receiving now
              else
                  MasterMode = TX_DATA_MODE;        // Continue to transmision with the data in Transmit Buffer
              break;

          case SWITCH_TO_RX_MODE:
              IE2 |= UCB0RXIE;              // Enable RX interrupt
              IE2 &= ~UCB0TXIE;             // Disable TX interrupt
              UCB0CTL1 &= ~UCTR;            // Switch to receiver
              MasterMode = RX_DATA_MODE;    // State state is to receive data
              UCB0CTL1 |= UCTXSTT;          // Send repeated start
              if (RXByteCtr == 1)
              {
                  //特别注意
                  //Must send stop since this is the N-1 byte
                  while((UCB0CTL1 & UCTXSTT));
                  UCB0CTL1 |= UCTXSTP;      // Send stop condition
              }
              break;

          case TX_DATA_MODE:
              if (TXByteCtr)
              {
                  UCB0TXBUF = TransmitBuffer[TransmitIndex++];
                  TXByteCtr--;
              }
              else
              {
                  //Done with transmission
                  UCB0CTL1 |= UCTXSTP;     // Send stop condition
                  MasterMode = IDLE_MODE;
                  IE2 &= ~UCB0TXIE;                       // disable TX interrupt
                  __bic_SR_register_on_exit(CPUOFF);      // Exit LPM0
              }
              break;

          default:
              __no_operation();
              break;
      }
  }
}


//******************************************************************************
// I2C Interrupt For Start, Restart, Nack, Stop ********************************
//******************************************************************************

#pragma vector = USCIAB0RX_VECTOR
__interrupt void USCIAB0RX_ISR(void)
{
    if (UCB0STAT & UCNACKIFG)
    {
        UCB0STAT &= ~UCNACKIFG;             // Clear NACK Flags
    }
    if (UCB0STAT & UCSTPIFG)                        //Stop or NACK Interrupt
    {
        UCB0STAT &=
            ~(UCSTTIFG + UCSTPIFG + UCNACKIFG);     //Clear START/STOP/NACK Flags
    }
    if (UCB0STAT & UCSTTIFG)
    {
        UCB0STAT &= ~(UCSTTIFG);                    //Clear START Flags
    }
}

TI例程的I2C代码是无需修改就能用的,代码也不是很复杂,流程就不过多分析。需要特别注意的地方有两处,就是代码中两处中文注释的地方。

MSP430G2553硬件I2C驱动-查询法

仅给出函数主体,添加到上述i2c.c,以及在i2c.h声明,即可使用。

uint8_t I2C_ReadBytes(uint8_t dev_addr, uint8_t reg_addr, uint8_t* pBuff, uint8_t nByte)
{
    int i;

    UCB0I2CSA = dev_addr;        // 7位设备地址
    UCB0CTL1 |= UCTR;            // 写模式
    UCB0CTL1 |= UCTXSTT;         // START
    UCB0TXBUF = reg_addr;        // 填充UCB0TXBUF,启动发送
    while(UCB0CTL1 & UCTXSTT);
    if( UCB0STAT & UCNACKIFG )   // 无应答 UCNACKIFG=1
    {
        return 1;
    }
    UCB0CTL1 &= ~UCTR;            // 读模式
    UCB0CTL1 |= UCTXSTT;          // 标记START
    while(UCB0CTL1 & UCTXSTT);   // 等待UCTXSTT=0
    for(i = nByte; i > 0; i--)
    {
        if(1 == i)
        {
            UCB0CTL1 |= UCTXSTP;      // 标记停止位,读取UCB0RXBUF后才会发送
        }
        while(!(IFG2 & UCB0RXIFG));  // 等待数据
        *pBuff++ = UCB0RXBUF;        // 读取数据
    }
    while( UCB0CTL1& UCTXSTP );      // 等待结束
    return 0;
}

uint8_t I2C_WriteBytes(uint8_t dev_addr, uint8_t reg_addr, uint8_t* pBuff, uint8_t nByte)
{
    int i;

    while( UCB0CTL1& UCTXSTP );
    UCB0I2CSA = dev_addr;            // 7位设备地址
    UCB0CTL1 |= UCTR;                // 写模式
    UCB0CTL1 |= UCTXSTT;             // 标记START
    UCB0TXBUF = reg_addr;            // 写UCB0TXBUF,启动发送
    while(!(IFG2 & UCB0TXIFG));     // 等待发送完成
    if( UCB0STAT & UCNACKIFG )      // 检查应答 UCNACKIFG=1无应答
    {
        return 1;
    }

    for(i = nByte; i > 0; i--)
    {
        UCB0TXBUF = *pBuff++;
        while(!(IFG2 & UCB0TXIFG));   // 等待发送完成
    }
    UCB0CTL1 |= UCTXSTP;              // 写模式下,停止条件立刻生效

    return 0;
}

特别注意

1.多字节时,必须在读取最后一个字节之前设置停止条件:UCB0CTL1 |= UCTXSTP。

2.多字节时,不能在写最后一个字节前就设置停止条件:UCB0CTL1 |= UCTXSTP。应该写完所有字节后设置停止条件。

 

读BQ24195寄存器

那我们就先来读一下看看寄存器默认值吧:

#include <msp430.h>
#include <stdint.h>
#include "i2c.h"

extern uint8_t ReceiveBuffer[];

void main(void)
{
    uint8_t bq24195_reg[11] = {0};

    WDTCTL = WDTPW | WDTHOLD;               // Stop watchdog timer
    initClockTo16MHz();
    initI2C();

    I2C_Master_ReadReg(0x6B, 0x00, 9);
    CopyArray(ReceiveBuffer, &bq24195_reg[0], 9);
    I2C_Master_ReadReg(0x6B, 0x09, 1);
    CopyArray(ReceiveBuffer, &bq24195_reg[9], 1);
    I2C_Master_ReadReg(0x6B, 0x0A, 1);
    CopyArray(ReceiveBuffer, &bq24195_reg[10], 1);
	__bis_SR_register(CPUOFF + GIE);
}

我们在__bis_SR_register(CPUOFF + GIE);处打个断点,下载调试,直接运行到断点处,来看看结果:

看到数据了,我们来和BQ24195手册上的默认值对比一下。

都一样,开心吧。好了,坐在最后面那位同学把手放下吧,我看到了,“你REG00读出来的是55(0x37)明显不是0x30啊!”

先来看看REG00[2:0]的描述:

这个默认值是和硬件设置有关系的,看到硬件连接图没有,老笨D+和D-是直接短接的(识别为适配器,因此电流最大),ILIM的电阻是100R(计算的理论限流值是5.3A),取两者最小值就是REG00[2:0]=111,所以读出来就是00110111=55(0x37)了。

需要注意

因为手册上说了:

REG09不持支连读,所以我们最后REG09和REG0A就单独来读了。当然老笨也试过连读所有的11个值,结果好像也没问题,但我们还是按手册上来吧。

写BQ24195寄存器

#include <msp430.h>
#include <stdint.h>
#include "i2c.h"

extern uint8_t ReceiveBuffer[];

void main(void)
{
    uint8_t bq24195_reg[11] = {0};

    WDTCTL = WDTPW | WDTHOLD;               // Stop watchdog timer
    initClockTo16MHz();
    initI2C();

    bq24195_reg[0] = 0x36;
    bq24195_reg[1] = 0x1D;
    bq24195_reg[5] = 0X8A;                 
    I2C_Master_WriteReg(0x6b, 0x00, &bq24195_reg[0], 2);
    I2C_Master_WriteReg(0x6b, 0x05, &bq24195_reg[5], 1); //关闭看门狗

    bq24195_reg[0] = 0;
    bq24195_reg[1] = 0;
    bq24195_reg[5] = 0;

    I2C_Master_ReadReg(0x6B, 0x00, 9);
    CopyArray(ReceiveBuffer, &bq24195_reg[0], 9);
    I2C_Master_ReadReg(0x6B, 0x09, 1);
    CopyArray(ReceiveBuffer, &bq24195_reg[9], 1);
    I2C_Master_ReadReg(0x6B, 0x0A, 1);
    CopyArray(ReceiveBuffer, &bq24195_reg[10], 1);
	__bis_SR_register(CPUOFF + GIE);
}

我们在写入后又读出来看看结果:

可以看到相应的寄存器值已经被正确更改。

特别注意

1.如果不需要看门狗,请务必关闭它,REG05[5:4]=00。否则所有寄存器将在40s后重置,如果不喂狗(REG01[6])的话。

2.要是bq24195掉电,所有寄存器将被重置。

关于I2C的地址问题

我们就举例bq24195,其它芯片同理。

设备地址

来看看手册上的描述:

明确说明了slave地址是6BH。再来看看通信流程图:

START后跟的那个字节,高7位Slave Address就是6BH,我们注意到操作硬件I2C时要在代码里转换成需要的模式,即发送模式(Transmit)和接收模式(Receive),低1位就由硬件根据收发模式自动补齐为1或0。所以注意图中的两个箭头,是由硬件(协议)自动完成的,因此6BH就是标准的7位地址。

但是有同学用0x6B收不到应答,写用0xD6读用0xD7才能有应答,这是为什么呢?

我们先来看看这两个“地址”的来源:0x6B<<1 | 0X00 = 0XD6,0x6B<<1 | 0x01=0xD7。所以答案是,因为那位同学用的是IO口模拟I2C,但是他的软件并不考虑所谓的标准7位地址模式。

寄存器地址

还是bq24195的手册:

Address:6BH!!!所以下图中Reg Addr是0x6B吗?要不回到上边看看代码?!

当然不是0x6B!而是0x00-0x0A,分别对应的是REG00-REG0A。有些同学一开始想当然,手册不都写了嘛,就是0X6B!所以呢,那些同学搞了三天三夜,读到的全是0XFF,甚至上拉电阻啊什么电源不稳定啊全都换了个遍,结果还是0xFF,一度怀疑人生。。。

 

END

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值