Computing stuff tied to the physical world

IR decoding with pin-change interrupts

In AVR, Hardware, Software on Oct 14, 2010 at 00:01

Yesterday’s post described a new “InfraredPlug” class which handles the main task of decoding IR pulse timings. The “irq_recv.pde” example sketch presented there depended on constant polling to keep the process going, i.e. there has to be a line like this in loop():

    ir.poll();

Worse, the accuracy of the whole process depends on calling this really often, i.e. at least every 100 ┬Ás or so. This is necessary to be able to time the pulse widths sufficiently accurately.

Can’t we do better?

Sure we can. The trick is to use interrupts instead of polling. Since I was anticipating support for pin-change interrupts, I already designed the class API for it. And because of that, the changes needed to switch to an interrupt-driven sketch are surprisingly small.

I’ve added a new irq_send_irq.pde sketch to the Ports library, which illustrates this.

The differences between using polling mode and pin-change interrupts in the code are as follows. First of all, we need to add an interrupt handler:

Screen Shot 2010 10 13 at 00.26.10

Second, we need to enable those interrupts on AIO2, i.e. analog pin 1:

Screen Shot 2010 10 13 at 00.26.44

And lastly, we can now get rid of that nasty poll() call in the loop:

Screen Shot 2010 10 13 at 00.27.51

That’s all there is to it. Does it still work? Of course:

Screen Shot 2010 10 13 at 00.29.16

Note: I made some small changes in the InfraredPlug implementation to tolerate interrupts and avoid race conditions.

This all seems like an insignificant change, but keep in mind that this completely changes the real-time requirements: instead of having to poll several thousands of times per second to avoid missing pulses or measuring them incorrectly, we can now check for results whenever we feel like it. Waiting too long would still miss data packets of course, but this means our code can now continue to do other lengthy things (or go into a low-power mode). Checking for incoming packets a few times a second is sufficient (IR remotes send out a packet every 100 ms or so while a button is pressed).

So the IR decoder now has the same background behavior as the RF12 driver: you don’t need to poll it in real-time, you just need to check once in a while to see whether a new packet has been received. Best of all, perhaps, is that you can continue to use calls to delay() even though they make the main loop less responsive.

There is another side effect of this change: if your code includes a call to “ir.send()”, then the receiver will see your own transmission, and report it as an incoming packet as well. Which shows that it’s running in the background. This could even be used for collision detection if you want to build a fancy IR wireless network on top of all this.

So there you go: an improved version of the InfraredPlug class, which lets you use either explicit polling or pin-change interrupts. The choice is yours…

  1. Thanks for sharing. Waitingfor ir-plug :)

    btw, wouldn’t AttachInterval make a code more readable?

    ps, link to pde scetch is wrong.

    • The name “AttachInterrupt” would be confusing – I’m not taking over the interrupt function, the poll() call is the body of the interrupt function you need to set up yourself.

      But i agree that “poll()” is now a confusing name. Would “check()” be better? Keep in mind that this code can be used for polling as well as from pin-change interrupts.

  2. I meant that “ISR(PCINT1_vect) { … }” and manipulations with PCMSK1, PCICR registers could be replaced by pure arduino (but probably more readable) “void interrupt { … }” and “AttachInterrupt(1, interrupt, CHANGE)”.

    poll() is perfectly fine (for me) :)

    • Ah, now I get it. Yes, you can use AttachInterrupt(1, …), but then you have to use the INT1 (i.e. JN’s IRQ) pin. I want to be able to use this on any I/O pin, for which you need the ATmega’s pin-change interrupts.

Comments are closed.