I2C协议是个啥?
IIC总线是Philips公司在八十年代推出的一种串行,半双工总线。主要用于近距离、低速的芯片之间的通信,IIC总线有两根双向的信号线,一根数据线SDA用于收发数据,一根时钟SCL用于通信双方时钟的同步。IIC总线硬件结构简单,成本较低,因此在各个领域得到广泛的应用。
IIC总线是一种多主机总线,连接在IIC线上的器件分为主机和从机,主机有权发起和结束一次通信,而从机只能被主机呼叫;当总线上有多个主机同时启用总线时,IIC也具备冲突检测和仲裁的功能来防止错误产生;每个连接到IIC总线上的器件都有一个唯一的地址(7bit),且每个器件都可以作为主机也可以作为从机(同一时刻只能有一个主机),总线上的器件增加和删除不影响其他器件正常工作;IIC总线在通信时总线上发送数据的器件为发送器,接收数据的器件为接收器。
I2C总线通信过程
第一步:主机发送起始信号启用总线;
第二步:主机发送一个字节数据确定从机地址和后续字节的传送方向;
第三步:被寻址的从机发送应答信号(ACK)以回应主机;
第四步:发送端发送一个字节的数据;
第五步:接收端发送应答信号回应发送端;
第六步:通信完成后主机发送停止信号(STOP)释放总线。
I2C总线寻址方式
在I2C总线通信的第二步时,主机发送一个字节数据来决定向某个从机发送数据或从某个从机接收数据,该字节的前7为指明从机地址;最后一位为1时表示数据由主机发往从机(MOSI,Master output Slave input),最后一位为0时表示数据由从机发往主机(MISO,Master input Slave output)。然后总线上的所有从机接收到该字节数据,将前七位地址与自己的地址比较,如果某个从机有相同地址,则认为自己被主机寻址,在根据第8位来确定自己是作为发送端或接收端,应注意的是,在发送过程中数据传输方向是不允许更改的。
I2C总线典型数据传输时序
注意:
带有下划线部分表示数据由主机向从机发送,
无下划线部分表示数据由从机向主机发送,
S表示起始信号,P表示终止信号,A表示应答信号,NA表 示非应答信号。
I2C代码,模拟I2C
void my_SDA_IN(void){
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Pin = SDA_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(SDA_GROUP, &GPIO_InitStruct);
}
void my_SDA_OUT(void){
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Pin = SDA_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(SDA_GROUP, &GPIO_InitStruct);
}
//产生IIC起始信号
void IIC_Start(void)
{
SDA_OUT(); //sda线输出
my_IIC_SDA(GPIO_PIN_SET);
my_IIC_SCL(GPIO_PIN_SET);
my_IIC_SDA(GPIO_PIN_RESET);
//START:when CLK is high,DATA change form high to low
my_IIC_SCL(GPIO_PIN_RESET);//准备发送或接收数据
}
//产生IIC停止信号
void IIC_Stop(void)
{
SDA_OUT();//sda线输出
my_IIC_SCL(GPIO_PIN_RESET);
my_IIC_SDA(GPIO_PIN_RESET);
//STOP:when CLK is high DATA change form low to high
my_IIC_SCL(GPIO_PIN_SET);
my_IIC_SDA(GPIO_PIN_SET);//发送I2C总线结束信号
}
//等待应答信号到来
//返回值:1,接收应答失败
// 0,接收应答成功
//使用该函数是注意返回值与其它代码是否一致
u8 IIC_Wait_Ack(void)
{
u8 ucErrTime=0;
SDA_IN(); //SDA设置为输入
my_IIC_SDA(GPIO_PIN_SET);
my_IIC_SCL(GPIO_PIN_SET);
while(my_READ_SDA())
{
ucErrTime++;
if(ucErrTime>250)
{
IIC_Stop();
return 1;
}
}
my_IIC_SCL(GPIO_PIN_RESET);//时钟输出0
return 0;
}
//产生ACK应答
void IIC_Ack(void)
{
my_IIC_SCL(GPIO_PIN_RESET);
SDA_OUT();
my_IIC_SDA(GPIO_PIN_RESET);
my_IIC_SCL(GPIO_PIN_SET);
my_IIC_SCL(GPIO_PIN_RESET);
}
//不产生ACK应答
void IIC_NAck(void)
{
my_IIC_SCL(GPIO_PIN_RESET);
SDA_OUT();
my_IIC_SDA(GPIO_PIN_SET);
my_IIC_SCL(GPIO_PIN_SET);
my_IIC_SCL(GPIO_PIN_RESET);
}
//IIC发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答
void IIC_Send_Byte(u8 txd)
{
u8 t;
SDA_OUT();
my_IIC_SCL(GPIO_PIN_RESET);//拉低时钟开始数据传输
for(t=0;t<8;t++)
{
if((txd&0x80)>>7)
my_IIC_SDA(GPIO_PIN_SET);
else
my_IIC_SDA(GPIO_PIN_RESET);
txd<<=1;
my_IIC_SCL(GPIO_PIN_SET);
my_IIC_SCL(GPIO_PIN_RESET);
}
}
//读1个字节
//ack=1,发送ACK
//ack=0,发送nACK
u8 IIC_Read_Byte(unsigned char ack)
{
unsigned char i,receive=0;
SDA_IN();//SDA设置为输入
for(i=0;i<8;i++ )
{
my_IIC_SCL(GPIO_PIN_RESET);
my_IIC_SCL(GPIO_PIN_SET);
receive<<=1;
if(my_READ_SDA())receive++;
}
if (!ack)
IIC_NAck();//发送nACK
else
IIC_Ack(); //发送ACK
return receive;
}