I2C Wire.h: Slave Returning 0s When Nothing To Send (Is I2C the Right Solution?)

Hello, I am running a master and slave board set up using Mega2560s. I have three questions about Wire.h and I2C, but if you don't have much time, I'd appreciate even an answer to the first one. I've ordered them in ascending complexity (I think) and you can just answer as many as you have time for. If you'd rather get a feel for the whole project and why I'm asking these questions, links to the whole code on github are at the end. Thank you!

Quick context: I'm setting up boards to each scan a piano keyboard to see if any notes have been played/released. I'll eventually need to scan four keyboards with slaves and the master will scan a fifth keyboard as well as being in charge of sending out the MIDI signals. I need to send that information to a master board so that the MIDI signals will all come from one place.

Right now, I'm just trying to get the master (without any scanning duties of its own) and a single slave working. I've verified that the circuits work, that the slave is set to the right address, master is requesting from the right address, and that, with the exception of the problems listed below, I2C is working okay.

First question: Right now, the slave board is returning 0 to the master board if it doesn't have any data to transmit (it works just fine if there's data to go out). This is causing the master board to have to deal with garbage 0s. I coded a work-around for the master to just ignore returned 0s, but this is causing other problems. How do I make sure that the slave doesn't send 0 to master if it doesn't have any data? Here's the relevant code for the master:

void queryKeyboard(int slaveAddress, int midiChannel)
{
  Serial.println("In the queryKeyboard function.");
  //send request for one byte to the address set in slaveAddress
  if (Wire.requestFrom(slaveAddress, 1) == 1)
  {
    //read incoming value and assign it to incomingKeyData
    int incomingKeyData = Wire.read();
    //debugging info, can be deleted when done
    Serial.print("Just got ");
    Serial.print(incomingKeyData);
    Serial.println(" from the slave board!");
    //take incomingKeyData and send the right info to the MIDI port
    rawDataToMidi(incomingKeyData, midiChannel);
  }
}

Slave code:

//This transmits the pressed notes to the master board 
void requestEvent()
{
  Serial.println("master is requesting notes, in requestEvent");
  //if there's something in the queue, send it to the master board (this is from the QueueArray library, a quick way to get a FIFO queue)
  if (!queue.isEmpty())
  {
    Serial.println("sending notes");
    Wire.write(queue.dequeue());
  }
// There's no else { return 0 }, so I don't know why it's sending 1 to master if the queue is empty.
// Is it because the function was successful, so it's just returning that?
}

How to I change the slave code so it doesn't send anything to master if it doesn't have any data to send? (I found this thread about it, but I don't think it applies.)

Second question: My slave board doesn't stay in the loop when many notes are pressed in rapid succession if I don’t put long delays in the loop function. (The master also seems to have this problem.) I have no idea why this is happening, I’ve messed around with it for a while and can’t seem to make any headway. Any ideas where to start? The delays won’t work as a solution, because I need the information to be sent quickly to the MIDI port.

Third question: The reason I'm doing this is because I want to send information about keys being pressed on a keyboard to the master. Because of this, it would be much better if the master could deal with all the values that are waiting in the queue of played notes rather than one at a time. I've seen some example code (in the "request/response" section) that makes it look like it's possible to put up all the available data in an array and then transmit it, but the problem is is that the master board won't know how many notes have been played on the slave, so won't know how much to request from the queue.

How could I alter my code so that the master board would be able to read everything in the slave queue and move on to query the next slave after it's gotten all that the current slave had queued up? I noticed that KenF suggested just using serial communication in this thread. I think that doesn't apply because it's just talking about two boards (I'm eventually going to have four slave boards plus the one master). Would that be a better solution and, if so, how would I implement it?

Here's a link to the entire master code (I've commented the code extensively, so it should be easy to read).

Here's a link to the entire slave code (also commented).

Link to the QueueArray library (I don't think it's part of the problem, but it's here for the sake of completeness)

Your understanding of I2C seems deeply misconceived.

Of course the slave should respond to the master's request for information. If you expect it to just not respond at all when it has nothing to report, how would the master know if the communication is broken ?

Every other example of I2C that I have seen, it is a two-step process.

The master sends a message which amount to asking, I am about to ask you for some information.
And then it sends another message, which actually causes the response.

Yours seems uniquely different, and I don't think it will work. In fact, I don't think your code is doing anything at all. You seem to believe that you have got a garbage zero response from the I2C, but I think you have actually received nothing at all.

Here's a fragment of code from the sparkfun website

  Wire.beginTransmission(MAG_ADDR); // transmit to device 0x0E
  Wire.send(0x01);              // x MSB reg
  Wire.endTransmission();       // stop transmitting
 
  delayMicroseconds(2); //needs at least 1.3us free time between start and stop
  
  Wire.requestFrom(MAG_ADDR, 1); // request 1 byte
  while(Wire.available())    // slave may send less than requested
  { 
    xh = Wire.receive(); // receive the byte
  }

Every example of I2C querying a slave device, that I have seen, follows this sort of pattern. Yours, does not.

if (Wire.requestFrom(slaveAddress, 1) == 1)
{
//read incoming value and assign it to incomingKeyData
int incomingKeyData = Wire.read();

This bit of your code is wrong. You can only get the data if Wire.available( ) is true.

How could I alter my code so that the master board would be able to read everything in the slave queue and move on to query the next slave after it's gotten all that the current slave had queued up?

There are at least two simple methods.

Method 1:

Master asks slave, how many notes did you get ? Slave responds with a number n between 0 and 127. If zero, master moves on. Otherwise, master asks for each note, one at a time, and it asks n times.

Method 2:

Specify a number which is not a valid note number. Like -1 or 255.

Master asks slave for a note. Slave sends either a note number or the invalid number, if it has no note. Master asks again. Slave sends another note number, or the invalid number. Master keeps asking the slave, until the slave sends the invalid number, which means that the slave has then sent all the notes which it has.

Michinyon, thanks for taking a look at my code. You're right, I don't have a clear idea of how I2C works: I've watched several YouTube tutorials, looked over this forum, read the Wire.h library documentation, and looked at Nick's examples on his website and still haven't found what I need.

At least in the official Wire.h documentation master reader example, the way that I set up my code is correct: the master reader tells the slave to send it information, it does, and the master does something with it. Perhaps you're referring to the if (Wire.requestFrom(slaveAddress, 1) == 1)? I got it from Gammon Forum : Electronics : Microprocessors : I2C - Two-Wire Peripheral Interface - for Arduino, which seems to be heavily endorsed on this forum (it works too). Your explanation makes sense, how would I implement it?

I am receiving information from the slave. If I take a look at the serial monitor (which I suppose I should have posted initially), I'll see something like this from the master (here you'll see I pressed C2, note 36):

In the master loop
In the queryKeyboard function.
Just got 0 from the slave board!
Sending key number 0 OFF to MIDI.
In the master loop
In the queryKeyboard function.
Just got 0 from the slave board!
Sending key number 0 OFF to MIDI.
In the master loop
In the queryKeyboard function.
Just got 136 from the slave board!
Sending key number 36 ON to MIDI!
In the master loop
In the queryKeyboard function.
Just got 36 from the slave board!
Sending key number 36 OFF to MIDI.

EDIT: sorry, just saw your reply about Wire.available() which was posted while I was typing this. That's not what I saw from my results above. I was using it earlier, but was getting the same results.

michinyon:
There are at least two simple methods.

Method 1:

Master asks slave, how many notes did you get ? Slave responds with a number n between 0 and 127. If zero, master moves on. Otherwise, master asks for each note, one at a time, and it asks n times.

Method 2:

Specify a number which is not a valid note number. Like -1 or 255.

Master asks slave for a note. Slave sends either a note number or the invalid number, if it has no note. Master asks again. Slave sends another note number, or the invalid number. Master keeps asking the slave, until the slave sends the invalid number, which means that the slave has then sent all the notes which it has.

Both those sound like good ideas. If I'm not mistaken, there can only be one requestEvent() on a slave board. How would I set this up with the code?

You can request information from a slave device as often as you like, I don't see why you would think there would only be one request.

At least in the official Wire.h documentation master reader example, the way that I set up my code is correct: the master reader tells the slave to send it information, it does, and the master does something with it. Perhaps you're referring to the if (Wire.requestFrom(slaveAddress, 1) == 1)? I got it from Gammon Forum : Electronics : Microprocessors : I2C - Two-Wire Peripheral Interface - for Arduino, which seems to be heavily endorsed on this forum (it works too).

With regard to Nick Gammon's tutorial, it's very long and there is a lot of examples in there. It's important to read all of them and understand the differences.

As for the "official" documentation you linked to, that example was written in 2006. Since then, a lot of things have been changed around, and that website has not been consistently updated. Wire has been changed, the interaction between Wire and the microcontroller hardware has been changed, and there were changed when the version of the arduino IDE changed from version 23 to version 1.0. I'd advise caution in assuming it is still correct.

michinyon:

if (Wire.requestFrom(slaveAddress, 1) == 1)

{
//read incoming value and assign it to incomingKeyData
int incomingKeyData = Wire.read();




This bit of your code is wrong. You can only get the data if Wire.available( ) is true.

If Wire.requestFrom(slaveAddress, 1) returns 1 then there is definitely something available, namely, one byte.

//This transmits the pressed notes to the master board 
void requestEvent()
{
  Serial.println("master is requesting notes, in requestEvent");

Don't do serial prints in an ISR. Get rid of those and then we can talk.

but the problem is is that the master board won't know how many notes have been played on the slave, so won't know how much to request from the queue.

The requestFrom returns the number of bytes you got, so I don't see the issue here.

I see no harm in requesting 32 bytes, even if you get less back.

Unfortunately my testing seems to indicate that if you request 32 bytes you get 32 returned which doesn't sound right at all (if the slave does not send 32). I'll check further tomorrow. Meanwhile there is nothing stopping you sending a "length" byte as the first part of the response.

Things are not going as expected here.

Master:

#include <Wire.h>

const int SLAVE_ADDRESS = 42;

void setup ()
  {
  Wire.begin ();   
  Serial.begin (115200);  // start serial for output
  }  // end of setup

char responseBuffer [30];

void loop()
  {
  Wire.beginTransmission (SLAVE_ADDRESS);
  Wire.write (66);  // do something
  Wire.endTransmission ();
  
  memset (responseBuffer, 0, sizeof responseBuffer);
  
  int count = Wire.requestFrom (SLAVE_ADDRESS, sizeof responseBuffer, true);  
  Serial.print ("Got ");
  Serial.print (count);
  Serial.println (" bytes.");
  
  for (int i = 0; i < count; i++)
    responseBuffer [i] = Wire.read ();
    
  Serial.print ("Response was ");
  Serial.println (responseBuffer);

  delay (500);   
  }  // end of loop

This requests up to 30 bytes from the slave and displays the response (and the size of the response).

Slave:

#include <Wire.h>
const byte MY_ADDRESS = 42;
volatile byte count;
void setup () 
  {
  Wire.begin (MY_ADDRESS);
  Wire.onReceive (receiveEvent);  // interrupt handler for incoming messages
  Wire.onRequest (requestEvent);  // interrupt handler for when data is wanted
  }  // end of setup

void loop() { }

volatile byte command;

// called by interrupt service routine when incoming data arrives
void receiveEvent (int howMany)
 {
 command = Wire.read (); 
 }  // end of receiveEvent

byte myResponse [8] = "ABCDEFG";

// called by interrupt service routine when a reply is wanted
void requestEvent ()
 {
 count++;
 Wire.write ((const byte *) myResponse, count & 7); 
 }  // end of receiveEvent

On request, it sends from 0 to 7 bytes in response to a request. The variable number was intended to show you could return less than requested. The response is from the string "ABCDEFG".

Actual behaviour:

Got 30 bytes.
Response was A�����������������������������
Got 30 bytes.
Response was AB����������������������������
Got 30 bytes.
Response was ABC���������������������������
Got 30 bytes.
Response was ABCD��������������������������
Got 30 bytes.
Response was ABCDE�������������������������
Got 30 bytes.
Response was ABCDEF������������������������
Got 30 bytes.
Response was ABCDEFG�����������������������
Got 30 bytes.
Response was 
Got 30 bytes.
Response was A�����������������������������

As we can see, it correctly sends from 0 to 7 bytes from the string. However the claimed size is always 30, which was what was requested. Also, throwing in a Wire.available() test achieves nothing, as it thinks that the buffer size is 30, so it returns true for all 30 bytes.

Now I always thought that Wire.requestFrom returned the number of bytes actually sent, as per Wire - Arduino Reference where it says:

Returns

byte : the number of bytes returned from the slave device

Looking at the transfer with the logic analyzer clearly only the requested text is being sent, however then 0xFF (255) which is what the "high" pull-up would be represented as:

Now we notice that the remaining bytes (the 255 ones) are being acknowledged, but who is doing the acknowledgement? Well, the sender sends and the receiver acknowledges. Therefore in this case the master acknowledges what the slave is sending.

But the slave isn't sending anything after the last byte! Well, the master doesn't know that as it just sees a 1 for each clock pulse, so it acknowledges it.

I'm going to do some more investigating about this, but if any other I2C expert wants to chime in, I'd be interested to hear.

1 Like

Pending a solution to this (and I am starting to suspect that one won't be practical) as far as the original question goes, you could always either:

  • Send a length byte (first byte) so you know how much needs to be read
  • Send two requests, the first gets the number of bytes (ie. do a requestFrom with a length of one) followed by another requestFrom for the data, this time for the length received in the first request

Here is an example of sending the length and then the data.

Master:

#include <Wire.h>

const int SLAVE_ADDRESS = 42;

void setup ()
  {
  Wire.begin ();   
  Serial.begin (115200);  // start serial for output
  }  // end of setup

char responseBuffer [30];

void loop()
  {
  Wire.beginTransmission (SLAVE_ADDRESS);
  Wire.write (66);  // do something
  Wire.endTransmission ();
  
  memset (responseBuffer, 0, sizeof responseBuffer);
  
  if (!Wire.requestFrom (SLAVE_ADDRESS, sizeof responseBuffer))
    {
    Serial.println (F("No length from slave"));
    return;  
    }
    
  byte count = Wire.read ();
  Serial.print (F("Got size of "));
  Serial.print (int (count));
  Serial.println (F(" bytes."));

  // don't bother if nothing to read
  if (count == 0)
    return;
    
  if (!Wire.requestFrom (SLAVE_ADDRESS, size_t (count)))
    {
    Serial.println (F("No data from slave"));
    return;  
    }
  
  for (int i = 0; i < count; i++)
    responseBuffer [i] = Wire.read ();
    
  Serial.print (F("Response was "));
  Serial.println (responseBuffer);

  delay (500);   
  }  // end of loop

Slave:

#include <Wire.h>
const byte MY_ADDRESS = 42;
volatile byte count;
void setup () 
  {
  Wire.begin (MY_ADDRESS);
  Wire.onReceive (receiveEvent);  // interrupt handler for incoming messages
  Wire.onRequest (requestEvent);  // interrupt handler for when data is wanted
  }  // end of setup

void loop() { }

volatile byte command;
volatile bool wantLength;

// called by interrupt service routine when incoming data arrives
void receiveEvent (int howMany)
 {
 command = Wire.read (); 
 wantLength = true;
 }  // end of receiveEvent

byte myResponse [8] = "ABCDEFG";

// called by interrupt service routine when a reply is wanted
void requestEvent ()
 {
 if (wantLength)
   {
   count++;
   Wire.write ((byte) (count & 7));
   wantLength = false;
   return;
   }
   
 Wire.write ((const byte *) myResponse, count & 7); 
 }  // end of receiveEvent

This seems to work OK:

Got size of 0 bytes.
Got size of 1 bytes.
Response was A
Got size of 2 bytes.
Response was AB
Got size of 3 bytes.
Response was ABC
Got size of 4 bytes.
Response was ABCD
Got size of 5 bytes.
Response was ABCDE
Got size of 6 bytes.
Response was ABCDEF
Got size of 7 bytes.
Response was ABCDEFG
Got size of 0 bytes.
Got size of 1 bytes.
Response was A
Got size of 2 bytes.
Response was AB
Got size of 3 bytes.
Response was ABC

In this example sending the "command" byte to the slave (eg. "how are you going?") triggers the slave to expect the length byte. Otherwise the master and slave might get out of synchronization.

1 Like

Done: Serial prints in that ISR are gone. That cleared up the problem of things jamming when notes were pressed too rapidly and now I don't have to have all those delays. Thank you!

Is there any advantage with going with one of these solutions over the other? I can visualize how I would program and work the first one better, so I'm a bit inclined to try that one first. However, with your example, I already have a head start on the second way.

Thank you for the example code!

I'll play with the code tomorrow and report back.

The disadvantage of sending the length byte as the first byte is that the master has to request all 32 (or whatever your maximum is). The other method would be more efficient for small quantities.

However maybe you could compromise. Send a length byte, and then a maximum of (say) 10 bytes. This wouldn't be particularly slow, even if there were only one or two notes. You would then have to allow for this not being enough and sending the remaining ones next time.

1 Like