2 x 7 keypad help

All, I can't seem to figure out what I'm doing wrong here. Probably I will kick myself when one of you points it out to me!

I have 14 pushbuttons arranged as a matrix of 2 rows of 7 columns.

I need to monitor these while the Arduino is busy performing calculations, so I'm trying to use interrupts to detect when a button is pressed. For the moment I'm assuming only one button will be pressed at a time.

The 2 rows are attached to the 2 Arduino interrupt pins (digital pins 2 and 3), which are set to INPUT_PULLUP.

The 7 columns are attached to 7 more digital pins, which are set to OUTPUT and LOW.

If any switch is pressed, it connects one if the input pins (2 & 3) to one of the output pins. This pulls the input pin to LOW and fires an interrupt routine.

There are 2 interrupt routines. Which is called depends on which row the closed switch is in. The interrupt routine then has to determine the column that the closed switch is in.

Remember, one of those 7 column pins is pulling the row pin LOW because the switch is closed. So the interrupt routine needs to test each of the 7 column pins in turn. To test each column pin, it sets the column pin to INPUT* and re-reads the row pin. If the switch in that column is closed, the row pin is no longer pulled low (because the column pin is no longer LOW) and so it reads high (because it it set to INPUT_PULLUP).

    • I am setting the column pin back to INPUT rather than HIGH, just in case buttons from 2 columns do accidentally get pressed at the same time. This avoids a short-circuit.

So that's my algorithm. But either my code is not correctly implementing it, or my algorithm is wrong, because, when a button is pressed, the code correctly reports the row, but always reports the column as 7, no matter which button was pressed.

Can anyone spot my error please?

const byte btnRow[2] = {2, 3};
const byte btnCol[7] = {4, 5, 6, 7, 8, 9, 10};

volatile byte rowTrig = 0;
volatile byte colTrig = 0;

void setup() {
  
  Serial.begin(57600);

  pinMode(btnRow[0], INPUT_PULLUP);
  pinMode(btnRow[1], INPUT_PULLUP);
  
  for (byte col=0; col<=6; col++) {
    pinMode(btnCol[col], OUTPUT);
    digitalWrite(btnCol[col], LOW);
  }
  
  attachInterrupt(0, readBtnRow1, FALLING);
  attachInterrupt(1, readBtnRow0, FALLING);
}

void readBtnRow0() {
  readBtn(0);
}

void readBtnRow1() {
  readBtn(1);
}

void readBtn(byte row) {
  
  delayMicroseconds(20000);
  for (byte col=0; col<=6; col++) {
    pinMode(btnCol[col], INPUT);
    delayMicroseconds(20000);
    if (digitalRead(btnRow[row]) == HIGH) {
      rowTrig = row+1;
      colTrig = col+1;
    }
    pinMode(btnCol[col], OUTPUT);
    digitalWrite(btnCol[col], LOW);
  }

}

void loop(){ 
  if (colTrig != 0 || rowTrig != 0) {
    Serial.print("Row ");
    Serial.print(rowTrig);
    Serial.print(" Col ");
    Serial.println(colTrig);
    rowTrig = 0;
    colTrig = 0;
  }
}

(I have tried the code with and without those delayMicrosecond() calls).

Serial monitor looks like this, when I press a random sequence:

Row 2 Col 7
Row 1 Col 7
Row 2 Col 7
Row 1 Col 7
Row 2 Col 7
Row 2 Col 7
Row 2 Col 7
Row 1 Col 7
Row 1 Col 7

Thanks!

I can't see that working at all.From what I can make out once the interrupt is triggered the column is made into an input thus allowing it to float or only being connected to a pulled up input thus pulling it up.

Is that a correct reading of the situation?

PaulRB:
I need to monitor these while the Arduino is busy performing calculations, so I'm trying to use interrupts to detect when a button is pressed.

Ah, well then the answer is dead easy.

Don't use interrupts.

Write your code properly.

PaulRB:
For the moment I'm assuming only one button will be pressed at a time.

You need a diode in series with every key. Then you can - and should - read multiple buttons at once and most importantly, de-bounce them.

You need to poll the buttons each millisecond. Whatever calculations you need to perform can be broken up into pieces which will not take (appreciably) more than one millisecond each.

You should never use "delayMicroseconds(20000)" or any form of "delay()" in a real application (unless there is some need in setup()). You control timing by polling millis() for specific timeouts as you run through your main process loop() at least every millisecond.

Paul__B & Grumpy_Mike, thanks for your support on this.

Just to prove (as much to myself as to you guys) that the circuit & algorithm works, I've written this simple test sketch, which uses no interrupts. It does, however, debounce, as Paul__B suggested.

const byte btnRow[2] = {2, 3};
const byte btnCol[7] = {4, 5, 6, 7, 8, 9, 10};

unsigned int btns = 0;
unsigned int lastBtns = 0;
unsigned long debounce;

void setup() {
  
  Serial.begin(57600);

  pinMode(btnRow[0], INPUT_PULLUP);
  pinMode(btnRow[1], INPUT_PULLUP);
  
  for (byte col=0; col<=6; col++) {
    pinMode(btnCol[col], OUTPUT);
    digitalWrite(btnCol[col], LOW);
  }
  
}

void loop() { 

  unsigned int btnsNow = 0xFFFF;
  
  for (byte row=0; row<=1; row++) {
    for (byte col=0; col<=6; col++) {
      pinMode(btnCol[col], INPUT);
      btnsNow = btnsNow << 1 | digitalRead(btnRow[row]);
      pinMode(btnCol[col], OUTPUT);
      digitalWrite(btnCol[col], LOW);
    }
  }
    
  if (btnsNow != lastBtns) {
    lastBtns = btnsNow;
    debounce = millis();
  }
  else if ((millis() - debounce) > 50) {
    if (btns != btnsNow) {
      btns = btnsNow;
      if (btns != 0xFFFF) {
        Serial.println(btns, BIN);
      }
    }
  }
  
}

And it does work. Here is the output, with me pressing each of the 14 buttons in turn. You can see the diagonal line of 1s corresponding to each button.

1111111110000001
1111111110000010
1111111110000100
1111111110001000
1111111110010000
1111111110100000
1111111111000000
1100000011111111
1100000101111111
1100001001111111
1100010001111111
1100100001111111
1101000001111111
1110000001111111

(Note that I set the unsigned int to 0xFFFF so that there are always two leading 1s. This is just so that each time it prints using BIN format, the columns are all justified).

So I think the problem before was related to lack of debouncing.

As for using interrupts, you are correct, I could poll the switches regularly between at certain points in the calculations. But thinking about it, I don't really need scan all 14, I just need to poll the two row inputs. To explain: this circuit is part of a game between human and Arduino. The calculations I was talking about are the Arduino considering its next move, which takes about 8 seconds. Any button activity at all during that period simply indicates that the human is cheating! The Arduino just needs to detect that, flash some lights, ring some bells and declare the game void.

When its the human's turn to make a move, the Arduino can devote full time to scanning the buttons to figure out what move has been made.

As for the need for diodes, I'm hoping not but need to do some more testing. The Arduino needs to accurately detect either or both buttons pressed in the same column. However, if buttons from two different columns are simultaneously pressed, this again would indicate cheating by the human player, so the Arduino just needs to detect that, but does not need to accurately detect which buttons from different columns are pressed.