Micro based Keypad

I have made a 3x4 keypad using a micro and i was wondering if it was possible to get the key press to repeat when held down like a normal keyboard would do so. I will put the code below (edit note: some of it is bloat and is not needed. Im too tired to clean up as of now)

/*******************************************************************
 * A multi-mode Macro keyboard with Arduino Pro Micro using      
 * row column matrix.
 * This is a blank sketch to understand key layout and assignments. 
 * (c) 2020 Ryan Bates

 *******************************************************************/
// ----------------------------
// Standard Libraries
// ----------------------------

#include "Keyboard.h"
// Library with a lot of the HID definitions and methods
// Can be useful to take a look at it see whats available
// https://github.com/arduino-libraries/Keyboard/blob/master/src/Keyboard.h

// ----------------------------
// Additional Libraries - each one of these will need to be installed.
// ----------------------------
#include <Encoder.h> //Library for simple interfacing with encoders (up to two)
//low performance ender response, pins do not have interrupts
Encoder RotaryEncoderA(14, 15); //the LEFT encoder (encoder A)
Encoder RotaryEncoderB(10, 16);  //the RIGHT encoder (encoder B)

#include <Keypad.h>
// This library is for interfacing with the 4x4 Matrix
// 
// Can be installed from the library manager, search for "keypad"
// and install the one by Mark Stanley and Alexander Brevig
// https://playground.arduino.cc/Code/Keypad/

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'},
  {'*', '0', '#'},
};
// Variables will change:
int modePushCounter = 0;   // counter for the number of button presses
int buttonState = 0;         // current state of the button
int lastButtonState = 0;     // previous state of the button

long positionEncoderA  = -999; //encoderA LEFT position variable
long positionEncoderB  = -999; //encoderB RIGHT position variable

const int ModeButton = A0;    // the pin that the Modebutton is attached to
const int Mode1= A2;
const int Mode2= A3; //Mode status LEDs

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

void setup() {
  pinMode(ModeButton, INPUT_PULLUP);  // initialize the button pin as a input:  
  Serial.begin(9600); // initialize serial communication:
  pinMode(Mode1,OUTPUT); digitalWrite(Mode1,LOW);
  pinMode(Mode2,OUTPUT); digitalWrite(Mode2,LOW);
  
  Serial.begin(9600);
  Keyboard.begin();
}


void loop() {
char key = keypad.getKey();
      encoderA();
      encoderB();
  
checkModeButton();

  switch (modePushCounter) { // switch between keyboard configurations:
    case 0:
      digitalWrite(Mode1,LOW); digitalWrite(Mode2,LOW); //indicate what mode is loaded
       if (key) {
    Serial.println(key);
    switch (key) {
      case '1': 
        Keyboard.press(KEY_KP_7); break;
      case '2': 
        Keyboard.press(KEY_KP_8); break;
      case '3':
        Keyboard.press(KEY_KP_9); break;
      case '4':
        Keyboard.press(KEY_KP_4); break;
      case '5':
        Keyboard.press(KEY_KP_5); break;
      case '6':
        Keyboard.press(KEY_KP_6); break;
      case '7':
        Keyboard.press(KEY_KP_1); break;
      case '8':
        Keyboard.press(KEY_KP_2); break;
      case '9':
        Keyboard.press(KEY_KP_3); break;
      case '0':
        Keyboard.press(KEY_KP_DOT); break;
      case '*': 
        Keyboard.press(KEY_KP_0); break;
      case '#':
        Keyboard.press(KEY_KP_ENTER); break;
    }
    delay(100); 
    Keyboard.releaseAll(); // this releases the buttons
      }
    break;
    }
  delay(1);// delay in between reads for stability

}

void checkModeButton(){
  buttonState = digitalRead(ModeButton);
  if (buttonState != lastButtonState) { // compare the buttonState to its previous state
    if (buttonState == LOW) { // if the state has changed, increment the counter
      // if the current state is LOW then the button cycled:
      modePushCounter++;
      Serial.println("pressed");
      Serial.print("number of button pushes: ");
      Serial.println(modePushCounter);
    } 
    delay(10); // Delay a little bit to avoid bouncing
  }
  lastButtonState = buttonState; // save the current state as the last state, for next time through the loop
   if (modePushCounter >3){ //reset the counter after 4 presses (remember we start counting at 0)
      modePushCounter = 0;}
}

void encoderA(){
  long newPos = RotaryEncoderA.read()/4; //When the encoder lands on a valley, this is an increment of 4.
  
  if (newPos != positionEncoderA && newPos > positionEncoderA) {
    positionEncoderA = newPos;
    //Serial.println(positionEncoderA);
    Keyboard.press(KEY_LEFT_ARROW);
    Keyboard.release(KEY_LEFT_ARROW);                      }

  if (newPos != positionEncoderA && newPos < positionEncoderA) {
    positionEncoderA = newPos;
    //Serial.println(positionEncoderA);
    Keyboard.press(KEY_RIGHT_ARROW);
    Keyboard.release(KEY_RIGHT_ARROW);                      }
}

void encoderB(){
  long newPos = RotaryEncoderB.read()/4; //When the encoder lands on a valley, this is an increment of 4.
  if (newPos != positionEncoderB && newPos > positionEncoderB) {
    positionEncoderB = newPos;
    //Serial.println(positionEncoderB);
    Keyboard.press(KEY_UP_ARROW);
    Keyboard.release(KEY_UP_ARROW);                      }

  if (newPos != positionEncoderB && newPos < positionEncoderB) {
    positionEncoderB = newPos;
    //Serial.println(positionEncoderB);
    Keyboard.press(KEY_DOWN_ARROW);
    Keyboard.release(KEY_DOWN_ARROW);                      }
}

Make use of keypad events.

You can see when the key is pressed. You can see when the key is released. While it is in between pressed and released, send out the keyboard key you want to at some rate.

Typically the fist keystroke is sent right away, then after a delay of one length, repetition ensues with a different shorter delay between simulated key strokes.

a7

See

a7

thanks for the reply, but for whatever reason i cannot get the Keypad state to be relased. It recognises pressing and holding, however it can't see relase. I can't really do anything atp without it. Is there a reason for this?

I've also removed most of the junk.

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

const byte ROWS = 4; //four rows
const byte COLS = 3; //three columns
char keys[ROWS][COLS] = {
    {'1','2','3'},
    {'4','5','6'},
    {'7','8','9'},
    {'*','0','#'}
};

byte rowPins[ROWS] = {9, 8, 7, 6}; //connect to the row pinouts of the keypad
byte colPins[COLS] = {5, 4, 3}; //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);
}

void loop(){
    Keyboard.begin();
    char key = keypad.getKey();
    if (key) {
    Serial.println(key);
    //Serial.print(getKeypadState());
    switch (key) {
      case '1': 
        Keyboard.press(KEY_KP_7); break;
      case '2': 
        Keyboard.press(KEY_KP_8); break;
      case '3':
        Keyboard.press(KEY_KP_9); break;
      case '4':
        Keyboard.press(KEY_KP_4); break;
      case '5':
        Keyboard.press(KEY_KP_5); break;
      case '6':
        Keyboard.press(KEY_KP_6); break;
      case '7':
        Keyboard.press(KEY_KP_1); break;
      case '8':
        Keyboard.press(KEY_KP_2); break;
      case '9':
        Keyboard.press(KEY_KP_3); break;
      case '0':
        Keyboard.press(KEY_KP_DOT); break;
      case '*': 
        Keyboard.press(KEY_KP_0); break;
      case '#':
        Keyboard.press(KEY_KP_ENTER); break;
    }
    delay(100); 
    Keyboard.releaseAll(); // this releases the buttons
      }
}

/*********** Event listener for the keypad ***********/
void keypadEvent(KeypadEvent key){
    switch (keypad.getState()){
    case PRESSED:
      break;

    case RELEASED:
      break;

    case HOLD:
      break;
    }
}

/*********** Returns the state of the keypad press ***********/
const char* getKeypadState() { 
  const char* state = nullptr; 
  if (keypad.getState() == PRESSED) {
    state = "STATE: PRESSED";
  }
  else if (keypad.getState() == RELEASED) {
    state = "STATE: RELEASED";
  }
  else if (keypad.getState() == HOLD) {
    state = "STATE: HOLD";
  }
  return state;
}

That will help. I can't play with your code now, so I would recommend studying the example, running the example and then exploiting what the example does with the events.

And also through the tiny window I'm looking, I ask you to be sure there's no confusion between keypad release and keyboard release… it doesn't seem to be so, but you gotta admit it is unfortunate to have two objects running around that might easily be confused one with the other.

If key pad event is a keypad press

    set up timing mechanism
    mark us as in between and issue the first key board keystroke, press and release


If  the key pad event is release

    mark us as no longer in between


If we in between (key pad press and key pad release)

    when it is time to, issue key 2nd and subsequent board keystrokes, press and release

I look when I am not moving.

a7

OK, by the time I was done, I only saw things you did not do, and to see how the library really works I ended up with the below, which should run on your hardware.

Run it on your hardware.

If you read the code carefully and don't see how it works, give a shout. You should read the hole thing, but searching for "machine" should turn up all and what I did to implement the pseudocode in #5.

# include <Keypad.h>

// we don't have no stinkin' keyboard

const byte ROWS = 4; //four rows
const byte COLS = 3; //three columns

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

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

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

void setup(){
  Serial.begin(9600);
  Serial.println("Jello Whirled!\n");

  keypad.addEventListener(keypadEvent);
}

void loop(){
  char key = keypad.getKey();
  if (key) {
    Serial.print(key); Serial.print(" ");
  }

// service the keystroke machine
  keystrokeMachine();
}

bool keyMachineActive;
char theCharacter;

void keystrokeMachine()
{
  if (!keyMachineActive) return;

  Serial.print("              stroking ");
  Serial.println(theCharacter);

  delay(222);
}

void keypadEvent(KeypadEvent key){
  switch (keypad.getState()){
  case PRESSED:

    Serial.println("     pressed, start the keystroke machine");
    keyMachineActive = true;
    theCharacter = key;

    break;

  case RELEASED:

    Serial.println("     released, stop the keystroke machine");
    keyMachineActive = false;

    break;

  case HOLD:

    break;
  }
}

Over to you - you could should do something more clever then delay(222) between keystrokes.

HTH

a7

You have added a keypad event listener

    keypad.addEventListener(keypadEvent);

but the function does not appear to do anything except read the state

/*********** Event listener for the keypad ***********/
void keypadEvent(KeypadEvent key)
{
    switch (keypad.getState())
    {
        case PRESSED:
            break;

        case RELEASED:
            break;

        case HOLD:
            break;
    }
}

Yes. When you give it something to do, it seems to.

Also the only call to getKeypadState() was commented out. I found no need for it and lost it.

a7

hey, thanks for helping me, i was able to remove the delay and make it print only one line while being held. I will finish the whole project tomorrow (probably).

Also the only call to getKeypadState() was commented out.

I used it for testing for a while.

Oh, wait, maybe you mean it repeats the keystroke, but only prints that it is doing one time each extended press...

I thought the hole idea was

but you do you, so.

Please post the code that ends up working, or any working sketch that may have been informed by my code.

TIA and CU

a7

Hey, I was able to get the key strokes to repeat with the code i will insert below. It repeats the keystroke when being held like my normal keyboard does.

#include <Keypad.h>
#include <Keyboard.h> // used in order to make the device act like a literal keyboard would.

const byte ROWS = 4; //four rows
const byte COLS = 3; //three columns

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

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

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

void setup()
{
    Serial.begin(9600);
    Serial.println("Jello Whirled!\n");

    keypad.addEventListener(keypadEvent);
}

void loop()
{
    char key = keypad.getKey();
    if (key)
    {
      /*Case declaration for each key. I don't think this can get any more efficent as we may need the ability to switch the function of a key if we want to.
      This will asign a key to press for each key. Again, im not aware of a method to do this any better without the keyboard lib to act as a keyboard.*/
      switch (key) {
      case '1': 
        Keyboard.press(KEY_KP_7); break;
      case '2':
        Keyboard.press(KEY_KP_8); break;
      case '3':
        Keyboard.press(KEY_KP_9); break;
      case '4':
        Keyboard.press(KEY_KP_4); break;
      case '5':
        Keyboard.press(KEY_KP_5); break;
      case '6':
        Keyboard.press(KEY_KP_6); break;
      case '7':
        Keyboard.press(KEY_KP_1); break;
      case '8':
        Keyboard.press(KEY_KP_2); break;
      case '9':
        Keyboard.press(KEY_KP_3); break;
      case '0':
        Keyboard.press(KEY_KP_DOT); break;
      case '*': 
        Keyboard.press(KEY_KP_0); break;
      case '#':
        Keyboard.press(KEY_KP_ENTER); break;
    }
    }

    // service the keystroke machine
    keystrokeMachine();
}

bool keyMachineActive;
char theCharacter;
bool holdingLinePrinted = false;

void keystrokeMachine()
{
    if (!keyMachineActive)
    {
        holdingLinePrinted = false; // Reset the flag when not active
        Keyboard.releaseAll();
        return;
    }

    if (!holdingLinePrinted)
    {
        Serial.print("Holding character: ");
        Serial.println(theCharacter);
        holdingLinePrinted = true; // Set the flag once the line is printed
    }
}

void keypadEvent(KeypadEvent key)
{
    switch (keypad.getState())
    {
    case PRESSED:
        Serial.print("Pressed character: ");
        Serial.println(theCharacter);
        keyMachineActive = true;
        theCharacter = key;
        break;

    case RELEASED:
        Serial.print("Released character: ");
        Serial.println(theCharacter);
        keyMachineActive = false;
        break;

    case HOLD:
        break;
    }
}

Edit: Oh and yes, i was only trying to get the actual keyboard press to repeat, not the print.

I don't argue with success, but I believe if you read and follow the logic of your version you clu,d eliminate the machine I wrote by moving all the actions it supervises into the switch/case statement in your keypadEvent() function.

It is usually best to do things with the least amount of code. Here between us we have made a logical maze of getting the desired functionality.

I excuse myself, as I was under the impression that you would need to generate the keystrokes yourself; all you've really ended up with is making the held key look held to the entity receiving the keystrokes - there is where the repetition is being performed.

Your big problem was issuing the release prematurely.

Can't but might try this later myself, the trouble is as you may have seen is that I am working from ignorance and a lack of a keyboard connected the way you are doing.

a7

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.