FT232H D2XX SPI Example Source

본 글은 visual studio c/c++로 구현한 ft232h spi 드라이버 예제를 싣고 있다.
 

이전 글 FT232H D2xx GPIO example에서 FT232H에서 d2xx드라이버 다운로드 및 GPIO를 사용하는 예제를 살펴봤고, 이 글에서는 이전에 구현된 소스에서 SPI를 설정 부분과 데이터 전송 부분을 추가하여 구현하고 테스트했다. 이전 구현된 소스의 설명은 https://ryanclaire.blogspot.com/2020/06/ft232h-d2xx-gpio-example.html 을 참조하면 된다. 
FT232H와 IC간의 SPI연결은 아래 그림과 같다. FT232는 Master Mode로 동작하고 테스트에 사용한 IC는 Slave Mode로 동작한다. SPI는 ADBUS0~3 핀을 사용하며, IC 리셋을 위해 별도의 GPIO하나도 사용한다.

 

구현된 CFT232H는 아래와 같다. 

#include "ftd2xx.h"

#define CLOCK_BYTES_OUT_NEG_EDGE_MSB_FIRST    0x11
#define CLOCK_BYTES_IN_POS_EDGE_MSB_FIRST     0x20
#define CLOCK_BYTES_IN_NEG_EDGE_MSB_FIRST     0x24
#define CLOCK_BYTES_IN_OUT_NEG_EDGE_MSB_FIRST 0x31
#define CLOCK_BITS_IN_OUT_NEG_EDGE_MSB_FIRST  0x33
#define CLOCK_BYTES_IN_OUT_POS_EDGE_MSB_FIRST 0x34
#define CLOCK_BITS_IN_OUT_POS_EDGE_MSB_FIRST  0x36
#define CLOCK_BYTES_IN_OUT_NEG_EDGE_LSB_FIRST 0x39
#define CLOCK_BITS_IN_OUT_NEG_EDGE_LSB_FIRST  0x3B
#define CLOCK_BYTES_IN_OUT_POS_EDGE_LSB_FIRST 0x3C
#define CLOCK_BITS_IN_OUT_POS_EDGE_LSB_FIRST  0x3E
#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 LOOP_BACK_ENABLE                      0x84
#define LOOP_BACK_DISABLE                     0x85
#define SET_CLOCK_DIVISOR                     0x86
#define SEND_IMMEDIATE                        0x87
#define CLOCK_DIVIDE_BY_5_DISABLE             0x8A

#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

#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
{
CS_PIN = 3,
GPIOL0 = 4,
GPIOL1,
GPIOL2,
GPIOL3,

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

typedef enum
{
SPI_30000000HZ = 0, // 30Mhz
SPI_15000000HZ = 1,// 15Mhz
SPI_10000000HZ = 2,// 10Mhz
SPI_7500000HZ = 3,// 7.5Mhz
SPI_6000000HZ = 4,// 6Mhz
}SPICLK_HZ;

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

CFT232H()
{
m_ftdiHandle = NULL;
m_TXRXBuffer = NULL;
m_dwReservedToRead = 0;
m_ACADBusVal = 0x000e;
m_ACADBusDir = 0xfffb;
m_CBUSBitBangMask = 0xff;
}

~CFT232H()
{
close();
}

int probe_available_device(long locids[16]);

FT_STATUS open(char *serial, long locid);
void close();
FT_STATUS configure(SPICLK_HZ spiclk, gpio_config_t *gpio_cfg, int size);

FT_STATUS SetGPIO(int gpio, int nVal);
FT_STATUS GetGPIO(int gpio, int &nVal);
FT_STATUS SPI_TXRX(BYTE *pTx, WORD wTxLen, BYTE *pRx, WORD wRxLen);


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

protected:
FT_HANDLE m_ftdiHandle;

PBYTE m_TXRXBuffer;
DWORD m_dwReservedToRead;
WORD m_ACADBusVal;
WORD m_ACADBusDir;
BYTE m_CBUSBitBangMask;
};


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;
}


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;
}

m_TXRXBuffer = (PBYTE)malloc(sizeof(BYTE)*(USB_TRANSFER_SIZE+16));
if (m_TXRXBuffer)
{
memset(m_TXRXBuffer, 0, sizeof(sizeof(BYTE)*(USB_TRANSFER_SIZE + 16)));
}
else
{
_trace(TEXT("buffer alloc fail !!\n"));
close();
return FT_IO_ERROR;
}

return FT_OK;
}

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

safe_free(m_TXRXBuffer);
}


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

if (!m_ftdiHandle || !m_TXRXBuffer) return;

// Get the number of bytes in the FT2232H receive buffer
ftStatus |= FT_GetQueueStatus(m_ftdiHandle, &dwNumInputBuffer);
if ((ftStatus == FT_OK) && (dwNumInputBuffer > 0))
{
//Read out the data from FT2232H receive buffer
Read(m_TXRXBuffer, dwNumInputBuffer);
}

}

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)
{
printf("FT_Write fail \n");
return dwSentLen;
}

dwSentLen += dwNumBytesSent;
}

return dwSentLen;
}

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;
}


디바이스 설정

gpio 사용을 위한 설정 코드에서 SPI 관련 설정을 추가했다. SPI 클럭은 6Mz, 7.5Mz, 10Mhz, 15Mhz, 30Mhz를 지원한다. 


FT_STATUS CFT232H::configure(SPICLK_HZ spiclk, gpio_config_t *gpio_cfg, int size)
{
USHORT ClockDivisor = 0;
DWORD dwLen = 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)
{
printf("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;

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

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

dwLen = 0;
// Set clock divisor to 0
m_TXRXBuffer[dwLen++] = SET_CLOCK_DIVISOR;
m_TXRXBuffer[dwLen++] = 0; m_TXRXBuffer[dwLen++] = 0;
// Turn off loop back
m_TXRXBuffer[dwLen++] = LOOP_BACK_DISABLE;
// Disables the clock divide
m_TXRXBuffer[dwLen++] = CLOCK_DIVIDE_BY_5_DISABLE;
// Flush all on IO chip
m_TXRXBuffer[dwLen++] = SEND_IMMEDIATE;
if (Write(m_TXRXBuffer, dwLen) != dwLen)
{
return FT_IO_ERROR;
}

Sleep(100);
ResetTransforQ();

// setclock...
//Value of clock divisor, SCL Frequency (Mhz) = 60 / ((1 + ClockDivisor) * 2) (MHz)
ClockDivisor = (USHORT)spiclk;
dwLen = 0;
m_TXRXBuffer[dwLen++] = SET_CLOCK_DIVISOR;
m_TXRXBuffer[dwLen++] = (BYTE)(ClockDivisor & 0xff);
m_TXRXBuffer[dwLen++] = (BYTE)((ClockDivisor >> 8) & 0xff);;
if (Write(m_TXRXBuffer, dwLen) != dwLen)
{
return FT_IO_ERROR;
}

Sleep(100);

return ftStatus;
}


FT_STATUS CFT232H::SetGPIO(int gpio, int nVal)
{
BYTE pTX[6] = { 0, };
int nbit = 0;
FT_STATUS ftStatus = FT_OK;
if (!m_ftdiHandle) return FT_INVALID_HANDLE;

// 핀 번호 및 입출력 설정 체크.
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;
}


FT_STATUS CFT232H::GetGPIO(int gpio, int &nVal)
{
BYTE pTX[3] = { 0, };
BYTE pRX[3] = { 0, };
int nbit = gpio;
if (!m_ftdiHandle) return FT_INVALID_HANDLE;

// 핀 번호 및 입출력 설정 체크.
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;
}


SPI 통신 코드

FT232H에 데이터를 커맨드를 써넣어 연결된 IC와 SPI통신을 수행한다. 
커맨드는 아래 그림과 같다. 아래 커맨드를 보내고 읽을 데이터가 있으면, FT232H에서 데이터를 읽어오면 된다. 



FT_STATUS CFT232H::SPI_TXRX(BYTE *pTx, WORD wTxLen, BYTE *pRx, WORD wRxLen)
{
FT_STATUS ftStatus = FT_OK;
int nCmdLen = 0;

if (!m_ftdiHandle) return FT_INVALID_HANDLE;

m_dwReservedToRead += (DWORD)wRxLen;

// set cs pin low
CLEARBIT(m_ACADBusVal, CS_PIN);
m_TXRXBuffer[nCmdLen++] = SET_DATABITS_LOW_BYTE;
m_TXRXBuffer[nCmdLen++] = (BYTE)(m_ACADBusVal & 0xff);
m_TXRXBuffer[nCmdLen++] = (BYTE)(m_ACADBusDir & 0xff);

if (pTx && wTxLen > 0)
{
m_TXRXBuffer[nCmdLen++] = CLOCK_BYTES_OUT_NEG_EDGE_MSB_FIRST;
m_TXRXBuffer[nCmdLen++] = (BYTE)((wTxLen - 1) & 0xFF);
m_TXRXBuffer[nCmdLen++] = (BYTE)(((wTxLen - 1) >> 8) & 0xFF);

memcpy(&m_TXRXBuffer[nCmdLen], pTx, wTxLen*sizeof(BYTE));
nCmdLen += wTxLen;
}

if (wRxLen > 0)
{
m_TXRXBuffer[nCmdLen++] = CLOCK_BYTES_IN_POS_EDGE_MSB_FIRST;
m_TXRXBuffer[nCmdLen++] = (BYTE)((wRxLen - 1) & 0xFF);
m_TXRXBuffer[nCmdLen++] = (BYTE)(((wRxLen - 1) >> 8) & 0xFF);
}

// set cs pin high
SETBIT(m_ACADBusVal, CS_PIN);
m_TXRXBuffer[nCmdLen++] = SET_DATABITS_LOW_BYTE;
m_TXRXBuffer[nCmdLen++] = (BYTE)(m_ACADBusVal & 0xff);
m_TXRXBuffer[nCmdLen++] = (BYTE)(m_ACADBusDir & 0xff);

m_TXRXBuffer[nCmdLen++] = SEND_IMMEDIATE;

if (Write(m_TXRXBuffer, nCmdLen) != nCmdLen)
{
return FT_IO_ERROR;
}

if (m_dwReservedToRead)
{
DWORD dwQlen = WaitForData(m_dwReservedToRead, 1000);
DWORD dwNumBytesRead = 0;
if (dwQlen > 0)
{
if (pRx)
{
dwNumBytesRead = Read(pRx, dwQlen);
}
else
{
dwNumBytesRead = Read(m_TXRXBuffer, dwQlen);
}

if (dwNumBytesRead != dwQlen)
ftStatus = FT_IO_ERROR;
}

m_dwReservedToRead = 0;
}

return ftStatus;
}

CFT232H 클래스 사용하여 IC의 chip id를 읽어오는 예제 소스다.

void test_ft232h_spi()
{
CFT232H ft232h;
int nVal = 0;

long locids[16] = { 0, };

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

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


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 },
};

ft232h.configure(CFT232H::SPI_15000000HZ, gpiocfg, 4);


// reset 
ft232h.SetGPIO(CFT232H::GPIOL0, 1);
Sleep(10);
ft232h.SetGPIO(CFT232H::GPIOL0, 0);
Sleep(20);
ft232h.SetGPIO(CFT232H::GPIOL0, 1);
Sleep(50);

BYTE tx[2] = { 0, 0 };
BYTE rx[2] = { 0, };

tx[0] = 0; tx[1] = 1;
ft232h.SPI_TXRX(tx, 2, rx, 1);
_trace(TEXT("force zero reg : 0x%x\n"), rx[0]);
tx[0] = 1; tx[1] = 1;
ft232h.SPI_TXRX(tx, 2, rx, 1);
_trace(TEXT("force one reg : 0x%x\n"), rx[0]);

tx[0] = 0x02; tx[1] = 2;
ft232h.SPI_TXRX(tx, 2, rx, 2);
_trace(TEXT("chip id : 0x%02x %02x\n"), rx[0],rx[1]);
}

실행하면 아래와 같이 chip id를 읽어오는 것을 볼 수 있다.
 



댓글

이 블로그의 인기 게시물

간단한 cfar 알고리즘에 대해

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

간단한 칼만 필터(Kalman Filter) 소스 코드와 사용 예제

안드로이드(android) 전체 화면 시계 앱(clock app) 예제 코드

mkfs.fat Device or resource busy 에러 해결법