I2C 总线

更新时间:
2024-03-14
下载文档

I2C 总线

I2C 的英文拼写是“Inter — Integrate Circuit”,即内置集成电路。I2C 是一种由 Philips 公司开发的两线式串行总线,用于连接微控制器及其外围设备。I2C 总线只有两根线分别为:时钟线 SCL(Serial Clock)和数据线 SDA(Serial Data)。总线空闲时,上拉电阻使 SDA 和 SCL 线都保持高电平。I2C 总线上任意器件输出低电平都会使相应总线上的信号变低。

I2C 总线简单而实用,占用的 PCB(印刷电路板)空间很小,芯片引脚数量少,设计成本低。I2C 总线支持多主控(Multi-Mastering)模式,任何能够进行发送和接收的设备都可以成为主设备。主控能够控制数据的传输和时钟频率,在任意时刻只能有一个主控。

I2C 总线原理

I2C 设备上的串行数据线 SDA 接口是双向的,用于向总线上发送或接收数据。串行时钟线 SCL 也是双向的,作为控制总线数据传输的主机通过 SCL 接口发送时钟信号提供给从设备;作为接受主机命令的从设备按照 SCL 上的信号发送或接收 SDA 上的信号。

I2C 总线在传输数据的过程中,主要有三种控制信号:起始信号,结束信号,应答信号。

起始信号:当 SCL 为高电平时,SDA 由高电平转为低电平时,开始传输数据。

结束信号:当 SCL 为高电平时,SDA 由低电平转为高电平时,结束数据传输。

应答信号:接收数据的器件在接收到 8bit 数据后,向发送数据的器件反馈一个应答信号,表示已经收到数据。应答信号为低电平时,规定为有效应答位(ACK 简称应答位),表示接收器已经成功地接收了该字节;应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。对于反馈有效应答位 ACK 的要求是,接收器在第 9 个时钟脉冲之前的低电平期间将 SDA 线拉低,并且确保在该时钟的高电平期间为稳定的低电平。接收器如果是主控器,则在它收到最后一个字节后,发送一个 NACK 信号,以通知被控发送器结束数据发送,并释放 SDA 线,以便主控接收器发送一个停止信号。

开始位和停止位都由 I2C 主机产生。在选择从设备时,如果从设备采用 7 位地址,则主设备在发起传输过程前,需先发送 1 字节的设备地址信息,前 7 位为设备地址,最后 1 位为读写标志。之后,I2C 每次传输的数据也是 1 字节,并从 MSB 开始传输(最高有效位)。每个字节传完后,在 SCL 的第 9 个上升沿到来之前,接收方应该发出一个 ACK 位。在 SCL 的时钟脉冲由 I2C 主控方发出,在第 8 个时钟周期之后,主控方应该释放 SDA。I2C 总线的时序如下图所示。

SylixOS I2C 总线框架分析

SylixOS 的 I2C 体系结构分为 3 个组成部分。

  • I2C 核心驱动程序

I2C 核心提供 I2C 总线驱动和设备驱动的注册、注销方法,I2C 通信方法(即 Algorithm),具体适配器无关的代码以及探测设备、检测设备地址等。I2C 核心驱 动程序可管理多个 I2C 总线适配器(控制器)和多个 I2C 从设备。每个 I2C 从设备驱动都能找到和它相连的 I2C 总线适配器。

  • I2C 总线驱动

I2C 总线驱动主要包括 I2C 适配器结构 lw_i2c_adapter 和 I2C 适配器的 Algorithm 数据结构。

开发者可以通过 I2C 总线驱动的代码,控制 I2C 适配器以主控方式产生开始位、停止位、读写周期,或者以从设备方式被读写、产生 ACK。

  • I2C 设备驱动

I2C 设备驱动是对 I2C 设备端的实现,设备一般挂接在受 CPU 控制的 I2C 适配器上,通过 I2C 适配器与 CPU 交换数据。每一条 I2C 总线对应一个 Adapter。在 内核中,每一个 Adapter 提供了一个描述的结构,也定义了 Adapter 支持的操作。再通过 I2C 核心层将 I2C 设备与 I2C 适配器关联起来。

I2C 总线适配器相关信息位于“libsylixos/SylixOS/system/device/i2c”下,其适配器创建函数原型如下:

#include <SylixOS.h>
INT  API_I2cAdapterCreate (CPCHAR                 pcName, 
                           PLW_I2C_FUNCS          pi2cfunc,
                           ULONG                  ulTimeout,
                           INT                  iRetry)

函数 API_I2cAdapterCreate 原型分析:

  • 此函数成功返回 ERROR_NONE ,失败返回 PX_ERROR
  • 参数 pcName 是 I2C 适配器的名称,即 shell 命令 buss 显示的名称。
  • 参数 pi2cfunc 是 I2C 总线传输函数的指针。
  • 参数 ulTimeout 是操作超时时间。
  • 参数 iRetry 是传输出错时的重试次数。

函数 API_I2cAdapterCreate 使用结构体 PLW_I2C_FUNCS 来向内核提供传输函数集合,其详细描述如下:

#include <SylixOS.h>
typedef struct lw_i2c_funcs {
    INT       (*I2CFUNC_pfuncMasterXfer)(PLW_I2C_ADAPTER     pi2cadapter,
                                         PLW_I2C_MESSAGE     pi2cmsg,
                                         INT                 iNum);
    INT       (*I2CFUNC_pfuncMasterCtl)(PLW_I2C_ADAPTER      pi2cadapter,
                                        INT                  iCmd,
                                        LONG                 lArg);
} LW_I2C_FUNCS;
typedef LW_I2C_FUNCS        *PLW_I2C_FUNCS;
  • I2CFUNC_pfuncMasterXfer :I2C 传输函数,I2C 设备会直接调用此函数实现消息发送。第一个参数 pi2cadapter 为 I2C 总线适配器指针,第二个参数 pi2cmsg 为 I2C 设备需要传输的消息结构体首地址指针,第三个参数 iNum 为需要传输的消息个数,通过以上三个参数即可知道 I2C 设备如何调用此函数实现消息传输。
  • I2CFUNC_pfuncMasterCtl :I2C 适配器控制函数,用来实现与硬件控制器相关的控制。第一个参数 pi2cadapter 为 I2C 总线适配器指针,第二个参数 iCmd 为控制命令,第三个参数 lArgiCmd 相关。

注意:
I2CFUNC_pfuncMasterCtl 函数是按照开发人员的需求实现,通常情况下不实现。

注册到内核的传输函数集合中要用到多种结构体, PLW_ I2C _ADAPTER 总线适配器结构体指针主要包含当前总线适配器节点信息, PLW_ I2C _DEVICE 总线设备结构体指针主要包含当前 I2C 设备的相关信息, PLW_ I2C _MESSAGE 消息请求结构体指针作用是指向需要发送的消息缓冲区,提供以上三种结构体后控制器即可知道如何进行发送,各种结构体的详细描述如下:

首先介绍 I2C 总线适配器结构体,该结构体详细描述如下:

#include <SylixOS.h>
typedef struct lw_i2c_adapter {
    LW_BUS_ADAPTER           I2CADAPTER_pbusadapter;           /*  总线节点              */
    struct lw_i2c_funcs     *I2CADAPTER_pi2cfunc;           /*  总线适配器操作函数      */
    LW_OBJECT_HANDLE         I2CADAPTER_hBusLock;            /*  总线操作锁            */
    ULONG                    I2CADAPTER_ulTimeout;          /*  操作超时时间           */
    INT                      I2CADAPTER_iRetry;             /*  重试次数              */
    LW_LIST_LINE_HEADER      I2CADAPTER_plineDevHeader;     /*  设备链表              */
} LW_I2C_ADAPTER;
typedef LW_I2C_ADAPTER  *PLW_I2C_ADAPTER;
  • I2CADAPTER_pbusadapter :系统总线节点结构体。
  • I2CADAPTER_pi2cfunc :指向总线适配器的操作函数,即 API_I2cAdapterCreate 函数注册到核心层的操作函数集指针。
  • I2CADAPTER_hBusLock :I2C 总线锁,不需要手动处理。
  • I2CADAPTER_ulTimeout :操作超时时间。
  • I2CADAPTER_iRetry :传输出错时的重试次数。
  • I2CADAPTER_plineDevHeader :指向此适配器下挂载的设备链表,不需要手动处理。

I2C 设备结构体的详细描述如下:

#include <SylixOS.h>
typedef struct lw_i2c_device {
    UINT16                     I2CDEV_usAddr;             /*  设备地址                     */
    UINT16                     I2CDEV_usFlag;             /*  标志, 仅支持 10bit 地址选项    */
#define LW_I2C_CLIENT_TEN    0x10                      /*  与 LW_I2C_M_TEN 相同         */
    PLW_I2C_ADAPTER          I2CDEV_pi2cadapter;        /*  挂载的适配器                  */
    LW_LIST_LINE             I2CDEV_lineManage;         /*  设备挂载链                    */
    atomic_t                I2CDEV_atomicUsageCnt;     /*  设备使用计数                  */
    CHAR I2CDEV_cName[LW_CFG_OBJECT_NAME_SIZE];        /*  设备的名称                    */
} LW_I2C_DEVICE;
typedef LW_I2C_DEVICE  *PLW_I2C_DEVICE;
  • I2CDEV_usAddr :设备地址。
  • I2CDEV_usFlag :若从设备的地址为 10bit,则将该标志位值置为 LW_I2C_CLIENT_TEN;否则置为 0。
  • I2CDEV_pi2cadapter :设备挂载的 I2C 总线适配器。
  • I2CDEV_lineManage :设备挂载的链表。
  • I2CDEV_atomicUsageCnt :设备使用的计数。
  • I2CDEV_cName :设备名称。

I2C 消息结构体是 I2C 主机和从机通信的消息格式,该结构体的详细描述如下:

#include <SylixOS.h>
typedef struct lw_i2c_message {
    UINT16                  I2CMSG_usAddr;         /*  器件地址              */
    UINT16                  I2CMSG_usFlag;         /*  传输控制参数           */
    UINT16                  I2CMSG_usLen;          /*  长度(缓冲区大小)       */
    UINT8                  *I2CMSG_pucBuffer;      /*  缓冲区                */
} LW_I2C_MESSAGE;
typedef LW_I2C_MESSAGE *PLW_I2C_MESSAGE;
  • I2CMSG_usAddr :器件地址。
  • I2CMSG_usFlag :传输控制参数,其取值见下表。
  • I2CMSG_usLen :存放消息内容的缓存区大小。
  • I2CMSG_pucBuffer :存放消息内容的缓存区。
传输控制参数的值含义
LW_I2C_M_TEN使用 10bit 设备地址
LW_I2C_M_RD为读操作,否则为写
LW_I2C_M_NOSTART不发送 start 标志
LW_I2C_M_REV_DIR_ADDR读写标志位反转
LW_I2C_M_IGNORE_NAK忽略 ACK NACK
LW_I2C_M_NO_RD_ACK读操作时不发送 ACK

EEPROM 驱动实现

一个具体的 I2C 设备驱动以 lw_i2c_device 结构体的形式进行组织,用于将设备挂接于 I2C 总线,组织好了后,再完成设备本身所属类型的驱动。下面以 EEPROM 为例介绍 I2C 设备驱动的实现。

如下所示,当用户在上层调用 open 函数打开 EEPROM 的设备文件时,会调用到此函数。该函数的主要作用是调用 API_I2cDeviceCreate 函数,在指定的 I2C 适配器上创建一个 I2C 设备。函数 API_I2cDeviceCreate 原型如下:

#include <SylixOS.h>
PLW_I2C_DEVICE  API_I2cDeviceCreate (CPCHAR  pcAdapterName,
                                     CPCHAR  pcDeviceName,
                                     UINT16  usAddr,
                                     UINT16  usFlag)

函数 API_I2cDeviceCreate 原型分析:

  • 此函数成功返回 pi2cdevice (I2C 设备结构体类型),失败返回 LW_NULL
  • 参数 pcAdapterName 是设备挂载的适配器名称。
  • 参数 pcDeviceName 是设备名称。
  • 参数 usAddr 是设备地址。
  • 参数 usFlag 是设备标志。
#include <SylixOS.h>
LONG  eepromOpen (EEPROM  *peeprom, PCHAR  pcName, INT  iFlags, INT  iMode)
{
    if (!peeprom) {
        _ErrorHandle(EINVAL);
        return  (PX_ERROR);
    }
    if (LW_DEV_INC_USE_COUNT(&peeprom->EEP_devhdr) == 1) {
        peeprom->EEP_i2cdev = API_I2cDeviceCreate(EEPROM_I2C_NAME, pcName, EEPROM_ADDR, 0);
        if (peeprom->EEP_i2cdev == LW_NULL) {
            return  (PX_ERROR);
        }
    }
    return  ((LONG)peeprom);
}

如下所示,当用户在上层调用 close 函数关闭 EEPROM 的设备文件时,会调用到此函数。该函数的主要作用是调用 API_I2cDeviceDelete 函数,删除指定的 I2C 设备。函数 API_I2cDeviceDelete 原型如下:

#include <SylixOS.h>
INT  API_I2cDeviceDelete (PLW_I2C_DEVICE   pi2cdevice)

函数 API_I2cDeviceCreate 原型分析:

  • 此函数成功返回 ERROR_NONE ,失败返回 PX_ERROR
  • 参数 pi2cdevice 是指定的 I2C 设备结构体。
#include <SylixOS.h>
INT  eepromClose (EEPROM  *peeprom)
{
    if (!peeprom) {
        _ErrorHandle(EINVAL);
        return  (PX_ERROR);
    }
    if (LW_DEV_DEC_USE_COUNT(&peeprom->EEP_devhdr) == 0) {
        API_I2cDeviceDelete(peeprom->EEP_i2cdev);
    }
    return  (ERROR_NONE);
}

EEPROM 的传输函数的实现,可以分为两个部分。第一部分,将需要发送的数据封装成 I2C 消息结构体类型;第二部分,调用 I2C 的传输函数 API_I2cDeviceTransfer,将数据传输出去。具体实现如下所示。函数 API_I2cDeviceTransfer 原型如下:

#include <SylixOS.h>
INT  API_I2cDeviceTransfer (PLW_I2C_DEVICE     pi2cdevice, 
                            PLW_I2C_MESSAGE    pi2cmsg,
                            INT                iNum)

函数 API_I2cDeviceTransfer 原型分析:

  • 此函数成功返回 iRet (完成传输的消息数量),失败返回 PX_ERROR
  • 参数 pi2cdevice 是指定的 I2C 设备结构体。
  • 参数 pi2cmsg 是传输消息结构体组。
  • 参数 iNum 是传输消息结构体中消息的数量。
#include <SylixOS.h>
INT  eepromByteRead (PLW_I2C_DEVICE  pi2c, UINT8  ucByteAddr, UINT8  *pucData)
{
    INT            iRet;
    if (!pi2c) {
        return  (PX_ERROR);
    }
    LW_I2C_MESSAGE msgs[] = {
        {
            .I2CMSG_usAddr         = pi2c->I2CDEV_usAddr,
            .I2CMSG_usFlag         = 0,
            .I2CMSG_usLen          = 1,
            .I2CMSG_pucBuffer      = &ucByteAddr,
        }, {
            .I2CMSG_usAddr         = pi2c->I2CDEV_usAddr,
            .I2CMSG_usFlag         = LW_I2C_M_RD,
            .I2CMSG_usLen          = 1,
            .I2CMSG_pucBuffer      = pucData,
        }
    };
    iRet = API_I2cDeviceTransfer(pi2c, msgs, 2);
    if (iRet < 0) {
        EEPROM_DBG("I2c msg read error: %d\n", iRet);
        return  (PX_ERROR);
    }
    return  (iRet);
}
文档内容是否对您有所帮助?
有帮助
没帮助