Keyboard Matrix Reduce I/O

I am currently working on a project that requires a (almost) full qwerty keyboard, as well as a 4x20 lcd(standard 4 bits of data interface). Trying to fit all of this onto an atmega328 is obviously going to be a difficult task, and the standard keypad library and matrices consume far too many pins to be of any use in this project. I believe that an LCD will take up 6 general purpose I/O, leaving me with very few to create a useful keyboard.

I came up with this project when viewing an episode of the Ben Heck Show, where he creates texting radios. His use of the Johnson counter intrigued me, but, besides a basic knowledge of standard matrices, I have very little experience with this type of coding and hardware. Fundamentally, I am attempting to lower the number of I/O it takes to run a 3x11 keyboard matrix. Should I use a Johnson Counter, or are general shift registers the way to go?

I would greatly appreciate any pointers or advice in this task, and apologise if my basic knowledge infuriates any of you experienced electronics engineers (I've had it before)!

3x11 keys == 33 keys
so you could make a 6x6 matrix so you need 12 pins.

you could also use 2 shift registers (one in , one out) to have an 8x8 matrix . The shift registers use 6 pins (3 each) and maybe you can share some lines (never tried).

Other option is to use 2 PCF8574 (I2C) chips - 8IO each - using only 2 lines.

Thanks Rob,

Would you be able to provide any code for scanning the columns and rows?

Ben

This implementation using 3 digital control lines and a 4017 may be of interest: 4017 IC switch matrix controller and library. Up to 100 switches with 3 pins! - Exhibition / Gallery - Arduino Forum

As it it can give 100 switches, by adding in another 4017 you can get 1000 switches. Scan times will obviously grow.

I wrote a library to implement this and have some circuit examples in the my codeplex libraries site (below).

Thanks, and great work on the library.

.
This is a complete shot in the dark here, but is it possible to use a CD4049UBE here? I only ask because I do not have immediate access to new parts. Also, do the pins defined in the sketch refer to the pins on the CD4017?

Ben

I would also appreciate greatly and code or hardware examples of using two of these in a matrix, perhaps to create a 4x11/12 keyboard if the capacity is there. I would also like to ask if I could use some 1N400 diodes as I have them on hand (sorry, but I have rarely used diodes so do not really understand the different types and functions).

Thanks again,
Ben

is it possible to use a CD4049UBE here?

No. The 4049 is a hex buffer and the 4017 is a decade counter. Different functions.

do the pins defined in the sketch refer to the pins on the CD4017

Pin numbers in the sketch are usually Arduino pin numbers.

use some 1N400 diodes

Just about any diode will do. The ones I used are really cheap, small general purpose signal diodes (which is why they are used a lot).

create a 4x11/12 keyboard

You can support up to 100 keys with 2 decade counters, so 4x11 or 4x12 can be done with 2 chips. You don't need 2 independent circuits unless you have a special need for it. From memory the circuits in the pdf articles with the library include examples you are looking for.

Hi, you can handle up to 42 buttons with 7 Arduino outputs. No other chips required, just a diode attached to each switch.

Paul

Thank you both (and sorry for the beginner questions!),

Paul, how will I implement this in code (and can it support multiple keypresses)? And Marco, sorry but I can't find the PDFs you mentioned.

Ben

Hi Ben, Yes, my suggestion will detect any number of simultaneous key presses.
EDIT: When using a Charlieplexed matrix, correct identification of multiple simultaneous presses can't be guaranteed to work, even if you do put a diode in series with each button. If you need N-key rollover, don't use a Charlieplexed matrix, use a conventional matrix where you have a pin for each column and a pin for each row. Ben: apologies for this error. @Paul_B thanks for pointing out my mistake.

The code would set all 7 output pins to INPUT_PULLUP initially. Then for each of the 7 pins in turn, it would set that pin to OUTPUT & LOW. Then it would read the other 6 pins. The pin would then be set back to INPUT_PULLUP before moving on to the next pin and so on.

I really do appreciate the help you are giving me, but being fairly new (14 years old) to the complex programming of MCUs I have no idea how to go about implementing this in the arduino syntax. If you could offer any more help, or point me in the right direction, I would be very grateful indeed.

No problem Ben.

Use the "Reference" link in the "Learning" menu at the top of this web page and look at the pinMode(), digitalRead() & digitalWrite() functions.

OK I will try this thanks,

Ben

I've made a start on the code, but I could use some help for a couple of things. Firstly, my line-by-line method of declaring and setting the variables is obviously very inefficient. I tried using a For loop but I couldn't get it to work. Secondly, I have no idea how to store and use the digitalRead variables. If you have any ideas then I would be very grateful.

int pin3 = 3;
int pin4 = 4;
int pin5 = 5;
int pin6 = 6;
int pin7 = 7;
int pin8 = 8;
int pin9 = 9;

void setup() {
  //Set all pins as INPUT_PULLUP 
  pinMode(pin3, INPUT_PULLUP);
  pinMode(pin4, INPUT_PULLUP);
  pinMode(pin5, INPUT_PULLUP);
  pinMode(pin6, INPUT_PULLUP);
  pinMode(pin7, INPUT_PULLUP);
  pinMode(pin8, INPUT_PULLUP);
  pinMode(pin9, INPUT_PULLUP);
}

void loop() {
  // put your main code here, to run repeatedly: 
  Scan();
}

void Scan() {
    pinMode(pin3, OUTPUT);
    digitalWrite(pin3, LOW);
    
}

Thanks again.

Ben, there's not much point saying "int pin3 = 3". It doesn't really add any advantage or clarify anything!

Better to give them names that are more descriptive of what they will be used for, such as "keyboardPin1" to "keyboardPin7" or "keyboardPinA" to "keyboardPinF" like in my diagram.

You have chosen 7 Arduino pins that are adjacent to each other (3 to 9), so you could say:

int keyboardPin1 = 3;
int keyboardPin7 = 9;

...

for (int p = keyboardPin1, p <= keyboardPin7, p++) {
  pinMode(p, INPUT_PULLUP);
}

However, its is rare to have the luxury of being able to choose 7 adjacent pins! If for example you could only use pins 3 to 5, pins 8 to 10 and pin 12, you could not do the simple for loop like above. Instead, its more normal to put the pin numbers in an array. That way you can use any pins that are available and they don't have to be adjacent:

int keyboardPin[7] = {3, 4, 5, 8, 9, 10, 12};

...

for (int p = 0, p <= 7, p++) {
  pinMode(keyboardPin[p], INPUT_PULLUP);
}

Thanks that's a great way to do it (and I used the simple variable names just to make them easy to use, but I guess adding a more descriptive name could help later on). Do you have any ideas about the code for scanning, as I can visualize it being extremely long without using some form of loop to 'automate' (like in your array) the code?

Thanks for your continued help!

To read the entire keyboard you will need a for-loop within a for-loop. The outer loop will make the pins OUTPUT & LOW in turn. The inner loop will read the remaining pins (ignoring the pin that is currently an output). Then the outer loop will change the output pin back to input again.

As this is going on, the code will also need to keep track of which pin is being read (1..33 or 1..42 whatever) and also detect when a button has just been pressed, as opposed to having been pressed for some time. In other words, is it pressed now but was not pressed last time it was checked.

sorry but I can't find the PDFs you mentioned.

They should be in the doc subfolder of the library folder for MD_4017_KM. Links to the original articles:
Article 1
Article 2

I think that if you have not done I/O scanning for keypresses before you are better off learning how to do it with many I/O pins before you try and take the minimalist approach. Once you understand what the Arduino is doing with the 6-7 I/O pins, then understanding how the other circuit works will be easier.

I have improved and added to the code, thanks to your suggestions, but have a few questions. Firstly, in the two if statements I have utilised, the else sections are empty, and I was wondering if I need a GOTO to break out of the for loops (they are commented in the code). Also, I'm not sure how to set the value of the keys and how the code will recognise these; maybe a 2D array? The code is as follows:

int keyboardPin[7] = {0, 1, 6, 7, 8, 9, 10}; //Using tx 
//and rx as programming with ISP

void setup(){
  for (int p = 0; p <= 7; p++) {
  pinMode(keyboardPin[p], INPUT_PULLUP);
}
}

void loop(){
  scan();
}
void scan(){
    for (int p = 0; p <= 7; p++){//Set as output and low
      pinMode(keyboardPin[p], OUTPUT);
      digitalWrite(keyboardPin[p], LOW);
      int low = p; //Set the output pin to use later
       for (int p = 0; p <= 7; p++){//Read all other pins
         if (p != low){//Check if p is the same as the low variable
           int readPin = digitalRead(keyboardPin[p]); 
             if (readPin == LOW){ //If pin is low
               //How do I know which pin this is? I.E. 33, 41
               //Should I multiply the low variable by keyboardPin[p]?
             }else{
               //Do nothing? GOTO statement to break out?
             }
         }else{
            //Do nothing? GOTO statement to break out?
         }//else
       }//Second for loop
    }//First for loop
};//scan()

Hi,

Are you really programming via isp and using pins 0 and 1, or did you copy that code from somwhere? If the latter, don't use pins 0 and 1. They are used to upload the sketch and are generally only used by experts and then only as a last resort.

You are using the variable name "p" for both for loops, forcing you to copy the outer loop's value for use in the inner loop. Why not simply give the inner loop variable a different name, eg. "q"?

I don't think any action is required for either of your "else" clauses, so just omit them.

You mentioned earlier that you wanted to detect multiple key presses, so its important to complete the loops and not jump out ("GOTO"? shame on you!)

A 2D array would be a good way to map your two loop variables to a character.

The other thing you may need to think about (I mentioned earlier) is detecting when a key changes from not being pressed to being pressed. Another 2D array would be usefull here, to hold the previous HIGH/LOW status of each button.

Finally, what to do when you find a button has just been pressed? You could just set a variable to the character detected, and process it after the loop. The chances of two keys changing from not pressed to pressed on the same scan are low. But if you find that's not the case, you will need to make a small buffer.