Matrix Scanning

Note:  If you are seeing this note, then you are looking at an incomplete draft.  Keep checking back as I complete these series of blog posts over the next week or two.

Lately, I have had more of a need for custom input devices, like connecting an two 8-way joysticks and some buttons to my MAME cabinet or converting a really cool 1990s HOTAS controller setup from using a gameport to USB.  Soon, I will need to interface around 100 switches and buttons for my flight simulator cabinet in progress.  Why not use a commercial solution, or hack an existing joystick or keyboard?   It is far more expensive, less flexible and I wouldn’t learn anything in the process.

In any embedded project, one of the biggest constraints is the number of available IO pins.  Using one input pin per switch would be extremely wasteful. Let’s say we have 16 switches as shown:

There is a significant cost savings if we’re willing to trade off pin usage for a little extra latency and complexity in hardware and software.  The way this is done is by multiplexing the switches into a two dimensional matrix, scanning through columns and reading off the rows (or vise-versa.)

The matrix is scanned by pulling each column low in succession (with all others floating tri-stated) and reading the corresponding rows for that column.  If a switch on the selected column is open, its row input will read high due to the pullup resistor, otherwise it will read as a low.  Some microcontrollers have weak pullup resistors that can be enabled, obviating the external pullups shown.

Roughly speaking, the number of pins needed will be twice the square root of the number of switches.  For 16 switches, the number of IO pins needed is down from 16 to just 8 (four rows, four columns.)  That’s a decent improvement, but the advantage is more obvious for 101 key keyboard, which would only need 21 pins.  The main tradeoff is that it takes increasingly longer to scan through the matrix as the number of columns increases.

This approach works well as long as one switch is pressed at a time.  Pressing multiple switches at once can lead to ghosting and masking. <read more>

Here is a rough outline of what the matrix scanning code might look like in C.  The application would call initMatrix() at startup, and then periodically call scanMatrix() to scan the matrix.  Of course to read the status of a particular switch at (row, col), the application would call the function buttonPressed(row, col).

#define NUM_ROWS (n)
#define NUM_COLS (m)

uint8_t keyMatrix[NUM_ROWS][NUM_COLS];

uint8_t buttonPressed(uint8_t row, uint8_t col)
{
    return keyMatrix[row][col];
}

void deselectCols(void)
{
    // This has the effect of turning off the
    // previously selected column 
    TRIS_COL0_SELECT = 1;
    ...
    TRIS_COL(n)_SELECT = 1;
}

void selectCol(uint8_t col)
{
    deselectCols();
    
    // col latch should already be set to low,
    // so just enable the driver out of
    // tri-state mode so it can pull the column
    // down.
    switch (col)
    {
        case 0:
            TRIS_COL0_SELECT = 0;
            break;
        ...
           
        case n:
            TRIS_COL(n)_SELECT = 0;
            break;
    }
}

void readRows(uint8_t col)
{
    // Read Rows.  Switches will be active low,
    // so invert the reading.
    keyMatrix[0][col] = !ROW0_INPUT;
    keyMatrix[1][col] = !ROW1_INPUT;
    ...
    keyMatrix[m][col] = !ROW(m)_INPUT;

}

//call every millisecond or so.
void scanMatrix(void)
{
    for (uint8_t i =0; i < NUM_COLS; i++)
    {
        selectCol(i);

	// Give time for voltage levels to stabilize
        __delay_us(50);      
        readRows(i);
        deselectCols();
    }
}

void initMatrixIO(void)
{
    //configure col output pins
    
    //The latch needs to remain low for the rest
    // of the code to work.  This way they can
    // pull columns low when no longer in
    // tri-state.
    COL0_SELECT = 0;
	...
    COL(n)_SELECT = 0;
    deselectCols();

    (configure row input pins)
    (IE: tristate, enable pullups);
    
}

void initMatrix(void)
{
    initMatrixIO();
    
    for (uint8_t i = 0; i < NUM_ROWS; i++)
        for (uint8_t j = 0; j < NUM_COLS; j++)
            keyMatrix[i][j] = 0;
            
}

For a real world example using matrix scanning for a USB joystick, check this<link> out.

 

Leave a Reply

Your email address will not be published. Required fields are marked *