12C two Nano's having a chat <<CLOSED PLEASE>>

I am trying to get two Nano's one master to receive data and one slave to send data. The data consists of codes only ie (ab01z).
something is missing in my sketch as nothing seems to get sent or received.
Could someone please take a look and advise me.
There is nothing attached to either nano other than the other nano.

/*
   i2c master test.ino
   this is a test for communication
   between two nano's
   The master will only read from the slave when requested to do so by the clave

   white is master   - receiver  - device 9
   yellow is slave   - sender    - device 8

   Nano PINS = A4 SDA  to A4 SDA
               A5 SCL  to A5 /SCL
               gnd     to gnd
*/


#include <Wire.h>

// VARIABLES
bool stringComplete = false;
String inputString = "";

void setup() {

  //COMM
  Wire.begin(9);                 // get on the bus
  Wire.onReceive(readData);      // register event

  Serial.begin(9600);            // start serial for debugging
  Serial.println("Receiving Started.....");
}

void loop() {

  readData(8);// from Junction

  if (stringComplete) {
    Serial.print("Code: "); Serial.print (inputString);
    //cleanup
    stringComplete = false;
    inputString = "";
  }


  delay(500);

}// end of loop

/*
   read the data from device 8
*/
void readData(int input) {

  Serial.println("Request data..");
  Wire.requestFrom(input, 4);    // request 4 bytes from  device #?

  if (Wire.available()) {
    Serial.println("Waiting..");

    char inChar = (char)Wire.read();
    inputString += inChar;

    if (inChar == 'z') {
      stringComplete = true;
      Serial.print("Got it "); Serial.println(inputString);
    }

  }
  else {
    Serial.println("not available..");
  }



}// end of readData

/*
i2c slave test.ino
this is a test for communication
between two nano's
white is master - receivers - device 9
yellow is slave - sender - device 8
*/

#include <Wire.h>

String outputString = "jo1z";// for testing only

void setup() {
Wire.begin(9); // get on the bus

Serial.begin(9600); // for debuggin
Serial.println("Sending started....");

}// end of setup

void loop() {
sendEvent();
delay(10);
}// end of loop

/*
this executes 5 times for the purpose of testing only
*/
void sendEvent() {
for (int x = 0; x < 5; x++) {
Wire.beginTransmission(9); // send to the master
Wire.write("jo1z");
Wire.endTransmission();
Serial.print("sent ");Serial.println(x);
}

}// end of send Event

Many thanks in advance

The code tags for the second sketch?

1 Like

You can't have both devices use address 9. The controller doesn't need an address so just "Wire.begin();"

Just asking... why I2C bus with only 2 ends? Are the Nanos far apart?

My I2C dislike is the time it takes to request and get data.

I use the SPI bus but at default clk/4, 4MHz, the max length is like 30cm.
I read that at 1MHz, SPI max length is 10m.
SPI docs - analog.com tech article on isolated SPI.

That's funny. How does the slave ask the master to request data?

Also requesting 4 bytes and only looking at the first one looks weird. Missing test for slave NACK or other errors.

The address clash has already be mentioned.

I would not use I2C at all. The I2C-library is blocking. If one partner is doing a little detail wrong how to switch between LOW/HIGH for the bitbanging the code freezes.

I would always use a serial connection. If the communication does not require reacting within a few milliseconds I would use softwareserial at a baudrate of 9600.
Serial (hardware "Serial" and software "SoftwareSerial" both have timeout
If something goes wrong in the communication the timeout will ensure that the code of the receiver keeps running.

Anyway if two microcontrollers have to communicate mostly I would use two ESP8266 Wemos D1 Mini or ESP8266nodeMCU or ESP32nodeMCU
price 5 to 10 $

But not ESP8266-01

All ESP-microcontrollers can be programmed with the arduino-IDE directly just the same way as Arduinos
After installing the board-definitions.

They offer communicating wireless over WiFi

  • one microcontroller as its own access-point the second connecting to this access-point
  • both connecting to your local WLAN-router
  • directly communicating wireless very fast using the ESP-NOW-protocol

The ESP-NOW-protocol requires quite some code to setup. But once you have understood how this works it is very good to use and you can transfer up to 250 bytes per message within one millisecond.

ESP-microcontrollers all work with 3.3V so this might require voltage-level-shifters for some sensors.

best regards Stefan

Thankfully, more and more peripherals are appearing with 3.3V supply. In some cases, moving to a 3.3V processor is one way to eliminate the need for level shifters.

wire.h does have some blocking commands but it has available() and read() which are not blocking at all.

Thanks for pointing out that if you code it wrong, it won't run. Does that happen with code that isn't I2C?

maurin: This is a far too unprecise question.
What do you mean by "that" ? what is an example for "code that isn't I2C?"

in this global version of your question the answer ist Yes / No/ it depens on ....
at the same time

You really should read this

best regards Stefan

made the change have yet to test it.

Thank
In exploring different methods of communication between the nano's I gave difficulty in trying to send a string using serial ie can someone show me how to.

/*
 Serial Sender test.ino

   Yellow  - sender - nano pins DO D1

 https://docs.arduino.cc/learn/built-in-libraries/software-serial#islistening
*/

#include <SoftwareSerial.h>
const byte rxPin = 1;
const byte txPin = 0;
SoftwareSerial portOne (rxPin, txPin);

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

 //INITIALIZE PORT COMM
 portOne.begin(9600);

}//end of setup

void loop() {
 
 portOneSend();
  
}// end of loop



//    FUNCTIONS
void portOneSend() {

 if (portOne.available() ) {
   for (int x=0; x > 9; x++) {
     portOne.write(x);
   }
 }
 String test = "abcd";
 portOne.write(test);  // this one needs help to send a string

}// end of portOneSend





"Serial" uses pins 0 and 1 on a Nano (and UNO). You can't use Serial and SoftwareSerial on the same pins at the same time. Pick other pins for SoftwareSerial.

Thanks for that - I changed the code and have included the newer version below, It is still not displaying the string sent in the portOneSend in the portOneListen.

this is the sender

/*
  Serial Sender test.ino

    Yellow  - sender - nano pins D2 D3

  https://docs.arduino.cc/learn/built-in-libraries/software-serial#islistening
*/

#include <SoftwareSerial.h>

const byte rxPin = 2;
const byte txPin = 3;

// Set up a new SoftwareSerial object
SoftwareSerial portOne (rxPin, txPin);

void setup() {

  //INITIALIZE PORT COMM
  portOne.begin(9600);

  //INITIALIZE SERIAL COMM --   FOR DEBUGGING
  Serial.begin(9600);
  Serial.println("Starting- sender.....");


}//end of setup

void loop() {

  portOneSend();

}// end of loop



//    FUNCTIONS
void portOneSend() {
  Serial.println("port One Send "); delay (1000);
  char test[] = "ab12";

  if (portOne.available() ) {
    portOne.write(test, sizeof(test));
    delay(1000);
    Serial.println(test);
  }
  else {
    Serial.println("one not available ");
  }

}// end of portOneSend`Preformatted text`


this is the reader

/*
  Serial Reader test.ino


   white   - reader - nano pins D2 D3
   LCD SDA                      A4
   LCD SDL                      A5

*/

#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 20, 4);

#include <SoftwareSerial.h>
const byte rxPin = 2;
const byte txPin = 3;
SoftwareSerial portOne (rxPin, txPin);


void setup() {

  //INITIALIZE COMM
  portOne.begin(9600);

  //INITIALIZE - LCD
  lcd.init();
  lcd.backlight();
  lcd.setCursor( 0, 0);  lcd.print( "listening:");

  Serial.begin(9600);// DEBUGGING ONLY
  Serial.println("Reader - starting ");

}//end of setup

void loop() {

  portOneListen();

}// end of loop



//    FUNCTIONS


void portOneListen() {

  portOne.listen();
  Serial.println("one:listening"); delay(1000);

  if (portOne.available() ) {
    lcd.setCursor( 0, 2);  lcd.print( "one: available");
    char temp = portOne.read();
    lcd.setCursor( 5, 3);  lcd.print( temp);
    Serial.println(temp);
  }
  else
  {
    lcd.setCursor( 0, 1);  lcd.print( "one: waiting");
  }
}// end of portOneListen





/*
    if (Serial.available())
      {
           char data = Serial.read();
           Serial.print(data);
      }
*/

Blockquote

hope I got it right in posting this time.

Thanks for this information - I don't have a project for this at the moment - still in the process of learning stuff -

What's that good for in the sender? It checks for something already sent by portOne.

OK took it out - but still not receiving anything.

/*
  Serial Reader test.ino


   white   - reader - nano pins D2 D3
   LCD SDA                      A4
   LCD SDL                      A5

*/

#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 20, 4);

#include <SoftwareSerial.h>
const byte rxPin = 2;
const byte txPin = 3;
SoftwareSerial portOne (rxPin, txPin);


void setup() {

  //INITIALIZE COMM
  portOne.begin(9600);

  //INITIALIZE - LCD
  lcd.init();
  lcd.backlight();
  lcd.setCursor( 0, 0);  lcd.print( "listening:");

  Serial.begin(9600);// DEBUGGING ONLY
  Serial.println("Reader - starting ");

}//end of setup

void loop() {

  portOneListen();

}// end of loop



//    FUNCTIONS


void portOneListen() {

  portOne.listen();
  Serial.println("one:listening"); delay(1000);

  if (portOne.available() ) {
    lcd.setCursor( 0, 2);  lcd.print( "one: available");
    char temp = portOne.read();
    lcd.setCursor( 5, 3);  lcd.print( temp);
    Serial.println(temp);
  }
  else
  {
    lcd.setCursor( 0, 1);  lcd.print( "one: waiting");
  }
}// end of portOneListen





/*
    if (Serial.available())
      {
           char data = Serial.read();
           Serial.print(data);
      }
*/

I guess that you are better off with I2C than SoftwareSerial connection.

See #19.

Sender-Code

/*
  Serial Sender test.ino
    Yellow  - sender - nano pins D2 D3
  https://docs.arduino.cc/learn/built-in-libraries/software-serial#islistening
*/

#include <SoftwareSerial.h>

const byte rxPin = 2;
const byte txPin = 3;

// Set up a new SoftwareSerial object
SoftwareSerial portOne (rxPin, txPin);

receivercode:

/*
  Serial Reader test.ino
   white   - reader - nano pins D2 D3
   LCD SDA                      A4
   LCD SDL                      A5
*/

#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 20, 4);

#include <SoftwareSerial.h>
const byte rxPin = 2;
const byte txPin = 3;
SoftwareSerial portOne (rxPin, txPin);

With a serial connection Rx/Tx must be crossover

Sender

const byte rxPin = 2;
const byte txPin = 3;

Receiver

const byte rxPin = 3;  //<= Crosover Rx-to-Tx
const byte txPin = 2; // <= Crossover Tx-to-Rx

I guess this is all

If you think I2C is easier. Can you post a demo-code?

best regards Stefan

Master Sketch:

#include <Wire.h>

void setup()
{
  Wire.begin();                 // get on the bus
  Serial.begin(9600);            // start serial for debugging

  Wire.beginTransmission(9);
  byte busStatus = Wire.endTransmission();
  if (busStatus != 0)
  {
    Serial.print("Salve is not found.");
    while (1);
  }
  Serial.print("Salve is found.");
}

void loop()
{
  Wire.requestFrom(9, 5);   //string to receive "ab01z"
  for (int i = 0; i < 5; i++)
  {
    char x = Wire.read();
    Serial.print(x);
  }
  Serial.println();
  delay(1000);
}

Slave Sketch:

#include <Wire.h>

void setup()
{
  Wire.begin(9);                 // get on the bus
  Serial.begin(9600);            // start serial for debugging
  Wire.onRequest(sendEvent);
}

void loop()
{

}

void sendEvent()
{
  Wire.print("ab01z");  //sending this string: "ab01z" to Master when asked.
}

Check that Master's Serial Monitor shows the string "ab01z" at 1-sec interval.

Hi Golam,

I tested your code with two Arduino Pro Micro as I don't have Leonardos at hand.
It works.

One problem that newcomers have is that Demo-Codes are mostly rather small documented. And that the demo-codes don't give hints about possible hardware-problems or restrictions.

Additionally this demo-code shows

  • how to use debug-output that prints a fixed text the variable-NAME and the variable-VALUE all in one line.

  • there is even a "intervalled" version where you can define how often the output shall be printed. This is very handy for fast running loops

  • an easy to use non-blocking timing

  • how to use functions

and serial debugoutput that gives feedback about what lines of code have been executed

So here is the modified sender-code

// start of macros dbg and dbgi
// You don't have to understand immidiately what the macro-code  
// inside these two macros does. Macros work on a different level than code
// just use them as described below
// with the macros you can add serial debug-output with a single line of code
// that does print a fixed text the variable-NAME and the variables VALUE
// if you already know these lines delete them
#define dbg(myFixedText, variableName) \
  Serial.print( F(#myFixedText " "  #variableName"=") ); \
  Serial.println(variableName);
// usage: dbg("1:my fixed text",myVariable);
// myVariable can be any variable or expression that is defined in scope


#define dbgi(myFixedText, variableName,timeInterval) \
  do { \
    static unsigned long intervalStartTime; \
    if ( millis() - intervalStartTime >= timeInterval ){ \
      intervalStartTime = millis(); \
      Serial.print( F(#myFixedText " "  #variableName"=") ); \
      Serial.println(variableName); \
    } \
  } while (false);
// usage: dbgi("2:my fixed text",myVariable,1000);
// myVariable can be any variable or expression that is defined in scope
// third parameter is the time in milliseconds that must pass by until the next time a 
// Serial.print is executed
// end of macros dbg and dbgi




// The I2C-Bus was designed for exchanging data over SHORT distances = SHORT wires
// 10cm up to 50 cm. Everything that goes beyond 1m can be problematic
// if you want to send/receive data over a longer distance with I2C there are 
// specialised long distance I2C-chips available
// the easier solution is to use serial based on RS485 which is very good protected
// against electromagnetic noise
 
// the REAL code starts here but makes use of the macros above

#include <Wire.h>

// helperfunction telling the serial-monitor the sourcecode file and compiletime
void PrintFileNameDateTime() {
  Serial.println( F("Code running comes from file ") );
  Serial.println( F(__FILE__));
  Serial.print( F("  compiled ") );
  Serial.print(F(__DATE__));
  Serial.print( F(" ") );
  Serial.println(F(__TIME__));  
}

// helper-function for easy to use non-blocking timing
boolean TimePeriodIsOver (unsigned long &periodStartTime, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();  
  if ( currentMillis - periodStartTime >= TimePeriod )
  {
    periodStartTime = currentMillis; // set new expireTime
    return true;                // more time than TimePeriod) has elapsed since last time if-condition was true
  } 
  else return false;            // not expired
}

unsigned long MyTestTimer = 0;                   // variables MUST be of type unsigned long
const byte    OnBoard_LED = 2;


void BlinkHeartBeatLED(int IO_Pin, int BlinkPeriod) {
  static unsigned long MyBlinkTimer;
  pinMode(IO_Pin, OUTPUT);
  
  if ( TimePeriodIsOver(MyBlinkTimer,BlinkPeriod) ) {
    digitalWrite(IO_Pin,!digitalRead(IO_Pin) ); 
  }
}



void setup(){
  // one of the very rare cases where a delay makes sense
  // an Arduino pro Micro needs some time to establish the serial connection
  delay(2000); 
  Serial.begin(115200);
  delay(2000); 
  Serial.println( F("Setup-Start") );
  PrintFileNameDateTime();

  Wire.begin();                 // get on the bus
  dbg("Wire.begin() done",0);
  Wire.beginTransmission(9);
  dbg("Wire.beginTransmission(9)",1);
  
  byte busStatus = Wire.endTransmission();
  dbg("busStatus = Wire.endTransmission()",busStatus);
  
  if (busStatus != 0)  {
    Serial.print("Slave is not found.");
    while (1);
  }
  Serial.print("Slave is found.");
}


void requestToSlave() {
  dbg("entering requestToSlave()",10);
  Wire.requestFrom(9, 5);   //string to receive "ab01z"
  dbg("Wire.requestFrom(9, 5); done",11);

  Serial.print("received from slave: #");
  
  for (int i = 0; i < 5; i++)  {
    char x = Wire.read();
    Serial.print(x);
  }
  Serial.println("#");
}


void loop(){
  BlinkHeartBeatLED(OnBoard_LED,500);

  if ( TimePeriodIsOver(MyTestTimer,1000) ) {
    requestToSlave();
  }  
}

and here the modified receiver-code

// start of macros dbg and dbgi
// You don't have to understand immidiately what the macro-code  
// inside these two macros does. Macros work on a different level than code
// just use them as described below
// with the macros you can add serial debug-output with a single line of code
// that does print a fixed text the variable-NAME and the variables VALUE
// if you already know these lines delete them
#define dbg(myFixedText, variableName) \
  Serial.print( F(#myFixedText " "  #variableName"=") ); \
  Serial.println(variableName);
// usage: dbg("1:my fixed text",myVariable);
// myVariable can be any variable or expression that is defined in scope

#define dbgi(myFixedText, variableName,timeInterval) \
  do { \
    static unsigned long intervalStartTime; \
    if ( millis() - intervalStartTime >= timeInterval ){ \
      intervalStartTime = millis(); \
      Serial.print( F(#myFixedText " "  #variableName"=") ); \
      Serial.println(variableName); \
    } \
  } while (false);
// usage: dbgi("2:my fixed text",myVariable,1000);
// myVariable can be any variable or expression that is defined in scope
// third parameter is the time in milliseconds that must pass by until the next time a 
// Serial.print is executed
// end of macros dbg and dbgi

void PrintFileNameDateTime() {
  Serial.println( F("Code running comes from file ") );
  Serial.println( F(__FILE__));
  Serial.print( F("  compiled ") );
  Serial.print(F(__DATE__));
  Serial.print( F(" ") );
  Serial.println(F(__TIME__));  
}


boolean TimePeriodIsOver (unsigned long &periodStartTime, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();  
  if ( currentMillis - periodStartTime >= TimePeriod ){
    periodStartTime = currentMillis; // set new expireTime
    return true;                // more time than TimePeriod) has elapsed since last time if-condition was true
  } 
  else return false;            // not expired
}

unsigned long MyTestTimer = 0;                   // variables MUST be of type unsigned long
const byte    OnBoard_LED = 13;


void BlinkHeartBeatLED(int IO_Pin, int BlinkPeriod) {
  static unsigned long MyBlinkTimer;
  pinMode(IO_Pin, OUTPUT);
  
  if ( TimePeriodIsOver(MyBlinkTimer,BlinkPeriod) ) {
    digitalWrite(IO_Pin,!digitalRead(IO_Pin) ); 
  }
}


// The I2C-Bus was designed for exchanging data over SHORT distances = SHORT wires
// 10cm up to 50 cm. Everything that goes beyond 1m can be problematic
// if you want to send/receive data over a longer distance with I2C there are 
// specialised long distance I2C-chips available
// the easier solution is to use serial based on RS485 which is very good protected
// against electromagnetic noise
 

#include <Wire.h>

void setup(){
  // one of the very rare cases where a delay makes sense
  // an Arduino Leonardo needs some time to establish the serial connection
  delay(2000); 
  Serial.begin(115200);
  delay(2000); 
  Serial.println( F("Setup-Start") );
  PrintFileNameDateTime();
  Wire.begin(9);                 // get on the bus
  dbg("Wire.begin(9) done",0);
  Wire.onRequest(sendEvent);  
  dbg("Wire.onRequest(sendEvent);  done",1);  
}


void sendEvent() {
  dbg("entering sendEvent()",10);
  Wire.print("ab01z");  //sending this string: "ab01z" to Master when asked.
  dbg("Wire.print("ab01z") done",11);
}


void loop() {
  BlinkHeartBeatLED(OnBoard_LED,500);

  if ( TimePeriodIsOver(MyTestTimer,1000) ) {

  }  
}

best regards Stefan

2 Likes