Unexpected behavior from Wire.h?

Ok, I'll admit to being in a bit over my head with the i2c stuff, and the wiring (and arduino) didn't give me much insight into my problem.

The problem is that I wanted to facilitate bi-directional communication between two arduinos using i2c (Wire.h).

That is, arduino 1 (a1, master) sends a command to arduino 2 (a2, slave @ address 1) and a2 responds with the results of that command. In my test case, command '1' should result in '5' and command '2' should result in '6'.

It took some serious experimenting, as I found that (undocumented, afaict) if I call Wire.requestFrom( 1, 1 ) - it doesn't trigger the callback for onRequest in the slave unless I first Wire.beginTransmission(1). (As an aside, what's the point of requesting a number of bytes if this isn't communicated to the slave? The callback for onReceive() gets a byte count, but the callback for onRequest() gets no argument?)

So, here's what I found that I could do:

Master:

1: Wire.beginTransmission(slave)
2: Wire.send(byte)
3: Wire.send(byte) -- I send two bytes
4: Wire.requestFrom(slave, 1) -- want one byte back
5: while( Wire.available ) { ... = Wire.receive() }
6: Wire.endTransmission()

Slave:

onReceive handler:
1: while( Wire.available() ) { ... = Wire.receive() }
2: set 'command' value

onRequest handler:
1: check command value
2: Wire.send() appropriate response

The real kicker here, is that onRequest is always triggered on the slave before onReceive(), and the response is received on the master after send()! That means my answer is always to the previous question, I have to throw away the first couple of answers, AND I never get a response to my last question.

How do I make it so that the onRequest() handler isn't called before the onReceive() handler?

Now, obviously, I've tried to Wire.send() from the slave w/o sending a Wire.requestFrom() from the master, but that just results in the master reading back the last value it sent to the slave. (Remove step 4 from the master steps above, that is)

Here's the serial output from the master (code in response below):

Ready...
Sending Values 1, 1
Sent Command:
1:1:1
Got Response:
0
Sending Values 2, 2
Sent Command:
1:2:2
Got Response:
5
Sending Values 1, 1
Sent Command:
1:1:1
Got Response:
6
Sending Values 2, 2
Sent Command:
1:2:2
Got Response:
5

And the slave's output:

Ready...
Entering receive_request
Got a request without a known command.
Entering receive_data
Got count of bytes to receive:
2
Got a command byte:
1
Got value:
1
Entering receive_request
Responding to command 1: 5
Entering receive_data
Got count of bytes to receive:
2
Got a command byte:
2
Got value:
2
Entering receive_request
Responding to command 2: 6
Entering receive_data
Got count of bytes to receive:
2
Got a command byte:
1
Got value:
1
Entering receive_request
Responding to command 1: 5
Entering receive_data
Got count of bytes to receive:
2
Got a command byte:
2
Got value:
2

Note in the slave's output, there isn't a receive_request following the final receive_data.

TIA,
!c

Master code:

/* 

  Quick and dirty i2c master test sketch
  c. a. church <drone@dronecolony.com>
  
*/

#include <Wire.h>

#define BYTEBUFLEN (sizeof(byte) * 8) + 1


void setup() 
{
  
      // initiaize wire as a master (no address given)
      
  Wire.begin();
  
        // initialize serial for debugging
  Serial.begin(19200);
  Serial.println("Ready...");
  
}

void loop() 
{

 
      /*
      
            We're going to send two bytes to the slave every 2 seconds
            
      */


      // we have an initial delay, b/c if you start the serial
      // monitors at different times, and the master has already
      // started, you will be out of sync
      
 delay(30000);

 byte retval;
 char buf[BYTEBUFLEN];
 
 Serial.println("Sending Values 1, 1");
 retval = transmit(1,1,1);
 delay(2000);

 Serial.println("Sending Values 2, 2");
 retval = transmit(1,2,2); 
 delay(2000);
 
}

byte transmit(int dev, byte command, byte value) 
{
  
  /* transmit( int device, byte command, byte value)
  
        sends two bytes to a slave device identified by
      address <device>
      
      first byte we'll call "command" and second we'll call
      "value".  
      
      Returns a single byte read back from the slave device
      (in our case, the last byte)
      
  */
  
  byte foo;
  char buf[BYTEBUFLEN];
  
        // light up the provided led, let us know we're doing
      // something
      
  digitalWrite(13, HIGH);
  
        // begin a transmission to slave device
      
  Wire.beginTransmission( dev );

        // send 1st byte to slave device
      
   Wire.send(command);

         // send 2nd byte to slave device
      
   Wire.send(value);
   
   
  Serial.println("Sent Command:");
  Serial.print(dev);
  Serial.print(':');

  itoa(command,  buf, 10);
  Serial.print(buf);
  Serial.print(':');

  itoa(value,  buf, 10);
  Serial.println(buf);
 

      // now, we tell the device to send us some
      // data back.  A count of bytes is supremely pointless
      // as the onRequest handler in the slave doesn't receive
      // this count!  Who knows why they did this in the Wire library...
      
      // We have to send this 'request' while we're still in a
      // transmission to the slave! (Otherwise we never get an
      // onRequest() handler called.
      
  Wire.requestFrom( dev, 1 );
  
        // read results from the slave
      
  while( Wire.available() ) 
  {
    
      // read one byte
      
    foo = (byte) Wire.receive();
    
    Serial.println("Got Response: ");
    itoa(foo, buf, 10);
    Serial.println(buf);
    
  }

        // go ahead and end our trasmission now
      
  Wire.endTransmission();
  
  digitalWrite(13, LOW);

  return(foo);

}

Slave code:

/* 

  quick and dirty i2c slave test
  
  c. a. church <drone@dronecolony.com>
  
*/

#include <Wire.h>

#define CHARBUF (sizeof(int) * 8) + 1

byte command = 0;

void setup() 
{
  
      // setup i2c 
      
      // slave device at address 1
  Wire.begin(1);
  
        // handlers for receiving data and getting requests
      
  Wire.onRequest(i2c_receive_request);
  Wire.onReceive(i2c_receive_data);
  
        // Serial setup
  Serial.begin(19200);
  Serial.println("Ready...");
  
}

void loop() 
{

      // the Wire library will call our handlers via an interrupt,
      // so we just loop and wait
      
  delay(100);
 
}

void i2c_receive_data(int count)
{
      
      /* 
            this callback is triggered each time data is received
            from the master.
            
            In our case, the master is going to send two bytes
            (command/value), and expect at least one byte in return.
      
            The onRequest handler (i2c_receive_request) will handle the
            request for data to be returned
            
      */
      
 byte input;
 byte loop = 0;
 char buf[CHARBUF];
 
 Serial.println("Entering receive_data");
 Serial.println("Got count of bytes to receive: ");
 itoa(count, buf, 10);
 Serial.println(buf);

 
       // loop while we read as much data as we're supposed
      // to get..
      
 while( Wire.available() && loop < count ) {
             // get data off the wire
       input = (byte) Wire.receive();
       
             // our first byte is our 'command'
            
       if( loop == 0 ) {
                   // the first byte is a command type
                  
            Serial.println("Got a command byte: ");
            itoa(input, buf, 10);
            Serial.println(buf);
            
                  // we save this so that our request handler can
                  // look it up later
                  
            command = input;
       }
        else {
            
                    // any subsequent bytes are 'values'
                  
            Serial.println("Got value: ");
            itoa(input, buf, 10);
            Serial.println(buf);
            
        }
        
              // increment received byte #
            
        loop++;
 }


}
             
       
void i2c_receive_request()
{

  Serial.println("Entering receive_request");

  switch( command ) 
  {
       case 1:

             Serial.println("Responding to command 1: 5");
            Wire.send(5);
            break;            
       case 2:      
             Serial.println("Responding to command 2: 6");
            Wire.send(6);
            break;
      default:
            Serial.println("Got a request without a known command.");
            break;
  }  
                    
}

!c

As an aside, I tried wrapping the requestFrom() in it's own transmission, e.g.:

Master:

1: Wire.beginTransmission(slave)
2: Wire.send(byte)
3: Wire.send(byte) -- I send two bytes
4: Wire.endTransmission()
5: Wire.beginTransmission(slave)
6: Wire.requestFrom(slave, 1) -- want one byte back
7: while( Wire.available ) { ... = Wire.receive() }
8: Wire.endTransmission()

to no avail, in this case, the slave never triggers the onRequest() handler, and the master just gets back the last byte it sent in the Wire.receive() call.

!c

Hmm. Replying to myself here, as I check that I should be reading the Arduino docs, and not the wiring docs. (Arduino docs mention that send() is queued and transmitted at endTransmission() time, whereas wiring docs do not.)

While that, and reading the Wire.cpp code, explains that behavior - it doesn't explain why the requestFrom() does not trigger a callback on the slave unless it happens inside of a transmission, and, especially, a transmission where I have queued data to send?

That is, if I take the requestFrom() out of the begin/end transmission, nothing gets called on the slave, and if I put it in its own begin/end transmission nothing gets called on the slave.

I guess the only way then that I can facilitate this sort of communication is to add my own send_nobuffer() method to Wire?

!c

Last reply to myself here, heh.

I got it working as I expected it to by adding my own send_no_buffer() method (basically, calling twi_writeTo() directly).

I wouldn't think that I'd have to do that to get such a simple use-case handled. Surely I can't be the first to try this? I'm presuming that I still have something screwy going on with requestFrom(), and why it's not working outside of my endTransmission/beginTransmission where I use send().

!c

Edit: it's worth noting that the included example (master_reader.pde) also has this problem - it doesn't trigger an onRequest() handler for me.

Last reply to myself here, heh.

I got it working as I expected

Glad you could help. :smiley:

--Phil.

Last reply to myself here, heh.

I got it working as I expected

Glad you could help. :smiley:

--Phil.

Well, it's not entirely fixed =)

I'm still at a loss to explain why requestFrom() doesn't trigger a callback on the second arduino unless it's used inside of a transmission w/ a send() request.

!c

It turns out the problem is that a delay() is needed between endTransmission() and requestFrom(). Without the delay, the requestHandler is not triggered on the slave arduino. It seemed like it worked with the requestFrom() inside of the transmission, because it was being sent before the data.

My 'send no buffer' method only worked for one byte, which caused me to try and figure out the root cause, and also why I've chosen not to share my modifications to Wire grin

BTW, it would be nice to include an example that combines master_reader and master_writer, such as a master_rw:

#include <Wire.h>

void setup()
{
  Wire.begin();        // join i2c bus (address optional for master)
  Serial.begin(9600);  // start serial for output
}

void loop()
{

  Wire.beginTransmission(4); // transmit to device #4
  Wire.send("x is ");        // sends five bytes
  Wire.send(x);              // sends one byte  
  Wire.endTransmission();    // stop transmitting

  x++;

  delay(10); // this delay is essential between endTransmission() and requestFrom();


  Wire.requestFrom(2, 6);    // request 6 bytes from slave device #2

  while(Wire.available())    // slave may send less than requested
  { 
    char c = Wire.receive(); // receive a byte as character
    Serial.print(c);         // print the character
  }

  delay(500);
}

!c