简介
I2C (Inter-Integrated Circuit) 总线是由PHILIPS公司开发的两线式串行总线,用于连接微控制器及其外围设备。是微电子通信控制领域广泛采用的一种总线标准。它是同步通信的一种特殊形式,具有接口线少,控制方式简单,器件封装形式小,通信速率较高,传输距离短等特性。
- 7位或10位的地址空间
- 普通I2C总线速度为100kbit/s的标准模式和10kbit/s的低速模式,任意时钟频率
结构
两条接口线
- 串行数据线(SDA)
- 串行时钟线(SCL)
时序
主机信号时序: start + address-read/write + ACK + data + ACK + … + stop
主机信号时序: start + address-read + ACK + data + ACK + … + restart + adress-write + stop
I2C协议
总线协议分析
参考设计为使用串行数据线(SDA)和串行时钟线(SCL)、拥有7bit寻址空间的总线。
- 主机 - 产生时钟并发起与从机的通信
- 从机 - 接收时钟并响应主机的寻址
主机发送-从机接收
- 主机发送start信号,发送7bit从机地址和写控制位位(0)
- 从机响应start信号,匹配地址。如果匹配判断读/写标志位,从机发送ACK校验;如果地址不匹配,不发送ACK
- 主机发送1byte数据
- 从机接收数据,发送ACK校验;否则不发送ACK校验
- 重复3、4
- 主机发送stop信号,终止本次通信
master | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|
信号 | start | address | write | ACK | data | ACK | 重复5、6 | stop |
bit | 7 | 1 | 1 | 8 | 1 | … |
主机接收-从机发送
- 主机发送start信号,发送7bit从机地址和写控制位(0)
- 从机相应start信号,匹配地址。如果匹配判断读写标志位,从机发送ACK校验;如果地址不匹配,不发送ACK
- 主机发送1byte数据
- 从机接收数据,并发送ACK校验;否则不发送ACK校验
- 重复3、4
- 主机发送restart信号,发送7bit从机地址和读控制位(1)
- 从机相应restart信号,并判断地址是否匹配。如果匹配判断读/写标志位,发送ACK校验信号。否则,不发送ACK校验
- 从机发送1byte数据
- 主机接收数据,并发送ACK校验
- 主机接收最后一位数据,发送NACK检验
- 主机发送stop信号,本次通信结束
master | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
信号 | start | address | write | ACK | restart | address | read | ACK | data | ACK | 重复9、10 | last data | NACK | stop |
bit | 7 | 1 | 1 | 7 | 1 | 1 | 8 | 1 | … | 8 | 1 |
#注:主机读取从机特定地址上的数据
I2C协议代码分析
开发环境
- 飞思卡尔FRDM-K64F开发板
- KDS3.2集成开发环境
- 飞思卡尔官方KSDK2.0开发库
- ubuntu16.04操作系统
代码实例
master主机
/* 产生start信号,发送地址数据,读/写操作 */
result = I2C_MasterStart(I2C_MASTER, I2C_SLAVE_ADDR, kI2C_Write);
/* 等待slave回复ACK信号 */
while (I2C_MASTER->S & kI2C_ReceiveNakFlag);
/* 发送数据 */
result = I2C_MasterWriteBlocking(I2C_MASTER, masterTxBuffer, I2C_DATA_LEN);
/* restart信号 */
result = I2C_MasterRepeatedStart(I2C_MASTER, I2C_SLAVE_ADDR, kI2C_Read);
/* 等待slave回复ACK信号 */
while (I2C_MASTER->S & kI2C_ReceiveNakFlag);
/* 读取数据 */
I2C_MasterReadBlocking(I2C_MASTER, masterRxBuffer, 2 * sizeof(uint8_t));
slave从机
/* check stop flag */
if (status & kI2C_StopDetectFlag) {
I2C_SLAVE->S = kI2C_IntPendingFlag;
I2C_SlaveClearStatusFlags(I2C_SLAVE, kI2C_StopDetectFlag);
isStop = true;
return ;
}
/* check start flag */
if (status & kI2C_StartDetectFlag) {
isStart = true;
I2C_SLAVE->S = kI2C_IntPendingFlag;
I2C_SlaveClearStatusFlags(I2C_SLAVE, kI2C_StartDetectFlag);
if (!(status & kI2C_AddressMatchFlag)) {
return;
}
}
/* clear pending flag */
I2C_SLAVE->S = kI2C_IntPendingFlag;
/* check NAK */
if (status & kI2C_ReceiveNakFlag) {
/* nothing to do */
/* Receive mode */
I2C_SLAVE->C1 &= ~(I2C_C1_TX_MASK | I2C_C1_TXAK_MASK);
I2C_SLAVE->D;
PRINTF("NAK\r\n");
}
/* Slave address match */
else if (status & kI2C_AddressMatchFlag) {
if (status & kI2C_TransferDirectionFlag) {
/* Slave transfer mode */
I2C_SLAVE->C1 |= I2C_C1_TX_MASK;
isTransfer = true;
} else {
/* Slave recive mode */
I2C_SLAVE->C1 &= ~(I2C_C1_TX_MASK | I2C_C1_TXAK_MASK);
I2C_SLAVE->D;
isRecive = true;
return;
}
}
/* Transfer data */
if (status & kI2C_TransferCompleteFlag) {
/* Transmit data to master */
if (status & kI2C_TransferDirectionFlag) {
I2C_SLAVE->D = slaveRxBuffer[1];
}
else {
/* Receive data from master */
slaveRxBuffer[receive_data_len] = I2C_SLAVE->D;
receive_data_len++;
}
}
问题分析
I2C从机读取数据时,读取的第一字节数据总是为0x00。在KSDK2.0代码库中,I2C主机在读取数据之前,会先读取1byte的无效数据(Do dummy read),然后读取有效数据,KSDK库函数代码如下:
status_t I2C_MasterReadBlocking(I2C_Type *base, uint8_t *rxBuff, size_t rxSize)
{
status_t result = kStatus_Success;
volatile uint8_t dummy = 0;
/* Add this to avoid build warning. */
dummy++;
/* Wait until the data register is ready for transmit. */
while (!(base->S & kI2C_TransferCompleteFlag))
{
}
/* Clear the IICIF flag. */
base->S = kI2C_IntPendingFlag;
/* Setup the I2C peripheral to receive data. */
base->C1 &= ~(I2C_C1_TX_MASK | I2C_C1_TXAK_MASK);
/* If rxSize equals 1, configure to send NAK. */
if (rxSize == 1)
{
/* Issue NACK on read. */
base->C1 |= I2C_C1_TXAK_MASK;
}
/* Do dummy read. */
dummy = base->D;
while ((rxSize--))
{
/* Wait until data transfer complete. */
while (!(base->S & kI2C_IntPendingFlag))
{
}
/* Clear the IICIF flag. */
base->S = kI2C_IntPendingFlag;
/* Single byte use case. */
if (rxSize == 0)
{
/* Read the final byte. */
result = I2C_MasterStop(base);
}
if (rxSize == 1)
{
/* Issue NACK on read. */
base->C1 |= I2C_C1_TXAK_MASK;
}
/* Read from the data register. */
*rxBuff++ = base->D;
}
return result;
}