На плате Марсоход3 есть ПЛИС Altera MAX10 и чип FTDI FT2232H.
Для скоростной передачи данных в ПЛИС платы Марсоход3 будем использовать режим синхронного FIFO микросхемы FTDI.
Вся подготовительная работа для этого проекта описана в моей предыдущей статье. Там есть описание, как запрограммировать микросхему памяти EEPROM на плате, с тем, чтобы перевести FT2232H в режим синхронного ФИФО и там есть описание, как поставить перемычки (jumpers) и подключить второй программатор для загрузки проекта в ПЛИС и для отладки проекта.
Теперь рассмотрим сигналы, используемые для передачи данных из FTDI чипа в ПЛИС.
Сама микросхема FT2232H имеет два канала приема-прередачи, но в режиме синхронного FIFO может быть использован только первый канал А. Это видно из документации на микросхему http://www.ftdichip.com/Support/Documents/DataSheets/ICs/DS_FT2232H.pdf
По схеме платы Марсоход3 видно, что все сигналы канала А микросхемы FT2232H идут или могут быть направлены с помощью перемычек к ПЛИС MAX10. Сигналы микросхемы вида ADBUSx и ACBUSx поименованы на схеме платы в FTDI_ADx и FTDI_ACx соответственно. В проекте Альтера Quartus II эти же сигналы имеют имена FTD[7..0] и FTC[7..0].
Как видно из приведенной выше таблицы сигналы шины ADBUS и ACBUS могут иметь разное назначение в зависимости от выбранного режима работы микросхемы. Нас интересует режим 245 FIFO SYNC, то есть второй столбик таблицы. Режим синхронного ФИФО включается автоматически, если правильно запрограммирована внешняя память EEPROM.
Рассмотрим передачу данных по USB2 из компьютера/ноутбука в ПЛИС платы Марсоход3.
Согласно документации FTDI http://www.ftdichip.com/Support/Documents/AppNotes/AN_130_FT2232H_Used_In_FT245%20Synchronous%20FIFO%20Mode.pdf нас должны интересовать сигналы:
- CLKOUT - в режиме синхронного ФИФО это сигнал тактовой частоты 60МГц из микросхемы FTDI;
- RXF# - в FIFO микросхемы FTDI есть данные доступные для чтения, когда этот сигнал переходит в ноль;
- OE# - когда мы собираемся читать данные из FTDI чипа, то опускаем этот сигнал в ноль, чтобы включить его выходные буферы;
- RD# - опускаем в ноль, когда читаем данные из микросхемы FTDI.
Сигнал OE# должен быть установлен хотя бы на один такт раньше, чем RD#.
Фиксировать принимаемые данные можно только когда и сигналы RXF# и RD# активны, то есть оба находятся в нуле.
Вот как нарисованы временные диаграммы в документации FTDI:
Чтобы реализовать такую логику мы сделаем в проекте в ПЛИС вот такую схему:
Я пока не собираюсь в этом проекте что-то делать с принимаемыми данными, просто буду выводить их на 8 светодиодов платы. У меня сейчас только одна цель - посмотреть как работает передача данных и с какой скоростью я смогу передавать их из ПК в плату.
В схеме управляющая логика сделана очень просто: два тригера в цепочке формируют из входного сигнала RXF# два выходных сигнала OE# и RD# задержанных на 1 и 2 такта соответственно. То есть, как только в FIFO микросхемы FTDI появляются доступные для чтения данные, так сразу и начинаем их читать. Принятые входные данные на шине FTD[7..0] фиксируются в регистре, а с регистра данные идут на светодиоды платы.
Вот пожалуй и все с проектом для ПЛИС.
Вот только если мы хотим увидеть временные диаргаммы сигналов, то можно еще подключить инструмент Altera SignalTap.
Теперь вторая часть проекта - нужно написать для компьютера программу, которая будет отправлять данные в ПЛИС через USB2, через микросхему FTDI.
// leds.cpp: определяет точку входа для консольного приложения. // #include "stdafx.h" #include #include #include "ftd2xx.h" FT_HANDLE ftHandle; // Handle of the FTDI device FT_STATUS ftStatus; // Result of each D2XX call DWORD dwNumDevs; // The number of devices DWORD dwNumBytesToRead = 0; // Number of bytes available to read in the driver's input buffer DWORD dwNumBytesRead; unsigned char byInputBuffer[1024]; // Buffer to hold data read from the FT2232H DWORD dwNumBytesSent; DWORD dwNumBytesToSend; unsigned char byOutputBuffer[1024]; // Buffer to hold MPSSE commands and data to be sent to the FT2232H int ft232H = 0; // High speed device (FTx232H) found. Default - full speed, i.e. FT2232D DWORD dwClockDivisor = 0; DWORD dwCount; int ftdi_init() { FT_DEVICE ftDevice; DWORD deviceID; char SerialNumber[16+1]; char Description[64+1]; // Does an FTDI device exist? printf("Checking for FTDI devices...\n"); ftStatus = FT_CreateDeviceInfoList(&dwNumDevs); // Get the number of FTDI devices if (ftStatus != FT_OK) // Did the command execute OK? { printf("Error in getting the number of devices\n"); return 1; // Exit with error } if (dwNumDevs < 1) // Exit if we don't see any { printf("There are no FTDI devices installed\n"); return 1; // Exist with error } printf("%d FTDI devices found - the count includes individual ports on a single chip\n", dwNumDevs); ftHandle=NULL; //go thru' list of devices for(int i=0; i<dwNumDevs; i++) { printf("Open port %d\n",i); ftStatus = FT_Open(i, &ftHandle); if (ftStatus != FT_OK) { printf("Open Failed with error %d\n", ftStatus); printf("If runing on Linux then try first\n"); continue; } FT_PROGRAM_DATA ftData; char ManufacturerBuf[32]; char ManufacturerIdBuf[16]; char DescriptionBuf[64]; char SerialNumberBuf[16]; ftData.Signature1 = 0x00000000; ftData.Signature2 = 0xffffffff; ftData.Version = 0x00000003; //3 = FT2232H extensions ftData.Manufacturer = ManufacturerBuf; ftData.ManufacturerId = ManufacturerIdBuf; ftData.Description = DescriptionBuf; ftData.SerialNumber = SerialNumberBuf; ftStatus = FT_EE_Read(ftHandle,&ftData); if (ftStatus == FT_OK) { printf("\tDevice: %s\n\tSerial: %s\n", ftData.Description, ftData.SerialNumber); printf("\tDevice Type: %02X\n", ftData.IFAIsFifo7 ); break; } else { printf("\tCannot read ext flash\n"); } } if(ftHandle==NULL) { printf("NO FTDI chip with FIFO function\n"); return -1; } //ENABLE SYNC FIFO MODE ftStatus |= FT_SetBitMode(ftHandle, 0xFF, 0x00); ftStatus |= FT_SetBitMode(ftHandle, 0xFF, 0x40); if (ftStatus != FT_OK) { printf("Error in initializing1 %d\n", ftStatus); FT_Close(ftHandle); return 1; // Exit with error } UCHAR LatencyTimer = 2; //our default setting is 2 ftStatus |= FT_SetLatencyTimer(ftHandle, LatencyTimer); ftStatus |= FT_SetUSBParameters(ftHandle,0x10000,0x10000); ftStatus |= FT_SetFlowControl(ftHandle,FT_FLOW_RTS_CTS,0x10,0x13); if (ftStatus != FT_OK) { printf("Error in initializing2 %d\n", ftStatus); FT_Close(ftHandle); return 1; // Exit with error } //return with success return 0; } int _tmain(int argc, _TCHAR* argv[]) { if( ftdi_init() ) { printf("Cannot init FTDI chip\n"); return -1; } byOutputBuffer[0]=0x55; FT_Write(ftHandle, byOutputBuffer, 1, &dwNumBytesSent); byOutputBuffer[0]=0x33; FT_Write(ftHandle, byOutputBuffer, 1, &dwNumBytesSent); byOutputBuffer[0]=0xaa; FT_Write(ftHandle, byOutputBuffer, 1, &dwNumBytesSent); byOutputBuffer[0]=0x69; FT_Write(ftHandle, byOutputBuffer, 1, &dwNumBytesSent); #define BLOCK_LEN (4096*16) unsigned char sbuf[BLOCK_LEN]; for(int i=0; i<BLOCK_LEN; i++) sbuf[i] = (i & 0xff); DWORD ticks = GetTickCount(); int sz = 0; while(1) { FT_Write(ftHandle,sbuf,BLOCK_LEN,&dwNumBytesSent); sz += BLOCK_LEN; DWORD t = GetTickCount(); if( (t-ticks) >= 1000) { //one second lapsed printf("sent %d bytes/sec\n",sz); ticks = t; sz = 0; } } return 0; }
Пример такой программы приведен в документации FTDI. Я использовал его за основу, только чуть-чуть видоизменил ее.
Программа ищет подключенные FTDI чипы на шине USB, выбирает чип у которого имеется EEPROM - это мой признак, что именно этот чип сейчас используется для передачи данных.
Тут такая небольшая проблема - я подключаю к компьютеру два одинаковых программатора, построенных на одном и том же чипе. Один программатор используется для передачи данных, он установлен на плате Марсоход3. Второй программатор MBFTDI сейчас используется как JTAG для загрузки и отладки проекта. Вот поэтому есть небольшая сложность - в Quartus II Programmer или SignalTap нужно выбрать "правильный" экземпляр программатора (то есть тот, который загружает проект). И программа, передающая данные, должна выбрать "правильный" экземпляр программатора, тот который в режиме синхронного ФИФО, тот который с EEPROM.. Это решаемые проблемы - просто попробуйте сперва загрузиить проект в ПЛИС и поймете какой программатор работает как JTAG, а какой не работает. Программа, передающая данные сама найдет нужный программатор..
Вот теперь, если запустить программу в Visual Studio под отладчиком, то можно пройти и посмотреть, как программа инициализирует FTDI чип, как пишет в него данные. Сперва пишет несколько одиночных байт. Можно посмотреть в Altera SignalTap, как выглядят сигналы:
Выглядят они точно так же, как написано в документации FTDI - актуальная запись происходит, когда в нуле и сигнал RXF# и сигнал RD#. Опять же - при одиночной записи хотя бы на светодиодах видно какое число записалось в регистр.
Дальше, если отпустить программу из пошаговой отладки в простое исполнение, то в сигналтап можно увидеть уже блоки передачи данных:
Видно, что между блоками передачи явно видны паузы.
К сожалению, хоть тактовая частота передачи и 60МГц, передавать 60Мегабайт в секунду (полные 480МБит/сек) не получится. Почему не получается приблизиться к заветным 60МБайт/сек написано, например, в этой статье: http://electronics.stackexchange.com/questions/24700/why-are-usb-devices-slower-than-480-mbit-s
Программа, передающая данные производит измерение пропускной способности:
Видно, что в плату Марсоход3 в режиме синхронного FIFO микросхемы FTDI FT2232H можно передавать около
35 МБайт/сек.
На самом деле - это очень не плохо.. На плате Марсоход2, там где не было режима синхронного FIFO мне удалось прокачать в 10 раз меньше, всего 3,5МБайта в секунду... Там использовался режим FTDI MPSSE.
Весь проект для платы Марсоход3/Марсоход3bis можно взять здесь:
Там же в архиве есть проект программы для ПК выполненный в среде Visual Studio 2013.
Подробнее...