Использование микросхемы FT2232H в режиме синхронной очереди (FIFO)
Для проекта понадобится:
- плата с микросхемой FT2232H;
- отладочная плата с ПЛИС («Марсоход» или любая другая);
- соединительные провода;
- логический анализатор (не обязательно);
- компьютер или ноутбук.
1Режим синхронного параллельного FIFOна микросхемах FT2232H
Режим синхронной параллельной очереди (или FIFO) позволяет достичь скорости передачи до 320 мбит/сек от источника этих данных в компьютер пользователя или иному потребителю. Синхронным этот режим называется потому, что обмен данными происходит строго по тактовым импульсам, которые генерирует микросхема FTDI. При этом тактовая частота CLKOUT постоянна и составляет 60 МГц. Параллельным режим называется потому что 8 бит данных выставляются одновременно на 8-ми линиях шины данных DATA.
Необходимо также помнить, что когда микросхема работает в данном режиме, её второй порт недоступен. Микросхема использует ресурсы обоих портов A и B.
Сначала рассмотрим режим приёма данных от внешнего устройства в FT2232H. На иллюстрации приведена диаграмма обмена. Видно, что кроме линий частоты CLKOUT и данных DATA используются ещё 2 линии: TXE# и WR#.

Поток данных регулируется двумя управляющими линиями: TXE# и WR#. Линией TXE# управляет микросхема FT2232H. Когда эта линия в логическом нуле, FT2232H готова принимать данные, когда в единице – не готова. Линией WR# управляет передающее устройство. Аналогично, когда линия в "LOW", данные на шине данных валидны, а когда в "HIGH" – данные не валидны. Обмен происходит, когда обе линии находятся в логическом нуле.
В свою очередь, при передаче данных от FT2232H во внешнее устройство, микросхема FT2232H сообщает о начале передачи с помощью ножки RXF#, а принимающее устройство на линиях RD# и OE# выставляет сигналы готовности или неготовности принимать данные.

Для работы в режиме параллельного синхронного FIFO, микросхема FTDI требует не только программной настройки, но и правильно сконфигурированного ПЗУ. Для этого, прежде всего, на вашей плате должна присутствовать микросхема ПЗУ. Чтобы «прошить» ПЗУ, необходимо воспользоваться программой MProg (или более современной FT_Prog), которую предоставляет сама компания FTDI. Программу MProg можно скачать в приложении к статье. С официального сайта её убрали; но на мой взгляд, интерфейс у MProg удобнее и проще, поэтому я привожу пример с ней.

Здесь можно ознакомиться с полным руководством к программе MProg.
Подключим плату к компьютеру. Перед дальнейшими действиями необходимо установить драйвер для микросхем FTDI, скачанный с официального сайта. Далее запустим программу MProg и нажмём на кнопку с лупой, чтобы обнаружить подключённые доступные микросхемы памяти. Если таковые обнаружились, выставим настройки, показанные на иллюстрации. Чтобы записать установленную конфигурацию в ПЗУ отладочной платы с FT2232H, нужно нажать на кнопку с молнией. После успешной прошивки микросхема FT2232H готова к программному конфигурированию режима.
2Передающая сторона на ПЛИС Altera Cyclone II
Для тестирования приёма данных необходим какой-то передатчик, который сможет гарантированно выдавать данные со скоростью 40 Мб/сек. И тут выбор, конечно же, ложится на программируемую логику (ПЛИС). Я буду использовать отладочную плату A-C2FB с ПЛИС Cyclone II фирмы Altera, купленную когда-то давно за копейки на Aliexpress. Документацию на плату A-C2FB можно скачать в конце статьи. К сожалению, такие платы сейчас не найти, но есть похожие более новые платы с Cyclone IV.

Собственно, код передатчика был написан именно для этой платы, и нумерация пинов соответствующая. Код здесь простейший. ПЛИС слушает входящую линию TXE#. Если она в логическом нуле, то на каждый приходящий от FT2232H тактовый импульс ПЛИС инкрементирует 8-разрядный счётчик и выставляет его значение на параллельной шине. Если TXE# не выставлен, то счётчик не инкрементируется. Тактовую частоту выдаёт FT2232H, поэтому в коде передатчика заботиться о синхронизации не нужно. Также выведем на светодиоды индикацию TXE и WR чтобы видеть, когда происходит информационный обмен. На 7-сегментные индикаторы было бы интересно вывести текущую скорость (в этом коде не реализовано).
Код передатчика на Verilog (разворачивается)
module BOARD_TOP
(
//кварц на плате /
input wire clk_50mhz, //кварцевый генератор 50 МГц /
//светодиоды зажигаются значением "0" /
output wire [7:0] LED, //8 светодиодов, "=0" -> светится /
//кнопки /
input wire button1, //кнопка К1 "=0" -> нажата/
input wire button2, //кнопка К2 "=0" -> нажата/
input wire button3, //кнопка К3 "=0" -> нажата/
input wire button4, //кнопка К4 "=0" -> нажата/
//динамик
output wire buzzer, //"=1" -> выключен, "=0" -> звучит /
// СЕГМЕНТНЫЙ ИНДИКАТОР /
// базы транзисторов, питание индикаторов/
output wire SI_T_LED1, //"=1" -> не светится, "=0" -> светится /
output wire SI_T_LED2, //"=1" -> не светится, "=0" -> светится /
output wire SI_T_LED3, //"=1" -> не светится, "=0" -> светится /
output wire SI_T_LED4, //"=1" -> не светится, "=0" -> светится /
output wire SI_T_LED5, //"=1" -> не светится, "=0" -> светится /
output wire SI_T_LED6, //"=1" -> не светится, "=0" -> светится /
output wire SI_T_LED7, //"=1" -> не светится, "=0" -> светится /
output wire SI_T_LED8, //"=1" -> не светится, "=0" -> светится /
// "земли" сегментов
output wire SI_G_LEDA, //"=0" -> светится, "=1" -> не светится верхняя гор. /
output wire SI_G_LEDB, //"=0" -> светится, "=1" -> не светится верхняя верт. правая /
output wire SI_G_LEDC, //"=0" -> светится, "=1" -> не светится нижняя верт. правая /
output wire SI_G_LEDD, //"=0" -> светится, "=1" -> не светится нижняя гор. /
output wire SI_G_LEDE, //"=0" -> светится, "=1" -> не светится нижняя верт. левая /
output wire SI_G_LEDF, //"=0" -> светится, "=1" -> не светится верхняя верт. левая /
output wire SI_G_LEDG, //"=0" -> светится, "=1" -> не светится средняя гор. /
output wire SI_G_LEDDP, //"=0" -> светится, "=1" -> не светится точка /
// пины штыревого разъёма /
// левая сторона /
input wire PIN_24,
input wire PIN_26,
input wire PIN_27,
input wire PIN_31,
// --> PIN_40, //это button1 /
output wire PIN_42,
output wire PIN_44,
output wire PIN_47,
output wire PIN_51,
output wire PIN_53,
output wire PIN_57,
output wire PIN_59,
output wire PIN_63,
// --> PIN_65, //это LED[0] /
// правая сторона /
input wire PIN_25,
input wire PIN_28,
input wire PIN_30,
// --> PIN_32, //это buzzer /
input wire PIN_41,
// --> PIN_43, //это button1 /
// --> PIN_45, //это button4 /
// --> PIN_48, //это button2 /
input wire PIN_52,
// --> PIN_55, //это SI_G_LEDE /
// --> PIN_58, //это SI_G_LEDF /
// --> PIN_60, //это LED[7] /
input wire PIN_64,
input wire PIN_67
);
// ПЕРЕХОД ОТ НОТАЦИИ ПРОЕКТА К ВЫХОДНЫМ ПИНАМ /
//assign LED[0]=cnt_LED[0]; /
assign LED[1]=1'b1;
assign LED[2]=1'b1;
assign LED[3]=1'b1;
assign LED[4]=cnt_div1[23];
assign LED[5]=FTDI_TXE;
assign LED[6]=1'b1;
assign LED[7]=cnt_LED[0];
assign FTDI_clk_FTDI=PIN_27; //FTDI -> ПЛИС, несущая частота /
assign FTDI_TXE =PIN_31; //FTDI -> ПЛИС, "=0" -> разрешена выдача данных, "=1" - запрещена /
assign LED[0] =FTDI_WR; //ПЛИС -> FTDI, строб, "=0" -> данные достоверны /
assign PIN_42 =FTDI_data[0]; //ПЛИС -> FTDI, данные /
assign PIN_44 =FTDI_data[1]; //ПЛИС -> FTDI, данные /
assign PIN_47 =FTDI_data[2]; //ПЛИС -> FTDI, данные /
assign PIN_51 =FTDI_data[3]; //ПЛИС -> FTDI, данные /
assign PIN_53 =FTDI_data[4]; //ПЛИС -> FTDI, данные /
assign PIN_57 =FTDI_data[5]; //ПЛИС -> FTDI, данные /
assign PIN_59 =FTDI_data[6]; //ПЛИС -> FTDI, данные /
assign PIN_63 =FTDI_data[7]; //ПЛИС -> FTDI, данные /
assign buzzer=1'b1; // выключить звук /
assign SI_T_LED1=1'b1; //"=1" -> не светится, "=0" -> светится /
assign SI_T_LED2=1'b1; //"=1" -> не светится, "=0" -> светится /
assign SI_T_LED3=1'b1; //"=1" -> не светится, "=0" -> светится /
assign SI_T_LED4=1'b1; //"=1" -> не светится, "=0" -> светится /
assign SI_T_LED5=1'b1; //"=1" -> не светится, "=0" -> светится /
assign SI_T_LED6=1'b1; //"=1" -> не светится, "=0" -> светится /
assign SI_T_LED7=1'b1; //"=1" -> не светится, "=0" -> светится /
assign SI_T_LED8=1'b1; //"=1" -> не светится, "=0" -> светится /
assign SI_G_LEDA =1'b1; //"=0" -> светится, "=1" -> не светится /
assign SI_G_LEDB =1'b1; //"=0" -> светится, "=1" -> не светится /
assign SI_G_LEDC =1'b1; //"=0" -> светится, "=1" -> не светится /
assign SI_G_LEDD =1'b1; //"=0" -> светится, "=1" -> не светится /
assign SI_G_LEDE =1'b1; //"=0" -> светится, "=1" -> не светится /
assign SI_G_LEDF =1'b1; //"=0" -> светится, "=1" -> не светится /
assign SI_G_LEDG =1'b1; //"=0" -> светится, "=1" -> не светится /
assign SI_G_LEDDP=1'b1; //"=0" -> светится, "=1" -> не светится /
//=============================================================//
reg [23:0] cnt_div; //делитель частоты на счётчике /
reg [23:0] cnt_div1; //делитель частоты на счётчике для 60 МГц /
reg [07:0] cnt_LED; //мигатель светодиодами /
wire clk_3Hz; //частота ~2,9802 Гц /
// ИНТЕРФЕЙС С FTDI /
wire FTDI_clk_FTDI; //FTDI -> ПЛИС, несущая частота /
wire FTDI_TXE; //FTDI -> ПЛИС, "=0" -> разрешена выдача данных, "=1" - запрещена /
wire [07:0] FTDI_data; //ПЛИС -> FTDI, данные /
wire FTDI_WR; //ПЛИС -> FTDI, строб, "=0" -> данные достоверны /
// ФОРМИРОВАТЕЛЬ ЧАСТОТЫ МИГАТЕЛЯ /
assign clk_3Hz=cnt_div[23];
always @(posedge clk_50mhz)
begin
cnt_div<=cnt_div+1'b1;
end
// МИГАЛКА СВЕТОДИОДАМИ /
always @(posedge clk_3Hz)
begin
cnt_LED<=cnt_LED+1'b1;
end
// МИГАЛКА ДЛЯ 60 мгЦ ftdi /
always @(posedge FTDI_clk_FTDI)
begin
cnt_div1<=cnt_div1+1'b1;
end
// ПЕРЕДАТЧИК FTDI /
FTDI_8bit FTDI_8bit_Tx
(
FTDI_clk_FTDI, //частота от FTDI /
FTDI_TXE, //разрешение передачи от FTDI, строб, "=1" -> передача разрешена /
FTDI_data, //данные для FTDI /
FTDI_WR, //подтверждение достоверности для FTDI, строб, "=0" -> данные достоверны /
);
endmodule
/*
Простой передатчик параллельной шины FTDI
*/
module FTDI_8bit
(
input wire clk, //частота от FTDI
input wire TXE, //разрешение передачи от FTDI, строб, "=0" - передача разрешена
output wire [7:0] DATA, //данные для FTDI
output reg WR //подтверждение достоверности для FTDI, строб, "=0" - данные достоверны
);
assign DATA=Data2Tx;
reg [7:0] Data2Tx;
reg [8:0] cntTx;
always @(posedge clk)
begin
//Если FTDI разрешает передачу данных, то данные достоверны
WR<=TXE;
end
always @(posedge clk)
begin
//Если FTDI разрешает передачу данных И данные достоверны, то инкремент
if (!(WR | TXE))
Data2Tx<=Data2Tx+1'b1;
end
initial
begin
Data2Tx=8'd0;
cntTx=9'd0;
WR=1'b1;
end
endmodule
В приложении к статье можно скачать полный проект передатчика для IDE Quartus II.
Загрузим этот код в память ПЛИС и вернёмся к микросхеме FTDI.
3Программа для приёма данных от FT2232Hв режиме синхронного параллельного FIFO
На сайте FTDI представлен код оболочки на C# для библиотеки ftd2xx.dll и примеры работы с ней. Я переписал его на VB.NET, т.к. приёмник FT2232H будет на VB.NET. Код снабжён комментариями, листинг был подробно рассмотрен в предыдущей статье. Также его можно скачать на Гитхабе. Сейчас он нам понадобится.
Мы готовы для настройки микросхемы FT2232H в интересующем нас параллельном режиме. Используя вышеприведённый код класса-обёртки, инициализация режима параллельного FIFO происходит таким образом:
Dim ft As New FTD2XX_NET.Ftdi() ft.OpenByIndex(0) ft.ResetDevice() ft.SetBitMode(&HFF, FT_BIT_MODES.FT_BIT_MODE_SYNC_FIFO) ft.SetFlowControl(FT_FLOW_CONTROL.FT_FLOW_RTS_CTS) ft.SetLatency(2) 'минимально возможная задержка Dim usbBuf As Integer = &H10000 'рекомендуемый размер буфера 64кБ = 0x10000 (см. Application Note 130) ft.SetTransferSize(usbBuf, usbBuf) ft.Purge(FT_PURGE.FT_PURGE_RX Or FT_PURGE.FT_PURGE_TX)
Упомянутое примечание Application Note 130 подробно описывает работу микросхемы FT2232H в режиме параллельного FIFO.
Проверкой того, что FT2232H вошла в режим параллельного FIFO, является появление тактового сигнала на ножке CLKOUT (пин 35 микросхемы или ACBUS5). Проверить это можно осциллографом.

Теперь микросхема настроена на работу, и можно принимать данные. Но ещё нужно обдумать несколько соображений.
Мы хотим получить непрерывный поток с постоянной максимальной скоростью, который будем сохранять на жёсткий диск компьютера. Одним потоком в программе точно не обойтись, потому что время сохранения на диск сильно неопределённо (оно зависит от типа накопителя, его скорости, заполненности, фрагментации и т.д.). Из-за этого в программе будут возникать временные лаги при приёме данных. А это может сильно снизить скорость приёма данных. Пример таких провалов в скорости показан на рисунке ниже. Здесь показаны 2 канала: WR# и TXE#, которые управляют информационным потоком между устройствами. Видно, что FT2232H постоянно прерывает чтение во время сброса данных на компьютер. Длительность таких временных простоев может достигать десятков и даже сотен миллисекунд.

Значит придётся использовать как минимум 2 потока: один для чтения данных из буфера FT2232H, второй – для сохранения данных на диск. Принятые данные нужно где-то хранить. Значит нужен какой-то временный промежуточный буфер, в который можно очень быстро помещать данные, а затем сохранять на диск с любой скоростью. Понятно, что если скорость сохранения на диск будет сильно отставать от скорости чтения, размер этого временного хранилища будет постоянно расти. Возникнет утечка памяти, которая рано или поздно приведёт к краху приложения. Поэтому необходим довольно мощный компьютер.
В качестве такого быстрого временного хранилища можно использовать встроенный тип System.Collections.Concurrent.ConcurrentQueue(Of Byte), который появился в .NET, начиная с версии 4.5. Это потокобезопасная очередь (FIFO). Её преимущество в том, что размер хранилища будет динамически подстраиваться под текущие скорости приёма и записи данных на диск, а также она позволяет безопасно обращаться к ней из разных потоков приложения. Предвосхищая дальнейшее, диаграмма приёма данных при таком подходе у меня получилась следующая. На верхней части рисунка примерно тот же масштаб, что и на предыдущем (почувствуйте разницу!), а на нижней – в укрупнённом. Длительность небольших пауз между пачками порядка единиц микросекунд. Средняя скорость приёма данных при таком подходе у меня держалась постоянно в районе 44 Мб/сек (350 мбит/сек, на минуточку).

Также в качестве временного хранилища можно использовать кольцевой буфер. Он будет постоянного размера, что исключает возможность утечки памяти. Но если скорость чтения потока существенно выше скорости сброса данных на диск, принимаемые данные будут по кругу перезатирать имеющиеся данные в буфере, что приведёт к потере данных.
Какой вариант выбрать? Тут нужно отталкиваться от своей задачи и имеющегося аппаратного обеспечения. Я приведу оба варианта.
Для начала код кольцевого буфера. В конструктор передаётся число, которое будет задавать ограничение размера буфера. Создаётся массив заданного типа и заданной длины. Имеются два указателя: один на чтение, один на запись. При добавлении нового элемента в очередь указатель записи увеличивается на единицу. При извлечении элемента из очереди указатель чтения увеличивается на единицу. Если указатели поравнялись, значит очередь пуста. Если указатель записи превысил длину буфера, значит пошло перезаписывание и потеря ещё не вычитанных данных. Тут можно либо выбросить исключение и уведомить пользователя о повреждении данных, либо продолжать перезаписывать буфер как ни в чём не бывало.

Буфер не асинхронный, соответственно, операции добавления и удаления элементов придётся блокировать, чтобы избежать ситуации, когда два потока одновременно пытаются изменять элементы в буфере. Что может снижать общую скорость передачи на время блокировок. Я проверил данное предположение при нескольких реализациях блокировки: с помощью стандартного SyncLock, с помощью ReaderWriterLock и с помощью атрибута <MethodImpl()>. Иллюстрация ниже говорит сама за себя. Красными флажками отмечен интервал в 1 секунду.

Поэтому было отдано предпочтение реализации с атрибутами <MethodImpl()> у двух публичных методов (добавления элемента в очередь и извлечение из очереди).
Кольцевой буфер на VB.NET (разворачивается)
''' <summary>
''' Очередь (FIFO), реализованная в виде кольцевого буфера заданной длины.
''' </summary>
''' <remarks>
''' + Занимает постоянный объём памяти.
''' - Если скорость записи выше скорости опустошения, данные могут быть замещены новыми данными.
''' </remarks>
Public NotInheritable Class FixedCapacityFifo(Of T)
#Region "CTOR"
''' <summary>
''' Создаёт очередь заданной фиксированной длины <paramref name="capacity"/>.
''' </summary>
Public Sub New(capacity As Integer)
Me.Capacity = capacity
Buffer = New T(capacity - 1) {}
End Sub
#End Region '/CTOR
#Region "PROPS"
''' <summary>
''' Ёмкость очереди.
''' </summary>
Public ReadOnly Property Capacity As Integer
''' <summary>
''' Пуста ли очередь.
''' </summary>
Public ReadOnly Property IsFifoEmpty As Boolean
Get
Dim empty As Boolean = (WritePointer = ReadPointer)
Return empty
End Get
End Property
''' <summary>
''' Сколько элементов сейчас в очереди.
''' </summary>
Public ReadOnly Property Count As Integer
Get
Dim len As Integer = WritePointer - ReadPointer
Return len
End Get
End Property
#End Region '/PROPS
#Region "FIELDS"
''' <summary>
''' Буфер для хранения данных очереди.
''' </summary>
Private ReadOnly Buffer As T()
''' <summary>
''' исходное значение указателя.
''' </summary>
Private Const START_POINTER As Integer = -1
''' <summary>
''' Указатель на текущий элемент при записи.
''' </summary>
Private WritePointer As Integer = START_POINTER
''' <summary>
''' Указатель на текущий элемент при чтении.
''' </summary>
Private ReadPointer As Integer = START_POINTER
#End Region '/FIELDS
#Region "METHODS"
''' <summary>
''' Помещает элемент в очередь.
''' </summary>
<MethodImpl(MethodImplOptions.Synchronized)>
Public Sub Enqueue(b As T)
If (WritePointer >= (Capacity - 1)) Then 'выход за границы выделенной памяти
WritePointer = START_POINTER 'при выходе за границы начинается перетирание содержимого кольцевого буфера
'Можно выбрасывать исключение, чтобы пользователь знал о переполнении очереди.
End If
CheckPointersEquals()
WritePointer += 1
Buffer(WritePointer) = b
End Sub
''' <summary>
''' Пытается вычитать очередной доступный элемент.
''' При успехе возвращает True и присваивает значение элемента <paramref name="item"/>, иначе возвращает False.
''' </summary>
<MethodImpl(MethodImplOptions.Synchronized)>
Public Function TryDequeue(ByRef item As T) As Boolean
If (ReadPointer >= (Capacity - 1)) Then 'выход за границы выделенной памяти
ReadPointer = START_POINTER
End If
If (Not CheckPointersEquals()) Then
ReadPointer += 1
item = Buffer(ReadPointer)
Return True
End If
Return False
End Function
''' <summary>
''' Возвращает True если указатели равны. Это является признаком, что очередь пуста.
''' </summary>
''' <remarks>
''' Если указатели чтения и записи поравнялись, значит буфер был полностью вычитан. В этом случае указатели сбрасываются в исходное значение.
''' </remarks>
Private Function CheckPointersEquals() As Boolean
Dim fifoEmpty As Boolean = (WritePointer = ReadPointer)
If fifoEmpty Then
WritePointer = START_POINTER
ReadPointer = START_POINTER
End If
Return fifoEmpty
End Function
#End Region '/METHODS
End Class
Объединяя всё сказанное, напишем консольное приложение для приёма данных FT2232H в синхронном параллельном режиме.
Приёмник FT2232H в синхронном параллельном режиме (разворачивается)
Imports System.ComponentModel
Imports System.Threading
Imports System.Threading.Tasks
Imports FTD2XX_NET.Ftdi
Module FtReceiver
#Region "FIELDS"
'Если CONCURRENT, то используется встроенный тип Concurrent.ConcurrentQueue; иначе - моё собственное fifo.
'Плюс первого - приём данных без лагов в диаграмме (если позволяет скорость ПК). Если скорость ПК не позволяет, то возникает всё возрастающее потребление памяти.
'Плюс второго - постоянный объём используемой памяти. Но возникает потеря данных, если приём идёт быстрее, чем они успевают обрабатываться.
#Const CONCURRENT = True
''' <summary>
''' Промежуточный буфер для принимаемых из FTDI данных.
''' </summary>
#If CONCURRENT Then
Private DataFifo As Concurrent.ConcurrentQueue(Of Byte)
#Else
Private DataFifo As FixedCapacityFifo(Of Byte)
#End If
Private ReadOnly LogFileName As New IO.FileInfo($"ftdi_{Now:HH_mm_ss}.dat")
Private WithEvents CheckFifoLenTimer As New Timers.Timer(1000)
Private ExitCondition As Boolean = False
Private BytesReceived As Integer = 0
#End Region '/FIELDS
#Region "METHODS"
Sub Main()
Try
Console.ForegroundColor = ConsoleColor.Green
#If CONCURRENT Then
Console.WriteLine("FT245 PARALLEL SYNC FIFO RECEIVER (CONCURRENT QUEUE)")
DataFifo = New Concurrent.ConcurrentQueue(Of Byte)
#Else
Console.WriteLine("FT245 PARALLEL SYNC FIFO RECEIVER (CIRCULAR BUFFER)")
DataFifo = New FixedCapacityFifo(Of Byte)(1000000)
#End If
Console.Title = "FT245 PARALLEL SYNC FIFO RECEIVER BY AAVE @ SOLTAU.RU"
Console.WriteLine()
Console.ForegroundColor = ConsoleColor.Gray
Dim ft As New FTD2XX_NET.Ftdi()
Do
Dim devs As FT_DEVICE_INFO_NODE() = ft.GetDeviceList()
If (devs.Count > 0) Then
Console.WriteLine("Найдены устройства:")
For Each f As FT_DEVICE_INFO_NODE In devs
Console.WriteLine($"- {f.Description}")
Next
Console.WriteLine()
Try
Console.ForegroundColor = ConsoleColor.Gray
Console.Write("Введите индекс устройства: ")
Dim ind As Integer = 0
Dim s As String = Console.ReadLine()
If Integer.TryParse(s, ind) Then
ft.OpenByIndex(CUInt(ind))
Else
ft.OpenByIndex(0)
End If
AddHandler AppDomain.CurrentDomain.UnhandledException, AddressOf AppDomainUnhandledExceptionHandler
CheckFifoLenTimer.Start()
Threading.ThreadPool.QueueUserWorkItem(AddressOf SaveToFileWorkerWorks)
ft.ResetDevice()
Threading.Thread.Sleep(200)
ft.SetBitMode(&HFF, FT_BIT_MODES.FT_BIT_MODE_SYNC_FIFO)
ft.SetFlowControl(FT_FLOW_CONTROL.FT_FLOW_RTS_CTS)
ft.SetLatency(2)
Dim usbBuf As Integer = &H10000
ft.SetTransferSize(usbBuf, usbBuf) 'рекомендуемый размер буфера 0x10000 = 64кБ (AN130)
ft.Purge(FT_PURGE.FT_PURGE_RX Or FT_PURGE.FT_PURGE_TX)
Console.WriteLine($"Открыто устройство {ind}. Для остановки нажмите ""S"".")
Do
#If CONCURRENT Then
Dim chunkSize As Integer = CInt(usbBuf - (usbBuf >> 5)) 'см. AN232B-03 (x/64*2)
#Else
Dim chunkSize As Integer = CInt(1 * 1024)
#End If
Dim bytesInRxBuffer As Integer = ft.GetRxBytesAvailable()
If (bytesInRxBuffer >= chunkSize) Then
Dim bytes As Byte() = ft.Read(bytesInRxBuffer)
BytesReceived += bytes.Count()
For i As Integer = 0 To bytes.Length - 1
DataFifo.Enqueue(bytes(i))
Next
End If
If Console.KeyAvailable Then
Dim k = Console.ReadKey(True)
If (k.Key = ConsoleKey.S) Then
ExitCondition = True
End If
End If
Loop Until ExitCondition
Catch ex As Exception
Console.ForegroundColor = ConsoleColor.Red
Console.WriteLine(ex.Message)
Finally
ExitCondition = True
ft.ResetDevice()
ft.Close()
Console.WriteLine()
Console.WriteLine("Устройство закрыто.")
End Try
Else
Console.WriteLine("Устройства не найдены. Нажмите любую клавишу чтобы повторить.")
End If
Console.ReadKey(True)
Loop Until ExitCondition
ft.UnloadLibrary()
Catch ex As Exception
Console.ForegroundColor = ConsoleColor.Red
Console.WriteLine(ex.Message)
Finally
Console.ReadKey()
End Try
End Sub
Private Sub AppDomainUnhandledExceptionHandler(sender As Object, e As UnhandledExceptionEventArgs)
Try
Console.ForegroundColor = ConsoleColor.Red
Console.WriteLine("")
Console.WriteLine(CType(e.ExceptionObject, Exception).Message)
Using fs As New IO.FileStream("ft_receiver.txt", IO.FileMode.Append), sw As New IO.StreamWriter(fs)
sw.WriteLine(Now.ToString())
sw.WriteLine(CType(e.ExceptionObject, Exception).Message)
End Using
Catch ex As Exception
Console.WriteLine(ex.Message)
Finally
Console.ForegroundColor = ConsoleColor.Gray
ExitCondition = True
End Try
End Sub
''' <summary>
''' Проверяет наличие данных в FIFO. Сохраняет из FIFO в файл.
''' </summary>
Private Sub SaveToFileWorkerWorks(state As Object)
Using fs As New IO.FileStream(LogFileName.FullName, IO.FileMode.Create), bw As New IO.BinaryWriter(fs)
Do
Do Until DataFifo.IsEmpty
Dim b As Byte
If DataFifo.TryDequeue(b) Then
bw.Write(b)
End If
Loop
Loop Until ExitCondition
End Using
End Sub
Private Sub TmrTick(sender As Object, e As Timers.ElapsedEventArgs) Handles CheckFifoLenTimer.Elapsed
Dim numline As Integer = 9
Console.CursorTop = numline
Console.CursorLeft = 0
For i As Integer = 0 To Console.BufferWidth * 2 - 1
Console.Write(" "c)
Next
Console.ForegroundColor = ConsoleColor.Gray
Console.CursorTop = numline
Console.Write("Текущая длина FIFO, байт: ")
Console.ForegroundColor = ConsoleColor.Cyan
Console.WriteLine(DataFifo.Count)
Console.ForegroundColor = ConsoleColor.Gray
Console.Write("Скорость, мб/с: ")
Console.ForegroundColor = ConsoleColor.Cyan
Dim speed As Double = BytesReceived / (60 * 1024 * 1024) 'b -> mb/sec
Console.WriteLine($"{speed:F3}")
BytesReceived = 0
If ExitCondition AndAlso (DataFifo.Count = 0) Then
CheckFifoLenTimer.Stop()
End If
End Sub
#End Region '/METHODS
End Module
В приложении к статье можно скачать архив, в котором находятся скомпилированные программы для приёма и для передачи, а также библиотека ftd2xx.dll. Напоминаю, что для использования программ должен быть установлен .NET версии 4.5, а также в директории c:\Temp должна лежать библиотека ftd2xx.dll.

По текущей длине FIFO в программе можно судить, успевает ли приём за скоростью передачи. Отрицательные значения длины будут говорить о переполнении FIFO и потере данных. В таком случае нужно либо запускать программу на более мощном компьютере, либо замедлять скорость потока от источника.
Скачать вложения:
- Скачать программу MProg 3.5 (553 Скачиваний)
- Скачать документацию на плату A-C2FB с ПЛИС Cyclone II (443 Скачиваний)
- Скачать приложение для передачи данных FT245 sync FIFO (603 Скачиваний)
- Проект передатчика для Quartus 11 (432 Скачиваний)
