Important information: Wire.write()
does NOT send anything over the I2C lines. It just puts the data into the libraries internal buffer. The actual transmission is then done by Wire.endTransmission()
.
I2C is packaged transmission protocol. That means, that the transmission is done in confined data packages. In your master code you are calling Wire.write()
two times before Wire.endTransmission()
. That means you are sending 1 package with 2 data bytes in it.
Now for your slave code: You are using a callback function for the onReceive
event. This callback function is called once per transmission. In there you can read all the data of that transmission. After that the data is thrown away (I think actually it gets deleted, when a new transmission is received). You are only reading once with Wire.read()
. So you only ever read the first byte of every transmission, which is always the letter 'c'
.
What to do know? This depends on how you want the code to behave. Since you are setting 2 LEDs based on the received byte it would be better to only send 1 byte transmissions. For that, you can change your master code. For example like this:
#include <Wire.h>
void setup()
{
Wire.begin();
Serial.begin(9600);
}
void loop()
{
// Send the command c
Wire.beginTransmission(9);
Wire.write('c');
Wire.endTransmission();
// Wait 1s, so that we can see the LEDs state accordingly
delay(1000);
// Send command a
Wire.beginTransmission(9);
Wire.write('a');
Wire.endTransmission();
// Wait 1s, so that we can see the LEDs state accordingly
delay(1000);
}
This code will change between 'c'
and 'a'
every second. You need a delay there to actually see the LEDs light up. Otherwise they would change too fast for you to see it.
Note: Actually you need to declare the variable x
as volatile
, because the onReceive callback is called from an ISR. With that keyword you are telling the compiler, that x
can change at any time (by the ISR of the corresponding I2C interrupt). It might be, that your current code works, because the compiler was smart enough to see that, but in general you should always use volatile
with every variable, that can change in an ISR. Declaration then looks like this:
volatile int x = 0;
And to handle the ISR data really correct, you would need to make the usage atomic, because the ISR can happen in the middle of the comparison for the if statements. An int
is two bytes, so the first byte might be updated and the second byte not. That would corrupt your data. There are two ways to handle that in your case:
Make the reading of x
atomic: Normally one would turn of the interrupts, read the volatile variables into local variables (as a copy) and then turn the interrupts on again. That way the interrupt cannot corrupt the data in the variable during reading. The further calculation/comparison would then be done with the local variable.
Use a single byte as volatile data: The described problem does not apply to variables, which only use 1 byte (since on the 8bit Arduinos like the Nano the reading of a single byte cannot be interrupted). So just make x
of type char
(or uint8_t
or byte
).
You should use the second option.
Wire
library works.Wire.write()
does not send anything over the I2C lines. It only puts the data into the libraries internal buffer. Transmission is then done byWire.endTransmission()
, which sends out the content of the internal buffer as one I2C transmission. My guess is, that your slave is only reading the first byte of every transmission, thus ignoring the second byte ('a'
)