Simultanous button reading?
I'm currently working on a project that requires that I simultanously check the state of two buttons. Each button HIGH state is assigned to one if loop. Here's the basic concept:
void loop() {
S1_State = digitalRead(S1_Pin);
S2_State = digitalRead(S2_Pin);
ButtonPRESSED = false;
P1_Time = random(2000, 5000);
delay(P1_Time);
digitalWrite(P1_Pin, HIGH);
while(ButtonPRESSED == false) {
if (S1_State == HIGH) {
Serial.print("Player 1 wins");
Serial.print("");
digitalWrite(P1_Pin, LOW);
ButtonPRESSED = true;
delay(5000); }
if (S2_State == HIGH) {
Serial.print("Player 2 wins");
Serial.print("");
digitalWrite(P1_Pin, LOW);
ButtonPRESSED = true;
delay(5000); }
The problem is that checking for the button states this way requires that the S1_State loop _has to be read before the S2_State loop_, thus giving player 1 an advantage. Although I'm not sure how pronounced this issue is in practice, it certainly was noticable when I was first trying this project out through python code on a Pi through similar means.
Is it possible check the conditions to both if loops simultanously? If not, are there other ways to circumvent this problem?
loop
New contributor
add a comment |
I'm currently working on a project that requires that I simultanously check the state of two buttons. Each button HIGH state is assigned to one if loop. Here's the basic concept:
void loop() {
S1_State = digitalRead(S1_Pin);
S2_State = digitalRead(S2_Pin);
ButtonPRESSED = false;
P1_Time = random(2000, 5000);
delay(P1_Time);
digitalWrite(P1_Pin, HIGH);
while(ButtonPRESSED == false) {
if (S1_State == HIGH) {
Serial.print("Player 1 wins");
Serial.print("");
digitalWrite(P1_Pin, LOW);
ButtonPRESSED = true;
delay(5000); }
if (S2_State == HIGH) {
Serial.print("Player 2 wins");
Serial.print("");
digitalWrite(P1_Pin, LOW);
ButtonPRESSED = true;
delay(5000); }
The problem is that checking for the button states this way requires that the S1_State loop _has to be read before the S2_State loop_, thus giving player 1 an advantage. Although I'm not sure how pronounced this issue is in practice, it certainly was noticable when I was first trying this project out through python code on a Pi through similar means.
Is it possible check the conditions to both if loops simultanously? If not, are there other ways to circumvent this problem?
loop
New contributor
read through your code line by line ..... imagine that both the buttons are pressed at the same time ....... is that the expected behavior?
– jsotola
2 days ago
add a comment |
I'm currently working on a project that requires that I simultanously check the state of two buttons. Each button HIGH state is assigned to one if loop. Here's the basic concept:
void loop() {
S1_State = digitalRead(S1_Pin);
S2_State = digitalRead(S2_Pin);
ButtonPRESSED = false;
P1_Time = random(2000, 5000);
delay(P1_Time);
digitalWrite(P1_Pin, HIGH);
while(ButtonPRESSED == false) {
if (S1_State == HIGH) {
Serial.print("Player 1 wins");
Serial.print("");
digitalWrite(P1_Pin, LOW);
ButtonPRESSED = true;
delay(5000); }
if (S2_State == HIGH) {
Serial.print("Player 2 wins");
Serial.print("");
digitalWrite(P1_Pin, LOW);
ButtonPRESSED = true;
delay(5000); }
The problem is that checking for the button states this way requires that the S1_State loop _has to be read before the S2_State loop_, thus giving player 1 an advantage. Although I'm not sure how pronounced this issue is in practice, it certainly was noticable when I was first trying this project out through python code on a Pi through similar means.
Is it possible check the conditions to both if loops simultanously? If not, are there other ways to circumvent this problem?
loop
New contributor
I'm currently working on a project that requires that I simultanously check the state of two buttons. Each button HIGH state is assigned to one if loop. Here's the basic concept:
void loop() {
S1_State = digitalRead(S1_Pin);
S2_State = digitalRead(S2_Pin);
ButtonPRESSED = false;
P1_Time = random(2000, 5000);
delay(P1_Time);
digitalWrite(P1_Pin, HIGH);
while(ButtonPRESSED == false) {
if (S1_State == HIGH) {
Serial.print("Player 1 wins");
Serial.print("");
digitalWrite(P1_Pin, LOW);
ButtonPRESSED = true;
delay(5000); }
if (S2_State == HIGH) {
Serial.print("Player 2 wins");
Serial.print("");
digitalWrite(P1_Pin, LOW);
ButtonPRESSED = true;
delay(5000); }
The problem is that checking for the button states this way requires that the S1_State loop _has to be read before the S2_State loop_, thus giving player 1 an advantage. Although I'm not sure how pronounced this issue is in practice, it certainly was noticable when I was first trying this project out through python code on a Pi through similar means.
Is it possible check the conditions to both if loops simultanously? If not, are there other ways to circumvent this problem?
loop
loop
New contributor
New contributor
edited 2 days ago
oh double-you oh
New contributor
asked 2 days ago
oh double-you ohoh double-you oh
184
184
New contributor
New contributor
read through your code line by line ..... imagine that both the buttons are pressed at the same time ....... is that the expected behavior?
– jsotola
2 days ago
add a comment |
read through your code line by line ..... imagine that both the buttons are pressed at the same time ....... is that the expected behavior?
– jsotola
2 days ago
read through your code line by line ..... imagine that both the buttons are pressed at the same time ....... is that the expected behavior?
– jsotola
2 days ago
read through your code line by line ..... imagine that both the buttons are pressed at the same time ....... is that the expected behavior?
– jsotola
2 days ago
add a comment |
5 Answers
5
active
oldest
votes
There is a very easy solution to your problem, and it's caller "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
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.
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.
This seems just about perfect, thank you!
– oh double-you oh
2 days ago
Does the interrupt handling give either player an advantage? What happens in the event of a tie?
– Craig
2 days ago
@Craig The more I think about it the more I think that there still will be a tiny bias, because ATmega will analyze values of pins with 16MHz frequency so there still is possible that a "tie" will happen and one button will be chosen by processor to be a "winner" incorrectly. But this time the decision will take only 1 clock cycle (6 nano seconds).digitalRead
take about 50 clock cycles, and we need two of them. So we gained 100 times more resolution and I think it's enough for a "game" to risk suck event. Nothing is perfect in electronics.
– Filip Franik
2 days ago
add a comment |
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.
add a comment |
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 winsn");
break;
case 2:
Serial.print("Player 2 winsn");
break;
case 3:
Serial.print("Both players winn");
break;
default:
assert(NULL); // Programming error
break;
}
playerXWon = player;
digitalWrite(P1_Pin, LOW);
}
The cheating pevention will come later, I'm aware of that problem and will add it later. I wanted to tackle this first as it's more important and unlike the cheating prevention I don't know how to do it.
– oh double-you oh
2 days ago
I'm not sure how much that approach would change things as it would most likely still require one button to be read after the other in whatever loop those 5 seconds take place in. Could you clarify what you mean by "change the delay"?
– oh double-you oh
2 days ago
Actually, it's better to remove the delay. When it's removed, many times per ms the buttons are checked, so whoever presses the button first will win. Now it is only checked every 5 seconds, and probably both players press the button within this time easily.
– Michel Keijzers
2 days ago
I also split the big function in smaller functions.
– Michel Keijzers
2 days ago
Lot's of stuff here I haven't learned yet like switch functions and using void loops as functions, thanks for the pointers.
– oh double-you oh
2 days ago
|
show 1 more comment
I was not sure about posting this as an answer, since it is basically some more considerations on the three existing answers (for reference, they are Michel Keijzers's one - using digitalRead -, Craig's one - read the PIND variable at once -, Filip Franik's one - use interrupts), but it became quite long and so a single comment could not fit all of this.
What you and the other answerers wrote is in theory correct. Checking the button of player 1 before player 2 gives an advantage to player one. What you lack is... How much advantage?
Mechanical actions (such as the pressing of a button) have a time duration that is around 50ms*.
Since I assume your application is a reaction time tester, human reaction times are around 250ms.
Moreover mechanical buttons have "bounces". Typical debounce times are in the range 20-100ms. You can avoid this by just checking the first edge.
A player gets an advantage if he is awarded as winner even if the button was pressed at the same time.
_* I tried to find a source for this, but I was not able to get some data. I tried with an online stopwatch and pressing the two spacebars (notebook and USB keyboard) roughly at the same time, obtaining results around 75ms. This is not a real value, so if someone has some measured values or estimations feel free to comment
Now, let's assume you properly coded your program so that you check for ties and avoid delays, since these will greatly affect the following measurements.
In the digitalRead
case you are executing this series of actions:
- Read status of pin 1 - 3.6us
- Read status of pin 2 - 3.6us
- Check the statuses and decide who won - some tens of instructions (about 1us)
- Loop - a couple instructions (0.5us)
The 60 instructions comes from this thread (3.6us = 57.8 instructions @16MHz); as for the others it's a rough measurement. Let's assume that the function actually samples the pin at the very end of the function.
Now, since you are checking for ties, if both buttons get pressed during phases 3, 4 or 1 you will get a tie (since both buttons will be read as pressed). If both buttons get pressed during phase 2, button 2 will be marked as pressed and button 1 not, thus giving advantage to that player. The time when this happens is about 3.6us long. So you are giving a 3.6us advantage to button 2 player.
In the PIND case you are reading the buttons exactly at the same time. The advantage is thus 0.
In the interrupt case when both EXT0 and EXT1 interrupts are triggered at the same time EXT0 gets executed, since it is before in the ISR. Consequently the player pressing the button on EXT0 has one interrupt checking cycle of advantage. I admit I do not know how often the inputs are checked for the EXT interrupt, but I assume it performs a check every clock cycle (consequently the advantage is 62.5ns).
Now, let's sum up the advantage
- digitalRead: 3.6us
- PIND: 0
- interrupt: 62.5ns
The solution is pretty obvious to me, and it is... Don't care! Even in the worst case scenario you are four order of magnitude faster than your phenomenon. You are giving one player a 4us advantage in a 250ms game (0.0016% of advantage). I'm pretty sure that the mechanical differences between the two buttons (maybe one is a bit stiffer, or has a slightly higher size, or has a slightly lower contact) affect the reading much more than this.
In the end, with this kind of setup you will reasonably be able to have an accuracy that is at most some tens of milliseconds. With other measurement setups (maybe optically based) you can go as low as 1ms. Adding 4us of advantage to one player will not influence the result.
Try to focus on the rest of the program, and for this problem use whichever solution you find more understandable.
NOTE: This is based on the assumption that a proper program is developed, with tie checks and no delays and so on. The program as you wrote in the question is not ok from this point of view (as it is now it will stay in the loop forever; if you change Sx_State
to digitalRead
it will become roughly fair - something like 4us of advantage to player one and 3.8us of advantage to player two)
add a comment |
I have to concur with frarugi87 here. The few microseconds taken by
digitalRead()
are completely inconsequential for your application. But
let me add that, even if your players were super-humans capable of
sub-millisecond reaction times, you are not giving an advantage to any
of them if you alternatively check button 1, then button 2,
then button 1, and so on. The reason is that, in the small time
window between reading button 1 and reading button 2, you give
an advantage to player 2: he will win if both buttons are pressed
simultaneously. In the following time window (between checking
button 2 and button 1) the same advantage goes to
player 1.
The net result is that the game has a tiny amount of randomness: if both
buttons are pressed simultaneously, the result is seemingly random. But
again, the chances of this happening are so tiny that it makes no sense
to worry about it.
Here is how I would determine the winner. I think this code makes clear
that we are alternatively checking button 1, then button 2,
then button 1...
int find_winner()
{
for (;;) {
if (digitalRead(S1_Pin) == HIGH)
return 1;
if (digitalRead(S2_Pin) == HIGH)
return 2;
}
}
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
return StackExchange.using("schematics", function () {
StackExchange.schematics.init();
});
}, "cicuitlab");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "540"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
oh double-you oh is a new contributor. Be nice, and check out our Code of Conduct.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2farduino.stackexchange.com%2fquestions%2f60618%2fsimultanous-button-reading%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
5 Answers
5
active
oldest
votes
5 Answers
5
active
oldest
votes
active
oldest
votes
active
oldest
votes
There is a very easy solution to your problem, and it's caller "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
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.
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.
This seems just about perfect, thank you!
– oh double-you oh
2 days ago
Does the interrupt handling give either player an advantage? What happens in the event of a tie?
– Craig
2 days ago
@Craig The more I think about it the more I think that there still will be a tiny bias, because ATmega will analyze values of pins with 16MHz frequency so there still is possible that a "tie" will happen and one button will be chosen by processor to be a "winner" incorrectly. But this time the decision will take only 1 clock cycle (6 nano seconds).digitalRead
take about 50 clock cycles, and we need two of them. So we gained 100 times more resolution and I think it's enough for a "game" to risk suck event. Nothing is perfect in electronics.
– Filip Franik
2 days ago
add a comment |
There is a very easy solution to your problem, and it's caller "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
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.
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.
This seems just about perfect, thank you!
– oh double-you oh
2 days ago
Does the interrupt handling give either player an advantage? What happens in the event of a tie?
– Craig
2 days ago
@Craig The more I think about it the more I think that there still will be a tiny bias, because ATmega will analyze values of pins with 16MHz frequency so there still is possible that a "tie" will happen and one button will be chosen by processor to be a "winner" incorrectly. But this time the decision will take only 1 clock cycle (6 nano seconds).digitalRead
take about 50 clock cycles, and we need two of them. So we gained 100 times more resolution and I think it's enough for a "game" to risk suck event. Nothing is perfect in electronics.
– Filip Franik
2 days ago
add a comment |
There is a very easy solution to your problem, and it's caller "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
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.
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.
There is a very easy solution to your problem, and it's caller "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
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.
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.
edited 2 days ago
answered 2 days ago
Filip FranikFilip Franik
32027
32027
This seems just about perfect, thank you!
– oh double-you oh
2 days ago
Does the interrupt handling give either player an advantage? What happens in the event of a tie?
– Craig
2 days ago
@Craig The more I think about it the more I think that there still will be a tiny bias, because ATmega will analyze values of pins with 16MHz frequency so there still is possible that a "tie" will happen and one button will be chosen by processor to be a "winner" incorrectly. But this time the decision will take only 1 clock cycle (6 nano seconds).digitalRead
take about 50 clock cycles, and we need two of them. So we gained 100 times more resolution and I think it's enough for a "game" to risk suck event. Nothing is perfect in electronics.
– Filip Franik
2 days ago
add a comment |
This seems just about perfect, thank you!
– oh double-you oh
2 days ago
Does the interrupt handling give either player an advantage? What happens in the event of a tie?
– Craig
2 days ago
@Craig The more I think about it the more I think that there still will be a tiny bias, because ATmega will analyze values of pins with 16MHz frequency so there still is possible that a "tie" will happen and one button will be chosen by processor to be a "winner" incorrectly. But this time the decision will take only 1 clock cycle (6 nano seconds).digitalRead
take about 50 clock cycles, and we need two of them. So we gained 100 times more resolution and I think it's enough for a "game" to risk suck event. Nothing is perfect in electronics.
– Filip Franik
2 days ago
This seems just about perfect, thank you!
– oh double-you oh
2 days ago
This seems just about perfect, thank you!
– oh double-you oh
2 days ago
Does the interrupt handling give either player an advantage? What happens in the event of a tie?
– Craig
2 days ago
Does the interrupt handling give either player an advantage? What happens in the event of a tie?
– Craig
2 days ago
@Craig The more I think about it the more I think that there still will be a tiny bias, because ATmega will analyze values of pins with 16MHz frequency so there still is possible that a "tie" will happen and one button will be chosen by processor to be a "winner" incorrectly. But this time the decision will take only 1 clock cycle (6 nano seconds).
digitalRead
take about 50 clock cycles, and we need two of them. So we gained 100 times more resolution and I think it's enough for a "game" to risk suck event. Nothing is perfect in electronics.– Filip Franik
2 days ago
@Craig The more I think about it the more I think that there still will be a tiny bias, because ATmega will analyze values of pins with 16MHz frequency so there still is possible that a "tie" will happen and one button will be chosen by processor to be a "winner" incorrectly. But this time the decision will take only 1 clock cycle (6 nano seconds).
digitalRead
take about 50 clock cycles, and we need two of them. So we gained 100 times more resolution and I think it's enough for a "game" to risk suck event. Nothing is perfect in electronics.– Filip Franik
2 days ago
add a comment |
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.
add a comment |
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.
add a comment |
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.
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.
answered 2 days ago
CraigCraig
1,865411
1,865411
add a comment |
add a comment |
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 winsn");
break;
case 2:
Serial.print("Player 2 winsn");
break;
case 3:
Serial.print("Both players winn");
break;
default:
assert(NULL); // Programming error
break;
}
playerXWon = player;
digitalWrite(P1_Pin, LOW);
}
The cheating pevention will come later, I'm aware of that problem and will add it later. I wanted to tackle this first as it's more important and unlike the cheating prevention I don't know how to do it.
– oh double-you oh
2 days ago
I'm not sure how much that approach would change things as it would most likely still require one button to be read after the other in whatever loop those 5 seconds take place in. Could you clarify what you mean by "change the delay"?
– oh double-you oh
2 days ago
Actually, it's better to remove the delay. When it's removed, many times per ms the buttons are checked, so whoever presses the button first will win. Now it is only checked every 5 seconds, and probably both players press the button within this time easily.
– Michel Keijzers
2 days ago
I also split the big function in smaller functions.
– Michel Keijzers
2 days ago
Lot's of stuff here I haven't learned yet like switch functions and using void loops as functions, thanks for the pointers.
– oh double-you oh
2 days ago
|
show 1 more comment
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 winsn");
break;
case 2:
Serial.print("Player 2 winsn");
break;
case 3:
Serial.print("Both players winn");
break;
default:
assert(NULL); // Programming error
break;
}
playerXWon = player;
digitalWrite(P1_Pin, LOW);
}
The cheating pevention will come later, I'm aware of that problem and will add it later. I wanted to tackle this first as it's more important and unlike the cheating prevention I don't know how to do it.
– oh double-you oh
2 days ago
I'm not sure how much that approach would change things as it would most likely still require one button to be read after the other in whatever loop those 5 seconds take place in. Could you clarify what you mean by "change the delay"?
– oh double-you oh
2 days ago
Actually, it's better to remove the delay. When it's removed, many times per ms the buttons are checked, so whoever presses the button first will win. Now it is only checked every 5 seconds, and probably both players press the button within this time easily.
– Michel Keijzers
2 days ago
I also split the big function in smaller functions.
– Michel Keijzers
2 days ago
Lot's of stuff here I haven't learned yet like switch functions and using void loops as functions, thanks for the pointers.
– oh double-you oh
2 days ago
|
show 1 more comment
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 winsn");
break;
case 2:
Serial.print("Player 2 winsn");
break;
case 3:
Serial.print("Both players winn");
break;
default:
assert(NULL); // Programming error
break;
}
playerXWon = player;
digitalWrite(P1_Pin, LOW);
}
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 winsn");
break;
case 2:
Serial.print("Player 2 winsn");
break;
case 3:
Serial.print("Both players winn");
break;
default:
assert(NULL); // Programming error
break;
}
playerXWon = player;
digitalWrite(P1_Pin, LOW);
}
edited 2 days ago
answered 2 days ago
Michel KeijzersMichel Keijzers
6,45141738
6,45141738
The cheating pevention will come later, I'm aware of that problem and will add it later. I wanted to tackle this first as it's more important and unlike the cheating prevention I don't know how to do it.
– oh double-you oh
2 days ago
I'm not sure how much that approach would change things as it would most likely still require one button to be read after the other in whatever loop those 5 seconds take place in. Could you clarify what you mean by "change the delay"?
– oh double-you oh
2 days ago
Actually, it's better to remove the delay. When it's removed, many times per ms the buttons are checked, so whoever presses the button first will win. Now it is only checked every 5 seconds, and probably both players press the button within this time easily.
– Michel Keijzers
2 days ago
I also split the big function in smaller functions.
– Michel Keijzers
2 days ago
Lot's of stuff here I haven't learned yet like switch functions and using void loops as functions, thanks for the pointers.
– oh double-you oh
2 days ago
|
show 1 more comment
The cheating pevention will come later, I'm aware of that problem and will add it later. I wanted to tackle this first as it's more important and unlike the cheating prevention I don't know how to do it.
– oh double-you oh
2 days ago
I'm not sure how much that approach would change things as it would most likely still require one button to be read after the other in whatever loop those 5 seconds take place in. Could you clarify what you mean by "change the delay"?
– oh double-you oh
2 days ago
Actually, it's better to remove the delay. When it's removed, many times per ms the buttons are checked, so whoever presses the button first will win. Now it is only checked every 5 seconds, and probably both players press the button within this time easily.
– Michel Keijzers
2 days ago
I also split the big function in smaller functions.
– Michel Keijzers
2 days ago
Lot's of stuff here I haven't learned yet like switch functions and using void loops as functions, thanks for the pointers.
– oh double-you oh
2 days ago
The cheating pevention will come later, I'm aware of that problem and will add it later. I wanted to tackle this first as it's more important and unlike the cheating prevention I don't know how to do it.
– oh double-you oh
2 days ago
The cheating pevention will come later, I'm aware of that problem and will add it later. I wanted to tackle this first as it's more important and unlike the cheating prevention I don't know how to do it.
– oh double-you oh
2 days ago
I'm not sure how much that approach would change things as it would most likely still require one button to be read after the other in whatever loop those 5 seconds take place in. Could you clarify what you mean by "change the delay"?
– oh double-you oh
2 days ago
I'm not sure how much that approach would change things as it would most likely still require one button to be read after the other in whatever loop those 5 seconds take place in. Could you clarify what you mean by "change the delay"?
– oh double-you oh
2 days ago
Actually, it's better to remove the delay. When it's removed, many times per ms the buttons are checked, so whoever presses the button first will win. Now it is only checked every 5 seconds, and probably both players press the button within this time easily.
– Michel Keijzers
2 days ago
Actually, it's better to remove the delay. When it's removed, many times per ms the buttons are checked, so whoever presses the button first will win. Now it is only checked every 5 seconds, and probably both players press the button within this time easily.
– Michel Keijzers
2 days ago
I also split the big function in smaller functions.
– Michel Keijzers
2 days ago
I also split the big function in smaller functions.
– Michel Keijzers
2 days ago
Lot's of stuff here I haven't learned yet like switch functions and using void loops as functions, thanks for the pointers.
– oh double-you oh
2 days ago
Lot's of stuff here I haven't learned yet like switch functions and using void loops as functions, thanks for the pointers.
– oh double-you oh
2 days ago
|
show 1 more comment
I was not sure about posting this as an answer, since it is basically some more considerations on the three existing answers (for reference, they are Michel Keijzers's one - using digitalRead -, Craig's one - read the PIND variable at once -, Filip Franik's one - use interrupts), but it became quite long and so a single comment could not fit all of this.
What you and the other answerers wrote is in theory correct. Checking the button of player 1 before player 2 gives an advantage to player one. What you lack is... How much advantage?
Mechanical actions (such as the pressing of a button) have a time duration that is around 50ms*.
Since I assume your application is a reaction time tester, human reaction times are around 250ms.
Moreover mechanical buttons have "bounces". Typical debounce times are in the range 20-100ms. You can avoid this by just checking the first edge.
A player gets an advantage if he is awarded as winner even if the button was pressed at the same time.
_* I tried to find a source for this, but I was not able to get some data. I tried with an online stopwatch and pressing the two spacebars (notebook and USB keyboard) roughly at the same time, obtaining results around 75ms. This is not a real value, so if someone has some measured values or estimations feel free to comment
Now, let's assume you properly coded your program so that you check for ties and avoid delays, since these will greatly affect the following measurements.
In the digitalRead
case you are executing this series of actions:
- Read status of pin 1 - 3.6us
- Read status of pin 2 - 3.6us
- Check the statuses and decide who won - some tens of instructions (about 1us)
- Loop - a couple instructions (0.5us)
The 60 instructions comes from this thread (3.6us = 57.8 instructions @16MHz); as for the others it's a rough measurement. Let's assume that the function actually samples the pin at the very end of the function.
Now, since you are checking for ties, if both buttons get pressed during phases 3, 4 or 1 you will get a tie (since both buttons will be read as pressed). If both buttons get pressed during phase 2, button 2 will be marked as pressed and button 1 not, thus giving advantage to that player. The time when this happens is about 3.6us long. So you are giving a 3.6us advantage to button 2 player.
In the PIND case you are reading the buttons exactly at the same time. The advantage is thus 0.
In the interrupt case when both EXT0 and EXT1 interrupts are triggered at the same time EXT0 gets executed, since it is before in the ISR. Consequently the player pressing the button on EXT0 has one interrupt checking cycle of advantage. I admit I do not know how often the inputs are checked for the EXT interrupt, but I assume it performs a check every clock cycle (consequently the advantage is 62.5ns).
Now, let's sum up the advantage
- digitalRead: 3.6us
- PIND: 0
- interrupt: 62.5ns
The solution is pretty obvious to me, and it is... Don't care! Even in the worst case scenario you are four order of magnitude faster than your phenomenon. You are giving one player a 4us advantage in a 250ms game (0.0016% of advantage). I'm pretty sure that the mechanical differences between the two buttons (maybe one is a bit stiffer, or has a slightly higher size, or has a slightly lower contact) affect the reading much more than this.
In the end, with this kind of setup you will reasonably be able to have an accuracy that is at most some tens of milliseconds. With other measurement setups (maybe optically based) you can go as low as 1ms. Adding 4us of advantage to one player will not influence the result.
Try to focus on the rest of the program, and for this problem use whichever solution you find more understandable.
NOTE: This is based on the assumption that a proper program is developed, with tie checks and no delays and so on. The program as you wrote in the question is not ok from this point of view (as it is now it will stay in the loop forever; if you change Sx_State
to digitalRead
it will become roughly fair - something like 4us of advantage to player one and 3.8us of advantage to player two)
add a comment |
I was not sure about posting this as an answer, since it is basically some more considerations on the three existing answers (for reference, they are Michel Keijzers's one - using digitalRead -, Craig's one - read the PIND variable at once -, Filip Franik's one - use interrupts), but it became quite long and so a single comment could not fit all of this.
What you and the other answerers wrote is in theory correct. Checking the button of player 1 before player 2 gives an advantage to player one. What you lack is... How much advantage?
Mechanical actions (such as the pressing of a button) have a time duration that is around 50ms*.
Since I assume your application is a reaction time tester, human reaction times are around 250ms.
Moreover mechanical buttons have "bounces". Typical debounce times are in the range 20-100ms. You can avoid this by just checking the first edge.
A player gets an advantage if he is awarded as winner even if the button was pressed at the same time.
_* I tried to find a source for this, but I was not able to get some data. I tried with an online stopwatch and pressing the two spacebars (notebook and USB keyboard) roughly at the same time, obtaining results around 75ms. This is not a real value, so if someone has some measured values or estimations feel free to comment
Now, let's assume you properly coded your program so that you check for ties and avoid delays, since these will greatly affect the following measurements.
In the digitalRead
case you are executing this series of actions:
- Read status of pin 1 - 3.6us
- Read status of pin 2 - 3.6us
- Check the statuses and decide who won - some tens of instructions (about 1us)
- Loop - a couple instructions (0.5us)
The 60 instructions comes from this thread (3.6us = 57.8 instructions @16MHz); as for the others it's a rough measurement. Let's assume that the function actually samples the pin at the very end of the function.
Now, since you are checking for ties, if both buttons get pressed during phases 3, 4 or 1 you will get a tie (since both buttons will be read as pressed). If both buttons get pressed during phase 2, button 2 will be marked as pressed and button 1 not, thus giving advantage to that player. The time when this happens is about 3.6us long. So you are giving a 3.6us advantage to button 2 player.
In the PIND case you are reading the buttons exactly at the same time. The advantage is thus 0.
In the interrupt case when both EXT0 and EXT1 interrupts are triggered at the same time EXT0 gets executed, since it is before in the ISR. Consequently the player pressing the button on EXT0 has one interrupt checking cycle of advantage. I admit I do not know how often the inputs are checked for the EXT interrupt, but I assume it performs a check every clock cycle (consequently the advantage is 62.5ns).
Now, let's sum up the advantage
- digitalRead: 3.6us
- PIND: 0
- interrupt: 62.5ns
The solution is pretty obvious to me, and it is... Don't care! Even in the worst case scenario you are four order of magnitude faster than your phenomenon. You are giving one player a 4us advantage in a 250ms game (0.0016% of advantage). I'm pretty sure that the mechanical differences between the two buttons (maybe one is a bit stiffer, or has a slightly higher size, or has a slightly lower contact) affect the reading much more than this.
In the end, with this kind of setup you will reasonably be able to have an accuracy that is at most some tens of milliseconds. With other measurement setups (maybe optically based) you can go as low as 1ms. Adding 4us of advantage to one player will not influence the result.
Try to focus on the rest of the program, and for this problem use whichever solution you find more understandable.
NOTE: This is based on the assumption that a proper program is developed, with tie checks and no delays and so on. The program as you wrote in the question is not ok from this point of view (as it is now it will stay in the loop forever; if you change Sx_State
to digitalRead
it will become roughly fair - something like 4us of advantage to player one and 3.8us of advantage to player two)
add a comment |
I was not sure about posting this as an answer, since it is basically some more considerations on the three existing answers (for reference, they are Michel Keijzers's one - using digitalRead -, Craig's one - read the PIND variable at once -, Filip Franik's one - use interrupts), but it became quite long and so a single comment could not fit all of this.
What you and the other answerers wrote is in theory correct. Checking the button of player 1 before player 2 gives an advantage to player one. What you lack is... How much advantage?
Mechanical actions (such as the pressing of a button) have a time duration that is around 50ms*.
Since I assume your application is a reaction time tester, human reaction times are around 250ms.
Moreover mechanical buttons have "bounces". Typical debounce times are in the range 20-100ms. You can avoid this by just checking the first edge.
A player gets an advantage if he is awarded as winner even if the button was pressed at the same time.
_* I tried to find a source for this, but I was not able to get some data. I tried with an online stopwatch and pressing the two spacebars (notebook and USB keyboard) roughly at the same time, obtaining results around 75ms. This is not a real value, so if someone has some measured values or estimations feel free to comment
Now, let's assume you properly coded your program so that you check for ties and avoid delays, since these will greatly affect the following measurements.
In the digitalRead
case you are executing this series of actions:
- Read status of pin 1 - 3.6us
- Read status of pin 2 - 3.6us
- Check the statuses and decide who won - some tens of instructions (about 1us)
- Loop - a couple instructions (0.5us)
The 60 instructions comes from this thread (3.6us = 57.8 instructions @16MHz); as for the others it's a rough measurement. Let's assume that the function actually samples the pin at the very end of the function.
Now, since you are checking for ties, if both buttons get pressed during phases 3, 4 or 1 you will get a tie (since both buttons will be read as pressed). If both buttons get pressed during phase 2, button 2 will be marked as pressed and button 1 not, thus giving advantage to that player. The time when this happens is about 3.6us long. So you are giving a 3.6us advantage to button 2 player.
In the PIND case you are reading the buttons exactly at the same time. The advantage is thus 0.
In the interrupt case when both EXT0 and EXT1 interrupts are triggered at the same time EXT0 gets executed, since it is before in the ISR. Consequently the player pressing the button on EXT0 has one interrupt checking cycle of advantage. I admit I do not know how often the inputs are checked for the EXT interrupt, but I assume it performs a check every clock cycle (consequently the advantage is 62.5ns).
Now, let's sum up the advantage
- digitalRead: 3.6us
- PIND: 0
- interrupt: 62.5ns
The solution is pretty obvious to me, and it is... Don't care! Even in the worst case scenario you are four order of magnitude faster than your phenomenon. You are giving one player a 4us advantage in a 250ms game (0.0016% of advantage). I'm pretty sure that the mechanical differences between the two buttons (maybe one is a bit stiffer, or has a slightly higher size, or has a slightly lower contact) affect the reading much more than this.
In the end, with this kind of setup you will reasonably be able to have an accuracy that is at most some tens of milliseconds. With other measurement setups (maybe optically based) you can go as low as 1ms. Adding 4us of advantage to one player will not influence the result.
Try to focus on the rest of the program, and for this problem use whichever solution you find more understandable.
NOTE: This is based on the assumption that a proper program is developed, with tie checks and no delays and so on. The program as you wrote in the question is not ok from this point of view (as it is now it will stay in the loop forever; if you change Sx_State
to digitalRead
it will become roughly fair - something like 4us of advantage to player one and 3.8us of advantage to player two)
I was not sure about posting this as an answer, since it is basically some more considerations on the three existing answers (for reference, they are Michel Keijzers's one - using digitalRead -, Craig's one - read the PIND variable at once -, Filip Franik's one - use interrupts), but it became quite long and so a single comment could not fit all of this.
What you and the other answerers wrote is in theory correct. Checking the button of player 1 before player 2 gives an advantage to player one. What you lack is... How much advantage?
Mechanical actions (such as the pressing of a button) have a time duration that is around 50ms*.
Since I assume your application is a reaction time tester, human reaction times are around 250ms.
Moreover mechanical buttons have "bounces". Typical debounce times are in the range 20-100ms. You can avoid this by just checking the first edge.
A player gets an advantage if he is awarded as winner even if the button was pressed at the same time.
_* I tried to find a source for this, but I was not able to get some data. I tried with an online stopwatch and pressing the two spacebars (notebook and USB keyboard) roughly at the same time, obtaining results around 75ms. This is not a real value, so if someone has some measured values or estimations feel free to comment
Now, let's assume you properly coded your program so that you check for ties and avoid delays, since these will greatly affect the following measurements.
In the digitalRead
case you are executing this series of actions:
- Read status of pin 1 - 3.6us
- Read status of pin 2 - 3.6us
- Check the statuses and decide who won - some tens of instructions (about 1us)
- Loop - a couple instructions (0.5us)
The 60 instructions comes from this thread (3.6us = 57.8 instructions @16MHz); as for the others it's a rough measurement. Let's assume that the function actually samples the pin at the very end of the function.
Now, since you are checking for ties, if both buttons get pressed during phases 3, 4 or 1 you will get a tie (since both buttons will be read as pressed). If both buttons get pressed during phase 2, button 2 will be marked as pressed and button 1 not, thus giving advantage to that player. The time when this happens is about 3.6us long. So you are giving a 3.6us advantage to button 2 player.
In the PIND case you are reading the buttons exactly at the same time. The advantage is thus 0.
In the interrupt case when both EXT0 and EXT1 interrupts are triggered at the same time EXT0 gets executed, since it is before in the ISR. Consequently the player pressing the button on EXT0 has one interrupt checking cycle of advantage. I admit I do not know how often the inputs are checked for the EXT interrupt, but I assume it performs a check every clock cycle (consequently the advantage is 62.5ns).
Now, let's sum up the advantage
- digitalRead: 3.6us
- PIND: 0
- interrupt: 62.5ns
The solution is pretty obvious to me, and it is... Don't care! Even in the worst case scenario you are four order of magnitude faster than your phenomenon. You are giving one player a 4us advantage in a 250ms game (0.0016% of advantage). I'm pretty sure that the mechanical differences between the two buttons (maybe one is a bit stiffer, or has a slightly higher size, or has a slightly lower contact) affect the reading much more than this.
In the end, with this kind of setup you will reasonably be able to have an accuracy that is at most some tens of milliseconds. With other measurement setups (maybe optically based) you can go as low as 1ms. Adding 4us of advantage to one player will not influence the result.
Try to focus on the rest of the program, and for this problem use whichever solution you find more understandable.
NOTE: This is based on the assumption that a proper program is developed, with tie checks and no delays and so on. The program as you wrote in the question is not ok from this point of view (as it is now it will stay in the loop forever; if you change Sx_State
to digitalRead
it will become roughly fair - something like 4us of advantage to player one and 3.8us of advantage to player two)
answered yesterday
frarugi87frarugi87
2,325415
2,325415
add a comment |
add a comment |
I have to concur with frarugi87 here. The few microseconds taken by
digitalRead()
are completely inconsequential for your application. But
let me add that, even if your players were super-humans capable of
sub-millisecond reaction times, you are not giving an advantage to any
of them if you alternatively check button 1, then button 2,
then button 1, and so on. The reason is that, in the small time
window between reading button 1 and reading button 2, you give
an advantage to player 2: he will win if both buttons are pressed
simultaneously. In the following time window (between checking
button 2 and button 1) the same advantage goes to
player 1.
The net result is that the game has a tiny amount of randomness: if both
buttons are pressed simultaneously, the result is seemingly random. But
again, the chances of this happening are so tiny that it makes no sense
to worry about it.
Here is how I would determine the winner. I think this code makes clear
that we are alternatively checking button 1, then button 2,
then button 1...
int find_winner()
{
for (;;) {
if (digitalRead(S1_Pin) == HIGH)
return 1;
if (digitalRead(S2_Pin) == HIGH)
return 2;
}
}
add a comment |
I have to concur with frarugi87 here. The few microseconds taken by
digitalRead()
are completely inconsequential for your application. But
let me add that, even if your players were super-humans capable of
sub-millisecond reaction times, you are not giving an advantage to any
of them if you alternatively check button 1, then button 2,
then button 1, and so on. The reason is that, in the small time
window between reading button 1 and reading button 2, you give
an advantage to player 2: he will win if both buttons are pressed
simultaneously. In the following time window (between checking
button 2 and button 1) the same advantage goes to
player 1.
The net result is that the game has a tiny amount of randomness: if both
buttons are pressed simultaneously, the result is seemingly random. But
again, the chances of this happening are so tiny that it makes no sense
to worry about it.
Here is how I would determine the winner. I think this code makes clear
that we are alternatively checking button 1, then button 2,
then button 1...
int find_winner()
{
for (;;) {
if (digitalRead(S1_Pin) == HIGH)
return 1;
if (digitalRead(S2_Pin) == HIGH)
return 2;
}
}
add a comment |
I have to concur with frarugi87 here. The few microseconds taken by
digitalRead()
are completely inconsequential for your application. But
let me add that, even if your players were super-humans capable of
sub-millisecond reaction times, you are not giving an advantage to any
of them if you alternatively check button 1, then button 2,
then button 1, and so on. The reason is that, in the small time
window between reading button 1 and reading button 2, you give
an advantage to player 2: he will win if both buttons are pressed
simultaneously. In the following time window (between checking
button 2 and button 1) the same advantage goes to
player 1.
The net result is that the game has a tiny amount of randomness: if both
buttons are pressed simultaneously, the result is seemingly random. But
again, the chances of this happening are so tiny that it makes no sense
to worry about it.
Here is how I would determine the winner. I think this code makes clear
that we are alternatively checking button 1, then button 2,
then button 1...
int find_winner()
{
for (;;) {
if (digitalRead(S1_Pin) == HIGH)
return 1;
if (digitalRead(S2_Pin) == HIGH)
return 2;
}
}
I have to concur with frarugi87 here. The few microseconds taken by
digitalRead()
are completely inconsequential for your application. But
let me add that, even if your players were super-humans capable of
sub-millisecond reaction times, you are not giving an advantage to any
of them if you alternatively check button 1, then button 2,
then button 1, and so on. The reason is that, in the small time
window between reading button 1 and reading button 2, you give
an advantage to player 2: he will win if both buttons are pressed
simultaneously. In the following time window (between checking
button 2 and button 1) the same advantage goes to
player 1.
The net result is that the game has a tiny amount of randomness: if both
buttons are pressed simultaneously, the result is seemingly random. But
again, the chances of this happening are so tiny that it makes no sense
to worry about it.
Here is how I would determine the winner. I think this code makes clear
that we are alternatively checking button 1, then button 2,
then button 1...
int find_winner()
{
for (;;) {
if (digitalRead(S1_Pin) == HIGH)
return 1;
if (digitalRead(S2_Pin) == HIGH)
return 2;
}
}
answered 19 hours ago
Edgar BonetEdgar Bonet
24.2k22345
24.2k22345
add a comment |
add a comment |
oh double-you oh is a new contributor. Be nice, and check out our Code of Conduct.
oh double-you oh is a new contributor. Be nice, and check out our Code of Conduct.
oh double-you oh is a new contributor. Be nice, and check out our Code of Conduct.
oh double-you oh is a new contributor. Be nice, and check out our Code of Conduct.
Thanks for contributing an answer to Arduino Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2farduino.stackexchange.com%2fquestions%2f60618%2fsimultanous-button-reading%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
read through your code line by line ..... imagine that both the buttons are pressed at the same time ....... is that the expected behavior?
– jsotola
2 days ago