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를 읽어오는 것을 볼 수 있다.
댓글
댓글 쓰기