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;
}