4×4 Keyboard Driver Development
The driver should scan any keyboard matrix arrangement up to an 8×8 matrix, but should easily be modified to handle a larger number of keys. The keyboard driver should have the following features:
- Scan any keyboard arrangement from a 4×4 to an 8×8 key matrix.
- Provide buffering (user configurable buffer size).
- Support auto-repeat.
- Keep track of how long a key has been pressed.
- Takes care of key de-bouncing
- The keyboard driver should assume the presence of a real-time kernel but should easily be modified to work in a foreground/background environment.
Keyboard Switch
A momentary contact switch is typically used in a keyboard, and a closure can easily be detected by a microprocessor using the simple circuit shown in the figure. The pull-up resistor provides a logic 1 when the switch is opened and a logic 0 when the switch is closed.
Unfortunately, switches are not perfect in that they do not generate a perfect 1 or 0 when they are pressed or released. Although a contact may appear to close firmly and quickly, at the fast running speed of a microprocessor, the action is comparatively slow. As the contact closes, the contact bounces like a ball. This bouncing effect produces multiple pulses as shown in the figure. The duration of the bounce typically will last between 5 and 50 mS.
The most efficient way to layout the switches in a keyboard (when more than five keys are needed) is to form a two-dimensional matrix as shown in Figure. The most optimum arrangement (where I/O lines are concerned) occurs when there are as many rows as columns, that is, a square matrix. A momentary contact switch (push button) is placed at the intersection of each row and column. Each row is driven by a bit of an output port, while each column is pulled up by a resistor and fed to a bit on an input port.
Keyboard Matrix
Keyboard Scanning
Keyboard scanning is the process of having the microprocessor look at the keyboard matrix at a regular interval to see if a key has been pressed. Once the processor determines that a key has been pressed, the keyboard scanning software filters out the bounce and determines which of the keys was pressed. Each key is assigned a unique identifier called a scan code. The scan code is used by the application to determine what action is to be taken based on the key pressed. In other words, the scan code tells the application which key was pressed.
During initialization, all rows (output port) are forced low. When no key is pressed, all columns (input port) read high. Any key closure will cause one of the columns to go low. To see if a key has been pressed, the microprocessor only needs to see if any of the input lines are low. Once the microprocessor has detected that a key has been pressed, it needs to find out which key it was. The microprocessor outputs a low on only one of the rows. If it finds a 0 on the input port, the microprocessor knows that the key closure occurred on the selected row. Conversely, if the input port had all highs, the key pressed was not on that row and the microprocessor selects the next row, repeating the process until it finds the row. Once the row has been identified, the specific column of the pressed key can be established by locating the position of the single low bit on the input port.
To filter through the bouncing problem, the microprocessor samples the keyboard at regular intervals, typically between 20 mS and 100 mS (called the debounce period) depending on the bounce characteristics of the switches being used. The scan code of the key pressed is typically placed in a buffer until the application is ready to process a keystroke. Buffering is a handy feature because it prevents losing keystrokes when the application cannot process them as they occur.
Matrix keyboard driver flow diagram
The following figure shows a flow diagram of the matrix keyboard module. The keyboard scanning module only makes use of two kernel services: semaphores and time delays. A single task, KeyScanTask( ), is responsible for scanning the keyboard. KeyScanTask() is created when the application calls KeyInit(). Once created, KeyScanTask() executes every KEY_SCAN_TASK_DLY milliseconds. KEY_SCAN_TASK_DLY should be set to produce a scan rate between 10 and 50 Hz.
Matrix keyboard driver state machine
Initially, the state machine is in the KEY_STATE_UP state. When a key is pressed, the state of the state machine changes to KEY_STATE_DEBOUNCE, which will execute KEY_SCAN_TASK_DLY milliseconds later.
After the delay, KeyScanTask() executes the code in the KEY_STATE_DEBOUNCE state, which again checks to see if the key is pressed. The state machine returns to the KEY_STATE_UP state if the key is released. If the key is still pressed, however, the scan code is found by calling KeyDecode() and inserted in the circular buffer through KeyBufIn( ). KeyBufIn() discards the scan code if the buffer is already full. KeyBufIn() also signals the keyboard semaphore, allowing the application to obtain the scan code of the key through KeyGetKey(). The state machine is then changed to the KEY_STATE_RPT_START_DLY state.
The auto-repeat function will engage if the key is pressed for more than KEY_RPT_START_DLY scan times. In this case, the scan code is inserted in the buffer and the state is changed to the KEY_STATE_RPT_DLY state. If the key is no longer pressed, the state of the state machine is changed to the KEY_STATE_DEBOUNCE state to debounce the released key.
After a scan period, KeyScanTask() executes the code in the KEY_STATE_RPT_DLY state, where the scan code for a pressed key will be inserted into the buffer every KEY_RPT_DLY scan times. As with the other states, debouncing will be required if the key is released.
The state machine is executed every debounce period. Only one of the four states is executed every KEY_SCAN_TASK_DLY milliseconds.
Block Diagram
The following figure shows a block diagram of the matrix keyboard module. The application knows about the keyboard module only through five functions: KeyFlush( ), KeyGetKey( ), KeyGetKeyDownTirne( ) , KeyHit( ), and Keylnit( ) .
Application Interface Functions
KeyInit()
void Keylnit(void);
Keylnit() is the initialization code for the driver module and it must be called before we invoke any of the other functions. Keylnit() is responsible for initializing internal variables used by the module, initializing the hardware ports, and creating a task that will be responsible for scanning the keyboard.
KeyFlush()
void KeyFlush(void);
The matrix keyboard module buffers user keystrokes until they are consumed by the application. In some instances, it may be useful to flush the buffer and start with fresh user input. In other words, you may want to throwaway previously accumulated keystrokes and start with an empty keyboard buffer. You can accomplish this by calling KeyFlush().
KeyGetKey()
uint8_t KeyGetKey(uint16_t to);
KeyGetKey() is called by the application to obtain a scan code from the keyboard module. If a key has not been pressed, the calling task will be suspended until the user presses a key or until a user-specified timeout expires; the timeout is passed as an argument to KeyGetKey(). If a timeout occurs, KeyGetKey() returns OxFF.
KeyGetKeyDownTime()
uint16_t KeyGetKeyDownTilne(void);
KeyGetKeyDownTime() returns the amount of time (in milliseconds) that a key has been pressed. This function is useful to speed up the process of incrementing or decrementing the value of a parameter based on how long a key has been pressed. The key down time is not cleared when the pressed key is released. Instead, the key down time is reset only when the next key is pressed. In other words, you can always obtain the amount of time that the last key was pressed.
KeyHit()
bool KeyHit(void);
KeyHit() allows the application to determine if a key has been pressed. Unlike KeyGetKey(), KeyHit() does not suspend the caller. KeyHit() immediately returns TRUE if a key was pressed and FALSE otherwise.
Hardware Interface functions
To make this module as portable as possible, access to hardware ports has to be isolated into three functions:
- KeylnitPort(),
- KeylnitPort() is responsible for initializing the I/O ports used for the rows and columns. KeylnitPort() should be called by Keylnit().
- KeySelRow(), and
- KeySelRow() is used to select rows. KeySelRow() expects a single argument that can either be KEY_ALL_ROWS (to force all rows low) or a number between 0 and 3 (to force a specific row low).
- KeyGetCol().
- KeyGetCol() reads and returns the complement of the columns input port (a 1 indicates a key pressed).
Code Snippets
#define KEYPAD_BUF_SIZE 16 /* Size of the keyboard buffer */ #define KEYPAD_MAX_ROWS 4 /* The maximum number of rows on the keyboard */ #define KEYPAD_MAX_COLS 4 /* The maximum number of columns on the keyboard */ #define KEYPAD_RPT_DLY 5 /* Number of Scan times before auto repeat executes again */ #define KEYAPD_RPT_START_DLY 10 /* Number of Scan times before auto repeat function engages */ #define KEYPAD_SCAN_TASK_DLY 50 /* Number of milliseconds between keyboard scans */ #define KEY_STATE_UP 1 /* key scanning states used in key_scan_task */ #define KEY_STATE_DEBOUNCE 2 #define KEY_STATE_RPT_START_DLY 3 #define KEY_STATE_RPT_DLY 4 /* * Global Variables * */ static uint8_t KeyBuf[KEYPAD_BUF_SIZE]; /* keyboard buffer */ static int16_t KeyBufInIndx; /* Index into key buff where next scan code will be inserted */ static int16_t KeyBufOutIndx; /* Index into key buff where next scan code will be removed */ static uint16_t KeyDownTimer; /* Counts how long key has been pressed */ static int16_t KeyNRead; /* Number of keys read from the keyboard */ static int16_t KeyRptStartDlyCtr; /* Number of scan times before auto repeat started */ static int16_t KeyRptDlyCtr; /* Number of scan times before auto repeat executes */ static int16_t KeyScanState; /* Current state of key scanning function */ /* * Pseudocode for Keyboard scanning Task (KeyScanTask) * */ void KeyScanTask(uint32_t entry_param) { uint8_t code; while( 1 ) { Yield Task for every 50 mili second; /* atomTimerDelay(MS_TO_TICKS(50)); */ switch(KeyScanState) { case KEY_STATE_UP: /* see if need to look for a key pressed */ If( key is Pressed ) { KeyScanState = KEY_STATE_DEBOUNCE; /* Next Call we will have de-bounced the key */ KeyDownTimer = 0; } break; case KEY_STATE_DEBOUNCE: /* key pressed, get scan code and buffer */ if( If any key is pressed ) { De-Select All Rows; code = Determine key scan code; Store scan code in buffer; KeyRptStartDlyCtr = KEYAPD_RPT_START_DLY; /* start delay to auto-repeat function */ KeyScanState = KEY_STATE_RPT_START_DLY; } else { Select All rows; KeyScanState = KEY_STATE_UP; /* key was not pressed after all */ } break; case KEY_STATE_RPT_START_DLY: if( Key is still pressed ) { if( KeyRptStartDlyCtr > 0 ) { /* see if we need to delay before auto-repeat */ KeyRptStartDlyCtr--; /* yes. decrement counter to start of repeat */ if( KeyRptStartDlyCtr == 0 ) { /* if delay to auto repeat is completed ... */ De-Select All Rows; code = Determine the key scan code; Store scan code in buffer; KeyRptDlyCtr = KEYPAD_RPT_DLY; /* Load delay before next repeat */ KeyScanState = KEY_STATE_RPT_DLY; } } } else { KeyScanState = KEY_STATE_DEBOUNCE; /* key was not pressed after all */ } break; case KEY_STATE_RPT_DLY: if( key is still pressed ) { if( KeyRptDlyCtr > 0 ) { /* see if we need to wait before repeat key */ KeyRptDlyCtr--; /* yes. decrement wait time to next key repeat */ if( KeyRptDlyCtr == 0 ) { /* see if it is time to repeat key */ De-Select All Rows; code = Determine the key scan code; Store scan code in buffer; KeyRptDlyCtr = KEYPAD_RPT_DLY; } } } else { KeyScanState = KEY_STATE_DEBOUNCE; /* key was not pressed after all */ } break; } } }
Recent Comments