Рейтинг@Mail.ru

Использование микросхемы FT2232H в режиме синхронной очереди (FIFO)

автор:
Be the first to comment! Программирование
Print Friendly, PDF & Email
В режиме одноканальной синхронной очереди (FIFO) микросхемы FT2232H способны обеспечить скорость передачи более 40 Мб/сек (или 320 мбит/сек). В статье рассматривается, как подключить и настроить микросхему FT2232H в режиме синхронного FIFO. В данном вопросе есть несколько подводных камней, которым уделено более подробное внимание. Будет написан и протестирован код для платформы .NET, который позволит извлечь из микросхемы FT2232H максимальную производительность.

Для проекта понадобится:

1Режим синхронного параллельного FIFOна микросхемах FT2232H

Режим синхронной параллельной очереди (или FIFO) позволяет достичь скорости передачи до 320 мбит/сек от источника этих данных в компьютер пользователя или иному потребителю. Синхронным этот режим называется потому, что обмен данными происходит строго по тактовым импульсам, которые генерирует микросхема FTDI. При этом тактовая частота CLKOUT постоянна и составляет 60 МГц. Параллельным режим называется потому что 8 бит данных выставляются одновременно на 8-ми линиях шины данных DATA.

Необходимо также помнить, что когда микросхема работает в данном режиме, её второй порт недоступен. Микросхема использует ресурсы обоих портов A и B.

Сначала рассмотрим режим приёма данных от внешнего устройства в FT2232H. На иллюстрации приведена диаграмма обмена. Видно, что кроме линий частоты CLKOUT и данных DATA используются ещё 2 линии: TXE# и WR#.

Диаграмма передачи данных из внешнего устройства в FT2232H в режиме синхронного параллельного FIFO
Диаграмма передачи данных из внешнего устройства в FT2232H в режиме синхронного параллельного FIFO

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

В свою очередь, при передаче данных от FT2232H во внешнее устройство, микросхема FT2232H сообщает о начале передачи с помощью ножки RXF#, а принимающее устройство на линиях RD# и OE# выставляет сигналы готовности или неготовности принимать данные.

Диаграмма передачи данных из FT2232H во внешнее устройство в режиме синхронного параллельного FIFO
Диаграмма передачи данных из FT2232H во внешнее устройство в режиме синхронного параллельного FIFO

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

Настройки программы MProg для работы в режиме FT245 sync FIFO
Настройки программы MProg для работы в режиме FT245 sync FIFO

Здесь можно ознакомиться с полным руководством к программе MProg.

Подключим плату к компьютеру. Перед дальнейшими действиями необходимо установить драйвер для микросхем FTDI, скачанный с официального сайта. Далее запустим программу MProg и нажмём на кнопку с лупой, чтобы обнаружить подключённые доступные микросхемы памяти. Если таковые обнаружились, выставим настройки, показанные на иллюстрации. Чтобы записать установленную конфигурацию в ПЗУ отладочной платы с FT2232H, нужно нажать на кнопку с молнией. После успешной прошивки микросхема FT2232H готова к программному конфигурированию режима.

2Передающая сторона на ПЛИС Altera Cyclone II

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

Отладочная плата с ПЛИС Cyclone II
Отладочная плата с ПЛИС Cyclone II

Собственно, код передатчика был написан именно для этой платы, и нумерация пинов соответствующая. Код здесь простейший. ПЛИС слушает входящую линию 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). Проверить это можно осциллографом.

Частота 60 МГц на ножке 35 микросхемы FT2232H - признак того, что микросхема работает в режиме синхронного параллельного FIFO
Частота 60 МГц на ножке 35 микросхемы FT2232H – признак того, что микросхема работает в режиме синхронного параллельного FIFO

Теперь микросхема настроена на работу, и можно принимать данные. Но ещё нужно обдумать несколько соображений.

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

Временные лаги при передаче данных из внешнего устройства в FT2232H в режиме синхронного параллельного FIFO
Временные лаги при передаче данных из внешнего устройства в FT2232H в режиме синхронного параллельного FIFO

Значит придётся использовать как минимум 2 потока: один для чтения данных из буфера FT2232H, второй – для сохранения данных на диск. Принятые данные нужно где-то хранить. Значит нужен какой-то временный промежуточный буфер, в который можно очень быстро помещать данные, а затем сохранять на диск с любой скоростью. Понятно, что если скорость сохранения на диск будет сильно отставать от скорости чтения, размер этого временного хранилища будет постоянно расти. Возникнет утечка памяти, которая рано или поздно приведёт к краху приложения. Поэтому необходим довольно мощный компьютер.

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

Непрерывный поток с постоянной скоростью при передаче данных из внешнего устройства в FT2232H в режиме синхронного параллельного FIFO
Непрерывный поток с постоянной скоростью при передаче данных из внешнего устройства в FT2232H в режиме синхронного параллельного FIFO

Также в качестве временного хранилища можно использовать кольцевой буфер. Он будет постоянного размера, что исключает возможность утечки памяти. Но если скорость чтения потока существенно выше скорости сброса данных на диск, принимаемые данные будут по кругу перезатирать имеющиеся данные в буфере, что приведёт к потере данных.

Какой вариант выбрать? Тут нужно отталкиваться от своей задачи и имеющегося аппаратного обеспечения. Я приведу оба варианта.

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

Принцип работы кольцевого буфера
Принцип работы кольцевого буфера

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

Разница в скорости потока при различных реализациях промежуточного FIFO
Разница в скорости потока при различных реализациях промежуточного FIFO

Поэтому было отдано предпочтение реализации с атрибутами <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.

Программа для приёма данных с помощью FT2232H в режиме синхронного параллельного FIFO
Программа для приёма данных с помощью FT2232H в режиме синхронного параллельного FIFO

По текущей длине FIFO в программе можно судить, успевает ли приём за скоростью передачи. Отрицательные значения длины будут говорить о переполнении FIFO и потере данных. В таком случае нужно либо запускать программу на более мощном компьютере, либо замедлять скорость потока от источника.

Last modified onСреда, 27 Июль 2022 11:24 Read 1562 times
Ключевые слова: :

Поделиться

Print Friendly, PDF & Email

Leave a comment