Lesson 19 / 25

External and Pin-change Interrupts

attachInterrupt, short ISRs and shared data.

Keep ISRs short and data safe

An interrupt makes the CPU pause loop(), run an interrupt service routine (ISR) and then resume. On the Uno R3, external interrupts are available on pins 2 and 3 through attachInterrupt(digitalPinToInterrupt(pin), isr, mode) with modes such as RISING, FALLING or CHANGE; the ATmega328P also has pin-change interrupts on most pins, configured through registers (PCICR, PCMSK0-2) or a library. Other boards allow interrupts on most pins; check your board's documentation. Rules: keep ISRs short (set a flag or count, do the work in loop()), avoid delay() and Serial printing inside them, and declare shared variables volatile. On an 8-bit MCU, reading a multi-byte variable takes several instructions, so an interrupt can change it halfway: copy it with interrupts briefly disabled (atomic access).

Reacting instantly, sleeping the rest of the time

Interrupts let hardware events pause the main loop; timers keep time without the CPU; sleep modes save energy.

Three ideas: interrupts, hardware timers, sleep modes.
Figure 7.1 — Event, interrupt service routine and back to the main loop.

Counting pulses safely

Uno R3: pulse source (e.g. a sensor output) on pin 2, sharing GND.

const uint8_t PULSE_PIN = 2;          // external interrupt pin on Uno R3
volatile uint32_t pulseCount = 0;      // shared with the ISR

void onPulse() {
  pulseCount++;                        // short: just count
}

void setup() {
  Serial.begin(115200);
  pinMode(PULSE_PIN, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(PULSE_PIN), onPulse, FALLING);
}

void loop() {
  static unsigned long last = 0;
  if (millis() - last >= 1000) {
    last = millis();
    noInterrupts();                    // 32-bit read is not atomic on AVR
    uint32_t count = pulseCount;
    pulseCount = 0;
    interrupts();
    Serial.print(F("Pulses/s: "));
    Serial.println(count);
  }
}

A doorbell

Polling is checking the front door every few seconds; an interrupt is a doorbell. When it rings you note who came and go back to what you were doing, rather than holding a long conversation on the doorstep.

Quick check: Why should a variable shared between an ISR and loop() be declared volatile?

  • So it becomes atomic automatically
  • So it is stored in EEPROM
  • So the ISR runs faster
  • So the compiler re-reads it from memory instead of using a cached copy
Answer

So the compiler re-reads it from memory instead of using a cached copy — volatile prevents caching; atomic access still needs interrupts disabled for multi-byte values on AVR.