Author Topic: Need getting-started example(s) using pyftdi to access GPIO and I2C on FT232H  (Read 1495 times)

0 Members and 1 Guest are viewing this topic.

Offline wb0gazTopic starter

  • Regular Contributor
  • *
  • Posts: 217
Not sure which section (of eevblog forum)  this question belongs in, so please advise if I should move this post to another area in eevblog forum. Although the host machine in this case would be a PC, the problem is likely very similar in context of a raspberry pi or similar embedded microcontroller using USB and FTDI232H multi-purpose interface IC.

I need to use a FTDI232H-based (multi-purpose) USB module to access (at separate moments in time) a few GPIO pins and an I2C bus (as a master of the I2C bus.) One complication is that I will need two FTDI232H modules attached to the host machine via USB (each using GPIO and I2C to access it's attached hardware.)

I have found pyftdi (a pure-python API for FTDI232H devices on a USB bus) which looks promising as a vehicle to interface from application program (Python) to the FTDI232H USB module(s).

The problem I'm having is that I've got only entry-level Python expertise, and pyftdi documentation provides no "worked examples", so I'm having real trouble understanding how to get started.

I'd be really grateful to find as example any sort of small program(s) that I could use to lead me through interfacing with pyftdi api set.

Thanks for any suggestions, requests for clarifying information, or other advice on this topic!

 

Offline MarkF

  • Super Contributor
  • ***
  • Posts: 2719
  • Country: us
I don't have anything to give you in Python.
But, I posted some C routines to access FTDI via its serial number.

  https://www.eevblog.com/forum/beginners/using-multiple-ft232h-devices-on-one-computer/msg5661229/#msg5661229
 

Offline tszaboo

  • Super Contributor
  • ***
  • Posts: 8096
  • Country: nl
  • Current job: ATEX product design
Not sure which section (of eevblog forum)  this question belongs in, so please advise if I should move this post to another area in eevblog forum. Although the host machine in this case would be a PC, the problem is likely very similar in context of a raspberry pi or similar embedded microcontroller using USB and FTDI232H multi-purpose interface IC.

I need to use a FTDI232H-based (multi-purpose) USB module to access (at separate moments in time) a few GPIO pins and an I2C bus (as a master of the I2C bus.) One complication is that I will need two FTDI232H modules attached to the host machine via USB (each using GPIO and I2C to access it's attached hardware.)

I have found pyftdi (a pure-python API for FTDI232H devices on a USB bus) which looks promising as a vehicle to interface from application program (Python) to the FTDI232H USB module(s).

The problem I'm having is that I've got only entry-level Python expertise, and pyftdi documentation provides no "worked examples", so I'm having real trouble understanding how to get started.

I'd be really grateful to find as example any sort of small program(s) that I could use to lead me through interfacing with pyftdi api set.

Thanks for any suggestions, requests for clarifying information, or other advice on this topic!
The examples are on their github:
https://github.com/eblot/pyftdi/tree/master/pyftdi/tests

Thanks for bringing this to my attention BTW!
 

Offline wb0gazTopic starter

  • Regular Contributor
  • *
  • Posts: 217
Thank you, MarkF - that helps, I'll look into the ftdi DLL driver stack (in lieu of pyftdi.)

tszaboo - I did look at several of those test routines, but they are almost totally opaque to me (in terms of the Python coding level) at this point given the state of my Python learning curve.

I'll need to assess the time cost of sufficiently conquering the Python learning curve.

Thanks to both of you for the quick and helpful replies!

Dave
 

Online NorthGuy

  • Super Contributor
  • ***
  • Posts: 3264
  • Country: ca
You can use FT2232H. It's practically two FT232H in one chip of about the same size and for about the same price.
 

Offline MarkF

  • Super Contributor
  • ***
  • Posts: 2719
  • Country: us
You can use FT2232H. It's practically two FT232H in one chip of about the same size and for about the same price.

It sounds to me that the OP has two separate devices that each need a USB interface?

From a software point of view, the FT2232 shows up as two devices each with its own serial number.

I have a USB module from DLP_Design with a FT2232 and a PIC16F877A where each channal is assigned its own serial number for accessing. 
Channel A is used for firmware programming of the PIC and channel B for I/O to/from the PIC.
 

Offline SiliconWizard

  • Super Contributor
  • ***
  • Posts: 15554
  • Country: fr
I have used pyftdi in one project for controlling a test board via Python (which was the easiest path, not requiring a microcontroller on the board, so no firmware, and allowing pretty much anyone to develop scripts for it).
It works rather well for that purpose.

Keep in mind a downside when using I2C with the FT232H/2232H chips. It's "very" slow. The reason is that each byte has an acknowledge bit, and acknowledges are handled in software (in the driver), meaning that it requires sending AND receiving one byte on the USB bus for each I2C byte transfered. That's kinda awful. So forget about it if throughput is a concern. But for controlling analog switches/relays/slow ADCs and DACs, slow sensors, etc, that's fine.
To get an idea of actual timings, just try it and look at I2C signals with a scope. You may not be happy.

I think FTDI has a better USB to I2C bridge chip if that's all you need (FT232H/2232H can do a lot of stuff, but I2C is definitely not what they do best), and it may be supported by pyftdi, so check this out.

Otherwise, here's a "short" sequence that should get you started with pyftdi using a FT232H:

Code: [Select]
import pyftdi
from pyftdi import i2c

try:
I2C_Controller = i2c.I2cController()
I2C_Controller.configure('ftdi://ftdi:232h:1/1', frequency = 100e3)
except Exception as e:
print(e)

Device = I2C_Controller.get_port(Slave_Address)

# For exchanging data with the I2C device, you have the 'exchange', 'write', 'write_to', 'read' and 'read_from' methods. You can refer to this for more details: https://eblot.github.io/pyftdi/api/i2c.html#pyftdi.i2c.I2cPort.read
# Example: reading 'nb_bytes' bytes from the device register at address 'register_address'. Returns an array of bytes:

Device.read_from(register_address, nb_bytes)

# When you're done, close the controller. Failing to do so may require unplugging/replugging the FT232H for reusing it later on, on some platforms.
I2C_Controller.close()

* Note that the device path 'ftdi://ftdi:232h:1/1' given here is what works if you only have one FT232H connected to USB, otherwise you can adapt it to your particular case. For listing all connected and supported devices, you can use this:

Code: [Select]
from pyftdi.ftdi import Ftdi

Ftdi.show_devices()

* Slave_Address is the slave address of the I2C device you want to communicate with. Note that you can access several devices with the same I2C controller by just calling I2C_Controller.get_port(Slave_Address) with other addresses, which would be the proper way to use it.

* I've tested it on Linux, macOS and Windows. Note that on macOS, you may have to go through some hoops to install pyftdi properly.

Hope that helps.
« Last Edit: November 09, 2024, 09:04:45 pm by SiliconWizard »
 

Offline adeuring

  • Contributor
  • Posts: 26
  • Country: de
pyfti is unfortunately not very well documented but it works quite well once you understand how it works. So it is perhaps worth to put some effort into getting comfortable with it.

Aside from tszaboo's suggestion to look into tests (located in the directory pyftdi/tests in the Github repo), there is also some documentation available: https://eblot.github.io/pyftdi/ . The I2C API is explained here: https://eblot.github.io/pyftdi/api/i2c.html . The example at the start of the page should give you a better idea how to access your I2C peripherals.

You will most likely have to change the call parameter in  the 4th line in the example:

Code: [Select]
    i2c.configure('ftdi://ftdi:2232h/1')
Run the script ftdi_urls.py from the pyftdi package to find the URIs of your adapters.

The two most important methods of the class I2cPort (used to access an I2C chip) are probably write_to() and read_from().

Oh, there is also the script i2cscan.py. Quite useful to quickly check if the I2C chips are properly wired to the FT232H.

[Pause writing this reply....]

Looking a bit closer at the "quickstart" section of https://eblot.github.io/pyftdi/api/i2c.html I notice that the example is not complete: At least an import statement is missing. So here is a complete and tested example. I used an FT232H, an AT24C02 EEPROM and an ADS1115 DAC. The script writes four random pytes into the EEPROM, triggers one conversion in the ADS1115 and shows the conversion result.

Code: [Select]
from pyftdi.i2c import I2cController
from random import randbytes
import sys
from time import time


def eeprom_example(i2c):
    """Write four random bytes at the memory address 0x23.
    Print the contents at the memory addresses 0x23..0x26 before and after
    the wrte operation.
    """
    # Use low-level access (methods read(), write()) to access a 24C02 EEPROM.
    eep = i2c.get_port(0x50)

    # Address of the first byte to read/write within the EEPROM:
    addr = b'\x23'

    eep.write(addr)
    print('Current content:', eep.read(4))

    new_content = randbytes(4)
    print(
        f'Changing EEPROM content at address {addr[0]:02x}x to '
        f'{new_content}')
    eep.write(addr + new_content)

    eep.write(addr)
    print('New content reads:', eep.read(4))

def ads1115_example(i2c):
    """Very simple and naive access to an ADS1115 ADC.
    """
    adc = i2c.get_port(0x48)

    # Write to register 1, the config register (16 bits, high byte comes
    # first)
    # bit 15, value 1: Start a conversion.
    # bits 12..14: Which input to use. Value 0: Pin AIN1.
    # bits 9..11: gain amplifier setting. Value 0: 6.144V FSR.
    # bit 8: continuous (0) or single-shot (1) mode.
    # bits 5..7: Conversion rate. Value 0 -> 8 conversion per second.
    config = b'\x81\x00'
    adc.write_to(1, config)
    started = time()
    while True:
        # wait until the converion is complete: Bit 15 of the config register
        # is 1.
        status = adc.read_from(1, 2)
        if status[0] & 0x80 != 0:
            break
    print('Conversion time:', time() - started)
    raw_value = adc.read_from(0, 2)
    print('Raw ADC value', raw_value)
    print('ADC value as an integer', int.from_bytes(raw_value, 'big'))

def main():
    i2c = I2cController()
    i2c.configure(sys.argv[1])
    try:
        eeprom_example(i2c)
        ads1115_example(i2c)
    finally:
        # This is important: Without this call, the next start of the
        # script may end with an exception:
        # "pyftdi.i2c.I2cNackError: NACK from slave"
        i2c.terminate()

if __name__ == '__main__':
    main()

Save the code in a file, lets say "ftdi_example.py", and invoke is as:

python ftdi_example.py ftdi://ftdi:232h:1:13/1

Replace the FTDI URL with a URL you get from running ftdi_urls.py.


Note that the code is not very useful as a starting point to really use an EEPROM or an ADS1115 in a real application: My main point was to show how pyftdi can be used.

Many I2C EEPROMs have a really messy addressing concept: Chips with a memory size of more that 2 kBit and less that 32kBit put the upper bits of the memory address into the I2C address. In other words: They appear at more that one address when you run ftdi_urls.py. To access larger chips (32 kBit and more), two memory address bytes are written after the on the I2C bus after the I2C address itself.

Similarly it makes sense to use a proper class to control the ADS1115: The chip has a few config options (gain, single ended/differential mode, conversion rate...) that deserve a proper class to control the chip.
 

Offline MarkF

  • Super Contributor
  • ***
  • Posts: 2719
  • Country: us
Depending on your skill level, you might consider starting where I did years ago.  With a USB module from DLP Design.  Their latest version has a small microcontroller that can do the I2C in hardware and has a few extra pins for discrete I/O.  This would off-load all the handshake timing from the PC and just allow it to do read and write requests.

   https://www.dlpdesign.com/usb/232PC.php

I ending up spinning my own module with a larger microcontroller and FT245R to meet my needs.

They use command token to request I/O from the microcontroller.  It is easy to build on what they have for more complex data transfers.  At one time, you could download their firmware after purchase (I assume that is still possible).

Even if you don't use them, it might be a good guide for your own interface.
 

Offline shabaz

  • Frequent Contributor
  • **
  • Posts: 506
I had a similar need a few months back, and created a Pi Pico adapter. It initially didn't support GPIO, but the code was so simple, I have now added it and uploaded it to GitHub. Maybe it's an option if the FTDI device isn't essential.

The steps are:

(1) Buy some Pi Pico's, and hold down the button on them while inserting the USB connection, then release the button, and you'll be in boot mode, you'll see a drive letter appear, like a USB flash drive.

(2) Drag-and-drop the executable from the pre_built_binary folder at the GitHub page.

(3) After a few seconds, the code will be uploaded, and you'll know it is running successfully, because the green LED on the Pi Pico will permanently blink very rapidly.

Now you can wire it up as shown in the attached diagram.

To use it, either open a serial console terminal, and type things such as shown in the serial-console-example.jpg screenshot, or, alternatively, download the easyadapter.py file from the GitHub page python_pc_interface folder, and then type the following from a command line:

Code: [Select]
pip install pyserial
The, run Python, and you can control I2C using commands like:

Code: [Select]
import easyadapter as ea
firstAdapter = ea.EasyAdapter()
secondAdapter = ea.EasyAdapter()
firstAdapter.init(0)
secondAdapter.init(1)
buffer = firstAdapter.i2c_read(0x5a, 4)
data = [0xaa, 0xbb, 0xcc]
secondAdapter.i2c_write(0x5b, data[0], data[1:])

The above example assumes that two Pico boards are connected, and that you want to use the I2C interface on the first Pico board, to read four bytes from address 0x5a, and use the second Pico board to write three bytes to address 0x5b.  Notice that with the write operation, the i2c_write function expects the first data byte to be separate, because that's often convenient for using the first byte as a register address.

To distinguish between different Pi Pico boards, use the BOARD_ID pins shown in the connection diagram (GPIO pins 4,3,2). They float high, and that's an ID value of 0 as used in the firstAdapter.init(0) Python code above. If you short GPIO2 to ground, then that's an ID value of 1.

To control GPIO from Python, the code would look like the following, to set GPIO6 to logic level 1:

Code: [Select]
firstAdapter.io_write(6,1)

To read the GPIO level:
Code: [Select]
val = firstAdapter.io_read(6)

The io_read and io_write functions automatically convert the GPIO pin to an input or an output respectively.

Main downside with this project is that it's still very fresh, there are bound to be errors. On the plus side, Pi Pico boards are so popular, and the code is so tiny, that if there's an issue, I'm sure myself, or others could help if there's any major issue.

The extent of my testing is reading and writing a couple of simple I2C devices, and controlling a couple of GPIO pins (reading and writing them), with two Pico boards.

Another limitation currently, is that you can't read/write more than a couple of hundred bytes at a time (so if you're reading/writing flash memory for instance, then you'll need to ensure the operations are split up into suitable chunks).

« Last Edit: November 10, 2024, 01:37:38 am by shabaz »
 

Offline bson

  • Supporter
  • ****
  • Posts: 2479
  • Country: us
Here's a complete hack I did to talk to an MCP7940N (battery backed RTC) over I2C using an FT232 adapter. (The current AdaFruit one, but I don't think there's anything special about that.)  Ignore the comment; it's just to enable the LEDs to show activity on that particular board I used.

It IS a complete hack.  Total throw-away code.  But maybe you can mine it for something useful.

 

Offline wb0gazTopic starter

  • Regular Contributor
  • *
  • Posts: 217
I *really* appreciate the discussion - sorry I didn't come back to this right away, but each posting has been helpful and provides valuable perspective.

As for speed of I2C transfers - this is a *very* non-speed-critical application (maybe a few dozen bytes ten times per second at most.)

As for the 2232 2-port device, I wasn't clear in my original posting (of course), however, the application has two devices, each at the end of a USB cable (one is short, local to the PC, the other is going thru a CAT5 type extender about 100' away.) I thought of pushing I2C thru the CAT5, but I'm trying get as close to "buy these parts and run this simple program under windows and you're good to go" type thing.

As for working on getting to know and understanding pyftdi --- I am basically heading down that path. Today I took the i2cscan.py utility, made a copy of it, and started (with difficulty - I don't speak python at all fluently) chopping it down (fix the device URL, get rid of various options, tracing/logging, etc.), then add as many comments as I could comprehend, and after a couple of hours at that, I am down to something with about 25 lines of code that i think I can follow (and it can detect the presence of a PCA9554N I connected to SCL/SDA with pull-ups.) At this point, I'm  pausing (a few days) until I receive the project's intended I2C peripheral devices, then I'll pick up where I left off, trying to extend the crunched-down version if i2cscan to actually send and receive a few bytes.

So, again thanks for the very helpful and encouraging discussion! eevblog forum participants are way easier to understand than the code in pyftdi!!!
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf