Button debounce inside an interrupt (IOC) on PIC16F688

Started by SebaG, Nov 30, 2021, 09:03 PM

Previous topic - Next topic

SebaG

Welcome! How to sensibly implement a button debounce inside an interrupt? I want to use Interrupt-On-Change mechanism, but a typical tactile switch requires debouncing for the program to work properly. Do you have any ideas / own solutions to this problem? I would like to add that I am interested in software, not hardware solutions. Regards.

trastikata

What does the button do?

Can you allow some software delay after the button is pressed? Simply delay the clearing of the flag by say 250-300 msec. If your application can not have the software delay (idling for that time), in the IOC routine disable the interrupt, start one of the timers and reset the IOC when the timer flag is raised.

SebaG

Quote from: trastikata on Nov 30, 2021, 09:44 PMCan you allow some software delay after the button is pressed? Simply delay the clearing of the flag by say 250-300 msec.

Let's say that the button is to change the state of a pin to the opposite one, for example to turn the LED on / off. The delay in the interrupt routine is probably not a good idea as long as I understand you right.

SebaG

Perhaps I did not explain it correctly. I want to change the pin state to the opposite (turn the LED on / off) in the interrupt (from IOC) - not in the main loop of the program. Everything including button debounce should be done inside an IOC handler. I don't use any other interrupts, only the IOC.

trastikata

#4
Quote from: SebaG on Nov 30, 2021, 10:09 PMPerhaps I did not explain it correctly. I want to change the pin state to the opposite (turn the LED on / off) in the interrupt (from IOC) - not in the main loop of the program. Everything including button debounce should be done inside an IOC handler. I don't use any other interrupts, only the IOC.

The PIC is so fast that without a time delay after the first edge change, it will register hundreds more transitions, therefore you need some kind of delay to hold the state (ignore transitions) for a certain time after that.

So if you don't want to use the software delay (DelayMS) inside the interrupt and stall the program, use one of the timers to generate a hardware time interrupt and reset the IOC in the timer interrupt routine. Here's the sequence:

Interrupt on change detected --> Disable IOC --> Clear IOC flag --> Toggle LED --> Timer preset --> Start Timer --> Timer interrupt generated --> Disable timer --> Clear Timer interrupt flag --> Enable IOC

SebaG

https://www.best-microcontroller-projects.com/easy_switch_debounce.html
This is interesting. How to translate method "Shift register debounce operation" into our Basic?

diebobo

Here is my debounce Routine.. Used within a Timer Interrupt at roughly 200 times a sec. I t debounces to pressed short/long or continuesly. Don't think you can do this effective in IOC ISR, thats why i just use it within Timer Interrupt frequently scanning the buttons.


Check_Switches:
If Button_1 = 0 Then
    If Button_1_Pressed_Counter < 65535 Then Inc Button_1_Pressed_Counter
    If Button_1_Pressed_Counter > 80 And Button_1_Pressed_Counter // 10 = 0 Then
        Button_1_Pressed_Cont = 1
    Else
        Button_1_Pressed_Cont = 0
    EndIf
    Switch_Pressed = 1
Else
    Select Case Button_1_Pressed_Counter
    Case > 60
        Button_1_Pressed_Counter = 0
        Button_1_Pressed_Long = 1
        Button_1_Pressed_Short = 0
        Button_1_Pressed_Cont = 0
    Case 1 To 60
        Button_1_Pressed_Counter = 0
        Button_1_Pressed_Short = 1
        Button_1_Pressed_Long = 0
        Button_1_Pressed_Cont = 0
    Case Else
        Button_1_Pressed_Counter = 0
        Button_1_Pressed_Short = 0
        Button_1_Pressed_Long = 0
        Button_1_Pressed_Cont = 0
    EndSelect
EndIf

top204

Below is a simple interrupt debounce mechanism using a Timer0 overflow interrupt, so it can be converted to any device family:

'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
' Simple interrupt switch debounce mechanism
'
' Written for the Positron8 compiler by Les Johnson
'
    Include "Amicus18.inc"
    Include "Amicus18_Timers.inc"           ' Load the Amicus18 Timer macros into the program
   
    On_Hardware_Interrupt GoTo My_ISR
 
    Symbol Button1_Pin = PORTA.1
    Symbol Button2_Pin = PORTA.2
  
    Dim tButton1_Pressed As Bit = 0
    Dim tButton2_Pressed As Bit = 0
   
    Dim bButton1_Counter As Byte Access
    Dim bButton2_Counter As Byte Access
 
'-------------------------------------------------------   
Main:
    PinInput Button1_Pin
    PinInput Button2_Pin
    bButton1_Counter = 0
    bButton2_Counter = 0  
'
' Configure Timer 0 for:
'                           Clear TMR0L and TMR0H registers
'                           Interrupt on Timer0
'                           16-bit operation
'                           Internal clock source
'                           1:8 Prescaler
'
    OpenTimer0(TIMER_INT_ON & T0_16BIT & T0_SOURCE_INT & T0_PS_1_8)

    INTCONbits_GIE = 1                      ' Enable global interrupts
   
    Do
        If tButton1_Pressed = 1 Then        ' Has button1 been pressed?
            tButton1_Pressed = 0            ' Yes. So reset its flag
            HRSOut "Button 1 Pressed\r"     ' Indicate a press
        EndIf  
       
        If tButton2_Pressed = 1 Then        ' Has button2 been pressed?
            tButton2_Pressed = 0            ' Yes. So reset its flag
            HRSOut "Button 2 Pressed\r"     ' Indicate a press
        EndIf
    Loop

'-------------------------------------------------------
' Interrupt service routine
' Input     : bButton1_Counter and bButton2_Counter count the iterations between a button press
' Output    : tButton1_Pressed is 1 if button 1 is pressed  
'           : tButton1_Pressed is 1 if button 2 is pressed
' Notes     : Interrupt's on an Timer0 overflow
'    
My_ISR:
    Context Save
    If INTCONbits_T0IF = 1 Then             ' Was it a Timer0 overflow that triggered the interrupt?
        If Button1_Pin = 0 Then             ' Yes. So is button1 pressed?
            Inc bButton1_Counter            ' Yes. So increment the counter
            If bButton1_Counter >= 5 Then   ' Has the counter reached the debounce value?
                bButton1_Counter = 0        ' Yes. So reset the debounce counter
                tButton1_Pressed = 1        ' Indicate that button1 has been pressed
            EndIf
        Else                                ' Otherwise...
            bButton1_Counter = 0            ' Reset the button pressed counter
        EndIf
       
        If Button2_Pin = 0 Then             ' Is button2 pressed?
            Inc bButton2_Counter            ' Yes. So increment the counter
            If bButton2_Counter >= 5 Then   ' Has the counter reached the debounce value?
                bButton2_Counter = 0        ' Yes. So reset the debounce counter
                tButton2_Pressed = 1        ' Indicate that button2 has been pressed
            EndIf
        Else                                ' Otherwise...
            bButton2_Counter = 0            ' Reset the button pressed counter
        EndIf
       
        Clear INTCONbits_T0IF               ' Clear the Timer0 overflow flag
    EndIf
   
    Context Restore

The timer interrupt operates as a state counter, so it waits a short while after a button press has been detected, then looks again at the pin's state, but does not block any of the program's flow.

To toggle the state of something from a single button press, you will need a simple state routine that holds the previous state of a press and compares it.

Because the Timer0 interrupt is constantly on and has a constant iteration time, it can also be used to detect how long a button has been pressed, with a few simple code additions.

Stephen Moss

You could do it this way assuming you can start/stop the timer...

IOC Interrupt:
Disable IOC
Clear IOC Flag
Timer0 = Value (Value is dependant on how long you want your delay and both the Timer clock frequency and pre/post scaller settings)
Timer0 On 'Start the Timer

Timer Interrupt:
Timer0 Off
Clear Timer Interrupt
Read Button Pin

If Button_state = Pressed then
switch is pressed so Toggle LED pin
else
switch is not pressed
end if

re-enable ICO to detect next button press

SebaG

Thank you very much.
While browsing the web for an effective debounce method in an IOC interrupt, I noticed that it is often discouraged to use IOC in favor of timer interrupt debounce method. Is there no viable method of debounce IOC without using a timer? So is IOC an unnecessary addition to microcontrollers?

PS Does the Proton / Positron interrupt mechanism allow the use of the method described below?



http://www.ganssle.com/debouncing-pt2.htm

SebaG

I'm too stupid for the software methods of debouncing the button, so I used an IOC and a "hardware" filter as below (I changed 1u to 100n). Now the IOC works flawlessly.


tumbleweed

QuoteIs there no viable method of debounce IOC without using a timer?
At some point you usually end up needing a timer/delay in order to do the debouncing, so it can be simpler just to use a timer in the first place.

QuoteSo is IOC an unnecessary addition to microcontrollers?
In many cases, yes. You have to be very careful using IOC since it's very easy to fool the change detect mechanism.
Usually any read of the port containing the IOC bits will reset the change, so if you have other bits on the port that can be a problem.

Stephen Moss

Quote from: SebaG on Dec 01, 2021, 11:55 AMIs there no viable method of debounce IOC without using a timer?
There is probably a way, just not one that immediately springs to mind as is it not something most people would do.
While you are servicing the interrupt the PIC cannot get on with running other code or detecting any other interrupts so generally you want to keep your interrupt routines as short as possible to prevent that.

As you know the problem with switch bounce is that if you are not hanging around in the IOC interrupt (or disable it) the bounce will activate the IOC interrupt multiple times for a single press which is not good. So after the initial detection you need a way to check and confirm the pressed state after the bounce has stopped to get the true state of the switch, i.e. was it actually pressed (in pressed state) or knocked (in unpressed state).
So generally it is easiest to use a timer, whether your write to it to set a desired period shorter that 255 (8bit) or 65535 (16bit) ticks, or use the default 255/65535 value and use the IOC to keep clearing it (no bounce = no IOC to clear and it eventually interrupts).

How long a switch bounces for and how many times depends on the switch, the force applied and even the angle it was pressed at so it is difficult to do something based on say counting the bounces and comparing that to a fixed value or if it has bounced either an odd or even number of times as it could vary slightly with every press.
That is why most people would go for either the timer or passive external RC option. 

I am sure the C code you listed could be converted to Positron, but it still uses a timer, the difference between that and the already suggested method is that the timer is being used to contently poll the switch state (as opposed to once after the initial change of state) , creating many more timer interrupts which have to be serviced slowing down the execution time of you main code, although depending on what you are doing and the Xtal speed that may not be a problem.

Also remember that if the device you are using has only basic IOC, and you cannot select IOC detection on just one edge or the other you will also generate an IOC interrupt when the button is released, not just when pressed.