libftdi1学习笔记 7 - MPSSE I2C

目录

1. 初始化

2. 原理

3. i2cStart

4. i2cStop

5. i2cRecvByte

6. i2cSendByte

7. i2cRead

8. i2cWrite

9. 验证

9.1 初始化i2c

9.2 初始化gpio

9.3 写10个字节到EEPROM

9.4 读回10字节数据

9.5 运行结果


I2C(主)采用2个或3个GPIO模拟的方式实现,如果sdao等于sdai相等,表示SDA是双向的,否则一个GPIO作为SDA输出,一个GPIO作为SDA输入。 如果电平不想转换方向则选择3个GPIO的方式。注意,scl和sda必须在同一组内,比如scl在ACBUS0~7,那么sda也必须是这组内的,不能是ADBUS0~ADBUS7。

typedef struct 
{
    struct ftdi_context *ftdi;
    
    uint8_t scl;
    uint8_t sdao;
    uint8_t sdai;

    int freq;
    uint8_t *pCommand;
    int iCommand;
}mpsse_i2c_s;

freq表示I2C的频率,0表示最快速度,值越大越慢。

一般I2C可以接多个从设备,所以这里只定义了2组I2C,如果需要更多,修改数组大小即可。

typedef enum
{
    I2C_PORT_0 = 0,
    I2C_PORT_1,
    I2C_PORT_MAX,
}mpsse_i2c_port_e;

mpsse_i2c_s i2c[I2C_PORT_MAX];

1. 初始化

void i2cInit(uint8_t port, mpsse_i2c_s init)
{
    if(port >= I2C_PORT_MAX)
        return;
    i2c[port] = init;
    i2c[port].freq += 1;
}

pCommand和iCommand这里不需要初始化。其他参数按照硬件配置即可。这里freq加1是为了后面循环时直接使用。

2. 原理

从gpio写可知,通过命令0x80或0x81写入3个字节命令,FTDI设备会执行一次IO操作,如果同样的命令写freq次,那么GPIO的操作频率就会降低。比如0x80 level dir 0x80 level dir,写了2次0x80,设备就是连续设置一样GPIO两次,频率就是写一次的一半。

注意设定I2C的GPIO必须是同一组,定义好读写命令

const uint8_t gpioWirteCommand[2] = {0x80, 0x82};
const uint8_t gpioReadCommand[2] = {0x81, 0x83};

然后根据I2C的GPIO中任意一个计算应该使用哪个命令。

#define gpioCommand(port) (i2c[port].scl / 8)

例如scl选择ACBUS0,对应IO编号为8,gpioCommand = 1, 对应的读写命令为0x83和0x82。

将命令更新到命令buffer中,注意,整个I2C的操作命令是最后才真正发送给FTDI设备。

#define i2cCommandWrite(port, n)    do{\
    for (int i = 0; i < (int)i2c[port].freq * n; i++)\
    {\
        i2c[port].pCommand[i2c[port].iCommand++] = gpioWriteCommand[gpioCommand(port)];\
        i2c[port].pCommand[i2c[port].iCommand++] = (gpio.level >> (gpioCommand(port) * 8)) & 0xff;\
        i2c[port].pCommand[i2c[port].iCommand++] = (gpio.dir >> (gpioCommand(port) * 8)) & 0xff;\
    }\
}while(0)

定义SDA的输入输出:

#define i2cSDAOut(port)     do{\
    gpio.dir |= ((uint16_t)1 << i2c[port].sdao);\
}while(0)

#define i2cSDAIn(port)      do{\
    gpio.dir &= (uint16_t)(~((uint16_t)1 << i2c[port].sdai));\
}while(0)

定义SDA输出高低电平:

#define i2cSDAHigh(port)    do{\
    gpio.level |= ((uint16_t)1 << i2c[port].sdao);\
}while(0)

#define i2cSDALow(port)     do{\
    gpio.level &= (uint16_t)(~((uint16_t)1 << i2c[port].sdao));\
}while(0)

定义读SDA的命令:

#define i2cSDARead(port)    do{\
    i2c[port].pCommand[i2c[port].iCommand++] = gpioReadCommand[gpioCommand(port)];\
}while(0)

由于SCL一直为输出,不需要单独设置方向,只需要定义SCL的输出高低电平:

#define i2cSCLHigh(port)    do{\
    gpio.level |= ((uint16_t)1 << i2c[port].scl);\
}while(0)

#define i2cSCLLow(port)     do{\
    gpio.level &= (uint16_t)(~((uint16_t)1 << i2c[port].scl));\
}while(0)

接下来根据i2c协议完成各个状态函数即可。 

3. i2cStart

SCL为高电平,SDA产生一个下降沿即表示I2C的Start信号

void i2cStart(uint8_t port)
{
    //Set SDA output
    //SCL outputs high, SDA outputs high
    i2cSDAOut(port);
    i2cSCLHigh(port);
    i2cSDAHigh(port);
    i2cCommandWrite(port, 1);

    //SDA outputs low
    i2cSDALow(port);
    i2cCommandWrite(port, 2);
}

计算command缓存区使用情况:9 * freq

#define I2C_START_CMD_LEN           (3 * 3 * i2c[port].freq)

4. i2cStop

SCL为高电平,SDA产生一个下降沿即表示I2C的Stop信号

void i2cStop(uint8_t port)
{
    //SDA outpus low
    i2cSDALow(port);
    i2cCommandWrite(port, 1);
    
    //SCL outputs high
    i2cSCLHigh(port);
    i2cCommandWrite(port, 1);
    
    //SDA outputs high
    i2cSDAHigh(port);
    i2cCommandWrite(port, 1);
}

计算command缓存区使用情况:9 * freq

#define I2C_STOP_CMD_LEN           (3 * 3 * i2c[port].freq )

5. i2cRecvByte

SCL产生一个上升沿,然后SDA读入1个位数据,同样的操作连续读入8个位,然后根据参数ack SDA是否输出ack信号,SDA输出低电平且SCL产生一个上升沿即为ack信号,否则SDA输出高电平。ack完成后写入0x87命令,这个命令是将FTDI设备缓冲区中的数据立刻回传到PC。

void i2cRecvByte(uint8_t port, bool ack)
{
    //SCL outputs low, SDA outputs low
    //Set SDA as input
    i2cSCLLow(port);
    i2cSDALow(port);
    i2cSDAIn(port);
    i2cCommandWrite(port, 1);

    uint8_t loop = 8;
    while (loop-- > 0)
    {
        //SCL output low
        i2cSCLLow(port);
        i2cCommandWrite(port, 1);

        //SCL output high
        i2cSCLHigh(port);
        i2cCommandWrite(port, 1);

        //Read SDA
        i2cSDARead(port);
    }

    //SCL outputs low, SDA outputs low
    //Set SDA as output
    i2cSCLLow(port);
    i2cSDALow(port);
    i2cSDAOut(port);
    i2cCommandWrite(port, 1);

    if (ack == true)
    {
        i2cSDALow(port);
    }
    else
    {
        i2cSDAHigh(port);
    }
    i2cCommandWrite(port, 1);

    //SCL output high
    i2cSCLHigh(port);
    i2cCommandWrite(port, 1);

    i2c[port].pCommand[i2c[port].iCommand++] = 0x87;

    //SCL output low
    i2cSCLLow(port);
    i2cCommandWrite(port, 1);
}

注意,读入的数据此时并没有处理,等全部完成再读入数据。

计算command缓存区使用情况:

#define I2C_RECV_CMD_LEN           (5 * 3 * i2c[port].freq + 8 * (2 * 3 * i2c[port].freq + 1) + 1)

6. i2cSendByte

SCL上升沿发送SDA的一个位信息,同样的操作连续发送8个位。发送完成后同样一个SCL上升沿读入ack信息。

void i2cSendByte(uint8_t port, uint8_t dat)
{
    for(uint8_t j = 0; j < 8; j++)
    {
        //SCL output low
        i2cSCLLow(port);
        i2cCommandWrite(port, 1);
        //SDA output dat
        if ((dat & 0x80) == 0x80)
            i2cSDAHigh(port);
        else
            i2cSDALow(port);
        dat <<= 1;
        //Set SDA as output
        i2cSDAOut(port);
        i2cCommandWrite(port, 1);
        //SCL output high
        i2cSCLHigh(port);
        i2cCommandWrite(port, 1);
    }

    //SCL outputs low, SDA outputs low
    //Set SDA as input
    i2cSCLLow(port);
    i2cSDALow(port);
    i2cSDAIn(port);
    i2cCommandWrite(port, 1);

    //SCL output low
    //i2cSCLLow(port);
    //i2cCommandWrite(port, 1);
    
    //SCL output high
    i2cSCLHigh(port);
    i2cCommandWrite(port, 10);
    //Read SDA
    i2cSDARead(port);

    //SDA outputs high
    //Set SDA as output
    i2cSDAHigh(port);
    i2cSDAOut(port);
    i2cCommandWrite(port, 1);

    //SCL output low
    i2cSCLLow(port);
    i2cCommandWrite(port, 1);
}

计算command缓存区使用情况:

#define I2C_SEND_CMD_LEN    (8 * 3 * (4 * i2c[port].freq) + 12 * 3 * i2c[port].freq + 1)

7. i2cRead

int i2cRead(uint8_t port, uint8_t slaveAddr, uint8_t addrbit, int addr, uint8_t* dat, int len)

首先判断参数合法性

    if(port >= I2C_PORT_MAX)
        return -1;
    if(len <= 0)
        return -1;

在读数据前先把USB缓存读空,可以通过变量ftdi->readbuffer_remaining获取当前缓存里面有多少有效数据,大于0的情况就把数据读出来丢弃。

    if(i2c[port].ftdi->readbuffer_remaining > 0)
    {
        uint8_t *pDummy = (uint8_t *)malloc(i2c[port].ftdi->readbuffer_remaining);
        if(pDummy)
        {
            ftdi_read_data(i2c[port].ftdi, pDummy, i2c[port].ftdi->readbuffer_remaining);
            free(pDummy);
        }
    }

初始化Command缓存,按照发送的字节数计算需要多少个字节保存命令。

//2 * start + 4 * sendbyte + len * recvbyte + 1 * stop
int commandlength = (I2C_START_CMD_LEN * 2 
    + I2C_SEND_CMD_LEN * 4
    + I2C_RECV_CMD_LEN * len
    + I2C_STOP_CMD_LEN);
i2c[port].pCommand = (uint8_t *)malloc(commandlength);
i2c[port].iCommand = 0;

按照I2C设备的协议配置好Command缓存

    i2cStart(port);
    if(addrbit > 0)
        i2cSendByte(port, slaveAddr);
    if(addrbit > 8)
        i2cSendByte(port, (uint8_t)(addr >> 8));
    if(addrbit > 0)
    {
        i2cSendByte(port, (uint8_t)addr);
        i2cStart(port);
    }
    i2cSendByte(port, slaveAddr | 0x01);
    int sendLen = len;
    while(--sendLen)
    {
        i2cRecvByte(port, true);
    }
    i2cRecvByte(port, false);
    i2cStop(port);

然后写到FTDI设备

    if(i2c[port].iCommand > commandlength)
    {
        printf("i2cRead error: command buffer is overflow %d:%d\n", 
            i2c[port].iCommand, commandlength);
        if(i2c[port].pCommand)
            free(i2c[port].pCommand);
        return -2;
    }
    ftdi_write_data(i2c[port].ftdi, i2c[port].pCommand, i2c[port].iCommand);
    if(i2c[port].pCommand)
        free(i2c[port].pCommand);

发送完这些数据后,就是等待FTDI设备执行完数据并把读入的数据更新到USB驱动层。读入的数据是8个位的GPIO数据,要计算一下一共读入了多少个字节数据,每个i2cSendByte读入包含ack的一个字节,最多4个,然后在读入len个8位数据(i2cRecvByte),所以一共读入字节数为

int rdLen = ((addrbit > 0) ? 2 : 0) + 
    ((addrbit > 8) ? 1 : 0) + 1 +
    len * 8;
uint8_t *pReadBuf = (uint8_t *)malloc(rdLen);

当ftdi_read_data时,实际是有一个超时的问题,如果读入的数据在设定的时间内没读回则返回超时错误,所以在调用前根据数据长度设置一个合适的超时时间:

int readtimeout = i2c[port].ftdi->usb_read_timeout;
i2c[port].ftdi->usb_read_timeout = 2 * i2c[port].iCommand + 2 * rdLen * i2c[port].freq;
int ret = ftdi_read_data(i2c[port].ftdi, pReadBuf, rdLen);

读入数据后如果出错就再试一次,如果再出错就返回错误退出。

    if(ret < rdLen)
    {//try again
        int remain;
        if(ret < 0)
        {
            remain = rdLen;
            ret = ftdi_read_data(i2c[port].ftdi, pReadBuf, rdLen);
        } 
        else
        {
            remain = rdLen - ret;
            ret = ftdi_read_data(i2c[port].ftdi, pReadBuf + ret, remain);
        }
        
        if(ret + remain < rdLen)
        {
            i2c[port].ftdi->usb_read_timeout = readtimeout;
            if(pReadBuf)
                free(pReadBuf);
            return -3;
        }
    }
    i2c[port].ftdi->usb_read_timeout = readtimeout;

此时读入的数据中包含了ack信息和I2C读回的数据信息。先判断一下ack是不是正确。

    int ackLen = ((addrbit > 0) ? 2 : 0) + 
        ((addrbit > 8) ? 1 : 0) + 1;
    for(int i = 0; i < ackLen; i++)
    {
        if ((pReadBuf[i] & ((uint8_t)(1 << (i2c[port].sdai % 8)))) 
            == (uint8_t)(1 << (i2c[port].sdai % 8))) //Check ACK
        {
            printf("check ack fail %d, 0x%x\n", i, pReadBuf[i]);
            if(pReadBuf)
                free(pReadBuf);
            return -4;
        }
    }

然后把每个位信息组合成字节数据保存在返回指针中。

    int j = 0;
    for(int i = ackLen; i < rdLen; i++)
    {
        int max = i + 8;
        uint8_t tmp = 0;
        for(; i < max; i++)
        {
            tmp <<= 1;
            if ((pReadBuf[i] & ((uint8_t)(1 << (i2c[port].sdai % 8)))) 
                == (uint8_t)(1 << (i2c[port].sdai % 8)))
            {
                tmp |= 0x01;
            }
        }
        dat[j++] = tmp;
        i--;
    }

最后释放malloc的数据,且返回0.

    if(pReadBuf)
        free(pReadBuf);
    printf("i2cRead OK\n");
    return 0;

8. i2cWrite

int i2cWrite(uint8_t port, uint8_t slaveAddr, uint8_t addrbit, int addr, uint8_t* dat, int len)

类似i2cRead。

首先判断参数合法性

    if(port >= I2C_PORT_MAX)
        return -1;
    if(len <= 0)
        return -1;

写数据前也读空一下缓存。

    if(i2c[port].ftdi->readbuffer_remaining > 0)
    {
        uint8_t *pDummy = (uint8_t *)malloc(i2c[port].ftdi->readbuffer_remaining);
        if(pDummy)
        {
            ftdi_read_data(i2c[port].ftdi, pDummy, i2c[port].ftdi->readbuffer_remaining);
            free(pDummy);
        }
    }

初始化Command缓存

    //1 * start + 3 * sendbyte + len * sendbyte + 1 * stop
    int commandlength = (I2C_START_CMD_LEN * 1 
        + I2C_SEND_CMD_LEN * 3
        + I2C_SEND_CMD_LEN * len
        + I2C_STOP_CMD_LEN);
    i2c[port].pCommand = (uint8_t *)malloc(commandlength);
    i2c[port].iCommand = 0;

按照I2C设备的协议配置好Command缓存

    i2cStart(port);
    i2cSendByte(port, slaveAddr);
    if(addrbit > 8)
        i2cSendByte(port, (uint8_t)(addr >> 8));
    if(addrbit > 0)
        i2cSendByte(port, (uint8_t)addr);
    int n = 0;
    int sendLen = len;
    while(sendLen--)
    {
        i2cSendByte(port, dat[n++]);
    }
    i2cStop(port);

将Command缓存一次发送出去

    if(i2c[port].iCommand > commandlength)
    {
        printf("i2cWrite error: command buffer is overflow %d:%d\n", 
            i2c[port].iCommand, commandlength);
        if(i2c[port].pCommand)
            free(i2c[port].pCommand);
        return -2;
    }
    int writetimeout = i2c[port].ftdi->usb_write_timeout;
    i2c[port].ftdi->usb_write_timeout = i2c[port].iCommand + len * i2c[port].freq;
    int ret = ftdi_write_data(i2c[port].ftdi, i2c[port].pCommand, i2c[port].iCommand);
    if(i2c[port].pCommand)
        free(i2c[port].pCommand);
    i2c[port].ftdi->usb_write_timeout = writetimeout;
    if(ret < 0)
    {
        printf("usb write fail %d\n", ret);
        return -5;
    }

i2cWrite只需要读回所有的ack信息

    int rdLen = ((addrbit > 0) ? 1 : 0) + 
        ((addrbit > 8) ? 1 : 0) + 1 + len;

同样的方式读回所有数据

    int rdLen = ((addrbit > 0) ? 1 : 0) + 
        ((addrbit > 8) ? 1 : 0) + 1 + len;
    printf("need to read data:%d\n", rdLen);
    uint8_t *pReadBuf = (uint8_t *)malloc(rdLen);
    int readtimeout = i2c[port].ftdi->usb_read_timeout;
    i2c[port].ftdi->usb_read_timeout = i2c[port].iCommand + rdLen * i2c[port].freq;
    printf("usb read timeout:%d\n", i2c[port].ftdi->usb_read_timeout);
    ret = ftdi_read_data(i2c[port].ftdi, pReadBuf, rdLen);
    printf("read data number:%d\n", ret);
    if(ret < rdLen)
    {//try again
        int remain;
        if(ret < 0)
        {
            remain = rdLen;
            ret = ftdi_read_data(i2c[port].ftdi, pReadBuf, rdLen);
        } 
        else
        {
            remain = rdLen - ret;
            ret = ftdi_read_data(i2c[port].ftdi, pReadBuf + ret, remain);
        }
        printf("retry read %d\n", ret + remain);
        if(ret + remain < rdLen)
        {
            if(pReadBuf)
                free(pReadBuf);
            i2c[port].ftdi->usb_read_timeout = readtimeout;
            return -3;
        }
    }
    i2c[port].ftdi->usb_read_timeout = readtimeout;

判断这些读回来的数据对应SDA位的值是否符合ack正确,注意,最后一个字节并不需要判断。

    for(int i = 0; i < rdLen - 1; i++)
    {
        if ((pReadBuf[i] & ((uint8_t)(1 << (i2c[port].sdai % 8)))) 
            == (uint8_t)(1 << (i2c[port].sdai % 8))) //Check ACK
        {
            if(pReadBuf)
                free(pReadBuf);
            printf("ack fail:%d, 0x%x\n", i, pReadBuf[i]);
            return -4;
        } 
    }

最后释放malloc的数据,且返回0.

    if(pReadBuf)
        free(pReadBuf);
    printf("i2cWrite OK\n");
    return 0;

9. 验证

将I2C接口的EEPROM芯片接到FT4232H模块的ADBUS0和ADBUS1口。首先初始化mode为mpsse模式。

    int ret;
    ret = ftdi_set_bitmode(ftdi, 0, BITMODE_MPSSE);
    if(ret < 0)
    {
        printf("Set Mode Fail: %d\n", ret);
        return EXIT_FAILURE;
    }

9.1 初始化i2c

    mpsse_i2c_s i2cSetting;
    i2cSetting.ftdi = ftdi;
    i2cSetting.scl = 2;
    i2cSetting.sdao = 3;
    i2cSetting.sdai = 3;
    i2cSetting.freq = 9;
    i2cInit(0, i2cSetting);

9.2 初始化gpio

将I2C的SCL和SDA初始化为输出高电平的初始状态。

    mpsse_gpio_s gpioSetting;
    gpioSetting.ftdi = ftdi;
    gpioSetting.dir = 0x0000; //All input
    gpioSetting.dir |= ((uint16_t)GPIO_DIR_OUT << i2cSetting.scl) 
        | ((uint16_t)GPIO_DIR_OUT << i2cSetting.sdao);    //scl output, sdao output
    gpioSetting.level = 0xFFFF;
    mpsseGpioInit(gpioSetting);

9.3 写10个字节到EEPROM

    uint8_t wrBuf[10];
    printf("i2c write data: ");
    srand(time(NULL));
    for(int i = 0; i < sizeof(wrBuf); i++)
    {
        wrBuf[i] = (uint8_t)rand();
        printf("0x%2x ", wrBuf[i]);
    }
    printf("\n");
    ret = i2cWrite(0, 0xa0, 16, 0, wrBuf, sizeof(wrBuf));
    if(ret < 0)
    {
        printf("write eeprom fail\n");
        return ret;
    }

9.4 读回10字节数据

    uint8_t rdBuf[10];
    ret = i2cRead(0, 0xa0, 16, 0, rdBuf, sizeof(rdBuf));
    if(ret < 0)
    {
        printf("read eeprom fail\n");
        return ret;
    }
    printf("i2c read data: ");
    for(int i = 0; i < sizeof(wrBuf); i++)
    {
        printf("0x%2x ", rdBuf[i]);
        if(wrBuf[i] != rdBuf[i])
        {
            printf("\neeprom verify fail\n");
            return 0;
        }
    }
    printf("\n");

9.5 运行结果

libftdi-example$ sudo ./libftdi1-example 
version:1.5.0, 1.5
Number of FTDI devices found: 1
Manufacturer: FTDI, Description: FT4232H MiniModule, Serial: FT8NZV77

Open device OK: 0
i2c write data: 0x8c 0x8d 0xc4 0xf4 0xc2 0x 4 0xd8 0x88 0x26 0xf0 
need to read data:13
usb read timeout:7383
read data number:13
i2cWrite OK
read data number:84
i2cRead OK
i2c read data: 0x8c 0x8d 0xc4 0xf4 0xc2 0x 4 0xd8 0x88 0x26 0xf0 

  • 25
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值