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 알고리즘에 대해

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

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

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

ARM NEON asm memcpy 코드