Go Down

Topic: Strange Interrupt behaviour - any ideas why? (Read 1 time) previous topic - next topic

Robin2

I am using a QRE1113 reflective optical detector to count revolutions and measure the speed for a small DC motor (one pulse per revolution). My ISR records the value of micros() and the code in loop() calculates the interval between interrupts - i.e. the time for a single revolution.

Using attachInterrupt(0, revMicrosISR, RISING); and this ISR code
Code: [Select]

void revMicrosISR() {
isrRevMicros = micros();
isrRevCount ++;
newRevMicros = true;
}

I get the correct interval between pulses most of the time (about 15000 µsecs) but maybe 10% of the time I get spurious values as low as 48 and anything in between.

However I have discovered that if I use a CHANGE interrupt and this code
Code: [Select]

void revMicrosISR() {
isrPinVal = digitalRead(2);
if (isrPinVal == HIGH && prevIsrPinVal == LOW) {
isrRevMicros = micros();
isrRevCount ++;
newRevMicros = true;
}
prevIsrPinVal = isrPinVal;
}

I get correct values all the time.

According to my oscilloscope the timing is very regular.

Just out of curiosity I wonder if anyone has any idea why the two approaches should give different results.

...R
Two or three hours spent thinking and reading documentation solves most programming problems.

econjack

Are the variables in the ISR's defined as volatile?

PaulS

Quote
but maybe 10% of the time I get spurious values as low as 48 and anything in between.
I would guess because of bouncing.

Quote
Just out of curiosity I wonder if anyone has any idea why the two approaches should give different results.
The second code calls digitalRead(), which is not a very fast function. Perhaps it is just slowing things down enough so that the next (spurious) interrupt doesn't happen.

If you use direct port manipulation to read the pin, does that affect the results?
The art of getting good answers lies in asking good questions.

dlloyd

#3
Apr 20, 2016, 05:36 pm Last Edit: Apr 20, 2016, 11:08 pm by dlloyd
I suspect its random noise on the opposite edge of the signal. Very difficult to debounce with RISING or FALLING mode (especially if the signal is a squarewave), very easy to debounce with CHANGE mode.

...check my latest update that works with all modes.

EDIT: If you decide to give it a try, just use stableWidth = 200000 / the max frequency that you're using the sensor. If your using it at up to 10Hz, use stableWidth = 20000 µs (debounce interval).

MorganS

Robin, we're going to have to ask you for a schematic. 8)

I've been doing some work with that same sensor this week. It's not really a digital device. It needs a comparator or some kind of adjustable threshold to decide between 'detected' and 'not detected'. As the motor arm approaches the sensor, the response curve will rather directly measure the distance to the arm.

For my usage, I just ran it to an analog input and I apply the threshold in software. If you must use an interrupt, then I expect some external circuitry is necessary.

For extra complexity, have a look at how Sparkfun does the "digital" version of this sensor. It's not digital at all. It just uses a digital pin to charge and discharge a capacitor, giving a numerical output to the distance-sensed.
"The problem is in the code you didn't post."

Robin2

#5
Apr 20, 2016, 08:05 pm Last Edit: Apr 20, 2016, 08:08 pm by Robin2
I might try what @PaulS has suggested in Reply #2. In a version that I did not post I thought I had tried to introduce a short delay but I realized after I posted the question that I had forgotten to clear the interrupt flag before I re-enabled the interrupt.

The circuit I am using is the same as the Sparkfun analog circuit except that I am using a 56k resistor instead of the 10k resistor - seems to work better. I have the wire marked OUT connected to Pin 2.



The added complexity of @dlloyd's de-bounce code does not seem to be necessary.

All the relevant variables are volatile.

...R
Two or three hours spent thinking and reading documentation solves most programming problems.

Coding Badly

and the code in loop() calculates the interval between interrupts - i.e. the time for a single revolution.
Another possibility is a race condition.  Post the relevant code from loop.


Robin2

In the CHANGE version I tried replacing digitalRead(2); with (PIND & 0b00000100) >> 2; and it made no difference - both versions work properly.

I can't think why there might be a race because each revolution takes about 15000 µsec.

This is the code that calculates the interval between interrupts - it is called directly from loop()
Code: [Select]
void calcRevMicros() {
if (newRevMicros == true) {
prevRevMicros = latestRevMicros;
noInterrupts();
latestRevMicros = isrRevMicros;
totalRevs = isrRevCount;
newRevMicros = false;
interrupts();
revMicros = latestRevMicros - prevRevMicros;
}



...R
Two or three hours spent thinking and reading documentation solves most programming problems.

Coding Badly

I can't think why there might be a race because each revolution takes about 15000 µsec.
Agree.  I can't see anything suspicious.

Quote
This is the code that calculates the interval between interrupts - it is called directly from loop()
I can see one very minor improvement but it won't have any affect on this problem.

I would guess because of bouncing.
That's the horse I'm backing.


Robin2

#9
Apr 21, 2016, 08:42 am Last Edit: Apr 21, 2016, 08:43 am by Robin2
In the absence of other ideas I agree that bouncing seems likely to be the culprit but I don't immediately see how the CHANGE version of the interrupt eliminates or avoids it.

As I have a simple working solution I don't think I will spend more time exploring this.

@Coding Badly, do you want to share your minor improvement?

...R
Two or three hours spent thinking and reading documentation solves most programming problems.

Coding Badly


Being a RISC processor data has to first be loaded into a register.  Registers are used for local variables.  Given those two facts, this should produce the same code except that some of the instructions will be moved out of the critical section reducing the amount of time interrupts are disabled...

Code: [Select]
void calcRevMicros()
{
  uint32_t tempRevMicros;
  uint16_t tempRevCount;

  if (newRevMicros == true)
  {
    noInterrupts();
    tempRevMicros = isrRevMicros;
    tempRevCount = isrRevCount;
    newRevMicros = false;
    interrupts();

    prevRevMicros = latestRevMicros;
    latestRevMicros = tempRevMicros;
    totalRevs = tempRevCount;

    revMicros = latestRevMicros - prevRevMicros;
  }


Coding Badly

#11
Apr 21, 2016, 09:40 am Last Edit: Apr 21, 2016, 09:41 am by Coding Badly

You do have a race condition...

Code: [Select]
void calcRevMicros()
{
if (newRevMicros == true)
{
prevRevMicros = latestRevMicros;

// <<<<< An interrupt here corrupts the state >>>>>

noInterrupts();


I believe the code in #10 resolves the problem.

However, if I'm correct, a RISING interrupt occurs before you process the previous interrupt.  Which may or may not indicate a bigger issue (like a bounce).


Robin2

@Coding Badly, thanks. Two interesting points.

Am I correct in thinking that your "race" problem is because an interrupt at the place you mention would have the result of prevRevMicros holding an out of date value?

When I designed the program I did not anticipate that because the interval between interrupts "should" be long.

However I remain puzzled by why the other version using a CHANGE interrupt does not exhibit the same problem.

I may do some more thinking.

...R
Two or three hours spent thinking and reading documentation solves most programming problems.

Robin2

I modified the code a bit so I could see the results for 50 consecutive interrupts and it appears that glitches also appear when using the CHANGE interrupt - but not as often as with the RISING interrupt. If the correct value is about 12000 I occasionally get a pair of shorter values - say 8000 and 4000 - which clearly indicate an extra interrupt has happened within the period of the regular pulse.

I think the simplest thing may be to reject readings that are significantly different from the norm. Taking a simple average is not the answer.

Incidently, using the concept in Reply #10 slightly increased the number of glitches.

...R
Two or three hours spent thinking and reading documentation solves most programming problems.


Go Up