Использование микросхемы 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 и потере данных. В таком случае нужно либо запускать программу на более мощном компьютере, либо замедлять скорость потока от источника.
Download attachments:
- Скачать программу MProg 3.5 (372 Downloads)
- Скачать документацию на плату A-C2FB с ПЛИС Cyclone II (300 Downloads)
- Скачать приложение для передачи данных FT245 sync FIFO (434 Downloads)
- Проект передатчика для Quartus 11 (273 Downloads)