Switch Debounce algorithm using interrupts and temporary timer

Go To Last Post
27 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello guys, I'm working with Atmega328 and I'm trying to do some type of smart switch debounce where I use pin interrupts (INT0 and ITN1) and a timer to sample pins value (2 pins).

I want to have your expert opinion about it and give me some clue on what is done wrong. I can debounce the 2 pins but with not constant and continuous consistent results, that is, a lot of times it debounce properly but some times it fails. I only use one "temporary" timer to sample pins after the first interrupt fires. I'm posting some important excerpts of my code (complete file attached):

 

void pin_config()
{
    // Led GRN and RED output - output low (sink)
    DDRD |= (1<<DDD6) | (1<<DDD7);
    //PORTC |= (1<<PORTC5);
    PORTD |= (1<<PORTD6) | (1<<PORTD7);
    
    // Pins PD2 and PD3 as inputs
    DDRD &= ~((1<<DDD2) | (1<<DDD3));
    // Enable pull-ups PCINT18, PCINT19
    PORTD |= (1<<PORTD2) | (1<<PORTD3);    
    // Interrupt enable INT0, INT1, falling edge detection
    EICRA |= (1<<ISC01) | (1<<ISC11);
    // External Interrupt mask
    EIMSK |= (1<<INT0) | (1<<INT1);
}

ISR(INT0_vect)	//ON_BTN - PD2
{
	EIMSK = 0x00;		//deactivate INT0, INT1
	ON_BTN_FLAG = 1;	//falling edge
	act = 1;
	prev = 0;
	cnt = 0;
	tcount = 0;
	TCCR0B |= (1 << CS01);	//initiate counting Timer 0
}
ISR(INT1_vect)	//OFF_BTN - PD3
{
	EIMSK = 0x00;		//deactivate INT0, INT1
	OFF_BTN_FLAG = 1;	//falling edge
	act = 1;
	prev = 0;
	cnt = 0;
	tcount = 0;
	TCCR0B |= (1 << CS01);	//initiate counting Timer 0
}
ISR(TIMER0_COMPA_vect)
{	
	tcount ++;
	
	if( bit_is_clear(PIND, 2) || bit_is_clear(PIND, 3))	//verify if pins INT0 e INT1 are set LOW	
	{
		prev=act;
		act=1;
				
		if(act==prev)
		{
			cnt++;
		}
	}
	else // PIND2==1 && PIND3==1
	{
		prev=act;
		act=0;
		cnt=0;
	}
	if(cnt==10)	// 10 successive LOW readings - 100ms total
	{		
		if(ON_BTN_FLAG)
		{
			ON_BTN_FLAG=0;
			ON_BTN_PRESSED=1;
				
			if(MODE_3)			//state transition M3 - M1
			{
				MODE_3 = 0;
				MODE_0 = 1;
			}		
			else if(MODE_2)		/* MODE 3 */
			{
				MODE_2 = 0;
				MODE_3 = 1;
				ICR1 = 1784;	//70Hz
			}
			else if (MODE_1)	/* MODE 2 */
			{
				MODE_1 = 0;
				MODE_2 = 1;	
				ICR1 = 2082;	//60Hz
			}
			else if (MODE_0)	/* MODE 1 */
			{
				MODE_0 = 0;
				MODE_1 = 1;
				ICR1 = 2499;	//50Hz
				TCCR1B |= (1 << CS11);								//start PWM timer 1
				TCCR2B |= (1 << CS20) | (1 << CS21) | (1 << CS22);	//start 100ms timer 2
			}		
		}
		else					/* MODE 0 */
		{	OFF_BTN_FLAG=0;
			OFF_BTN_PRESSED=1;
			
			MODE_0 = 1;
			MODE_1 = 0;	
			MODE_2 = 0;
			MODE_3 = 0;
			
			TCCR1B &= ~(1 << CS11);									//stop PWM timer 1
			TCCR2B &= ~((1 << CS20) | (1 << CS21) | (1 << CS22));	//stop 100ms timer 2
		}
		
		TCCR0B &= ~(1 << CS01);			// turn off Timer 0
		EIMSK |= (1<<INT0) | (1<<INT1);	// activate INT0, INT1
	}
	if(tcount==20)	// 200ms
	{
		ON_BTN_FLAG=0;
		OFF_BTN_FLAG=0;
		TCCR0B &= ~(1 << CS01);			// turn off Timer 0
		EIMSK |= (1<<INT0) | (1<<INT1);	// activate INT0, INT1
	}
		
}

 

I have 4 working modes where I set some timer 1 values to generate a PWM wave. MODE_0, MODE_1, MODE_2 and MODE_3. INT0 has my ON switch attached and INT1 the OFF switch. When the micro starts for the first time, it stays automatically on MODE_0 until I press ON button, then pressing again ON button it should switch to MODE_2, the press again switch to MODE_3, then press again and switch to MODE_1. The OFF button simply makes the micro return to MODE_0.

 

I'm not getting consistent debounce results and Im wondering if the problem is in fact related to the switch bounce that Im not properly debouncing or if I have some flaw in my code.

Sometimes when I press ON button it switches from MODE_1 to MODE_3 (for example).

 

I would appreciate your help on this and also your feedback about the algorithm itself since for me it is a smart way of "saving" one timer.

 

Attachment(s): 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I see nowhere where you are clearing the interrupt flags, so I don't see how this is working at all. After the first button press, the interrupt would never fire again.

Regards,
Steve A.

The Board helps those that help themselves.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I see nowhere where you are clearing the interrupt flags, ...

OP has catching ISRs which will clear the flag -- the first time...

 

(Buttons bounce, so there might be more pending hit(s).)

 

My first impression was:  What happens when both buttons are "active" at the same time?

 

Button/switch bounce time varies, but 100ms (or more) would be a very exceptional case.

 

Most apps need an internal "tick" for timekeeping anyway; typical values might be a few milliseconds.  (My most common is probably 10ms.)

 

Every "tick", you read the state of the input pins.  Then you enter them into the debounce mechanism.  Coming out the other end is the verified state of the buttons, along with rising/falling edges if you care to calculate them.

 

Many extensive threads on this.  Typical search terms are "danni lee debounce".  Yes, I do have the debounce "engine" that works with 8/16/32 bits in parallel.  But still, you have a lot of specialized code.  And consider how you will extend to 4 or 6 or 8 or more inputs.

 

 

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'd try to see the bounce on a scope so you know if you are trying to get rid of 3 bounces 2ms apart or 6 bounces 4ms apart or ???. Of cource once you know how long it bounces for, just read it a few more ms than that. If this is some mechanical contraption that is not operated by a human, you might run into the problem that you just cant actuate it faster than it settles. I wrote a little program that looks for bounces and counts em and you adjust the loop delay with 2 keys to get it out of the bounce window.

 

Imagecraft compiler user

Last Edited: Mon. Jul 13, 2015 - 08:44 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

First of al I forgot to mention that I'm using AS6 to generate code. 

I see nowhere where you are clearing the interrupt flags, ...

According to datasheet the INT0 and INT1 flags should be cleared automatically by executing the interrupt vector and also the timer interrupt should be cleared when a match happens.

Am I missing something here on the timer and pin interrupt flags?

 

(Buttons bounce, so there might be more pending hit(s).)

Exactly! I only use the interrupts to catch the first "hit" (being it caused by some noise or a true button press), then I disable external interrupts (EIMSK  = 0x00) for a while and start polling the pins every 10ms. I know that my switches do not bounce more than 10ms as I saw on my scope. Maybe I'm using a too large tick. What are the downsides of doing this??

 

If the "hit" was due to noise, the timer will count 200ms and then turns off, meaning that it was not a real button hit, otherwise, if I have a low state in more then 10 ticks (100ms) I consider a valid button press. This way after the debounce I can use the timer for other purposes without being constantly polling the pins.

 

My first impression was:  What happens when both buttons are "active" at the same time?

Yes, this is the downside of my approach but I considered that in my application the probability to press both buttons at the same time is very low and it should not be critical.

 

If this is some mechanical contraption that is not operated by a human, you might run into the problem that you just cant actuate it faster than it settles.

This buttons are pressed by a human, thus the low probability of them being pressed at same time.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

I must add into every pin debounce discussion that it is far easier to add a one-cent 0.1 uF capacitor to each AVR pin requiring debouncing instead of elaborate software constructions.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

According to datasheet the INT0 and INT1 flags should be cleared automatically by executing the interrupt vector

It will clear the interrupt flag upon entering the ISR, but then you turn the interrupt off. Between the time you turn off the interrupt and turn it back on, the switch bounce will set the flag again. That means that the ISR will again be entered immediately after the interrupt is enabled giving you a false positive. You need to manually clear the interrupt flags just before re-enabling the interrupts.

Regards,
Steve A.

The Board helps those that help themselves.

Last Edited: Mon. Jul 13, 2015 - 08:35 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I must add into every pin debounce discussion that it is far easier to add a one-cent 0.1 uF capacitor to each AVR pin requiring debouncing instead of elaborate software constructions. 

But does it work always well for you with just a simple capacitor? Not too much simple? Usually the HW debouncers I see are a little more complex, even the ones that use capacitors...

Between the time you turn off the interrupt and turn it back on, the switch bounce will set the flag again.

Now I see the interrupt flags stuff... I will clear them as you say and give it a try to see if it solved the problem.

I will come back soon with results.

TY guys!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Simonetta wrote:

I must add into every pin debounce discussion that it is far easier to add a one-cent 0.1 uF capacitor to each AVR pin requiring debouncing instead of elaborate software constructions.

 

But surely any capacitor can be replaced with hundreds of lines of tangled, buggy and unreliable code.

If you don't know my whole story, keep your mouth shut.

If you know my whole story, you're an accomplice. Keep your mouth shut. 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Did anyone download compile and run the bounce detector program posted 'a couple months  ago?

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello bobgardner, what is the post you talk about? Link please.

 

Regards

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 3

Simonetta wrote:

I must add into every pin debounce discussion that it is far easier to add a one-cent 0.1 uF capacitor to each AVR pin requiring debouncing instead of elaborate software constructions.

Absolutely!

 

In the same vein, I'm tired of telling people that you can actually buy LEDs that flash - by themselves! So there's really no need for anyone to waste time learning to program AVRs.

 

Four legs good, two legs bad, three legs stable.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Torby wrote:
... But surely any capacitor can be replaced with hundreds of lines of tangled, buggy and unreliable code.
Or a function with 1–3 lines to debounce each input (e.g., 8–24 lines to debounce 8 inputs), that you can quickly adopt or write, and then just use and reuse.

 

Sometimes it is better to add a capacitor, but you have to find board space for it and place it, each time.

- John

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Any algorithm can be implemented in all hardware, all software, or a combination of the two, based on various criteria like cost.

 

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

 

0.1uF capacitor value is just a guess but I bet that it works fine.

You want an R-C time constant that is long enough that the positive

switch contact pulses can't rise to the logic-hi threshold level.

You can use tiny surface-mount caps on a custom PCB next to the switch.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If you just wait for the switch bounce time before reading a second time, you dont need the C.

 

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

bobgardner wrote:

If you just wait for the switch bounce time before reading a second time, you dont need the C.

 

 

But he doesn't want to do it the obviously correct way

If you don't know my whole story, keep your mouth shut.

If you know my whole story, you're an accomplice. Keep your mouth shut. 

Last Edited: Wed. Jul 15, 2015 - 12:54 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Simonetta wrote:

 

0.1uF capacitor value is just a guess but I bet that it works fine.

You want an R-C time constant that is long enough that the positive

switch contact pulses can't rise to the logic-hi threshold level....

Jack Ganssle wrote a nice article about switch debouncing with measurements and many more details. He did not use this circuit because few microprocessors, back then, had input hysteresis. The AVR, however, does. cool

 

 

- John

Last Edited: Wed. Jul 15, 2015 - 06:49 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Using a pin change interrupt to debounce push button switches, can be done, but it can get a bit tricky.

 

It is much simpler to have a timer interrupt that fires every X mSec, and you read the switch states.

10 mSec was mentined above, I often use 2 mSec, and a higher count value before deciding a switch is pressed or released.

 

Your uC is running in the MHz clock frequency range.  It only takes a few instructions to read a state, and increment of decrement a counter, and test it.

Is your program really running sometine so time critical and processor intensive that you can t spare a few clock clycles for debouncing using a timer in CTC mode.

 

If you are having trouble then start over with a clean sheet of paper, and use a traditional approach.

Once you have it working, then decide if you have a good reason to switch to a new method.

 

JC

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I think nobody understood the reason of my approach... First I already made the HW so Im obligated to use the switches attached to INT0 and INT1.

Second I did not want to "waste" one timer just to periodically (continuous!) check switch state. So I decided to use a timer only temporarily, that is, when and only one of the interrupts fire, the timer starts counting for a maximum period of 200ms. If in my debounce algorithm I have 10 consecutive positive readings "button pressed", then I switch off the timer and then I can use it for anything else on my code.

In case I have a negative "button pressed" (it was only a spur!), then the timer counts  200ms and then switches off the same way.

The only reason of my approach was just to free the timer after it debounces the switched :)

 

Anyway, thanks for your expert interrupt skills. Next time I test it I think it will work properly.

 

EB

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

EB,

 

It realy doesn't mattrer if the switches are connected to INT0, INT1, etc.  In the classic approach, you are just reading the digitial I/O pin's input state, within the (timer generated) ISR.  So the switches can be on ANY (digital) pin.

 

Agreed, running out of Timer/Counters, or I/O pins, or hardware PWM drivers, or..., or..., or..., is a problem.

That is part of the challenge (and fun), in uC project design.

It is generally much easier to start with a "big" uC that has lots of memory and hardware resources to write one's code, and then when things are working, decide if it is really worth while to try proting the code over to a smaller uC.

If you are building 1000's, or millions, then saving a few cents off the cost adds up.

If you are only building a few, then it often just isn't worth the design time and effort to try to redesign for a maximally small chip.

 

Note, also, that several of the Freaks are very good at getting multiple simultaneous uses out of a single timer.

(Not me, I just pick a larger uC with more resources.)

 

Even if you really want to debounce using your approach, I'd still suggest writing (and debugging), a classic debouncer first.

Do this as a learning step along the process to developing your final project.

 

Once you truly understand the inner workings of the classic approach, then branch off and work on your modified technique.

(Or pick a larger micro, with more Timer/Counters, and move on!)

 

JC

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Bye the way, you COULD even add your own Timer Interrupt to the uC.

If you have two spare pins, one of which is an Analog Comparator, available, then you could add an RC to the A.C. pin.

The RC will charge up and fire the A.C. interrupt every so often.

Inside the A.C. interrupt your use your other I/O pin to pulse a 2N7000 / 2N7002 NFet to short out the cap, and hence restart its charging again.

 

While inside the ISR you also do your other required ISR processing.

 

This is clearly a hardware hack, but the point is there are many ways to approach a "problem".

 

JC

 

Edit: Typo

Last Edited: Wed. Jul 15, 2015 - 08:51 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

then I switch off the timer and then I can use it for anything else on my code.

But with risk that that " anything else" process can be at anytime interrupted by a button press and the timer value overwritten.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yes, there are risks and I'm aware of them. I'm trying to use the timer to perform non critical operations. I'm using an ATmega 328p and Timer1 and Timer 2 are already used for critical operations.

At this stage I will use Timer 0 for LED blinking, so 200ms to use on the debouncer would not be even noticeable in LEDs blinking. These LEDs will blink according to how many "button pressed" I have out of debounce, so the switches will choose LED blinking intensity (and other stuff).  

 

I think I opened a good discussion regarding debouncing taking HW resources saving into account :)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Here is a way to read 2 buttons that bounce 4 times 2ms per bounce using no hw timers and no interrupts

unsigned char sw1state; //pressed or not
unsigned char sw2state;
unsigned char sw1edge;  //pressed this pass
unsigned char sw2edge; 
unsigned char sw1lastpass;
unsigned char sw12astpass;
.
.
.
//in main while(1) loop
sw1state = (PINB & 0x01)==0;
sw2state = (PINB & 0x02)==0;
sw1edge = sw1state && !sw1lastpass;
sw2edge = sw2state && !sw12astpass;
sw1lastpass = sw1state;
sw2lastpass = sw2state;

if(sw1edge){
    inc count; //or something similar
}
//similar handling of sw2

delay(10); //longer than sw bounce time
//end of while 1 loop

 

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I get all (mostly) that and it's neat... by changing  delay(500) , will the key press auto increment (say)  twice a second, or is the routine just an 'edge detect' so will only fire once until the switch is released ?

I ask because I can't decipher this bit :-

 

sw1edge = sw1state && !sw1lastpass;
sw2edge = sw2state && !sw2lastpass; ( corrected typo in your post above)
 

into assembler :(

Last Edited: Thu. Jul 16, 2015 - 11:49 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

switch state pressed this pass and not pressed last pass is true for one 10ms pass. Its not true the next pass. I asked by old buddy George Boole to check that equation.

Sounds like you want to do something like push the button and hold it, and increment every 500ms. So I'd have a pass counter. 500ms is 50 passes at 10ms per pass, then set os500. Or you could slow the loop time down to 100ms and count 5 of them. I think somerhing like if(sw1state && os500) dosomething; Note that no edge detect oneshot is used here, just the switch state. Also, doesnt need any debouncing, timers, or interrupts.

 

Imagecraft compiler user