Gear motor encoder reading

Hi,
I'm trying to build my own mobile robot. Using the Arduino Mega. And I'm facing couple issues with reading the encoder.

So, I'm directly connecting the motor with 12V battery to have max rpm, and using "attachInterrupt()"
to read the encoder. (I'm attaching the code).

#define CHA 2
#define CHB 7

volatile double master_count = 0; // universal count
volatile byte INTFLAG1 = 0; // interrupt status flag
volatile double rev_count = 0 ; //

void setup() {
pinMode(CHA, INPUT);
pinMode(CHB, INPUT);
Serial.begin(9600);
Serial.println(master_count);

attachInterrupt(digitalPinToInterrupt(CHA), flag, RISING);
// interrupt 0 digital pin 2 positive edge trigger
}

void loop() {

if (INTFLAG1) {
//rev_count = master_count*(2*PI/9750);
//Serial.println(rev_count);
Serial.println(master_count);
INTFLAG1 = 0; // clear flag

} // end if
} // end loop

void flag() {
INTFLAG1 = 1;
// add 1 to count for CW
if (digitalRead(CHA) && !digitalRead(CHB)) {
master_count++;
}
// subtract 1 from count for CCW
if (digitalRead(CHA) && digitalRead(CHB)) {
master_count-- ;

}
}

But, It cannot keep up the encoder count. I'm assuming that seen the motor shaft is rotating higher rpm it can not realize whether the pules has been changed or not.

If you know how to overcome this issue please help me out.

Thanks,


Here are the specs of my hardware (I'm also attaching pdf file):
Motor : Pittman GM8000 series (Gear ratio : 19.5:1, Voltage : 12 v , No Load rpm : 230).
Encoder : HEDS-9000/9100 ( 500 counts per revolution ).

motor_datasheet.pdf (119 KB)

Encoder_datasheet.pdf (343 KB)

count_print_2.ino (905 Bytes)

If the output of your gear box is 230 RPM and the gear ratio is 19.5 then the motor shaft is turning 4485RPM (74.75 revs/second). If you have a 500ppr encoder then you're seeing 37375 ticks per second; the time between rising edges on each phase of the encoder is 26.8uS.

You may not know it but functions like "digitalRead" take a considerable time (in the case of digitalRead it's something like 5uS.) You have 4 of them in your ISR; at the very least there's 20uS spent just reading the pins.

You're also doing logic operations and time-consuming math on a floating point number. None of this considers the additional time added by latency (i.e. the time the processor takes to actually recognize the interrupt, change context, load the PC with the address of your ISR and to undo it all at the end.

Even if you optimized the code to use, say, an unsigned long instead of a float, used direct pin access instead of library functions to read DIO etc I doubt you'd be able to reliably read an encoder at this frequency on a 16MHz processor. There's just not enough cycles and too much overhead. Even if you could just eke out accurate reading, the processor will be 100% consumed just doing this at high speed, spending effectively all of its time in the ISR.

Things you might consider include:

  • switching to a processor with built-in QEI hardware (e.g. a Due); the speedier Due (84MHz) may possibly keep up with the encoder pulses without dedicated hardware
  • asking yourself if you need the level of resolution offered; 500ppr of the motor shaft is 9750 ticks for one revolution of the road-wheel; do you need to tick off every 0.0369231 of a road-wheel degree for your application? If you don't maybe you can go to a lower-res encoder, add hardware division logic between the encoder phases the Mega etc. Can the encoder be adapted to the road wheel itself?

as Blackfin has explained, your program is likely to spend most of its time in the ISR, but if all you want to do is measure speed there are some things you can try

since you're triggering the ISR on the one encoder input, presumably there's no need to read it. And you can read the other input by reading I/O registers directly instead of calling digitalRead().

there's no need to test for both cases, if the other encoder is HIGH increment master_count "else" decrement it. No need for INTFLAG.

your ISR should be simple (compiles but untested)

#define CHB_BIT  (1 << CHB)
void isr () {
  if (PORTD & CHB_BIT)
    master_count++;
 else
    master_count-- ;
}

at the rates being discussing, there's no way you can send a serial message for each ISR event. you can try doing some processing and sending a serial message perhaps every few seconds, sending to number of ISR events (change in master_count) since the last message

and master_count should be signed long not double.

Hey,
So, I have changed my code,

#define CHA 2
#define CHB 7
#define CHB_BIT  (1 << CHB)
signed long pulse = 0; // universal count

void setup() {
  pinMode(CHA, INPUT);
  pinMode(CHB, INPUT);
  Serial.begin(9600);
  Serial.println(pulse);
  
  attachInterrupt(0, isr, RISING); 
 }
 void loop() {

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

void isr () {
  if (PORTD & CHB_BIT)
    pulse++;
 else
    pulse-- ;
}

But it can not count when wheel is rotating opposite direction (Means, initially running forward and suddenly change backward).

And I'm new to this environment, could you please explain me little bit about your code as well ?

#define CHB_BIT  (1 << CHB)
void isr () {
  if (PORTD & CHB_BIT)
    master_count++;
 else
    master_count-- ;
}

Thank you for your help.

you defined CHB, which is intended for use with digitalRead(). But the since PORTD is a register that contains the state of 8 I/O pins, the bit corresponding to the CHB bit needs to be tested. bit 7 in the register corresponds to the most significant bit. ANDing the register with b1000 0000 == 0x80 can be used to test for the bit in the register (e.g. PORTD & 0x80) so using CHB, the mask is create by shifting the value 1 up CHB positions (i.e. 1<< CHB == 0x80)

are you sure the interrupt attached to the intended pin (see digitalPinToInterrupt())

Thank you again

I have used this code
attachInterrupt(digitalPinToInterrupt(CHA), isr, RISING);

But it still can not count opposite direction..... :cold_sweat:

i hope you understand the logic and agree with it? (am i missing something)?

can you verify that the encoder pins are working as expected (check with voltmeter as you turn the motor by hand?

Have you tried a 3.3 ~ 4.7k pullup resistor from input pins to 5V?

Yes I have check with voltmeter there is no issue
and if i try with previous code

void motor_1_encoder() {
 if (digitalRead(motor_1_cha) == digitalRead(motor_1_chb))
    motor_1_master_count++;
 else
    motor_1_master_count--;
}

It will read both direction

sorry, i guess the PORTD thing was wrong.

but you just need to do one digitalRead(CHB)

On the 2560 pin '7' is connected to PH4. Does this work?

#define CHB_MASK    0x10    //pin 7 is PH4 of Mega2560
#define CHB_PRT     PINH    //Port H input pins
void isr() 
{
    master_count += (CHB_PRT & CHB_MASK) ? 1:-1;
}//isr