上图是TPS65987的i2c读写协议,和标准i2c协议有点出入,不过也不难理解,在读的时候i2c slave在发送数据过来之前会先发送1byte数据表示后面会有几个字节数据过来,在写的时候i2c host要先写1byte数据告诉i2c slave接下来会写几个bytes数据。
Talk is cheap. Show me the code.
以下代码是基于STM8S。
/******************************************************************************* **函数名称:void IIC_Read(unsigned char subaddr , unsigned char Byte_addr , unsigned char *buffer) **功能描述:向IIC器件读数据 **入口参数: subaddr : 从器件地址 Byte_addr : 确定从器件写地址的起始地址 *buffer : 读数据的缓冲区起始地址 **输出:无 *******************************************************************************/ void TPS65987_IIC_Read(unsigned char subaddr , unsigned char Byte_addr , unsigned char *buffer) { unsigned char i2csr1; unsigned char DataLen; I2C_CR2_bit.ACK = 1; //产生应答信号 I2C_CR2_bit.START = 1; //发送起始信号 while(I2C_SR1_bit.SB == 0); //等待起始信号产生 i2csr1 = I2C_SR1; //SR1.AF?? I2C_DR = subaddr; //发送器件地地址,并清除SB标志位 while(I2C_SR1_bit.ADDR == 0); //等待器件地址发送完成 i2csr1 = I2C_SR1; i2csr1 = I2C_SR3; //读状态寄存器1和状态寄存器3清除发送器件地址标志位 I2C_DR = Byte_addr; while(I2C_SR1_bit.BTF == 0);//等待移位发送器发送完成 i2csr1 = I2C_SR1; //清除BIT标志位 //重新发送起始信号 I2C_CR2_bit.START = 1;//I2C1->CR1 |= I2C_CR1_START; while(I2C_SR1_bit.SB == 0);//等待起始信号产生 i2csr1 = I2C_SR1;//SR1.AF?? I2C_DR = (char)(subaddr | 0x01); //发送器件地地址,并清除SB标志位 while(I2C_SR1_bit.ADDR == 0); //等待器件地址发送完成 i2csr1 = I2C_SR1; i2csr1 = I2C_SR3; //读状态寄存器1和状态寄存器2清除发送器件地址标志位 while (I2C_SR1_bit.RXNE == 0); //先读取Byte Count到DataLen i2csr1 = I2C_SR1; DataLen = I2C_DR; while(DataLen) { if(DataLen == 1) { I2C_CR2_bit.ACK = 0; //最后一个字节不产生应答信号 I2C_CR2_bit.STOP = 1; //发送停止信号结束数据传输 } while(I2C_SR1_bit.RXNE == 0); i2csr1 = I2C_SR1; *buffer = I2C_DR; buffer++; DataLen--; } }
/******************************************************************************* **函数名称:void IIC_Write(unsigned char subaddr , unsigned char Byte_addr , unsigned char *buffer , unsigned short num) **功能描述:向IIC器件写数据 **入口参数: subaddr : 从器件地址 Byte_addr : 确定器件写地址的起始地址 *buffer : 写数据的起址地址 num : 要写数据的个数 **输出:无 *******************************************************************************/ void TPS65987_IIC_Write(unsigned char subaddr , unsigned char Byte_addr , unsigned char *buffer , unsigned short num) { unsigned char i2csr1; //while(I2C1->SR2 & I2C_SR2_BUSY); //判断I2C模块是否忙 //发送起始信号 I2C_CR2_bit.START = 1; while(I2C_SR1_bit.SB == 0); //等待起始信号产生 i2csr1 = I2C_SR1; //SR1.AF I2C_DR = (subaddr); //发送从器件地址,并清除SB标志位 while(I2C_SR1_bit.ADDR == 0); //等待器件地址发送完成 i2csr1 = I2C_SR1; i2csr1 = I2C_SR3; //读状态寄存器1和状态寄存器3清除发送器件地址标志位 I2C_DR = Byte_addr; //发送从器件存储首地址 #if 1 while(I2C_SR1_bit.BTF == 0); //等待移位发送器发送完成 i2csr1 = I2C_SR1; //清除BIT标志位 #else while((I2C_SR1_bit.TXE) == 0);//数据寄存器为空,跳出循环继续运行 i2csr1 = I2C_SR1; #endif I2C_DR = (unsigned char)num; //把Byte Count先告诉给TPS65987 while(I2C_SR1_bit.BTF == 0);//等待移位发送器发送完成 i2csr1 = I2C_SR1; //清除BIT标志位 i2csr1 = I2C_DR; while(num > 0) { I2C_DR = *buffer; //发送器件存储首地址 while(I2C_SR1_bit.BTF == 0);//等待移位发送器发送完成 i2csr1 = I2C_SR1; //清除BIT标志位 i2csr1 = I2C_DR; buffer++; num--; } I2C_CR2_bit.STOP = 1; //发送停止信号结束数据传输 }
这样就可以对TPS65987进行读写了。
上面的i2c读写函数没有加上timeout功能,如果不想在i2c通信不成功时一直阻塞的话,可以在while循环里面加上,例如:
unsigned int count = 0; while(I2C_SR1_bit.ADDR == 0) //等待器件地址发送完成 { if (++count > 6000) { //count大于6000立即返回 I2C_CR2_bit.STOP = 1; return; } }
注意:
从Windows下上位机工具也可以进行TPS65987的register读写,TPS65981_2_6_7_8 Application Customization 6.1.1上显示的i2c1地址为0x20, i2c2的地址为0x38;注意0x20/0x38是七位地址位的值,进行i2c读写时的地址要左移一位,即0x20/0x38 << 1等于0x40/0x70。
如果直接用地址0x20/0x38进行读写会怎么样呢,结果就是地址发过去没收到ACK。下图是用0x38地址去读寄存器值的时候示波器抓到的波形(黄色波形是SCL,粉红色波形是SDA)。
从波形上看0x38地址发过去是没有ACK的,所以slave地址0x38肯定是错误的了。
后面用示波器量了一下TPS65981_2_6_7_8 Application Customization 6.1.1上位机软件和TPS65987 EVM进行i2c读写时的波形,发现i2c2的地址发过去的确实是0x70。