keypad.c - Implements a 4x3 key scanned keypad interface.ΒΆ
Implements a 4x3 key scanned keypad interface. A periodic timer interrupt is used to poll the keypad. Thanks goes to David Weaver for suggestions on improvements to the scan function.
#include "pic24_all.h"
#define C0 _RB9
#define C1 _RB8
#define C2 _RB7
#define C3 _RB6
static inline void CONFIG_COLUMN() {
CONFIG_RB9_AS_DIG_INPUT();
ENABLE_RB9_PULLUP();
CONFIG_RB8_AS_DIG_INPUT();
ENABLE_RB8_PULLUP();
CONFIG_RB7_AS_DIG_INPUT();
ENABLE_RB7_PULLUP();
CONFIG_RB6_AS_DIG_INPUT();
ENABLE_RB6_PULLUP();
}
#define R0 _LATB5
#define R1 _LATB4
#define R2 _LATB3
#define CONFIG_R0_DIG_OUTPUT() CONFIG_RB5_AS_DIG_OUTPUT()
#define CONFIG_R1_DIG_OUTPUT() CONFIG_RB4_AS_DIG_OUTPUT()
#define CONFIG_R2_DIG_OUTPUT() CONFIG_RB3_AS_DIG_OUTPUT()
void CONFIG_ROW() {
CONFIG_R0_DIG_OUTPUT();
CONFIG_R1_DIG_OUTPUT();
CONFIG_R2_DIG_OUTPUT();
}
static inline void DRIVE_ROW_LOW() {
R0 = 0;
R1 = 0;
R2 = 0;
}
static inline void DRIVE_ROW_HIGH() {
R0 = 1;
R1 = 1;
R2 = 1;
}
void configKeypad(void) {
CONFIG_ROW();
DRIVE_ROW_LOW();
CONFIG_COLUMN();
DELAY_US(1); //wait for pullups to stabilize inputs
}
//drive one row low
void setOneRowLow(uint8_t u8_x) {
switch (u8_x) {
case 0:
R0 = 0;
R1 = 1;
R2 = 1;
break;
case 1:
R0 = 1;
R1 = 0;
R2 = 1;
break;
default:
R0 = 1;
R1 = 1;
R2 = 0;
break;
}
}
#define NUM_ROWS 3
#define NUM_COLS 4
const uint8_t au8_keyTable[NUM_ROWS][NUM_COLS] = { {'1', '4', '7', '*'},
{'2', '5', '8', '0'},
{'3', '6', '9', '#'}
};
#define KEY_PRESSED() (!C0 || !C1 || !C2 || !C3) //any low
#define KEY_RELEASED() (C0 && C1 && C2 && C3) //all high
uint8_t doKeyScan(void) {
uint8_t u8_row, u8_col;
//determine column
if (!C0) u8_col = 0;
else if (!C1) u8_col = 1;
else if (!C2) u8_col = 2;
else if (!C3) u8_col = 3;
else return('E'); //error
//determine row
for (u8_row = 0; u8_row < NUM_ROWS; u8_row++) {
setOneRowLow(u8_row); //enable one row low
if (KEY_PRESSED()) {
DRIVE_ROW_LOW(); //return rows to driving low
return(au8_keyTable[u8_row][u8_col]);
}
}
DRIVE_ROW_LOW(); //return rows to driving low
return('E'); //error
}
typedef enum {
STATE_WAIT_FOR_PRESS = 0,
STATE_WAIT_FOR_PRESS2,
STATE_WAIT_FOR_RELEASE,
} ISRSTATE;
ISRSTATE e_isrState = STATE_WAIT_FOR_PRESS;
volatile uint8_t u8_newKey = 0;
//Interrupt Service Routine for Timer3
void _ISR _T3Interrupt (void) {
switch (e_isrState) {
case STATE_WAIT_FOR_PRESS:
if (KEY_PRESSED() && (u8_newKey == 0)) {
//ensure that key is sampled low for two consecutive interrupt periods
e_isrState = STATE_WAIT_FOR_PRESS2;
}
break;
case STATE_WAIT_FOR_PRESS2:
if (KEY_PRESSED()) {
a key is ready
u8_newKey = doKeyScan();
e_isrState = STATE_WAIT_FOR_RELEASE;
} else e_isrState = STATE_WAIT_FOR_PRESS;
break;
case STATE_WAIT_FOR_RELEASE:
//keypad released
if (KEY_RELEASED()) {
e_isrState = STATE_WAIT_FOR_PRESS;
}
break;
default:
e_isrState = STATE_WAIT_FOR_PRESS;
break;
}
_T3IF = 0; //clear the timer interrupt bit
}
#define ISR_PERIOD 15 // in ms
void configTimer3(void) {
//ensure that Timer2,3 configured as separate timers.
T2CONbits.T32 = 0; // 32-bit mode off
//T3CON set like this for documentation purposes.
//could be replaced by T3CON = 0x0020
T3CON = T3_OFF | T3_IDLE_CON | T3_GATE_OFF
| T3_SOURCE_INT
| T3_PS_1_64 ; //results in T3CON= 0x0020
PR3 = msToU16Ticks (ISR_PERIOD, getTimerPrescale(T3CONbits)) - 1;
TMR3 = 0; //clear timer3 value
_T3IF = 0; //clear interrupt flag
_T3IP = 1; //choose a priority
_T3IE = 1; //enable the interrupt
T3CONbits.TON = 1; //turn on the timer
}
int main (void) {
configBasic(HELLO_MSG);
/** PIO config ******/
configKeypad();
/** Configure the Timer */
configTimer3();
while (1) {
if (u8_newKey) {
outChar(u8_newKey);
u8_newKey = 0;
}
doHeartbeat(); //ensure that we are alive
} // end while (1)
}