Arduino Keyboard Matrix Code and Hardware Tutorial

Here's how a keyboard works

As a kid, I got the book “Upgrading and Repairing PCs.” (Now in its 22nd edition.) It was the first book to explain to me the PC architecture. I considered, how were there so few pins on an AT-style keyboard connector when there were 101 keys on the keyboard? That is when I first learned about the keyboard matrix.

Intel_P8049_AH_controller

Original image from Deskthority Wiki. (Edited image is shown.)

The keyboard matrix itself did not amaze me, but instead the idea there was an entirely separate 8-bit microcontroller inside of the keyboard. Early keyboards may have used the P8049AH, which, there is still some stock available to purchase. I was fascinated with the idea an entire computer was necessary to run the keyboard, to use my “real” computer. Why did it take something as complicated as a microcontroller?

The key benefit (get it?) of a keyboard matrix is that it reduces the number of pins necessary to capture the input of a large number of the keys. Even though a PC keyboard has 101 keys, it does not mean there is a microcontroller with 101 pins. Nor does it need a cable with over 100 wires.

I will first explain with simple four and nine button examples.

Without a matrix

First, let’s look at what happens with four buttons. Without an array, each switch would get an input pin. This count probably does not sound bad. Now, what if you used nine buttons instead of just 4?

9 buttons without a matrix

You would need 9 I/O pins! Also, consider the cost of wiring that many individual buttons. If the buttons are on a different PCB from the microcontroller or you are hand wiring a prototype, that is many wires.

Let’s put those same nine buttons into a 3×3 matrix. This method saves you three pins!

9x9 Keyboard Matrix

Elements of a Keyboard Matrix

Every matrix has rows and columns. By accessing a single row and a single column, we can individually access each button. This method drives one side and senses the other.

Keyboard Matrix Diodes

Ghosting Example Keyboard Matrix

Matrix with and without blocking diodes

In the schematic, I have included blocking diodes. The diode prevents a condition called “ghosting.” In a keyboard matrix, ghosting means you see non-existent button pushes.

The image above compares the same button presses with and without diodes. (The 1 ohm resistors keep iCircuit from getting annoyed with short circuits.) On the right-side schematic, reading the “selected” button happens with no additional current paths.

Keyboard Matrix Code

For() loops and arrays make the code work. The steps for scanning the keyboard matrix include:

  1. Enable the column
  2. Scanning each row
  3. Capture button state
  4. Disable the column
// Keyboard Matrix Tutorial Example
// baldengineer.com
// CC BY-SA 4.0
 
// JP1 is an input
byte rows[] = {2,3,4};
const int rowCount = sizeof(rows)/sizeof(rows[0]);

// JP2 and JP3 are outputs
byte cols[] = {8,9,10};
const int colCount = sizeof(cols)/sizeof(cols[0]);

byte keys[colCount][rowCount];

void setup() {
	Serial.begin(115200);

	for(int x=0; x<rowCount; x++) {
		Serial.print(rows[x]); Serial.println(" as input");
		pinMode(rows[x], INPUT);
	}

	for (int x=0; x<colCount; x++) {
		Serial.print(cols[x]); Serial.println(" as input-pullup");
		pinMode(cols[x], INPUT_PULLUP);
	}
		
}

void readMatrix() {
	// iterate the columns
	for (int colIndex=0; colIndex < colCount; colIndex++) {
		// col: set to output to low
		byte curCol = cols[colIndex];
		pinMode(curCol, OUTPUT);
		digitalWrite(curCol, LOW);

		// row: interate through the rows
		for (int rowIndex=0; rowIndex < rowCount; rowIndex++) {
			byte rowCol = rows[rowIndex];
			pinMode(rowCol, INPUT_PULLUP);
			keys[colIndex][rowIndex] = digitalRead(rowCol);
			pinMode(rowCol, INPUT);
		}
		// disable the column
		pinMode(curCol, INPUT);
	}
}

void printMatrix() {
	for (int rowIndex=0; rowIndex < rowCount; rowIndex++) {
		if (rowIndex < 10)
			Serial.print(F("0"));
		Serial.print(rowIndex); Serial.print(F(": "));

		for (int colIndex=0; colIndex < colCount; colIndex++) {	
			Serial.print(keys[colIndex][rowIndex]);
			if (colIndex < colCount)
				Serial.print(F(", "));
		}	
		Serial.println("");
	}
	Serial.println("");
}

void loop() {
	readMatrix();
	if (Serial.read()=='!')
		printMatrix();
}

1. Column Enable (Line 32)

Keyboard matrix columns are enabled by setting the pin to OUTPUT and then to LOW. This step provides the path to ground. The rest of the columns pins are held in their high impedance state, effectively disabling them from the matrix.

2. Scan each row (Line 39)

A for() loop runs through each pin the row array. The pin’s input pull-up resistor is enabled, providing the connection to VCC. Most Arduino boards turn on the resistor with pinMode()’s INPUT_PULLUP state.

3. Capture the pin’s state

A two-dimensional array stores the pin’s value. The pin’s pull-up resistor is turned off, and the loop increments.

The idea here is to capture all of the pressed buttons. After the matrix has been scanned action can be taken. Just like with regular buttons, you could also create a “previous state” matrix to detect button state changes.

Once read, the pin’s state goes back to INPUT, disabling the row by turning off the pull-up resistor.

Bitwise operators would be better

I’d like to point out; this method is very memory inefficient. The problem is that an entire byte, or 8 bits, is being used to store the state of each button. You only need 1 bit for each. A more efficient method would be using bitwise operators to keep track of each key as a bit.

4. Disable the Column

After checking each row, putting the column pin back to an INPUT state disables it.

Processing the newly acquired button presses happens after scanning the entire matrix. In this example code, the function “printMatrix()” prints the contents of the array. In your sketch, you would perform some action based on the button’s states.

Potential Optimizations

As mentioned before, you could be using bit operators to store each button press as a single bit, instead of a byte.

Converting the code into a state machine using millis() could be critical on slower MCUs or very time sensitive code. As it is, most matrices will be scanned so fast the blocking time doesn’t matter. However, I could imagine using a microcontroller at 1 MHz where the time it takes for a large matrix read to finish might be too much.

Alternative code

There is an Arduino keyboard matrix library available. You can install Keypad from the Library manager.

Arduino Keypad Library

It simplifies programming a matrix. The most effort is defining the keys. A trade-off with the stock library is that it does not handle multiple key presses. There’s a code example for that, but that adds to the complexity.

Conclusion

A keyboard matrix is a great way to add buttons without using up all of your I/O pins. In this keyboard matrix tutorial,  I showed how a 9-button matrix works. This same code and circuit are what I’m using for a new project. My latest project has 64 buttons. More on that to follow.

Long comments, URLs, and code tend to get flagged for spam moderation. No need to resubmit.

Leave a comment