Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / IoT / Arduino

Keypad Scanning Hardware/Firmware

4.82/5 (7 votes)
14 Jan 2011CPOL7 min read 51.4K   688  
Scan any size switch matrix using 5 general purpose I/O ports.

Introduction

Many moons ago I used to do embedded programming for the automotive industry and really enjoyed it, but the time came that I had to make a career decision. I decided that there would be a better opportunity in other areas and moved to a location that I believed would afford me that chance. Many years have passed since, but about a year ago, I started seeing articles on micro controllers and embedded programming here on CodeProject and after corresponding with a couple of the authors, I decided that I would like to try and get back into the field I had abandoned so many years ago.

Setup

Once I decided on the hardware platform I was going to use, the next step was to set up the development environment. I researched and downloaded quite a few IDEs and for one reason or another, most just didn't give me that warm fuzzy until I discovered the AVR Studio IDE available [here]. It was the closest thing I could find that worked for me. AVR Studio along with the WinAVR tool chain provide the tools necessary to program in the C and ASM languages, and this is the part I really like: they are both free. The IDE provides one of the best debugging environments of all the IDEs I evaluated, and along with the AVR Dragon I purchased for $50 that does in circuit programming and debugging, I'm in hog heaven. As a side note, the AVR Dragon is one heck of a tool; get it [here].

Hardware

A common activity that you will likely run into when doing embedded programming is the need to scan a keypad, keyboard, or some kind of switch matrix, and as you will find the number of ports or lines needed to scan, these can add up very quickly, so what I am presenting in this article is a way to scan any size matrix type device using only 5 general purpose ports on the micro controller and just a few components. The hardware for this solution is controller independent, and the software can be modified to run on any processor that has general purpose I/O port capability.

For this project, I purchased a very inexpensive keypad from BGMicro that can be purchased [here] for the paltry sum of $5.10 USD and whose schematic representation is shown in the image that follows. The actual keypad is very similar in appearance to that shown in the image.

keypad-1-II.png

To build the hardware for this project, we just need: one serial to parallel shift register, I am using the 74HC595; one parallel to serial shift register, in this case the 74LS165 and 8 X 10K ohm resisters. I won't go into the specifics of how the shift registers operate, but will instead refer you to an excellent article written by a a fellow CPian, DaveAuld, that can be found [here].

The current implementation shown in the schematic that follows is suitable for up to an 8X8 matrix, but can be extended by daisy chaining the shift registers together in any configuration desired. The connector on the left of the schematic is wired to the micro controller and is not shown for brevity, but as stated earlier, may be any controller with 5 available general purpose ports. The connector on the right in the schematic is for the experimenters keypad that I am using.

keypad-II.png

Let's take a little closer look at the connector on the left in the schematic above that interfaces with the controller; the table that follows defines how each line is used.

PinDescription
5Clock line
4Data input line from the 74LS165
3Data output line to the 74HC595
274HC595 Latch - data output to parallel pins
174LS165 Load - data input from parallel pins

The 74HC595 serial in the parallel out shift register is used to pull the column lines to a high logic level one by one. As each column is pulled high, the 74LS165 parallel in serial out shift register loads the current row data in and shifts it out to the controller where it is checked to see if any of the lines are at a high positive level indicating that a key has been pressed. I originally had left out the resistor pack but learned a valuable lesson here about floating inputs and it didn't take me long to realize that the resistors were needed to pull the lines to a defined state. As a side note, the resistor pack may be replaced by individual 10K resistors; I just happened to have a resistor pack in stock.

As can be seen in the schematic above, the clock pin of both devices is tied together and is possible because the 74HC595 is being used as an output device and the 74LS165 as an input device so they do not interfere with each other. Also, the individual devices Latch and Load the data independently so this provides further independence.

The only thing remaining is a way to view the results of the scan; this can be done in any fashion you see fit and that your controller is capable of. In my case, I took advantage of the on chip UART feature and used TeraTerm on the PC to view the results.

Firmware

As I stated at the beginning of this article, I am using the WinAVR gcc "C" complier and the AVR studio IDE to create the firmware for this project. There's not a lot I can say about the firmware without doing a show and tell, so that's what we will do in this section.

We start out by defining what posts we will be using for the interface between the controller and the shift registers; see the connector table for a definition of each of the lines.

C++
#define CMD_PORT     DDRC
#define DATA_PORT     PORTC
#define PIN_PORT    PINC

#define CLOCK        PC5
#define MISO        PC4     //Master In Slave Out
#define MOSI        PC3     //Master Out Slave In
#define LATCH        PC2
#define LOAD        PC1

static char buffer[] = "Pressed: x";
//Lookup table W-Z are the function keys, couldn't think
//of anything else to represent them.
static char scan_table[5][5] = { { '<', '0', '1', '2', '3' },
                                { 'D', '4', '5', '6', '7' },
                                { '>', '8', '9', 'A', 'B' },
                                { 'E', 'C', 'D', 'E', 'F' },
                                { 'S', 'W', 'X', 'Y', 'Z' }};

typedef unsigned char byte;
typedef union ushort
{                       
    uint16_t Short;                          
    uint8_t Byte[2];
} ushort;

This table represents the actual scan codes for this particular keypad and is only given here for those using this keypad and alternately as a reference:

keypad_table-II.png

This is the main loop in the main routine and since this application does nothing else but service the keypad, we just poll and wait.

C++
while(1)
{
    //Scan keypad
    result = scan();

    //If a key was pressed convert values and do lookup
    if (result.Short > 0)
    {
        row = convert_result(result.Byte[0]);
        col = convert_result(result.Byte[1]);
        
        //Do lookup using column and row scan_code returned
        chr = scan_table[col][row];

        buffer[9] = chr;
        
        //Send result via UART to PC
        SendDataPacket(buffer, 10);
    }
    _delay_ms(200);
}

The listing below is the heart of the code and its purpose is to make one complete scan of the keypad by using a mask to pull each column high one by one and shift in the row data checking it for a key press. If there is, then the processing is done and the press is decoded and the scan_code value returned to the main routine.

C++
/* -- scan ---------------------------------------------------------------
**
**    Description: Makes one complete scan of the keypad
**
**    Params:    None
**    Returns: ushort - 2 bit scan code made up of row - low byte and 
**      column high byte.
** -----------------------------------------------------------------------*/
ushort scan()
{
    //Uses a mask to pull individual columns high
    //  1000 0000   1st pass
    //  0100 0000   
    //  0010 0000   
    //  0001 0000   
    //  0000 1000   Last pass since there are 5 columns
    byte col_mask = 0x80;
    byte last_col = 0x04;
    
    volatile byte row = 0;
    ushort scan_code;

    scan_code.Short = 0;
    for(byte i = 0; i < 8; i++)
    {
        //Shift out column data to the 74595
        output_data(col_mask);

        //Shift in row data from the 74165
        row = input_data();
        if (result != 0)
        {
            scan_code.Byte[0] = row; 
            scan_code.Byte[1] = col_mask;
            break;
        }
        if (col_mask == last_col)
            col_mask = 0x80;
        else
            col_mask >>= 1;
    }
    return scan_code;
}

This routine is used to clock data out to the 74HC595 shift register, and reading through the code, we first bring the Latch to a low level, effectively disabling the output. Contents of the shift register are transferred to output latches on the rising edge so when we are done shifting out our data, we bring this line high. To transfer the data, we start with the most significant bit and set the output port appropriately, toggling the clock after each bit is set, this is called bit banging and is a pretty common practice. If we had used the SPI interface provided on the ATMega328, all we would have had to do was to set the SPI data port with the byte value and wait for it to complete; all shifting is done automatically and has various options that can be set as to whether to send the MSB or LSB first, clock on rising or falling edges, etc..

C++
/* -- output_data --------------------------------------------------------
**
**    Description: Transfers one byte of data to 74HC595
**
**    Params:    byte - data to send
**    Returns: None
** -----------------------------------------------------------------------*/
void output_data(byte data)
{
    //Bring the Latch low
    DATA_PORT &= ~_BV(LATCH);

    //Shift data bits out one at a time
    for (byte i = 0; i < 8;i++)
    {
        //Set the data bit 0 or 1
        if ((data & 0x80) == 0x80)
            DATA_PORT |= _BV(MOSI);
        else
            DATA_PORT &= ~_BV(MOSI);

        //Clock the bit into the shift register
        DATA_PORT &= ~_BV(CLOCK);
        DATA_PORT |= _BV(CLOCK);

        data <<= 1;
    }
    //Latch the data we just sent
    DATA_PORT |= _BV(LATCH);
}

To input the row data, we use similar logic to that used to shift it out, but in this case, we read the value bit by bit and shift it into the result to be returned.

C++
/* -- input_data --------------------------------------------------------
**
**    Description: Shifts one byte of data from 74LS165
**
**    Params:    None
**    Returns: byte - data returned from shift register
** -----------------------------------------------------------------------*/
byte input_data()
{
    volatile byte result = 0;

    //Load the parallel data into the shift register
    DATA_PORT &= ~_BV(LOAD);
    DATA_PORT |= _BV(LOAD);

    //Shift data bits in one at a time
    for (byte i = 0; i < 8;i++)
    {        
        result <<= 1;

        if ((PINC & 0x10) == 0x10)
            result |= 0x01;        

        //Clock the bit into the shift register
        DATA_PORT &= ~_BV(CLOCK);
        DATA_PORT |= _BV(CLOCK);
    }
    return result;
}

This final routine is used to convert the scan code to a value that can be used to do a lookup to retrieve the keypad code.

C++
/* -- convert_result -----------------------------------------------------
**
**    Description: Converts the bit position to a number for lookup
**
**    Params:    byte - number to be converted
**    Returns: byte - converted value
** -----------------------------------------------------------------------*/
byte convert_result(byte data)
{
    byte value = 0;
    
    do
    {
        if (data & 1)
            break;

        ++value;
        data >>= 1;

    } while(value < 8);

    return value;
}

Conclusion

Shift registers are handy little devices and I have just recently started uncovering their full potential. Used in this application, these two types of shift registers play very nicely together and serve to provide a solution to reduce the number of pins needed to scan a keypad. I've learned a lot from this project, and with every step it becomes a little easier to understand how components may be used like building blocks that you can combine to create or solve a particular problem.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)