Скоростная передача данных в плату Марсоход2

speed

Для одного из будущих проектов (если он получится) мне нужно передавать данные в плату и из платы Марсоход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 принимает скоростные данные от компьютера.

sch

В этом проекте четыре линии, идущие от микросхемы 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-мя способами.

Во-первых, сама сишная программа измеряет количество переданных данных в секунду. Вот вывод программы на консоли:

console

Во-вторых, модуль checker в проекте Altera Quartus II ведет измерение принятых данных (сигнал шины data_cnt[23..0]).

Показания на редкость одинаковые.. Примерно 3,3Мбайта/сек.

signal tap

Вот скриншот из инструмента 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 можно взять на нашем сайте:

 


Добавить комментарий