Stepper Motor and rotary encoder ppr mismatch

Hi all,

wasn't sure whether to post this in Sensors or Motors as it involves both. I need to accurately count pulses from an encoder that is hooked up in line to a stepper motor. The stepper is set for 1600 ppr and the encoder is 400ppr. if I feed the steeper with 1600 pulses the encoder returns between 393 and 396 ppr not 400 as expected, here is the code I have been testing this with

# define EN  28 //Orange
# define DIR 26//Brown
# define STP 24 //Red
# define encoderPin1 20

volatile int encoderCount=0;
int stepperCount=0;
unsigned long prevMicros;


void setup()
{
  Serial.begin(115200);
  pinMode (EN, OUTPUT); //ENA+(+5V)
  pinMode (DIR, OUTPUT); //DIR+(+5v)
  pinMode (STP, OUTPUT); //PUL+(+5v)
  digitalWrite (EN, LOW); //ENA+(+5V) low=enabled
  digitalWrite (DIR, LOW); //DIR+(+5v)
  digitalWrite (STP, LOW); //PUL+(+5v

  pinMode(encoderPin1, INPUT);
  digitalWrite(encoderPin1, HIGH); //turn pullup resistor on
  attachInterrupt(digitalPinToInterrupt(encoderPin1), GetEncoderPulse, RISING);

  //digitalWrite (EN,HIGH); //ENA+(+5V) low=enabled

 
}

void loop()
{

   if (micros() - prevMicros >= 625) {prevMicros = micros();
                                      digitalWrite(STP, HIGH);
                                      digitalWrite(STP, LOW);
                                      stepperCount++;
                                      if (stepperCount>=1600){Serial.println(stepperCount);
                                                             Serial.println(encoderCount);
                                                             stepperCount=0;
                                                             encoderCount=0;}
                                     }
 
                                         
}

void GetEncoderPulse()
{
  encoderCount++;
}

any ideas? is there something wrong with the code or is it the encoder?

Thanks

Do you get the same results if you use a longer pulse interval than 625? Try something much more conservative just as a test. If the pulse interval is too short it can cause the stepper to skip steps. That would be especially likely during the initial acceleration phase.

I'm also not sure about your method of generating a pulse with two calls to digitalWrite(), relying on the overhead of the function to provide a sufficient pulse duration. Have you checked to make sure that the resulting timing is compatible with the specs of your stepper driver?

Bad form to not turn off the isr before serial print.

Agree about 2x digital writes, normally we see some sort of delaymicros(x) in between them.

Thanks for the replies, odd thing is I ran the same test this morning and I consistently get 1600/400 from the serial print, so that’s with the stepper cold. I tried different intervals and it reports the same 1600/400. I have taken all of your comments on board and will make changes and report back later.

Using back-to-back digitalWrite()s should be fine for generating a step pulse - but it is worth checking the driver's requirement

What is the purpose of using an encoder and a stepper motor? Why can't you just rely on the step count?

Reading an encoder without missing steps is hard work for an Arduino. Every time an encoder pulse causes an interrupt it will interfere with the rest of the program and the effects of that can be difficult to figure out.

...R

Use AccelStepper library and avoid missing steps. You cannot accelerate a stepper to full speed instantly,
it has inertia.

Mark: I understand your point but the encoder reports a loss of 4/6 steps even after the stepper has reached the required speed, the test code cycles every 1600 pulses, if it was an acceleration issue would it not be that the first cycle would be missing steps but subsequent cycles would not? Or is that the acceleration issue is between each pulse? I have not looked at AccelStepper but I will.

Robin: I'm just trying to ascertain the accuracy of the encoder, in the "real world " application the stepper would be a 3 phase induction motor, the stepper is just a substitute. Missing a few steps on one or two revolutions is not to much of a problem but over 100/200 it obviously creates a cumulative error, I'm beginning to wonder if using an Arduino for cnc type applications is going to work.

As the encoder is of the optical type and unless the mask disk is defective in someway it should be reporting the correct amount of pulses per revolution, So I'm leaning toward the stepper and driver causing the problem.

If I run the code so it cycles 100 times and then stops, if the stepper is missing pulses it should be visible, 100x4 missing pulses per revolution=400=1/4 of a turn?

The driver is a TB6600 QD07-UK-FBA, I've not been able to find out what STP pulse width it requires but I have tried between 10 and 100 micros and it has made no difference. I've also tried varying the interval but again no difference. It's a cheap driver, I have better ones I'll try one of those as well.

So, this morning I set the test sketch to stop after the code after it had cycled 100 times and the stepper stopped exactly where it started so I think that rules out missing steps (160000 steps in total)? The encoder reports 39600 pulses where it should be 40,000. I then set up a second Arduino just with the encoder and the best I could manage was 39959 pulses, so that’s 41 short, that’s with the absolute minimum of code to do the job, so it’s looking like it’s probably not going to be able to do I want It to do without some short of software “kludge” to take into account the missing encoder counts, the only plus seems to be that it’s a fairly consistent error.

TobyOne:
So, this morning I set the test sketch to stop after the code after it had cycled 100 times and the stepper stopped exactly where it started so I think that rules out missing steps (160000 steps in total)?

That is as expected. Arduinos are widely used in 3D printing and small scale CNC.

I then set up a second Arduino just with the encoder and the best I could manage was 39959 pulses,

Please post the program you used and a link to the datasheet for your encoder.

Is the wiring for the encoder sufficiently separate from the motor wiring to ensure there is no risk of interference?

My general expectation is that you might be experiencing extra encoder pulses due to interference. I can’t imagine how there might be fewer pulses. Have you checked the working of the encoder at a very low speed - such as when rotated by hand? Is the encoder fit for the speeds you are operating at? Have you considered that the encoder may be faulty?

…R

The encoder is an HN3086-AB-400N 400ppr 6300 max rev which is well bellow what I've been testing it at, usual Chinese stuff no data sheet. The code is as follows

# define encoderPin1 20

volatile long encoderCount=0;

unsigned long prevMicros;


void setup()
{
  Serial.begin(115200);
  pinMode(encoderPin1, INPUT);
  digitalWrite(encoderPin1, HIGH); //turn pullup resistor on
  attachInterrupt(digitalPinToInterrupt(encoderPin1), GetEncoderPulse, RISING);

}

void loop()
{
  noInterrupts();
  long int count=encoderCount;
  interrupts();  
  if (count>=39900)Serial.println(encoderCount);                               
}

void GetEncoderPulse()
{
  encoderCount++;
 
  
}

absolute minimum I could think of.

I think I've proved that it's not the stepper that's the issue and looking at the results of the test encoder sketch it's definitely missing pulses whether that's the encoder or the Arduino is what I'm now trying to figure. Trouble is now I've got the error down to 40 odd steps per 40,000 it might be a bit impractical to try rotating it by hand. Now I'm using a second Arduino to capture the encoder pulses they are physical not close to each so I don't think it's anything to do with crosstalk interference. I have considered that the Encoder may be faulty but as the errors are so consistent and I've now got the error down to 40pulses per 40,000 I'm not so sure.

I reckon this code

void loop()
{
  noInterrupts();
  long int count=encoderCount;
  interrupts();  
  if (count>=39900)Serial.println(encoderCount);                               
}

is disabling interrupts much too frequently.

Try putting delay(1000); in loop()

A more sophisticated approach would be for the interrupt to set a flag at intervals - perhaps every 400 counts and then the code in loop() can check that flag before deciding to read the value of encoderCount. There is no need to disable interrupts to check a byte variable.

...R

just noticed an error in my code

void loop()
{
  noInterrupts();
  long int count=encoderCount;
  interrupts();  
  if (count>=39900)Serial.println(encoderCount);                               
}

should be

void loop()
{
  noInterrupts();
  long int count=encoderCount;
  interrupts();  
  if (count>=39900)Serial.println(count);                               
}

not that it made any difference, I thought long int is a 2 byte number? Anyway I have tried your suggestions (removing the nointerupt and delay etc)and I also tried using ATOMIC_BLOCK(ATOMIC_RESTORESTATE) but nothing seems to make the count more accurate, I have reduced the cycles to 10, just cos I got feed up waiting for the test to run and now the count error is even worse, losing around 70 counts per 4000, not sure where to go from here but if the encoder isn't faulty(I don't have another at present to run a comparison) It's looking like this is not a great way of doing things. To make matters worse is that this test code wouldn't work in the real application because if this is an ISR/interrupt issue there would be a few other things that the ISR has to do when called which would probably make the miscount even worse.

TobyOne:
not that it made any difference, I thought long int is a 2 byte number?

It is. I did not suggest otherwise. OOPs - my mistake. A long is a 4 byte number

Anyway I have tried your suggestions (removing the nointerupt and delay etc)

I did not suggest either of those.

...R

Edited to correct mistake - apologies for any confusion

I queried the byte comment because why would you refer to the variable as "a byte"? That could be considered to mean one byte which is what I thought you meant. Why bother referring to it as a byte at all? Int, long int, unsigned int, float double etc; is the usual way to refer to numerical variable. Typically you are helping people who know a lot less then yourself, I don't think you need to muddy the water.

I actually meant adding a delay, which you did suggest, but yes I could have made that comment a little clearer, after that made no difference I removed the no interrupt altogether as you had suggested and I quote "There is no need to disable interrupts to check a byte variable."

TobyOne:
I queried the byte comment because why would you refer to the variable as "a byte"? That could be considered to mean one byte which is what I thought you meant.

It is. I was referring to my proposed flag variable, not your counter.

as you had suggested and I quote "There is no need to disable interrupts to check a byte variable."

I was referring to my proposed flag variable. I had in mind modifying your ISR so it would be like this

void GetEncoderPulse()
{
  encoderCount++;
  intermidateCount ++;
  if (intermediateCount >= 400) {
     count400 = true;  // hoist the flag
     intermediateCount = 0;
  }
}

then in loop() you could have

if (count400 == true) {
   count400 = false; // lower the flag
   // present code to get the encoderCount
}

which would mean that you only read the encodeCount once every 400 pulses so that you are using noInterrupts() much less often.

The count400 variable can be defined as byte or a boolean - either way it can be checked without needing to disable interrupts.

The intermediateCount variable can be defined as an int.

It is frequently difficult to know the level of expertise of a person asking a question. As you are using an interrupt I assumed you have reasonable experience of programming.

...R

Thank you for clarifying your comments, I'm fairly new to C and Arduino but not programming in general.
Using interrupts is not too difficult but understanding EXACTLY what happens is ,yes, a bit more challenging, I have not tried the new suggestions, I'll get on it tomorrow, but I have tried just about everything else and I'm still getting count errors. As I have previously remarked that this is just a test program to check the encoder is accurate, the real application needs to fire a pulse at a stepper motor when the encoder produces a pulse. Staying roughly on the same topic, when an ISR is triggered by the interrupt, if a function is called from within the ISR does the ISR complete the code within the function as if it was part of the ISR routine, thus not handing control back to the main loop before it completes, I'm guessing that as the Arduino is not multithreading then the answer would be yes?

TobyOne:
if a function is called from within the ISR does the ISR complete the code within the function as if it was part of the ISR routine, thus not handing control back to the main loop before it completes,

Yes.

Make sure that the total time within the ISR and any functions it calls is very short. 100 microsecs would be a long time.

Also don't call a function from within an ISR that might also be called from the main body of code - unless you really know what you are doing and know how to climb out of deep holes :slight_smile:

...R

Just a thought...just wondering if the errors might be caused by a higher ranking interrupt? If so is there a way of prioritising the interrupts on an Atmel ATmega2560 like you can on some of the ARM chips? (nvic_setpriority)

AFAIK the priority is fixed.

However I would be very surprised if you are missing counts because of the priority order. And the priority only matters if two interrupts trigger at the exact same time. Even then the lower priority interrupt can expect to be handled as soon as the other one completes.

...R

Thanks. Ok, out of desperation I have removed the encoder from the equation and I have simulated the encoder pulses with a second Arduino. The count routine now returns the expected results, so looks like the encoder is at fault, had me fooled (no surprise there then) as it was so consistent. Next step to break out my scope and have a look at the encoder output. I probably should have checked that first! I should know by now, having been in the IT industry for over 20 years, to always check the simple stuff first. :confused: Just once I'd like to be able to post on this forum and not make a twat out of myself :frowning: