Encoder missing count

Hello,
I finally got a motor driver to work but I am having trouble with the optical encoder.

At top speed, the motor is turning 4000 rpm with a 500 CPR encoder (33.3kHz, or a pulse every 30 us)

I am using one Arduino to send a step and direction to the motor board which is an ATMEGA328 . The motor driver step count always matches the Arduino step count (I have used a temporary Serial.println(StepCount).

I am sending the step pulse on interrupt INT1, checking direction on pin 4

I am using INT0 (RISING only) for channel A of the encoder, checking direction on pin 5 (Encoder channel B).

The idea is if channel B is high when A causes an interrupt, then it add, else it subtracts from the EncoderCount variable.

If I send steps it moves in the right direction and moves close to the right spot. I am well within the limits of the encoders, HEDS-9100, which are spec’ed at 100kHz. Again, I have checked to see if the step count is correct and it matches very time. even is I send a step signal at around 50kHz.

Am I missing something? Is there a better way?

#define OFF 780

volatile long EncoderCount = 0;
volatile long StepCount = 0;

long Error=0;
long SumOfError=0;
long PreviousEncoderCount=0;

double P=0.0;
double I=0.0;
double D=0.0;
double PID=0.0;

double Kp=7.0;
double Ki=0.0;
double Kd=0.0;

ISR(INT0_vect)
{
  if (PIND &_BV(5) ) // get state of PD5 ENCODER B 
  {
    EncoderCount++;
  }
  else
  {
    EncoderCount--;
  }
} 

ISR(INT1_vect)
{
  if (PIND &_BV(4) ) // get state of PD4 DIRECTION
  {
    StepCount++;
  }
  else
  {
    StepCount--;
  }
}


void setup()
{
  // Serial.begin(57600);
  // set used pins as input/output
  DDRD&=~_BV(2); //set PD2 pin 4 to zero as input ENCODER A (2)
  DDRD&=~_BV(3); //set PD3 pin 5 to zero as input STEP (3)
  DDRD&=~_BV(4); //set PD4 pin 6 to zero as input DIRECTION (4)
  DDRD&=~_BV(5); //set PD5 pin 11 to zero as input ENCODER B (5)
  DDRD&=~_BV(6); //set PD6 pin 12 to zero as input ENCODER Z (6)
  
   // set motor driver pins
  DDRD|=_BV(7); // set PD7 pin 13 to one as output Q1 (7) 
  DDRB|=_BV(0); // set PB0 pin 14 to one as output Q2 (8) 
  DDRB|=_BV(1); // set PB1 pin 15 as PWM output Q3 (9)
  DDRB|=_BV(2); // set PB2 pin 16 as PWM output Q4 (10)
  
  // set unused open pins as output
  DDRB|=_BV(3); //set PC3 pin 17 to one as output (11) 
  DDRB|=_BV(4); //set PC4 pin 18 to one as output (12) 
  DDRB|=_BV(5); //set PC5 pin 19 to one as output (13) 
  
  DDRC|=_BV(0); //set PC0 pin 23 to one as output (A0) 
  DDRC|=_BV(1); //set PC1 pin 24 to one as output (A1) 
  DDRC|=_BV(2); //set PC2 pin 25 to one as output (A2) 
  DDRC|=_BV(3); //set PC3 pin 26 to one as output (A3) 
  DDRC|=_BV(4); //set PC4 pin 27 to one as output (A4) 
  DDRC|=_BV(5); //set PC5 pin 28 to one as output (A5) 
  
  DDRD|=_BV(0); //set PC3 pin 17 to one as output RX (0) 
  DDRD|=_BV(1); //set PC4 pin 18 to one as output TX (1) 

 // Set timer1 for 10 bit PWM at 15.6 kHz, 
  TCCR1A =163; // COM1A1=1, COM1B1=1, WGM11=1, WGM10=1
  TCCR1B=9;   // WGM12=1, CS10=1
  OCR1A =OFF;  // Set PWM Pin 9
  OCR1B =OFF;  // Set PWM Pin 10
  
  EICRA = 15; //The rising edge of INT0 and INT1 generates an interrupt request
  EIMSK = 3;  //enable both interrupt INT0 and INT1
}


void loop()
{
 // Error is the difference in stepper counter and encoder count interrupts
 
 // proportional 
 Error = StepCount - EncoderCount;
 P =Kp * Error;

// Integral 
 SumOfError += Error;  // Add up the error
 I = Ki * SumOfError;
  
//derivative
 D = (Kd * (StepCount - PreviousEncoderCount));
 PreviousEncoderCount= EncoderCount;

 PID= P + I - D;

if(PID>OFF)
  {
    PID=OFF;
  }
else if (PID<-OFF)
  {
   PID=-OFF; 
  }
  
 if (PID>0)
   {
    PORTD&= ~_BV(7);  // Q1 low
    OCR1B = OFF;      // Q4 off
    delayMicroseconds(20);
    PORTB|= _BV(0);   // Q2 high
    OCR1A = OFF-PID;  // Set  Q3
   }
 else if (PID<0)
   {
    PORTB&= ~_BV(0);   // Q2 low
    OCR1A = OFF;       // Q3 off
    delayMicroseconds(20);
    PORTD|= _BV(7);    // Q1 high
    OCR1B = OFF+PID;   // Set Q4
   }
    else 
   {
    //Brake
    PORTD|= _BV(7);   // Q1 high
    PORTB|= _BV(0);   // Q2 high
  }

}

More information:

I have looked at channels A and B on an oscilloscope and the look okay to me.

The error in position seems to happen a half speed too.

Seem like the error is greater in the negative Encoder direction.

I have no idea if this will help but you certainly have a serious bug that needs to be addressed. Start here...
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1261124850

The bug is how EncoderCount is accessed in loop.

Okay, I think I understand. I disabled the interrupt while copying the values to other variables. See highlighted lines. Coding Badly, Is this what you were referring to?

I also removed the doubles, removed all but the error term in the loop, and wrote a wait function to replace the delayMicroseconds.

No improvement…any suggestions…

#define OFF 800
#define WAIT 500L
volatile long EncoderCount = 0;
volatile long StepCount = 0;

long EncoderCountT = 0;
long StepCountT = 0;

long Error=0;
long SumOfError=0;
long PreviousEncoderCount=0;

long P=0;
long I=0;
long D=0;
long PID=0;

long Kp=7000;
long Ki=0;
long Kd=0;
long i=0;


ISR(INT0_vect)
{
  if (PIND &_BV(5) ) // get state of PD5 ENCODER B 
  {
    EncoderCount++;
  }
  else
  {
    EncoderCount--;
  }
} 

ISR(INT1_vect)
{
  if (PIND &_BV(4) ) // get state of PD4 DIRECTION
  {
    StepCount++;
  }
  else
  {
    StepCount--;
  }
}


void setup()
{
  // set used pins as input/output
  DDRD&=~_BV(2); //set PD2 pin 4 to zero as input ENCODER A (2)
  DDRD&=~_BV(3); //set PD3 pin 5 to zero as input STEP (3)
  DDRD&=~_BV(4); //set PD4 pin 6 to zero as input DIRECTION (4)
  DDRD&=~_BV(5); //set PD5 pin 11 to zero as input ENCODER B (5)
  DDRD&=~_BV(6); //set PD6 pin 12 to zero as input ENCODER Z (6)
  
  //PORTD|=_BV(2); //set PD2 pin 4 pullup ENCODER A (2)
  ///PORTD|=_BV(3); //set PD3 pin 5 pullup STEP (3)
  //PORTD|=_BV(4); //set PD4 pin 6 pullup DIRECTION (4)
  //PORTD|=_BV(5); //set PD5 pin 11 pullup ENCODER B (5)
 // PORTD|=_BV(6); //set PD6 pin 12 pullup ENCODER Z (6)
  
  // set motor driver pins
  DDRD|=_BV(7); // set PD7 pin 13 to one as output Q1 (7) 
  DDRB|=_BV(0); // set PB0 pin 14 to one as output Q2 (8) 
  DDRB|=_BV(1); // set PB1 pin 15 as PWM output Q3 (9)
  DDRB|=_BV(2); // set PB2 pin 16 as PWM output Q4 (10)
  
  // set unused open pins as output
  DDRB|=_BV(3); //set PC3 pin 17 to one as output (11) 
  DDRB|=_BV(4); //set PC4 pin 18 to one as output (12) 
  DDRB|=_BV(5); //set PC5 pin 19 to one as output (13) 
  
  DDRC|=_BV(0); //set PC0 pin 23 to one as output (A0) 
  DDRC|=_BV(1); //set PC1 pin 24 to one as output (A1) 
  DDRC|=_BV(2); //set PC2 pin 25 to one as output (A2) 
  DDRC|=_BV(3); //set PC3 pin 26 to one as output (A3) 
  DDRC|=_BV(4); //set PC4 pin 27 to one as output (A4) 
  DDRC|=_BV(5); //set PC5 pin 28 to one as output (A5) 
  
  DDRD|=_BV(0); //set PC3 pin 17 to one as output RX (0) 
  DDRD|=_BV(1); //set PC4 pin 18 to one as output TX (1) 

 // Set timer1 for 10 bit PWM at 15.6 kHz, 
  TCCR1A =163; // COM1A1=1, COM1B1=1, WGM11=1, WGM10=1
  TCCR1B=9;   // WGM12=1, CS10=1
  OCR1A =OFF;  // Set PWM Pin 9
  OCR1B =OFF;  // Set PWM Pin 10
  
  EICRA = 15; //The rising edge of INT0 and INT1 generates an interrupt request
  EIMSK = 3;  //enable both interrupt INT0 and INT1
}


void loop()
{
  
     [glow] uint8_t SaveSREG = SREG;   // save interrupt flag
      cli();   // disable interrupts
      StepCountT=StepCount;
      EncoderCountT=EncoderCount;
      SREG = SaveSREG;   // restore the interrupt flag[/glow]
  
  
  
 // Error is the difference in stepper counter and encoder count interrupts

 // proportional 
 Error = StepCountT - EncoderCountT;
 P =Kp * Error;

// Integral 
// SumOfError += Error;  // Add up the error
// I = Ki * SumOfError;
  
//derivative
// D = (Kd * (StepCountT - PreviousEncoderCount));
// PreviousEncoderCount= EncoderCountT;

 PID= (P + I - D)/1000L;

if(PID>OFF)
  {
    PID=OFF;
  }
else if (PID<-OFF)
  {
   PID=-OFF; 
  }


  
 if (PID>0)
   {
    PORTD&= ~_BV(7);  // Q1 low
    OCR1B = OFF;      // Q4 off
    wait(WAIT);
    PORTB|= _BV(0);   // Q2 high
    OCR1A = OFF-PID;  // Set  Q3
   }
 else if (PID<0)
   {
    PORTB&= ~_BV(0);   // Q2 low
    OCR1A = OFF;       // Q3 off
    wait(WAIT);
    PORTD|= _BV(7);    // Q1 high
    OCR1B = OFF+PID;   // Set Q4
   }
    else 
   {
    //Brake
    wait(WAIT);
    PORTD|= _BV(7);    // Q1 high
    PORTB|= _BV(0);   // Q2 high
  }


}


void wait(long c)
{
   for ( i=0; i <=c; i++){

   } 
}

Okay, I think I understand. I disabled the interrupt while copying the values to other variables. See highlighted lines. Coding Badly, Is this what you were referring to?

Exactly. This kind of bug that is nearly impossible to identify except by code inspection. The kind of bug that can be a royal pain the arse.

No improvement...any suggestions....

Just so I'm clear... The problem is that the final shaft position differs from the expected position? You send X pulses but the shaft ends up at a position where Y pulses had been sent?

Yes, I send 8000 pulses with frequency's from 8 kHz to 40 kHz. At 500 CPR this should rotate and come back to the same mark on the pulley.

By calculations it is missing close to 25 steps in one direction and less in the other. If I repeat forward, then reverse for several cycles the error adds up.

I did verify 500 CPR...it is printed on the encoder wheel and I moved it by hand 1 revolution.

I have read StepCount with a serial.println and it matches the step count sent. The only difference in the ISR's is that the Direction pin is either high or low for the whole 8000 pulses of the step.

Since the A/B encoders our out of phase, I would think that I would have plenty of time when A triggers to see B. I wonder if I am getting a ns pulse on B when A triggers?

I'm a compete newbie at microprocessors but I do know a bit about stepper motors from building a CNC machine recently so here's my "dos centavos":
(I'm assuming you are using a stepper motor otherwise all of this is not true.) Steppers can easily lose steps. In other words the step pulses are generated but the motor doesn't move for a few steps. This usually happens when the load exceeds the torque of the motor. (and torque in a stepper drops off quickly at higher speeds). The same thing can happen if the motor accelerates too quickly. Using encoders and a PID loop to control steppers doesn't work well because the the motor will try to accelerate too fast lose steps and then compound it by accelerating even faster. So, usually a closed loop motor control uses a brushed motor or on larger machines an AC or brushless DC motor. It is possible to use the encoder count to compare to the steps and generate an error condition as a check however. I've considered using that method for my current project (unless I end up going with a closed loop servomotor setup)
Another thing to remember is if there is a load on the motor you need an acceleration ramp to avoid the problem of missed steps. If your going from 8k to 40k steps/sec in the space of 8000 steps you have a very high acceleration so I think that is why you lose the steps.
All of the above may be complete BS though ;). I'm really just passing on what I've picked up from various CNC forums. You might want to check out CNCzone.com where you'll find a wealth of info on motor control and can get more knowledgeable advice than mine.

Good luck
Greg

Hello, I have built several cnc machines with stepper motors. And stepper motors work well.

This driver that I am working on is for a servo brushed motor with encoder. I am going to try to build a 5 axis machine.

I have 4 forums that I am addicted to, CNC Zone, All About Circuits, Arduino, and AVR Freaks.....All cools stuff. Too much new stuff, not enough time.

I hope to create a servo controller, then a Arduino controller to replace the need for a computer to run the g-code.....Just a pipe dream for now. It is a hobby.

Just a crazy idea...but I did not see any difference. I was thinking if I delayed the ISR to get past a bit of noise, it might help.

I tried a delayMicroseconds(4) inside the ISR too, no improvement.

ISR(INT0_vect)
{
 [glow] __asm__("nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t");[/glow]
  if (PIND &_BV(5) ) // get state of PD5 ENCODER B 
  {
    EncoderCount++;
  }
  else
  {
    EncoderCount--;
  }
}

Yes, I send 8000 pulses with frequency's from 8 kHz to 40 kHz

While I cannot find any problems in your Sketch, the bug could certainly lie there. But, the problem could also be with the hardware. Or even both. You need to perform enough experiments to isolate the culprit.

I suggest running an experiment at a much lower frequency ... like one pulse per second ... and then slowly increase the frequency. Start with a new Sketch that outputs one pulse per second and prints the accumulated encoder pulses when they change. If that works well, output a specific number of pulses (like 500) once per second. Obviously, the result from the encoder should match. Then output a specific number of pulses at increasing frequencies.

Sounds like a good plan. I did change pin 5 to pin 6...no difference. I will try another Atmega328 and check that out.

I have tried it at 200 Hz and it is less but I still get an error. I tried a different encoder but no luck there either.

Thanks for your help. I guess I will keep trying new things until something works.

Please report back what you find. It's very likely what you uncover will help someone else.

I fount some noise on the encoder input that might be causing me problems. I was checking on the encoder input before the invertrs which looks okay, but after the inverter, the scope shoes some noise where the A and B channels cross.

It might take me a bit to get this straightened out, due to a busy work schedule, but I will report back when I find something new.

i have exacly the same problem , after 10 runs of loop ( sending from -4000pulses to 4000 pulses ) the shaft has offset from starting position of about 45 degree , always in one direction.
With Heds 200cpr @ 1000 rpm

Can you show us your code? I do not think mine is code related, but until I get to try some things I will not know....

Mine has an error when going slow also.....

it’s very messy becouse i’m trying to make ramping at IN and OUT points.

void docalc_slide_pass() {
    attachInterrupt(5, do_encoder_slide_pass , RISING );

//Serial.print(encoder_slide_pass);
//Serial.print('\n');


      int joy_speed_slide2 = analogRead(3);
      int ramp_val = analogRead(0) * 4 ;
time = millis();
timenew = time - timeold;      

  
int j_speed_slide_lewo = map ( joy_speed_slide2 , 1 , 1024 , 1 , 255);
if ( digitalReadFast(49) == HIGH){

  digitalWriteFast (34 , LOW);

  if(encoder_slide_pass > (limit_slide_max-ramp_val) ){
    if(encoder_slide_pass > limit_slide_max){
      analogWrite(10 , 0);
      
    }else{
      timedesire = encoder_slide_pass - (limit_slide_max-ramp_val);
      timedesire = map(timedesire , 1 , (limit_slide_max - (limit_slide_max-ramp_val)) , 30 , 9);
      int delta = timenew - timedesire ; 
 
      int error_slidexx = delta ;

    lastError_slide = error_slidexx;    
    sumError_slide += error_slidexx;
    
    
    if(sumError_slide > iMax) {
      sumError_slide = iMax;
    } else if(sumError_slide < iMin){
      sumError_slide = iMin;
    }
    

    int ms_slidexx = 2 * error_slidexx + 1 * (error_slidexx - lastError_slide) + 0.1 * (sumError_slide);

    if(ms_slidexx > 0){
      digitalWriteFast (34 ,HIGH ); 
  
    }
    if(ms_slidexx < 0){
      digitalWriteFast ( 34 , LOW );     
      ms_slidexx = -1 * ms_slidexx;
        
    }
 
    int motorSpeed_slidexx = map(ms_slidexx,0,1024,0,joy_speed_slide2); 
    motorSpeed_slidexx = constrain(motorSpeed_slidexx , 0 , 255);
    analogWrite (10,  motorSpeed_slidexx );
    }
//PID
 }
 else
analogWrite(10 , j_speed_slide_lewo);
}

  else

if ( digitalReadFast(47) == HIGH){
  digitalWriteFast (34 , HIGH);
  



  if(encoder_slide_pass < (limit_slide_min+ramp_val)){
// PID
  if(encoder_slide_pass < limit_slide_min){
   analogWrite(10 , 0);
 }else{
      timedesire = encoder_slide_pass - (limit_slide_min+ramp_val);
      
      timedesire = map(czasdesire , 1 , (limit_slide_min - (limit_slide_min+ramp_val)) , 30 ,9 );
      int delta = timenew - timedesire ; 
 
      int error_slidexx = delta ;

    lastError_slide = error_slidexx;    
    sumError_slide += error_slidexx;
    
    
    if(sumError_slide > iMax) {
      sumError_slide = iMax;
    } else if(sumError_slide < iMin){
      sumError_slide = iMin;
    }
    

    int ms_slidexx = 2 * error_slidexx + 1 * (error_slidexx - lastError_slide) + 0.1 * (sumError_slide);

    if(ms_slidexx > 0){
      digitalWriteFast (34 ,LOW ); 
  
    }
    if(ms_slidexx < 0){
      digitalWriteFast ( 34 , HIGH );     
      ms_slidexx = -1 * ms_slidexx;
        
    }
 
    int motorSpeed_slidexx = map(ms_slidexx,0,1024,0,joy_speed_slide2); 
    motorSpeed_slidexx = constrain(motorSpeed_slidexx , 0 , 255);
    analogWrite (10,  motorSpeed_slidexx );
 }
// PID 
  }
  else
analogWrite(10 , j_speed_slide_lewo);
}

else {
  analogWrite(10 , 0 );
}
}




// -----------------------------------------------------------   ENKODERY ---------------------------------------------

void do_encoder_slide_pass(){
  timeold = time;
  if (digitalReadFast(17) == HIGH ) {   
    encoder_slide_pass++;
  }
  else{
  encoder_slide_pass--;
  } 
}

I think I am getting closer...still need to more testing. I changed my interrupt code to be:

ISR(INT0_vect) 
{
  DecodeEncoder = ((PIND &_BV(2))>>2) ^ EncoderCodeB;
  EncoderCodeB = (PIND &_BV(5))>>5;
  
  if (DecodeEncoder)
    {
      EncoderCount++;
    }
 else 
    {
      EncoderCount--;
    }

By doing an XOR with the interrupt (channel A and the previous value of channel B you get a 1 or a 0 depending on which direction.

In my previous code you could "dither" back and forth on and edge of a transition from low to high and cause the counter fire unintentionally.

Tomeks...I don't know if this helps....try it and see....It doubles the encoder resolution because I have to check the interrupt on both raising and falling edges.

Thanks , i'll check this . i tried different encoders and the more resolution , the smaller the error , which is very strange .
I'm driving 4 servomotors from Mega board , and there is no difference in error if one or four are working .

regards.

Have you tried putting a schmitt trigger on the outputs of the encoder?

This may help to clean/filter some noise if you have that kind of trouble.

Yes, I have schmitt triggers and 1k pull-ups on both A and B channels. I also put a 1uF capacitor from ground to the 5 vdc right at the encoder. I also tried a comparator...it shows a clean signal too.

My latest testing shows great improvements...it might even be okay. I need to work on the PID parameters.