eblot / pyftdi

FTDI device driver written in pure Python
Other
509 stars 212 forks source link

eeprom.dump_config() cannot dump EEPROM configuration when two devices are attached #335

Open gkamendje opened 1 year ago

gkamendje commented 1 year ago

I have two devices with a FT232H chip (Adafruit FT232H and Digilent Usb Device ) attached to my computer (Win10). The available interfaces are listed as follow

Available interfaces:
  ftdi://ftdi:232h:1/1              (￿￿￿￿￿￿)
  ftdi://ftdi:232h:210321B80F0A/1   (Digilent USB Device)

I use the following code to try to access the EEPROM of the Adafruit board

# Instantiate an EEPROM manager
eeprom = FtdiEeprom()

# Select the FTDI device to access (the interface is mandatory but any
# valid interface for the device fits)
eeprom.open('ftdi://ftdi:232h:1/1')

# Show the EEPROM content
eeprom.dump_config()

# Show the raw EEPROM content
print(hexdump(eeprom.data))

I use the URL ftdi://ftdi:232h:1/1 since it was the one reported for the Adafruit board. However, when I run this piece of code, I get the EEPROM content of the Digilent device. The only way I can get the content of the Adafruit is by disconnecting the Digilent Device. I guess there is something I am missing regarding the usage of URL.

eblot commented 1 year ago

Hi,

Windows USB stack is a gigantic pile of crap, and I'm always amazed that some great devs managed to plug libusb on top of this... I would definitely -not- recommend to use pyftdi eeprom management on Windows. You really want to use the native FT_PROG application from FTDI chip manufacturer to mess with the EEPROM content. It is definitely more reliable, and far more fitted to this sh.tty OS.

About URLs (ftdi://vendor:product[:n|:bus:address|:serial]/port):

Unfortunately, Windows does not expose any bus/address concept, and if you do not have a serial number or a corrupted EEPROM, you are out of luck. In this case the only remaining solution is to keep a single device plug to your computer so you can be sure which device is addressed. I would definitely not trust any USB device selection through libusb on Windows, as you might end up accessing the wrong device otherwise...

Again, on Windows, I'd really recommend to use FT_PROG. Do not use PyFTDI to update your EEPROM, you might really corrupt it. Do not use it with FT232R or FT230X device that use internal EEPROMs, as you might brick your FTDI chip.

gkamendje commented 1 year ago

Hi thanks for your answer, I was wondering how to retrieve the bus: address information from the command line. In Linux when running lsub i get the following

Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 001 Device 004: ID 0403:6014 Future Technology Devices International, Ltd FT232H Single HS USB-UART/FIFO IC
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

And in windows (with WSL installed) running usbipd wsl list I get

BUSID  VID:PID    DEVICE                                                        STATE
1-2    0403:6014  USB Serial Converter                                          Not attached
1-5    0403:6014  USB Serial Converter                                          Not attached
1-7    8087:0a2b  Intel(R) Wireless Bluetooth(R)                                Not attached
1-8    138a:003f  Synaptics FP Sensors (WBF) (PID=003f)                         Not attached
1-9    05c8:0397  HP HD Camera                                                  Not attached

Is there a way to retrieve the bus:address information that would be consistent in both Window and Linux?

BTW is there a way to generate signals on the GPIOs with a period in the range between 10us and 200us using the pyftdi library? I have tried using something like the code below

PIN_4 =0x010
PIN_5 =0x020
PIN_6 =0x040
PIN_7 =0x080
USED_PINS = (PIN_4 | PIN_5 | PIN_6 | PIN_7)
PINS_STATE  = 0x00
gpio = GpioAsyncController()
gpio.configure(URL, direction=0x070)
iter_count = 10000
PINS_STATE =0x00

for x in range(iter_count):
   # set Pin4
   PINS_STATE = PINS_STATE | PIN_4
   ins = gpio.write(PINS_STATE)
   sleep(50/1000000)
   # clear Pin4
   PINS_STATE = PINS_STATE & ( PIN_5 | PIN_6 | PIN_7 ) 
   ins = gpio.write(PINS_STATE)
   sleep(50/1000000)
   PINS_STATE =0x00
ins = gpio.write(PINS_STATE)
gpio.close() 

However the period is way higher (in the ms range) than what I was expecting.

eblot commented 1 year ago

Hi thanks for your answer, I was wondering how to retrieve the bus: address information from the command line.

In Linux, it should match the current bus/device values. However, remembers bus/device (or bus:address) values are transient, i.e. they may change every time you plug out and plug back a device, and restart your host. Usually on Linux if you unplug and replug into the same USB slot, the bus/device values are the same, but I'm not sure this is 100% reliable.

The only reliable way to select a specific device if multiple similar ones are connected to a host is to identify them with their serial number.

And in windows (with WSL installed) running usbipd wsl list I get

I have really no idea on Windows, but I would be surprised that Windows is able to enumerate the same device in the same way each time, at it has never been able to do so since USB has been implemented several decades ago.

Is there a way to retrieve the bus:address information that would be consistent in both Window and Linux?

No, as it depends on when and where you plug your USB devices, and how the USB stack enumerates them.

BTW is there a way to generate signals on the GPIOs with a period in the range between 10us and 200us using the pyftdi library? I have tried using something like the code below

Remember that your FTDI device is connected over a USB bus, which is typically using 1 ms cycle. I.e. it is likely that two FTDI packets are sent in two distinct USB frame, that is at best 1ms appart from each other. Worse scenario is that the USB SW stack may take time to forward the packet (pyftdi->pyusb (+python interpreter) -> libusb -> kernel -> usb driver -> usb host HW (+FIFOs) -> USB device (+FIFO) -> FTDI engine -> FTDI GPIO ....) so you might even miss a dummy USB frame and get a 2 ms delay for example.

Most of the FTDI features are definitely not designed to support real time application, i.e. there is no way to guarantee a reproducible delay between two commands.

  1. using legacy commands (such as CBUS or set pins), it is likely that 1 ms or more USB frames are used to convey commands
  2. using MPSEE commands, you could bufferize several commands into a buffer and send a single buffer with multiple commands into a unique USB frame, which would translate into a real-time I/O sequence on FTDI output ... for a single buffer. The next buffer would require another USB frame, which may be tricky to send at the right pace so that it can be bufferized on the FTDI FIFO before the previous command is fully processed. I do not think this is achievable. PyFTDI does not support this kind of feature
  3. using synchronous FIFO mode, it might be possible, but PyFTDI does not implement this mode. Maybe the native FTD2XX library support synchronous FIFO, I do not know.

However the period is way higher (in the ms range) than what I was expecting.

Yes, this is due to the USB protocol, and PyFtdi do not implement mitigations for this. There is no plan to do so (that would require a lot of work, and FTDI MPSEE is really a legacy hardware. There are very cheap micro controller or even FGPA boards available that could much better than an expensive FTDI... :-)