Keypad decoder IC

I'm currently using a 74C923 to decode a 3x5 key keypad (ignoring the 4th column). But I think this IC is becoming obsolete, so before I design a PCB and get quite a few made, I would like to know if there is an alternative IC?

I'm passing the data back to a PCF8574 port expander.

Has been considered obsolete, like a lot of the other 74C9xx series parts for about 20 years.

Sure, you can find them if you look... however... I would not create a design that goes into production with them.

These (C9xx) series were a very purpose built set of parts and therefore alternatives are quite limited. you CAN use an arduino to directly emulate what the 922 or 923 does.

Could you direct me to a URL showing how to emulate what that obsolete IC does? Many thanks.

You can use a pcf8574 to scan the keypad directly, up to 4x4 keys, or 3x5. No other components needed. I coded this before, so I can give you some code fragments.

The way I coded it, the Arduino can tell, with just one read operation, if any keys are pressed. If keys are pressed, then 3 or 4 more write+read operations can scan the keypad.

PaulRB:
You can use a pcf8574 to scan the keypad directly, up to 4x4 keys, or 3x5. No other components needed. I coded this before, so I can give you some code fragments.

The way I coded it, the Arduino can tell, with just one read operation, if any keys are pressed. If keys are pressed, then 3 or 4 more write+read operations can scan the keypad.

Paul,

If you could show me an example of the code using this IC that would be great. Ages ago when I was reading the keypads directly I was getting interference in the rest of my circuit from the kaypads, that's why I sarted using the 74C923's via PCF8574.

AndyInSurrey:
Ages ago when I was reading the keypads directly I was getting interference in the rest of my circuit from the kaypads, that's why I sarted using the 74C923's via PCF8574.

What was the rest of your circuit? I don't see how using that combination of chips would be immune to interference that affected either chip on is own.

The rest of the circuit consisted of 24 3x4 7-segment common cathode LEDs and 12 MAX7219's with appropriate resistors and capicitors.

And the keypad did not suffer from interference if the display circuit was disconnected? Sounds like lack of bypass caps to me. Each max7219 should have a 0.1uF bypass close to its power pins, and a 10uF reservoir cap also. The pcf chip should also have a 0.1uF bypass cap. When you say "appropriate caps", did you have all those?

For scanning a 4x4 keypad with a pcf8574, there is some code on the Playground. If you need help adapting that for 5x3 keys, post your attempt and I can take a look. Use code tags!

Thanks Paul, I'll take a look at the weekend when I'll next have a chance to do any of this.

PaulRB:
And the keypad did not suffer from interference if the display circuit was disconnected? Sounds like lack of bypass caps to me. Each max7219 should have a 0.1uF bypass close to its power pins, and a 10uF reservoir cap also. The pcf chip should also have a 0.1uF bypass cap. When you say "appropriate caps", did you have all those?

For scanning a 4x4 keypad with a pcf8574, there is some code on the Playground. If you need help adapting that for 5x3 keys, post your attempt and I can take a look. Use code tags!

I've been thinking about what the code would look like for a 5x3 keypad (5 rows & 3 columns). I'll attempt to adjust your code on Saturday for a 5x3 keypad, but in the meantime could you explain the significance of the pcf8574_row_data array? I can see that you have assigned the following values to it:

pcf8574[0] -> 1111 1110 // 0xfe    254
pcf8574[1] -> 1011 1110 // 0xbe   190
pcf8574[2] -> 1101 1110 // 0xde   222
pcf8574[3] -> 1111 0110 // 0xf6    246

It appears to be a mask for the four rows but I'm confused by either the lower bytes & upper bytes being the same between rows.

I didn't write that code. I just found it on the playground for you. It's not written how I wrote my code, to me it seems more complicated than it needs to be. But I cannot locate the code fragments you asked about in it.

I've worked it out.

Say we have a 3x5 keypad, and we have the pins connected as follows:

Col#0 -> P#0
Col#1 -> P#1
Col#2 -> P#2
Row#0 -> P#3
Row#1 -> P#4
Row#2 -> P#5
Row#3 -> P#6
Row#4 -> P#7

Iterate through each row, and for each row do:

Send a 1 to all three columns and a 0 to the row we're testing for and a 1 for the other, i.e. send a binary bit pattern.

Read the PCF8574 and receive the bit pattern, then do a BITWISE AND against:

The column on which the key pressed is held on will have had its 1 bit changed to a 0 bit.

So for each key read, iterate through each of the three columns and do a BITWISE AND for the value read against each of the following values:

00000001 - to test for Col#0
00000010 - to test for Col#1
00000100 - to test for Col#2

If the result == 0 then we have found the column on which the key pressed is held on.

For example, if we pressed key on Col#1 Row#0, so we send the bit pattern: 11110111 to the PCG8574 and receive the bit pattern back as: 11110101. We do a BITWISE AND against 0x01 for Col#0 which yields 1, then we do a BITWISE AND against 0x02 for Col#1 which yields 0, and a BITWISE AND against 0x04 which yields 100.

We would have stopped checking when our result == 0 corresponding to Col#1 and as we were testing for Row#0 we then know that the key pressed is on Col#1 Row#0.

BTW, 0x01 == BINARY 00000001
0x02 == BINARY 00000010
0x04 == BINARY 00000100

Yes, you've got the right idea. Let's see some code!

Paul, I'll write the code on Saturday. This was just theory worked out while at work. I'm quite confident I can get it to work. Will post my code on here on Saturday.

Well here's my code for my project.

I've got an 8-digit 7-segment display to show the numbers. I could have used a 1-digit 7-segment but I want my eventual project to contain quite a few 7-segment displays. Plus a 5x3 keypad connected via a PCF8574 IC.

keypad_test.ino:

#include "Display.h"
#include "Keypad.h"

#define DIN_PIN       5
#define CS_PIN        6
#define CLK_PIN       7
#define NUM_DISPLAYS  1

#define KEYPAD_ADDR   0x20  // PCF8574 A0, A1, and A2 all grounded

CDisplay m_display(DIN_PIN, CLK_PIN, CS_PIN, NUM_DISPLAYS);
CKeypad m_keypad(KEYPAD_ADDR);

void setup() 
{
  m_display.Init();
  m_keypad.Init();

  for (long num = 0; num < 10; ++num)
  {
    m_display.ShowNumber(1, num);
    delay(500);
  }
}

void loop() 
{
  /*for (long num = 0; num < 1000000; ++num)
  {
    m_display.ShowNumber(1, num);
    delay(500);
  }

  m_display.ClearDisplay(1);*/

  char key = m_keypad.GetKey();

  if (key)
  {
    if (isdigit(key))
    {
      long number = atol(key);
      m_display.ShowNumber(1, number);
    }
  }
}

Display.h:

class LedControl;


class CDisplay
{
  public:
    CDisplay(const int dataPin, const int clkPin, const int csPin, const int numDevices);
    ~CDisplay();

    void Init();
    void ShowNumber(int deviceId, unsigned long number);
    void ClearDisplay(int deviceId);

  private:
    void printNumber(int addr, unsigned long number);
    unsigned int didgitCount(const unsigned long num, const unsigned int max);
    
    const int m_nDataPin;
    const int m_nClkPin;
    const int m_nCsPin;
    const int m_nNumDevices;
    LedControl* m_pLC;
};

Display.cpp:

#include "Display.h"
#include <LedControl.h>


CDisplay::CDisplay(int dataPin, int clkPin, int csPin, int numDevices)
  : m_nDataPin(dataPin)
  , m_nClkPin(clkPin)
  , m_nCsPin(csPin)
  , m_nNumDevices(numDevices)
{
  m_pLC = new LedControl(dataPin, clkPin, csPin, numDevices);
}


CDisplay::~CDisplay()
{
  delete m_pLC;
}


void CDisplay::Init()
{
  for (int addr = 0; addr < m_pLC->getDeviceCount(); ++addr)
  {
    // Initialize the module 
    m_pLC->shutdown(addr , false);
    // display brightness adjustment 
    m_pLC->setIntensity(addr , 0x06);
    // Delete the display 
    m_pLC->clearDisplay(addr);
  }
}


void CDisplay::ShowNumber(int deviceId, unsigned long number)
{
  printNumber(deviceId-1, number);
}


void CDisplay::ClearDisplay(int deviceId)
{
  m_pLC->clearDisplay(deviceId-1);
}


void CDisplay::printNumber(int addr, unsigned long number)
{
  unsigned int digits = didgitCount(number, 8);
  // Calculate the value of each digit 
  int digit1 = number % 10 ;
  int digit2 = (number / 10)% 10 ;
  int digit3 = (number / 100)% 10 ;  
  int digit4 = (number / 1000)% 10 ;
  int digit5 = (number / 10000)% 10 ;
  int digit6 = (number / 100000)% 10 ;
  int digit7 = (number / 1000000)% 10 ;
  int digit8 = (number / 10000000)% 10 ;
  
  // Display the value of each digit in the display 
  switch (digits)
  {
    case 8:
      m_pLC->setDigit(addr, 7, (byte)digit8, false);
    case 7:
      m_pLC->setDigit(addr, 6, (byte)digit7, false);
    case 6:
      m_pLC->setDigit(addr, 5, (byte)digit6, false);
    case 5:
      m_pLC->setDigit(addr, 4, (byte)digit5, false);
    case 4:
      m_pLC->setDigit(addr, 3, (byte)digit4, false);
    case 3:
      m_pLC->setDigit(addr, 2, (byte)digit3, false);
    case 2:
      m_pLC->setDigit(addr, 1, (byte)digit2, false);
    case 1:
      m_pLC->setDigit(addr, 0, (byte)digit1, false);
  }
}


//
//  Returns the number of digits that need to
//  be lit up on the LED, to show num
//
unsigned int CDisplay::didgitCount(const unsigned long num, const unsigned int max)
{
  if (num == 0 || num == 1) return 1;
  if (num == 10) return 2;
    
    unsigned int count(max);

    for (; count > 0; --count)
    {
        if (pow((double)10, (double)(count - 1)) < num)
            break;
    }

    return count;
}

Keypad.h:

#include <Arduino.h>

#define ROWS 5
#define COLS 3

class CKeypad
{
  public:
    CKeypad(const byte address);
    ~CKeypad();

    void Init();
    char GetKey();

  private:
    byte Read();
    void Write(byte);

    const byte m_addr;    
    
    byte m_rows[ROWS];
    byte m_cols[COLS];
    char m_keypad[ROWS][COLS];
};

Keypad.cpp:

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



CKeypad::CKeypad(const byte address)
  : m_addr(address)
{
  m_rows[0] = B11110111;
  m_rows[1] = B11101111;
  m_rows[2] = B11011111;
  m_rows[3] = B10111111;
  m_rows[4] = B01111111;

  m_cols[0] = B00000001;
  m_cols[1] = B00000010;
  m_cols[2] = B00000100;

  m_keypad[0][0] = 'C';
  m_keypad[0][1] = 'M';
  m_keypad[0][2] = 'N';
  m_keypad[1][0] = '7';
  m_keypad[1][1] = '8';
  m_keypad[1][2] = '9';
  m_keypad[2][0] = '4';
  m_keypad[2][1] = '5';
  m_keypad[2][2] = '6';
  m_keypad[3][0] = '1';
  m_keypad[3][1] = '2';
  m_keypad[3][2] = '3';
  m_keypad[4][0] = '*';
  m_keypad[4][1] = '0';
  m_keypad[4][2] = '-';
}


CKeypad::~CKeypad()
{
  
}


void CKeypad::Init()
{
  Wire.begin();
}

char CKeypad::GetKey()
{
  char key = '\0';
  
  for (int row = 0; row < ROWS && !key; ++row)
  {
    Write(m_rows[row]);
    byte receive = Read();

    if (receive != m_rows[row])
    {
      for (int col = 0; col < COLS && !key ; ++col)
      {
        if (m_cols[col] & receive == 0)
        {
          key = m_keypad[row][col];
        }
      }
    }
  }

  return key;
}


byte CKeypad::Read()
{
  byte data;
  Wire.requestFrom(m_addr, 1);

  if (Wire.available())
  {
    data = Wire.read();
  }

  return data;
}


void CKeypad::Write(byte data)
{
  Wire.beginTransmission(m_addr);
  Wire.write(data);
  Wire.endTransmission();
}

If I just run the code without connecting the breadboard with the PCF8574 and keypad connected, and enable the code to loop through numbers 0 to 1000000, the 7-segment will happily show the numbers. But if I then disable that part of the program and enable the program to read the key press and display the number accordingly and connect the 7-segment to the breadboard and the Arduino to the breadboard then the display will just show complete random data!! This is why a year or so back when I last looked at creating this project I started to use the 74C922 keypad decoder.

I'll draw up a schematic and post that here if that helps. I'm at a loss at the moment again.

I've changed the code slightly in the CKeypad::GetKey() functions to:

char CKeypad::GetKey()
{
  char key = 'x';
  
  for (int row = 0; row < ROWS && key == 'x'; ++row)
  {
    Write(m_rows[row]);
    byte receive = Read();

    if (receive != m_rows[row])
    {
      for (int col = 0; col < COLS && key == 'x' ; ++col)
      {      
        if (m_cols[col] & receive == 0)
        {
          key = m_keypad[row][col];
          Serial.print("Row: ");
          Serial.println(row);
          Serial.print("Col: ");
          Serial.println(col);
          Serial.println(key);
        }
      }
    }
  }

  return key;
}

and in the main program code setup to add the Serial coms:

void setup() 
{
  Serial.begin(9600);
  m_display.Init();
  m_keypad.Init();

  for (long num = 0; num < 10; ++num)
  {
    m_display.ShowNumber(1, num);
    delay(500);
  }

  m_display.ClearDisplay(1);
}

and in the main program loop:

void loop() 
{
  /*for (long num = 0; num < 1000000; ++num)
  {
    m_display.ShowNumber(1, num);
    delay(500);
  }

  m_display.ClearDisplay(1);*/

  char key = m_keypad.GetKey();

  if (key != 'x')
  {
    if (key == 'C')
    {
      Serial.println("Cancel key pressed");
    }
    else if (key == 'M')
    {
      Serial.println("'M' key pressed");
    }
    else if (key == 'N')
    {
      Serial.println("'N' key pressed");
    }
    else if (key == '*')
    {
      Serial.println("'*' key pressed");
    }
    else if (key = '-')
    {
      Serial.println("'minus key pressed");
    }
    else if (isdigit(key))
    {
      long number = atol(key);
      m_display.ShowNumber(1, number);
    }
  }
}

The problem I've got is that code just returns a 'C' all the time for the key pressed even when no key has been pressed.

  m_keypad[0][0] = 'C';

The 'C' code is the first one the code checks for. Maybe it looks like every key is pressed?

Put some Serial.print() lines in to help with debugging. Each byte sent to the pcf chip, each byte received, start/end of scan, key presses detected, that sort of thing. Put a 2s delay after each scan so you have time to press & hold down or release a key before the next scan. Post some results.

Changed my CKeypad::GetKey() function to add some Serial output for debugging:

char CKeypad::GetKey()
{
  char key = 'x';
  
  for (int row = 0; row < ROWS && key == 'x'; ++row)
  {
    Serial.print("Bit pattern sent: ");
    Serial.println(btoa(m_rows[row]));
    
    Write(m_rows[row]);
    byte receive = Read();

    Serial.print("Bit pattern received: ");
    Serial.println(btoa(receive));

    if (receive != 0 && receive != m_rows[row])
    {
      for (int col = 0; col < COLS && key == 'x' ; ++col)
      {      
        if (m_cols[col] & receive == 0)
        {
          key = m_keypad[row][col];
          Serial.print("Row: ");
          Serial.println(row);
          Serial.print("Col: ");
          Serial.println(col);
          Serial.println(key);
        }
      }
    }
  }

  return key;
}

Added a 2s delay in main loop:

and this was the output despite me pressing keys:

Bit pattern sent: 11110111
Bit pattern received: 00000000
Bit pattern sent: 11101111
Bit pattern received: 00000000
Bit pattern sent: 11011111
Bit pattern received: 00000000
Bit pattern sent: 10111111
Bit pattern received: 00000000
Bit pattern sent: 01111111
Bit pattern received: 00000000
Bit pattern sent: 11110111
Bit pattern received: 00000000

As you can see I'm always getting B00000000 back!!

Something is wrong. You should not be getting B00000000 back. At the very least, you should be getting "1" corresponding to the other 4 rows you are writing "1" to...

Have you tried the "bog-standard" Arduino i2c scanner sketch, just to confirm that the Arduino can see the pcf chip at the expected address? Google for it. Do you have pull-up resistors on the sda/scl lines or are you relying on the internal pull-ups? The internal pull-ups are usually enough, but try the external ones anyway. 4K7 is normally fine.

Do you have a 0.1uF bypass cap on the pcf chip?

I'll take another look at it later in the week. I think I've probably got the column & rows mapping wrong.