Computing stuff tied to the physical world

Edge interrupts

In AVR, Hardware, Software on Jun 27, 2012 at 00:01

To continue yesterday’s discussion about level interrupts, the other variant is “edge-triggered” interrupts:

JC s Grid page 22

In this case, each change of the input signal will trigger an interrupt.

One problem with this is that it’s possible to miss input signal changes. Imagine applying a 1 MHz signal, for example: there is no way for an ATmega to keep up with such a rate of interrupts – each one will take several µs.

The underlying problem is a different one: with level interrupts, there is sort of a handshake taking place: the external device generates an interrupt and “latches” that state. The ISR then “somehow” informs the device that it has seen the interrupt, at which point the device releases the interrupt signal. The effect of this is that things always happen in lock step, even if it takes a lot longer before the ISR gets called (as with interrupts disabled), or if the ISR itself takes some time to process the information.

With edge-triggered interrupts, there’s no handshake. All we know is that at least one edge triggered an interrupt.

With “pin-change interrupts” in microcontrollers such as the ATmega and the ATtiny, things get even more complicated, because several pins can all generate the same interrupt (any of the PD0..PD7 pins, for example). And we also don’t get told whether the pin changed from 0 -> 1 or from 1 -> 0.

The ATmega328 datasheet has this to say about interrupt handling (page 14):

Screen Shot 2012 06 26 at 23 08 46

(note that the “Global Interrupt Enable” is what’s being controlled by the cli() and sei() instructions)

Here are some details about pin-change interrupts (from page 74 of the datasheet):

Screen Shot 2012 06 26 at 23 13 01

The way I read all the above, is that a pin change interrupt gets cleared the moment its associated ISR is called.

What is not clear, however, is what happens when another pin change occurs before the ISR returns. Does this get latched and generate a second interrupt later on, or is the whole thing lost? (that would seem to be a design flaw)

For the RF12 driver, to be able to use pin-change interrupts instead of the standard “INT0” interrupt (used as level interrupt), the following is needed:

  • every 1 -> 0 pin change needs to generate an interrupt so the RFM12B can be serviced
  • every 0 -> 1 pin change can be ignored

The current code in the RF12 library is as follows:

Screen Shot 2012 06 26 at 23 19 59

I made that change from an “if” to a “while” recently, but I’m not convinced it is correct (or that it even matters here). The reasoning is that servicing the RFM12B will clear the interrupt, and hence immediately cause the pin to go back high. This happens even before rf12_interrupt() returns, so the while loop will not run a second time.

The above code is definitely flawed in the general case when more I/O pins could generate the same pin change interrupt, but for now I’ve ruled that out (I think), by initializing the pin change interrupts as follows:

Screen Shot 2012 06 26 at 23 23 28

In prose:

  • make the RFM12B interrupt pin an input
  • enable the pull-up resistor
  • allow only that pin to trigger pin-change interrupts
  • as last step, enable that pin change interrupt

Anyway – I haven’t yet figured out why the RF12 driver doesn’t work reliably with pin-change interrupts. It’s a bit odd, because things do seem to work most of the time, at least in the setup I tried here. But that’s the whole thing with interrupts: they may well work exactly as intended 99.999% of the time. Until the interrupt happens in some particular spot where the code cannot safely be interrupted and things get messed up … very tricky stuff!

  1. hi jc. Thanks for this topic, which is both fascinating and very timely for me… a newbie trying to design a multi-function wireless node that reliably counts pulses from a water meter, periodically reads an analogue soil moisture sensor, and sleeps for lowest possible power consumption; hence three different types of interrupts. I’d be glad to hear other experiences in this area of reliably servicing multiple types of interrupts… it’s starting to look more complex than I was hoping it would be!

  2. PCIF2 cannot be cleared as the interrupt routine is entered – I think a typo in the spec sheet. I guess it would be easy to test that in the first few instructions of the interrupt routine. Has anyone scoped the RFM12b interrupt signal – is it clean?

    • Not so fast John, the status of PCIF2 could, in the ISR, be viewed as a queue for the next pin change interrupt. So long as PCIE2 is not re-enabled until the ISR completes.

  3. The only problem I had with the RF12 interrupt using the original code has been when the ‘low battery’ interrupt of the RF12 (which in the original code is by default enabled and set to a quite high value) fires. This ‘extra’ interrupt can (and will!) confuse the receive or transmit state machine. I solved this by setting the battery low level to the lowest value (2.0V??) and have never experienced any problems.

    But I think, you are aware of this and have a different problem?!

    • This should actually be solved with this patch over here:

      It moves from the state machine to a system based on the rf12 status.

    • Ah, I did not follow that! (Topic is ‘ATtiny using 1.8µA @3.3V’)

      Looks very much like my own solution (which I made additionally to the above level change). Did not make a big change for me though, as I do not experience any interrupt problems (apart from this ‘battery low’ thing).

  4. I would believe in datasheet here, the flag gets cleared so it can be set again if there happens to be another change.

    Think of the flags as of interrupt requests.

    And as I said yesterday, you can even set them yourself. I was not exact in describing the way of doing it though as you cannot just write to the flag (it must have been different cpu..). The flag will get updated even if your software changes the level of the watched pin while it is configured as output so you can trade unused pin for more interrupts.

  5. I have a setup where a bluetooth module (BTM222) and the RF12 share the same pin change Interrupt “Area”. It really is a problem that one cannot find out which pin triggered the ISR. My solution was to jump to both interruot functions (that is rf12_interrupt and the software serial routine) as fast as possible and check for a pin transition. This works quite good (even waking up the 328p from standby) but there is some risk to losse a bit because the ISR itself get´s quite large …

  6. So why not test it? Wire from an output pin to an input pin, with a change-of-state interrupt enabled on it. Then toggle the output pin inside an interrupt service routine & see what happens. You can also use additional output bits, setting each at the very start of the ‘inner’ and ‘outer’ interrupts, and clearing them just before returning from interrupt. Hook them up to your ‘scope and see what happens!

  7. My JeeBrick (pin change interrupts) only appear to give me problems when sleep functions are also used. I guess this is covered by jcw’s 99.99% but when sleep functions are used alongside pin change interrupts I get 100% failure.

  8. Wouldn’t you need to disable the pin change interrupt until the signal gets high again (hance ignoring rising edges and serving interrupts on falling edges)?

    • The code examples test for low levels, so anytime an interrupt is triggered and the pin is not low, it gets ignored.

Comments are closed.