#include <xc.h>

#define _XTAL_FREQ 20000000UL

#define LCD_RS LATAbits.LATA0
#define LCD_E  LATAbits.LATA1

const char keymap[4][4] =
{
    {'1','2','3','A'},
    {'4','5','6','B'},
    {'7','8','9','C'},
    {'*','0','#','D'}
};

void LCD_Command(unsigned char cmd)
{
    LATB = cmd;
    LCD_RS = 0;
    LCD_E = 1;
    __delay_ms(2);
    LCD_E = 0;
    __delay_ms(2);
}

void LCD_Data(unsigned char data)
{
    LATB = data;
    LCD_RS = 1;
    LCD_E = 1;
    __delay_ms(2);
    LCD_E = 0;
    __delay_ms(2);
}

void LCD_String(const char *str)
{
    while(*str != '\0')
    {
        LCD_Data(*str);
        str++;
    }
}

char Keypad_GetKey(void)
{
    unsigned char row, col;

    for(row = 0; row < 4; row++)
    {
        LATD = 0x0F;                    // RD0-RD3 all HIGH
        LATD = LATD & ~(1 << row);      // make one line LOW
        __delay_us(100);

        for(col = 0; col < 4; col++)
        {
            if((PORTD & (0x10 << col)) == 0)        // RD4..RD7 LOW?
            {
                __delay_ms(20);                     // debounce
                if((PORTD & (0x10 << col)) == 0)
                {
                    while((PORTD & (0x10 << col)) == 0);   // wait release
                    return keymap[row][col];
                }
            }
        }
    }
    return 0;
}

void main(void)
{
    char key;

    ADCON1 = 0x0F;              // make pins digital

    TRISB = 0x00;               // LCD data
    TRISAbits.TRISA0 = 0;       // RS
    TRISAbits.TRISA1 = 0;       // E

    TRISD = 0xF0;               // RD0-RD3 output, RD4-RD7 input
    LATD  = 0x0F;

    __delay_ms(20);
    LCD_Command(0x38);          // 8-bit, 2 lines
    LCD_Command(0x0C);          // display ON
    LCD_Command(0x01);          // clear
    __delay_ms(5);
    LCD_Command(0x06);          // auto-increment

    LCD_Command(0x80);
    LCD_String("PRESS A KEY");

    while(1)
    {
        key = Keypad_GetKey();

        if(key >= '0' && key <= '9')            // numeric key
        {
            LCD_Command(0x01);
            __delay_ms(5);
            LCD_Command(0x80);
            LCD_String("VALID");
            LCD_Command(0xC0);
            LCD_String("KEY: ");
            LCD_Data(key);
        }
        else if(key >= 'A' && key <= 'D')       // function key
        {
            LCD_Command(0x01);
            __delay_ms(5);
            LCD_Command(0x80);
            LCD_String("FUNCTION");
            LCD_Command(0xC0);
            LCD_String("KEY: ");
            LCD_Data(key);
        }
        // '*' and '#' are ignored
    }
}
