Might have bit off more than I can chew. 56 buttons, 5 rotaries and 2 joys

I’m trying to impress my step son as he is a huge American Trucking Simulator and Farming Simulator fan. He discovered they sell premade button boxes by Saitek (Logitech) and EvolvPC for both games. With a little googling I found a site that has code to make a 32 function box which is nice but I wanted to combine the best of both retail boxes. Soooo, along came the idea of a 56 button, 5 rotary and 2 3-axis joystick game controller. I researched high and low and thought it would be possible with a Leonardo, some code changes and the use of a couple mcp23017 expanders. After almost 2 weeks of beating my head against my keyboard I’m at a loss and could really use some help.

So what I made was an 8x7 matrix and I’ve varified all buttons do work. I haven’t even attempted to get the rotaries / joys going as I wanted to tackle the buttons first. Unfortunately I have since discovered DirectX only likes you to have 32 buttons max. So then I thought well, the Joystick.h library can make it look like there is multiple HID devices so why not make it look like I have 2 multibutton joysticks.

And that’s where I’ve hit a brick wall. I could really use some help / guidance with this huge undertaking. The most programming experience I ever had was programming in basic on a C64 so bear with me.

The code I’m tinkering with. Also Is it possible to have the rotaries / joys on the i/o expanders using the included libraries or do I just need to buy 2 more leonardo’s and divide everything up :slight_smile:

#include <Keypad.h>
#include <Joystick.h>
#include <Wire.h>
#include "Adafruit_MCP23017.h"

#define ENABLE_PULLUPS

#define NUMROTARIES 4

#define NUMBUTTONS0 56
#define NUMBUTTONS1 56
#define NUMROWS0 8
#define NUMCOLS0 7
#define NUMROWS1 8
#define NUMCOLS1 7

#define JOYSTICK_COUNT 2

byte buttons[NUMROWS0][NUMCOLS0] = {
  {0,1,2,3,4,5,6},
  {7,8,9,10,11,12,13},
  {14,15,16,17,18,19,20},
  {21,22,23,24,25,26,27},
  {28,29,30,31,32,33,34},
  {35,36,37,38,39,40,41},
  {42,43,44,45,46,47,48},
  {49,50,51,52,53,54,55}
};

byte buttons[NUMROWS1][NUMCOLS1] = {
  {56,57,58,59,60,61,62},
  {63,64,65,66,67,68,69},
  {70,71,72,73,74,75,76},
  {77,78,79,80,81,82,83},
  {84,85,86,87,88,89,90},
  {91,92,93,94,95,96,97},
  {98,99,100,101,102,103,104}
};

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

rotariesdef rotaries[NUMROTARIES] {
  {0,1,24,25,0},
  {2,3,26,27,0},
  {4,5,28,29,0},
  {6,7,30,31,0},
};

#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

byte rowPins[NUMROWS0] = {1,0,A5,A4,A3,A2,A1,A0}; 
byte colPins[NUMCOLS0] = {8,7,6,5,4,3,2}; 

byte rowPins[NUMROWS1] = {A0,A1,A2,A3,A4,A5,0,1}; 
byte colPins[NUMCOLS1] = {2,3,4,5,6,7,8}; 

Keypad buttbx = Keypad( makeKeymap(buttons), rowPins, colPins, NUMROWS0, NUMCOLS0);
Keypad buttbx1 = Keypad( makeKeymap(buttons), rowPins, colPins, NUMROWS1, NUMCOLS1); 

Joystick_ Joystick(0x05, JOYSTICK_TYPE_MULTI_AXIS, 32, 2, true, true, true, true, true, true, false, false, false, false, false);

Joystick_ Joystick(0x06, JOYSTICK_TYPE_MULTI_AXIS, 32, 2, true, true, true, true, true, true, false, false, false, false, false);


void setup() {
  Joystick.begin();
  rotary_init();}

void loop() { 

  CheckAllEncoders();

  CheckAllButtons();

}

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) {
   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) {
  for (int i=0;i<NUMROTARIES;i++) {
    unsigned char result = rotary_process(i);
    if (result == DIR_CCW) {
      Joystick.setButton(rotaries[i].ccwchar, 1); delay(50); Joystick.setButton(rotaries[i].ccwchar, 0);
    };
    if (result == DIR_CW) {
      Joystick.setButton(rotaries[i].cwchar, 1); delay(50); Joystick.setButton(rotaries[i].cwchar, 0);
    };
  }
}

Removed since changes are being made

You are creating two variables with the same name - which you can not do. You have to give them separate names like buttons1, buttons2, etc.

byte buttons1[NUMROWS0][NUMCOLS0] = {
  {0,1,2,3,4,5,6},
  {7,8,9,10,11,12,13},
  {14,15,16,17,18,19,20},
  {21,22,23,24,25,26,27},
  {28,29,30,31,32,33,34},
  {35,36,37,38,39,40,41},
  {42,43,44,45,46,47,48},
  {49,50,51,52,53,54,55}
};

byte buttons2[NUMROWS1][NUMCOLS1] = {
  {56,57,58,59,60,61,62},
  {63,64,65,66,67,68,69},
  {70,71,72,73,74,75,76},
  {77,78,79,80,81,82,83},
  {84,85,86,87,88,89,90},
  {91,92,93,94,95,96,97},
  {98,99,100,101,102,103,104}
};

Still a noob at this myself. So I can't be much assistance with your issue. However, if you're shooting for some sort of realism with your setup, the yellow "Parking Brake" should be on the right. Well, on American trucks anyway. I've driven pretty much every major brand available in the U.S. Always on the right.

Oh dang, thank you for catching that. Will swap them around.

I'm making custom keyboard as well. Mine has 36 buttons, 2 rot. encoders, 16 cap. touch sensors and 105 RGB LEDs.

I got fully working prototype made with 32U4 (Leonardo chip), including code – it works great, winning most RTS games I'm playing with it :slight_smile:

Tho 32U4 is too weak (and too expensive) to drive 105 LEDs, so I decided to upgrade MCU. First I tried STM32 (blue pill), it's great chip (a bit over 1usd) if you need it only as HID but I wanted to use serial at the same time as well. Trying to do that I accoutered issues that are "out of my league" and decided to go with SAMD21G18 (2.5usd) – its support is equal to Leonardo, but SAMD has tone of more memory and are many times faster.

Currently I'm changing PCB for SAMD prototype.

I shared these details in case you'll need some help.

First my idea was to use IO expander as well, but Leonardo don't have all pins exposed to the user – it's possible to get 8x7 matix (15pins and more) out of 32U4 (MCU used in Leonardo) chip itself if you design your own PCB or solder jumper wires to some Leonardo MCU pins.

Trust me – not using IO expander will simplify your code a TONE!

Fihn:
DirectX only likes you to have 32 buttons max

What do you mean by this? My keyboard has more than 32 buttons and they all work fine in games...

I just checked – Leonardo has 20 pins exposed to the user, it's way over what you need for your inputs (you can add rotary encoders to your matrix as well), so I strongly recommend to remove IO expander. Are you using diodes in your matrix?

Thank you for continued help!! I am not using any diodes. I didn’t think they were needed as I can use the code (which I have updated below based on your help and in another thread) and I can see all 56 (64 once rotary code is added) button presses as well as able to press buttons simultaneously with no issues. The 32 limitation I refer to is when you open game controller properties (devices and printers) for windows the max displayed is 32 buttons and the joystick library can create multiple HID devices so I split the matrixes into 2 8x8s giving me 32 buttons per “controller”.

Since this does eat up 16 wires I only have 4 inputs left which is where I thought the IO expander would come into play giving me 12 analog inputs to use for the 2 3-axis controllers or use the analogs on the LEO for the joysticks and the 16 pins on the expander for the matrixes.

I’m definitely open to suggestions on getting this completed.

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

#define ENABLE_PULLUPS

#define NUMBUTTONS0 28
#define NUMBUTTONS1 28
#define NUMROWS0 4
#define NUMCOLS0 7
#define NUMROWS1 4
#define NUMCOLS1 7

#define JOYSTICK_COUNT 2

byte buttons0[NUMROWS0][NUMCOLS0] = {
  {0, 1, 2, 3, 4, 5, 6},
  {7, 8, 9, 10, 11, 12, 13},
  {14, 15, 16, 17, 18, 19, 20},
  {21, 22, 23, 24, 25, 26, 27}
};

byte buttons1[NUMROWS1][NUMCOLS1] = {
  {0, 1, 2, 3, 4, 5, 6},
  {7, 8, 9, 10, 11, 12, 13},
  {14, 15, 16, 17, 18, 19, 20},
  {21, 22, 23, 24, 25, 26, 27}
};
byte rowPins0[NUMROWS0] = {A4, A5, 0, 1};
byte colPins0[NUMCOLS0] = {2, 3, 4, 5, 6, 7, 8};

byte rowPins1[NUMROWS1] = {A0, A1, A2, A3};
byte colPins1[NUMCOLS1] = {2, 3, 4, 5, 6, 7, 8};

Keypad buttbx = Keypad( makeKeymap(buttons0), rowPins0, colPins0, NUMROWS0, NUMCOLS0);
Keypad buttbx1 = Keypad( makeKeymap(buttons1), rowPins1, colPins1, NUMROWS1, NUMCOLS1);

Joystick_ Joystick[JOYSTICK_COUNT] = {
  Joystick_ (0x04, JOYSTICK_TYPE_JOYSTICK, 32, 0, true, true, true, true, true, true, false, false, false, false, false),
  Joystick_ (0x05, JOYSTICK_TYPE_MULTI_AXIS, 32, 2, true, true, true, true, true, true, false, false, false, false, false)
};

void setup() {
  Joystick[0].begin();
  Joystick[1].begin();

}

void loop() {
  CheckAllButtons0();
  CheckAllButtons1();

}

void CheckAllButtons0(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[0].setButton(buttbx.key[i].kchar, 1);
            break;
          case RELEASED:
          case IDLE:
            Joystick[0].setButton(buttbx.key[i].kchar, 0);
            break;
        }
      }
    }
  }
}
  void CheckAllButtons1(void) {
    if (buttbx1.getKeys())
    {
      for (int i = 0; i < LIST_MAX; i++)
      {
        if ( buttbx1.key[i].stateChanged )
        {
          switch (buttbx1.key[i].kstate) {
            case PRESSED:
            case HOLD:
              Joystick[1].setButton(buttbx1.key[i].kchar, 1);
              break;
            case RELEASED:
            case IDLE:
              Joystick[1].setButton(buttbx1.key[i].kchar, 0);
              break;
          }
        }
      }
    }
  }

Here is the code that will include the rotaries as joystick buttons as well once I solder them in

#define NUMROTARIES 4

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

rotariesdef rotaries[NUMROTARIES] {
  {9,10,24,25,0},
  {11,12,26,27,0},
  {13,14,28,29,0},
  {15,16,30,31,0},
};

#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


void setup() {
  rotary_init();}

void loop() { 

  CheckAllEncoders();


}


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) {
   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) {
  for (int i=0;i<NUMROTARIES;i++) {
    unsigned char result = rotary_process(i);
    if (result == DIR_CCW) {
      Joystick.setButton(rotaries[i].ccwchar, 1); delay(50); Joystick.setButton(rotaries[i].ccwchar, 0);
    };
    if (result == DIR_CW) {
      Joystick.setButton(rotaries[i].cwchar, 1); delay(50); Joystick.setButton(rotaries[i].cwchar, 0);
    };
  }
}

Wow, that is quite an undertaking. Razer or Corsair may be calling soon to patent your design! :slight_smile: So is it designed like a gamepad similar to the Logitech G13? Last RTS game I think I played was I don't even remember. I was going to say Jagged Alliance 2 but that's turn based.

Sounds like the 32 button maximum is for joysticks (rather than keyboards). Presumably your HID device needs to be a joystick if it is to have axes. With 2 x 3-axis joysticks and 5 rotaries, that's 11 axes. Does that exceed the maximum axes allowed on single device also?

I notice some of your buttons are toggle switches, so I assume they are double-throw rather than momentary? If so, you may have another problem to solve. When you make a matrix of buttons, you get the problem of "ghosting" where certain combinations of buttons pressed at the same time make it appear that other buttons are pressed when they are not. This is a problem with QUERTY keyboards and music keyboards, because often multiple keys are expected to be pressed simultaneously. With the type of keyboard you are making, it is often not expected that multiple buttons will be pressed at the same time, so no problem with ghost presses. But if some of your buttons are double-throw (I.e. 2 position) then ghosts will be a problem. The way to avoid this is to attach a diode in series with every button in the matrix, all pointing the same way, e.g. from column to row.

Point specifically where you need help.

You will realize later that diodes are required, so just add them now... :smiley:
Diodes are for "ghosting", this means when you press few buttons at the same time signal can loop and cause MCU to detect false buttons presses.

For all analog joystics you can use only 1 analog input from LEO and multiplex it as your doing with matrix (just include joystics in the existing matrix), this will free a lot of pins.

As I mentioned – for all your inputs Leonardo IO should be enough.

Fihn:
Wow, that is quite an undertaking. Razer or Corsair may be calling soon to patent your design! :slight_smile: So is it designed like a gamepad similar to the Logitech G13? Last RTS game I think I played was I don’t even remember. I was going to say Jagged Alliance 2 but that’s turn based.

It’s proprietary design targeted at professionals, I can PM you a pic if you’re interested.
Would be nice to get a big check from Razer on Logi but I doubt it :slight_smile:

PaulRB:
…When you make a matrix of buttons, you get the problem of “ghosting” where certain combinations of buttons pressed at the same time make it appear that other buttons are pressed when they are not. This is a problem with QUERTY keyboards and music keyboards, because often multiple keys are expected to be pressed simultaneously…

Coco is right, great minds think alike, tho I was a bit late :slight_smile:

PaulRB:
I notice some of your buttons are toggle switches, so I assume they are double-throw rather than momentary?

They are On-OFF-On momentary toggles. Between the controller I’m building and the Thrustmaster T300 controller he should have enough buttons so none will ever be pressed simultaneously

This is where my knowledge of how this works is a barrier. Since each joystick axis is a potentiometer with 3 pins and uses a range of 0-1023 (forgot the exact arduino range) for positioning I don't follow how I could add the 6 different axis into the same button matrix.

Fihn:
This is where my knowledge of how this works is a barrier. Since each joystick axis is a potentiometer with 3 pins and uses a range of 0-1023 (forgot the exact arduino range) for positioning I don't follow how I could add the 6 different axis into the same button matrix.

It should be something like this (depends on you matrix as well, diodes are required):
I advise you to make a schematics on your circuit, this way we can correct it and add additional elements to it. Maybe try Eady EDA for that. M1 to M6 connect to your matrix output pins.

Here's my hand drawn schematic. It'll take some time for me to learn how to draw the circuit using the software. I think I follow your image. So if I'm looking at it right I'll have one common ground, a common input but I'm not sure I follow the output part since the stick is an input. So 1 input pin and 6 output pins all connected to the arduino?

Here's a diagram of what I went off of when designing my project. This is a little cleaner :grinning: 5x5 matrix then they had the rotaries going to pins 0-7 for 32 total buttons.

Wow, that crafty :smiley:

Nah, don't worry, if you use EasyEDA it's super easy, you just drag and drop component from component list and connect them with line, one 10min youtube video how to use it will get you up and running. Don't be intimidated, its super easy hence it's name.

Are those 2 rows of switches has 3 positions? Can you link or draw their internal diagram? I'm not familiar with those switches. Also those knobs are pulse rotary encoders on potentiometers?
Tomorrow I'll will make you a simplified diagram how I would connect everything, tho as we told you – you like it or not you'll have to use diodes, any will do, even a cheap zeners, but schottky diodes with lower voltage drop is preffered, especially for analog stuff.

3Dgeo:
Are those 2 rows of switches has 3 positions? Can you link or draw their internal diagram? I'm not familiar with those switches. Also those knobs are pulse rotary encoders on potentiometers?

Had some time to sit down and play with EasyEDA. You're right it's pretty straightforward. They are 2 rows of 3 position switches momentary on-off-on, bought off amazon. The drawn diagram below reflects internal diagram for their connections.
Link to product. The knobs are Digital Potentiometer with Push Button 5 Pins. Something like these for the diodes? 20v 1a Diodes

I didn't know you could use coloured & dashed lines in easyEDA.

So your on-off-on momentary switches are, effectively, two momentary pushbuttons in the matrix? If so, that's ok. Since they are momentary, you might get away without diodes and ghosting, it depends how the game is played. If one of those switches is being held in the up or down position while another button is pressed, you could have ghosting problems.

Just wanted to check something: you show the encoders in the above diagram. But all that is connected is the "select" button they have inside. The encoder outputs themselves are shown unconnected. The encoder outputs will be connected directly to the Arduino? My concern is if you try connect the encoder outputs to I/o expanders, this may not be fast enough and could result in missed pulses.

Fihn:
Had some time to sit down and play with EasyEDA. You're right it's pretty straightforward.

Most people waits till they get help, you moving forward yourself – I respect that.

Fihn:
They are 2 rows of 3 position switches momentary on-off-on, bought off amazon. The drawn diagram below reflects internal diagram for their connections.
Link to product. The knobs are Digital Potentiometer with Push Button 5 Pins. Something like these for the diodes? 20v 1a Diodes

I examined your circuit bit more and I have to admit that you did really good job with wiring your matrix.
Switches wiring is good (needs diodes tho).

Lets talk about analog stuff – if I count correctly you have 11 analog inputs, this is more than 7 or 8, so 2 Leonardo analog inputs have to be used, thus making 9x8 matrix. Looking at you great job on making digital matrix I have no doubt that you will have no problem adding analog matrix to it (use diagram I posted yesterday, just double it). You will be left with 7 "free" matrix point where you can add more switches if you feel brave enough :smiley: It does not matter if those extra switches will be analog or digital because they will be hooked to analog input.

EDIT:
Sorry, word "Potentiometer" confused me – they are simple pulse rotary encoders, so you can wire them to matrix as your "on/off/on" switches (diodes required), but still you will need extra 2 pins (only 1 is analog input for joysticks), so total 8x9 matrix.

Also buy bunch of 20K (10K will do if you can't find 20K) resistors, they will be handy later to add pullups/pulldowns and limit the current. As I remember Leo only has internal Pullups, but we can't use pullups cos we are using analog pins in the matrix (this is getting more complicated than you anticipated, isn't it? :smiley: ).

Diodes you linked are more than enough, 1A is way overkill, even 100mA would be overkill, but they will do the job.

I think I covered everything you will need to finish this project.

Oh, another tip – when designing my matrix I kept in mind coding itself, because matrix wiring order can reduce or increase coding amount required to read those buttons (simple "for" loop might not cut it). I understand that this can be a bit overwhelming now, but I just wanted you to know this.

PaulRB:
I didn't know you could use coloured & dashed lines in easyEDA.

Yeah, I didn't know you can do dashed lines either :smiley:

PaulRB:
So your on-off-on momentary switches are, effectively, two momentary pushbuttons in the matrix? If so, that's ok. Since they are momentary, you might get away without diodes and ghosting, it depends how the game is played. If one of those switches is being held in the up or down position while another button is pressed, you could have ghosting problems.

If diodes are required comes to one simple thing – if at certain time there will be only one button active then diodes are not required, if there will more than one button/switch active at the same time – diodes are required, period, end of discussion. :smiley:

PaulRB:
Just wanted to check something: you show the encoders in the above diagram. But all that is connected is the "select" button they have inside. The encoder outputs themselves are shown unconnected. The encoder outputs will be connected directly to the Arduino? My concern is if you try connect the encoder outputs to I/o expanders, this may not be fast enough and could result in missed pulses.

He is working on adding analog stuff at the moment. Yes, slow IO expander one of the reason I advised to remove IO expander and use only LEO pins (there are plenty IO for this).