Search code examples
c#python-3.xlinuxftdid2xx

Reading from FTDI D2XX device using Python3 on Ubuntu 20.04


I am working with an FTDI device that has native software for Windows, but nothing available for Linux. I am trying to read data from the device using pylibftdi. I would like to translate C# code that is provided by the device manufacturer and purportedly works (unclear if this is true) but have not been successful. So far I have done the following:

  1. Installed the Linux D2XX drivers based on these instructions. Installation was successful.

  2. Followed the directions here and here to enable the FTDI device to connect to the Linux system.

  3. After plugging the FTDI device into the Linux system USB port:

$ lsusb
Bus 006 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 005 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 004 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 001 Device 003: ID 046d:c52b Logitech, Inc. Unifying Receiver
Bus 001 Device 002: ID 04f2:0833 Chicony Electronics Co., Ltd KU-0833 Keyboard
**Bus 001 Device 006: ID 0403:6001 Future Technology Devices International, Ltd FT232 Serial (UART) IC**
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

The bolded device (Bus 001 Device 006: ID 0403:6001) is the device from which I would like to read.

  1. Then installed pylibftdi and verified that the device was readable via the pylibftdi API:
$ python3
Python 3.9.5 (default, Jun  4 2021, 12:28:51) 
[GCC 7.5.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys, pylibftdi as ftdi
>>> print(ftdi.Driver().list_devices())
[('FTDI', 'Cognionics Quick-20 20CH 1706Q20N', 'AI2SUN90')]
  1. As is clear, the device is connected and recognized. However, when I try to read from the device, I receive empty arrays:
>>> d = ftdi.Device()
>>> vars(d)
{'_opened': True, 'driver': <pylibftdi.driver.Driver object at 0x7fd819320910>, 'fdll': <CDLL 'libftdi.so.1', handle 557bc3ca6560 at 0x7fd8190aee80>, 'device_id': None, 'mode': 'b', 'encoding': 'latin1', 'encoder': <encodings.latin_1.IncrementalEncoder object at 0x7fd819320a60>, 'decoder': <encodings.latin_1.IncrementalDecoder object at 0x7fd8190aefd0>, '_baudrate': 9600, 'interface_select': None, 'device_index': 0, 'list_index': None, 'ctx': <ctypes.c_char_Array_1024 object at 0x7fd819342c40>}
>>> d.read(100)
b''
>>> d.read(100)
b''
>>> d.read(100)
b''
  1. This C# code (provided by the manufacturer) purportedly works, but I haven't been able to test it. It seems like the easiest approach would be to translate this into python but even that is challenging as I do not know how to replicate the constants and ftdi function calls that are being used. The provided C# code is:
UInt32 ftDevCount = 0;
ftStatus = ftDev.GetNumberOfDevices(ref ftDevCount);

ftdiDeviceList = new FTDI.FT_DEVICE_INFO_NODE[ftDevCount];
ftStatus = ftDev.GetDeviceList(ftdiDeviceList);
String[] deviceNames = new String[ftDevCount];

for (int c = 0; c < ftDevCount; c++)
{
deviceNames[c] = ftdiDeviceList[c].Description.ToString();
}

Connecting to a device and configuring the serial port settings

if (ftDev.OpenBySerialNumber(ftdiDeviceList[devID].SerialNumber) == FTDI.FT_STATUS.FT_OK)
{    ftDev.SetFlowControl(FTDI.FT_FLOW_CONTROL.FT_FLOW_RTS_CTS, 0x11, 0x13);
    ftDev.SetDataCharacteristics(FTDI.FT_DATA_BITS.FT_BITS_8, FTDI.FT_STOP_BITS.FT_STOP_BITS_1, FTDI.FT_PARITY.FT_PARITY_NONE);
    ftDev.SetLatency(2);
    ftDev.SetBaudRate((uint)3000000);

    connectedName = ftdiDeviceList[devID].Description.ToString();

    return true;
}
else
{
    return false; //failed to open!

}

public byte ReadByte()
{
    UInt32 bytesRead = 0;
    byte[] t_data = new byte[1];
    ftDev.Read(t_data, 1, ref bytesRead);
    return t_data[0];
}

public void WriteByte(byte dat)
{
    UInt32 bytesWritten = 0;
    byte[] data = new byte[1];
    data[0] = dat;
    ftDev.Write(data, 1, ref bytesWritten);
}

//wait for sync byte 0xFF
while (byteInterface.ReadByte() != 255) {};

//read packet counter
int packetCount = byteInterface.ReadByte();

//read the 20 EEG channels
int NumEEG = 20;
for (int c = 0; c < NumEEG; c++)
{
    msb =  byteInterface.ReadByte();
    lsb2 = byteInterface.ReadByte();
    lsb1 = byteInterface.ReadByte();

    int tempEEG = (msb << 24) | (lsb2 << 17) | (lsb1 << 10);
}

int NumACC = 3;
//read the 3 ACC channels
for (int c = 0; c < NumACC; c++)
{
    msb =  byteInterface.ReadByte();
    lsb2 = byteInterface.ReadByte();
    lsb1 = byteInterface.ReadByte();

    int tempACC = (msb << 24) | (lsb2 << 17) | (lsb1 << 10);
}


//read packet tail
int impStatus = byteInterface.ReadByte();

//read battery voltage
int batteryByte = byteInterface.ReadByte();

//read trigger
int trigger = (byteInterface.ReadByte()<<8) + byteInterface.ReadByte();
  1. Based on the documentation in pylibftdi github repo, I can find some wrapper function calls as well as a few constants, but I am unaware of how to turn just the setup snippet, for example, :
ftDev.SetFlowControl(FTDI.FT_FLOW_CONTROL.FT_FLOW_RTS_CTS, 0x11, 0x13);
ftDev.SetDataCharacteristics(FTDI.FT_DATA_BITS.FT_BITS_8, FTDI.FT_STOP_BITS.FT_STOP_BITS_1, FTDI.FT_PARITY.FT_PARITY_NONE);
ftDev.SetLatency(2);
ftDev.SetBaudRate((uint)3000000);

into something in python. I think I can reset the baudrate using d.baudrate = 3000000, and I can change the latency timer using d.ftdi_fn.ftdi_set_latency_timer(2) but I do not know how to set the data characteristics, what the constants mean (FTDI.FT_DATA_BITS.FT_BITS_8, etc.), and how to set the flow control identically to the C# code.

  1. Other SO posts have also referred to the D2XX programmers guide found here but didn't see a way to apply it to this problem

Any suggestions would be appreciated.


Solution

  • As response to the comment and as an follow up answer:

    I cannot confirm the claim that D2XX is more reliable than the VCP from personal experience and the second one is only partially correct: one can e.g. use the VID:PID combination in most cases IIRC.

    I would highly recommend to stick to the easier VCP + pyserial solution. But if you really want (or need to) use pylibftdi, you can take a look at https://github.com/codedstructure/pylibftdi/blob/4662ebe069eefd5a89709d4165e3be808cad636c/docs/advanced_usage.rst - it describes how to access none exposed functionality directly. The naming is slightly different e.g. ftdi_setflowctrl instead of SetFlowControl but you will figure it out. Just check https://www.intra2net.com/en/developer/libftdi/documentation/ftdi_8c.html .