I2C通讯

原理

SCL,SDA挂载上拉电阻,保持空闲状态,有最大设备数限制,有传输速率分别

image-20230408171531425

软件层面

空闲状态:两个都处于高电平

应答信号:主机从机都可以发送,告诉发送者我收到数据了

停止信号:使通讯状态保持空闲

有效性:时钟线处于高电平期间,数据才有效

信号分析

起始信号:时钟线处于高电平期间,数据线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脚的高低电平状态,从而实现输入检测。

Last updated