
在每个抽屉的使用期内,都有一段时间要在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有关):
- 活动串行(AS)。
- 主动并行(AP)
- 无源串行(PS)
- 快速被动并行(FPP)。
- 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 ,用于控制的nCONFIG , nSTATUS和CONF_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模式的算法可以描述如下:
- 在系统中找到设备并打开它。
- 初始化芯片并将其置于MPSSE模式。
- 设置MPSEE的操作模式。
- 直接处理数据:传输,接收,管理GPIO-我们实现目标交换协议。
- 关闭设备。
编写一个引导程序
让我们开始实践。 在我的实验中,我将使用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;
欢迎我的动物园 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”。 打开它:
大多数API函数都会返回其调用类型为FT_STATUS
的状态,所有可能的值都在头文件中以枚举形式描述。 它们很多,但是足以知道值FT_OK
是没有错误,所有其他值都是错误代码。 良好的编程风格是在每次调用API函数后检查状态值。
如果设备已成功打开,则在ftHandle
变量ftHandle
显示除零以外的某个值,即一些等效的文件描述符,该文件描述符在处理文件时使用。 生成的句柄建立与硬件接口的连接,并在调用所有需要访问芯片的库函数时使用。
为了在实践中确认系统在当前阶段的可操作性,我们应该立即进行算法的第五步。
使用完芯片后,需要关闭它。 为此,请使用FT_Close
函数:
FT_Close(ftHandle);
点2.初始化芯片并打开MPSSE
该设置对于大多数模式来说都是典型的,并且在AN_135 FTDI MPSSE基础文档中有很好的描述。
- 我们执行芯片的重置(重设)。
FT_ResetDevice
函数。 - 如果接收缓冲区中有任何垃圾,我们将其清除。
FT_Purge
函数。 - 调整用于读取和写入的缓冲区的大小。 函数
FT_SetUSBParameters
。 - 关闭平价。
FT_SetChars
。 - 我们设置了读写超时。 默认情况下,超时是禁用的,启用传输超时。
FT_SetTimeouts
。 - 我们配置从芯片向主机发送数据包的等待时间。 默认情况下为16毫秒,加速到1毫秒。
FT_SetLatencyTimer
。 - 打开流控制以同步传入的请求。
FT_SetFlowControl
。 - 一切准备就绪,可以激活MPSSE模式。 重置MPSSE控制器。 我们使用
FT_SetBitMode
函数,将模式设置为0(模式= 0,掩码= 0)。 - 打开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);
项目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);
作为实验,我们将发送值0xFE而不是实际的命令0x8A,该值与任何操作码(控制台输出)都不对应:
dwNumBytesToRead = 2: FAh FEh
处理器返回了两个字节,错误命令字节为0xFA,该错误命令的值。 因此,通过一次发送多个命令,我们不仅可以跟踪错误本身的事实,而且可以了解该错误发生在哪个团队中。
为了将来不再处理“魔术数字”,我们将以常量的形式格式化所有操作码,并将它们放置在单独的头文件中。
要完全配置模式,您需要指定I / O线的方向及其默认值。 我们来看一下连接图。 为了不弄乱已经很article肿的文章,我绘制了一个有趣的方案片段:

必须将DCLK , DATA [0]和nCONFIG线配置为输出,将nSTATUS , CONF_DONE线配置为输入。 使用该图,我们确定线应具有的初始状态。 为了清楚起见,下表中总结了电路的引脚排列:
FPGA引脚 | 引脚名称 | 销钉 | MPSSE | 方向 | 默认值 |
---|
时钟 | BDBUS0 | 38 | TCK / SK | 出 | 0 |
数据[0] | BDBUS1 | 39 | TDI /溶解氧 | 出 | 1个 |
配置文件 | BDBUS2 | 40 | TDO / DI | 出 | 1个 |
状态 | BDBUS3 | 41 | TMS / CS | 在 | 1个 |
CONF_DONE | BDBUS4 | 43 | GPIOL0 | 在 | 1个 |
所有使用的行都位于MPSSE端口的低字节上。 要设置该值,请使用操作码0x80。 该命令采用两个参数:操作码后的第一个字节是逐位值,第二个是方向(一个是输出端口,零是输入端口)。
作为与“幻数”斗争的一部分,所有序列号及其默认值都将格式化为常量:
定义端口 #define PORT_DIRECTION (0x07) #define DCLK (0) #define DATA0 (1) #define N_CONFIG (2) #define N_STATUS (3) #define CONF_DONE (4)
仅需确保已禁用TDI-TDO循环(可以激活以进行测试)并将其置于单独的功能中:
列出MPSSE_setup函数 static FT_STATUS MPSSE_setup () { DWORD dwNumBytesToSend, dwNumBytesSent, dwNumBytesToRead, dwNumBytesRead; BYTE byOutputBuffer[8], byInputBuffer[8]; FT_STATUS ftStatus;
第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在前 |
4 | TDI数据传输 |
5 | 从TDO线上读取数据 |
6 | TMS数据传输 |
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;
— 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);
, 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;
, . : 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;
启动程序示例:
pen "LESO7 B" OK nConfig -> 0 nConfig -> 1 ** Load complete Configuration complete
rbf- . . 30 / .
, - JTAG.
相关资料
- FTDI-MPSSE-Altera PS . .
- . . .
- Software Application Development D2XX Programmer's Guide . FTDI. API D2XX.
- FTDI MPSSE Basics. Application Note AN_135 . . FTDI MPSSE. .
- MPSSE和MCU主机总线仿真模式的命令处理器。应用笔记AN_108。操作码参考。没有它没有办法。
- D2XX驱动程序。FTDI驱动程序。