FTDI D2XX Driver (accessing USB device through a DLL)
My past projects required two devices to communicate over USB protocol. Just for an example, I have a mobile robot that I can control it via my PC or in other words, I send the command directly from my computer over Bluetooth or Xbee network to my robot. Xbee or Bluetooth transceiver connected to the robot brain( microcontroller) using rs232 protocol. But then, how to connect Bluetooth or Xbee at the other end to PC since most of PCs today are not coming together with comport (serial port)? A simple answer: USB.
FTDI offer a range of products to allow easy interfacing to devices over USB. You can go to their website and look for products that can meets your objective. I have been using 2 types of FTDI IC, which are FT232R (USB UART) and FT245BM (USB FIFO).
The IC comes with a driver that you can download. There are 2 types of drivers, VCP or Virtual COM Port and D2XX. VCP create a virtual serial port (COM port) and allow you to access the USB device in the same way as you access a standard COM port. That’s mean you can simply use HyperTerminal to read/write USB device.
However, that’s not the only way to access the IC. D2XX driver allow you to access USB device through a DLL and it offer flexibility of utilize the IC. So, what you need to know is how to use the DLL.
In this article, I will show an example how to use FTD2XX.DLL in VBA environment. FTD2XX.dll comes with a bunch of pre-defined function and properties. I have a long list of functions and properties of the driver, but i will only copy a part of it here.
Pre-defined constants
'=========================================================================
' FTD2XX Return codes
'=========================================================================
Private Const FT_OK = 0
Private Const FT_INVALID_HANDLE = 1
Private Const FT_DEVICE_NOT_FOUND = 2
Private Const FT_DEVICE_NOT_OPENED = 3
Private Const FT_IO_ERROR = 4
Private Const FT_INSUFFICIENT_RESOURCES = 5
Private Const FT_INVALID_PARAMETER = 6
Private Const FT_INVALID_BAUD_RATE = 7
Private Const FT_DEVICE_NOT_OPENED_FOR_ERASE = 8
Private Const FT_DEVICE_NOT_OPENED_FOR_WRITE = 9
Private Const FT_FAILED_TO_WRITE_DEVICE = 10
Private Const FT_EEPROM_READ_FAILED = 11
Private Const FT_EEPROM_WRITE_FAILED = 12
Private Const FT_EEPROM_ERASE_FAILED = 13
Private Const FT_EEPROM_NOT_PRESENT = 14
Private Const FT_EEPROM_NOT_PROGRAMMED = 15
Private Const FT_INVALID_ARGS = 16
Private Const FT_OTHER_ERROR = 17
Function Declaration
'=======================================================================
' Declarations for device information functions in FTD2XX.dll:
'=======================================================================
Private Declare Function FT_GetNumDevices Lib "FTD2XX.DLL" Alias "FT_ListDevices" (ByRef arg1 As Long, ByVal arg2 As String, ByVal dwFlags As Long) As Integer
Private Declare Function FT_GetDeviceString Lib "FTD2XX.DLL" Alias "FT_ListDevices" (ByVal arg1 As Byte, ByVal arg2 As String, ByVal dwFlags As Long) As Integer
Private Declare Function FT_GetDeviceInfo Lib "FTD2XX.DLL" (ByVal lngHandle As Long, ByRef lngFT_Type As Long, ByRef lngID As Integer, ByVal pucSerialNumber As String, ByVal pucDescription As String, ByRef pvDummy As Byte) As Integer
'=======================================================================
' Declarations for functions in FTD2XX.dll:
'=======================================================================
Private Declare Function FT_OpenByIndex Lib "FTD2XX.DLL" Alias "FT_Open" (ByVal intDeviceNumber As Integer, ByRef lngHandle As Long) As Integer
Private Declare Function FT_OpenBySerialNumber Lib "FTD2XX.DLL" Alias "FT_OpenEx" (ByVal SerialNumber As String, ByVal lngFlags As Long, ByRef lngHandle As Long) As Integer
Private Declare Function FT_OpenByDescription Lib "FTD2XX.DLL" Alias "FT_OpenEx" (ByVal Description As String, ByVal lngFlags As Long, ByRef lngHandle As Long) As Integer
Private Declare Function FT_Close Lib "FTD2XX.DLL" (ByVal lngHandle As Long) As Integer
Private Declare Function FT_Read_String Lib "FTD2XX.DLL" Alias "FT_Read" (ByVal lngHandle As Long, ByVal lpvBuffer As String, ByVal lngBufferSize As Long, ByRef lngBytesReturned As Long) As Integer
Private Declare Function FT_Write_String Lib "FTD2XX.DLL" Alias "FT_Write" (ByVal lngHandle As Long, ByVal lpvBuffer As String, ByVal lngBufferSize As Long, ByRef lngBytesWritten As Long) As Integer
Private Declare Function FT_Read_Bytes Lib "FTD2XX.DLL" Alias "FT_Read" (ByVal lngHandle As Long, ByRef lpvBuffer As Byte, ByVal lngBufferSize As Long, ByRef lngBytesReturned As Long) As Integer
Private Declare Function FT_Write_Bytes Lib "FTD2XX.DLL" Alias "FT_Write" (ByVal lngHandle As Long, ByRef lpvBuffer As Byte, ByVal lngBufferSize As Long, ByRef lngBytesWritten As Long) As Integer
Private Declare Function FT_ResetDevice Lib "FTD2XX.DLL" (ByVal lngHandle As Long) As Integer
Private Declare Function FT_GetModemStatus Lib "FTD2XX.DLL" (ByVal lngHandle As Long, ByRef lngModemStatus As Integer) As Integer
Private Declare Function FT_Purge Lib "FTD2XX.DLL" (ByVal lngHandle As Long, ByVal lngMask As Integer) As Integer
Private Declare Function FT_SetTimeouts Lib "FTD2XX.DLL" (ByVal lngHandle As Long, ByVal lngReadTimeout As Integer, ByVal lngWriteTimeout As Integer) As Integer
Private Declare Function FT_GetQueueStatus Lib "FTD2XX.DLL" (ByVal lngHandle As Long, ByRef lngRxBytes As Integer) As Integer
Private Declare Function FT_SetUSBParameters Lib "FTD2XX.DLL" (ByVal lngHandle As Long, ByVal lngInTransferSize As Integer, ByVal lngOutTransferSize As Integer) As Integer
Private Declare Function FT_SetBreakOn Lib "FTD2XX.DLL" (ByVal lngHandle As Long) As Integer
Private Declare Function FT_SetBreakOff Lib "FTD2XX.DLL" (ByVal lngHandle As Long) As Integer
Private Declare Function FT_GetStatus Lib "FTD2XX.DLL" (ByVal lngHandle As Long, ByRef lngamountInRxQueue As Integer, ByRef lngAmountInTxQueue As Integer, ByRef lngEventStatus As Integer) As Integer
The codes above used to define functions/properties of ftd2xx.dll so that VBE could ‘recognize’ the functions. The word “Alias” allows the functions to be called using another name.
Private Declare Function FT_OpenByIndex Lib "FTD2XX.DLL" Alias "FT_Open" (ByVal intDeviceNumber As Integer, ByRef lngHandle As Long) As Integer
Instead of calling FT_Open, you could ‘rename’ the function to FT_OpenByIndex. However, it’s up to you either to follow or not. After all the functions have been declared, now, you can start to write your codes/functions.
Note : in VB6, integer is 16-bit unsigned number Long is 32-bit unsigned number. Be careful when using the function, if it cannot behave like what you want, please check the data type. I personally prefer to use long rather than integer.
You can start using the device after you ‘open’ the device. There are several ways to open the device. 2 methods that works for me are ‘open by index’ and ‘open by serial number’
Open by index method required the device’s index as a parameter and it will return a handle of the device that will be used later. For an example, if you have 2 FTDI device connected to the PC, the 1st device indexed to 0 and the 2nd device indexed to 1. So, in order to use the 1st device, place 0 as an input parameter.
FT_OpenByIndex(index, FT_Handle) ‘ index is 0
How do you know how many device connected to PC? Take a look at your PC or sniff the connected device using “FT_GetNumDevices” function. It will return the number of device connected to your PC.
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Open by index
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'Public Function OpenDevice(ByVal index As Byte) As Boolean
' If FT_GetNumDevices(FT_Device_Count, vbNullChar, FT_LIST_NUMBER_ONLY) <> FT_OK Then
' ' no usb device found
' MsgBox "No Device Found"
' OpenDevice = False
' Else
' ReDim FT_Handle(FT_Device_Count - 1)
' If FT_Device_Count <> 0 Then
' If FT_OpenByIndex(index, FT_Handle(index)) <> FT_OK Then
' ' cannot open device [communication error]
' MsgBox "Failed to Open Device"
' OpenDevice = False
' Else
' 'MsgBox ("Success")
' OpenDevice = True
' End If
' Else
' OpenDevice = False
' MsgBox "No Device Found...."
' End If
' End If
'End Function
The second method is using device serial number as an input parameter. This is more powerful technique, yet helps you to open the right device.
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Open by serial number
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' same as open by index but using device's serial number as a reference
Public Function OpenDevice(ByVal SerNum As String, ByVal DevID As Byte) As Boolean
If FT_GetNumDevices(FT_Device_Count, vbNullChar, FT_LIST_NUMBER_ONLY) <> FT_OK Then
' no usb device found
MsgBox "No Device Found....", vbCritical, "ERROR"
OpenDevice = False
Else
'ReDim FT_Handle(FT_Device_Count - 1)
SerNum = Trim(SerNum)
If FT_Device_Count <> 0 And FT_Handle(DevID) = 0 Then
If FT_OpenBySerialNumber(SerNum, FT_OPEN_BY_SERIAL_NUMBER, FT_Handle(DevID)) <> FT_OK Then
'MsgBox ("failedEX")
OpenDevice = False
Else
'MsgBox ("SuccessEX")
OpenDevice = True
End If
Else
OpenDevice = False
MsgBox "No Device(s) Found.... or" & vbCrLf & "ID " & DevID & " has been assigned to other device", vbCritical, "ERROR"
End If
End If
End Function
Before using the function, you need to know the device’s serial number. There is a dedicated function to make your work easy. This function works by getting the number of FTDI driver attached to the PC and then, get the serial number of each device and paste to current active cell.(VBA).
Public Sub ChDevice()
Dim TempDevString() As String
Dim inta As Long
If FT_GetNumDevices(FT_Device_Count, vbNullChar, FT_LIST_NUMBER_ONLY) <> FT_OK Then
' no usb device found
MsgBox "No Device Found"
Exit Sub
Else
If FT_Device_Count <> 0 Then
ReDim TempDevString(FT_Device_Count - 1)
For inta = 0 To FT_Device_Count - 1
TempDevString(inta) = Space(16)
If FT_GetDeviceString(inta, TempDevString(inta), FT_LIST_BY_INDEX Or FT_OPEN_BY_SERIAL_NUMBER) <> FT_OK Then
'could not enumerate USB device
MsgBox ("Couldn't get Device(s) Serial Number")
Exit Sub
Else
TempDevString(inta) = Left(TempDevString(inta), InStr(1, TempDevString(inta), vbNullChar) - 1)
End If
Next
' display device(s) ID
For inta = 0 To FT_Device_Count - 1
ActiveCell.Offset(inta, 0).value = TempDevString(inta)
Next
Else
MsgBox "No Device(s) Found", vbCritical, "ERROR"
End If
End If
End Sub
Note : FTD2XX.dll also has dedicated function to obtain the device(s) descriptions.
Another function that I will cover is FT_READ. There are 2 type of read access or may be more (please check programming guide). Since, I know my incoming data are in array form, I prefer to use FT_READ_BYTE.
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Read
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Public Function ReadFifo(ByVal index As Byte, ByRef FT_In_Buffer() As Byte, ByVal Read_Count As Long) As Boolean
Dim Read_Result As Long
' clear FT_in_buffer before used
Erase FT_In_Buffer
' ReDim FT_In_Buffer(1) As Byte
If FT_OK <> FT_Read_Bytes(FT_Handle(index), FT_In_Buffer(0), Read_Count, Read_Result) Then
'MsgBox ("Error while reading from FIFO1")
ReadFifo = False
Else
If Read_Count <> Read_Result Then
'MsgBox ("Error while reading from FIFO2")
ReadFifo = False
Else
ReadFifo = True
End If
End If
End Function
The same thing applied to write access. I keep data in an array (byte type) and then write it to the device.
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Write
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Public Function WriteFifo(ByVal index As Byte, ByRef FT_Out_Buffer() As Byte, ByVal Write_Count As Long) As Boolean
Dim Write_Result As Long
If FT_OK <> FT_Write_Bytes(FT_Handle(index), FT_Out_Buffer(0), Write_Count, Write_Result) Then
'cannot write to USB
'MsgBox ("Error while writing to FIFO")
WriteFifo = False
Else
If Write_Result <> Write_Count Then
'some byte missing
'MsgBox ("Error while writing to FIFO")
WriteFifo = False
Else
WriteFifo = True
End If
End If
End Function
The simplest function to use is FT_CLOSE as shown below. The function required a handle of a device that will be terminated / closed.
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Close
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Public Function CloseDevice(ByVal index As Byte) As Boolean
If FT_Close(FT_Handle(index)) <> FT_OK Then
'MsgBox ("Error : FT_CLOSE")
CloseDevice = False
Exit Function
End If
FT_Handle(index) = 0 'clear 'old' item
CloseDevice = True
End Function
What if your program/application keeps waiting the incoming data that will never come? The computer will keep waiting or if you lucky, it will give you an error. Therefore, FT_SetTimeouts plays an important role here. You can set device timeouts for read and write access and without having to “waste” your time.
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' set times out
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Public Function Set_USB_Device_Timeouts(ByVal index As Byte, ByVal ReadTimeOut As Integer, ByVal WriteTimeout As Integer)
If FT_OK <> FT_SetTimeouts(FT_Handle(index), ReadTimeOut, WriteTimeout) Then
' dont expect sumthing here
End If
End Function
These are the basic functions that usually been used but there are still a bunch of functions that have not been discussed. Well, why don’t you give a try☺.