Need to execute code if two buttons are pressed within ~0.1 sec of each other

I am building some behavioral equipment for my neuroscience lab. I will have two levers each attached to their own button. I need to be able to run a subroutine if both buttons are pressed simultaneously or within 0.1 sec of each other. My idea is to use a hardware interrupt to detect the first button push. Have that interrupt run a routine to check if the other button is pushed within the allowed time. If that happens then call the routine to execute the code the should be run. With this there would need to be two routines to check for the second button push trigger by their interrupt. One for button A waiting on B and one for B waiting on A.

My questions:
-Using interrupts seems like they would interfere with each other after the second button is pushed. So I don't think hardware interrupts are the right way to go. What is the right way to do this?
-How should the timing be handled? I don't think I need (nor want) an RTC, but I do not know how to handle some relatively precise timing without one.
-What happens in the remote condition if both buttons are pressed simultaneously? I have no clue how to handle that.

In summary, Help and help. Thanks in advance for any direction given.

Welcome to the forum

Is the sketch doing anything else whilst waiting for the buttons to be pressed ?

Why use an interrupt ?

Read the inputs in a tight loop. When either button becomes pressed, save the value of millis() to separate variables. Calculate the absolute value of one value subtracted from the other and you have the time between button presses

  • Not really necessary, scanning the switches (every 20 to 50ms) while looking for a change in state is all that’s needed.
  • You can add hardware debouncing to your switches and scan them even faster.

Not tested, but it should be a good start

// Constant definitions
const int BUTTON1 = 2;
const int BUTTON2 = 3;
const int LIMIT_MS = 100;

void setup() 
{
    pinMode(BUTTON1, INPUT_PULLUP);
    pinMode(BUTTON2, INPUT_PULLUP);
    Serial.begin(115200);
}

void loop() 
{
    static uint32_t timestamp1 = 0;
    static uint32_t timestamp2 = 0;
    static int lastBtn1 = -1;
    static int lastBtn2 = -1;
    
    // Alternative to debouncing
    delay(40);
    
    int btn1 = digitalRead(BUTTON1);
    int btn2 = digitalRead(BUTTON2);

    if (btn1 == HIGH)
    {
        timestamp1 = 0;
    }
    
    if (btn2 == HIGH)
    {
        timestamp2 = 0;
    }
    
    if ((btn1 == LOW) && (lastBtn1 == HIGH))
    {
        timestamp1 = millis();
    }

    if ((btn2 == LOW) && (lastBtn2 == HIGH))
    {
        timestamp2 = millis();
    }

    if (abs(timestamp2 - timestamp1) < LIMIT_MS)
    {
        Serial.println("Success");
    }

    lastBtn1 = btn1;
    lastBtn2 = btn2;
}

No debounce is required, as the OP indicates he's interested in the time delta between button pushes; just store the time of first transition on either (and block all further on that input), then watch for first transition on the other; reset the whole schmear when, for example, 100 ms have passed without contact closure on either, and continue. Code it right, you can start the timing with either button and watch for the other.

That would be my first cut at it, anyway.

when a button is pressed start a timer. if it expires before a 2nd button is pressed, recognize that that button is pressed. otherwise recognize that the pair of buttons is pressed when the 2nd button is pressed

i modified some existing code that toggled LEDs

// check multiple button presses

enum { Off = HIGH, On = LOW };

byte pinsLed [] = { 10, 11, 12 };
byte pinsBut [] = { A1, A2, A3 };
#define N_BUT   sizeof(pinsBut)

byte butState [N_BUT];

// -----------------------------------------------------------------------------
const unsigned long MsecButton = 100;
      unsigned long msecBut0;
      unsigned long msec;

const int NoBut = -1;
      int butId = NoBut;;

int
chkButtons ()
{
    int result;

    if (NoBut != butId && msec - msecBut0 >= MsecButton)  {
        result = butId;
        butId   = NoBut;
        return 1 << result;
    }

    for (unsigned n = 0; n < sizeof(pinsBut); n++)  {
        byte but = digitalRead (pinsBut [n]);

        if (butState [n] != but)  {
            butState [n] = but;
            delay (40);     // debounce

            if (LOW == but)  {
                if (NoBut == butId)  {
                    msecBut0 = msec;
                    butId    = n;
                }
                else {
                    result   = 1 << n | 1 << butId;
                    butId    = NoBut;
                    return result;
                }
            }
        }
    }
    return NoBut;
}

// -----------------------------------------------------------------------------
void
loop ()
{
    msec = millis ();

    int buts = chkButtons ();
    if (NoBut != buts)
        Serial.println (buts);
}

// -----------------------------------------------------------------------------
void
setup ()
{
    Serial.begin (9600);

    for (unsigned n = 0; n < sizeof(pinsBut); n++)  {
        pinMode (pinsBut [n], INPUT_PULLUP);
        butState [n] = digitalRead (pinsBut [n]);
    }

    for (unsigned n = 0; n < sizeof(pinsLed); n++)  {
        digitalWrite (pinsLed [n], Off);
        pinMode      (pinsLed [n], OUTPUT);
    }
}

This is what I needed! I taught myself to program 35 years ago and then didn't get back to it until a few years ago. I know structure and concept but I don't know functions and syntax in contemporary languages. Thanks!

I looked up bouncing. I'm just going to pretend it does not exist. lol Using millis() is what I really needed. Thanks for introducing me to a new concept!

This makes perfect sense to me. I'm am going to use this as an outline, but write my own code because otherwise I will not learn it. Thanks!

Chuckling... I got into microcontrollers 7-8 years ago when my son entered the EE/ESET program at A&M. Judging by your user-name and length of time coding, it looks like you are having a similar introduction. I've found it quite enjoyable, and you are taking the right approach -- enjoy.
Tips - avoid blocking in loop(). Using millis() or micros() to keep the state of a couple variables updated -- as the other posts provide, is the way to go. Good luck!

I am actually the lab manager in a neuroscience lab. I'm usally the oldest and most experienced one around so I try to look out for and help everyone while also giving them a good natured ribbing. Hence, I'm the dad in the lab.

Thanks for the help everyone! Code and circuit have been tested. Let me know if you think changes should be made.

const int Btn1 = 2;
const int Btn2 = 3;
const int Limit = 500;
const int baud = 9600;

void setup() 
{

  pinMode(Btn1, INPUT_PULLUP);
  pinMode(Btn2, INPUT_PULLUP);
   
  //int btn1 = digitalRead(Btn1);
  //int btn2 = digitalRead(Btn2);

  Serial.begin(9600);
  while (!Serial){};
  delay(1000);
  Serial.setTimeout(20000);
  Serial.println();
  Serial.print("Serial Communication begun @ ");
  Serial.print(baud);
  Serial.println("."); 

}

void loop() 
{
  Serial.println("Waiting for a button.");
  chkBtn1();
  chkBtn2();
  delay(20);
}

void chkBtn1()
{
  if (digitalRead(Btn1) == LOW)
    {
      static uint32_t time1 = millis();
      time1 = millis();
      Serial.println("Button 1 pressed");
      while (millis() <= time1 + Limit)
      {
        if (digitalRead(Btn2) == LOW)
        {
          Serial.println("Button 2 pressed");
          pump();
        }
        Serial.println("Waiting for button 2.");
        delay(20);
      }
    }
}
void chkBtn2()
{
  if (digitalRead(Btn2) == LOW)
    {
      static uint32_t time2 = millis();
      time2= millis();
      Serial.println("Button 2 pressed");
      while (millis() <= time2 + Limit)
      {
        if (digitalRead(Btn1) == LOW)
        {
          Serial.println("Button 1 pressed");
          pump();
        }
        Serial.println("Waiting for button 1.");
        delay(20);
      }
    }
}

void pump()
{
  Serial.println("Pump called.");
  delay (2000);
}

you have no need to detect either single button pressed?

these statements are redundant and can simple be

       uint32_t time1 = millis();
const int Limit = 500;

What happened to the 100 ms specification?

  Serial.begin(9600);

There's no reason not to run the Serial at 115200.

 while (millis() <= time1 + Limit)
 while (millis() <= time2 + Limit)

These statements which use <= will be subject to rollover issues if the system is run for more than 49 days continuously. This may or may not be an issue for you.

Since there are 2^32 bits in an unsigned long it can count from 0 to 4294967295.
Computing this in terms of days we have:
2^32 / 1000 / 60 / 60 / 24 = 49.710 days