LCD and Keypad sharing data pins

Keypad and LCD share data pins...

Note, I have made corrections to this post as identified in the following discussion...

Basic system to provide security access:

  • Allows three different passwords...
  • Lockout if tampering (allows three attempts and then lock the keypad for X seconds)...
  • Uses a piezo device to generate keyboard tone and door open tone...
  • Software driven contrast (user can set level)...
  • Uses shares data pin to accommodate more functionality...

Revision IV, removed the pot and replaced with code... (Contrast can changed by user)...

The source code:

/*
Connecting Keypad and LCD sharing common data pins, using keypad and lcd libraries...
Mike O'Toole 28 03 2012...

Reversion IV (08 April 2012), if you make any improvements let me know...
Many thanks to liudr, mstanley and bperrybap @ http://arduino.cc for help getting it right...
On previous versions I transposed the col/row pins in sketch and Fritzing image...
Mike

Email: o2l at eircom.net
Site: http://www.phpbbireland.com
Copyright 2012 - Under creative commons license 3.0:
*/

#include <Keypad.h>
#include <LiquidCrystal.h>

LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

const byte ROWS = 4;
const byte COLS = 4;

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

byte colPins[COLS] = {5, 4, 3, 2}; //connect to the column pinouts of the keypad
byte rowPins[ROWS] = {6, 7, 8, 9}; //connect to the row pinouts of the keypad

#define   CONTRAST_PIN   10
#define   SESSOR_PIN     A0
#define   LATCH_PIN      A1

Keypad keypad = Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS);

byte con[8] = {
  0b00100,
  0b01110,
  0b11111,  
  0b00000,  
  0b00000,  
  0b11111,
  0b01110,
  0b00100
};

// Support three passwords //
char firstPassword[] = "123456*";
char secondPassword[] = "246855*";
char masterPassword[] = "1234567890*";

int contrast = 75;
int counter = 0;
int attempts = 0;
int attempted = 0;
int access = 0;
int xcol = 3;
int xrow = 2;
int clean = 0;
int loopCount = 0;
int aData = 0;
int repaint = 0;

char key;

unsigned int lockoutDelay = 3000;
unsigned int lockedout = 0;
unsigned int inMenu = 0;
unsigned int valid0 = 0;
unsigned int valid1 = 0;
unsigned int valid2 = 0;
unsigned long resetline;
unsigned long duration = 3000;;
unsigned long startTime;

void setup()
{
  pinMode(13, OUTPUT);

  // not implemented yet //
  //pinMode(LATCH_PIN, OUTPUT);
  
  pinMode(CONTRAST_PIN, OUTPUT);
  analogWrite (CONTRAST_PIN, contrast);
  
  lcd.createChar(1, con);

  Serial.begin(9600);

  keypad.addEventListener(keypadEvent); //add an event listener for this keypad
  
  lcd.begin(20, 4);
  lcd.setCursor(0, 0);
  lcd.print("    System Armed    ");
  delay(500);
  lcdstart();
  intro();
}

void lcdstart()
{
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("   Enter Password   ");
  lcd.setCursor(0, 2);
  lcd.setCursor(xcol, xrow);
  repaint = 0;
}

void resetvars()
{
  counter = 0;
  attempts = 0;
  valid0 = 0;
  valid1 = 0;
  valid2 = 0;
  xcol = 3;
  xrow = 2;
  loopCount = 0;
  if(!repaint) attempted = 0;
}
  
void buzzer()
{
  if (startTime > 0 && (startTime + duration) > millis())
  {
    //digitalWrite(LATCH_PIN, HIGH);
    tone(13, 240, 150); delay(550);
  }
  else
  {
    //digitalWrite(LATCH_PIN, LOW);
  }
}

void soundAlarm()
{
  tone(13, 243, 50); delay(40);
  tone(13, 443, 50); delay(50);
  tone(13, 743, 50); delay(70);
}

void intro()
{
    // do something fancy here //
    tone(13, 294, 250); delay(325);
}

// using the callback to better organise //
void keypadEvent(KeypadEvent key)
{
  switch (keypad.getState())
  {
    case PRESSED:
      if(key == '*' && access == 1)
      {
        tone(13, 870, 90);
      }
      else
      {
        tone(13, 543, 70);
        access = 0;
      }
    break;
    case RELEASED:
      switch (key)
      {
        case '*':
                  if(access == 0 && inMenu == 0)
                  {
                    repaint = 1;
                  }
        break;
        case '#': 
                  if(inMenu == 0)
                  {
                    resetvars();
                    lcdstart();
                  }
        break;
        default:
                    if(!inMenu)
                    {
                      lcd.setCursor(xcol++,xrow);
                      lcd.print('*');
                    }
        break;         
      }
    break;
    case HOLD:
                // print some info //
    break;
    default:
    break;
  }
}

// main loop //
void loop()
{
  key = keypad.getKey();

  if(key == 'D')
  {
    inMenu = 1;
    lcd.clear();
    lcd.setCursor(0,0);
    lcd.print("    Set Contrast    ");
    lcd.setCursor(0,1);
    lcd.print("Dark(2)     (8)Light");
    lcd.setCursor(9,1);   
    lcd.write(1);
    lcd.setCursor(0,3);
    lcd.print(" Press * to exit: ");
    lcd.setCursor(17,3);
    
    if(contrast < 99)
    {
      lcd.print(" ");
    }
    lcd.print(contrast);
    
    while(key != '*')
    {
      key = keypad.getKey();
          
      if(key == '8')
      {
        lcd.setCursor(17,3);
        if(contrast < 99)
        {
          lcd.print(" ");
        }

        if(contrast <= 10) 
          contrast = 10;           
        lcd.print(contrast); 
        contrast += 5; analogWrite (CONTRAST_PIN, contrast);
        //Serial.println(contrast);
      }
      if (key == '2')
      {
        lcd.setCursor(17,3);
        if(contrast < 99)
        {
          lcd.print(" ");
        }

        if(contrast >= 220)
          contrast = 220;
        contrast -= 5; analogWrite (CONTRAST_PIN, contrast);
        lcd.print(contrast);
        //Serial.println(contrast);
      }
    }
    inMenu = 0;
  }
  else if (key)
  {
    if(key == masterPassword[counter])
    {
      valid0++;
    }
    else
    {
      valid0 = 0;
    }
    if(key == firstPassword[counter])
    {
      valid1++;
    }
    else
    {
      valid1 = 0;
    }
    if(key == secondPassword[counter])
    {
      valid2++;
    }
    else
    {
      valid2 = 0;
    }

    if(key != firstPassword[counter] && key != secondPassword[counter] &&  key != masterPassword[counter])
    {
      valid0 = valid1 = valid2 = 0;
      attempts++;
    }
    counter++;
    
    // one of the valid passwords //
    if(attempts == 0 && valid1 == strlen(firstPassword) || valid2 == strlen(secondPassword) || valid0 == strlen(masterPassword))
    {
      access = 1;
      startTime = millis();
      resetvars();
      lcdstart();
    }

    /* Tampering > count entry attempts, if exceeds 3 force delay before next attempt */
    if(attempts >= 6 || key == '*' || counter > 12)
    {
      attempted++;
      attempts = 0;
    }
    if(attempted > 2)
    {
      repaint = 1;
      lcd.clear();      
      lcd.setCursor(0,0);
      lcd.print("   Security delay   ");
      lcd.setCursor(0,2);      
      lcd.print("    PLEASE WAIT!    ");
      delay(lockoutDelay);
      lcdstart();
      resetvars();
    }
    //Serial.println(key);
  }
    
  if(repaint)
  {
    resetvars();
    lcdstart();
  }

  buzzer();
}

Contrast Adjustment:
To modify the contrast press the D key, followed by 2 and 8 to decrease/increase contrast... Press * when complete...

Updates:
3: Remove then need for the 10k pot by doing the contrast in code...
4: Fixed LCD issue and corrected pinouts thanks to: liudr, mstanley and bperrybap @ http://arduino.cc

Fritzing image:

Please note the power supply should be 5 Volts DC also disregard wire to A0...
Also note the LCD above does not have standard pin outs... I will replace next time...

Todo:
Turn off backlight when inactive and/or play around with sleep mode and hardware interrupts...

Are you asking for critic or sharing some completed project?

Your diagram is missing one real important connection, plus a couple of current-limiting resistors.

A real important question awaits answering: are you sharing the row pins (read pins in keypad) with the LCD data pins (write pins in LCD) and how are you turning off the column pins (write pins in keypad) to prevent shorting?

liudr:
Your diagram is missing one real important connection, plus a couple of current-limiting resistors

The speaker should have been a piezo sounder and the pot was missing the positive rail, other than that, did I miss something else?

I have removed the pot and added code to set the contrast (user can adjust), code require updates... later...

Shared pins:
The LCD is hard-wired read, both the keypad and LCD pins are assigned at read/write time, so not conflict can occur (unless I missed something)...

I built it because I came across a lot of people running out of pins in their projects and also because I could not find any circuits where both the keypad and LCD were used without resorting to additional components or switching to a serial LCD or mega...

Admittedly it's my first Arduino project am I a bit rusty not having designed anything for over twenty years... It's like going back to school with Arduino but this time it's fun :wink:

Please critique... when complete it will make a nice little tutorial...
Mike

PS, will add the updated code as soon as I can confirm it's sound...

I don't see any mechanism in your code to make sure the keypad won't short your arduino when you use the 4 pins on display and a user pushes a button. If you used column pins to multiplex, there would not be any problem. ASAIK, the keypad library uses columns as outputs and rows as inputs. And you seem to be using some sort of event driven code so is the keypad sensed periodically by interrupt? I would multiplex with column pins and don't use interrupt driven keypad codes. Don't know if you know the difference but it's very bad practice to use resource that an interrupt service routine (ISR) also uses without a mechanism to tell the ISR the resource is in use.

I better give you some more info on what I intended so that you might be able to verify if I am screwing up or not...

I use the column pins as outputs to both the keypad and LCD, this never changes, the row pins are inputs, again this never changes...

Data to the LCD only has effect during the LCD write sequence, all other times the data is read by the keyboard code to determine key press...

Taking advantage of the fact that the microcontroller operate sequentially at any point in time we are either in the keypad read /write cycle or the LCD write cycle... In the event the user presses a key during the LCD write sequence the data will be ignored as the keypad read cycle has not yet started...

I did not look too closely but assume the interrupt routine is software based...
I use the key states (getState) to execute some quick code and return, thereby avoiding any long process... it also provides a method to better sequence events and avoid everything happening together...

The LCD write cycle is executed after a valid key is detected allowing plenty of time to display messages...
In theory if any conflict occurred it would be during the LCD write cycle but worst case scenario would only result in the LCD displaying a strange character or two... In practice this does not happen but it helps to add a small delay before writing data to the LCD...

In an ideal world I would disable the keypad during the LCD write, thereby avoiding any conflict but not sure how I would do this...

Back to the ISR...
I've written a substantial amount of interrupt code in assembly and c but that was a lifetime ago and was written for DOS machines...
I must admit to little knowledge in relation to the Arduino hardware and am likely to mess up... a little knowledge may be dangerous :wink:

I hope this is a bit clearer but have nagging doubts as to whether or not I have it right...
The test board has bee running for several days without problem but that's no guarantee...

Thank you for your help, I hope I'm not taking up too much of your time, it is much appreciated...
Mike

Mike,

Your original code had row pins shared with LCD not column pins. Now you are saying sharing column pins with LCD, it is safe from electric point of view. If you had used rows and you pulled rows down and there were a 5V on column you press the button something will burn.
Your suspicion on possible strange characters is what I suspected too. LCD write gets messed up by keypad read. I don't think LCD write will set pins to output every time in the library so you need to check that. Same for keypad read. I would stay away from interrupt driven part of the keypad lib, just call get key to get key when I am not displaying and restore the pins to write before calling LCD write.

I must have got the pin number wrong in the first Fritzing image... luckily not on the test board :wink:
I checked both libraries initially and they both use pinMode before writing data, otherwise I planned modifying them, so no problem there...

Originally I did not use the interrupts but I noticed when writing a few lines of text (using lcd.clear, go to line/row and write something), the strange/random chars occurred more often... Once I used the interrupts option to space events, everything was better controlled and almost never writes strange chars anymore... (comer to think of it, it may have had more to do with the delay(200) before writing to LCD that fixed the problem)...

I will build another sketch without the interrupts and see how it goes... who know, maybe the interrupt part is causing the random characters...

Thanks liudr...

MikeOToole:
I will build another sketch without the interrupts and see how it goes... who know, maybe the interrupt part is causing the random characters...

Hi Mike,

I looked at your code and I just wanted to let you know that the keypad driver does not use any interrupt service routines. The function, keypadEvent, that you are using is technically know as a call-back function. Alexander added it to the driver quite a while ago and you are the first to actually use it besides myself.

Anyway, keypadEvent is a really handy tool for servicing key-events without having to clutter up your loop. Also, I would like to warn you against using delays in your code. They can really cause the keypad to become unresponsive. You may want to try using a state-machine to control what is happening rather than using delays.

@mstanley
Looks like I got the functionality right, but the technical name wrong (getting use to this ;))...
As I read the code, I assumed it would simply call my function based on key state change... essentially a software interrupt...

mstanley:
I would like to warn you against using delays in your code. They can really cause the keypad to become unresponsive. You may want to try using a state-machine to control what is happening rather than using delays.

There are no delays added to the actual keypad procedures, only to the LCD print processes.

These delay are essential in order to reduce the possibility of printing random characters where the user enters data so quickly that the LCD routines can't complete the write cycle before the next key is pressed... What I really need is a method to delay the keypad until the LCD write cycle is complete, in most cases this delay should not be noticeable...

I note the state-machine approach is use by the keypad code... I will look into extending it to reduce the LCD problem, it may be the best approach...

mstanley:
Anyway, keypadEvent is a really handy tool for servicing key-events without having to clutter up your loop

Totally agree, but more importantly (to me anyway), it allows me to divide up processor time by process simple events immediately (allowing for time constraints) or schedule a process for later...

I rewrote the code without using the callback function but to be honest, without the scheduling afforded by the callback functionality the code suffers badly, so this is a non starter...

Now that I've talked myself into a possible solution it's back to the code...
Mike

MikeOToole:
@mstanley
Looks like I got the functionality right, but the technical name wrong (getting use to this ;))...
As I read the code, I assumed it would simply call my function based on key state change... essentially a software interrupt...

It does call your listener function when you get a key state change but that is not really an interrupt.
It all happens in the same context underneath the getKey() function so it is just a callback.
The AVR can't even do a real software interrupt as the AVR does not include the TRAP instruction.
(s/w can be used to create a hardware interrupt though)

There are no delays added to the actual keypad procedures, only to the LCD print processes.
These delay are essential in order to reduce the possibility of printing random characters where the user enters data so quickly that the LCD routines can't complete the write cycle before the next key is pressed...
What I really need is a method to delay the keypad until the LCD write cycle is complete, in most cases this delay should not be noticeable...

This does not make sense.
Some things are synchronous like all the software and some things are asynchronous
like the user pressing a button.

Since all the software is synchronous (no ISRs, everything runs serially all in the same context)
the keypad code will never interrupt or overlap the LCD code
and the LCD library code will never interrupt or overlap the keypad code.

When you call an LCD library function, it goes away and does not return until
the data has been sent to the display. - The E signal has been lowered
and the write operation to the LCD has completed.
At that point, the data lines hooked up to the LCD are no longer looked at by the LCD.

When you call the getKey() library function, it goes away, scans the keypad
if 10ms has gone by since the last scan. If it did a scan and detects a key state change,
it will call the listener function, before returning.
After getKey() returns, the row and column pins are no longer looked at.

Adding delays before printing to the LCD is merely adding delay/latency before the data is sent
to the LCD. It is also adding delay/latency to the keypad code because it is delaying
the next call to getkey().

The keypad library tries to allow the sharing of all the column and row pins
with other libraries.
This is not really possible without protection circuitry
as it can potentially create shorts when the users presses a button
because his presses are totally asynchronous to the software controlling the pins.

In reality, only the keypad column pins can be shared.
This is because a column pin
and row pin are shorted together by the keypad when a button is pressed.
But since the row pins are all inputs
it won't affect the outputs on the column pins when used by some
other library even if a button connected to that column is pressed
which shorts it to a row pin.

Your example sketch is sharing the row pins with the LCD.
As the Dr pointed out this is not good and will have problems.

As far as LCD timing goes here is some timing:

The stock LiquidCrystal library in 4bit mode takes 338uS to send a
single character to the display. Commands like CLEAR and HOME take 20ms.

It takes the same amount of time to send a character to the display as to
set the cursor position as both are single byte transfer to the display.
Your listener function keypadEvent() sets the LCD cursor and sends a character
every time a key is "released".
Setting the cursor position and sending a character will be under 1ms.
This is faster than the 10ms keypad poll interval
and is much faster than a human could ever press the buttons.

Fm's liquidCrystal library reduces that 338us to 98us.

While Fm's library does not reset the LCD data lines back to outputs,
if pins for the LCD data lines are the ones used for the keypad column pins,
they will remain outputs all the time and can be used by either library
when needed.

--- bill

@Bill
When I said "essentially a software interrupt", I simply meant it interrupted the normal program flow of the mail loop... in hindsight, not a good idea to use that term...

Your detailed and very welcomed explanation serves to confirm my initial reading of the processes involved... Admittedly I started to question my understanding when the LCD glitch appeared...

Looking more closely I may have found the problem... looks like everyone spotted it except me. When my error was pointed out to me, I assumed I only had the graphic wrong but I also managed to get both the wiring, and sketch wrong (I transposed pins on both).

My humblest apologies and thanks to all, I feel like a complete amateur... who would have guessed I once did this for a living...

I'm off to fix the problem, once complete I will correct the code and image above...

Mike

Don't feel bad.
It's always the simple stuff that is so easy to overlook that often trips me up too.

--- bill

Updated first post...
I think I have corrected everything... no more LCD issues, no more unnecessary delays, mind you after screwing up the pins several times, I will hold off on saying it's complete...
Mike

Updated sketch here: http://www.phpbbireland.com/kiss2/viewtopic.php?f=80&t=265

Features:

  • Allows three different passwords...
  • Lockout if tampering (allows three attempts and then lock the keypad for X seconds)...
  • Uses a piezo device to generate keyboard tone and door open tone...
  • Backlight off if inactive, any key to turn on backlight...
  • Software driven contrast (user can set level)...
  • Latched output (relay 3 seconds)...
  • A test menu with some options (just testing)...

Updated first post...

AARRGGHH - Your original post had content that generated several replies. Now that you have modified that post most likely many of those replies are no longer pertinent and you may even have inadvertently made the repliers appear to be somewhat ignorant. The problems that existed in your original content and the suggestions that followed in the resulting replies can be a valuable resource that is effectively negated when the original post is 'corrected'.

Don

MikeOToole:
Updated sketch here: http://www.phpbbireland.com/kiss2/viewtopic.php?f=80&t=265

Features:

  • Allows three different passwords...
  • Lockout if tampering (allows three attempts and then lock the keypad for X seconds)...
  • Uses a piezo device to generate keyboard tone and door open tone...
  • Backlight off if inactive, any key to turn on backlight...
  • Software driven contrast (user can set level)...
  • Latched output (relay 3 seconds)...
  • A test menu with some options (just testing)...

Why not just use a couple shift registers (one for the display, one for the keypad)? This method would only use 6 pins (or less), cheap (like $0.50), and to me far easier to make and program. Heck, you could even make the individual keys lighted and still have pins left over.

Tim