Incremental encoder - cannot detect Z signal

Hello All,

I have a stepper motor which has an integrated optical incremental encoder with channels A, B and Z.

I want to use the Z signal for homing. I'm new to all this but as far as I understood, Z signal is sent once per rotation at the same position. So I want to rotate the motor until a signal is detected on the Z channel and that would be my home position (or did I misunderstood something?).

Here's is the data sheet: https://www.trinamic.com/fileadmin/assets/Products/Motors_Documents/QSH4218-x-10k_datasheet_Rev1.40.pdf

I wrote this simple code but it didn't work. It is supposed to stop the motor if a Z signal is detected. Otherwise the motor will keep moving. Right now, it keeps moving indefinetely:

#include <Wire.h>
#include <Adafruit_MotorShield.h>


int encoder0PinZ = 5; // Zero (index) signal from the encoder is connected to digital I/O pin #5

int Z = LOW; // Initialize the zero signal
bool loOp = true; // This will be used to break out of the loop if Z == HIGH


// Create the motor shield object
Adafruit_MotorShield AFMS = Adafruit_MotorShield();

// Connect the stepper motor with (200 steps per revolution, motor port #1)
Adafruit_StepperMotor *myMotor = AFMS.getStepper(200, 1);


void setup() {
  pinMode (encoder0PinZ, INPUT);
  Serial.begin (115200);
  Serial.println("Encoder test begins!");
  AFMS.begin(); // stepper default frequency 1.6KHz
}


void loop() {

  while (loOp = true)
  {
    myMotor->step(1, FORWARD, MICROSTEP); // Move the stepper motor 1 step at a time

    Z = digitalRead(encoder0PinZ);       // Read the Z signal

    if (Z == LOW) {                      // If Z is not detected, print 0 and keep moving
      Serial.println (Z);
    } else {                             // If Z is detected, print 1 and break out of the loop
      Serial.println (Z);
      Serial.println ("Index found!");
      loOp = false;
    }
    delay(1000);
  }
}

I checked this thread but it didn't help: Incremental encoder - help with reading signals
I know there is a point where Z is high because I checked it with an oscilloscope. But the signal width was much smaller compared to A and B. Is that why my code cannot detect it? Because it happens too fast? I also recently learned about interrupts. Would it make a difference if I use interrupt?

By the way, I am using Arduino Uno and motor shield with external power supply. I connected the GND and "shield" wire of the encoder to the ground, and VCC to the 5V. A and B are also connected. A-, B-, Z- are not connected. I tried some example codes that does not use Z channel and they seem to work fine.

Also this might be relevant: the project is kind of a robotics project. The motor will turn a small stage. It is supposed to start at the same position every time and stop after certain number of steps. I wanted to use the encoder not just for homing but also for precision (not losing steps even after multiple turns). Now I realise an absolute encoder would have been easier but this encoder-integrated stepper motor is all I have and I would prefer putting it to use.

Any feedback is appreciated.

1 Like

Interrupts are good for capturing short signals coming asynchronously.
Have you tried it? Now is the time to apply what you have learned.
Try it and report the result, We will be able to help.

That loop is never going to end. You meant:
while (loOp == true)
or just
while (loOp)

1 Like

Hi,

bool loOp = true; // This will be used to break out of the loop if Z == HIGH

Your code doesn't update loOp variable in the while loop...

You check the value of Z but never change the value of loOp to exit the while loop.

The while loop with ;

 while (loOp == true)

Will keep looping while loOp is true.

If loOp = Z, loOp will be true when you are at home.

You need;

 while (loOp == false)

Check if the motor is at home or not, before you enter the while.....
The way you have it written, the motor will first take a step before you first check for home.

Tom... :smiley: :+1: :coffee: :australia:

It doesn’t?

Thanks everyone, I just had a chance to try your suggestions so here's the update.
@johnwasser and @TomGeorge, yes, it should have been
while (loOp == true)
and I fixed it but unfortunately it did not make a difference. Neither did checking the Z value before entering the while loop.

So I changed the pin and tried the interrupt method as @chrisknightley said. And it works.
When I first got the encoder, I actually tried an example I found which was using interrupts and it did not work. But at that time I wasn't aware that interrupts can only be used via Pins 2 & 3 on Arduino Uno :slight_smile: and my Z was connected to Pin 5.

Anyways, this is what I have for now:


#include <Wire.h>
#include <Adafruit_MotorShield.h>

const int encoder0PinZ = 3; // Zero (index) signal from the encoder is connected to Pin #3
bool loOp = true;

// Create the motor shield object
Adafruit_MotorShield AFMS = Adafruit_MotorShield();

// Connect the stepper motor with (200 steps per revolution, motor port #1)
Adafruit_StepperMotor *myMotor = AFMS.getStepper(200, 1);

void setup() {
  pinMode (encoder0PinZ, INPUT_PULLUP);    // I changed this too
  Serial.begin (115200);
  Serial.println("Encoder test begins!");
  AFMS.begin(); // stepper default frequency 1.6KHz

  attachInterrupt(digitalPinToInterrupt(encoder0PinZ), zero_detection_isr, HIGH);
}

void loop() {
  while (loOp == true) {
    myMotor->step(1, FORWARD, MICROSTEP); // Move the stepper motor 1 step at a time
    Serial.println (digitalRead(encoder0PinZ));
    delay(1000);
  }
}

void zero_detection_isr() {
  loOp = false;
  myMotor->release();   //release function does not work?
}

Stepper motor stops at the home position every time so that's great! Although I cannot do much else inside the ISR. For example I wanted to release the motor after it stops but it does not work. Any further ideas are welcome, although this is enough to get me started I guess. Now I know how to make use of the Z channel. Thanks again.

1 Like

Nice work. :+1:
If release() doesn't work in ISR, ISR should only change the flag and change it to monitor the flag with while block, and use release() function.
However, is it right to release it even though you got the Z signal? Detent torque remains, but I think the stepper will lose track of its position...

I only put the release() to manually move the motor after the code runs so that I can change the code if necessary and try again. So, you're right, it won't be needed for the actual project anyways!

Oh, I see.
Also, in the code use to actual project, I think it's better to detach the interrupt when find the home position.

example:

#include <Wire.h>
#include <Adafruit_MotorShield.h>

const int encoder0PinZ = 3; // Zero (index) signal from the encoder is connected to Pin #3
bool loOp = true;

// Create the motor shield object
Adafruit_MotorShield AFMS = Adafruit_MotorShield();

// Connect the stepper motor with (200 steps per revolution, motor port #1)
Adafruit_StepperMotor *myMotor = AFMS.getStepper(200, 1);

void setup() {
  pinMode (encoder0PinZ, INPUT_PULLUP);    // I changed this too
  Serial.begin (115200);
  Serial.println("Encoder test begins!");
  AFMS.begin(); // stepper default frequency 1.6KHz

  attachInterrupt(digitalPinToInterrupt(encoder0PinZ), zero_detection_isr, HIGH);
}

void loop() {
  if (!loOp) {
    while (loOp) {
      myMotor->step(1, FORWARD, MICROSTEP); // Move the stepper motor 1 step at a time
      Serial.println (digitalRead(encoder0PinZ));
      delay(1000);
    }
    detachInterrupt(digitalPinToInterrupt(encoder0PinZ));
    myMotor->release();
  }
}

void zero_detection_isr() {
  loOp = false;
}
1 Like

Thanks, noted!

I have also used interrupts and I made the "enable - disable interrupts window" as small as possible to not let it skip a signal. I tested it for my sketch, that reads 100 ppr plus the z-channel by using a drill to check how fast it can go without getting any errors and I maxed out the drill at 1700upm without skipping any pulses.
But I don't understand the fancy ways some people suggest. I simply searched for encoder interrupt sketches and put everything in the main loop and that's it.

If a variable is used both inside an ISR and outside it then it needs to be declared as volatile.

volatile bool loOp = true;