CM4_I2C使用参考


REVISION HISTORY

Revision No.
Description
Date
1.0 Initial release 05/29/2024

1. 概述

1.1 I2C

I2C,全称Inter-Integrated Circuit,集成电路总线,使用多主从架构,是一种串行、同步、半双工的通信总线,包含串行时钟线(SCL)与串行数据线(SDA),都是双向IO线。通信过程中,时钟信号由主设备全程提供;数据信号取决于主设备是做读操作还是写操作,当进行写操作时,数据信号由主设备提供,当进行读操作时,数据信号由从设备提供。

1.2 MIIC

MIIC,即Master IIC device,是SigmaStar提供的专门作为I2C通信主设备的IP,可实现与各种外接的I2C slave device通讯,满足绝大部分I2C通讯协议设备的需求。

设备节点和硬件组别的关系如下表所示:

MIIC Group bank addr
HW MIIC group0 0x003E

2. 关键字

sysdesc:

RTOS用于描述外设硬件属性的文件,外设节点中包含的属性值可用于外设的配置,类似Linux的设备树文件

padmux:

引脚复用,用于将模块功能引脚连接到具体的外部引脚,打通信号连接。

open-drain:

开漏输出,也称为“开漏极输出”,是指输出端被连接到一个晶体管的漏极上,并且输出信号只能是低电平(0V)或无连接状态。当输出为低电平时,晶体管处于导通状态,输出端可以提供一定的电流;当输出为无连接状态时,晶体管处于截止状态,输出端的电平由外部电路决定。

push-pull:

推挽输出,也称为“全双工输出”,是指输出端被连接到两个互补的晶体管上,并且输出信号可以是高电平或低电平。当输出为高电平时,一个晶体管处于导通状态,另一个晶体管处于截止状态,输出端提供高电平电压;当输出为低电平时,两个晶体管均处于导通状态,输出端提供低电平电压。有较强的驱动能力。


3. 功能描述

功能 说明 备注
FIFO transfer 每个传输信号都需要由CPU参与,通过写入不同的MIIC寄存器发送不同的信号。 采用polling方式,CPU loading较高;在每次传输数据较少时可以使用此模式。
DMA transfer 整个通信过程都由MIIC自行动作,仅需要提前填入传输内容,再触发即可;HW MIIC会根据已经填入的时序设定来进行通讯。 采用中断方式,CPU loading会比FIFO mode小;每次传输量都比较大时建议使用此模式;当传输期间出现错误时HW会立即停止当前通讯。
timing 调整 开放了几个I2C通讯时序可供调整,当具体某部分时序不满足从设备要求时,可在sysdesc文件中的i2c节点调整 可调时序: t-SU-STA, t-HD-STA, t-SU-STO, t-HD-STO
rate 支持通讯速率区间可调,不分档位,可根据实际需要在sysdesc节点中调整 支持50kHz - 1500KHz,更大速率需求不做保证
输出方式调整 支持open-drain与push-pull两种输出方式,可在sysdesc节点中调整 MIIC不支持任意调整波形的上升/下降沿时间,仅可以通过调整输出方式来改变,且时间无法确认。push-pull上升沿时间会较快
padmux 可配置不同的padmux将I2C功能复用到不同的引脚面
1toN 部分padmux会同时连接几组PAD引脚,通讯时信号会同时提供到这几组PAD 该方式有如下限制: 所有的从设备必须类型一样且地址一样,而且MIIC只能写数据不能读数据。一般用于同时接入几个相同从设备,需要同步设定的情况。
外部上拉 I2C总线上必须要接外部上拉

4. 总线与通信协议

4.1 总线

上面提到I2C为多主从架构,顾名思义一路总线上面可以有多个主设备与多个从设备,他们之间通过从设备地址与应答信号来建立彼此联系。

SigmaStar的I2C,每一路总线上仅提供了一个MIIC(master iic)主设备,从设备个数无要求,根据使用需求自行设定。

同时遵从I2C标准协议,在总线的SDA与SCL上必须要接入外部上拉,这是因为I2C通讯需要输出高电平的能力,而当器件输出为开漏输出时,是无法输出高电平的,这时就需要外部上拉的帮助,借此实现“线与”功能,决定最终电平。

如下为I2C总线示例图:


图4-1 I2C总线示例图

4.2 通信协议

一个完整的I2C通信,应包含:起始信号、从设备地址与读写位、应答信号、任意字节长度的数据以及停止信号。

起始信号: 一次完整I2C通信的第一个信号,由主设备提供。示意本次通讯开始。电平信号为 - 当SCL为高电平,SDA从高电平拉为低电平

从设备地址: 通信从设备的“身份证”,与读写位一起共享address frame。占据高7bit或高10bit。通过主设备发出来的地址来确认自己是否被选中。设备地址可以相同,但如果同时接在同一总线上,很容易导致通讯失败或数据错误。

读写位: 用于指示当次传输主设备要进行写操作还是读操作,b0为写操作,b1为读操作。与从设备地址一起共享address frame。占据最低位bit0。

例: 如果从设备地址为7bit的0x50,进行读操作,那么

address frame = (0x50 << 1) | 0x01 = 0xA1

应答信号: 数据接收方反馈给发送方的信号,低电平表示接收成功,高电平表示接收失败。接收方可以是主设备也可以是从设备。当主设备在读取数据时,发送完从设备地址之后,就会变成数据的接收方。但是clock信号始终都是由主设备提供的。

数据: 传输的数据,写操作或者读操作。以字节为单位。另外,一般在I2C通讯写操作时,从设备地址之后会跟随所要读写的从设备寄存器地址,可能是8bit长度,可能是16bit长度,他们也属于传输过程中的数据。

停止信号: 一次完整通讯的最终信号,由主设备提供。示意本次通讯到此结束。电平信号为 - 当SCL为高电平,SDA从低电平拉为高电平。

如下为I2C通信协议格式:


图4-2 I2C通信协议格式

4.3 时序

在数据传输的信号行进中,进一步将每个时钟信号和数据位时间长度细分,形成了更细致的时序,也进一步保证了同步通讯的数据准确性。

如下为I2C时序:


图4-3 I2C时序

在通信时序当中,SCL低电平允许数据信号跳变,在SDA高电平时进行采样,SCL高电平时,SDA至少要维持t-SU-DAT时间段,以供对方采样电平。

时序
说明
t-SU-STA 起始信号建立时间
t-HD-STA 起始信号保持时间
t-SU-STO 结束信号建立时间
t-HD-STO 结束信号保持时间
t-LOW 时钟低电平时间
t-HIGH 时钟高电平时间
t-SU-DAT 数据信号保持时间

5. RTOS用法介绍

I2C通讯的时候,需要保证基本的如下步骤:

  • 硬件设备连接,外部上拉电阻确认;
  • CONFIG支持MIIC驱动;
  • SYSDESC文件配置需求属性;
  • PADMUX设定;
  • API调用,执行通讯。

5.1 DRIVER PATH

sc/driver/sysdriver/i2c/os/iic_os.h
sc/driver/sysdriver/i2c/drv/pub/drv_iic.h
sc/driver/sysdriver/i2c/drv/src/drv_iic.c
sc/driver/sysdriver/i2c/drv/src/drv_iic_test.c
sc/driver/sysdriver/i2c/hal/chipname/src/hal_iic.c
sc/driver/sysdriver/i2c/hal/chipname/inc/hal_iic.h
sc/driver/sysdriver/i2c/hal/chipname/inc/hal_iic_reg.h
sc/driver/sysdriver/i2c/hal/chipname/inc/hal_iic_cfg.h

5.2 CONFIG配置

config文件位于mak/options_iford_cm4_xxx.mak

使能i2c驱动需要将CONFIG_I2C_SUPPORT = TRUE

# Feature_Name = [DRV] I2C driver support
# Description = I2C driver support
# Option_Selection = TRUE, FALSE
CONFIG_I2C_SUPPORT = TRUE

5.3 SYSDESC配置

chipname.sys文件位于sc/driver/sysdriver/sysdesc/hal/chipname/pub

<pm_i2c0>
  [reg_u32_u16] 0x40007C00 0x200;
  [interrupts_u8] INT_IRQ_MIIC0_DMA;
  [dma_u8] 0;
  [output_mode_u8] 2;
  [speed_u32] 200000;
  [t_hd_sto_u16] 0;
  [t_su_sta_u16] 0;
  [t_hd_sta_u16] 0;
  [t_su_sto_u16] 0;
  [status_u8] 1;

Master IIC驱动中支持配置的属性如下表:

属性 描述 设定值 备注
reg_u32_u16 指定bank的地址 不需要修改
interrupts_u8 指定硬件中断号 不需要更改
dma_u8 选择是否使能DMA模式 1-开启;0-关闭 可根据需要修改
output_mode_u8 选择输出模式 1:开漏输出;
2:开漏推1T输出;
3:开漏推1T强上拉;
4:推挽输出
可根据需要修改,
1->4上升沿时间依次缩短
speed_u32 设定通讯频率,单位HZ 范围50K~1500K HZ 可根据需要修改
t_hd_sto_u16 设定结束信号保持时间 count value 可根据需要修改
t_su_sta_u16 设定起始信号保持时间 count value 可根据需要修改
t_hd_sta_u16 设定起始信号建立时间 count value 可根据需要修改
t_su_sto_u16 设定结束信号建立时间 count value 可根据需要修改
camclk_u16 指定时钟源 不需要修改
status_u8 选择是否使能驱动 1-enable;0-disable 可根据需要修改

详细说明:

下图5-1为miic对详细时序的分解,图5-2为可调整的几个时序的位置。

图5-1 miic通信时序

图5-2 miic起始信号与结束信号时序调整

sysdesc中开放的几个时序调整,对应波形位置为:

时序
波形位置
t-su-sta reg_start_setup_cnt
t-hd-sta reg_start_cnt
t-su-sto reg_stop_cnt
t-hd-sto reg_stop_hold_cnt

如果节点中属性值为0,driver会按照既定计算得出count值,如果有设定则按照设定值,设定的理论最大值为0xFFFF,即65535,但当设置过大时,如果是RIU mode的轮询方式会导致轮询超时,导致失败返回。属性值与时间对应关系如下:

首先确定时钟源,当前MIIC根据speed选择时钟源:

50K <= speed <= 200K, 时钟源为12MHz;

200k < speed <= 700K, 时钟源为54MHz;

700K < speed <= 1500K, 时钟源为72MHz;

需要注意的是I2C3的时钟源只有24MHz.

计算公式:

设定时间 = (属性值)/(时钟源)

举例,设定时间需要4us,时钟源72MHz:

属性值 = 设定时间 * 时钟源 = (4 * 10^-6) * (72 * 10^6) = 288

5.4 PADMUX设定

查看位于mak/options_iford_cm4_xxx.makconfig文件各项驱动的支持情况

方法一:CONFIG_PADMUX_SUPPORT = TRUE (目前使用方法一)

该方法通过使能PADMUX驱动,在chipname-padmux.c文件配置引脚复用功能,该文件位于sc/driver/sysdriver/padmux/hal/chipname/src,只需要在对应的schematic属性添加如下内容中设定:

pad_info_t schematic[] =
{
    {PAD_PM_I2C_SCL,    PINMUX_FOR_PM_I2CM0_MODE_1,    MDRV_PUSE_I2C0_SCL},
    {PAD_PM_I2C_SDA,    PINMUX_FOR_PM_I2CM0_MODE_1,    MDRV_PUSE_I2C0_SDA},
};

第一列为引脚索引号,可以在sc/drivers/sysdriver/gpio/hal/chipname/pub/gpio.h中查询;

第二列为模式定义,可以在sc/drivers/sysdriver/gpio/hal/chipname/pub/padmux.h中查询;

第三列为引脚及搭配模式的索引名称,可以在sc/drivers/sysdriver/padmux/drv/pub/drv_puse.h中查询;

方法二:CONFIG_PADMUX_SUPPORT = FALSE && CONFIG_GPIO_SUPPORT = TRUE

该方法通过SYSDESC在chipname.sys文件配置引脚复用功能,该文件位于sc/driver/sysdriver/sysdesc/hal/chipname/pub,可在对应的I2C节点属性padmux_u8配置padmux.h定义的模式:

<pm_i2c0>
  [reg_u32_u16] 0x40007C00 0x200;
  [interrupts_u8] INT_IRQ_MIIC;
  [dma_u8] 1;
  [output_mode_u8] 2;
  [speed_u32] 200000;
  [t_hd_sto_u16] 0;
  [t_su_sta_u16] 0;
  [t_hd_sta_u16] 0;
  [t_su_sto_u16] 0;
  [padmux_u8] PINMUX_FOR_PM_I2CM0_MODE_1;
  [status_u8] 1;

方法三:CONFIG_PADMUX_SUPPORT = FAlSE && CONFIG_GPIO_SUPPORT = TRUE

该方法通过SYSDESC在chipname.sys文件配置引脚复用功能,该文件位于sc/driver/sysdriver/sysdesc/hal/chipname/pub,可在对应的I2C节点属性padmux_u8配置引脚模式,例如使用i2c mode 1则赋值padmux_u8为1:

<pm_i2c0>
  [reg_u32_u16] 0x1F222800 0x200;
  [interrupts_u8] INT_IRQ_MIIC;
  [dma_u8] 1;
  [output_mode_u8] 2;
  [speed_u32] 200000;
  [t_hd_sto_u16] 0;
  [t_su_sta_u16] 0;
  [t_hd_sta_u16] 0;
  [t_su_sto_u16] 0;
  [padmux_u8] 1;
  [status_u8] 1;

各I2C硬件组padmux罗列

I2C BUS register addr padmod PAD PIN_NAME
0 bank 3fH offset 50H bit[2:0] 1 PAD_PM_I2C_SDA I2C3_SDA
PAD_PM_I2C_CLK I2C3_SCL
2 PAD_PM_GPIO12 I2C3_SDA
PAD_PM_GPIO11 I2C3_SCL
3 PAD_PM_GPIO3 I2C3_SDA
PAD_PM_GPIO2 I2C3_SCL
4 PAD_PM_PWM1 I2C3_SDA
PAD_PM_PWM0 I2C3_SCL
5 PAD_PM_GPIO8 I2C3_SCL
PAD_PM_GPIO7 I2C3_SCL

5.5 API说明

头文件位于sc/driver/sysdriver/i2c/drv/pub/drv_iic.h

typedef struct i2c_msg {
    u16 addr;   /*slave address*/
    u16 flags;
#define CAM_I2C_RD                   0x0001  // support
#define CAM_I2C_STOP_BEFORE_RESTART  0x0002  // support
#define CAM_I2C_TEN                  0x0010
#define CAM_I2C_DMA_SAFE             0x0200
#define CAM_I2C_RECV_LEN             0x0400
#define CAM_I2C_NO_RD_ACK            0x0800
#define CAM_I2C_IGNORE_NAK           0x1000
#define CAM_I2C_REV_DIR_ADDR         0x2000
#define CAM_I2C_NOSTART              0x4000  // support
#define CAM_I2C_STOP                 0x8000
    u16 len;        /*msg length*/
    u8 *buf;        /*pointer to msg data*/
}tI2cMsg;
s32 drv_i2c_master_xfer(u8 para_group, struct i2c_msg *para_msg, s32 para_num)
  • 参数

    参数名称 描述
    para_group i2c bus
    para_msg struct i2c_msg 用于描述单次通讯的信息
    para_num i2c_msg的数量
  • 返回值

    结果 描述
    成功 返回已通讯成功的i2c_msg数量
    失败 返回0
s32 drv_i2c_set_speed(u8 para_group, u32 speed)
  • 参数

    参数名称 描述
    para_group i2c bus
    speed 通讯频率,单位HZ
  • 返回值

    结果 描述
    成功 返回0
    失败 返回-1

5.6 DEMO

demo源码位于sc/driver/sysdriver/i2c/drv/src/drv_iic_test.c

static int i2c_ut_test(CLI_t * cli, char * p)
{
    u8 i;
    u8 argc;
    u32 port;
    u32 slave;
    char *cmd;
    tI2cMsg msg;
    u32 addr;
    u32 value;
    u8 data[32];
    u8 e_data[32];
    int ret   = 0;
    u8 r_flag = 0;
    u32 speed = 0;

    argc = CliTokenCount(cli);
    if (argc < 2)
        return eCLI_PARSE_INPUT_ERROR;

    cmd = CliTokenPop(cli);
    if (strcmp(cmd, "r") == 0)
    {
        argc = CliTokenCount(cli);
        if (argc != 4)
            return eCLI_PARSE_INPUT_ERROR;
        r_flag = 1;

    }
    else if (strcmp(cmd, "w") == 0)
    {
        argc = CliTokenCount(cli);
        if (argc < 5 || argc > 6)
            return eCLI_PARSE_INPUT_ERROR;
    }
    else
    {
        return eCLI_PARSE_INPUT_ERROR;
    }

    if (CliTokenPopNum(cli, &port, 0) != eCLI_PARSE_OK)
    {
        return eCLI_PARSE_INPUT_ERROR;
    }

    if (CliTokenPopNum(cli, &slave, 0) != eCLI_PARSE_OK)
    {
        return eCLI_PARSE_INPUT_ERROR;
    }

    cmd = CliTokenPop(cli);
    if (strcmp(cmd, "A16D8") == 0)
    {
        msg.addr = (u16)slave;
        if (r_flag)
        {
            if (CliTokenPopNum(cli, &addr, 0) != eCLI_PARSE_OK)
            {
                return eCLI_PARSE_INPUT_ERROR;
            }

            //write
            msg.flags = 0;
            data[0] = (u8)((addr >> 8) & 0xff);
            data[1] = (u8)(addr & 0xff);
            msg.buf = data;
            msg.len = 2;
            drv_i2c_master_xfer(port, &msg, 1);

            //read
            msg.flags = CAM_I2C_RD;
            msg.buf = data;
            msg.len = 1;
            drv_i2c_master_xfer(port, &msg, 1);
            cliPrintf(" %04x : %02x\r\n", (u16)addr, (u8)data[0]);
        }
        else
        {
            if (CliTokenPopNum(cli, &addr, 0) != eCLI_PARSE_OK)
            {
                return eCLI_PARSE_INPUT_ERROR;
            }

            if (CliTokenPopNum(cli, &value, 0) != eCLI_PARSE_OK)
            {
                return eCLI_PARSE_INPUT_ERROR;
            }

            CliTokenPopNum(cli, &speed, 0);

            data[0] = (u8)((addr >> 8) & 0xff);
            data[1] = (u8)(addr & 0xff);
            data[2] = (u8)(value & 0xff);

            //write
            msg.flags = 0;
            msg.buf = data;
            msg.len = 3;

            //set i2c speed
            if (speed)
                drv_i2c_set_speed(port, speed);

            //transfer
            drv_i2c_master_xfer(port, &msg, 1);
        }

    }

    return eCLI_PARSE_OK;
}

6. DEBUG方法

当出现通讯异常时,可以参考如下方面进行问题调试,提供了几种较为常见的排查方向,另调试过程建议抓取波形方便分析。

排查方向
常见问题
备注
外部上拉 1. 提示通讯超时错误信息;
2. 抓取波形无变化,SCL始终为低电平;
3. 波形读写的数据都为0x00
padmux 1. 提示无ACK信号;
2. 波形没有变化,电平一直维持
参考PADMUX章节,或padmux模块说明
通信速率 通讯失败,无ACK信号 可查找从设备手册,确认正常工作的通信速率范围
时钟源 1. 提示通讯超时错误信息;
2. 无波形
参考CLKGEN模块说明
时序 通讯无ACK信号 若从设备会对各部分详细时序有特定要求,可从设备手册查找确认
从设备工作状态 通讯失败,无ACK 当从设备没有处在正常的工作状态的时候,无法对主设备发起的信号进行响应