FT232H D2xx GPIO example

본 글은 ft232h GPIO 컨트롤 예제 코드를 싣고 있다.


FT232H는 USB2.0 Hi-Speed(480Mb/s) 단일 채널을 가지고 있으며, 컨트롤러의 MPSSE(Multi-Protocol Synchronous Serial Engine)설정에 따라 USB to JTAG, I2C, SPI(master mode) 등으로 사용할 수 있는 IC다. Datasheet에 나와 있는 칩의 블록 다이어그램은 아래 그림과 같다. 본 글에서는 ADBUS4~7, ACBUS0~9에 연결된 핀을 GPIO로 사용하는 예제를 구현할 예정이고, 다음 글에는 SPI 사용 예제를 구현할 예정이다.
 

GPIOL0~3, GPIOH0~7번은 MPSSE모드에서 사용 가능하며, GPIOH8~9는 ACBUS BitBang 모드에서 출력 컨트롤 가능하다. GPIOL0~3, GPIOH0~7을 MPSSE모드에서 사용하면, 다른 프로토콜과 같이 사용할 수 있다. 예를 들어 ADBUS0~3을 SPI 핀으로 사용시 나머지는 GPIO로 사용하여 칩 리셋이나 인터럽트용으로 사용할 수 있다. GPIOH8~9번은 컨트롤러 모드를 BitBang모드로 전환하여 사용해야 하기 때문에 다른 SPI등과 같이 사용하기에 약간의 불편함이 있다. (모드 전환 시 약간의 시간 딜레이가 있다.)
 



드라이버 및 D2XX lib

먼저, FT232H IC를 사용하기 위해서 드라이버를 다운로드해야 한다. 드라이버는 https://www.ftdichip.com/Drivers/D2XX.htm에서 다운 받을 수 있으며, 원하는 플랫폼의 드라이버를 다운로드 하면 된다. 본 글의 예제에서는 windows용을 다운받아 사용했다. 다운로드한 압축 파일에는 해당 OS의 설치 드라이버와 ftd2xx 헤더와 라이브러리, dll 파일등이 들어있다. 드라이버는 OS에 설치하고, 헤더와 라이브러리는 프로그래밍에 사용할 것이다.
 

D2XX 라이브러리를 사용하기 위해 아래와 같이 visual studio project 설정에서 라이브러리 파일이 있는 위치와 라이브러리를 추가한다. 
 
 


 
FT232H 구동은 위 그림과 같은 순서로 진행되며, 구현된 FT232H 예제 코드의 class는 다음과 같다. 

#include "ftd2xx.h"

#define SET_DATABITS_LOW_BYTE                 0x80
#define READ_DATABITS_LOW_BYTE                0x81
#define SET_DATABITS_HIGH_BYTE                0x82
#define READ_DATABITS_HIGH_BYTE               0x83

#define BIT(bit) (1 << (bit))
#define SETBIT(address,bit) ((address) |= BIT(bit))
#define CLEARBIT(address,bit) ((address) &= ~BIT(bit))

#define MAX_NUM_BYTES_USB_WRITE 512
#define USB_TRANSFER_SIZE 65535 // 64k


/*
0x0 = Reset
0x1 = Asynchronous Bit Bang
0x2 = MPSSE (FT2232, FT2232H, FT4232H and FT232H devices only)
0x4 = Synchronous Bit Bang (FT232R, FT245R, FT2232, FT2232H, FT4232H and FT232H devices only)
0x8 = MCU Host Bus Emulation Mode (FT2232, FT2232H, FT4232H and FT232H devices only)
0x10 = Fast Opto-Isolated Serial Mode (FT2232, 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)
*/
#define BITMODE_RESET 0x00
#define BITMODE_ASYNC_BITBANG 0x01
#define BITMODE_MPSSE 0x02
#define BITMODE_SYNC_BITBANG 0x04
#define BITMODE_MCU_HOST_BUS_EMUL 0x08
#define BITMODE_SERIAL 0x10
#define BITMODE_CBUS_BITBANG 0x20
#define BITMODE_SYNC_FIFO 0x40

#define LATENCY_TIMER_mSEC 2
#define READ_TIMEOUT_mSEC 5000
#define WRITE_TIMEOUT_mSEC 1000


class Cft232h
{
public:
enum
{
GPIOL0 = 4,
GPIOL1,
GPIOL2,
GPIOL3,

GPIOH0,
GPIOH1,
GPIOH2,
GPIOH3,
GPIOH4,
GPIOH5,
GPIOH6,
GPIOH7,
GPIOH8,
GPIOH9,
};

typedef struct _gpio_config_t
{
int gpio;
int dir;
int initial_state;
}gpio_config_t;

Cft232h();
~Cft232h();

int probe_available_device(long locids[16]);
FT_STATUS open(char *serial, long locid);
void close();
FT_STATUS configure(gpio_config_t *gpio_cfg,int size);

FT_STATUS SetGPIO(int gpio, int nVal);
FT_STATUS GetGPIO(int gpio, int &nVal);

private:
void ResetTransforQ();
DWORD Write(BYTE *pTx, DWORD dwTxLen);
DWORD WaitForData(DWORD dwtoRxLen, int WaitCount);
DWORD Read(BYTE *pRx, DWORD dwtoRxLen);

FT_HANDLE m_ftdiHandle;
WORD m_ACADBusVal;
WORD m_ACADBusDir;
BYTE m_CBUSBitBangMask;
};

연결된 ft232h usb 디바이스 리스트 가져오기 및 디바이스 열기

아래코드는 PC에 연결된 ft232h(D2xx 드라이버를 지원하는IC)의 리스트와 locid를 받아오는 함수다.

int Cft232h::probe_available_device(long locids[16])
{
int i = 0;
int availdevs = 0;
DWORD numDevs = 0;
FT_STATUS ftStatus = 0;

ftStatus = FT_CreateDeviceInfoList(&numDevs);
if (ftStatus != FT_OK || numDevs == 0)
{
return -1;
}

for (i = 0; i < numDevs && i<16; i++)
{
DWORD Flags;
DWORD ID;
DWORD Type;
DWORD LocId;
char sn[16];

ftStatus = FT_GetDeviceInfoDetail(i, &Flags, &Type, &ID, &LocId, sn, NULL, NULL);
if (ftStatus == FT_OK)
{
locids[availdevs] = LocId;
_trace(TEXT("usb locid %d\n"), LocId);
availdevs++;
}
}

return availdevs;
}

현재는 usb 디바이스 하나가 연결되어 있다. 
 

위 probe_available_device 함수에서 검색한 locid를 사용해 usb device를 open한다. 물론 usb device의 serial number를 사용하여 open할수도 있다. Locid를 사용해 usb device를 여는 이유는 여러 디바이스가 연결된 경우 locid나 serial number를 사용하면 원하는 디바이스를 열수 있기 때문이다.

FT_STATUS Cft232h::open(char *serial, long locid)
{
int i = 0;
FT_STATUS ftStatus = FT_INVALID_HANDLE;

if (m_ftdiHandle)
{
close();
}

if (serial)
{
ftStatus = FT_OpenEx(serial, FT_OPEN_BY_SERIAL_NUMBER, &m_ftdiHandle);
}
else if (locid)
{
ftStatus = FT_OpenEx((PVOID)locid, FT_OPEN_BY_LOCATION, &m_ftdiHandle);
}

if (ftStatus != FT_OK)
{

_trace(TEXT("ftx232h_device_open fail !!\n"));
close();
return ftStatus;
}

return FT_OK;
}

void Cft232h::close()
{
if (m_ftdiHandle)
{
FT_Close(m_ftdiHandle);
m_ftdiHandle = NULL;
}
}

디바이스 설정

gpio 사용을 위한 설정 코드다. 간단하게 디바이스 초기화 후 컨트롤러 BitMode는 MPSSE 사용으로 설정하고, ADBUS0~7, ACBUS0~7의 입/출력 설정과 초기 상태 값은 입력 받은 파라메터 대로 설정한다.

FT_STATUS Cft232h::configure(gpio_config_t *gpio_cfg, int size)
{
DWORD dwLen = 0;
BYTE pTX[8] = { 0, };
FT_STATUS ftStatus = FT_OK;

if (!m_ftdiHandle) return FT_INVALID_HANDLE;
if (!gpio_cfg || size <= 0) return FT_INVALID_ARGS;

ftStatus = FT_ResetDevice(m_ftdiHandle);
ResetTransforQ();

// 초기화
ftStatus |= FT_SetUSBParameters(m_ftdiHandle, USB_TRANSFER_SIZE, USB_TRANSFER_SIZE); //Set USB request transfer size
ftStatus |= FT_SetTimeouts(m_ftdiHandle, READ_TIMEOUT_mSEC, WRITE_TIMEOUT_mSEC); //Sets the read and write timeouts for the FT2232H
ftStatus |= FT_SetLatencyTimer(m_ftdiHandle, LATENCY_TIMER_mSEC); //Set the latency timer

ftStatus |= FT_SetBitMode(m_ftdiHandle, 0x00, BITMODE_RESET); //Reset controller
ftStatus |= FT_SetBitMode(m_ftdiHandle, 0x00, BITMODE_MPSSE); //enable mpsse

if (ftStatus != FT_OK)
{
_trace(TEXT("fail on initialize FT2232H device ! \n"));
return ftStatus;
}

// Wait for more then 50ms for all the USB stuff to complete and work
Sleep(100);

// Pin name Signal Direction Config Initial State Config
// ADBUS0 TCK/SK output 1 low 0
// ADBUS1 TDI/DO output 1 high 1
// ADBUS2 TDO/DI input 0 1
// ADBUS3 TMS/CS output 1 high 1

m_ACADBusVal = 0x000e;
m_ACADBusDir = 0xfffb;
// pin name signal state
// ACBUS8 GPIO8    low
// ACBUS9 GPIO9 low
m_CBUSBitBangMask = 0xff;

// config gpio 
for (int i = 0; i < size; i++)
{
if (gpio_cfg[i].gpio < GPIOL0 || gpio_cfg[i].gpio >= GPIOH8) continue;
int nbit = gpio_cfg[i].gpio;

if (gpio_cfg[i].dir == 1) SETBIT(m_ACADBusDir, nbit);
else CLEARBIT(m_ACADBusDir, nbit);

if (gpio_cfg[i].initial_state == 1) SETBIT(m_ACADBusVal, nbit);
else CLEARBIT(m_ACADBusVal, nbit);
}

m_CBUSBitBangMask = 0xff;

pTX[dwLen++] = SET_DATABITS_LOW_BYTE;
pTX[dwLen++] = (BYTE)(m_ACADBusVal & 0xff);
pTX[dwLen++] = (BYTE)(m_ACADBusDir & 0xff);
pTX[dwLen++] = SET_DATABITS_HIGH_BYTE;
pTX[dwLen++] = (BYTE)((m_ACADBusVal>>8) & 0xff);
pTX[dwLen++] = (BYTE)((m_ACADBusDir>>8) & 0xff);

if (Write(pTX, dwLen) != dwLen)
{
return FT_IO_ERROR;
}

return ftStatus;
}

ADBUS/ACBUS 설정은 데이터는 아래와 같은 설정할 경우 DATABIT의 위치는 아래 그림과 같다. 

Cft232h::gpio_config_t gpiocfg[] = {
{ Cft232h::GPIOL0, 1/*out*/, 0 },
{ Cft232h::GPIOL1, 1/*out*/, 0 },
{ Cft232h::GPIOL2, 1/*out*/, 0 },
{ Cft232h::GPIOL3, 1/*out*/, 0 },
{ Cft232h::GPIOH0, 0/*in*/, 0 },
{ Cft232h::GPIOH1, 0/*in*/, 0 },
{ Cft232h::GPIOH2, 0/*in*/, 0 },
{ Cft232h::GPIOH3, 0/*in*/, 0 },
{ Cft232h::GPIOH4, 1/*out*/, 0 },
{ Cft232h::GPIOH5, 1/*out*/, 0 },
{ Cft232h::GPIOH6, 1/*out*/, 0 },
{ Cft232h::GPIOH7, 1/*out*/, 0 },
{ Cft232h::GPIOH8, 1/*out*/, 0 }, // out only
{ Cft232h::GPIOH9, 1/*out*/, 0 }, // out only
};

ft232h.configure(gpiocfg,14);
 
ADBUS0~4은 SPI용으로 사용된다.

GPIO 컨트롤 코드

gpio 출력 레벨 값을 설정 코드다. GPIOL0~GPIOH7은 MPSSE 모드에서 디바이스에 데이터를 Write하여 ACBUS/ADBUS 값을 설정하는 방식을 사용하였고, GPIOH8,GPIOH9는 BitBang Mode를사용하여 컨트롤 한다.

FT_STATUS Cft232h::SetGPIO(int gpio, int nVal)
{
BYTE pTX[6] = { 0, };
int nbit = 0;
FT_STATUS ftStatus = FT_OK;

// 핀 번호 및 입출력 설정 체크.
if (gpio >= GPIOL0 && gpio <= GPIOH7)
{
nbit = gpio;
if (!(m_ACADBusDir & BIT(nbit)))
return FT_INVALID_ARGS;

if (nVal == 1)
{
SETBIT(m_ACADBusVal, nbit);
}
else
{
CLEARBIT(m_ACADBusVal, nbit);
}

pTX[0] = SET_DATABITS_LOW_BYTE;
pTX[1] = (BYTE)(m_ACADBusVal & 0xff);
pTX[2] = (BYTE)(m_ACADBusDir & 0xff);
pTX[3] = SET_DATABITS_HIGH_BYTE;
pTX[4] = (BYTE)((m_ACADBusVal >> 8) & 0xff);
pTX[5] = (BYTE)((m_ACADBusDir >> 8) & 0xff);

if (Write(pTX, 6) != 6)
{
return FT_IO_ERROR;
}

return FT_OK;

}
else if (gpio >= GPIOH8 && gpio <= GPIOH9)
{
// I/O mode ACBUS5, ACBUS6,ACBUS8, ACBUS9 : ACBUS BitBang
if (GPIOH8 == gpio) nbit = 2;
else nbit = 3;

ftStatus |= FT_SetBitMode(m_ftdiHandle, 0x00, BITMODE_RESET); //Reset controller

if (m_ACADBusVal & 0x2000)
CLEARBIT(m_CBUSBitBangMask, 0);
else
SETBIT(m_CBUSBitBangMask, 0);

if (m_ACADBusVal & 0x4000)
CLEARBIT(m_CBUSBitBangMask, 1);
else
SETBIT(m_CBUSBitBangMask, 1);

if (nVal)
{
CLEARBIT(m_CBUSBitBangMask, nbit);
}
else
{
SETBIT(m_CBUSBitBangMask, nbit);
}

ftStatus |= FT_SetBitMode(m_ftdiHandle, m_CBUSBitBangMask, BITMODE_CBUS_BITBANG);

// resotre mpsse mode
ftStatus |= FT_SetBitMode(m_ftdiHandle, 0x00, BITMODE_RESET);
ftStatus |= FT_SetBitMode(m_ftdiHandle, 0x00, BITMODE_MPSSE);
Sleep(1);

return ftStatus;
}
return FT_INVALID_ARGS;
}

gpio 입력 레벨 값을 읽어오는 함수다. GPIOL0~GPIOH7은 MPSSE 모드에서 디바이스에서 ACBUS/ADBUS 상태 값을 읽어오는 방식을 사용하였다.
FT_STATUS Cft232h::GetGPIO(int gpio, int &nVal)
{
BYTE pTX[3] = { 0, };
BYTE pRX[3] = { 0, };
int nbit = gpio;

// 핀 번호 및 입출력 설정 체크.
if (gpio < GPIOL0 || gpio > GPIOH7 || (m_ACADBusDir & BIT(gpio)))
{
return FT_INVALID_ARGS;
}

if (gpio < GPIOH0)
{
pTX[0] = READ_DATABITS_LOW_BYTE;
nbit = gpio;
}
else
{
pTX[0] = READ_DATABITS_HIGH_BYTE;
nbit = gpio - GPIOH0;
}

if (Write(pTX, 1) != 1)
{
return FT_IO_ERROR;
}

if (WaitForData(1, 1000) < 1)
{
return FT_IO_ERROR;
}

if (Read(pRX, 1) < 1)
{
return FT_IO_ERROR;
}

if (pRX[0] & BIT(nbit))
{
nVal = 1;
}
else
{
nVal = 0;
}

return FT_OK;
}

데이터 송수신 함수

데이터 Queue 리셋 함수로 Queue에 남아있는 데이터를 읽어 Queue를 비운다.

void Cft232h::ResetTransforQ()
{
DWORD dwNumInputBuffer = 0;
FT_STATUS ftStatus = 0;

if (!m_ftdiHandle) return;
// Get the number of bytes in the FT2232H receive buffer
ftStatus |= FT_GetQueueStatus(m_ftdiHandle, &dwNumInputBuffer); 
if ((ftStatus == FT_OK) && (dwNumInputBuffer > 0))
{
BYTE Rx = 0;
for (int i = 0; i < (int)dwNumInputBuffer; i++)
{//Read out the data from FT2232H receive buffer
Read(&Rx, 1);
}
}

}

FT232H 디바이스에 데이터를 쓰는 함수다.

DWORD Cft232h::Write(BYTE *pTx, DWORD dwTxLen)
{
FT_STATUS ftStatus = 0;
DWORD dwSentLen = 0;

if (!m_ftdiHandle) return FT_INVALID_HANDLE;

if (!m_ftdiHandle || !pTx || dwTxLen <= 0)
{
return 0;
}

while (dwSentLen < dwTxLen)
{
DWORD dwToSendLen = dwTxLen - dwSentLen;
DWORD dwNumBytesSent = 0;

if (dwToSendLen > MAX_NUM_BYTES_USB_WRITE)
{
dwToSendLen = MAX_NUM_BYTES_USB_WRITE;
}

ftStatus = FT_Write(m_ftdiHandle, pTx + dwSentLen, dwToSendLen, &dwNumBytesSent);
if (ftStatus != FT_OK)
{
_trace(TEXT("FT_Write fail \n"));
return dwSentLen;
}

dwSentLen += dwNumBytesSent;
}

return dwSentLen;
}

데이터 Queue의 상태를 검사하여 읽을 수 있는 데이터가 있는지 알아보는 함수다.

DWORD Cft232h::WaitForData(DWORD dwtoRxLen, int WaitCount)
{
FT_STATUS ftStatus = FT_OK;
int timeoutcnt = 0;
DWORD dwNumBytesInputBuffer = 0;

if (!m_ftdiHandle || dwtoRxLen == 0)
{
return 0;
}

do
{
// Get the number of bytes in the FT2232H receive buffer
ftStatus = FT_GetQueueStatus(m_ftdiHandle, &dwNumBytesInputBuffer);
if (ftStatus != FT_OK)
{
_trace(TEXT("FT_GetQueueStatus fail\n"));
return dwNumBytesInputBuffer;
}

timeoutcnt++;
if ((timeoutcnt % 100) == 0)
{
Sleep(0);
}

} while (dwNumBytesInputBuffer < dwtoRxLen && (timeoutcnt < WaitCount));

if (timeoutcnt >= WaitCount)
{
_trace(TEXT("ftx232h_WaitForReceived  timeout\n"));
}

return dwNumBytesInputBuffer;
}

데이터 읽어오는 함수다.

DWORD Cft232h::Read(BYTE *pRx, DWORD dwtoRxLen)
{
FT_STATUS ftStatus = FT_OK;
DWORD dwNumBytesRead = 0;

if (!m_ftdiHandle || !pRx || dwtoRxLen<=0)
{
return FT_INVALID_HANDLE;
}

// Zero terminate input buffer
pRx[0] = 0;
ftStatus = FT_Read(m_ftdiHandle, pRx, dwtoRxLen, &dwNumBytesRead);

if (ftStatus != FT_OK)
{
_trace(TEXT("FT_Read fail \n"));
}

return dwNumBytesRead;
}


Cft232h 사용 예제 소스다.

void test_ft232h_gpio()
{
Cft232h ft232h;
int nVal = 0;
long locids[16] = { 0, };
Cft232h::gpio_config_t gpiocfg[] = {
{ Cft232h::GPIOL0, 1/*out*/, 0 },
{ Cft232h::GPIOL1, 1/*out*/, 0 },
{ Cft232h::GPIOL2, 1/*out*/, 0 },
{ Cft232h::GPIOL3, 1/*out*/, 0 },
{ Cft232h::GPIOH0, 0/*in*/, 0 },
{ Cft232h::GPIOH1, 0/*in*/, 0 },
{ Cft232h::GPIOH2, 0/*in*/, 0 },
{ Cft232h::GPIOH3, 0/*in*/, 0 },
{ Cft232h::GPIOH4, 1/*out*/, 0 },
{ Cft232h::GPIOH5, 1/*out*/, 0 },
{ Cft232h::GPIOH6, 1/*out*/, 0 },
{ Cft232h::GPIOH7, 1/*out*/, 0 },
{ Cft232h::GPIOH8, 1/*out*/, 0 }, // out only
{ Cft232h::GPIOH9, 1/*out*/, 0 }, // out only
};

if (ft232h.probe_available_device(locids) <= 0)
{
return;
}

if (ft232h.open(NULL, locids[0]) != FT_OK)
{
return;
}

ft232h.configure(gpiocfg,14);

ft232h.SetGPIO(Cft232h::GPIOL0, 1);
ft232h.GetGPIO(Cft232h::GPIOH1, nVal);
ft232h.SetGPIO(Cft232h::GPIOH8, 1);
ft232h.SetGPIO(Cft232h::GPIOH9, 1);
ft232h.SetGPIO(Cft232h::GPIOH8, 0);
ft232h.SetGPIO(Cft232h::GPIOH9, 0);

}

테스트 진행한 보드에는 GPIOH8, GPIOH9에 LED가 연결 되어있고, 아래 사진처럼 LED가 켜지는 것을 볼 수 있다.
 

댓글

이 블로그의 인기 게시물

간단한 cfar 알고리즘에 대해

바로 프로젝트 적용 가능한 FIR Filter (low/high/band pass filter )를 c나 python으로 만들기

쉽게 설명한 파티클 필터(particle filter) 동작 원리와 예제

windows에서 간단하게 크롬캐스트(Chromecast)를 통해 윈도우 화면 미러링 방법

ARM NEON asm memcpy 코드