News:

Let's find out together what makes a PIC Tick!

Main Menu

Long press button

Started by Yves, Oct 18, 2024, 12:51 PM

Previous topic - Next topic

Yves

In my project one of the buttons has to have 2 functions. If it is pressed for a shorter time less than about 300 ms seconds it do something and if it is pressed more than about 3 seconds it quite the routine and generate a break in the loop.I Can't use the Button function described on the manual.I'm sure some of you had worked a simple routine for it. Many thanks.

Cheers

Yves
Yves

SCV

Use a hardware timer. Zero timer upon change OFF-ON, check timer value upon change ON-OFF. Don't sit in a loop waiting for the button to release, just regularly poll the button for a change of state. Small debounce glitches can be filtered out too.

I always implement a 1mS timer click and simply count the milliseconds button was down.
Tim.

dnaci

It was needed years ago when using PBP. It can be done with For Next.

time VAR Byte
main:
For time=1 TO 30 step 1
Pause 100
if portb.3=0 then goto main 'button
Next
portb.1=0 'control pin
goto main

JonW

#3
If you want to wait in a continuous loop, use a while loop. While the button is pressed, increment a counter and add a delay for a debounce. Keep looping and counting until the button is released (the loop should break). Now, test the count and do something. If you need to run other tasks, you could read a timer value or use a counter that counts on an interrupt to get a longer time period, then set an interrupt on button pin change and read the counter or timer after the button release

diebobo

Here is my code for a single button, short, long and continues press while perrforming other tasks in main loop..

Assume Main_Loop_Ticks_Counter_Stored = the loops per second the main loop makes, so button short/long is independendent of speed / busyness of the PIC... The part of "ElseIf Buttons_1_Pressed_Counter > 0 And... " replace the 0 with a 5 or 10 if debounce is needed.

Proc Functions_Buttons (), Bit ' All Inputs needs PullUp. With CN or Hardwired Resistors.
    Dim Short_Value As Word
    Dim Long_Value As Word

    Short_Value     = Main_Loop_Ticks_Counter_Stored
    Long_Value      = Main_Loop_Ticks_Counter_Stored * 2

    Result = FALSE
   
    Input Functions_Buttons_1
    If Functions_Buttons_1 = 0 Then
        If Buttons_1_Pressed_Counter < 65535 Then Inc Buttons_1_Pressed_Counter
        If Buttons_1_Pressed_Counter > Long_Value Then Buttons_1_Pressed_Cont = 1
        Result = TRUE
    Else
        If Buttons_1_Pressed_Counter > Short_Value And Buttons_1_Pressed_Counter < Long_Value Then ' Moet met AND geschreven worden, met 2e if gaat het fout met elseif enzo
            Buttons_1_Pressed_Long       = 1
            Buttons_1_Pressed_Short      = 0
            Buttons_1_Pressed_Cont       = 0
            Result                       = TRUE
        ElseIf Buttons_1_Pressed_Counter > 0 And Buttons_1_Pressed_Counter < Short_Value Then ' Moet met AND geschreven worden, met 2e if gaat het fout met elseif enzo
            Buttons_1_Pressed_Short      = 1
            Buttons_1_Pressed_Long       = 0
            Buttons_1_Pressed_Cont       = 0
            Result                       = TRUE
        Else
            Buttons_1_Pressed_Short      = 0
            Buttons_1_Pressed_Long       = 0
            Buttons_1_Pressed_Cont       = 0
        EndIf
        Buttons_1_Pressed_Counter        = 0
    EndIf
Endproc



The main code could look like:

Main:

...... Do STUFF Here.

  If Functions_Buttons () = TRUE Then
    PC_Serout [ Dec Buttons_1_Pressed_Counter, 10]
   
    If Buttons_1_Pressed_Cont = TRUE Then
      EEPROM_Schrijven_SWord ( 1, Functions_RotaryEncoder_Result )
    EndIf
    If Shows_Display = 4 Then
      SSD1322_DrawString(74, 23, "BUTN", NOT_Underlined, 15 )
      If Buttons_1_Pressed_Cont = TRUE Then SSD1322_DrawString(102, 23, "CONT", NOT_Underlined, 15 )
    EndIf
 
  EndIf
GoTo Main

top204

There are several methods of timing a button press, and below is another method using a Timer0 interrupt that counts in milliseconds, and a procedure that indicates how long a button has been pressed for, based upon its parameters:

'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' A button press procedure that detects a single button press of certain durations.
' No blocking is performed within the program's flow while waiting for a button press duration.
'
' Written for the Positron8 compiler by Les Johnson
'
    Device = 18F26K22                                                   ' Tell the compiler what device to compile for
    Declare Xtal = 16                                                   ' Tell the compiler what frequency the device is operating at (in MHz)
    On_Hardware_Interrupt GoTo ISR_Handler                              ' Point the compiler to the Interrupt Handler routine
'
' Setup USART1
'
    Declare Hserial1_Baud = 9600
    Declare HRSOut1_Pin   = PORTC.6

    Symbol Button_Pin = PORTB.0                                         ' The pin used for the button (active low)

$ifndef True
    $define True 1
$endif
$ifndef False
    $define False 0
$endif
'------------------------------------------------------------------------------
' General Interrupt Meta-Macros for a PIC18F26K22 device
'
$define IntGlobal_Enable()  INTCONbits_GIE = 1                          ' Enable global interrupts
$define IntGlobal_Disable() INTCONbits_GIE = 0                          ' Disable global interrupts
$define IntPeriph_Enable()  INTCONbits_GIEL = 1                         ' Enable peripheral interrupts
$define IntPeriph_Disable() INTCONbits_GIEL = 0                         ' Disable peripheral interrupts

'------------------------------------------------------------------------------
' Timer0 Meta-Macros for a PIC18F26K22 device
'
$define Timer0_IntBit       INTCONbits_TMR0IE                           ' The bit to enable/disable a Timer0 interrupt
$define Timer0_Flag         INTCONbits_T0IF                             ' Timer Timer0 overflow Flag
$define Timer0_FlagClear()  Timer0_Flag = 0                             ' Clear the Timer0 overflow flag
$define Timer0_IntEnable()  Timer0_IntBit = 1                           ' Enable a Timer0 interrupt
$define Timer0_IntDisable() Timer0_IntBit = 0                           ' Disable a Timer0 interrupt
$define Timer0_Start()      T0CONbits_TMR0ON = 1                        ' Start Timer0
$define Timer0_Stop()       T0CONbits_TMR0ON = 0                        ' Stop Timer0
'
' Alter the bits for a required Timer0 Prescaler on a PIC18F26K22 Device
'
$define Timer0_Prescaler(pPrescaler)'
    $if pPrescaler = 2              '
        T0CONbits_T0PS0 = 0         '
        T0CONbits_T0PS1 = 0         '
        T0CONbits_T0PS2 = 0         '
    $elseif pPrescaler = 4          '
        T0CONbits_T0PS0 = 1         '
        T0CONbits_T0PS1 = 0         '
        T0CONbits_T0PS2 = 0         '
    $elseif pPrescaler = 8          '
        T0CONbits_T0PS0 = 0         '
        T0CONbits_T0PS1 = 1         '
        T0CONbits_T0PS2 = 0         '
    $elseif pPrescaler = 16         '
        T0CONbits_T0PS0 = 1         '
        T0CONbits_T0PS1 = 1         '
        T0CONbits_T0PS2 = 0         '
    $elseif pPrescaler = 32         '
        T0CONbits_T0PS0 = 0         '
        T0CONbits_T0PS1 = 0         '
        T0CONbits_T0PS2 = 1         '
    $elseif pPrescaler = 64         '
        T0CONbits_T0PS0 = 1         '
        T0CONbits_T0PS1 = 0         '
        T0CONbits_T0PS2 = 1         '
    $elseif pPrescaler = 128        '
        T0CONbits_T0PS0 = 0         '
        T0CONbits_T0PS1 = 1         '
        T0CONbits_T0PS2 = 1         '
    $elseif pPrescaler = 256        '
        T0CONbits_T0PS0 = 1         '
        T0CONbits_T0PS1 = 1         '
        T0CONbits_T0PS2 = 1         '
    $else                           '
        $error "Unknown prescaler value. Supported values for this device are 2 or 4 or 8 or 16 or 32 or 64 or 128 or 256" '
    $endif

'---------------------------------------------------------------------------------
' Calculate the value to place into the TMR0L\H registers in order to achieve a certain overflow interrupt rate (in us)
'
$define Timer0_cPrescaler 8                                             ' The prescaler used for the timer (must be the same prescaler as used for the timer's setup)
$define Timer0_cMicroSeconds 1000                                       ' Interrupt rate (in uS)
$define Timer0_cTweakValue 0                                            ' Holds a tweak value for the timer calculation

$define Timer0_cValue $eval ((65536 + Timer0_cTweakValue) - ((Timer0_cMicroSeconds / Timer0_cPrescaler) * (_xtal / 4)))

$if Timer0_cValue > 65535
    $error "Timer0_cValue is too large for the interrupt duration"
$elseif Timer0_cValue <= 0
    $error "Timer0_cValue is too small for the interrupt duration"
$endif
'
' Create global variables here
'
    Dim Button_lMilliSeconds As Long Access                             ' Counts milliseconds from the interrupt
    Dim Button_tPressedShort As Bit = False                             ' Is set true if the button press reaches its short interval time
    Dim Button_tPressedLong  As Bit = False                             ' Is set true if the button press reaches its long interval time
    Dim wTimer0_SFR          As TMR0L.Word                              ' Combine TMR0L and TMR0H into a 16-bit SFR

'----------------------------------------------------------------------------------------------------------------------------
' The main program starts here
' Wait for button presses of 50ms or 3000ms before indicating it has been pressed
'
Main:
    Setup()                                                             ' Setup the program and any peripherals
    HRSOutLn "Press the Button"
    Do                                                                  ' Create a loop
        Button_Get(Button_Pin, 50, 3000)                                ' Detect a button press with a short press being 50ms and a long press being 3000ms
        If Button_tPressedLong = True Then                              ' Has the button been pressed for the duration of a long press?
            Button_tPressedLong = False                                 ' Yes. So reset its flag
            HRSOutLn "Button Long Press"                                ' Transmit to a serial terminal that a long duration press has occured
        EndIf
        If Button_tPressedShort = True Then                             ' Has the button been pressed for the duration of a short press?
            Button_tPressedShort = False                                ' Yes. So reset its flag
            HRSOutLn "Button Short Press"                               ' Transmit to a serial terminal that a short duration press has occured
        EndIf
    Loop                                                                ' Do it forever

'----------------------------------------------------------------------------------------------------------------------------
' Setup the program and any peripherals
' Input     : None
' Output    : None
' Notes     : None
'
Proc Setup()
    Timer0_Setup()
    IntGlobal_Enable()
EndProc

'----------------------------------------------------------------------------------------------------------------------------
' Detect a button press for a certain length of time
' Input     : pButton holds the pin that the button is attached to
'           : pTime1 holds the short time (in milliseconds) to count the button pressed
'           : pTime2 holds the long time (in milliseconds) to count the button pressed
' Output    : Global Bit flag "Button_tPressedShort" is set true if the short interval time is reached (must be reset in the main program)
'           : Global Bit flag "Button_tPressedLong" is set true if the long interval time is reached (must be reset in the main program)
' Notes     : Operates with an active low button
'           : Uses Timer0 operating within an interrupt at a 1ms iteration
'
Proc Button_Get(pButton As Pin, pTime1 As Word, pTime2 As Word)
Static Dim tButtonState As Bit = False                                  ' Holds the button state, so it sets up flags and SFRs only once

    PinMode(pButton, Input_PullUp)                                      ' Make the button's pin an input, with an internal pull-up resistor

    If PinGet(pButton) = 0 Then                                         ' Is the button pressed?
        If tButtonState = False Then                                    ' Yes. So is tButtonState false?
            tButtonState = True                                         ' Yes. So toggle it, so it will not setup the Timer again while pressed
            Timer0_FlagClear()                                          ' Clear the Timer0 overflow flag
            wTimer0_SFR = Timer0_cValue                                 ' Re-load TMR0L\H with the value to give a specified overflow time
            Timer0_Start()                                              ' Start Timer0
            Button_lMilliSeconds = 0                                    ' Reset the millisecond counter  
        EndIf
        Select Button_lMilliSeconds                                     ' Compare the value held in Button_lMilliSeconds
            Case pTime1 + 10 To pTime1 + 10                             ' Is the pressed time approx the short interval time?
                Button_tPressedShort = True                             ' Yes. So set Button_tPressedShort true

            Case >= pTime2                                              ' Is the pressed time the long interval time or over?
                Button_tPressedLong = True                              ' Yes. So set Button_tPressedLong true
        EndSelect
    Else                                                                ' Otherwise... The button is not pressed
        tButtonState = False                                            ' So... Reset the tButtonState flag
        Timer0_Stop()                                                   ' Stop Timer0
    EndIf
EndProc

'---------------------------------------------------------------------------------
' Setup Timer0
' Input     : None
' Output    : None
' Notes     : Uses the Meta-Macro calculation above to set the value held in Timer0_cValue
'
Proc Timer0_Setup()
    T0CON = %00000000                                                   ' Clear T0CON
    Timer0_Prescaler(Timer0_cPrescaler)                                 ' Set the prescaler value for Timer0
    wTimer0_SFR = Timer0_cValue                                         ' Load the value for a specific overflow time
    Timer0_FlagClear()                                                  ' Clear the Timer0 interrupt flag
    Timer0_IntEnable()                                                  ' Enable a Timer0 interrupt
EndProc

'----------------------------------------------------------------------------------------------------------------------------
' Interrupt Handler routine
' Input     : None
' Output    : Button_lMilliSeconds holds the amount of times the timer has overflowed
' Notes     : Interrupts on a Timer0 overflow
'
ISR_Handler:
    Context Save                                                        ' Save any compiler system variables and important SFRs used in the interrupt

    If Timer0_IntBit = True Then                                        ' \
        If Timer0_Flag = True Then                                      ' / Is it a Timer0 interrupt?
            Inc Button_lMilliSeconds                                    ' Yes. So increment the millisecond counter
            Timer0_FlagClear()                                          ' Clear the Timer0 interrupt flag
            wTimer0_SFR = Timer0_cValue                                 ' Re-load TMR0L\H with the value to give a specified overflow time
        EndIf
    EndIf

    Context Restore                                                     ' Restore variables and SFRs used and exit the interrupt

A screenshot of the above program working in a simulator is shown below:

Button_Timer.jpg

Peter Truman

I tend to do these things in an interrupt - generally I set up a 1ms timer interrupt that forms the basis of all ny timing functions. If I want to discriminate between a long press and a short press (say with >299ms for a long press and <299 for a short press) I set a variable at 300ms when the button press is detected, then count down in the interrupt, if the counter >299 when the button is release then that is a short press, but if it hits 0 while the button is still pressed I'll break out of the loop and execute the code for a long press. I'll sit in a loop while the button is pressed, but the code itself is non blocking. The user can't hold the button down for too long since I will break out at 0.

In each interrupt I look at any of the counters I have running, if they are >0 then I decrement by 1 - that way I only have to look for 0 instead of some timer variable.

I find it easier to count down than up I guess!