Help to use Encoder with Arduino Mega

I'd like to read data from my motor encoders, but i do not know how to accomplish this task.

The problem is that I've bought these motors with an included encoder which comes with no datasheet or usefull information.

I phoned to the motor provider in order to ask information about the encoder and they sent me just a short diagram which you can find here.
They told me their motors use magnetic encoders, but since their products are discontinued they can't give me more information.

Can you help me to read data from these sensors with my Arduino Mega 2560, please?
I never used encoders and so i do not know what i have to do.

Thanks to all, guys!

It's a Hall effect quadrature encoder. Connect white to ground, brown to +5v, and green and yellow to an Arduino digital input pin each. As long as there is nothing in your sketch that disables interrupts for long periods (e.g. software serial), one way of processing the inputs is to have one of them generate an interrupt on (say) the rising edge, and read the state of the other inside the ISR. The interval between interrupts will give you the rpm, and the state of the other input when you read it in the ISR will give you the direction of rotation.

[EDIT: I just noticed that the timing diagrams in that document are not those of a standard quadrature encoder. However, they don't make much sense either - if they are to be believed, then the signals on the two channels are just the inverse of each other, so you might as well only have one channel. Either way, you need only connect one of the two outputs to your Mega if you are only interested in rpm and not in direction.]

Thanks a lot for your answer!

I use Arduino to read data from different kind of sensors: sonar, gyroscope and encoder and I communicate all these information to a PC using the USB communication.
I use Serial.print(..) to send data over USB.

I'd like to use these encoders (at least two) to read the distance covered by my mobile rover and trasmit it to my pc.

If I understand well what you told me, I have to read each rising edge (does it means to check when digital input is HIGH?) from a single input and calculate the interval for the rpm while the other digital input specify me the direction of the rotation?
Is there any sketch which can show me how to structure the sketch?

I think I have to use interrupts in order to catch the interval for each rising edge, but i never used interrupts on Arduino, can you give me an example, please?
Using interrupt instead of polling each sensor is the best solution for my mobile application, but i need some help to implement it.

For information on interrupts, see the documentation in the Reference section of the main site, in particular the attachInterrupt function. Also check out the edit I just made to my earlier reply

I've read now your Edited post: i need the rpm, the direction and also the distance.
do you think is it possible to acquire these information with my encoders?

rpm - yes, just measure the interval between pulses.

direction - yes if it is a quadrature encoder (which is the usual type), no if the waveforms on that document are to be believed.

distance - yes, just count the pulses.

Here's a sketch I used to measure rpm from a single Hall sensor on a Uno. You'll need to change sensorPin and sensorInterrupt for a mega.

const int sensorPin = 2;
const int sensorInterrupt = 0;
const int timeoutValue = 5;

unsigned long lastPulseTime;
volatile unsigned long interval = 0;
volatile uint8_t timeoutCounter;

bool blink = false;

void setup()
{
  pinMode(sensorPin, INPUT);
  digitalWrite(sensorPin, HIGH);    // enable internal pullup (if Hall sensor needs it)
  attachInterrupt(sensorInterrupt, sensorIsr, RISING);
  Serial.begin(9600);
  lastPulseTime = micros();
  timeoutCounter = 0;
}

void sensorIsr()
{
  unsigned long now = micros();
  interval = now - lastPulseTime;
  lastPulseTime = now;
  timeoutCounter = timeoutValue;
}

void loop()
{
  Serial.print(" RPM ");
  unsigned int rpm;
  if (timeoutCounter != 0)
  {
    noInterrupts();
    --timeoutCounter;
    unsigned long copyInterval = interval;
    interrupts();
    rpm = (unsigned int)(60000000UL/copyInterval);
  }
  else
  {
    rpm = 0;
  }
  Serial.println(rpm);
  delay(500);
}

Thank you very much for the sample sketch!

Can i ask you why you set timeoutValue = 5?

Do you think i can't check the direction since the waveforms are not suitable for this kind of task?
However, i have to say that the most important values i need are rpm and distance, but it would be useful to have also the direction of the motors.

P.S. Thanks to your input, i've read a little bit about interrupts and they are great! I've read it's possible to use more then two pins for interrupts even if it is a littler complicated.

The purpose of the timeout is so that when the rotation stops, the rpm reading drops to zero instead of remaining at the previous reading. A value of 5 in conjunction with the 500ms delay in the loop means that this happens in 2.5 seconds, and that any RPM below 60/2.5 will read as zero. Use whatever works for you.

I'm not sure I believe the waveforms in that diagram, I suspect what you have is a quadrature encoder. Try rotating the motor slowly while you monitor the encoder outputs, to find out what they really do.

Thanks a lot for all your suggestions!

I tryed the sketch and serial monitor reads about 5600 rpm while my motor should reach at max 18.8 rpm.
My motor use a motor reductor with a factor of 1/216.

Technically, my motor is structured as: PIGNON <- MOTOR <- MOTOR REDUCER <- ENCODER

Do you think the RPM reads are affected my the motor reductor?

UPDATE: i added the pulses counter in order to calculate the distance.

I realized that each four pulses, I have a complete revolution, is it correct?

I store the pulses counter in a integer variable, but what kind of formula i have to use?
I need to know the wheel circumference, is it true?
Is there any experimental formula which can provide the most suitable results?

Yes, the encoder is probably mounted directly on the motor, before the reducing gearbox.

That's correct according to the diagram, but I don't trust the diagram. However, I expect you will get either 1, 2 or 4 pulses per revolution. You need to take some measurements and work out which it is.

Distance travelled = (number_of_motor_revolutions * wheel_circumference)/gearbox_reduction_ratio

dc42:
Distance travelled = (number_of_motor_revolutions * wheel_circumference)/gearbox_reduction_ratio

So, in my case, it should be:

distance = (pulses_counter * wheel_circ) / (216) ?

Yes, the encoder is probably mounted directly on the motor, before the reducing gearbox.

So, when i read the rpm, i have to do this:

REAL_RPM = rpm / 216?

The problem is that even if i divide by 216, i do not reach the maximum standard rpm which are 13.8.
In fact, it reads about 5600 rpm and then REAL_RPM = 5600 / 126 = 26 rpm which are twice the standard rpm.
Should i divide again by two? If yes, why? It seems strange to me..

If the encoder is producing more than 1 pulse per revolution, you need to divide by the number of pulses per rev as well.

Yes, the encoder is probably mounted directly on the motor, before the reducing gearbox.

Like I said, you need to measure the number of pulses per revolution, which is probably 1, 2 or 4. Then you need to divide the rpm you are calculating by that number. It sounds like the encoder may be producing 2 pulses per revolution.

ok, thanks a lot for your help, dc42!

You are really a skilled users and you helped me a lot!

I'm trying to calculate the distance covered by my robot but i'm having some problems.

I'm using the formula: Distance travelled = (number_of_motor_revolutions * wheel_circumference)/gearbox_reduction_ratio

(my encoder use two pulses for each revolution).

I added a pulse count in sensorIsr() function, but the variable grows very fast:

void setup()
{
  pinMode(sensorPin, INPUT);
  digitalWrite(sensorPin, HIGH);    // enable internal pullup (if Hall sensor needs it)
  attachInterrupt(sensorInterrupt, sensorIsr, RISING);
  Serial.begin(9600);
  lastPulseTime = micros();
  timeoutCounter = 0;
}

void sensorIsr()
{
  unsigned long now = micros();
  interval = now - lastPulseTime;
  lastPulseTime = now;
  timeoutCounter = timeoutValue;
  count++; <-- PULSES COUNTER, IS IT CORRECT?
}

void loop()
{
  Serial.print(" RPM ");
  unsigned int rpm;
  if (timeoutCounter != 0)
  {
    noInterrupts();
    --timeoutCounter;
    unsigned long copyInterval = interval;
    interrupts();
    rpm = (unsigned int)(60000000UL/copyInterval);
  }
  else
  {
    rpm = 0;
  }
 real_rpm = (rpm/216)/2;
  Serial.println(real_rpm);
  Serial.println(count);
  delay(10);
}

I do not know if this is the correct because i do not receive good results.
If i do: (count/2) * 148 / 216 i do not obtein the correct distance (wheel circumference = 148 cm, 216 = gearbox ratio).
Moreover, count grows a lot.

Can i directly sum the rpms in a variable and then multiply the total rpms with the circumference?

Can you help me to understand how to calculate the covered distance, please?
Thanks a lot!

Your calculation looks correct to me, but here are some observations:

  • your robot must be very large if the wheel circumference is 148cm
  • you should declare count like this:

volatile unsigned long count;

  • in loop() you should disable interrupts while reading count:

noInterrupts();
unsigned long currentCount = count;
interrupts();
float distance = ((float)currentCount * 148)/(2 * 216);

  • count will indeed grow quickly (by 432 per wheel revolution), because of the large gearbox reduction ratio
  • count will eventually overflow. This will occur after 2^31 motor revolutions, which (using your figures) is equivalent to more than 14000km. So I think you can ignore that possibility.

Thanks a lot for your answer.

Yes, my robot is very big :slight_smile:

I tried to use the previous equation to find the distance covered by using 2 pulses.
To verify if it works, i did this test:

I supposed: 360 degrees = 148 cm
so i tried to do a complete motor rotation (360 degrees), but the distance was no correct, so i tought that the encoder gives four pulses for each complete rotation.

I changed the code with this:

unsigned long currentCount = count;
  interrupts();
  float distance = ((float)currentCount * 1.48)/(4 * 216);
  if ( distance > 12000000)
  {
   distance = 0;
  }
  real_rpm = (rpm/216)/2;

Now, if i do a complete motor rotation, distance is about 146 cm, so i think it's ok.
The problem is that for the rpm i do divide by 4 but by 2 and it works..

To avoid the overflow, i check the distance value if it's greater than 12.000 km it return to zero: do you think it can be ok?

Did you mean a complete wheel rotation?

Are you sure that the reduction ratio is 216, and there isn't an extra 2:1 reduction somewhere in the drive train?

No, what you need to do is reset the current count (although it will reset to 0 anyway when it reaches 2^32). But, if your vehicle really is likely to travel as much as 14000km without being powered off (or if you are saving/restoring count in EEPROM during power off), then you could use another variable to count the overflows:

volatile unsigned long count = 0;
volatile unsigned int highCount = 0;
...
++count;
if (count == 0) {
  ++highCount;
}
...
noInterrupts();
unsigned long currentCount = count;
unsigned int currentHighCount = highCount;
interrupts();
float totalCount = (float)currentCount + ((float)currentHighCount * (float)65536 * (float)65536);
float distance = (totalCount * 1.48)/(4 * 216);
  • Yes, i mean a complete wheel rotation.

  • From the technical specs, it seems to use only a gear box with a reduction ratio of 216; when i calculate the correct rpm i have to divide by two and not by four.
    but when i calculate the distance, i have to divide by four to have a correct result, so i do not know why this happens.

  • However, the code works fine now, thanks a lot! I store the distance variable in memory in order to use it again so, the overflow control is very good for me!
    Can i ask you why you multiply for 65536, please? is it some data adjustment?