News:

;) This forum is the property of Proton software developers

Main Menu

Improved debounce for keypad

Started by John Drew, Apr 16, 2022, 08:16 AM

Previous topic - Next topic

John Drew

I've been unhappy with the usual method of debounce (read key and then add a delay), as even then I'd sometimes get a double entry due to me being slow to release.
The following code snippet works in a different way and irrespective of how long the finger is left on the button the routine only returns one value. I don't want autorepeat. I'm using it with a graphic display and with my shaky fingers and an inconvenient installation position in a remote site it will overcome the occasional double entries with the old method. Users may wish to change the delayms value in the code below to suit their circumstances or keypad.
Hopefully the comments explain how it works.  The Procedure decodetouch() reads the Touch on the graphic panel and returns a value from 0 to 16 (16=no touch). The code could be tidier if bCountIn and bTemp had been local variables.

Proc GetKey()
    bCountIn = 0                'could have been made a local variable
    DecodeTouch()                'check keypad for a press, return with bInkey
    If bInKey = 16 Then Return  'no keypress so return
    bTemp = bInKey              'key pressed, hold value in bTemp
    Repeat                      'look for bounce or key up
    Agn:                        'now do debouncing
        DecodeTouch()          'check keypad and bring in bInkey
        If bInKey = bTemp Then  'check if key still held down
            GoTo Agn            'if so start again
        Else
            If bInKey = 16 Then  'if no button or button bounced inc delay count
              Inc bCountIn
              DelayMS 10        'small delay
            Else
              bCountIn = 0      'if key still held in then restart timer
            EndIf                'bCountIn and Delayms 10 determine delay after release
        EndIf
    Until bCountIn = 10          'been ten times through loop so button must be unpressed
    bInKey = bTemp              'value of bInkey used elsewhere
                                'now drop through to actions based on value of bInKey e.g. writing value to screen

Yes, it takes more code but performance is much better. In my use the transmitters, receivers, interrupts and various timers are disabled during touch access so the program can be dedicated to the touch function so a simple loop timer can be used.
John

Front display.jpg
keypad.jpg

top204

Excellent code John.

You would think that after all of these years using graphic LCDs, they would become rather boring. But the opposite is the case. :-) When you see your creation in full colour on a graphic LCD, it gives a buzz to the creative part of the mind. When it does not, it is time to stop. :-)


TimB


A bit late in the game but my method is to react to the first detection then count the number of times I see no detection before I allow for a repeat.

As all my code runs on tick timings for the key board I think 10ms with a bit of a state machine to code for autorepeat etc

'--------------------------------------------------------------------------------------
' Keyboard related routines
'--------------------------------------------------------------------------------------

    Dim bfNewKey As Bit
    Dim bfAutoRepeat As Bit
    Dim bAutoRepeatPeriod As Byte
    Dim bKeyDownCnter As Byte
    Dim bAutoSpeedCnter As Byte
   
    Dim cAutoKeyStage1 As 20                                ; Default cnts in 10ths before auto repeat
    Dim cAutoKeyStage2 As 14
    Dim cAutoKeyStage3 As 8
    Dim cAutoKeyStage4 As 4
   
    Dim cStage1AutoSpeed As 40
    Dim cStage2AutoSpeed As 70
    Dim cStage3AutoSpeed As 90

Proc GetKeys(), Byte
    Dim bLastKeys As Byte
    Dim bCurrentKeys As Byte
    Dim bAutoRepeatCnt As Byte
   

    bCurrentKeys = (PORTB & %11111000) >> 3
    bCurrentKeys.5 = PORTE.2

    If bCurrentKeys = $3F Then
        bKeyDownCnter = 0
    EndIf
   
    If bLastKeys = $3F Then                                 ; If last key check there was no keys
        If bCurrentKeys <> $3F Then                         ; But this time there is a key press
            bfNewKey = cTrue                                ; Flag a key pressed
        Else
            bfNewKey = cFalse                               ; Else no key pressed so clear key pressed
        EndIf
    Else                                                    ; Last time there was a key press
        If bfAutoRepeat = cTrue Then                        ; Is auto repeat on?
            If bCurrentKeys = bLastKeys Then                ; Yes now is the key same as last time
                Inc bAutoRepeatCnt                          ; Inc the counter
                Inc bKeyDownCnter                           ; Inc the external counter
                Inc bAutoSpeedCnter
               
                If bAutoSpeedCnter > cStage3AutoSpeed + 1 Then
                     Dec bAutoSpeedCnter   
                EndIf
               
                ; ifs used as select does not
                If  bAutoSpeedCnter > cStage1AutoSpeed  Then
                    bAutoRepeatPeriod = cAutoKeyStage2
                EndIf
                If bAutoSpeedCnter > cStage2AutoSpeed Then
                    bAutoRepeatPeriod = cAutoKeyStage3
                EndIf
                If bAutoSpeedCnter > cStage3AutoSpeed Then
                    bAutoRepeatPeriod = cAutoKeyStage4     
                EndIf 
                 
                         
                If bAutoRepeatCnt >= bAutoRepeatPeriod Then ; Is the counter >= than setting
                    bfNewKey = cTrue                        ; Yes so flag new key
                    bAutoRepeatCnt = 0                      ; Reset the counter
                EndIf
            Else                                            ; Key not the same as last time
                bAutoRepeatCnt = 0                          ; Reset the counter
                bKeyDownCnter = 0                           ; Clear external B down counter
                bAutoRepeatPeriod = cAutoKeyStage1          ; Set autorepeat time to slow
                bAutoSpeedCnter = 0                         ; reset the autoreat speed change counter
            EndIf
        Else
            bAutoRepeatCnt = 0                              ; Reset the counter anyways dispite auto repeat being off
        EndIf
    EndIf

    bLastKeys = bCurrentKeys                                ; Save current to last key

    Result = bCurrentKeys                                   ; Return with the keys

EndProc

John Drew

@ Les, thanks for the comment Les, it's appreciated.
@ Tim, the wonderful thing about code is that there are so many ways to achieve a result. As some say "There's more than one way to skin a cat".

shantanu@india

Well my traditional way to skin the cat has always been a 500 msec TMR1 interrupt to trigger the kbd scanning ISR. But I have never used a kbd matrix as John but separate pushbuttons.
Regards
Shantanu

Giuseppe MPO

John Drew I looked at your code and I realize that the end procedure is missing.
Are there any other lines of code missing too?

John Drew

Hello Giuseppe,
Yes, you are right. I didn't show the rest of the procedure which then actioned the value from the pressed button with a case statement.

You could put an EndProc and have it return bKey.

If users are interested I could show how I draw the screen and then read the screen to give me a key value for the debounce.
John

GaryC

Hello John
    I look on in amazement to the quality of your work and your support of Proton.
 A humble thank you
 Gary

basiclover

HI jOHN
I'm in the course of learning procedures. I would like to use your procedure but don't see how to do
Could you give a small example using the INKEY command
thanks

John Drew

I'll see what I can do in the next few days.
Getting late here.
John

basiclover

thanks John
I have no idee how to call the procedure when I use INKEY

John Drew

Quote from: basiclover on May 18, 2022, 02:31 PMthanks John
I have no idee how to call the procedure when I use INKEY
We'll sort that out.
Do you want a result from 1 to 16 or do you want an ASCII result such as "1", "2" etc?
John

basiclover

My final goal using INKEY is to convert x successive key presses  on a  4x3 keypad into a numerical integer value of x digits. Here I found a procedure from LES https://protoncompiler.com/index.php/topic,887.0.html for x=10 (if I understand it well). Don't understand neither how to call his procedure.
I want to replace in his procedure the simple debounce delay by your debounce procedure.
I thought that starting understanding how to call your procedure 'which is simple)would help me understanding how to call Les procedure

top204

A procedure is, essentially, a wrapped subroutine that can optionally accept parameters and optionally return a value. So calling a procedure is just like calling a subroutine, but the Gosub is not required.

The code listing I added for the keypad has a demo, and you will see two calls to two procedures that return values:

fValue = fKeyPad()  ' Convert a floating point value from the keypad

and

dValue = iKeyPad()  ' Convert an integer value from the keypad

Where each procedure returns a value for the numbers tapped onto the keypad.

Yasin

Hi Les. Let me ask a question about PROC. Generally speaking, PROCs work like subprograms. While anywhere in the proc. If we want to terminate, is ENDPROC right to jump over or is it the same as using RETURN?

Best regards.

basiclover

I use a 16F877A
when I
compile your program I get error on line 60:  "unrecognised or incorrect operator syntax"

60 $if (_core = 16) Or (_core = 14)                    ' 8-bit devices?

where must I specify "core=14" or does it work only with 16 bit devices ? For now I"m only iterested in integer return values


top204

The code was designed for the 18F devices, but should work on enhanced 14-bit core devices.

The standard 14-bit core devices are now, very much, outdated by about 20 years! So the program will not work on them because they do not support String variables because of the fragmented RAM.

basiclover

now I understand the line "bKey = InKey"   execute automaticaly the procedure when a key is pressed
I thought that a procedure must be called passing parameters
                                   

keytapper

Quote from: Yasin on May 19, 2022, 08:14 AMIf we want to terminate, is ENDPROC right to jump over or is it the same as using RETURN?
If you want to abandon the proc right away from a certain point, just use the keyword ExitProc.
Ignorance comes with a cost

Yasin

@keytapper thaks a lot. This was out of my mind.