I'm having an I2C issue

I'm trying to make an I2C communication between a master Arduino Nano and a slave Arduino Uno. In the master part, I want the serial of the Arduino Nano to read my input.. if I write "red" as an input, I want the master Arduino Nano to write "red" to the slave so that the Arduino Uno can detect "red" and turn on the red LED. But they're giving me an error that I don't understand on Wire.write(color); the error message is so long, but I will paste a part of it:
error: no matching function for call to 'TwoWire::write(String&)'
Wire.write(color);
note: candidate: virtual size_t TwoWire::write(uint8_t)
virtual size_t write(uint8_t);
..

Arduino Nano Master code (where the error is located):

// MASTER NANO

#include <Wire.h>
#define slave_adr 0x11
String color = "";

void setup() {
  Wire.begin();
  Serial.begin(9600);
}

void loop() {
  while (Serial.available()==0) {}
  color = Serial.readString();
  delay(100);
  Wire.beginTransmission(slave_adr);
  Wire.write(color);
  Wire.endTransmission();
  delay(500);
}

Arduino Uno slave code (until now, no errors):

// SLAVE UNO

#include <Wire.h>
#define slave_adr 0x11

String led = "";
int red = 10;
int green = 9;
int blue = 8;

void setup() {
  Wire.begin(slave_adr);
  Wire.onReceive(receiveEvent);
  pinMode(red, OUTPUT);
  pinMode(green, OUTPUT);
  pinMode(blue, OUTPUT);
}

void loop() {
}

void receiveEvent() {
  while (Wire.available() > 1) {
    char c = Wire.read();
    led += c;
  }
  if (led == "red") {
    digitalWrite(red, HIGH);
  } else {
    digitalWrite(red, LOW);
  }
}

I would say that the I2C write() function doesn't take a variable of type String as a parameter. You can probably get around this by using c strings (i.e. nulll terminated byte arrays) instead.

Your sketches are corrected/adjusted and tested. Slave received red message from Master in every 2-sec interval. The message appears on SM of Slave and the L (built-in LED) blinks for a while. If you want to receive the red message from the InputBox of the Serial Monitor, then add necessary codes in the Master Sketch. I have left that one for you as an exercise.

Master Sketch:

// MASTER NANO

#include <Wire.h>
#define slaveAdrs 0x11
char myMsg[] = "red";
void setup() 
{
  Wire.begin();
  Serial.begin(9600);
}

void loop() 
{
  Wire.beginTransmission(slaveAdrs);
  Wire.write(myMsg, sizeof myMsg);
  Wire.endTransmission();
  delay(2000);  //test interval
}

Slave Sketch:

// SLAVE UNO

#include <Wire.h>
#define slaveAdrs 0x11
#define redPin 13
char myMsg[10];
volatile bool flag = false;

void setup() 
{
  Serial.begin(9600);
  Wire.begin(slaveAdrs);
  Wire.onReceive(receiveEvent);
  pinMode(redPin, OUTPUT);
  digitalWrite(13, LOW);
}

void loop() 
{
  if(flag == true)
  {
    digitalWrite(redPin, HIGH);
    delay(200);
    digitalWrite(redPin, LOW);
    delay(200);
    Serial.println(myMsg);
    flag = false;
  }
}

void receiveEvent(int howMany) //howMany = characters/byte received
{
  for(int i=0; i<howMany; i++)
  {
    myMsg[i] = Wire.read();
  }
  myMsg[howMany] = '\0'; //insert null character
  flag = true;
}
1 Like

A much better approach is to not pass around strings over i2c. There is no good reason to pass "red" when you could just as easily pass a 0/1. If you have multiple colors possible, each one would represent a different value.

1 Like

First of all, thank you for your time on writing this code and testing it. 2nd, I'm still a beginner so I can't do your exercise since I don't understand half the codes you wrote :sweat_smile: so I will be doing my searches on some of the stuffs you wrote. And last, the thing is I want to use more LEDs, that's why I put "red" as a test. On my breadboard I have 3 LEDs: red, green, and blue. What I'm trying to do is that when I put "red" on my nano input, the red led connected to the Arduino Uno slave turns on, and when I type in "green", the green LED turns on, and same applies to blue.. so this code might not work as how I want it to for this particular project.

I tried your way, and it's not working out with me. I tried changing the input value to an integer, where 0 is red, 1 is green, and 2 is blue. .I'll paste my modified version code (just note I commented out the string parts).

Arduino Nano master code:

// MASTER NANO

#include <Wire.h>
#define slave_adr 0x11
String color = "";
int selectLED; // 0 for red / 1 for green / 2 for blue

void setup() {
  Wire.begin();
  Serial.begin(9600);
}

void loop() {
  while (Serial.available() == 0) {}
  //  color = Serial.readString();
  selectLED = Serial.read();
  delay(100);
  Wire.beginTransmission(slave_adr);
  //  Wire.write(color);
  Wire.write(selectLED);
  Wire.endTransmission();
  delay(500);
}

Arduino Uno slave code:

// SLAVE UNO

#include <Wire.h>
#define slave_adr 0x11

String led = "";
int red = 10; // 0
int green = 9; // 1
int blue = 8; // 2

void setup() {
  Wire.begin(slave_adr);
  Wire.onReceive(receiveEvent);
  pinMode(red, OUTPUT);
  pinMode(green, OUTPUT);
  pinMode(blue, OUTPUT);
}

void loop() {
}

void receiveEvent() {
  /*
    while (Wire.available() > 1) {
    char c = Wire.read();
    led += c;
    }
    if (led == "red") {
    digitalWrite(red, HIGH);
    } else {
    digitalWrite(red, LOW);
    }
  */

  while (Wire.available() > 1) {
    int readData = Wire.read();
    if (readData == 0) {
      digitalWrite(red, HIGH);
    } else {
      digitalWrite(red, LOW);
    }
  }
}

There are no errors, but the code is not working. When I put 0 in the Nano serial input, the red led won't turn on.

You can use unique codes to represent your LEDs as @blh64 has suggested. Think of it similar to a secret code between your nano and your uno. When the nano reads "red", it knows to send the number 1. When it sees "green", it knows to send the number 2.

Your uno also knows the secret code, so it can decode the number such that a 1 means turn on the red LED, 2 means turn on the green LED, and so on.

You can transfer these numbers over your I2C link as a single byte. That way you can command up to 255 LEDs in 1 byte - just 1 LED at a time.

It also saves your uno having to parse a text string to decide on the LED to turn on.

Yup, that's what I did. See my reply to @blh64

It's probably because the '0' you type in on the IDE serial monitor isn't seen as a '0'. When you read in the '0', what you are getting is the ASCII code for the character '0'. Off the top of my head the number you read for a '0' is 48 decimal.

A quick google for ASCII table should show you the relationship.

Your receiving code in your uno is looking for the value 0 rather than the character '0', which are 2 different things.

Yeah.. because I defined selectLED as an integer and did Wire.write(selectLED); which is supposed to write an integer to the uno

Yes, it does. But this line:

will put the value 48 into selectLED when you input the character '0'. The 48 will get transferred to your uno. Your uno code can't ever turn the red LED on because you can't type in a binary 0 on the serial monitor.

I changed my selectLED to char, and it still won't work. I will only paste the modified code parts.

Arduino Nano :

char selectLED; 

note that selectLED was changed from int to char

Arduino Uno :

  while (Wire.available() > 1) {
    char c = Wire.read();
    if (c == '0') {
      digitalWrite(red, HIGH);
    } else {
      digitalWrite(red, LOW);
    }

I typed 0 on the Nano serial, and the red LED still won't turn on.

What would help you is to print out the value of c on your uno.

I'm not sure but what might be happening is you type in '0' and hit enter/send. The IDE may be also adding in a carriage return and/or linefeed as well. If it's adding in a CR or LF, then for a brief moment your uno will get the '0' and turn the LED on. Your uno may then receive the CR and/or LF character which will turn your LED off again almost as soon as it's turned on.

This is in the official examples, but it puts every user in the wrong direction: "while (Wire.available() > 1)" :face_vomiting:
Sometimes I write: What is wrong with the last byte. If the last byte is wrong, then why send it ?

@ziadkassab007 GolamMostafa gave good code. I agree with the others that a single byte as a command is better. So you have to adapt the example by GolamMostafa. Please don't continue with your own code.

  char myCommand = 100;                 // char or byte

  Wire.beginTransmission(slaveAdrs);
  Wire.write(myCommand);
  Wire.endTransmission();
void receiveEvent(int howMany) //howMany = characters/byte received
{
  if (howMany == 1)          // expecting 1 byte
  {
    myCommand = Wire.read();
    flag = true;
  }
}

Is that enough to write both the Master and Slave sketch ? Please show your new complete sketches.

Then it's an implementation error. @blh64's advice is sound.

@ziadkassab007

1. The learning starts by running an example that works and then search begins about finding the reason(s) why this line of code is here.

2. To help you in finding the reasons, I have attached two files which may worth reading and practicing.
Ch-7I2CLec.pdf (337.9 KB)
Expt-7I2C.pdf (305.2 KB)

3. Basic Principles of I2C Bus
(1) At Master side, you create I2C Bus by including the following codes in the sketch.

#include<Wire.h>
Wire.begin();

(2) Bring START condition on I2C Bus, make Roll call of the slave, put data in the buffer, initiate transmission, and then bring STOP condition on the I2C Bus. To do these tasks, include the following codes in the sketch.

  Wire.beginTransmission(slaveAdrs);
  Wire.write(myMsg, sizeof myMsg);
  Wire.endTransmission();

(3) At the Slave side, you create I2C Bus by including the following codes in the sketch.

#include<Wire.h>
Wire.begin(slaveAddress);

(4) At the Slave side, declare and define the following ISR() function to which the Slave makes a jump whenever a data byte/frame/msg comes from the Master.
Declaration:

Wire.onReceive(receiveEvent);

Definition:

void receiveEvent(int howMany) //howMany = characters/byte received
{
  for(int i=0; i<howMany; i++)
  {
    myMsg[i] = Wire.read();
  }
  myMsg[howMany] = '\0'; //insert null character
  flag = true;
}

(5) Execution of print() method is not allowed in ISR() as it needs active interrupt logic; but, it remains disabled in ISR(). So, you set a flag in the SR() which will be tested by the loop() function to understand that the Slave has indeed received message from Master and now is the time to process/display data. Keep minimum codes as much as possible in the ISR() to allow other interrupts keep going like millis(), delay(), etc.

Select Newline option in the Line ending tab of the SM (Fig-1) of Master.
SerialMonitor
Figure-1:

Master Sketch:

// MASTER NANO

#include <Wire.h>
#define slaveAdrs 0x11
char myMsg[10];

void setup() 
{
  Wire.begin();
  Serial.begin(9600);
}

void loop() 
{
  while(Serial.available() == 00) //no character has yet arrived
  {
    ;  //wait
  }
  byte m = Serial.readBytesUntil('\n', myMsg, sizeof myMsg);
  myMsg[m] = '\0'; //include null-character
  //--------------------------------------
  Wire.beginTransmission(slaveAdrs);//STRAT I2C Bus, roll call in wtite mode
  Wire.write(myMsg, sizeof myMsg); //send all charcaters of myMSg array
  Wire.endTransmission();  //intiate transmission and then STOP I2C Bus
  delay(2000);  //test interval
}

Slave Sketch:

// SLAVE UNO
#include <Wire.h>
#define slaveAdrs 0x11
#define redPin 10
char myMsg[10];
volatile bool flag = false;
byte dataCount;

void setup()
{
  Serial.begin(9600);
  Wire.begin(slaveAdrs);
  Wire.onReceive(receiveEvent);
  pinMode(redPin, OUTPUT);
  digitalWrite(redPin, LOW);
}

void loop()
{
  if (flag == true)
  {
    for (int i = 0; i < dataCount; i++)
    {
      myMsg[i] = Wire.read();
    }
    myMsg[dataCount] = '\0'; //insert null character
    if (strcmp(myMsg, "red") == 0) //strcmp() returns 0 when strings matches
    {
      digitalWrite(redPin, HIGH);
      delay(200);
      digitalWrite(redPin, LOW);
      delay(200);
      Serial.println(myMsg);
      flag = false;
    }
  }
}

void receiveEvent(int howMany) //howMany = characters/byte received
{
  dataCount = howMany;
  flag = true;
}

I used this exact code, and it is not working. The red LED is just turning on and off by itself every 2 seconds. And typing red in the SM of the nano will not make the LED turn on permanently. And yes, I made my SM settings same as yours.

Sorry, I'm not sure what you mean by ISR() and SR(). I will checkout the attached files. Also, I used this new version code, it works but the led instantly turns on and off the moment I send "red". I want it to stay on until I type anything else.

When quoting/referring, please, always cite the post # number.
ISR() stands for Interrupt Service Routine and it is a function that does something and that's why the () (opening and closing parentheses) is present. It is a side job to which Slave is forced to go when a a data byte/frame/msg comes form Master. When the Slave goes to a side job at its own will (no forcing), the side job is called subroutine (SUR).

SR() is a typing mistake and it is ISR().

To stop the blink of LED at DPin-10, bring the following modification in the loop() function of the Slave of post #17.

{
      digitalWrite(redPin, HIGH);
      delay(200);
      digitalWrite(redPin, LOW);
      delay(200);
      Serial.println(myMsg);
      flag = false;
    }

====>

{
      digitalWrite(redPin, HIGH);
      //delay(200);
      //digitalWrite(redPin, LOW);
     //delay(200);
      Serial.println(myMsg);
      flag = false;
    }

To get green LED ON in response to string "green", use if-else structure.