Using I2C to connect Arduinos

I've been working with Nicholas Zambetti's examples for I2C master/slave.
I can communicate between two Arduinos in both directions.
From the master Arduino, I can send multiple bytes to the slave.

However, I can not seem to get the slave to send more than 1 byte to the master.
(I can, however get the slave to send a string of characters to the master.)

For example, this slave code will send 1 byte to the master just fine:

void requestEvent(){
  Wire.send(bVar);
}

This sends a string to master just fine:

void requestEvent(){
  Wire.send("Hello ");
}

But it seems I can't do this . . .

void requestEvent(){
  Wire.send(bVar);
  Wire.send(bVar2);
}

Am I only allowed one Wire.send at the slave when responding to a request?
Or am I doing something wrong?

It seems odd that I can send a string, but be limited to a single byte otherwise.

If that's how it is, I can live with it - just need to know.
In general these are great examples and they seem to be the way to go for inter-Arduino communications. Zambetti did a great job writing the twi and the wiring code.

Any help appreciated. I can't seem to find much on how to use the wire library in general.
[edit]Oops! RTFM! I'm sure my answer is here:
Arduino - Home (quantity param)
Sorry about the flack. ::slight_smile:
[/edit]

I want to be able to send a command to a slave, wait for the slave to complete the command (sometimes 2-3 sec.) and receive a response back from the slave that the command was completed.

My question is:
When a master makes a Wire.requestFrom to get data from a slave, how long will it wait for a response ?

(by checking Wire.available() )

BTW - In regard to the previous post, I now assume you're limited to only one Wire.send as a response to a request at the slave. (However you can send strings and arrays.) If someone can confirm that, it would be great.

Wow! Three in a row! :-[

Not exactly an overwhelming response! I guess my questions were to vague. In any case, I can see now that they were the wrong questions. So here, I will just document what I found out in case that may help someone in the future.

As background, I bought an AdaFruit motor shield. It's great for small steppers, but takes most of the pins and a fair bit of memory.
So my idea was to run it as an I2C slave. That was easy to do. However, I wanted the slave to respond to status requests from the master and I didn't want the I2C master to be issuing commands while the slave was doing blocking stepper functions.

I've finally accomplished this, although not how I originally intended.

In the process, this is what I believe I've learned about wire commands :

1.) On the master while(Wire.available() may not work how you expect. It appears to me that it will always be "available" after a Wire.requestFrom. (There is no such thing as "while(**!**Wire.available())"

In addition, the number of bytes available will always be the number of bytes requested with Wire.requestFrom. If you request 6 at the master and send 2 from the slave "while(Wire.available()" will loop 6 times. The inverse is also true, if the slave sends more than requested, you will only get what is requested.

2.) On the slave, the function registered as an event with Wire.onRequest must be the function that sends the response back (using Wire.send()). Of course you cannot have delay() in it, which is understandable, but neither can you get tricky and set a flag that will later call a function from loop(). From all I've seen, the Wire.send() must be in the event.

This may be 'old news' to some of you but FWIMW.

I have not been able to get my i2c device here to work, so take my input with a grain of salt...

The Wire library does quite a bit of abstracting- it maintains a transmit and receive buffer and then pretends as if it's sending/receiving one byte at a time. That's not the case.

When you call send(someByte) it just copies someByte to a buffer. Once you call endTransmission() the entire sendBuffer gets transmitted as fast as the slave can handle it. This is basic I2C, thus the master waits for a slave acknowledgement. If the slave can't process data quickly it will simply stall the CPU until it's done.

When receiving data from a slave keep in mind that the slave can NOT initiate transfers. The protocol basically consists of the master writing the slave ID on the bus and then repeatedly signalling "give me another byte". As long as the slave has bytes it will comply with that request. The slave can not decide that it wants to send some more data unless the master requests it.
When you call receiveFrom you have to specify the slave ID and the number of bytes to request up front. That data is once again transmitted all at once and stored in a buffer. With wire.available() you check if there's anything in the buffer and with wire.receive() you pull out the data one byte at a time. Corollary: you can not add delays between multi-byte reads or writes. For that you'd have to split them up into single byte requests.

While the slave can not initiate a transfer it is possible to hook a digital pin on the slave to a pin on the master which the slave can use to signal the availability of data.

In the original example the master would send a command to the slave. The slave would take 2-3 seconds to complete the command after which it would raise the signaling pin to HIGH indicating that status information is available.

On the master this signaling pin could either be tied to one of the interrupts or could simply be connected to an input pin. If connected to an input the master would poll the status of this pin after sending a command waiting for the slave to signal its readiness to return the status of the last command.

Then it would not be Two-Wire anymore :stuck_out_tongue:

I have an ADC here which uses a polling approach to avoid extra pins- the master has to query a status register from the slave and check whether one bit is set to high indicating that a new result is available.

2.) On the slave, the function registered as an event with Wire.onRequest must be the function that sends the response back (using Wire.send()). Of course you cannot have delay() in it, which is understandable, but neither can you get tricky and set a flag that will later call a function from loop(). From all I've seen, the Wire.send() must be in the event.

This is not entirely true, it doesn't have to be the function, but it does have to be called by it, or within a reasonable amount of time. The protocol should define the maximum amount of time between the request and the response to be valid, but depending on how you do it, it should work fine.

Here is some code I successfully used with Arduino 011, to do almost the exact same thing you're talking about.

One, specific handler (for sending an int, and requesting a variable amount of data back):

void send_command_to_motor(byte command, byte sub_com, unsigned int val, byte rec_len) {
 byte comm_init;
 byte cnt = 0;
 
 
 memset(recv, 0, SIZE_OF_I2C_BUF);
 
 Wire.beginTransmission(SLAVE_I2C_ADDR);
 
       // we're going to send our command value (3 bits)
      // and our sub-com/data length (4 bits)
      
 comm_init = command << 5;
       // we're sending three bytes...
 comm_init |= B00000110;
 
 Wire.send(comm_init);
 Wire.send(sub_com);
 
       // send the int as two bytes, MSB first
 Wire.send((byte) (val >> 8));
 Wire.send((byte) (val & 0xFF));
 
       // flush out the buffer
      
 Wire.endTransmission();
 
       // we have to delay so our request works...
      
 delay(100);
 
 Wire.requestFrom(SLAVE_I2C_ADDR, rec_len);
 
       // get up to total number of bytes expected in return
      
 while( Wire.available() && cnt < rec_len) {
      recv[cnt] = Wire.receive();
      cnt++;
 }
 
 return;
 
}

And, on the slave end:

void twi_receive_from_master(int count) {
      // called whenever we receive data from the master via TWI
      //
      // a command byte (1st byte) is received, and then a specified
      // number of sub-command bytes

 bool got_com     = false;
 byte cur_inp     = 0;
 byte twi_com_len = 0;
 byte input;
 
 while( Wire.available() ) {
       input = Wire.receive();
       
       if( got_com == false ) {
                   // we don't have a command yet
                  // so this is our initial command
                  
//             Serial.print("Rec Byte Val: ");
//             Serial.println(input, DEC);

                  // we use got_com to make sure we got a valid command
                  // from the master.  Zero means no command, no length,
                  // so that's not valid
                  
             if( input == 0 ) {
//                   Serial.println("Received Empty Request, Not Taking Action");
             }
             else {
                   
                         // set our flag to say we've received a command
                   got_com     = true;
                  
             
                         // we have 3 bits for a command, 8 possible values [0-7]
                   twi_command = (byte) ( input >> 5);
             
                   // we have 4 bits for a sub-command byte count, 
                  // 16 possible values
                  // mask out any bits we're not interested in..
                  
                   twi_com_len = (byte) ( (input & B00011110) >> 1);

                   // there are two bits left, we might do something
                  // with them later
                  
//                   Serial.print("Got Command: ");
//                   Serial.println(twi_command, DEC);
//                   Serial.print("Got SubCom Cnt: ");
//                   Serial.println(twi_com_len, DEC);

                   
                  // clear out our previous command buffer
                  
                   for(int i = 0; i < TWI_MAX_BYTES; i++) 
                        twi_command_bytes[i] = 0; 
             }
             
       }
       else {
             twi_command_bytes[cur_inp] = input;
             
//             Serial.print("Sub Com: ");
//             Serial.println(input, DEC);
             
             cur_inp++;
             
/*             if( cur_inp == twi_com_len ) {
                         // we've received all the bytes we're going to
                        // receive
                        
                   Serial.println("Received all sub-command bytes");
                   
             }
*/
       }
       
 }
 
}

void twi_request_from_master() {

// Serial.println("In Rcv Request");
 
      //called whenever a request for data is received from the
      // master
      // 
      // any number of bytes may be in the twi_command_bytes
      // array, based on our command type and sub-command values
      // the command byte defines the action we want to take, and the sub
      // commands define any further specifics
      
 switch(twi_command) {
       case 0:
             // status request
            twi_send_status();
            break;
      case 1:
            // set variable command
            twi_set_variable();
            break;
      case 2:
            // start
//            Serial.println("Got Start Request");
            is_running = true;
            Wire.send(1);
            break;
      case 3:
            // stop
//            Serial.println("Got Stop Request");
            is_running = false;
            do_manual  = false;
            Wire.send(1);
            break;
      case 4:
            // manual control
            Wire.send(1);
            twi_run_manual();
            break;
      default:
                  // unrecognized command
            Wire.send(0);
            break;
 }
 
}

void twi_send_status() {

      // send status information back to the master
      
 byte stat_str;
 
/* Serial.print("Sending Status Typ: ");
 Serial.println(twi_command_bytes[0], DEC);
 Serial.print("Got SubStat: ");
 Serial.println(twi_command_bytes[1], DEC);
*/ 
 
      // first byte is the type of status response requested
      
 switch(twi_command_bytes[0]) {
       
      case 0:
      
            // is running or not
            

            Wire.send((byte) is_running);
            break;
            
      case 1:
            // camera cycles shot in current program

            stat_str = (byte) (camera_done_cycles >> 8);
            Wire.send(stat_str);
            
            stat_str = (byte) camera_done_cycles & 0xFF;
            Wire.send(stat_str);
            
            
            break;
      default:
            Wire.send(0);
            break;
 }
 
}

void twi_set_variable() {
      
      // set a variable from data sent by the master

// Serial.print("Setting Variable: ");
// Serial.println(twi_command_bytes[0], DEC);
 
      // first byte is type of variable to set
 switch(twi_command_bytes[0]) {

      case 0:
            // directions
            truck_dir = (bool) (twi_command_bytes[1] >> 2);
            pan_dir   = (bool) ((twi_command_bytes[1] & B00000010) >> 1);
            tilt_dir  = (bool) (twi_command_bytes[1] & B00000001);
            
            break;
      case 1:
            // truck steps
            truck_move_step = (int) twi_command_bytes[1] * 10;
            
            break;
            
      case 2:
            // truck skip cycles
            
            truck_skip_cycles = twi_command_bytes[1];
            break;


       // and a whole bunch more stuff
      default:
      
            break;
            
            
            
 }
 
 Wire.send(1);
}

(Kinda ugly, but it works =)

!c

Thank you all for your responses. (I felt like the Lone Ranger there for while. :))

Cross - I understand what your saying, however I wasn't trying to initiate communication from the slave, more like I wanted to delay response at the slave of a request from the master.

dogsop - Your suggestion would solve the problem, however the extra wire would make it difficult to add an I2C buss extender.

Drone - I was amazed how similar our protocol was - cmd/sub-cmd, variable request & response! I also understand that the requestEvent can call another function that can make the the response - but it can't totally leave the event, it seems.

However if I understood your example correctly, there are some differences with what I was trying to do.

In your case, you wait 100ms at the master before requesting a response from the slave. This gives a fixed amount of time for the slave to process the original command and get back with a response. Since the stepper command was blocking for a variable amount of time (depending on how many steps & speed) I was hoping to find a way to delay the response at the slave until the command was complete. (I hope I understood your example right, though.)

What I concluded is that you can't get there from here. So I ended up having the slave calculate a delay (based on steps and speed) to send back immediately to the master. After the master ran this delay, it could request another status from the slave. Even if it doesn't, the delay keeps the master from sending commands while the slave is processing a prior command and that was the main goal.

Your line to memset (recv,0,SIZE_OF_I2C_BUF is interesting. Is that used to clear the buffer so don't read it again if there's a problem?

Again, thanks to all. I hope I understood all of your suggestions correctly.

John

John -

Yeah, it seems the best way to do it, when you have a limited number of commands, and a reasonably small # of bytes of data to send per command, to go ahead and just use one byte to represent all of that, and save some time.

Well, the 100ms delay between endTransmission and requestFrom really doesn't have anything to do with the slave. The delay is required by the protocol. If you don't delay() there, all you'll receive() is the last byte you just sent. (I went crazy trying to figure this out early on!)

In my case, I don't have any requirements to wait before making the slave do something, it won't actually start acting on my commands until I issue a "start" command, and I expect it to stop immediately when it receives a "stop" command. All of the other stuff tells it what to do when it gets a start command.

I think you're on the right path, but you really do need a minimum delay between endTransmission and requestFrom - if you have no delay ("send back immediately to master") your data you receive will be suspect. You can test this by having your slave send the byte value '2' back to any request, put no delay in there - and I bet you'll find your response is '1' =)

the memset() call just sets every value in my buffer to 0, since the buffer is global (accessed by numerous functions) this guarantees that it doesn't accidentally retain data from an earlier command/response. There's a for loop in the slave that achieves the same thing, and should just be written via memset() grin

!church

drone,

Thanks again for your input. For requests that return immediately I am using a 5ms delay between endTransmission and requestFrom. (Probably a little too tweaked out.)

Anyway, the protocol I came up with, works, so I'll let the sleeping dog lie. :wink: