Monday 30 August 2010

libftdi on Mac OS X

Update 2011/02/02: In trying to port pylibftdi to Python3, I found that the libraries which get built following the instructions below are 64-bit. All well and good, but the Python3.1 installation on Mac OS X 10.6 is 32-bit only (unlike the Python2.6 install).

While it's possible to get both 32-bit and 64-bit universal libraries, I haven't tried that yet. The solution to built 32-bit libraries was to use the following:

CFLAGS="-arch i386" ./configure && make && sudo make install

(Note I omitted libusb and sudo (for make install) in previous instructions - I've updated below). I also found I needed to do:

export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig/

prior to building libusb-compat - I can't remember if this was a problem or not the first time round. Anyway, hopefully I'll have pylibftdi working with Python3 soon.
End of Update

I've not written much about my ftdi projects recently, so here's an update. I've played around with reading values from an encoder and outputting to a RGB LED with PWM, but both of these require a fairly high constant poll rate (or in the case of reading the encoder, ideally interrupt-on-change). The jitter is quite annoying, and decent PWM seems tricky, even when following FTDI's instructions (pdf link) for maxmising the buffer size. There's always going to be a gap when one buffer has finished and the next starts.

On a different note, I'm now mostly using my new Mac mini, so I've spent a few moments getting things to work there. Prerequisites:

These should be installed in the order listed; for each file, download it, untar it (tar -xf filename), then run './configure && make && sudo make install' in the new directory which tar has created.

On the Python side, there were a couple of issues when I tried to run the old code. First was that the library could not be found at all. This was due to the library extension (.so) being hardcoded. For a cross-platform solution, it seems the best way is to use the following:

from ctypes import *
from ctypes.util import find_library

dll = CDLL(find_library('ftdi'))
Rather than specifying '', we have stripped off the 'lib' prefix and the .so extension. The simple 'ftdi' string would be used if we were linking this library with gcc using the -l flag. Try 'gcc -lftdi' and it should report only that it can't find _main, not that the library is missing (try gcc -lmadeupname and it should complain about being unable to find 'madeupname')

Once this was done, the program would some times run (especially under pdb, which lead me up the garden path of thinking it was a timing issue...), but other times would cause a segfault. This was tracked down to another hard-coded value - the size of the context structure. The following will report the size of the ftdi_context structure:

echo -e '#include \nint main(){return sizeof(struct ftdi_context);}' | gcc -xc - && (./a.out; echo $?)
This is somewhat fragile, as it will fail if the structure ever gets larger than 255 bytes, but this seems unlikely for the time being. On this Mac this gives 112 bytes; on my Linux boxes it gives 84 - though they are running on the previous (0.17) version of libftdi. There is also the issue that the Mac library is x86_64 (i.e. 64-bit), while the Linux libraries are 32-bit.

One solution, though not exactly a purist's, is to allocate far more memory than will be needed. It won't slow anything down, as only a pointer to the start of the block is passed around, and probably won't make a difference to the memory consumption as applications will always use whole numbers of pages (4KB minimum) anyway. So for now, an allocation of 1KB seems a good solution.

The result of this is that the changes needed to the earlier code for compatibility with both Mac OS X and Linux are as follows:


from ctypes.util import find_library

def ftdi_start():
    global ctx, fdll  # frown... :P
    fdll = CDLL(find_library('libftdi'))
    # size of ftdi_context varies
    # seen 112 (x86_64, v0.18). 84 (i386, v0.17)
    # over-allocate to 1KB.
    ctx = create_string_buffer(1024)
    fdll.ftdi_usb_open(byref(ctx), 0x0403, 0x6001)
    fdll.ftdi_set_bitmode(byref(ctx), 0xFF, 0x01)


Me loves == 'posix' :-)

1 comment:

  1. I had to do one trick to get libftdi to build, since it seemed to be unable to find my boost library. This is to be done between "./configure" and "make", having previously run "sudo port install boost" in the libftdi-0.18 directory.

    ln -s /opt/local/include/boost .