通过USB将配置下载到FPGA或拆卸FTDI MPSSE



在每个抽屉的使用期内,都有一段时间要在FPGA中编写自己的配置文件加载器。 我必须参加技术大学系的培训摊位的开发。 该支架旨在研究数字信号处理,尽管在本文的框架中这并不是特别重要。 而且意义在于,FPGA(Altera Cyclone IV)是展台的核心,正如展台作者所设想的那样,学生们可以在其上收集各种DSP方案。 支架通过USB连接到计算机。 您需要通过USB从计算机下载FPGA。

决定在其双通道版本FT2232H中使用FTDI连接到PC。 一个通道将用于FPGA配置,另一个通道可用于高速FIFO交换。


FTDI拥有MORPH-IC-II调试板,其中Cyclone II FPGA通过USB闪存。 公共领域中的概念。 引导加载程序的源代码是部分开放的:引导加载程序本身可用,但是,与FTDI配合使用的所有逻辑都已移至专用库,无法进行修改。 实际上,我最初计划在项目中使用此引导加载程序,或者在极端情况下,根据其dll构建我的shell。 固件以被动串行模式(被动串行-PS)加载到FPGA中,FTDI以MPSSE模式运行。 在试验板上,MORPH-IC-II解决方案的性能得到了充分证实,但是问题经常出在哪里,而并非出自何处。 事实证明,在DLL MORPH-IC-II运行期间,所有连接的FTDI设备都被阻止,并且作为培训中心的一部分,还有另外两个具有类似转换器的设备:发生器和信号分析仪。 无法与他们同时工作。 该死的怪异和烦人。


火星漫游者的家伙实施了一个类似的案例: USB JTAG程序员MBFTDI 。 FTDI也用于MPSSE模式,但与MORPH-IC-II不同,FPGA操作以JTAG模式执行。 资料来源是免费的,但我没有清楚地表明其状态(许可证)。 因此,要在商业项目中使用它们,我的手并没有举起。


我将纠正这种错误,本文框架中将介绍的所有内容均按照BSD许可发布在开放存储库中。


将配置文件下载到FPGA芯片


首先,您应该处理FPGA引导模式。 对于那些刚开始熟悉该主题的人,我将进行一些小型游览。 尽管我的板上安装了Cyclone IV E系列的Altera(Intel)FPGA,但整个Cyclone FPGA组的加载方法相似,并且怀疑一种或多种形式都适用于许多其他系列。


这种类型的FPGA使用易失性SRAM来存储配置数据。 这些配置数据确定最终设备的功能。 用专业术语来说,此数据通常称为“固件”。 因此,固件存储在特殊的RAM中,并且每次打开设备电源时,都必须将其加载到FPGA芯片中。 可以通过多种方式(配置方案)将固件加载到SRAM中(该列表与Cyclone IV E有关):


  1. 活动串行(AS)。
  2. 主动并行(AP)
  3. 无源串行(PS)
  4. 快速被动并行(FPP)。
  5. JTAG。

使用FPGA(MSEL组)的外部端子执行特定引导模式的选择。 JTAG模式始终可用。 主动模式表示加电后,FPGA独立地从外部存储器(串行或并行)读取数据。 在被动模式下,FPGA等待外部媒体主动向其传输配置数据。 这些方案非常适合主机(master)-奴隶(slave)的概念。 在主动模式下,FPGA充当主机,而在被动模式下则充当从机。


在这个问题中,它不是FPGA,而是用户必须决定何时更新固件,因此引导模式应该是被动的。 为了节省芯片成本,我们选择了一个串行接口。 无源串行(PS)模式和JTAG适用于此。 JTAG的逻辑有些复杂,因此让我们关注第一个选项。
下图显示了FPGA与外部控制器的连接方案,以PS模式下载。



要开始配置,外部主机必须在nCONFIG线上产生从低到高的变。 一旦FPGA准备好接收数据,它将在nSTATUS线上形成高电平。 之后,主机可以开始在DATA线[0]上发送数据,并在DCLK线上发送相应的时钟脉冲。 必须将数据传输到目标设备,直到在CONF_DONE线上建立高电平(否则数据没有结束),并且FPGA切换到初始化状态。 应该注意的是,在CONF_DONE设置为1之后,必须再施加两个时钟脉冲,以便FPGA初始化开始。


数据通过最低有效位( LSB )向前发送,也就是说,如果配置文件包含序列02 1B EE 01 FA(以手册中的示例为例),则该序列应在数据线上形成:


0100-0000 1101-1000 0111-0111 1000-0000 0101-1111 

因此,仅使用五条线:用于串行传输的数据线[0]DCLK ,用于控制的nCONFIGnSTATUSCONF_DONE线
从本质上讲,PS模式仅是带有附加标志操作的SPI。
数据传输速率应低于文档中指示的最大频率;对于项目中使用的Cyclone IV E系列,该频率为66 MHz。


最小传输频率不存在,理论上可以无限期地暂停配置。 这为在示波器的帮助下进行逐步调试提供了极好的机会,我们一定会使用它。


下图显示了具有最重要时序的接口时序图。



狡猾的野兽MPSSE


考虑在MPSSE模式下FTDI的操作。 我认为,MPSSE(多协议同步串行引擎)模式或多或少成功地尝试了创建某种串行接口设计器,使开发人员有机会实现广泛的数据传输协议,例如SPI,I2C,JTAG,1-wire等。其他基于他们。


当前,该模式适用于微电路:FT232H,FT2232D,FT2232H,FT4232H。 在我的项目中,我使用FT2232H,因此在更大程度上我们在谈论它。 对于MPSSE模式,分配了16个分支,分为两个字节:低L和高H。每个字节均可读取或设置。 字节L的四个小腿具有特殊功能-可以通过它们进行串行数据传输。 每个支路都可以配置为输入或输出,可以为输出设置默认值。 对于顺序传输,位的顺序( MSB / LSB ),传输的字的长度,时钟脉冲的频率,同步的前-前(上升)或后(下降),您可以选择仅发送不带数据的时钟脉冲,或者选择三相时钟(与I2C相关)等等。


无缝地进行编程。 与FTDI芯片进行软件交互的方法有两种:第一种,我们称之为经典,在这种情况下,当连接到USB端口时,系统中的芯片被定义为虚拟串行端口(COM),操作系统使用VCP驱动程序(虚拟COM端口)。 所有其他编程都与经典COM端口的编程没有不同:打开-传输/计数-关闭。 这对于包括Linux和Mac OS在内的各种操作系统都是如此。 但是,采用这种方法将无法实现FTDI控制器的所有功能-该芯片将用作USB-UART适配器。 第二种方法由FTD2XX专有库提供,此接口提供了标准COM端口API中不提供的特殊功能,尤其是可以配置和使用特殊操作模式,例如MPSSE,245 FIFO,Bit-bang。 FTD2XX API库在《 软件应用程序开发D2XX程序员指南》中有详尽的记录,该指南在狭窄的圈子中广为人知。 是的,FTD2XX也可用于各种操作系统。


FTDI开发人员的任务是将相对较新的MPSSE放入现有的D2XX软件交互模型中。 他们成功了,在MPSSE模式下工作时,使用了与其他“经典”模式相同的功能集,并使用了相同的FTD2XX API库。


简而言之,用于MPSSE模式的算法可以描述如下:


  1. 在系统中找到设备并打开它。
  2. 初始化芯片并将其置于MPSSE模式。
  3. 设置MPSEE的操作模式。
  4. 直接处理数据:传输,接收,管理GPIO-我们实现目标交换协议。
  5. 关闭设备。

编写一个引导程序


让我们开始实践。 在我的实验中,我将使用Eclipse版本的Oxygen.3a Release(4.7.3a)作为IDE,并使用mingw32-gcc(6.3.0)作为编译器。 Win7操作系统。


FTDI网站,我们为我们的操作系统下载了最新版本的驱动程序。 在档案中,我们找到了带有所有API功能描述的头文件ftd2xx.h。 API本身以ftd2xx.dll的形式实现,但是我们将动态导入留待以后使用,并使用静态链接:我们需要库文件ftd2xx.lib。 就我而言,ftd2xx.lib在i386目录中。


在Eclipse中,创建一个新的C项目。 IDE可以信任创建makefile 。 在链接器设置中,指定ftd2xx库的路径和名称(我将所需的文件传输到ftdi文件夹中的项目目录中)。 因为我怀疑其中大多数都将其他环境和编译器用于Win编程,所以我不会重点介绍为Eclipse设置项目的功能。


要点一 查找设备并打开它


FTD2XX API允许您使用一个或另一个已知的有关信息来打开芯片。 这可能是其在系统中的序列号:连接的第一个FTDI芯片将采用数字0,其后是1,依此类推。 系统中的数字由微电路的连接顺序决定,但要适度地说,这并不总是很方便。 要通过数字打开筹码,请使用FT_Open函数。 您可以通过序列号( FT_OPEN_BY_SERIAL_NUMBER ),描述( FT_OPEN_BY_DESCRIPTION )或位置( FT_OPEN_BY_LOCATION )打开芯片,为此,可以FT_OpenEx函数。 序列号和说明存储在芯片的内部存储器中,可以在安装了FTDI的设备制造过程中记录在芯片的内部存储器中。 通常,该描述描述了设备或系列的类型,并且序列号对于每个产品都必须是唯一的。 因此,识别正在开发的程序支持的设备的最便捷方法是对其进行描述。 我们将根据描述(描述符)打开FTDI芯片。 实际上,如果我们最初了解芯片描述符行,那么我们就不需要在系统中寻找设备,但是,作为实验,我们将显示所有通过FTDI连接到计算机的设备。 使用FT_CreateDeviceInfoList函数, FT_CreateDeviceInfoList将创建已连接芯片的详细列表,而使用FT_GetDeviceInfoList函数, FT_GetDeviceInfoList考虑。


已连接设备的列表。 清单:
 ftStatus = FT_CreateDeviceInfoList(&numDevs); if (ftStatus == FT_OK) { printf("Number of devices is %d\n",numDevs); } if (numDevs == 0) return -1; // allocate storage for list based on numDevs devInfo = (FT_DEVICE_LIST_INFO_NODE*)malloc(sizeof(FT_DEVICE_LIST_INFO_NODE)*numDevs); ftStatus = FT_GetDeviceInfoList(devInfo,&numDevs); if (ftStatus == FT_OK) for (int i = 0; i < numDevs; i++) { printf("Dev %d:\n",i); printf(" Flags=0x%x\n",devInfo[i].Flags); printf(" Type=0x%x\n",devInfo[i].Type); printf(" ID=0x%x\n",devInfo[i].ID); printf(" LocId=0x%x\n",devInfo[i].LocId); printf(" SerialNumber=%s\n",devInfo[i].SerialNumber); printf(" Description=%s\n",devInfo[i].Description); } 

欢迎我的动物园
 D:\workspace\ftdi-mpsse-ps\Debug>ftdi-mpsse-ps.exe Number of devices is 4 Dev 0: Flags = 0x0 Type = 0x5 ID = 0x4036001 LocId = 0x214 SerialNumber = AI043NNV Description = FT232R USB UART Dev 1: Flags = 0x2 Type = 0x6 ID = 0x4036010 LocId = 0x2121 SerialNumber = L731T70OA Description = LESO7 A Dev 2: Flags = 0x2 Type = 0x6 ID = 0x4036010 LocId = 0x2122 SerialNumber = L731T70OB Description = LESO7 B Dev 3: Flags = 0x2 Type = 0x8 ID = 0x4036014 LocId = 0x213 SerialNumber = FTYZ92L6 Description = LESO4.1_ER 

我的PC连接了三个带有FTDI芯片的设备:FT232RL(类型0x5),FT2232H(类型0x6)和FT232H(tepe 0x8)。 系统中的FT2232H芯片显示为两个独立的设备(Dev 1和Dev 2)。 FPGA PS接口连接到Dev 2,其描述符为“ LESO7 B”。 打开它:


 //Open a device with device description "LESO7 B" ftStatus = FT_OpenEx("LESO7 B", FT_OPEN_BY_DESCRIPTION, &ftHandle); if (ftStatus != FT_OK) { printf ("pen failure\r\n"); return -1; } 

大多数API函数都会返回其调用类型为FT_STATUS的状态,所有可能的值都在头文件中以枚举形式描述。 它们很多,但是足以知道值FT_OK是没有错误,所有其他值都是错误代码。 良好的编程风格是在每次调用API函数后检查状态值。


如果设备已成功打开,则在ftHandle变量ftHandle显示除零以外的某个值,即一些等效的文件描述符,该文件描述符在处理文件时使用。 生成的句柄建立与硬件接口的连接,并在调用所有需要访问芯片的库函数时使用。
为了在实践中确认系统在当前阶段的可操作性,我们应该立即进行算法的第五步。


使用完芯片后,需要关闭它。 为此,请使用FT_Close函数:


 FT_Close(ftHandle); 

点2.初始化芯片并打开MPSSE


该设置对于大多数模式来说都是典型的,并且在AN_135 FTDI MPSSE基础文档中有很好的描述。


  1. 我们执行芯片的重置(重设)。 FT_ResetDevice函数。
  2. 如果接收缓冲区中有任何垃圾,我们将其清除。 FT_Purge函数。
  3. 调整用于读取和写入的缓冲区的大小。 函数FT_SetUSBParameters
  4. 关闭平价。 FT_SetChars
  5. 我们设置了读写超时。 默认情况下,超时是禁用的,启用传输超时。 FT_SetTimeouts
  6. 我们配置从芯片向主机发送数据包的等待时间。 默认情况下为16毫秒,加速到1毫秒。 FT_SetLatencyTimer
  7. 打开流控制以同步传入的请求。 FT_SetFlowControl
  8. 一切准备就绪,可以激活MPSSE模式。 重置MPSSE控制器。 我们使用FT_SetBitMode函数,将模式设置为0(模式= 0,掩码= 0)。
  9. 打开MPSSE模式。 函数FT_SetBitMode模式= 2,掩码= 0。

我们在MPSSE_open函数中结合并配置芯片,作为参数,我们在一行中打开了要打开的设备的句柄:


清单MPSSE_open
 static FT_STATUS MPSSE_open (char *description) { FT_STATUS ftStatus; ftStatus = FT_OpenEx(description, FT_OPEN_BY_DESCRIPTION, &ftHandle); if (ftStatus != FT_OK) { printf ("open failure\r\n"); return FT_DEVICE_NOT_OPENED; } printf ("open OK, %d\r\n", ftHandle); printf("\nConfiguring port for MPSSE use...\n"); ftStatus |= FT_ResetDevice(ftHandle); //Purge USB receive buffer first by reading out all old data from FT2232H receive buff: ftStatus |= FT_Purge(ftHandle, FT_PURGE_RX); //Set USB request transfer sizes to 64K: ftStatus |= FT_SetUSBParameters(ftHandle, 65536, 65536); //Disable event and error characters: ftStatus |= FT_SetChars(ftHandle, 0, 0, 0, 0); //Sets the read and write timeouts in milliseconds: ftStatus |= FT_SetTimeouts(ftHandle, 0, 5000); //Set the latency timer to 1mS (default is 16mS): ftStatus |= FT_SetLatencyTimer(ftHandle, 1); //Turn on flow control to synchronize IN requests: ftStatus |= FT_SetFlowControl(ftHandle, FT_FLOW_RTS_CTS, 0x00, 0x00); //Reset controller: ftStatus |= FT_SetBitMode(ftHandle, 0x0, FT_BITMODE_RESET); //Enable MPSSE mode: ftStatus |= FT_SetBitMode(ftHandle, 0x0, FT_BITMODE_MPSSE); if (ftStatus != FT_OK) { printf("Error in initializing the MPSSE %d\n", ftStatus); return FT_OTHER_ERROR; } Sleep(50); // Wait for all the USB stuff to complete and work return FT_OK; } 

项目3.配置MPSEE操作模式


实际上,在此阶段,MPSSE处理器已激活并准备接收命令。 命令是字节序列,其第一个字节是“操作码”,后跟命令参数。 该命令可能没有参数,并且包含一个“操作码”。 使用FT_Write函数发送命令,使用FT_Write函数可以从MPSSE处理器获得响应。


在每个命令发送之后,读取处理器响应非常有用,因为在命令不正确的情况下,响应可能包含错误消息-字符0xFA。 “错误命令-0xFA响应”机制可用于将应用程序与MPSSE处理器同步。 如果一切正常,则芯片将在一个故意错误的命令上返回0xFA字符。 在MPSSE和MCU主机总线仿真模式的命令处理器中介绍了操作码。
配置MPSSE取决于设置I / O线的数据速率,方向和初始状态。
考虑设置MPSSE处理器的数据速率。 仅支持全速模式(FT2232 D )的芯片和具有高速(FT2232 H ,FT232H,FT4232H)的芯片的设置有些不同。 传统的FT2232D使用12MHz时钟,而现代的FT2232D使用60MHz。 因此,用于计算数据传输速率的公式为:



其中f core是FTDI 核心频率, Divisor是一个两字节的分频器,它实际上设置了数据时钟频率。
结果,如果分频器等于零,则最大数据传输速率将为30 Mbps,而最小数据传输速率将为分频器65535-458 bit / s。
我们将把除法器的计算委托给预处理器。 宏返回除数:


 #define FCORE 60000000ul #define MPSSE_DATA_SPEED_DIV(data_speed) ((FCORE/(2*data_speed)) -1) 

这两个宏分别返回除法器的高字节和低字节:


 #define MPSSE_DATA_SPEED_DIV_H(data_speed) ((MPSSE_DATA_SPEED_DIV(data_speed)) >> 8) #define MPSSE_DATA_SPEED_DIV_L(data_speed) \ (MPSSE_DATA_SPEED_DIV(data_speed) - (MPSSE_DATA_SPEED_DIV_H(data_speed)<< 8)) 

此外,应该注意的是,在与旧FT2232D兼容的现代芯片中,还有一个额外的5分频器,它将60 MHz变为12 MHz。 该分隔线默认情况下处于激活状态,在我们的情况下应将其关闭。
我们找到相应的操作码(0x8A)和头盔命令给处理器:


团队提交清单
 BYTE byOutputBuffer[8], byInputBuffer[8]; DWORD dwNumBytesToRead, dwNumBytesSent = 0, dwNumBytesRead = 0; byOutputBuffer[0] = 0x8A; ftStatus = FT_Write(ftHandle, byOutputBuffer, 1, &dwNumBytesSent); Sleep(2); // Wait for data to be transmitted and status ftStatus = FT_GetQueueStatus(ftHandle, &dwNumBytesToRead); ftStatus |= FT_Read(ftHandle, byInputBuffer, dwNumBytesToRead, &dwNumBytesRead); if (ftStatus != FT_OK) { printf("Error\r\n"); return FT_OTHER_ERROR; } else if (dwNumBytesToRead > 0) { printf("dwNumBytesToRead = %d:", dwNumBytesToRead); for ( int i = 0; i < dwNumBytesToRead; i++) printf (" %02Xh", byInputBuffer[i]); printf("\r\n"); return FT_INVALID_PARAMETER; } return FT_OK; 

作为实验,我们将发送值0xFE而不是实际的命令0x8A,该值与任何操作码(控制台输出)都不对应:


 dwNumBytesToRead = 2: FAh FEh 

处理器返回了两个字节,错误命令字节为0xFA,该错误命令的值。 因此,通过一次发送多个命令,我们不仅可以跟踪错误本身的事实,而且可以了解该错误发生在哪个团队中。
为了将来不再处理“魔术数字”,我们将以常量的形式格式化所有操作码,并将它们放置在单独的头文件中。
要完全配置模式,您需要指定I / O线的方向及其默认值。 我们来看一下连接图。 为了不弄乱已经很article肿的文章,我绘制了一个有趣的方案片段:



必须将DCLKDATA [0]nCONFIG线配置为输出,将nSTATUSCONF_DONE线配置为输入。 使用该图,我们确定线应具有的初始状态。 为了清楚起见,下表中总结了电路的引脚排列:


FPGA引脚引脚名称销钉MPSSE方向默认值
时钟BDBUS038TCK / SK0
数据[0]BDBUS139TDI /溶解氧1个
配置文件BDBUS240TDO / DI1个
状态BDBUS341TMS / CS1个
CONF_DONEBDBUS443GPIOL01个

所有使用的行都位于MPSSE端口的低字节上。 要设置该值,请使用操作码0x80。 该命令采用两个参数:操作码后的第一个字节是逐位值,第二个是方向(一个是输出端口,零是输入端口)。
作为与“幻数”斗争的一部分,所有序列号及其默认值都将格式化为常量:


定义端口
 #define PORT_DIRECTION (0x07) #define DCLK (0) #define DATA0 (1) #define N_CONFIG (2) #define N_STATUS (3) #define CONF_DONE (4) // initial states of the MPSSE interface #define DCLK_DEF (1) #define DATA0_DEF (0) #define N_CONFIG_DEF (1) #define N_STATUS_DEF (1) #define CONF_DONE_DEF (1) 

仅需确保已禁用TDI-TDO循环(可以激活以进行测试)并将其置于单独的功能中:


列出MPSSE_setup函数
 static FT_STATUS MPSSE_setup () { DWORD dwNumBytesToSend, dwNumBytesSent, dwNumBytesToRead, dwNumBytesRead; BYTE byOutputBuffer[8], byInputBuffer[8]; FT_STATUS ftStatus; // Multple commands can be sent to the MPSSE with one FT_Write dwNumBytesToSend = 0; // Start with a fresh index byOutputBuffer[dwNumBytesToSend++] = MPSSE_CMD_DISABLE_DIVIDER_5; byOutputBuffer[dwNumBytesToSend++] = MPSSE_CMD_DISABLE_ADAPTIVE_CLK; byOutputBuffer[dwNumBytesToSend++] = MPSSE_CMD_DISABLE_3PHASE_CLOCKING; ftStatus = FT_Write(ftHandle, byOutputBuffer, dwNumBytesToSend, &dwNumBytesSent); dwNumBytesToSend = 0; // Reset output buffer pointer // Set TCK frequency // Command to set clock divisor: byOutputBuffer[dwNumBytesToSend++] = MPSSE_CMD_SET_TCK_DIVISION; // Set ValueL of clock divisor: byOutputBuffer[dwNumBytesToSend++] = MPSSE_DATA_SPEED_DIV_L(DATA_SPEED); // Set 0xValueH of clock divisor: byOutputBuffer[dwNumBytesToSend++] = MPSSE_DATA_SPEED_DIV_H(DATA_SPEED); ftStatus |= FT_Write(ftHandle, byOutputBuffer, dwNumBytesToSend, &dwNumBytesSent); dwNumBytesToSend = 0; // Reset output buffer pointer // Set initial states of the MPSSE interface // - low byte, both pin directions and output values /* | FPGA pin | Pin Name | Pin | MPSSE | Dir | def | | --------- | -------- | --- | ------ | --- | --- | | DCLK | BDBUS0 | 38 | TCK/SK | Out | 0 | | DATA[0] | BDBUS1 | 39 | TDI/DO | Out | 1 | | nCONFIG | BDBUS2 | 40 | TDO/DI | Out | 1 | | nSTATUS | BDBUS3 | 41 | TMS/CS | In | 1 | | CONF_DONE | BDBUS4 | 43 | GPIOL0 | In | 1 | */ // Configure data bits low-byte of MPSSE port: byOutputBuffer[dwNumBytesToSend++] = MPSSE_CMD_SET_DATA_BITS_LOWBYTE; // Initial state config above: byOutputBuffer[dwNumBytesToSend++] = (DCLK_DEF << DCLK) | (DATA0_DEF << DATA0) | (N_CONFIG_DEF << N_CONFIG) | (N_STATUS_DEF << N_STATUS) | (CONF_DONE_DEF << CONF_DONE); // Direction config above: byOutputBuffer[dwNumBytesToSend++] = PORT_DIRECTION; ftStatus |= FT_Write(ftHandle, byOutputBuffer, dwNumBytesToSend, &dwNumBytesSent); // Send off the low GPIO config commands dwNumBytesToSend = 0; // Reset output buffer pointer // Set initial states of the MPSSE interface // - high byte, all input, Initial State -- 0. // Send off the high GPIO config commands: byOutputBuffer[dwNumBytesToSend++] = MPSSE_CMD_SET_DATA_BITS_HIGHBYTE; byOutputBuffer[dwNumBytesToSend++] = 0x00; byOutputBuffer[dwNumBytesToSend++] = 0x00; ftStatus |= FT_Write(ftHandle, byOutputBuffer, dwNumBytesToSend, &dwNumBytesSent); // Disable loopback: byOutputBuffer[dwNumBytesToSend++] = MPSSE_CMD_DISABLE_LOOP_TDI_TDO; ftStatus |= FT_Write(ftHandle, byOutputBuffer, dwNumBytesToSend, &dwNumBytesSent); Sleep(2); // Wait for data to be transmitted and status ftStatus = FT_GetQueueStatus(ftHandle, &dwNumBytesToRead); ftStatus |= FT_Read(ftHandle, byInputBuffer, dwNumBytesToRead, &dwNumBytesRead); if (ftStatus != FT_OK) { printf("Unknown error in initializing the MPSSE\r\n"); return FT_OTHER_ERROR; } else if (dwNumBytesToRead > 0) { printf("Error in initializing the MPSSE, bad code:\r\n"); for ( int i = 0; i < dwNumBytesToRead; i++) printf (" %02Xh", byInputBuffer[i]); printf("\r\n"); return FT_INVALID_PARAMETER; } return FT_OK; } 

第4点。我们实现了加载协议


一切似乎都可以进行实际实验了。 首先,检查初始化是否正确执行,在程序主体中,调用MPSSE_open()MPSSE_setup() ,然后在关闭设备( FT_Close )之前,放置一个空的getchar() 。 运行该程序并使用示波器确保所有PS线都设置为默认电平。 更改初始化中这些级别的值(FPGA不会发生任何不良情况),我们确保MPSSE处理器将期望的结果视为有效-一切正常,您可以继续传输数据。
数据的顺序发送和接收是在命令模式下使用相同的操作码执行的。 该命令的第一个字节是操作码,它确定操作的类型,然后是发送或接收的序列的长度,如果是发送,则是实际数据。 MPSSE处理器可以发送和接收数据,也可以同时进行。 传输可以是最低有效位转发(LSB)或最高有效位(MSB)。 数据传输可以发生在时钟脉冲的上升沿或下降沿。 每个选项组合都有其自己的操作码,每个操作码位都描述了操作模式:


功能介绍
0前写同步:0-正,1-负
1个1-使用字节,0-使用位
2读取的前边缘:0-正,1-负
3传输模式:1-LSB,0-MSB在前
4TDI数据传输
5从TDO线上读取数据
6TMS数据传输
7必须为0,否则这是另一组命令

当根据PS方案配置FPGA时,数据以LSB模式在前沿传输。 , , op-code 0001_1000b 0x18 . ( , ), . : . , , 0, 65536, 65535. , . MPSSE_send .


MPSSE_send
 static BYTE byBuffer[65536 + 3]; static FT_STATUS MPSSE_send(BYTE * buff, DWORD dwBytesToWrite) { DWORD dwNumBytesToSend = 0, dwNumBytesSent, bytes; FT_STATUS ftStatus; // Output on rising clock, no input // MSB first, clock a number of bytes out byBuffer[dwNumBytesToSend++] = MPSSE_CMD_LSB_DATA_OUT_BYTES_POS_EDGE; // 0x18 bytes = dwBytesToWrite -1; byBuffer[dwNumBytesToSend++] = (bytes) & 0xFF; // Length L byBuffer[dwNumBytesToSend++] = (bytes >> 8) & 0xFF; // Length H memcpy(&byBuffer[dwNumBytesToSend], buff, dwBytesToWrite); dwNumBytesToSend += dwBytesToWrite; ftStatus = FT_Write(ftHandle, byBuffer, dwNumBytesToSend, &dwNumBytesSent); if (ftStatus != FT_OK ) { printf ("ERROR send data\r\n"); return ftStatus; } else if (dwNumBytesSent != dwNumBytesToSend) { printf ("ERROR send data, %d %d\r\n", dwNumBytesSent, dwNumBytesToSend); } return FT_OK; } 

— 65 , - , op-code . byBuffer , buff , , op-code . , , .
, "" , 25 , , , 1 ( , #define DATA_SPEED 1000000ul ). :


 BYTE byOutputBuffer[] = {0x02, 0x1B, 0xEE, 0x01, 0xFA}; MPSSE_send(byOutputBuffer, sizeof(byOutputBuffer)); 

( ):


DATA[0] , — DCLK . . , , .


, SPI ( ). , PS, . nCONFIG , nSTATUS , CONF_DONE . — , , — , .


MPSSE_get_lbyte , , .


MPSSE_get_lbyte
 static FT_STATUS MPSSE_get_lbyte(BYTE *lbyte) { DWORD dwNumBytesToSend, dwNumBytesSent, dwNumBytesToRead, dwNumBytesRead; BYTE byOutputBuffer[8]; FT_STATUS ftStatus; dwNumBytesToSend = 0; byOutputBuffer[dwNumBytesToSend++] = MPSSE_CMD_GET_DATA_BITS_LOWBYTE; ftStatus = FT_Write(ftHandle, byOutputBuffer, dwNumBytesToSend, &dwNumBytesSent); Sleep(2); // Wait for data to be transmitted and status ftStatus = FT_GetQueueStatus(ftHandle, &dwNumBytesToRead); ftStatus |= FT_Read(ftHandle, lbyte, dwNumBytesToRead, &dwNumBytesRead); if ((ftStatus != FT_OK) & (dwNumBytesToRead != 1)) { printf("Error read Lbyte\r\n"); return FT_OTHER_ERROR; // Exit with error } return FT_OK; } 

, op-code , . , - , , . , . MPSSE_set_lbyte :


MPSSE_set_lbyte
 static FT_STATUS MPSSE_set_lbyte(BYTE lb, BYTE mask) { DWORD dwNumBytesToSend, dwNumBytesSent; BYTE byOutputBuffer[8], lbyte; FT_STATUS ftStatus; ftStatus = MPSSE_get_lbyte(&lbyte); if ( ftStatus != FT_OK) return ftStatus; // Set to zero the bits selected by the mask: lbyte &= ~mask; // Setting zero is not selected by the mask bits: lb &= mask; lbyte |= lb; dwNumBytesToSend = 0; // Set data bits low-byte of MPSSE port: byOutputBuffer[dwNumBytesToSend++] = MPSSE_CMD_SET_DATA_BITS_LOWBYTE; byOutputBuffer[dwNumBytesToSend++] = lbyte; byOutputBuffer[dwNumBytesToSend++] = PORT_DIRECTION; ftStatus = FT_Write(ftHandle, byOutputBuffer, dwNumBytesToSend, &dwNumBytesSent); if ((ftStatus != FT_OK) & (dwNumBytesSent != 1)) { printf("Error set Lbyte\r\n"); return FT_OTHER_ERROR; } return FT_OK; } 

, . : FTDI; MPSSE; rbf- , nCONFIG , N_STATUS ; rbf- ; , , CONF_DONE . , MPSSE FTDI . , nCONFIG "" , , , .


main
 int main(int argc, char *argv[]) { FT_STATUS ftStatus; BYTE lowByte; DWORD numDevs; // create the device information list if ( argv[1] == NULL) { printf ("NO file\r\n"); return -1; } frbf = fopen(argv[1],"rb"); if (frbf == NULL) { printf ("Error open rbf\r\n"); return -1; } ftStatus = FT_CreateDeviceInfoList(&numDevs); if ((numDevs == 0) || (ftStatus != FT_OK)) { printf("Error. FTDI devices not found in the system\r\n"); return -1; } ftStatus = MPSSE_open ("LESO7 B"); if (ftStatus != FT_OK) { printf("Error in MPSSE_open %d\n", ftStatus); EXIT(-1); } MPSSE_setup(); if (ftStatus != FT_OK) { printf("Error in MPSSE_setup %d\n", ftStatus); EXIT(-1); } printf ("nConfig -> 0\r\n"); MPSSE_set_lbyte(0, 1 << N_CONFIG); printf ("nConfig -> 1\r\n"); MPSSE_set_lbyte(1 << N_CONFIG, 1 << N_CONFIG); if (MPSSE_get_lbyte(&lowByte) != FT_OK) { EXIT(-1); } if (((lowByte >> N_STATUS) & 1) == 0) { printf("Error. FPGA is not responding\r\n"); EXIT(-1); } int i = 0; size_t readBytes = 0; // Send the configuration file: do { readBytes = fread(buff, 1, MPSSE_PCK_SEND_SIZE, frbf); if (MPSSE_send(buff, readBytes) != FT_OK) EXIT(-1); putchar('*'); if (!((++i)%16)) printf("\r\n"); } while (readBytes == MPSSE_PCK_SEND_SIZE); printf("\r\n"); memset(buff, 0x00, sizeof(buff)); MPSSE_send(buff, 1); //        ? printf("Load complete\r\n"); // wait CONF_DONE set // A low-to-high transition on the CONF_DONE pin indicates that the configuration is // complete and initialization of the device can begin. i = 0; do { if (MPSSE_get_lbyte(&lowByte) != FT_OK) { printf ("Error read CONF_DONE\r\n"); EXIT(-1); } if (i++ > TIMEOUT_CONF_DONE) { printf ("Error CONF_DONE\r\n"); EXIT(-1); } Sleep(2); } while (((lowByte >> CONF_DONE) & 1) == 0); printf("Configuration complete\r\n"); FT_Close(ftHandle); fclose(frbf); } 

启动程序示例:


 pen "LESO7 B" OK nConfig -> 0 nConfig -> 1 ** Load complete Configuration complete 

rbf- . . 30 / .
, - JTAG.


相关资料


  1. FTDI-MPSSE-Altera PS . .
  2. . . .
  3. Software Application Development D2XX Programmer's Guide . FTDI. API D2XX.
  4. FTDI MPSSE Basics. Application Note AN_135 . . FTDI MPSSE. .
  5. MPSSE和MCU主机总线仿真模式的命令处理器。应用笔记AN_108操作码参考。没有它没有办法。
  6. D2XX驱动程序FTDI驱动程序。

Source: https://habr.com/ru/post/zh-CN426131/


All Articles