I am currently working on a project and want to transmit the information from a distance sensor from one arduino the another, but what i receive in the master board are not the correct measurements.
Slave Code
#include <Wire.h>
const int trig = 12;
const int echo = 13;
int state = 0;
int count = 0;
int contor = 0;
int set_distance = 5;
long duration, Distanceincm;
void setup()
{
Wire.begin(4); // join i2c bus with address #4
Wire.onReceive(receiveEvent); // register event
pinMode(trig, OUTPUT);
pinMode(echo, INPUT);
Serial.begin(9600); // start serial for output
}
void loop()
{
digitalWrite(trig, HIGH);
delay(15);
digitalWrite(trig, LOW);
duration = pulseIn(echo, HIGH);
Distanceincm = (duration / 58);
if (state == 0 && (Distanceincm < set_distance) ) {
state = 1;
count++;
contor++;
Serial.print("Distance=");
Serial.println(Distanceincm);
Serial.print("Count=");
Serial.println(count);
Serial.print("ConuntDay=");
Serial.println(contor);
}
if (Distanceincm > set_distance) {
state = 0;
}
delay(100);
}
// function that executes whenever data is received from master
// this function is registered as an event, see setup()
void receiveEvent(int howMany)
{
while(1 < Wire.available()) // loop through all but the last
{
Wire.write(Distanceincm);
Wire.write(count);
Wire.write(contor);
}
Master Code
#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, 6); // request 6 bytes from slave device #2
while(Wire.available()) // slave may send less than requested
{
long a = Wire.read();
Serial.print ("Distance = ");
Serial.println(a);
int b = Wire.read();
Serial.print ("Count = ");
Serial.println(b);
int c = Wire.read();
Serial.print ("CountDay = ");
Serial.println(c);
}
delay(500);
}
What i get on the serial monitor is this : enter image description here
1 Answer 1
Your code has some flaws, that I will address here:
The main problem, that you see here, is that you only let the slave write data into the buffer, when it is receiving data. But you never send data to it. So there is never anything in the buffer.
In the slave code you have
void receiveEvent(int howMany) { while(1 < Wire.available()) // loop through all but the last { Wire.write(Distanceincm); Wire.write(count); Wire.write(contor); }
Besides the unbalanced parentheses (which let's me think, that this might not be the actual code, that you are running currently), you are writing to the buffer only inside the
receiveEvent()
. That only get's called, when the master does a Master-Write transmission (viaWire.beginTransmission()
andWire.endTransmission()
). In the master code you are only usingWire.requestFrom()
. If you want to send back data on a request, you need to use theonRequest
callback of theWire
library. You need to set the appropriate callback function:Wire.onRequest(onRequestEvent);
And then write data inside that callback function:
void onRequestEvent(){ Wire.write(...) }
I will explain how to write the values correctly in a different point below.
In the
receiveEvent()
function you want to loop through all received bytes but the last. But you are not doing anything with the data. You aren't even reading it. As your code currently stands, this line does not make any sense.In your master code you are requesting data from the slave with the address
2
. Your slave code initiates theWire
library with the address4
. From that point there should never be a successful I2C transmission, unless you have another slave on the bus with the address2
(another point, where I suspect, that these codes are not exactly the ones used for the test).In the master code you are using
while(Wire.available())
because the slave might have send less data, than requested. You are testing, that the number of available bytes is not zero. But then you are reading 3 bytes without checking again, which means, when you received 2 byte, you still would try to read 3 bytes, which cannot give good results.
The condition should be
Wire.available() >= 3
, to check, if at least 3 bytes are in the buffer, because you are reading 3 bytes from it. But also the loop does not make much sense to me. As you have a binary protocol here, how would you treat additional data (meaning excess data, send after the first valid data frame)? You want to receive 3 values per transmission and you are reading 3 values in one iteration of the loop. If there where more data in the buffer, you would be reading the next 3 bytes, throwing away the first 3 bytes. I don't think, that is what you want.Instead use the return value of
Wire.requestFrom()
, which is the number of received bytes. Use it to distinguish valid transmissions, from those, which are too short:uint8_t n = Wire.requestFrom(4, 3); if(n >= 3){ // at least 3 bytes where received uint8_t value1 = Wire.read(); uint8_t value2 = Wire.read(); uint8_t value3 = Wire.read(); }
Any transmission, that is too small, will get ignored. Excess data in too long messages will also be ignored. Note, that I used
uint8_t
, which is an unsigned integer with 8 bits (1 byte) here for simplicity of that points logic. More about your data types and their implications on your code below.For the to send datatypes on the slave you have
int
andlong
.int
is a signed integer with 16 bits,long
is a signed long integer with 32 bits. (I suspect you don't need along
for the distance, as ultrasonic sensors mostly reach only 3m, but that is up to you). In contrast the argument ofWire.write()
is anuint8_t
, which has only 8 bits. That call is used to put exactly 1 byte into the buffer, not more. When you are usingWire.write(Distanceincm);
you are putting the least significant byte of
Distanceincm
into the buffer. The rest will just be thrown away (this happens at the cast fromlong
touint8_t
). This works as long, as the value is between 0 and 127 (which is the upper part of theint8_t
value range). With higher (or lower) values your received data will be garbage. Instead you need to write every byte of your data into the buffer:Wire.write(Distanceincm & 0x000000FF); Wire.write((Distanceincm & 0x0000FF00) >> 8); Wire.write((Distanceincm & 0x00FF0000) >> 16); Wire.write((Distanceincm & 0xFF000000) >> 24);
With bitwise ANDing (
&
) of the hexadecimal value0x000000FF
, we are deleting all bits, beside the least significant byte (analog with0x0000FF00
for the second least significant byte, and so on). Then we are shifting the corresponding byte to the position of the least significant byte (8 bits for the seconds least significant byte, 16 bits for the third least significant byte, and so on), which then gets used byWire.write()
. To better understand this, read about bitwise operators on the web.You have to send both integer values in a similar fashion (though they both have only 2 bytes instead of 4).
Now that we are sending more bytes, we also need to receive and read all these bytes correctly. First we need to request the correct number of bytes. 4 bytes for the
long
value and 2 bytes for bothint
values --> 8 bytes. So you need to request 8 bytes:Wire.requestFrom(4, 8);
Then you need to read these bytes into the corresponding variables:
long distance = Wire.read() | (Wire.read() << 8) | (Wire.read() << 16) | (Wire.read() << 24);
We are using the bitwise OR (
|
) to put the bytes together, shifting every byte to its correct position in thelong
value. In a similar way you are putting together the 2int
values.
Wire.requestFrom()
is a blocking call. When it exits the whole I2C message was received. It also returns the number of received bytes, I think.