One Incremental Rotary Encoder on multiple Inputs

Hey All,

this is my first post here, and it´s all about incremental rotary encoder (quadrature encoded ).
I read a lot in the forums about the highest tick frequency one can handle with an Arduino and interrupts and how to increase the speed of my program.

My setup consists of an Arduino Mega and a high-res (500 PPR) incremental encoder.
On the software side I use the highspeed encoder library found here:Encoder Library, for Measuring Quadarature Encoded Position or Rotation Signals

Since I wanna connect four rotary encoders and I don't need the 4x counting support of the library, I connect the A-Output to an Interrupt Input and the B-Output to a normal Digital-Input. (a little slower as connecting both to interrupts)

So far so good. When testing this setup with one encoder connected and turning by hand as fast as I can( I don't need real high RPM ) it works as expected.

Since I just have on encoder and I wanna test the other inputs, I connected A and B outputs also to the second pair of inputs, so using the same encoder on two input pairs. If I do this and turn it fast and abruptly by hand the two tick counts in the software differ.

Since I don't believe the Arduino is to slow I thought one of the followings could be the case:

  1. Since the two input interrupts get triggered at the same moment I miss one every time. This wouldn't be the case if I would use two separate encoders, since the chance is pretty small they are triggered at the exact same time (withing µs) ?
  2. The problem is that the encoder can't handle multiple outputs at the same time. Maybe something todo with the voltage ?

Can anyone give me tips on handing multiple encoder ?

Attached is the full code with all four encoders and also sending the data over the network every 100ms.
REF_BUTTONS and max_steps is used to synchronize with an external reed switch.

/*
 Name:		SensorApp.ino
 Created:	10/6/2017 5:19:25 PM
 Author:	Patrick Fuerst
*/
//#define ENCODER_OPTIMIZE_INTERRUPTS
//#define ENCODER_DO_NOT_USE_INTERRUPTS

#include <Encoder.h>
// Change these two numbers to the pins connected to your encoder.
//   Best Performance: both pins have interrupt capability
//   Good Performance: only the first pin has interrupt capability
//   Low Performance:  neither pin has interrupt capability


#include <SPI.h>
#include <Ethernet.h>
#include "Arduino.h"
#include <digitalWriteFast.h>

//#define DEBUG

#define REF_BUTTON1_PIN 22 //I00
#define REF_BUTTON2_PIN 23 //I01
#define REF_BUTTON3_PIN 24 //I02
#define REF_BUTTON4_PIN 25 //I03

byte mac[] = {
  0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED
};
IPAddress ip_remote(192, 168, 1, 255);
IPAddress ip (192, 168, 1, 211);
// An EthernetUDP instance to let us send and receive packets over UDP
EthernetUDP Udp;
unsigned int remote_port = 8888;      // local port to listen on
unsigned int local_port = 8888;      // local port to listen on
char  ReplyBuffer[] = "acknowledged\n";       // a string to send back

#define ENCODER_ONE_PIN_A 21
#define ENCODER_ONE_PIN_B 17
#define ENCODER_TWO_PIN_A 20
#define ENCODER_TWO_PIN_B 15
#define ENCODER_THREE_PIN_A 19
#define ENCODER_THREE_PIN_B 14
#define ENCODER_FOUR_PIN_A 18
#define ENCODER_FOUR_PIN_B 3

Encoder enc_one(ENCODER_ONE_PIN_A, ENCODER_ONE_PIN_B);
Encoder enc_two(ENCODER_TWO_PIN_A,ENCODER_TWO_PIN_B);
Encoder enc_three(ENCODER_THREE_PIN_A,ENCODER_THREE_PIN_B);
Encoder enc_four(ENCODER_FOUR_PIN_A, ENCODER_FOUR_PIN_B);

unsigned long last_time_send = 0;
Encoder* encoders[] = {&enc_one, &enc_two, &enc_three, &enc_four};

bool button_state[] = {false,false,false,false};
long oldPosition[] = {-1,-1,-1,-1};
long newPosition[] = {0,0,0,0};
 // used to normalize the values 
 // max position gets written when we write zero to the encoder position
long max_steps[] = {0,0,0,0};


// the setup function runs once when you press reset or power the board
void setup() {
	Serial.begin(2000000);
	Serial.println("Basic Encoder Test:");
  pinMode(REF_BUTTON1_PIN, INPUT);
  pinMode(REF_BUTTON2_PIN, INPUT);
  pinMode(REF_BUTTON3_PIN, INPUT);
  pinMode(REF_BUTTON4_PIN, INPUT);

 // start the Ethernet connection:
  Serial.println("Trying to get an IP address using DHCP");
  if (Ethernet.begin(mac) == 0) {
    Serial.println("Failed to configure Ethernet using DHCP");
    // initialize the Ethernet device not using DHCP:
    Ethernet.begin(mac, ip);
  }
  // print your local IP address:
  Serial.print("My IP address: ");
  ip = Ethernet.localIP();
  for (byte thisByte = 0; thisByte < 4; thisByte++) {
    // print the value of each byte of the IP address:
    Serial.print(ip[thisByte], DEC);
    Serial.print(".");
  }
  Serial.println();
  Udp.begin(local_port);

}

// the loop function runs over and over again until power down or reset
void loop() {
  newPosition[0] = enc_one.read();
  newPosition[1] = enc_two.read();
  newPosition[2] = enc_three.read();
  newPosition[3] = enc_four.read();

  button_state[0] = (boolean)digitalReadFast(REF_BUTTON1_PIN);
  button_state[1] = button_state[0];
  button_state[2] = button_state[0];
  button_state[3] = button_state[0];

  // whenever we get a ref signal we put the position back to zero 
   for(int i=0; i<4; i++){
    if ( button_state[i]) {
      encoders[i]->write(0);
      if(abs(newPosition[i]) > max_steps[i])
         max_steps[i] = abs(newPosition[i]);
    }
  }

#ifdef DEBUG

  for(int i=0; i<4; i++){
    if (newPosition[i] != oldPosition[i]) {
      oldPosition[i] = newPosition[i];
      Serial.print("Sensor:");
      Serial.print(i);
      Serial.print(" ");
      Serial.println(360*newPosition[i]/max_steps[i]);
    }
  }
  if(button_state[0]){
      Serial.println("REF");
  }

#endif
 
  int normalized_position[4];
  
  if( millis() - last_time_send > 100){

    for(int i=0; i<4; i++){
        normalized_position[i] = 360*newPosition[i]/max_steps[i]; 
      
    }
   
   // send a reply to the IP address and port that sent us the packet we received
    Udp.beginPacket(ip_remote,remote_port);
    Udp.write((char*)&normalized_position, sizeof(int)*4);
    Udp.endPacket();
    last_time_send = millis();
  }
}
  1. Since the two input interrupts get triggered at the same moment I miss one every time. This wouldn't be the case if I would use two separate encoders, since the chance is pretty small they are triggered at the exact same time (withing µs) ?

Both interrupts are handled but one after the other. This is a race condition that happen quite often in the hardware setup you've chosen to test your code. Once you have separate encoders on the inputs the chance for this race condition is substantially lower.

  1. The problem is that the encoder can't handle multiple outputs at the same time. Maybe something todo with the voltage ?

I don't know an encoder design that would have this flaw.

The fact that you use network code quite often (this uses interrupts too) doesn't make the situation better.

So don't use that test hardware setup to test code that doesn't handle this very good. Or use a Due which has interrupt capabilities on all 54 pins (and is quite a bit faster).