FTDI libMPSSE: реализация интерфейса SPI Печать
Добавил(а) microsin   

Multi Protocol Synchronous Serial Engine (MPSSE) это аппаратура, которая реализована на кристалле некоторых чипов FTDI. Она позволяет им реализовать такие синхронные последовательные интерфейсы, как I2C, SPI или JTAG, с которыми можно обмениваться данными через USB. В настоящий момент MPSSE доступна на кристаллах FT2232D, FT2232H, FT4232H и FT232H, которые подключаются к хосту управления (обычно это компьютер PC, подключенный к чипу FTDI через USB. Приложения PC или какой-либо встраиваемой системы обмениваются данными с MPSSE в этих чипах через драйверы D2XX USB [4].

MPSSE принимает различные команды для вывода данных из чипов в различных форматах, а именно I2C, SPI и JTAG. Библиотека libMPSSE предоставляет удобный для программиста API-интерфейс, позволяющий написать приложения для обмена с устройствами I2C/SPI/JTAG без необходимости глубокого понимания устройства системы MPSSE и изучения её команд. Однако если пользователю это необходимо, то он может изучить работу MPSSE и использовать её возможности напрямую с помощью вызова функций D2XX.

libMPSSE SPI software and hardware stack fig01

Рис. 1. Программный и аппаратный стек, через который проходит поток данных стандартных протоколов (I2C, SPI или JTAG).

Как показано на рис. 1, у библиотеки libMPSSE есть 3 разных вида API, каждый предназначен для I2C, SPI и JTAG. Этот документ (перевод даташита AN_178 [1]) рассматривает только SPI. Примеры кода, использующего libMPSSE (версии для Linux и Windows), замечания по релизу и все необходимые файлы можно загрузить с сайта FTDI по ссылке [3] (также см. [2]).

[Обзор системы]

libMPSSE SPI system organization fig02

Рис. 2. Общая организация системы.

На рис. 2 показан базовая структура системы реализации интерфейса SPI с помощью чипов FTDI и libMPSSE. Устройством PC/Host (хост приложения) может быть компьютер PC, устройство Android или какая-либо встраиваемая система. Чип FTDI и устройство SPI обычно размещаются на одной печатной плате, или соединяются друг с другом коротким кабелем. На рисунке показано только одно внешнее устройство SPI, однако к каждому блоку MPSSE может быть подключено до 5 устройств SPI.

[Интерфейс программирования (API)]

Функции libMPSSE-SPI API можно поделить на 2 больших набора. Первый состоит из 6 функций управления (control API), и второй содержит 2 набора API-функций для перемещения данных. Все API-функции возвращают значения кодов из перечисления FT_STATUS, как они определены в драйвере D2XX.

Примечание: описание структуры используемых в API структур и типах данных см. в разделе "Типы данных". Некоторые структуры см. в руководстве программирования драйвера D2XX [4].

Функции SPI. Ниже во врезках описаны функции SPI, которые позволяют аппаратно формировать сигналы SPI для чтения и записи данных при обмене с подчиненным устройством SPI (SPI slave).

FT_STATUS SPI_GetNumChannels (uint32 *numChannels);

Получает количество каналов SPI (портов), подключенных к системе хоста. Количество доступных портов может различаться для каждого чипа.

Параметры:

numChannels [out] Количество доступных для хоста каналов.

Чип FTDI представляет собой аппаратный мост между интерфейсом USB и внешним миром, и у него на борту есть несколько каналов для работы в режиме SPI. Однако не все эти каналы могут быть сконфигурированы как SPI master. Эта функция вернет общее количество каналов, которое может быть сконфигурировано как SPI. Например, если к компьютеру подключено сразу чипы FT2232D (1 порт MPSSE), FT232H  (1 порт MPSSE), FT2232H (2 порта MPSSE) и FT4232H (2 порта MPSSE), то вызов SPI_GetNumChannels вернет 6 в переменной *numChannels.

Предупреждение: эта функция не должна вызываться из двух приложений или из двух потоков одновременно.

FT_STATUS SPI_GetChannelInfo (uint32 index, FT_DEVICE_LIST_INFO_NODE *chanInfo);

Эта функция принимает индекс канала (допустимы значения от 0 до значения, которое вернула SPI_GetNumChannels минус 1) и предоставляет информацию о канале в форме заполненной структуры FT_DEVICE_LIST_INFO_NODE.

Параметры:

index [in] Индекс опрашиваемого канала.
chanInfo [out] Указатель на структуру FT_DEVICE_LIST_INFO_NODE.

Эта функция должна вызываться только после успешного вызова SPI_GetNumChannels.

Предупреждение: эта функция не должна вызываться из двух приложений или из двух потоков одновременно.

FT_STATUS SPI_OpenChannel (uint32 index, FT_HANDLE *handle);

Функция откроет канал по указанному индексу и предоставит дескриптор для него (handle). Для индекса допустимы значения от 0 до значения, которое вернула SPI_GetNumChannels минус 1. Дескриптор используется для дальнейшей работы с этим каналом.

Параметры:

index [in] Индекс канала.
handle [out] Указатель на ячейку памяти типа FT_HANDLE, куда будет записан дескриптор.

Если будет сделана попытка открыть уже открытый канал, то функция вернет код ошибки.

FT_STATUS SPI_InitChannel (FT_HANDLE handle, ChannelConfig *config);

Функция инициализирует канал параметрами коммуникации.

Параметры:

handle [in] Дескриптор канала.
config [in] Указатель на заполненную структуру с обновляемыми значениями для тактовой частоты и таймера задержки (latency timer).

Эта функция выполняет все необходимые действия по установке рабочего состояния канала, такие как сброс и разрешение работы MPSSE.

FT_STATUS SPI_CloseChannel (FT_HANDLE handle);

Закроет канал и освободит выделенные под него ресурсы.

Параметры:

handle [in] Дескриптор канала.

FT_STATUS SPI_Read (FT_HANDLE handle,
                    uint8 *buffer,
                    uint32 sizeToTransfer,
                    uint32 *sizeTransferred,
                    uint32 transferOptions);

Функция прочитает указанное количество бит или байт (в зависимости от параметра transferOptions) из устройства SPI slave (подчиненное устройство шины SPI).

Параметры:

handle [in] Дескриптор канала.
buffer [out] Указатель на буфер, куда должны быть помещены прочитанные данные.
sizeToTransfer [in] Количество байт или бит, которые должны быть прочитаны.
sizeTransferred [out] Указатель на ячейку, куда будет записано количество прочитанных байт или бит.
transferOptions [in] Этот параметр задает опции транзакции данных. Позиции бит для этих опций следующие:

BIT0: если 1, то sizeToTransfer указывает количество бит, иначе количество байт. Маски для этого бита определены константами SPI_TRANSFER_OPTIONS_SIZE_IN_BYTES и SPI_TRANSFER_OPTIONS_SIZE_IN_BITS.
BIT1: если 1, то ножка выборки (chip select) сгенерирует сигнал активности перед началом транзакции. Маска для этого бита определена в SPI_TRANSFER_OPTIONS_CHIPSELECT_ENABLE.
BIT2: если 1, то ножка выборки снимет сигнал активности по окончанию транзакции. Маска для этого бита определена в SPI_TRANSFER_OPTIONS_CHIPSELECT_DISABLE.
BIT3 .. BIT31: зарезервированы.

Это блокирующая функция, которая не выполнит возврат из неё, пока не будет прочитано указанное количество данных, или пока не произойдет ошибка.

FT_STATUS SPI_ Write (FT_HANDLE handle,
                      uint8 *buffer,
                      uint32 sizeToTransfer,
                      uint32 *sizeTransferred,
                      uint32 transferOptions);

Функция запишет указанное количество бит или байт (в зависимости от параметра transferOptions) в устройство SPI slave (подчиненное устройство шины SPI).

Параметры:

handle [in] Дескриптор канала.
buffer [in] Указатель на буфер, откуда должны быть взяты записываемые данные.
sizeToTransfer [in] Количество байт или бит, которые должны быть записаны.
sizeTransferred [out] Указатель на ячейку, куда будет записано количество записанных байт или бит.
transferOptions [in] Этот параметр задает опции транзакции данных. Позиции бит для этих опций следующие:

BIT0: если 1, то sizeToTransfer указывает количество бит, иначе количество байт. Маски для этого бита определены константами SPI_TRANSFER_OPTIONS_SIZE_IN_BYTES и SPI_TRANSFER_OPTIONS_SIZE_IN_BITS.
BIT1: если 1, то ножка выборки (chip select) сгенерирует сигнал активности перед началом транзакции. Маска для этого бита определена в SPI_TRANSFER_OPTIONS_CHIPSELECT_ENABLE.
BIT2: если 1, то ножка выборки снимет сигнал активности по окончанию транзакции. Маска для этого бита определена в SPI_TRANSFER_OPTIONS_CHIPSELECT_DISABLE.
BIT3 .. BIT31: зарезервированы.

Это блокирующая функция, которая не выполнит возврат из неё, пока не будет передано указанное количество данных, или пока не произойдет ошибка.

FT_STATUS SPI_ReadWrite (FT_HANDLE handle,
                         uint8 *inBuffer,
                         uint8 *outBuffer,
                         uint32 sizeToTransfer,
                         uint32 *sizeTransferred,
                         uint32 transferOptions);

Эта функция выполняет одновременное чтение и запись при обмене с устройством SPI slave. Т. е. при каждом такте вводится и выводится один бит данных.

Параметры:

handle [in] Дескриптор канала.
inBbuffer [in] Указатель на буфер, куда должны быть записаны считываемые данные.
outBbuffer [out] Указатель на буфер, откуда должны быть взяты записываемые данные.
sizeToTransfer [in] Количество байт или бит, которые должны быть записаны.
sizeTransferred [out] Указатель на ячейку, куда будет записано количество записанных байт или бит.
transferOptions [in] Этот параметр задает опции транзакции данных. Позиции бит для этих опций следующие:

BIT0: если 1, то sizeToTransfer указывает количество бит, иначе количество байт. Маски для этого бита определены константами SPI_TRANSFER_OPTIONS_SIZE_IN_BYTES и SPI_TRANSFER_OPTIONS_SIZE_IN_BITS.
BIT1: если 1, то ножка выборки (chip select) сгенерирует сигнал активности перед началом транзакции. Маска для этого бита определена в SPI_TRANSFER_OPTIONS_CHIPSELECT_ENABLE.
BIT2: если 1, то ножка выборки снимет сигнал активности по окончанию транзакции. Маска для этого бита определена в SPI_TRANSFER_OPTIONS_CHIPSELECT_DISABLE.
BIT3 .. BIT31: зарезервированы.

Это блокирующая функция, которая не выполнит возврат из неё, пока не будет передано указанное количество данных, или пока не произойдет ошибка.

FT_STATUS SPI_IsBusy (FT_HANDLE handle, bool *state);

Эта функция опрашивает состояние линии MISO без выдачи тактов на шину SPI.

Для некоторых приложений требуется, чтобы SPI master опрашивал состояние сигнала MISO (Master Input Slave Output) без генерации тактов, чтобы определить, завершило ли устройство SPI slave предыдущую операцию, чтобы быть готовым к следующей операции. Для этой цели походит функция SPI_IsBusy.

Параметры:

handle [in] Дескриптор канала.
state [out] Указатель на переменную, куда будет записано состояние MISO.

FT_STATUS SPI_ChangeCS (FT_HANDLE handle, uint32 configOptions);

Функция изменит состояние сигнала выборки, который используется для обмена с устройством SPI slave.

Параметры:

handle [in] Дескриптор канала.
configOptions [in] В этом параметре указывается способ генерации сигнала выборки (chip select) и выбирается один из четырех режимов (SPI mode). Тот же самый параметр в ChannelConfig.configOptions передается в функцию SPI_InitChannel. Подробно эти опции описаны в секции "Типы данных", см. описание структуры ChannelConfig.

Функции GPIO. Каждый канал MPSSE в чипе FTDI снабжен портом ввод/вывода общего назначения (General Purpose Input/Output, GPIO), у которого есть 8 линий, дополняющих синхронный последовательный обмен данныхми. Например, у чипа FT232H есть только один канал MPSSE с двумя 8-битными портами ADBUS и ACBUS. Из них ADBUS используется для синхронного последовательного обмена (I2C/SPI/JTAG), и ACBUS свободен для использования в качестве GPIO. Две описанные ниже функции предоставлены для доступа к этим линиям GPIO (также они называются старшим байтом линий MPSSE), которые доступны в различных чипах FTDI с блоками MPSSE.

FT_STATUS FT_WriteGPIO (FT_HANDLE handle, uint8 dir, uint8 value);

Функция запишет данные в 8 линий GPIO, связанных со старшим байтом канала MPSSE.

Параметры:

handle [in] Дескриптор канала.
dir [in] Каждый бит в этом байте представляет направление соответствующих линий GPIO. 0 задает вход, 1 выход.
value [in] Если направление ножки порта GPIO настроено на выход, то соответствующий бит этого байта установит уровень этой ножки порта в заданное состояние. 0 установит выход в лог. 0, и 1 в лог. 1.

FT_STATUS FT_ReadGPIO (FT_HANDLE handle, uint8 *value);

Функция прочитает состояние 8 линий GPIO, связанных со старшим байтом канала MPSSE.

Параметры:

handle [in] Дескриптор канала.
value [out] Если направление ножки порта GPIO настроено на вход, то соответствующий бит этого байта покажет уровень на этой ножке порта. 0 означает что на входе лог. 0, и 1 означает на входе лог. 1.

Примечание: перед использованием функции FT_ReadGPIO необходимо вызвать функцию FT_WriteGPIO, где будет настроено направление работы 8 ножек портов GPIO.

Функции библиотечной инфраструктуры. Следующие 2 функции обычно не нужно вызывать в приложениях пользователя, поскольку они автоматически вызываются во время входа/выхода. Однако эти функции не будут вызваны автоматически, когда библиотека линкована статически с использованием Microsoft Visual C++. В этом случае они должны быть явно вызваны из приложений пользователя. Пример статической линковки, предоставленный в этом руководстве, использует макрос, который проверят, компилируется ли код с использованием тулчейна Microsoft, и если это так, то он автоматически вызовет эти функции.

void Init_libMPSSE (void);

Эта функция инициализирует библиотеку libMPSSE.

void Cleanup_libMPSSE (void);

Освободит ресурсы, используемые библиотекой libMPSSE.

[Типы данных]

ChannelConfig это структура, которая хранит параметры, используемые для инициализации канала.

/* Структура, содержащая информацию о конфигурации канала SPI. Она заполняется
в приложении пользователя во время инициализации канала, затем сохраняется
в связанный список и используется внутри других функций SPI при выполнении
их операций. Эта структура удаляется из списка, когда приложение пользователя
вызовет SPI_CloseChannel. */
typedef struct ChannelConfig_t
{
   DWORD	ClockRate; /* частота тактов SPI, должно быть меньше или равна 30000000 */
   UCHAR	LatencyTimer; /* значение в миллисекундах, значение должно быть меньше
                          или равно 255 */
   DWORD	configOptions;/* Это поле предоставляет способ разрешить/запретить
                          отдельные функции, связанные с протоколом, как он
                          реализован в микросхеме.
   BIT1-0=CPOL-CPHA: 00 - MODE0 - data captured on rising edge, propagated on falling
                     01 - MODE1 - data captured on falling edge, propagated on rising
                     10 - MODE2 - data captured on falling edge, propagated on rising
                     11 - MODE3 - data captured on rising edge, propagated on falling
   BIT4-BIT2: 000 - A/B/C/D_DBUS3 = ChipSelect
            : 001 - A/B/C/D_DBUS4 = ChipSelect
            : 010 - A/B/C/D_DBUS5 = ChipSelect
            : 011 - A/B/C/D_DBUS6 = ChipSelect
            : 100 - A/B/C/D_DBUS7 = ChipSelect
   BIT5: для ChipSelect активный уровень 1, если этот бит равен 0
   BIT6-BIT31: зарезервировано
   */
   DWORD Pin;/* BIT7  - BIT0:  начальное направление работы ножек порта    */
             /* BIT15 - BIT8:  начальные уровни ножек порта                */
             /* BIT23 - BIT16: конечное направление работы ножек порта     */
             /* BIT31 - BIT24: конечные уровни ножек порта                 */
   USHORT currentPinState;/* BIT7  - BIT0: текущее направление ножек порта */
                          /* BIT15 - BIT8: текущие уровни ножек порта      */
}ChannelConfig;

ClockRate. Этот параметр принимает значение тактовой частоты SPI в Гц. Допустимы значения от 0 до 30 МГц.

LatencyTimer. Требуемое значение в миллисекундах для таймера задержки (latency timer), допустимый диапазон 0 .. 255. Однако FTDI рекомендует следующие значения для latency timer:

2 .. 255 Для полноскоростных (full speed) устройств (FT2232D).
1 .. 255 Для высокоскоростных (full speed) устройств (FT232H, FT2232H, FT4232H).

configOptions. В таблице ниже описано назначение бит этого поля.

№ бит Описание Знач. Назначение Макрос (если он есть)
1
..
0
Эти биты задают один из стандартных режимов SPI (описание режимов см. в [5]), которые можно настроить для SPI master. 00 SPI MODE0 SPI_CONFIG_OPTION_MODE0
01 SPI MODE1 SPI_CONFIG_OPTION_MODE1
10 SPI MODE2 SPI_CONFIG_OPTION_MODE2
11 SPI MODE3 SPI_CONFIG_OPTION_MODE3
4
..
2
Эти биты определяют, какие линии должны использоваться для сигнала выборки SPI (chip select, CS). 000 xDBUS3 SPI_CONFIG_OPTION_CS_DBUS3
001 xDBUS4 SPI_CONFIG_OPTION_CS_DBUS4
010 xDBUS5 SPI_CONFIG_OPTION_CS_DBUS5
011 xDBUS6 SPI_CONFIG_OPTION_CS_DBUS6
100 xDBUS7 SPI_CONFIG_OPTION_CS_DBUS7
5 Этот бит задает, должен ли быть активным лог. 0 для сигнала выборки. 0 Активный уровень лог. 1 SPI_CONFIG_OPTION_CS_ACTIVEHIGH
1 Активный уровень лог. 0 SPI_CONFIG_OPTION_CS_ACTIVELOW
31..6 Зарезервировано      

Примечание: xDBUS0 .. xDBUS7 соответствуют линиям ADBUS0 .. ADBUS7, если используется первый канал MPSSE, иначе линиям BDBUS0 .. BDBUS7, если используется второй канал (конечно, если он доступен для этого чипа).

Режимы SPI:

SPI MODE0 - данные захватываются по фронту нарастания уровня сигнала тактов, выводятся по спаду уровня сигнала тактов.
SPI MODE1 - данные захватываются по спаду уровня сигнала тактов, выводятся по фронту нарастания уровня сигнала тактов.
SPI MODE2 - данные захватываются по спаду уровня сигнала тактов, выводятся по фронту нарастания уровня сигнала тактов.
SPI MODE3 - данные захватываются по фронту нарастания уровня сигнала тактов, выводятся по спаду уровня сигнала тактов.

Поробнее про режимы SPI см. Википедию или [5].

Pins. Это поле задает направления и значения линий, связанных с младшим байтом канала MPSSE после того, как вызываны SPI_InitChannel и SPI_CloseChannel.

№ бит Описание Комментарий
7 .. 0 Направление линий после того, как была вызвана SPI_InitChannel. Лог. 1 соответствует выходу, и лог. 0 входу.
15 .. 8 Состояния линий после того, как была вызвана SPI_InitChannel. Лог. 1 соответствует лог. 1 на выходе, лог. 0 соответствует лог. 0 на выходе.
23 .. 16 Направление линий после того, как была вызвана SPI_CloseChannel. Лог. 1 соответствует выходу, и лог. 0 входу.
31 .. 24 Состояния линий после того, как была вызвана SPI_CloseChannel. Лог. 1 соответствует лог. 1 на выходе, лог. 0 соответствует лог. 0 на выходе.

Обратите внимание, что направления сигналов SCLK, MOSI и указнного сигнала выборки будут перезаписаны в 1, и направление MISO подобным образом будет перезаписано в 0, независимо от значений, которые были переданы из приложения пользователя. Остальные 4 линии будут настроены в соответствии с этим параметром.

reserved. Это поле зарезервировано и не должно использоваться.

[Пример использования]

Этот пример демонстрирует, как подключить FT2232H к устройству SPI (микросхема EEPROM-памяти 93LC56B), и как её программировать с использованием библиотеки libMPSSE-SPI.

libMPSSE SPI schematic connecting FT2232H to SPI EEPROM 93LC56B fig03

Рис. 3. Схема подключения FT2232H к SPI EEPROM 93LC56B.

Обратите внимание, что чип FT2232H может быть доступен на готовом модуле (например [6]), который содежит все компоненты схемы на рис. 3, кроме микросхемы 93LC56B и подключенных к ней верхних подтягивающих резисторов (pull-up). Чип FT2232H работает как SPI master, подключенный к PC через интерфейс USB.

Пример программы. В системе должен быть установлен требуемый драйвер D2XX (это зависит от используемой операционной системы [3]). Если используется Linux PC, то должны быть удалены драйверы по умолчанию usbserial и ftdi_sio (с помощью команды rmmod).

Как только схема, показанная на рис. 3, подключена к PC, и установлены соответствующие драйверы, мы можем поместить код следующего примера (sample-static.c), файлы D2XX.h, libMPSSE_spi.h и библиотеку libMPSSE.a в одну из папок. После этого скомпилируйте и запустите пример.

/*!
 * \file sample-static.c
 *
 * \author FTDI
 * \date 20110512
 *
 * Copyright © 2011 Future Technology Devices International Limited
 *
 * Проект: libMPSSE
 * Модуль: SPI Sample Application - подключение 94LC56B SPI EEPROM
 * \sa Даташит 93LC56B http://ww1.microchip.com/downloads/en/DeviceDoc/21794F.pdf
 *
 * История ревизий:
 * 0.1 - 20110512 - начальная версия
 * 0.2 - 20110801 - изменено LatencyTimer на 255
 *                  делается попытка открыть канал, только если он доступен
 *                  добавлены и изменены макросы
 *                  подключен stdlib.h
 * 0.3 - 20111212 - добавлены комментарии
 */
 
/******************************************************************************/
/* Подключаемые заголовки                                                     */
/******************************************************************************/
/* Стандартные библиотеки C */
#include < stdio.h>
#include < stdlib.h>
/* Библиотеки, специфичные для OS */
#ifdef _WIN32
#include < windows.h>
#endif
 
/* Заголовок для драйвера D2XX */
#include "ftd2xx.h"
 
/* Заголовок libMPSSE */
#include "libMPSSE_spi.h"
 
/******************************************************************************/
/* Определение макросов и типов                                               */
/******************************************************************************/
/* Вспомогательные макросы */
#define APP_CHECK_STATUS(exp) {if(exp!=FT_OK){printf("%s:%d:%s(): status(0x%x) \
!= FT_OK\n",__FILE__, __LINE__, __FUNCTION__,exp);exit(1);}else{;}};
#define CHECK_NULL(exp){if(exp==NULL){printf("%s:%d:%s(): NULL expression \
encountered \n",__FILE__, __LINE__, __FUNCTION__);exit(1);}else{;}};
 
/* Определение макросов, специфичных для приложения */
#define SPI_DEVICE_BUFFER_SIZE 256
#define SPI_WRITE_COMPLETION_RETRY 10
#define START_ADDRESS_EEPROM 0x00 /* начальный адрес read/write внутри EEPROM */
#define END_ADDRESS_EEPROM 0x10
#define RETRY_COUNT_EEPROM 10     /* количество повторны попыток, если была неудача read/write */
#define CHANNEL_TO_OPEN 0         /* 0 для первого доступного канала, 1 для следующего, и т. д. */
#define SPI_SLAVE_0 0
#define SPI_SLAVE_1 1
#define SPI_SLAVE_2 2
#define DATA_OFFSET 3
 
/******************************************************************************/
/* Глобальные переменные                                                      */
/******************************************************************************/
uint32 channels;
FT_HANDLE ftHandle;
ChannelConfig channelConf;
uint8 buffer[SPI_DEVICE_BUFFER_SIZE];
 
/******************************************************************************/
/* Определение публичных функций                                              */
/******************************************************************************/
/*!
 * \brief Чтение EEPROM
 *
 * Эта функция прочитает байты по указанному адресу в 93LC56B EEPROM
 *
 * \param[in] address адрес, куда запишется байт
 * \param[in] data указатель, куда пишутся прочитанные байты
 * \return Возвратит код состояния FT_STATUS (см. D2XX Programmer's Guide, или [4])
 */
FT_STATUS read_byte (uint8 address, uint16 *data)
{
   uint32 sizeToTransfer = 0;
   uint32 sizeTransfered;
   bool writeComplete = 0;
   uint32 retry = 0;
   bool state;
   FT_STATUS status;
 
   /* CS_High + Write command + Address */
   sizeToTransfer = 1;
   sizeTransfered = 0;
   buffer[0] = 0xC0; /* Команда чтения (3 бита) */
   // Добавление 5 старших бит:
   buffer[0] = buffer[0] | ( ( address >> 3) & 0x0F );
   status = SPI_Write(ftHandle, buffer, sizeToTransfer, &sizeTransfered,
   SPI_TRANSFER_OPTIONS_SIZE_IN_BYTES|
   SPI_TRANSFER_OPTIONS_CHIPSELECT_ENABLE);
   APP_CHECK_STATUS(status);
 
   /* Запись части бит адреса */
   sizeToTransfer = 4;
   sizeTransfered = 0;
   // Остальные 3 младших бита адреса:
   buffer[0] = ( address & 0x07 ) << 5;
   status = SPI_Write(ftHandle, buffer, sizeToTransfer, &sizeTransfered,
   SPI_TRANSFER_OPTIONS_SIZE_IN_BITS);
   APP_CHECK_STATUS(status);
 
   /* Чтение 2 байт */
   sizeToTransfer = 2;
   sizeTransfered = 0;
   status = SPI_Read(ftHandle, buffer, sizeToTransfer, &sizeTransfered,
   SPI_TRANSFER_OPTIONS_SIZE_IN_BYTES|
   SPI_TRANSFER_OPTIONS_CHIPSELECT_DISABLE);
   APP_CHECK_STATUS(status);
 
   *data = (uint16)(buffer[1] << 8);
   *data = (*data & 0xFF00) | (0x00FF & (uint16)buffer[0]);
 
   return status;
}
 
/*!
 * \brief Запись EEPROM
 *
 * Эта функция запишет байты по указанному адресу в 93LC56B EEPROM
 *
 * \param[in] address адрес памяти в EEPROM
 * \param[in] data указатель, откуда берутся записываемые байты
 * \return Returns status code of type FT_STATUS(see D2XX Programmer's Guide)
 * \return Возвратит код состояния FT_STATUS (см. D2XX Programmer's Guide, или [4])
 */
FT_STATUS write_byte (uint8 address, uint16 data)
{
   uint32 sizeToTransfer = 0;
   uint32 sizeTransfered = 0;
   bool writeComplete = 0;
   uint32 retry = 0;
   bool state;
   FT_STATUS status;
 
   /* Запись команды EWEN (с сигналом выборки CS_High -> CS_Low) */
   sizeToTransfer = 11;
   sizeTransfered = 0;
   buffer[0] = 0x9F;/* SPI_EWEN -> binary 10011xxxxxx (11 бит) */
   buffer[1] = 0xFF;
   status = SPI_Write(ftHandle, buffer, sizeToTransfer, &sizeTransfered,
                      SPI_TRANSFER_OPTIONS_SIZE_IN_BITS|
                      SPI_TRANSFER_OPTIONS_CHIPSELECT_ENABLE|
                      SPI_TRANSFER_OPTIONS_CHIPSELECT_DISABLE);
   APP_CHECK_STATUS(status);
 
   /* CS_High + Write command + Address */
   sizeToTransfer = 1;
   sizeTransfered = 0;
   buffer[0] = 0xA0; /* Команда записи (3 бита) */
   // Добавление 5 старших бит:
   buffer[0] = buffer[0] | ( ( address >> 3) & 0x0F );
   status = SPI_Write(ftHandle, buffer, sizeToTransfer, &sizeTransfered,
                      SPI_TRANSFER_OPTIONS_SIZE_IN_BYTES|
                      SPI_TRANSFER_OPTIONS_CHIPSELECT_ENABLE);
   APP_CHECK_STATUS(status);
 
   /* Запись 3 младших бит адреса */
   sizeToTransfer = 3;
   sizeTransfered = 0;
   buffer[0] = ( address & 0x07 ) << 5;   // младшие 3 бита адреса
   status = SPI_Write(ftHandle, buffer, sizeToTransfer, &sizeTransfered,
                      SPI_TRANSFER_OPTIONS_SIZE_IN_BITS);
   APP_CHECK_STATUS(status);
 
   /* Запись 2 байт данных + CS_Low */
   sizeToTransfer = 2;
   sizeTransfered = 0;
   buffer[0] = (uint8)(data & 0xFF);
   buffer[1] = (uint8)((data & 0xFF00)>>8);
   status = SPI_Write(ftHandle, buffer, sizeToTransfer, &sizeTransfered,
                      SPI_TRANSFER_OPTIONS_SIZE_IN_BYTES|
                      SPI_TRANSFER_OPTIONS_CHIPSELECT_DISABLE);
   APP_CHECK_STATUS(status);
 
   /* Ожидание перехода D0 в лог. 1 */
#if 1
   /* Strobe Chip Select */
   sizeToTransfer = 0;
   sizeTransfered = 0;
   status = SPI_Write(ftHandle, buffer, sizeToTransfer, &sizeTransfered,
                      SPI_TRANSFER_OPTIONS_SIZE_IN_BITS|
                      SPI_TRANSFER_OPTIONS_CHIPSELECT_ENABLE);
   APP_CHECK_STATUS(status);
#ifndef __linux__
   Sleep(10);
#endif
   sizeToTransfer = 0;
   sizeTransfered = 0;
   status = SPI_Write(ftHandle, buffer, sizeToTransfer, &sizeTransfered,
                      SPI_TRANSFER_OPTIONS_SIZE_IN_BITS|
                      SPI_TRANSFER_OPTIONS_CHIPSELECT_DISABLE);
   APP_CHECK_STATUS(status);
#else
   retry = 0;
   state = FALSE;
   SPI_IsBusy(ftHandle, &state);
   while((FALSE==state) && (retry < SPI_WRITE_COMPLETION_RETRY))
   {
      printf("SPI device is busy(%u)\n", (unsigned)retry);
      SPI_IsBusy(ftHandle, &state);
      retry++;
   }
#endif
   /* Запись команды EWEN(with CS_High -> CS_Low) */
   sizeToTransfer = 11;
   sizeTransfered = 0;
   buffer[0] = 0x8F;   /* SPI_EWEN -> binary 10011xxxxxx (11 бит) */
   buffer[1] = 0xFF;
   status = SPI_Write(ftHandle, buffer, sizeToTransfer, &sizeTransfered,
                      SPI_TRANSFER_OPTIONS_SIZE_IN_BITS|
                      SPI_TRANSFER_OPTIONS_CHIPSELECT_ENABLE|
                      SPI_TRANSFER_OPTIONS_CHIPSELECT_DISABLE);
   APP_CHECK_STATUS(status);
 
   return status;
}
 
/*!
 * \brief Точка входа в приложение примера.
 *
 * Эта функция откроет канал, запишет данные в EEPROM, и прочитает
 *
 данные обратно.
 *
 * \return Вернет 0 в случае успеха
 */
int main()
{
   FT_STATUS status;
   FT_DEVICE_LIST_INFO_NODE devList;
   uint8 address = 0;
   uint16 data;
   int i, j;
#ifdef _MSC_VER
   Init_libMPSSE();
#endif
   channelConf.ClockRate = 5000;
   channelConf.LatencyTimer = 255;
   channelConf.configOptions = SPI_CONFIG_OPTION_MODE0 | SPI_CONFIG_OPTION_CS_DBUS3;
   /* FinalVal-FinalDir-InitVal-InitDir (для 0=in, 1=out) */
   channelConf.Pin = 0x00000000;
 
   status = SPI_GetNumChannels(&channels);
   APP_CHECK_STATUS(status);
   printf("Number of available SPI channels = %d\n", channels);
 
   if(channels>0)
   {
      for(i=0; i < channels; i++)
      {
         status = SPI_GetChannelInfo(i, &devList);
         APP_CHECK_STATUS(status);
         printf("Information on channel number %d:\n", i);
         /* Печать информации об устройстве */
         printf(" Flags=0x%x\n", devList.Flags);
         printf(" Type=0x%x\n", devList.Type);
         printf(" ID=0x%x\n", devList.ID);
         printf(" LocId=0x%x\n", devList.LocId);
         printf(" SerialNumber=%s\n", devList.SerialNumber);
         printf(" Description=%s\n", devList.Description);
         // ftHandle == 0, если канал не открыт:
         printf(" ftHandle=0x%x\n", devList.ftHandle);
      }
 
      /* Открытие первого доступного канала */
      status = SPI_OpenChannel(CHANNEL_TO_OPEN, &ftHandle);
      APP_CHECK_STATUS(status);
      printf("\nhandle=0x%x status=0x%x\n", ftHandle, status);
      status = SPI_InitChannel(ftHandle, &channelConf);
      APP_CHECK_STATUS(status);
 
      for(address=START_ADDRESS_EEPROM; address < END_ADDRESS_EEPROM; address++)
      {
         printf("writing address = %d data = %d\n", address,
                                                    address+DATA_OFFSET);
         write_byte(address, (uint16)address+DATA_OFFSET);
      }
 
      for(address=START_ADDRESS_EEPROM; address < END_ADDRESS_EEPROM; address++)
      {
         read_byte(address, &data);
         read_byte(address, &data);
         printf("reading address=%d data=%d\n", address, data);
      }
 
      status = SPI_CloseChannel(ftHandle);
   }
 
#ifdef _MSC_VER
   Cleanup_libMPSSE();
#endif
   return 0;
}

Показанная выше программа выполняет запись данных в микросхему EEPROM по адресам 0 .. 15. Записываемое значение генерируется из адреса +3, например если адрес 5, то по этому адресу записывается значение 8. Когда этот пример программы компилируется и запускается, мы должны увидеть следующий вывод:

libMPSSE SPI sample output fig04

Рис. 4. Вывод примера программы.

Пример кода, который можно скачать по ссылке [3], рассчитан на современную версию Visual Studio (не менее 2019). Чтобы его скомпилировать в среде Visual Studio 2010 Ultimate, мне пришлось немного повозиться. Ниже описан процесс по шагам.

1. Установите Visual Studio 2010 Ultimate.

2. Создайте проект Visual C++ -> Консольное приложение Win32.

3. Скачайте архив LibMPSSE_1.0.3.zip с сайта FTDI (LibMPSSE-SPI Software Examples site:ftdichip.com).

4. Создайте главный модуль исходного кода по аналогии с примером из AN_178 (*.cpp, см. врезку выше).

5. Добавьте в проект модуль ftdi_infra.c из архива LibMPSSE_1.0.3.zip, предварительно переименовав его в *.cpp.

6. Оберните в комментарий префикс FTDIMPSSE_API у функций из ftdi_infra.cpp.

7. Распакуйте библиотеку libmpsse-windows-1.0.3.zip\release\build\Win32\libmpsse.lib в директорию проекта. Добавьте её в проект перетаскиванием в корень дерева проекта.

8. Настройте в проекте пути до подключения нужных заголовков.

9. Скомпилируйте проект.

10. Распакуйте libmpsse-windows-1.0.3.zip\release\build\Win32\libmpsse.dll в каталог, где находится *.exe файл компилируемого проекта (обычно по умолчанию это папка Debug).

[Ссылки]

1. AN_178 Programming Guide for libMPSSE SPI site:ftdichip.com.
2. AN_114: подключение FT2232H к шине SPI.
3. LibMPSSE-SPI Software Examples site:ftdichip.com.
4. FTDI: справочник по функциям библиотеки D2XX.
5. Интерфейс SPI.
6. FT2232H Board - макетная плата на высокоскоростном чипе моста USB фирмы FTDI.
7. 220728FT2232H-SPI-writer.zip - документация, готовый проект для Visual Studio 2010.