Работа с микросхемой FTDI FT2232 в режиме BitBang

Скромная и незаметная
На демоплате Pinboard в качестве интерфейса для связи с компом установлена микросхема USB<->UART преобразователя от FTDI. В 99% случаев ее используют именно для этого и никак иначе. Нужен один UART ставят — FT232xx, нужно два канала — FT2232xx.
 

Но мало кто обращает внимание на то, что эти микросхемы, фактически, имеют полноценные порты ввода вывода позволяющие рулить ими программно со стороны компа, что позволяет вообще избавиться от микроконтроллера в схеме. Зачем он нужен, если его функции можно повесить на интерфейсную микросхему?
 


Ведь помимо банального USB<->UART. Там есть такая приятная вещь как Синхронный битбанг (бурж. Synchronous Bit bang). Сей режим позволяет управлять ногами микросхемы почти так-же, как если бы это были ноги микроконтроллера.
 

Управлять можно пинами из группы DBUS — их восемь для каждого канала (для FT2232 у 232RL выводов меньше т.к. всего один канал), что дает нам полноценный порт с помощью которого можно опрашивать датчики, управлять реле, выводить данные на ЖК экран, а при достаточно извращенной фантазии — эмулировать I2C, SPI и подобные протоколы при помощи ножкодрыганья и реализовывать разные программаторы только лишь на одной FTDI, чем мы, кстати, и активно пользуемся. Обратите внимание, что программаторы и JTAG отладчики для AVR или ARM, используемые на Pinboard II либо вообще ничего на борту толком не имеют, либо состоят из тупой логики для соглассования, а всю работу делают микросхема FTDI и софт на компе. Кроме того, этот режим используется для общения с устройствами (вроде ПЛИС или быстрых АЦП/ЦАП), по параллельной шине.
 

Надо только заметить, что на FT2232D для эмуляции всяких скоростных протоколов вроде SPI есть специальный режим — MPSSE (Multi Protocol Synchronous Serial Engine), но это уже совсем другая история.
 

Приступим!
В качестве практической работы, научимся управлять с компьютера ЖК-дисплеем на базе HD44780, что стоит на Pinboard II. Просто выводить текст не интересно, поэтому я включил в программку такие плюшки, как вывод названия текущей песни из Winamp и времени.
 

Вместо FT2232D можно взять FT232R или любую другую, которая поддерживает синхронный битбанг (а это практически весь модельный ряд, начиная с FT245), лишь бы вам хватило выводов под вашу задачу. А там где не хватает, можно применять сдвиговые регистры, превращая последовательную передачу в параллельную ценой скорости. Но кто сказал, что скорость это главное?
 

Инструмент
Управляющая программа написана на Delphi. Ниже будет подробно описан алгоритм работы с дисплеем, поэтому при желании повторить её на другом языке не составит труда. Более того, в пост яростно реквестируется то же самое (или похожее), но на Qt, C++ builder, LabView, Java или на чем вы там пишете. Было бы здорово собрать коллекцию однотипных примеров в разных реализациях.
 

Как это работает
Синхронный битбанг — самый простой способ подрыгать ногами FT2232 (У её младшей сестры был еще более топорный CBUS битбанг, но тут его выкинули).
 

Свободно и совершенно произвольно можно управлять можно аж целыми 8 пинами из комплекта хDBUS. А еще два с шины xCBUS будут передавать стробы WR и RD, которые можно использовать для облегчения себе жизни при работе с параллельными шинами. На 2232 WR и RD функции фиксированы для конкретных пинов CBUS, а вот на 232RL их можно переназначить в пределах шины CBUS или выключить вообще, если не нужны.
 

Каждый из восьми пинов может быть в трех состояниях — вход (без подтяжки), выход с низким уровнем или выход с высоким. На то, чтобы поменять направление пина, нужно некоторое время, поэтому поиграть с быстрым переключением вход-выход (как в AVR при работе с 1-wire) не выйдет.
 

Для начала работы в режиме синхронного битбанга достаточно:
 

  • открыть FTDI
  • установить какие пины будут выходами, а какие — входами
  • установить бодрейт

 

Никакой дополнительной подготовки не нужно.
 

Основная работа по приему-передачи данных идет через FIFO буфер микросхемы, как и в режиме UART. Благодаря этому можно сразу набросать в буфер паттерн какого-нибудь сигнала (например передачи байта по SPI) и отправить на выполнение одним махом. Шансов на срыв таймингов у нашей любимой многозадачной Оси не остаётся (В Windows, благодаря куче параллельно запущенных процессов, практически невозможно точно рассчитать тайминги используя конструкции типа поднять_пин — подождать — опустить_пин).
 

Все операции с данными FT2232D совершает по тактовому сигналу. Его частоту мы можем изменить программно через установку бодрейта. Частота тактирования равна BaudRate*16, и может достигать 48МГц, что уж больно дофига и скорее всего никому не понадобится :)).
 

Для того, чтобы перевести выводы в нужное состояние, просто посылаем байт в буфер. Каждый его бит соответствует пину из группы xDBUSx. Нулевой для xDBUS0, первый для xDBUS1 и т.д. Высокий уровень обозначается единичкой, низкий — нулем. Правда это не дает возможность рулить отдельными битами. Только всем портом сразу. Хочешь изменить один бит — используй битмаски и операции чтение-модификация-запись.
 

Естественно, что переключаться будут только те пины, которые настроены на выход. Входы останутся без изменений. О том, как настроить направление пинов, расскажу чуть ниже.
 

Перед тем, как вывести данные на пины, FT2232D запишет во входной буфер логические уровни на каждом из них. В таком-же порядке: 0 бит — DBUS0, 1 — DBUS1, и т.д. Если входной буфер (жалкие 128 байт) заполнится, то передаваемые с компа в микросхему данные перестанут выдаваться на пины. Но к счастью драйвер сам регулярно проверяет этот буфер, копируя из него данные в свой (в ОЗУ компа). Поэтому переполнение при нормальной работе нам не грозит.
 

При записи байта в порт FTDI мы получаем в ответ, во входной буфер, чтение с данных с того же порта и только потом новые данные вылезают наружу. Т.е. чтение из порта делается записью туда значения. Есть и специальная функция, которая позволяет только прочитать состояние пинов. Но использовать её в задачах, требовательных к скорости, нельзя: чтение будет тормозить весь процесс.
 

Пины RD и WR ведут себя следующим образом:

  • WR (В FT2232D этот сигнал по-умолчанию заведен на CBUS2). Все время находится в высоком уровне. После того, как новые данные выведены на пины, WR дает импульс длительностью в один период тактового сигнала. «Принимающая сторона» по этому импульсу может считывать данные.
  • RD (На CBUS3). Находится в низком уровне, и дает положительный импульс сразу после того, как FT2232D прочитала данные с линии. После этого импульса устройство, которое общается с компом через FT2232D должно выдать на пины данные, которые будут прочитаны при следующем запросе.

 

В общем виде, без хитростей, работа в режиме синхронного битбанга строится по такой схеме:
 

Боевая задача и разбор полетов
Мы хотим управлять дисплеем на PINBOARD II через FT2232D. Для начала разберемся с конфигурацией питания и уровней.
 

  • Снимем все джамперы с коммутатора TX-RX, чтобы они нам не мешались.
  • Напряжение линий ввода-вывода для FTDI установим равным напряжению питания дисплея — 5В. Для этого переключаем джампер VCCIO на 5V.

 

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

Подключение дисплея
Линию RW дисплея подключим к земле — читать что-то из дисплея никто не собирается. Будем просто переписывать ему всю видеопамять.
 

  • E — DBUS0

Почему? А логичнее всего подключить к одной из тех линий FT2232D, которая в режиме UART настроена на выход. Например — TX (это и есть DBUS0). Так как при подключении платы к компу микросхема стартует в режиме USB-UART и на TX у нас будет +5В. Если же подключить E, к примеру, на RX, то можно поймать глюки из-за наводок на этот пин, ведь мы соединим два входа и они будут просто болтаться в воздухе.
 

Остальные пины можно подключать произвольно, с той лишь оговоркой, что шину данных лучше завести на пины, идущие подряд — так с ними будет удобнее работать. У нас подключение следующим образом:

  • RS — DBUS1
  • D4 — DBUS2
  • D5 — DBUS3
  • D6 — DBUS4
  • D7 — DBUS5

Последние два пина (DBUS 6 и 7) остались свободными и используются для демонстрации чтения. Мы их повесим на кнопки BTN3 и BTN2
 


Поглядеть крупным планом
 
 

Дельный совет при выборе пинов в битбанг режиме
При старте FT2232D хотя бы пару секунд будет в режиме USB<>UART, перед тем, как программа переключит её в битбанг. Планируй подключение исходя из этого, чтобы избежать конфликтов уровней.

 

Софт
Теперь подумаем, что нужно со стороны программы для руления этим безобразием. Основное действие при работе с дисплеем в четырехбитном режиме — передача тетрады (половинка байта, если кто-то школьный курс инфы забыл :). На нем строятся более сложные функции — передача байта, инициализация, очистка…
 

Напомню, что дисплей читает данные с пинов D4-D7 по спаду на линии E. При этом RW должен быть на земле (у нас он всегда на земле) , а уровень на RS обозначает, что именно мы ему суем — команду (0) или данные (1). *
 

Поскольку мы не можем рулить одним битом, то для того, чтобы реализовать передачу тетрады, мы отдаем FT2232D подряд два байта, которые отличаются только значением E. В первом на E высокий уровень, во втором — низкий.
 

Затем читаем два байта, в которые FTDI записала значения с пинов. Не то, чтобы они нам нужны — просто чтобы не забивать буфер. Помним, что запись дает и чтение, комплектом.
 

Выше по иерархии стоит функция передачи байта. Она реализуется элементарно: сначала передаем старшую тетраду байта, потом младшую.
 

D2XX
Работа с микросхемой FTDI в каком-либо альтернативном режиме невозможна без драйвера D2XX. Кроме функций для альтернативных режимов, драйвер предоставляет функции для получения данных о подключенных устройствах, функции для работы с EEPROM и еще много чего. Полное описание можно найти в D2XX Programmer’s Guide.
 

Я мог бы описать тут функции, которые предоставляет драйвер, но напрямую их я не использовал, а работал через функции-обертки, которые почти полностью повторяют функционал оригинального API драйвера. Поэтому, чтобы не плодить сущности, рассказ о функциях D2XX я пропущу.
 

Реализация на Delphi
На сайте FTDI лежит удобный заголовочный файлик для работы с драйвером D2XX в Delphi — D2XXUnit.pas. Также он лежит в архиве с исходниками к этой статье.
 

В этом файле объявлены все функции драйвера, и кроме того, есть функции-обертки сильно упрощающие общение с микросхемой. Еще в нем объявлены константы и переменные, которыми можно оперировать для настройки микросхемы (плюс — не надо заводить свои переменные, минус — если надо одновременно открыть два устройства, то придется пользоваться голым API драйвера).
 

Тут-же реализована обработка ошибок. По умолчанию при каждой ошибке выводится сообщение с её описанием, что не всегда удобно. Это легко исправить, заменив вывод сообщения записью в лог или еще чем.
 

Описывать все, что там содержится я не буду, но настоятельно советую заглянуть в этот файл, заручившись поддержкой «D2XX Programmer’s Guide».
 

В своей программе я активно использовал функции из этого файла, поэтому, чтобы не возникало непонятных моментов при изучении кода, покажу что именно я юзал:
 

  • Function Open_USB_Device_By_Device_Description(Device_Description:string) : FT_Result;
    В качестве аргумента принимает название устройства. После открытия в переменной FT_Handle будет записан идентификатор устройства, который можно потом использовать для вызова функций драйвера напрямую. Результат равен FT_OK (это нуль), если устройство открыто успешно.
  • Function Close_USB_Device : FT_Result;
    Закрывает устройство, которое сейчас открыто.
  • Function Set_USB_Device_BaudRate : FT_Result;
    Устанавливает желаемый бодрейт. Напомню, что тактовая частота в битбанг-режиме в 16 раз выше бодрейта. Перед вызовом функции само значение бодрейта нужно записать в переменную FT_Current_Baud.
  • Function Set_USB_Device_BitMode(Mask,Enable:Byte) : FT_Result;
    Переключает устройство (заранее открытое) в альтернативный режим и настраивает направление пинов.
    Параметр Mask отвечает за направление — 1 на выход, 0 — на вход. Нулевой бит для DBUS0 и т.д. А параметр Enable — за режим, в который будет переключена микросхема:
    0x0 = Reset
    0x1 = Asynchronous Bit Bang
    0x2 = MPSSE (FT2232D, FT2232H, FT4232H and FT232H devices only)
    0x4 = Synchronous Bit Bang (FT232R, FT245R, FT2232D, FT2232H, FT4232H and FT232H devices only)
    0x8 = MCU Host Bus Emulation Mode (FT2232D, FT2232H, FT4232H and FT232H devices only)
    0x10 = Fast Opto-Isolated Serial Mode (FT2232D, FT2232H, FT4232H and FT232H devices only)
    0x20 = CBUS Bit Bang Mode (FT232R and FT232H devices only)
    0x40 = Single Channel Synchronous 245 FIFO Mode (FT2232H and FT232H devices only)
  • Function Get_USB_Device_BitMode(var BitMode:Byte) : FT_Result;
    Записывает в переменную BitMode значения с пинов.
  • Function Get_USB_Device_List : FT_Result;
    Заполняет массив (FT_DeviceInfoList) записями о каждом устройстве. Каждая запись состоит из:
    Flags : DWord;
    DeviceType : Dword;
    ID : DWord;
    LocID : DWord;
    SerialNumber : array [0..15] of Char;
    Description : array [0..63] of Char;
    DeviceHandle : DWord;
     

    Последний параметр равен 0, если устройство не открыто.
    Общее количество найденных устройств хранится в переменной FT_Device_Count.

  • Function GetFTDeviceCount : FT_Result;
    Получает количество подключенных устройств FTDI и записывает его в FT_Device_Count.
  • Function Get_USB_Device_QueueStatus : FT_Result;
    Получает количество байт, скопившееся во входном буфере. Оно записывается в переменную FT_Q_Bytes. Очень удобно использовать эту функцию, чтобы прочитать все байты из буфера.
  • function Write_USB_Device_Buffer( Write_Count : Integer ) : Integer;
    Передает в микросхему Write_Count байт из массива FT_Out_Buffer (перед вызовом функции в него надо записать данные, начиная с 0й ячейки). Возвращает количество байт, которое реально было передано в микросхему.
  • function Read_USB_Device_Buffer( Read_Count : Integer ) : Integer;
    Читает из микросхемы Read_Count байт. Прочитанные байты складываются в массив FT_In_Buffer, начиная с 0й ячейки. Результат — количество реально прочитанных байт.

 

Теперь переходим собственно к созданию программы. Я использую старенькую Borland Delphi 10, но проект должен открыться и в других версиях IDE.
 

Для начала набросаем простенькое подобие интерфейса:

В папку с проектом кидаем D2XXUnit.pas и добавляем его в проект. Иногда этот файл приходится править, поэтому лучше будет завести для каждого проекта свою копию, чем один на всех.
 

По событию появления окошка (OnShow) у нас будет проверка на наличие устройств FTDI и некоторые приготовления:
 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
procedure TMainForm.FormShow(Sender: TObject);
begin
//Проверяем наличие фтди
GetFTDeviceCount;
if FT_Device_count=0 then
 begin //Если ничего нет, то выходим с сообщением об ошибке.
  MessageBox(MainForm.Handle,
  'Не подключено ни одно устройство FTDI. Не буду запускаться. :-/',
  'Стоп!',
  MB_OK);
  MainForm.Close;
  Exit;
 end;
 
//А если есть, то
CheckFTDI; //Создаем список
 
EnableInterface(false); //Отключаем интерфейс (фтди не открыта ведь)
end;

Функция СheckFTDI заполняет выпадающий список (что в верней части окна) названиями всех найденных FTDI устройств. Выглядит она вот так:

1
2
3
4
5
6
7
8
9
10
11
12
procedure TMainForm.CheckFTDI;
var
I: integer;
begin
 cbSelectFTDI.Clear; //Очищаем список
 
 Get_USB_Device_List; //Получаем список всех FTDI устройств
 for I := 0 to FT_Device_Count - 1 do
    cbSelectFTDI.Items.Add(FT_DeviceInfoList[i].Description); //И выводим его в combobox
 
 cbSelectFTDI.ItemIndex := 0; //Выбираем первый элемент из списка
end;

Вообще-то тут можно было обойтись функцией GetFTDeviceDescription, каждый раз передавая ей номер следующего устройства, но я решил сделать через список.
 

Функция EnableInterface, которая вызывается в конце — просто набор конструкций вида [Название элемента].Enabled := value. Нужна для того, чтобы включать/отключать интерфейс работы с микросхемой (то есть кнопки, переключатели и т.д.) в зависимости от того — открыта она или нет.
 

При нажатии кнопки «Обновить» просто еще раз вызывается функция CheckFTDI, заполняющая список с доступными устройствами.
 

По событию OnClick кнопки «Открыть» мы открываем (или закрываем, если уже открыто) выбраное устройство:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
procedure TMainForm.bOpenFTClick(Sender: TObject);
begin
if bOpenFT.Tag=0 then //Если тег=0 (показывает, что устройство еще не открыто)
 begin
  if OpenFTDI(cbSelectFTDI.Text)=0 then //Пытаемся открыть выбранную FTDI
   begin //Если удалось
     bOpenFT.Tag := 1;//То ставим тег=1
     bOpenFT.Caption := 'Закрыть'; //И меняем заголовок кнопки
 
     //После чего инициализируем и очищаем дисплей
     InitDisplay;
     ClearDisplay;
 
     MainTimer.Enabled := true; //Запускаем таймер
     EnableInterface(true); //И включаем интерфейс
   end;
 end
else //Если тег=1 (а кнопка называется "закрыть")
 begin
  bOpenFT.Tag := 0; //Меняем тег на 0
  bOpenFT.Caption := 'Открыть';
  CloseFTDI; //Закрываем фтди
  EnableInterface(false); //Отключаем интерфейс
  MainTimer.Enabled := false; //и останавливаем таймер
 end;
end;

Здесь реализован простенький переключатель на свойстве tag кнопки. Если tag = 0, то мы должны открыть FTDI, поменять надпись на кнопке на «Закрыть» и записать в tag 1. Если же мы нажали на кнопку, а tag = 1, то закрываем устройство, обнуляем tag и меняем заголовок на «Открыть».
 

Таймер тут нужен для работы плюшек вроде вывода на дисплей названия песенки из Winamp или текущего времени. Он тикает раз в секунду и вызывает обновление текста на дисплее.
 

Функция OpenFTDI это не очередная полезность из файла D2XXUnit, а моя собственная функция, которая открывает устройство по названию и переключает его в битбанг режим. Находится она, как и другие функции, которые работают непосредственно с FT2232D, в файле Display.pas.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//Открываем FT2232 и переводим в битбанг режим
function OpenFTDI(dev_name: string): byte;
begin
//Пытаемся открыть FTDI по имени
Result := Open_USB_Device_By_Device_Description(dev_name);
if Result=0 then //Если получилось,
begin
 //То устанавливаем режим работы - синхронный битбанг
 Set_USB_Device_BitMode((1 shl PinE) or (1 shl PinRS) or (1 shl PinD4)
 or (1 shl PinD5) or (1 shl PinD6) or (1 shl PinD7), 4); //Переключаем в синхронный битбанг
 
 //Выставляем скорость с которой данные будут вбрасываться на пины.
 //Точнее выставляем мы бодрейт, а реальная скорость будет в 16 раз больше
 FT_Current_Baud := 4800; //В данном случае частота обновления составит 76 кГц
 Set_USB_Device_BaudRate; //Устанавливаем это значение
end;
end;

Константы PinE, PinRS и т.д. это номера пинов, к которым привязаны соответствующие сигналы. Нумеруются они как обычно: 0 это DBUS0, 1 — DBUS1…
 

Функция CloseFTDI, из того-же файла, просто закрывает устройство.
 

Теперь посмотрим на то, как реализованы функции непосредственно для работы с дисплеем:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//Посылает в дисплей младшую тетраду.
// !!! FTDI должна быть открыта OpenFTDI()
procedure SendNibble(nibbl: byte; mode: byte);
begin
 //Выставляем на линию данные и поднимаем пин Е
 FT_Out_Buffer[0] := ((nibbl and $0F) shl 2) or (1 shl PinE) or ((mode and 1) shl PinRS);
 //Опускаем E - данные ушли
 FT_Out_Buffer[1] := ((nibbl and $0F) shl 2) or (0 shl PinE) or ((mode and 1) shl PinRS);
 
 Write_USB_Device_Buffer(2); //Записываем всю пачку
 
 repeat
 Get_USB_Device_QueueStatus;
 until (FT_Q_Bytes>=2);
 
 Read_USB_Device_Buffer(2);
end;

Здесь все происходит согласно алгоритму, который был описан выше. Обратите внимание, как производится передача данных в микросхему: сначала в буфер (объявлен в файле D2XXUnit.pas) закидываются 2 байта данных, а потом вызывается функция отправки с указанием количества байт. Затем мы запрашиваем количество байт в приемном буфере при помощи Get_USB_Device_QueueStatus, и ждем пока оно не будет равно 2. После чего читаем эти два байта из буфера.
 

Небольшое отступление. Иногда нужно прочитать из буфера все, что в нем есть. Реализуется это вот таким образом:
 

1
2
 Get_USB_Device_QueueStatus;
 Read_USB_Device_Buffer(FT_Q_Bytes);

Функция передачи байта ничего особенного из себя не представляет и полностью повторяет описанный выше алгоритм. Код функций инициализации и очистки дисплея я приводить тоже не буду, при желании их можно найти в исходниках, в файле Display.pas.
 

Еще одна функция используется для установки курсора в нужную позицию:

1
2
3
4
5
6
7
8
9
10
11
//Установка указателя в нужную позицию
// !!! FTDI должна быть открыта OpenFTDI()
procedure SetPos(x,y: integer);
var
temp: byte;
begin
temp := line_1;
if y>0 then temp := line_2;
 
SendByte(temp+x, DISPLAY_MODE_COMMAND);
end;

Константы line_1 и line_2 содержат начальные адреса первой и второй строки видеопамяти дисплея.
 

Используя все эти функции можно вполне полноценно работать с дисплеем.
 

Стоит показать еще одну функцию, которая призвана исправлять коды русских букв при передаче в дисплей:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//Если надо и разрешено (ResolveRusText=true) преобразует русские буквы в подходящие для дисплея коды
function ResolveChar(input_char: char):char;
var
i: integer;
begin
 //На тот случай, если преобразования не будет - запихиваем в результат
 //Изначальный символ
 Result := input_char;
 //Если символ из первой половины таблицы ASCII то преобразование не нужно.
 if Byte(input_char)<=127 then exit;
 //Если запрещено, то тоже выходим
 if not ResolveRusText then exit;
 
 //Пробегаемся циклом по массиву
 for I := 0 to 65 do
  if Chrs1[i] = input_char then //Если нашли наш символ, то
   begin
    Result := Chrs2[i]; //Выдаем его в результат
    exit; // И выходим
   end;
end;

Массив Chrs1 содержит русский алфавит (заглавные и строчные буквы), а Chrs2 — коды на которые надо их заменять.
 

Рассмотрим теперь, как реализуется заполнение дисплея текстом. Это происходит при нажатии на кнопку «Вывести» или по событию от таймера.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//Процедура вывода строк.
//Первая строка берется из edDisplayLine1.text
//Вторая из edDisplayLine2.text
procedure TMainForm.SendLines;
var
I: integer;
begin
 SetPos(0,0); //Позиция на первый символ первой строки
 for I := 1 to Length(edDisplayLine1.Text) do  //Выводим все символы первой строки
 //Для вывода символа просто посылаем байт в режиме данных
 //Предварительно преобразовав символ (если это русская буква)
  SendByte(Byte(ResolveChar(edDisplayLine1.Text[I])),DISPLAY_MODE_DATA);
 
 SetPos(0,1); //Переходим на вторую строку
 for I := 1 to Length(edDisplayLine2.Text) do
  SendByte(Byte(ResolveChar(edDisplayLine2.Text[I])),DISPLAY_MODE_DATA);
end;

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

Вывод названия песни из Winamp или текущего времени работает через эту-же функцию, предварительно заполняя текстовые поля нужными данными.
 

Примеры на других языках

Пример на Python от Oxyum

Показать »

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# -*- coding: utf-8; mode: python; -*-
 
from sys import argv
from time import sleep
from pylibftdi import BitBangDevice, Device, Driver, driver, FtdiError
from ctypes import byref, create_string_buffer
 
# FT2232C / PinBoard2
driver.USB_VID = 0x0403
driver.USB_PID = 0x6010
 
PinE  = 0
PinRS = 1
 
PinD4 = 2
PinD5 = 3
PinD6 = 4
PinD7 = 5
 
DEBUG = False
 
def port_f(n):
    p = "{0:0>{1}}".format(bin(n)[2:], 6)
    return "e={0} rs={1} D={2}".format(p[-1], p[-2], p[0:-2])
 
def debug_port(text, port):
    if DEBUG:
        p = "{0:0>{1}}".format(bin(port)[2:], 6)
        print "{0}: e={1} rs={2} D={3}".format(text, p[-1], p[-2], p[0:-2])
 
class HD44780(object):
 
    LINE_1 = 0x80;
    LINE_2 = 0xC0;
 
    COMMAND = 0;
    DATA = 1;
 
    RUS_CHARS_MAP = {
        u"а": "a"      , u"б": chr(0xB2), u"в": chr(0xB3), u"г": chr(0xB4), 
        u"д": chr(0xE3), u"е": "e"      , u"ё": chr(0xB5), u"ж": chr(0xB6), 
        u"з": chr(0xB7), u"и": chr(0xB8), u"й": chr(0xB9), u"к": chr(0xBA), 
        u"л": chr(0xBB), u"м": chr(0xBC), u"н": chr(0xBD), u"о": "o"      , 
        u"п": chr(0xBE), u"р": "p"      , u"с": "c"      , u"т": chr(0xBF), 
        u"у": "y"      , u"ф": chr(0xE4), u"х": "x"      , u"ц": chr(0xE5), 
        u"ч": chr(0xC0), u"ш": chr(0xC1), u"щ": chr(0xE6), u"ь": chr(0xC4), 
        u"ы": chr(0xC3), u"ъ": chr(0xC2), u"э": chr(0xC5), u"ю": chr(0xC6), 
        u"я": chr(0xC7), u"А": "A"      , u"Б": chr(0xA0), u"В": "B"      , 
        u"Г": chr(0xA1), u"Д": chr(0xE0), u"Е": "E"      , u"Ё": chr(0xA2), 
        u"Ж": chr(0xA3), u"З": chr(0xA4), u"И": chr(0xA5), u"Й": chr(0xA6), 
        u"К": "K"      , u"Л": chr(0xA7), u"М": "M"      , u"Н": "H"      , 
        u"О": "O"      , u"П": chr(0xA8), u"Р": "P"      , u"С": "C"      , 
        u"Т": "T"      , u"У": chr(0xA9), u"Ф": chr(0xAA), u"Х": "X"      , 
        u"Ц": chr(0xE1), u"Ч": chr(0xAB), u"Ш": chr(0xAC), u"Щ": chr(0xE2), 
        u"Ь": chr(0xC4), u"Ы": chr(0xAE), u"Ъ": chr(0xAD), u"Э": chr(0xAF), 
        u"Ю": chr(0xB0), u"Я": chr(0xB1),
    }
 
 
    def __init__(self, bb_device):
        self.bb_device = bb_device
 
    def send_tetra(self, tetra, mode):
        """Посылает тетраду"""
 
        assert tetra == tetra & 0x0F
 
        self.set_port(rs=(mode & 0b1))
 
        sleep(0.01)
 
        self.set_port(e=1, rs=(mode & 0b1))
 
        sleep(0.01)
 
        self.set_port(
            e=1,
            rs=(mode & 0b1),
            d4=((tetra >> 0) & 0b1),
            d5=((tetra >> 1) & 0b1),
            d6=((tetra >> 2) & 0b1),
            d7=((tetra >> 3) & 0b1)
        )
 
        sleep(0.01)
        self.set_port(
            e=0,
            rs=(mode & 0b1),
            d4=((tetra >> 0) & 0b1),
            d5=((tetra >> 1) & 0b1),
            d6=((tetra >> 2) & 0b1),
            d7=((tetra >> 3) & 0b1)
        )
        sleep(0.01)
 
    def send_octet(self, data, mode):
        """Посылает октет"""
        assert data == data & 0xFF
 
        self.send_tetra((data & 0xF0) >> 4, mode)
        self.send_tetra((data & 0x0F), mode)
 
        # self.set_port(d4=1, d5=1, d6=1, d7=1)
        sleep(0.01)
 
    def set_port(self, e=0, rs=0, d4=0, d5=0, d6=0, d7=0):
        if DEBUG:
            print "params:", e, rs, d4, d5, d6, d7
        debug_port("before", self.bb_device.port)
        self.bb_device.port = e << PinE | rs << PinRS | d4 << PinD4 | d5 << PinD5 | d6 << PinD6 | d7 << PinD7
        debug_port("after ", self.bb_device.port)
 
    def init(self):
        """Инициализация (4 бита шина, 2 строки)"""
 
        """Стандартная процедура инициализации в 4х-битном режиме (RTFM)"""
 
        self.send_tetra(0x02, self.COMMAND)
 
        self.send_octet(0x28, self.COMMAND)
        self.send_octet(0x06, self.COMMAND)
        self.send_octet(0x0C, self.COMMAND)
        sleep(0.1)
 
    def clear(self):
        """Очистка дисплея"""
        self.send_octet(0x01, self.COMMAND)
        sleep(0.1)
 
    def set_pos(self, x, y):
        start = self.LINE_1 if y == 0 else self.LINE_2
        self.send_octet(start+x, self.COMMAND)
 
    def send_lines(self, line_1="", line_2=""):
        self.clear()
 
        self.set_pos(0, 0)
        for c in line_1:
            self.send_octet(ord(self.RUS_CHARS_MAP.get(c, c)), self.DATA)
 
        self.set_pos(0, 1)
        for c in line_2:
            self.send_octet(ord(self.RUS_CHARS_MAP.get(c, c)), self.DATA)
 
def main(line_1="", line_2=""):
 
    with Device() as dev:
        dev.ftdi_fn.ftdi_disable_bitbang()
        sleep(0.1)
 
    with BitBangDevice() as bb:
        bb.baudrate = 4800
        display = HD44780(bb)
        display.init()
        display.clear()
        display.send_lines(line_1, line_2)
 
    with Device() as dev:
        dev.ftdi_fn.ftdi_disable_bitbang()
        sleep(0.1)
 
 
if __name__ == "__main__":
    line_1 = u"Уррра! :)"
    line_2 = u"Zzzz..."
 
    if len(argv) >= 2:
        line_1 = argv[1].decode("utf-8")
 
    if len(argv) >= 3:
        line_2 = argv[2].decode("utf-8")
 
    main(line_1, line_2)
    print "OK"

 
 

Пример от Онуфрия Аношенкова.
Написано на C++Builder6 2002 года, для знакосинтезирующих дисплеев WH2004. Для работы — в комбобоксе выбрать своё устройство, инициализация и очистка дисплея происходит автоматически. Для вывода информации нажать кнопку вывода и выбрать необходимый радиобаттон (в любом порядке); при выводе по таймеру не рекомендуется выбирать время меньше чем 330 мс, так как происходит наложение событий таймера. Вывод организован только для латиницы и знаков, остальные символы игнорируются. Текст вводить в таблицу. Так как для Си FTDI не предусмотрели анналогичный файлик как для Delphi, то написанно на голом API.
FTDI_Builder6.rar

Асинхронный битбанг
Есть еще один альтернативный режим работы FT2232, который настолько похож на синхронный битбанг, что о нем можно рассказать прямо здесь и сейчас. Это Asynchronous Bitbang.
 

Единственное отличие от синхронного в том, что данные читаются с пинов в буфер непрерывно с частотой тактового сигнала. Отправка данных осуществляется тем-же путем, что и при синхронном варианте. Более того, набор функций, используемый в обоих случаях, абсолютно идентичен.
 

Асинхронный битбанг появился раньше синхронного и CBUS-битбанга. Это самый первый битбанг режим в микросхемах FTDI, оставленный в FT2232 в назидание потомкам (и для совместимости). Сейчас практическую пользу из него можно извлечь разве что попытавшись построить подобие логического анализатора на FTDI.
 

Заключение
Вот и все. Возможно я упустил некоторые подробности, но сделал это намерено, чтобы не перегружать статью лишними подробностями. Исходники лежат в архиве. Комментариев там навалом, так что разобраться можно. Кроме того в конце поста есть несколько полезных ссылок.
 

Файлы к статье

Полезные ссылки

 

Титры… медленно уплывают за горизонт (включите воображение)
 

  • Техзадание на разработку и итоговая редактура — Di Halt
  • Кодинг и техническая часть — dcoder

58 thoughts on “Работа с микросхемой FTDI FT2232 в режиме BitBang”

        1. Я, кстати, когда об этом прочитал у тебя в свое время, часть с «BLS, только без обоймы» как-то прохлопал и просто одел в термоусадку зачищенный проводок. Так оно тоже работает, правда, термоусадка слезает периодически.

          1. Возьми усадку поменьше. У меня контакт в нее почти в притык входит до обсаживания, а после усадки она очень плотно обтягивает железку, огибая все неровности.

      1. DI HALT, ответь на крайний коммент на странице, пожалуйста. Прошу прощения за навязчивость, сроки поджимают. Мне только надо знать — возможно ли на этой микрухе сделать то, что мне надо, или надо плату с МК покупать? Спасибо.

  1. А еще если подсоединить FT2232 c JTAG портом ATmega, то можно наблюдать за или управлять пинами ATmega с компьютера. Эта технология называется boundary-scan, изначально создавалась для тестирования печатных плат. Софт для boundary-scan который умеет работать через FT2232 — TopJTAG Probe.

  2. Гм, так вот как работает битбанг в этой микре. Любопытно.
    Единственное, чего я не понял — она выставляет состояние порта каждый тик тактового сигнала (который 16*BaudRate) и, соответсвенно, требует передачи в нее 16*BaudRate байт в секунду, или же каждый 16-й тик (т.е. со скоростью BaudRate)?

    Ну а по программной части я бы многое сделал иначе. В частности, чем выходить, не найдя микросхем — лучше просто оставить список пустым, не редкость, когда программу запускают раньше, чем воткнут девайс. Список устройств я бы запрашивал (если это занимает не более 100мс) прямо при разворачивании комбобокса, для включения/выключения интерфейса тоже есть более удобные методы. Перекодировку разумней было сделать вида TranslatedChar:=CharTable[InputChar] — тогда не нужны ни проверки, ни, тем более, поиск (также можно задать CharTable: array[128..255] of Char и сэкономить на первой половине кодовой таблицы, но тогда проверку на принадлежность символа Latin-1 придется оставить)..

    1. >>она выставляет состояние порта каждый тик тактового сигнала (который 16*BaudRate) и, соответсвенно, требует передачи в нее 16*BaudRate байт в секунду, или же каждый 16-й тик (т.е. со скоростью BaudRate)?

      Неа, она ничо не требует. Ты можешь закидывать данные в буфер, когда захочешь, но выводится из буфера на пины они будут со скоростью 16*BaudRate

      1. Ну все равно, если я хочу чтобы достаточно длинный сигнал вывелся без пауз — мне нужно его заливать со скоростью 16*BaudRate байт в секунду, как я понял.

    1. Не вижу проблем переписать в тот же пошарпаный Си. ДиХалт ведь все выложил… Зачем ждать, пока кто-то сделает, если есть и желание и возможности?..

  3. может чуть не в тему, чисто для информации — ftdi стала делать и специализированные мосты
    и для i2c и для spi, и вроде это дешевле, чем ft232. так что для специальных целей (типа программаторов ) можно будет их использовать. без всяких битбангов и MPSSE
    http://www.ftdichip.com/FT-X.htm

  4. Еще бы узнать как одновременно работать с DBUS и CBUS, а то у меня чегото не получается на ft232rl , переходя на битбанг CBUS данные на DBUS теряются.

  5. Давно думал, а чем в свете современных ОС прожигать старенькие, но вкусные AT89c10(20,40)51?
    Времянки все расписаны в ДШ, немножко повозится с софтом (ну, не совсем «немножко»:) ).
    Да и с теми же AVR попробовать «голый» параллельный» программатор замутить? Еще с выходом первых 232/245 возился с битбангом. Тогда он был немножко упрощенный, а сейчас не нарадуюсь — AVRealUSB ну просто летает! Спасибо за придачу начального ускорения в одно ЖО. :)

    1. Одно время был такой сайт atprog.boom.ru где был клевый и простой программатор, а также сорцы прошивки для АТ89С51 и для какой то древней AVR. Вот ту древнюю авр можно было легко переписать на ту же мегу8535 или подобную. Но сайтик похерился, а я сорцы прошивки под авр проеп куда то.

      1. Дык в том-то и фишка. Не хочу отдельный процик ставить. Хотя вполне тот же копеешный STM8S103xx справится. И времянки точные отработает, и пинами на ключи пошевелит, и битов, вроде, хватит, даже в 20-выводном варианте…
        Но и битбанг вкусно! :)

        1. Я делал погроматор AT89Cx051 на основе пинборда (или самостоятельного ATmega16).
          Одной FTDI-шкой не отделаешься, да и 20-пинового STM8 хватит врядли — в интерфейсе программирования этихМК юзаются практически все выводы (1-2 пина не задействованы, т.е. 16-17 сигнальный линий, а надо еще как-то интерфейс до компа сделать). С AT89C51/52/55 все еще хуже — им нужно более 30 линий. Так что как минимум потребуется регистр.

  6. В честь больших выходных я, немножко подзатрахавшись, написал код на Python, который у меня работает на Ubuntu 12.04.

    Кнопки я делать не стал — было лень, так что только дисплей. Да и тот глючит — между перезапусками программы надо обязательно ресетить FT’шку иначе нифига не пашет! :(

  7. DI, подскажи, можно ли настроить 2232h на 16-бит паралельный режим? Даже, наверное больше… хочу привесить жки 16-бит + c/d + res + c/s. И смогу ли я на ней выжать хотя-бы fullspeed?

  8. DI HALT не планируете написать статью по AVR-микроконтроллерам со встроенным USB? По цене они дешевле чем FDTI + аналогичный контроллер без USB, а вкусностей больше.

  9. Привет! Не шарю в электронике, но жизнь заставляет. Мне нужно опрашивать состояние логического входа (лог 1 +5в, лог 0 — 0), c приблизительной частотой 2Кгц. Поможет ли мне эта микруха в этом? Как я понимаю, нужно опрашивать вход со скоростью 2048/16=128бит в секунду. Или скорость порта должна быть выровнена с микрухой? Подскажите, пожалуйста, а я тогда код на c# выложу, как всё получится.

    1. Ты лучше у dcodera спроси. Он эту статью писал и он разбирался с FTDI. Мое дело все ,что после FTDI и в комповое программирование я не лезу вообще. А вообще там все хитро и сильно зависит от ситуации.

  10. А как подключить библиотеки d2xx к c++ builder 6? ftd2xx.h вроде прикрутился, а вот на .lib компилятор ругается и выдает linker error. Драйвера скачивал с офф. сайта (http://www.ftdichip.com/Drivers/D2XX.htm) для x64 windows. Очень хотелось бы составить свою версию данной программки на c++.

  11. Вдохновившись этотй статьей, собрал устройство «USB2UART + GPIO». Вот беда — как только перехожу в режим bitbang — система теряет USB2UART устройство. В смысле — в Linuxе пропадает /dev/ttyUSB0
    Кто-нибудь знает, как это побороть? Или это закон природы?

    1. Это никак не побороть, ибо включая bit-bang, микросхема перестаёт предоставлять интерфейс UART.

      Теоретически есть шанс переключить в bit-bang только один канал, а второй оставить в UART, ибо официально они абсолютно независимы. Но проверить мне сейчас не на чем.

  12. День добрый! А как вы вывели картинку над фразой «Теперь FTDI и дисплей будут говорить друг с другом на одном языке»? Это из скетч АПА или прям в игле можно такой вид включить?

  13. У этой микрухи ведь 2 канала, A и B, но я не могу понять по коду, как именно задается канал для работы и как отправка данных отличается для разных каналов. Можете пояснить, пожалуйсиа?

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