原理
SCL,SDA挂载上拉电阻,保持空闲状态,有最大设备数限制,有传输速率分别
软件层面
空闲状态:两个都处于高电平
应答信号:主机从机都可以发送,告诉发送者我收到数据了
停止信号:使通讯状态保持空闲
有效性:时钟线处于高电平期间,数据才有效
信号分析
起始信号:时钟线处于高电平期间,数据线SDA从高电平往低电平跳变
停止信号:时钟线处于高电平期间,数据线从低电平往高电平跳变
应答信号:在发送8字节数据后,时钟线SCL处于第九个高电平期间,SDA检测到高电平就是NACK(默认上拉电阻下就是高定平),SDA检测到低电平就是ACK(从机若应答,发送低电平,拉低SDA电平)
应答完再停止
代码
起始信号
void iic_start(void)
{
IIC_SDA(1); //把sda拉高,后面才能拉低发出起始信号
IIC_SCL(1); //先把scl拉高,确保信号有效
iic_delay(); //延迟时间与设备有关
IIC_SDA(0); /* START信号: 当SCL为高时, SDA从高变成低, 表示起始信号 */
iic_delay();
IIC_SCL(0); /* 钳住I2C总线,准备发送或接收数据,在发送时再拉高 */
iic_delay();
}
停止信号
void iic_stop(void)
{
IIC_SDA(0); /* STOP信号: 当SCL为高时, SDA从低变成高, 表示停止信号 */
iic_delay();
IIC_SCL(1); //SCL拉高确保数据有效性
iic_delay();
IIC_SDA(1); /*SDA从低到高,实现结束信号的发送 发送I2C总线结束信号 */
iic_delay();
}
检测应答信号
uint8_t iic_wait_ack(void)
{
uint8_t waittime = 0;
uint8_t rack = 0;
IIC_SDA(1); /* 主机释放SDA线(此时外部器件可以拉低SDA线) */
iic_delay();
IIC_SCL(1); /* SCL=1, 此时从机可以返回ACK */
iic_delay();
while (IIC_READ_SDA) /* 高电平状态,等待应答,若变为低电平,完成应答 */
{
waittime++;
if (waittime > 250) //超时
{
iic_stop();
rack = 1; //标志位置1,表示失败
break;
}
}
IIC_SCL(0); /* SCL=0, 结束ACK检查 */
iic_delay();
return rack; //返回标志位,若能直接从while出来,而不是超时break,则为0,代表应答成功
}
发送应答信号
void iic_ack(void)
{
IIC_SDA(0); /* SCL 0 -> 1 时 SDA = 0,表示应答 */
iic_delay();
IIC_SCL(1); /* 拉高数据有效, 产生一个时钟 */
iic_delay();
IIC_SCL(0);
iic_delay();
IIC_SDA(1); /* 主机释放SDA线 */
iic_delay();
}
发送非应答
void iic_nack(void)
{
IIC_SDA(1); /* SCL 0 -> 1 时 SDA = 1,表示不应答 */
iic_delay();
IIC_SCL(1); /* 产生一个时钟 */
iic_delay();
IIC_SCL(0);
iic_delay();
}
发送一字节数据
void iic_send_byte(uint8_t data)
{
uint8_t t;
for (t = 0; t < 8; t++)
{
IIC_SDA((data & 0x80) >> 7); /* 高位先发送 */
iic_delay();
IIC_SCL(1); //SCL高,数据有效
iic_delay();
IIC_SCL(0);
data <<= 1; /* 左移1位,把此次发送的最高位丢掉,用于下一次发送 */
}
IIC_SDA(1); /* 发送完成, 主机释放SDA线,恢复默认的 */
}
数据接收
uint8_t iic_read_byte(uint8_t ack)
{
uint8_t i, receive = 0;
for (i = 0; i < 8; i++ ) /* 接收1个字节数据 */
{
receive <<= 1; /* 高位先输出,所以先收到的数据位要左移 */
IIC_SCL(1);
iic_delay();
if (IIC_READ_SDA) //读取SDA数据,为1就让receive+1
{
receive++;
}
IIC_SCL(0);//释放SCL
iic_delay();
}
if (!ack)
{
iic_nack(); /* 发送nACK */
}
else
{
iic_ack(); /* 发送ACK */
}
return receive;
}
总结:
会发现每次操作完都把SCL拉低
AT24C02实操
AT24C02是一款2K BIT(256Byte)的eeprom,有32页,每页8Byte,有WP写保护引脚,当该引脚置高电平,只可读不可写
地址方面:前四位固定1010固定,中间三位由三个引脚A2 A1 A0决定,最后一位地址由数据传输方向决定,当读的时候最后一位为0,写的时候最后一位为1
对本精英开发板来说 写地址为0XA0,读地址为0XA1
前7位称设备地址,真正编程的时候操作的八位地址称作通讯地址
**写操作:**AT24C02支持字节写模式和页写模式。
页写模式就是连续写入数据。只需要写一个地址,连续写入数据时地址会自增,但存在页的限制,超出一页时,超出数据覆盖原先写入的数据。但读会自动翻页。
**读操作:**AT24C02支持当前地址读模式,随机地址读模式和顺序读模式
当前读模式是基于上一次读/写操作的最后位置继续读出数据。
随机地址读模式是指定地址读出数据,一个字节一个字节的读
IO口配置
SCL推挽输出 SDA 开漏输出
输出时:开漏输出的特点是能输出低电平,无法输出高电平,但可以被外部上拉至高电平,主机(MCU)输出0,可以拉低信号,来实现低电平发送,主机输出1(实际不起作用),由外部上拉电阻上拉,实现高电平发送。
输入时:当SDA作输入时,设置输出1状态,此时因为MCU无法输出1,相当于释放了SDA脚,此时外部器件可以主动拉低SDA脚/释放SDA脚(同样由上拉电阻提供“输出1的功能”),实现SDA脚的高低电平变化。由于开漏输出模式下,MCU还是可以读取IDR状态寄存器,来获取引脚高低电平,因此MCU读取IDR,即可获得SDA脚的高低电平状态,从而实现输入检测。