Matrix multitap keypad

Hi guys,

I am designing a USB keyboard for my flight sim with x4 columns and x7 rows and x24 buttons in total.

Arduino Pro Micro is used as the main controller device.

All x4 columns are tied to VCC via 10k resistors. Each key has a diode placed in COL to ROW direction. The circuit diagram is shown on the screengrab below:

Cols and rows are connected to the Arduino as per following table:

COL1 COL2 COL3 COL4
D4 D5 D6 D7
ROW1 A3 ECS 1 2, A, B, C 3, D, E, F
ROW2 A2 # 4, G, H, I 5, J, K, L 6, M, N, O
ROW3 A1 * 7, P, Q, R, S 8, T, U, V 9, W, X, Y, Z
ROW4 A0 / - 0 +
ROW5 D8 ENTER CLEAR . SPACE
ROW6 D9 ARR. UP
ROW7 D10 ARR. LEFT ARR. DOWN ARR. RIGHT

Overall keypad layout looks as follows:

Using nice library and examples created and prepared by Nick Gammon ( Gammon Forum : Electronics : Microprocessors : Using a keypad matrix ) and default keyboard.h library I have managed to make it working as a normal USB keyboard no problem.

Here is the code for a reference (works flawlessly):


#include <Keypad_Matrix.h>
#include "Keyboard.h"

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

// Map the buttons to an array for the Keymap instance
const char keys[ROWS][COLS] =

// 4x7 matrix
{
  {'E', '1', '2', '3'},
  {'#', '4', '5', '6'},
  {'*', '7', '8', '9'},
  {'/', '-', '0', '+'},
  {'R', 'C', '.', 'S'},
  {'x', 'x', 'u', 'x'},
  {'x', 'l', 'd', 'r'}
}; 

const byte rowPins[ROWS] = {A3, A2, A1, A0, 8, 9, 10}; //connect to the row pinouts of the keypad
const byte colPins[COLS] = {4, 5, 6, 7}; //connect to the column pinouts of the keypad

// Initialise the Keypad
Keypad_Matrix kpd = Keypad_Matrix( makeKeymap (keys), rowPins, colPins, ROWS, COLS );


void keyDown (const char which) 
{
  //Serial.print (F("Key down: "));
  //Serial.println (which);
  switch (which)
  {
    // --------------   ROW 1 -------------------
    case 'E':
    {
      Serial.println ("ESC BUTTON IS PRESSED");
      Keyboard.press(KEY_ESC);
      delay(100);
      Keyboard.releaseAll();
    }
    break;

    case '1':
    {
      Serial.println ("1 BUTTON IS PRESSED");
      Keyboard.write('1');
      delay(100);
      Keyboard.releaseAll();
    }
    break;

    case '2':
    {
      Serial.println ("2 BUTTON IS PRESSED");
      Keyboard.write('2');
      delay(100);
      Keyboard.releaseAll();
    }
    break;

    case '3':
    {
      Serial.println ("3 BUTTON IS PRESSED");
      Keyboard.write('3');
      delay(100);
      Keyboard.releaseAll();
    }
    break;

    // --------------   ROW 2 -------------------
    case '#':
    {
      Serial.println ("# BUTTON IS PRESSED");
      Keyboard.write('#');
      delay(100);
      Keyboard.releaseAll();
    }
    break;

    case '4':
    {
      Serial.println ("4 BUTTON IS PRESSED");
      Keyboard.write('4');
      delay(100);
      Keyboard.releaseAll();
    }
    break;

    case '5':
    {
      Serial.println ("5 BUTTON IS PRESSED");
      Keyboard.write('5');
      delay(100);
      Keyboard.releaseAll();
    }
    break;

    case '6':
    {
      Serial.println ("6 BUTTON IS PRESSED");
      Keyboard.write('6');
      delay(100);
      Keyboard.releaseAll();
    }
    break;

    // --------------   ROW 3 -------------------
    case '*':
    {
      Serial.println ("* BUTTON IS PRESSED");
      Keyboard.write('*');
      delay(100);
      Keyboard.releaseAll();
    }
    break;

    case '7':
    {
      Serial.println ("7 BUTTON IS PRESSED");
      Keyboard.write('7');
      delay(100);
      Keyboard.releaseAll();
    }
    break;

    case '8':
    {
      Serial.println ("8 BUTTON IS PRESSED");
      Keyboard.write('8');
      delay(100);
      Keyboard.releaseAll();
    }
    break;

    case '9':
    {
      Serial.println ("9 BUTTON IS PRESSED");
      Keyboard.write('9');
      delay(100);
      Keyboard.releaseAll();
    }
    break;

    // --------------   ROW 4 -------------------
    case '/':
    {
      Serial.println ("/ BUTTON IS PRESSED");
      Keyboard.write('/');
      delay(100);
      Keyboard.releaseAll();
    }
    break;

    case '-':
    {
      Serial.println ("- BUTTON IS PRESSED");
      Keyboard.write('-');
      delay(100);
      Keyboard.releaseAll();
    }
    break;

    case '0':
    {
      Serial.println ("0 BUTTON IS PRESSED");
      Keyboard.write('0');
      delay(100);
      Keyboard.releaseAll();
    }
    break;

    case '+':
    {
      Serial.println ("+ BUTTON IS PRESSED");
      Keyboard.write('+');
      delay(100);
      Keyboard.releaseAll();
    }
    break;

    // --------------   ROW 5 -------------------
    case 'R':
    {
      Serial.println ("ENTER BUTTON IS PRESSED");
      Keyboard.press(KEY_RETURN);
      delay(100);
      Keyboard.releaseAll();
    }
    break;

    case 'C':
    {
      Serial.println ("CLEAR BUTTON IS PRESSED");
      Keyboard.press(KEY_BACKSPACE);
      delay(100);
      Keyboard.releaseAll();
    }
    break;

    case '.':
    {
      Serial.println (". BUTTON IS PRESSED");
      Keyboard.write('.');
      delay(100);
      Keyboard.releaseAll();
    }
    break;

    case 'S':
    {
      Serial.println ("SPACE BUTTON IS PRESSED");
      Keyboard.write(' ');
      delay(100);
      Keyboard.releaseAll();
    }
    break;

    // --------------   ROW 6 -------------------
    case 'u':
    {
      Serial.println ("ARROW UP BUTTON IS PRESSED");
      Keyboard.press(KEY_UP_ARROW);
      delay(100);
      Keyboard.releaseAll();
    }
    break;

    // --------------   ROW 7 -------------------
    case 'l':
    {
      Serial.println ("ARROW LEFT BUTTON IS PRESSED");
      Keyboard.press(KEY_LEFT_ARROW);
      delay(100);
      Keyboard.releaseAll();
    }
    break;

    case 'd':
    {
      Serial.println ("ARROW DOWN BUTTON IS PRESSED");
      Keyboard.press(KEY_DOWN_ARROW);
      delay(100);
      Keyboard.releaseAll();
    }
    break;

    case 'r':
    {
      Serial.println ("ARROW RIGHT BUTTON IS PRESSED");
      Keyboard.press(KEY_RIGHT_ARROW);
      delay(100);
      Keyboard.releaseAll();
    }
    break;

  }
}

void setup() 
{
  Serial.begin(9600);   // Initialise the serial monitor
  Serial.println ("Starting.");

  // initialize control over the keyboard:
  Keyboard.begin();

  kpd.begin ();
  kpd.setKeyDownHandler (keyDown);
}

void loop() 
{
  kpd.scan ();
  // do other stuff here
}

The only one thing is left to implement is Multi Tap keys ( 2 thru 9) to be able to type letters..

I have tried a library created by ZulNs ( GitHub - ZulNs/MultitapKeypad: Arduino Library which allows to interface with matrix 4 x 3 phone's keypad as well as 4 x 4. ) but had no luck even when I kept just 4 col x 4 rows arrangement for testing...

I assume the main culprit is that I use COLs pulled high, and he used Rows pulled high (inverted logic) ?

Is there a way to manipulate the library to change switching logic? Or is there another multytap library I have missed?

Appreciate any help and thanks in advance.

Regards,

Phil.

  • When you detect a number 2 thru 9, start a 1 second TIMER.
    If the same button is pressed within 1 sec, advance to the character as seen on the switch face.
    If 1 second goes by without the same button being pushed, the last character is saved.

  • Example:

    • Button 3 is pressed, character is 3.
    • Within 1 second, you press 3 a second time, character is D.
    • Within 1 second, you press 3 a third time, character is E.
    • You wait for 1 whole second, the character is finalized at E.

Note you ça make the code shorter

#include <Keypad_Matrix.h>
#include "Keyboard.h"

const byte rowPins[] = {A3, A2, A1, A0, 8, 9, 10};
const byte colPins[] = {4, 5, 6, 7};

const byte ROWS = sizeof rowPins / sizeof *rowPins;
const byte COLS = sizeof colPins / sizeof *colPins;

const char keys[ROWS][COLS] = {
  {'E', '1', '2', '3'},
  {'#', '4', '5', '6'},
  {'*', '7', '8', '9'},
  {'/', '-', '0', '+'},
  {'R', 'C', '.', 'S'},
  {'x', 'x', 'u', 'x'},
  {'x', 'l', 'd', 'r'}
};

Keypad_Matrix kpd = Keypad_Matrix(makeKeymap(keys), rowPins, colPins, ROWS, COLS);

void keyDown(const char which) 
{
  Serial.print(which);
  Serial.println(" BUTTON IS PRESSED");

  switch (which) {
    case 'E': Keyboard.press(KEY_ESC); break;
    case 'R': Keyboard.press(KEY_RETURN); break;
    case 'C': Keyboard.press(KEY_BACKSPACE); break;
    case 'u': Keyboard.press(KEY_UP_ARROW); break;
    case 'l': Keyboard.press(KEY_LEFT_ARROW); break;
    case 'd': Keyboard.press(KEY_DOWN_ARROW); break;
    case 'r': Keyboard.press(KEY_RIGHT_ARROW); break;
    default: Keyboard.write(which); break;
  }

  delay(100);
  Keyboard.releaseAll();
}

void setup()  {
  Serial.begin(115200); // no need to go slow
  Serial.println("Starting.");
  Keyboard.begin();
  kpd.begin();
  kpd.setKeyDownHandler(keyDown);
}

void loop()  {
  kpd.scan();
}

A state machine is likely a good approach b for handling multitap. Here is a small introduction to the topic: Yet another Finite State Machine introduction

  • Here is a version of a T9 keypad text sender sketch:
//
//================================================^================================================
//                                    4 X 3 Keypad ASCII Writer
//================================================^================================================
//
//  https://forum.arduino.cc/t/matrix-multitap-keypad/1431545/2
//
//  LarryD
//
//  Version    YY/MM/DD    Comments
//  =======    ========    ========================================================================
//  1.00       25/11/14    Running code
//
//
//
//
//  OVERVIEW:
//  ---------------------
//  This sketch implements a 4x3 matrix touch tone phone keypad for sending texting.
//  It allows for alphanumeric input through button cycling.
//
//  FUNCTIONAL LOGIC:
//  ---------------------
//  1. Tapping on buttons 0-9 cycles through assigned ASCII characters when
//     pressed within a 600ms window.
//  2. Auto-Commit: If no key is pressed for 600ms, the current character
//     is locked into the message buffer.
//  3. Manual Commit (Single #): Pressing '#' once immediately finalizes the
//     current character and transmits the full string via Serial.
//  4. Buffer Clear (Double #): Pressing '#' twice within 400ms wipes the
//     entire message buffer to start fresh.
//  5. Backspace (*): Pressing '*' removes the last finalized character.
//
//  NOTES:
//  ---------------------
//  - Memory: Uses C style char arrays (strings) instead of the Arduino 'String'
//    object to ensure 100% stability and prevent heap fragmentation.
//  - Non-blocking: Uses millis() for all timing, ensuring the keypad remains
//    highly responsive.
//  - Safety: Implements buffer overflow protection (20 character buffer limit).
//
//  HARDWARE CONNECTIONS:
//  ---------------------
//  - Matrix Rows 1-4: Arduino Pins 8, 9, 10, 11
//  - Matrix Cols 1-3: Arduino Pins 5, 6, 7
//  - Serial Monitor: 115200 Baud
//
//
//

#include <Keypad.h>



//Keypad Layout
//--------------------
const byte ROWS                        = 4;
const byte COLS                        = 3;

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


//Row and column #s   1  2   3   4
byte rowPins[ROWS] = {8, 9, 10, 11};
byte colPins[COLS] = {5, 6,  7};

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

//https://europe1.discourse-cdn.com/arduino/original/4X/e/c/4/ec46a48da407aa96d9645d284557898a5ed59006.png

//Keypad Table
//For a "T9 phone" keypad.
//--------------------
const char *Table[] =
//Change as needed
{          
  "0 ?-",  // 0: 0 and SPACE - ?
  "1.,!",  // 1: 1 and . , !
  "2ABC",  // 2
  "3DEF",  // 3
  "4GHI",  // 4
  "5JKL",  // 5
  "6MNO",  // 6
  "7PQRS", // 7
  "8TUV",  // 8
  "9WXYZ"  // 9
};

//Miscellaneous
//--------------------
const byte heartbeatLED                = 13;

char lastKey                           = NO_KEY;
int charCycleIndex                     = 0;

//Text message buffer.
//--------------------
int bufferIndex                        = 0;
const int MAX_BUFFER                   = 20;     //Maximum message length.
char messageBuffer[MAX_BUFFER];

//Timing Stuff
//--------------------
unsigned long heartbeatTime;
const unsigned long heartbeatInterval  = 500ul;

unsigned long lastPressTime            = 0;
const unsigned long timeoutInterval    = 600ul;  //Timeout detection to finalizing the character.

unsigned long lastPoundTime             = 0;
const unsigned long doubleTapInterval  = 400ul;  //Double tap detection time-out.


//                                           s e t u p ( )
//================================================^================================================
//
void setup()
{
  Serial.begin(115200);
  Serial.println(F("Buttons 0-9 AlphaNumeric, # Send, Double # Clear, * Backspace"));

  pinMode(heartbeatLED, OUTPUT);

  //Clear message buffer at powerup.
  memset(messageBuffer, 0, MAX_BUFFER);

} //END of   setup()


//                                            l o o p ( )
//================================================^================================================
//
void loop()
{
  //========================================================================  T I M E R  heartbeatLED
  //Is it time to toggle the heartbeat LED ?
  //
  if (millis() - heartbeatTime >= heartbeatInterval)
  {
    //Restart this TIMER.
    heartbeatTime = millis();

    //Gives us a rough indication if the code is non-blocking.
    //Toggle the heartbeat LED.
    digitalWrite(heartbeatLED, !digitalRead(heartbeatLED));
  }


  //========================================================================  T I M E R  pound
  //Has the single # (pound) TIMER expired ?
  //
  //If we pressed # and the double-tap window has expired, it's a Single Tap (Send)
  if (lastPoundTime > 0 && (millis() - lastPoundTime >= doubleTapInterval))
  {
    if (bufferIndex > 0)
    {
      Serial.print(F("\nBuffer SENT: "));
      Serial.println(messageBuffer);
      
      memset(messageBuffer, 0, MAX_BUFFER);
      bufferIndex = 0;
    }

    lastPoundTime = 0;
  }
  

  //======================================================================== T I M E R  lastPress
  //Has the button time out period expired ?
  //
  if (lastKey != NO_KEY && (millis() - lastPressTime >= timeoutInterval))
  {
    finalizeCharacter();
  }


  //========================================================================
  //Check for a T9 matrix button press.
  //
  char currentKey = keypad.getKey();
  if (currentKey != NO_KEY)
  {
    handleInput(currentKey);
  }


  //================================================
  //       Other non blocking code goes here
  //================================================


} //END of   loop()


//                                    h a n d l e I n p u t ( )
//================================================^================================================
//
void handleInput(char key)
{
  switch (key)
  {
    //==================
    case '#':
      {
        unsigned long now = millis();

        //If pressed again within interval: Double Tap (Clear)
        if (now - lastPoundTime < doubleTapInterval && lastPoundTime != 0)
        {
          memset(messageBuffer, 0, MAX_BUFFER);

          bufferIndex = 0;
          lastKey = NO_KEY;
          
          lastPoundTime = 0; 

          Serial.println(F("\nBUFFER CLEARED"));
        }

        else
        {
          //First #, finalize character and start TIMER.
          finalizeCharacter();
          
          //Restart this TIMER.
          lastPoundTime = now;
        }
      }
      break;

    //==================
    case '*':
      {
        finalizeCharacter();

        if (bufferIndex > 0)
        {
          bufferIndex--;
          messageBuffer[bufferIndex] = '\0';
          Serial.print(F("\n[BS] Buffer: "));
          Serial.println(messageBuffer);
        }
      }
      break;

    //==================
    case '0' ... '9':
      {
        int keyNum = key - '0';

        //Check if user is cycling the same key
        if (key == lastKey && (millis() - lastPressTime < timeoutInterval))
        {
          charCycleIndex++;

          if (Table[keyNum][charCycleIndex] == '\0')
          {
            //Wrap back to the start of the key's list.
            charCycleIndex = 0; 
          }
        }

        else
        {
          //New key pressed, finalize existing character first.
          finalizeCharacter();

          lastKey = key;
          charCycleIndex = 0;
        }

        //Restart this TIMER
        lastPressTime = millis();
      }
      break;
  }

} //END of   handleInput()


//                               f i n a l i z e C h a r a c t e r ( )
//================================================^================================================
//
void finalizeCharacter()
{
  if (lastKey != NO_KEY)
  {
    if (bufferIndex < MAX_BUFFER - 1)
    {
      int keyNum = lastKey - '0';
      char finalChar = Table[keyNum][charCycleIndex];

      messageBuffer[bufferIndex] = finalChar;
      bufferIndex++;
      messageBuffer[bufferIndex] = '\0'; //Null terminator

      Serial.print(finalChar); 
    }

    else
    {
      Serial.println(F("\n[ERROR] Buffer Overflow"));
    }

    //Reset state for next cycle
    lastKey = NO_KEY;
    charCycleIndex = 0;
  }

} //END of   finalizeCharacter()


//                                              E N D
//================================================^================================================
//

Your code works with Serial

There is just an extra element to take into account and handle when moving to USB keyboard emulation as you need to send a release for the key and you need at least a handful of ms between the keypress and the release (2 might be enough).

  • Just an example how one can handle multi character buttons like those found on a T9 keypad.
  • Would need to be tweaked for use as USB board.

Thank you @LarryD & @J-M-L !
That T9 keypad sketch worked, but I had to invert Columns to Rows and Rows to Columns, but I guess it is manageable. Out of curiosity, it is possible to invert that logic?

Here’s the modified code with USB keyboard implementation in it (works like a charm):

#include <Keypad.h>
#include "Keyboard.h"



//Keypad Layout
//--------------------
const byte ROWS                        = 4;
const byte COLS                        = 7;

char keys[ROWS][COLS] =
{
  {'E', '#', '*', '/', 'R', 'x', 'x'},
  {'1', '4', '7', '-', 'C', 'x', 'l'},
  {'2', '5', '8', '0', '.', 'u', 'd'},
  {'3', '6', '9', '+', 'S', 'x', 'r'}
};


//Row and column #s   1  2   3   4
byte rowPins[ROWS] = {4, 5, 6, 7};
byte colPins[COLS] = {A3, A2, A1, A0, 8, 9, 10};

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

//https://europe1.discourse-cdn.com/arduino/original/4X/e/c/4/ec46a48da407aa96d9645d284557898a5ed59006.png

//Keypad Table
//For a "T9 phone" keypad.
//--------------------
const char *Table[] =
//Change as needed
{          
  "0 ?-",  // 0: 0 and SPACE - ?
  "1.,!",  // 1: 1 and . , !
  "2ABC",  // 2
  "3DEF",  // 3
  "4GHI",  // 4
  "5JKL",  // 5
  "6MNO",  // 6
  "7PQRS", // 7
  "8TUV",  // 8
  "9WXYZ"  // 9
};

//Miscellaneous
//--------------------
const byte heartbeatLED                = 13;

char lastKey                           = NO_KEY;
int charCycleIndex                     = 0;

//Text message buffer.
//--------------------
int bufferIndex                        = 0;
const int MAX_BUFFER                   = 20;     //Maximum message length.
char messageBuffer[MAX_BUFFER];

//Timing Stuff
//--------------------
unsigned long heartbeatTime;
const unsigned long heartbeatInterval  = 500ul;

unsigned long lastPressTime            = 0;
const unsigned long timeoutInterval    = 600ul;  //Timeout detection to finalizing the character.

unsigned long lastPoundTime             = 0;
const unsigned long doubleTapInterval  = 400ul;  //Double tap detection time-out.


//                                           s e t u p ( )
//================================================^================================================
//
void setup()
{
  Serial.begin(115200);
  Serial.println(F("Buttons 0-9 AlphaNumeric, # Send, Double # Clear, * Backspace"));

  pinMode(heartbeatLED, OUTPUT);

  //Clear message buffer at powerup.
  memset(messageBuffer, 0, MAX_BUFFER);

    // initialize control over the keyboard:
  Keyboard.begin();

} //END of   setup()


//                                            l o o p ( )
//================================================^================================================
//
void loop()
{
  //========================================================================  T I M E R  heartbeatLED
  //Is it time to toggle the heartbeat LED ?
  //
  if (millis() - heartbeatTime >= heartbeatInterval)
  {
    //Restart this TIMER.
    heartbeatTime = millis();

    //Gives us a rough indication if the code is non-blocking.
    //Toggle the heartbeat LED.
    digitalWrite(heartbeatLED, !digitalRead(heartbeatLED));
  }


  //========================================================================  T I M E R  pound
  //Has the single # (pound) TIMER expired ?
  //
  //If we pressed # and the double-tap window has expired, it's a Single Tap (Send)
  if (lastPoundTime > 0 && (millis() - lastPoundTime >= doubleTapInterval))
  {
    if (bufferIndex > 0)
    {
      Serial.print(F("\nBuffer SENT: "));
      Serial.println(messageBuffer);
      
      memset(messageBuffer, 0, MAX_BUFFER);
      bufferIndex = 0;
    }

    lastPoundTime = 0;
  }
  

  //======================================================================== T I M E R  lastPress
  //Has the button time out period expired ?
  //
  if (lastKey != NO_KEY && (millis() - lastPressTime >= timeoutInterval))
  {
    finalizeCharacter();
  }


  //========================================================================
  //Check for a T9 matrix button press.
  //
  char currentKey = keypad.getKey();
  if (currentKey != NO_KEY)
  {
    handleInput(currentKey);
  }


  //================================================
  //       Other non blocking code goes here
  //================================================


} //END of   loop()


//                                    h a n d l e I n p u t ( )
//================================================^================================================
//
void handleInput(char key)
{
  switch (key)
  {
    // --------------   ROW 1 -------------------
    case 'E':
    {
      Serial.print ("ESC");
      Keyboard.press(KEY_ESC);
      delay(100);
      Keyboard.releaseAll();
    }
    break;

    case '1':
    {
      Serial.print ("1");
      Keyboard.write('1');
      delay(100);
      Keyboard.releaseAll();
    }
    break;


    // --------------   ROW 2 -------------------
    case '#':
    {
      Serial.print ("#");
      Keyboard.write('#');
      delay(100);
      Keyboard.releaseAll();
    }
    break;

    // --------------   ROW 3 -------------------
    case '*':
    {
      Serial.print ("*");
      Keyboard.write('*');
      delay(100);
      Keyboard.releaseAll();
    }
    break;

    // --------------   ROW 4 -------------------
    case '/':
    {
      Serial.print ("/");
      Keyboard.write('/');
      delay(100);
      Keyboard.releaseAll();
    }
    break;

    case '-':
    {
      Serial.print ("-");
      Keyboard.write('-');
      delay(100);
      Keyboard.releaseAll();
    }
    break;

    case '0':
    {
      Serial.print ("0");
      Keyboard.write('0');
      delay(100);
      Keyboard.releaseAll();
    }
    break;

    case '+':
    {
      Serial.print ("+");
      Keyboard.write('+');
      delay(100);
      Keyboard.releaseAll();
    }
    break;

    // --------------   ROW 5 -------------------
    case 'R':
    {
      Serial.print ("ENTER");
      Keyboard.press(KEY_RETURN);
      delay(100);
      Keyboard.releaseAll();
    }
    break;

    case 'C':
    {
      Serial.print ("CLEAR");
      Keyboard.press(KEY_BACKSPACE);
      delay(100);
      Keyboard.releaseAll();
    }
    break;

    case '.':
    {
      Serial.print (".");
      Keyboard.write('.');
      delay(100);
      Keyboard.releaseAll();
    }
    break;

    case 'S':
    {
      Serial.print ("SPACE");
      Keyboard.write(' ');
      delay(100);
      Keyboard.releaseAll();
    }
    break;

    // --------------   ROW 6 -------------------
    case 'u':
    {
      Serial.print ("ARROW UP");
      Keyboard.press(KEY_UP_ARROW);
      delay(100);
      Keyboard.releaseAll();
    }
    break;

    // --------------   ROW 7 -------------------
    case 'l':
    {
      Serial.print ("ARROW LEFT");
      Keyboard.press(KEY_LEFT_ARROW);
      delay(100);
      Keyboard.releaseAll();
    }
    break;

    case 'd':
    {
      Serial.print ("ARROW DOWN");
      Keyboard.press(KEY_DOWN_ARROW);
      delay(100);
      Keyboard.releaseAll();
    }
    break;

    case 'r':
    {
      Serial.print ("ARROW RIGHT");
      Keyboard.press(KEY_RIGHT_ARROW);
      delay(100);
      Keyboard.releaseAll();
    }
    break;

    //==================
    case '2' ... '9':
      {
        int keyNum = key - '0';

        //Check if user is cycling the same key
        if (key == lastKey && (millis() - lastPressTime < timeoutInterval))
        {
          charCycleIndex++;

          if (Table[keyNum][charCycleIndex] == '\0')
          {
            //Wrap back to the start of the key's list.
            charCycleIndex = 0; 
          }
        }

        else
        {
          //New key pressed, finalize existing character first.
          finalizeCharacter();

          lastKey = key;
          charCycleIndex = 0;
        }

        //Restart this TIMER
        lastPressTime = millis();
      }
      break;
  }

} //END of   handleInput()


//                               f i n a l i z e C h a r a c t e r ( )
//================================================^================================================
//
void finalizeCharacter()
{
  if (lastKey != NO_KEY)
  {
    if (bufferIndex < MAX_BUFFER - 1)
    {
      int keyNum = lastKey - '0';
      char finalChar = Table[keyNum][charCycleIndex];

      messageBuffer[bufferIndex] = finalChar;
      bufferIndex++;
      messageBuffer[bufferIndex] = '\0'; //Null terminator

      Serial.print(finalChar); 

      Keyboard.write(finalChar);
      delay(100);
      Keyboard.releaseAll();

    }

    else
    {
      Serial.println(F("\n[ERROR] Buffer Overflow"));
    }

    //Reset state for next cycle
    lastKey = NO_KEY;
    charCycleIndex = 0;
  }

} //END of   finalizeCharacter()


//                                              E N D
//================================================^================================================
//

Thank you so much, guys!:+1:

//Row and column #s   1  2   3   4
byte rowPins[ROWS] = {4, 5, 6, 7};
byte colPins[COLS] = {A3, A2, A1, A0, 8, 9, 10};
  • I might be a bit obsessive. :shushing_face:
//Row and column #s    1   2   3   4  5  6   7
byte colPins[COLS] = {A3, A2, A1, A0, 8, 9, 10};
byte rowPins[ROWS] = { 4,  5,  6,  7};

A bit more information :thinking:

You have diodes, so current flows from Columns to Rows. Not sure what you mean by "inverting".

What I meant by inverting is to assign columns in code to hardware columns and rows in code to hardware rows.

I.e in my hardware design the flow is from COLs to ROWs, and, apparently, keypad.h library got it from ROWs to COLs, hence I requred to swap COLS with ROWS in my code to make it working with my hardware.

But, TBH, it works and I’m happy with that :)

Just rename the variables :)

  • I think Keyboard.write() already presses + releases. :thinking:

That’s an option :slight_smile:

Good shout, indeed Keyboard.write() passes both press and release actions. They are leftovers from me playing with Keyboard.press() function, which requires Keyboard.release() or Keyboard.releaseAll();

Right now I’m trying to implement double function keys. E.g. for the “clear” button (case 'C'):

Single press < 1000ms - it deletes just one character at a time (as it does now).

Holding it for >= 1000ms and it will keep on deleting until the button is released (similar to Backspace key action on a normal PC keyboard).

  • That should be doable; start a 1 second timer when the '*' button is detected, then backspace per unit of time until the first character is deleted.

The code will need to handle both press and release events for this to work.