Shop OBEX P1 Docs P2 Docs Learn Events
4x4 matrix keypad code help — Parallax Forums

4x4 matrix keypad code help

Don MDon M Posts: 1,653
edited 2011-06-13 04:09 in Propeller 1
I need some help understanding some code posted originally by Peter Jakacki. I have a 4x4 keypad with the following layout:

attachment.php?attachmentid=82032&d=1307803644

and here is his driver code:
{{
        KEYPAD ENCODER - Peter Jakacki

        Starts up a cog that scans a keypad matrix
        and translates and buffers the codes.
        This code was originally written as a hardcoded 4X4 (tested)
        but was adapted for larger matrices (not tested)
        This is a simple but complete Spin implementation

Keypad actions don't have to be fast but should be reliable so debounce periods are several ms
The keypad matrix is made up of rows (horizontal) and columns. (vertical)
All column lines are driven low while the row lines are inputs pulled high (10K).
When a key is pressed one of the row lines will become low.
The column lines are now scanned one by one to find out which column it belongs to.
When the column is found the column index is combined with the row index to form a keycode.
The keycode is translated in a user specified table and pushed into the keypad buffer.
}}

CON
  bufsz = 16                    ' keypad buffer size (keep in multiples of 2)
  
VAR
  long  stack[32]
  long  rows,cols,colpins,rowpins,akey,lastkey,keyrd,keywr,keycodes,ms1
  byte  keybuf[bufsz]




' Start the keypad scanner - column and row pins do not have to be adjacent 
' If keypad code translation is required then set keycodeptr to address of
' translation table or else set to zero for no translation 
'
pub start(keyrows,keycols,firstcolpin,firstrowpin,keycodeptr) : okay | i
  rows := keyrows
  cols := keycols
  colpins := firstcolpin
  rowpins := firstrowpin
  keyrd := 0
  keywr := 0
  keycodes := keycodeptr                                ' remember pointer to user keycodes
  ms1 := clkfreq / 1000 - 4296
  okay := cognew(scankeys,@stack) +1                    ' run keypad in it's own cog


' Returns a non-zero value if there is a keypad code in the buffer
' 
pub getkey : keycode
  keycode := 0
  if keywr <> keyrd
    keycode := keybuf[keyrd++&(bufsz-1)]
  return

' ******************* background methods run in their own cogs ********************

pri scankeys                                           'main scanning loop
  dira[rowpins..rowpins+rows-1]~                       'ensure row lines are inputs 
  repeat
    outa[colpins..colpins+cols-1]~                     'drive all columns low (all active)
    dira[colpins..colpins+cols-1]~~
    lastkey := 0
    repeat until row                                    'wait until any key is pressed
      ms(5)                                             'oh hum, this is so boring                                
    ms(5)                                               'there was a signal, debounce
    if row                                              'double-check row again (noise?)
      keypressed                                        'seems solid, let's try to encode it      

pri keypressed | i
      outa[colpins..colpins+cols-1]~                    'columns are always active low
      repeat i from 0 to cols-1                         'find out whch column is active
        dira[colpins..colpins+cols-1]~                  'all columns inactive except...
        dira[colpins+i]~~                               'only one column active
        ms(1)                                           'debounce period
        if row                                          'ignore false press (glitch)
          akey := i + (( >|row)-1 )*cols                'convert row and column
          if keycodes                                   'translate keycodes?
            akey := @keycodes.byte[akey]                'lookup new code (usually ASCII)
          if lastkey <> akey                            'great! but is it a new key?
            lastkey := keybuf[keywr++&(bufsz-1)] := akey    'yes, let's buffer it
          release                                       'wait for button release
          return
      return

pri release
  repeat until row == 0                                'row lines are released
    repeat while row                                   'wait if still glitching (shouldn't)
      ms(5)                                            'debounce a further 5ms
  
pri row
  return !ina[rowpins..rowpins+rows-1] & (rows*cols-1)   

pri ms(period)
    waitcnt(ms1*period + cnt)

My first question is how would you make a "dat" table for the keypad shown in above picture? In his code he refers to this:
'
 Start the keypad scanner - column and row pins do not have to be adjacent 
' If keypad code translation is required then set keycodeptr to address of
' translation table or else set to zero for no translation 

Second question- in my main method I want to just display what keys are pressed on PST but don't understand what would be returned from Peter's code to display. Here is a start to my Main code with ? where I don't understand:
pub main | ?
  term.start(31, 30, %0000, 115_200)                            ' start terminal (use PST)
    kp.start(4, 4, 0, 4, 0)                                     ' start keypad driver

   term.str(string("Keypad demo testing... press a key", 13))

   kp.getkey( ? )
   term.str( ? )

The keypad is connected to the prop as follows (with appropriate pull-up resistors as mentioned in his code):

Col 1 -> P0
Col 2 -> P1
Col 3 -> P2
Col 4 -> P3
Row 1 -> P4
Row 2 -> P5
Row 3 -> P6
Row 4 -> P7

So in kp.start I set up 4 rows, 4 columns, Columns start on P0, Rows start on P4, and then 0 because I don't know what to put there for lookup table.

Thanks.
Don
640 x 480 - 194K

Comments

  • StefanL38StefanL38 Posts: 2,292
    edited 2011-06-11 10:04
    Hi Don,

    the method getkey returns a 32bit integer-value.
    I would display this value with the method ".dec" from the term-object.
    [FONT=monospace]
    [/FONT]   Pressed_Key := kp.getkey    
      term.dec(Pressed_Key)[FONT=Arial][/FONT]
    

    I guess it will return the values 1-16. But I haven't looked into the scancode.
    Anyway by analysing it this way you will know what values are coming.

    Based on that you simplay code a case-statement
    best regards

    Stefan
  • Don MDon M Posts: 1,653
    edited 2011-06-11 10:52
    Stephan- Thanks for the help to get me started. Now I see some results it returns 0 - 15. The problem I now have is that without the "if" statment in my code below it continually displays 0's so I can never see the result as it fly's by continually so fast. kp.getkey always returns a result of "0" without pressing any keys. With my "if" statement it will then only display whenever there is a result returned greater then 0 but then it excludes 1 key which happens to be Column 1 Row 4.

    Out of curiousity I tried changing kp.start(4, 4, 0, 4, 0) to kp.start(4, 4, 0, 4, 1) and with that change it will give me a different result for each key but instead of being 0 - 15 they are now 64 - 79 so I don't understand the keycode translation table mentioned in the code.

    Here is my Main code:
    pub main | Pressed_Key
    
       term.start(31, 30, %0000, 115_200)                            ' start terminal (use PST)
       kp.start(4, 4, 0, 4, 0)                                       ' start keypad driver
    
       term.str(string("Keypad demo testing... press a key", 13))
    
       repeat
    
         Pressed_Key := kp.getkey
         if Pressed_Key > 0
           term.dec(Pressed_Key)
           term.str(string(13))
    
  • StefanL38StefanL38 Posts: 2,292
    edited 2011-06-11 12:06
    a quick solution could be to have 16 if-statements.
    another solution is to use a case-statement.

    It doesn't really matter what the values are
    if the returned value matches a certain number your code can surely conclude which key is pressed.

    I haven't looked into the code so I have no idea how the code works.
    But anyhow if no key is pressed the return-value should be different from any pressed key.

    If you want more help attach your archived code to a posting by using the archive-project-function of the propellertool.
    mainmenu File - archive ... project

    best regards

    Stefan
  • Don MDon M Posts: 1,653
    edited 2011-06-11 12:36
    Attached please find the archived file. Along your line of thinking I would agree that I can associate whatever values are returned to which key is pressed. The main issue at the moment is to understand the end parameter value in the kp.start function.

    The keypad code itself seems very solid at recognizing keypresses. If you press 2 keys at once it only recognizes whichever key was pressed first.
  • StefanL38StefanL38 Posts: 2,292
    edited 2011-06-11 13:35
    OK I have taken a short look into the code.

    You the user can define a translation-table. As far as I have seen it this translationtable is not included in the object.

    You the user would have to define it in your top-object-file (in your case the file "Mach_control_Main.spin)

    the last parameter of the start-method of the Keypad_encoder-object is the pointer to this translation-table

    in the method keypressed there is an if-condition

    if keycodes ... which means if variable keycodes contains a value different from zero
    the code is translated through the codeline
      akey := @keycodes.byte[akey] 
    
    What I don't understand - and want more pointer-experienced user to chime in - is

    the last parameter of
    pub start(keyrows,keycols,firstcolpin,firstrowpin,keycodeptr) : okay | i 
    
    is named keycodeptr which makes me think it is a pointer to a variable

    now this value is copied to variable "keycodes"
      keycodes := keycodeptr                                ' remember pointer to user keycodes
    
    and later on this codeline
      akey := @keycodes.byte[akey] 
    
    uses the "@"-operator which means use adress of variable "keycodes"
    Now if I understand right keycodes already contains an adress to a variable
    and this does not make any sense for me.

    But I guess I'm overlooking something or haven't understand an important detail of the code.

    So please take the tomatoes from my eyes: how the heck does this code work?

    And how does a translation-table look like in the top-object-file?

    it's not my code but now I'm very interested in how it works.

    By the way: this is a "good" example of how too small code-documentation makes code hard to understand.
    I appreciate each uploaded object but this is not what I would call a gold-standard-object. Not even silver-standard-object.
    It's just an object. Not more not less.

    best regards

    Stefan
  • Clive WakehamClive Wakeham Posts: 152
    edited 2011-06-11 17:55
    StefanL38 wrote: »


    But I guess I'm overlooking something or haven't understand an important detail of the code.

    So please take the tomatoes from my eyes: how the heck does this code work?

    And how does a translation-table look like in the top-object-file?

    it's not my code but now I'm very interested in how it works.

    By the way: this is a "good" example of how too small code-documentation makes code hard to understand.
    I appreciate each uploaded object but this is not what I would call a gold-standard-object. Not even silver-standard-object.
    It's just an object. Not more not less.

    best regards

    Stefan

    It is more likely a data sequence that whatever the position in the data file will return the value in that location.

    When I was developing my 74C922/74C923 Keypad object I had to use an lookupz

    lookupz(bufkey2: 1, 2, 3, 10, 4, 5, 6, 11, 7, 8, 9, 12, 15, 0, 14, 13, 16, 17, 18, 19) since pressing a certain key would give an value of 0, and as the Don M had it would fly past as 0's. I then had to use another method to make sure the it correctly debounced the key press.

    And I sometimes over-document my code, so I can read my coding later because I can read some of my code and don't understand how I got it working and the logic behind it at a later date.
  • Clive WakehamClive Wakeham Posts: 152
    edited 2011-06-11 18:30
    Don M wrote: »
    Stephan- Thanks for the help to get me started. Now I see some results it returns 0 - 15. The problem I now have is that without the "if" statment in my code below it continually displays 0's so I can never see the result as it fly's by continually so fast. kp.getkey always returns a result of "0" without pressing any keys. With my "if" statement it will then only display whenever there is a result returned greater then 0 but then it excludes 1 key which happens to be Column 1 Row 4.

    Out of curiousity I tried changing kp.start(4, 4, 0, 4, 0) to kp.start(4, 4, 0, 4, 1) and with that change it will give me a different result for each key but instead of being 0 - 15 they are now 64 - 79 so I don't understand the keycode translation table mentioned in the code.

    The keycode transaction table needs to be set up by you if you want one. By changing the kp.start(4, 4, 0, 4, 0) to kp.start(4, 4, 0, 4, 1) the method is accessing data at location 1 rather than not accessing any data when you have written an 0 in the method.
  • kuronekokuroneko Posts: 3,623
    edited 2011-06-11 18:57
    StefanL38 wrote: »
      akey := @keycodes.byte[akey] 
    
    uses the "@"-operator which means use adress of variable "keycodes"
    Now if I understand right keycodes already contains an adress to a variable and this does not make any sense for me.
    Seconded. That's not going anywhere near the user's translation table except by accident.
    [COLOR="orange"]value.byte[n][/COLOR] is equivalent to [COLOR="orange"]byte[@value][n][/COLOR]
    
    [COLOR="orange"]@value.byte[n][/COLOR] is equivalent to [COLOR="orange"]@value + n[/COLOR], the expression [COLOR="orange"]@value.byte[n][/COLOR] is limited to 16bit
    
    What you want instead is simply
    akey := byte[keycodes][akey]
    
    and to avoid the null-response issue I suggest passing the address of a simple byte array of (in your case) length 16 which doesn't contain 0 (ASCII seems best). And while you are updating the driver, you might as well change the default response of getkey to -1.

    An example DAT section translation table could look like this:
    DAT
    table   byte    "0", "1", "2", "3", "4", "5", "6", "7"
            byte    "8", "9", "A", "B", "C", "D", "E", "F"
    
    and would be communicated to the driver as @table.
  • Don MDon M Posts: 1,653
    edited 2011-06-12 07:17
    kuroneko wrote: »
    and would be communicated to the driver as @table.

    So where do I place this table? In the Main object or the Keypad object?
  • StefanL38StefanL38 Posts: 2,292
    edited 2011-06-12 10:59
    Hi Don,

    it is placed in the main-object.
    Then the fourth parameter of the keyboard-startmethod is "@table" which means tell the start-method the RAM-adress of the table that the keypad-object can access the table through the given pointer

    Pointers are one possability to access variables across objects.

    best regards

    Stefan
  • Don MDon M Posts: 1,653
    edited 2011-06-12 12:04
    Stefan- Thanks! That works. Now a little issue- I know it has to do with the proper type of setting (dec, str, hex)- it's displaying the decimal value of the ascii character. How do I get it to show the actuall ascii character as in the table? Do I put the decimal or hex value of the ascii character I want in the table?
    obj
    
      kp   : "Keypad_encoder"                                       ' keypad driver object P0 - P7
      term : "FullDuplexSerial"                                     ' for serial input & output monitoring
      vfd  : "parallel_lcd_don"
      
    
    var
    
    
    pub main | Pressed_Key
    
       term.start(31, 30, %0000, 115_200)                            ' start terminal (use PST)
       vfd.start
       kp.start(4, 4, 0, 4, @table)                                       ' start keypad driver
       
       vfd.out($00)                                                  ' clear vfd screen 
       term.str(string("Keypad demo testing... press a key", 13))
       vfd.str(string("Key pressed: "))
    
       repeat
    
         Pressed_Key := kp.getkey 
         if Pressed_Key > 0
           term.dec(Pressed_Key)
           vfd.dec(Pressed_Key)
           term.str(string(13))
       
       
    dat
    
    table   byte  "1", "2", "3", "S", "4", "5", "6", "R"
            byte  "7", "8", "9", "E", "N", "0", "P", "X"
    
  • StefanL38StefanL38 Posts: 2,292
    edited 2011-06-12 12:50
    Hi Don,

    if you want to proceed faster as with asking and waiting for answers in the forum.

    look inside the FullDuplexSerial-object what methods this object provides. Think a couple minutes about how a method might work.
    If you are familiar with programming in general I estimate in 70% of the cases you will understand what the code does.
    If you don't understand it you have two options

    1.) recode the method in a testprogram and add debug-output to whatever you are interested in

    2.) come back to the forum with a specific question

    the codeline
    DAT
      byte "A","B"
    

    makes the compiler store the ASCII-codes of character "A" and "B" into HUB-RAM.
    The ASCII-codes of "A" is decimal 65 and "B" decimal 66

    If you want to display a "A" in the terminalprogram you use
      term.tx(65)
    

    You could have find out that yourself by looking into the FullDuplexSerial-object what methods it does provide and how they work

    best regards

    Stefan
  • Don MDon M Posts: 1,653
    edited 2011-06-12 16:05
    To all- thanks for your help. I have it figured out and working. I'm marking this thread as solved. I really like this keypad object. It reads and responds to the key inputs very well.

    Thanks again.

    BTW- I don't mind posting and waiting as it helps me learn and hopefully others as well. During the downtime in between I do try other bits of code to see what the results are.
  • kuronekokuroneko Posts: 3,623
    edited 2011-06-12 18:20
    Don M wrote: »
    To all- thanks for your help. I have it figured out and working.
    Out of curiosity, what changes did you apply to the driver object? Both, lookup and default response or just lookup? Might be a good idea to inform Peter (author).
  • Don MDon M Posts: 1,653
    edited 2011-06-12 19:04
    Attached please find the Keypad_encoder driver object along with Keypad_encoder_demo. The only change I made to the original Keypad_encoder object was to change
    akey := @keycodes.byte[akey]
    

    to
    akey := byte[keycodes][akey]
    

    as you mentioned earlier in the thread. With your and others help here I made the simple demo program to test the driver.

    Here's the demo program object:
    ' Keypad layout
    '
    ' 1  2  3  Start                    ' sample keypad used. top left is R1C1
    ' 4  5  6  Prog
    ' 7  8  9  Set
    ' -  0  +  Stop                     ' bottom right is R4C4
    '
    
    con
    
      _clkmode = xtal1 + pll16x         ' enable external clock and pll times 16
      _xinfreq = 5_000_000              ' set frequency to 5 MHZ
    
    
    obj
    
      kp   : "Keypad_encoder"           ' keypad driver object P0 - P7
      term : "FullDuplexSerial"         ' for serial input & output monitoring
    
    pub main | Pressed_Key
    
       term.start(31, 30, %0000, 115_200)      ' start terminal (use PST)
       kp.start(4, 4, 0, 4, @table)            ' start keypad driver
    
       term.str(string("Keypad demo testing... press a key", 13))
    
       repeat
    
         Pressed_Key := kp.getkey 
         if Pressed_Key > 0
           term.tx(Pressed_Key)
           term.str(string(13))
       
       
    dat
    
    table   byte  "1", "2", "3", "S"
            byte  "4", "5", "6", "R"   ' translation table.
            byte  "7", "8", "9", "E"
            byte  "N", "0", "P", "X" 
    
  • visorguyvisorguy Posts: 7
    edited 2011-06-13 04:09
    Hi don will you still sell me your handspring please send me an email or contact me its John Garra from Genova ok!
Sign In or Register to comment.