I2C seems slow between Leonardo and Uno

Hey all,

I’m using I2c to send a value from an Arduino Leonardo, to an Uno.

I’m currently using the following send code:

(The buffer is a single digit number that is being read from an RFID tag.)


#include <Wire.h>
#include <SPI.h>
#include <Adafruit_PN532.h>


// If using the breakout or shield with I2C, define just the pins connected
// to the IRQ and reset lines.  Use the values below (2, 3) for the shield!
#define PN532_IRQ   (6)
#define PN532_RESET (3)  // Not connected by default on the NFC Shield


// Or use this line for a breakout or shield with an I2C connection:
Adafruit_PN532 nfc(PN532_IRQ, PN532_RESET);

void setup(void) {

  // setup so we can send to the other Arduino
 Wire.begin();
  
  // has to be fast to dump the entire memory contents!
  Serial.begin(115200);
  while (!Serial) delay(10); // for Leonardo/Micro/Zero

  Serial.println("Looking for PN532...");

  nfc.begin();

  uint32_t versiondata = nfc.getFirmwareVersion();
  if (! versiondata) {
    Serial.print("Didn't find PN53x board");
    //while (1); // halt
  }
  // Got ok data, print it out!
  Serial.print("Found chip PN5"); Serial.println((versiondata>>24) & 0xFF, HEX);
  Serial.print("Firmware ver. "); Serial.print((versiondata>>16) & 0xFF, DEC);
  Serial.print('.'); Serial.println((versiondata>>8) & 0xFF, DEC);

  // configure board to read RFID tags
  nfc.SAMConfig();

  Serial.println("Waiting for an ISO14443A Card ...");
}


void loop(void) {
  uint8_t success;                          // Flag to check if there was an error with the PN532
  uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 };  // Buffer to store the returned UID
  uint8_t uidLength;                        // Length of the UID (4 or 7 bytes depending on ISO14443A card type)
  uint8_t currentblock;                     // Counter to keep track of which block we're on
  bool authenticated = false;               // Flag to indicate if the sector is authenticated
  uint8_t data[16];                         // Array to store block data during reads

  // Keyb on NDEF and Mifare Classic should be the same
  uint8_t keyuniversal[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };

  // Wait for an ISO14443A type cards (Mifare, etc.).  When one is found
  // 'uid' will be populated with the UID, and uidLength will indicate
  // if the uid is 4 bytes (Mifare Classic) or 7 bytes (Mifare Ultralight)
  success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength);

  if (success) {
    // Display some basic information about the card
    Serial.println("Found an ISO14443A card");
    Serial.print("  UID Length: ");Serial.print(uidLength, DEC);Serial.println(" bytes");
    Serial.print("  UID Value: ");
    nfc.PrintHex(uid, uidLength);
    Serial.println("");

    if (uidLength == 4)
    {
      // We probably have a Mifare Classic card ...
      Serial.println("Seems to be a Mifare Classic card (4 byte UID)");

      // Now we try to go through all 16 sectors (each having 4 blocks)
      // authenticating each sector, and then dumping the blocks
      for (currentblock = 4; currentblock < 5; currentblock++)
      {
        // Check if this is a new block so that we can reauthenticate
        if (nfc.mifareclassic_IsFirstBlock(currentblock)) authenticated = false;

        // If the sector hasn't been authenticated, do so first
        if (!authenticated)
        {
          // Starting of a new sector ... try to to authenticate
          Serial.print("------------------------Sector ");Serial.print(currentblock/4, DEC);Serial.println("-------------------------");
          if (currentblock == 0)
          {
              // This will be 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF for Mifare Classic (non-NDEF!)
              // or 0xA0 0xA1 0xA2 0xA3 0xA4 0xA5 for NDEF formatted cards using key a,
              // but keyb should be the same for both (0xFF 0xFF 0xFF 0xFF 0xFF 0xFF)
              success = nfc.mifareclassic_AuthenticateBlock (uid, uidLength, currentblock, 1, keyuniversal);
          }
          else
          {
              // This will be 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF for Mifare Classic (non-NDEF!)
              // or 0xD3 0xF7 0xD3 0xF7 0xD3 0xF7 for NDEF formatted cards using key a,
              // but keyb should be the same for both (0xFF 0xFF 0xFF 0xFF 0xFF 0xFF)
              success = nfc.mifareclassic_AuthenticateBlock (uid, uidLength, currentblock, 1, keyuniversal);
          }
          if (success)
          {
            authenticated = true;
          }
          else
          {
            Serial.println("Authentication error");
          }
        }
        // If we're still not authenticated just skip the block
        if (!authenticated)
        {
          Serial.print("Block ");Serial.print(currentblock, DEC);Serial.println(" unable to authenticate");
        }
        else
        {
          // Authenticated ... we should be able to read the block now
          // Dump the data into the 'data' array
          success = nfc.mifareclassic_ReadDataBlock(currentblock, data);
          if (success)
          {
            // Read successful
        //    Serial.print("Block ");Serial.print(currentblock, DEC);


           char buffer[5];
// read the card
buffer[0] = data[9];
buffer[1] = '\0';  // terminate string

Serial.println( "buffer" );
Serial.println(buffer);

char rfidValue = buffer;    // THIS RFID VALUE IS IN A ASCII DECIMAL FORMAT. SO THE VALUE 49 == 1

Wire.beginTransmission(8);
Wire.write(buffer);
Wire.endTransmission();
delay(500); // Slight pause to avoid multiple triggers
          }
          else
          {
            // Oops ... something happened
            Serial.print("Block ");Serial.print(currentblock, DEC);
            Serial.println(" unable to read this block");
          }
        }
      }
    }
    else
    {
      Serial.println("Ooops ... this doesn't seem to be a Mifare Classic card!");
    }
  }
  // Wait a bit before trying again
  //Serial.println("\n\nSend a character to run the mem dumper again!");
  //Serial.flush();
  //while (!Serial.available());
  //while (Serial.available()) {
  //Serial.read();
  //}
  Serial.flush();
}

I’m using the following code on the Uno to recieve. This is just from the Wire Example:

#include <Wire.h>

void setup() {
  Wire.begin(8);                // join i2c bus with address #8
  Wire.onReceive(receiveEvent); // register event
  Serial.begin(9600);           // start serial for output
}

void loop() {
  //delay(100);
}

// function that executes whenever data is received from master
// this function is registered as an event, see setup()
void receiveEvent(int howMany) {
  //while (1 < Wire.available()) { // loop through all but the last
    char c = Wire.read(); // receive byte as a character
    Serial.print(c);         // print the character
 // }
  int x = Wire.read();    // receive byte as an integer
  Serial.println(x);         // print the integer
}

The serial monitor of the Uno looks like this:

Blockquote
16:43:19.308 → 11-1
16:43:27.757 → 1-1
16:43:36.216 → 1-1
16:43:44.675 → 1-1
16:43:53.122 → 1-1
16:44:01.607 → 1-1
16:44:10.060 → 1-1
16:44:18.509 → 1-1
16:44:26.959 → 1-1
16:44:35.398 → 1-1

I have the following issues:

  • The first value comes across wrong (11-1)
  • There seems to be a “-1” value appearing from somewhere
  • There seems to be about a 9 second delay from the values being received. I’d like the value to be sent/recieved each time I scan the tag.

Any ideas please on how I can get this to work?

Snippets are, in general, not appreciated here…
Please use code tags, the </> symbol. In case the code limit is exceeded, attach the .ino file.

I’ve updated the post. Thanks!

The first “11” value may stem from some initialization, before overwritten by other data.
The “-1” comes from the println(x).
The delay comes most probably from slow RFID reading.

Please check the compiler warnings, I suspect an illegal conversion.

Also let the IDE fix the indentation of your code.

The sender code:

This looks odd to me. The for loop will only run once, for currentblock equal to 4, not any more.

Receiver:

void receiveEvent(int howMany) {
//while (1 < Wire.available()) { // loop through all but the last
char c = Wire.read(); // receive byte as a character
Serial.print(c); // print the character
// } Railroader comment: End of Even code
int x = Wire.read(); // receive byte as an integer **Railroader comment: Will this line execute?
Serial.println(x); // print the integer **Railroader comment: Will this line execute?

Try to install debugging Serial.prints, in sender as well as receiver, and use Serial Monitors.

The For loop was a hack, as I only wanted to read a value from a specific block. It’s on my to-do list to head back and tidy this.

The reciever code is pretty much the bog standard ‘slave_receive’ example file found in the Wire library. As far as I can tell, the code in the while loop in the receiveEvent function does not execute, the values I’m seeing in the console are coming from the ‘int = x’ code. I’ll try your suggestions - thanks!

When I scan my RFID tag, it shows up in the console immediately as expected. There’s then a delay until I see the value show up in the console of the other Arduino.

Okey. You know what You’r doing.
Next…

What exactly is the wanted result? I’m not sure I get the details right. Just the time and “1”?

This looks like a possibly time costly part of the sender code:

Could that “Wire.begin” and “Wire.end” possibly send extra characters?

I’m defiantly new to Arduino! I’ve stored a value between 1 and 10 onto each RFID tag, so my final outcome is to get that number. In the example above then the result would be ‘1’.

The Time stamp is from the Serial Monitor window, I included it to show the delay I’m getting between the responses in case it helped solve my issues.

Why should it not execute?
The difference from preceding print(c) is the unsigned byte c and signed int x. If only one byte is sent and howMany==1 then the “-1” may be an error code returned on transmission timeout after several seconds.

Maybe the Buffer is full, and it’s sending loads of extra values? Is there an easy way to strip out just the bit I need? I think I need to understand what the buffer part of the code does, it was an answer provided on the forum to a previous issue I was having.

I missed that a right curly bracket actually is commented out…

Okey. Describe how the system is used. The communication is “simplex”, one way only as I get it. Then it’s mandatory to make sure that the receiver has got the previous message before sending the next message. Slow down the transmission to once per 20 seconds, per 10 seconds…

The idea behind the final system is that a large group of people could each have an RFID tag. There’s a mixture of tag numbers between 1 and 10. When a person swipes over the receiver, I read the number from the tag, and then send the value using OSC through an Ethernet shield.

I can send values using OSC, so I’m happy from that side. I can scan Numbers from the tag, so I’m happy on that side. In a perfect world, I would stack both of the shields into 1 Arduino, which would save the whole i2c hassle. I couldn’t get both shields to play nicely, so this was meant to be a simple way around it!

Here’s a link to a previous post of mine about the same project, for a bit of background.

Thanks for your help so far!

Can you describe the data that you want to transfer ?

Please describe everything, and I mean everything (8-bit bytes, or 16-bit integers, signed or unsigned, how many, a fixed number or a variable number, a combination of different types ? as a readable string ? without or without zero-terminator ? and what is the contents ?).

I’m after an unsigned int between 1 and 50. The number is from an RFID tag attached to a person, so would be a random value. I guess that makes it a uint8_t? I don’t think I need the zero-terminator in this case?

Arduino has the ‘byte’ type which is the same as ‘uint8_t’.
You can use Wire.read() to read a byte and Wire.write() to write a byte.

byte myData = 30;

Wire.beginTransmission(8);
Wire.write( myData);   // a single byte
Wire.endTransmission();

Sometimes the data can be processed in the receiveEvent() function, for example to set a digital pin. In almost all cases, the processing should be done in the loop(). To process that data in the loop(), the data has to be global and also a global ‘flag’ is needed to signal that something is received.

Using Serial.print() in the receiveEvent() might be possible for a quick test, but that is not safe. The receiveEvent() is run from a interrupt routine and the Serial functions use interrupts themself.

volatile bool flag = false;
volatile byte myData;

void loop()
{
  if( flag)
  {
    Serial.println( myData);
    flag = false;
  }
}

void receiveEvent( int howMany) 
{
  if( howMany == 1)   // extra safety check, just one byte is expected.
  {
    myData = Wire.read();  // read a single byte, set the data in a global variable.
    flag = true;   // signal that something is received.
  }
}

This code assumes that the data is processed before new data is received. It is possible to be 100% sure that the data will be processed with more code, but that is often not needed.

You could rename the ‘flag’ to something better. For example ‘SomethingWasReceivedViaTheI2Cbus’, but then shorter.

This makes the program unresponsive as soon as more than 1 byte is on hold.

If the Master sends just 1 byte:

Wire.beginTransmission(8);
Wire.write( myData);   // a single byte
Wire.endTransmission();

And if the Slave receives something else than 1 byte, then there something very wrong. It would be bad to use very wrong data.

What do you mean “on hold” ? The I2C bus uses packages of data, it is not a stream.

The Wire library is part of the Stream class family, but that was a good or bad choice, depending on how someone looks at it. The Stream class is for serial data, but the I2C bus is about packages of data.