2x2 Button Pad

This question has been asked a lot, however I have yet to find an answer. I purchased the 2x2 button pad from Sparkfun, and while I've figured out how to control the LEDs, I cannot read button presses.

I've tried what seems like everything, and I've been working on it for days. However, the Arduino behaves very strangely when I tell it to read the 4 button states. I set pin 13 high to output to all the buttons, and I set the 4 button pins as inputs. When I tested it with the multimeter, each button pin was 0v when the button was unpressed, and 5v when pressed (what I expected). The Arduino reads these values strangely, such as 1,0,1,1.

What am I doing wrong? I've experimented with internal pullup resistors, etc., but nothing seems to work. I would extremely appreciate any help.

That's part of the problem. I'm not sure exactly how to wire it. I really just need to learn how to use 4 buttons with 5 pins.

I have the buttons on pins 6, 7, 8, and 12; and the common button pin wired to pin 13 (which might as well be wired to 5v, since it's always HIGH). After reading a little, I added resistors coming from pins 6, 7, 8, and 12 wired to ground that act as pull down resistors.

I think this may have solved my problem, as the serial outputted correct values (for up to 2 buttons pushed at the same time, but I'm not worried about that). However, my code sucks at reading presses (detects some but not others). I'm using the Bounce library, and this is the code I'm using:

guess = -1;
//Waits until a button is pressed
while (guess < 0)
{
    for (int i=0; i<4; i++)
    if (buttons[i].update() && buttons[i].read()==HIGH)
        guess = i;
}

The buttons array contains 4 Bounce objects with 50 second debounce times.

with 50 second debounce times.

50 seconds? That is going to be one unresponsive game.

Pin 13 has an onboard resistor and LED. The common wire should be attached to +V, unless you are setting pin 13 HIGH.

When posting code, please post all of it. Often times, the error in the code is not where you think it is. If you post all of it, we have a better chance of seeing the problem.

Did I say 50 seconds? Haha, I meant 50 milliseconds. And 13 is set to HIGH. I soldered it to pin 13 not knowing how it would function, so I just leave 13 set to HIGH. I wasn't going to post all of my code to avoid revealing my mess to the world, but here it is. A lot may redundant/sloppy; I've been completely focused on button behavior.

#include <RGBLEDs.h>  //My library for controlling the 4 LEDs
#include <Bounce.h>
#include <TrueRandom.h>

int level = 0;
int guess;  //Button press
bool correct = true;  //Tracks progress

int leds[] = {2,3,4,5};  //Contains LED pin locations
int ledColors[] = {9,10,11,12};  //LED color values

RGBLEDs matrix(11,10,9,leds);  //Creates a RGB LED controller

//Pushbuttons
Bounce button1 = Bounce(6,50);
Bounce button2 = Bounce(7,50);
Bounce button3 = Bounce(8,50);
Bounce button4 = Bounce(12,50);

Bounce buttons[] = {button1,button2,button3,button4};  //Bounce objects
int buttonIndexes[] = {6,7,8,12};  //Button pins

int pattern[10];  //Contains the light sequence

/**Sets pin modes and initializes sequence arrray */
void setup()
{
  Serial.begin(9600);
  //Button inputs
  pinMode(6,INPUT);
  pinMode(7,INPUT);
  pinMode(8,INPUT);
  pinMode(12,INPUT);
  pinMode(13,OUTPUT);  //Common button pin
  digitalWrite(13,HIGH);
  for (int i=0; i < 10; i++)
    pattern[i] = TrueRandom.random(0,4);  //Creates random sequence
  for (int i=0; i<2; i++)  //Opening sequence
  {
    matrix.displayColors(500,9,10,11,12);
    matrix.displayColors(70,9,10,11,12);
    matrix.displayColors(70,10,11,12,9);
    matrix.displayColors(70,11,12,9,10);
    matrix.displayColors(70,12,9,10,11);
  }
  delay(500);
}

/*Plays sequence, awaits input, and repeats */
void loop()
{
  displaySequence(level);
  if (acceptInput(level))
    level++;
  else
    correct = true;
}

/*Sequentially lights up each LED in the sequence */
void displaySequence(int currentLevel)
{
  for (int i = 0; i <= currentLevel; i++)
  {
    delay(100);
    matrix.lightButton(pattern[i]);
  }
}

/*Accepts user input with the pushbuttons */
bool acceptInput(int currentLevel)
{
  //Recursively calls the method as per the level
  if (currentLevel > 0)
    acceptInput(currentLevel-1);
  if (correct)
  {
    guess = -1;
    //Waits until a button is pressed
    while (guess < 0)
    {
      for (int i=0; i<4; i++)
        if (buttons[i].update() && buttons[i].read()==HIGH)
          guess = i;
    }
    Serial.println(guess);
    //If the guess is wrong, the game ends
    if (guess != pattern[currentLevel])
      return gameOver();
    else
    {
      matrix.lightButton(guess);
      return true;
    }
    delay(100);
  }
}

/*Plays a sequence signifying game over */
bool gameOver()
{
  for (int i=0; i<1; i++)
  {
    matrix.displayColors(500,9,10,11,12);
    matrix.displayColors(70,9,10,11,12);
    matrix.displayColors(70,10,11,12,9);
    matrix.displayColors(70,11,12,9,10);
    matrix.displayColors(70,12,9,10,11);
  }
  level = 0;
  correct = false;
  return false;
}

UPDATE: Okay, I wired the common button pin to VCC since the onboard resistor might have been interfering. It works great, but you have to hold down the button for a second or so for it to register.

      for (int i=0; i<4; i++)
        if (buttons[i].update() && buttons[i].read()==HIGH)
          guess = i;

Once you have determined that a button is pressed, is it necessary to check the others? You should add curly braces after the if test (around the guess assignment statement), and add a break; statement to the body.

I didn't notice that, thanks. It seems to be a bit more responsive, but still not consistently instant.

Is it possible that I have a memory leak somewhere? No matter what code I put in, the Arduino stops responding after about 30 seconds of use.

Personally, I'd get rid of the recursion. I think that is the source of your problems, even though I can't see any glaring errors.

That's a good point. For some reason I just felt cool including recursion, but I guess I shouldn't sacrifice efficiency.

I took out the recursion, but it still stopped responding around the same time. Could it be my RGB LEDs library? It was my first attempt at making one. Here's the implementation file:

#include "WProgram.h"
#include "RGBLEDs.h"
#include "TrueRandom.h"

int time;

/*Creates a 4 RGB LED matrix that is easily manipulated */
RGBLEDs::RGBLEDs(int red, int green, int blue, int leds[])
{
      for (int i=0; i<4; i++)
      {
            _leds[i] = leds[i];
            pinMode(_leds[i],OUTPUT);
            digitalWrite(_leds[i],HIGH);
      }
      pinMode(red,OUTPUT);
      pinMode(green,OUTPUT);
      pinMode(blue,OUTPUT);
      _red = red;
      _green = green;
      _blue = blue;
}

/* Primary method for lighting a single LED */
void RGBLEDs::displayColors(int length, int color1, int color2, int color3, int color4)
{
      time = millis();
      while (millis()-time < length)
      {
            activateColor(0,color1);
            activateColor(1,color2);
            activateColor(2,color3);
            activateColor(3,color4);
      }
}

/* Lights a single LED based on its location */
void RGBLEDs::lightButton(int button)
{
      time = millis();
      while (millis()-time < 300)
            activateColor(button,button+9);
}

/* Displays a random light sequence */
void RGBLEDs::randomFlashing(int length)
{
      time = millis();
      while (millis()-time < length)
      {
            displayColors(100,TrueRandom.random(9,15),TrueRandom.random(9,15),TrueRandom.random(9,15),TrueRandom.random(9,15));
      }
}

/* Simplified method for activating a single LED */
void RGBLEDs::activateColor(int led, int color)
{
      if (color==9)  //Red 
            activateCode(led,255,0,0);
      else if (color==10)  //Green
            activateCode(led,0,255,0);
      else if (color==11)  //Blue
            activateCode(led,0,0,255);
      else if (color==12)  //Yellow
            activateCode(led,255,150,0);
      else if (color==13)  //Purple
            activateCode(led,200,0,255);
      else if (color==14)  //Orange
            activateCode(led,255,50,0);
      else if (color==15)  //White
            activateCode(led,255,255,255);
}

/* Lights on an LED given RGB codes */
void RGBLEDs::activateCode(int led, int red, int green, int blue)
{
      //Sets RGB values
      analogWrite(_red,red);
      analogWrite(_green,green);
      analogWrite(_blue,blue);
      delay(2);  //Accounts for flickering
      digitalWrite(_leds[led],LOW);
      delayMicroseconds(2100);  //Accounts for flickering
      digitalWrite(_leds[led],HIGH);
}
      for (int i=0; i<4; i++)
      {
            _leds[i] = leds[i];
            pinMode(_leds[i],OUTPUT);
            digitalWrite(_leds[i],HIGH);
      }

You have no control over when constructors are called. So, code that relies on things being ready to initialize are generally not a good idea.

You'll notice, for instance, that Serial has a begin() method, to explicitly initialize the Serial instance. Your class, too, should have a begin() method. All the things that you would normally do in a constructor, in a typical C++ class, should be done in the begin method, if it involves hardware or other classes. The pinMode and digitalWrite methods both involve hardware. You may be trying to initialize hardware that is not yet ready to be initialized when your constructor is called.

      delay(2);  //Accounts for flickering

How does the delay account for flickering? Or the delayMicroseconds?

All the delays are affecting the responsiveness of the buttons.

            displayColors(100,TrueRandom.random(9,15),TrueRandom.random(9,15),TrueRandom.random(9,15),TrueRandom.random(9,15));

White space is a good thing. It is ignored by the compiler, but makes reading the code easy for us non-silicon based life forms.

I fixed the Class so it would set the variables in the constructor, then I created a begin() method for the hardware and called it in my setup() method in the sketch.

/*Creates a 4 RGB LED matrix that is easily manipulated */
RGBLEDs::RGBLEDs(int red, int green, int blue, int leds[])
{
      for (int i=0; i<4; i++)
            _leds[i] = leds[i];
      _red = red;
      _green = green;
      _blue = blue;
}

void RGBLEDs::begin()
{
      for (int i=0; i<4; i++)
      {
            pinMode(_leds[i],OUTPUT);
            digitalWrite(_leds[i],HIGH);
      }
      pinMode(_red,OUTPUT);
      pinMode(_green,OUTPUT);
      pinMode(_blue,OUTPUT);
}

How does the delay account for flickering? Or the delayMicroseconds?

I'm controlling the LEDs by grounding one and sending it the RGB values, then grounding another, etc. Without the delays, the lights blend together to make white. I used trial and error to figure out how long the Arduino needed to set everything up, and 2 & 2.1 milliseconds seemed to work the best.

Could it really be affecting the buttons? This code only runs when the sequence is being displayed.

Could it really be affecting the buttons? This code only runs when the sequence is being displayed.

Probably not, then. Maybe I missed something. You are flashing a series of colors, 10 of them. Then, the user is expected to press some buttons that are related to the colors that were flashed. Is there no feedback when a button is pressed?

How do you communicate pass/fail?

How do you communicate pass/fail?

I've actually been working on that today. I edited the code so each button press would register it's color, the final correct press at each level flashes each light green, and any wrong press flashes all LEDs red. I have yet to write a sequence for winning, since the Arduino still lasts 30 seconds.

#include <RGBLEDs.h>  //My library for controlling the 4 LEDs
#include <Bounce.h>
#include <TrueRandom.h>

int level = 0;
int guess;  //Button press
bool correct = true;  //Tracks progress
int i;  //Loop variable

int leds[] = {2,3,4,5};  //Contains LED pin locations
int ledColors[] = {9,10,11,12};  //LED color values

//Pushbuttons
Bounce button1 = Bounce(6,0);
Bounce button2 = Bounce(7,0);
Bounce button3 = Bounce(8,0);
Bounce button4 = Bounce(12,0);

RGBLEDs matrix(11,10,9,leds);  //Creates a RGB LED controller

Bounce buttons[] = {button1,button2,button3,button4};  //Bounce objects
int buttonIndexes[] = {6,7,8,12};  //Button pins

int pattern[10];  //Contains the light sequence

/**Sets pin modes and initializes sequence arrray */
void setup()
{
  Serial.begin(9600);
  //Button inputs
  pinMode(6,INPUT);
  pinMode(7,INPUT);
  pinMode(8,INPUT);
  pinMode(12,INPUT);
  for (int i=0; i < 10; i++)
    pattern[i] = TrueRandom.random(0,4);  //Creates random sequence
  matrix.begin();
  for (int i=0; i<2; i++)  //Opening sequence
  {
    matrix.displayColors(500,9,10,11,12);
    matrix.displayColors(70,9,10,11,12);
    matrix.displayColors(70,10,11,12,9);
    matrix.displayColors(70,11,12,9,10);
    matrix.displayColors(70,12,9,10,11);
  }
  delay(500);
}

/*Plays sequence, awaits input, and repeats */
void loop()
{
  displaySequence();
  if (acceptInput())
    level++;
  else
    correct = true;
}

/*Sequentially lights up each LED in the sequence */
void displaySequence()
{
  for (i = 0; i <= level; i++)
  {
    delay(100);
    matrix.lightButton(pattern[i]);
  }
}

/*Accepts user input with the pushbuttons */
bool acceptInput()
{
  for (int gameLevel=0; gameLevel<=level;  gameLevel++)
  {
    if (!correct)
      break;
    guess = -1;
    //Waits until a button is pressed
    while (guess < 0)
    {
      for (i=0; i<4; i++)
        if (buttons[i].update() && buttons[i].read()==HIGH)
        {
          guess = i;
          break;
        }
    }
    Serial.println(guess);
    if (guess != pattern[gameLevel])  //If the guess is wrong, the game ends
      return gameOver();
    else if (gameLevel==level)  //If the user completes the level, green is displayed
      matrix.displayColors(500,10,10,10,10);
    else  //Each correct press (except for last) displays its color
      matrix.lightButton(guess);
    delay(100);
  }
  return true;
}

/*Plays a sequence signifying game over */
bool gameOver()
{
  for (i=0; i<2; i++)
  {
    matrix.displayColors(300,9,9,9,9);
    delay(200);
  }
  level = 0;
  correct = false;
  return false;
}

The 30 second run time is killing me. What am I missing?

What is the ledColors array for? Pin 12 is in that array and the buttonIndexes array, and appears to be used by the matrix object.

Huh. I don't know why I put that there. But in the library, 9-15 represent color values. I deleted it and the code compiles and runs the same.

The button issue puzzles me as well. Sometimes it registers it instantaneously, other times I have to hold it down for 5 seconds for it to recognize it.

You have a Serial.begin() statement in setup(), but there are no Serial.print() or Serial.println() calls anywhere. I'd add some print statements to see when various functions are entered and exited and generally get a sense of what is happening.

Does the setup() function get called again when the Arduino has issues? What does happen when it appears to quit working?

I added some println statements, and it seems that the LEDs just stop working. Serial outputs "Waiting" while it's checking for presses, and it functions normally when the Arduino doesn't light the LEDs.

How would I use Serial methods in my library?

UPDATE: After a little testing with the multimeter, I discovered voltage was not being effectively transferred to the digital pin. When I press the button, the voltage difference goes from 0.00 to 2.00 max, but it takes anywhere from half a second to three seconds. I don't think it's the material, although I can reach 3 volts with a standard 22 AWG wire. I assume this is why each button takes so long to respond.

Finally, I ended up checking the RAW and VCC pins, and they're only outputting 3.4 volts! I know I ordered the 5v Pro Mini, so why am I getting less than 4 volts?

The two variations of the Pro Mini are 3.3V and 5V versions. Sounds like you didn't order what you thought you did, or you did not get what you ordered.

There are a number of things that the Arduino can be connected to, like SD card readers, that require 3.3V, so I wouldn't necessarily send it back.

That it is a 3.3V board shouldn't make any difference to the behavior of the application.

How would I use Serial methods in my library?

The Serial instance is a global variable, so you would use it from the library methods just like from a sketch method.

When I press the button, the voltage difference goes from 0.00 to 2.00 max, but it takes anywhere from half a second to three seconds. I don't think it's the material, although I can reach 3 volts with a standard 22 AWG wire.

This does not sound right. When you press a button, the voltage should jump to 3.3V, unless the 2x2 matrix is wired using resistors and is expected to be plugged into a single analog pin for reading.

Do you have a link to the 2x2 button pad?

Oops, I should have sent the link from the beginning.

It might be the button material. Someone suggested it was acting as a capacitor, and I couldn't get continuity with them.

What's the best way to wire the pad?

Yeah yeah yeah, I know. I've posted this on so many forums that I must have gotten lazy.

That's the way I have it configured, but with 10k resistors. I'm going to add some foil to the bottom of the buttons to see if it helps. It seems the foam just won't transfer voltage fast enough.

It's definitely the foam they used on the buttons. I added wire to the bottom of each button and it registered an instant press.

Resistance across what? From the 5v line on all the buttons to the pressed shorted button's pin, it fluctuated around 150-200+ ohms. I'm no multimeter expert, so I couldn't figure out why it gave me negative 5.0+ when I tried to go above 200 ohms.

So now, my question is: how can I easily attach thin solder, wire, or foil to the bottom of the buttons? I tried hot glue, and the buttons worked perfectly, but the hot glue just comes off.