Inter-Integrated Circuit Bus 半双工通信
 
物理层
 
 
 
支持多设备的总线,支持多通讯主机以及多个通讯从机。双向串行数据线SDA用于传输数据,串行时钟线SCL用于数据收发同步。连接到总线的设备都有一个独立的地址。总线通过上拉电阻接到电源。I2C设备空闲时,输出高阻态;当所有设备都空闲时,由上拉电阻把总线拉成高电平。多个主机同时使用总线,仲裁决定由哪个设备占用总线。标准模式传输速率100kbit/s,快速模式400kbit/s,高速模式3.4Mbit/s。连接到相同总线的I2C数量受到总线的最大电容400pF限制。 
协议层
 
基本读写过程
 
 
 (1) 主机的I2C接口产生传输起始信号(S),所有的从机都会接收到该信号。  (2) 主机广播从机地址信号+传输方向的选择位,0-主机向从机写数据,1-主机由从机读数据。  (3) 从机接收到匹配的地址后,从机返回应答(ACK)或非应答(NACK)信号,主机收到应答信号后,才能继续发送或接收数据。  (4) “写数据”-主机每发送完一个字节数据,都要等待从机的应答信号。数据传输完成后,主机向从机发送停止信号§。  (5) “读数据”-从机每发送完一个字节数据,都要等待主机的应答信号。主机想停止接收数据,向从机发送非应答信号(NACK)。  (6) 复合格式-两次起始信号;寻找从设备,发送从设备内部的寄存器或存储器地址,读写该地址的内容。
 
起始和停止信号
 
 
 SCL时钟电平为高  SDA数据线由高 -> 低为总线开始条件  SDA数据线由低 -> 高为总线停止条件
 
数据有效性
 
 
地址及数据方向
 
 
 读数据方向-由从机控制SDA信号线,主机接收信号  写数据方向-由主机控制SDA信号线,从机接收信号
 
响应
 
 
 在第9个时钟,数据发送端释放SDA的控制权,由数据接收端控制SDA,SDA为高电平表示非应答信号(NACK),SDA为低电平表示应答信号(ACK)。
 
模拟I2C实现
 
IO配置
 
/************************************************
函数名称 : I2C_Delay
功    能 : I2C延时(非标准延时,请根据MCU速度 调节大小)
参    数 : 无
返 回 值 : 无
作    者 : strongerHuang
*************************************************/
static void I2C_Delay(void)
{
	uint16_t cnt = 100;
	while(cnt--);
}
 
/************************************************
函数名称 : I2C_GPIO_Configuration
功    能 : I2C引脚配置(开漏输出)
参    数 : 无
返 回 值 : 无
作    者 : strongerHuang
*************************************************/
void I2C_GPIO_Configuration(void)
{
	GPIO_InitTypeDef  GPIO_InitStructure;
  	GPIO_InitStructure.GPIO_Pin = PIN_I2C_SCL | PIN_I2C_SDA;
  	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
  	GPIO_Init(PORT_I2C_SCL, &GPIO_InitStructure);
}
 
/************************************************
函数名称 : I2C_Initializes
功    能 : I2C初始化
参    数 : 无
返 回 值 : 无
作    者 : strongerHuang
*************************************************/
void I2C_Initializes(void)
{
  I2C_GPIO_Configuration();
  I2C_SCL_HIGH;                                  //置位状态
  I2C_SDA_HIGH;
}
 
/************************************************
函数名称 : I2C_SDA_SetOutput
功    能 : I2C_SDA设置为输出
参    数 : 无
返 回 值 : 无
作    者 : strongerHuang
*************************************************/
void I2C_SDA_SetOutput(void)
{
  GPIO_InitTypeDef GPIO_InitStructure;
  GPIO_InitStructure.GPIO_Pin   = PIN_I2C_SDA;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
  GPIO_Init(PORT_I2C_SDA, &GPIO_InitStructure);
}
 
/************************************************
函数名称 : I2C_SDA_SetInput
功    能 : I2C_SDA设置为输入
参    数 : 无
返 回 值 : 无
作    者 : strongerHuang
*************************************************/
void I2C_SDA_SetInput(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
  	GPIO_InitStructure.GPIO_Pin   = PIN_I2C_SDA;
  	GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_IN_FLOATING;
  	GPIO_Init(PORT_I2C_SDA, &GPIO_InitStructure);
}
 
开始和停止信号
 
/************************************************
函数名称 : I2C_Start
功    能 : I2C开始
参    数 : 无
返 回 值 : 无
作    者 : strongerHuang
*************************************************/
void I2C_Start(void)
{
  	I2C_SCL_HIGH;	//SCL高
  	I2C_Delay();
  	I2C_SDA_HIGH;	//SDA高 -> 低
  	I2C_Delay();
  	I2C_SDA_LOW;	//SDA低
  	I2C_Delay();
  	I2C_SCL_LOW;	//SCL低(待写地址/数据)
  	I2C_Delay();
}
 
/************************************************
函数名称 : I2C_Stop
功    能 : I2C停止
参    数 : 无
返 回 值 : 无
作    者 : strongerHuang
*************************************************/
void I2C_Stop(void)
{
	I2C_SDA_LOW;	//SDA低 -> 高
  	I2C_Delay();
  	I2C_SCL_HIGH;	//SCL高
  	I2C_Delay();
  	I2C_SDA_HIGH;	//SDA高
  	I2C_Delay();
}
 
应答位信息
 
主机写,从机应答,主机读取应答
 
/************************************************
函数名称 : I2C_GetAck
功    能 : I2C主机读取应答(或非应答)位
参    数 : 无
返 回 值 : I2C_ACK ----- 应答
            I2C_NOACK --- 非应答
作    者 : strongerHuang
*************************************************/
uint8_t I2C_GetAck(void)
{
  	uint8_t ack;
  	I2C_SCL_LOW;	//SCL低 -> 高
  	I2C_Delay();
  	I2C_SDA_SetInput();	//SDA配置为输入模式(开漏模式可以不用切换方向)
	I2C_SCL_HIGH;	//SCL高(读取应答位)
  	I2C_Delay();
  	if(I2C_SDA_READ)
    	ack = I2C_NOACK;	//非应答
  	else
    	ack = I2C_ACK;		//应答
  	I2C_SCL_LOW; 			//SCL低
	I2C_Delay();
	I2C_SDA_SetOutput();	//SDA配置为输出模式
	return ack;				//返回应答位
}
 
主机读,主机产生应答
 
/************************************************
函数名称 : I2C_PutAck
功    能 : I2C主机产生应答(或非应答)位
参    数 : I2C_ACK ----- 应答
            I2C_NOACK --- 非应答
返 回 值 : 无
作    者 : strongerHuang
*************************************************/
void I2C_PutAck(uint8_t Ack)
{
  	I2C_SCL_LOW;	//SCL低
  	I2C_Delay();
  	if(I2C_ACK == Ack)
    	I2C_SDA_LOW;	//应答
  	else
    	I2C_SDA_HIGH; 	//非应答
  	I2C_Delay();
  	I2C_SCL_HIGH; 		//SCL高 -> 低
  	I2C_Delay();
  	I2C_SCL_LOW;		//SCL低
  	I2C_Delay();
}
 
I2C写一字节
 
/************************************************
函数名称 : I2C_WriteByte
功    能 : I2C写一字节
参    数 : Data -------- 数据
返 回 值 : I2C_ACK ----- 应答
            I2C_NOACK --- 非应答
作    者 : strongerHuang
*************************************************/
uint8_t I2C_WriteByte(uint8_t Data)
{
  	uint8_t cnt;
  	for(cnt=0; cnt<8; cnt++)
  	{
    	I2C_SCL_LOW;		//SCL低(SCL为低电平时变化SDA有效)
    	I2C_Delay();
    	if(Data & 0x80)
      		I2C_SDA_HIGH;	//SDA高
    	else
      		I2C_SDA_LOW;	//SDA低
    	Data <<= 1;
    	I2C_Delay();
    	I2C_SCL_HIGH;		//SCL高(发送数据)
    	I2C_Delay();
	}
  	I2C_SCL_LOW;			//SCL低(等待应答信号)
  	I2C_Delay();
  return I2C_GetAck();		//返回应答位
}
 
I2C读一字节
 
/************************************************
函数名称 : I2C_ReadByte
功    能 : I2C读一字节
参    数 : ack --------- 产生应答(或者非应答)位
返 回 值 : data -------- 读取的一字节数据
作    者 : strongerHuang
*************************************************/
uint8_t I2C_ReadByte(uint8_t ack)
{
  	uint8_t cnt;
  	uint8_t data;
  	I2C_SCL_LOW;		//SCL低
  	I2C_Delay();
  	I2C_SDA_SetInput();	//SDA配置为输入模式
  	for(cnt=0; cnt<8; cnt++)
  	{
    	I2C_SCL_HIGH;	//SCL高(读取数据)
    	I2C_Delay();
    	data <<= 1;
    	if(I2C_SDA_READ)
      	data |= 0x01;	//SDA为高(数据有效)
    	I2C_SCL_LOW;	//SCL低
    	I2C_Delay();
	}
  	I2C_SDA_SetOutput();	//SDA配置为输出模式
	I2C_PutAck(ack);	//产生应答(或者非应答)位
  	return data;		//返回数据
}
 
EEPROM写一字节
 
/************************************************
函数名称 : EEPROM_WriteByte
功    能 : EEPROM写一字节
参    数 : Addr -------- 地址
            Data -------- 数据
返 回 值 : I2C_ACK ----- 应答
            I2C_NOACK --- 非应答
作    者 : strongerHuang
*************************************************/
uint8_t EEPROM_WriteByte(uint16_t Addr, uint8_t Data)
{
  	uint8_t  ack;
  	/* 1.开始 */
  	I2C_Start();
  	/* 2.设备地址/写 */
  	ack = I2C_WriteByte(EEPROM_DEV_ADDR | EEPROM_WR);
  	if(I2C_NOACK == ack)
  	{
    	I2C_Stop();
    	return I2C_NOACK;
	}
  	/* 3.数据地址 */
#if (8 == EEPROM_WORD_ADDR_SIZE)
	ack = I2C_WriteByte((uint8_t)(Addr&0x00FF));   //数据地址(8位)
  	if(I2C_NOACK == ack)
  	{
    	I2C_Stop();
    	return I2C_NOACK;
  	}
#else
  	ack = I2C_WriteByte((uint8_t)(Addr>>8));       //数据地址(16位)
  	if(I2C_NOACK == ack)
  	{
    	I2C_Stop();
    	return I2C_NOACK;
  	}
  	ack = I2C_WriteByte((uint8_t)(Addr&0x00FF));
  	if(I2C_NOACK == ack)
  	{
    	I2C_Stop();
    	return I2C_NOACK;
  	}
#endif
  	/* 4.写一字节数据 */
  	ack = I2C_WriteByte(Data);
  	if(I2C_NOACK == ack)
  	{
    	I2C_Stop();
    	return I2C_NOACK;
  }
  	/* 5.停止 */
  	I2C_Stop();
  	return I2C_ACK;
}
 
EEPROM读一字节
 
/************************************************
函数名称 : EEPROM_ReadByte
功    能 : EEPROM读一字节
参    数 : Addr -------- 地址
            Data -------- 数据
返 回 值 : I2C_ACK ----- 应答
            I2C_NOACK --- 非应答
作    者 : strongerHuang
*************************************************/
uint8_t EEPROM_ReadByte(uint16_t Addr, uint8_t *Data)
{
  	uint8_t  ack;
  	/* 1.开始 */
  	I2C_Start();
  	/* 2.设备地址/写 */
  	ack = I2C_WriteByte(EEPROM_DEV_ADDR | EEPROM_WR);
  	if(I2C_NOACK == ack)
  	{
    	I2C_Stop();
    	return I2C_NOACK;
  	}
  	/* 3.数据地址 */
#if (8 == EEPROM_WORD_ADDR_SIZE)
  	ack = I2C_WriteByte((uint8_t)(Addr&0x00FF));   //数据地址(8位)
  	if(I2C_NOACK == ack)
  	{
    	I2C_Stop();
    	return I2C_NOACK;
  	}
#else
  	ack = I2C_WriteByte((uint8_t)(Addr>>8));       //数据地址(16位)
  	if(I2C_NOACK == ack)
  	{
    	I2C_Stop();
    	return I2C_NOACK;
  	}
  	ack = I2C_WriteByte((uint8_t)(Addr&0x00FF));
  	if(I2C_NOACK == ack)
  	{
    	I2C_Stop();
    	return I2C_NOACK;
  	}
#endif
  	/* 4.重新开始 */
  	I2C_Start();
  	/* 5.设备地址/读 */
  	ack = I2C_WriteByte(EEPROM_DEV_ADDR | EEPROM_RD);
  	if(I2C_NOACK == ack)
  	{
    	I2C_Stop();
    	return I2C_NOACK;
	}
  	/* 6.读一字节数据 */
  	*Data = I2C_ReadByte(I2C_NOACK);//只读取1字节(产生非应答)
  	/* 7.停止 */
  	I2C_Stop();
  	return I2C_ACK;
}