Speaking SPI & I2C With The FT-2232

For a while now I’ve been looking for an easy way to interface with external SPI and I2C devices over USB in a manner that can be easily integrated into future projects as well as used in a simple stand-alone system.

Although there are many existing SPI/I2C interface solutions, most of them are microcontroller based and connect to the PC though a USB to serial converter. This works fine, but I wanted something with a bit more speed while also remaining simple, cheap, and readily available.

After some searching, the FTDI FT-2232 family of chips seemed to fit the bill nicely. Although they are more commonly used to interface with JTAG devices, the FT-2232′s Multi-Protocol Synchronous Serial Engine (MPSSE) also supports the SPI and I2C protocols, clock rates of up to 30MHz, and a full-speed USB interface. Development boards are also cheap – the UM232H is $20 from DigiKey or Mouser in single quantities.

I’ve written libmpsse, a Linux wrapper library around libftdi that provides an easy to use API for interfacing with SPI and I2C devices using C and Python.

So how does this relate to hacking embedded systems you ask? Let’s take a look…

Although convenient, firmware update files are not always sufficient for performing code analysis on an embedded device. The firmware update file may be encrypted/obfuscated, or it may only be a partial update for the embedded system. In some cases, there may be no firmware update available at all. In these situations, being able to dump the firmware directly from the target device’s flash storage is invaluable.

SPI flash chips are increasingly replacing parallel flash chips, in both embedded devices as well as traditional PCs, and using libmpsse we can easily read and modify their contents.

As an example, let’s read the entire contents of a 1MB SPI flash chip. After making the appropriate hardware connections between the target flash chip and the FTDI chip, we can use the following Python script to dump the flash contents:

from mpsse import *


data = Read(0x100000)


open('flash.bin', 'wb').write(data)

Although simple, the above script can read data from the flash chip very quickly:

eve@eve:~/libmpsse/src/examples$ time sudo python spiflash.py 
FT232H Future Technology Devices International, Ltd initialized at 30000000Hz (SPI mode 0)
Dumped 1048576 bytes to flash.bin

real	0m0.556s
user	0m0.020s
sys	0m0.016s

With the data extracted from the flash chip, we can now analyze it using our standard tools and techniques.

Of course libmpsse can be used to interface with other SPI/I2C devices such as data sensors, frequency synthesizers and EEPROM chips. It can be an easy way to add USB functionality to existing or future hardware designs, or to just have a simple SPI/I2C interface for development and testing.

The libmpsse source, documentation and examples are available from the Google Code project page.

Bookmark the permalink.

29 Responses to Speaking SPI & I2C With The FT-2232

  1. Pingback: FT-2232 bridges Python and I2C/SPI - Hack a Day

  2. Pingback: FT-2232 bridges Python and I2C/SPI | ro-Stire

  3. Pingback: FT-2232 bridges Python and I2C/SPI | HackDom | The Hacking Kingdom

  4. Pingback: FT-2232 bridges Python and I2C/SPI « Gadgets « The Depot of Talk

  5. kiran says:

    what is that ft2232 breakout board in the picture? from where did you buy that?

    • Craig says:

      It’s actually an FT-232H eval board (not to be confused with the FT-232R, which does not support MPSSE!), and I bought it from DigiKey. They also sell one built into a USB cable, which is quite handy. Mouser carries both of these products as well.

  6. molli123 says:

    which chip did you actually use in your example? When do I have to use i2ceeprom and when spiflash when connecting to 8 pin chips?

  7. molli123 says:

    is it AT24XX is needs I2C and AT25XX SPI, but with different wiring?

    • Craig says:

      Yes, the wiring for the SPI and I2C EEPROMs will be different. You will need to consult the EEPROM chip’s datasheet to determine which pins do what. The AN135 PDF included in the libmpsse docs folder has examples of connecting the FTDI chip to various devices, such as EEPROMs.

      You will likely need to modify the example applications as well to ensure that they are reading the right number of bytes from your EEPROM chip.

  8. Paul says:

    I tried to configure and install this with –prefix set to a location where I could install without being root, but the makefile install rule seems to not really support that. It was still trying to create things in /usr/local/….

    • Craig says:

      I just checked in a change to the Makefile so that it doesn’t try to create symlinks in /usr/lib. With –prefix and PYLIB properly specified, it should now install fine to wherever you point it.

  9. edo says:

    did you see flashroom project?
    it implement spi over libftdi and support many EEPROM chips

    • Craig says:

      Yes, I’m familiar with the flashrom project. Although there is some overlap in capabilities with libmpsse, they really are very different.

      Flashrom specifically targets reading/writing flash chips, including SPI chips as mentioned here, particularly things like BIOS chips.

      libmpsse provides a generic SPI/I2C library, letting you talk not only to flash chips, but EEPROMs, real time clocks, I/O expanders, and pretty much anything else that has an SPI/I2C interface.

  10. roboknight says:

    So, I’ve been using libmpsse to successfully interface to I2C rheostats from microchip (MCP42xx). The problem I’m having now is that data is sent during a write cycle… I can’t see how libmpsse actually handles this. It appears you have write, then read and that you can’t actually write/read in one operation. Am I missing something? I looked through at the libftdi library, and it seems that this is pretty much the way it works too. Since SPI can actually clock bytes in during all write operations, it seems to be an oversight. But as, I asked, am I missing something?

    • Craig says:

      Yes, this is a limitation in the libmpsse API. In theory it should be possible to do an SPI write and then read back the contents of the FTDI chip’s read buffer, but AFAIK libftdi doesn’t expose this functionality. One solution would be to bypass libftdi and have libmpsse do the usb bulk reads itself, but I’ll have to look into it further.

      • roboknight says:

        I was afraid of that. I was trying to find the info on the MPSSE commands that I had run across (I think they are buried on FTDIs website somewhere, but it isn’t obvious if I remember correctly), so that I could maybe hack some functionality into libftdi and expose that back to libmpsse… but I can’t find the command set anywhere for some reason now. All I can find are the FTDI API interfaces into D2XX. Maybe it was buried in their data sheets… I just don’t remember now.

      • roboknight says:

        I think I might have fixed it. I created a WriteRead command. It doesn’t output the data like the read command does yet, but at least I can do a regular read and see the data from the buffer. It has to do with how you implement the rx/tx commands. You need one that supports clocking in on rx AND tx. At any rate I might have a patch soon.

  11. Pingback: Interfacing Using FT-2232 | HACKOLOG - Amazing Hacks and Mods

  12. 42loop says:

    has anyone gotten this to compile for openwrt ?

    i had to remove a check for malloc in the configure script, but then discovered there is no ftdi.h in the buildroot of ‘attitude adjustment’.
    anyway i guess swig will be the next hurdle…

    • Craig says:

      I haven’t tried building it for openwrt, but regardless I would not expect the configure script’s malloc checks to fail; that’s a pretty standard check for configure scripts.

      I’m not sure if libftdi will build nicely for openwrt and/or your architecture; you might want to check that first, as libmpsse is useless without it.

      Unless you need Python support, you can skip the swig portion of the build. It is not needed for the C library. If you do need Python support, it should be platform independent as long as the C library built OK.

      • 42loop says:

        found out later that the malloc check even fails trying to (not cross-) compile on an ubuntu 12.04 with all build environment installed correctly

  13. Vivien says:

    It’s look like there is a problem with Open function, it fail on:
    ftdi_set_bitmode(&mpsse->ftdi, 0, BITMODE_MPSSE);
    I use a 0×6010
    Do you have any Idea ?

    • Vivien says:

      Here is my backtrace :
      0 0x00007ffff743a4cf libusb_submit_transfer /home/vivien/libusb/libusb/io.c 1290
      1 0x00007ffff743bf80 libusb_control_transfer /home/vivien/libusb/libusb/sync.c 98
      2 0x00007ffff76499a2 ftdi_set_bitmode /home/vivien/ftdigit/libftdi/src/ftdi.c 1937
      3 0x00007ffff7bdb567 Open /home/vivien/libmpsse-1.2/src/mpsse.c 133
      4 0x00007ffff7bdb656 MPSSE /home/vivien/libmpsse-1.2/src/mpsse.c 52
      5 0x0000000000400ae8 main /home/vivien/.codelite/Vivien/DumpSpi/main.c 16

      • Vivien says:

        I use last (git) libusb 1.0 + last (git) libusb-compat + last (git) libftdi.

        • Vivien says:

          And my debug libusb backtrace (sorry for spamming :( )

          libusb: 0.000000 debug [libusb_init] libusb-1.0.9 git:1.0.9-24-g2b044ab
          libusb: 0.000092 debug [find_usbfs_path] found usbfs at /dev/bus/usb
          libusb: 0.000131 debug [op_init] bulk continuation flag supported
          libusb: 0.000138 debug [op_init] zero length packet flag supported
          libusb: 0.000159 debug [op_init] found usb devices in sysfs
          libusb: 0.000257 debug [usbi_add_pollfd] add fd 7 events 1
          libusb: 0.000273 debug [usbi_io_init] using timerfd for timeouts
          libusb: 0.000280 debug [usbi_add_pollfd] add fd 11 events 1
          libusb: 0.000306 debug [libusb_get_device_list]
          libusb: 0.000335 debug [sysfs_scan_device] scan usb1
          libusb: 0.000424 debug [sysfs_scan_device] bus=1 dev=1
          libusb: 0.000433 debug [enumerate_device] busnum 1 devaddr 1 session_id 257
          libusb: 0.000439 debug [enumerate_device] allocating new device for 1/1 (session 257)
          libusb: 0.000508 debug [sysfs_scan_device] scan usb2
          libusb: 0.000575 debug [sysfs_scan_device] bus=2 dev=1
          libusb: 0.000584 debug [enumerate_device] busnum 2 devaddr 1 session_id 513
          libusb: 0.000590 debug [enumerate_device] allocating new device for 2/1 (session 513)
          libusb: 0.000643 debug [sysfs_scan_device] scan usb3
          libusb: 0.000711 debug [sysfs_scan_device] bus=3 dev=1
          libusb: 0.000719 debug [enumerate_device] busnum 3 devaddr 1 session_id 769
          libusb: 0.000725 debug [enumerate_device] allocating new device for 3/1 (session 769)
          libusb: 0.000778 debug [sysfs_scan_device] scan usb4
          libusb: 0.000846 debug [sysfs_scan_device] bus=4 dev=1
          libusb: 0.000854 debug [enumerate_device] busnum 4 devaddr 1 session_id 1025
          libusb: 0.000861 debug [enumerate_device] allocating new device for 4/1 (session 1025)
          libusb: 0.000914 debug [sysfs_scan_device] scan usb5
          libusb: 0.000983 debug [sysfs_scan_device] bus=5 dev=1
          libusb: 0.000991 debug [enumerate_device] busnum 5 devaddr 1 session_id 1281
          libusb: 0.000997 debug [enumerate_device] allocating new device for 5/1 (session 1281)
          libusb: 0.001050 debug [sysfs_scan_device] scan usb6
          libusb: 0.001118 debug [sysfs_scan_device] bus=6 dev=1
          libusb: 0.001126 debug [enumerate_device] busnum 6 devaddr 1 session_id 1537
          libusb: 0.001132 debug [enumerate_device] allocating new device for 6/1 (session 1537)
          libusb: 0.001185 debug [sysfs_scan_device] scan usb7
          libusb: 0.001253 debug [sysfs_scan_device] bus=7 dev=1
          libusb: 0.001262 debug [enumerate_device] busnum 7 devaddr 1 session_id 1793
          libusb: 0.001268 debug [enumerate_device] allocating new device for 7/1 (session 1793)
          libusb: 0.001321 debug [sysfs_scan_device] scan usb8
          libusb: 0.001390 debug [sysfs_scan_device] bus=8 dev=1
          libusb: 0.001398 debug [enumerate_device] busnum 8 devaddr 1 session_id 2049
          libusb: 0.001404 debug [enumerate_device] allocating new device for 8/1 (session 2049)
          libusb: 0.001457 debug [sysfs_scan_device] scan 8-1
          libusb: 0.001526 debug [sysfs_scan_device] bus=8 dev=6
          libusb: 0.001534 debug [enumerate_device] busnum 8 devaddr 6 session_id 2054
          libusb: 0.001540 debug [enumerate_device] allocating new device for 8/6 (session 2054)
          libusb: 0.001594 debug [discovered_devs_append] need to increase capacity
          libusb: 0.001609 debug [sysfs_scan_device] scan 6-1
          libusb: 0.001677 debug [sysfs_scan_device] bus=6 dev=2
          libusb: 0.001686 debug [enumerate_device] busnum 6 devaddr 2 session_id 1538
          libusb: 0.001692 debug [enumerate_device] allocating new device for 6/2 (session 1538)
          libusb: 0.001746 debug [sysfs_scan_device] scan 6-2
          libusb: 0.001814 debug [sysfs_scan_device] bus=6 dev=3
          libusb: 0.001823 debug [enumerate_device] busnum 6 devaddr 3 session_id 1539
          libusb: 0.001829 debug [enumerate_device] allocating new device for 6/3 (session 1539)
          libusb: 0.001895 debug [libusb_get_device_descriptor]
          libusb: 0.001919 debug [libusb_get_device_descriptor]
          libusb: 0.001939 debug [libusb_get_device_descriptor]
          libusb: 0.001960 debug [libusb_get_device_descriptor]
          libusb: 0.001979 debug [libusb_get_device_descriptor]
          libusb: 0.002000 debug [libusb_get_device_descriptor]
          libusb: 0.002020 debug [libusb_get_device_descriptor]
          libusb: 0.002039 debug [libusb_get_device_descriptor]
          libusb: 0.002058 debug [libusb_get_device_descriptor]
          libusb: 0.002079 debug [libusb_open] open 8.6
          libusb: 0.002089 debug [op_open] opening /dev/bus/usb/008/006
          libusb: 0.002119 debug [usbi_add_pollfd] add fd 12 events 4
          libusb: 0.002139 debug [libusb_close]
          libusb: 0.002147 debug [usbi_remove_pollfd] remove fd 12
          libusb: 0.002159 debug [libusb_open] open 8.6
          libusb: 0.002166 debug [op_open] opening /dev/bus/usb/008/006
          libusb: 0.002179 debug [usbi_add_pollfd] add fd 12 events 4
          libusb: 0.002188 debug [libusb_get_device_descriptor]
          libusb: 0.002210 debug [libusb_get_config_descriptor] index 0
          libusb: 0.002243 debug [libusb_detach_kernel_driver] interface 0
          libusb: 0.002257 debug [libusb_get_configuration]
          libusb: 0.002282 debug [libusb_get_configuration] active config 1
          libusb: 0.002291 debug [libusb_claim_interface] interface 0
          libusb: 0.002337 debug [libusb_handle_events_timeout_completed] doing our own event handling
          libusb: 0.002345 debug [handle_events] poll() 3 fds with timeout in 60000ms
          libusb: 0.002421 debug [handle_events] poll() returned 1
          libusb: 0.002445 debug [reap_for_handle] urb type=2 status=0 transferred=0
          libusb: 0.002462 debug [handle_control_completion] handling completion status 0
          libusb: 0.002479 debug [disarm_timerfd]
          libusb: 0.002495 debug [usbi_handle_transfer_completion] transfer 0×603778 has callback 0x7ffff743a6a0
          libusb: 0.002503 debug [ctrl_transfer_cb] actual_length=0
          libusb: 0.002510 debug [libusb_get_device_descriptor]
          libusb: 0.002535 debug [libusb_get_config_descriptor] index 0
          libusb: 0.002576 debug [libusb_handle_events_timeout_completed] doing our own event handling
          libusb: 0.002584 debug [handle_events] poll() 3 fds with timeout in 60000ms
          libusb: 0.002621 debug [handle_events] poll() returned 1
          libusb: 0.002630 debug [reap_for_handle] urb type=2 status=0 transferred=0
          libusb: 0.002636 debug [handle_control_completion] handling completion status 0
          libusb: 0.002642 debug [disarm_timerfd]
          libusb: 0.002647 debug [usbi_handle_transfer_completion] transfer 0×603778 has callback 0x7ffff743a6a0
          libusb: 0.002653 debug [ctrl_transfer_cb] actual_length=0
          libusb: 0.002661 debug [libusb_unref_device] destroy device 1.1
          libusb: 0.002668 debug [libusb_unref_device] destroy device 2.1
          libusb: 0.002673 debug [libusb_unref_device] destroy device 3.1
          libusb: 0.002679 debug [libusb_unref_device] destroy device 4.1
          libusb: 0.002685 debug [libusb_unref_device] destroy device 5.1
          libusb: 0.002690 debug [libusb_unref_device] destroy device 6.1
          libusb: 0.002696 debug [libusb_unref_device] destroy device 7.1
          libusb: 0.002702 debug [libusb_unref_device] destroy device 8.1
          libusb: 0.002708 debug [libusb_unref_device] destroy device 6.2
          libusb: 0.002719 debug [libusb_unref_device] destroy device 6.3
          libusb: 0.002744 debug [libusb_handle_events_timeout_completed] doing our own event handling
          libusb: 0.002764 debug [handle_events] poll() 3 fds with timeout in 60000ms
          libusb: 0.002870 debug [handle_events] poll() returned 1
          libusb: 0.002895 debug [reap_for_handle] urb type=2 status=0 transferred=0
          libusb: 0.002914 debug [handle_control_completion] handling completion status 0
          libusb: 0.002934 debug [disarm_timerfd]
          libusb: 0.002947 debug [usbi_handle_transfer_completion] transfer 0×603778 has callback 0x7ffff743a6a0
          libusb: 0.002966 debug [ctrl_transfer_cb] actual_length=0
          libusb: 0.002994 debug [libusb_handle_events_timeout_completed] doing our own event handling
          libusb: 0.003016 debug [handle_events] poll() 3 fds with timeout in 60000ms
          libusb: 0.003119 debug [handle_events] poll() returned 1
          libusb: 0.003143 debug [reap_for_handle] urb type=2 status=0 transferred=0
          libusb: 0.003163 debug [handle_control_completion] handling completion status 0
          libusb: 0.003180 debug [disarm_timerfd]
          libusb: 0.003195 debug [usbi_handle_transfer_completion] transfer 0×603778 has callback 0x7ffff743a6a0
          libusb: 0.003213 debug [ctrl_transfer_cb] actual_length=0
          libusb: 0.003258 debug [libusb_handle_events_timeout_completed] doing our own event handling
          libusb: 0.003280 debug [handle_events] poll() 3 fds with timeout in 60000ms
          libusb: 0.003369 debug [handle_events] poll() returned 1
          libusb: 0.003392 debug [reap_for_handle] urb type=2 status=0 transferred=0
          libusb: 0.003413 debug [handle_control_completion] handling completion status 0
          libusb: 0.003431 debug [disarm_timerfd]
          libusb: 0.003445 debug [usbi_handle_transfer_completion] transfer 0×606528 has callback 0x7ffff743a6a0
          libusb: 0.003463 debug [ctrl_transfer_cb] actual_length=0

  14. Leo says:

    Wow, that’s an amazing project. I’m impressed, and I think this libmpsse will be useful for me in the future.

    I am thinking about using the FT232H for a project at work, in your experience, do you think the device is capable of maintaining a 30 Mbit/s data throughput continuously (using MPSSE)?

    Thank you

    • Craig says:

      I haven’t tried to maintain a full 30Mbit/s speed with this, though it is theoretically possible as long as your target device supports a 30MHz clock and you are doing only continuous reads.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>