eblot / pyftdi

FTDI device driver written in pure Python
Other
501 stars 211 forks source link

Wrong "spi.gpio" pin direction reconfig, and fix suggestion ! #233

Open Bnjamn opened 3 years ago

Bnjamn commented 3 years ago

Hello eblot,

I'm using this pyftdi library for a professionnal project. It is very great. Thanks you for that, it's a big help !

But I want to report an issue on the spi.gpio "pins reconfiguration direction". I noticed that if I try to reconfigure one pin direction (and I want to keep previous config for others), it erase the previous config and keeps only the config for the pin I requested...

Going through the code (spi.py) I found (in SpiController() class) the following function : -----> _set_gpio_direction
The last line is :
-----> self._gpio_mask = gpio_mask & pins
But this is wrong (I guess) Instead of using the "&" operator, I suggest to use the "|", and it actualise much better the _gpio_mask. I helps to keeps the previous config of other pins which was erasing before by using "&"... :-)

eblot commented 3 years ago

Hi. Good catch!

I think the line should be removed. There is no reason to change the GPIO mask here, only the direction of the selected pins.

Bnjamn commented 3 years ago

Hi back,

By trying to remove the line, it doesn't work anymore...

The "@property direction" return a correct feedback according to my re-configuration. But at the end I'm not able to read anymore the bits of my gpio port....

So I prefer to keep the "or" operand on that line (701) : -----> self._gpio_mask = gpio_mask | pins

Actually, the "read_gpio" function DO USE "self._gpio_mask" So it must be updated, isn't it ? (just trying to understand a bit better, sorry ^^)

zzts commented 3 years ago

If you follow the code, this is the only place the _gpio_mask is set and/or updated except for the initialization to 0 at the beginning of the class. At this point in the code gpio_mask is FFF0 for a 16 bit port and F0 for an 8 bit port. The current code "forgets" any previously configured pins. If you remove line 701, _gpio.mask will never be changed. If you OR it you get nothing new (since it is FFF0). Prior to running the set_direction for the first time, gpio.pins returns FFF0 or F0 indicating that all pins are configured. This a result of line 432 432 self._set_gpio_direction(16, (~self._spi_mask) & 0xFFFF, io_dir) which is run to configure the spi interface. This done to allow the initial setting of direction and pin state with the configure command.

After setting pins and directions, gpio.pins returns only newly configured pins, which is ok the first time around if you didn't set direction and pin state with the configure command.

self._gpio_mask |= pins will add the new pins and retains previously configured pins, however it does not allow you remove pins from configuration and retains every pin as configured except the spi port pins. This is also not acceptable.

I'm not sure yet how to fix the situation in the code, so I have to keep track of the pins and directions in my code and resend the ones I want to retain to the set_direction. ie. gpio = GPIO port assignment

config = gpio.pins
direct = gpio.direction
config &= ~newPins  
config |=  newPins
direct &= ~newdirect
direct |= newDirect
gpio.set_direction(config, direct)

To deconfig a pin and leave the remainder as is: gpio.set_direction(gpio.pins & ~ pins_to_release, gpio.direction & ~pins_to_release)

TedKus commented 3 years ago

I tried a few cases with the gpio pins. The trouble is going into and out of tri-state (I'm using I2C) if the direction is set to 0 (input or tri-state) then that pin cannot be driven again. if not careful about it, none of them can be driven again. My branch has a fix for the I2C side and the following code works to update a specific pin gpio_position = pin + 3 # bit number 3 pin_mask = 1 << gpio_position pins_states = self.gpio().read(with_output=True) & 0x78 if state:

Note: drive = 1 is HIGH

         new_pins_states = pins_states | pin_mask

else:

Note: drive = 0 is LOW

         new_pins_states = pins_states & ~pin_mask

try: self.gpio().write(new_pins_states) except I2cIOError: temp = ((self.gpio().direction >> gpio_position) & 1) print(f"gpio:{pin} is Tri-State\n" f"the pin direction is: {temp}\n" f"the .direction is {bin(self.gpio().direction)}\n" f"the .value is {bin(new_pins_states)}") raise

with my patch in place, and catching the exception: by printing the binary like that it was easy to see that read was returning 16 bits, so the inner function failed unless I added the & with 0x78 to keep it to the lower bits (all the C232 can reach). There may be some cases where wide-port devices are not really addressable.

I guess what I'm saying is that you were right. the mask should be in the configure, and it doesn't need to be altered (after all, they're fixed pins!) in configure: gpio_mask = (1 << 16) - 1 # set mask for wide port (16-bit) gpio_mask &= ~self._i2c_mask self._gpio_mask = gpio_mask self._set_gpio_direction(io_out, io_dir)

TedKus commented 3 years ago

Pull #260 addresses similar issue in I2C. I will be using SPI later this year and will look at this then, but the changes would be similar.