Encoder wheel not moving exact distance

I have an encoder wheel that is attached to a DC geared motor and is told to advance a certain distance via the command

M602 E45A0519-A1A7-499C-BF30-720107FE5FFB 6

M602 = Forward
E45A0519-A1A7-499C-BF30-720107FE5FFB = Address of slave (I have many encoder wheels)
6 = 6mm distance. This is converted in the slave code to encoder counts. There's a setting Xmm = Y counts.

There are two reflective sensors under the wheel that count the holes in the wheel. There is also an op-amp that filters the pulses and I checked with oscilloscope, and from what I can see, the square waves appear very clear, although the ON pulses are wider than the OFF pulses, not sure if this has any effect. See attached picture.

There are several other features on the PCB and code such as a forward and a reverse button to manually rotate the encoder. And there's also an optical slot sensor called "pulse_sense" that isn't related to this, it's to detect something else.

I had someone else with more experience write this code for me but the issue I'm experiencing is that the encoder doesn't move the correct, or same distance every time I send that command. Sometimes it moves a bit less, sometimes a bit more. I'm wondering if anyone can check my code to see if they can spot anything wrong with it or is there a better way to write it? It would be much appreciated. The code is above my knowledge.

Master_v2.ino (2.18 KB)

Slave_v3.ino (10.4 KB)

You have polling and calls to delay. Getting rid of delays is probably your first concern. How fast are these motors and more importantly, how long are the encoder pulses?

wildbill:
You have polling and calls to delay. Getting rid of delays is probably your first concern. How fast are these motors and more importantly, how long are the encoder pulses?

Hi wildbill,
Thank you for your reply. I'm using an N20 geared DC motor that is rated for 300 RPM. Running the entire board at 5V. I'm not sure how to measure "how long are the encoder pulses". Do you have any suggestion how to do that? I'm assuming it's from the oscilloscope that it can be told?

void blink() {
  if (start == 1) {        //If motor is start read the encoder low pulse
    counter = counter + 1;                            //Increment the counter
    Serial.println(counter);
    check = 0;
    c = false;
  }
}

Don't do serial printing inside the ISR.

counter should be declared with the volatile keyword added and should have noInterrupts() before and interrupts() following where it ( counter ) is read.

The only action ever taken on check is to set it to zero. What might be its purpose?

@wildbill

Here's also a screenshot of the console to show how the code responds.

Also, can you please point out in the code where the polling/delays are happening?

Doesn't look like a good quadrature signal to me, the blue pulse should switch near the center of the yellow, not near the edge.

JCA34F:
Doesn't look like a good quadrature signal to me, the blue pulse should switch near the center of the yellow, not near the edge.

Wouldn't it be when one signal goes off the next goes up? (which is close to what it's like, but agreed there's a gap, not sure why) The sensors are placed exactly so that one is off and one is on and the holes are divided 50/50 around the circumference.

Yes, the 'scope will tell you. It looks like about 2.2Hz, which is slow enough that a few little delays aren't quite so important.

However, since the two channels change around the same time, those delays and some of your serial prints may actually cause you to miss a transition. People often use interrupts to ensure that no edge is missed. It looks like you were doing this at some point.

I'd try writing something mush more simple using interrupts to prove to yourself that you are getting good counts from the encoder.

Polling is here where you read the encoders:

void readInput() {
  int value = digitalRead(Encoder);                   //Read encoder signal
  int value1 = digitalRead(Encoder1);

The problem is that that may not be happening often enough.

Delay is anytime you call the delay function, plus the possibility of serial data blocking. Increase the baud rates if you can.

A proper quadrature waveform:

JCA34F:
A proper quadrature waveform:

Wouldn't that assume that both sensors can be on at the same time? Because that isn't how this was designed in mind and is why my square wave isn't showing up like that intentionally.

The reason there are 2 sensors is to get a higher resolution reading of angle of rotation. I could only fit so many holes on the encoder so I used 2 sensors that are offset from one another so that while 1 sensor is on, the other is off and then they switch. This allows me to get a finer rotation reading by using 2 sensors instead of doubling the hole count in the encoder.

Quadrature is usually used to be able to tell direction of rotation. I'm not trying to do that here, just encoder count.

This is the function which reads the signals. As @shai says, its not a quadrature encoder.

void readInput() {
  int value = digitalRead(Encoder);                   //Read encoder signal
  int value1 = digitalRead(Encoder1);

  if (start == 1 && value != value1 && c == true) {        //If motor is start read the encoder low pulse
    counter = counter + 1;                                 //Increment the counter
    Serial.println(counter);
    check = 0;
    c = false;
  }
  else if (value == 1) {                              //Check encoder HIGH pulse
    c = true;
  }

  if (start != -1 && digitalRead(coverP) == LOW && digitalRead(Pulse) == HIGH) {  //If S4 pulled low, optical sensor not blocked, turn on motor
    if (Motor2_Dir)
      driver.motorBForward(motorSpeed1);
    else
      driver.motorBReverse(motorSpeed1);
  }
  else if (digitalRead(Pulse) == LOW || digitalRead(coverP) == HIGH) { //If S4 pulled low, optical sensor blocked, turn off motor
    driver.motorBStop();
  }

  if (start == 2 && digitalRead(Pulse) == 0 && ((millis() - timer) < nZvalue )) {
    digitalWrite(EnRS485 , HIGH);
    delay(1);
    Serial.println(counter);
    Serial.println("ok");
    delay(20);
    digitalWrite(EnRS485 , LOW);
    start = -1;
  }

  if (start > 0 && digitalRead(Pulse) == HIGH && ((millis() - timerCover) > nZvalue )) {
    //When main motor stops moving and direction is forward check pulse sensor and after Zvalue turn overide pin and send transional error
    digitalWrite(EnRS485 , HIGH);
    delay(1);
    Serial.println("error: Feeder halted. Check tape cover of feeder with red light!");
    delay(20);
    digitalWrite(EnRS485 , LOW);
    driver.motorBStop();
    digitalWrite(Led , HIGH);
    start = -1;
    timer = millis();
    driver.motorAStop();
    ch = 1;
  }

  if (digitalRead(ButtonA) == LOW && p1 == 0) {            //Check advance button
    p1 = 1;
    timer1 = millis();
    if (Motor1_Dir)
      driver.motorAForward(motorSpeed);
    else
      driver.motorAReverse(motorSpeed);
    delay(50);
  }
  else if (digitalRead(ButtonA) == LOW && (p1 == 1 || p1 == 2) && millis() - timer1 > Xvalue) {
    if (Motor1_Dir)
      driver.motorAForward(motorSpeed);
    else
      driver.motorAReverse(motorSpeed);
    p1 = 3;
  }
  else if (digitalRead(ButtonA) == HIGH && (p1 == 2 || p1 == 3)) {
    if (p1 == 3)
      p1 = 4;
    else
      p1 = 0;
  }

  if (digitalRead(ButtonR) == LOW && p2 == 0) {            //Check reverse button
    p2 = 1;
    timer2 = millis();
    if (Motor1_Dir)
      driver.motorAReverse(motorSpeed);
    else
      driver.motorAForward(motorSpeed);
    delay(50);
  }
  else if (digitalRead(ButtonR) == LOW && (p2 == 1 || p2 == 2) && millis() - timer2 > Xvalue) {
    if (Motor1_Dir)
      driver.motorAReverse(motorSpeed);
    else
      driver.motorAForward(motorSpeed);
    p2 = 3;
  }
  else if (digitalRead(ButtonR) == HIGH && (p2 == 2 || p2 == 3)) {
    if (p2 == 3)
      p2 = 4;
    else
      p2 = 0;
  }

  if (value == 0 && p1 == 1) {                      //Stop the motor for encoder click when it is triggered by advance button
    driver.motorAStop();
    p1 = 2;
  }
  else if (value == 0 && p1 == 4) {
    driver.motorAStop();
    p1 = 0;
  }

  if (value == 0 && p2 == 1) {                     //Stop the motor for encoder click when it is triggered by reverse button
    driver.motorAStop();
    p2 = 2;
  }
  else if (value == 0 && p2 == 4) {
    driver.motorAStop();
    p2 = 0;
  }
}

So, does anyone have any suggestions on how to fix the code so the encoder wheel turns more precisely?

Did you ever run the program with the one encoder and the interrupt? What were the results?

cattledog:
Did you ever run the program with the one encoder and the interrupt? What were the results?

I've had this work before where it was precise at one point with two sensors, but somewhere along the development, it stopped working precisely and my only conclusion that I can come up with is that it's the code since the oscilloscope shows a good signal, but I have no clue what it is because I didn't write the code.

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.