FTDI SPI Tutorial: LibMPSSE with Visual Studio 2015
December 28, 2015
FTDI is mostly known for their USB UART chips, but some of their higher-end ICs also have an “MPSSE” (multi-protocol synchronous serial engine) that can do SPI, I2C and JTAG. I made a YouTube video tutorial covering the basics of their SPI library for C. I thought I'd also write this post to summarize that video and provide its content in text form for easier referencing.
FTDI sells a ready-made USB adapter, it's part number C232HM-DDHSL-0. You can buy it from the usual suppliers (Mouser, DigiKey, etc.) for around $26 + shipping. It's essentially an FT232H broken out to 10 wires with female 0.1” connectors.
Some Useful Links
http://www.ftdichip.com/Products/Cables/USBMPSSE.htm
The product page for this USB adapter and some other ones.
http://www.ftdichip.com/Drivers/D2XX.htm
The D2XX driver you'll need to install. Windows will automatically install the VCP driver (UART driver) when you plug the cable in, but you need to manually install this D2XX driver if you want to use the cable for SPI/I2C/JTAG projects.
http://www.ftdichip.com/Support/Documents/DataSheets/Cables/DS_C232HM_MPSSE_CABLE.PDF
The datasheet for this USB adapter. It's got the schematic, a brief summary of what's possible, and tells you which color wire is used for what.
http://www.ftdichip.com/Support/Documents/AppNotes/AN_188_C232HM_MPSSE_Cable_in_USB_to_SPI-Interface.pdf
An App Note that provides a HelloWorld-level demo of their SPI library for C. It's a useful document, but their code isn't as clear as I'd like. They also did things the hard way (declaring a bunch of function pointers for the DLL) which is not needed if you will be using Visual Studio.
http://www.ftdichip.com/Support/Documents/AppNotes/AN_178_User_Guide_For_LibMPSSE-SPI.pdf
The User Guide for their SPI library for C. It documents all of the functions and data structures.
http://www.ftdichip.com/Support/SoftwareExamples/MPSSE/LibMPSSE-SPI/LibMPSSE-SPI.zip
The actual library and a simple demo project. This ZIP file contains the headers and the .lib file you will need to copy into your project.
Prepare a Visual Studio 2015 Project
- Open Visual Studio 2015, then create a new project:
File > New > Project Templates > Visual C++ > Win32 Console Application Give the project a name, then click OK In the Application Wizard: click Next, then uncheck "Precomiled Header", then click Finish
- Remove some unneeded files from the project: stdafx.h, targetver.h, and stdafx.cpp
In the Solution Explorer, right-click over each of those files > Remove > Delete
- Copy four files from FTDI's LibMPSSE-SPI zip file (linked above) into the Visual Studio project: ftd2xx.h, libMPSSE.lib, libMPSSE_spi.h, and WinTypes.h
Those files are in LibMPSSE-SPI.zip > LibMPSSE-SPI > Release > samples > SPI > SPI Copy those four files into your Visual Studio project's folder. By default, that would be in: This PC > Documents > Visual Studio > Projects > YourProjectName > YourProjectName
- Tell Visual Studio about those four recently added files:
In the Solution Explorer, right-click on Header Files > Add > Existing Item Select ftd2xx.h, libMPSSE_spi.h, and WinTypes.h, then click Add In the Solution Explorer, right-click on Resource Files > Add > Existing Item Select linMPSSE.lib, then click Add
- Fix an error in WinTypes.h: Replace "sys/time.h" with "time.h"
Change line 5: Before: #include <sys/time.h> After: #include <time.h>
Minimalist Demo Program
By now the Visual Studio project is fully setup. You can starting using the API. Below is a simple demo program I wrote. It's communicates with an SPI gyroscope and displays X/Y/Z velocities on screen.
- The program starts by displaying information about each MPSSE "channel" that is available. An MPSSE channel is the part of the IC that can do SPI/I2C/JTAG protocols. Some FTDI ICs are UART-only, so they won't have any MPSSE channels. The USB adapter I'm using has one MPSSE channel. Some of their more advanced ICs have two MPSSE channels.
- The user will be asked to specify which MPSSE channel to use.
- The program will prepare that channel for SPI Mode 0 with a 1 MHz clock.
- The SPI gyro will be configured by writing to five of it's registers.
- Finally, an infinite loop is used to poll the gyro's X/Y/Z velocity registers. Those velocities are formatted and printed to the screen.
- The program can be closed by pressing Ctrl-C.
There are 1 channels available.
Channel number: 0
Description: C232HM-DDHSL-0
Serial Number: 1637980
Enter a channel number to use: 0
x = -182, y = -395, z = -1157
x = -50, y = -14, z = -276
x = -351, y = -159, z = -1936
x = -211, y = -293, z = -190
x = -217, y = -310, z = -200
x = -232, y = -329, z = -177
x = -216, y = -301, z = -185
x = -239, y = -329, z = -167
x = -211, y = -304, z = -185
x = -215, y = -307, z = -209
x = -234, y = -285, z = -189
...
Source Code
// red wire = 3v3
// black wire = ground
// orange wire = sclk
// yellow wire = mosi
// green wire = miso
// brown wire = cs
#include <stdio.h>
#include <Windows.h>
#include "libMPSSE_spi.h"
// a helper function for showing fatal error messages
void print_and_quit(char cstring[]) {
printf("%s\n", cstring);
getc(stdin);
exit(1);
}
int main(int argc, char **argv) {
Init_libMPSSE();
FT_STATUS status;
FT_DEVICE_LIST_INFO_NODE channelInfo;
FT_HANDLE handle;
// check how many MPSSE channels are available
uint32 channelCount = 0;
status = SPI_GetNumChannels(&channelCount);
if (status != FT_OK)
print_and_quit("Error while checking the number of available MPSSE channels.");
else if (channelCount < 1)
print_and_quit("Error: no MPSSE channels are available.");
printf("There are %d channels available.\n\n", channelCount);
// print out details for each MPSSE channel
for (int i = 0; i < channelCount; i++) {
status = SPI_GetChannelInfo(i, &channelInfo);
if (status != FT_OK)
print_and_quit("Error while getting details for an MPSSE channel.");
printf("Channel number: %d\n", i);
printf("Description: %s\n", channelInfo.Description);
printf("Serial Number: %d\n", channelInfo.SerialNumber);
}
// ask the user to select a channel
uint32 channel = 0;
printf("\nEnter a channel number to use: ");
scanf_s("%d", &channel);
// open the MPSSE channel (get the handle for it)
status = SPI_OpenChannel(channel, &handle);
if (status != FT_OK)
print_and_quit("Error while opening the MPSSE channel.");
// init the channel (configure it)
ChannelConfig channelConfig;
channelConfig.ClockRate = 1000000; // 1 MHz
channelConfig.configOptions = SPI_CONFIG_OPTION_MODE0 | SPI_CONFIG_OPTION_CS_DBUS3 | SPI_CONFIG_OPTION_CS_ACTIVELOW;
channelConfig.LatencyTimer = 75;
status = SPI_InitChannel(handle, &channelConfig);
if (status != FT_OK)
print_and_quit("Error while initializing the MPSSE channel.");
// configure the gyro
uint8 tx_buffer[6] = {
0x20 | (1 << 6) | (0 << 7), // first register is 0x20, bit6 high for auto-increment mode, bit7 low for write mode
0b01111111, // value for register 0x20
0b00101001, // value for register 0x21
0b00001000, // value for register 0x22
0, // value for register 0x23
0 // value for register 0x24
};
uint32 transferCount = 0;
uint32 options = SPI_TRANSFER_OPTIONS_SIZE_IN_BYTES | SPI_TRANSFER_OPTIONS_CHIPSELECT_ENABLE | SPI_TRANSFER_OPTIONS_CHIPSELECT_DISABLE;
status = SPI_Write(handle, tx_buffer, 6, &transferCount, options);
if (status != FT_OK)
print_and_quit("Error while configuring the gyro.");
// enter an infinite loop that reads X/Y/Z velocities from the gyro
while (1) {
uint8 tx_buffer[7] = {
0x28 | (1 << 6) | (1 << 7), // first register is 0x28, bit6 high for auto-increment mode, bit7 high for read mode
0, // read register 0x28
0, // read register 0x29
0, // read register 0x2A
0, // read register 0x2B
0, // read register 0x2C
0 // read register 0x2D
};
uint8 rx_buffer[7] = { 0 };
uint32 transferCount = 0;
uint32 options = SPI_TRANSFER_OPTIONS_SIZE_IN_BYTES | SPI_TRANSFER_OPTIONS_CHIPSELECT_ENABLE | SPI_TRANSFER_OPTIONS_CHIPSELECT_DISABLE;
status = SPI_ReadWrite(handle, rx_buffer, tx_buffer, 7, &transferCount, options);
if (status != FT_OK)
print_and_quit("Error while reading from the gyro.");
int16 x = (rx_buffer[2] << 8) | rx_buffer[1];
int16 y = (rx_buffer[4] << 8) | rx_buffer[3];
int16 z = (rx_buffer[6] << 8) | rx_buffer[5];
printf("x = %+6d, y = %+6d, z = %+6d\n", x, y, z);
}
// this code will never be reached due to the infinite loop above, but you would normally want to end with this:
Cleanup_libMPSSE();
return 0;
}