Issue with Stepper Motor with Builtin Decoder

You said 8200 versus the expected 8192, which is exactly 8 = 2 full steps. Try doing 1599 steps in each direction and see if you get 8192. I’ve been using QuadEncoder for years, and TTBOMK it has always been perfect, never over or under.
Good point. That fact got past me. Sometimes it's not quite that neat, but this time it was either 8199 or 8200.

I'll try that. Maybe today will bring fresh new insights! Is there a way on quad encoder to get an interrupt on the index pulse so I can check the count value at the index? It should always be n, or n % 4096, correct?
 
So I rewrote my code. Primarily to make it explicit what was happening. I seem to not get any errors. However, I changed a bunch of things, including the stepper driver code. To make myself feel that I haven't pulled the wool over my own eyes, I will put in Luni's SW encoder, and I should get the same thing. Maybe the stepper driver code that I cribbed is no good. (Wouldn't be the first time...)
Here's what I wrote:
C++:
#include<QuadEncoder.h>

// Rotary Encoder Pins
#define IDX   4
#define A     0
#define B     1
// Stepper driver pins
#define STEP_PIN 5
#define DIR_PIN 6
#define EN_PIN 7

#define pulseWidth 90  // if your stepper won't start, increase the PW
#define fwd 0
#define rev 1

int PPR = 200;  // stepper pulses per revolution
int MPR = 4;    // microsteps per step
int revolutions = 10; // desired number of revolutions for test
uint8_t adir;
int16_t count = 0;
int16_t stopcnt = 5;  // do the test 5 times
unsigned long mytime;
int32_t mCurPosValue, old_position;   // encoder outputs

QuadEncoder myEnc(1, A, B, 0, IDX); // HW spindle encoder

void setup() {
  pinMode(A, INPUT);  pinMode(B, INPUT);  pinMode(IDX, INPUT);
  pinMode(STEP_PIN, OUTPUT);  pinMode(DIR_PIN, OUTPUT);  pinMode(EN_PIN, OUTPUT);

  while(!Serial && millis() < 5000);
  myEnc.setInitConfig();
  myEnc.init();
  myEnc.write(16384);  // just because
  old_position = 16384;
  adir = fwd;
  digitalWriteFast(DIR_PIN, adir);  // set the direction before enable
  delayMicroseconds(4);
  digitalWriteFast(EN_PIN, LOW);  // enables the stepper driver
  Serial.printf("Initial position = %d\n", myEnc.read());
  Serial.printf("Number of revolutions before reversal = %d\n", revolutions);
}

void loop() {
  runMove();
  adir = !adir;
  digitalWriteFast(DIR_PIN, adir);
  delay(100);
  runMove();
  adir = !adir;
  digitalWriteFast(DIR_PIN, adir);
  delay(100);
  count +=1;
  if (count >= stopcnt) {
    digitalWriteFast(EN_PIN, HIGH);  // disable stepper driver
    Serial.printf("Final position = %d\n", myEnc.read());  // what's the encoder say?
    while(1);
  }
}

void runMove()
{
  int stepsPerRevolution = PPR * MPR;
  int total_revs = revolutions * stepsPerRevolution;
  Serial.println("Moving");
  for(int x = 0; x < total_revs; x++) {
    mytime = micros();
    digitalWriteFast(STEP_PIN, HIGH);
    delayMicroseconds(pulseWidth);
    digitalWriteFast(STEP_PIN, LOW);
    delayMicroseconds(pulseWidth);
    getPosition();
  }
}

void getPosition(){
    /* Read the position values. */
  mCurPosValue = myEnc.read();
  if(mCurPosValue != old_position){
    Serial.printf("%d, %d\n", micros() - mytime, mCurPosValue);
  }
  old_position = mCurPosValue;
}
Output
Code:
Initial position = 16384
Number of revolutions before reversal = 10
Moving
Moving
Moving
Moving
Moving
Moving
Moving
Moving
Moving
Moving
Final position = 16384
 
Ok, here's a version with both encoders. But one needs to compile the option, one vs the other. Just comment out the #define HW 1 or #undef HW statement. Luni's encoder behaves a lot different, and I can't explain why right now. I get zero output with QuadEncoder but voluminous output with EncoderTool. But at the end, the final position is the same, 16384 counts. At least at slower speeds. Here's the code with both options.
C++:
#undef HW
//#define HW 1
#ifdef HW
  #include<QuadEncoder.h>
#else
  #include <EncoderTool.h>
  using namespace EncoderTool;
#endif

// Rotary Encoder Pins
#define A     0
#define B     1
#define IDX   4
// Stepper driver pins
#define STEP_PIN 5
#define DIR_PIN 6
#define EN_PIN 7

#define pulseWidth 100  // if your stepper won't start, increase the PW
#define fwd 0
#define rev 1

int PPR = 200;  // stepper pulses per revolution
int MPR = 4;    // microsteps per step
int revolutions = 10; // desired number of revolutions for test
uint8_t adir;
int16_t count = 0;
int16_t stopcnt = 2;  // do the test N times
unsigned long mytime;

#ifdef HW
  volatile int mCurPosValue, old_position;   // encoder outputs
  QuadEncoder myEnc(1, A, B, 0, IDX); // HW spindle encoder
#else
  uint32_t errorcount = 0;
  int val1;
  volatile int mCurPosValue, old_position;   // encoder outputs
  Encoder myEnc;
#endif

void setup() {
  pinMode(A, INPUT);  pinMode(B, INPUT);  pinMode(IDX, INPUT);
  pinMode(STEP_PIN, OUTPUT);  pinMode(DIR_PIN, OUTPUT);  pinMode(EN_PIN, OUTPUT);

  while(!Serial && millis() < 5000);
  #ifdef HW
    myEnc.setInitConfig();
    myEnc.init();
    myEnc.write(16384);  // just because
    Serial.printf("Using HW encoder\n");
  #else
    myEnc.begin(A, B, CountMode::full);
    myEnc.attachCallback(nullptr);
    myEnc.setValue(16384); // sets encoder count to 16K
    myEnc.attachCallback(onEncChanged);  // enable spindle enc callback
    Serial.printf("Using SW encoder\n");
  #endif

  old_position = 16384;
  adir = fwd;
  digitalWriteFast(DIR_PIN, adir);  // set the direction before enable
  delayMicroseconds(4);
  digitalWriteFast(EN_PIN, LOW);  // enables the stepper driver
  #ifdef HW
    Serial.printf("Initial position = %d\n", myEnc.read());
  #else
    Serial.printf("Initial position = %d\n", myEnc.getValue());
  #endif
  Serial.printf("Number of revolutions before reversal = %d\n", revolutions);
}

void loop() {
  runMove();
  adir = !adir;
  digitalWriteFast(DIR_PIN, adir);
  delay(100);
  runMove();
  adir = !adir;
  digitalWriteFast(DIR_PIN, adir);
  delay(100);
  count +=1;
  if (count >= stopcnt) {
    digitalWriteFast(EN_PIN, HIGH);  // disable stepper driver
    #ifdef HW
      Serial.printf("Final position = %d\n", myEnc.read());  // what's the encoder say?
    #else
      Serial.printf("Final position = %d\n", myEnc.getValue());
    #endif
    while(1);
  }
}

void runMove()
{
  int stepsPerRevolution = PPR * MPR;
  int total_revs = revolutions * stepsPerRevolution;
  Serial.println("Moving");
  for(int x = 0; x < total_revs; x++) {
    mytime = micros();
    digitalWriteFast(STEP_PIN, HIGH);
    delayMicroseconds(pulseWidth);
    digitalWriteFast(STEP_PIN, LOW);
    delayMicroseconds(pulseWidth);
    getPosition();
  }
}

void getPosition(){
    /* Read the position values. */
  #ifdef HW 
    mCurPosValue = myEnc.read();
  #else
    mCurPosValue = myEnc.getValue();
  #endif
  if(mCurPosValue != old_position){
    Serial.printf("%d, %i\n", micros() - mytime, mCurPosValue);
  }
  old_position = mCurPosValue;
}

#ifndef HW
void onEncChanged(int value, int delta) {
  // if (delta > 0)  {
  //   if (delta > 1) {
  //     Serial.printf("fault! delta = %lli\n", delta);  errorcount += 1; }
  //   else {
  //     // delta = 1
  //   } 
  // }
  // if (delta < 0)  {
  //   if (delta<-1) {
  //     Serial.printf("fault! delta = %lli\n", delta);
  //     errorcount += 1;
  //   }
  //   else  {
  //     // delta = -1
  //   } 
  // }
  val1 = value;
}
#endif
Output of SW encoder.
Code:
Using SW encoder
Initial position = 16384
Number of revolutions before reversal = 10
Moving
200, 16383
200, 16382
200, 16381
201, 16378
201, 16375
200, 16371
200, 16366
200, 16360
200, 16354
200, 16348
200, 16341
200, 16333
200, 16324
200, 16316
200, 16307
200, 16299
200, 16291
200, 16283
...
200, 16316
200, 16321
200, 16326
200, 16331
201, 16336
200, 16341
201, 16347
200, 16352
200, 16357
200, 16362
200, 16367
200, 16373
200, 16378
Final position = 16384
The behavior of myEnc.getValue is very different from myEnc.read! The output has micros(), count. Micros should equal the time between pulses to the stepper driver.
 
Seems that the SW encoder has lost a count.
Code:
Using SW encoder
Initial position = 16384
Number of revolutions before reversal = 10
Pulse Repetition Interval = 180 microseconds
Full steps per revolution = 200
Micro-steps/full step = 4
Repetitions of the test = 10
Steps/sec =     5555.556
Delay between reversals = 50 msec

Final position = 16383
HW encoder did not.
Code:
Using HW encoder
Initial position = 16384
Number of revolutions before reversal = 10
Pulse Repetition Interval = 180 microseconds
Full steps per revolution = 200
Micro-steps/full step = 4
Repetitions of the test = 10
Steps/sec =     5555.556
Delay between reversals = 50 msec

Final position = 16384

Oh dear, this is a problem. My whole SW is based around using EncoderTool, because it allows me to make a custom callback. If I'm losing counts, this is an issue.
 
That doesn't necessarily mean that the Encoder library lost a count. Let's say that when you start the motion, the encoder position is right at the edge of a pulse. If you tell the stepper to go 10 revolutions forward and back, the encoder won't be at _exactly_ the same physical position, so you should expect to see +/-1 encoder count. This is not an error. It just reflects the discrete nature of the encoder.

Try doing 100 revs. Or 10 cycles of 10 revs forward and back. As long as your system does not accumulate error, then I think it's okay.
 
That doesn't necessarily mean that the Encoder library lost a count. Let's say that when you start the motion, the encoder position is right at the edge of a pulse. If you tell the stepper to go 10 revolutions forward and back, the encoder won't be at _exactly_ the same physical position, so you should expect to see +/-1 encoder count. This is not an error. It just reflects the discrete nature of the encoder.

Try doing 100 revs. Or 10 cycles of 10 revs forward and back. As long as your system does not accumulate error, then I think it's okay.
Post #29 is 10 cycles of 10 revs forward, then 10 revs back. That's +/-1 count.

I tried 10 cycles of 15 revs forward then 15 cycles back, and got 16386 counts with the SW encoder. HW encoder is always 16384. Well the thing is, if I repeat the SW test, it's sometimes 16384. Tests are inconclusive still.
 
When you write to Serial (USB), interrupts will occasionally be disabled. I don't know for how long, but maybe long enough to occasionally miss an edge with Encoder? QuadEncoder keeps count in hardware, so it's using much less CPU and won't miss counts if interrupts are disabled.

You might try commenting out all of the Serial.print that occur during motion and see if that helps.
 
When you write to Serial (USB), interrupts will occasionally be disabled. I don't know for how long, but maybe long enough to occasionally miss an edge with Encoder? QuadEncoder keeps count in hardware, so it's using much less CPU and won't miss counts if interrupts are disabled.

You might try commenting out all of the Serial.print that occur during motion and see if that helps.
Good point. My arch nemesis SerialUSB. That bit of code (SerialUSB) is poorly behaved. I'm waiting for my FTDI cable to arrive, to dump that to the curb.

I'll try that. Edit: Gained a count. Initial 16384, Final 16385.
 
Could you temporarily add some gearing to scale up the number of counts from the encoder to get a multiplied measurement for comparison (8:1 gearing to get 8x the counts) ??

Mark J Culross
KD5RXT
Decent idea, but hard for me to implement. I'd need to procure timing pulleys, source a new belt and machine a new mounting plate. Previously I printed a plate to hold the motor and encoder. It wasn't rigid enough and I got more counts. My aluminum plate is much more rigid.
 
With the trials I have been doing, (USB Serial off during acquisition) it seems that the HW encoder never seems to lose or gain counts, but the SW encoder may be +/- 1. Usually get 0 or +1. Basically these results are inconclusive, not sure if there's an issue or not. It is curious that the HW encoder is invariant.
 
Hmm, these encoders behave differently and making things hard to compare. The SW encoder counts position from t=0. The HW encoder is doing something different. My experiment is to have 10 revs forward, and 11 revs backwards. With an ideal stepper, the motor should return to the same angular location. But the count should reflect that a rotation has occurred.

HW encoder reports the same count as in the beginning. 65536.
SW encoder reports initial value = 65536. Final value 69633. 69633 - 65536 = 4097, which is one revolution + 1.

Is there a way to make HW encoder not do the modulo thing? I want the counts to be the same, so it's easier to compare.

Code:
Using HW encoder
Initial position = 65536
Number of revolutions before reversal = 10.000000, 11.000000
Pulse Repetition Interval = 180 microseconds
Full steps per revolution = 200
Micro-steps/full step = 4
Repetitions of the test = 1
Steps/sec =     5555.556
Encoder RPM =    416.67
Delay between reversals = 70 msec
Final position = 65536

Using SW encoder
Initial position = 65536
Number of revolutions before reversal = 10.000000, 11.000000
Pulse Repetition Interval = 180 microseconds
Full steps per revolution = 200
Micro-steps/full step = 4
Repetitions of the test = 1
Steps/sec =     5555.556
Encoder RPM =    416.67
Delay between reversals = 70 msec
Final position = 69633
 
More fundamental question, how do I invoke void QuadEncoder::printConfig(enc_config_t *config)?

C++:
QuadEncoder myEnc(1, A, B, 0, IDX); // HW spindle encoder
myEnc.setInitConfig();
myEnc.init();
myEnc.write(16384*4); 

myEnc.printConfig(&config); // ???
// want to print out all the settings, so I get visibility
Code:
error: 'config' was not declared in this scope
   59 |     myEnc.printConfig(&config);
 
@joepasquariello - @clinker8

there actually is a print function for initial settings. Proper way to call it is:
Code:
myEnc.printConfig( &myEnc.EncConfig );
Thanks. Now I have the initial settings.
Code:
enableReverseDirection: 0
decoderWorkMode: 0
HOMETriggerMode: 0
INDEXTriggerMode: 0
IndexTrigger: 0
HomeTrigger: 0
clearCounter: 0
clearHoldCounter: 0
filterCount: 0
filterSamplePeriod: 0
positionCompareValue: ffffffff
revolutionCountCondition: 0
enableModuloCountMode: 0
positionModulusValue: 0
positionInitialValue: 0
positionROIE: 0
positionRUIE: 0

If I rotate the stepper motor one more revolution in reverse than in forward, why am I reading the same final position as I started out with? (For the HW encoder) That would imply the above settings might be incorrect? Shouldn't I see a count of -4096, which is equal to one reverse rotation of a wheel of 4096? The stepper is connected to the rotary encoder with 1:1 ratio.

I initialize the encoder value to 64K, turn the stepper (and encoder) 10 revolutions forward, and 11 revolutions backwards. The HW encoder returns a final position of 64K after motion has ceased. How can this be? It should return one revolution in reverse. How can I configure QuadEncoder to match physical reality?

How can one read the modulo count register? I see a way to enable the mode, but no way to read or write the register value.

Does enableReverseDirection effectively change the sense of rotation, kind of like swapping A for B?

Thanks.
 
Is this the correct way to reverse the count direction?
C++:
    myEnc.setInitConfig();
    myEnc.init();
    //myEnc.write(initvalue);  // just because
    Serial.printf("\nUsing HW encoder\n");
    //myEnc.printConfig(&myEnc.EncConfig);
    myEnc.EncConfig.enableReverseDirection = true;  // reverses count direction
    //Serial.printf("ModuloCountMode = %i\n", myEnc.EncConfig.enableModuloCountMode);
    myEnc.Init(&myEnc.EncConfig);
    myEnc.printConfig(&myEnc.EncConfig);
I tried this and I'm a bit perplexed.

Code:
Using HW encoder
    enableReverseDirection: 0
    decoderWorkMode: 0
    HOMETriggerMode: 0
    INDEXTriggerMode: 0
    IndexTrigger: 0
    HomeTrigger: 0
    clearCounter: 0
    clearHoldCounter: 0
    filterCount: 0
    filterSamplePeriod: 0
    positionCompareValue: ffffffff
    revolutionCountCondition: 0
    enableModuloCountMode: 0
    positionModulusValue: 0
    positionInitialValue: 0
    positionROIE: 0
    positionRUIE: 0

Number of revolutions before reversal = 10.500000, 11.000000

IRQ_TEMPERATURE_PANIC  =    0
IRQ_USB1  =  128
IRQ_ENC1  =   32

Final position = 65536
Error position = 0
Error position = -2048.000000.  Case 1.
Diff = 0
Diff = 0
Diff = 0
Diff = 0
Diff = 0
Diff = 0
Diff = 0
Diff = 0
Diff = 0
Diff = 0

Using HW encoder
    enableReverseDirection: 1
    decoderWorkMode: 0
    HOMETriggerMode: 0
    INDEXTriggerMode: 0
    IndexTrigger: 0
    HomeTrigger: 0
    clearCounter: 0
    clearHoldCounter: 0
    filterCount: 0
    filterSamplePeriod: 0
    positionCompareValue: ffffffff
    revolutionCountCondition: 0
    enableModuloCountMode: 0
    positionModulusValue: 0
    positionInitialValue: 0
    positionROIE: 0
    positionRUIE: 0

Number of revolutions before reversal = 10.500000, 11.000000

IRQ_TEMPERATURE_PANIC  =    0
IRQ_USB1  =  128
IRQ_ENC1  =   32

Final position = 65536
Error position = 0
Error position = -2048.000000  <--- Case 2.  Shouldn't this be negated?
Diff = 0
Diff = 0
Diff = 0
Diff = 0
Diff = 0
Diff = 0
Diff = 0
Diff = 0
Diff = 0
Diff = 0
So what does enableReverseDirection do? Is it connected to ControlReg bit 10?
It seems so.

#define ENC_CTRL_REV_MASK (0x400U)

Confused.
 
Last edited:
@clinker8

A bit confused now on what is happening. I just hooked up my setup again.

If I run my stepper in reverse 1 revolution I am seeing negative counts up to 4K which is correct for my stepper/encoder. Then if i tell it to go forward 1 revolution it returns to 0 because its counting in the positive direction.

If I say to enableReverseDirection all it does is saying that ccw is now counting positive while cw is counting in the negative direction.
 
@mjs513 I'm apparently more confused then you.
This program works as expected.
C:
#include "QuadEncoder.h"

uint32_t mCurPosValue;
uint32_t old_position = 0;
uint32_t mCurPosValue1;
uint32_t old_position1 = 0;
QuadEncoder myEnc1(1, 0, 1, 0, 4);  // Encoder on channel 1 of 4 available
                                   // Phase A (pin0), PhaseB(pin1), Pullups Req(0), Index Z (pin4)
// Allowable encoder pins:
// 0, 1, 2, 3, 4, 5, 7, 30, 31 and 33
                            
void setup()
{
  while(!Serial && millis() < 4000);

  /* Initialize the ENC module. */
  myEnc1.setInitConfig();
  //myEnc1.EncConfig.IndexTrigger = ENABLE;  //enable to  use index counter
  //myEnc1.EncConfig.INDEXTriggerMode = RISING_EDGE;
  // above makes modulo 4096
  myEnc1.EncConfig.enableReverseDirection = true;
  myEnc1.init();
  myEnc1.printConfig(&myEnc1.EncConfig);
}

void loop(){
 
  /* This read operation would capture all the position counter to responding hold registers. */
  mCurPosValue = myEnc1.read();

  if(mCurPosValue != old_position){
    /* Read the position values. */
    Serial.printf("Current position value1: %ld\r\n", mCurPosValue);
    Serial.printf("Position differential value1: %d\r\n", (int16_t)myEnc1.getHoldDifference());
    //Serial.printf("Get Revolution value: %d\r\n", (int16_t)myEnc1.getRevolution());
    Serial.printf("Position HOLD revolution value1: %d\r\n", (int16_t)myEnc1.getHoldRevolution());
    Serial.printf("Index Counter: %d\r\n", myEnc1.indexCounter);
    Serial.println();
  }

  old_position = mCurPosValue;

}
It changes sign sense, if I change the enableReverseDirection logic state. What I did earlier, isn't doing what I expect at all. I will dig into it more. My original program is not apparently counting correctly. At the moment, I don't see the difference - been staring at it too long...
 
Back
Top