Arduino and high-speed rotary encoders

I'm working on a project that will use a high-speed rotary encoder attached to a DC Motor shaft which in turns drives a belted conveyor - sort of. I am using this library to record signals. I have a Leonardo, which according to specification has interrupt capability pins on 2,3 and I am using those two pins.

But I'm having serious problems. Using a digital vernier scale, and a second measurement with a ruler, I managed to compute a 250 impulse/mm. But when I drive the shaft by using a while loop like this:

Encoder myEncoder(2,3);
computedCounter = 50 * 250; //move about 50mm

void loop()
{
   analogWrite(9,255); //send pwm
   while(myEncoder.read() < computedCounter)
   {
      digitalWrite(8, HIGH); //enables direction pin
   }
   digitalWrite(8, LOW); //disable pin
   analogWrite(9,0);
}

the reference moves more than 50. What's worse, the error is not systematic. If I send a lower pwm it gives me a larger error. And sometimes it goes under 50.

It's very confusing because I can't figure out what's going on. The mechanical system is quite stable. I've tried connecting 1k resistors to clean signals. It' the same.

My question is, how does Arduino deal with this kind of problem in general. Anyone that's worked with a similar project perhaps could share a thought?

It could be that my board is damaged, I've had this one for a while.

Hi,
Can you post a link to the specification of the encoder please?
Can you please post a copy of your circuit, in CAD or a picture of a hand drawn circuit in jpg, png?
What is the shaft speed?

Tom... :slight_smile:

If you have any other code that might slow down the system, it might cause the misreadings.

Have you tried throwing everything on an osciloscope?

That code snippet doesn't include setup() so it's obviously not your complete code. Please post all your code. If it's part of a big program, then trim it down and then ACTUALLY RUN IT to be sure that your small program shows the error that you're having trouble with.

It also helps to define named constants for the pin numbers. I can't tell from here what pin 8 and pin 9 are doing. Are they controlling the motor which is moving the belt?

Your use of the term "send PWM" makes it sound like you don't understand what PWM is. It doesn't send a specific number of pulses, so you can't use that to simulate an encoder. What is this PWM supposed to be doing?

@Tom,

The motor is an old maxon DC motor 2140.934-58.236-050. I couldn't find a datasheet online, but this document outlines how to read the product id. It has a gearbox with an id of 2938.803.0030.0.000 and an encoder called maxon tacho 3149.030.0100.0-000. Again, I couldn't find a datasheet, but it does look similar to this one for whatever it's worth. And I'm pretty sure the number 100 on the id means 100 pulses per turn.

I've attached a drawing of the circuit. I could not find a 2 channel encoder so I used a stepper as a representation for the encoder (I'm learning Fritzing atm).

@far_1

Yes, I understand. I did take out all the debugging Serial.prints out, there's nothing else on the code, except that.

No, not yet. I will be doing that first thing tomorrow.

@Morgan_s
Yes, I didn't include the code because it's irrelevant to the problem, so I assumed less clutter would be better. My question was very general, and about Arduino as a system, not my specific code and/or problem.

Here is the full code for reference,

#include <Encoder.h>

Encoder myEncoder(2,3);

void setup(){
    // put your setup code here, to run once:
    Serial.begin(9600);
    pinMode(9, OUTPUT);
    pinMode(8, OUTPUT);
}

long computedCounter = 50 * 250; //move about 50mm

void loop()
{
   // put your main code here, to run repeatedly:
   analogWrite(9,255);
   while(myEncoder.read() < computedCounter)
   {
      digitalWrite(8, HIGH);
   }
   digitalWrite(8, LOW);
   analogWrite(9,0);
}

I have trimmed only this piece of code from a larger program, and I've run it. Although I am not sure what do you mean by:

... to be sure that your small program shows the error that you're having trouble with.

Let me be clear, there are no compiling or software errors here. The only problem is that when I measure the motion of the part, that's supposed to move ~50mm, it shows errors in the range of 40-60. A 10mm tolerance is not allowed on my project.

On your third point, indeed it was wrong of me to comment code like that, and yes as I stated right before the code box, the while loop drives the motor. Pin 9 outputs PWM while Pin 8 is the direction pin, that is forward and pin 10 is backwards.

I am not trying to simulate an encoder, not sure where you got that from my post.

Diagram.pdf (1.05 MB)

Hi
Good on the connections was worried that the encoder needed pull ups because it may be open collector, which you have taken care of.

Can I suggest ExpressPCB CAD package to do schematics, rather than fritzy.

http://www.expresspcb.com/free-cad-software/

It is free software, not gigabytes in size, is free of offers and other extra luggage like most other free stuff has.

It uses recognizable schematic symbols.

Tom...... :slight_smile:

There are a few things with your code which are not optimal for high speed encoder response. Let me start by saying that I don't use Encoder.h and there may be ways to optimize its performance, but I am not familiar with that.

I would not use the library Encoder.h . You will have more control with your own encoder reading code which is not difficult. I believe that myEncoder.read() in the while loop is a potential problem because I'm sure the library disables interrupts for the read and then re enables them. I would rather have the conditional test within the ISR and that may be easier to do with your own code than to reconfigure the library.

Secondly, you have a digitalWrite, which is relatively slow, in the while loop.

In general, the digitalWrite statements could be changed for bitWrite or Port Manipulation. The analog writes of 255 and 0 for full on and off could easily be changed to faster digital equivalents, if you are only turning the motor on an off.

Here is a fast encoder reading routine with the test for computedCounter within the ISR. I have not changed any of the output commands from the digitalWrite and analogWrite you used. If this code doesn't work reliably, we can make it faster with Port Manipulation. Also, pin change interrupts may be slightly faster than the external interrupts. Furthermore, depending upon your positional requirements, things could also be made slightly faster by reading only 2 of the 4 quadrature states available.

//#include <Encoder.h>
//Encoder myEncoder(2,3);

//Variables for encoder reading
const char encTable[16] = {0, 1, -1, -0, -1, 0, -0, 1, 1, -0, 0, -1, -0, -1, 1, 0}; //gives -1, 0 or 1 depending on encoder movement
volatile long actPos;
volatile byte encState;

long computedCounter = 50 * 250; //move about 50mm

volatile boolean targetFlag = false;

void setup() {
  Serial.begin(115200);//speed up any Serial output
  Serial.println("starting");
  pinMode(9, OUTPUT);
  pinMode(8, OUTPUT);

  attachInterrupt(0, readEnc, CHANGE);//pin 2
  attachInterrupt(1, readEnc, CHANGE);//pin 3

  analogWrite(9, 255); //start motor
  //digitalWrite(9, HIGH);
  digitalWrite (8, HIGH);//forward direction
}

void loop()
{
  if (targetFlag == true)
  {
    targetFlag = false;
    noInterrupts();
    long copyActPos = actPos;
    actPos = 0;
    interrupts();

    Serial.println(copyActPos);
    //add code to restart motor for another 50 mm if desired

  }
  // put your main code here, to run repeatedly:
  //analogWrite(9,255);

  //while(myEncoder.read() < computedCounter)
  //{
  //   digitalWrite(8, HIGH);
  // }
  // digitalWrite(8, LOW);
  // analogWrite(9,0);
}

void readEnc() {
  encState = ((encState << 2) | ((PIND >> 2) & 3)) & 15; //use encoder bits and last state to form index
  actPos += encTable[encState];//update actual position on encoder movement
  if (actPos == computedCounter)
  {
    analogWrite(9, 0);
    //digitalWrite(9,LOW);
    digitalWrite(8, LOW);
    targetFlag = true;
  }

}