|
Forum Index : Microcontroller and PC projects : RP2040 program for Keypad with more than 4x4 keys.
| Author | Message | ||||
| phil99 Guru Joined: 11/02/2018 Location: AustraliaPosts: 3016 |
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 KingdomPosts: 8568 |
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: AustraliaPosts: 3016 |
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: AustraliaPosts: 3016 |
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: NetherlandsPosts: 5703 |
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 KingdomPosts: 1730 |
|
||||
| Volhout Guru Joined: 05/03/2018 Location: NetherlandsPosts: 5703 |
Yeah, I did that one on an Arduino once. Volhout PicomiteVGA PETSCII ROBOTS |
||||
| phil99 Guru Joined: 11/02/2018 Location: AustraliaPosts: 3016 |
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 |
||||
| The Back Shed's forum code is written, and hosted, in Australia. | © JAQ Software 2026 |