目录

1、I2C简介

2、I2C主设备与从设备的一般通信过程

3、I2C通信的实现

4、上干货


1、I2C简介

I2c属于两线式串行总线,由飞利浦公司开发用于微控制器(MCU)和外围设备(从设备)进行通信的一种总线,属于一主多从(一个主设备(Master),多个从设备(Slave))的总线结构,总线上的每个设备都有一个特定的设备地址,以区分同一I2C总线上的其他设备。

物理I2C接口有两根双向线,串行时钟线(SCL)串行数据线(SDA)组成,可用于发送和接收数据,但是通信都是由主设备发起,从设备被动响应,实现数据的传输。

I2C正常情况下都会用到外部上拉。

2、I2C主设备与从设备的一般通信过程

        2.1 主设备给从设备发送/写入数据:

1. 主设备发送起始(START)信号

2. 主设备发送设备地址到从设备

3. 等待从设备响应(ACK)

4. 主设备发送数据到从设备,一般发送的每个字节数据后会跟着等待接收来自从设备的响应(ACK)

5. 数据发送完毕,主设备发送停止(STOP)信号终止传输

   2.2 主设备从从设备接收/读取数据

1. 设备发送起始(START)信号

2. 主设备发送设备地址到从设备

3. 等待从设备响应(ACK)

4. 主设备接收来自从设备的数据,一般接收的每个字节数据后会跟着向从设备发送一个响应(ACK)

5. 一般接收到最后一个数据后会发送一个无效响应(NACK),然后主设备发送停止(STOP)信号终止传输

3、I2C通信的实现

3.1. 使用I2C控制器实现

就是使用芯片上的I2C外设,也就是硬件I2C,它有相应的I2C驱动电路,有专用的IIC引脚,效率更高,写代码会相对简单,只要调用I2C的控制函数即可不需要用代码去控制SCL、SDA的各种高低电平变化来实现I2C协议,只需要将I2C协议中的可变部分(如:从设备地址、传输数据等等)通过函数传参给控制器,控制器自动按照I2C协议实现传输,但是如果出现问题,就只能通过示波器看波形找问题。

3.2. 使用GPIO通过软件模拟实现

软件模拟I2C比较重要因为软件模拟的整个流程比较清晰哪里出来bug很快能找到问题模拟一遍会对I2C通信协议更加熟悉。 如果芯片上没有IIC控制器,或者控制接口不够用了,通过使用任意IO口去模拟实现IIC通信协议,手动写代码去控制IO口的电平变化,模拟IIC协议的时序,实现IIC的信号和数据传输,下面会讲到根据通信协议如何用软件去模拟

4、上干货

// 起始信号 S
void iic_start(void)
{
    IIC_SDA_HIGH();
    IIC_SCL_HIGH();
    iic_delay_us(5);
    IIC_SDA_LOW();           // SDA先拉低
    iic_delay_us(5);
    IIC_SCL_LOW();           // SCL再拉低,占用总线
}

// 停止信号 P
void iic_stop(void)
{
    IIC_SDA_LOW();
    IIC_SCL_HIGH();
    iic_delay_us(5);
    IIC_SDA_HIGH();          // SDA拉高,产生停止条件
    iic_delay_us(5);
}

// 发送一个字节(高位在前)
void ic_send_byte(uint8_t data)
{
    uint8_t i;
    for(i = 0; i < 8; i++)
    {
        if(data & 0x80)      // 高位先发
            IIC_SDA_HIGH();
        else
            IIC_SDA_LOW();
        
        data <<= 1;
        iic_delay_us(2);
        
        IIC_SCL_HIGH();       // 拉高SCL,从机采样数据
        iic_delay_us(5);
        IIC_SCL_LOW();        // 拉低SCL,准备发送下一位
        iic_delay_us(2);
    }
}

// 接收一个字节
uint8_t iic_recv_byte(void)
{
    uint8_t i, data = 0;
    
    IIC_SDA_HIGH();           // 释放SDA,让从机控制
    for(i = 0; i < 8; i++)
    {
        data <<= 1;
        
        IIC_SCL_HIGH();       // SCL高电平,从机输出数据
        iic_delay_us(5);
        
        if(IIC_SDA_READ())
            data |= 0x01;
        
        IIC_SCL_LOW();        // SCL拉低,从机准备下一位
        iic_delay_us(2);
    }
    
    return data;
}

// 发送应答 ACK
void iic_send_ack(void)
{
    IIC_SDA_LOW();            // 低电平 = 应答
    iic_delay_us(2);
    
    IIC_SCL_HIGH();
    iic_delay_us(5);
    IIC_SCL_LOW();
    
    IIC_SDA_HIGH();           // 释放SDA
    iic_delay_us(2);
}

// 发送非应答 NACK
void iic_send_nack(void)
{
    IIC_SDA_HIGH();           // 高电平 = 非应答
    iic_delay_us(2);
    
    IIC_SCL_HIGH();
    iic_delay_us(5);
    IIC_SCL_LOW();
    
    IIC_SDA_HIGH();           // 释放SDA
    iic_delay_us(2);
}

// 等待从机应答
uint8_t iic_wait_ack(void)
{
    uint8_t timeout = 0;
    
    IIC_SDA_HIGH();           // 释放SDA
    iic_delay_us(2);
    
    IIC_SCL_HIGH();
    iic_delay_us(5);
    
    while(IIC_SDA_READ())     // 等待SDA被拉低(应答)
    {
        timeout++;
        if(timeout > 200)     // 超时处理
        {
            IIC_SCL_LOW();
            return 1;         // 无应答
        }
    }
    
    IIC_SCL_LOW();
    return 0;                 // 收到应答
}

// 向指定设备地址写多个字节
uint8_t iic_write_bytes(uint8_t dev_addr, uint8_t reg_addr, 
                        uint8_t *data, uint16_t len)
{
    uint16_t i;
    
    iic_start();
    
    // 发送设备地址 + 写位
    iic_send_byte(dev_addr << 1 | 0);
    if(iic_wait_ack()) {
        iic_stop();
        return 1;
    }
    
    // 发送寄存器地址
    iic_send_byte(reg_addr);
    if(iic_wait_ack()) {
        iic_stop();
        return 1;
    }
    
    // 发送数据
    for(i = 0; i < len; i++) {
        iic_send_byte(data[i]);
        if(iic_wait_ack()) {
            iic_stop();
            return 1;
        }
    }
    
    iic_stop();
    return 0;
}

// 从指定设备地址读多个字节
uint8_t iic_read_bytes(uint8_t dev_addr, uint8_t reg_addr, 
                       uint8_t *buf, uint16_t len)
{
    uint16_t i;
    
    // 先伪写,设置寄存器地址
    iic_start();
    iic_send_byte(dev_addr << 1 | 0);
    if(iic_wait_ack()) {
        iic_stop();
        return 1;
    }
    
    iic_send_byte(reg_addr);
    if(iic_wait_ack()) {
        iic_stop();
        return 1;
    }
    
    // 重启,开始读
    iic_start();
    iic_send_byte(dev_addr << 1 | 1);
    if(iic_wait_ack()) {
        iic_stop();
        return 1;
    }
    
    // 读取数据
    for(i = 0; i < len; i++) {
        buf[i] = iic_recv_byte();
        
        if(i == len - 1)
            iic_send_nack();     // 最后一个字节发NACK
        else
            iic_send_ack();       // 其他字节发ACK
    }
    
    iic_stop();
    return 0;
}
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐