Basic I2C problem

Can someone PLEASE show me what im not doing correctly! I have 2 arduinos connected via i2c. One controller (the slave) needs to send an integer (2 bytes) to another controller (the master) when it requests them. Here is the test code.

For the Master:

#include <Wire.h>

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

void loop()
{
  Wire.requestFrom(2, 2);    // request 2 bytes from slave device #2
  while(Wire.available())    // slave may send less than requested
  { 
    byte c = Wire.receive(); // receive a byte

    Serial.println(c, BIN);         // print the value
  }

  delay(500);
}

And for the Slave:

#include <Wire.h>

void setup()
{
  Serial.begin(9600);
  Wire.begin(2);                // join i2c bus with address #2
  Wire.onRequest(requestEvent); // register event
}

byte LowB;
byte HighB;

void loop()
{
  int result = 20560; //0101000001010000
  LowB = result;      //01010000
  HighB = result>>8; //01010000
  
  Serial.println(result, DEC);
  Serial.println(result, BIN);
  Serial.println(LowB, BIN);
  Serial.println(HighB, BIN);  
}

// function that executes whenever data is requested by master
// this function is registered as an event, see setup()
void requestEvent()
{
  // respond with message of 2 bytes
  Wire.send(LowB);
  Wire.send(HighB); 
}

The code was adopted from this Arduino example writeup which works as expected:

When i run my code, i can see the highB value and lowB value print to the console from the slave as expected, however, when i observe the results that were read by the master, I only get the lowB value. The highB value does print, however its all 1's (11111111) which tells me that the data line is not transmitting the highB value. What am i dong incorrectly?
Thanks!

Ah, now there's a trap. :wink:

I had a similar problem, and it turns out that it is a bug/feature of the I2C library that you can't do two sends in a row in a requestEvent function (or if you do only one gets sent). So I worked around it like this:

int val = analogRead (which);
byte buf [2];

  buf [0] = val >> 8;
  buf [1] = val & 0xFF;
  Wire.send (buf, 2);

This was in the requestEvent function. I sent the high-order byte first, but the principle is the same. You have to assemble the message into a buffer and then do a single Wire.send of the appropriate length.

LOL! :smiley: Nice "feature". Your work-around worked perfectly! That is something that should be fixed! But since im too lazy to write my own i2c library, i cant complain...too much. Its curious why the sample code, where in the event function, the 6 byte value "hello " was successfully transmitted with a single send(). ie: Wire.send("hello ").

I tried:
Wire.send(result); //result being an int type
and
Wire.send(result, 2);

with no luck at all. Oh well. Thank you for your quick and knowledgeable response!

The "hello" gets sent because it is a single send. That copies the text into an internal buffer which is clocked out on the send interrupt. So that works.

I tried:
Wire.send(result); //result being an int type

Well check this out (from the library code):

void TwoWire::send(int data)
{
  send((uint8_t)data);
}

The library throws away the high-order byte for you.

and
Wire.send(result, 2);

I presume that didn't compile. But this would, and I guess works too:

Wire.send ((byte *) &result, sizeof result);

That would send however many bytes are in "result" in one send, so that should work OK. Not sure which byte gets sent first, I would have to look up the endianness of this processor.

I know where the problem is, but it is probably a design decision:

In Wire.cpp:

// must be called in:
// slave tx event callback
// or after beginTransmission(address)
void TwoWire::send(uint8_t data)
{
  if(transmitting){
  // in master transmitter mode
    // don't bother if buffer is full
    if(txBufferLength >= BUFFER_LENGTH){
      return;
    }
    // put byte in tx buffer
    txBuffer[txBufferIndex] = data;
    ++txBufferIndex;
    // update amount in buffer   
    txBufferLength = txBufferIndex;
  }else{
  // in slave send mode
    // reply to master
    twi_transmit(&data, 1);
  }
}

Notice how, outside a requestEvent, the send just causes the data to be added to an internal buffer? However in "slave send mode" it simply sends it (and twi_transmit resets the buffer length back to the specified length and fills it from the start).

Now my guess it that this is a timing issue. On a normal send, it doesn't actually send anything. It waits for the endTransmission call. So the "send" doesn't really send, it fills a buffer. And then endTransmission clocks the buffer out.

But on a requestEvent, timing is crucial. We are already in an interrupt service routine, and we can't really afford to wait around while the program slowly does one send after another, does debugging prints, etc. Time being of the essence I guess the idea is you quickly assemble a single response, and do one send.

Still, suitably documented, it probably wouldn't hurt to allow multiple sends. After all, we are effectively doing that anyway by making our own buffer, filling it, and then sending that buffer. So since the send routine is already filling a buffer, there isn't that much difference.

It waits for the endTransmission call. So the "send" doesn't really send, it fills a buffer. And then endTransmission clocks the buffer out.

That makes sense. I assumed that the onRequest() function handled the endTransmission() function. Im finding that my understanding of i2c (learned from writing a driver for ARM) is hindering me from following some of the logic of the arduino i2c.

I will play around with some of the exampled from your last post. Thanks again for the detailed responses!