Simultaneous button reading?

Instead of using the high level digitalRead() function access the low level port registers. See this documentation.

A port register is one byte and each bit represents one of the digital inputs on the arduino.

Do something like this:

pin_status = PIND; //Input port D (pins 0-7)
button_1 = bitRead(pin_status, 2); // digital pin 2
button_2 = bitRead(pin_status, 3); // digital pin 3

The values are read simultaneously so it is possible to have a tie.


There is a very easy solution to your problem, and it's called "interrupts".

The processes inside loop method are executed synchronously, so always one read will be done before second one, and always one check will be done before a second one (even if they are inside one if statement)

Fortunately there is a way of simulating asynchronous behavior in ATmega. It's called "external interrupt". You can setup ATmega inside Arduino to stop processing loop method and instead immediately run a different method whenever a voltage on one of the pins change. You are "interrupting" execution of loop hence the name "interrupt". In Arduino Uno this can be done (only) on pins 2 and 3.

https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/

I made a little model i Tinkercad

Arduino with two push buttons

Whenever you push left button PIN2 of the Arduino gets shorted to ground. Whenever you push right button PIN3 of the Arduino gets shorted to ground.

And now my source code

void setup()
{
  pinMode(2, INPUT_PULLUP);
  pinMode(3, INPUT_PULLUP);

  interrupts();
  attachInterrupt(digitalPinToInterrupt(2), Player1ButtonPress, FALLING);
  attachInterrupt(digitalPinToInterrupt(3), Player2ButtonPress, FALLING);

  Serial.begin(9600);
}

void Player1ButtonPress() {
  Serial.println("Player 1 wins");
}

void Player2ButtonPress() {
  Serial.println("Player 2 wins");
}

void loop()
{
    //nothing here
}

And now some explanation. In setup we first setup PIN2 and PIN3 as "input with pullup resistor". In our design this means that when button is not pressed those pins will have 5V on them, but as soon as the buttons are pressed voltages go down to 0V. Then we make sure that interrupts are turned on (they are on by default, but I like to show everyone who is reading the code that I will be using them) and then we do some attachInterrupt magic. We make that a "falling edge" (change from 5V to 0V) on PIN2 and PIN3 will immediately run methods Player1ButtonPress and Player2ButtonPress.

When I was playing with buttons they worked perfectly.

enter image description here

And a word about possible cheating. Keeping the button pressed does not create a "falling edge" (voltage on a pin is constantly 0) so if you only add some global variables with info when game starts and who won. Then add some conditions in the two methods responsible for interrupts. You can then use the loop method to start the game, and show result.


I think you want after a random time, a 'Start' LED to be lit, and whoever presses his/her button first wins.

There are some problems in your sketch:

  • It seems even when a button is pressed to early, the button is still taken into account. You should check for this 'cheating'. So check directly after the 'start' LED is lit, that no buttons are pressed. If so, that player loses.
  • Assuming the players are honest, you check for their buttons, which probably the first iteration it will not be pressed (that would be an immediate reaction speed). However, than you wait 5 seconds; probably both players pressed their buttons, and you check the button of the first player first. It will help, if you change the delay to 1 ms, or even remove it completely.

Untested proposal:

int playerXWon = 0; // No player won yet, 1 = player 1 wins, 2 = player 2 wins, 3 = draw

void loop() 
{ 
   // Delay for a random time.
   P1_Time = random(2000, 5000);
   delay(P1_Time);

   CheatCheck();

   if (playerXWon == 0)
   {
      // Start
      digitalWrite(P1_Pin, HIGH); 

      checkFirstPress(); 
   }
}

void CheatCheck()
{
   // Check if a player did not press its button too early. 
   // (Btw, it does not matter if the player presses and release its button before the LED goes on.
   S1_State = digitalRead(S1_Pin);
   S2_State = digitalRead(S2_Pin);

   if (S1_State == HIGH)
   {
      else if (S2_State == HIGH) 
      {
         PlayerWin(3);
      }
      else 
      {
         PlayerWin(2);
      }
   }
   else 
   {
      if (S2_State == HIGH) 
      {
         PlayerWin(1);
      }
   }
}

void checkFirstPress() 
{
   // Whoever presses first wins.
   while (playerXWon == 0) 
   {
      S1_State = digitalRead(S1_Pin);
      S2_State = digitalRead(S2_Pin);  

      if (S1_State == HIGH) 
      {
         if (S2_State == HIGH) 
         {
            PlayerWin(3);
         }
         else if (S1_State == HIGH) 
         {
            PlayerWin(1);
         }
      } 
      else if (S2_State == HIGH)
      {
         PlayerWin(2);
      }
   }
}

void playerWins(int player)
{
   switch (player) 
   {
   case 1:
      Serial.print("Player 1 wins\n");
      break;

   case 2:
      Serial.print("Player 2 wins\n");
      break;

   case 3:
      Serial.print("Both players win\n");
      break;

    default:
       assert(NULL); // Programming error
       break;
   }

   playerXWon = player;

   digitalWrite(P1_Pin, LOW);
}

Tags:

Loop