4x4 matrix keypad code help
I need some help understanding some code posted originally by Peter Jakacki. I have a 4x4 keypad with the following layout:

and here is his driver code:
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:
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:
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
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
Comments
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
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))
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
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.
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 - isthe 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 variablenow this value is copied to variable "keycodes"
keycodes := keycodeptr ' remember pointer to user keycodes
and later on this codelineakey := @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
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.
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.
[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 simplyakey := 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.So where do I place this table? In the Main object or the Keypad object?
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
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"
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
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.
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"