Python Class for Fast USB-based Digital I/O
Using an FTDI USB-to-Parallel I/O Device
(Windows 64-bit)


Introducing the FTDI UM245R USB-to-Parallel Interface
UM245R Data Sheet      FT245R Data Sheet

A simple, reliable, fast and inexpensive approach to reading and writing to an 8-bit digital I/O interface can be achieved using the FTDI FT234R USB-to-Parallel interface module (cost: approximately $20).  A photograph of the UM245R development module (which contains a FT245R device with easily accessible pin outs) is shown in Figure 1. This device allows the developer to quickly read and/or update the states of 8 digital I/O lines over a USB interface.  The FTDI drivers and programming API are free of charge and easily downloadable from the FTDI website.  The 8 I/O lines are depicted as pins DB0-DB7 in Figure 2.  The directionality (i.e., input vs. output functionality) of these 8 lines is completely programmable (i.e., any combination of input and output lines can be configured by the user).  Detailed setup instructions and python code for accessing this device are presented below.

Figure 1.
FTDI's UM245R Development Module

Figure 2.
UM245R pin descriptions and layout

Where to Purchase UM245R Module     Mouser Electronics     Newark

Computer Setup
The FTDI USB driver installation package can be download an run from the following link: FTDI Driver Downloads
Detailed information about the FTDI driver installation process can be found at FTDI Driver Installation Guidelines

Step-by-step installation on Windows 7/10/11 computer:

(1) Visit the FTDI DX22 Drivers web page and download the Windows (Desktop) 'setup executable' (e.g., CDM212364_setup.zip)
(2) Unzip the downloaded installer to expose the setup executable (e.g., 'CDM212364_setup.exe')
(3) Run the .exe installer program in administrator mode (i.e., As Administrator)
(4) Connect the UM245R device to an available USB port on your computer
(5) Open the Device Manager to verify the successful installation of the device:
   (A) Under the Ports (COM & LPT) category:
         You should find a COM port labelled as 'USB Serial Port (COMn)' where 'n' is arbitrary.
         Double-click on this device to access the Properties interface and check the 'Device status' message.
   (B) Under the Universal Serial Bus Controllers category:
         You should find a controller interface labelled 'USB Serial Converter'.
         Double-click on this device to access the Properties interface and check the 'Device status' message.
At this point, all of the necessary FTDI-specific ssoftware has been installed.  Note, however, that additional python software will also need to be downloaded and installed.

Required Python Software:

Install the python module which allows access to the FTDI D2XX software library (i.e., ftd2xx 1.3.1)
    e.g., python -m pip install ftd2xx
   NOTE: This module is already installed if you downloaded the standard PsychoPy software suite.
Download UM245R.py and move it to a directory/folder in your PATH.
Follow the examples below to test the functionality of the Python UM245R USB interface. Enjoy!
Sample um245 script:   um245-Module-Test
Demo scripts with ftd2xx WITHOUT using um245 class: USB-Output-Test   USB-INPUT-Test

um245() Command Usage Summary:

device = um245(devno=0, iomode=0xf0) Calling um245() with no input arguments creates a persistent Python object that can be used to interact with the USB-based UM245R device.  The iomode argument is an 8-bit integer value that is used to configure the mode (input or output) of each of the 8 I/O lines of the device. The least significant bit of iomode defines the mode of line D0 while the most significant bit specifies the mode of line DB7.  If the bit representing a given I/O line is set to '1' then that line will be configured as an output; if zero, the line will be configured as an input.  For example: setting iomode to 0 (binary: 00000000) would configure all of the I/O lines as inputs.  Setting iomode to 255 (binary: 11111111) would configure all of the I/O lines as outputs.  Setting iomode to 240 (binary: 11110000) would configure lines DB0-DB3 as inputs and lines DB4-DB7 as outputs.  Setting iomode to 15 (binary: 00001111) would configure lines DB0-DB3 as outputs and lines DB4-DB7 as inputs.  The devno=0 argument specifies the first FTDI device discovered upon quering the operating system.  On systems with only one FTDI device the devno will always be 0.  The devno parameter is used to select a specific device when multiple FTDI devices are installed on the system.
iomap = device.getIOMap() um245.getIOMap() returns a Python dictionary that reports the mode (input vs. output) of each of the device's 8 I/O lines.
e.g.  {0:'input' 1:'input' 2:'input 3:input 4:'output' 5:'output' 6:'output' 7:'output'}
iomode = device.getMode() um245.getMode() returns an 8-bit integer value. Each bit of this value denotes the functional mode of its corresponding I/O port line.  A bit value of '0' represents an input line while a bit value of '1' represents an output line.  Use format(iomode, '#010b') to display the 8-bit integer return value in binary format.
device.output(data) um245.output(data) updates the device's 8 I/O lines based upon the 8-bit value (0-255) contained in the data argument.  Only lines previously configured as outputs are affected (The states of input lines remain unaffected).
data = device.input() um245.input() scans the 8 I/O lines and returns a single 8-bit value representing the instantaneous state of the 8 lines. 
status = device.isDeviceOpen() Returns a boolean value of True if the UM245R device is properly connected (otherwise returns False)
devs = device.getDeviceList() Returns a list of bytearray-formatted strings ( e.g., [b'FT9LH03L'] ) representing the serial number(s) of all FTDI devices installed on the system.  If no FTDI devices are found the um245.ftdiOpen class attribute is set to False.
info = device.getDeviceInfo(devno=0) Returns a directory of operational parameter names and their corresponding values for the FTDI device specified by the devno argument. e..g., { 'index':0, 'flags':0, 'type':5, 'id':67330049, 'location':25, 'serial':b'FT9LH03L', 'description':b'UM245R', 'handle':c_word_p(None) }
device.close() Closes the connection between the um245 object and the FTDI device.



The following Python code snippet demonstrates how to use the um245() class:

#load the code extension module
from UM245R import um245 
# create a um245 interface object using standard default parameters
#     devno = 0 --> Uses first FTDI device enumerated during search of the computer
#     iomode = 0xf0 --> bits 4-7 = outputs, bits 0-3 = inputs
device = um245(); 
# verify successful connection to FTDI device (i.e., status = True)
status = device.isDeviceOpen()
print('Device ready = {}'.format(status))

# let's try reading an 8-bit value from the UM245R device
data = device.input()
print('Current state of I/O lines = %s' % format(data,'#08b'))

#
#  now let's activate the most significant bit on the data port (bit-7)
data = data | 128          #use bitwise OR operator to set most significant bit
device.output(data );   #update state of I/O lines
print('Updated state of I/O lines = %s' % format(data,'#08b'))
 

Parsing Individual Bits within an I/O Byte

When one reads an I/O port one is usually interested in the status of a single bit among the 8-bits returned by a call to <um245>.input(). Python provides a number of operators to deal with data on a 'bitwise' basis.  Some examples follow:

Activate Output Line DB7:

from UM245R import um245      #load software interface to FTDI UM245R device (i.e., um245() class)
#create interface object specifying that I/O port bits(0-3) are inputs and bits(4-7) are outputs
usb = um245(iomode = 0b11110000)
#read current state of I/O port lines
ioport = usb.input()
#use bitwise OR operator to set bit-7 (corresponding to DB7 output line)
ioport = ioport | 0b10000000
#update I/O port thus activating DB7 output line (but leaving all other bits unaffected)
usb.output(ioport)

Deactivate Output Line DB7:

from UM245R import um245      #load software interface to FTDI UM245R device (i.e., um245() class)
#create interface object specifying that I/O port bits(0-3) are inputs and bits(4-7) are outputs
usb = um245(iomode = 0b11110000)
#read current state of I/O port lines
ioport = usb.input()
#use bitwise AND operator to reset bit-7 (corresponding to DB7 output line)
ioport = ioport & 0b01111111
#update I/O port thus de-activating the DB7 output line (but leaving all other bits unchanged)
usb.output(ioport)

 

 Test the State of Digital Input Line DB0:

 #read current state of the 8 I/O port lines
ioport = usb.input()
#use a mask with a bitwise AND operator to isolate I/O port input DB0
state = ioport & 0b00000001
#check to see if DB0 input line is active (i.e., ON = logic '0' = input line 'grounded')
if state == 0:
    DB0_active = True   #DB0 is active because it is at logic level-0
else:
    DB0_active = False  #DB0 is inactive because it is at logic level-1 (internal pull-up resistor)

Search Computer for Installed FTDI USB Devices:   find_ftdi.py

Fig. 5: Sample Setup for Testing the UM245R Hardware [Sample circuit: 12v solenoid driver]

Python Code for Exercising the Test Circuit

from UM245R import um245
import time

#create interface to UM245R with DB0-3 = inputs and DB4-7 = outputs
usb = um245(iomode = 0b11110000)
#turn-on LED by making DB7 output level go 'high'
usb.output(128)
#wait for 3 seconds
time.sleep(3.0)
#turn-off LED by making DB7 output level go 'low'
usb.output(0)
#
#after reading the state of the I/O port determine the state
#of the Test Switch connected to the DB0 input line
ioport = usb.input()
if (ioport & 0b00000001) == 0:
    print('The Test Switch is CLOSED (i.e., DB0 is grounded)')
else:
    print('The Test Switch is OPEN')


Simple UM245R Timing Benchmark
HP ZBook G2 Laptop; i7-4810MQ CPU 2.8 GHz; Windows 10 Pro (Build 19045.2251)

Code snippet:

import time
from UM245R import um245
usb = um245()   #create interface to UM245R device
times = []
for i in range(500):  #500 iterations of input timing measurement
    startTime = time.perf_counter()   #read system clock (secs)
    data = usb.input()   #fetch input from UM245R device
    endTime = time.perf_counter()    #read system clock (secs)
    latency = (endTime-startTime) * 1000  #compute input read latency (millisec)
    times[i] = latency   #save result

Benchmark Results:

Plot of input latency distribution

Last revised: 10 December 2022


Python for Psychologists Page