4x4 matrix keypad with all alphabets code with arduino using i2c with sx1509

Hi,
I am trying to code for arduino using sx1509 IOExpander for 4x4 keypad with keypad having all alphabets in it. If anyone have done any coding regarding this, please provide me. It will be very helpful

Being 4x4, you’re limited to 15 usable keys at most, assuming the 16th will shift the other 15 through several pages…

I’d guess 3 pages of 15 would be almost usable (45 unique keys)

The code isn’t anything too complicated, but will be tedious to use.

follow the great hookup guide from Sparkfun:

https://learn.sparkfun.com/tutorials/sx1509-io-expander-breakout-hookup-guide/all

if you can read the 16 buttons, design a keypad like on old cellphones where you have pressed a button multiple times to get a different character. 2abc 3def 4ghi ...

yes, I tried this example and it worked for only 1,2,3,4.. means numeric values, but how to write the code of alphanumeric like 1abc, 2def.. I have made the arrays like that --
char keyMap[KEY_ROWS][KEY_COLS] = {
{'1', '2', '3'},
{'4', '5', '6'},
{'7', '8', '9'},
{'*', '0', '#'}};

String keyStringArray[12] = {
"1abcABC", "2defDEF", "3ghiGHI",
"4jklJKL", "5mnoMNO", "6pqrPQR",
"7stuSTU", "8vwxVWX", "9yz.YZ.",
"ok", "0+ -", "cancel",
};

but how to implement it in code using sx1509, I really don't get that.
Please provide some light on it.

Instead of assigning ASCII characters to the keypad keys, give the keys numeric values from 1 through 12, then uses the key value less 1 as the index into your array of Strings.
Zero cannot be used for a key because that is used to indicate no key has been pressed.

Do you have a library for scanning the keypad using the I/O expander?

no, I don't have

  • Define a threshold of milliseconds to distinguish between two single clicks and a series of multiple clicks (for example 100ms)
  • on first press remember the actual millis(), the pressed key and increase a counter
  • on next press check against the threshold and if it was the same key. Keep counting or fire the character
  • when you have fired a character reset the counter

before I go deeper into this rabbit hole, please explain your project in detail. It just sounds so odd to me when someone wants to enter upper/lower case alfanumeric values with just 16 keys instead of 10+26+26+1 keys. The user experience on a T9 without predictive text sounds odd to me (in 2022).

Please place the entire code in code tags.

#include <Keypad.h>
const byte ROWS = 4; //four rows
const byte COLS = 4; //three columns
static byte kpadState;

unsigned long currentTimeInMill;
int timeDiff = 1500;//time diff for mutilple press
int keyIndex = 1;//index of first char of each key
int prevKeyValue;
String finalTextOut = "";//output text
String action = "";
bool longpress = false;
char keys[ROWS][COLS] = {
  { 0, 1, 2, 3 },
  { 4, 5, 6, 7 },
  { 8, 9, 10, 11 },
  { 12, 13, 14, 15 }
};
String keyStringArray[16] = {
  "1ABC", "2DEF", "3GHI", "F1",
  "4JKL", "5MNO", "6PQR", "F2",
  "7STU", "8VWX", "9.YZ.", "F3",
  "ok", "0+ -*#@", "cancel", "F4",
};

int numbersArray[10] = {0, 1, 2, 4, 5, 6, 8, 9, 10, 13};
char* longPress[16] = {
  "", "", "", "",
  "", "", "", "",
  "", "", "", "",
  "", "mode", "", ""
};
char* keymodes[3] = {"no", "small", "cap"};
int keymodeIndex = 0;
byte rowPins[ROWS] = {2, 3, 4, 5}; //connect to the row pinouts of the keypad
byte colPins[COLS] = {6, 7, 8, 9}; //connect to the column pinouts of the keypad

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

void setup() {
  Serial.begin(9600);
  keypad.addEventListener(keypadEvent);
  keypad.setHoldTime(1500);
  currentTimeInMill = millis();
}

void loop() {
  char key = keypad.getKey();
  char * cstr = new char [finalTextOut.length() + 1];
  cstr = finalTextOut.c_str();
  //delay(1000);
  Serial.println(finalTextOut);



}
void getCharFromKey(int keyVal) {
  int modeRef = keymodeIndex == 2 ? 3 : 0;
  if ((millis() - currentTimeInMill) < timeDiff && prevKeyValue == keyVal) {
    keyIndex++;
    if (keyIndex == 4) {
      keyIndex = 1;
    }
    finalTextOut.remove(finalTextOut.length() - 1);
    finalTextOut = finalTextOut + keyStringArray[keyVal].substring(keyIndex + modeRef, keyIndex + modeRef + 1);
  } else {
    keyIndex = 1;
    finalTextOut = finalTextOut + keyStringArray[keyVal].substring(keyIndex + modeRef, keyIndex + modeRef + 1);
  }
  prevKeyValue = keyVal;
  currentTimeInMill = millis();
}
bool isAplhaNum(int keyVal) {
  int arrayIndex = 0;
  while (arrayIndex < 10) {
    if (numbersArray[arrayIndex] == keyVal) {
      return true;
    }
    arrayIndex++;
  }
  return false;
}
void getLongPressValue(int keyValue) {
  if (longPress[keyValue] == "mode") {
    keymodeIndex++;
    if (keymodeIndex == 3) {
      keymodeIndex = 0;

    }
  }
}
void keypadEvent(KeypadEvent key) {
  kpadState = keypad.getState();
  int keyVal = key;
  //Serial.println(keyVal);
  switch (kpadState) {
    case PRESSED:
      //Serial.println("pressed");
      break;

    case HOLD:
      longpress = true;
      break;

    case RELEASED:
      // Serial.println("release");
      if (longpress) {
        getLongPressValue(keyVal);
        longpress = false;
      } else {
        //Serial.println(keyStringArray[keyVal]);
        if (isAplhaNum(keyVal)) {
          if (keymodeIndex == 0) {
            finalTextOut = finalTextOut + keyStringArray[keyVal].substring(0, 1);
          } else if (keymodeIndex == 1) {
            getCharFromKey(keyVal);
          }
          else if (keymodeIndex == 2) {
            getCharFromKey(keyVal);
          }
        } else {
          action = keyStringArray[keyVal];
          if (action == "cancel") {
            finalTextOut.remove(finalTextOut.length() - 1);
          }

        }
      }

      break;
  }
}

sorry, but that project was not abounded, right now I am trying directly with Arduino.

Can you give a general description of the final project? Something like this will be much easier if you are going to be using a display that can be updated while the key is held, that way the user can press a key, see the display cycling through the various characters assigned to that key, and release the key when the desired character is displayed.

Be very careful with the use of "new", I do not see a corresponding "delete" to free up the allocated memory.

As I suggested in your other thread, in the char array for the keypad, use the numeric values 1 through 16 instead of ASCII characters, the when a key is pressed you can print stringarray[keycode - 1]. The number 0 cannot be used for a key because that is used by the library to indicate no key has been pressed

@komal_dhiman,

Your two topics on the same or similar subject have been merged.

Please do not duplicate your questions as doing so wastes the time and effort of the volunteers trying to help you as they are then answering the same thing in different places.

Please create one topic only for your question and choose the forum category carefully. If you have multiple questions about the same project then please ask your questions in the one topic as the answers to one question provide useful context for the others, and also you won’t have to keep explaining your project repeatedly.

Repeated duplicate posting could result in a temporary or permanent ban from the forum.

Could you take a few moments to Learn How To Use The Forum

It will help you get the best out of the forum in the future.

Thank you.

Thank you, I'll try this.

Here is an example of what I was suggesting, it just prints the entire String instead of attempting to choose one of the characters in the String.

#include <Keypad.h>

const byte ROWS = 4; //four rows
const byte COLS = 4; //four columns
char keys[ROWS][COLS] = {
  { 1,  2,  3,  4},
  { 5,  6,  7,  8},
  { 9, 10, 11, 12},
  {13, 14, 15, 16},
};

String keyStringArray[16] = {
  "1ABC", "2DEF", "3GHI", "F1",
  "4JKL", "5MNO", "6PQR", "F2",
  "7STU", "8VWX", "9.YZ.", "F3",
  "ok", "0+ -*#@", "cancel", "F4",
};
byte rowPins[ROWS] = {2, 3, 4, 5}; //connect to the row pinouts of the keypad
byte colPins[COLS] = {6, 7, 8, 9}; //connect to the column pinouts of the keypad

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

void setup() {
  Serial.begin(9600);
}

void loop() {
  char key = keypad.getKey();
  if (key) {
    Serial.println(keyStringArray[key - 1]);
  }
}

yes it print whole string, but I am trying to print one character one by one like done in previous keypad phone in which we press 1 key and it can print A,B,C like that.

So that’s where you need to put your thinking cap on.
You’ve been given a working example - run with it, then ask the next question.

It would help if you could explain what your ultimate goal is.

In your last code, be very careful when setting more than one row pin as OUTPUT at the same time, pressing two keys in different columns can destroy the arduino by shorting two output together (have a look at the keypad library code to see how to avoid this).

Here is a simple example, note that it does not check for multiple key presses, or properly handle the F1-F4, OK, and Cancel keys properly (not sure what code those should convert to anyway). If you want to do this to enter multiple-character text, it will get more complex.

#include <Keypad.h>

const byte ROWS = 4; //four rows
const byte COLS = 4; //four columns
char keys[ROWS][COLS] = {
  { 1,  2,  3,  4},
  { 5,  6,  7,  8},
  { 9, 10, 11, 12},
  {13, 14, 15, 16},
};
struct {
  const char chars[8];
  const bool multichar;
} const keyCharArray[16] = {
  {"1ABC", true}, {"2DEF", true}, {"3GHI", true}, {"F1", false},
  {"4JKL", true}, {"5MNO", true}, {"6PQR", true}, {"F2", false},
  {"7STU", true}, {"8VWX", true}, {"9.YZ.", true}, {"F3", false},
  {"ok", false}, {"0+ -*#@", true}, {"cancel", false}, {"F4", false},
};

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

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

void setup() {
  Serial.begin(9600);
}

unsigned long keyTimer;
size_t keyCharIndex;
char keyChar;
bool keyHeldDown;
char keyEncoded;
void loop() {
  if (keypad.getKeys()) {
    for (int i = 0; i < LIST_MAX; i++) { //check key list
      if (keypad.key[i].stateChanged) {
        if (keypad.key[i].kstate == PRESSED) {
          keyTimer = millis();
          keyCharIndex = 0;
          keyChar = keypad.key[i].kchar;
          keyHeldDown = true;
          Serial.print(F("key press:"));
          Serial.println((byte)keyChar);
          Serial.println(keyCharArray[keyChar - 1].chars[keyCharIndex]);
        }
        else if (keypad.key[i].kstate == RELEASED) {
          if (keypad.key[i].kchar == keyChar) {
            keyHeldDown = false;
            Serial.print(F("key released:"));
            Serial.print(keyChar);
            keyEncoded = keyCharArray[keyChar - 1].chars[keyCharIndex];
            Serial.print(F(" encoded character:"));
            Serial.println(keyEncoded);
            keyChar = '\0';
          }
        }
      }
    }
  }
  if (keyHeldDown && ((millis() - keyTimer) > 1000ul)) {
    keyTimer += 1000ul;
    keyCharIndex++;
    if (keyCharArray[keyChar - 1].chars[keyCharIndex] == '\0') {
      keyCharIndex = 0;
    }
    Serial.println(keyCharArray[keyChar - 1].chars[keyCharIndex]);
  }
}

Ok, I'll let someone else try to help.
I am too confused, have no idea what you are trying to do.

1 Like

See if this is close to what you want, pressing a key multiple times less than 1/2 second apart will cycle through the characters assigned to that key:

#include <Keypad.h>

const byte ROWS = 4; //four rows
const byte COLS = 4; //four columns
char keys[ROWS][COLS] = {
  { 1,  2,  3,  4},
  { 5,  6,  7,  8},
  { 9, 10, 11, 12},
  {13, 14, 15, 16},
};
byte rowPins[ROWS] = {2, 3, 4, 5}; //connect to the row pinouts of the keypad
byte colPins[COLS] = {6, 7, 8, 9}; //connect to the column pinouts of the keypad

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

// ASCII characters
// 0x08 BS  <backspace>
// 0x0A LF  <newline>
// 0x0D CR  <carriage return>
// 0x11 DC1 <device control 1>
// 0x12 DC1 <device control 2>
// 0x13 DC1 <device control 3>
// 0x14 DC1 <device control 4>
// 0x7F DEL <delete>

#define backspace 0x08
#define reset     0x11
#define dial      0x12
#define enter     0x0A

// 1ABC 2DEF 3GHI <reset>
// 4JKL 5MNO 6PQR <dial>
// 7STU 8VWX 9YZ. <backspace>
// <sp>  0   +-   <enter>

const char keyCharArray[16][5] PROGMEM = {
  "1ABC", "2DEF", "3GHI", "\x11",
  "4JKL", "5MNO", "6PQR", "\x12",
  "7STU", "8VWX", "9YZ.", "\x08",
  " ",    "0",   "+-", "\x0A",
};

char finalText[80] = "";
size_t finalTextIndex = 0;

void setup() {
  Serial.begin(9600);
}

void loop() {
  char key = mygetKey();
  switch (key) {
    case '\0':
      break;
    case enter:
      Serial.print(F("final text:"));
      finalText[finalTextIndex] = '\0';
      Serial.println(finalText);
      finalText[0] = '\0';
      finalTextIndex = 0;
      break;
    case backspace:
      if (finalTextIndex > 0) {
        finalTextIndex--;
      }
      Serial.print(F("current text:"));
      Serial.write(finalText, finalTextIndex);
      Serial.print('\n');
      break;
    case dial:
      break;
    case reset:
      break;
    default:
      finalText[finalTextIndex] = key;
      finalTextIndex++;
      if (finalTextIndex >= sizeof(finalText)) {
        finalTextIndex = sizeof(finalText) - 1;
      }
      Serial.print(F("current text:"));
      Serial.write(finalText, finalTextIndex);
      Serial.print('\n');
      break;
  }
}

char mygetKey() {
  const unsigned long keyDelay = 500ul; //maximum delay between multiple presses of same key
  static unsigned long keyTimer; 
  static size_t keyCharIndex = 0; //index of current character for multi-character key
  static char keyPrevious = '\0'; //previous key pressed
  static char keyPending = '\0';  //temporary storeage for key pressed within multi-charactere delay for another key
  char key;
  char keyChar = '\0'; //character to be returned from this function
  
  if (keyPending == '\0') { //check for key pressed during multi-character delay for another key
    key = keypad.getKey();
  } else {
    key = keyPending;
    keyPending = '\0';
  }
  
  if (key) {
    if (keyPrevious == '\0') {
      if (strlen_P(keyCharArray[key - 1]) == 1) { 
        //key represents a single character
        keyChar = (char)pgm_read_byte(&keyCharArray[key - 1][0]);
      } else {
        //key represents multiple characters
        keyTimer = millis();
        keyPrevious = key;
        keyCharIndex = 0;
      }
    } else {
      if (keyPrevious == key) { //same key has been pressed multiple times
        if ((millis() - keyTimer) < keyDelay) {
          keyCharIndex++; //advance to next character for key
          if (keyCharIndex >= strlen_P(keyCharArray[key - 1])) {
            keyCharIndex = 0;
          }
          keyTimer = millis();
        }
      } else { //key pressed during the timer delay for another key
        keyChar = (char)pgm_read_byte(&keyCharArray[keyPrevious - 1][keyCharIndex]); //return character from previous key
        keyPrevious = '\0';
        keyPending = key;  //save current key to be processed next time function is called
      }
    }
  }
  if ((keyPrevious != '\0') && ((millis() - keyTimer) > keyDelay)) { //return multi-character key press when timer runs out
    keyChar = (byte)pgm_read_byte(&keyCharArray[keyPrevious - 1][keyCharIndex]);
    keyPrevious = '\0';
  }
  return keyChar;
}