Menu
JAQForum Ver 19.10.27

Forum Index : Microcontroller and PC projects : RP2040 program for Keypad with more than 4x4 keys.

   Page 1 of 2    
Posted: 06:11am
31 Dec 2025
Copy link to clipboard
phil99
Guru


The RP2350 MMBasic can utilize large keypads. This provides that for a RP2040.
There is bound to be much room for improvement in how it goes about it so any suggestions are welcome. However it seems to get the job done as it is.

The initial version was much simpler but the PORT Command and Function GP numbers had to be set one at a time throughout the program. This sets them all at the start.
' KeyPad2040.bas - Program for more than 4x4 Key Pad
'rows = number of rows
'cols =  number of columns
'RowPinGP = first of the row GP pin numbers
'ColPinGP = first of the column GP pin numbers
'Key = number of the key that has been pressed. eg. 0 to 19 for 4 columns x 5 rows. 1st row 0 to 3, last 16 to 19.

Option base 0
Dim integer n, Key=-1, RowPinGP=0, rows=5, ColPinGP=5, cols=4

For n=RowPinGP To RowPinGP+rows-1
 SetPin MM.Info(pinno "GP"+Str$(n)),DOUT 'set the Port out pins
Next

For n=ColPinGP To ColPinGP+Cols-1
 SetPin MM.Info(pinno "GP"+Str$(n)), INTH, Key.Pad, PULLDOWN 'set the Port input pins and ISR
Next

Execute "Port(GP"+Str$(RowPinGP)+",rows) = 2^rows-1" 'set all row pins high

Do
 ' main processing loop
  If Key+1 Then Print Key, Hex$(Key) : key=-1 'to retain the value of Key set a flag in the sub and reset it here
Loop

Sub Key.Pad
  Local integer y, x

  For y = 0 To rows-1 'read the rows
    Execute "Port(GP"+Str$(RowPinGP)+",rows) = 1 << y"
    Pause 9 'contact bounce delay
    Execute "x =  Cint(Log(Port(GP"+Str$(ColPinGP)+",cols)+.001) / Log(2))" 'read the columns, +.001 prevents divide by zero error
    Key = y*4+x 'get key no. from row no and col. no.
    Execute "If Port(GP"+Str$(ColPinGP)+",cols) Then Exit For"
  Next

  Execute "Port(GP"+Str$(RowPinGP)+",rows) = 0" 'block further input to prevent double press
'   Print y, x,, Key, Hex$(Key)
  Pause 200
  Execute "Port(GP"+Str$(RowPinGP)+",rows) = 2^rows-1" 'prepare for next key input
  'Set a flag here if main prog. needs it
End Sub

End


Footnote added 2025-12-31 17:12 by phil99
First error.
In the Sub For-Next 2nd last line replace:-
   Key = y*4+x 'get key no. from row no and col. no.
With:-
   Key = y * cols + x 'get key no. from row no and col. no.
 
Posted: 06:20am
31 Dec 2025
Copy link to clipboard
Geoffg
Guru


Excellent. This should be posted to https://fruitoftheshed.com

Geoff
 
Posted: 06:38am
31 Dec 2025
Copy link to clipboard
phil99
Guru


Thanks Geoff, if no problems or improvements are found I will post it on FoTS.
 
Posted: 07:12am
31 Dec 2025
Copy link to clipboard
phil99
Guru


First error.
In the Sub For-Next 2nd last line replace:-
   Key = y*4+x 'get key no. from row no and col. no.
With:-
   Key = y * cols + x 'get key no. from row no and col. no.
 
Posted: 08:37am
31 Dec 2025
Copy link to clipboard
JohnS
Guru

Does it need to use Execute?

John
 
Posted: 09:42am
31 Dec 2025
Copy link to clipboard
phil99
Guru


  Quote  Does it need to use Execute?
The initial version didn't use Execute but the GP numbers had to be set individually throughout the program as the Port command and function won't accept a string for the GP number.

They do accept Pico pin numbers but then you have to account for the ground pin numbers breaking the sequence. That appeared even more complicated.
 
Posted: 09:44am
31 Dec 2025
Copy link to clipboard
Volhout
Guru

@john,

Unless you hard code the GP Numbers, the port command needs it.

@phill,

So LOG(0) causes a dividend by zero error. Didn’t know that. Thanks for educating. Happy new year, must be now in your location..,


Volhout
Edited 2025-12-31 19:49 by Volhout
 
Posted: 09:54am
31 Dec 2025
Copy link to clipboard
Frank N. Furter
Guru

It would be fantastic if this command could be used as an OPTION. Then you could create a portable device with an LCD and keyboard using ONLY ONE CHIP.

Frank
 
Posted: 09:54am
31 Dec 2025
Copy link to clipboard
phil99
Guru


  Quote  Dividend by zero may not happen
I didn't think it would happen at all, but occasionally that line gave a 'Divide by zero' error. I don't know why, perhaps contact bounce.
Execute Isn't causing it as it was happening in the first version without Execute.

My workaround is an ugly kludge but the error hasn't happened since adding it.
 
Posted: 10:02am
31 Dec 2025
Copy link to clipboard
phil99
Guru


  Quote  Then you could create a portable device with an LCD and keyboard using ONLY ONE CHIP.

Peter did that for the RP2350.

Footnote added 2026-01-01 14:30 by phil99
It is the Palm Pico in this thread.
 
Posted: 10:18am
31 Dec 2025
Copy link to clipboard
phil99
Guru


Just for fun, how close to log(0) can you get?
This was one step too close!
> ? log(0.00000000000000000000000000001)
Error : Expression syntax
Error : Not enough Heap memory
Error : Not enough Heap memory
Error : Not enough Heap memory
Error : Not enough Heap memory
Error : Not enough Heap memory
Error : Not enough Heap memory
Error : Not enough Heap memory
Error : Not enough Heap memory
Error : Not enough Heap memory
Error : Not enough Heap memory
Error : Not enough Heap memory
Error : Not enough Heap memory
Error : Not enough Heap memory
Error : Not enough Heap memory
Error : Not enough Heap memory

*** PANIC ***

            Out of memory
 
Posted: 10:18am
31 Dec 2025
Copy link to clipboard
JohnS
Guru

Instead of
Execute "Port(GP"+Str$(RowPinGP)+",rows) = 2^rows-1"

I was hoping (well, expecting) such as this would work
Port("GP"+Str$(RowPinGP), rows) = 2^rows-1

Does it fail?

John
 
Posted: 10:39am
31 Dec 2025
Copy link to clipboard
Volhout
Guru

There is an mm.info that translates a pin number to a GPxx. Maybe that can be used for the starting point in the PORT command. Not at home atm, so cannot test.

Volhout
 
Posted: 10:40am
31 Dec 2025
Copy link to clipboard
phil99
Guru


> RowPinGP = 0
rows = 5
> Port("GP"+Str$(RowPinGP), rows) = 2^rows-1
Error : Expected a number
>

  Quote  There is an mm.info that translates a pin number to a GPxx
Yes, used that for the SETPIN commands but for PORT the numbers must be contiguous. You have to start again after each ground pin.
Eg for 8 pins starting with 1 :-
PORT(1,2, 4,4, 9,2) as pins 3 and 8 are ground pins.
Edited 2025-12-31 20:51 by phil99
 
Posted: 10:44am
31 Dec 2025
Copy link to clipboard
JohnS
Guru

Odd.

The PORT command uses getargs which is a macro for makeargs and that reads as if it would work.

@matherp - is it a bug / can it be made to work (without too much hassle)?

edit: Oh, it's not expecting a string; it wants such as GP0 rather than any variant of "GP0"

Does feel worth fixing... those Executes are not lovely.

John
Edited 2025-12-31 20:49 by JohnS
 
Posted: 10:56am
31 Dec 2025
Copy link to clipboard
matherp
Guru

If you want nxm keypads you can use the RP2350 where they are fully supported. Do you want me to include this functionality on the RP2040? Worst case it increases flash usage by 16Kbytes and reduces A: drive by the same amount. Best case no change in flash usage as this always goes up in 16K chunks
 
Posted: 11:11am
31 Dec 2025
Copy link to clipboard
phil99
Guru


  Quote  you can use the RP2350 where they are fully supported.
Yes, and the extra pins of the RP2350B makes it better still.
The RP2040 can already do 4x4 and most of the time that is enough.
 
Posted: 03:10am
01 Jan 2026
Copy link to clipboard
phil99
Guru


An updated version.
This handles dodgy button contacts a little better by resetting for another attempt.
Replaced the the Log() function with a x=2^n loop as it tolerates bad data without exiting with an error.
Plus some general tidying up.
' KeyPad2040v2.bas - Program for more than 4x4 Key Pad
'rows = number of rows
'cols =  number of columns
'RowPinGP = first of the row GP pin numbers
'ColPinGP = first of the column GP pin numbers
'Key = number of the key that has been pressed. eg. 0 to 29 for 6 columns x 5 rows. 1st row 0 to 4, last 25 to 29.
' X x Y Keypad
Option base 0
Dim integer n, Key=-1, RowPinGP=0, rows=5, ColPinGP=5, cols=6

For n=RowPinGP To RowPinGP+rows-1
 SetPin MM.Info(pinno "GP"+Str$(n)),DOUT 'set the Port out pins
Next

For n=ColPinGP To ColPinGP+Cols-1
 SetPin MM.Info(pinno "GP"+Str$(n)), INTH, Key.Pad, PULLDOWN 'set the Port input pins and ISR
Next

Execute "Port(GP"+Str$(RowPinGP)+",rows) = 2^rows-1" 'set all row pins high

Do
 ' Your main processing loop
  If Key+1 Then Print Key, Hex$(Key,2) : key=-1 'to retain the value of Key set a flag in the sub and reset it here
Loop

Sub Key.Pad
  Local integer y, x, n
  Pause 5 'contact bounce delay

  For y = 0 To rows 'scan the rows
    Execute "Port(GP"+Str$(RowPinGP)+",rows) = 1 << y" 'set the row to read
    Execute "x=Port(GP"+Str$(ColPinGP)+",cols)" 'read the columns
    For n = 0 To cols-1 'get the column number from the Port value
      If x=2^n Then : x=n+1 : Exit For : EndIf
    Next
    If x>cols Or y>rows-1 Then 'check for values out of range and abort this read
      x=0 :y=0 : Key=-1
      Execute "Port(GP"+Str$(RowPinGP)+",rows) = 2^rows-1" 'prepare for next key input
      Exit Sub
    EndIf
    Key = y * cols + x - 1 'get key no. from row no. and col. no., starting from 0
    If x Then Exit For
  Next

  Execute "Port(GP"+Str$(RowPinGP)+",rows) = 0" 'block further input to prevent double press
'   Print y, x,, Key
  Pause 150
  Execute "Port(GP"+Str$(RowPinGP)+",rows) = 2^rows-1" 'prepare for next key input
  'Set a flag here if main prog. needs it
End Sub

End

Tested up to 8x8 with all keys tested.
Edited 2026-01-01 14:25 by phil99
 
Posted: 08:44am
01 Jan 2026
Copy link to clipboard
Mixtel90
Guru


Or use a Zero to scan a *big* keypad and feed it back over I2C plus an interrupt line or a COM port. I like to make things complicated. :)  The point is, very few pins are needed and I2C can be shared with a RTC. The interrupt says "I have a character, read me" as I2C isn't buffered.
 
Posted: 07:34am
02 Jan 2026
Copy link to clipboard
phil99
Guru


Yet another version.
The previous ones hold the row output pins high until a key is pressed. If there is a short to ground the chip may be damaged.
This holds the output port low while idle and uses inverted logic to read the keys.
' KeyPad2040v3.bas - Program for more than 4x4 Key Pad
' With inverted logic to minimize risk of damage if an
' output is shorted to ground - the Port is normally at 0V.
'
' rows = number of rows
' cols =  number of columns
' RowPinGP = first of the row GP pin numbers
' ColPinGP = first of the column GP pin numbers
' Key = number of the key that has been pressed.
' eg. 0 to 29 for 6 columns x 5 rows. 1st row 0 to 4, last 25 to 29.
' X x Y Keypad with buttons bridging column x to row y

Option base 0
Dim integer n, Key=-1, RowPinGP=0, rows=5, ColPinGP=5, cols=6
' Dim KeyMap$(rows * cols - 1)=("Q","W","E","R","T","Y", etc) 'map keys to characters if req.

For n=RowPinGP To RowPinGP+rows-1
  SetPin MM.Info(pinno "GP"+Str$(n)),DOUT 'set the output Port pins
Next

For n=ColPinGP To ColPinGP+Cols-1
  SetPin MM.Info(pinno "GP"+Str$(n)), INTL, Key.Pad, PULLUP 'set the input Port pins and ISR
Next

Do
  ' Your main processing loop
  If Key+1 Then Print Key, Hex$(Key,2) : key=-1 'to retain the value of Key set a flag in the sub and reset it here
Loop

Sub Key.Pad
  Local integer y, x, n
  Pause 5 'contact bounce delay

  For y = 0 To rows 'scan the rows
    Execute "Port(GP"+Str$(RowPinGP)+",rows) = INV (1 << y)" 'set the row to read
    Execute "x=2^cols-1-Port(GP"+Str$(ColPinGP)+",cols)" 'read the columns
    For n = 0 To cols-1 'get the column number from the Port value
      If x=2^n Then : x=n+1 : Exit For : EndIf
    Next
    If x>cols Or y>rows-1 Then 'check for values out of range and abort this read
      x=0 :y=0 : Key=-1
      Execute "Port(GP"+Str$(RowPinGP)+",rows) = 0" 'prepare for next key input
      Exit Sub
    EndIf
    Key = y * cols + x - 1 'get key no. from row no. and col. no., starting from 0
    If x Then Exit For
  Next

'   Execute "Port(GP"+Str$(RowPinGP)+",rows) = 2^rows-1" 'block further input to prevent double press
'   Print y, x,, Key
  Pause 100 'set to minimum value that is reliable, or remove it and/or the Execute line above
  Execute "Port(GP"+Str$(RowPinGP)+",rows) = 0" 'prepare for next key input
  'Set a flag here if main prog. needs it
End Sub

End


One way to use KeyMap$() to enter text is assign a Key to be <Enter> and append characters to a string until that character (eg Chr$(10), Line Feed) is entered.
In this partial example Shift Lock is the second last Key and Enter is the last.
It uses 4 rows x 5 columns or 2 rows x 10 columns so adjust accordingly.
Add the setup lines to the program above and replace its Main Loop with the one below.
Eg.
Dim KeyMap$(rows * cols - 1)=("q","w","e","r","t","y","u","i","o","p","a","s","d","f","g","h","j","k",Chr$(8),Chr$(10))

Dim S$ 'temp string to hold text till you process it
Dim ShiftLock% 'Shift lock flag
SetPin GP22, DOUT 'Shift lock LED
...

Do  ' Your main processing loop
  If Key+1 Then
    If Key = Rows*Cols-2 Then
      ShiftLock% = Not ShiftLock% 'toggle ShiftLock, next to last Key is the <ShiftLock> key
      Pin(GP22) = ShiftLock%  'set ShiftLock LED
     Else
      If ShiftLock% Then
        Inc S$, UCase$(KeyMap$(Key)) 'append upper case characters to S$
        Print UCase$(KeyMap$(Key));
       Else
        Inc S$, KeyMap$(Key) 'append lower case characters to S$
        Print KeyMap$(Key);
      EndIf
    EndIf
    key=-1
  EndIf

  If Right$(S$,1) = Chr$(10) Then
    'do stuff with S$
    Print " = S$ = ";S$
    S$ = "" 'then reset S$
  EndIf
Loop


Edit.
Another slight refinement.
' KeyPad2040v4.bas - Program for more than 4x4 Key Pad
'
' rows = number of rows
' cols =  number of columns
' RowPinGP = first of the row GP pin numbers
' ColPinGP = first of the column GP pin numbers
' Key = number of the key that has been pressed.
' eg. 0 to 29 for 6 columns x 5 rows. 1st row 0 to 4, last 25 to 29.
' X x Y Keypad with buttons bridging column x to row y

Option base 0
Dim integer n, Key=-1, RowPinGP=0, rows=5, ColPinGP=5, cols=6
' Dim KeyMap$(rows * cols - 1)=("Q","W","E","R","T","Y", etc) 'map keys to characters with KeyMap$(Key) if req.

For n=RowPinGP To RowPinGP+rows-1
  SetPin MM.Info(pinno "GP"+Str$(n)),DOUT 'set the output Port pins
Next

For n=ColPinGP To ColPinGP+Cols-1
  SetPin MM.Info(pinno "GP"+Str$(n)), INTL, Key.Pad, PULLUP 'set the input Port pins and ISR
Next

Do
 ' Your main processing loop
 If Key+1 Then Print "Key";Key, "&H";Hex$(Key,2) : key=-1
 'to retain the value of Key set a flag in the sub and reset it here
Loop

Sub Key.Pad
  Local integer y, x, x1, n
  Pause 10 'contact bounce delay, set to minimum value that is reliable

  For y = 0 To rows 'scan the rows
    Execute "Port(GP"+Str$(RowPinGP)+",rows) = INV (1 << y)" 'set the row to read
    Execute "x1 = (2^cols-1) XOR Port(GP"+Str$(ColPinGP)+",cols)" 'read the columns
    If x1 Then x=Log(x1)/Log(2)+1 'get the column number from the Port value

    If x>cols Or y>rows-1 Then 'check for values out of range and abort this read
      x=0 :y=0 : Key=-1
      Execute "Port(GP"+Str$(RowPinGP)+",rows) = 0" 'prepare for next key input
      Exit Sub
    EndIf

    Key = y * cols + x - 1 'get key no. from row no. and col. no., starting from 0
    If x1 Then Exit For
  Next

'   Execute "Port(GP"+Str$(RowPinGP)+",rows) = 2^rows-1" 'block further input to prevent double press
'   Print y, x,, Key
  Pause 150 'set to minimum value that is reliable, or remove it and/or the Execute line above
  Execute "Port(GP"+Str$(RowPinGP)+",rows) = 0" 'prepare for next key input
  'Set a flag here if main prog. needs it
End Sub

End

Edited 2026-01-06 21:30 by phil99
 
   Page 1 of 2    
The Back Shed's forum code is written, and hosted, in Australia.
© JAQ Software 2026