Register forum user name Search FAQ

Gammon Forum

Notice: Any messages purporting to come from this site telling you that your password has expired, or that you need to "verify" your details, making threats, or asking for money, are spam. We do not email users with any such messages. If you have lost your password you can obtain a new one by using the password reset link.
 Entire forum ➜ Electronics ➜ Microprocessors ➜ Multiple interrupts on one pin - wired "or" configuration

Multiple interrupts on one pin - wired "or" configuration

Postings by administrators only.

Refresh page


Posted by Nick Gammon   Australia  (23,070 posts)  Bio   Forum Administrator
Date Tue 10 May 2011 02:23 AM (UTC)
Message
A question that comes up a few times is, how can we detect an interrupt (eg. a switch press) from multiple sources when the Atmega328 processor only has two interrupt inputs?

This is a bit fiddly to get perfect, so I am documenting it here...

The trick is to wire each input to a separate pin (in my example, pins 3, 4, 5, 6) but to also connect them together to the interrupt input pin (pin 2) using diodes, like this:



All pins are enabled with internal pull-ups, so the default state for them all is on (high). Then if any switch is closed its corresponding pin will be low, and also the interrupt-detection pin (pin 2) will be low as well. However the diodes prevent the other pins from being brought low.

So now an interrupt service routine can fire, detecting a falling pulse. To work out which one caused it, it polls (reads) each of the data pins to see which one is low. The results are saved in an array, ready for testing by the main loop.


// Multiple interrupts on one pin example.
// Author: Nick Gammon
// Date:   10th May 2011

volatile byte pins [4];  //pins 3, 4, 5, 6
volatile boolean fired;

#define FIRST_PIN 3

#define DEBOUNCE_TIME 100  // ms

unsigned long last_time;

void isr ()
{
  // they haven't processed the last one yet
  if (fired)
    return;
    
  for (byte i = 0; i < sizeof pins; i++)
    {
    byte val = pins [i] = digitalRead (i + FIRST_PIN);
    if (val == LOW)
      fired = true;  
    }  // end of for
}  // end of isr

void setup ()
{
  // enable pullup on interrupt-detect line
  digitalWrite (2, HIGH); 
  
  for (byte i = 0; i < sizeof pins; i++)
    digitalWrite (i + FIRST_PIN, HIGH);  // enable pullups on data pins

  attachInterrupt (0, isr, FALLING);

  Serial.begin (115200);
}  // end of setup

void loop ()
{

  if (fired)
  {
    unsigned long now = millis ();

    if (now - last_time > DEBOUNCE_TIME)
    {
      last_time = now;
      Serial.print ("Fired! Switch ");

      for (byte i = 0; i < sizeof pins; i++)
        if (pins [i] == LOW)
          Serial.print (i + 1, DEC);

      Serial.println (" was pressed.");
    }  // end of debounce time elapsed

    fired = false;
  }  // end if fired

}  // end of loop



Testing showed a couple of "race conditions" hence the way it is structured.

For one thing, because of switch bounce the interrupt might fire, but by the time the pins were tested to see which one caused it, none of them registered low. Thus the "fired" flag is only set if a low pin is found.

Also, in the main loop, the "fired" flag is cleared last thing, so that the interrupt routine knows to not change the array of pins if they are still being processed. That is why the interrupt routine does nothing if the "fired" flag is already set.

This general technique should be able to be extended to any number of switches (that you have pins to connect them to), but only require a single interrupt input.

The other interesting thing about the above code is that the debouncing is done by checking elapsed time, not by building in a delay. This way the main loop does not have any "artificial" delays in it which might stop other important processing.

Milliseconds wrap-around


Also of note is the way that the debounce code is written with a subtraction rather than an addition. This caters for the wrap-around that occurs every 4294967296 (0xFFFFFFFF) milliseconds, or roughly 49.7 days.

As an example, say the last keypress was at 0xFFFFFFF0 milliseconds (so there were 16 milliseconds to go before wrap-around), and then the next keypress is at 0x0000000A milliseconds (after the wrap-around). Doing the subtraction:


0x0000000A - 0xFFFFFFF0 --> 0x0000001A


This calculation correctly returns that 26 seconds have elapsed.

Test rig




The blue wire on the right is connected to the common pins of the diodes, and goes to pin 2 of the Arduino (the interrupt input). The other four wires each go to the other end of each diode. The orange wire is connected to ground, and by touching it against the diodes I can "press" the switch to test the circuit.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
Top

The dates and times for posts above are shown in Universal Co-ordinated Time (UTC).

To show them in your local time you can join the forum, and then set the 'time correction' field in your profile to the number of hours difference between your location and UTC time.


14,933 views.

Postings by administrators only.

Refresh page

Go to topic:           Search the forum


[Go to top] top

Information and images on this site are licensed under the Creative Commons Attribution 3.0 Australia License unless stated otherwise.