什么是I2C
首先需要知道什么是I2C协议。I2C总线是由Philips公司开发的一种简单、双向二线制同步串行总线。它只需要两根线即可在连接于总线上的器件之间传送信息(摘自百度百科)。I2C主要有两条线,一条SDA数据线,一条SCL时钟线。由于I2C支持主机与多从机之间的通信,所以在传输数据之前先进行寻址操作,才能与对应的从机建立通信。多从机的存在可能出现多个从机同时需要占用总线的情况,这时候就要通过总线仲裁来选择与某个从机进行通信了。每次通信发送或接收的数据为8位一个字节。
I2C时序图详解及通信协议程序
空闲时的SCL和SDA两条线都为高电平,每次通信结束后都需要释放总线,将SCL和SDA拉高。
起始信号和结束信号
起始信号和结束信号时序图如下:
可以看到起始信号是这样产生的:先将SDA数据总线拉低,再将SCL时钟线拉低;而结束信号与其实信号相反,先把SCL时钟线拉高后再将SDA数据总线拉高。
程序代码如下:
//起始信号
void I2C_start()
{
I2C_SCL_1;
I2C_SDA_1;
I2C_Delay();
I2C_SDA_0;
I2C_Delay();
I2C_SCL_0;
}
//结束信号
void I2C_stop()
{
I2C_SDA_0;
I2C_SCL_1;
I2C_Delay();
I2C_SDA_1;
}
数据传输
I2C要求在SCL高电平期间数据线保持稳定,在低电平期间SDA可以产生电平跳变和数据变化。
发送数据代码如下:
void I2C_sendbyte(uint8_t send_data)
{
uint8_t i;
for(i=0;i<8;i++)
{
if(send_data&(0X80>>i))
I2C_SDA_1;
else
I2C_SDA_0;
I2C_SCL_1;
I2C_Delay();
I2C_SCL_0;
}
}
接收数据如下:
uint8_t I2C_readbyte(uint8_t ack)
{
uint8_t read_data=0;
uint8_t i;
uint8_t readbit;
for(i=0;i<8;i++)
{
if(I2C_SDA_0)
readbit=0;
else
readbit=1;
I2C_Delay();
I2C_SCL_1;
I2C_Delay();
I2C_SCL_0;
read_data= (read_data|readbit)<<1;
}
I2C_Delay();
//应答信号
if(ack==0)
I2C_NACK();
else
I2C_ACK();
return Data;
}
应答信号
有应答信号(ACK)和非应答信号(NACK)两种,代码如下:
void I2C_ACK(void) // 应答信号
{
I2C_SDA_0;
I2C_Delay();
I2C_SCL_1;
I2C_Delay();
I2C_SCL_0;
I2C_SDA_1;
}
void I2C_NACK(void) //非应答信号
{
I2C_SDA_1;
I2C_Delay();
I2C_SCL_1;
I2C_Delay();
I2C_SCL_0;
I2C_Delay();
I2C_SDA_0;
}
还有一个等待从机发送应答信号的函数:
uint8_t waitACK()
{
uint8_t receive;
I2C_SDA_1;
I2C_Delay();
I2C_SCL_1;
I2C_Delay();
if(I2C_SDA_1)
receive=1;
else
receive=0;
I2C_SCL_0;
I2C_Delay();
return receive;
}
通信协议
在对某个芯片的寄存器进行读写操作的时候,需要严格按照时序图的操作来执行:
I2C的通讯协议大概分为几个步骤:
1.主机发出起始信号
2.主机发出寻址信号并确认本次操作是读还是写(由寻址信号的最后一位是0还是1决定,0为写,1为读)
3.从机发送应答信号,主机接收
4.主机发送数据信号,从机接收
5.从机发送应答信号,主机接收
6.主机发送停止信号,从机接收,然后结束一次通信。
写入寄存器时的代码如下:
void I2C_write_reg(uint8_t slaveaddr,uint8_t regaddr,uint8_t*writebuffer,uint8_t Wlen)
{
uint8_t writeflag=0;
uint8_t i;
uint8_t error;
I2C_start(); //起始信号
I2C_sendbyte(slaveaddr|0x00); //寻址信号:从机地址+写操作
if(waitACK) //等待从机应答信号
error|=0x01;
I2C_sendbyte(regaddr); //寄存器地址
if(waitACK)
error|=0x02;
for(i=0;i<Wlen;i++) //写入数据
{
I2C_sendbyte(writebuffer[i]);
if(waitACK)
error|=0x04;
}
I2C_stop(); //停止信号
}
读取从机数据的代码如下。需要注意每读取完一个字节(8位)数据后,都需要发送一次应答信号,当最后一个字节数据读取完毕之后发送非应答信号和结束信号以结束本次通信:
void I2C_read_reg(uint8_t slaveaddr,uint8_t regaddr,uint8_t*readbuffer,uint8_t Rlen)
{
uint8_t error=0;
uint8_t i=0;
I2C_start();
I2C_sendbyte(slaveaddr|0x00);
if(waitACK)
error|=0x01;
I2C_sendbyte(regaddr);
if(waitACK)
error|=0x02;
I2C_sendbyte(slaveaddr|0x01);
if(waitACK)
error|=0x04;
for(i=0;i<=(Rlen-1);i++)
{
*readbuffer=I2C_readbyte(1); //每读取完一个字节,都需要给从机一个应答信号
readbuffer++;
}
*readbuffer=I2C_readbyte(0); //最后一个字节读取完发送非应答信号
I2C_stop();
}