A Chip Does One Type Conversion
Well known products and development boards make use of FT232R from FTDI as a USB to UART converter. However, FT232R converts only UART among the other available serial interfaces without the need of doing configuration or programming and it provides a Virtual COM (VCOM) to communicate with your USB port. This means that you need to use a different chip if you want to convert SPI to USB .i.e:FT220X , or I2C to USB .i.e:FT201X , …etc.
A Chip Can Do Many Types Conversion!
FTDI introduced In the next generation chips, a generic serial conversion engine. So customers can use the same chip to convert USB to UART or to SPI or other serial protocols.
“Multi-Protocol Synchronous Serial Engine” or MPSSE is the name of the block inside new generation chips from FTDI to provide the flexibility of USB to a variety of serial protocols conversion. This engine drives the chip pins to perform the desired serial protocol.
This flexibility allows MPSSE to be used in different applications including different types of USB to serial conversion and one of famous examples is using OpenOCD, the JTAG/SWD debugger with MPSSE chips. To read more about OpenOCD please refer to this getting started guide.
In this article, we are going to understand the basics of MPSSE, how to configure, and write a small program to drive an FT2232H chip, which contains MPSSE engine.
The breakout board that will be used in this tutorial is from a Chinese provider from Aliexpress. However, FTDI has an official module and can be used as well, called FT2232H mini module.
MPSSE in The Eye of “Device Manager”
The MPSSE block can be found in the new generations of FTDI chips namely: FT232H, FT2232H, FT4232H and FT2232D. To configure the MPSSE, a software USB interface called D2XX is used which is a proprietary interface specifically for FTDI devices, and it’s available to use its functions using a “FTD2XX.DLL” library. However, the usual Virtual COM Port (VCP) interface is available too. So you will see 2 different interfaces when you connect the chip to PC and actually belong to the same hardware.
You can see that by opening ‘device manager’ in Windows after connecting FT2232H. You will see FTDI chip in 2 places: under ‘Ports (COM & LPT)’ and ‘Universal Serial Bus controllers’ for the reason mentioned above.
From each one’s driver details, we can see the difference:
Mr. MPSSE Pins
Depending on the chip you are using, you may have one channel (FT232H) , dual channels (FT2232D and FT2232H) or quad channels (FT4232H).
Each channel has fixed pins to do the serial communication (Data Out, Data In, Clock and Chip Select if needed) like what the table below shows:
The following table tells how to assign protocols signals to the main 4 fixed pins for serial communication in MPSSE:
Talk to MPSSE: Commands
First step to drive any FTDI chip with MPSSE engine from your program is to understand MPSSE commands and how to use the ‘FTD2XX.DLL’ library.
You will find later that MPSSE is totally driven by commands, and that’s why it’s called a command processor. Each function or action you need from MPSSE to do is driven by a command. That includes: putting data on lines, pulling a gpio high, reading a gpio state, …etc.
Talk to MPSSE: Library
To do the required communication between your program and MPSEE this will be done though ‘FTD2XX.DLL’ library. However, if you don’t want to understand and use MPSSE commands directly, then a higher-level of abstraction is available in other libraries from FTDI. FTDI provides a library for SPI, I2C and JTAG; FTCSPI.DLL, FTCI2C.DLL and FTJTAG.DLL respectively.
You may start with FTCSPI.DLL, FTCI2C.DLL or FTJTAG.DLL, but I find it important to do at least a simple example using FTD2XX.DLL with bare-metal MPSSE commands in order to understand how MPSSE really works, and that’s what we are going to do in the following example. FTDI chip will drive a gpio directly using MPSSE.
The Example: Include Library
In this example I will use the QT C++ framework and you can use any other environment you feel comfortable with like Visual Studio and the steps should be similar.
First, we start by downloading the DLL files from the download page, then include your DLL file in your program. This is done in QT by adding the following line in ‘.pro’ file in your QT project:
INCLUDEPATH += "LIBs" LIBS += -L$$_PRO_FILE_PWD_/LIBs -lftd2xx
The library file FTD2XX.DLL is found in the download and unzipped ‘CDM v2.12.28 WHQL Certified’ folder. Copy the content of ‘amd64’ or ‘i386’ directory to your project directory. I made a folder called LIBs for this purpose.
Later, include in the place of using D2XX APIs, the ‘ftd2xx.h’ header.
The Example: Start Using The APIs
FTDI provides a full documentation of D2XX API in their D2XX programmer’s guide.
We scan first for the connected devices via USB using the following API:
FT_CreateDeviceInfoList(&numDevs);
Where numDevs will contain the number of detected FT devices
Then to get a detailed list of these devices using this function:
FT_GetDeviceInfoList(devInfo, &numDevs);
Where devInfo is a pointer to an array of FT_DEVICE_LIST_INFO_NODE elements. The FT_DEVICE_LIST_INFO_NODE contains the following members:
typedef struct _ft_device_list_info_node { ULONG Flags; ULONG Type; ULONG ID; DWORD LocId; char SerialNumber[16]; char Description[64]; FT_HANDLE ftHandle; } FT_DEVICE_LIST_INFO_NODE;
Later to open a connection with the target device using FT_Open,
FT_Open(device_num, &ftHandle);
Where device_num is the number of the device to connect with. Device numbers will be like the order stored in devices list ‘devInfo’.
ftHandle is a pointer to a variable of type FT_HANDLE where the handle will be stored. This handle must be used to access the device in the program.
After the connection is established, the MPSSE is ready for get commands and each command consists of an op-code followed by any necessary parameters or data.
The Example: Sending MPSSE Commands
Before sending any command, 2 steps are required:
1- Setting some configurations to the MPSSE <-> USB connection like: IN and OUT transfer size, read and write timeouts for the device and latency.
ftStatus |= FT_SetUSBParameters(ftHandle, 65535, 65535); //Set USB request transfer size ftStatus |= FT_SetChars(ftHandle, false, 0, false, 0); //Disable event and error characters ftStatus |= FT_SetTimeouts(ftHandle, 3000, 3000); //Sets the read and write timeouts in 3 sec for the FT2232H ftStatus |= FT_SetLatencyTimer(ftHandle, 1); //Set the latency timer
2- Make sure that your application and MPSSE are in a right sync. For this end, MPSSE has a special command called ’bad command’ and when it is detected, the MPSSE returns the value of 0xFA, followed by the byte that caused the bad command.
What documentation says about the process is that “the use of the bad command detection is the recommended method of determining whether the MPSSE is in sync with the application program. By sending a bad command on purpose and looking for 0xFA, the application can determine whether communication with the MPSSE is possible”.
To send a command between your application and MPSSE via USB, you need to send the data using ‘FT_Write’ api.
Then to read the input using FT_Read when the checking of the status using FT_GetQueueStatus returns a non-zero number of bytes to read.
The code to send a ‘bad command’ 0xAA or 0xAB will look like the following:
dwNumBytesToSend = 0; OutputBuffer[dwNumBytesToSend++] = '\xAA'; //Add BAD command & xAA* ftStatus = FT_Write(ftHandle, OutputBuffer, dwNumBytesToSend, &dwNumBytesSent); // Send off the BAD commands dwNumBytesToSend = 0; //Clear output buffer do { ftStatus = FT_GetQueueStatus(ftHandle, &dwNumInputBuffer); // Get the number of bytes in the device input buffer } while ((dwNumInputBuffer == 0) && (ftStatus == FT_OK)); //or Timeout bool bCommandEchod = false; ftStatus = FT_Read(ftHandle, InputBuffer, dwNumInputBuffer, &dwNumBytesRead); //Read out the data from input buffer for (dwCount = 0; dwCount < (dwNumBytesRead - 1); dwCount++) //Checkif Bad command and echo command received { if ((InputBuffer[dwCount] == BYTE('\xFA')) && (InputBuffer[dwCount + 1] == BYTE('\xAA'))) { bCommandEchod = true; break; } }
The Example: The Widget
The widget below scans and adds the discovered devices to a table, then the user selects the desired device to connect with.
The Example: Driving GPIOs
Now, let’s do a real usage of MPSSE by setting up a GPIO.
Although MPSSE is a serial engine, GPIO functionality is needed. For example, in SPI we may use an additional pin as Chip Select.
The commands used in GPIO example are:
0x80 <VALUE> <Direction>
This will set up the direction of the first 8 lines and force a value on the bits that are set as output. A 1 in the Direction byte will make that bit an output.
0x82 <VALUE> <Direction>
This will set up the direction of the high 8 lines and force a value on the bits that are set as output.A 1 in the Direction byte will make that bit an output.
0x81
This will read the current state of the first 8 pins and send back 1 byte.
0x83
This will read the current state of the high 8 pins and send back 1 byte.
Example:
To set TCK/SK, TDI/D0, TMS/CS as output and TDO/DI, GPIOL0-> GPIOL3 as input with low state. We send the following command:
0x80 0x00 0x0B
Knowing that a 1 in the Direction byte will make that bit an output
I did not come across the references to a bit fields table of the GPIO commands parameters. I had to figure that in practice. In the diagram below a demonstration of bits order of high and low GPIO ports.
The past widget is updated to control the available GPIOs in the channel. Files for this stage, scan and connect and GPIO control , are found in this commit.
Here is a test of the output functionality with the updated Widget. FT2232H sends the signals and “Analog Discovery 2” with its static I/O feature in Waveforms checks the state.
Later the Widget was updated to support the input functionality too.
Here is an example of how to read from MPSSE
OutputBuffer[dwNumBytesToSend++] = '\x81'; ftStatus = FT_Write(ftHandle, OutputBuffer, dwNumBytesToSend, &dwNumBytesSent); dwNumBytesToSend = 0; //Clear output buffer Sleep(10); do { ftStatus = FT_GetQueueStatus(ftHandle, &dwNumInputBuffer); } while ((dwNumInputBuffer == 0) && (ftStatus == FT_OK)); //or Timeout ftStatus = FT_Read(ftHandle, InputBuffer, dwNumInputBuffer, &dwNumBytesRead); //Read out the data from input buffer
Here is a test of inputs reading of the MPSSE. test is done using Static I/O feature in Waveforms with Analog Discovery 2 from Digilent. More about Analog Discovery 2 in the previous introduction we have on Atadait.
Read more about Analog Discovery 2:
- The Multi-function Instrument “Analog Discovery 2” Review
- ‘Given’ Hardware Behavioral Testing Is Needed ‘Then’ Use Analog Discovery 2 With Behave Python Framework
- Use AD2 to Check FT2232H Outputs
What is Next
Till now, we get familiar with MPSSE basics and who it works and how to write a basic application to scan and connect to a FT device, and then later control the GPIOs using MPSSE commands. In the next part we will see how to do a serial communication using SPI device and FT2232H.
References and Read More
- MPSSE Basics
- FTDI Hi Speed USB to SPI Example
- D2XX Programmer’s Guide
- Command Processor for MPSSE and MCU Host Bus Emulation Modes