Monday, May 13, 2013

Using 3x4 matrix keypad with the Raspberry Pi

UPDATE 8/23/13
This page will stay since it explains the original scripts but I've made a number of updates and have packaged them up on the PyPI so they are easy to maintain for me and to distribute and install for everyone else. Let me know if there are any issues and I'll attempt to take care of them as time allows.
https://pypi.python.org/pypi/matrix_keypad
http://crumpspot.blogspot.com/p/keypad-matrix-python-package.html

Another module of my alarm interface is done. I picked up a 3x4 matrix keypad from Adafruit (http://www.adafruit.com/products/419) But couldn't find any good python code examples for it's use. I was able to find a few examples that lead me down the right path in terms of scanning rows first as inputs and then swapping pins to scan the columns. I wrote two libraries, the first just uses 7 of the Raspberry Pi's GPIO pins then I also wrote one that works for the MCP23008 chip since I'm really in a mode of saving pins when I can :) Then also just wrote a demo script to show calling the libraries and their use.

Here is the code as it stands now, I have some ideas on how to change it to be better but this is fully working for now.

UPDATE: I received a 4x4 keypad to test changes to the script and it worked like a charm once I changed the keypad and the Row and Column values like so:
KEYPAD = [
[1,2,3,"A"],
[4,5,6,"B"],
[7,8,9,"C"],
["*",0,"#","D"]
]
ROW = [7,6,5,4]
COLUMN = [3,2,1,0]


matrixKeypad_RPi_GPIO.py:


# #####################################################
# Python Library for 3x4 matrix keypad using
# 7 of the avialable GPIO pins on the Raspberry Pi. 
# 
# This could easily be expanded to handle a 4x4 but I 
# don't have one for testing. The KEYPAD constant 
# would need to be updated. Also the setting/checking
# of the colVal part would need to be expanded to 
# handle the extra column.
# 
# Written by Chris Crumpacker
# May 2013
#
# main structure is adapted from Bandono's
# matrixQPI which is wiringPi based.
# https://github.com/bandono/matrixQPi?source=cc
# #####################################################

import RPi.GPIO as GPIO

class keypad():
    # CONSTANTS   
    KEYPAD = [
    [1,2,3],
    [4,5,6],
    [7,8,9],
    ["*",0,"#"]
    ]
    
    ROW         = [18,23,24,25]
    COLUMN      = [4,17,22]
    
    def __init__(self):
        GPIO.setmode(GPIO.BCM)
    
    def getKey(self):
        
        # Set all columns as output low
        for j in range(len(self.COLUMN)):
            GPIO.setup(self.COLUMN[j], GPIO.OUT)
            GPIO.output(self.COLUMN[j], GPIO.LOW)
        
        # Set all rows as input
        for i in range(len(self.ROW)):
            GPIO.setup(self.ROW[i], GPIO.IN, pull_up_down=GPIO.PUD_UP)
        
        # Scan rows for pushed key/button
        # A valid key press should set "rowVal"  between 0 and 3.
        rowVal = -1
        for i in range(len(self.ROW)):
            tmpRead = GPIO.input(self.ROW[i])
            if tmpRead == 0:
                rowVal = i
                
        # if rowVal is not 0 thru 3 then no button was pressed and we can exit
        if rowVal <0 data-blogger-escaped-or="" data-blogger-escaped-rowval="">3:
            self.exit()
            return
        
        # Convert columns to input
        for j in range(len(self.COLUMN)):
                GPIO.setup(self.COLUMN[j], GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
        
        # Switch the i-th row found from scan to output
        GPIO.setup(self.ROW[rowVal], GPIO.OUT)
        GPIO.output(self.ROW[rowVal], GPIO.HIGH)

        # Scan columns for still-pushed key/button
        # A valid key press should set "colVal"  between 0 and 2.
        colVal = -1
        for j in range(len(self.COLUMN)):
            tmpRead = GPIO.input(self.COLUMN[j])
            if tmpRead == 1:
                colVal=j
                
        # if colVal is not 0 thru 2 then no button was pressed and we can exit
        if colVal <0 data-blogger-escaped-colval="" data-blogger-escaped-or="">2:
            self.exit()
            return

        # Return the value of the key pressed
        self.exit()
        return self.KEYPAD[rowVal][colVal]
        
    def exit(self):
        # Reinitialize all rows and columns as input at exit
        for i in range(len(self.ROW)):
                GPIO.setup(self.ROW[i], GPIO.IN, pull_up_down=GPIO.PUD_UP) 
        for j in range(len(self.COLUMN)):
                GPIO.setup(self.COLUMN[j], GPIO.IN, pull_up_down=GPIO.PUD_UP)
        
if __name__ == '__main__':
    # Initialize the keypad class
    kp = keypad()
    
    # Loop while waiting for a keypress
    digit = None
    while digit == None:
        digit = kp.getKey()
    
    # Print the result
    print digit  

matrixKeypad_MCP230xx.py

# #####################################################
# Python Library for 3x4 matrix keypad using
# the MCP23008 chip via I2C from the Raspberry Pi.
# 
# This could easily be expanded to handle a 4x4 but I 
# don't have one for testing. The KEYPAD constant 
# would need to be updated. Also the setting/checking
# of the colVal part would need to be expanded to 
# handle the extra column.
# 
# Written by Chris Crumpacker
# May 2013
#
# main structure is adapted from Bandono's
# matrixQPI which is wiringPi based.
# https://github.com/bandono/matrixQPi?source=cc
# #####################################################

from Adafruit_MCP230xx import Adafruit_MCP230XX

class keypad(Adafruit_MCP230XX):
    # Constants
    INPUT       = 0
    OUTPUT      = 1
    HIGH        = 1
    LOW         = 0
    
    KEYPAD = [
    [1,2,3],
    [4,5,6],
    [7,8,9],
    ["*",0,"#"]
    ]
    
    ROW         = [6,5,4,3]
    COLUMN      = [2,1,0]
    
    def __init__(self, address=0x21, num_gpios=8):
        
        self.mcp2 = Adafruit_MCP230XX(address, num_gpios)
        
    def getKey(self):
        
        # Set all columns as output low
        for j in range(len(self.COLUMN)):
            self.mcp2.config(self.COLUMN[j], self.mcp2.OUTPUT)
            self.mcp2.output(self.COLUMN[j], self.LOW)
        
        # Set all rows as input
        for i in range(len(self.ROW)):
            self.mcp2.config(self.ROW[i], self.mcp2.INPUT)
            self.mcp2.pullup(self.ROW[i], True)
        
        # Scan rows for pushed key/button
        # valid rowVal" should be between 0 and 3 when a key is pressed. Pre-setting it to -1
        rowVal = -1
        for i in range(len(self.ROW)):
            tmpRead = self.mcp2.input(self.ROW[i])
            if tmpRead == 0:
                rowVal = i
                
        # if rowVal is still "return" then no button was pressed and we can exit
        if rowVal == -1:
            self.exit()
            return
        
        # Convert columns to input
        for j in range(len(self.COLUMN)):
            self.mcp2.config(self.COLUMN[j], self.mcp2.INPUT)
        
        # Switch the i-th row found from scan to output
        self.mcp2.config(self.ROW[rowVal], self.mcp2.OUTPUT)
        self.mcp2.output(self.ROW[rowVal], self.HIGH)
        
        # Scan columns for still-pushed key/button
        colVal = -1
        for j in range(len(self.COLUMN)):
            tmpRead = self.mcp2.input(self.COLUMN[j])
            if tmpRead == 1:
                colVal=j
        
        if colVal == -1:
            self.exit()
            return
              
        # Return the value of the key pressed
        self.exit()   
        return self.KEYPAD[rowVal][colVal]
            
    def exit(self):
        # Reinitialize all rows and columns as input before exiting
        for i in range(len(self.ROW)):
                self.mcp2.config(self.ROW[i], self.INPUT) 
        for j in range(len(self.COLUMN)):
                self.mcp2.config(self.COLUMN[j], self.INPUT)
        
if __name__ == '__main__':
    # Initialize the keypad class
    kp = keypad()
    
    # Loop while waiting for a keypress
    r = None
    while r == None:
        r = kp.getKey()
        
    # Print the result
    print r  

Demo code, matrixKeypad_test.py

# #####################################################
# Demo script showing the use of the Python 
# matrix Keypad library for both the Raspberry Pi
# GPIO and the MSP230xx I2C Chip set.
#
# Librarys needed:
# matrixKeypad_MCP230xx.py or matrixKeypad_RPi_GPIO.py
#
# Also needed is the Adafruit python libraries for the 
# MCP230xx chips (Adafruit_MCP230xx.py) and I2C (Adafruit_I2C.py)
# 
# Written by Chris Crumpacker
# May 2013
#
# #####################################################

from matrixKeypad_MCP230xx import keypad
#from matrixKeypad_RPi_GPIO import keypad
from time import sleep

# Initialize the keypad class
kp = keypad()

def digit():
    # Loop while waiting for a keypress
    r = None
    while r == None:
        r = kp.getKey()
    return r 

print "Please enter a 4 digit code: "

# Getting digit 1, printing it, then sleep to allow the next digit press.
d1 = digit()
print d1
sleep(1)

d2 = digit()
print d2
sleep(1)

d3 = digit()
print d3
sleep(1)

d4 = digit()
print d4

# printing out the assembled 4 digit code.
print "You Entered %s%s%s%s "%(d1,d2,d3,d4) 

21 comments:

  1. Hi,
    this tuto seems perfect, but I got a problem.
    I have a raspi rev B, an MCP23017, and it's set to address 0x20, and matrix keyboard from GPIO 1 to 7 of my MCP23017.

    So I changed :
    ROW = [7,6,5,4]
    COLUMN = [3,2,1]

    def __init__(self, address=0x20, num_gpios=16):

    But I only have *, 0 and # that works.
    Any idea?

    ReplyDelete
    Replies
    1. Well, I would venture a guess seeing as the last row works, that you have your wiring backwards. i.e. your column 1 is wired to row 7, column 2 to row 6, etc. SO either try flipping the wiring. or change the code:

      ROW = [1,2,3,4]
      COLUMN = [5,6,7]

      Delete
    2. This comment has been removed by the author.

      Delete
    3. This comment has been removed by the author.

      Delete
  2. Hi,

    Can you please share the code for a 4*4 alternative i made the changes that you had required to be done but couldnt get it to work.

    Regards,
    Ashok

    ReplyDelete
    Replies
    1. I could try but I don't have one for testing. Can you send me the script as you have it and I can see if it is what I was thinking and if I can spot anything easily

      Delete
    2. Done. Check the package linked above.

      Delete
  3. OK, I found your mistake :)
    Lines 59 et 60 are not correctly indented


    # Scan rows for pushed key/button
    # valid rowVal" should be between 0 and 3 when a key is pressed. Pre-setting it to -1
    rowVal = -1
    for i in range(len(self.ROW)):
    tmpRead = self.mcp2.input(self.ROW[i])
    if tmpRead == 0:
    rowVal = i

    ReplyDelete
    Replies
    1. Looks like some kind of issue with the blog formatting or maybe copy and paste. My .py file doesn't have it like this... Anyway glad you found the issue.

      Delete
    2. Code on the blog has been updated

      Delete
    3. Can u send the latest code for the keypad...I am getting error at GPIO.INPUT(self.ROW[i])

      Delete
    4. Too many people (including myself were having issues with coping the code from this post. Blogger messes with the indents etc. too much Anyway I posted it as an installable packages on the pypi page. try that out and let me know. Make sure you get at least 1.0.5

      https://pypi.python.org/pypi/matrix_keypad

      Delete
    5. Now it is working but i cannot loop to expect more digit.. i need to execute again... the demo code is also not working... it gives (program exited with code: 0)Press return to continue

      Delete
    6. I hadn't made a few needed changes to the demo code after renaming the package. I've updated it now and it should be working. current version is 1.1.1. If you installed it via pip you can use this:

      "sudo pip install --upgrade matrix_keypad"

      Delete
  4. Great stuff, Chris. Always good to see something 'else' working on the Pi :-)

    ReplyDelete
  5. This comment has been removed by the author.

    ReplyDelete
  6. Great work Chris! Came across your blog when searching for a solution for the 3x4 keypad. I will need it for my home alarm/automation project. Thanks!

    ReplyDelete
  7. This comment has been removed by a blog administrator.

    ReplyDelete
  8. question is it possible to use an interrupt with this instead of a loop?

    ReplyDelete
    Replies
    1. No Because there aren't enough interrupt pins on the MCP chips. You'd need at least 3.

      Delete
  9. Thanks for the attribution to my work!

    I had a late post on Python class for any m x n matrix keypad using WiringPi to wrap up the old deprecated library by the way:

    http://lakm.us/logit/2014/02/any-m-x-n-matrix-keypad-raspberry-pi/

    ReplyDelete