Home
JAQForum Ver 24.01
Log In or Join  
Active Topics
Local Time 12:37 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 2 of 2    
Author Message
phil99

Guru

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

There appears to be some interest in a MMBasic driven custom keyboard so here is an expanded version of the Keypad program.
It uses one or more 74HC595 serial-to-parallel chips to drive the rows, freeing up Pico pins for a bigger input Port for more columns.
12 columns x 8 rows would give you 96 keys.
Still not enough? Add another 74HC595 and go for 16 x 16 = 256 keys!

The next firmware release looks like it will have a two pin version of Device Bitstream (perhaps only on the RP2350?). If so the HC595 Sub can be simplified.
' KeyPad_RP2040_74HC595_v03.bas - Program for more than 4x4 Key Pad
' Rows scanned by a 74HC595 serial-to-parallel chip
' rows = number of rows. For more than 8 rows daisychain another 74HC595 chip.
' cols =  number of columns
' RowPinGP = row GP pin number for the 74HC595
' ColPinGP = first of the column GP pin numbers
' Key = number of the key that has been pressed.
' eg. 0 to 63 for 8 columns x 8 rows. 1st row 0 to 7, last row 55 to 63.
' X x Y Keypad with buttons bridging column x to row y

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

Dim integer RowPinNo = MM.Info(pinno "GP"+Str$(RowPinGP))

SetPin RowPinNo, DOUT 'set the output pin to the 74HC595 and set its outputs to 0
HC595 RowPinNo, Rows+7, 0 'Call Sub with DataPin, max. number of Rows, Word to send, 0=all low
'Rows+7 to ensure any unused pins are also set to 0

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), Timer-t;"mS" : key=-1
'to retain the value of Key set a flag in the sub and reset it here
Loop

Sub Key.Pad
 t=Timer
 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
   HC595 RowPinNo, Rows, 1<<y Xor (1<<Rows+1)-1 'Call Sub with DataPin, no. of Rows, Row to read, inverted
   Execute "x1 = (1<<cols)-1 XOR Port(GP"+Str$(ColPinGP)+",cols)" 'read the columns

   If x1 Then x=Log(x1)/Log(2)+1

   If x>cols Or y>rows Then 'check for values out of range and abort this read
     x=0 :y=0 : Key=-1
     HC595 RowPinNo, Rows+7, 0 'prepare for next key input, 0=all low
     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

 HC595 RowPinNo, Rows+1, (1 << Rows+1)-1 'all high, block further input to prevent double press
 Pause 150 'set to minimum value that is reliable, or remove it and/or the HC595 Sub call above
 HC595 RowPinNo, Rows+7, 0 'prepare for next key input, 0=all low
 'Set a flag here if main prog. needs it
End Sub

Sub HC595 OutPin As integer, R As integer, Dat As integer 'Data output Pin, number of Rows, Word to output
 Pin(OutPin)=1 'ensure correct polarity for Bitstream
 Local integer n, el=R*2-1, B(el)
 For n=0 To el Step 2 'prepare Bitstream array
   B(n)=3-(Dat>>(el-n)\2 And 1)*2 'low data pulses, 3uS=0 ,1uS=1
   B(n+1)=1 '1uS high spaces
 Next
 Device BITSTREAM OutPin, R*2, B() 'send it
 Pin(OutPin)=1 : Pause 0.04 ' >20uS high = latch clock
End Sub
End
'  74HC595 connections
' 3V3 to pins 16, 10
' Gnd to pins 8, 13
' BitStream input to pin 11 (clock)
' pin 11 - 1k8 - pin 14 (data in) - 1n5 - Gnd
' pin 11 - A-diode-K - pin 14 (ser. data in) eg 1N914 or 1N4148
' pin 11 - 4k7 - pin 12 (latch) - 1n5 - Gnd
' pin 11 - K-diode-A - pin 14 (latch)
' pin 9 (ser. data out) to next 74HC595 (if used) pin 14 (ser. data in),
' parallel pin 11 to pin 11 and pin 12 to pin 12
' Row output pins:- 15, 1, 2, 3, 4, 5, 6, 7



Edited 2026-01-17 15:46 by phil99
 
Mixtel90

Guru

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

pack of 3 74HC595 is £3.90 + £3.50 delivery from The Pi Hut

1x RP2040-Zero is £1.30 from AliExpress (bundle deal for 3x is £3.90 with free shipping).

They now have a RP2350-Zero for £2.90. :)  That would make a nice I2C+IRQ X-Y keyboard scanner. 72 keys on 3 GPIO pins.  :)
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: 09:14am 17 Jan 2026
Copy link to clipboard 
Print this post

  Quote  pack of 3 74HC595 is £3.90 + £3.50 delivery from The Pi Hut
Some years ago I wanted 4 to drive a display. Too expensive in the local shops but just a couple of $ for a tube of 25 from AE, postage included.
Now have to find excuses to use them. ;-)
 
phil99

Guru

Joined: 11/02/2018
Location: Australia
Posts: 3016
Posted: 02:50am 18 Feb 2026
Copy link to clipboard 
Print this post

Using the MMBasic Keypad command requires 8 pins for 4 x 4 keys.
A 74HC595 and a diode/resistor matrix can read the keypad using just 2 MCU pins.
A DOUT scans the keys via Bitstream and a Interrupt Sub reads the pressed key.
As with the previous version the rows are driven by idle low outputs.
Inverted logic is used to read the columns via diode-resistor logic. Switching the columns is done by more idle low outputs.
For more than 16 keys more 74HC595 chips can be daisy-chained. 2 give up to 64 keys and 3 up to 144.

Testing was done on a PicoMite MMBasic RP2040 V6.02.00 @ 200MHz and 378MHz.
Time taken to read a key (@ 378MHz) and return to the main loop is about 5mS for Key 0, varying up to 10mS for Key 15.
It varies because it stops scanning when it finds the key.

' KeyPad_RP2040_74HC595_2_Pin_v01.bas - Program 4x4 Key Pad
'
' Keypad reader using a 74HC595 and 2 MCU I/O pins.
' For more than 8 rows plus columns daisy-chain another 74HC595 chip or two.
' then adjust the Rows and Cols limits for the For / Next loops.
' 1 chip is up to 16 Keys, 2 is up to 64 (8 x 8), 3 is up to 144 (12 x 12)
' keypad does not need to be square. 2 chips = 16 outputs so could do 12 x 4 (12 + 4 = 16)
'
Dim Integer Rows = 4, Cols = 4, Key = -1, OutPin=MM.Info(pinno GP0), InPin=MM.Info(pinno GP1)
SetPin OutPin,dout : Pin(OutPin)=1 'initialize keypad row output
HC595 OutPin,Rows+Cols,0   'set all pins low
SetPin InPin,intl,KP_int,pullup 'initialize keypad column input

Sub KP_Int
  Pause 2 'key de-bounce time, adjust to suit your keypad
  Local integer R, C

  For R=0 To Rows-1   'Scan the rows (low) one at a time, the rest high
   HC595 OutPin,Rows+Cols,15-(1<<R)
   If 1-Pin(InPin) Then Exit For 'exit when the active row is found
  Next

  For C=0 To Cols-1   'Scan the columns (low) one at a time, the rest high
   HC595 1,Rows+Cols, (15-(1<<C))<<4
   If 1-Pin(InPin) Then Exit For 'exit when the active column is found
  Next

  If Rows-R Then Key = R*Rows+C 'Evaluate the key unless the row count is too high
  'Set a flag here if the main loop needs it.
  HC595 OutPin,Rows+Cols,0 'Prepare for next press, set all pins low
End Sub

Sub HC595 Out.Pin As integer, RC As integer, Dat As integer
  'Data output Pin, number of Rows+Cols, Word to output
  Pin(Out.Pin)=1 'ensure correct polarity for Bitstream
  Local integer n, el=RC*2-1, B(el)
  Array Set 1,B() '1uS low data and 1uS high spaces = all 1s
  For n=0 To el Step 2 'prepare Bitstream array
    B(n)=3-(Dat>>(el-n)\2 And 1)*2 'low data pulses, 0 = 3uS, 1 = 1uS
  Next
  Device BITSTREAM Out.Pin, el+1, B() 'send it
  Pin(Out.Pin)=1 : Pause 0.025 ' >20uS high = latch clock
End Sub

Do
  If Key+1 Then Print "Key";Key : Key = -1
  'If you need to preserve the value of "Key" replace "Key+1" with a flag
  ' that was set in the ISR and reset the flag instead of "Key = -1"
Loop

End
'  74HC595 connections
' 3V3 to pins 16, 10
' Gnd to pins 8, 13
' BitStream input to pin 11 (clock)
' pin 11 - 1k8 - pin 14 (data in) - 1n5 - Gnd
' pin 11 - A-diode-K - pin 14 (ser. data in) eg 1N914 or 1N4148
' pin 11 - 4k7 - pin 12 (latch) - 1n5 - Gnd
' pin 11 - K-diode-A - pin 14 (latch)
' pin 9 (ser. data out) to next 74HC595 (if used) pin 14 (ser. data in),
' parallel pin 11 to pin 11 and pin 12 to pin 12
' 4x4 Row output pins:- 15, 1, 2, 3,
' 4x4 Column output pins:- 4, 5, 6, 7

And the circuit diagram. The Bitstream pin goes to the input and the output goes to the INTL pin.

Keypress response times:-
Key 0    5.003000006 mS
Key 1    4.600000001 mS
Key 2    6.101999998 mS
Key 3    6.671000004 mS
Key 4    4.600999996 mS
Key 5    6.107000001 mS
Key 6    5.691 mS
Key 7    6.275000006 mS
Key 8    5.148000002 mS
Key 9    6.662 mS
Key 10   7.221000001 mS
Key 11   7.800999999 mS
Key 12   6.663000003 mS
Key 13   7.230999999 mS
Key 14   6.831 mS
Key 15   7.395999998 mS

Edited 2026-02-18 16:49 by phil99
 
Volhout
Guru

Joined: 05/03/2018
Location: Netherlands
Posts: 5703
Posted: 09:16am 18 Feb 2026
Copy link to clipboard 
Print this post

Hi Phil,

When you have only 2 pins you can also use a PCF8574, that can be had for few cents on the internet. The PCF8574 is perfect for this in that it has weak pullups, on bi-directional pins. A 4x4 keyboard requires only that chip, nothing else. Not resistors, caps, diodes. But it needs I2C bus. In this test the PCF8574 was connected to system I2C bus on pins GP14 and GP15.

This is possible code. Requires between 0.5ms and 1.5ms on a 200MHz RP2040. Dual key presses are reported as key numbers above 32.

'PCF8574 4x4 keyboard
'connect keyboard rows to pins 9,10,11,12, and columns to pin 4,5,6,7

pcf%=&h20            'I2C address when A0/A1/A2 are GND
pinrow%=&b00001111   'setup rows and columns, lsb in, msb out

'for decoding
Dim pin2num%(15)=(8,8,8,8,8,8,8,3,8,8,8,2,8,1,0,8)
Dim rowpat%(3)=(&b01111111,&b10111111,&b11011111,&b11101111)


'---------------------------- main program -----------------------

'init default for rows and columns
I2C2 write pcf%,0,1,pinrow%


'main loop
Do
 I2C2 read pcf%,0,1,d%                'read keyboard
 If d%<>pinrow% Then                  'check if key pressed
   Print whatkey%()                   'show the key after scanning
 End If

 Pause 100
Loop


'------------------------------- subs -----------------------------

'scan keys
Function whatkey%()
 Local i%,d%
 For i% = 0 To 3                      'scan rows
   I2C2 write pcf%,0,1,rowpat%(i%)    'push pattern
   I2C2 read pcf%,0,1,d%              'read response
   If d%<>rowpat%(i%) Then            'different then key pressed
     whatkey%=i%+4*pin2num%(d% And 15)'decode
     I2C2 write pcf%,0,1,pinrow%      'restore default rows
     Exit Function                    'and quit
   End If
 Next
End Function


Tested on V6.02.01b3 on RP2040

In case you have 3 pins available you can also connect the INTn pin on the PCF8574 and do not need to poll at all.

For 64 keys (8x8) use 2x PCF8574 on different I2C addresses. Scan time will double. Routing/coding is simplest if you have one PCF8574 do columns, the other Rows.

Volhout
Edited 2026-02-18 19:46 by Volhout
PicomiteVGA PETSCII ROBOTS
 
PhenixRising
Guru

Joined: 07/11/2023
Location: United Kingdom
Posts: 1730
Posted: 11:02am 18 Feb 2026
Copy link to clipboard 
Print this post

  Quote  


4 x 4 keypad reader using a single I/O pin, four 7.5K resistors, four 30K resistors, and a 1000pF capacitor
 
 
  Start(pin)       : I/O pin connected to the keypad circuit
  Poll             : Returns the key pressed in ASCII when key is pressed initially
                      Returns false if no key is pressed or if a key is held down
  Check_Keypad     : Returns key even if held down

                                       
   ┌────────────┐                             The keypad that I tested with has an 8-pin header:
   │ 1  2  3  A │─┳──────┐                     1 - row 2    (4,5,6,B)                          
   │            │  30K   │                     2 - row 3    (7,8,9,C)                          
   │ 4  5  6  B │─┫                           3 - column 1 (1,4,7,*)                          
   │            │  30K                         4 - row 4    (*,0,#,D)                          
   │ 7  8  9  C │─┫                            5 - column 2 (2,5,8,0)                          
   │            │  30K                         6 - column 3 (3,6,9,#)                          
   │ *  0  #  D │─┫                            7 - column 4 (A,B,C,D)  
   └────────────┘  30K                         8 - row 1    (1,2,3,A)          
               │                                                                            
     ┣┻┻┻────┘                      
      7.5K                                
     ┣─────────────────── I/O pin        
    1000pF                      


 
Volhout
Guru

Joined: 05/03/2018
Location: Netherlands
Posts: 5703
Posted: 11:22am 18 Feb 2026
Copy link to clipboard 
Print this post

Yeah, I did that one on an Arduino once.

Volhout
PicomiteVGA PETSCII ROBOTS
 
phil99

Guru

Joined: 11/02/2018
Location: Australia
Posts: 3016
Posted: 08:44pm 18 Feb 2026
Copy link to clipboard 
Print this post

The fixed width font of a code box untangles the mess.
That looks interesting so will have a go at reading it with MMBasic.
It looks like you set the pin as an output to charge the capacitor then switch the pin to input and measure how long it takes to discharge.

  ┌────────────┐                             The keypad that I tested with has an 8-pin header:
  │ 1  2  3  A │─┳──────┐                     1 - row 2    (4,5,6,B)                          
  │            │  30K   │                     2 - row 3    (7,8,9,C)                          
  │ 4  5  6  B │─┫      ⏚                     3 - column 1 (1,4,7,*)                          
  │            │  30K                         4 - row 4    (*,0,#,D)                          
  │ 7  8  9  C │─┫                            5 - column 2 (2,5,8,0)                          
  │            │  30K                         6 - column 3 (3,6,9,#)                          
  │ *  0  #  D │─┫                            7 - column 4 (A,B,C,D)  
  └────────────┘  30K                         8 - row 1    (1,2,3,A)          
              │                                                                            
    ┣┻┻┻────┘                      
     7.5K                                
    ┣─────────────────── I/O pin        
   1000pF


As I mentioned to Mick I have a heap of 74HC595s left over from a project years ago so they are effectively free. They must be good for something! :)
Edited 2026-02-19 07:05 by phil99
 
     Page 2 of 2    
Print this page


To reply to this topic, you need to log in.

The Back Shed's forum code is written, and hosted, in Australia.
© JAQ Software 2026