#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);
}

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];        // ASCII of key
                }
            }
        }
    }
    return 0;                           // no key pressed
}

void main(void)
{
    char key;
    unsigned char pos = 0;

    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);          // line 1

    while(1)
    {
        key = Keypad_GetKey();

        if(key != 0)
        {
            LCD_Data(key);      // display the character
            pos++;

            if(pos == 16)       // line 1 full -> go to line 2
                LCD_Command(0xC0);

            if(pos == 32)       // screen full -> clear
            {
                LCD_Command(0x01);
                __delay_ms(5);
                LCD_Command(0x80);
                pos = 0;
            }
        }
    }
}
