RISCV_I2C使用参考


REVISION HISTORY

Revision No.
Description
Date
1.0
  • Initial release
  • 11/11/2022

    1. 概述

    1.1. IP组

    设备地址和硬件组别的关系如下表所示。

    I2C Group bank addr
    HW I2C group0 1114H
    HW I2C group1 1115H
    HW I2C group2 1116H
    HW I2C group3 1117H
    HW I2C group4 1118H
    HW I2C group5 1119H

    1.2. master device

    所有的I2C都只能作为主设备(master),无法作为从设备(slave)。

    1.3. public file

    FreeRTOS:

    sc/driver/sysdriver/i2c/drv/pub/drv_iic.h

    NoNOS:

    sc/driver/sysdriver/i2c/drv_pub_drv_iic.h

    1.4. 功能说明

    a) 支持DMA/非DMA两种模式,可在chipname.sys中修改dma属性值选择支持模式。

    b) 读写支持,支持S+W+RS+R和S+W+P+RS+R两种通讯时序,使用方法参考Demo使用介绍;对每次支持的字节数,非dma mode不做限制,理论上允许length变量的最大长度为u32即0xFFFFFFFF。dma mode因dma buffer长度限制每次最大允许为4096字节。建议较长字节数的时候打开dma mode,减少cpu loading。

    c) 时序可调,周期内起始结束信号可调,通过修改chipname.sys中的属性调整。

    d) 电平输出方式两种可选:推挽输出、开漏输出,通过chipname.sys修改push-pull属性选择。

    e) 通讯速率:理论上最高速率可达1.5M,速率设定在chipname.sys中设置,可根据实际需要修改,不分档位,修改方法见SYSDESC定义章节。

    f) 支持1toN通讯,只写。

    g) PAD可选,参考PADMUX章节。

    h) 寻址方式:传输过程在struct i2c_msg当中把slave addr给下来,目前driver支持8bit addr,register addr放在data当中,所以原则上register addr支持8bit或16bit或更高,不做限制。


    2. SYSDESC配置

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

    <i2cm0>
        [reg_u32_u16] 0x2222800 0x200;
        [interrupts_u8] INT_IRQ_MIIC_0;
        [pushen_u8] 1;
        [padmux_u8] 7;
        [dma_u8] 0;
        [camclk_u16] CAMCLK_miic0;
        [oencnt_u8] 0;
        [speed_u32] 200000;
        [thd_u16] 0;
        [tsu_u16] 0;
        [start_u16] 0;
        [stop_u16] 0;
        [status_u8] 0;
    

    I2C master驱动中支持配置的属性如下表:

    属性 描述 设定值 备注
    reg_u32_u16 用于指定IIC寄存器bank的地址 不需要修改
    interrupts_u8 用于指定使用的硬件中断号 不需要更改
    camclk_u16 用于指定使用的时钟源 不需要更改
    padmux_u8 用于pad设定(仅不开启CONFIG_PADMUX_SUPPORT时使用) 可根据需要修改
    speed_u32 用于选择IIC Speed,直接填写实际值 如:250000即250K 可根据需要修改
    dma_u8 用于选择是否使用DMA模式 1-开启;0-关闭 可根据需要修改
    thd_u16 用于设定起始信号建立时间 cnt value 可根据需要修改
    tsu_u16 用于设定结束信号保持时间 cnt value 可根据需要修改
    start_u16 用于设定起始信号保持时间 cnt value 可根据需要修改
    stop_u16 用于设定结束信号建立时间 cnt value 可根据需要修改
    pushen_u8 用于打开推挽输出方式 1-推挽输出;0-开漏输出 可根据需要修改
    oencnt_u8 用于优化推挽输出的上升沿波形 1-开启;0-关闭 可根据需要修改
    status_u8 用于选择是否使能IIC master驱动 1-enable;0-disable 可根据需要修改

    几个详细说明:

    thd_u16/ tsu_u16/ start_u16/stop_u16:如下图2-1所示几个红框,就是几个属性调整的位置。如果节点中属性值为0,driver会按照既定计算得出count值,如果有设定则按照设定值,设定的理论最大值为0xFFFF,即65535,但当设置过大时,如果是RIU mode的轮询方式会导致轮询超时,导致失败返回。属性值与时间对应关系如下:

    首先通过speed_u32属性值确定时钟源:

    speed <= 200KHz, src clk = 12MHz
    
    speed <= 700KHz, src clk = 54MHz
    
    else src clk = 72MHz
    

    计算公式:

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

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

    属性值 = 设定时间 * 时钟源 = 4 * 10-6 * 12 * 106 = 48

    图2-1: thd_u16/ tsu_u16/ start_u16/ stop_u16属性调整位置

    pushen_u8:更改引脚的输出方式,设为1时是推挽输出,0为开漏输出。

    oencnt_u8:当开启推挽输出时,可以设定这个属性来调整clk的上升沿波形,具体如下图,图2.2为该属性值为1的波形,图2.3为属性值为0的波形,如果i2c-push-pull没有打开的话,i2c-oen-cnt的设定都无效。

    图2-2: i2c-oen-cnt = 1

    图2-3: i2c-oen-cnt = 0


    3. PADMUX配置

    3.1. 使用与修改

    [重要] 由于各外设驱动各自管理各自使用引脚的复用设置会引起某些引脚的复用关系冲突,故现已将引脚复用关系移至chipname.sys,padmux配置只需要在对应的chipname.sys的padmux属性添加如下内容中设定:

    1.  PAD_I2C0_SCL     PINMUX_FOR_I2C0_MODE_5       MDRV_PUSE_I2C0_SCL,
    2.  PAD_I2C0_SDA     PINMUX_FOR_I2C0_MODE_5       MDRV_PUSE_I2C0_SDA,
    

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

    第二列为模式定义,在 sc/drivers/sysdriver/gpio/hal/{chipname}/src/mhal_pinmux.cST_PadMuxInfo 数组里,罗列了所有引脚的复用关系,查询该引脚支持哪些复用功能可以查询该数组;

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


    3.2. 各I2C硬件组padmux罗列

    I2C BUS padmod register addr PAD PIN_NAME
    I2C-0 1 bank 103cH offset 6fH bit[2:0] PAD_I2C0_SDA I2C0_SDA0
    PAD_I2C0_SCL I2C0_SCL0
    2 PAD_KEY5 I2C0_SDA0
    PAD_KEY6 I2C0_SCL0
    3 PAD_FUART_RTS I2C0_SDA0
    PAD_FUART_CTS I2C0_SCL0
    4 PAD_I2C0_SDA I2C0_SDA0
    PAD_I2C0_SCL I2C0_SCL0
    PAD_I2C1_SDA I2C0_SDA1
    PAD_I2C1_SCL I2C0_SCL1
    PAD_I2C2_SDA I2C0_SDA2
    PAD_I2C2_SCL I2C0_SCL2
    PAD_I2C3_SDA I2C0_SDA3
    PAD_I2C3_SCL I2C0_SCL3
    I2C-1 1 bank 103cH offset 53H bit[2:0] PAD_I2C1_SDA I2C1_SDA0
    PAD_I2C1_SCL I2C1_SCL0
    2 PAD_SD0_D3 I2C1_SDA0
    PAD_SD0_D2 I2C1_SCL0
    3 PAD_SAR_ADC_2 I2C1_SDA0
    PAD_SAR_ADC_3 I2C1_SCL0
    4 PAD_I2C0_SDA I2C1_SDA0
    PAD_I2C0_SCL I2C1_SCL0
    PAD_I2C1_SDA I2C1_SDA1
    PAD_I2C1_SCL I2C1_SCL1
    5 PAD_I2C0_SDA I2C1_SDA0
    PAD_I2C0_SCL I2C1_SCL0
    PAD_I2C1_SDA I2C1_SDA1
    PAD_I2C1_SCL I2C1_SCL1
    PAD_I2C2_SDA I2C1_SDA2
    PAD_I2C2_SCL I2C1_SCL2
    PAD_I2C3_SDA I2C1_SDA3
    PAD_I2C3_SCL I2C1_SCL3
    I2C-2 1 bank 103cH offset 6fH bit[9:8] PAD_I2C2_SDA I2C2_SDA
    PAD_I2C2_SCL I2C2_SCL
    2 PAD_RGMII0_RXD0 I2C2_SDA
    PAD_RGMII0_RXD1 I2C2_SCL
    3 PAD_I2C3_SDA I2C2_SDA
    PAD_I2C3_SCL I2C2_SCL
    I2C-3 1 bank 103cH offset 73H bit[1:0] PAD_I2C3_SDA I2C3_SDA
    PAD_I2C3_SCL I2C3_SCL
    2 PAD_PWM_OUT8 I2C3_SDA
    PAD_PWM_OUT9 I2C3_SCL
    3 PAD_OUTP_TX1_CH[4] I2C3_SDA
    PAD_OUTN_TX1_CH[4] I2C3_SCL
    I2C-4 1 bank 103cH offset 73H bit[6:4] PAD_KEY5 I2C4_SDA
    PAD_KEY6 I2C4_SCL
    2 PAD_PWM_OUT10 I2C4_SDA
    PAD_PWM_OUT11 I2C4_SCL
    3 PAD_OUTN_TX1_CH[1] I2C4_SDA
    PAD_OUTP_TX1_CH[2] I2C4_SCL
    4 PAD_RGMII0_MDIO I2C4_SDA
    PAD_RGMII0_MDC I2C4_SCL
    I2C-5 1 bank 103cH offset 103c offset 73H bit[10:8] PAD_I2C5_SDA I2C5_SDA
    PAD_I2C5_SCL I2C5_SCL
    2 PAD_SAR_ADC_4 I2C5_SDA
    PAD_SAR_ADC_5 I2C5_SCL
    3 PAD_RGMII0_TXD2 I2C5_SDA
    PAD_RGMII0_TXD3 I2C5_SCL
    4 PAD_PM_I2CM_SCL I2C5_SDA
    PAD_PM_I2CM_SDA I2C5_SCL

    3.3. 1toN

    功能说明:

    硬件预设某一路I2C的padmod同时接入到多组PAD上面,在每一路PAD上都可以接一个I2C slave device, 注意:这些slave device的地址都必须要相同,同时该功能只能做写操作,不能做读操作。

    操作方式:

    参考3.2节当中,如I2C-0 padmod4,共接到四路PAD上面,对应四组I2C PIN_NAME为I2C0_SCL0 – I2C0_SCL3,I2C0_SDA0 – I2C0_SDA3,I2C0_SCL0和I2C0_SDA0为一组,可接入一个I2C从设备,因此一共可以接入四组。

    1toN的padmux配置同样是只需要在对应的chipname.sys的padmux属性添加内容中,依照统一格式设定把对应的所有pad都写出来。请注意,一定要如下代码段把所有的PAD都写出来,否则padmux driver那边匹配不到所有的PAD的话,padmod设定就会失败。

    3.  PAD_I2C0_SDA            PINMUX_FOR_I2C0_MODE_4          MDRV_PUSE_I2C0_SDA,
    4.  PAD_I2C0_SCL            PINMUX_FOR_I2C0_MODE_4          MDRV_PUSE_I2C0_SCL,
    5.  PAD_I2C1_SDA            PINMUX_FOR_I2C0_MODE_4          MDRV_PUSE_I2C0_SDA1,
    6.  PAD_I2C1_SCL            PINMUX_FOR_I2C0_MODE_4          MDRV_PUSE_I2C0_SCL1,
    7.  PAD_I2C2_SDA            PINMUX_FOR_I2C0_MODE_4          MDRV_PUSE_I2C0_SDA2,
    8.  PAD_I2C2_SCL            PINMUX_FOR_I2C0_MODE_4          MDRV_PUSE_I2C0_SCL2,
    9.  PAD_I2C3_SDA            PINMUX_FOR_I2C0_MODE_4          MDRV_PUSE_I2C0_SDA3,
    10. PAD_I2C3_SCL            PINMUX_FOR_I2C0_MODE_4          MDRV_PUSE_I2C0_SCL3,
    

    之后通过正常的写操作调用完成写操作。


    4. Demo使用介绍

    4.1. Demo application

    图4-1 Demo application介绍

    • 目录1,inc,存放的是模块(module)内部的头文件,不被其他模块使用的头文件

    • 目录2,pub,存放的是会被其他模块使用的头文件

    • 目录3,src,则是模块的源代码文件

    • 函数4,rtos_application_initcall(),用来指定application模块的入口函数,第一个参数是函数名,第二个参数用于设置模块入口函数的执行顺序,一般都配置为0,当存在多个application模块时,可以通过配置参数0,1,2,3来决定application函数的执行顺序

    • Bench模块的入口函数RtosAppMainEntry()只是单纯的调用coremark_main()函数,执行coremark操作


    4.2. FreeRTOS Demo的使用

    4.2.1 public file

    sc/driver/sysdriver/i2c/drv/pub/drv_iic.h

    4.2.2 结构体说明

    typedef struct i2c_msg {
        u16 addr;   /*slave address*/
        u16 flags;
    #define CAM_I2C_RD                   0x0001
        u16 len;        /*msg length*/
        u8 *buf;        /*pointer to msg data*/
    }tI2cMsg;
    
    成员 说明
    addr 从设备地址,注意是未做左移1bit的设备地址。
    flags 通讯格式,设定读/写标志。
    len 本次通讯要传输的字节数,注意一个i2c_msg结构体代表一次读或写通讯。
    buf 数据指针,用于存放要发送的数据,或者都回来的数据。

    4.2.3 API说明

    基本的API调用流程为:

    1. drv_i2c_init(); //这一步也可以不做,因为在步骤2的调用中会判断是否init,没有则先自动调用init

    2. drv_i2c_master_xfer(); //返回值为成功完成的tI2cMsg个数

    extern s32 drv_i2c_deinit(u8 para_group);

    • 功能

      i2c注销,注销之后,若要重新使用,需再进行drv_i2c_init()调用

    • 参数

      参数 说明
      para_group i2c设备
      0: i2c-0
      1: i2c-1
      2: i2c-2
      3: i2c-3
      4: i2c-4
      5: i2c-5
    • 返回值

      说明
      0 -

    extern s32 drv_i2c_init(u8 para_group);

    • 功能

      i2c注册初始化,未进行初始化的i2c无法正常通讯

    • 参数

      参数 说明
      para_group i2c设备
      0: i2c-0
      1: i2c-1
      2: i2c-2
      3: i2c-3
      4: i2c-4
      5: i2c-5
    • 返回值

      说明
      0 成功
      1 失败

    extern s32 drv_i2c_master_xfer(u8 para_group, struct i2c_msg *para_msg, s32 para_num);

    • 功能

      进行i2c通讯

    • 参数

      参数 说明
      para_group i2c设备
      0: i2c-0
      1: i2c-1
      2: i2c-2
      3: i2c-3
      4: i2c-4
      5: i2c-5
      para_msg 通讯内容结构体指针,指向本地调用传输所要传输的格式和内容的首地址
      para_num 通讯内容结构体个数
    • 返回值

      返回正确完成的结构体个数,若全部正确完成,则返回值与@para_num相等

    extern s32 drv_i2c_set_speed(u8 para_group, u32 speed);

    • 功能

      设定i2c通讯速率

    • 参数

      参数 说明
      para_group i2c设备
      0: i2c-0
      1: i2c-1
      2: i2c-2
      3: i2c-3
      4: i2c-4
      5: i2c-5
      speed 要设定的目标速率
    • 返回值

      说明
      0 设定成功
      -1 设定失败

    4.2.4 demo code

    #define (__VER_I2C__)//决定是否开启I2C命令行
    #if defined(__VER_I2C__)
    #include "vm_types.ht"
    #include "drv_bus_i2c.h"
    #include "sys_sys_isw_cli.h"
    #include "sys_sys_isw_uart.h"
    #include "cam_drv_i2c.h"
    #include "drv_iic.h"
    
    static int i2c_ut_test(CLI_t * pCli, char * p)
    {
        u8 argc;
        u32 port;
        u32 slave;
        char *cmd;
        tI2cMsg msg;
        u32 addr;
        u32 value;
        u8 data[4];
        u8 r_flag = 0;
    
        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)
                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, "A8D8") == 0)
        {
            msg.addr = (u16)slave;
            if (r_flag)
            {
                if (CliTokenPopNum(cli, &addr, 0) != eCLI_PARSE_OK)
                {
                    return eCLI_PARSE_INPUT_ERROR;
                }
    
                msg.flags = 0;
                data[0] = addr;
                msg.buf = data;
                msg.len = 1;
                drv_i2c_master_xfer(port, &msg, 1);
    
                msg.flags = CAM_I2C_RD;
                msg.buf = data;
                msg.len = 1;
                drv_i2c_master_xfer(port, &msg, 1);
                cliPrintf(" %02x : %02x\r\n", (u8)addr, 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;
                }
    
                data[0] = (u8)(addr & 0xff);
                data[1] = (u8)(value & 0xff);
    
                msg.flags = 0;
                msg.buf = data;
                msg.len = 2;
    
                drv_i2c_master_xfer(port, &msg, 1);
            }
        }
        else if (strcmp(cmd, "A8D16") == 0)
        {
            msg.addr = (u16)slave;
            if (r_flag)
            {
                if (CliTokenPopNum(cli, &addr, 0) != eCLI_PARSE_OK)
                {
                    return eCLI_PARSE_INPUT_ERROR;
                }
    
                msg.flags = 0;
                data[0] = addr;
                msg.buf = data;
                msg.len = 1;
                drv_i2c_master_xfer(port, &msg, 1);
    
                msg.flags = CAM_I2C_RD;
                msg.buf = data;
                msg.len = 2;
                drv_i2c_master_xfer(port, &msg, 1);
                value = (data[0] << 8) + data[1];
                cliPrintf(" %02x : %04x\r\n", (u8)addr, (u16)value);
            }
            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;
                }
    
                data[0] = (u8)(addr & 0xff);
                data[1] = (u8)((value >> 8) & 0xff);
                data[2] = (u8)((value ) & 0xff);
    
                msg.flags = 0;
                msg.buf = data;
                msg.len = 3;
    
                drv_i2c_master_xfer(port, &msg, 1);
            }
    
        }
        else 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;
                }
    
                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);
    
                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;
                }
    
                data[0] = (u8)((addr >> 8) & 0xff);
                data[1] = (u8)(addr & 0xff);
                data[2] = (u8)(value & 0xff);
    
                msg.flags = 0;
                msg.buf = data;
                msg.len = 3;
    
                drv_i2c_master_xfer(port, &msg, 1);
            }
    
        }
        else if (strcmp(cmd, "A16D16") == 0)
        {
            msg.addr = (u16)slave;
            if (r_flag)
            {
                if (CliTokenPopNum(cli, &addr, 0) != eCLI_PARSE_OK)
                {
                    return eCLI_PARSE_INPUT_ERROR;
                }
    
                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);
    
                msg.flags = CAM_I2C_RD;
                msg.buf = data;
                msg.len = 2;
                drv_i2c_master_xfer(port, &msg, 1);
                value = (data[0] << 8) + data[1];
                cliPrintf(" %04x : %04x\r\n", (u16)addr, (u16)value);
            }
            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;
                }
    
                data[0] = (u8)((addr >> 8) & 0xff);
                data[1] = (u8)((addr) & 0xff);
                data[2] = (u8)((value >> 8) & 0xff);
                data[3] = (u8)((value ) & 0xff
    );
    
                msg.flags = 0;
                msg.buf = data;
                msg.len = 4;
    
                drv_i2c_master_xfer(port, &msg, 1);
            }
    
        }
    
        return eCLI_PARSE_OK;
    }
    
    SS_RTOS_CLI_CMD(i2c, " i2c ut test",
                            " i2c <r/w> <port> <slave> <format> <data...>\r\n"
                            " <format> : A8D8 A8D16 A16D8 A16D16\r\n"
                            " e.g.\r\n"
                            "    i2c w 0 0x50 A16D8 0x0000 0x55\r\n"
                            "    i2c r 0 0x50 A16D8 0x0000", i2c_ut_test);
    #endif