本文作为bq24195的I2C使用教程,主要涉及I2C通信代码的实现以及一些注意事项,硬件部分稍有涉及但不是主要内容。
硬件连接图:
I2C的上拉电阻10K或4.7K都行,阻值影响的是跳变沿的时间,即使fast mode I2C通信的频率也才400k左右,所以影响不大。
我们用的是G2553的硬件I2C,有中断法和查询法,不想用中断的可以用查询法。如果选择了低功耗,建议用中断法。
IT已经给我们准备好了,直接照搬msp430g2xx3_usci_i2c_standard_master.c例程就行。稍微整理一下做成i2c.h和i2c.c文件,力求简洁美观。
/*
* i2c.h
*/
#ifndef I2C_H_
#define I2C_H_
#include
#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
#include
#include
#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代码是无需修改就能用的,代码也不是很复杂,流程就不过多分析。需要特别注意的地方有两处,就是代码中两处中文注释的地方。
仅给出函数主体,添加到上述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。应该写完所有字节后设置停止条件。
那我们就先来读一下看看寄存器默认值吧:
#include
#include
#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个值,结果好像也没问题,但我们还是按手册上来吧。
#include
#include
#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掉电,所有寄存器将被重置。
我们就举例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,一度怀疑人生。。。