I2C and more with the FTDI FT2232H Mini Module and an Arduino
Interact with I2C, SPI, or bit-bang hardware using the FTDI FT2232H Mini module in Python3 and test it with an Arduino!
Sometimes you’ll find yourself needing to bridge the gap between your PC and a low-level hardware device like a microcontroller or an FPGA. These devices usually “speak” in protocols like I2C and SPI, but you might even need to read and write bits (aka “bit-bang”) in some situations. Some ADC converters are configured via I2C, but the actual data is returned as sets of 8-bit words.
You might be tempted to deploy another microcontroller (like an Arduino) that has USB functionality built-in. This can be a great solution, but also increases your programming overhead. You’d have to set up the wiring, program the microcontroller, select the pins and make sure they support the logic level and speeds you’ll be interfacing at, and then package up the data and send it over serial, and then unpack it on the other end… it can quickly go from a 10-line quickie to an overdeveloped microcontroller nightmare.
Fortunately, there are other options for reading data straight from these low-level components. Enter FTDI: (Future Technology Devices International). This company offers several solutions for this exact issue (and many others, too). FTDI chip devices are found in almost all Arduino boards (either natively or as clones) and many other consumer devices as well. Their solution is simple: use a pre-programmed microcontroller and driver suite to manage all your hardware protocols at a high level. While not perfect for every situation, this approach can save you a lot of time, especially for smaller applications.
Let’s look at how this might work: we’ll use the FT2232H Mini Module (available as a breakout — yay!) and simulate a the low-level component with an Arduino. Of course, your hardware will typically be much nastier than an Arduino: no easily accessible USB IO, and maybe not even debug access. But we’ll pretend for this example.
We’ll start with some simple wiring. Our make-believe hardware component will have I2C IO as well as some bit-bang lanes for reading words directly. I’ll use an Arduino Due, and make use of one of its hardware I2C buses and some digital pins in output mode. The FTDI module will be wired for I2C according to its datasheet, and we’ll use some of its extra pins as digital inputs. The Arduino Due runs with 5V logic, and the FTDI chip has 3.3V logic but is 5V-tolerant, so this should work fine.
The datasheet describes how to wire the FTDI chip, but I’ve extracted the relevant info for you here. You’ll want it in bus-powered mode (drawing power from USB). We’ll need 3 wires for I2C: two for data (SDA
) and one for the clock (SCL
). The FTDI chip will act as the “primary” device on the bus. We can use either of the channels on the FT2232H for I2C, but I picked channel B
— more on that soon. To power the FT2232H Mini Module and configure it for I2C, you can wire it as follows:
Power
FTDI CN3–1 → FTDI CN3–3
(any of) FTDI CN2–1, CN2–3 and CN2–5 → (all of) FTDI CN2–11, CN2–21, CN3–12 and CN3–22
I2C
SDA: FTDI CN3–24 and CN3–25 → Arduino SDA (D20 on the Due)
SCL: FTDI CN3–26 → Arduino SCL (D21 on the Due)
Both of the I2C lines must be pulled high (3.3V via a 2K resistor or similar), but the Arduino has internal pull-ups on its I2C bus so we don’t need to worry about that in this example. It is also a good idea to run a grounding wire between the two devices as well.
For the digital logic lines, we’ll use the AD bus on CN2. Wire up AD0–AD7 to some digital pins on the Arduino (on the Due, there’s an abundance of these). In this example, I’ll use Arduino pins 2–9. (wired as a mirror image for coding reasons…) Pulling from the FT2232H datasheet, our connections for the digital logic are as follows:
FTDI AD0 = CN2–7 → Arduino Pin 9
FTDI AD1 = CN2–10 → Arduino Pin 8
FTDI AD2 = CN2–9 → Arduino Pin 7
FTDI AD3 = CN2–12 → Arduino Pin 6
FTDI AD4 = CN2–14 → Arduino Pin 5
FTDI AD5 = CN2–13 → Arduino Pin 4
FTDI AD6 = CN2–16 → Arduino Pin 3
FTDI AD7 = CN2–15 → Arduino Pin 2
With the wiring taken care of, we’re ready to look at some code. In the Arduino world, we’ll keep things simple: wait for a byte over I2C (as a “secondary” I2C device), and when we get one, send the same byte back over the digital lines by breaking up the byte into its bits and writing them to the pins.
As for the Python code, things are a bit more complex. FTDI drivers come in two flavors: VCP (“Virtual Com Port”) and D2XX. VCP is the simpler way to do things: FTDI devices show up as serial port devices, and the OS exposes them as COM/TTY devices natively. This is the mode that Arduinos use (in most cases). However, to do more complicated things like I2C and bit-banging, we need the D2XX drivers.
The FTD2232H has two separate “channels”, A
and B
, and they operate independently in separate modes (if desired). To do things like SPI and I2C, we need the channel to be in MPSSE
(“Multi-Protocol Synchronous Serial Engine”) mode. For bit-banging, we’ll use a channel in bit-bang
mode. For more information about modes in FTDI devices, check out the FTDI D2XX Programming Guide. (Page 55, in particular.) We’ll put channel B
in MPSSE
mode for I2C, and channel A
in bit-bang mode for the digital logic.
So, start by downloading the D2XX and MPSSE drivers. You can get them for your system at the FTDI website. There is an installer binary, but we just need the DLLs, so the zip will do fine. We’ll also want the MPSSE
driver DLL from here. Download these and dig out ftd2xx.dll
andlibMPSSE.dll
(or the equivalent library files for Linux/Mac, if you’re using UNIX).
We’ll load these DLLs in Python using ctypes
. It’s always a good idea to check out a library’s header files before writing code that uses it: fdt2xx.h
and libMPSSE_i2c.h
each contain lots of useful information (found in the download archive for their respective libraries). Beware of the channel indices on the FT2232H! On my device, channel A
is index 1
and channel B
is index 0
… yikes! FTDI has several useful documents (called “application notes”) describing how the FT2232 and other similar chips can be used. Have a look at AN232R and AN2232C.
First, we’ll make a few Python classes to make things a bit easier to handle. I’ve pulled some status codes and mode constants from the header files, as well as some structs representing MPSSE
channels:
That is a lot of boilerplate. But, it could easily be packed away in a module or set of classes to make life easier. We’ll leave it as-is for this example. Next, we’ll look at the the code to do the fun stuff:
Putting it all together, you should see the Python output looking like this:
$ python3 test.py
Loaded MPSSE library
Loaded D2XX library
Listing channels...
Found 2 channels (status FT_OK)
Getting info for channel with index 0...
Channel description: FT2232H MiniModule B (status FT_OK)
Channel B opened with handle: 0xfd9f58 (status FT_OK)
InitChannel() B (status FT_OK)
FT_Open() (status FT_OK)
FT_SetBaudRate() (status FT_OK)
FT_SetBitMode() (status FT_OK)
Wrote 1 byte(s) (status FT_OK)
FT_GetBitMode() (status FT_OK)
Byte read: 0x42 (0b01000010)
CloseChannel() B (status FT_OK)
FT_Close() (status FT_OK)
And in the Arduino IDE serial console…
Testing...
Got 0x42
Cool! We sent a byte (0x42
, in this case) via I2C to our mysterious hardware device (the Arduino Due) and it sent back the same byte in parallel, using 8 digital pins. This illustrates the usefulness and (relative) simplicity of using a FTDI device to interface with your low-level hardware instead of programming a microcontroller to do it for you.
Hopefully you found this helpful! Integrating hardware with higher-level systems in languages like Python can get complicated and tedious in a hurry. But with a reasonable degree of organization, you can manage complex hardware devices easily with FTDI chips and their ready-to-go drivers.