Для одного из будущих проектов (если он получится) мне нужно передавать данные в плату и из платы Марсоход2 на весьма большой скорости.
Самый простой вариант обмена данными между FPGA Cyclone III платы Марсоход2 и компьютером - через USB порт, через виртуальный последовательный порт, который уже реализован на нашей плате с помощью микросхемы FTDI FT2232H. Максимальная скорость передачи данных последовательного порта FTDI - это 12Мбит/секунду. Это довольно приличная скорость, но мне этого мало. Если учесть, что при последовательной передаче у каждого передаваемого
байта есть еще старт бит и стоп бит, то получается максимум 1,2Мбайта/секунду.
Можно ли передавать быстрее?
Оказывается, передавать быстрее можно, если использовать API функции FTDI, если запрограммировать внутренний контроллер микросхемы FTDI, так называемый MPSSE (Multi Purpose Synchronous Serial Engine).
Вот я и сделал этот проект. Его цель - убедиться в том, что передавать данные в плату Марсоход2 можно довольно быстро.
Первое. Сделал простой проект Altera Quartus II для платы Марсоход2, Cyclone III принимает скоростные данные от компьютера.
В этом проекте четыре линии, идущие от микросхемы FTDI к ПЛИС Cyclone III, интерпретируются как линии интерфейса JTAG: TCK, TDI, TDO, TMS. Собственно для приема данных от ПК меня интересуют данные на сигнале TDI тактируемые сигналом TCK. Правда я еще использую сигнал TMS для сброса, инициализации приемника перед каждым блоком данных. По описанию микросхемы FTDI сигнал TCK может быть до 30Мгц, значит эта частота определяет теоретический предел скорости передачи на одном канале FTDI FT2232H.
Модуль fast_sio, написан на Verilog, принимает последовательные данные и отдает уже принятые данные на шину data[7..0] в момент сигнала data_rdy.
Модуль checker - это просто тестовый модуль, он измеряет количество принятых байт в секунду - результат на шине data_cnt[23..0].
Еще модуль checker подсчитывает количество ошибок приема данных. Я для целей тестирования хочу передавать возрастающие байты-числа: 1, 2, 3, 4... Таким образом, приемник всегда знает какой ожидается следующий принимаемый байт и может сравнить его с принятым.
Второе. Написал программу на C++ для компьютера. Я использую Visual Studio 2012.
Программирую микросхему FTDI и ее интерфейс MPSSE и в цикле постоянно записываю блоки данных в канал передачи данных.
Вот программа:
// leds.cpp: определяет точку входа для консольного приложения. // #include "stdafx.h" #include <string.h> #include <windows.h> #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); // Open the port - For this application note, assume the first device is a FT2232H or FT4232H // Further checks can be made against the device descriptions, locations, serial numbers, etc. // before opening the port. printf("Assume first device has the MPSSE and open it...\n"); ftStatus = FT_Open(1, &ftHandle); if (ftStatus != FT_OK) { printf("Open Failed with error %d\n", ftStatus); printf("If runing on Linux then try <rmmod ftdi_sio> first\n"); return 1; // Exit with error } memset(SerialNumber, 0, sizeof(SerialNumber)); memset(Description, 0, sizeof(Description)); // Detecting device type, hi-speed or full-speed ftStatus = FT_GetDeviceInfo( ftHandle, &ftDevice, &deviceID, SerialNumber, Description, NULL ); ft232H = 0; // deviceID contains encoded device ID // SerialNumber, Description contain 0-terminated strings if (ftStatus == FT_OK) { printf("Device: %s\nSerial: %s\n", Description, SerialNumber); if (/* ftDevice == FT_DEVICE_232H || */ ftDevice == FT_DEVICE_4232H || ftDevice == FT_DEVICE_2232H) { printf("Hi-speed device (FT232H, FT2232H or FT4232H) detected\n"); ft232H = 1; } } else { printf("FT_GetDeviceType FAILED!\n"); } // Configure port parameters printf("Configuring port for MPSSE use...\n"); //Reset USB device ftStatus |= FT_ResetDevice(ftHandle); //Purge USB receive buffer first by reading out all old data from FT2232H receive buffer // Get the number of bytes in the FT2232H receive buffer ftStatus |= FT_GetQueueStatus(ftHandle, &dwNumBytesToRead); //Read out the data from FT2232H receive buffer if ((ftStatus == FT_OK) && (dwNumBytesToRead > 0)) FT_Read(ftHandle, &byInputBuffer, dwNumBytesToRead, &dwNumBytesRead); //Set USB request transfer sizes to 64K ftStatus |= FT_SetUSBParameters(ftHandle, 65536, 65535); //Disable event and error characters ftStatus |= FT_SetChars(ftHandle, false, 0, false, 0); //Sets the read and write timeouts in milliseconds ftStatus |= FT_SetTimeouts(ftHandle, 0, 5000); //Set the latency timer (default is 16mS) ftStatus |= FT_SetLatencyTimer(ftHandle, 16); ftStatus |= FT_SetBitMode(ftHandle, 0x0, 0x00); //Reset controller ftStatus |= FT_SetBitMode(ftHandle, 0x0, 0x02); //Enable MPSSE mode if (ftStatus != FT_OK) { printf("Error in initializing the MPSSE %d\n", ftStatus); FT_Close(ftHandle); return 1; // Exit with error } //return with success return 0; } int configure_mpsse() { unsigned char bCommandEchod; // ----------------------------------------------------------- // Synchronize the MPSSE by sending a bogus opcode (0xAA), // The MPSSE will respond with "Bad Command" (0xFA) followed by // the bogus opcode itself. // ----------------------------------------------------------- // Reset output buffer pointer dwNumBytesToSend=0; //Add bogus command 'xAA' to the queue byOutputBuffer[dwNumBytesToSend++] = 0xAA; // Send off the BAD commands ftStatus = FT_Write(ftHandle, byOutputBuffer, dwNumBytesToSend, &dwNumBytesSent); do { // Get the number of bytes in the device input buffer ftStatus = FT_GetQueueStatus(ftHandle, &dwNumBytesToRead); } while ((dwNumBytesToRead == 0) && (ftStatus == FT_OK)); //Read out the data from input buffer ftStatus = FT_Read(ftHandle, &byInputBuffer, dwNumBytesToRead, &dwNumBytesRead); //Check if Bad command and echo command received bCommandEchod = false; for (dwCount = 0; dwCount < dwNumBytesRead - 1; dwCount++) { if ((byInputBuffer[dwCount] == 0xFA) && (byInputBuffer[dwCount+1] == 0xAA)) { bCommandEchod = true; break; } } if (bCommandEchod == false) { printf("Error in synchronizing the MPSSE\n"); FT_Close(ftHandle); return 1; // Exit with error } // ----------------------------------------------------------- // Configure the MPSSE settings for JTAG // Multiple commands can be sent to the MPSSE with one FT_Write // ----------------------------------------------------------- // Set up the Hi-Speed specific commands for the FTx232H // Start with a fresh index dwNumBytesToSend = 0; if (ft232H) { // Use 60MHz master clock (disable divide by 5) byOutputBuffer[dwNumBytesToSend++] = 0x8A; // Turn off adaptive clocking (may be needed for ARM) byOutputBuffer[dwNumBytesToSend++] = 0x97; // Disable three-phase clocking byOutputBuffer[dwNumBytesToSend++] = 0x8D; ftStatus = FT_Write(ftHandle, byOutputBuffer, dwNumBytesToSend, &dwNumBytesSent); } // Send off the HS-specific commands dwNumBytesToSend = 0; // Set initial states of the MPSSE interface - low byte, both pin directions and output values // Pin name Signal Direction Config Initial State Config // ADBUS0 GPIOL0 input 0 0 // ADBUS1 GPIOL1 input 0 0 // ADBUS2 GPIOL2 input 0 0 // ADBUS3 GPIOL3 input 0 0 // ADBUS4 GPIOL4 input 0 0 // ADBUS5 GPIOL5 input 0 0 // ADBUS6 GPIOL6 input 0 0 // ADBUS7 GPIOL7 input 0 0 // Set data bits low-byte of MPSSE port byOutputBuffer[dwNumBytesToSend++] = 0x80; // Initial state config above byOutputBuffer[dwNumBytesToSend++] = 0x00; // Direction config above byOutputBuffer[dwNumBytesToSend++] = 0x0B; ftStatus = FT_Write(ftHandle, byOutputBuffer, dwNumBytesToSend, &dwNumBytesSent); // Send off the low GPIO config commands dwNumBytesToSend = 0; // Set initial states of the MPSSE interface - high byte, both pin directions and output values // Pin name Signal Direction Config Initial State Config // ACBUS0 GPIOH0 input 0 0 // ACBUS1 GPIOH1 input 0 0 // ACBUS2 GPIOH2 input 0 0 // ACBUS3 GPIOH3 input 0 0 // ACBUS4 GPIOH4 input 0 0 // ACBUS5 GPIOH5 input 0 0 // ACBUS6 GPIOH6 input 0 0 // ACBUS7 GPIOH7 input 0 0 // Set data bits low-byte of MPSSE port byOutputBuffer[dwNumBytesToSend++] = 0x82; // Initial state config above byOutputBuffer[dwNumBytesToSend++] = 0x00; // Direction config above byOutputBuffer[dwNumBytesToSend++] = 0x00; ftStatus = FT_Write(ftHandle, byOutputBuffer, dwNumBytesToSend, &dwNumBytesSent); // Send off the high GPIO config commands dwNumBytesToSend = 0; // Set TCK frequency // TCK = 60MHz /((1 + [(1 +0xValueH*256) OR 0xValueL])*2) //Command to set clock divisor byOutputBuffer[dwNumBytesToSend++] = 0x86; //Set 0xValueL of clock divisor byOutputBuffer[dwNumBytesToSend++] = dwClockDivisor & 0xFF; //Set 0xValueH of clock divisor byOutputBuffer[dwNumBytesToSend++] = (dwClockDivisor >> 8) & 0xFF; // Send off the clock divisor commands ftStatus = FT_Write(ftHandle, byOutputBuffer, dwNumBytesToSend, &dwNumBytesSent); dwNumBytesToSend = 0; // Disable internal loop-back // Disable loopback byOutputBuffer[dwNumBytesToSend++] = 0x85; // Send off the loopback command ftStatus = FT_Write(ftHandle, byOutputBuffer, dwNumBytesToSend, &dwNumBytesSent); return ftStatus; } void set_bits(unsigned char dir, unsigned char val) { // Send off the low GPIO config commands dwNumBytesToSend = 0; // Set data bits low-byte of MPSSE port byOutputBuffer[dwNumBytesToSend++] = 0x80; // Initial state config above byOutputBuffer[dwNumBytesToSend++] = val; // Direction config above byOutputBuffer[dwNumBytesToSend++] = dir; ftStatus = FT_Write(ftHandle, byOutputBuffer, dwNumBytesToSend, &dwNumBytesSent); } int read_bits(unsigned char* presult) { dwNumBytesToSend = 0; if(presult) *presult = 0; // force read status byOutputBuffer[dwNumBytesToSend++] = 0x81; //complete wr buffer with fast reply command byOutputBuffer[dwNumBytesToSend++] = 0x87; ftStatus = FT_Write(ftHandle, byOutputBuffer, dwNumBytesToSend, &dwNumBytesSent); if( ftStatus != FT_OK || dwNumBytesSent != dwNumBytesToSend ) return 1; //error //wait for answer do { // Get the number of bytes in the device input buffer ftStatus = FT_GetQueueStatus(ftHandle, &dwNumBytesToRead); } while ((dwNumBytesToRead == 0) && (ftStatus == FT_OK)); if( ftStatus != FT_OK ) return 1; //error //read status bits from programmer ftStatus = FT_Read(ftHandle, presult, 1, &dwNumBytesToRead); if( ftStatus != FT_OK ) return 1; //error return 0; } int send_data(unsigned char* pdata, int len) { dwNumBytesSent = 0; dwNumBytesToSend = 0; byOutputBuffer[dwNumBytesToSend++] = 0x4b; //Clock Data to TMS pin (no read) byOutputBuffer[dwNumBytesToSend++] = 0; //mean 1 bit send byOutputBuffer[dwNumBytesToSend++] = 1; //01b to TMS int length = len-1; byOutputBuffer[dwNumBytesToSend++] = 0x19; //Clock Data bytes to TDI pin (no read) byOutputBuffer[dwNumBytesToSend++] = length & 0xFF; //low byte length byOutputBuffer[dwNumBytesToSend++] = (length>>8) & 0xFF; //high byte length memcpy(&byOutputBuffer[dwNumBytesToSend],pdata,len); dwNumBytesToSend+=len; ftStatus = FT_Write(ftHandle, byOutputBuffer, dwNumBytesToSend, &dwNumBytesSent); if( ftStatus != FT_OK ) return 1; //error return 0; } int _tmain(int argc, _TCHAR* argv[]) { if( ftdi_init() ) { printf("Cannot init FTDI chip\n"); return -1; } if( configure_mpsse() ) { printf("Cannot init MPSSE chip\n"); return -1; } #define BLOCK_LEN 256 unsigned char sbuf[BLOCK_LEN]; for(int i=0; i<BLOCK_LEN; i++) sbuf[i] = i | 0x80; DWORD ticks = GetTickCount(); int sz = 0; while(1) { send_data(sbuf,BLOCK_LEN); 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 в функции FT_Open(1, &ftHandle);
Ну и второе отличие, конечно, передача блоков данных в микросхему FTDI функцией FT_Write().
В функции send_data() сперва подготавливается блок передаваемых данных: первая команда в MPSSE идет 0x4B. Эта команда подает короткий импульс на сигнале JTAG_TMS. Следующая команда 0x19 передает в последовательном виде блок данных на сигнале JTAG_TDI.
Я в начале каждого блока данных решил подавать короткий импульс на TMS, чтобы приемник как-то мог синхронизироваться, определять, где начинается блок данных, где начало первого байта в начале каждого блока.
Команды FTDI MPSSE описаны в документе http://www.ftdichip.com/Support/Documents/AppNotes/AN_108_Command_Processor_for_MPSSE_and_MCU_Host_Bus_Emulation_Modes.pdf
Теперь расскажу о результатах измерений по скорости передачи. Оказалось немного хуже чем я надеялся, но, в принципе, вполне прилично. Оказалось, что я могу передавать данные в плату со скоростью примерно 3,3Мбайта в секунду. Это почти в 2 раза больше , чем через последовательный порт.
Измерял я скорость передачи 2-мя способами.
Во-первых, сама сишная программа измеряет количество переданных данных в секунду. Вот вывод программы на консоли:
Во-вторых, модуль checker в проекте Altera Quartus II ведет измерение принятых данных (сигнал шины data_cnt[23..0]).
Показания на редкость одинаковые.. Примерно 3,3Мбайта/сек.
Вот скриншот из инструмента Altera SignalTap. С его помощью я просматриваю состояния внутренних сигналов микросхемы ПЛИС, в том числе и сигналы FTDI (TCK, TDI, TDO, TMS), data, data_rdy, data_cnt. Сигнал data_cnt - количество принятых байт в секунду.
Видно, что на прием одного байта уходит 8 тактов TCK и еще один импульс TCK отсутствует. То есть, если максимально возможная TCK - это 30МГц, то данных можно передать 30 / 9 МБайт в секунду. Это 3333333 байт в секунду. У меня получается чуть меньше из-за того, что я делаю в начале каждого блока данных вставку короткого импульса TMS. Почему-то там микросхема FTDI сделала пропуск около десятка импульсов TCK. Ну это не беда.
Ну, что же, считаю эксперимент довольно удачным. Мне удалось достичь более высокой скорости передачи данных из ПК в ПЛИС платы Марсоход2.
Весь проект для Cyclone III и программу C++ проекта Visual Studio можно взять на нашем сайте:
Подробнее...