gassajor000 / pn532pi

Python interface for ndef communication using PN532 chip on raspberry pi
36 stars 18 forks source link

[IOERROR] - I2C not running on bookworm 64bit/3B+ #22

Closed soundstorm closed 2 months ago

soundstorm commented 3 months ago

What happened?

I'm running a Pi3+ with Raspbian Lite Bookworm 64bit. While the Library works on the bullseye 64bit on a Zero 2 W / python3.9, the I2C init fails on bookworm 64bit / python3.11. https://github.com/hoanhan101/pn532 seems to be working just fine.

[...]/pn532pi/interfaces/pn532i2c.py", line 158, in _readAckFrame
    responses = self._wire.transaction(reading(PN532_I2C_ADDRESS, len(PN532_ACK) + 1))
[...]/quick2wire/i2c.py", line 78, in transaction
    ioctl(self.fd, I2C_RDWR, ioctl_arg)
OSError: [Errno 5] Input/output error

Reproducibility

Always (100% of runs)

Steps to Reproduce

see code to reproduce

Code to reproduce

i2c = Pn532I2c(1)
nfc = Pn532(i2c)
nfc.begin()
nfc.setPassiveActivationRetries(0xFF)
nfc.SAMConfig()

Host Controller

Raspberry 3B+

Python Version

3.11

Interface Mode

I2C

Power Supply

HMP4040

Logic Traces and other details

IMG_5267

gassajor000 commented 3 months ago

Hi soundstorm, I can see from your logic capture that the PN532 is failing to send an ACK to the read command after writing the setPassiveActivationRetries. There might be a few causes of this so I will ask you to collect some more data.

  1. Before calling setPassiveActivationRetries() try reading back the values of these registers: I2CCON (D8h), I2CSTA (D9h), and I2CDAT (DAh). Also try to try/except the setPassiveActivationRetries error and then immediately read back the I2CSTA register.
  2. Please do a logic capture performing the same operation with the other library you mentioned and if possible on the Pi Zero 2 W. If you could also send the full logic capture files (rather than the screenshot) that would also be helpful.
  3. If possible, please monitor the Debug TX line (Usually DBGTXD on the underside) during the exchange as well.
soundstorm commented 3 months ago

Attached are the two exchanges recorded by the Saleae Analyzer. Configuration between pn532pi and pn532.api differ slightly, but as said, the Pi Zero 2 with bullseye aarch64 works flawlessly with the same pn532pi code, however, I noticed the codebase is older and therefor running the Library in V1.3. However, as the code is still matching the examples, this should generally still be fine was my assumption. I've tried to add the getFirmwareVersion() as in the example right after setup, but any read will fail (readRegister(0xD8) or any else throws an I/O error). Unfortunately the DBGTX stays quiet.

Might this be related to the gpio restructuring in the bookworm release? E.g. rpi-gpio is no longer working properly (interrupts, ...) and needed a replacement by rpi-lgpio. But as i2c is only accessing the file descriptor, it shouldn't be related. And also the Pi as a master should not generate a NACK.

Code for pn532.api for comparison:

nfc = PN532()
nfc.setup()
nfc._in_list_passive_target()
id = nfc.read()

pn532pi.csv pn532.api.csv

gassajor000 commented 2 months ago

Ok I think I have a better idea of what's going on. I think there are two relevant differences between pn432pi and pn532.api. First, pn532.api takes a 'rest' before each read/write to ensure the PN532 has completed the last command. Second, pn532.api does not read back the ack frame after each command which pn532pi does. I also dug into the _readAckFrame() function and realized that the original C library expected the first few attempts to read the ack to fail and so they poll until the device responds. (This behavior was backed up by some comments online about having to poll while the device is busy). The quick2wire library throws an exception however when the read isn't acked. To match the behavior of the C library we will probably have to catch this error and only raise it if we exceed the timeout. I suspect this is the reason a lot of other people have run into IOErrors when running longer running commands.

gassajor000 commented 2 months ago

If you would like to test this version of _readAckFrame() I think it will address the issue

    def _readAckFrame(self) -> int:
        PN532_ACK = [0, 0, 0xFF, 0, 0xFF, 0]

        DMSG("wait for ack at : ")
        DMSG(time.time())
        DMSG('\n')

        t = 0
        while t <= PN532_ACK_WAIT_TIME:
            try:
                responses = self._wire.transaction(reading(PN532_I2C_ADDRESS, len(PN532_ACK) + 1))
                data = bytearray(responses[0])
                if (data[0] & 1):
                    # check first byte --- status
                    break # PN532 is ready
            except IOError as e:
                # As of Python 3.3 IOError is the same as OSError so we should check the error code
                if e.errno != errno.EIO:
                    raise   # Reraise the error   
                # Otherwise do nothing, sleep and try again

            time.sleep(.001)    # sleep 1 ms
            t+=1
        else:
            DMSG("Time out when waiting for ACK\n")
            return PN532_TIMEOUT

        DMSG("ready at : ")
        DMSG(time.time())
        DMSG('\n')

        ackBuf = list(data[1:])

        if ackBuf != PN532_ACK:
            DMSG("Invalid ACK {}\n".format(ackBuf))
            return PN532_INVALID_ACK

        return 0
soundstorm commented 2 months ago

NameError: name 'errno' is not defined but after altering it to import time, errno it's working. But the code is printing the timestamp once at start with no other DMSG output which I find weird, as I can't track it down. But at least it's resolving the issue.

gassajor000 commented 2 months ago

Ah, yeah should have mentioned the import. Ok I'll look into the DMSG and then push a patch once I've tested it on my reference setup.

gassajor000 commented 2 months ago

For the sake of documentation, the relevant section in the PN532 user guide is 6.2.4 (I2C communication details). It mentions both the polling of the status byte and the case where the PN532 may not acknowledge a read immediately after executing a command. https://www.nxp.com/docs/en/user-guide/141520.pdf

soundstorm commented 2 months ago

Still sometimes exceptions, surrounded the read by an additional try/except. I also might wanna switch to interrupt based readout, as currently the polling is blocking for some time.

gassajor000 commented 2 months ago

Ok, how long is it blocking for? The ack polling should time out after 10ms.