Essentially, if you have a 4x4 matrix, you have 4 columns and 4 rows. You apply a test signal to each of the rows in turn, then inspect the columns to see if they signal appears on any of them. (Possibly rows/columns interchanged.)
If we have \(m\) rows and \(n\) colums, we need \(m+n\) pins to handle the \(mn\) buttons. So for a 4x4 matrix that is \(4+4=8\) pins.
Basics
There are four pins: C1..C4 and R1..R4.
- Set one row pin high, the rest low.
- Read the colum pins to see if any are high.
Code
We use the Keypad library. In the Arduino IDE, go to Sketch→Include Library→Manage Libraries (C-S-i), search for Keypad, and you want the one by Mark Stanley and Alexander Brevig.
This is the roughly the example code from the Circuit Basics page linked below:
#include <Keypad.h>
const byte ROWS = 4;
const byte COLS = 4;
char theKeys[ROWS][COLS] = {
{'0','1','2','3'},
{'4','5','6','7'},
{'8','9','A','B'},
{'C','D','E','F'}
};
byte rowPins[ROWS] = {7, 6, 5, 4};
byte colPins[COLS] = {8, 9, 10, 11};
Keypad customKeypad = Keypad(makeKeymap(theKeys), rowPins, colPins, ROWS, COLS);
void setup(){
Serial.begin(9600);
}
void loop(){
char customKey = customKeypad.getKey();
if (customKey){
Serial.print(customKey);
}
}
Tutorials
Questions
- If we have two button arrays, and connect the row pins (so that we probe the rows on both arrays at the same time), do we need only 12 IO pins in total? (If we get one working and like it, get a second and do the experiment.)
Pyserial
We can read from the serial port using Pyserial. This is a toy example that associates shell commands to the buttons:
#!/usr/bin/env python
import serial
from time import sleep
import sys
from subprocess import run
from threading import Thread
class Runner:
def __init__(self,cmd):
self.cmd = cmd
self.thread = Thread(target=self)
self.result = None
def __call__(self):
self.result = run(self.cmd)
def start(self):
self.thread.start()
#COM = 'COM3'# windows non cygwin
#COM = '/dev/ttyACM0' # Linux
COM = '/dev/ttyUSB0' # Linux
#COM = '/dev/ttyS2' # cygwin
#COM = '/dev/tty.usbserial-1460' # mac
BAUD = 9600
#BAUD = 256000 # fine over a USB cable
ser = serial.Serial(COM, BAUD, timeout = .1)
print('Waiting for device');
sleep(3)
print(ser.name)
class Handler:
def __init__(self):
self.cmds = {}
self.cmds[15] = ["echo","hello world"]
self.cmds[3] = ["xeyes"]
def __call__(self,x):
if x >= ord("0") and x <= ord("9"):
return self.handleIndex(x-ord("0"))
if x >= ord("A") and x <= ord("F"):
return self.handleIndex(10+x-ord("A"))
return self.handleFunnyChar(x)
def handleIndex(self,i):
print(f"Button {i}")
if i in self.cmds:
Runner(self.cmds[i]).start()
def handleFunnyChar(self,x):
print(f"Funny char {x}")
handler = Handler()
try:
while True:
b = ser.read(1)
if len(b) == 0:
continue
handler(b[0])
except KeyboardInterrupt:
print(f"Ctrl-C")
exit()
More ambitions
There is this electronoobs page which aims to build a bigger matrix.