Home
JAQForum Ver 24.01
Log In or Join  
Active Topics
Local Time 11:28 19 Feb 2026 Privacy Policy
Jump to

Notice. New forum software under development. It's going to miss a few functions and look a bit ugly for a while, but I'm working on it full time now as the old forum was too unstable. Couple days, all good. If you notice any issues, please contact me.

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

     Page 1 of 2    
Author Message
phil99

Guru

Joined: 11/02/2018
Location: Australia
Posts: 3016
Posted: 06:11am 31 Dec 2025
Copy link to clipboard 
Print this post

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.
 
Geoffg

Guru

Joined: 06/06/2011
Location: Australia
Posts: 3340
Posted: 06:20am 31 Dec 2025
Copy link to clipboard 
Print this post

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

Geoff
Geoff Graham - http://geoffg.net
 
phil99

Guru

Joined: 11/02/2018
Location: Australia
Posts: 3016
Posted: 06:38am 31 Dec 2025
Copy link to clipboard 
Print this post

Thanks Geoff, if no problems or improvements are found I will post it on FoTS.
 
phil99

Guru

Joined: 11/02/2018
Location: Australia
Posts: 3016
Posted: 07:12am 31 Dec 2025
Copy link to clipboard 
Print this post

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.
 
JohnS
Guru

Joined: 18/11/2011
Location: United Kingdom
Posts: 4244
Posted: 08:37am 31 Dec 2025
Copy link to clipboard 
Print this post

Does it need to use Execute?

John
 
phil99

Guru

Joined: 11/02/2018
Location: Australia
Posts: 3016
Posted: 09:42am 31 Dec 2025
Copy link to clipboard 
Print this post

  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.
 
Volhout
Guru

Joined: 05/03/2018
Location: Netherlands
Posts: 5703
Posted: 09:44am 31 Dec 2025
Copy link to clipboard 
Print this post

@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
PicomiteVGA PETSCII ROBOTS
 
Frank N. Furter
Guru

Joined: 28/05/2012
Location: Germany
Posts: 1046
Posted: 09:54am 31 Dec 2025
Copy link to clipboard 
Print this post

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
 
phil99

Guru

Joined: 11/02/2018
Location: Australia
Posts: 3016
Posted: 09:54am 31 Dec 2025
Copy link to clipboard 
Print this post

  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.
 
phil99

Guru

Joined: 11/02/2018
Location: Australia
Posts: 3016
Posted: 10:02am 31 Dec 2025
Copy link to clipboard 
Print this post

  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.
 
phil99

Guru

Joined: 11/02/2018
Location: Australia
Posts: 3016
Posted: 10:18am 31 Dec 2025
Copy link to clipboard 
Print this post

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
 
JohnS
Guru

Joined: 18/11/2011
Location: United Kingdom
Posts: 4244
Posted: 10:18am 31 Dec 2025
Copy link to clipboard 
Print this post

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
 
Volhout
Guru

Joined: 05/03/2018
Location: Netherlands
Posts: 5703
Posted: 10:39am 31 Dec 2025
Copy link to clipboard 
Print this post

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
PicomiteVGA PETSCII ROBOTS
 
phil99

Guru

Joined: 11/02/2018
Location: Australia
Posts: 3016
Posted: 10:40am 31 Dec 2025
Copy link to clipboard 
Print this post

> 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
 
JohnS
Guru

Joined: 18/11/2011
Location: United Kingdom
Posts: 4244
Posted: 10:44am 31 Dec 2025
Copy link to clipboard 
Print this post

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
 
matherp
Guru

Joined: 11/12/2012
Location: United Kingdom
Posts: 10965
Posted: 10:56am 31 Dec 2025
Copy link to clipboard 
Print this post

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
 
phil99

Guru

Joined: 11/02/2018
Location: Australia
Posts: 3016
Posted: 11:11am 31 Dec 2025
Copy link to clipboard 
Print this post

  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.
 
phil99

Guru

Joined: 11/02/2018
Location: Australia
Posts: 3016
Posted: 03:10am 01 Jan 2026
Copy link to clipboard 
Print this post

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
 
Mixtel90

Guru

Joined: 05/10/2019
Location: United Kingdom
Posts: 8568
Posted: 08:44am 01 Jan 2026
Copy link to clipboard 
Print this post

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.
Mick

Zilog Inside! nascom.info for Nascom & Gemini
Preliminary MMBasic docs & my PCB designs
 
phil99

Guru

Joined: 11/02/2018
Location: Australia
Posts: 3016
Posted: 07:34am 02 Jan 2026
Copy link to clipboard 
Print this post

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    
Print this page
The Back Shed's forum code is written, and hosted, in Australia.
© JAQ Software 2026