Button Matrix Check and question about encoders

Hello again!

I am attempting to work towards a goal of making a joystick "button box" (as you've seen many a youtube video of, and probably a million forum posts).

Before I go into the coding aspect of everything, I wanted to see if you guys could check and see if my button matrix is correct. This would be my first time attempting something of the sort, so I want to make sure I'm good to go:

The Code would look something like this:

#include <Keypad.h>
#include <Joystick.h>

//DEFINITIONS
#define ENABLE_PULLUPS
#define NUMROTARIES 3 //replace "?" with number of rotary encoders you are using
#define NUMBUTTONS 12 //replace "?"with number of buttong you are using
#define NUMROWS 4 //replace "?" with number of rows you have
#define NUMCOLS 3 //replace "?" with number of columns you have
//BUTTON MATRIX
//first change number of rows and columns to match your button matrix, 
//then replace all "?" with numbers (starting from 0)
byte buttons[NUMROWS][NUMCOLS] = {
  {0,1,2},
  {3,4,5},
  {6,7,8},
  {9,10,11}
};

struct rotariesdef {
  byte pin1;
  byte pin2;
  int ccwchar;
  int cwchar;
  volatile unsigned char state;
};

//ROTARY ENCODERS
//each line controls a different rotary encoder
//the first two numbers refer to the pins the encoder is connected to 
//the second two are the buttons each click of the encoder wil press 
//do NOT exceed 31 for the final button number
rotariesdef rotaries[NUMROTARIES] {
  {3,4,22,23,0}, //rotary 1
  {15,14,24,25,0}, //rotary 2
  {16,10,26,27,0}, //rotary 3


};

#define DIR_CCW 0x10
#define DIR_CW 0x20
#define R_START 0x0

#ifdef HALF_STEP
#define R_CCW_BEGIN 0x1
#define R_CW_BEGIN 0x2
#define R_START_M 0x3
#define R_CW_BEGIN_M 0x4
#define R_CCW_BEGIN_M 0x5
const unsigned char ttable[6][4] = {
  // R_START (00)
  {R_START_M,            R_CW_BEGIN,     R_CCW_BEGIN,  R_START},
  // R_CCW_BEGIN
  {R_START_M | DIR_CCW, R_START,        R_CCW_BEGIN,  R_START},
  // R_CW_BEGIN
  {R_START_M | DIR_CW,  R_CW_BEGIN,     R_START,      R_START},
  // R_START_M (11)
  {R_START_M,            R_CCW_BEGIN_M,  R_CW_BEGIN_M, R_START},
  // R_CW_BEGIN_M
  {R_START_M,            R_START_M,      R_CW_BEGIN_M, R_START | DIR_CW},
  // R_CCW_BEGIN_M
  {R_START_M,            R_CCW_BEGIN_M,  R_START_M,    R_START | DIR_CCW},
};
#else
#define R_CW_FINAL 0x1
#define R_CW_BEGIN 0x2
#define R_CW_NEXT 0x3
#define R_CCW_BEGIN 0x4
#define R_CCW_FINAL 0x5
#define R_CCW_NEXT 0x6

const unsigned char ttable[7][4] = {
  // R_START
  {R_START,    R_CW_BEGIN,  R_CCW_BEGIN, R_START},
  // R_CW_FINAL
  {R_CW_NEXT,  R_START,     R_CW_FINAL,  R_START | DIR_CW},
  // R_CW_BEGIN
  {R_CW_NEXT,  R_CW_BEGIN,  R_START,     R_START},
  // R_CW_NEXT
  {R_CW_NEXT,  R_CW_BEGIN,  R_CW_FINAL,  R_START},
  // R_CCW_BEGIN
  {R_CCW_NEXT, R_START,     R_CCW_BEGIN, R_START},
  // R_CCW_FINAL
  {R_CCW_NEXT, R_CCW_FINAL, R_START,     R_START | DIR_CCW},
  // R_CCW_NEXT
  {R_CCW_NEXT, R_CCW_FINAL, R_CCW_BEGIN, R_START},
};
#endif

//BUTTON MATRIX PART 2
byte rowPins[NUMROWS] = {5,6,7,8}; //change "?" to the pins the rows of your button matrix are connected to
byte colPins[NUMCOLS] = {A0,A1,A2}; //change "?" to the pins the rows of your button matrix are connected to

Keypad buttbx = Keypad( makeKeymap(buttons), rowPins, colPins, NUMROWS, NUMCOLS);

//JOYSTICK SETTINGS
Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID,
  JOYSTICK_TYPE_JOYSTICK,
  32, //number of buttons
  0, //number of hat switches
  //Set as many axis to "true" as you have potentiometers for
  false, // y axis
  false, // x axis
  false, // z axis
  false, // rx axis
  false, // ry axis
  false, // rz axis
  false, // rudder
  false, // throttle
  false, // accelerator
  false, // brake
  false); // steering wheel

const int numReadings = 20;
 
int readings[numReadings];      // the readings from the analog input
int index = 0;              // the index of the current reading
int total = 0;                  // the running total
int currentOutputLevel = 0;

//POTENTIOMETERS PART 1
//add all the axis' which are enabled above
int zAxis_ = 0;
int RxAxis_ = 0;   

               
//POTENTIOMETERS  PART 2
//Which pins are your potentiometers connected to?
//int potentiometerPin1 = ?; //Change "?" to the pin your potentiometer is connected to
//int potentiometerPin2 = ?;
//const bool initAutoSendState = true;


void setup() {
  Joystick.begin();
  rotary_init();
  for (int thisReading = 0; thisReading < numReadings; thisReading++) {
    readings[thisReading] = 0;
  }
}

void loop() {

  CheckAllEncoders();
  CheckAllButtons();
  CheckAllPotentiometers();
 
}

//POTENTIOMETERS PART 3
//change the details to match teh details above for each potentiometer you are using
void CheckAllPotentiometers(){
                           
  //potentiometer 1
//  currentOutputLevel = getAverageOutput(potentiometerPin1);
//  zAxis_ = map(currentOutputLevel,0,1023,0,255);
//  Joystick.setZAxis(zAxis_); 

  //potentiometer 2
//  currentOutputLevel = getAverageOutput(potentiometerPin2);
//  RxAxis_ = map(currentOutputLevel,0,1023,0,255);
//  Joystick.setRxAxis(RxAxis_);


}

int getAverageOutput(int pinToRead){
  index = 0;
  total = 0; 
 
  while (index < numReadings){
    readings[index] = analogRead(pinToRead);
    total = total + readings[index];
    index = index + 1;
    //delay (1);
  }
  return total / numReadings;
}


void CheckAllButtons(void) {
      if (buttbx.getKeys())
    {
       for (int i=0; i<LIST_MAX; i++)   
        {
           if ( buttbx.key[i].stateChanged )   
            {
            switch (buttbx.key[i].kstate) { 
                    case PRESSED:
                    case HOLD:
                              Joystick.setButton(buttbx.key[i].kchar, 1);
                              break;
                    case RELEASED:
                    case IDLE:
                              Joystick.setButton(buttbx.key[i].kchar, 0);
                              break;
            }
           }   
         }
     }
}


void rotary_init() {
  for (int i=0;i<NUMROTARIES;i++) {
    pinMode(rotaries[i].pin1, INPUT);
    pinMode(rotaries[i].pin2, INPUT);
    #ifdef ENABLE_PULLUPS
      digitalWrite(rotaries[i].pin1, HIGH);
      digitalWrite(rotaries[i].pin2, HIGH);
    #endif
  }
}


unsigned char rotary_process(int _i) {
  //Serial.print("Processing rotary: ");
  //Serial.println(_i);
  unsigned char pinstate = (digitalRead(rotaries[_i].pin2) << 1) | digitalRead(rotaries[_i].pin1);
  rotaries[_i].state = ttable[rotaries[_i].state & 0xf][pinstate];
  return (rotaries[_i].state & 0x30);
}

void CheckAllEncoders(void) {
  Serial.println("Checking rotaries");
  for (int i=0;i<NUMROTARIES;i++) {
    unsigned char result = rotary_process(i);
    if (result == DIR_CCW) {
      Serial.print("Rotary ");
      Serial.print(i);
      Serial.println(" <<< Going CCW");
      Joystick.setButton(rotaries[i].ccwchar, 1); delay(50); Joystick.setButton(rotaries[i].ccwchar, 0);
    };
    if (result == DIR_CW) {
      Serial.print("Rotary ");
      Serial.print(i);
      Serial.println(" >>> Going CW");
      Joystick.setButton(rotaries[i].cwchar, 1); delay(50); Joystick.setButton(rotaries[i].cwchar, 0);
    };
  }
  Serial.println("Done checking");
}

Can someone look over this and see if I'm on the correct path here? Please note that I'm omitting the potentiometers because I don't plan on using them in this build.

Also you'll note that there are 3 rotary encoders. Can anyone walk me through how the 5 pinout is connected to the Arduino here? Most rotary encoders I see have 2 pins being connected.

Thank you everyone so much in advance. I am having a lot of fun!

Do you have a device name for the encoders you have? Did you try Google? e.g., "device name datasheet"

Regarding your picture. It is pretty but not a schematic. Schematic are the language of electronics hardware. They allow to identify pins and device names. Your picture does not.
Assuming all pins are I/Os and you want to scan the buttons, this look like a regular matrix.

It is probably a good idea to use some resistors in line to protect the I/O. Also think about what happend if bad or stupid people push multiple buttons at the same time.

The datasheet can be found here! https://www.epitran.it/ebayDrive/datasheet/25.pdf
I see there's a simple tutorial on there, so I should be able to figure out how to get it working with the examples and translate that.

As for the buttons themselves, you'll see I'm a bit green behind the ears (fairly obvious) and do not know if the pins are all I/O. The buttons are momentary no lock buttons and can be found here: DIYhz Momentary Push Button Switch Mini No Lock Round Switch AC 250V / 3A AC 125V /6A Off-ON Red & Black Cap (10 pcs): Amazon.com: Industrial & Scientific

I was a little concerned about how this went from a tutorial push button with ground to button with no ground.

As for the resistors, could you make any recommendations?

[edit] accidentally pushed save before I was ready to publish.

Is that a Pro Micro board ?

The Keypad library should be able to detect multiple key presses. Someone tested that a week ago for me.
The Keypad library is often used with ASCII values for the keys. Suppose the first one is labeled "0", then the ASCII value of '0' is used.

byte buttons[NUMROWS][NUMCOLS] = {
  {'0','1','2'},
  {'3','4','5'},
  {'6','7','8'},
  {'9','10','11'}
};

Some Keypad functions return a zero if no key is pressed. That is no longer possible if you use a 0 for a key.

Three rotary encoders without interrupts ? I don't know how reliable that is. The "Encoder" library can use two or one or none interrupts for a rotary encoder.
The attachInterrupt reference page shows the interrupt pins for each board.

The way you have them connected will mean that if multiple buttons are pressed simultaneously, you will get "phantom" presses appearing, making it look like extra buttons are also pressed when they are not. This may not be a problem for your project and how the button box will be used. But if it is, you need to add a diode in series with each button. A small signal diode would be best, like 1n4148.

3 minimum! Perhaps you were not counting the ground connection?

The fourth pin is for the built-in pushbutton many of these encoders have. You don't have to use them, you can simply ignore them if you want. If you do want to use them, the best way would be to make them part of your matrix to save Arduino pins. With bare encoders, you can do this because normally the 2 pins for the pushbutton are not connected to the 4 pins for the encoder itself. Unfortunately, with this type of encoder module, one of the pushbutton pins is "commoned" to the common pin for the encoder, making it Impossible to make the pushbuttons part of your matrix without also making the encoder also part of the matrix.

Making the encoders part of the matrix is a great idea at first sight. But the pulses from encoders can be quite short, especially if the encoders are turned quickly. If connected as part of a matrix, it would be very easy for the code to miss these short pulses, making the encoder appear faulty to the user. This is why one or two of the pins on an encoder are often connected to interrupt pins on the Arduino, enabling the Arduino to catch even the shortest pulses.

Also, the Keypad library is not written to be able to deal with encoders as part of the matrix (although it would be fine to use the pushbuttons built in to the encoders as part of the matrix, if you used bare encoders).

Are you sure ? It is not hard to detect every button separately and I think the Keypad library does it correctly.

The use of this would be for games like Elite Dangerous and other flying games, so fortunately there should not be a scenario where multiple buttons are pushed, let alone buttons in quick succession.

Yeah I'm a dummy and forgot to count the ground :stuck_out_tongue:
I'm still debating on whether or not to include the encoders, in all honesty. I'm following a bit of a tutorial where the gentleman goes through pretty in depth, but his layout is a little different than mine, making me have to make some small here and there changes. Youtube link: How to Build a Button Box - YouTube

Yes, simultaneous presses can cause this and the Keypad library cannot fix that, only those diodes can. But @dlawler1 says this will not be a problem for this project.

As you are a beginner, it's possible that you could write some code incorrectly which, possibly combined with pushing multiple buttons at once, could cause a short-circuit which could damage the Arduino pins. Some low value resistors, like 220R or 330R would protect against that. The resistor value is not critical.

But you are using the keypad library, so I think the chances of you making one of those coding errors is very low, because of the way the library works, so I'm not sure the resistors would add a worthwhile protection factor. After all, even with resistors, beginners can still make mistakes which can damage Arduino pins.

I'm absolutely planning on not getting this right the first try! That's how ya learn!

I can do you one better. Here is something easy to read.

https://www.digikey.de/en/articles/protecting-inputs-in-digital-electronics

Especially while you are playing with your code use some resistors to keep the current below what the pins can drive (see microcontroller datasheet) in case you create a short.

This is perfect! Thank you so much again!