pylibftdi / pylibftdi /

pylibftdi - python wrapper for libftdi

Copyright (c) 2010-2012 Ben Bass <>
See LICENSE file for details and (absence of) warranty



from pylibftdi.driver import Device, FtdiError, BITMODE_BITBANG
from ctypes import c_ubyte, byref


class BitBangDevice(Device):
    simple subclass to support bit-bang mode

    Internally uses async mode at the moment, but provides a 'sync'
    flag (defaulting to True) which controls the behaviour of port
    reading and writing - if set, the FIFOs are ignored (read) or
    cleared (write) so operations will appear synchronous

    Adds two read/write properties to the base class:
     direction: 8 bit input(0)/output(1) direction control.
     port: 8 bit IO port, as defined by direction.
    def __init__(self,
        # initialise the super-class, but don't open yet. We really want
        # two-part initialisation here - set up all the instance variables
        # here in the super class, then open it after having set more
        # of our own variables.
        super(BitBangDevice, self).__init__(device_id=device_id,
        self.direction = direction
        self.sync = sync
        self.bitbang_mode = bitbang_mode
        self._last_set_dir = None
        # latch is the latched state of output pins.
        # it is initialised to the read value of the pins
        # 'and'ed with those bits set to OUTPUT (1)
        self._latch = None
        if not lazy_open:

    def open(self):
        "open connection to a FTDI device"
        # in case someone sets the direction before we are open()ed,
        # we intercept this call...
        super(BitBangDevice, self).open()
        if self.direction != self._last_set_dir:
            self.direction = self._direction
        return self

    def read_pins(self):
        read the current 'actual' state of the pins

        :return: 8-bit binary representation of pin state
        :rtype: int
        pin_byte = c_ubyte()
        res = self.ftdi_fn.ftdi_read_pins(byref(pin_byte))
        if res != 0:
            raise FtdiError("Could not read device pins")
        return pin_byte.value

    def latch(self):
        latch property - the output latch (in-memory representation
        of output pin state)

        Note _latch is not masked by direction (except on initialisation),
        as otherwise a loop incrementing a mixed input/output port would
        not work, as it would 'stop' on input pins. This is the primary
        use case for 'latch'. It's basically a `port` which ignores input.

        :return: the state of the output latch
        if self._latch is None:
            self._latch = self.read_pins() & self.direction
        return self._latch

    def latch(self, value):
        self.port = value  # this updates ._latch implicitly

    # direction property - 8 bit value determining whether an IO line
    # is output (if set to 1) or input (set to 0)
    def direction(self):
        get or set the direction of each of the IO lines. LSB=D0, MSB=D7
        1 for output, 0 for input
        return self._direction

    def direction(self, dir):
        if not (0 <= dir <= 255):
            raise FtdiError("invalid direction bitmask")
        self._direction = dir
        if not self.closed:
            self.ftdi_fn.ftdi_set_bitmode(dir, self.bitbang_mode)
            self._last_set_dir = dir

    # port property - 8 bit read/write value
    def port(self):
        get or set the state of the IO lines.  The value of output
        lines is persisted in this object for the purposes of reading,
        so read-modify-write operations (e.g. drv.port+=1) are valid.
        if self._direction == ALL_OUTPUTS:
            # a minor optimisation; no point reading from the port if
            # we have no input lines set
            result = self.latch
            if self.sync:
                result = self.read_pins()
                # the coercion to bytearray here is to make this work
                # transparently between Python2 and Python3 - equivalent
                # of ord() for Python2, a time-wasting do-nothing on Python3
                result = bytearray(super(BitBangDevice, self).read(1))[0]

            # replace the 'output' bits with current value of self.latch -
            # the last written value. This makes read-modify-write
            # operations (e.g. 'drv.port |= 0x10') work as expected
            result = ((result & ~self._direction) |    # read input
                      (self.latch & self._direction))  # output latch
        return result

    def port(self, value):
        self._latch = value
        if self.sync:
        return super(BitBangDevice, self).write(chr(value))