4x4 Keypad With Four I/O Pins (using shift registers)
Duane Degn
Posts: 10,588
I'm working on a terminal for a CNC controller.
After adding TV, VGA, keyboard and a mouse, I was starting to get low on pins. I wanted to include a 4x4 keypad as an input device but I didn't want to use eight I/O pins to interface with it.
I'm sure there are lots of better solutions, but I have lots of 74xx595 serial to parallel shift registers and several 74xx165 parallel to serial shift registers. I used one of each to interface with the keypad.
Four of the eight pins of the chips are used. It would be possible to use the extra pins to control additional inputs and outputs.
I wired up the shift registers before having a clear idea of who I'd program them. I figured I should be able to share clock and latch lines and I also shared the data line. I figured I'd need to control the chips' enable pins and I thought I could use the enable pins as chip select pins of SPI devices. Once I got around to writing the code, I realized I needed to be able to turn the '165 on and off but that the '595 chip needed to be continuously active. In hindsight, I see I could have tied the '595 enable pin low.
Another "hindsight" realization was the '165 chip clock the "A" input in last. While I only need to clock four output clocks, I have to clock a full eight cycles to retrieve the input bits.
All attach the code in case anyone is interested in it. I'm also including some of it inline to make it easier for people to see how I took care of shifting bits out and in. I'm curious if any of you have ideas on how it could have possible been done better?
I have some simple '165 code here in case anyone is interested (I'm pretty sure there are lots of '165 objects around).
After adding TV, VGA, keyboard and a mouse, I was starting to get low on pins. I wanted to include a 4x4 keypad as an input device but I didn't want to use eight I/O pins to interface with it.
I'm sure there are lots of better solutions, but I have lots of 74xx595 serial to parallel shift registers and several 74xx165 parallel to serial shift registers. I used one of each to interface with the keypad.
Four of the eight pins of the chips are used. It would be possible to use the extra pins to control additional inputs and outputs.
I wired up the shift registers before having a clear idea of who I'd program them. I figured I should be able to share clock and latch lines and I also shared the data line. I figured I'd need to control the chips' enable pins and I thought I could use the enable pins as chip select pins of SPI devices. Once I got around to writing the code, I realized I needed to be able to turn the '165 on and off but that the '595 chip needed to be continuously active. In hindsight, I see I could have tied the '595 enable pin low.
Another "hindsight" realization was the '165 chip clock the "A" input in last. While I only need to clock four output clocks, I have to clock a full eight cycles to retrieve the input bits.
All attach the code in case anyone is interested in it. I'm also including some of it inline to make it easier for people to see how I took care of shifting bits out and in. I'm curious if any of you have ideas on how it could have possible been done better?
CON _clkmode = xtal1 + pll16x _xinfreq = 5_000_000 DATA_PIN = 0 CLOCK_PIN = 1 LATCH_PIN = 2 CS_165_PIN = 3 CS_595_PIN = 4 KEYPAD_STACK = 32 KEYPAD_BUFFER_POWER_OF_2 = 4 ' Use this constant to change buffer size. KEYPAD_BUFFER_SIZE = 1 << KEYPAD_BUFFER_POWER_OF_2 '16 KEYPAD_BUFFER_MAX_INDEX = KEYPAD_BUFFER_SIZE - 1 ' Used to wrap head and tail pointers KEYPAD_COLUMNS = 4 KEYPAD_ROWS = 4 KEYPAD_SIZE = KEYPAD_COLUMNS * KEYPAD_ROWS MAX_COLUMN_INDEX = KEYPAD_COLUMNS - 1 MAX_ROW_INDEX = KEYPAD_ROWS - 1 BITS_TO_DISPLAY = KEYPAD_SIZE UNUSED_INPUTS = 4 ' '165 inputs to clock in without reading ' These inputs may be used for other purposes. QUOTE = 34 KEYPAD_LOC_X = 0 KEYPAD_LOC_Y = 4 BITS_IN = KEYPAD_COLUMNS DEBUG_BAUD = 115_200 NO_KEY_PRESS = 0 VAR long stack[KEYPAD_STACK] long keypadBits, previousKeypadBits byte keypadBuffer[KEYPAD_BUFFER_SIZE] byte head, tail OBJ Com : "Parallax Serial Terminal" PUB Setup Com.Start(DEBUG_BAUD) result := cognew(Keypad, @stack) Com.Str(string("Keypad started in cog #")) Com.Dec(result) Com.Char(13) waitcnt(clkfreq * 3 + cnt) Com.Clear repeat if head <> tail Com.Position(KEYPAD_LOC_X, KEYPAD_LOC_Y - 3) Com.Str(string("Keypad Bits = %")) Com.Bin(previousKeypadBits, BITS_TO_DISPLAY) ' Display previousKeypadBits since ' keypadBits spends much of the time ' cleared. Com.Position(KEYPAD_LOC_X, KEYPAD_LOC_Y - 1) if keypadBuffer[tail] == NO_KEY_PRESS Com.Str(string("No Key ")) else Com.Str(string("Key = ", QUOTE)) Com.Char(keypadBuffer[tail]) Com.Char(QUOTE) tail++ tail &= KEYPAD_BUFFER_MAX_INDEX ' zero tail if it passes end of buffer PUB Keypad | rowData, outputBit outa[CS_165_PIN] := 1 dira[CS_165_PIN] := 1 outa[CS_595_PIN] := 0 ' The enable pin could be tied low. dira[CS_595_PIN] := 1 dira[CLOCK_PIN] := 1 dira[LATCH_PIN] := 1 dira[DATA_PIN] := 1 repeat rowData := 1 previousKeypadBits := keypadBits keypadBits := 0 repeat KEYPAD_COLUMNS dira[DATA_PIN] := 1 outputBit := rowData repeat KEYPAD_COLUMNS ' shift out active column ' The four unused output pins may be used ' for other purposes. outa[DATA_PIN] := outputBit outa[CLOCK_PIN] := 1 outputBit >>= 1 outa[CLOCK_PIN] := 0 outa[CS_165_PIN] := 0 outa[LATCH_PIN] := 0 outa[LATCH_PIN] := 1 ' latch both output and input dira[DATA_PIN] := 0 repeat UNUSED_INPUTS ' since I used the wrong four pins on the '165 chip outa[CLOCK_PIN] := 1 outa[CLOCK_PIN] := 0 repeat KEYPAD_ROWS keypadBits <<= 1 keypadBits += ina[DATA_PIN] outa[CLOCK_PIN] := 1 outa[CLOCK_PIN] := 0 rowData <<= 1 ' Move bit to next column. outa[CS_165_PIN] := 1 ' don't gather input from next latch outa[LATCH_PIN] := 1 ' latch output so it's ready for real output outa[LATCH_PIN] := 0 ' The output from this latch isn't used. if keypadBits <> previousKeypadBits previousKeypadBits := keypadBits keypadBuffer[head++] := keypadCharacters[>|keypadBits] head &= KEYPAD_BUFFER_MAX_INDEX DAT ' The character order was determined by running the program ' and observing the bit position each button produced. ' These characters will likely need to be rearranged ' when using a keypad from a different vendor or ' if the wire order connection the shift registers to ' the keypad is changed. keypadCharacters byte NO_KEY_PRESS, "2580" byte "369F" byte "ABCD" byte "137E"
I have some simple '165 code here in case anyone is interested (I'm pretty sure there are lots of '165 objects around).
Comments
Thanks for describing your project and posting code.
That's a good idea.
I'll try to make a better schematic. I do have connections listed in the comments of the code. These comments aren't included in the code I posted in the code block but they are included in the Spin file.
Here are the comments with partial schematic.
I don't think the code could be written to use fewer pins (fewer than four that is). Since the data lines are shared, the '165 needs to be disabled while bits are clocked out to the '595 or the data from the '165 chip would interfere with the data going out to the '595.
The code posted works fine but I'm often surprised to see how others use shift registers in clever ways which require few than expected I/O pins or less code than I had supposed.
The obvious improvement to the way I did it would be to use the other set of four input pins on the '165. I had assumed the "A" input would be the first to be clocked in since the "0" output on the '595 is the first to be clocked out.
As I said, I'll try to get a better schematic to post, but since this already works, it's not a high priority. If someone would really like to see an improved schematic let me know and I'll make an extra effort to get it posted soon.
I was pleased not to need eight I/O pins to read the keypad. I may find a use for the unused output and input pins.
The 4000 series have light drive so do not need drive protection resistors.
You wire the 3 OP to 3 rows, and tie the 4th row to GND.
To check any key, you shift out L to all 3, and check for any IP LO -> Have Key.
Then you release the L from OP one at a time, and check IP.
If the IP is still low with all 3 released, it is on the 4th (gnd) row, and you can decode 4x8 using this.
I'll admit to not understanding a lot of what you just wrote, but I won't ask any questions until I have a look at the 4021 datasheet. Thanks for the suggestion. I'll probably include some in my next electronics order to experiment with.
Here's the way I have the '595 and '165 wired up.
As I've mentioned a couple of times, I used the wrong four pins of the '165.
Do I need to worry about letting the four unused inputs of the '165 float?
One thing I'd like to figure out is a way to use these shift registers with other SPI chips. I think if I do use other SPI chips in a project, I should separate the two data lines so there's a MOSI and MISO line.
I suppose even with other SPI devices, I could leave the enable pin of the '595 tied low (though it's not presently) since I doubt it would hurt the '595 to be shifting bits while the MOSI line is used on another device. The keypad garbage data won't be read in with the '165 disabled.
I actually had 8 pins I could have used for the keypad but it wouldn't have left many to spare and I'm not sure I won't need a few more I/O pins for something else.
The HEF4021 is essentially a '165 + 3/8 of a '595, in one package. (on an IP/OP basis)
The last 3 bits of the 8 bit shifter, come out to pins.
So you give it 8 clocks, and the first 3 bits loaded, appear on the pins, then you pulse PL, and 8 more clocks shift out the loaded IPs
It is a good idea to tie unused input pins to something, as a floating pin can draw more power from Vcc.
- usually whatever is nearest will do.
Now I think I should have looked around a bit more before starting wiring up my shift registers. The next 4x4 keypad I use, I'll try your object. If I end up needing more pins in my current project, I'll also try your object.
I think I ought to post my plans to the forum before I start them so I can be told the better options available.
Thanks for pointing out your object. It sure seems like a great idea.
As I mentioned in this thread, I'm using a TV with the keypad as a controller for my CNC machine.
When I use the keypad with the TV I get a lot of false keypresses. Most of these false keypresses appear to be from the top row of keys. If I move the TV control board away from the keypad and wires, the noise goes away.
My first of two ideas to reduce the noise would be to use some metal shielding between the TV control board and the keypad/wires. My second idea is to use stronger pull-down resistors on the '165 input pins.
Do one of these ideas sound better than the other? If I use a stronger pull-down resistor any suggested values? I'm currently using 10K ohm resistors on the pull-downs.
Any other ideas?
The wires from the keypad, are they twisted or open type wires? Shieding would probably help. In the old days I would use aluminmum foil at hi rf fields. Possibly cardboard with foil on it seperated from bottom/top of boards??
Still like that case!!!
The wire is open. 8 individual wire (like the wire used in indoor phone wiring).
I realize I have a lot of wire running through the enclosure. There an 18-pin header above the VGA port in the picture I posted. The 18 pins control the step and direction pulses to the stepper controller. It will also need to be protected from the TV driver noise.
I'm debating between making metal sleeves for the various wires with some 2" copper tape I have or to wrap the TV driver board in the copper tape. I'm afraid wrapping the driver board with copper tape would cause heat issues with the TV board.
I was leaning towards encasing either the wires or drive board with metal, do you really think just a metal sheet between the two boards would stop the noise? I was thinking I'd need a faraday cage?
I'll wait a bit before I try anything to see if anyone else has ideas of how to block the noise.
I'll probably try the metal and cardboard barrier you suggest since it's the easiest to implement.
I'm hoping once I have my CNC router going, cutting holes in enclosures will be easier. The cutouts I made look pretty good as long as you don't look carefully (or with your eyes open).
LOL about the faraday cage, times I could have used one also!
Either one should work, so whatever is easiest. Try 1K pulldowns as a starting point.
I have used some aluminum foil with mactac on both sides as insulator for a shield on occasions. Works well as long as you leave an area of foil bare to connect to ground. A copper clad pcb also works.
I don't think I have room for copper clad PCB material. I think whatever I use will have to somewhat flexible in order to fit between all the parts I'm jamming in the enclosure.
I'm glad you said something about grounding the shield; I hadn't thought of it.
Thanks.
The code (at least for now) is in Spin (see post #1 of thread). I'd think Spin is slow enough to let the pins be driven high? I'll try reading the inputs a few times to see if that would help. If the noise is short in duration, it shouldn't be the same with multiple reads. I could read the inputs four times and only accept inputs which remained constant over all four reads (or something like that).
Even if a software solution could be found, I'll probably need to address the hardware side of the problem since I don't think there will be a software trick that will clean up signals to the stepper driver.
Thanks for the suggestions.
There are quite a few different ways you can do a reliable keypad scan. The thing though to do with keypad scanning is to take it easy, don't rush it. If for instance you have all columns active so that you can sense any key press then once some key has been sensed you need to find out which column it's on so you start scanning through the column outputs one at a time (normally). To give each input a chance to settle you test the row inputs not after you select a new column but before you select it as it has had time from the last column select. If it's active then you can hang ten (hundred's of microseconds or even milliseconds) before you sample it again. If it is still active then you could accept as “good enough for me”. Once you encode a keypress or even no press you also want to compare this with the last key code so that you only pass a single code for each press. I like to encode these as standard ASCII representing the keys themselves and the null character is the code for no keys pressed.
Although shielding could stop the noise it's not really necessary as it's not a high-speed databus that you are trying to shield, the keypad is running on simple I/O and just needs to be validated for a whole number of reasons.
BTW, keypad contacts are necessarily "switches" as they could have hundreds of ohms contact resistance which combined with pin capacitance requires a delay for settling.
Now you tell me. I needed to know to use a PCF8574 I2C I/O expander before my last electronic order.
As I mentioned, I have a bunch of '595 and '165 chips and they also happen to be among the handful of ICs I've previously worked with.
The scanning is done in Spin so it's pretty slow already. I will add some code to check the input multiple times before deciding it's a valid press.
Done and done. I'm glad I did this part right.
The constant "NO_KEY_PRESS" equals zero.
The keypresses (or NO_KEY_PRESS) is stored in a small circular buffer.
I just noticed I have the columns and rows switched. I could switch them back by adding a half twist to the wires but the keypad works the same either way so I'll probably leave it as is.
The code, as it is now, only reads the highest bit. Only one button will be noticed if more than one button is pressed.
The keypad code as posted in post #1 worked fine with my test pad. Even the keypad withing the enclosure works fine with the original code if I move the TV control board away from the wires.
I'll try a software solution as suggested by Peter and Cluso99. I'm concerned even if I can get the keypad to work without shielding, I may still need shielding on the step and direction wires which pass by the same TV control board.
BTW, I'm using the USB on the Protoboard to program both Propeller chips. I cut the traces to P30 and P31 and route them through a DPDT switch. The reset and ground lines are shared. When I want to program one of the Propellers, I just position the switch appropriately and load the program. Since the reset lines are shared both Propellers are reset so I need to make sure the other Propeller's program is stored in EEPROM.
I've thought about using a 3PDT switch to also switch the reset line but all the three pole switches I've seen are really big. I can live with the double reset.
Good, but with the Prop outputs you shouldn't have to worry much at all as they are low-impedance and far less likely to be affected by the normal EMI you are seeing whereas the keypad inputs are at least 10K pull-down. Of course you could decrease the pull-down or add caps but as mentioned software is all that is needed. One thing though about the stepper lines is that it might be a good idea to add pull-up/downs to the step clock line (DIR doesn't matter) so that this isn't glitched during reset and power-up etc.
As for the reset switch I would think that you only really need a single pole switching the receive line with the transmits diode OR'd together (with a pull-up). But a DPDT is a simple solution in your case. The other way you could do it is to hold the unwanted Prop (or Props) in reset so that only the active one responds in which case you just need a simple push-button for each Prop and a diode back to the serial reset. Push buttons are much more compact than any DPDT switch and you end up with a manual reset button for each Prop.
Reading the inputs twice was enough to eliminate the noise problem. I used bitwise "&" to make sure the inputs were high on both reads (I also tried three and four reads but two turned out to be enough).
I've heard of doing this on other motor controllers but I hadn't though to use them on the stepper controller. Thanks, I'll take your advice in this matter.
I'll keep the diode ideas in mind if I do something like this again, but in this project I really like being able to switch both the RX and TX debug lines with the DPDT switch. I can switch the serial connection with the PC from one Prop to the other this way.
I added some copper tape to the back side of the keypad and to the wires. I scrapped way the coating on the copper and soldered some wires to the copper in order to connect them to ground. The shielding reduced the amount of noise I was experiencing but it didn't eliminate it. Adding the software changes suggested in this thread took care of the rest of the noise (it probably would have taken care of all of it but then I wouldn't have been able to use my cool copper tape).
I'm marking the thread "Solved". Thanks for all your help. I have sure learned a lot on this forum.