I2C port Expander and 74C922 Keypad Decoders

Author: Andrew Davison (software@davison-family.com)

Background

In an attempt to reduce the amount of code I needed to process keypad inputs, I decided to extend Angel's code library to accomodate a 74C922 keypad decoder.

Why?

1) Because the decoder gives me a keypad value between 0x00 and 0x0F I don't need to write any code to pulse the keypad rows and identify the input keystroke

2) The 74C922 has a very handy "Data Available" signal that let's you know when there's been a "press-and-release" event, so no more mucking about to figure out if the value you're getting from the keypad is a new keypress of just a long keypress from the user

Everything you'll need to get this one working is here, including circuits, PCB designs and source code for the library.

The Circuit

Here's the schematic for the keypad port expander.

The capacitors can be left out, but the stability of the circuit suffers as you're no longer debouncing the keystrokes.

Power and ground to the the IC's is not indicated in the schematic, so don't forget them :)

The 8 pin header runs off to your keypad - the circuit as it's designed assumes a keypad with this pinout:

  • Pin 1: Col 1

  • Pin 2: Col 2

  • Pin 3: Col 3

  • Pin 4: Col 4

  • Pin 5: Row 1

  • Pin 6: Row 2

  • Pin 7: Row 3

  • Pin 8: Row 4

Getting this wrong isn't fatal, but you won't get the right output. All the keypads I've used have this pinout, but I've seen talk of other posibilities.

The 4 pin header runs back to your arduino, with the following pinout:

  • Pin 1: Vcc

  • Pin 2: I2C Clock (SCL)

  • Pin 3: I2C Data (SDA)

  • Pin 4: Gnd

Also note that I've tied the address lines of the 8574 to Vcc. This locks the 3 bit address of the device to be 0x7. If you want to change this for any reason, just cut the tracks on the circuit board to leave those address lines floating.

Sound

I've assumed that there is a buzzer connected to pin 13 of the Arduino for audible prompts. The software library (below) uses this to generate keyclicks (a single keyclick for most keys, and a double click for the # key which I use as an "Enter" key).

The constructor for the class takes two arguments, one of which tells it where to find the buzzer. If you put in -1, it will turn sound off and never try to write to that pin.

Here is the circuit board design for those wanting to cut one at home:


Component Layout


PCB Design (Negative)

Here's a pdf of the pcb you can use to print onto film to photoetch the board.

And lastly some images of the finished board...


Etched PCB


Populated board - note I've changed the capacitor values since taking this photo, so the 0.1uF cap will be an unpolarised Tantulum or similar.


The populated board solder side up... I'm using metal stand offs, so don't forget to add paper washers if you're doing similar because the board design doesn't allow room for these to avoid track bridging.

Coding

I'vce written a small library to manage the board. The interface is as follows.

Interface Specs

Class: I2CDecodedKeypad

Constructor

I2CDecodedKeypad(int kbdAddr, int buzzAddr);

Sets up the library constants, but does not initialise the board.

  • kbdAddr: the 8 bit address of this board. This is a combination of a 3 bit device ID (corresponding to the state of pins 1,2 and 3 on the PCF8574) and the bus driver address (for the PCF8574 this is 00100). Use the formula (0x4<<3|ADR) to calculate this, where ADR is the 3 bit address (in my case 0x7).
  • buzzAddr: The pin of the Arduino you have a buzzer connected to. Use -1 if you have no buzzer to disable sound altogether.

init()

void I2CDecodedKeypad::init(void)

Initialises the board. You must have called Wire.begin() before you call init!

getKeyStroke()

char I2CDecodedKeypad::getKeyStroke(void)

Retrieves a keystroke from the board. If the value is 0, there has been no keypress. Otherwise it returns the ASCII code for the key pressed.

beep()

void I2CDecodedKeypad::beep(int on, int off, int reps)

Sounds the buzzer (if present).
  • on: the length of the beep in microseconds

  • off: the length of silence after the beep

  • reps: the number of times this cycle occurs.

beepOn()

void I2CDecodedKeypad::beepOn(void)

Enables keyclicks (if the constructor has been given the buzzer address).

beepOff()

void I2CDecodedKeypad::beepOff(void)

Disables keyclicks

Source Code

i2cdeckpd.h

#ifndef i2cdeckpd_h
#define i2cdeckpd_h

/* I2C DecodedKeypad
 * 
 * Library for interfacing to keypad using an I2C bus and a 74C922 
 * keypad decoder
 *
 * Copyright (c) 2009, A. Davison (software@davison-family.com)
 * All rights reserved.
 *
 * Source contributions from i2ckeypad by Angel Sancho (angelitodeb@gmail.com)
 *
 *
 *
 *  LICENSE
 *  -------
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *
 *  EXPLANATION
 *  -----------
 *  This library interfaces to a 4x4 keypad which has been decoded using
 *  a 74C922 keypad decoder, and then serialised using the PCF8574 I2C bus 
 *  expander.
 *
 *  Wiring diagrams for for the circuit can be found under the "hardware" 
 *  folder, along with a PDF circuit board design.
 *
 *  IMPORTANT! You have to call Wire.begin() before init() in your code
 *
 */

class I2CDecodedKeypad
{
	private: 
		int 	decoderDataAvailable;
		int	useBeep;
		static 	char	charSet[17];
		char	keypadAddr;
		char	buzzAddr;
	public:
			I2CDecodedKeypad(int,int);
		char 	getKeyStroke();
		void 	init(void);
		void	beepOn(void);
		void	beepOff(void);
		void	beep(int,int,int);
	private:
		void 	i2cWrite(int data);
		int 	i2cRead();


};	



#endif


#endif


i2cdeckpd.cpp

/* I2C DecodedKeypad
 * 
 * Library for interfacing to keypad using an I2C bus and a 74C922 
 * keypad decoder
 *
 * Copyright (c) 2009, A. Davison (software@davison-family.com)
 * All rights reserved.
 *
 * Source contributions from i2ckeypad by Angel Sancho (angelitodeb@gmail.com)
 *
 *
 *
 *  LICENSE
 *  -------
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *
 *  EXPLANATION
 *  -----------
 *  This library interfaces to a 4x4 keypad which has been decoded using
 *  a 74C922 keypad decoder, and then serialised using the PCF8574 I2C bus 
 *  expander.
 *
 *  Wiring diagrams for for the circuit can be found under the "hardware" 
 *  folder, along with a PDF circuit board design.
 *
 *  IMPORTANT! You have to call Wire.begin() before init() in your code
 *
 */

#include "i2cdeckpd.h"
#include <Wire.h>

extern "C"
{
	#include "WConstants.h"
}

#define KBDADDR (0x4<<3|0x7)

char I2CDecodedKeypad::getKeyStroke()
{
	int tmpKey;

	tmpKey = i2cRead();

	if ((decoderDataAvailable == 1)&&((tmpKey&0x10)==0))
	{
		// OK. A key has been pressed and released, so return the value.
		tmpKey&=0x0F;
		if (tmpKey==14)
		{
			beep(25,25,2);
		} 
		else
		{
			beep(25,00,1);
		}
		decoderDataAvailable=0;
		return charSet[tmpKey];
	}
	else if ((tmpKey&0x10)>0)
	{
		decoderDataAvailable=1;
	}
	// Otherwise nothing happened...
	return 0;

}

char I2CDecodedKeypad::charSet[17]="123A456B789C*0#D";

void
I2CDecodedKeypad::beep(int on, int off, int reps)
{
	if (useBeep>0)
	{
		while(reps>0)
		{
			digitalWrite(buzzAddr,HIGH);
			delay(on);
			digitalWrite(buzzAddr,LOW);
			delay(off);
			reps--;
		}
	}
}

void 
I2CDecodedKeypad::init(void)
{
	i2cWrite(0xff);
	if (buzzAddr >=0)
	{
		pinMode(buzzAddr,OUTPUT);
	}
}	

I2CDecodedKeypad::I2CDecodedKeypad(int kAddr, int bAddr)
{
	keypadAddr=kAddr;
	buzzAddr = bAddr;
	decoderDataAvailable=0;
	useBeep=1;
	if (buzzAddr==-1)
	{
		useBeep = 0;
	}

}

void
I2CDecodedKeypad::i2cWrite(int data)
{
	Wire.beginTransmission(keypadAddr);
	Wire.send(data);
	Wire.endTransmission();
}

int
I2CDecodedKeypad::i2cRead()
{
	Wire.requestFrom(keypadAddr,1);
	return Wire.receive();
}

void
I2CDecodedKeypad::beepOn(void)
{
	useBeep=1;
}
void
I2CDecodedKeypad::beepOff(void)
{
	useBeep=0;
}


test_i2cdeckpd.pde

// Code that uses the i2sdeckpd library to get keystrokes from a 4x4
// matrix keypad.
//
// My original source output the keystrokes on an LCD display. I've
// crudely hacked that around so that you just get a stream on the 
// serial output.
//
// Thanks to Angel Sancho (angelitodeb@gmail.com) for posting code that got 
// me started with i2c comms.
//
// Copyright (c) 2009, Andrew Davison (software@davison-family.com)
//



#include <Wire.h>
#include <i2cdeckpd.h>


//I2C Address for devices is 0 1 0 0 A2 A1 A0

#define KBDADDR (0x4<<3|0x7)

#define buzzerPin 13

I2CDecodedKeypad kpd(KBDADDR,buzzerPin);

void setup()
{
  Wire.begin();
  kpd.init();
}
int keyStrokes=0;

void loop()
{
  int key;

  key = kpd.getKeyStroke();
  if (key>0)
  {
    keyStrokes++;
      Serial.print (keyStrokes);
      Serial.print(" keys pressed.?n");
      Serial.print("keyval: ");
      Serial.print(key,BYTE);
      Serial.print("    ?n");
  }

  delay(50);  
}


Share