Interface keypad with PIC16F84a / PIC16F88

Thread Starter

Pinnacle187

Joined Apr 28, 2016
16
Hello, everyone I need a little help with making a Keypad interface with a LCD (16x2) and a PIC16F88 or PIC16F84 (they have same layout). I want to implement a keypad but with a phone keypad function (see image below), also instead of # and * buttons, shift and DELETE. By phone keypad function I mean is that if "2 button" is pressed once a "2" is displayed; BUT when SHIFT is pressed and then press 2-button once an "a" is displayed and twice "b", three times a "c"...
I have most of the code finished, but i don't know how to add a SHIFT button (along with the function described) and the deleted button.
I have put the code and the simulation in a winrar file, along with a capture of the circuit.

MAIN
Code:
///THIS IS MAIN CODE

/*PIC16F88A interface to 3x4 Keypad and 16x2 LCD

*/

#include <16f88.h>
#fuses XT,NOLVP,NOWDT,NOPROTECT
#use delay(clock=4000000)
#include "flex_lcd.c"
#include <kbd.c>

void main()
{
   char k=0;                     // El caracter k se guarda Character k to store keypad input
   delay_ms(1);
   /* Peripherals Configurations */
   lcd_init();                   // Turn LCD ON, along with other initialization commands
   kbd_init();                   // Initialize Keypad

   lcd_gotoxy(1,1);              // point LCD cursor to col1 row1
  // lcd_putc(" ");  // print on LCD
   lcd_gotoxy(1,2);              // point LCD cursor to col1 row2
   //lcd_putc(" ");
   while(1) // infinite loop
   {
      k = kbd_getc();            // read keypad input (if exists)
      if(k!=0)                   // if key captured
      {
         lcd_putc(k);            // print captured key to lcd
         k=0;                    // reset k for another loop round
         delay_ms(250);          // delay between key presses
      }
      delay_ms(1);               // delay_between read trials
   }
}

LCD CODE
Code:
// flex_lcd.c

// Para los pines del LCD

#define LCD_DB4   PIN_A2
#define LCD_DB5   PIN_A3
#define LCD_DB6   PIN_A4
#define LCD_DB7   PIN_B0

#define LCD_E     PIN_A1
#define LCD_RS    PIN_A0
#define LCD_RW    PIN_A1

// olny 6-pin interface on LCD, so[ATTACH]107114[/ATTACH] [ATTACH]107114[/ATTACH] [ATTACH]107116[/ATTACH] [ATTACH]107114[/ATTACH] [ATTACH]107116[/ATTACH]
// connect the R/W pin on the LCD to ground, and commented
// out the following line.

//#define USE_LCD_RW   1   

//========================================

#define lcd_type 2        // 0=5x7, 1=5x10, 2=2 lines
#define lcd_line_two 0x40 // LCD RAM address for the 2nd line


int8 const LCD_INIT_STRING[4] =
{
0x20 | (lcd_type << 2), // Func set: 4-bit, 2 lines, 5x8 dots
0xc,                    // Display on
1,                      // Clear display
6                       // Increment cursor
};
                           

//-------------------------------------
void lcd_send_nibble(int8 nibble)
{
// Note:  !! converts an integer expression
// to a boolean (1 or 0).
output_bit(LCD_DB4, !!(nibble & 1));
output_bit(LCD_DB5, !!(nibble & 2));
output_bit(LCD_DB6, !!(nibble & 4)); 
output_bit(LCD_DB7, !!(nibble & 8)); 

delay_cycles(1);
output_high(LCD_E);
delay_us(2);
output_low(LCD_E);
}

//-----------------------------------
// This sub-routine is only called by lcd_read_byte().
// It's not a stand-alone routine.  For example, the
// R/W signal is set high by lcd_read_byte() before
// this routine is called.   

#ifdef USE_LCD_RW
int8 lcd_read_nibble(void)
{
int8 retval;
// Create bit variables so that we can easily set
// individual bits in the retval variable.
#bit retval_0 = retval.0
#bit retval_1 = retval.1
#bit retval_2 = retval.2
#bit retval_3 = retval.3

retval = 0;
 
output_high(LCD_E);
delay_cycles(1);

retval_0 = input(LCD_DB4);
retval_1 = input(LCD_DB5);
retval_2 = input(LCD_DB6);
retval_3 = input(LCD_DB7);

output_low(LCD_E);
 
return(retval); 
} 
#endif

//---------------------------------------
// Read a byte from the LCD and return it.

#ifdef USE_LCD_RW
int8 lcd_read_byte(void)
{
int8 low;
int8 high;

output_high(LCD_RW);
delay_cycles(1);

high = lcd_read_nibble();

low = lcd_read_nibble();

return( (high<<4) | low);
}
#endif

//----------------------------------------
// Send a byte to the LCD.
void lcd_send_byte(int8 address, int8 n)
{
output_low(LCD_RS);

#ifdef USE_LCD_RW
while(bit_test(lcd_read_byte(),7)) ;
#else
delay_us(60);
#endif

if(address)
   output_high(LCD_RS);
else
   output_low(LCD_RS);
   
delay_cycles(1);

#ifdef USE_LCD_RW
output_low(LCD_RW);
delay_cycles(1);
#endif

output_low(LCD_E);

lcd_send_nibble(n >> 4);
lcd_send_nibble(n & 0xf);
}

//----------------------------
void lcd_init(void)
{
int8 i;

output_low(LCD_RS);

#ifdef USE_LCD_RW
output_low(LCD_RW);
#endif

output_low(LCD_E);

delay_ms(15);

for(i=0 ;i < 3; i++)
   {
    lcd_send_nibble(0x03);
    delay_ms(5);
   }

lcd_send_nibble(0x02);

for(i=0; i < sizeof(LCD_INIT_STRING); i++)
   {
    lcd_send_byte(0, LCD_INIT_STRING[i]);
 
    // If the R/W signal is not used, then
    // the busy bit can't be polled.  One of
    // the init commands takes longer than
    // the hard-coded delay of 60 us, so in
    // that case, lets just do a 5 ms delay
    // after all four of them.
    #ifndef USE_LCD_RW
    delay_ms(5);
    #endif
   }

}

//----------------------------

void lcd_gotoxy(int8 x, int8 y)
{
int8 address;

if(y != 1)
   address = lcd_line_two;
else
   address=0;

address += x-1;
lcd_send_byte(0, 0x80 | address);
}

//-----------------------------
void lcd_putc(char c)
{
switch(c)
   {
    case '\f':
      lcd_send_byte(0,1);
      delay_ms(2);
      break;
 
    case '\n':
       lcd_gotoxy(1,2);
       break;
 
    case '\b':
       lcd_send_byte(0,0x10);
       break;
 
    default:
       lcd_send_byte(1,c);
       break;
   }
}

//------------------------------
#ifdef USE_LCD_RW
char lcd_getc(int8 x, int8 y)
{
char value;

lcd_gotoxy(x,y);

// Wait until busy flag is low.
while(bit_test(lcd_read_byte(),7));

output_high(LCD_RS);
value = lcd_read_byte();
output_low(lcd_RS);

return(value);
}
#endif

KEYPAD CODE

Code:
// Un-commented the following define to use port B
#define use_portb_kbd TRUE

// Maked sure the port used has pull-up resistors (or the LCD) on
// the column pins

#if defined use_portb_kbd
   #byte kbd = getenv("SFR:PORTB")
#else
   #byte kbd = getenv("SFR:PORTD")
#endif

#if defined use_portb_kbd
   #define set_tris_kbd(x) set_tris_b(x)
#else
   #define set_tris_kbd(x) set_tris_d(x)
#endif

//Keypad connection:   (for example column 0 is B2)
//                Bx:

#ifdef blue_keypad  ///////////////////////////////////// For the blue keypad
#define COL0 (1 << 2)
#define COL1 (1 << 3)
#define COL2 (1 << 6)

#define ROW0 (1 << 4)
#define ROW1 (1 << 7)
#define ROW2 (1 << 1)
#define ROW3 (1 << 5)

#else ////////////////////////////////////////////////// For the black keypad
#define COL0 (1 << 5)
#define COL1 (1 << 6)
#define COL2 (1 << 7)

#define ROW0 (1 << 1)
#define ROW1 (1 << 2)
#define ROW2 (1 << 3)
#define ROW3 (1 << 4)

#endif

#define ALL_ROWS (ROW0|ROW1|ROW2|ROW3)
#define ALL_PINS (ALL_ROWS|COL0|COL1|COL2)

// Keypad layout:
char const KEYS[4][3] = {{'1','2','3'},
                         {'4','5','6'},
                         {'7','8','9'},
                         {'*','0','#'}};

#define KBD_DEBOUNCE_FACTOR 33    // Set this number to apx n/333 where
                                  // n is the number of times you expect
                                  // to call kbd_getc each second


void kbd_init() {
}

char kbd_getc( ) {
   static BYTE kbd_call_count;
   static int1 kbd_down;
   static char last_key;
   static BYTE col;

   BYTE kchar;
   BYTE row;

   kchar='\0';
   if(++kbd_call_count>KBD_DEBOUNCE_FACTOR) {
       switch (col) {
         case 0   : set_tris_kbd(ALL_PINS&~COL0);
                    kbd=~COL0&ALL_PINS;
                    break;
         case 1   : set_tris_kbd(ALL_PINS&~COL1);
                    kbd=~COL1&ALL_PINS;
                    break;
         case 2   : set_tris_kbd(ALL_PINS&~COL2);
                    kbd=~COL2&ALL_PINS;
                    break;
       }

       if(kbd_down) {
         if((kbd & (ALL_ROWS))==(ALL_ROWS)) {
           kbd_down=FALSE;
           kchar=last_key;
           last_key='\0';
         }
       } else {
          if((kbd & (ALL_ROWS))!=(ALL_ROWS)) {
             if((kbd & ROW0)==0)
               row=0;
             else if((kbd & ROW1)==0)
               row=1;
             else if((kbd & ROW2)==0)
               row=2;
             else if((kbd & ROW3)==0)
               row=3;
             last_key =KEYS[row][col];
             kbd_down = TRUE;
          } else {
             ++col;
             if(col==3)
               col=0;
          }
       }
      kbd_call_count=0;
   }
  set_tris_kbd(ALL_PINS);
  return(kchar);
}
 

Attachments

GopherT

Joined Nov 23, 2012
8,009
We can't decipher your code without a circuit and an explanation (block diagram) of how you hope to implement your circuit/software.

Is the keypad multiplexed? Is it the type of tuch-tone key pad (different audio frequency for each row/column pair) - post the datasheet of the keypad to be used.
 

Thread Starter

Pinnacle187

Joined Apr 28, 2016
16
We can't decipher your code without a circuit and an explanation (block diagram) of how you hope to implement your circuit/software.

Is the keypad multiplexed? Is it the type of tuch-tone key pad (different audio frequency for each row/column pair) - post the datasheet of the keypad to be used.
The circuit is in the rar file
 

DatLe

Joined Jun 1, 2016
1
Hi Pinnacle187,
How about the function of "*" and "#" button.
Think about hold action? You can using hold action to use as SHIFT and DEL function.
In that case, you can use apply function of "*" and "#" button when they're released.
 

joeyd999

Joined Jun 6, 2011
5,283
This is quite easy, I've done it a number of times (I think the first time was around 12 y.o. or so), and I don't require a schematic.

1. Your actual keypad routine should send back a code for each key based upon row, column position. You've got a 4x3 keypad, so I'd send the following: Row 1: 0-2, Row 2: 3-5, Row 3: 6-8, Row 4: 9-11. Each code only requires a nibble, so I'd also send back the shift status as an additional bit, say bit 7 (MSB).

2. Make 4 map strings. These strings map key codes to actual values. For instance:

keymap0 = "123456789*0#"
keymap1 = "1ADGJMPTW*0#"
keymap2 = "1BEHKNRUY*0#"
keymap3 = "1CFILOSVZ*0#"

3. Have another routine, a state machine, that keeps track of the history of button presses (and the state of the shift key), reads the keycode, and returns the proper character from the appropriate keymap, depending upon state.

The end.
 

GopherT

Joined Nov 23, 2012
8,009
This is quite easy, I've done it a number of times (I think the first time was around 12 y.o. or so), and I don't require a schematic.

1. Your actual keypad routine should send back a code for each key based upon row, column position. You've got a 4x3 keypad, so I'd send the following: Row 1: 0-2, Row 2: 3-5, Row 3: 6-8, Row 4: 9-11. Each code only requires a nibble, so I'd also send back the shift status as an additional bit, say bit 7 (MSB).

2. Make 4 map strings. These strings map key codes to actual values. For instance:

keymap0 = "123456789*0#"
keymap1 = "1ADGJMPTW*0#"
keymap2 = "1BEHKNRUY*0#"
keymap3 = "1CFILOSVZ*0#"

3. Have another routine, a state machine, that keeps track of the history of button presses (and the state of the shift key), reads the keycode, and returns the proper character from the appropriate keymap, depending upon state.

The end.
I'm sure he'll have no more questions after that explanation.
 

dannyf

Joined Sep 13, 2015
2,197
It is fairly simple within the framework of your code. You showed fairly good sophistication in your code. The construct of yoyr kbd-getc() is far morr advaanced than manny code pieces shown here so I'm pretty sure you can get it done all by yourself.

Good luck.
 
Top