21

Currently, my sketch is checking an input pin every time round the main loop. If it detects a change, it calls a custom function to respond to it. Here's the code (trimmed down to the essentials):

int pinValue = LOW;

void pinChanged()
{
    //...
}

void setup()
{
    pinMode(2, INPUT);
}

void loop()
{
    // Read current input
    int newValue = digitalRead(2);

    // Has the input changed?
    if (newValue != pinValue) {
        pinValue = newValue;
        pinChanged();
    }
}

Unfortunately, this doesn't always work properly for very short changes on the input (e.g. brief pulses), especially if loop() is running a bit slowly.

Is there a way to make the Arduino detect the input change and call my function automatically?

  • 1
    What you are looking for its a External Interrupt – Butzke Feb 20 '14 at 10:48
26
+50

You can do this using external interrupts. Most Arduinos only support this on a limited number of pins though. For full details, see the documentation on attachInterrupt().

Assuming you're using an Uno, you could do it like this:

void pinChanged()
{
    //...
}

void setup()
{
    pinMode(2, INPUT);
    attachInterrupt(0, pinChanged, CHANGE);
}

void loop()
{
}

This will call pinChanged() whenever a change is detected on external interrupt 0. On the Uno, that corresponds to GPIO pin 2. The external interrupt numbering is different on other boards, so it's important to check the relevant documentation.

There are limitations to this approach though. The custom pinChanged() function is being used as an Interrupt Service Routine (ISR). That means the rest of the code (everything in loop()) is temporarily stopped while the call is executing. In order to prevent disrupting any important timing, you should aim to make ISRs as fast as possible.

It's also important to note that no other interrupts will run during your ISR. That means anything relying on interrupts (such as the core delay() and millis() functions) may not work properly inside it.

Lastly, if your ISR needs to change any global variables in the sketch, they should usually be declared as volatile, e.g.:

volatile int someNumber;

That's important because it tells the compiler that the value could change unexpectedly, so it should be careful not to use any out-of-date copies/caches of it.

  • regarding the "brief pulses" mentioned in the question, is there a minimum time the pin must be at a state for it to trigger the interrupt? (obviously it'll be much less than polling, which depends on what else is happening in the loop) – sachleen Feb 20 '14 at 4:02
  • 1
    @sachleen That will work as long as it does not happen during the execution of an ISR function (as explained in the answer); that's why pinChanged() should be as short as possible. Hence typically the minimum time should be the time to execute the pinChanged() function itself. – jfpoilpret Feb 20 '14 at 5:12
  • 2
    +1 for this very detailed answer which includes all imortant stuff one must care about when using interrupts! – jfpoilpret Feb 20 '14 at 5:13
  • 3
    In addition to declaring shared globals volatile, if the global variable is wider than 1 byte, as someNumber is, you must protect against the pin-change interrupt occurring between byte accesses by the program. A statement like someNumber +=5; involves adding the low bytes and adding the high bytes with carry included. These two (more, for wider variables) must not be divided by an interrupt. Turning the interrupts off and restoring them before and after the operation (respectively) is sufficient. – JRobert Feb 26 '14 at 21:21
  • @sachleen - regarding the minimum pulse size. It is hard to find a definite answer in the datasheet, but judging by the timing for pin-change interrupts, they are latched within half a clock cycle. Once the interrupt is "remembered" it stays remembered until the ISR kicks in and deals with it. – Nick Gammon Jul 1 '15 at 1:36
5

Any state of change on any pin configured as digital input can create an interrupt. Unlike the unique vectors for the interrupts causes by INT1 or INT2 the PinChangeInt feature uses a common vector and then Interrupt Service Routine (aka ISR) for this vector needs to then determine which pin changed.

Fortunately PinChangeInt Library makes this easy.

PCintPort::attachInterrupt(PIN, burpcount,RISING); // attach a PinChange Interrupt to our pin on the rising edge
// (RISING, FALLING and CHANGE all work with this library)
// and execute the function burpcount when that pin changes
0

In the event that you want to detect a voltage passing a threshold, rather than being merely HIGH or LOW, you can use the analog comparator. Example sketch:

volatile boolean triggered;

ISR (ANALOG_COMP_vect)
  {
  triggered = true;
  }

void setup ()
  {
  Serial.begin (115200);
  Serial.println ("Started.");
  ADCSRB = 0;           // (Disable) ACME: Analog Comparator Multiplexer Enable
  ACSR =  bit (ACI)     // (Clear) Analog Comparator Interrupt Flag
        | bit (ACIE)    // Analog Comparator Interrupt Enable
        | bit (ACIS1);  // ACIS1, ACIS0: Analog Comparator Interrupt Mode Select (trigger on falling edge)
   }  // end of setup

void loop ()
  {
  if (triggered)
    {
    Serial.println ("Triggered!"); 
    triggered = false;
    }

  }  // end of loop

This can be useful for things like light-detectors, where you might need to detect a change from (say) 1V to 2V on an input.

Example circuit:

enter image description here

You can also use the Input Capture Unit on the processor, which will remember the exact time of certain inputs, by saving the current count of Timer/Counter 1. This lets you store the exact (well, almost exact) moment that the event of interest occurred, rather than introducing the delay (of probably a few microseconds) before an ISR can be used to capture the current time.

For timing-critical applications, this can give somewhat increased accuracy.

Example application: Turn your Arduino into a capacitor tester

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.