一,硬件层
原理:多个设备共用scl和sda信号线,支持多主机多从机。scl是单向串行时钟线,始终由主设备控制,sda是双向串行数据线,可以由主机和从机控制。每个设备在这个协议中都有固定的设备号。有俩个上拉电阻,当设备空闲时,由上拉电阻拉高。如果是多主机,那么会使用仲裁,避免数据冲突。有3种传输模式,100k,400k,3.4m。
为什么要使用俩条信号线?节省资源。为什么scl时钟线由主设备控制?进行通信的主动权是掌握在主机手中。为什么sda是双向串行数据线?因为设备和设备之间要通信,交换数据。为什么通过设备号区分?因为主设备发送数据是全部从设备都接收到了,就要通过一个特殊的id来区分,一般是厂家设置。上拉电阻的作用?避免数据冲突,如果主设备和从设备同时输出高电平,造成短路。空闲时,电平也会自动拉高。
二,协议层
主机发送起始信号,然后发送需要通信的从设备地址,选择读写位,等待应答,然后发送数据等待应答。传输数据需要停止时,主机发送停止信号,接收数据需要停止时,给从机发送非应答。
- 起始信号 scl高电平时,sda从高电平向低电平跳变
- 停止信号 scl高电平时,sda从低电平向高电平跳变
- 只有scl为高电平时,sda的数据才有效
- 读数据时,sda会被主机释放掉,由从机控制sda,主机通过sda接收数据;写数据时,sda由主机控制,从机通过sda接收;iic的设备地址有 7位和11位。
- 响应,不管是主机和从机接收到数据都要发送应答,为了信号的完整性。有发送应答ack和发送非应答nack,接收到nack会停止数据发送。比如在主机发送完数据后,从机会立马得到sda的控制权,0是应答,1是非应答。
三,软件模拟iic
好处:不用依赖硬件资源,灵活,操作简单。
坏处:速度比硬件慢。
- iic起始信号
//起始信号 sda先拉低在拉低scl void IIC_Start(void) { IIC_SDA_1(); IIC_SCL_1(); IIC_Delay(); IIC_SDA_0(); IIC_Delay(); IIC_SCL_0();//方便发送数据中,不用在拉低时钟线 IIC_Delay(); }
- iic停止信号:动SDA的时候一定要先保证SCL是低,如果进来的时候两个数据线都是高,你进来就把SDA拉低,变成了启始信号。
//停止信号 sda先拉低,确保scl是1,在拉高sda void IIC_Stop(void) { IIC_SCL_0(); //放置信号混淆 IIC_SDA_0(); IIC_SCL_1(); //确保读到sda低电平 IIC_Delay(); IIC_SDA_1(); }
- 发送应答信号:sda 0
//主机scl拉高,读取从机sda的电平,为低电平为应答 void IIC_Ack(void) { IIC_SCL_0(); IIC_SDA_0(); IIC_Delay(); IIC_SCL_1(); IIC_Delay(); IIC_SCL_0(); }
-
发送非应答信号:sda 1
//主机scl拉高,读取从机sda的电平,高电平为非应答 void IIC_Ack(void) { IIC_SCL_0(); IIC_SDA_1(); IIC_Delay(); IIC_SCL_1(); IIC_Delay(); IIC_SCL_0(); }
- 等待应答信号
uint8_t IIC_WaitAck(void) { uint8_t re; IIC_SDA_1(); //释放总线 IIC_Delay(); IIC_SCL_1(); //CPU读取sda端口线的状态,因为主机会释放sda,然后由从机接管,端口线的结果是主机与从机的线与结果 if(IIC_SDA_READ()) { re=1; } else re=0; IIC_SCL_0(); return re; }
- 发送一个字节数据
void IIC_SendByte(uint8_t _ucByte) { uint8_t i; for(i=0;i<8;i++) { //转换数据为信号 为1还是0 = 移位寄存器,先准备数据,在开时钟线,因为在scl为1时,sda不允许改变 if(_ucByte&0x80) { IIC_SDA_1(); } else IIC_SDA_0(); IIC_Delay(); IIC_SCL_1(); IIC_Delay(); IIC_SCL_0(); //确保下一次能之间写入数据 if(i==7) { IIC_SDA_1(); } _ucByte<<=1; } }
- 接收一个字节
uint8_t IIC_ReadByte(u8 ack) { uint8_t i; uint8_t value; value=0; for(i=0;i<8;i++) { value<<=1; IIC_SCL_1(); IIC_Delay(); if(IIC_SDA_READ()) { value++; } IIC_SCL_0(); IIC_Delay(); } if(ack==0) IIC_NAck(); else IIC_Ack(); return value; }
四,硬件iic
- 第一部分 是俩个通信引脚
- 第二部分 是控制iic的通信速率 ccr和iic外设输入时钟源共同构成iic时钟
- 第三部分 硬件的对数据进行移位,发送时把数据寄存器数据一位一位发送,接收时把移位寄存器的数据一位一位放进数据寄存器。自身地址寄存器,存放的是自身的地址,做从机时,接收到主机的设备信号,会进行比较。双地址,同时使用俩个iic设备。pec是和数据校验相关,存放校验结果。
- 第四部分 控制iic整体逻辑
iic的硬件代码
-
初始化结构体
typedef struct { uint32_t I2C_ClockSpeed; /*!< Specifies the clock frequency. This parameter must be set to a value lower than 400kHz */ uint16_t I2C_Mode; /*!< Specifies the I2C mode. This parameter can be a value of @ref I2C_mode */ uint16_t I2C_DutyCycle; /*!< Specifies the I2C fast mode duty cycle. This parameter can be a value of @ref I2C_duty_cycle_in_fast_mode */ uint16_t I2C_OwnAddress1; /*!< Specifies the first device own address. This parameter can be a 7-bit or 10-bit address. */ uint16_t I2C_Ack; /*!< Enables or disables the acknowledgement. This parameter can be a value of @ref I2C_acknowledgement */ uint16_t I2C_AcknowledgedAddress; /*!< Specifies if 7-bit or 10-bit address is acknowledged. This parameter can be a value of @ref I2C_acknowledged_address */ }I2C_InitTypeDef; //iic的时钟频率 iic的工作模式 时钟占空比 配置自身地址 配置ack 配置寻址模式7或10
-
发送数据,要判断事件
uint32_t I2C_EE_ByteWrite(u8* pBuffer, u8 WriteAddr) { /* Send STRAT condition */ I2C_GenerateSTART(EEPROM_I2Cx, ENABLE); I2CTimeout = I2CT_FLAG_TIMEOUT; /* Test on EV5 and clear it */ while(!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_MODE_SELECT)) { if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(0); } I2CTimeout = I2CT_FLAG_TIMEOUT; /* Send EEPROM address for write */ I2C_Send7bitAddress(EEPROM_I2Cx, EEPROM_ADDRESS, I2C_Direction_Transmitter); /* Test on EV6 and clear it */ while(!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)) { if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(1); } /* Send the EEPROM's internal address to write to */ I2C_SendData(EEPROM_I2Cx, WriteAddr); I2CTimeout = I2CT_FLAG_TIMEOUT; /* Test on EV8 and clear it */ while(!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED)) { if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(2); } /* Send the byte to be written */ I2C_SendData(EEPROM_I2Cx, *pBuffer); I2CTimeout = I2CT_FLAG_TIMEOUT; /* Test on EV8 and clear it */ while(!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED)) { if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(3); } /* Send STOP condition */ I2C_GenerateSTOP(EEPROM_I2Cx, ENABLE); return 1; }
- 接收数据,判断事件
uint32_t I2C_EE_BufferRead(u8* pBuffer, u8 ReadAddr, u16 NumByteToRead)
{
I2CTimeout = I2CT_LONG_TIMEOUT;
//*((u8 *)0x4001080c) |=0x80;
while(I2C_GetFlagStatus(EEPROM_I2Cx, I2C_FLAG_BUSY))
{
if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(9);
}
/* Send START condition */
I2C_GenerateSTART(EEPROM_I2Cx, ENABLE);
//*((u8 *)0x4001080c) &=~0x80;
I2CTimeout = I2CT_FLAG_TIMEOUT;
/* Test on EV5 and clear it */
while(!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_MODE_SELECT))
{
if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(10);
}
/* Send EEPROM address for write */
I2C_Send7bitAddress(EEPROM_I2Cx, EEPROM_ADDRESS, I2C_Direction_Transmitter);
I2CTimeout = I2CT_FLAG_TIMEOUT;
/* Test on EV6 and clear it */
while(!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED))
{
if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(11);
}
/* Clear EV6 by setting again the PE bit */
I2C_Cmd(EEPROM_I2Cx, ENABLE);
/* Send the EEPROM's internal address to write to */
I2C_SendData(EEPROM_I2Cx, ReadAddr);
I2CTimeout = I2CT_FLAG_TIMEOUT;
/* Test on EV8 and clear it */
while(!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED))
{
if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(12);
}
/* Send STRAT condition a second time */
I2C_GenerateSTART(EEPROM_I2Cx, ENABLE);
I2CTimeout = I2CT_FLAG_TIMEOUT;
/* Test on EV5 and clear it */
while(!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_MODE_SELECT))
{
if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(13);
}
/* Send EEPROM address for read */
I2C_Send7bitAddress(EEPROM_I2Cx, EEPROM_ADDRESS, I2C_Direction_Receiver);
I2CTimeout = I2CT_FLAG_TIMEOUT;
/* Test on EV6 and clear it */
while(!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED))
{
if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(14);
}
/* While there is data to be read */
while(NumByteToRead)
{
if(NumByteToRead == 1)
{
/* Disable Acknowledgement */
I2C_AcknowledgeConfig(EEPROM_I2Cx, DISABLE);
/* Send STOP Condition */
I2C_GenerateSTOP(EEPROM_I2Cx, ENABLE);
}
/* Test on EV7 and clear it */
I2CTimeout = I2CT_LONG_TIMEOUT;
while(I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED)==0)
{
if((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(3);
}
{
/* Read a byte from the EEPROM */
*pBuffer = I2C_ReceiveData(EEPROM_I2Cx);
/* Point to the next location where the byte read will be saved */
pBuffer++;
/* Decrement the read bytes counter */
NumByteToRead--;
}
}
/* Enable Acknowledgement to be ready for another reception */
I2C_AcknowledgeConfig(EEPROM_I2Cx, ENABLE);
return 1;
}