AN_114: подключение FT2232H к шине SPI Печать
Добавил(а) microsin   

Этот апноут представляет синхронный последовательный интерфейс SPI, и иллюстрирует, как реализовать его с помощью высокоскоростного чипа FT2232H. Микросхема FT2232H будет использоваться для записи и чтения данных в SPI serial EEPROM (перевод AN_114 [1]).

[1. Введение]

Микросхемы FT2232H и FT4232H это микросхемы высокоскоростного моста USB (USB 2.0 Hi-Speed, 480 мегабит/сек), предназначенные для передачи данных между компьютером и аппаратным UART/FIFO. Они могут быть сконфигурированы на работу в режиме различных последовательных интерфейсов с помощью встроенной системы MPSSE (MultiProtocol Synchronous Serial Engine). У микросхемы FT2232H на борту 2 независимых порта, которые оба можно сконфигурировать через MPSSE, в то время как у микросхемы FT4232H только каналы A и B можно сконфигурировать через MPSSE (подробный перевод даташита FT2232H см. в статье [2]).

Использование технологии MPSSE может упростить реализацию последовательных протоколов (когда необходимо преобразование интерфейсов из USB в SPI, I2C, JTAG, и т. д.). В этом апноуте показано, как использовать MPSSE FT2232H для подключения к шине SPI. Пользователи могут использовать пример схемы (см. рис. 3) и функциональный код программы примера (см. раздел 3) для своей собственной разработки.

Примечание: имейте в виду, что пример программы приведен для иллюстрации процесса, и для него не гарантируется поддержка со стороны компании FTDI.

Здесь показано, как подключить и сконфигурировать FT2232H для чтения и записи данных от хоста PC в serial EEPROM через шину последовательного интерфейса SPI. Обсуждаемые темы:

• Обзор обмена через SPI [3].
• Пример аппаратуры моста USB к интерфейсу SPI serial EEPROM с использованием FT2232H.
• Пример кода на C++, показывающего как конфигурировать FT2232H в режиме SPI.
• Диаграммы сигналов осциллографа, показывающие циклы чтения и записи через SPI.

1.2. Обзор интерфейса SPI. SPI (аббревиатура от Serial to Peripheral Interface) это синхронная последовательная шина, работающая по принципу master/slave (на шине имеется одно главное устройство master и одно или несколько подчиненных устройств slave), которая состоит обычно из 4 сигналов. SPI master инициирует все транзакции данных по шине. С помощью моста на FT2232H полнодуплексные перемещения данных могут быть осуществлены на скорости до 30 мегабит/сек. Длина бита SPI не фиксирована. Обычная система SPI использует следующие сигналы, показанные на рис. 1.

SCLK последовательные такты, которые master генерирует для slave.
MOSI последовательный выход данных со стороны master (MOSI это аббревиатура от Master Out Slave In, т. е. выход данных главного устройства и вход подчиненного).
MISO последовательный вход данных со стороны master (MISO это аббревиатура от Master In Slave Out, т. е. вход данных главного устройства и выход подчиненного).
CS выборка подчиненного устройства (Chip Select), выходной сигнал, генерируемый master.

AN 114 generic SPI system fig1

Рис. 1. Диаграмма соединений стандартной системы SPI.

FT2232H всегда работает как SPI master. К нему может быть подключено несколько slave-устройств с помощью переключающего сигнала выборки CS, который формирует master. Передача данных осуществляется одновременно в обоих направлениях через сигналы MOSI и MISO, синхронно с тактами, поступающими от master через сигнал SCLK. В зависимости от способа реализации slave-устройства данные могут посылаться через SPI по разному - либо старшим битом (MSB) вперед, либо младшим битом (LSB) вперед. Slave-устройства могут иметь входы выборки либо с активным лог. 0, либо с активным лог. 1 (однако традиционно чаще всего используется активный уровень лог. 0). На рис. 2 показан пример диаграммы сигналов SPI (устройство SPI использует режим SPI Mode 0 с активным сигналом выборки лог. 0).

AN 114 example SPI timing diagram fig2

Рис. 2. Пример диаграммы времени сигналов SPI.

Фаза сигнала тактов SCLK (clock phase, CPHA) и их полярность (clock polarity, CPOL) определяют 4 уникальных режима работы SPI, известных как Mode 0, Mode 1, Mode 2 и Mode 3. Таблица 1 суммарно показывает эти режимы фазы/полярности.

Для CPOL=0 базовый (не активный) уровень SCLK это лог. 0. В этом режиме:

• Когда CPHA=0, данные будут считываться (через MISO) по нарастанию сигнала SCLK, и выдвигаться наружу (через MOSI) по спаду уровня SCLK.
• Когда CPHA=1, данные будут считываться (через MISO) по спаду сигнала SCLK, и выдвигаться наружу (через MOSI) по нарастанию уровня SCLK.

Для CPOL=1 базовый (не активный) уровень SCLK это лог. 1. В этом режиме:

• Когда CPHA=0, данные будут считываться (через MISO) по спаду уровня SCLK, и выдвигаться наружу (через MOSI) по нарастанию уровня SCLK.
• Когда CPHA=1, данные будут считываться (через MISO) по нарастанию уровня SCLK, и выдвигаться наружу (через MOSI) по спаду уровня SCLK.

Таблица 1. Режимы, определяющие фазу и полярность тактов SCLK.

Mode CPOL CPHA
0 0 0
1 0 1
2 1 0
3 1 1

Есть некое затруднение в том, что обмен через интерфейс SPI slave может быть реализован различными способами (4 режима, 2 варианта порядка следования бит), поэтому при работе с подчиненными устройствами требуется тщательное изучение даташита slave, чтобы правильно выбрать режим SPI. Микросхемы FT2232H и FT4232H могут аппаратно поддерживать только режимы 0 и 2 из-за ограничений системы MPSSE.

1.3. Цоколевка выводов FT2232H/FT4232H SPI. Таблица 2 показывает место расположения (номер вывода на корпусе чипа) и функцию сигналов SPI для каналов A и B микросхем FT2232H и FT4232H.

Выв. FT2232H Выв. FT4232H Имя Функция MPSSE Тип Описание
Channel A
16 16 ADBUS0 SCLK Выход Такты для последовательных данных.
17 17 ADBUS1 DO (MOSI) Выход Выход данных master.
18 18 ADBUS2 DI (MISO) Вход Вход данных master.
19 19 ADBUS3 CS Выход Выборка для устройства slave.
Channel B
38 26 BDBUS0 SCLK Выход Такты для последовательных данных.
39 27 BDBUS1 DO (MOSI) Выход Выход данных master.
40 28 BDBUS2 DI (MISO) Вход Вход данных master.
41 29 BDBUS3 CS Выход Выборка для устройства slave.

Таблица 2. Цоколевка сигналов SPI микросхем FT2232H и FT4232H.

[2. Пример схемы SPI]

Микросхема FT2232H подключена к 93LC56 Serial SPI EEPROM компании Microchip. На рис. 3 показана упрощенная схема соединений (без сигналов USB, питания и т. п.). Для простоты используется выводы SPI канала A. Дополнительные подробности по микросхемам см в даташитах, которые можно скачать с сайта производителей FTDI и Microchip (или можно скачать архив [5] вместе с примером кода).

AN 114 FT2232H 93LC56 EEPROM connecting SCH fig3

Рис. 3. Подключение FT2232H к 93LC56 EEPROM.

[3. Пример кода программы]

Ниже приведен пример кода, который конфигурирует FT2232H для работы в режиме SPI. В нем сначала проверяется, что есть подключение устройства FTDI Hi Speed device (это и есть микросхема FT2232H), и происходит определение типа микросхемы: FT2232H или FT4232H. После этого приложение посылает команду через SPI стереть все содержимое EEPROM, и затем записывает в EEPROM слово длиной 16 бит.

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

После того, как будут записаны 128 байт данных, приложение будет считывать данные обратно и проверять, насколько правильно записаны данные по 128 адресам ячеек памяти.

Процессы с записью/чтением 128 байт могут быть повторены путем изменения параметра LoopCntr программы.

Пример кода использует FTCSPI dll, и требует использования среды программирования Microsoft Visual Studio 2008 C++ (или более свежей версии). Исходный код проекта можно скачать в архиве FT2232HS_SPI.zip с сайта FTDI. Для своей работы код требует установленного драйвера FTDI D2XX, который также можно скачать с сайта FTDI. Дополнительную информацию по API драйвера D2XX API можно найти в руководстве программирования D2XX Programmer’s Guide [4].

// SPITEST.cpp: консольное приложение VC++.
// Это проект примера, использующий канал A микросхемы FT2232H для доступа
// к SPI EEPROM 93C56. Мы посылаем 16 слов данных в 93C56 и читаем их обратно,
// пользователь может увидеть результат теста в командном режиме.
#include "stdafx.h"
#include < windows.h >
#include "FTD2XX.h"
#include < stdlib.h >
 
// Декларирование параметров для 93C56:
#define MemSize 16   // определение количества данных, которые нужно послать
const BYTE SPIDATALENGTH = 11;   //3 бита команды + 8 бит адреса
const BYTE READ = '\xC0';        //110xxxxx
const BYTE WRITE = '\xA0';       //101xxxxx
const BYTE WREN = '\x98';        //10011xxx
const BYTE ERAL = '\x90';        //10010xxx
// Декларации для команды BAD:
const BYTE AA_ECHO_CMD_1         = '\xAA';
const BYTE AB_ECHO_CMD_2         = '\xAB';
const BYTE BAD_COMMAND_RESPONSE  = '\xFA';
//Декларации для команд MPSSE:
const BYTE MSB_RISING_EDGE_CLOCK_BYTE_OUT    = '\x10';
const BYTE MSB_FALLING_EDGE_CLOCK_BYTE_OUT   = '\x11';
const BYTE MSB_RISING_EDGE_CLOCK_BIT_OUT     = '\x12';
const BYTE MSB_FALLING_EDGE_CLOCK_BIT_OUT    = '\x13';
const BYTE MSB_RISING_EDGE_CLOCK_BYTE_IN     = '\x20';
const BYTE MSB_RISING_EDGE_CLOCK_BIT_IN      = '\x22';
const BYTE MSB_FALLING_EDGE_CLOCK_BYTE_IN    = '\x24';
const BYTE MSB_FALLING_EDGE_CLOCK_BIT_IN     = '\x26';
 
// Статус, определяемый драйвером D2XX, чтобы показать результат операции:
FT_STATUS ftStatus;
 
// Буфер, где содержатся команды MPSSE и данные для отправки в FT2232H:
BYTE OutputBuffer[512];
// Буфер, где содержатся байты данных, прочитанные из FT2232H:
BYTE InputBuffer[512];
// Значение для делителя тактов, частота SCL = 60/((1+29)*2) = 1 МГц
DWORD dwClockDivisor = 29;
// Индекс выходного буфера:
DWORD dwNumBytesToSend = 0;
// Счетчики байт:
DWORD dwNumBytesSent = 0, dwNumBytesRead = 0, dwNumInputBuffer = 0;
BYTE ByteDataRead;
WORD MemAddress = 0x00;
 
WORD i=0;
WORD DataOutBuffer[MemSize];
WORD DataInBuffer[MemSize];
 
// Эта подпрограмма используется для разрешения работы устройства SPI.
void SPI_CSEnable()
{
   // Одна команда 0x80 займет 0.2 мкс, повторяем её 5 раз, чтобы остаться в этой
   // ситуации в течение 1 мкс.
   for(int loop=0; loop < 5; loop++)
   {
      OutputBuffer[dwNumBytesToSend++] = '\x80';   // команда GPIO для ADBUS
      OutputBuffer[dwNumBytesToSend++] = '\x08';   // перевод CS в лог. 1, MOSI и SCLK в лог. 0
      OutputBuffer[dwNumBytesToSend++] = '\x0b';   // bit3:CS, bit2:MISO, bit1:MOSI, bit0:SCLK
   }
}
 
// Эта подпрограмма используется для запрещения работы устройства SPI.
void SPI_CSDisable()
{
   // Одна команда 0x80 займет 0.2 мкс, повторяем её 5 раз, чтобы остаться в этой
   // ситуации в течение 1 мкс.
   for(int loop=0; loop < 5; loop++)
   {
      OutputBuffer[dwNumBytesToSend++] = '\x80';   // команда GPIO для ADBUS
      OutputBuffer[dwNumBytesToSend++] = '\x00';   // перевод CS, MOSI и SCLK в лог. 0
      OutputBuffer[dwNumBytesToSend++] = '\x0b';   // bit3:CS, bit2:MISO, bit1:MOSI, bit0:SCLK
   }
}
 
// Эта подпрограмма используется для отправки команды в 93C56 EEPROM.
FT_STATUS WriteEECmd(FT_HANDLE ftHandle, BYTE command)
{
   dwNumBytesSent=0;
   SPI_CSEnable();
   //SPIDATALENGTH = 11, это можно поделить на 8+3 бита
   OutputBuffer[dwNumBytesToSend++] = MSB_FALLING_EDGE_CLOCK_BIT_OUT;
   OutputBuffer[dwNumBytesToSend++] = 7;  //7+1 = 8
   OutputBuffer[dwNumBytesToSend++] = command;
   OutputBuffer[dwNumBytesToSend++] = MSB_FALLING_EDGE_CLOCK_BIT_OUT;
   OutputBuffer[dwNumBytesToSend++] = SPIDATALENGTH - (8+1);
   OutputBuffer[dwNumBytesToSend++] = '\xff';
   SPI_CSDisable();
   // Отправка команды в MPSSE:
   ftStatus = FT_Write(ftHandle, OutputBuffer, dwNumBytesToSend, &dwNumBytesSent);
   dwNumBytesToSend = 0;   // Очистка выходного буфера
   return ftStatus;
}
 
// Эта подпрограмма используется для инициализации интерфейса SPI.
BOOL SPI_Initial(FT_HANDLE ftHandle)
{
   DWORD dwCount;
   ftStatus = FT_ResetDevice(ftHandle);   // Сброс устройства USB.
   // Сначала очищается буфер приема USB путем вычитывания всех старых данных
   // из буфера приема FT2232H.
   // Получение количества байт в буфере приема FT2232H:
   ftStatus |= FT_GetQueueStatus(ftHandle, &dwNumInputBuffer);
   if ((ftStatus == FT_OK) && (dwNumInputBuffer > 0))
   {
      // Чтение данных из буфера приема FT2232H:
      ftStatus |= FT_Read(ftHandle, InputBuffer, dwNumInputBuffer, &dwNumBytesRead);
   }
   // Установка размера запроса USB (request transfer size):
   ftStatus |= FT_SetUSBParameters(ftHandle, 65535, 65535);
   // Запрет символов события и ошибки (event and error characters):
   ftStatus |= FT_SetChars(ftHandle, false, 0, false, 0);
   // Установка таймаутов чтения и записи для FT2232H на 3 секунды:
   ftStatus |= FT_SetTimeouts(ftHandle, 3000, 3000);
   // Установка таймера задержки (latency timer):
   ftStatus |= FT_SetLatencyTimer(ftHandle, 1);
   // Сброс контроллера:
   ftStatus |= FT_SetBitMode(ftHandle, 0x0, 0x00);
   // Разрешить режим MPSSE:
   ftStatus |= FT_SetBitMode(ftHandle, 0x0, 0x02);
   if (ftStatus != FT_OK)
   {
      printf("fail on initialize FT2232H device ! \n");
      return false;
   }
   Sleep(50); // ожидание 50 мс, чтобы все железо USB было готово к работе
 
   //////////////////////////////////////////////////////////////////
   // Синхронизация интерфейса MPSSE отправкой "плохой" команды &xAA*
   //////////////////////////////////////////////////////////////////
   dwNumBytesToSend = 0;
   OutputBuffer[dwNumBytesToSend++] = '\xAA'; // добавление BAD command &xAA*
   // Отправка команд BAD:
   ftStatus = FT_Write(ftHandle, OutputBuffer, dwNumBytesToSend, &dwNumBytesSent);
   dwNumBytesToSend = 0;   // очистка выходного буфера
   do
   {
      // Получение количества байт во входном буфере устройства:
      ftStatus = FT_GetQueueStatus(ftHandle, &dwNumInputBuffer);
      // Повторение цикла, пока не появятся данные или не произойдет таймаут:
   }while ((dwNumInputBuffer == 0) && (ftStatus == FT_OK));
   bool bCommandEchod = false;
   // Считывание данных из входного буфера:
   ftStatus = FT_Read(ftHandle, InputBuffer, dwNumInputBuffer, &dwNumBytesRead);
   for (dwCount = 0; dwCount < (dwNumBytesRead - 1); dwCount++)
   {
      // Проверка: принята ли команда Bad и эхо команды:
      if ((InputBuffer[dwCount] == BYTE('\xFA')) && (InputBuffer[dwCount+1] == BYTE('\xAA')))
      {
         bCommandEchod = true;
         break;
      }
   }
   if (bCommandEchod == false)
   {
      // Ошибка, не получилось принять эхо команды, сбой синхронизации
      // интерфейса MPSSE.
      printf("fail to synchronize MPSSE with command '0xAA' \n");
      return false;
   }
   
   //////////////////////////////////////////////////////////////////
   // Синхронизация интерфейса MPSSE отправкой "плохой" команды &xAB*
   //////////////////////////////////////////////////////////////////
   //dwNumBytesToSend = 0;  // очистка выходного буфера
   OutputBuffer[dwNumBytesToSend++] = '\xAB';   // добавление BAD command &xAB*
   // Отправка команд BAD:
   ftStatus = FT_Write(ftHandle, OutputBuffer, dwNumBytesToSend, &dwNumBytesSent);
   dwNumBytesToSend = 0;   // очистка выходного буфера
   do
   {
      // Получение количества байт во входном буфере устройства:
      ftStatus = FT_GetQueueStatus(ftHandle, &dwNumInputBuffer);
      // Повторение цикла, пока не появятся данные или не произойдет таймаут:
   }while ((dwNumInputBuffer == 0) && (ftStatus == FT_OK));
   bCommandEchod = false;
   // Считывание данных из входного буфера:
   ftStatus = FT_Read(ftHandle, InputBuffer, dwNumInputBuffer, &dwNumBytesRead);
   for (dwCount = 0;dwCount < (dwNumBytesRead - 1); dwCount++)
   {
      // Проверка: принята ли команда Bad и эхо команды:
      if ((InputBuffer[dwCount] == BYTE('\xFA')) && (InputBuffer[dwCount+1] == BYTE( '\xAB')))
      {
         bCommandEchod = true;
         break;
      }
   }
   if (bCommandEchod == false)
   {
      // Ошибка, не получилось принять эхо команды, сбой синхронизации
      // интерфейса MPSSE.
      printf("fail to synchronize MPSSE with command '0xAB' \n");
      return false;
   }
 
   ////////////////////////////////////////////////////////////////////
   // Конфигурирование MPSSE для обмена с EEPROM через SPI
   ////////////////////////////////////////////////////////////////////
   // Запрет делителя тактов для master clock = 60 МГц:
   OutputBuffer[dwNumBytesToSend++] = '\x8A';
   // Выключение адаптивного тактирования
   OutputBuffer[dwNumBytesToSend++] = '\x97';
   // Запрет 3 phase data clock:
   OutputBuffer[dwNumBytesToSend++] = '\x8D';
   // Отправка подготовленных команд:
   ftStatus = FT_Write(ftHandle, OutputBuffer, dwNumBytesToSend, &dwNumBytesSent);
   dwNumBytesToSend = 0;   //очистка выходного буфера
   // Команда для установки направлений младших 8 выводов,
   // чтобы настроить все биты как выходы:
   OutputBuffer[dwNumBytesToSend++] = '\x80';
    // Установка SDA, SCL в лог. 1, WP запрещен, SCLK, DO, GPIOL0 на бите &*:
   OutputBuffer[dwNumBytesToSend++] = '\x00';
   // Установка выводов SCLK, DO, GPIOL0 как выходов с битом **,
   // другие выводы как входы с битом &*
   OutputBuffer[dwNumBytesToSend++] = '\x0b';
   // Тактовая частота SCLK может быть установлена с делителем на 5 по формуле:
   // SCLK freq = 60 МГц /((1 + [(1 +ValueH*256) OR ValueL])*2)
   // Команда для установки делителя:
   OutputBuffer[dwNumBytesToSend++] = '\x86';
   // Установка ValueL (младший байт) делителя тактов:
   OutputBuffer[dwNumBytesToSend++] = BYTE(dwClockDivisor & '\xFF');
   // Установка ValueH (старший байт) делителя тактов:
   OutputBuffer[dwNumBytesToSend++] = BYTE(dwClockDivisor >> 8);
   // Отправка команд:
   ftStatus = FT_Write(ftHandle, OutputBuffer, dwNumBytesToSend, &dwNumBytesSent);
   dwNumBytesToSend = 0;   // очистка выходного буфера
   Sleep(20);              // задержка
   // Выключение петли внутреннего зацикливания с выхода на вход (loop back).
   // Команда отключения loop back (соединения TDI - TDO):
   OutputBuffer[dwNumBytesToSend++] = '\x85';
   // Отправка команд:
   ftStatus = FT_Write(ftHandle, OutputBuffer, dwNumBytesToSend, &dwNumBytesSent);
   dwNumBytesToSend = 0;   // очистка выходного буфера
   Sleep(30);              // задержка
   printf("SPI initial successful\n");
   return true;
}
 
// Эта подпрограмма используется для отправки одного слова данных
// с произвольным доступом по указанному адресу.
BOOL SPI_WriteByte2RandomAddr(FT_HANDLE ftHandle, WORD address,WORD bdata)
{
   dwNumBytesSent=0;
   SPI_CSEnable();
   // Отправка команды WRITE (запись):
   OutputBuffer[dwNumBytesToSend++] = MSB_FALLING_EDGE_CLOCK_BIT_OUT;
   OutputBuffer[dwNumBytesToSend++] = 2;
   OutputBuffer[dwNumBytesToSend++] = WRITE;
   // Отправка адреса:
   OutputBuffer[dwNumBytesToSend++] = MSB_FALLING_EDGE_CLOCK_BIT_OUT;
   OutputBuffer[dwNumBytesToSend++] = 7;
   OutputBuffer[dwNumBytesToSend++] = (BYTE)(address);
   // Отправка данных:
   OutputBuffer[dwNumBytesToSend++] = MSB_FALLING_EDGE_CLOCK_BYTE_OUT;
   OutputBuffer[dwNumBytesToSend++] = 1;
   OutputBuffer[dwNumBytesToSend++] = 0;  // длина данных 0x0001 означает, что будет
                                          // отправлено 2 байта данных
   OutputBuffer[dwNumBytesToSend++] = bdata >> 8;     // вывод старшего байта
   OutputBuffer[dwNumBytesToSend++] = bdata & 0xff;   // вывод младшего байта
   SPI_CSDisable();
   // Отправка команды MPSSE:
   ftStatus = FT_Write(ftHandle, OutputBuffer, dwNumBytesToSend, &dwNumBytesSent);
   dwNumBytesToSend = 0;   // очистка выходного буфера
   return ftStatus;
}
 
// Эта подпрограмма используется для чтения одного слова данных
// с произвольным доступом по указанному адресу:
BOOL SPI_ReadByteRandomAddr(FT_HANDLE ftHandle, WORD address, WORD* bdata)
{
   dwNumBytesSent=0;
   SPI_CSEnable();
   // Отправка команды READ (чтение):
   OutputBuffer[dwNumBytesToSend++] = MSB_FALLING_EDGE_CLOCK_BIT_OUT;
   OutputBuffer[dwNumBytesToSend++] = 2;
   OutputBuffer[dwNumBytesToSend++] = READ;
   // Отправка адреса:
   OutputBuffer[dwNumBytesToSend++] = MSB_FALLING_EDGE_CLOCK_BIT_OUT;
   OutputBuffer[dwNumBytesToSend++] = 7;
   OutputBuffer[dwNumBytesToSend++] = (BYTE)(address);
   // Чтение данных:
   OutputBuffer[dwNumBytesToSend++] = MSB_FALLING_EDGE_CLOCK_BYTE_IN;
   OutputBuffer[dwNumBytesToSend++] = '\x01';
   OutputBuffer[dwNumBytesToSend++] = '\x00';   // длина данных 0x0001 означает,
                                                // что будет вдвигаться 2 байта
   SPI_CSDisable();
   // Отправка команды MPSSE:
   ftStatus = FT_Write(ftHandle, OutputBuffer, dwNumBytesToSend, &dwNumBytesSent);
   dwNumBytesToSend = 0;   // очистка выходного буфера
   // Чтение 2 байт из буфера приема устройства:
   ftStatus = FT_Read(ftHandle, InputBuffer, 2, &dwNumBytesRead);
   *bdata = (InputBuffer[0] < < 8) + InputBuffer[1];
   return ftStatus;
}
 
int _tmain(int argc, _TCHAR* argv[])
{
   FT_HANDLE ftdiHandle;
   DWORD numDevs;
   FT_DEVICE_LIST_INFO_NODE *devInfo;
   ftStatus = FT_CreateDeviceInfoList(&numDevs);
   if (ftStatus == FT_OK)
      printf("Number of devices is %d\n",numDevs);
   else
      return 1;
   if (numDevs > 0)
   {
      // Выделение памяти под хранилище списка на основании
      // количества устройств numDevs:
      devInfo = (FT_DEVICE_LIST_INFO_NODE*)malloc(sizeof(FT_DEVICE_LIST_INFO_NODE)*numDevs);
      // Получение списка информации об устройствах:
      ftStatus = FT_GetDeviceInfoList(devInfo,&numDevs);
      if (ftStatus == FT_OK)
      {
         for (i = 0; i < numDevs; i++)
         {
            printf("Dev %d:\n",i);
            printf(" Flags=0x%x\n",devInfo[i].Flags);
            printf(" Type=0x%x\n",devInfo[i].Type);
            printf(" ID=0x%x\n",devInfo[i].ID);
            printf(" LocId=0x%x\n",devInfo[i].LocId);
            printf(" SerialNumber=%s\n",devInfo[i].SerialNumber);
            printf(" Description=%s\n",devInfo[i].Description);
            printf(" ftHandle=0x%x\n",devInfo[i].ftHandle);
         }
      }
   }
   else
      return 1;
   ftStatus = FT_Open(0,&ftdiHandle);
   if (ftStatus != FT_OK)
   {
      printf("Can't open FT2232H device! \n");
      return 1;
   }
   else
   {
      // Порт устройства был успешно открыт.
      printf("Successfully open FT2232H device! \n");
   }
   if(SPI_Initial(ftdiHandle) == TRUE)
   {
      byte ReadByte = 0;
      // Инициализация выходного буфера:
      for(i=0; i < MemSize; i++)
         DataOutBuffer[i] = i;
      // Первоначальная очистка буфера приема USB перед операцией чтения.
      // Получение количества байт в буфере приема устройства:
      ftStatus = FT_GetQueueStatus(ftdiHandle, &dwNumInputBuffer);
      if ((ftStatus == FT_OK) && (dwNumInputBuffer > 0))
      {
         // Чтение всех байт из буфера приема:
         FT_Read(ftdiHandle, InputBuffer, dwNumInputBuffer, &dwNumBytesRead);
      }
      WriteEECmd(ftdiHandle, WREN);
      WriteEECmd(ftdiHandle, ERAL);
      Sleep(20);
      for(i=0; i < MemSize; i++)
      {
         SPI_WriteByte2RandomAddr(ftdiHandle, i,DataOutBuffer[i]);
         Sleep(2);
         printf("Write data %d to address %d\n",DataOutBuffer[i],i);
      }
      Sleep(20);
      for(i=0; i < MemSize; i++)
      {
         SPI_ReadByteRandomAddr(ftdiHandle, i,&DataInBuffer[i]);
         printf("Read data from address %d = %d\n",i,DataInBuffer[i]);
      }
      getchar();  // ожидать нажатия клавиши пользователем...
   }
   FT_Close(ftdiHandle);
   return 0;
}

3.2. Осциллограммы чтения/записи. Скриншоты ниже показывают вид сигналов чтения и записи интерфейса SPI. Они предоставлены для иллюстрации работы команд WRITE и READ, используемые для обмена данными между хостом PC, FT2232H и 93LC56 EEPROM.

Рис. 4 показывает команду записи (101), выдаваемую через MOSI, за которой передается число 3, записываемое по адресу 3.

AN 114 Write3 to Address3 fig4

Рис. 4. Запись (WRITE) числа 3 по адресу 3.

Рис. 5 показывает команду чтения (110), передаваемую через MOSI, за которой идет адрес 3. На сигнале MISO осуществляется чтение данных (3).

AN 114 Read3 from Address3 fig5

Рис. 5. Чтение (READ) числа 3 по адресу 3.

[Ссылки]

1. AN_114 Interfacing FT2232H Hi-Speed Devices To SPI Bus site:ftdichip.com.
2FT2232H: двухканальная высокоскоростная USB микросхема для I/O.
3. Интерфейс SPI.
4. FTDI: справочник по функциям библиотеки D2XX.
5. 170804AN_114-FTDI.ZIP - даташиты, примеры кода, драйвер для FT2232H и FT4232H.