Whatis wrong with this Writer-Reader sketch?

I made a project by two UNO, the first one used 2 potentiometers as analog input, then send the ADC data to the second UNO via IIC, the second UNO use these data as input to 2 servos. And, my reference is here - Master Writer/Slave Receiver

the Writer code:

#include <Wire.h>

int potentioMeter2 = 0;
int potentioMeter1 = 0;

byte potentialSend1 = 0;
byte potentialSend2 = 0;

void setup()
{
  pinMode(A0, INPUT);
  pinMode(A1, INPUT);
  
  Wire.begin(); // join i2c bus (address optional for master)
}

void loop()
{
  potentioMeter1 = analogRead(A0);
  potentioMeter2 = analogRead(A1);
  
  potentialSend1 = map(potentioMeter1, 0, 1023, 0, 180);
  potentialSend2 = map(potentioMeter2, 0, 1023, 0, 180);
  
  Wire.beginTransmission(0); // transmit to device #0
  Wire.write("Potential1 is: ");        // sends 15 bytes
  Wire.write(potentialSend1);              // sends one byte  
  Wire.endTransmission();    // stop transmitting
  
  Wire.beginTransmission(0); // transmit to device #0
  Wire.write("Potential2 is: ");        // sends 15 bytes
  Wire.write(potentialSend2);              // sends one byte  
  Wire.endTransmission();    // stop transmitting
 
  delay(10); // Delay a little bit to improve simulation performance
}

the Reader code:

#include <Wire.h>
#include <Servo.h>

Servo myServo1;
Servo myServo2;

void setup()
{
  Wire.begin(0);                // join i2c bus with address #0
  Wire.onReceive(receiveEvent1); // register event for myServo1
  Wire.onReceive(receiveEvent2); // register event for myServo2
  Serial.begin(9600);           // start serial for output

  myServo1.attach(5);
  myServo2.attach(6);
}

void loop()
{
  delay(100);
}

// function that executes whenever data is received from master
// this function is registered as an event, see setup()
void receiveEvent1(int howMany)
{
  while(1 < Wire.available()) // loop through all but the last
  {
    char c = Wire.read(); // receive byte as a character
    Serial.print(c);         // print the character
  }
  int x = Wire.read();    // receive byte as an integer
  Serial.println(x);         // print the integer
  myServo1.write(x);
}

void receiveEvent2(int howMany)
{
  while(1 < Wire.available()) // loop through all but the last
  {
    char c = Wire.read(); // receive byte as a character
    Serial.print(c);         // print the character
  }
  int x = Wire.read();    // receive byte as an integer
  Serial.println(x);         // print the integer
  myServo2.write(x);
}

now, the problem is, when I started simulation, I found there was value in the first UNO, and there wasn't value in the second UNO, what maybe the reason for this situation?

Try understanding the Wire library, you can specify only 1 receiveEvent handler.

Start with only transferring the values without text.

Finish with hardware instead of only simulation. Heaven knows how the simulator implements I2C.

2 Likes

The address '0' is a special broadcast address. I think you should try some different address.

1 Like

Yes, I changed it from "0" to "1", then the date could be transferred - when I turning one of the 2 potentiometers, one of the 2 servos was twisting.

I found the 2 receiveEvents were all works - I could see the 2 data flow changing in the Serial Monitor, but only one Servo moving, the other fixed in most of the time, and some time would just sweep a little.

Only in your imagination :frowning:

Exclude one and you'll still see all data in Serial Monitor but only one servo reacting.

I'm sorry to say, but the official examples by Arduino are wrong.
We don't use readable text with variable length for I2C. Since the I2C bus is not a stream of data, but packages of data.

I agree with DrDiettrich, it is easier to transfer fixed size binary packages.
You could for example transfer an array of 2 integers.

I2C address 0 is the special "broadcast" address.
There are other special addresses, but those are not used on the Uno board. Most Arduino examples start with I2C address 8.

Do you use Tinkercad ? Can you give a public link to your project ?
Don't use the URL of your browser, that is private and can not be opened by others. There is perhaps a button somewhere to share your project.

Right.

Thanks.

I think that you used a "Send To" for a shared project instead of a public link.
Is that a shared project now ? I can not make a copy for myself of it and I can not store it to my own projects.
I have added a GND wire between the Arduino boards and two text labels. Can you see that ?

You might want to remove that link in your Reply #10 or someone else might change things.

Can you change the sketch to transfer a block of binary data with a fixed size ?

Yes, I saw your GND and text labels. How to make this project shared?
Could you make a copy of it now? I have clicked "Invite people" - "Generate new link" in "Send to".

That was indeed inviting others to take part in your project.
I rather make a new project to show an example and let you fix your own project.
There should be an option to make a public link, but I don't know how to do that.

You can try to improve your sketches and show them here. That is better for everyone. I just happen to use Tinkercad now and then, but most others don't.

It was said:

That is the same "share your project with others" link. I can not even save that to my own projects in Tinkercad. Please remove this link as well from this forum.

I can not find that link to make it public. Tinkercad has changed that, and not for the good. There is an option in the settings to make a project either public or private.

I'm a fan of Wokwi: https://wokwi.com/
They don't support two Arduino boards at the same time yet.

I adjusted the code, while the output in Serial Monitor was wrong:

Potentio1 is:  80

Potentio2 is:  @0

Potentio1 is:  80

Potentio2 is:  @0

Potentio1 is:  80

Potentio2 is:  @0

This is my master code:

#include <Wire.h>

int potentioMeter1 = 0;
int potentioMeter2 = 0;

byte potentialSend1 = 0;
byte potentialSend2 = 0;

void setup()
{
  pinMode(A0, INPUT);
  pinMode(A1, INPUT);
  
  Wire.begin(); // join i2c bus (address optional for master)
}

void loop()
{
  potentioMeter1 = analogRead(A0);
  potentioMeter2 = analogRead(A1);
  
  potentialSend1 = map(potentioMeter1, 0, 1023, 0, 180);
  potentialSend2 = map(potentioMeter2, 0, 1023, 0, 180);
  
  //Wire.beginTransmission(8); // transmit to device #8
  Wire.beginTransmission(8); // transmit to device 1
  Wire.write("Potentio1 is:  ");        // sends 15 bytes
  Wire.write(potentialSend1);              // sends one byte  
  Wire.write("Potentio2 is:  ");        // sends 15 bytes
  Wire.write(potentialSend2);              // sends one byte  
  Wire.endTransmission();    // stop transmitting
 
  delay(10); // Delay a little bit to improve simulation performance
}

This is the slave code:

#include <Wire.h>
#include <Servo.h>

Servo myServo1;
Servo myServo2;

byte dataReceived[32] = {};	// The Wire library implementation uses a 32 byte buffer, 
							// therefore any communication should be within this limit. 
							// Exceeding bytes in a single transmission will just be dropped.
int dataIndex = 0;

void setup()
{
  //Wire.begin(8);                // join i2c bus with address #8
  Wire.begin(8);                  
  Wire.onReceive(receiveEvent); // register event for myServo1 and myServo2
 
  Serial.begin(9600);           // start serial for output

  myServo1.attach(5);
  myServo2.attach(6);
}

void loop()
{
  for(int i = 0; i <15 ; i++){
    Serial.print((char)dataReceived[i]);
  }
  Serial.println((int)dataReceived[16]);
  
  for(int i = 16; i < 32 ; i++){
    Serial.print((char)dataReceived[i]);
  }
  Serial.println((unsigned int)dataReceived[32]);
  
  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
  while(0 < Wire.available()) // loop through all
  {
    //char c = Wire.read(); // receive byte as a character
    //Serial.print(c);         // print the character
    dataReceived[dataIndex] = Wire.read();
    dataIndex ++;    
  }
  
  dataIndex = 0;
  //int x = Wire.read();    // receive byte as an integer
  //Serial.println(x);         // print the integer
  //myServo1.write(x);
}

Would this circuit be the same ?

As I wrote before, the official examples are bad and we don't use readable ASCII text over I2C. If you don't mind, then I will not fix your sketch, but I go straight to sending two bytes over I2C.
Can you take a look at the code. It sends two bytes and it receives two bytes. That's all.
I have also changed the variables a little, but that is just an extra.

Master (Controller) sketch:

#include <Wire.h>

const int potmeter1Pin = A0;
const int potmeter2Pin = A1;

void setup()
{
  Serial.begin(9600);
  Serial.println("The Master (Controller) sketch has started");
  
  Wire.begin(); // join i2c bus (address optional for master)
}

void loop()
{
  // Using local variables when they are only used local
  // The analogRead() function can be used without pinMode().
  
  int potentioMeter1 = analogRead(potmeter1Pin);
  int potentioMeter2 = analogRead(potmeter2Pin);
  
  byte dataSend[2];

  dataSend[0] = map(potentioMeter1, 0, 1023, 0, 180);
  dataSend[1] = map(potentioMeter2, 0, 1023, 0, 180);
  
  Wire.beginTransmission(8);    // select I2C address 8
  Wire.write(dataSend[0]);      // one byte
  Wire.write(dataSend[1]);      // one byte  
  Wire.endTransmission();       // finish I2C session
  
  // Delay a little bit to improve simulation performance
  // Delay a little more to prevent that the I2C bus is too busy
  delay(20); 
}

Slave (Target) sketch:

#include <Wire.h>
#include <Servo.h>

Servo myServo1;
Servo myServo2;

// The Wire library implementation uses a 32 byte buffer, 
// therefore any communication should be within this limit. 
// Exceeding bytes in a single transmission will just be dropped.
volatile byte dataReceived[2];
volatile bool newData;


void setup()
{
  Serial.begin(9600);           // start serial for output
  Serial.println("The Slave (Target) sketch has started");
  
  Wire.begin(8);                // join i2c bus with address #8
  Wire.onReceive(receiveEvent); // register event for myServo1 and myServo2
 
  myServo1.attach(5);
  myServo2.attach(6);
}


// No delay in the loop().
// Continously check if something needs to be done.
//
// In the loop(), the Servo functions and Serial functions
// can be called without problem.
void loop()
{
  if(newData)
  {
    myServo1.write(dataReceived[0]);
    myServo2.write(dataReceived[1]);
    newData = false;
  }
}


// This function executes whenever data is received from master.
// This function is registered as an event, see setup().
// This is run from a interrupt, keep the code short and fast.
void receiveEvent(int howMany)
{
  if(howMany == 2)          // expecting two bytes
  {
    dataReceived[0] = (byte) Wire.read();  // read first byte
    dataReceived[1] = (byte) Wire.read();  // read second byte
    newData = true;
  }
}

If you have that working, then you can improve the sketch by sending and receiving an array as an array without using [0] and [1]

This is the note, my project works. Thanks, I got some tricks also from you drawing. Now, I make a new project based on SPI.

There is a small question: how about the argument "howMany" in "
receiveEvent(int howMany)"?

See: Wire - Arduino Reference