一、I2C总结
(1)I2C低层时序:起始信号、停止信号、发送字节、读取字节
(2)三根线:GND、SCL、SDA,串行,电平式
(3)总线式结构,可以一对多,总线上可以挂上百个器件,用从地址来区分
(4)主从式,由主设备来发起通信及总线仲裁,从设备被动响应。
(5)通信速率一般,不适合语音、视频等信息类型
(6)只有在总线非忙时才被允许进行数据传输
(7)起始信号:时钟线保持高电平期间,数据线从高电平跳变到低电平
(8)停止信号:时钟线保持高电平期间,数据线从低电平跳变到高电平
(9)应答信号:每次数据传输成功后,接收器件发送一个应答信号。当第九个时钟信号产生是,产生应答信号的器件将SDA拉低,表示已经接收到8为数据。
(10)SCL:串行时钟输入,SCL同步数据传输,上升沿数据写入,下降沿数据读出,这是针对主设备来讲的。
二、对I2C底层时序进行编程
(1)起始条件
void i2c_start(void)
{
I2C_SDA = 1; //将数据线拉高,为产生下降沿做准备
delay10us(); //由于24C02芯片规定了电平持续的时间长短为至少0.4微妙
I2C_SCL = 1; //拉高时钟线,为产生起始条件做准备
delay10us();
I2C_SDA = 0; //产生下降沿,同时也发出了,起始条件
delay10us();
I2C_SCL = 0; //将时钟线拉低
delay10us();
}
(2)停止条件
void i2c_stop(void)
{
//当SCL为高电平时SDA的上升沿叫做停止条件
//和起始条件类似进行操作即可
I2C_SDA = 0;
delay10us();
I2C_SCL = 1;
delay10us();
I2C_SDA = 1;
delay10us();
}
(3)发送一个字节
void i2c_send_byte(uchar date)
{
uchar i = 0, temp = 0;
I2C_SCL = 0; //再次确认时钟线为低电平,主要是为了确保能产生上升沿
delay10us();
for (i=0; i<8; i++)
{
temp = ((date & 0x80) >> 7); //将date的最高位取出来
date <<= 1; //将已经取出来的最高位舍弃掉
I2C_SDA = temp; //将最高位放到数据线上
delay10us();
I2C_SCL = 1; //产生上升沿将最高位发送出去
delay10us();
I2C_SCL = 0; //为方便数据进行更新,将时钟线拉低
delay10us(); //不然的话24C02会以为是发送控制信号
}
}
(4)接收一个字节
uchar i2c_receive_byte(void)
{
uchar i = 0, date = 0;
I2C_SDA = 1; //主设备释放总线
delay10us(); //由于是接收从设备的数据,主设备必须将数据线释放掉
for (i=0; i<8; i++)
{
I2C_SCL = 1; //将时钟线拉高,方便产生下降沿
delay10us(); //通过观察下面的时序图我们发现,当始终线产生上升沿的时候
//此时数据线上已经准备好了要发送的数据数据已经在数据线上准备好,
date <<= 1;
date |= I2C_SDA; //将数据的最高位保存起来
delay10us();
I2C_SCL = 0; //产生下降沿将数据读出
delay10us();
}
return date;
}
(5)、产生一个应答
uchar i2c_ack(void)
{
uchar b = 0;
I2C_SDA = 1; //释放掉数据线,方便从器件产生应答信号
delay10us();
I2C_SCL = 1; //从下面的时序图中我们可以看出来,当第九个时钟的上升沿
//产生后,从器件就会进行应答,因此下面我们就要赶紧接收应答
delay10us();
while (I2C_SDA && b < 200) //判断是否产生了应答
{
b++
if (b == 200)
{
b = 0;
return 1; //从器件没有应答
}
}
I2C_SCL = 0;
delay10us();
return 0; //从器件有应答
}
三、对24C02的读写函数进行编写
(1)针对上面的写时序,我们编写如下函数
uchar at24c02_write(uchar addr, uchar date)
{
i2c_start(); //发送起始条件
i2c_write_byte(0xa0); //写器件地址
while (i2c_ack()) //判断应答信号
{
return 1;
}
i2c_write_byte(addr); //写入需要写入数据的地址
while (i2c_ack()) //判断应答信号
{
return 1;
}
i2c_write_byte(date); //写入需要写入的数据
while (i2c_ack()) //判断应答信号
{
return 1;
}
i2c_stop(); //发送停止条件
return 0;
}
(2)针对上面的随机读时序,我们编写如下函数:
我们需要注意的是,随机读的时序里面在中间还有一个起始条件,这一点千万不能忘记,不然程序会有很大的问题,读出来绝对是错的。如果你发现无论如何你读出来的数据都是一样的,那么请看一下是不是这里出现了问题。
uchar at24c02_read(uchar addr)
{
uchar date = 0;
i2c_start(); //发送起始信号
i2c_write_byte(0xa0); //发送器件地址和写的指令
while (i2c_ack()) //产生应答信号
{
return 1;
}
i2c_write_byte(addr); //发送需要读的地址
while (i2c_ack()) //产生应答信号
{
return 1;
}
i2c_start(); //产生起始条件,这一步不能少 少了读就会出现异常
i2c_write_byte(0xa1); //发送器件地址和读的命令
while (i2c_ack()) //判断应答信号
{
return 1;
}
date = i2c_read_byte(); //读取数据
i2c_stop(); //产生停止条件
return date;
}
备注:我写的注释相同的话写的不一样,哪一种能让你理解你就看哪一种。至于如何测试这些函数,你可以先给某个地址写入一个数据,然后再将这个地址的数据读出来,然后通过串口输出,观察是否一致。需要注意一点就是,由于24C02的写周期很长,在发送一个写命令之后要延时至少5个毫秒,不然还没写完你就读的话,会有问题,有可能读不出了,有可能会读错。