Sending an encoder value over i2c between two arduinos

Hello,
First time posting! :slight_smile:
I am troubleshooting a project where I am using two arduinos to communicate.

The components are:
Encoder:

  • A signal, B signal, 5v, gnd
    Arduino Nano 328:
  • Reads encoder using encoder.h library via D2 and D3
  • saves long encoder value in a union then sends individual bytes over i2c SDA & SCL
    Adafruit Metro 328 (Uno clone):
  • requests 4 bytes from nano via i2c SDA & SCL
  • saves 4 bytes into union in the same order they were sent
  • reads long from union

Here's the problem: When I set everything up to read the encoder value with the nano and then send it over i2c, nothing appears on the serial monitor connected to the Metro. I suspect this has to do with interrupts and/or delays messing with the i2c or encoder library, but I am out of my depth here.

Here's the nano/encoder code:

#include <Wire.h>
#include <Encoder.h>

#define ENCODER_USE_INTERRUPTS
#define ENCODER_OPTIMIZE_INTERRUPTS

// Initializations
#define encoderPinA 2
#define encoderPinB 3
// #define encoderPinZ 4
Encoder myEnc(encoderPinA, encoderPinB);

long ePos = -999; // encoder position

union u_tag { // allow long to be read as 4 seperate bytes
   byte b[4]; // 4 bytes to be sent over I2C
   long LePos; // encoder position as 4 byte long
} u;



void setup() {
	  Wire.begin(8);                // join i2c bus with address #8
	  Wire.onRequest(requestEvent); // register event
	  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  	delay(100);
}


void requestEvent() { // i2c sending function
	// respond with message of 4 bytes
	// as expected by master

	digitalWrite(LED_BUILTIN, LOW);
	ePos = myEnc.read(); // read encoder value (2 byte int)
	u.LePos = ePos;
	Wire.write(u.b[0]);  // send 4 bytes over I2C
	Wire.write(u.b[1]);  // reconstructed in the same order by master
	Wire.write(u.b[2]); 
	Wire.write(u.b[3]); 
	delay(100); // allow all bytes to send
	digitalWrite(LED_BUILTIN, HIGH);
}

Note: I have tried with and without delays and the LEDs, but am too scared to try using nointerrupts() for fear of losing an encoder reading pulse.

Here is the master reader code on the Metro:

#include <Wire.h>

long ePos = 0; // encoder position

union u_tag { // allow long to be read as 4 seperate bytes
   byte b[4]; // 4 bytes to be received over I2C
   long LePos; // encoder position as 4 byte long
} u;

void setup() {
  Wire.begin();        // join i2c bus (address optional for master)
  Serial.begin(9600);  // start serial for output
  Serial.println("Serial Start");
}

void loop() {
  Wire.requestFrom(8, 4);    // request 6 bytes from slave device #8
  Serial.print("Incoming: ");
  byte index = 0;
  while (Wire.available()) { // slave may send less than requested
    if(index == 0){
    	u.b[index] = Wire.read();
    }
    if(index == 1){
    	u.b[index] = Wire.read();
    }
    if(index == 2){
    	u.b[index] = Wire.read();
    }
    if(index == 3){
    	u.b[index] = Wire.read();
    }
    index++;
  }

  Serial.print(u.LePos, DEC );
  Serial.print(" = ");

  Serial.print(u.b[0], HEX );

  Serial.print(" : ");
  Serial.print(u.b[1], HEX );
  
  Serial.print(" : ");
  Serial.print(u.b[2], HEX );
  
  Serial.print(" : ");
  Serial.print(u.b[3], HEX );

  Serial.println();
  delay(100);
}

I have successfully gotten the unions (first time with those) to correctly save a value, increment the value, and send the bytes over i2c to the uno, where it is constructed and then printed to the serial monitor. Using a single arduino, I have also successfully read and then printed the encoder value to the serial monitor using a union to print the individual bytes.

To test the i2c functionality I commented out the encoder sections and manually incremented the 'encoder value' then sent it over i2c. This works, and using the master code from above, the Metro prints the long value correctly after saving it to the union.

Here's the test code:

// Wire Slave Sender
// by Nicholas Zambetti <http://www.zambetti.com>

// Demonstrates use of the Wire library
// Sends data as an I2C/TWI slave device
// Refer to the "Wire Master Reader" example for use with this

// Created 29 March 2006

// This example code is in the public domain.


#include <Wire.h>
// #include <Encoder.h>

// #define ENCODER_USE_INTERRUPTS
// #define ENCODER_OPTIMIZE_INTERRUPTS

// Initializations
// #define encoderPinA 2
// #define encoderPinB 3
// #define encoderPinZ 4
// Encoder myEnc(encoderPinA, encoderPinB);

long ePos = -999; // encoder position

union u_tag { // allow long to be read as 4 seperate bytes
   byte b[4]; // 4 bytes to be sent over I2C
   long LePos; // encoder position as 4 byte long
} u;



void setup() {
	  Wire.begin(8);                // join i2c bus with address #8
	  Wire.onRequest(requestEvent); // register event
	  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  	delay(100);
  	ePos += 1;
}

// function that executes whenever data is requested by master
// this function is registered as an event, see setup()
void requestEvent() {
	// respond with message of 4 bytes
	// as expected by master

	// digitalWrite(LED_BUILTIN, LOW);
	// ePos = myEnc.read(); // read encoder value (2 byte int)
	u.LePos = ePos;
	Wire.write(u.b[0]);  // send 4 bytes over I2C
	Wire.write(u.b[1]);  // reconstructed in the same order by master
	Wire.write(u.b[2]); 
	Wire.write(u.b[3]); 
	// delay(100);
	// digitalWrite(LED_BUILTIN, HIGH);
}

I've been furiously scratching my head, please help save my hair!

-Trev

Are you able to establish I2C communication without encoder? Remove encoder library and try to send some hardcoded data first.

alesam,
I have been able to communicate between the two boards, with the third code snippet on the nano and the second snippet running on the metro. Because this worked, and the encoder library worked independantly, I have to assume that the issues lie between the encoder and wire libraries somewhere.

@OP

Your Sender and Receiver codes are slightly modified/changed as follows. Please, upload and post the results:
NANO Codes:

#include <Wire.h>
#include <Encoder.h>

#define ENCODER_USE_INTERRUPTS
#define ENCODER_OPTIMIZE_INTERRUPTS

// Initializations
#define encoderPinA 2
#define encoderPinB 3
// #define encoderPinZ 4
Encoder myEnc(encoderPinA, encoderPinB);

long ePos = -999; // encoder position

union u_tag { // allow long to be read as 4 seperate bytes
   byte b[4]; // 4 bytes to be sent over I2C
   long LePos; // encoder position as 4 byte long
} u;



void setup() {
  Wire.begin(8);                // join i2c bus with address #8
  Wire.onRequest(requestEvent); // register event
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
   //delay(100);
}


void requestEvent() { // i2c sending function
 // respond with message of 4 bytes
 // as expected by master

 digitalWrite(LED_BUILTIN, LOW);
 ePos = myEnc.read(); // read encoder value (2 byte int)
 u.LePos = ePos;
 Wire.write(u.b[0]);  // send 4 bytes over I2C
 Wire.write(u.b[1]);  // reconstructed in the same order by master
 Wire.write(u.b[2]); 
 Wire.write(u.b[3]); 
 //delay(100); // allow all bytes to send
 digitalWrite(LED_BUILTIN, HIGH);
}

//------------------------------------------------------------

UNO Codes:

#include <Wire.h>

long ePos = 0; // encoder position

union u_tag { // allow long to be read as 4 seperate bytes
   byte b[4]; // 4 bytes to be received over I2C
   long LePos; // encoder position as 4 byte long
} u;

void setup() {
  Wire.begin();        // join i2c bus (address optional for master)
  Serial.begin(9600);  // start serial for output
  Serial.println("Serial Start");
}

void loop() 
{
  Wire.requestFrom(8, 4);    // request 6 bytes from slave device #8
  Serial.print("Incoming: ");
  
  for (int i=0; i<4; i++) //requestFrom() is a looping code; it terminates when all requested has come
  {
      u.b[i] = Wire.read();  //data bytes come from FIFO Buffer that has been filled up by requestFrom()
  }

/*
//byte index = 0;
  //while (Wire.available()) { // slave may send less than requested
    //if(index == 0){
   // u.b[index] = Wire.read();
    //}
    //if(index == 1){
    // u.b[index] = Wire.read();
   // }
    //if(index == 2){
     //u.b[index] = Wire.read();
   // }
    if(index == 3){
     u.b[index] = Wire.read();
    }
    index++;
  }
*/

  Serial.print(u.LePos, DEC );
  Serial.print(" = ");

  Serial.print(u.b[0], HEX );

  Serial.print(" : ");
  Serial.print(u.b[1], HEX );
  
  Serial.print(" : ");
  Serial.print(u.b[2], HEX );
  
  Serial.print(" : ");
  Serial.print(u.b[3], HEX );

  Serial.println();
//  delay(100);
}

Golam,
Thank you for your comment, but when I upload I am having the same issue that nothing is being read via the i2c connection. I found someone else has had the same issue in the past: High Performance Encoder Library and I2C Not Compatible? - Project Guidance - Arduino Forum
It seems like there is an issue between the wire library and the encoder library, most likely due to interrupts. What I am trying for now is to write my own encoder code with interrupts to see if I can get that to send over i2c without issues. I may switch to spi also, it seems like people resort to that when i2c doesn't work. (more wires to connect though ): )
-Trev

@OP

1. I don't have the Encoder device; so, I can't test my ideas.

2. Please, try the following (slightly modified) Sketch for the NANO. If encoder signal is being acquired on interrupts, then the signal should not be collected under void requestEvent() handler which is an interrupt context where the interrupt logic is expected to remain disabled. Instead, the encoder signal should be acquired under loop() function.

#include <Wire.h>
#include <Encoder.h>

#define ENCODER_USE_INTERRUPTS
#define ENCODER_OPTIMIZE_INTERRUPTS
bool flag1 = false;

// Initializations
#define encoderPinA 2
#define encoderPinB 3
// #define encoderPinZ 4
Encoder myEnc(encoderPinA, encoderPinB);

long ePos = -999; // encoder position

union u_tag { // allow long to be read as 4 seperate bytes
   byte b[4]; // 4 bytes to be sent over I2C
   long LePos; // encoder position as 4 byte long
} u;



void setup() {
  Wire.begin(8);                // join i2c bus with address #8
  Wire.onRequest(requestEvent); // register event
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
   //delay(100);

   if (flag1 == true)
  {
     digitalWrite(LED_BUILTIN, LOW);
     ePos = myEnc.read(); // read encoder value (2 byte int)
     u.LePos = ePos;
     Wire.write(u.b[0]);  // send 4 bytes over I2C
     Wire.write(u.b[1]);  // reconstructed in the same order by master
     Wire.write(u.b[2]); 
     Wire.write(u.b[3]); 
     //delay(100); // allow all bytes to send
     digitalWrite(LED_BUILTIN, HIGH);
     flag1 = false;
  }

}


void requestEvent() 
{ // i2c sending function
 // respond with message of 4 bytes
 // as expected by master

    flag1 = true;

 
}

Golam,
Thank you so much, it works!
The issue was definitely due to interrupts, that myEnc.read() call inside the i2c command was exactly what was causing the hangup. All I had to do was write the statement in the loop instead:

void loop() {
if(ePos != myEnc.read()) ePos = myEnc.read();
}

I suspect your code would run too, my change just seemed simpler to do.

-Trev

It vvorks! It's vvorking!

The encoder I am using is this one: NEMA14-AMT112S Series | CUI Devices
It is a stepper/encoder combo.

There is more to this project, I will be communicating with a display, SD card and a stepper driver in addition to the nano. The other elements worked just fine, so this Encoder>Nano>i2c>Uno was the last thing holding me back from finishing this project.

Here is the working code on the nano:

#include <Wire.h>
#include <Encoder.h>

#define ENCODER_USE_INTERRUPTS
#define ENCODER_OPTIMIZE_INTERRUPTS

// Initializations
#define encoderPinA 2
#define encoderPinB 3
// #define encoderPinZ 4 // encoder zero alignment position signal
Encoder myEnc(encoderPinA, encoderPinB);

long ePos = -999; // encoder position
bool LEDstate = false;

union u_tag { // allow long to be read as 4 seperate bytes
   byte b[4]; // 4 bytes to be sent over I2C
   long LePos; // encoder position as 4 byte long
} u;

void setup() {
  Wire.begin(8);                // join i2c bus with address #8
  Wire.onRequest(requestEvent); // register event
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
	if(ePos != myEnc.read()) ePos = myEnc.read(); // read encoder value (4 byte long))
}


void requestEvent() { // i2c sending function
 // respond with message of 4 bytes as expected by master
 digitalWrite(LED_BUILTIN, LEDstate); // write to LED if i2c is sending
 LEDstate = !LEDstate; // change LED every other time (to avoid delays but still be visible)
 u.LePos = ePos;
 Wire.write(u.b[0]);  // send 4 bytes over I2C
 Wire.write(u.b[1]);  // reconstructed in the same order by master
 Wire.write(u.b[2]);
 Wire.write(u.b[3]);
}

The code running on the Uno (master):

#include <Wire.h>

long ePos = 0; // encoder position

union u_tag { // allow long to be read as 4 seperate bytes
   byte b[4]; // 4 bytes to be received over I2C
   long LePos; // encoder position as 4 byte long
} u;

void setup() {
  Wire.begin();        // join i2c bus (address optional for master)
  Serial.begin(9600);  // start serial for output
  Serial.println("Serial Start");
}

void loop()
{
  Wire.requestFrom(8, 4);    // request 6 bytes from slave device #8
  Serial.print("Incoming: ");
  
  for (int i=0; i<4; i++) //requestFrom() is a looping code; it terminates when all requested has come :: thanks GolamMostafa!!
  {
      u.b[i] = Wire.read();  //data bytes come from FIFO Buffer that has been filled up by requestFrom()
  }

  Serial.print(u.LePos, DEC );
  Serial.print(" = ");

  Serial.print(u.b[0], HEX );

  Serial.print(" : ");
  Serial.print(u.b[1], HEX );
  
  Serial.print(" : ");
  Serial.print(u.b[2], HEX );
  
  Serial.print(" : ");
  Serial.print(u.b[3], HEX );

  Serial.println();
}

Here is some of what the serial monitor from the main arduino shows:

Incoming: -41 = D7 : FF : FF : FF
Incoming: -41 = D7 : FF : FF : FF
Incoming: -41 = D7 : FF : FF : FF
Incoming: -40 = D8 : FF : FF : FF
Incoming: -40 = D8 : FF : FF : FF
Incoming: -40 = D8 : FF : FF : FF
Incoming: -40 = D8 : FF : FF : FF
Incoming: -40 = D8 : FF : FF : FF
Incoming: -32 = E0 : FF : FF : FF
Incoming: -17 = EF : FF : FF : FF
Incoming: -10 = F6 : FF : FF : FF
Incoming: -9 = F7 : FF : FF : FF
Incoming: -10 = F6 : FF : FF : FF
Incoming: -10 = F6 : FF : FF : FF
Incoming: -10 = F6 : FF : FF : FF
Incoming: -9 = F7 : FF : FF : FF
Incoming: -2 = FE : FF : FF : FF
Incoming: 0 = 0 : 0 : 0 : 0
Incoming: 0 = 0 : 0 : 0 : 0
Incoming: 1 = 1 : 0 : 0 : 0
Incoming: 18 = 12 : 0 : 0 : 0
Incoming: 48 = 30 : 0 : 0 : 0
Incoming: 79 = 4F : 0 : 0 : 0
Incoming: 127 = 7F : 0 : 0 : 0
Incoming: 179 = B3 : 0 : 0 : 0
Incoming: 240 = F0 : 0 : 0 : 0
Incoming: 306 = 32 : 1 : 0 : 0
Incoming: 411 = 9B : 1 : 0 : 0
Incoming: 495 = EF : 1 : 0 : 0
Incoming: 509 = FD : 1 : 0 : 0
Incoming: 509 = FD : 1 : 0 : 0
Incoming: 535 = 17 : 2 : 0 : 0
Incoming: 575 = 3F : 2 : 0 : 0
Incoming: 618 = 6A : 2 : 0 : 0
Incoming: 655 = 8F : 2 : 0 : 0
Incoming: 694 = B6 : 2 : 0 : 0
Incoming: 726 = D6 : 2 : 0 : 0
Incoming: 786 = 12 : 3 : 0 : 0
Incoming: 867 = 63 : 3 : 0 : 0
Incoming: 970 = CA : 3 : 0 : 0
Incoming: 1023 = FF : 3 : 0 : 0
Incoming: 1044 = 14 : 4 : 0 : 0
Incoming: 1100 = 4C : 4 : 0 : 0
Incoming: 1126 = 66 : 4 : 0 : 0
Incoming: 1154 = 82 : 4 : 0 : 0
Incoming: 1154 = 82 : 4 : 0 : 0
Incoming: 1153 = 81 : 4 : 0 : 0
Incoming: 1153 = 81 : 4 : 0 : 0

I will now be able to process the incoming encoder value and convert it to degrees, offset the number, etc, without having to worry about constantly keeping track of the encoder.

Thanks again!

-Trev

tman1733:
It vvorks! It's vvorking!

Are you using v-after-v to enter vv? I am just joking to greet (K+) you on this auspicious occasion when your 'hard worked' codes are fantastically working.

Haha.
it's a reference to your Canadian uncle AvE (Arduino vs Evil) on youtube!