2

I've got an Arduino Mega periodically running an (~400ms) operation that is sensitive to repeatable timing, so I don't ever want it to be interrupted. But, I'd like to be able to use an interrupt flag from one of the built-in peripherals (PCINT, comparator, etc.) to check afterward if an external event (say, a pin going high momentarily) ever happened during the operation.

That is, it'd be nice if I could just treat one of the interrupt flags as a kind of async hardware latch that I can check (and reset) at my next earliest convenience instead of having my code interrupted.

It wouldn't be hard to add a discrete latch IC, but I was hoping I could get away with just using what was already built into the AVR without adding more hardware (and using an extra pin to reset the latch).

Looking at the documentation for each of the peripherals, it was unclear whether the flags will still be set when the interrupts themselves are disabled. I saw a mention someplace of an "EMPTY_INTERRUPT" macro, but I'm guessing that still generates an interrupt vector with a single "return" instruction, so I'd still be losing cycles to the interrupt preamble, etc.

Can this be done without any ISR being called at all? Or do I need to add more hardware? I'm not sure wrapping the long-running task in noInterrupts()/interrupts() is a choice unless that also doesn't interfere with UART receiving. Thanks!

7
  • According to my page about interrupts the minimum time to execute an ISR is 2.625 µs plus whatever the code does (like set a flag). Is that not acceptable? Once? In your 400ms routine? That's a tiny fraction of how long your code takes.
    – Nick Gammon
    Jul 2, 2023 at 7:09
  • I'm not sure wrapping the long-running task in noInterrupts()/interrupts() is a choice unless that also doesn't interfere with UART receiving. - so some interrupts are actually OK?
    – Nick Gammon
    Jul 2, 2023 at 7:16
  • If you are receiving from a UART then time isn't really of the essence, is it? What is the baud rate?
    – Nick Gammon
    Jul 2, 2023 at 7:18
  • What are the specific requirements of that periodic operation, jitter, frequency, tolerances, and so on? Jul 2, 2023 at 11:39
  • I'm strobing LEDs to provide uniform illumination for a dozen or so successive industrial camera exposures (using a busy-wait). It's less about exact timing and more about repeatable timing. Each exposure is only a couple hundred microseconds, so even 2.625µs of periodic jitter would increase the exposure time by at least 1%. I could use one of the timers, but I preferred the simplicity of the straight-line code. I didn't know the UART interrupts the CPU; I'll have to check out the datasheet and maybe ask a separate question about that.
    – Nicholas
    Jul 2, 2023 at 23:34

2 Answers 2

6

The answer to your question is: yes, you can use an interrupt flag even if the interrupt itself is disabled. The flag is raised whenever the peripheral detects the relevant event. You can check it at your convenience.

As for resetting the flag, the way to do it varies from flag to flag. Most are reset by writing a 1 bit to it (yes, it is kind of backwards, but that's how it works). Others are reset by removing the condition that raised them in the first place (e.g. the USART Data Register Empty interrupt). You will have to check the datasheet for the specific interrupt flag you are interested in.

That being said, I would question your idea that you don't want your code to be interrupted at all. As Nick Gammon writes in a comment, the minimum time to execute an ISR is 2.625 µs. This is about 0.0007% of the process period. If your operation is so time-sensitive that it cannot tolerate this kind of variation, then it cannot run on a Mega at all. The ceramic resonator that clocks a Mega has a typical accuracy of about 0.1%, which is 150 time worse than the effect of a minimal interrupt. Even if you calibrate this oscillator against an atomic clock, in only a few hours its frequency will drift past 0.0007%.

In addition, if you want the code to work with no interrupts, you will have to disable them explicitly with noInterrupts(). You will then loose the Arduino timing function millis(), delay(), etc. And you will not be able to receive data from the UART unless you write your own code that polls the “receive complete” UART interrupt flag and then grabs the data from the UART data register.

8
  • Are you certain about that? In the datasheet it says The External Interrupts 7 - 4 are activated by the external pins INT7:4 if the SREG I-flag and the corresponding interrupt mask in the EIMSK is set. My reading of that was that the flag is set if the interrupt is enabled. Otherwise, how would it know if it is rising or falling?
    – Nick Gammon
    Jul 2, 2023 at 10:29
  • I might be wrong about that, I admit.
    – Nick Gammon
    Jul 2, 2023 at 10:31
  • @NickGammon: In edge-detection mode, the flag is set even if the interrupt is disabled (I just checked). However, this is a bad example, as the interrupt flag does not work in level-detection mode: “These flags are always cleared when INT7:0 are configured as level interrupt.” I changed the example in my answer. Jul 2, 2023 at 12:34
  • 2
    I left a comment above, too, but the actual process period is ~200µs (repeated many times) while exposing an image to a camera sensor. And it's less sensitive to any particular clock rate. I just want each identical busy-wait to take the same wall-clock time so there isn't any brightness difference between each exposure. Even that 2.6µs gives around 1.3% additional illumination. On a 12-bit sensor, that starts to add up quickly. The UART intentionally isn't sending or receiving during that period, so hopefully it's alright to just leave it enabled...
    – Nicholas
    Jul 2, 2023 at 23:43
  • It seems to me that the question of whether the flag is set or not might be somewhat implementation-specific. Even if it is 100% guaranteed for AVR chips it might not be for others, hence relying on that behaviour is not very good practice. Jul 3, 2023 at 6:49
2

I'm not sure wrapping the long-running task in noInterrupts()/interrupts() is a choice ...

That's right. Turning off interrupts would stop the timing/delay working so you wouldn't be able to time your 200 µs (unless you use delayMicroseconds).

Each exposure is only a couple hundred microseconds, so even 2.625µs of periodic jitter would increase the exposure time by at least 1%.

I think you might be better off using a hardware timer to time the exposure, rather than a loop. I generated VGA signals in a post here where I wanted the horizontal and vertical sync to have no jitter whatsoever. The timers can be set to turn on, or off, an output pin, so that could be used to control your shutter. Then it would be completely precise.

I didn't know the UART interrupts the CPU ...

Incoming (or indeed outgoing) serial communications causes interrupts. The timers cause interrupts. In particular, if you haven't disabled it, Timer 0 generates interrupts which are counted so that millis() and micros() can function. You can disable interrupts but then timing exact intervals becomes problematic. My suggestion of using the timer hardware will give the most reliable and repeatable performance, in the same way that my VGA signal generation was completely without jitter.

Because of the timer interrupts, if you haven't disabled interrupts, some of your exposures may have the timer interrupt in the middle (making them longer) and some not, or at least, a different number of interrupts. This will make the exposure time non-precise.


Another approach would be to turn off interrupts for the exposure only (ie. your 200 µs, not the 400 ms you mention), and then use delayMicroseconds() to time the exposure. Then when interrupts are back on the closure of a switch (an external interrupt) could be processed by a normal ISR. This might add slight differences to the time between exposures, but the exposure time itself would be constant.

Even with interrupts off, an external interrupt is "remembered" and would be processed when interrupts are enabled again.


This is turning into an XY problem somewhat. It would have been better to mention your exact requirements (the photography part) rather than launching into questions about using interrupts as latches.


Example code

To demonstrate how you can do this with timers, I have written some sample code:

// Example of strobing a flash FRAMES_TO_TAKE times to take pictures
// The time between each flash is in TIME_BETWEEN_SEQUENCES
// The flash "on" time is in STROBE_ON_TIME
//
// For Atmega328P (Arduino Uno and similar)
// and Atmega2560 (Arduino Mega)

// Author: Nick Gammon
// Date: 4 July 2023

const byte FRAMES_TO_TAKE = 12;
const byte CPU_CLOCK_SPEED = 16;  // MHz

const unsigned long TIME_BETWEEN_SEQUENCES = 20000;  // µs
const unsigned long STROBE_ON_TIME = 200;  // µs
const byte PRESCALER = 64;

// set correct output pin
#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328P__)
  const byte FLASH_PIN = 10;  // OC1B (Output Compare Timer 1: B side) - Atmega328P
#elif defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
  const byte FLASH_PIN = 12;  // OC1B (Output Compare Timer 1: B side) - Atmeg12560
#else
  #error Processor not supported
#endif

volatile byte count;  // how many times it fired

// Timer 1 Compare "B" ISR vector
// count up to the required number of exposures, then stop
ISR (TIMER1_COMPB_vect)
  {
  if (++count > FRAMES_TO_TAKE)
    {
    // stop timer 1
    TCCR1A = 0;
    TCCR1B = 0;
    } // end if number of frames reached
    
  }  // end of TIMER1_COMPB_vect

// does one sequence of FRAMES_TO_TAKE shots and then stops
void startSequence ()
{
  // stop timer 1
  TCCR1A = 0;
  TCCR1B = 0;

  TCCR1A = bit (WGM10) | bit (WGM11);  // fast PWM - top at OCR1A
  TCCR1B = bit (WGM12) | bit (WGM13);  //  (mode 15)

  TCCR1A |= bit (COM1B1);   // Set OC1B during duty cycle
  
  OCR1A = ((TIME_BETWEEN_SEQUENCES * CPU_CLOCK_SPEED) / PRESCALER) - 1;  // total cycle time
  OCR1B = ((STROBE_ON_TIME * CPU_CLOCK_SPEED) / PRESCALER) - 1;          // duty cycle time
  count = 0;

  TCNT1 = 0;    // reset timer count
  
  TIFR1   = bit (OCF1B);    // clear "compare B" flag
  TIMSK1  = bit (OCIE1B);   // interrupt on compare B match
  TCCR1B |= bit (CS10) | bit (CS11);     // start timer with prescaler of 64
} // end of startSequence

void setup ()
  {
  // stop timer 1
  TCCR1A = 0;
  TCCR1B = 0;

  // set up pin so we can toggle it
  pinMode (FLASH_PIN, OUTPUT);
  digitalWrite (FLASH_PIN, LOW);

  // for debugging etc.
  Serial.begin (115200);
  }  // end of setup

void loop ()
  {
  startSequence ();
  delay (20);
  Serial.print ("But does it get goat's blood out?\n");
  delay (1000);
   }  // end of loop; 

Code explanation

What this code does is use Timer 1 to time the intervals you require. Based on what you posted earlier, I have assumed:

  • 12 exposures in a "session" (FRAMES_TO_TAKE) - you said "a dozen or so"
  • Exposures are to be 20 ms apart (TIME_BETWEEN_SEQUENCES) - so a session is 240 ms
  • Exposure time is to be 200 µs (STROBE_ON_TIME) - you said "a couple of hundred microseconds"
  • You want to do other stuff like serial comms, checking switches etc.

The function startSequence initiates the timer which has a period of 20 ms and a duty cycle of 200 µs. At the completion of a duty cycle the ISR is called which counts duty cycles. At the end of the nominated number (12) the timer is turned off.

The output pin (pin 12 on the Mega, pin 10 on the Uno) is turned on during the duty cycle (ie. for 200 µs and off the rest of the time). Because this is done in hardware there is no jitter at all. In my demo I am doing serial comms to show that the intervals are exactly correct, despite the interrupts caused by the comms.


Logic analyser output

The logic analyser screenshot shows that the intervals are exactly correct (to within a fraction of a percent). In any case the intervals are consistent with each other.

Logic analyser output

This second screenshot shows that there are 12 pulses in that interval.

Logic analyser showing count of pulses

I have used a prescaler of 64 to get a nice long interval that can be timed (without overflowing the 16-bit counter size). This gives a granularity of 64 clock cycles (4 µs).


Timer 1 cheat sheet


Timer 1 cheat sheet

4
  • These are all good suggestions. Disabling interrupts for the individual exposures sounds like it might be the way to go. I was only avoiding timers out of the convenience of not having to reason about the usual ISR problems: atomic (16-bit) read/writes, etc., but my scheme is simple enough that I could probably just set the timer, busy wait until it fires, and then continue on my way. Regarding the XY problem, this is still an interesting question to me and I'm happy I know the answer for future projects, even if I don't end up using the interruptless-flags approach here.
    – Nicholas
    Jul 3, 2023 at 3:48
  • 1
    @Nicholas If you let the timer itself change the pin state then you eliminate the (small) jitter based on where in your tight loop checking the timer to finish, it actually finishes. The read of the timer state, the compare, and the branch would probably amount to 6 to 8 clock cycles.
    – Nick Gammon
    Jul 3, 2023 at 6:41
  • You are right that checking a timer flag in software introduces some jitter, but it's not as bad. A tight loop testing a flag on timer 1, 3, 4 or 5 is just sbis + rjmp. It takes 3 cycles, thus you have a 2-cycle jitter. Timers 0 and 2 require an lds, making it a 5-cycle loop. Jul 3, 2023 at 8:13
  • 1
    @Nicholas See amended answer with example code of using a timer.
    – Nick Gammon
    Jul 4, 2023 at 4:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.