NAME

WiringPi::API::INTERRUPTS - interrupt (ISR) usage examples for WiringPi::API

DESCRIPTION

Worked, runnable examples for handling GPIO interrupts with WiringPi::API. The self-pipe interrupt API described here is implemented in WiringPi::API 3.18 and verified on Pi 5 hardware; the snippets run as written.

This document is ISR-only and uses no use threads - general concurrency/worker examples (worker()) live in WiringPi::API::WORKERS / docs/threads-examples.md. Callbacks fire in your own interpreter when you service dispatch, so they work on any Perl, threaded or not.

See the WiringPi::API POD for the authoritative per-function reference.

ABOUT THESE EXAMPLES

  • Interrupts never require use threads. wiringPi runs its own C threads internally and writes events to a pipe; your Perl reads that pipe. Hands-off handling uses an in-process signal (scenario 7) or a forked process (scenarios 8 and 10) - never threads.

  • Callbacks receive ($edge, $timestamp_us) - the edge that fired (INT_EDGE_FALLING=1 / INT_EDGE_RISING=2) and a microsecond timestamp.

  • Pin numbering follows whichever setup you call: setup() = wiringPi numbering, setup_gpio() = BCM. Examples use setup().

  • Mode constants for pin_mode: INPUT=0, OUTPUT=1 (shown as integers).

  • To hide the most work, prefer the hands-off options. auto_dispatch_interrupts (scenario 7) fires callbacks in your own process with no loop; background_interrupt (scenario 8) runs an independent handler in its own process. Sections 1-6 (cooperative) explain the explicit dispatch model that 7 and 8 hide - read them to understand what happens under the hands-off calls, but most programs only need 7 or 8.

  • If you fork yourself (scenario 9): call setup() and pin_mode in the parent before forking, and arm the interrupt in the child that dispatches it.

DECISION GUIDE

None of these need use threads. To hide the most plumbing, prefer the hands-off rows (7, 8, 10).

7 vs 8 in one line: auto_dispatch_interrupts (7) gives you lock-free shared state but defers during a long non-yielding C call; background_interrupt (8) fires regardless of what main is doing but can't touch main's variables. No long C calls? Pick 7. Long C calls? Pick 8.

  • Attach a handler and forget it; it updates my program's state - scenario 7 (auto_dispatch_interrupts).

  • Independent handler that fires even during long/blocking work - scenario 8 (background_interrupt).

  • The same, but several pins in one background process - scenario 10 (background_interrupts).

  • React to a pin while running my own loop, on my terms - scenarios 1, 3.

  • A program whose only job is reacting to pins - scenario 2.

  • Several pins, each with its own handler - scenario 4.

  • Specific edges / debounce a noisy input - scenario 5.

  • Tear down or re-arm a pin - scenario 6.

  • Deliver edges back to the parent to handle there - scenario 9 (or the results channel in scenario 8 for simple value reporting).

REACTING TO INTERRUPTS

1. Cooperative dispatch in your main loop

Why/when: You already have a main loop and want to control exactly when callbacks run. Simplest model, works on any Perl - but a callback only fires when you call dispatch_interrupts(), so keep the loop snappy. (Want it fully hands-off? See scenario 7.)

Main & interrupt: One thread. The callback runs inside dispatch_interrupts(), so it can read/write any of main's variables with no locking - but it only fires when main calls dispatch, and it blocks main while it runs.

use strict;
use warnings;
use WiringPi::API qw(setup pin_mode set_interrupt dispatch_interrupts INT_EDGE_RISING);

setup();
pin_mode(0, 0);            # INPUT

set_interrupt(0, INT_EDGE_RISING, sub {
    my ($edge, $ts_us) = @_;
    print "pin 0 rising at ${ts_us}us\n";
});

while (1) {
    do_other_work();
    dispatch_interrupts();  # non-blocking: runs callbacks for any events that arrived
}

Tradeoff: if do_other_work() blocks for a long time, callbacks wait until the next dispatch_interrupts(). Conversely, if it returns instantly with nothing to do, this loop busy-spins at 100% CPU - pace it with real periodic work, a short sleep, or by selecting on interrupt_fd with a timeout (scenario 3).

2. Blocking wait loop

Why/when: Reacting to pins is the whole job - there's no other work to do. The process sleeps efficiently until an edge arrives.

Main & interrupt: One thread. Main is blocked in wait_interrupts() until an edge, then runs the callback inline (full access to program state).

use strict;
use warnings;
use WiringPi::API qw(setup pin_mode set_interrupt wait_interrupts INT_EDGE_BOTH);

setup();
pin_mode(0, 0);            # INPUT

set_interrupt(0, INT_EDGE_BOTH, \&on_change);

while (1) {
    wait_interrupts(1000);  # block up to 1000ms, dispatch whatever fired
}

sub on_change {
    my ($edge, $ts_us) = @_;
    print "edge $edge at ${ts_us}us\n";
}

Shortcut. If the loop is literally just wait_interrupts while 1, call the built-in helper instead of writing it yourself:

run_interrupt_loop(1000);             # blocks, dispatching, forever
run_interrupt_loop(1000, 50);         # ... or until 50 events have fired

It returns the number of events dispatched and stops when stop_interrupt_loop() is called (from inside a callback, or a signal handler) or after the optional event cap. When nothing is armed it sleeps the poll interval instead of busy-spinning.

3. Event-loop integration with the interrupt fd

Why/when: You already run an event loop (AnyEvent/IO::Async) or juggle sockets/timers, and want GPIO to be just another fd in it.

interrupt_fd() returns a read fd you can select/poll alongside your other descriptors - so one loop handles sockets, timers, and GPIO together.

use strict;
use warnings;
use WiringPi::API qw(setup pin_mode set_interrupt dispatch_interrupts interrupt_fd INT_EDGE_RISING);

setup();
pin_mode(0, 0);            # INPUT
set_interrupt(0, INT_EDGE_RISING, \&on_edge);

my $fd = interrupt_fd();
vec(my $mask = '', $fd, 1) = 1;

while (1) {
    select(my $ready = $mask, undef, undef, undef);  # block until readable
    if (vec($ready, $fd, 1)) {
        dispatch_interrupts();
    }
}

sub on_edge {
    my ($edge, $ts_us) = @_;
    print "edge!\n";
}

The same $fd plugs into AnyEvent->io or IO::Async::Handle.

4. Multiple pins and callbacks

Why/when: Several inputs, each with its own handler, serviced by one loop.

use strict;
use warnings;
use WiringPi::API qw(setup pin_mode set_interrupt wait_interrupts
                     INT_EDGE_RISING INT_EDGE_FALLING INT_EDGE_BOTH);

setup();
pin_mode($_, 0) for (0, 2, 3);   # INPUT

set_interrupt(0, INT_EDGE_RISING,  sub { print "button A\n" });
set_interrupt(2, INT_EDGE_FALLING, sub { print "button B\n" });
set_interrupt(3, INT_EDGE_BOTH,    \&sensor);

while (1) {
    wait_interrupts(1000);
}

sub sensor {
    my ($edge, $ts_us) = @_;
    print "sensor edge=$edge\n";
}

One shared handler for several pins. The callback only receives ($edge, $ts_us), not the pin. If you arm the same coderef on multiple pins, call last_interrupt() inside it to recover which pin (and the BCM number) fired:

my $cb = sub {
    my $i = last_interrupt();   # { pin, pin_bcm, edge, status, ts_us }
    printf "pin %d (BCM %d) edge %d\n", $i->{pin}, $i->{pin_bcm}, $i->{edge};
};
set_interrupt($_, INT_EDGE_BOTH, $cb) for (0, 2, 3);

last_interrupt() returns a hash reference describing the most recently dispatched event (or undef if none yet); it is published before the callback runs, so the callback can read it.

5. Edge types and debounce

Why/when: You care about a specific edge, or the input is electrically noisy (a button) and you want the kernel to suppress bounce so the callback fires once per press.

Edge constants: INT_EDGE_FALLING=1, INT_EDGE_RISING=2, INT_EDGE_BOTH=3. An optional 4th argument sets a kernel debounce period in microseconds (default 0 = off). wiringPi applies it as a Linux GPIO-v2 line attribute (GPIO_V2_LINE_ATTR_ID_DEBOUNCE) at arm time, so the kernel drops bounce edges before they reach the pipe - it is not a hardware debounce. The attribute's field is a u32, so the effective maximum is ~2**32 us (~71 minutes).

use WiringPi::API qw(setup pin_mode set_interrupt wait_interrupts INT_EDGE_FALLING);

setup();
pin_mode(0, 0);

# debounce a noisy button: ignore repeat edges within 5ms
set_interrupt(0, INT_EDGE_FALLING, \&pressed, 5000);

wait_interrupts(1000) while 1;

sub pressed {
    print "clean press\n";
}

6. Teardown and re-arming

Why/when: You need to stop watching a pin, swap a handler at runtime, or clean up on exit.

set_interrupt(0, INT_EDGE_RISING, \&handler_a);

# Re-arm the same pin with a different handler - the old listener is stopped
# automatically first, so no stacked/duplicate registration:
set_interrupt(0, INT_EDGE_RISING, \&handler_b);

stop_interrupt(0);    # stop one pin, forget its callback
stop_interrupts();    # stop every pin, drain + close the pipe

Bursts and dropped edges. Edges are FIFO-queued in a kernel pipe until you dispatch them. If a fast source outruns your dispatching the queue fills, and the overflowing edges are dropped - never merged, never blocked - and counted, so loss is never silent:

my $lost = interrupt_dropped();       # 0 unless the pipe overflowed

If you expect bursts, enlarge the queue with interrupt_buffer($bytes) (it may be set before arming and persists across teardown):

interrupt_buffer(1 << 20);            # ~1 MiB of queue; returns the granted size
my $size = interrupt_buffer;          # read the current capacity

The kernel rounds up to a page and caps at /proc/sys/fs/pipe-max-size. Other mitigations: dispatch faster, use background_interrupt (a dedicated process keeps the pipe drained), or debounce (scenario 5) to cut the edge rate.

HANDS-OFF HANDLING (NO DISPATCH LOOP)

7. Fire with no loop (auto_dispatch_interrupts)

Why/when: The most hands-off in-process option - "attach a handler and forget it," closest to Arduino's attachInterrupt. The callback runs in your program (it can read/update your variables, no locking) and fires on its own while your code runs, with no dispatch loop. Caveat: a long non-yielding C/XS call can delay it.

Main & interrupt: The callback runs in main's interpreter at op boundaries (and on interrupted sleeps), so it can read/write main's variables with no locking - but a long non-yielding C call defers it until that call returns.

use strict;
use warnings;
use WiringPi::API qw(setup pin_mode set_interrupt auto_dispatch_interrupts INT_EDGE_RISING);

setup();
pin_mode(0, 0);            # INPUT

auto_dispatch_interrupts(1);          # callbacks now fire on their own - no loop to write

my $count = 0;
set_interrupt(0, INT_EDGE_RISING, sub { $count++ });   # updates your own variable

while (1) {
    do_main_work();        # the callback fires between ops, and during the sleep
    print "edges so far: $count\n";
    sleep 1;
}

The one caveat: a long, non-yielding C/XS call delays the callback until it returns (it fires at Perl's safe points). To fire even during such work, use scenario 8.

Choosing the signal. By default the fd is wired to SIGIO. If your program already uses SIGIO/O_ASYNC, pass a different signal so they don't clash:

auto_dispatch_interrupts(1, 'USR1');  # deliver via SIGUSR1 instead (F_SETSIG)

Opt in while arming. Instead of a separate auto_dispatch_interrupts(1) call, you can turn it on as part of set_interrupt - this enables the same process-wide switch:

set_interrupt(0, INT_EDGE_RISING, sub { $count++ }, { auto_dispatch => 1 });
# or pick the signal:  { auto_dispatch => 'USR1' }

8. A background process (background_interrupt)

Why/when: True fire-while-busy with zero servicing, even during long blocking work - because the handler runs in a separate process. Best for independent handlers (drive a pin, log, notify) that don't need your main program's variables.

Main & interrupt: The callback runs in a separate process, truly concurrently - it fires even while main blocks, but cannot see or change main's variables (separate memory; share via IPC).

use strict;
use warnings;
use WiringPi::API qw(setup pin_mode background_interrupt INT_EDGE_RISING);

setup();
pin_mode(0, 0);            # INPUT

my $h = background_interrupt(0, INT_EDGE_RISING, sub {
    my ($edge, $ts_us) = @_;
    # runs in the background on each rising edge - independent work only
});

# main carries on; the handler fires on its own
for (1 .. 10) {
    do_other_work();
    sleep 1;
}

$h->stop;                  # stops + reaps the background handler

No pipe, no fork, no select, no waitpid - the library owns all of it (and an END hook reaps the child even if you forget stop). $h->stop is idempotent: safe to call more than once, and safe after the child has already exited. Needs no threaded Perl.

Reporting values back (the results channel). For a handler that just needs to report a value to the parent, you don't need the manual fork of scenario 9 - pass { results => 1 } and return a value from the handler; the parent drains it:

my $h = background_interrupt(0, INT_EDGE_RISING, sub {
    my ($edge, $ts_us) = @_;
    return "$edge\@$ts_us";            # shipped back to the parent
}, { results => 1 });

while (defined(my $msg = $h->read)) {  # non-blocking drain
    print "handler reported: $msg\n";
}
# $h->fh is the read filehandle, for select / IO::Select

For anything more elaborate than reporting the handler's return value, scenario 9 shows the manual fork with your own results channel.

9. Under the hood: manual fork

Why/when: You rarely write this by hand - it's what scenario 8 does for you. Use it directly only when you need each edge delivered back to the parent to handle there, rather than handled in the child.

The child runs the wiringPi handler and only forwards events over a pipe; the parent drains and reacts (in main's interpreter, full access to its state).

use strict;
use warnings;
use IO::Select;
use WiringPi::API qw(setup pin_mode set_interrupt wait_interrupts INT_EDGE_RISING);

setup();
pin_mode(0, 0);                          # INPUT - plain config, before fork

pipe(my $rx, my $tx) or die "pipe: $!";  # YOUR OWN results channel (child -> parent)

my $pid = fork // die "fork: $!";

if ($pid == 0) {
    close $rx;
    set_interrupt(0, INT_EDGE_RISING, sub {
        my ($edge, $ts_us) = @_;
        syswrite $tx, "$edge $ts_us\n";          # one text line up to the parent
    });
    wait_interrupts(1000) while 1;
    exit 0;
}

close $tx;
my $sel = IO::Select->new($rx);
my $buf = '';

# Reap the child on normal exit/die even if you forget to stop it explicitly.
END { if ($pid) { kill 'TERM', $pid; waitpid $pid, 0; $pid = undef; } }

while (1) {
    do_other_work();

    while ($sel->can_read(0)) {
        my $n = sysread($rx, my $chunk, 4096);
        if (!defined $n) {
            last if $!{EINTR};                    # signal: retry next pass
            $sel->remove($rx); last;              # other error: stop, don't spin
        }
        if ($n == 0) { $sel->remove($rx); last }  # EOF: child exited
        $buf .= $chunk;
        while ($buf =~ s/^([^\n]*)\n//) {         # consume only complete lines
            print "edge: $1\n";
        }
    }
}

Use newline-delimited text framing (self-delimiting and portable). A stalled parent blocks the child's syswrite, which backpressures the internal self-pipe and can drop edges - observable via interrupt_dropped(). After the fork only the child reads the interrupt fd; the parent uses the results pipe.

10. Many pins in one background child (background_interrupts)

Why/when: You want background handling (scenario 8) for several pins, but a separate child per pin is wasteful. background_interrupts forks one child that services them all, and lets you arm/disarm individual pins at runtime.

Main & interrupt: Each callback runs in the one shared child (separate memory from main, as in scenario 8). The callbacks are fixed when the child forks - fork can't carry new code - so arm/disarm only toggle pins registered in the initial call.

use strict;
use warnings;
use WiringPi::API qw(setup pin_mode background_interrupts
                     INT_EDGE_RISING INT_EDGE_BOTH);

setup();
pin_mode($_, 0) for (0, 2);

my $h = background_interrupts(
    [0, INT_EDGE_RISING, \&on_button],
    [2, INT_EDGE_BOTH,   \&on_sensor, 5000],   # optional debounce
);

# ... main does its own thing; both pins are handled in the one child ...

$h->disarm(2);    # stop servicing pin 2 (the child keeps running for pin 0)
$h->arm(2);       # resume it
$h->stop;         # tear down and reap the single child

The handle has the same stop/pid/running as scenario 8, plus arm($pin)/disarm($pin). Arming a pin that wasn't in the initial list croaks.

NON-THREADED PERL

The interrupt API needs nothing special. Everything in this document - including background handling via auto_dispatch_interrupts (7) or fork (8, 10) - works on a Perl built without ithreads. "Background" does not imply use threads; only the ithread variants in docs/threads-examples.md do.

ANTI-PATTERNS TO AVOID

  • Forgetting to service the fd in cooperative mode. If you never call dispatch_interrupts()/wait_interrupts(), callbacks never fire - there is no background process doing it for you unless you set one up (scenarios 7-8, 10).

  • Forking after arming interrupts. wiringPi's ISR pthreads don't survive fork, and a mutex held at fork time is left locked in the child. Fork first, then arm in the child that dispatches.

  • Sharing one device fd across forked processes. An i2c/spi/serial handle should be used by a single process; two processes transacting on it interleave.

  • Two processes reading the same interrupt fd. After a fork, exactly one context should drain the library's interrupt fd. A second reader steals records from the first.

  • Relying on auto_dispatch_interrupts during a long non-yielding C/XS call. Its callbacks fire at Perl's safe points (op boundaries, interrupted sleeps); a long C call that never yields delays them. Use background_interrupt (separate process) if a handler must fire during such work.

  • Enabling auto_dispatch_interrupts when your program already uses SIGIO/O_ASYNC. It claims that signal; pick one owner, or choose a different delivery signal (eg auto_dispatch_interrupts(1, 'USR1')).

  • Busy-spinning a do_work + poll loop. A while (1) { do_other_work(); dispatch_interrupts() } (scenario 1) or can_read(0) drain (scenario 9) burns 100% CPU if the work returns instantly. Pace it, sleep, or select with a timeout. run_interrupt_loop avoids this when nothing is armed.

API REFERENCE FOR THESE EXAMPLES

See the WiringPi::API POD for full per-function documentation.

setup() / setup_gpio()

Init (wiringPi / BCM numbering); once, in main. Returns an int status (0 = ok).

pin_mode($pin, $mode)

0=INPUT, 1=OUTPUT.

set_interrupt($pin, $edge, $cb [, $debounce_us] [, \%opts])

Arm an interrupt; $cb->($edge, $ts_us). The optional \%opts accepts { auto_dispatch => 1 } (or a signal name) to enable auto-dispatch as part of arming. Returns true on success.

background_interrupt($pin, $edge, $cb [, $debounce_us] [, \%opts])

Run the handler in a forked child. \%opts accepts { results => 1 } to ship the handler's return value back to the parent. Returns a handle (stop/pid/running, plus read/fh when results is on).

background_interrupts([$pin, $edge, $cb [, $deb]], ...)

One shared child servicing many pins. Returns a handle with the usual methods plus arm($pin)/disarm($pin).

auto_dispatch_interrupts($bool [, $signal])

Fire set_interrupt callbacks automatically in-process with no loop (default SIGIO; a named signal is wired via F_SETSIG). Returns true.

wait_interrupts($timeout_ms)

Block until an event or timeout, then dispatch. Returns the count dispatched (0 on timeout).

run_interrupt_loop($timeout_ms [, $max]) / stop_interrupt_loop()

A built-in blocking dispatch loop; stops via the flag or the optional event cap. Returns the count dispatched.

dispatch_interrupts()

Non-blocking: dispatch pending events. Returns the count dispatched.

interrupt_fd()

The read fd, for select/event loops.

interrupt_dropped()

Count of events dropped because the pipe was full.

interrupt_buffer([$bytes])

Get or set the event-queue (pipe) capacity in bytes.

last_interrupt()

Full status of the most recent dispatched event - a hash reference {pin, pin_bcm, edge, status, ts_us}, or undef.

stop_interrupt($pin) / stop_interrupts()

Teardown (one pin / all pins).

INT_EDGE_FALLING (1) / INT_EDGE_RISING (2) / INT_EDGE_BOTH (3)

Edge constants.

CODE FLOW - PERL -> API.PM -> API.XS -> WIRINGPI

Worked numbers use setup() (wiringPi numbering) where wpi pin 0 = BCM 17, showing why userdata (the caller's pin) is the dispatch key, not wfiStatus.pinBCM.

Arming - set_interrupt(0, 2, \&cb):

1 Perl (user)     set_interrupt(0, 2, \&cb);
2 Perl (API.pm)   $_interrupt_cb{0} = \&cb;  _arm_interrupt(0, 2, 0);     # callback stays in Perl
3 API.xs          _arm_interrupt(pin=0,edge=2,deb=0):
                    (lazily create the pipe, both ends O_NONBLOCK)
                    wiringPiISR2(0, 2, isr2_writer, 0, (void*)0);          # userdata = caller pin 0
4 wiringPi.c      wiringPiISR2 -> wiringPiISRInternal: ToBCMPin 0->17;
                    isrFunctionsV2[17]=isr2_writer; isrUserdata[17]=(void*)0;
                    pthread_create(&isrThreads[17], ..., interruptHandlerV2);

Firing - a rising edge on GPIO 17 (NO Perl on this path):

4 wiringPi.c      interruptHandlerV2 (BCM-17 thread): wfiStatus={pinBCM=17,edge=2,ts};
                    isrFunctionsV2[17](wfiStatus, isrUserdata[17]);        # -> isr2_writer(.., (void*)0)
3 API.xs          isr2_writer(wfiStatus, ud):
                    rec = { .pin=(int)(intptr_t)ud /*=0, the caller pin*/, .pin_bcm=17, .edge=2,
                            .status=wfiStatus.statusOK, .ts_us=wfiStatus.timeStamp_us };
                    if (write(pipe_wr, &rec, sizeof rec) != sizeof rec) dropped++;   # 24-byte record
                    # returns immediately; interpreter never touched

Dispatch - when Perl services the fd (main thread, or a fork child):

2 Perl (API.pm)   wait_interrupts($ms): select(interrupt_fd) -> dispatch_interrupts():
                    while (sysread interrupt_fd, $buf, 24) {
                        ($pin,$pin_bcm,$edge,$status,$ts) = unpack "i I i i q", $buf;
                        $_last_interrupt = {...}; $_interrupt_cb{$pin}->($edge,$ts);   # $pin==0 -> matches
                    }
1 Perl (user)     &cb runs in the consuming interpreter, in normal context (G_EVAL-able).

Keying on userdata (0) - not pinBCM (17) - is what makes $_interrupt_cb{0} match under setup(); under setup_gpio() they'd coincide. The wiringPi ISR thread only ever does a write() of the fixed 24-byte record; it never enters the Perl interpreter, which is why this works on any Perl without locking.

SEE ALSO

WiringPi::API

AUTHOR

Steve Bertrand, <steveb@cpan.org>