3

The ADXL345, for example, requires that interrupts are responded to by querying the INT_SOURCE (interrupt source) register. In example code for the SparkFun ADXL345 Arduino Library, there is code that looks like:

void ADXL_ISR() {
  // getInterruptSource clears all triggered actions after returning value
  // Do not call again until you need to recheck for triggered actions
  byte interrupts = adxl.getInterruptSource();

  // Free Fall Detection
  if(adxl.triggered(interrupts, ADXL345_FREE_FALL)){

However, under the covers adxl.getInterruptSource() is sending an I2C message and receiving a response. This seems to be a bad idea in an interrupt service routine (ISR).

A possible option seems to be to turn on interrupts prior to calling adxl.getInterruptSource(), however there is much advice about not doing this (such as, Nick Gammon's informative post). I've managed to get it to work with something of the form:

void setup() {
  attachInterrupt(digitalPinToInterrupt(INT_PIN), isr, LOW);
  ...
}
void isr() { 
  detachInterrupt(digitalPinToInterrupt(INT_PIN));
  interrupts();
  byte interrupts = adxl.getInterruptSource();
  ...
  attachInterrupt(digitalPinToInterrupt(INT_PIN), isr, LOW);
}

This seems to work but I'm worried I'm missing something. Any suggestions?

EDIT: Thanks for the answers that suggest polling, however I am using a MKR1400 and wish to minimise power consumption by putting the processor into a sleep state - only waking up to process the relatively rare "activity" or "free fall" events. As pointed out by @Majenko, a combination of sleeping and polling might be used. So this really just leaves the question of why is the above a bad idea.

  • I think your solution is allowed, but it is still totally nuts. Other interrupts might be missed because this one takes so long. To use the adxl.getInterruptSource function in a ISR is probably with a SPI interface in mind. Can you use the sensor with SPI mode ? – Jot Jul 1 at 10:39
  • @Jot thanks for the response. I don't quite get why another interrupt gets missed (but very happy to be educated). It's not possible for us to change to SPI and presumably it should be able to interface with this sort of device using I2C. – James Brusey Jul 1 at 10:47
  • For example Timer0 runs at 1ms interrupt for millis, or when there is serial input with 115200 baud. I think something will go wrong with everything together. – Jot Jul 1 at 16:03
  • @Jot this would be true if I didn't re-enable interrupts in the second line of the ISR. As it is, interrupts are only disabled during the call to detachInterrupt. – James Brusey Jul 3 at 17:05
8

Just because a pin is called "interrupt" doesn't mean that you have to read it using an interrupt input. The INTx pins of the ADXL345 are simple "level" outputs. All the time there is an "interrupt" pending (a signal from the ADXL345 that something has happened) the corresponding INT pin is held LOW. It remains LOW until the interrupt has been handled by your code.

It's not something that is very brief - it's something that persists as long as you need it to.

You can just read the INTx pins with normal digitalRead() from within your loop() function (polling). Unless you really need to respond right now to an event on the ADXL345 there is no need to use an interrupt input.

  • Although this is true - this doesn't help when you have a low power application. Polling would mean that the main processor is constantly on whereas if I drive from an interrupt, the main processor can be in a low power "sleep" state. – James Brusey Jul 2 at 8:04
  • You can still poll and sleep. The two aren't mutually exclusive. Poll an interrupt pin. – Majenko Jul 2 at 8:11
  • OK - it's a bit cumbersome as it means that you need to make sure that you wake up often enough to service the interrupts. Can you help me understand, though, the reason why there is a problem with detaching the interrupt handler, turning on interrupts and then proceeding from there? – James Brusey Jul 2 at 8:26
  • What is cumbersome? Wake on the interrupt pin, then sample in your loop. Or set a flag in the interrupt routine and use that to decide whether or not to sample. – Majenko Jul 2 at 8:37
  • ISRs should be short. Nothing else happens while the ISR is running. Things that use interrupts cannot be used inside an ISR. It's best to do as little as possible in an ISR. – Majenko Jul 2 at 8:39
5

Majenko's answer is useful — you can simply forego the use of interrupts for this and poll the pin directly in your main loop. Especially on AVR where the external pins are exposed as registers which can be read as fast as main memory this is a reasonable approach. But what if you really want the code to be "interrupt-fast" without writing code that might deadlock an ISR handler? Given the case of a fall or impact sensor, latency might be a genuine issue. Well, there are some things you can do.

First, structure your code so that you never go too long without polling the pin. That means that every bit of code needs to either return to the main loop within an acceptably short period of time, or else needs to take on the job of periodically polling the pin itself. Large compute tasks should have polls sprinkled into them, or be broken into subtasks that can be run by a state machine in the main loop. Long delays should be rewritten in the form

start = micros();
while (micros() - start < howLong) {
  // poll the ADXL interrupt
}

or, again, avoided entirely by queuing up work for the main loop to run later, instead of calling delay.

Second, consider using direct port access for the polling. A check like if (PORTB & (1<<3)) compiles down to a bare couple of instructions, and costs 2 cycles if the interrupt isn't actually asserted (which will be the usual case). Doing if (digitalRead(port)) does a call to a function which takes around 25 times as long. It's still only a few microseconds, but if you've taken the advice of the previous step to poll as frequently as possible to minimize latency, then you don't want all of those microseconds to add up and impact the performance of other code.

Lastly, a technique that doesn't make a lot of sense for this scenario, but could be applicable if you were running on a system other than an AVR-based Arduino, or if you wanted to react to some interrupt other than a level-triggered pin interrupt: you have an ISR that doesn't do any real work, but instead sets a global volatile variable to true and then immediately returns. Then in your main code, instead of polling the port directly, you check the value of that variable. If it's true, you set it to false and then run your "interrupt handler" code. The handler runs shortly after the interrupt triggers, but it's run in an interrupts-on context where it's safe to do slower operations or operations that require timer interrupts to work. This is equivalent to the old Linux concept of "bottom halves".

  • 1
    Nice thorough answer. (Voted) I would suggest adding an imperative "Whatever you do, don't try to read a serial port from an ISR" however. – Duncan C Jul 2 at 1:28
  • Thanks for the detailed answer. It might also help if you can describe the failure mode of the code in the question (i.e., why will it fail?). Your second part helped me most. – James Brusey Jul 2 at 8:57
2

"What is the right way to query an I2C device from an interrupt service routine?"

The right way is to NOT query an I2C device from in ISR. Interrupt service routines should be very fast to execute. (people advise going as far as avoiding digitalWrite() and digitalRead() function calls and using port registers directly, since those functions are a little slow. Anything done on a serial line is, by definition, not fast. It takes multiple milliseconds to read or write a few characters from a serial port, which is an eternity.)

When the Arduino services an interrupt it stops doing everything else, including handling other interrupts. Your timers will stall, other sensor data will be missed, any other serial communications that are in progress when the interrupt triggers will get errors, etc.

JUST DON'T DO IT.

  • I am not sure you understood my code - I enable interrupts just after calling detachInterrupt. The period of time when interrupts are disabled won't be very long. – James Brusey Jul 2 at 8:28

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.