Go Down

Topic: [SOLVED] Dealing with a noisy interrupt signal (Read 12887 times) previous topic - next topic

Coding Badly

#30
Apr 07, 2016, 11:12 am Last Edit: Apr 07, 2016, 11:12 am by Coding Badly
Quote
Tested & working sketch to blink led13 using PINB |= 0x20;
You tried it (in a very limited test).  It did what you thought it would do.  It is correct.  Specious reasoning. 

Code: [Select]
PINB |= 0x20;

Let's expand that...

Code: [Select]
PINB = PINB | 0x20;

Let's say PINB is 0x1F before that line runs.  Substituting the value...

Code: [Select]
PINB = 0x1F | 0x20;

Reducing the logical-or...

Code: [Select]
PINB = 0x3F;

Instead of toggling one output you toggled six.  Thank goodness you were just blinking an LED instead of trying to control a jet engine.

The correct use of PINB to toggle an LED on pin 13 is...

Code: [Select]
PINB = 0x20;

See the difference?  Do you now understand what I meant by, "you have too many operators"?


GoForSmoke

#31
Apr 07, 2016, 11:45 am Last Edit: Apr 07, 2016, 11:48 am by GoForSmoke
Ahhhhhh. And the correct way is quicker too!

If I hadn't been so lazy and used 4 or 6 leds I might have noticed in time.
TKS for the explanation!
1) http://gammon.com.au/blink  <-- tasking Arduino 1-2-3
2) http://gammon.com.au/serial <-- techniques howto
3) http://gammon.com.au/interrupts
Your sketch can sense ongoing process events in time.
Your sketch can make events to control it over time.

Coding Badly


dlloyd

Added new features, the code is ready (I believe) for real hardware testing. Quite busy right now, but will report back with more results at a later date.

Debounce Interval Calculated Automatically:

No need to determine required debounce interval. This is determined by DUTY and MAX_HZ varibles. Debounce interval is calculated to occupy 50% of the HIGH period of the pulse. This interval expands until bounce has settled.
 
Three modes of operation:

1. pulseConditioner(DEBOUNCE);
Debounces/deglitches unwanted transitions at each end of the pulse, can be false triggered by noise during stable periods, near instantaneous response. Clean output follows input.



2. pulseConditioner(STABLE_HIGH);
Debounces/deglitches unwanted transitions at each end of the pulse, can be false triggered by noise during stable high periods, immune to noise anywhere else in the signal. Near instantaneous response, output toggles at the first falling edge after any stable high period.



3. pulseConditioner(STABLE_LOW);
Debounces/deglitches unwanted transitions at each end of the pulse, can be false triggered by noise during stable low periods, immune to noise anywhere else in the signal. Near instantaneous response, output toggles at the first rising edge after any stable low period.



Latest code:

Code: [Select]
byte ledPin = 13;
byte inputPin = 2;
byte outputPin = 12;

const byte DEBOUNCE = 2;
const byte STABLE_HIGH = 1;
const byte STABLE_LOW = 0;

const byte DUTY = 50;                // 10-90%
const unsigned long MAX_HZ = 1000;   // 1-100000Hz
const unsigned long TIMEOUT = 5000;  // 1-4000000ms

byte inputState;
unsigned long minMicros, maxMicros;
volatile unsigned long nowMicros, prevMicros, elapsedMicros;
volatile byte cleanOutput, toggleOnFall, toggleOnRise, timeoutFlag;

void setup() {
  minMicros = (1000000 * DUTY) / (MAX_HZ * 100 * 2); // debounce interval
  maxMicros = TIMEOUT * 1000;
  pinMode(ledPin, OUTPUT);
  pinMode(outputPin, OUTPUT);
  Serial.begin(115200);
  attachInterrupt(0, onPulse, CHANGE);
}

void loop() {
  pulseConditioner(DEBOUNCE);
}

void onPulse()
{
  nowMicros = micros();
  elapsedMicros = nowMicros - prevMicros;
  prevMicros = nowMicros;

  if (elapsedMicros >= minMicros)  // ALL cases >= min time
  {
    inputState = PIND & 0x4;  // read pin 2
    (elapsedMicros >= maxMicros) ? timeoutFlag = 1 : timeoutFlag = 0;
    if (inputState)
    {
      cleanOutput = 1;               // debounced output
      toggleOnRise = !toggleOnRise;  // toggle on rising input
    }
    else
    {
      cleanOutput = 0;               // debounced output
      toggleOnFall = !toggleOnFall;  // toggle on falling input
    }
  }
}

void pulseConditioner(byte mode)
{
  if (mode == DEBOUNCE)
  {
    (cleanOutput) ? PORTB |= 0x10 : PORTB &= 0xEF; // pin12 HIGH : LOW
  }
  else if (mode == STABLE_HIGH)
  {
    (toggleOnFall) ? PORTB |= 0x10 : PORTB &= 0xEF; // pin12 HIGH : LOW
  }
  else  // mode == STABLE_LOW
  {
    (toggleOnRise) ? PORTB |= 0x10 : PORTB &= 0xEF; // pin12 HIGH : LOW
  }
}

dlloyd

#34
Apr 08, 2016, 05:58 am Last Edit: Apr 08, 2016, 06:13 am by dlloyd
Doesn't letting the ISR handle the noise get a little CPU intensive? Because the ISR gets called on every transition?
Yes ... I'll need to see how it responds with external hardware. I see various threads where users usually get 2-4 extra interrupts with various devices. Whatever the practical frequency limit is, it gets reduced the noisier the signal is. A good thing is that many sensors send low frequency pulses and there's more than enough CPU cycles available.

My next testing (at a later date) will be using the pattern generator at high frequencies on a separate Arduino so it will be non-synchronous. I'll recheck the signals with a logic analyzer. Another test will be with a noisy relay that has some severe "bounce" issues.

My debounce switch tester/calibration catches bounces 12 to 20 micros in length while reporting them to look for N micros with no change. Grounding a jumper on the USB port box to make dirty switching, I only need 2000 micros to debounce that and I don't use interrupts to do it. Want the sketch?
Are you debouncing or making an oscilloscope? Both?
Sounds interesting ... I think I may have seen it a while back, but I wouldn't mind taking another look.
My intentions here are to see what can be accomplished signal conditioning interrupts in software by ignoring the unwanted ones. I would basically like to create a universal function that would be practical to use for low to mid-range frequencies from a wide range of sensors and devices.

GoForSmoke

Sounds interesting ... I think I may have seen it a while back, but I wouldn't mind taking another look.
My intentions here are to see what can be accomplished signal conditioning interrupts in software by ignoring the unwanted ones. I would basically like to create a universal function that would be practical to use for low to mid-range frequencies from a wide range of sensors and devices.
I'm not finding that sketch under the name I remember and what's more I can't make one detect changes that short and print at the same time. Printing gets change detects closer to 300 us.

So I wrote something that collects change detects in an array and then spent a while trying to make it prettier and may still have a non-critical bug (finds no-bounce presses too often, not always but these are jumper buttons) but when it shows data that should be good and yes it gets sub-20us changes.

Maybe if your sketch makes data tables and prints those after collection you will get a tighter picture?

Code: [Select]

// bounce --- ground the jumper as a dirty button and see bounce
// some buttons are just as dirty, especially with a hard press
// if button state is stable >= 100ms it will print results

// it is possible to get 1 change per contact and break but no guarantee

byte jumperPin = 7; // using an UNO and a jumper I ground on the USB.
byte lastState = 0x80;

unsigned int tStart, tWait = 1000U; // wait is 1 second
byte dontPrintExtraLines;

unsigned long bounceMicroStart, bounceMicros, nowMicros, stableMicros = 100000UL;
unsigned int bounceTbl[ 250 ];
byte bounceCntr;
byte printFlag;


void setup( void )
{
  Serial.begin( 115200 );
  Serial.println( "\nserial connected..." );
  pinMode( jumperPin, INPUT_PULLUP );
}

void loop( void )
{
  if ( lastState != ( PIND & 0x80 ))
  {
    dontPrintExtraLines = 0;
    lastState ^= 0x80;
    nowMicros = micros();
    if ( !bounceCntr )      bounceMicros = 0;
    else  bounceMicros = nowMicros - bounceMicroStart;
    if ( bounceMicros >= stableMicros )   printFlag = 1;
    else
    {
      bounceTbl[ bounceCntr++ ] = bounceMicros & 0xFFFF;
      if ( bounceCntr >= 250 )    printFlag = 1;
    }
    bounceMicroStart = nowMicros;
    tStart = millis();
  }

  if ( printFlag || (( millis() & 0xFFFF ) - tStart >= tWait ))
  {
    if ( !dontPrintExtraLines )
    {
      dontPrintExtraLines = 1;

      Serial.println( "\n--------\n" );
      if ( bounceCntr >= 250 )
      {
        Serial.println( "full table, no stable!" );
      }
      else if ( !bounceCntr )
      {
        Serial.println( "no bounces? really?" );
      }

      if ( bounceCntr )
      {
        for ( byte i = 0; i < bounceCntr; i++ )
        {
          Serial.println( bounceTbl[ i ] );
        }
      }
    }
    printFlag = bounceCntr = 0;
    tStart = millis();
  }
}
1) http://gammon.com.au/blink  <-- tasking Arduino 1-2-3
2) http://gammon.com.au/serial <-- techniques howto
3) http://gammon.com.au/interrupts
Your sketch can sense ongoing process events in time.
Your sketch can make events to control it over time.

Robin2

#36
Apr 08, 2016, 05:53 pm Last Edit: Apr 08, 2016, 05:54 pm by Robin2
I have just now found time to look at this more closely. The stuff I was testing earlier was just not working properly - it was responding to electrical noise rather than optical changes. I think I have now got past that stage. But, at the moment the hardware I have is far from ideal. I may get a specialist reflective optical detector on Monday.

My initial thoughts on this subject are what @aarg mentioned
Quote
Doesn't letting the ISR handle the noise get a little CPU intensive?
I only need a RISING interrupt which should reduce the load (I think).

As far as I can see the code in Reply #33 works by ignoring interrupts during a pre-determined interval after the first interrupt - which is how I would debounce a switch if I was polling it. But the difference is that if I was using polling I just would not check the I/O pin during the interval.

My inclination with the interrupts is to switch off the interrupt after the first trigger and not re-instate it until the interval has elapsed (and clearing out any interrupts-in-waiting first). I think that could significantly reduce the load on the Arduino. But the part I have not figured yet is how to re-instate the interrupt if the interval is short. However for my application I should not be getting more than 250 useful interrupts per second - i.e. one every 4 millisecs so there should be plenty of time for the code in loop() to re-instate the interrupt.

I guess it would not be too difficult to arrange for the first interrupt to switch of future external interrupts on that pin and start a timer interrupt that would re-instate the external interrupt after the interval. That should take ALL the debounce load off the CPU.

One thing I have not seen mentioned in the discussion (apart from @aarg's comment) is the need to use a few CPU cycles as possible so the Arduino has time to other stuff. The best debounce system is useless if there is no time left to make use of the debounced input.

I probably won't get time to do any more with this today.

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

GoForSmoke

In the Mitsubishi white paper on led to led wireless (range 2cm) they got reliable serial using short and long pulses for 0 and 1 by taking around 4 (don't remember the range) HIGH reads in a row to establish a good short pulse and 7 to 10 to establish a long pulse.
They did that at 50K reads/second at less than 3V operation. I was unable to match it using Arduino regular commands, more like 22K. I tried that back in 2012.
1) http://gammon.com.au/blink  <-- tasking Arduino 1-2-3
2) http://gammon.com.au/serial <-- techniques howto
3) http://gammon.com.au/interrupts
Your sketch can sense ongoing process events in time.
Your sketch can make events to control it over time.

dlloyd

#38
Apr 08, 2016, 11:47 pm Last Edit: Apr 09, 2016, 06:42 am by dlloyd
@GoForSmoke: Thank you for your code and suggestions.

@Robin2:

Quote
As far as I can see the code in Reply #33 works by ignoring interrupts during a pre-determined interval after the first interrupt - which is how I would debounce a switch if I was polling it. But the difference is that if I was using polling I just would not check the I/O pin during the interval.
It checks the I/O pin only if the interval has expired, not during. For any interrupt within the interval, only the time variables are updated, then the CPU exits the ISR.

Quote
One thing I have not seen mentioned in the discussion (apart from @aarg's comment) is the need to use a few CPU cycles as possible so the Arduino has time to other stuff. The best debounce system is useless if there is no time left to make use of the debounced input.
Yes, this is true, however with my testing I'll be hogging as many CPU cycles as possible with really messy signals to get a better idea of what can be achieved.

Quote
I guess it would not be too difficult to arrange for the first interrupt to switch of future external interrupts on that pin and start a timer interrupt that would re-instate the external interrupt after the interval. That should take ALL the debounce load off the CPU.
I wouldn't worry about CPU load for your application. You mentioned earlier that at about 200Hz you were getting about 1000 counts (per sec I presume). This would create no more CPU load than measuring a 1kHz signal at the interrupt pin. If you use CHANGE mode, there will be twice as many interrupts and the CPU load will be similar to measuring a 2kHz signal at the interrupt pin.

Here's what I think is happening ... you getting several extra interrupts for each switching transition of the photo sensor. With 2 extra interrupts on each side of the pulse, and assuming roughly 50% duty cycle, the signal may look something like this:
Notice there are 5 rising edges and 5 falling edges for a single pulse your trying to count. This would give 1000 counts when 200 is expected. Rather difficult to debounce using RISING or FALLING mode because the debounce interval would need to cover most of the signal's period.

With debounce or filtering, I believe there are valid cases for a hardware solution or a software solution or even both. I think in your case, if 2000 interupts per second is too high, then your signal should be very easy to filter with a 1ms RC circuit like this:


Below, I've attached an example you might want to try.

Robin2

I wouldn't worry about CPU load for your application. You mentioned earlier that at about 200Hz you were getting about 1000 counts (per sec I presume). This would create no more CPU load than measuring a 1kHz signal at the interrupt pin. If you use CHANGE mode, there will be twice as many interrupts and the CPU load will be similar to measuring a 2kHz signal at the interrupt pin.
I don't want to waste CPU cycles on 1000 interrupts if I can  reduce it to 200 - the Atmega 328 will be working at 8MHz and it has other stuff to do.

It is too early for either of us to speculate about the possible causes of too many interrupts on my project. Your little diagram would require two extra components - I have very limited space so I want to avoid that if I can.

Quote
Notice there are 5 rising edges and 5 falling edges for a single pulse your trying to count. This would give 1000 counts when 200 is expected. Rather difficult to debounce using RISING or FALLING mode because the debounce interval would need to cover most of the signal's period.
I'm not clear if your diagrams of the bouncing are to scale, and if so, what is the scale. The bouncing seems to take up a huge proportion of the cycle time.

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

dlloyd

#40
Apr 09, 2016, 03:26 pm Last Edit: Apr 09, 2016, 03:31 pm by dlloyd
Quote
I don't want to waste CPU cycles on 1000 interrupts if I can  reduce it to 200 - the Atmega 328 will be working at 8MHz and it has other stuff to do.
Some options?
  • don't use interrupts
  • somehow turn off the pin2 interrupt only during bounce intervals
  • use different sensor with debounced output
  • modify the existing sensor (see below)

Quote
I'm not clear if your diagrams of the bouncing are to scale, and if so, what is the scale. The bouncing seems to take up a huge proportion of the cycle time.
Its to illustrate a few points. The scale of the bouncing doesn't matter very much if it occurs on each end of the pulse. If your pulses are much more sharp with only 10µs bounce at each end and you have 50% duty cycle, you'll still need a debounce interval > 50% of the signal's period (same as for the diagram). What if the duty is 60/40, 70/30 or higher?

I'm not sure how you've designed your optic sensor, but if you made the black/blocking portion or clear portion much greater than the other so you get (for example) 25% duty, then software debounce would be more practical and easier.

Also, if the signal is open-collector or open-drain type, then there should be an external pullup resistor somewhere. Adding a very physically small capacitor from the signal to GND might be all that's needed. In this case, FALLING mode is more appropriate.

Quote
It is too early for either of us to speculate about the possible causes of too many interrupts on my project. Your little diagram would require two extra components - I have very limited space so I want to avoid that if I can.
Why not use an analog input and run the Serial Plotter to get a picture of the signal?

Robin2

Why not use an analog input and run the Serial Plotter to get a picture of the signal?
It does not exist in IDE 1.5.6 AFAIK

You seem to be doing what I suggested not to do " too early for either of us to speculate about the possible causes ..."

It turned out that the entire problem was caused by a lack of supression capacitors on my motor which was causing electrical rather than optical interference.

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

dlloyd

#42
Apr 10, 2016, 05:16 am Last Edit: Apr 11, 2016, 03:32 pm by dlloyd
EDIT:

Initial test results for pulseConditioner(DEBOUNCE) measured on Saleae LogicPro8 Logic Analyzer.
Arduino Pro Mini uploaded with debounce code, Arduino UNO generates the signal.

With 1.255kHz input frequency, 17.92µs latency, 12,550 interrupts per second.
With 10.629kHz input frequency, 31.36µs latency, 63,776 interrupts per second.

For now, I'm going to continue working with CHANGE mode to improve the logic and latency. What I'd like to accomplish is having an efficient function for this that eliminates all noise. I'll also investigate using a simple method of monitoring CPU load.

I know it seems impractical using a dirty signal like this, but as long as the code can handle the noise correctly, then a simple solution for high CPU loading would be to use an RC filter in combination with the debounce code.

  • The RC filter could be set around 10x the max input frequency to deal with noise while not filtering out low duty cycle signals (i.e. 10% duty).
  • The debounce code could deal with anomalies using logic and timing ... anomalies that are impossible to filter out externally without destroying the signal.
An advantage of CHANGE mode is that duty cycle information is preserved and timing slow pulse rates with 50% duty can be done in 1/2 cycle.

... any comments welcomed!

dlloyd

#43
Apr 16, 2016, 03:16 pm Last Edit: Apr 19, 2016, 02:53 am by dlloyd
As far as I can tell, this debouncer in software prevents wasted CPU cycles caused by noisy interrupts. It uses timer2 with extended compare to give a debounce interval range of 1µs to 240 seconds. The main loop is empty. Test by touching ground with a jumper wire connected to pin2 and the built-in LED will toggle once per touch.

Code: [Select]
const byte inputPin = 2;
volatile word extendedCompare, extendedCounter;
unsigned long stableWidth = 125000; // µs (debounce interval)

ISR(TIMER2_COMPA_vect)
{
  extendedCounter++;
  if (extendedCounter >= extendedCompare)
  {
    TIMSK2 &= ~(1 << OCIE2A);  // disable timer compare interrupt
    EIFR = (1 << INTF0);       // clear pending inputPin interrupt
    EIMSK |= (1 << INT0);      // enable inputPin interrupts
    extendedCounter = 0;
  }
}

void inputPin_ISR()
{
  PINB |= _BV (5);          // toggle pin 13
  EIMSK &= ~(1 << INT0);    // disable inputPin interrupt
  TCNT2  = 0;               // reset timer counter
  TIFR2 = (1 << OCF2A);     // clear pending timer interrupt
  TIMSK2 |= (1 << OCIE2A);  // enable timer compare interrupt
}

void timer2Init() {
  stableWidth = constrain(stableWidth, 1, 240000000); // 1µs-4min
  const float clockResolution = 1000000.0 / F_CPU;
  unsigned long timerCycles, prescaledCycles;
  word prescaler;
  timerCycles = (stableWidth / clockResolution) - 1;
  TCCR2A = 0;
  TCCR2B = 0;
  TCNT2  = 0; // reset counter
  TCCR2A |= (1 << WGM21); // turn on CTC mode
  if (timerCycles < 2048) { // 1-128µs, 1µs resolution
    TCCR2B |= (0 << CS22) | (1 << CS21) | (0 << CS20);
    prescaler = 8;
    prescaledCycles = timerCycles / prescaler;
    OCR2A = prescaledCycles;
  } else if (timerCycles < 16384) { // 129-1024µs, 4µs resolution
    TCCR2B |= (1 << CS22) | (0 << CS21) | (0 << CS20);
    prescaler = 64;
    prescaledCycles = timerCycles / prescaler;
    OCR2A = prescaledCycles;
  } else if (timerCycles < 65536) { // 1025-4096µs, 16µs resolution
    TCCR2B |= (1 << CS22) | (1 << CS21) | (0 << CS20);
    prescaler = 256;
    prescaledCycles = timerCycles / prescaler;
    OCR2A = prescaledCycles;
  } else if (timerCycles < 262144) { // 4097-16384µs, 64µs resolution
    TCCR2B |= (1 << CS22) | (1 << CS21) | (1 << CS20);
    prescaler = 1024;
    prescaledCycles = timerCycles / prescaler;
    OCR2A = prescaledCycles;
  } else if (timerCycles < 1048576) {  // 16385-65536µs, 256µs resolution
    TCCR2B |= (1 << CS22) | (1 << CS21) | (1 << CS20);
    prescaler = 1024;
    prescaledCycles = timerCycles / prescaler;
    OCR2A = 3;
    extendedCompare = prescaledCycles >> 2;
  } else if (timerCycles < 4194304) { // 65537-262144µs, 1024µs resolution
    TCCR2B |= (1 << CS22) | (1 << CS21) | (1 << CS20);
    prescaler = 1024;
    prescaledCycles = timerCycles / prescaler;
    OCR2A = 15;
    extendedCompare = prescaledCycles >> 4;
  } else if (timerCycles < 16777216) { // 262145-1048576µs, 4096µs resolution
    TCCR2B |= (1 << CS22) | (1 << CS21) | (1 << CS20);
    prescaler = 1024;
    prescaledCycles = timerCycles / prescaler;
    OCR2A = 63;
    extendedCompare = prescaledCycles >> 6;
  } else { // 1048577-240000000µs, 16384µs resolution
    TCCR2B |= (1 << CS22) | (1 << CS21) | (1 << CS20);
    prescaler = 1024;
    prescaledCycles = timerCycles / prescaler;
    OCR2A = 255;
    extendedCompare = prescaledCycles >> 8;
  }
  TIMSK2 |= (1 << OCIE2A); // enable timer compare interrupt
}

void setup()
{
  pinMode(inputPin, INPUT_PULLUP);
  DDRB |= _BV (5);  // pinMode (13, OUTPUT);
  attachInterrupt(digitalPinToInterrupt(inputPin), inputPin_ISR, CHANGE);
  timer2Init();
}

void loop()
{
}


Results on UNO with clean input signal:

  • Maximum input frequency: 32µs period, 31.25kHz (the signals drop out at higher frequencies).
  • Interrupts per second: 125,000 (4 interrupts per cycle, 2 timer + 2 input)
  • Latency: 3.1 µs
  • Interrupt mode: CHANGE
  • Loop: empty





Results on UNO with noisy input signal:

  • Tested input frequency: 41.4µs period, 24.33kHz.
  • State changes/sec: 291,960 (12 per cycle, 2 timer + 10 input)
  • Interrupts/sec : 97,320 (4 interrupts per cycle, 2 timer + 2 input)
  • Interrupts eliminated/sec: 194,640
  • Latency: 3.1 µs
  • Interrupt mode: CHANGE
  • Loop: empty




Another test:

  • Tested input frequency: 22.86µs period, 43.74kHz.
  • State changes/sec: 524,880 (12 per cycle, 2 timer + 10 input)
  • Interrupts/sec : 174,960 (4 interrupts per cycle, 2 timer + 2 input)
  • Interrupts eliminated/sec: 349,920
  • Latency: 3.1 µs
  • Signal stable high 5.14µs, signal stable low 4.48µs, stableWidth setting = 3µs
  • Interrupt mode: CHANGE
  • Loop: empty



Robin2

As far as I can tell, this debouncer in software prevents wasted CPU cycles
Is that using the concept I suggested in Reply #36 ?

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

Go Up