GD32实战10__I2C

知识点

  1. 掌握I2C总线
  2. 如何看时序图
  3. 如何使用I2C接口的器件,例如AT24C02

原理

​ I2C/IIC(集成电路总线)是philips推出的一种串行总线。

主要特性

  1. 只有两根线,串行数据线SDA,串行时钟线SCL
  2. 总线上的所有器件必须都有唯一的地址
  3. 多主机总线,可同时支持多个slave和多个master,即支持冲突检测和仲裁
  4. 8位双向数据传输,速率标准模式下最高100kbit/s,快速模式下最高400kbit/s,高速模式下最高3.4Mbit/s

硬件电路要求

如图,

  1. 由于设备之间是线与到一起的,所以设备的GPIO必须是开漏输出,不能是推挽输出
  2. 由于GPIO是开漏输出,所以必须接上拉电阻,因此SDA和SCL默认是高电平

在这里插入图片描述

协议要求

可以这么理解,

  1. I2C上的设备要进行通信,必然需要收发两方,即发送器和接收器,
  2. 总线需要初始化,需要有设备产生时钟信号,总线上的设备总要知道是谁正在访问谁,即主机和从机,主机完成总线初始化,产生时钟信号,并向从机发起寻址访问
  3. 既然各方角色已经明确了,那么
    1. 当主机要访问从机时,总要告诉从机要开始了,即起始调节
    2. 当从机收到后,
      1. 可以告诉主机自己收到了,让主机继续发生后面的数据,即应答信号
      2. 也可以告诉主机自己不想搭理主机了,让主机停止发生,即非应答信号
    3. 当主机判断需要停止该次传输时,应该告诉从机通信结束,即停止调节

具体解释如下:

1. 数据格式

如图,

  1. SDA上的数据每个字节必须是8位,且是按bit传输的,高位先传,低位后传
  2. 前8个bit是数据bit位,最后一个bit是应答或非应答信号

在这里插入图片描述

2. 有效数据位识别

如图,

1. 当SCL为低电平时,SDA的状态是允许切换的,即发送的数据要在时钟是低电平时输出
2. 当SCL是高电平时,SDA的状态是不能变化的,即需要读取的数据要在时钟是高电平时读走

在这里插入图片描述

3. 起始条件

​ 如图,当SCL是高电平的时候,SDA由高电平变化到低电平

在这里插入图片描述

4. 应答信号

如图,

在这里插入图片描述

5. 非应答信号

如图,

在这里插入图片描述

6. 停止条件

​ 如图,当SCL是高电平时,SDA由低电平变化到高电平

在这里插入图片描述

通过I2C总线读写EEPROM

硬件设计

如图,我们通过I2C总线完成对EEPROM AT24C02的读写操作,A0,A1,A2都接地,即该芯片物理地址是0

在这里插入图片描述

AT24C02C手册导读

要实现对AT24C02C读写操作,我们需要了解AT24C02C的基本功能和要求,这些可以从Datasheet中找到,例如

1. 写保护和地址要求

在这里插入图片描述

2. 上电复位要求

如下图,芯片复位时长在130到270ms之间,稳妥起见,上电后至少应该在270ms后在对该芯片进行操作

在这里插入图片描述

3. I2C总线时序要求

如下图,SCL和SDA的每个电平的时间都做了详细的说明,编码时需要严格按照该时间要求编码。

在这里插入图片描述

在这里插入图片描述

4. 读操作的基本时序
  1. 下面的图中,都只画了SDA的变化,没画SCL,原因是从I2C协议规定了SDA和SCL的关系,因此只要知道SDA怎么变化的自然就知道SCL应该如何变化,所以只画SDA足够了
  2. 度操作支持下面三种,在手册里都有详细的介绍

在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

5. 写操作的基本时序

写操作支持两种,手册里也有详细介绍

在这里插入图片描述

软件设计

  1. 为了更深入的理解I2C总线协议,此例中没用硬件I2C控制器,而是用GPIO口模拟的,原理明白了,硬件方式就更加简单了

  2. 从前面的介绍可以看出,I2C总线是通用的,不仅AT24C02C可以使用,其它的芯片也可以使用,且协议规范是一样的,因此分成三部分,这样三部分的代码在后续的编码中可以做到通用

  3. 下面三部分的编码与上面原理和手册中的时序是完全匹配的,可以对照阅读

1. I2C总线驱动
  1. 我把电平切换和延时封装成一个宏,这样函数中只要把精力都投入到电平变化就可以了
  2. 延时时间固定5us,这样代码会简单很多,原因如“I2C总线时序要求”一节中,时序基本都要求了最小时间间隔5us足以,有两项有最大时间要求,却不影响我们编码。
#define I2C_Set1(i2c) GPIO_SetBits(i2c);I2C_Delay(5);
#define I2C_Set0(i2c) GPIO_ResetBits(i2c);I2C_Delay(5);
#define I2C_Get(i2c) GPIO_ReadInputBit(i2c);

VOID DRV_I2C_Start(VOID)
{
    I2C_SetOutput(I2C_SDA);
    I2C_Set1(I2C_SDA);
    I2C_Set1(I2C_SCL);
    I2C_Set0(I2C_SDA);
    I2C_Set0(I2C_SCL);
}

VOID DRV_I2C_Stop(VOID)
{
    I2C_SetOutput(I2C_SDA);
    I2C_Set0(I2C_SDA);
    I2C_Set1(I2C_SCL);
    I2C_Set1(I2C_SDA);
}


U32 DRV_I2C_WriteByte(IN U8 data)
{
    U8 i = 0;
    U8 byte = data;
    U8 sda = 0;
    
    I2C_SetOutput(I2C_SDA);
        
    for (i = 0; i < 8; i++)
    {
        I2C_Set0(I2C_SCL);
        if (byte & 0x80)
        {
            I2C_Set1(I2C_SDA);
        }
        else
        {
            I2C_Set0(I2C_SDA);
        }
        I2C_Set1(I2C_SCL);
        byte <<= 1;
    }
    
    I2C_Set0(I2C_SCL);
    I2C_SetInput(I2C_SDA);
    I2C_Set1(I2C_SCL);

    sda = I2C_Get(I2C_SDA);
    if (sda)
    {
        I2C_Set0(I2C_SCL);
        I2C_SetOutput(I2C_SDA);
        return OS_ERROR;
    }
    
    I2C_Set0(I2C_SCL);
    I2C_SetOutput(I2C_SDA);
    I2C_Set1(I2C_SDA);
    
    return OS_OK;
}

U32 DRV_I2C_ReadByte(OUT U8 *byte)
{
    U8 i = 0;
    U8 bit = 0;
    U8 sda = 0;

    I2C_SetInput(I2C_SDA);
    
    for (i = 0; i < 8; i++)
    {
        I2C_Set1(I2C_SCL);
        sda = I2C_Get(I2C_SDA);
        if (sda)
        {
            bit |= 0x1;
        }
        I2C_Set0(I2C_SCL);
        if (i != 7)
        {
            bit <<= 1;
        }
    }
    *byte = bit;
    return OS_OK;
}

VOID DRV_I2C_NoAck(VOID)
{
    I2C_Set0(I2C_SCL);
    I2C_SetOutput(I2C_SDA);
    I2C_Set1(I2C_SDA);
    I2C_Set1(I2C_SCL);
    I2C_Set0(I2C_SCL);
}

VOID DRV_I2C_Ack(VOID)
{
    I2C_Set0(I2C_SCL);
    I2C_SetOutput(I2C_SDA);
    I2C_Set0(I2C_SDA);
    I2C_Set1(I2C_SCL);
    I2C_Set0(I2C_SCL);
}

VOID DRV_I2C_Init(VOID)
{
    GPIO_InitPara GPIO_InitStructure;

    RCC_APB2PeriphClock_Enable(RCC_APB2PERIPH_GPIOB,ENABLE);

    GPIO_InitStructure.GPIO_Pin =  GPIO_PIN_6 | GPIO_PIN_7;
    GPIO_InitStructure.GPIO_Mode = GPIO_MODE_OUT_OD;
    GPIO_InitStructure.GPIO_Speed = GPIO_SPEED_50MHZ;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    GPIO_SetBits(GPIOB, GPIO_PIN_6);
    GPIO_SetBits(GPIOB, GPIO_PIN_7);
}
2. AT24C02C驱动

下面的函数接口与手册中的读写接口一一对应,其中立即地址读没有做,因为不常用。

/* 字节写 */
VOID DRV_AT24C02C_WriteByte(IN U8 slaveAddr, IN U8 byteAddr, IN U8 data)
{
    DRV_I2C_Start();
    DRV_I2C_WriteByte(slaveAddr);
    DRV_I2C_WriteByte(byteAddr);
    DRV_I2C_WriteByte(data);
    DRV_I2C_Stop();
}

/* 页写 */
VOID DRV_AT24C02C_WritePage(IN U8 slaveAddr, IN U8 byteAddr, IN U8 data[], IN U8 len)
{
    U8 i = 0; 
    
    DRV_I2C_Start();
    DRV_I2C_WriteByte(slaveAddr);
    DRV_I2C_WriteByte(byteAddr);
    for (i = 0; i < len; i++)
    {
        DRV_I2C_WriteByte(data[i]);
    }
    DRV_I2C_Stop();
}

/* 选择地址读 */
VOID DRV_AT24C02C_ReadByte(IN U8 slaveAddr, IN U8 byteAddr, OUT U8 *data)
{
    U8 tmp = 0; 

    DRV_I2C_Start();
    DRV_I2C_WriteByte(slaveAddr);
    DRV_I2C_WriteByte(byteAddr);
    DRV_I2C_Start();
    DRV_I2C_WriteByte(slaveAddr+1);
    DRV_I2C_ReadByte(&tmp);
    DRV_I2C_NoAck();    
    DRV_I2C_Stop(); 
    *data = tmp;
}

/* 连续读 */
VOID DRV_AT24C02C_ReadPage(IN U8 slaveAddr, IN U8 byteAddr, OUT U8 data[], IN U8 len)
{
    U8 tmp = 0; 
    U8 i = 0;

    DRV_I2C_Start();
    DRV_I2C_WriteByte(slaveAddr);
    DRV_I2C_WriteByte(byteAddr);
    DRV_I2C_Start();
    DRV_I2C_WriteByte(slaveAddr+1);
    for (i = 0; i < len-1; i++)
    {
        DRV_I2C_ReadByte(&tmp);
        DRV_I2C_Ack();
        data[i] = tmp;
    }
    
    DRV_I2C_ReadByte(&tmp);
    DRV_I2C_NoAck();    
    data[i] = tmp;
    DRV_I2C_Stop();    
}

VOID DRV_AT24C02C_Init(VOID)
{
    DRV_I2C_Init();
}

3. 功能测试举例

下面的代码,只是为了举例说明如何使用上述接口而已,其中0xA0的含义如下图,其中R/W位接口内部有处理,此处统一填了0。

在这里插入图片描述

#define I2C_AT24C02C_ADDR 0xA0

VOID APP_I2C_Test(VOID)
{
    U8 len = 255;
    U8 databufIn[5] = {0};
    U8 databufOut[255] = {0};
    U8 dataOut = 0;
    U8 byteAddr = 0x00;

    DRV_AT24C02C_Init();
    
    dataOut = 0;
    DRV_AT24C02C_ReadByte(I2C_AT24C02C_ADDR, byteAddr, &dataOut);
    APP_DEBUG("read=0x%x", dataOut);
    DRV_AT24C02C_WriteByte(I2C_AT24C02C_ADDR, byteAddr, 0x11);
    APP_Delay(10);
    DRV_AT24C02C_ReadByte(I2C_AT24C02C_ADDR, byteAddr, &dataOut);
    APP_DEBUG("read=0x%x", dataOut);
    
    DRV_AT24C02C_WriteByte(I2C_AT24C02C_ADDR, byteAddr, 0x2);
    APP_Delay(10);
    DRV_AT24C02C_ReadByte(I2C_AT24C02C_ADDR, byteAddr, &dataOut);
    APP_DEBUG("read=0x%x", dataOut);
    
    DRV_AT24C02C_WriteByte(I2C_AT24C02C_ADDR, byteAddr, 0xff);
    APP_Delay(10);
    DRV_AT24C02C_ReadByte(I2C_AT24C02C_ADDR, byteAddr, &dataOut);
    APP_DEBUG("read=0x%x", dataOut);

    DRV_AT24C02C_ReadPage(I2C_AT24C02C_ADDR, byteAddr, databufOut, len);
    I2C_Dump(databufOut, len);
    DRV_AT24C02C_WritePage(I2C_AT24C02C_ADDR, byteAddr, databufIn, 5);
    APP_Delay(10);
    DRV_AT24C02C_ReadPage(I2C_AT24C02C_ADDR, byteAddr, databufOut, len);
    I2C_Dump(databufOut, len);
    
    while(1);
}

代码路径

https://github.com/YaFood/GD32F103/tree/master/TestI2C

猜你喜欢

转载自blog.csdn.net/qq_17854661/article/details/91949760