Компания FTDI реализовала в своих чипах аппаратную технологию Multi-Protocol Synchronous Serial Engine, сокращенно MPSSE. MPSSE предоставляет гибкое средство сопряжения синхронных последовательных интерфейсов с портом USB. "Multi-Protocol" в названии MPSSE означает, что возможна реализация множества синхронных популярных последовательных протоколов, таких как SPI, I2C и JTAG. Форматирование данных и синхронизация тактов можно сконфигурировать различными способами, что в большинстве случаев может удовлетворить требованиям любых протоколов. В дополнение к выводам последовательных данных доступны дополнительные сигналы GPIO. В этом документе (перевод апноута AN_135 [1]) описываются базовые методы конфигурирования MPSSE для использования, и демонстрируются некоторые доступные режимы работы.
[Требуемые компоненты]
Использование MPSSE требует применения определенных аппаратных и программных средств:
1. Микросхема FTDI FT-серии, оборудованная MPSSE. На момент публикации апноута [1] компания FTDI производила три устройства с блоком MPSSE:
a. FT2232D – USB 2.0 Full-Speed Dual UART/FIFO с одним блоком MPSSE (6Mbps максимум). b. FT2232H – USB 2.0 Hi-Speed Dual UART/FIFO с двумя блоками MPSSE (по 30Mbps каждый, максимум). c. FT4232H – USB 2.0 Hi-Speed Quad UART с двумя блоками MPSSE (по 30Mbps каждый, максимум).
2. Драйверы устройства FTDI D2XX:
a. Требуется последняя версия драйверов устройства D2XX. Этот драйвер доступен для нескольких операционных систем. Для загрузки драйвера посетите страничку [2]. На момент создания перевода [1] 190918 для всех версий операционных систем Windows от Windows XP до Windows 10 (как 32-битных, так и 64-битных версий) существовал для закачки единый пакет драйверов CDM v2.12.28 WHQL Certified.zip, где находились драйверы VCP и D2XX, а также библиотеки DLL для работы с функциями API D2XX [3].
3. Документация:
a. Даташит на используемый чип FTDI FT-серии с блоком MPSSE. b. Руководство по программированию D2XX [3]. c. Апноут AN_108 (или см. его перевод [4]).
Хотя предоставленные на сайте FTDI примеры программирования и библиотеки специфичны для реализации интерфейсов SPI, I2C и JTAG, часто проще получить доступ к MPSSE напрямую через вызовы D2XX. В этой статье как раз рассматриваются примеры таких вызовов.
Примеры кода, содержащиеся в этом документе, предназначены только для демонстрации, и FTDI не несет никакой ответственности и не гарантирует правильность этого кода.
Режимы Master/Slave. MPSSE всегда является master-контроллером для выбранного синхронного интерфейса. Таким образом, генерируются такты, требуемые сигналы интерфейса и сигналы выборкиs. MPSSE не работает в режиме slave.
[Аппаратные соединения]
Имеется 4 определенных вывода в каждом канале MPSSE вместе с набором выводов GPIO. Таблица 2.1 показывает различные назначения выводов.
Таблица 2.1. Назначение выводов MPSSE.
Имя сигнала
FT2232H
FT4232H
FT2232D
Channel A
Channel B
Channel A
Channel B
Channel A
TDI/DO
17
39
17
27
23
TDO/DI
18
40
18
28
22
TCK/SK
16
38
16
26
24
TMS/CS
19
41
19
29
21
GPIOL0
21
43
21
30
20
GPIOL1
22
44
22
32
19
GPIOL2
23
45
23
33
17
GPIOL3
24
46
24
34
16
GPIOH0
26
48
-
-
15
GPIOH1
27
52
-
-
13
GPIOH2
28
53
-
-
12
GPIOH3
29
54
-
-
11
GPIOH4
30
55
-
-
-
GPIOH5
32
57
-
-
-
GPIOH6
33
58
-
-
-
GPIOH7
34
59
-
-
-
MPSSE можно сконфигурировать для поддержки почти любого синхронного интерфейса. Таблица 2.2 показывает назначение сигналов для наиболее популярных интерфейсов SPI, I2C и JTAG, а также сигналы GPIO, которые получают предварительно определенную функцию.
Таблица 2.2. Присваивание сигналов популярных синхронных шин.
Сигнал MPSSE
Назначение для SPI
Назначение для I2C
Назначение для JTAG
TDI/DO (вывод данных)
MOSI
SDA(1)
TDI(2)
TDO/DI (ввод данных)
MISO
SDA(1)
TDO(2)
TCK/SK (такты)
SCLK
SCK
TCK
TMS/CS (выборка)
CS
не используется
TMS
GPIOL0
доступно
GPIOL1
WAIT(4) или STOPCLK(3)(4)
GPIOL2
доступно
GPIOL3
доступно
RTCK(3)(4)
GPIOH0
доступно
GPIOH1
доступно
GPIOH2
доступно
GPIOH3
доступно
GPIOH4
доступно
GPIOH5
доступно
GPIOH6
доступно
GPIOH7
доступно
Примечания:
(1) Выводы DI и DO нужно соединить вместе, чтобы создать полный сигнал SDA для протокола I2C/TWI. Вывод DO требуется сконфигурировать как вход кроме случая, когда идет передача - чтобы избежать конфликта выходов при передаче данных slave-устройством. (2) Имена сигналов даны по отношению к цепочке JTAG. TDI это вход для первого устройства в цепочке JTAG; сигнал MPSSE DO будет подключаться к TDI. TDO это выход из последнего устройства в цепочке JTAG; сигнал MPSSE DI будет подключен к TDO. (3) Только для микросхем FT2232H и FT4232H. (4) Эти выводы доступны для GPIO если не используются команды MPSSE, которые задействуют эти выводы.
[Рекомендуемые соединения]
Каждый тип интерфейса требует для себя определенных уникальных схемных решений. Из-за такого разнообразия здесь нельзя рассмотреть каждый конкретный случай. Далее показаны некоторые реализации интерфейсов, которые можно рассматривать как отправную точку для создания каждого из популярных интерфейсов.
Драйверы сигналов I/O высокоскоростных устройств FTDI USB (Hi-Speed devices FT2232H и FT4232H) работают с уровнями логики 3.3V. Их входы допускают уровни логики 5V (5V-tolerant), т. е. их можно напрямую подключать к устройствам с интерфейсом, где действуют уровни 5V. Для полноскоростного устройства FTDI USB (Full-Speed device FT2232D) интерфейс I/O работает с уровнями логики, определяемыми напряжением VCCIO. Следует внимательно изучить все даташиты применяемых устройств, чтобы гарантировать совместимость порогов уровней I/O и максимально допустимых напряжений.
SPI – Single Slave. При подключении одного подчиненного SPI-устройства существует взаимосвязь сигналов один-к-одному. У некоторых SPI-устройств нет одновременного использования входных и выходных сигналов данных. Например, АЦП может не иметь сигнала ввода данных MOSI, а у ЦАП может не быть сигнала вывода данных MISO. Сигналы чипов FTx232D/FTx232DH снабжены внутренними резисторами верхней подтяжки (pull-up), поэтому если они не используются, то могут оставаться не подключенными.
Рис. 2.1. Пример схемы SPI с подключением одного подчиненного устройства (Single Slave).
Сигнал выборки (chip select, CS) используется для разрешения интерфейса slave-устройства. Когда на шине присутствует только одно slave-устройство SPI, его выборка может напрямую подключена к активному уровню выборки интерфейса. Некоторые микросхемы SPI требуют для обмена подачи импульса выборки.
SPI – Multiple Slaves. Несколько подчиненных устройств SPI (multiple SPI slaves) совместно используют сигналы входа данных, выхода данных и тактов. Однако для каждого slave-устройства требуется отдельный сигнал выборки (chip select, CS). Для выборки можно использовать любой доступный сигнал GPIO, в дополнение к аппаратному сигналу MPSSE CS, который можно использовать как еще один сигнал выборки. В любой момент времени на шине может быть активным только одно устройство. Программа приложения должна отслеживать, какое из SPI slave устройство разрешено в любой момент времени.
Рис. 2.2. Пример схемы SPI с подключением нескольких подчиненных устройств (Multiple Slaves).
Как и со случаем единственного slave-устройства, не используемые сигналы DO и DI можно оставить не подключенными.
I2C – Single Slave. I2C это двунаправленная, полудуплексная схема обмена данными. Хотя полная спецификация позволяет использовать несколько master-устройств на шине, MPSSE позволяет поддерживать режим только с одним мастером. Подчиненных стройств может быть как одно, так и несколько. Ниже на рис. 2.3 показан случае с одним подчиненным устройством I2C (Single Slave).
Рис. 2.3. Пример подключения одного подчиненного устройства I2C (Single Slave).
Дополнительно программе приложения понадобятся дополнительные шаги для изменения направления сигнала MPSSE DO, чтобы не допустить конфликта выходов на шине.
Возможны и другие схемы соединения, однако их рассмотрение выходит за рамки этого документа.
I2C – Multiple Slaves. Подключение к шине I2C нескольких устройств осуществляется таким образом, что все сигналы соединяются параллельно.
Рис. 2.4. Пример подключения нескольких подчиненных устройств I2C (Multiple Slaves).
Как и для одного подчиненного устройства, на шине не может быть только одно master-устройство (чипа FTDI), и направление сигнала MPSSE DO требуется переключать, чтобы не было конфликта выходов на шине.
JTAG. Реализации JTAG хорошо описаны в спецификации IEEE 1149.1. DO подключается к первому сигналу JTAG TAP TDI в цепочке. Первый JTAG TDO соединяется со вторым JTAG TDI. Так продолжается для всех устройств в цепочке JTAG. Последний TDO будет подключаться ко входу MPSSE DI. Сигналы TCK и TMS соединяются параллельно для всех устройств JTAG TAP devices.
Рис. 2.5. JTAG - пример схемы с несколькими TAP.
Часто с целью разбиения длинной цепочки JTAG применяются другие схемы соединения. Эти случаи выходят за рамки описания в этом документе.
[Конфигурация последовательного протокола]
Все команды MPSSE, как и общие коммуникации с FTDI MPSSE, передаются по шине USB от компьютера-хоста к микросхеме FT-серии. Для этого требуются вызовы D2XX API, и все команды MPSSE формируются посредством вызовов чтения и записи (см. выше раздел "Требуемые компоненты").
После того, как сформирована требуемая схема соединений, нужно определиться, как конфигурировать MPSSE для реального обмена с управляемым устройством (target device) или устройствами. Ниже рассматривается, какие параметры нужно для этого установить.
Порядок бит и длина данных (MSB/LSB и Data Length). Команды MPSSE конфигурируют обмен для отправки и приема данных, когда первым отправляется либо старший бит (MSB), либо младший (LSB). Проконсультируйтесь с описанием протокола управляемой микросхемы, в каком порядке следует посылать биты данных. Важно правильно определить нужную команду MPSSE, чтобы направление передачи данных было соответствующим.
Команды MPSSE задаются в байтах, независимо от ориентации целевого управляемого устройства (target device). Например, если 93C46D EEPROM сконфигурирована для 16-битной ориентации, то байт 0 передается первым из очереди MPSSE, и он будет самым значащим битом в 16-битном слове (байты передаются с порядком следования big endian). Байт 1 будет передан следующим, и он станет младшим значащим байтом в 16-битном слове.
Существуют как байт-ориентированные, так и бит-ориентированные команды MPSSE, чтобы можно было приспособиться к устройствам, которые могут не работать с 8-битными инкрементами внутренних данных.
Частота тактов. Как и с направлением передачи данных, важно определить не только скорость (частоту) следования импульсов тактов, но также их активные перепады по отношению к другим сигналам. Т. е. на каких перепадах (спад, нарастание) уровня тактов устройство FTx232H/FTx232D будет передавать и принимать данные.
FT2232D работает на базовой тактовой частоте 12 МГц. 16-битный делитель частоты (Divisor), используемый для программирования скорости передачи, рассчитывается по следующей формуле:
12 МГц Скорость данных = ----------------- (1 + Divisor) * 2
16-битное значение Divisor в диапазоне от 0x0000 до 0xFFFF покрывает возможные скорости передачи между 6 МГц и 92 Гц.
Микросхемы FT2232H и FT4232H (FTx232H), работающие на базовой частоте 60 МГц, в 5 раз быстрее FT2232D. Для них формула скорости передачи следующая:
60 МГц Скорость данных = ----------------- (1 + Divisor) * 2
16-битное значение Divisor микросхем FTx232H в диапазоне от 0x0000 до 0xFFFF покрывает возможные скорости передачи между 30 МГц и примерно 460 Гц.
У микросхем FTx232H также есть опция деления на 5. По умолчанию она разрешена, чтобы сохранить совместимость с FT2232D. Когда активна опция дополнительного деления на 5, для расчета скорости используется формула для микросхемы FT2232D.
Активные перепады тактов. Данные обычно вдвигаются и выдвигаются по перепадам уровня тактов. Для передачи или приема могут использоваться либо спад (от лог. 1 к лог. 0), либо нарастание (от лог. 0 к лог. 1) уровня сигнала тактов. В результате получается 6 возможных вариантов, показанных в таблице 3.1.
Таблица 3.1. Перемещение данных и перепады тактов.
Перепад тактов для передачи
Перепад тактов для приема
Состояние тактов в режиме ожидания
Фронт ↑
Фронт ↑
Недопустимо
Фронт ↑
Спад
Лог. 0
Спад ↓
Фронт ↑
Лог. 1
Спад ↓
Спад ↓
Недопустимо
Фронт ↑
Нет передачи данных
Лог. 0
Спад ↓
Нет передачи данных
Лог. 1
Нет передачи данных
Фронт ↑
Лог. 0
Нет передачи данных
Спад ↓
Лог. 1
Обратите внимание, что возможны как двунаправленные, так и однонаправленные перемещения данных.
На основе этой таблицы и даташита на целевое устройство программист выбирает, какие перепады нужны для передачи в определенном направлении. В вышеупомянутом примере 93C46D данные EEPROM вдвигаются и выдвигаются по фронту нарастания тактов. В этом случае MPSSE должна быть сконфигурирована для передачи данных по спаду тактов для передачи и приема. Это позволит стабилизироваться данным на шине в момент возникновения фронта нарастания уровня тактов.
Такты без передачи данных. Могут быть выданы команды для генерации сигнала тактов без какой-либо передачи данных. Доступны опции, которые останавливают генерацию тактов сигналом GPIO, или простым указанием нужного количества генерируемых импульсов тактов.
3-фазное тактирование (только для FT2232H и FT4232H). Для I2C данные доступны для target на обоих перепадах тактового сигнала SCK. Изменение уровня данных SDA происходит только когда на сигнале тактов SCK присутствует уровень лог. 0.
Особенности реализации JTAG. В дополнение к сигналам in/out данных и тактам, JTAG требует четвертого сигнала TMS, уровни которого осуществляют навигацию по машине состояний IEEE 1149.1. MPSSE обрабатывает TMS как некий второй выход данных. Цепочка команд, отправляемых в MPSSE, состоит как из ввода/вывода данных, так и инструкций вывода TMS.
Все обмены данными JTAG осуществляются с передачей первым младшего бита (LSB). Данные вдвигаются в Test Access Port (TAP) по фронту нарастания тактов TCK, так что MPSSE должна вытолкнуть данные наружу по спаду уровня тактов. Данные выдвигаются их TAP по спаду уровня тактов TCK, поэтому MPSSE должна ввести данные на фронте нарастания тактов TCK. Начальное состояние (как и в состоянии ожидания на шине) тактов TCK - лог. 1.
Адаптивное тактирование это средство синхронизации при генерации TCK для некоторых target MCU. Сигнал RTCK (return clock) это вход, по которому MPSSE подстраивает выход TCK на соответствие внутренней частоте JTAG TAP. Обычно эта функция используется в микроконтроллерах на ядре ARM.
Пример программирования высокоскоростной микросхемы FT2232H см. в апноуте "AN_129 Interfacing FTDI USB Hi-Speed Devices to a JTAG TAP" (перевод этого апноута см. в статье [5]).
Начальные состояния выводов. Важно отметить, что генерация тактов зависит от начального состояния выхода SK. Генерация тактов выполняется в соответствии с таблицей 3.2.
Таблица 3.2. Логические уровни для генерации импульса тактов.
Начальное состояние SK/TCK
Генерируемый импульс тактов
0
0 → 1 → 0
1
1 → 0 → 1
Перед отправкой или приемом любых данных должны быть сконфигурированы 4 выделенных вывода MPSSE и любые сигналы GPIO, чтобы у них были правильные направления, и если это выход, то у него должно быть установлено правильное начальное состояние. Для дополнительной информации по тактированию данных см. секцию "2.2 Clock Operation" апноута AN_108 (или см. его перевод [4], секция "Работа тактирования").
Более подробное обсуждение шагов программирования для конфигурации MPSSE и обмена через неё приведено в секции "Программная конфигурация" далее.
Размеры буферов. Данные и команды MPSSE смешиваются в одном буфере, как показано ниже в таблице 3.3. Можно отправить в MPSSE несколько команд через один вызов FT_Write. Буферы приложения должны быть соответствующего размера, чтобы в них поместилась самая большая комбинация команд и данных в одном вызове.
Таблица 3.3. Выделение размера буфера.
Тип данных
Длина (в байтах)
Команда (opcode)
1
Длина данных
2
Данные, полезная нагрузка
от 1 до 65536
Всего
от 4 до 65539
[Программная конфигурация]
Весь обмен с MPSSE осуществляется через FTDI D2XX API, в соответствии с документом "D2XX Programmer’s Guide" компании FTDI. Перевод этого документа см. в статье [3].
Рис. 4.1. Алгоритм использования MPSSE.
Следующие секции описывают требуемые шаги для конфигурирования MPSSE и обмена с ней.
Подтверждение наличие устройства и открытие его дескриптора. Перед тем, как начать использовать MPSSE, программа приложения должна найти в компьютере хоста, сколько подключено к нему USB-устройств FT-серии, и после нужно выбрать одно из этих устройств. Эти действия выполняются следующими вызовами D2XX API:
1. FT_CreateDeviceInfoList – этот вызов возвратит количество подключенных к системе устройств FT-серии. Важно отметить, что в это количество входит каждый порт многопортового чипа (например, у микросхемы FT2232H таких портов два). 2. FT_GetDeviceInfoList или FT_GetDeviceInfoListDetail – в зависимости от того, как это используется, вызов возвратить информацию по каждому из доступных устройств. Информация включает имя устройства, какой это порт (например “FT2232H A” или “FT2232H B”), USB Location ID, USB Serial Number, и наиболее важный дескриптор USB (USB Handle). 3. FT_Open или FT_OpenEx – как только определена информация порта, приложение должно открыть порт, используя дескриптор, полученный на шаге 2.
Конфигурирование порта FTDI для использования MPSSE. После открытия порта должны быть сконфигурированы несколько параметров перед тем, как можно будет разрешить MPSSE. Конфигурирование состоит из следующих шагов:
1. FT_ResetDevice – сброс периферии порта FTDI. 2. FT_SetUSBParameters – конфигурирование максимальных размеров передач USB. Значение размера может быть установлено от 64 байт до 64 килобайт, и это зависит от количества данных, которые нужно передать или принять. Можно установить отдельные размеры входных и выходных передач. 3. FT_SetChars – конфигурирует символы события и ошибки (event, error characters). Большинство приложений запрещают любые символы события и ошибки. 4. FT_SetTimeouts – конфигурирует таймауты чтения и записи в миллисекундах. Эти таймауты по умолчанию запрещены, что обычно дает драйверу устройства шанс избежать неправильной передачи. 5. FT_SetLatencyTimer – конфигурирует величину времени для ожидания перед отправкой неполного пакета USB от периферийного устройства обратно хосту. Для приложений, которые требуют быстрого ответа от периферийного устройства, установите таймер latency в низкое значение. 6. FT_SetFlowControl – сконфигурируйте RTS/CTS flow control, чтобы гарантировать что драйвер не будет высылать запросы IN, если буфер не может принять данные. 7. FT_SetBitMode – mode = 0, mask = 0 – сброс контроллера MPSSE. Выполняет общий сброс MPSSE, но не самого порта. 8. FT_SetBitMode – mode = 2, mask = 0 – разрешает контроллер MPSSE. Направления вывода устанавливаются позже командами MPSSE.
Конфигурирование MPSSE. Начиная с этого момента MPSSE готова к приему команд (код операции, op-code). Команды MPSSE состоят из кода операции, за которым идут один или несколько параметров. Коды операции определены в апноуте AN_108 (см. перевод этого апноута в статье [4]). Функция FT_Write используется для отправки команд и параметров в MPSSE. Ответы от MPSSE программа приложения читает с помощью функции FT_Read.
Синхронизация и определение неправильной команды (Bad Command Detection). Если была определена неправильная команда, то MPSSE возвратит значение 0xFA, за которым идет байт, который вызвал состояние bad command.
Рекомендуется использовать bad command detection как метод определить, засинхронизирована ли MPSSE с программой приложения. Путем отправки ошибочной команды и ожидания ответа 0xFA приложение может определить, возможен или нет обмен с MPSSE.
MPSSE Setup. Когда связь установлена, MPSSE должна теперь быть сконфигурирована на частоту тактов, направление работы выводов и начальное состояние выводов. MPSSE в микросхемах FT2232H и FT4232H Hi-Speed USB имеют дополнительные параметры, которые надо установить: Divide clock by 5 (дополнительное деление тактовой частоты на 5), 3-phase data clocking (трехфазное тактирование данных) и JTAG adaptive clocking (адаптивное тактирование JTAG). Хотя установки по умолчанию могут подойти к какому-то приложению, всегда хорошей практикой будет явно задать все настройки, отправив коды операций для разрешения или запрета каждой из этих функций.
Обмен последовательными данными. Как только все параметры сконфигурированы, можно осуществлять обмен данными с внешними периферийными устройствами.
С целью диагностики MPSSE можно перевести в режим зацикливания данных со входа на выход (loop-back mode). Данные, отправляемые через вывод DO, будут внутри микросхемы приходить на вывод DI.
И в нормальном режиме (normal mode), и в режиме loop-back, есть 32 варианта выбора, как данные передаются или принимаются, или одновременно передаются/принимаются. Выбор соответствующего op-code зависит от следующего:
- Порядок следования бит: первым идет MSB или LSB. Имейте в виду, что каждый байт будет передаваться в заданном порядке бит. Если данные состоят из более чем 8 бит, особое внимание следует уделять тому, в каком порядке будут помещены байты в буфер. - Как будет осуществляться обмен: только передача данных, только их прием, или же одновременные передача и прием. - Передача по нарастанию уровня тактов или по спаду; прием по нарастанию уровня тактов или по спаду.
Команды в буфере, используемые вместе с FT_Write, обычно должны сопровождаться FT_GetStatus и FT_Read, чтобы прочитать ответ от периферийного устройства.
[Доступ к GPIO]
У каждого чипа FTDI, оборудованного MPSSE, есть несколько выводов, которые можно использовать как порты ввода/вывода (general purpose input/output, сокращенно GPIO), как было показано в таблице 2.2. Как и в последовательном обмене данными, для настройки направления работы ножек GPIO, вывода значений и запроса от MPSSE состояния уровней используется FT_Write. После выдачи в MPSSE команды “read GPIO” используется FT_Read для получения данных, отражающих состояние выводов GPIO.
[Закрытие дескриптора]
Когда приложение завершило все обмены данными с периферийным устройством, должен быть закрыт дескриптор (handle) устройства FTDI. Хотя это необязательно делать явно, хорошей практикой будет перед закрытием дескриптора сбросить сначала MPSSE, поместив порт в состояние ожидания (idle state). Закрытие дескриптора осуществляется вызовом FT_Close.
Код примера, показанный ниже, следует процедуре настройки, описанной выше в разделе "Программная конфигурация". Он практически совпадает с кодом примера из статьи [5].
Хотя здесь описана общая настройка FTDI MPSSE, в приведенном примере код сфокусирован на одном наборе параметров. Этот пример программы использует драйвер устройства FTDI D2XX и рассчитан на FT2232H, и может быть легко портирован на другой чип FTDI, оборудованный MPSSE. Он написан в линейном стиле с целью демонстрации реальных байт, которые отправляются в MPSSE, и чтобы легче было разобраться с результирующими данными, прочитанными из MPSSE. Один вызов FT_Write может в реальности содержать несколько команд и аргументов.
Первая секция кода устанавливает несколько переменных, которые будут использоваться далее в программе:
int_tmain(int argc, _TCHAR* argv[])
{
// -----------------------------------------------------------// Переменные// -----------------------------------------------------------
FT_HANDLE ftHandle; // Дескриптор устройства FTDI.
FT_STATUS ftStatus; // Результат каждого вызова D2XX.
DWORD dwNumDevs; // Количество устройств в системе.unsignedint uiDevIndex =0xF; // Устройство в списке, которое мы// будем использовать.
BYTE byOutputBuffer[65536]; // Буфер вывода, предназначенный для// хранения команд и данных MPSSE, которые// будут отправлены в FT2232H.
BYTE byInputBuffer[65536]; // Буфер ввода, предназначенный для// хранения данных, прочитанных из FT2232H.
DWORD dwCount =0; // Общий индекс цикла.
DWORD dwNumBytesToSend =0; // Индекс буфера вывода.
DWORD dwNumBytesSent =0; // Счетчик реально отправленных байт,// используется в FT_Write.
DWORD dwNumBytesToRead =0; // Количество байт, доступных для чтения// в буфере ввода.
DWORD dwNumBytesRead =0; // Счетчик реально прочитанных байт, используется// в FT_Read.
DWORD dwClockDivisor =0x05DB; // Значение делителя тактов, частота SCL// = 60/((1+0x05DB)*2) (МГц) = 1 МГц.
[Конфигурирование подключения к микросхеме FTDI]
Проверка, что устройство доступно, и открытие дескриптора:
// -----------------------------------------------------------------------// Есть ли подключенные к компьютеру устройства FTDI?// -----------------------------------------------------------------------
printf("Checking for FTDI devices...\n");
ftStatus = FT_CreateDeviceInfoList(&dwNumDevs);
// Получение количества устройств FTDI:if (ftStatus != FT_OK) // Команда выполнилась успешно?
{
printf("Error in getting the number of devices\n");
return1; // Выход по ошибке.
}
if (dwNumDevs <1) // Есть устройства FTDI?
{
printf("There are no FTDI devices installed\n");
return1; // Выход, если ни одного устройства не обнаружено.
}
// Количество найденных устройств на самом деле означает количество портов// в устройстве FTDI. Например, если к системе подключена одна микросхема// FT2232H, то в dwNumDevs будет возвращено значение 2, потому что у FT2232H// два порта: A и B. У FT4232H портов 4, поэтому будет возвращено значение 4.
printf("%d FTDI devices found \
- the count includes individual ports on a single chip\n", dwNumDevs);
// -----------------------------------------------------------------------// Открытие порта. В этом примере подразумевается, что первое устройство// это FT2232H или FT4232H. Какое именно подключено устройство, можно// узнать вызовами API D2XX - есть возможность прочитать описание// устройства, его размещение на шине, серийные номера и т. д. Это можно// сделать перед открытием порта.// -----------------------------------------------------------------------
printf("\nAssume first device has the MPSSE and open it...\n");
ftStatus = FT_Open(0, &ftHandle);
if (ftStatus != FT_OK)
{
printf("Open Failed with error %d\n", ftStatus);
return1; // Выход по ошибке.
}
Конфигурирование порта FTDI на использование MPSSE:
// Конфигурирование параметров порта:
printf("\nConfiguring port for MPSSE use...\n");
ftStatus |= FT_ResetDevice(ftHandle); // Сброс устройства USB.// Очистка буфера приема USB микросхемы FT2232H путем// предварительного чтения всех её старых данных:
ftStatus |= FT_GetQueueStatus(ftHandle, &dwNumBytesToRead);
// Получение количества байт в буфере приема FT2232H:if ((ftStatus == FT_OK) && (dwNumBytesToRead >0))
// Чтение данных из буфера приема FT2232H:
FT_Read(ftHandle, &byInputBuffer, dwNumBytesToRead, &dwNumBytesRead);
// Установка размера запросов передач на 64 килобайта:
ftStatus |= FT_SetUSBParameters(ftHandle, 65536, 65535);
// Запрет символов событий и ошибок (event, error characters):
ftStatus |= FT_SetChars(ftHandle, false, 0, false, 0);
// Установка таймаутов чтения и записи в миллисекундах:
ftStatus |= FT_SetTimeouts(ftHandle, 0, 5000);
// Установка таймера латентности на 1 мс (по умолчанию стояло 16 мс):
ftStatus |= FT_SetLatencyTimer(ftHandle, 1);
// Включение управления потоком (flow control) для синхронизации// IN-запросов:
ftStatus |= FT_SetFlowControl(ftHandle, FT_FLOW_RTS_CTS, 0x00, 0x00);
// Сброс контроллера:
ftStatus |= FT_SetBitMode(ftHandle, 0x0, 0x00);
// Разрешение режима MPSSE:
ftStatus |= FT_SetBitMode(ftHandle, 0x0, 0x02);
if (ftStatus != FT_OK)
{
printf("Error in initializing the MPSSE %d\n", ftStatus);
FT_Close(ftHandle);
return1; // Выход по ошибке.
}
// Небольшое ожидание, чтобы микросхема пришла в готовность// и заработала:
Sleep(50);
Возвращаемое из вызовов API D2XX значение переменной ftStatus можно проверять как индивидуально (после каждого вызова), или как итоговый результат нескольких предыдущих вызовов API. В остальной части этой программы примера переменная ftStatus проверяется не всегда, чтобы упростить понимание кода - какая задача выполняется каждой строкой.
[Конфигурирование MPSSE]
В этой точке программы MPSSE готова к получению команд. Каждая команда состоит из op-code, за которым идут необходимые параметры или данные. Для ясности каждая команда отправляется в MPSSE в индивидуальном вызове FT_Write. На практике может быть более удобным комбинировать несколько команд в одном вызове FT_Write.
Синхронизация по методу детектирования "плохой команды" (Bad Command Detection):
// Разрешение внутреннего loop-back:
byOutputBuffer[dwNumBytesToSend++] =0x84;
ftStatus = FT_Write(ftHandle,
byOutputBuffer,
dwNumBytesToSend,
&dwNumBytesSent);
// Получение количества байт в буфере приема FT2232H:
ftStatus = FT_GetQueueStatus(ftHandle, &dwNumBytesToRead);
// Проверка буфера приема, он должен быть пустым:if (dwNumBytesToRead !=0)
{
printf("Error - MPSSE receive buffer should be empty\n", ftStatus);
// Сброс порта для запрета MPSSE:
FT_SetBitMode(ftHandle, 0x0, 0x00);
FT_Close(ftHandle); // Закрытие порта USB.return1; // Выход по ошибке.
}
// -----------------------------------------------------------// Синхронизация приложения с состоянием MPSSE путем отправки// неправильного opcode (0xAB), на который MPSSE ответит// "Bad Command" (0xFA), за которым будет идти этот// неправильный opcode.// -----------------------------------------------------------
dwNumBytesToSend =0; // Сброс индекса буфера.
byOutputBuffer[dwNumBytesToSend++] =0xAB;
ftStatus = FT_Write(ftHandle,
byOutputBuffer,
dwNumBytesToSend,
&dwNumBytesSent);
// Цикл, пока в буфере устройства не появятся данные,// или не произойдет таймаут:do
{
// Получение количества байт в буфере приема устройства:
ftStatus = FT_GetQueueStatus(ftHandle, &dwNumBytesToRead);
} while ((dwNumBytesToRead ==0) && (ftStatus == FT_OK));
bool bCommandEchod =false;
// Вычитывание данных из входного буфера:
ftStatus = FT_Read(ftHandle,
&byInputBuffer,
dwNumBytesToRead,
&dwNumBytesRead);
for (dwCount =0; dwCount < dwNumBytesRead -1; dwCount++)
{
// Проверка - получен ли ответ на Bad command:if ((byInputBuffer[dwCount] ==0xFA) && (byInputBuffer[dwCount+1] ==0xAB))
{
bCommandEchod =true;
break;
}
}
if (bCommandEchod ==false)
{
printf("Error in synchronizing the MPSSE\n");
FT_Close(ftHandle);
return1; // Выход по ошибке.
}
// Запрет внутреннего loop-back:
dwNumBytesToSend =0; // Сброс индекса буфера.
byOutputBuffer[dwNumBytesToSend++] =0x85;
ftStatus = FT_Write(ftHandle,
byOutputBuffer,
dwNumBytesToSend,
&dwNumBytesSent);
dwNumBytesToSend =0; // Сброс индекса буфера.
// Получение количества байт в буфере приема FT2232H:
ftStatus = FT_GetQueueStatus(ftHandle, &dwNumBytesToRead);
// Проверка буфера приема, он должен быть пустым:if (dwNumBytesToRead !=0)
{
printf("Error - MPSSE receive buffer should be empty\n", ftStatus);
// Сброс порта для запрета MPSSE:
FT_SetBitMode(ftHandle, 0x0, 0x00);
FT_Close(ftHandle); // Закрытие порта USB.return1; // Выход по ошибке.
}
Настройка MPSSE:
// -----------------------------------------------------------// Конфигурирование настроек MPSSE для JTAG.// -----------------------------------------------------------
dwNumBytesToSend =0; // Сброс индекса буфера.// Настройка специфических Hi-Speed (HS) команд FTx232H.// Использование 60 МГц master clock (запрет деления на 5):
byOutputBuffer[dwNumBytesToSend++] =0x8A;
// Выключение adaptive clocking (это может понадобиться для ARM):
byOutputBuffer[dwNumBytesToSend++] =0x97;
// Запрет трехфазного тактирования:
byOutputBuffer[dwNumBytesToSend++] =0x8D;
// Отправка HS-команд:
ftStatus = FT_Write(ftHandle,
byOutputBuffer,
dwNumBytesToSend,
&dwNumBytesSent);
// Установка частоты TCK.// TCK = 60MHz /((1 + [(1 +0xValueH*256) OR 0xValueL])*2)
dwNumBytesToSend =0; // Сброс индекса буфера.// Команда установки clock divisor:
byOutputBuffer[dwNumBytesToSend++] ='\x86';
// Младший байт 0xValueL настройки clock divisor:
byOutputBuffer[dwNumBytesToSend++] = dwClockDivisor &0xFF;
// Старший байт 0xValueH настройки clock divisor:
byOutputBuffer[dwNumBytesToSend++] = (dwClockDivisor >>8) &0xFF;
// Отправка команды установки clock divisor:
ftStatus = FT_Write(ftHandle,
byOutputBuffer,
dwNumBytesToSend,
&dwNumBytesSent);
// Установка начального состояния ножек интерфейса MPSSE.// Младший байт задает одновременно направление выводов// и выходные значения.//---------------------------------------------------// Вывод Сигнал Напр. Конф. Нач. сост. Конф.//---------------------------------------------------// ADBUS0 TCK/SK output 1 high 1// ADBUS1 TDI/DO output 1 low 0// ADBUS2 TDO/DI input 0 0// ADBUS3 TMS/CS output 1 high 1// ADBUS4 GPIOL0 output 1 low 0// ADBUS5 GPIOL1 output 1 low 0// ADBUS6 GPIOL2 output 1 high 1// ADBUS7 GPIOL3 output 1 high 1
dwNumBytesToSend =0; // Сброс индекса буфера.// Конфигурирование бит данных младшего байта порта MPSSE:
byOutputBuffer[dwNumBytesToSend++] =0x80;
// Начальное состояние, как в таблице выше:
byOutputBuffer[dwNumBytesToSend++] =0xC9;
// Направление, как в таблице выше:
byOutputBuffer[dwNumBytesToSend++] =0xFB;
// Отправка команды настройки младшего байта MPSSE:
ftStatus = FT_Write(ftHandle,
byOutputBuffer,
dwNumBytesToSend,
&dwNumBytesSent);
// Обратите внимание, что поскольку в последующих секциях кода// данные будут тактироваться по фронту нарастания уровня тактов TCK,// то начальное состояние TCK выбрано как лог. 1. Импульс тактов// будет генерироваться 1-0-1. Т. е. для нашего примера данные// будут изменяться по фронту нарастания TCK. Это даст достаточно// времени для стабилизации данных, когда они будут приниматься// внешним устройством по спаду уровня TCK.
// Продолжение установки начального состояния ножек интерфейса// MPSSE. Здесь уже устанавливается старший байт, который// не используется.//---------------------------------------------------// Вывод Сигнал Напр. Конф. Нач. сост. Конф.//---------------------------------------------------// 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
dwNumBytesToSend =0; // Сброс индекса буфера.// Конфигурирование бит данных старшего байта порта MPSSE:
byOutputBuffer[dwNumBytesToSend++] =0x82;
// Начальное состояние, как в таблице выше:
byOutputBuffer[dwNumBytesToSend++] =0x00;
// Направление, как в таблице выше:
byOutputBuffer[dwNumBytesToSend++] =0x00;
// Отправка команды настройки старшего байта MPSSE:
ftStatus = FT_Write(ftHandle,
byOutputBuffer,
dwNumBytesToSend,
&dwNumBytesSent);
// Очистка буферов передачи и приема (необязательно):
memset(byOutputBuffer, 0, sizeof(byOutputBuffer));
memset(byInputBuffer, 0, sizeof(byInputBuffer));
Последовательный обмен данными:
// Только передача, приема нет. Вывод по фронту нарастания TCK,// ввода нет, MSB идет первым, выдвигается заданное количество// байт:
dwNumBytesToSend =0; // Сброс индекса буфера.
byOutputBuffer[dwNumBytesToSend++] =0x10;
// Длина в байтах Length равна 0x0001 + 1.
byOutputBuffer[dwNumBytesToSend++] =0x01; // Length L
byOutputBuffer[dwNumBytesToSend++] =0x00; // Length H// Данные 0xA50F:
byOutputBuffer[dwNumBytesToSend++] =0xA5;
byOutputBuffer[dwNumBytesToSend++] =0x0F;
// Отправка:
ftStatus = FT_Write(ftHandle,
byOutputBuffer,
dwNumBytesToSend,
&dwNumBytesSent);
// Ожидание завершения передачи данных и получения// статуса от драйвера (см. установку таймера// латентности выше):
Sleep(2);
// Получение количества байт в буфере приема FT2232H:
ftStatus = FT_GetQueueStatus(ftHandle, &dwNumBytesToRead);
// Проверка буфера приема, он должен быть пустым, потому// что отправленная команда не подразумевала ввода данных:if (dwNumBytesToRead !=0)
{
printf("Error - MPSSE receive buffer should be empty\n", ftStatus);
// Сброс порта для запрета MPSSE:
FT_SetBitMode(ftHandle, 0x0, 0x00);
FT_Close(ftHandle); // Закрытие порта USB.return1; // Выход по ошибке.
}
// Пауза с ожиданием нажатия на клавишу:
printf("Press < Enter > to continue\n");
getchar();
Как показано на рис. 5.1, четыре основных сигнала FTDI MPSSE совпадают с ожидаемым значением. Модуль FT2232H использовался так, что TDI/DO был соединен с TDO/DI, и были сделаны соответствующие соединения для питания от шины USB. Сигналы DATA и CLK, показанные на рис. 5.1, дублируют TDI/DO и TCK/SK соответственно. Оба они используются как входы для функции декодирования протокола SPI в осциллографе.
Рис. 5.1. Осциллограмма режима, в котором данные только выводятся.
На осциллограмме хорошо видно, что данные TDI/DO выдвигаются по фронту нарастания уровня тактов TCK/SK. Это дает возможность считать установившиеся данные внешним периферийным устройством в момент следующего спада уровня тактов.
// Теперь выполним команду с одновременной передачей и приемом// вместо предыдущей команды, где была только передача.// Вывод по фронту тактов, ввод по спаду, MSB идет первым,// тактируется заданное количество байт:
dwNumBytesToSend =0; // Сброс индекса буфера.
byOutputBuffer[dwNumBytesToSend++] =0x34;
// Length = 0x0001 + 1
byOutputBuffer[dwNumBytesToSend++] =0x01; // Length L
byOutputBuffer[dwNumBytesToSend++] =0x00; // Length H// Данные 0xA50F:
byOutputBuffer[dwNumBytesToSend++] =0xA5;
byOutputBuffer[dwNumBytesToSend++] =0x0F;
// Отправка команды:
ftStatus = FT_Write(ftHandle,
byOutputBuffer,
dwNumBytesToSend,
&dwNumBytesSent);
// Ожидание завершения передачи данных и получения// статуса от драйвера (см. установку таймера// латентности выше):
Sleep(2);
// Проверка буфера приема - в нем должны быть данные, закольцованные// обратно. Получение количества байт в буфере приема:
ftStatus = FT_GetQueueStatus(ftHandle, &dwNumBytesToRead);
// Чтение данных:
FT_Read(ftHandle,
&byInputBuffer,
dwNumBytesToRead,
&dwNumBytesRead);
// В буфере приема должно быть 2 байта. Если не так, то ошибка:if (dwNumBytesToRead !=2)
{
printf("Error - MPSSE receive buffer should have the");
printf(" looped-back data\n");
// Сброс порта для запрета MPSSE:
FT_SetBitMode(ftHandle, 0x0, 0x00);
FT_Close(ftHandle); // Закрытие порта USB.return1; // Выход по ошибке.
}
printf("The correct number of bytes have been received\n");
// Переданные и принятые данные должны совпадать.for(dwCount =0; dwCount < dwNumBytesRead; dwCount++)
{
// Выводимые данные начинаются в буфере передачи с ячейки 3,// после opcode и length.if (byInputBuffer[dwCount] != byOutputBuffer[dwCount +3])
{
printf("Error - Data received does not match data output\n");
// Сброс порта для запрета MPSSE:
FT_SetBitMode(ftHandle, 0x0, 0x00);
FT_Close(ftHandle); // Закрытие порта USB.return1; // Выход по ошибке.
}
}
printf("The input data matches the output data\n");
Рис. 5.2 показывает те же сигналы, как и на рис. 5.1. Сигнал данных TDI/DO также соединен с TDO/DI, данные вдвигаются в MPSSE по спаду уровня тактов. Очевидно, что переданные данные совпадают с теми, что были прочитаны из буфера приема MPSSE.
Рис. 5.2. Осциллограмма режима, в котором данные одновременно и выводятся, и вводятся.
// Пауза с ожиданием нажатия на клавишу:
printf("Press < Enter > to continue\n");
getchar();
// Очистка буферов передачи и приема (необязательно):
memset(byOutputBuffer, 0, sizeof(byOutputBuffer));
memset(byInputBuffer, 0, sizeof(byInputBuffer));
[Доступ к GPIO]
Ножки портов FTDI MPSSE, к которым не привязаны специальные функции, могут использоваться как программно-управляемые порты ввода/вывода общего назначения (GPIO). Это включает любой сигнал выборки (chip select, CS), необходимый для работы SPI. Установка в лог. 1 и лог. 0 любого из этих выводов делается одним и тем же способом. Если управлять этими выводами по отдельности, как показано далее, то максимальная скорость переключения этих сигналов GPIO, которая может быть достигнута, ограничена отправкой данных по шине USB в отдельной транзакции, при каждом вызове FT_Write. Это как раз тот случай, когда гораздо выгоднее соединить в одну последовательность несколько команд MPSSE, чтобы из можно было отправить одним вызовом FT_Write. Таким образом, можно установить выборку CS, передать данные и снять выборку CS в одном вызове FT_Write.
Код, показанный ниже, выполняет простую операцию read-modify-write для гарантии, что все другие биты останутся нетронутыми.
// Чтение младшего байта GPIO.// *************************************************************// Изменение перепада на спад уровня триггера канала 4 (TMS/CS)// *************************************************************// Получение бит данных: возврат состояния выводов - либо входов,// либо выходов, в младшем байте MPSSE:
dwNumBytesToSend =0; // Сброс индекса буфера.
byOutputBuffer[dwNumBytesToSend++] =0x81;
// Чтение младшего байта GPIO:
ftStatus = FT_Write(ftHandle,
byOutputBuffer,
dwNumBytesToSend,
&dwNumBytesSent);
// Ожидание завершения передачи данных и получения// статуса от драйвера (см. установку таймера// латентности выше):
Sleep(2);
// Опрос статуса буфера приема, в нем должен быть один байт:
ftStatus = FT_GetQueueStatus(ftHandle, &dwNumBytesToRead);
// Чтение байта:
ftStatus |= FT_Read(ftHandle,
&byInputBuffer,
dwNumBytesToRead,
&dwNumBytesRead);
if ((ftStatus != FT_OK) & (dwNumBytesToRead !=1))
{
printf("Error - GPIO cannot be read\n");
// Сброс порта для запрета MPSSE:
FT_SetBitMode(ftHandle, 0x0, 0x00);
FT_Close(ftHandle); // Закрытие порта USB.return1; // Выход по ошибке.
}
// Буфер ввода содержит только 1 байт в ячейке 0 буфера:
printf("The GPIO low-byte = 0x%X\n", byInputBuffer[0]);
// Ожидания нажатия клавиши для продолжения:
printf("Press < Enter > to continue\n");
getchar();
// Модификация данных GPIO (только TMS/CS) и отправка// их обратно на выводы порта. Команда установки// бит данных младшего байта порта MPSSE:
dwNumBytesToSend =0; // Сброс индекса буфера.
byOutputBuffer[dwNumBytesToSend++] =0x80;
// Будет изменен в 0 только разряд TMS/CS:
byOutputBuffer[dwNumBytesToSend++] = byInputBuffer[0] &0xF7;
// Конфигурация направления все еще нужна для каждой операции// записи GPIO:
byOutputBuffer[dwNumBytesToSend++] =0xFB;
// Отправка команды:
ftStatus = FT_Write(ftHandle,
byOutputBuffer,
dwNumBytesToSend,
// Ожидание завершения передачи данных и получения// статуса от драйвера (см. установку таймера// латентности выше):
Sleep(2);
Рис. 5.3 показывает эффект от чтения младшего байта GPIO, перевод бита 3 (TMS/CS) в лог. 0 и записи прочитанного значения обратно в порт GPIO.
Рис. 5.3. Осциллограмма, показывающая результат управления разрядом GPIO.
[Закрытие дескриптора]
Когда все функции завершены, FTDI MPSSE должна быть сброшена и запрещена. После этого закрывается дескриптор (handle) порта FT2232H, чем порт освобождается для использования в другом приложении.
// -----------------------------------------------------------// Закрываем и выключаем все, что было использовано// -----------------------------------------------------------
printf("\nAN_135 example program executed successfully.\n");
// Пауза с ожиданием нажатия на клавишу:
printf("Press < Enter > to continue\n");
getchar();
FT_SetBitMode(ftHandle, 0x0, 0x00); // Сброс MPSSE
FT_Close(ftHandle); // Закрытие портаreturn0; // Успешное завершение
По ссылкам [6 .. 9] см. описания других практических применений чипов и библиотек FTDI.
[Ссылки]
1. AN_135 FTDI MPSSE Basics site:ftdichip.com. 2. D2XX Direct Drivers site:ftdichip.com. 3. FTDI: справочник по функциям библиотеки D2XX. 4. FTDI: командный процессор MPSSE и режимы эмуляции шины хоста. 5. Подключение к JTAG TAP через FT2232H. 6. AN_113 Interfacing FT2232H Hi-Speed Devices To I2C Bus site:ftdichip.com. 7. AN_114 Interfacing FT2232H Hi-Speed Devices To SPI Bus site:ftdichip.com. 8. AN_129 Interfacing FT2232H Hi-Speed Devices to a JTAG TAP site:ftdichip.com. 9. TN_109 Instructions On Including The FTD2xx DLL In A VS2008 Project site:ftdichip.com.