FTDI FT2232H在嵌入式教学中的应用

FT2232H是FTDI chip在2012年发布的一款高速USB转串行通信的协议转换芯片。作为第五代USB协议转串行总线通信协议的芯片,完全符合USB2.0规范(480Mb/s)并且可以依靠编程的方式配置成为串行或者并行的其他总线接口规范。这对于在进行嵌入式教学是的传感器总线仿真是非常方便的,甚至可以作为嵌入式教学中的调试工具使用。具体的芯片介绍可以参考官方资料(DS_FT2232H),这里笔者只举一个编程实现I2C总线通信的例子,来说明FT2232H的使用。


一、MPSSE简介
在用户程序使用Multi Protocol Synchronous Serial Engine (MPSSE)作为实现三个较常见的串行总线(SPI、I2C和JTAG)的中间件。MPSSE实际上是一个函数库,封装了实现不同协议的流程化操作,用户程序可以通过调用这些函数,来实现和D2XX Driver的数据交换,而D2XX在逻辑上直接驱动FT2232H芯片。实际上程序也可以直接调用D2XX中的函数,来直接实现所需的协议规范。
FT232HL的栈结构
图 MPSSE所处的位置
如上图所示,MPSSE提供了三种类型的API来分辨实现I2C、SPI和JTAG,在使用的使用下载相应的函数库就可以了。官网提供了Linux和Windows的版本
http://www.ftdichip.com/Support/SoftwareExamples/MPSSE.htm
这里写图片描述
图 系统结构
在上图中,Host可以使PC或者是嵌入式系统给,通过FT2232H桥接到I2C总线。作为示例,图中只有一个I2C设备,在实际应用中,在I2C总线上的设备可能不止一个,这就需要在使用的时候配置FT2232H的I2C地址。
libMPSSE-I2C的函数库文件结构如下,在使用是只需将inlcude和lib文件夹下的windows\i386下的内容拷贝到工程文件夹中,如下图所示:
这里写图片描述
图 libMPSSE-I2C的目录结构
这里写图片描述
图 添加后的文件组成
另外,还需在Visual Studio的工程中添加对Lib的依赖,如下图:
这里写图片描述
libMPSSE-I2C可以分为两个部分,其一为五个控制函数;其二为两个数据传输函数。所有的API都返回FT_STATUS宏(FT_STATUS在D2XX Driver中的定义)。

二、被控制的I2C器件的时序和函数参数
被控制(访问)的I2C设备,在本文中选用的是DIP封装的老款24C128(I2C接口的128K EEPROM),新款的型号是24C128C,引脚(A0-A3在设备寻址是稍有不同,老款只使用A0-A1两个引脚),这里只说明24C128的设备寻址。
这里写图片描述
图 24C128的引脚和引脚说明
这里写图片描述
图 设备寻址是8位地址的组成
在使用时,高5位是不变的,在使用时只需配置A1-A0和LSB(读写标志位)即可。当只对一片24C128操作时,A1=A0=0。在进行写操作时,务必使WP接地或者悬空。在使用FT2322H访问I2C设备时,只要求地址为0B0101000(七位,最后一位不用写)。 具体的寻址请参考AT24C128的使用手册,此处不再赘述。下面参照按字节写的时序,讲解如何写入一个字节和一个64字节的页面。
这里写图片描述
图 Byte写模式的时序
要用使用函数:FT_STATUS I2C_DeviceWrite(FT_HANDLE handle, uint32 deviceAddress, uint32
sizeToTransfer, uint8 *buffer, uint32 *sizeTransferred, uint32 options)
前五个参数较易理解,可以自行查看文档。重点讲述最后一个参数options-此参数是配合写操作时SDA数据线上的时序。它是一个字节码,可以按位进行设置。

BIT0置位:在传输数据前,产生一个开始状态位。使用I2C_TRANSFER_OPTIONS_START_BIT来置位。
BIT1置位:在传输结束以后I2C期间会产生一个结束位。使用I2C_TRANSFER_OPTIONS_STOP_BIT来置位。

BIT2:如果置位,函数则返回I2C设备的nAcks(已接收)在一个字节被传输以后。如果不置位,函数将继续传输字节流不忽略设备发送nAcks信号。使用掩码I2C_TRANSFER_OPTIONS_BREAK_ON_NACK
来设置此位。
BIT3: 保留,只在读函数中使用。
BIT4:此位置位,将引发一个没有起始(START)、寻址(ADDRESS)、数据传输(DATA)和结束(STOP)阶段延时的按字节(Byte)数据连续传输。字节数的多少有参数sizeToTransfer定义;实际传输字节数返回至输出参数sizeTransfered。使用I2C_TRANSFER_OPTIONS_FAST_TRANSFER_BYTES* 置位。

由于I2C_DeviceWrite函数发送命令给MPSSE,读取MPSSE的反馈,并根据反馈来发送进一步的命令,

Bit4置位以后,实际上实现了按照页面写入的模式,省去了按字节传输时重发发送的起始位、设备地址和数据地址。
这里写图片描述
图 页面写模式

BIT5:此位置位,和BIT4类似,唯一的不同是将引发一个按“位(Bit)”的连续传输。使用掩码I2C_ _TRANSFER_OPTIONS_FAST_TRANSFER_BITS*来置位。
Bit6:置位将忽略deviceAddress,这对于有些可以忽略设备地址的特殊应用场合尤其适用。只有当BIT4或者BIT5任何一位置位的时候,置位BIT6才有效。置位Bit6可以使用掩码I2C_ _TRANSFER_OPTIONS_NO_ADDRESS*。
Bit7-Bit31:保留。

三、连接被控器件并进行通信
除了常规的Vcc、GND、WP、A0和A1以外,SDA和SCL和FT232HL的连接可以参考下表。
这里写图片描述
图 不同配置下引脚的功能
由上图可以得知,SCL连接Pin16(AD0),I2C器件的SDA连接Pin17和Pin18,用于串行数据的输入和输出。由于在I2C设备端,只有一个数据线SDA,但在和FT2322HL连接的时候,就会被分成输出和输入两个单独的方向。下面看具体的代码:

//其中SlaveAddress为发送给FT2322HL的7位I2C设备地址,registerAddrss是FirstWord address和
//SecondWord Address(由于访问的内存地址少于255,所以只需有地址低8位有效), data是发送的一个字节。
FT_STATUS write_byte(uint8 slaveAddress, uint8 registerAddress, uint8 data)
{
    uint32 bytesToTransfer = 0;
    uint32 bytesTransfered = 0;
    bool writeComplete = 0;
    uint32 retry = 0;
    bytesToTransfer = 0;
    bytesTransfered = 0; 
    //First Word Address,即高八位,此处访问小于256字节,以此设置为buffer[bytesToTransfer++] = 0x00.
    buffer[bytesToTransfer++] = 0x00; 
    //Buffer[1]第八位
    buffer[bytesToTransfer++] = registerAddress; 
    //Buffer[2]数据字节,最后bytesToTrabsfer=3(发送字节数)
    buffer[bytesToTransfer++] = data; 
    );
    //此处第一次调用I2C_DevieWrite函数
    status = I2C_DeviceWrite(ftHandle, slaveAddress, bytesToTransfer, buffer ,&bytesTransfered,I2C_TRANSFER_OPTIONS_START_BIT | I2C_TRANSFER_OPTIONS_STOP_BIT   while ((writeComplete == 0) && (retry<I2C_WRITE_COMPLETION_RETRY))
    {
        bytesToTransfer = 0;
        bytesTransfered = 0;
        //First Word Address,即高八位,此处访问小于256字节,以此设置为buffer[bytesToTransfer++] = 0x00.
        buffer[bytesToTransfer++] = 0x00;
        //Second Word Address,即低八位,此处会依次写入0x00-0x09,详见main函数内部
        buffer[bytesToTransfer++] = registerAddress;    
        //由于Data已经写入bufffer[2]中
        status = I2C_DeviceWrite(ftHandle, slaveAddress, bytesToTransfer, buffer, &bytesTransfered, I2C_TRANSFER_OPTIONS_START_BIT | I2C_TRANSFER_OPTIONS_BREAK_ON_NACK);//此处第二次调用I2C_DeviceWrite函数
        if ((FT_OK == status) && (bytesToTransfer == bytesTransfered))
        {
            writeComplete = 1;
            std::cout << "...a byte write done!\n";
        }
        retry++;
    }
    return status;
}
int main()
{
    FT_STATUS status;
    FT_DEVICE_LIST_INFO_NODE devList;
    uint8 address;
    uint8 data;
    int i, j;
    #ifdef _MSC_VER
        Init_libMPSSE();
    #endif
    channelConfig.ClockRate = I2C_CLOCK_FAST_MODE;
    channelConfig.LatencyTimer = 255;
    status = I2C_GetNumChannels(&channels);
    APP_CHECK_STATUS(status);
    printf("Number of available I2C channles = %d",channels);
    if (channels>0)
    {
        for (i = 0; i<channels; i++)
        {
            status = I2C_GetChannelInfo(i, &devList);
            APP_CHECK_STATUS(status);
            std::cout << "Info on channel number: "<<i<<std::endl;
            std::cout << "Flags=0x" << devList.Flags << std::endl;
            std::cout << "Type =0x" << devList.Type << std::endl;
            std::cout << "ID=0x" << devList.ID << std::endl;
            std::cout << "LocID=0x" << devList.LocId<<std::endl;
            std::cout << "SerialNumber =0x" << devList.SerialNumber << std::endl;
            std::cout << "Description=" << devList.Description << std::endl;
            std::cout << "ftHandle=0x" << devList.ftHandle<< std::endl;

        }

        /*Open first ava channel*/
        status = I2C_OpenChannel(CHANNEL_TO_OPEN, &ftHandle);
        APP_CHECK_STATUS(status);
        std::cout << "\n handle=0x" <<ftHandle<<"status="<< status<< std::endl;
        status = I2C_InitChannel(ftHandle, &channelConfig);
        APP_CHECK_STATUS(status);


        for (address = START_ADDRESS_EEPROM; address<END_ADDRESS_EEPROM; address++)
        {

            std::cout << "Writing address = "<< address<< ", data ="  << address + DATA_OFFSET << std::endl;
            status = write_byte(I2C_DEVICE_ADDRESS_EEPROM, address, address + DATA_OFFSET);
            for (j = 0; ((j<RETRY_COUNT_EEPROM) && (FT_OK != status)); j++)
            {
                std::cout << "----Writing again to address ="<< address<< ", data = "  << address + DATA_OFFSET << std::endl;
                status = write_byte(I2C_DEVICE_ADDRESS_EEPROM, address, address + DATA_OFFSET);
            }
            APP_CHECK_STATUS(status);
        }
        std::cout << "\n";
        for (address = START_ADDRESS_EEPROM; address<END_ADDRESS_EEPROM; address++)
        {
            status = read_byte(I2C_DEVICE_ADDRESS_EEPROM, address, &data);
            for (j = 0; ((j<RETRY_COUNT_EEPROM) && (FT_OK != status)); j++)
            {
                std::cout << "----Reading error retrying ....\n" << std::endl;
                status = read_byte(I2C_DEVICE_ADDRESS_EEPROM, address, &data);
            }
            APP_CHECK_STATUS(status);
        }

        status = I2C_CloseChannel(ftHandle);
    }
    #ifdef _MSC_VER
        Cleanup_libMPSSE();
    #endif

    return 0;
}

当第一次调用I2C_DeviceWrite函数的时候,Buffer[0]放置的是first word address(0x00),Bufffer[1]放置的是second word address(程序中传递的地址数据从0x00 0x00开始,以此往后写入10个字节),首先发送两个EEPROM的地址字节,并使用I2C_TRANSFER_OPTIONS_START_BIT | I2C_TRANSFER_OPTIONS_STOP_BIT掩码发送。这两位掩码意味着MPSSE(FT2322HL)端将发送一个起始位Start和一个结束位Stop。在第二次调用I2C_DeviceWrite的时候,其意在与查询第一次的函数调用是否完成,由于I2C_DeviceWrite函数是异步调用,第一次函数调用后,函数立即返回,但第一次没有掩码I2C_TRANSFER_OPTIONS_BREAK_ON_NACK的参与,所以是将要发送的三个字节一股脑的延SDA线发送过去,即便设备产生了ACK低电平也不会理会。为了提高总线的使用效率,需要知道I2C设备写操作何时结束,所以在第一个I2C_DeviceWrite之后,紧跟一个Ack通知查询的循环。查询语句(第二个I2C_DeviceWrite)直接向I2C设备发送一个开始位,并对其实际写入的字节数和应写入字节数进行判断,若二者相等则表示写操作结束。本直升I2C控制器和I2C设备之间的关系有些像是主仆关系,主人向仆人发送命令;仆人按照主人的指令做事,并在接收主人的命令以后,接受主人的查询。实际IC扩展连接如下图:
这里写图片描述
这里写图片描述
但在运行的时候,发生了无法找到I2C通道的情况。分别试了16-17-18和38-39-40三组Channel的引脚,都没有任何改善。
这里写图片描述
最后估计是MPSSE配置的问题,查看FT232HL的用户手册,在4.6节发现了如下的文字:MPSSE mode is enabled using Set Bit Bang Mode driver command. A hex value of 2 will enable it, and a hex value of 0 will reset the device. See application note AN2232L-02, “Bit Mode Functions for the FT2232D” for more details and examples. The MPSSE command set is fully described in application note AN_108 – “Command Processor for MPSSE and MCU Host Bus Emulation Modes”. The following additional application notes are available for configuring the MPSSE:
AN_109 – “Programming Guide for High Speed FTCI2C DLL
AN_110 – “Programming Guide for High Speed FTCJTAG DLL
AN_111 – “Programming Guide for High Speed FTCSPI DLL

大致的意思就是说想要让芯片处于MPSSE模式,需要发送一个Bit Bang设置的命令,此命令是十六进制的2, 发送十六进制的0可以复位。可以参考应用指南AN2232L-02 FT2232D的比特模式函数。MPSSE的设置命令的详细使用说明可以参考另外的一个文档(编号AN_108),并推荐了另外的三篇编程指南。关于FT2232的MPSSE设置编程以及问题的后续解决,请继续关注软通大学的专栏。

软通大学 王明浩


版权声明:本文为isscollege原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。