[SOLVED] I2C requestFrom() - how to read > 32 bytes?


#1

Am attempting to fix a small bug in the AdaFruit_PN532 library.

The following routine is called with n = 64 (have edited out the debug and SPI code):

void Adafruit_PN532::readdata(uint8_t* buff, uint8_t n) {
// Start read (n+1 to take into account leading 0x01 with I2C)
Wire.requestFrom((uint8_t)PN532_I2C_ADDRESS, (uint8_t)(n+2));
// Discard the leading 0x01
readbyte();

  for (uint8_t i=0; i<n; i++) {
       delay(1);
       buff[i] = readbyte();
   }

}

The issue is that Wire.requestFrom() has a maximum quantity value of 32.

How does one correctly read from the I2C bus in this case? Naively you might think that calling Wire.requestFrom() twice with reads inbetween would work… but it doesn’t… Am hoping it is something simple…

Thanks!


Using I2C for SMBus device communications
#2

Hi @UMD

I had a read through the source and discovered a few things:

int ii = 0;
byte recv[64];
Wire.requestFrom(i2c_addr, 32, 0);  /* Zero means don't stop */
while(Wire.available())  
  {
    recv[ii++] = Wire.read();  
  }
Wire.requestFrom(i2c_addr, 32);  /* do stop here */
while(Wire.available())  
  {
    recv[ii++] = Wire.read();  
  }

Be careful to make sure you always call a Wire.requestFrom() with stop at the end since you will hang the i2c bus if you don’t!


#3

Thanks @bko!

Have raised this issue of increasing the buffer size last night in Github as IMHO it really needs to baked into the production software.

Digging into the NXP PN532 user manual, I note the following which could be helpful:

If this (status) byte indicates that the PN532 is not available, an I2C STOP condition should be generated by the bus controller. In fact, all the bytes read following a NOT READY status byte are not relevant.

If the PN532 indicates that it is ready, the rest of the frame shall be read before sending an I2C STOP condition. Inf an I2C STOP condition is sent before a complete fame is read, the remaining bytes are lost.

So, your suggested code should in theory work because of the third parameter being false. Am pretty sure that I had tried this (have been trying lots of things!), but will do so again an report back soon.

I do note that others have release I2C libraries, specifically I2CDEV which mentions the 32 byte issue, but this was not fruitful either.

Hmmm…


#4

@bko, the results are in: your suggested technique did not work in my application of the PN532.

Drats and double drats!

Am going to see if the Saleae logic analyser will divulge anything. More experimentation to come.


#5

@bko, the logic analyser has confirmed what is happening in software, and now that I see it, it is obvious what the issue is (am pretty sure).

Wire.requestFrom() signals an I2C START, which is followed by the device’s ADDRESS followed by n reads.

The second call of requestFrom() in the proposal has the effect of starting a new read transaction, and hence why the device is responding with a not ready status byte and garbage thereafter.

What is needed is access to a low level read data routine that that does not issue an I2C START, ie it just does n reads. This would overcome the need to increase the buffer size… I will have a look at i2c_hal.cpp to see if there is a public but undocumented function.

Anyhow, the good news is at least we now understand the problem…


#6

Well I was going to suggest soft-i2c but then I went back to why you need the 64-byte i2c read and I found this ported library for PN532 card readers here from @BDub and @peekay123

It works for i2c, spi, etc.


#7

@bko, unfortunately that port has the same problem when n > 32:

// Start read (n+1 to take into account leading 0x01 with I2C)
Wire.requestFrom((uint8_t)PN532_I2C_ADDRESS, (uint8_t)(n+2));

Out of interest @peekay123, not sure why it is n+2 and not n+1 above.

The library works okay for small data transfers, not when you have more than 32 bytes to transfer, which is the issue with my particular case.

Looks like we have two options available to us for the I2C interface to the chip:
(a) get the increase in buffer size baked in,
(b) somehow get access from the application to the low level I2C read byte function within the firmware

Have raised a Github issue for (a).

Q. Do you think that (b) is possible?


#8

Hi @UMD

I think the answer to your github issue is that you can compile locally (using gcc), fix the size yourself, and then your issue will go away. There is no defined way to increase that buffer size today on Particle or on Arduino (which also has a 32-byte buffer), so Particle could invent something but that would be incompatible with Arduino and therefore seems undesirable to me.

You can do low level i2c programming by copying the relevant headers files and code from the system into your user program, change them as you like and then compile. From reading the code, you would need to be fairly expert to get this to work.

Or you could port a soft i2c library like this one:

This might be the easiest but would require a bit of simple porting work.

Maybe @peekay123 has some thoughts here. If you can explain why everyone else is ok with a maximum of 32-byte transfers but you need 64-byte transfers, that might help too.


#9

@bko, I have avoided changing the firmware to date because I believe that it should be maintained by Particle and the community, don’t want to have my own firmware branch!

Good ideas re:

  • taking the source code from the firmware branch and incorporating into my application - but could become tricky due to a bucket of header requirements, etc???

  • trying SoftI2CMaster (this is a most definite next attempt)

Will report back.

In relation to “why me?”, I don’t know if anyone has tried to do what I am doing with the PN532, which involves reading packets more than 32 characters long AND using the I2C interface. Considering that the PN532 handles serial and SPI interfaces as well, it could well be that no-one has come across this issue.

The Adafruit and modified Adafruit code both have a buffer of 64 bytes defined for packet reception, but the requestFrom() has a 32 byte limit - ie it will not be able to handle packets from the PN532 greater than this with I2C. @peekay123 may be able to confirm this is an issue!

In relation to compatibility of 32 vs 64 byte buffer, it would certainly be downward compatible - so should be no issues on legacy code falling foul, but I do take the point about caution.

Anyhow, onwards, upwards!


#10

I would not see this a problem. Adding features others don’t have can be considered a perk as long existing interfaces don’t get changed.
Restraining oneself to the limited horizon of others seems more like a problem tho’ IMHO

Why sailing in the slipstream of Arduino and not getting the confidence to take the lead?


#11

@bko, had a quick look at taking the i2c_hal.cpp source and including it in my app. Looks to be very, very difficult due to dependancies… this leaves the SoftI2CMaster route… shouldn’t be too hard to port, but I must admit to being reluctant because I have other I2C integrations in play…


#12

@UMD, to speak to the PN532 library, I am not the author, only the guy who ported it for the Particle platform. You are not the first to request a larger buffer for I2C. Arduino creates 5 x 32 bytes buffers (not sure why 5!) for I2C pushing against the 2KB RAM constraints of the Atmel ATMega328 (think Uno).

One Arduino user designed his own I2C library with user-specified buffers. To me, this would be ideal for the Particle platform and allow more flexibility for the user. However, I’m not sure what appetite the Particle team has for adapting the firmware to do this.

Looking through an older ported MPU6050 library repo I have, the large data issue is addressed this way:

		#if defined (SPARK)
            // Spark built-in Wire library
            // Adds official support for repeated start condition, yay!

            // I2C/TWI subsystem uses internal buffer that breaks with large data requests
            // so if user requests more than BUFFER_LENGTH bytes, we have to do it in
            // smaller chunks instead of all at once
            for (uint8_t k = 0; k < length; k += min(length, I2C_BUFFER_LENGTH)) {
                Wire.beginTransmission(devAddr);
                Wire.write(regAddr);
                Wire.endTransmission();
                Wire.beginTransmission(devAddr);
                Wire.requestFrom(devAddr, (uint8_t)min(length - k, I2C_BUFFER_LENGTH));

                for (; Wire.available() && (timeout == 0 || millis() - t1 < timeout); count++) {
                    data[count] = Wire.read();
                    #ifdef I2CDEV_SERIAL_DEBUG
                        Serial.print(data[count], HEX);
                        if (count + 1 < length) Serial.print(" ");
                    #endif
                }
            }

:smiley:


#13

@peekay123, great idea about using one’s own buffer for the I2C data - it is the way to go, way better than my “double it” request (I will update my Github PR).

I have tried your snippet of code previously, and got the same result (noting that there is no concept of register with the PN532).

I think the problem is that Wire.requestFrom() starts, correctly, with an I2C Start. This kicks the PN532 to return its status byte. Because this is done before all the > 32 bytes are transmitted, it stops reception of any further data.

Am nearly completed the SoftI2CMaster port, so we will see how this goes. Thanks for the link to the other library - I may have to revert to this!


#14

If we are pondering about Particles appetite to address this issue, we could ping @BDub to chime in :wink:


#15

@ScruffR, good idea!

@BDub, what do you think of @peekay123’s idea for user specified buffers? It shouldn’t be too hard to implement and would readily be designed to be backward compatible eg null pointer = use internal buffer as per normal.


#16

@UMD, the only catch is the need for dynamic allocation of the buffer. @mdma or @BDub need to advise.


#17

I’d definetly like to see that.
That was also an idea for serial buffer and is how SPI can deal with bigger data.


#18

Have updated Github issue #1112 with @peekay123’s suggestion. Fingers crossed!


#19

For the record, now working fine with (modified) SoftI2CMaster library.

I like the way that SoftI2CMaster gives finer control to the application - requestFrom() below just sets up the read, it does not buffer, ie “n” can be greater than 32 now:

    // Note SoftI2CMaster operates differently to Arduino code - there is no internal buffering
    // which is exactly what we need
    status = i2c.requestFrom(PN532_I2C_ADDRESS);
    
    if (status != I2C_ACK)
    {   
        Serial.printf("requestFrom() Read setup was NAK'd (0x%02X)\r\n", status);
        // Did not accept the read setup (ie write of address+READ BIT), try again at a higher level
        return(false);
    }

    // ** Status ** byte is the first returned
    // 0 = NOT ready
    // 1 = READY
    // ---------------------------------
    status = i2c.read();

    if (status != 0x01)
    {
        // PN532 has indicated that it is not ready, no point carrying on
        return(false);     
    }

    for (i = 0;  i < n - 1; i++)                // NOTE n - 1, ie don't read the last byte in this loop
        buff[i] = i2c.read();                   // read+ACK

    // This is **necessary** for PN532!!!
    buff[i] = i2c.readLast();                   // read+NAK

** Conclusion ** - if you want to read > 32 bytes using I2C, use a I2C library that gives what you need.

Case closed!

PS - I will post the ported SoftI2CLibrary with @peekay123’s help sometime soon.


#20

@UMD @peekay123 Did you ever get this SoftI2CLibrary ported?

I have a device that has 1 command that returns 34 bytes and the current wire library is keeping me from reading those last 2 bytes :disappointed_relieved:

I searched for the SoftI2C library on the Build IDE but it’s not there.

What is the easiest way to get this working on a Photon?