4
$\begingroup$

I am building a simple robot with two driving wheel. I want to control the wheel rotation using a wheel encoder like this one.

Here is a code I have on Arduino to try to understand the problem I'm facing:

int count = 0;
void setup() {
  Serial.begin(9600);
  attachInterrupt(digitalPinToInterrupt(2), upL, RISING);
  pinMode(2, INPUT);
}

void loop() {
  Serial.println(String(digitalRead(2)) + "__" + String(count));
}

void upL(){
  count++;
}

What I notice is:
The interrupt is triggered multiple times when the sensor beam is cut once.
But when I digitalRead the pin, then there is only one change.

I also noticed that the interrupt is also triggered when going from HIGH to LOW.

Here is an example of the ouput I have:

0__0
0__0
0__0
0__0
...
...
0__0
0__0
0__0   <<< change from LOW to HIGH... 
1__9   <<< the interrupt must have incremented only once... 9 instead !
1__9
1__9
1__9
...
...
1__9
1__9   <<< change from HIGH to LOW. the interrupt shouldn't be triggered
0__24  <<< still... we have 15 increments
0__24
0__24
0__24
0__24
...
...
0__24
0__24   <<< same here...
1__51   <<< 26 increments
1__51
...
...
1__51  <<< same here...
0__67  <<< 16 increments
0__67
0__67

The only way I can explain that is that during a change of state, the signal received from the sensor is not really square, but somehow noisy.
Like in this image :
explanation?
Therefore we would have, indeed, many RISING on one change....
(However reading the output of the sensor on an analog pin shows a direct variation from 880(HIGH) to 22(LOW))

Does anyone have another explanation? Or a solution to this problem ?


EDIT

Thanks to @TobiasK I know that this is called a bouncing effect. By doing further research I came across this solution:
playground.arduino.cc/Main/RotaryEncoders (Ctrl+F for rafbuff).
I'm trying it and i'll let you know.

$\endgroup$
  • 2
    $\begingroup$ This might be an effect called boucing. This is because switching a signal does not produce a perfect edge... It rather bounces a bit up and down. Anyway this is a common issue in electronics and there are plenty of solutions for this issue, including electronic approaches (like low-pass filter) and software approaches. $\endgroup$ – TobiasK Jan 20 '16 at 12:11
  • $\begingroup$ By the way normally they recommend the "Count" variable used in the routine should be declared Volatile. (I wonder if this might make a difference?) $\endgroup$ – Andy Jan 20 '16 at 15:15
  • $\begingroup$ @TobiasK For the electronic approach. Will adding a capacitor between the output of the sensor and the arduino solve this? What kind of software solution would you advise? I could disable the interrupt for a while after receiving the first RISING. but we don't want to miss a real rising due to the beam being cut off... $\endgroup$ – ejalaa12 Jan 20 '16 at 16:55
  • 1
    $\begingroup$ Try triggering on both edges $\endgroup$ – TobiasK Jan 20 '16 at 16:57
  • $\begingroup$ This will lead to higher processorload but ad hoc I cannot think of a sophisticated interrupt-based solution. Only polling-based. $\endgroup$ – TobiasK Jan 20 '16 at 16:59
1
$\begingroup$

Your signal actual going back and forth like that; it's registering with the microcontroller because you're at an intermediate voltage. As @TobiasK mentions, this is called "bouncing".

You're trying to use this for controlling tires, so I would suggest you do a little math to determine whether or not a "subsequent" signal could be considered valid or not.

Distance traveled is d = r*theta;, where r is the wheel radius and theta is the angular distance (in radians) the wheel rotated. Your encoder has some number of pulses per revolution (ppr), so, at top speed, you can expect some number of pulses per minute, or pulses per second. You can either determine this with the maximum wheel speed in RPM or in maximum vehicle speed in [distance]per minute.

Then, take the inverse of the maximum pulses per second to get the minimum seconds per pulse. Any pulse that you detect in a time period shorter than the minimum seconds per pulse is then invalid, so you can ignore it.

Logically, some pseudo-code might look like:

# Initialization:
validPulse = 0;

# In the loop:
if (pulse detected)
  if ( (currentTime) - validPulse ) < ( pulseThreshold )
    <do nothing>
  else
    validPulse = (currentTime);
    <do your thing here>
  end
end

In thinking about this for a moment, I would actually caution you to double your pulses per second (halve the seconds per pulse) value because you're prone to bounce on the rising AND falling pulse, as your data above shows.

Ultimately you're going to have to do something to filter the noise (bounce) out of your signal. You can either do this in a fixed manner, with electronic components (RC filter) or you can do it in software. I would always recommend software whenever possible because it's so easy to update or modify if you find your parameters change down the line, such as upgrading to faster motors, etc.

You can even perform all of the calculations in your initialization section of your code, where the calculations are based on parameters you have. Then, if you swap motors, just update the motor top speed in the code and the bounce-checking duration updates automatically.

$\endgroup$
  • $\begingroup$ Great reading. So basically, your idea is to obtain the minimum second per pulse by experience. And then ignore any interrupt that happen in less time than the minimum second per pulse? Also, I have studied the example i gave in the EDIT of my question. It's a software solution. You may want to see it. $\endgroup$ – ejalaa12 Jan 20 '16 at 20:00
  • $\begingroup$ @ejalaa12 - Yes, you can obtain the pulse rate either experimentally or from the motor datasheet. It's also possible to setup lag filters or other low-pass filters, but you still need to know what the cutoff threshold is that you need, so this work all needs to get done. $\endgroup$ – Chuck Jan 20 '16 at 20:19
  • $\begingroup$ I'll add too that you should really consider cutoff thresholds even when designing the RC filter, if you want to go that route. It makes a difference! $\endgroup$ – Chuck Jan 20 '16 at 20:20
0
$\begingroup$

After studying the code I cited on the EDIT of my question.
I came up with this solution which is working so far

# define encoderRight 2
# define encoderLeft 3

volatile int countR = 0;
volatile int countL = 0;
volatile boolean firstChangeR = true; // expecting the 1st change of the bounce
volatile boolean firstChangeL = true; // same
boolean right_set = true;
boolean left_set = true;

void setup() {
  Serial.begin(9600);
  // Right encoder
  attachInterrupt(digitalPinToInterrupt(encoderRight), doEncoderRight, CHANGE);
  pinMode(encoderRight, INPUT);
  digitalWrite(encoderRight, HIGH);  // turn on pullup resistor

  // Left encoder
  attachInterrupt(digitalPinToInterrupt(encoderLeft), doEncoderLeft, CHANGE);
  pinMode(encoderRight, INPUT);
  digitalWrite(encoderLeft, HIGH);
}

void loop() {
  firstChangeR = true; // we reset to true to expect next change triggered
  firstChangeL = true; // by the interrupt
  // printing
  String txt = String(countL);
  txt += "__";
  txt += String(countR);
  Serial.println(txt);
}

void doEncoderRight(){
  if (firstChangeR) delay(1); // if this is the first detection then we wait
                              // for the bounce to be over

  // if the current state is different from the last saved state then:
  // a real change happened and it's not part of the bouncing but 
  // actually the real beginning of the change: the first bounce !
  if (digitalRead(2) != right_set) {
    right_set = !right_set; // so we change the real state
    countR ++;  // we also increment the right encoder

    // since this was the firstChange the next are part of bouncing, so:
    firstChangeR = false;
  }

}

void doEncoderLeft(){
  if (firstChangeL) delay(1);

  if (digitalRead(3) != left_set) {
    left_set = !left_set;
    countL ++;

    firstChangeL = false;
  }

}

Tell me what do you think about it? Do you think it's reliable and is there any improvement you can propose?

$\endgroup$
  • 2
    $\begingroup$ This is a question, not an answer. Please revise your original question or, better, make a new question and link to this question as a reference. $\endgroup$ – Chuck Jan 21 '16 at 0:01
  • $\begingroup$ @Chuck I wasn't sure about the way to do this. But I see it as a proposition of answer that solves my problem. Do you still advice that I put it inside the question? $\endgroup$ – ejalaa12 Jan 21 '16 at 15:20
  • $\begingroup$ I would make a new question, but only if you're having some trouble with the code. Currently, if this code is working, then you can accept it as the answer to your original question. Because this site is for problems you actually face (and not a code review site), if you don't have a problem with the code you've written then there's probably not a lot of feedback you're going to get and the question is liable to get closed. $\endgroup$ – Chuck Jan 21 '16 at 15:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.