Скромная и незаметная
На демоплате 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.
Заключение
Вот и все. Возможно я упустил некоторые подробности, но сделал это намерено, чтобы не перегружать статью лишними подробностями. Исходники лежат в архиве. Комментариев там навалом, так что разобраться можно. Кроме того в конце поста есть несколько полезных ссылок.
Файлы к статье
Полезные ссылки
- Драйвер D2XX. Для Windows, Linux и яблока.
- Раздел с доками на сайте производителя. Рекомендую пробежаться — есть много интересностей.
- Даташит на FT2232D
- Описание битбанг режимов. Относится не только к FT232, но и ко всем микросхемам, где есть эти режимы
- D2XX Programmer’s Guide. Ценный документ с описанием всех функций, которые предоставляет драйвер D2XX.
- Статья dcoder’a в сообществе про конфигурацию FTDI и ее параметры.
- Статья dcoder’a в сообществе про CBUS bitbang
Титры… медленно уплывают за горизонт (включите воображение)
- Техзадание на разработку и итоговая редактура — Di Halt
- Кодинг и техническая часть — dcoder
Ух блин, зачетно! Может как-нибудь не поскуплюсь и таки куплю ее. Кстати, а что за чиптьюн играет, обожаю его. И нескромный вопрос: почему бУтон, а не бАтон?))
Ой. забыл еще спросить: где взять такие проводочки? Handmade?
Да, обычные. Разьем BLS только без обоймы, а просто в термоусадке. Так удобней.
Я, кстати, когда об этом прочитал у тебя в свое время, часть с «BLS, только без обоймы» как-то прохлопал и просто одел в термоусадку зачищенный проводок. Так оно тоже работает, правда, термоусадка слезает периодически.
Возьми усадку поменьше. У меня контакт в нее почти в притык входит до обсаживания, а после усадки она очень плотно обтягивает железку, огибая все неровности.
В конце написано кто автор музыки. А вообще это AY emulator. Потому, что там U :)
DI HALT, ответь на крайний коммент на странице, пожалуйста. Прошу прощения за навязчивость, сроки поджимают. Мне только надо знать — возможно ли на этой микрухе сделать то, что мне надо, или надо плату с МК покупать? Спасибо.
А еще если подсоединить FT2232 c JTAG портом ATmega, то можно наблюдать за или управлять пинами ATmega с компьютера. Эта технология называется boundary-scan, изначально создавалась для тестирования печатных плат. Софт для boundary-scan который умеет работать через FT2232 — TopJTAG Probe.
Более того, можно и внутрисхемную отладку делать через Happy Jtag 2
вот это крутотенюшка! только мне до программирования PC еще расти и расти) а без этого и битбаг не битбанг
Гм, так вот как работает битбанг в этой микре. Любопытно.
Единственное, чего я не понял — она выставляет состояние порта каждый тик тактового сигнала (который 16*BaudRate) и, соответсвенно, требует передачи в нее 16*BaudRate байт в секунду, или же каждый 16-й тик (т.е. со скоростью BaudRate)?
Ну а по программной части я бы многое сделал иначе. В частности, чем выходить, не найдя микросхем — лучше просто оставить список пустым, не редкость, когда программу запускают раньше, чем воткнут девайс. Список устройств я бы запрашивал (если это занимает не более 100мс) прямо при разворачивании комбобокса, для включения/выключения интерфейса тоже есть более удобные методы. Перекодировку разумней было сделать вида TranslatedChar:=CharTable[InputChar] — тогда не нужны ни проверки, ни, тем более, поиск (также можно задать CharTable: array[128..255] of Char и сэкономить на первой половине кодовой таблицы, но тогда проверку на принадлежность символа Latin-1 придется оставить)..
>>она выставляет состояние порта каждый тик тактового сигнала (который 16*BaudRate) и, соответсвенно, требует передачи в нее 16*BaudRate байт в секунду, или же каждый 16-й тик (т.е. со скоростью BaudRate)?
Неа, она ничо не требует. Ты можешь закидывать данные в буфер, когда захочешь, но выводится из буфера на пины они будут со скоростью 16*BaudRate
Ну все равно, если я хочу чтобы достаточно длинный сигнал вывелся без пауз — мне нужно его заливать со скоростью 16*BaudRate байт в секунду, как я понял.
как-то Delphi мне далёк , есть на чём нить другом
Нету. Напиши и будет. Пока желающих дополнить пример на других реализация я не наблюдаю.
Не вижу проблем переписать в тот же пошарпаный Си. ДиХалт ведь все выложил… Зачем ждать, пока кто-то сделает, если есть и желание и возможности?..
Там самое сложное либу подключить. Как по мне. Вообще, если не сложно, написали бы на шарпе сей примерчик, я бы в статью добавил.
может чуть не в тему, чисто для информации — ftdi стала делать и специализированные мосты
и для i2c и для spi, и вроде это дешевле, чем ft232. так что для специальных целей (типа программаторов ) можно будет их использовать. без всяких битбангов и MPSSE
http://www.ftdichip.com/FT-X.htm
Еще бы узнать как одновременно работать с DBUS и CBUS, а то у меня чегото не получается на ft232rl , переходя на битбанг CBUS данные на DBUS теряются.
Давно думал, а чем в свете современных ОС прожигать старенькие, но вкусные AT89c10(20,40)51?
Времянки все расписаны в ДШ, немножко повозится с софтом (ну, не совсем «немножко»:) ).
Да и с теми же AVR попробовать «голый» параллельный» программатор замутить? Еще с выходом первых 232/245 возился с битбангом. Тогда он был немножко упрощенный, а сейчас не нарадуюсь — AVRealUSB ну просто летает! Спасибо за придачу начального ускорения в одно ЖО. :)
Одно время был такой сайт atprog.boom.ru где был клевый и простой программатор, а также сорцы прошивки для АТ89С51 и для какой то древней AVR. Вот ту древнюю авр можно было легко переписать на ту же мегу8535 или подобную. Но сайтик похерился, а я сорцы прошивки под авр проеп куда то.
Дык в том-то и фишка. Не хочу отдельный процик ставить. Хотя вполне тот же копеешный STM8S103xx справится. И времянки точные отработает, и пинами на ключи пошевелит, и битов, вроде, хватит, даже в 20-выводном варианте…
Но и битбанг вкусно! :)
Я делал погроматор AT89Cx051 на основе пинборда (или самостоятельного ATmega16).
Одной FTDI-шкой не отделаешься, да и 20-пинового STM8 хватит врядли — в интерфейсе программирования этихМК юзаются практически все выводы (1-2 пина не задействованы, т.е. 16-17 сигнальный линий, а надо еще как-то интерфейс до компа сделать). С AT89C51/52/55 все еще хуже — им нужно более 30 линий. Так что как минимум потребуется регистр.
Таки да!
>>Одно время был такой сайт atprog.boom.ru
оно?
http://web.archive.org/web/20090225075950/http://atprog.boom.ru/download.html
Fuck Eaahh!!! Не знал, что вебархив сохраняет и вложения с сайтов. Оно, да.
Только они почти все битые. Причем самые интересные — сурцы шелла и фирмваря.
Ничо не битое. Только что утащил все что нужно. Все сорцы на месте
Держи все награбленное
http://dl.dropbox.com/u/12226548/ATProg.zip
У меня на часть архивов WinRAR ругается. На большинство — «поврежден заголовок файла «???», на тот что с хексами — «ошибка CRC в таком-то файле».
Он гонит. Попробуй открыть другим архиватором чтоль. Я тоталкоммандеровским открывал, вообще без сучка без задоринки.
Похоже последнее сохранение вот это.
http://web.archive.org/web/20100626134942/http://atprog.boom.ru/download.html
В честь больших выходных я, немножко подзатрахавшись, написал код на Python, который у меня работает на Ubuntu 12.04.
Кнопки я делать не стал — было лень, так что только дисплей. Да и тот глючит — между перезапусками программы надо обязательно ресетить FT’шку иначе нифига не пашет! :(
Хм, научился сбрасывать битбанг режим у FT’шки, глюков стало меньше! ;)
Хальт, под Mac OS X этот пример трэба?
Почему бы и нет?
Кстати, если мой питонячий код таки выложат, то он наверное и под Mac OS X пойдёт нормально.
Добавил твой пример в пост. Спасибо.
DI, подскажи, можно ли настроить 2232h на 16-бит паралельный режим? Даже, наверное больше… хочу привесить жки 16-бит + c/d + res + c/s. И смогу ли я на ней выжать хотя-бы fullspeed?
DI HALT не планируете написать статью по AVR-микроконтроллерам со встроенным USB? По цене они дешевле чем FDTI + аналогичный контроллер без USB, а вкусностей больше.
Не планирую. Я не знаю что делать с этим встроенным USB со стороны компа.
А вот интересно. Может 2232 работать с разными скоростями в разных каналах?
должна
Спасибо. Придется купить и проверить.
Привет! Не шарю в электронике, но жизнь заставляет. Мне нужно опрашивать состояние логического входа (лог 1 +5в, лог 0 — 0), c приблизительной частотой 2Кгц. Поможет ли мне эта микруха в этом? Как я понимаю, нужно опрашивать вход со скоростью 2048/16=128бит в секунду. Или скорость порта должна быть выровнена с микрухой? Подскажите, пожалуйста, а я тогда код на c# выложу, как всё получится.
Ты лучше у dcodera спроси. Он эту статью писал и он разбирался с FTDI. Мое дело все ,что после FTDI и в комповое программирование я не лезу вообще. А вообще там все хитро и сильно зависит от ситуации.
ок! спасибо!
А как подключить библиотеки d2xx к c++ builder 6? ftd2xx.h вроде прикрутился, а вот на .lib компилятор ругается и выдает linker error. Драйвера скачивал с офф. сайта (http://www.ftdichip.com/Drivers/D2XX.htm) для x64 windows. Очень хотелось бы составить свою версию данной программки на c++.
Вдохновившись этотй статьей, собрал устройство «USB2UART + GPIO». Вот беда — как только перехожу в режим bitbang — система теряет USB2UART устройство. В смысле — в Linuxе пропадает /dev/ttyUSB0
Кто-нибудь знает, как это побороть? Или это закон природы?
Это никак не побороть, ибо включая bit-bang, микросхема перестаёт предоставлять интерфейс UART.
Теоретически есть шанс переключить в bit-bang только один канал, а второй оставить в UART, ибо официально они абсолютно независимы. Но проверить мне сейчас не на чем.
Спасибо. Это очень грустно. У меня были большие планы на это дело. А можно хотя-бы вернуть интерфейс автоматически, на выходе из программы?
Завести ресет на один из пинов и самоубиться.
Что то похожее для режима FT245 (Синхронный FIFO) есть возможность написать?
День добрый! А как вы вывели картинку над фразой «Теперь FTDI и дисплей будут говорить друг с другом на одном языке»? Это из скетч АПА или прям в игле можно такой вид включить?
Нет это я в фотошопе рисовал. Взял слой платы из GERBV
У этой микрухи ведь 2 канала, A и B, но я не могу понять по коду, как именно задается канал для работы и как отправка данных отличается для разных каналов. Можете пояснить, пожалуйсиа?
С точки зрения компа (и API) 2232 представляется как две отдельных 232. Соответственно, которую откроешь — тот канал и будет использоваться.
Сделал проект для FT232RL на C++ Builder6. Могу выложить.
Было бы круто! пришли на мыло dihalt@dihalt.ru
Есть либа для питона pylibftdi + готовые примеры вроде переключение битов из браузера
python -m pylibftdi.examples.bit_server