Button Matrix - Columns and rows need to be reversed to work

I'm a complete beginner with an Arduino and coding but I can follow tutorials and I can usually stumble my way through it.

So I jumped into making a tiny PCB to simplify wiring with a button matrix on a Pro Micro for a game controller, I knew I needed diodes so that was a new challenge as well. Its a little board the supports 16 buttons and 4 rotary encoders.

I got my PCBs and used code that seemed to have fit the bill of what I needed with some modification, except that for it to work it the rows and column pins need to be reversed, I just find it odd so I'm hoping someone can shed some light on that.

I've followed similar diagrams which showed the placement of the diodes.

//Modfied Code of TOPMO3 version 0.2
//
//
//Arduino IDE 1.6.6 (or above) !
//
//Joystick library from  Matthew Heironimus, https://github.com/MHeironimus/ArduinoJoystickLibrary
// 
//Encoders code from Ben Buxton
//More info: http://www.buxtronix.net/2011/10/rotary-encoders-done-properly.html
// 
//



//Uncomment this for HALFSTEP encoder operation
#define HALF_STEP

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



#define ENABLE_PULLUPS
#define NUMROTARIES 4
#define NUMBUTTONS 16
#define NUMROWS 4
#define NUMCOLS 4


//define the symbols on the buttons of the keypads
byte buttons[NUMROWS][NUMCOLS] = {
  {0,1,2,3},
  {4,5,6,7},
  {8,9,10,11},
  {12,13,14,15},
};



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

rotariesdef rotaries[NUMROTARIES] {
  {2,3,16,17,0}, // RT1
  {4,5,18,19,0}, // RT2
  {6,7,20,21,0}, // RT4
  {8,9,22,23,0}, // RT3
};



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

#ifdef HALF_STEP
// Use the half-step state table (emits a code at 00 and 11)
#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
// Use the full-step state table (emits a code at 00 only)
#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[NUMROWS] = {15,14,16,10}; //connect to the row pinouts of the keypad
byte colPins[NUMCOLS] = {21,20,19,18}; //connect to the column pinouts of the keypad

//initialize an instance of class NewKeypad
Keypad buttbx = Keypad( makeKeymap(buttons), rowPins, colPins, NUMROWS, NUMCOLS); 

//initialize an Joystick with 34 buttons;
Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID, 
  JOYSTICK_TYPE_JOYSTICK, 24, 0,
  false, false, false, false, false, false,
  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++)   // Scan the whole key list.
        {
           if ( buttbx.key[i].stateChanged )   // Only find keys that have changed state.
            {
            switch (buttbx.key[i].kstate) {  // Report active key state : IDLE, PRESSED, HOLD, or RELEASED
                    case PRESSED:
                    case HOLD:
                              Joystick.setButton(buttbx.key[i].kchar, 1);
                              break;
                    case RELEASED:
                    case IDLE:
                              Joystick.setButton(buttbx.key[i].kchar, 0);
                              break;
            }
           }   
         }
     }
}


/* Call this once in setup(). */
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
  }
}


/* Read input pins and process for events. Call this either from a
 * loop or an interrupt (eg pin change or timer).
 *
 * Returns 0 on no event, otherwise 0x80 or 0x40 depending on the direction.
 */
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);
    };
  }
}

Thanks for the help.

We can see from your code that rows are connected to 15,14,16,10 and columns to 21,20,19,18. But when we look at your schematics, the pin names are quite different, so we can't tell what pins on the schematic correspond to what pins in the code. Can you re-draw your schematic so that it uses the same pin numbers as your code?

PaulRB:
We can see from your code that rows are connected to 15,14,16,10 and columns to 21,20,19,18. But when we look at your schematics, the pin names are quite different, so we can't tell what pins on the schematic correspond to what pins in the code. Can you re-draw your schematic so that it uses the same pin numbers as your code?

WOW, I feel like an idiot...
I never saw your reply and now I'm revisiting and someone tried to help me and I ghosted.
Anyways, I've updated the image and hopefully this helps with the issue.

It makes sense now, but I can't see why it would be necessary to swap rows & cols. Never heard anyone else ask about this.

You are using the "keypad" library. Apparently, you do not know how this library works. (Mind you, I don't really either. :grinning: )

The reason that you need to exchange rows and columns is due to the way the diodes are wired. If you reversed all the diodes, you could swap back rows and columns.

It makes sense that I would need to swap the rows and columns because of the direction of the diodes, but, does this have something to do with whether or not the rows are Inputs or outputs or vice versa with the columns?

I guess I'm just trying to figure this out so if I make another PCB I understand the "flow" of how the rows and columns interact.

Yup, that's it. From Keypad.cpp

void Keypad::scanKeys() {
	// Re-intialize the row pins. Allows sharing these pins with other hardware.
	for (byte r=0; r<sizeKpd.rows; r++) {
		pin_mode(rowPins[r],INPUT_PULLUP);
	}

	// bitMap stores ALL the keys that are being pressed.
	for (byte c=0; c<sizeKpd.columns; c++) {
		pin_mode(columnPins[c],OUTPUT);
		pin_write(columnPins[c], LOW);	// Begin column pulse output.

So the "rows" are pulled high (weakly) and the "columns" are pulled (strongly) low during the scan. Therefore current flows from "row" to "column" when a button is pressed. So the diode's anodes need to be connected to the "rows". What are considered "rows" Vs "columns" would be just a point of view, if it were not for the diodes.

Awesome, thanks for the help and clarification.

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