Библиотека ftd2xx и пример ее использования.

Я по образованию химик, а не программист и не электронщик, но очень уж мне нравится иногда спаять какое-нибудь полезное (и не очень) устройство. Здесь то мне на помощь приходит сайт easyelectronics и сообщество. Это действительно классный проект! Пользуясь случаем хочу передать привет еще раз поблагодарить создателей и участников данного проекта за информацию, которую они предоставляют людям далеким от электроники в доступной форме. Вношу и я свой скромный вклад в распространение знаний в области электроники.

Вдоволь наработавшись с 8-битными AVRами, захотелось мне опробовать 32-битные микроконтроллеры. Выбрал STM32. Подкупила стоимость отладочной платы. По-сути, из-за отсутствия отладочных средств отказался от AVR. Помигав светодиодом, решил углубит свои знания и создать более «взрослую» программу. Выбор пал на модуль USART. Тем более я всегда хотел организовать обмен между ПК и внешним устройством.

Так вот вооружившись переходником USB<->USART, демо-версией Keil'a, отладочной платой STM32VLDISCOVERY и желанием реализовать проект получилось вот эта статья. Людям, профессионально занимающимися микроконтроллерами эта статья вряд ли будет полезна, но новичкам в этой области, таким как я, надеюсь она пригодится, особенно тем, кто хочет разобраться с модулем USART микроконтроллеров STM32.

Итак, по порядку.

Идея


Программа на ПК посылает ASCII-код (символ). Микроконтроллер считывает этот ASCII-код из буфера данных и увеличивает его на 1 и отправляет обратно. Т.е. отправляем 1 получаем 2, отправляем а получаем b и т.д. Нужно же как-то проверит, что программа работает правильно?:)

Программа для микроконтроллера


Пишем программу. Я использовал демо-версию Keil'a.
#include "stm32f10x.h"
#include "stm32f10x_gpio.h" 
#include "stm32f10x_rcc.h" 
#include "stm32f10x_tim.h"
#include "misc.h"
#include "stm32f10x_usart.h"

unsigned int Data;
/***********************************************************
------------------------USART1 Interrupt service routine------------------------
************************************************************/
void USART1_IRQHandler(void)
{
//После получения байта, программа попадает в прерывание.
        if (USART_GetITStatus(USART1, USART_IT_RXNE) != (u16)RESET)
    {
        USART_ClearITPendingBit(USART1, USART_IT_RXNE);
                //В случае приема данных, после выполнения стандартных процедур, 
                //программа проверяет состояние 9 ножки порта А, если выставлен высокий уровень (светодиод включен), 
                //то программа выставляет низкий (гасит светодиод)
                if ((GPIOC->ODR & GPIO_Pin_9) != Bit_RESET)                                     
                {
                GPIOC->BSRR = GPIO_BSRR_BR9;                                                                    
                }
                else
                {
                // и наоборот. 
                GPIOC->BSRR = GPIO_BSRR_BS9;                                                                    
                }
                //Далее считывает содержимое буфера в переменную Data. 
                //Таким образом в переменной оказывается записан ASCII код символа
                //(который моет быть в диапазоне от 0 до 255), отправленного с клавиатуры. 
                Data = USART_ReceiveData(USART1);
                //После получения кода символа, программа увеличивает его на 1 (здесь можно произвести любые действия,
                // лишь бы определить, что программа работает правильно).
                Data = Data + 1;
                //Затем помещает его в буфер передатчика и разрешает прерывание при завершении передачи.                                                                                                                
                USART_SendData(USART1, Data);
                USART_ITConfig(USART1, USART_IT_TXE, ENABLE);

        }
        if (USART_GetITStatus(USART1, USART_IT_TXE) != (u16)RESET)
        {
                USART_ClearITPendingBit(USART1, USART_IT_TXE);
                //Запрещаем прерывание по завершении передачи. 
                USART_ITConfig(USART1, USART_IT_TXE, DISABLE);
                //Возврат в бесконечный цикл. Ждем приема следующего символа.

        } 
}


/**********************************************************
------------------------Main function------------------------
***********************************************************/
int main(void)
{
//>>>>>>>>>>>>>>>>>>>>>>>>Variables and structures definitions>>>>>>>>>>>>>>>>>>>>>>>>//
NVIC_InitTypeDef NVIC_InitStructure;

USART_InitTypeDef USART_InitStruct={
                9600,
                USART_WordLength_8b,
                USART_StopBits_1,
                USART_Parity_No,
                (USART_Mode_Rx | USART_Mode_Tx),
                USART_HardwareFlowControl_None
};

GPIO_InitTypeDef GPIO_InitStructure;
//<<<<<<<<<<<<<<<<<<<<<<<Variables and structures definitions<<<<<<<<<<<<<<<<<<<<<<//

RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO | RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOC | RCC_APB2Periph_USART1,ENABLE);               //Enable clock

//>>>>>>>>>>>>>>>>>>>>>>>>Pins configuration>>>>>>>>>>>>>>>>>>>>>>>>//
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;                                                                       //Pin9 output TX
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_Init(GPIOA, &GPIO_InitStructure);

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;                                                                      //Pin 10 input RX
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; 
GPIO_Init(GPIOA, &GPIO_InitStructure);

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;                                                                       //Pin 8 output. Controls blue LED
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 
GPIO_Init(GPIOC, &GPIO_InitStructure);

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;                                                                       //Pin 9 output. Controls green LED
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 
GPIO_Init(GPIOC, &GPIO_InitStructure);
//<<<<<<<<<<<<<<<<<<<<<<<<Pins configuration<<<<<<<<<<<<<<<<<<<<<<<<//

USART_Init(USART1,&USART_InitStruct);

//>>>>>>>>>>>>>>>>>>>>>>>>NVIC configuration>>>>>>>>>>>>>>>>>>>>>>>>//
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure); 
//<<<<<<<<<<<<<<<<<<<<<<<<NVIC configuration<<<<<<<<<<<<<<<<<<<<<<<<//

USART_Cmd(USART1,ENABLE);
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
GPIOC->BSRR = GPIO_BSRR_BS9;


while (1)
{
//В функции main происходит настройка портов ввода-вывода, usarta. 
//Программа "крутится" в бесконечном цикле ожидая прерывания, сообщающего о приеме данных. 
//Заметьте, что сначала разрешено прерывание при приеме данных, а не отправки. 
}

return 0;}

Написав программу, проверив на отсутствие ошибок не забываем записать в память микроконтроллера. Напомню, что я использовал в своих экспериментах STM32F100RBT6B — тот что установлен в отладочной плате STM32VLDISCOVERY.

Микроконтроллер прошит и ожидает своего звездного часа. Осталось написать программу для ПК. Есть одно замечание. Если у вас нет желания заниматься программированием для ПК, то можно поиграться с терминалом. Например, использовать Terminal v1.9b by Bray. У меня была идея написать отдельную статью с подробным описанием всей последовательности действий, и я ее почти довел до завершения, но потом решил, что получится совсем детская статья.

Обзор основных функций библиотеки ftd2xx


Начну я с краткого описания функций, необходимых для организации обмена данными между устройством и ПК. Описывать все функции я не буду, т.к. их слишком много. Тем более, что всегда можно обратиться к первоисточнику. Все функции, типы данных, структуры описаны в файле D2XX_Programmer's_Guide(FT_000071), который можно скачать с сайта FTDI.

Перед выполнением каких-либо действий, операций чтения-записи устройства, необходимо произвести определенные манипуляции по настройке устройства. В первую очередь необходимо вызвать функцию FT_CreateDeviceInfoList. Эта функция принимает указатель на переменную, в которую функция поместит количество устройств D2xx, подключенных к ПК.
FT_STATUS FT_CreateDeviceInfoList (LPDWORD lpdwNumDevs) 

В случае успешного выполнения, функция вернет значение FT_OK. В противном случае код ошибки:
FT_STATUS (DWORD) 
  FT_OK = 0 
  FT_INVALID_HANDLE = 1 
  FT_DEVICE_NOT_FOUND = 2 
  FT_DEVICE_NOT_OPENED = 3 
  FT_IO_ERROR = 4 
  FT_INSUFFICIENT_RESOURCES = 5 
  FT_INVALID_PARAMETER = 6 
  FT_INVALID_BAUD_RATE = 7 
  FT_DEVICE_NOT_OPENED_FOR_ERASE = 8 
  FT_DEVICE_NOT_OPENED_FOR_WRITE = 9 
  FT_FAILED_TO_WRITE_DEVICE = 10 
  FT_EEPROM_READ_FAILED = 11 
  FT_EEPROM_WRITE_FAILED = 12 
  FT_EEPROM_ERASE_FAILED = 13 
  FT_EEPROM_NOT_PRESENT = 14 
  FT_EEPROM_NOT_PROGRAMMED = 15 
  FT_INVALID_ARGS = 16 
  FT_NOT_SUPPORTED = 17 
  FT_OTHER_ERROR = 18 


Пример использования функции:
FT_STATUS ftStatus;         //Объявляем переменную типа FT_STATUS. Содержит результат работы функции
DWORD numDevs;              //Предварительно объявляем переменную, в которой будет храниться число
                            //подключенных D2XX утройств
 
// Создаем список устройств
ftStatus = FT_CreateDeviceInfoList(&numDevs); 
if (ftStatus == FT_OK) { 
  printf("Number of devices is %d\n",numDevs);             //Печатаем количество устройств
} 
else { 
  // Функция FT_CreateDeviceInfoList завершилась неудачей 
}


Если функция завершилась успешно, то можно идти дальше. На следующем этапе вызываем функцию FT_GetDeviceInfoList.

FT_STATUS FT_GetDeviceInfoList (FT_DEVICE_LIST_INFO_NODE *pDest, LPDWORD lpdwNumDevs)

Она принимает 2 параметра: *pDest — указатель на массив структур FT_DEVICE_LIST_INFO_NODE, которые содержат поля, описывающие каждое устройство;
lpdwNumDevs — количество элементов в массиве. Это и есть количество наших устройств, подключенных к ПК, полученное с помощью функции FT_CreateDeviceInfoList.
Описание структуры FT_DEVICE_LIST_INFO_NODE
typedef struct _ft_device_list_info_node { 
  DWORD Flags;              
  DWORD Type;  
  DWORD ID;  
  DWORD LocId;  
  char SerialNumber[16];  
  char Description[64];  
  FT_HANDLE ftHandle;  
} FT_DEVICE_LIST_INFO_NODE;  


Пример использования функции (опять же из документа)
FT_STATUS ftStatus; 
FT_DEVICE_LIST_INFO_NODE *devInfo;         //Объявляем указатель на массив структур 
DWORD numDevs;                             //Объявляем переменную, содержащую количество элементов       
                                           //(устройств, структур) в массиве
 
//Это уже знакомо 
ftStatus = FT_CreateDeviceInfoList(&numDevs); 
 
if (ftStatus == FT_OK) { 
  printf("Number of devices is %d\n",numDevs); 
} 
//Если подключено хотя бы одно устройство
if (numDevs > 0) { 
  //Выделить память для хранения массива в зависимости от количества элементов 
  devInfo = 
(FT_DEVICE_LIST_INFO_NODE*)malloc(sizeof(FT_DEVICE_LIST_INFO_NODE)*numDevs);  
  //Получить информацию о каждом устройстве в массиве 
  ftStatus = FT_GetDeviceInfoList(devInfo,&numDevs);  
  if (ftStatus == FT_OK) { 
    //Если успешно 
    for (int i = 0; i < numDevs; i++) { 
      printf("Dev %d:\n",i);                                  //Напечатать номер устройства, начиная с 0
      printf("  Flags=0x%x\n",devInfo[i].Flags);              //Напечатать все поля каждой структуры
      printf("  Type=0x%x\n",devInfo[i].Type);                
      printf("  ID=0x%x\n",devInfo[i].ID);                    
      printf("  LocId=0x%x\n",devInfo[i].LocId);  
      printf("  SerialNumber=%s\n",devInfo[i].SerialNumber);  
      printf("  Description=%s\n",devInfo[i].Description);  
      printf("  ftHandle=0x%x\n",devInfo[i].ftHandle);  
    } 
  } 
} 


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

Для извлечения полей данных определенной структуры в библиотеке ftd2xx существует функция FT_GetDeviceInfoDetail. Описание приведено ниже
FT_STATUS FT_GetDeviceInfoDetail (DWORD dwIndex, LPDWORD lpdwFlags,  
                LPDWORD lpdwType, 
                LPDWORD lpdwID, LPDWORD lpdwLocId, 
                PCHAR pcSerialNumber, PCHAR pcDescription, 
                FT_HANDLE *ftHandle) 


dwIndex — индекс устройства в массиве. Минимальное значение 0.
lpdwFlags — флаги устройства.
lpdwType — тип устройства.
lpdwID — ID устройства.
lpdwLocId — обозначает к какому порту подключено. Здесь стоит упомянуть о небольшой утилите, которую можно скачать с сайта FTDI. Позволяет определить к какому хост-контроллеру подключено наше устройство.
pcSerialNumber — серийный номер устройства. По-видимому, уникальный номер, по которому и можно отличить нужное устройство.
pcDescription — описание устройства.
*ftHandle — указатель на дескриптор устройства.

Пример использования функции FT_GetDeviceInfoDetail
FT_STATUS ftStatus;          //Объявляем нужные переменные
FT_HANDLE ftHandleTemp;  
DWORD numDevs;  
DWORD Flags;  
DWORD ID;  
DWORD Type;  
DWORD LocId;  
char SerialNumber[16];  
char Description[64];  
 
//Уже знакомо
ftStatus = FT_CreateDeviceInfoList(&numDevs);  
if (ftStatus == FT_OK) { 
  printf("Number of devices is %d\n",numDevs);  
} 
 
if (numDevs > 0) { 
  //Получить информацию об устройстве с индексом "0" 
  ftStatus = FT_GetDeviceInfoDetail(0, &Flags, &Type, &ID, &LocId, SerialNumber, 
Description,           &ftHandleTemp);  
  if (ftStatus == FT_OK) { 
//Если функция завершилось успешно, напечатать поля соответствующей структуры
    printf("Dev 0:\n"); 
    printf("  Flags=0x%x\n",Flags);  
    printf("  Type=0x%x\n",Type);  
    printf("  ID=0x%x\n",ID);  
    printf("  LocId=0x%x\n",LocId);  
    printf("  SerialNumber=%s\n",SerialNumber);  
    printf("  Description=%s\n",Description);  
    printf("  ftHandle=0x%x\n",ftHandleTemp);  
  } 
} 


Еще для обеспечения минимальной функциональности программы понадобится функция FT_Open, а лучше ее расширенная версия FT_OpenEx.
FT_STATUS FT_OpenEx (PVOID pvArg1, DWORD dwFlags, FT_HANDLE *ftHandle) 

Функция принимает 3 параметра:
pvArg1 — указатель на аргумент, который определяется вторым параметром dwFlags;
dwFlags — одна из констант (FT_OPEN_BY_SERIAL_NUMBER, FT_OPEN_BY_DESCRIPTION, FT_OPEN_BY_LOCATION), определяющих что именно содержится в переменной pvArg1: серийный номер, описание, локация.
*ftHandle — сюда функция поместит указатель на дескриптор устройства. Самый важный параметр, т.к. именно по дескриптору устройства происходит настройка и обмен данными с устройством.

Один из примеров, описанных в документации. Открытие устройства по его серийному номеру:
FT_STATUS ftStatus;
FT_HANDLE ftHandle1; 
ftStatus = FT_OpenEx("FT000001",FT_OPEN_BY_SERIAL_NUMBER,&ftHandle1); 
if (ftStatus == FT_OK) { 
  // Устройство с серийным номером "FT000001" открыто 
} 
else { 
  // Неудача:(
} 
 


Я библиотеке ftd2xx я нашел минимум 2 функции, которые производят настройку интерфейса. Это FT_SetBaudRate и FT_SetDataCharacteristics. Первая, как ясно из названия устанавливает скорость передачи данных.
FT_STATUS FT_SetBaudRate (FT_HANDLE ftHandle, DWORD dwBaudRate)

Функция принимает 2 параметра:
ftHandle — дескриптор устройства, подлежащего настройке;
dwBaudRate — собственно скорость с которой мы хотим обмениваться информацией с устройством.

Описание функции FT_SetDataCharacteristics представлено ниже:
FT_STATUS FT_SetDataCharacteristics (FT_HANDLE ftHandle, UCHAR uWordLength, 
UCHAR uStopBits, UCHAR uParity) 

Функция принимает значения:
ftHandle — дескриптор устройства, подлежащего настройке;
uWordLength — число бит в слове. Может быть либо 7, либо 8. Эти значения задаются соответственно константами FT_BITS_8 и FT_BITS_7;
uStopBits — количество стоп-битов — 1 (FT_STOP_BITS_1) или 2 (FT_STOP_BITS_2);
uParity — проверка четности. Должно быть одним из: FT_PARITY_NONE, FT_PARITY_ODD, FT_PARITY_EVEN, FT_PARITY_MARK или FT_PARITY SPACE.
Пример настройки устройства со следующими параметрами передачи данных: длина — 8 бит, количество стоп-битов — 1, отсутствие проверки четности.
FT_HANDLE ftHandle; 
FT_STATUS ftStatus; 
//Открываем устройство с индексом 0 в массиве устройств 
ftStatus = FT_Open(0, &ftHandle); 
if(ftStatus != FT_OK) { 
  //Устройство не открыто
  return; 
} 
 
// Если устройство было открыто, то настраиваем устройство
ftStatus = FT_SetDataCharacteristics(ftHandle, FT_BITS_8, FT_STOP_BITS_1, 
FT_PARITY_NONE); 
  if (ftStatus == FT_OK) { 
    // Устройство успешно настроено
  } 
  else { 
    // Неудача:( 
  } 
} 


Теперь поговорим о функциях, с помощью которых производится обмен данными с устройством. Функция записи передает данные от ПК к устройству.
FT_STATUS FT_Write (FT_HANDLE ftHandle, LPVOID lpBuffer, DWORD dwBytesToWrite,  
   LPDWORD lpdwBytesWritten) 

ftHandle — дескриптор устройства, в который передают данные;
lpBuffer — буфер. Другими словами это переменная, содержащая значение, которое мы хотим записать в устройство. В функцию нужно передать указатель на эту переменную;
dwBytesToWrite — количество записываемых байт. Т.е. размер переменной-буфера в байтах. Обычно используют конструкцию sizeof(Имя_переменной_буфера), которая возвращает размер переменной.
lpdwBytesWritten — фактически переданное количество байт.
FT_HANDLE ftHandle; 
FT_STATUS ftStatus; 
DWORD BytesWritten; 
char TxBuffer[256]; // Содержит данные для передачи устройству 
 
ftStatus = FT_Open(0, &ftHandle); 
if(ftStatus != FT_OK) { 
  // Не удалось открыть устройство
  return; 
} 
 
ftStatus = FT_Write(ftHandle, TxBuffer, sizeof(TxBuffer), &BytesWritten); 
  if (ftStatus == FT_OK) { 
    // Данные успешно записаны
  } 
  else { 
    // Ошибка при записи
  } 
} 
FT_Close(ftHandle); 

В примере была, не упомянутая функция FT_Close.
FT_STATUS FT_Close (FT_HANDLE ftHandle) 

Она принимает один параметр — дескриптор устройства. В случае успешного выполнения, как бы не странно это звучало, закрывает устройство :)

Если мы хотим прочитать данные из устройства, используем функцию FT_Read.
FT_STATUS FT_Read (FT_HANDLE ftHandle, LPVOID lpBuffer, DWORD dwBytesToRead,  
        LPDWORD lpdwBytesReturned) 

ftHandle — дескриптор устройства, из которого будем читать данные;
lpBuffer — указатель на переменную, в которую функция поместит считанные данные;
dwBytesToRead — количество байт, которые будем читать. Очевидно, что следует читать столько байт каков размер переменной-буфера, поэтому можно использовать sizeof();
lpdwBytesReturned — указатель на переменную, в которую функция сохранит количество фактически прочитаных байт.
FT_HANDLE ftHandle; 
FT_STATUS ftStatus; 
DWORD EventDWord; 
DWORD TxBytes; 
DWORD RxBytes; 
DWORD BytesReceived; 
char RxBuffer[256]; 
 
ftStatus = FT_Open(0, &ftHandle); 
if(ftStatus != FT_OK) { 
  // Ошибка при открытии устройства 
  return; 
} 
 
FT_GetStatus(ftHandle,&RxBytes,&TxBytes,&EventDWord); 
if (RxBytes > 0) { 
  ftStatus = FT_Read(ftHandle,RxBuffer,RxBytes,&BytesReceived); 
  if (ftStatus == FT_OK) { 
    // Чтение успешно
  } 
  else { 
    // Ошибка при чтении
  } 
} 
 
FT_Close(ftHandle);

Хорошо, когда функции завершаются без ошибок. Но это случается не всегда. Для определения причины ошибки в библиотеке есть функция FT_W32_GetLastError, которая принимает один параметр — дескриптор устройства, для которого нужно получить ошибку. В случае успешного завершения возвращает код ошибки, в случае неудачи — 0.
DWORD FT_W32_GetLastError (FT_HANDLE ftHandle)

Программа для ПК

А теперь самое вкусное — программа со стороны ПК, организующая передачу данных. Программа написана на С в Visual Studio 2008. Прикрепить файлы проекта к статье у меня не удалось( Если получится, то загружу как-нибудь. Я не создавал exe-файл, а проверял работу программы прямо из среды.
#include <Windows.h>
#include <iostream>
#include <conio.h>
#include <stdlib.h>
extern "C" {
#include "ftd2xx.h"
}

#pragma comment (lib,"ftd2xx.lib")

using namespace std;


int main (int argc, char *argv[])
{
	//Declare variables
	FT_STATUS ftStatus;						//Результат работы функции
	DWORD dwNumDevs=0;						//Количество устройств, подключенных к ПК
	FT_DEVICE_LIST_INFO_NODE *devInfo;		//Структура, описывающая устройство
	FT_STATUS ftStatus_GetInfoDetail;		//Результат работы функции FT_GetDeviceInfoDetail 
	FT_HANDLE ftHandleTemp;					//Переменные, в которые помещают поля структуры devInfo
	DWORD Flags;							
	DWORD ID;  
	DWORD Type;  
	DWORD LocId;  
	char SerialNumber[16]; 
	char Description[64];  

	FT_HANDLE	UserDevice0_Handle;				//Дескриптор нашего устройства
	char UserDevice0_SN[16]="A700fbLi";			//Серийный номер устройства
	char	UserDevice0_TransmitterBuffer=0;	//Буфер передатчика
	char	UserDevice0_ReceiverBuffer=0;		//Буфер приемника
	DWORD	UserDevice0_BytesWritten;			//Фактически записано байтов в устройство
	DWORD	UserDevice0_BytesRead;				//Фактически прочитано байтов из устройства
	FT_STATUS	UserDevice0_WriteStatus;		
	FT_STATUS	UserDevice0_ReadStatus;
	
	/*
	//Следующий фрагмент кода не обязателен. Но его можно включить в программу, если собираемся записывать в файл нужные 
	//данные
	DWORD	dwBytesWrite;					//Количество байтов, записываемых в файл
	//Удаляем старый файл
	DeleteFile(TEXT("C:\\MicrosoftVisualStudioProjects\\Project4_Dinamic\\Project4_Dinamic\\Log\\DevInfoList.txt"));
	
	//Создаем файл 
	HANDLE	hDevInfoListFile=CreateFile(TEXT("C:\\MicrosoftVisualStudioProjects\\Project4_Dinamic\\Project4_Dinamic\\Log\\DevInfoList.txt"),
										GENERIC_WRITE,
										0,
										NULL,
										CREATE_NEW,
										FILE_ATTRIBUTE_NORMAL,
										NULL);
	
	if (hDevInfoListFile==INVALID_HANDLE_VALUE)
	{
		cout << "Can't create DevInfoList file. Error # " << GetLastError() << endl;
	}
	else
	{
		cout << "DevInfoList file created" << endl;
	}
	*/

	//Определяем количство, подключенных устройств 
	ftStatus = FT_CreateDeviceInfoList(&dwNumDevs); 
	if (ftStatus == FT_OK) 
	{ 
		if ( dwNumDevs > 0)
		{
			//Если подключено хотя бы одно устройство
			//Печатаем число, подключенных устройств
			printf("Number of devices is %d\n",dwNumDevs); 
			 //Выделяем нужный объем памяти для хранения нужного объема информации 
			devInfo = (FT_DEVICE_LIST_INFO_NODE*)malloc(sizeof(FT_DEVICE_LIST_INFO_NODE)*dwNumDevs);  
			 //Создаем структуры, описывающие каждое из устройств
			ftStatus = FT_GetDeviceInfoList(devInfo,&dwNumDevs);  
			if (ftStatus == FT_OK) 
			{ 
				//Если успешно, то...
				for (int i = 0; i < dwNumDevs; i++) 
				{   
					//...для каждого устройства извлекаем поля структуры, описывающей его
					ftStatus_GetInfoDetail = FT_GetDeviceInfoDetail(i,
																&Flags, 
																&Type,
																&ID, 
																&LocId, 
																SerialNumber, 
																Description, 
																&ftHandleTemp); 

					if (ftStatus == FT_OK)		 
					{ 
						//Если успешно выполнена процедура, то печатаем серийный номер (может пригодиться потом, когда к одному ПК будет поделючено несколько 
						//D2xx устройств. Аналогично можно поступить с остальными полями.
						printf("  SerialNumber=%s\n",SerialNumber);
						/*
						//Не обязательный блок, особенно в том случае, когда программу запускаем впервые и не знаем 
						//серийный номер устройства. Но может пригодиться в дальнейшем. В массиве UserDevice0_SN[16]
						//храниться номер нужного мне устройства (определяется после первого запуска программы)
						if (SerialNumber[0] == UserDevice0_SN[0] && SerialNumber[1] == UserDevice0_SN[1] &&	
							SerialNumber[2] == UserDevice0_SN[2] && SerialNumber[3] == UserDevice0_SN[3] &&
							SerialNumber[4] == UserDevice0_SN[4] && SerialNumber[5] == UserDevice0_SN[5] &&	
							SerialNumber[6] == UserDevice0_SN[6] && SerialNumber[7] == UserDevice0_SN[7] &&
							SerialNumber[8] == UserDevice0_SN[8])
						{
						*/
						//Открываем устройство
							ftStatus=FT_OpenEx(UserDevice0_SN,
												FT_OPEN_BY_SERIAL_NUMBER,
												&UserDevice0_Handle);
							if (ftStatus == FT_OK)
							{
								//Сообщаем об успешном открытии устройства
								cout << "Device which serial number " << UserDevice0_SN << " is opened. Device handle is " << UserDevice0_Handle << endl;
							}
							else
							{
								//Сообщаем о неудаче и выходим из программы, т.к. далее делать нечего 
								//(не можем получить дескриптор устройства, а без него никуда:(
								cout << "Can't open the device with serial number " << UserDevice0_SN << "Error # " << FT_W32_GetLastError(UserDevice0_Handle) << endl;
								return 0;
							}
							/*
						}
						*/

					} 
					else
					{
						//Не удалось получить значение полей структуры, описывающей устройство
						cout << "FT_GetDeviceInfoDetail failed. Error # "<< GetLastError() << endl;
						return 0;
					}
				 } 
			}  
			else 
			{

				cout << "Can't get the device info list. Error # "<< GetLastError() << endl;
				return 0;
			}
		}
		else
		{
			//К ПК не подключено ни одного устройства
			cout << "No device is connected to the PC" << endl;
			return 0;
		}
	}
	else 
	{ 
		cout << "FT_CreateDeviceInfoList failed" << endl;
		return 0;
	} 
	//Настраиваем скорость передачи 9600 бод. ВНИМАНИЕ!!! скорость должна соответствовать той,
	//на которую был настроен USART модуль в микроконтроллере, иначе возможы ошибки при передаче
	if (FT_SetBaudRate(UserDevice0_Handle,9600) == FT_OK)
	{
		cout << "Baud rate is set to 9600" << endl;
	}
	else
	{
		cout << "Can't set baud rate. Error # " << FT_W32_GetLastError(UserDevice0_Handle) << endl;
		return 0;
	}
	//Настройка передачи: слово - 8 бит, стоп-битов - 1, проверка очетности отстутствует
	if (FT_SetDataCharacteristics (UserDevice0_Handle, 
									FT_BITS_8,
									FT_STOP_BITS_1,
									FT_PARITY_NONE) == FT_OK)
	{
		cout << "Device has been configured" << endl;
	}
	else
	{
		cout << "Can't configure device. Error # " << FT_W32_GetLastError(UserDevice0_Handle) << endl;
		return 0;
	}
	//Записываем в буфер передатчика ASCII-код символа с клавиатуры. Я выбрал 51, что соответствует цифре клавише 
	//с символом "3". Можно записать любой другой.
	UserDevice0_TransmitterBuffer=51;
	//Записывем байт, содержащийся в буфере передатчика в устройство. 
	UserDevice0_WriteStatus = FT_Write(UserDevice0_Handle,
			&UserDevice0_TransmitterBuffer,
			sizeof(UserDevice0_TransmitterBuffer),  
			&UserDevice0_BytesWritten); 
	//Успешно ли отправлен байт?
	if (UserDevice0_WriteStatus == FT_OK)
	{
		cout << "Data was successfully written" << endl;
	}
	else
	{
		cout << "Can't write the data into the device. Error # " << FT_W32_GetLastError(UserDevice0_Handle) << endl;
		return 0;
	}
	//Читаем байт в буфер приемника. Устройство должно вернуть символ "4" (см. комментарии к программе для МК)
	UserDevice0_ReadStatus = FT_Read(UserDevice0_Handle,
			&UserDevice0_ReceiverBuffer,
			sizeof(UserDevice0_ReceiverBuffer),  
			&UserDevice0_BytesRead); 
	//Успешно ли прочитан байт?
	if (UserDevice0_ReadStatus == FT_OK)
	{
		cout << "Data received " << UserDevice0_ReceiverBuffer << endl;
		cout << "Number of the bytes read " << UserDevice0_BytesRead << endl;
	}
	else
	{
		cout << "Can't read the data. Error # " << FT_W32_GetLastError(UserDevice0_Handle) << endl;
	}

	//Чтобы в консоли был виден результат работы программы, программа ждет ввода символа
	cout << "Press Enter to continue" << endl;
	cin.get();
	//Закрываем устройство
	FT_Close(UserDevice0_Handle);
	return 0;
}


Тест

Начальные условия: все устройства отключены от ПК и обесточены. Соединяем выводы RX переходника c выводом PA9 микроконтроллера, а вывод TX переходника с выводом PA10 МК, подключаем отладочную плату к порту USB, подключаем переходник к порту USB. Такая последовательность манипуляция мне показалась наиболее безопасной, к тому же у меня ничего не загорелось:)

Открываем окно Visual Studio, нажимаем F5 или зеленую стрелочку. Идет компиляция, компоновка… Если все хорошо, то появляется окно консоли такого содержания:


Строка Data received отображает символ, который нам отправил микроконтроллер. Как вы помните, мы посылали «3» (ASCII-код 51), МК увеличил его на 1, получили 52, что соответствует «4». Кажется все работает правильно:)

Изменим содержимое переменной буфера-передатчика UserDevice0_TransmitterBuffer с 51 на 72 (число введено «с головы», главное, чтобы было в диапазоне от 0 до 255). Жмем F5. Теперь окно консоли имеет вид:

Действительно, 72 — это код символа «I». Считаем, что программа работает правильно:)

Вместо эпилога

Т.к. я только недавно ознакомился с функциями вышеупомянутой библиотеки, то у меня самого много вопросов. Например фрагмент кода
//Записывем байт, содержащийся в буфере передатчика в устройство. 
	UserDevice0_WriteStatus = FT_Write(UserDevice0_Handle,
			&UserDevice0_TransmitterBuffer,
			sizeof(UserDevice0_TransmitterBuffer),  
			&UserDevice0_BytesWritten); 

работает правильно, но в случае отправки 1 байта. Что будет, если отправлять несколько десятков или сотен байт? Возможно придется написать дополнительную функцию, которая будет отслеживать состояние устройства. Ведь он может не успеть принять всю информацию, а ПК уже требует переслать обратно. В общем, так или иначе, я надеюсь эта статья станет быстрым стартом для людей, желающих разобраться с библиотекой ftd2xx. Успехов!

P.S. Если достану фотоаппарат, то будет видео. А пока что только текст и два скриншота.
  • +3
  • 18 марта 2012, 01:14
  • NBS

Комментарии (17)

RSS свернуть / развернуть
Возможно что я что-то не так понял, но у вас тут просто com-порт используется, без дополнительных пинов и бит-бэнга?
Тогда почему не возпользоваться стандартными виндовыми функциями ReadFile/WriteFile (read, write в линуксе)?
0
  • avatar
  • atd
  • 18 марта 2012, 02:05
Во-первых, это простой пример того как можно использовать функции библиотеки ftd. Во-вторых, я не нашел (скорее всего потому что плохо искал) функции виндовс, которые бы настраивали скорость передачи, количество байт в слове и т.д.
0
0
Спасибо за информацию!
0
И тема к STM32 отношения не имеет. перенесена в другой блог.
0
Привет DIHALT.
У меня есть к тебе пару вопросов конечно если ты захочеш помочи.
Кароче:
Я пытаюси испольшовать FT2232D в режиме 245FIFO Sincronus Mode…
Мне нужно в первый чипа А записать данные — это у меня получеается на все 100. А на втором чипе В я должен читать данные (тоесть те данные которые приходят в чип с внешнего источника). Но к сожаленью из второго чипа ни как не могу читать. Всегда и в первом и во вором чипе сигнал ACBUS[0] и ВCBUS[0] стоит единица когда я делаю FT_SetBitMode(Handle, 0xFF, 4) для первого чипа (А) и T_SetBitMode(Handle, 0x00, 4) для чипа (В); Астальные ACBUS[1], ACBUS[2], ACBUS[3] и ВCBUS[1], ВCBUS[2], ВCBUS[3] всегда ноль.
Я поменял микросхемы пару раз и ничего не меняется.
Если я пишу во втором чипе а потом читаю с него, то получяю то что записал. А сигналы с пина никак нет.
Цепь смрнтированно как написанно в даташите, EEPROM поставил и програмировал без проблем.

Просьба если знаеш помоги разобраться. Либо запиши какую то статью по этому чипу по програмироанью либо даи по шагам что я должен делать чтобы прчитать данные с пинами.
Заранее спосиба.
0
А если каналы поменять? Дело в том, что у FTDI каналы немного не одинаковые. Какой то из них более фукнциональный, а какой то менее. Вроде бы В не умеет чето.
0
Привет
квнал менял но потушить сигналы от АС..0 и ВС..0 таки не удалось.
0
Я по образованию химик
Здравствуй, коллега.
Если не секрет — где работаешь? Может быть объеденим усилия? Я как раз хочу собрать вакуометр с обратной связью на насос, чтобы получилось как в модных роторах, улавливание вскипания растворителя и предотвращение плевка на охладитель.
0
Почитал я статью и ужаснулся. Пусть автор меня простит конечно. Но так и есть. Я работал сам с этой библиотекой причём оч серойзно. Где описание передачи данных? Где описание про скрытый таймер? Где хотябы ссылки на эти документы:
AN232B-03 Optimising D2XX Data Throughput и
AN232B-04 Data Throughput, Latency & Handshaking? (Эти документы обязательно стоит прочесть!!!!!)
Скажу что делал я. Передавал данные со скоростью полтора мегабита. Организовал в программе отдельный поток в котором непрерывно читал байты, и собирал их в пакеты.
По поводу приёма данных из мк в комп:
драйвер FTD2XX отводит буфер для приёма. Также устанавливается размер этого буфера. Если в буфере есть свободное мето то посылается запрос чтение из микросхемы. А потом функция FT_READ читает данные из буфера драйвера. По поводу Latency timer то там ситуация вкратце такая:
Если в буфер микросхемы набралось 62 байта+2 байта статуса, то эти данные отправляются в комп. Если набралось меньше чем 62 байта, то данные отправляются после истечении времени Latency time. И это время тоже можно настраивать. Также не стоит забывать, что можно в функции FT_READ указывать что хотим прочитать не 256 байт а больше, например 1000 байт. Также ещё хочу рассказать по поводу чтения. Елси читать порциями по 256 байт например, то ВЫПОЛНЕНИЕ функции чтения, будет выполняться быстро, но данных прочтёт мало. Если читать например по 1000 байт, то функция чтения будет выполяться долго, данные будут появляться реже, но зато большими порциями!
В общем мой всем совет: читайте выше преведенные документы, думайте и пробуйте.
+2
Narod skajite pojaluista mojet ktoto imeet ili smojet dati primer kak manipulirovati pinami CBUS[0]...CBUS[3] na microsheme FT2232D. A to ia pisati v nee magu a vot citati uje 3-a nedelea muceaiusi i nikak ne poluceaetsea. Ni kak Out(CBUS0) ne magu otkliuciti.
EEPROM imeetsea programirovan.
Tupo po sagam razbiralse vrode v ciom-to no ne hvataiet mozghi dovesti delo do konta.
Prosiba pomoghite esli esti znanie i vozmojnosti.
0
Cм. это (гл. 3.6). Команды для записи (и установки режима — вход или выход) 0x80 или 0x82, для чтения 0x81 или 0x83. Т.е. сначала (после того, как открыли порт и перешли в режим MPSSE) надо выдать 0x82 0x00 0xf0, чтобы переключить CBUS[0...3] на вход (если не ошибаюсь, DBUS — это младший байт, CBUS — старший), а для чтения использовать команду 0x83 (возвращает 1 байт). DBUS[0...3] могут этими командами не управляться, потому что используются для протоколов JTAG, SPI и т. п. (Caveat: написано по памяти, читайте внимательнее документацию.)
0
Ia ne ispolizuiu MPSSE regim mne nujen 245 FIFO Sincronus… Potomu cto Chip A ia ispolizuiu dlia zapisi toiesti v nego ia pisu s compiuterea dannie. A iz Chip B pitaiusi citati cto u nego na vhod. Ia tam daiu poka v rucnuiu opredelionoiie combinatiu bitov i pitaiusi ih citati i ne poluceaietsea.
Dai mne pojaluista esli esti kusok koda gde ato napisanno ili pokazanno kak delaietsea. Ili esti esti vremea ili jelanii ia magu dati moi koding ctobi ti posmotrel ili esli smojes mojno i po skype govoriti(uvidis zaodno cto ia smasteril).
0
Автор, «using namespace std;» — это не C, а C++ =))))
0
Автор, «using namespace std;» — это не C, а C++ =))))
DA ATA TAK
0
Автор, поделитесь хедером. Оч надо, ато я что-то толи не то делаю, толи шпиёны за мной следят и забирают норм хэдеры из архивов.
0
а почему не libftdi?
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.