Interrupt fires as soon as attached

I am writing a library to interface with a GPS receiver and am implementing a function to calculate the offset between the Arduino's internal clock (using the millis() counter) and the GPS clock. To do this, I have a function that configures the receiver then looks for a "timepulse" event from the receiver that indicates that it is now a particular time of day. The timepulse is a 10% duty cycle square wave but the actual sync event is the rising edge of each pulse, which is being detected by an interrupt. I have run into a problem where the interrupt fires as soon as its attached, rather than upon the next rising edge.

I've found several other references saying that the attachInterrupt() function is supposed to(?) clear interrupt flags before enabling but this seems like its not working. Also others that say the interrupts can sometimes have a sort of "memory" from events that occurred before attaching.

Can anyone tell me either what I'm doing wrong here or how I should be manually clearing the interrupt? I also get the impression that the method to do this varies board to board, is there a board-agnostic solution to this problem?

Additional Details

  • Arduino MKR1010
  • Using board pin 0 for the interrupt
  • Interrupt is called from a class object. Yes the class is needed, but only ever one instance.
  • Interrupt input is a clean square wave from another device, not a bouncy button.
  • @Coding_Badly I saw several of your posts saying that EIFR = bit(INTF0); achieves this but I think that was for AVR?

Similar issues


My Code (Simplified)

#include <Wire.h>

class UBLOX_INTERFACE
{
    private:
    const uint8_t i2cAddress;
    const uint8_t timePulsePin; //The digital input pin on the Arduino used to measure the receiver's timepulse signal
    static volatile bool timepulseDetected; //Declaration. Used in the time sync interrupt
    
    public:
    UBLOX_INTERFACE(const uint8_t addressParam, const uint8_t timePulsePinParam) : i2cAddress(addressParam), timePulsePin(timePulsePinParam) {};
    void setup();
    static void timepulseInterrupt();
    void gnssTimepulse();
};

volatile bool UBLOX_INTERFACE::timepulseDetected = false; //Definition

UBLOX_INTERFACE ublox(0x42, 0);

void setup()
{
    pinMode(1, OUTPUT); //Used so I can see when the interrupt is fired on the logic analyzer
    Serial.begin(19200);
    while(!Serial){}
    Wire.begin();
    ublox.setup();
}

void loop()
{
    ublox.gnssTimepulse();
    delay(1000);
    Serial.println();
}

void UBLOX_INTERFACE::setup() { pinMode(timePulsePin, INPUT_PULLDOWN); }

void UBLOX_INTERFACE::gnssTimepulse()
{
  Serial.println("Enter TP function");
  timepulseDetected = false;
  
  Serial.print("tpDetected before attach: ");
  Serial.println(timepulseDetected);
 
  attachInterrupt(digitalPinToInterrupt(timePulsePin), timepulseInterrupt, RISING);
  
  Serial.print("tpDetected after attach:  ");
  Serial.println(timepulseDetected);
  while(true) //Normally there is a timeout to escape this loop, removed for brevity
  { 
      Serial.print("tpDetected during loop:   ");
      Serial.println(timepulseDetected);
      if(timepulseDetected == true) //Defaults to false, only true when interrupt fires
      {
          Serial.println("TP detected");
          break;
      }
      delay(10); //Added so I don't spam the serial output as much
  }
  
  detachInterrupt(digitalPinToInterrupt(timePulsePin)); //Don't want this firing at any other time
  timepulseDetected = false; //Once you get to this point in the code this variable is no longer needed
  
  delay(10);
  digitalWrite(1, LOW); //TODO remove this test stuff
  Serial.println("Leave TP Function");
}

void UBLOX_INTERFACE::timepulseInterrupt()
{
    timepulseDetected = true;
    digitalWrite(1, HIGH); //Used so I can see when the interrupt is fired on the logic analyzer
}

Serial Monitor Output
The output from this code shows that immediately after attaching the interrupt, it fires. The same can be seen in the output from a logic analyzer where the interrupt firing is nowhere near the event that supposedly triggered that interrupt.

image
image


Working Simple Code

#define OUTPUT_PIN 1
#define INTERRUPT_PIN 0
volatile byte state = LOW;
void setup() {
  pinMode(OUTPUT_PIN, OUTPUT);
  pinMode(INTERRUPT_PIN, INPUT_PULLDOWN);
  delay(2000);
  attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), interrupt, RISING);
}
void loop() {
  // nothing here!
}
void interrupt() {
  state = !state;
  digitalWrite(OUTPUT_PIN, state);
}

Output
In this case the interrupt fires as expected, on the rising edge of the timepulse signal. I don't understand why this code works and mine does not. Though functionally simpler, they seem to be doing fundamentally the same thing.
image

How to reset clear interrupt flag MKR MKR1010 SAMD SAMD21

Thanks for the detailed post and sharing your research

Some of the links you refer to are not for a MKR1010 architecture but for the good old Arduino UNO (well the ATMEGA 328P) so the registers and way the interrupts work are different.

can you clarify the wiring? a drawing of your full circuit including GPS would be useful.

There must be a way to do that with the SAMD21 M0 processor, but it is very unlikely that there is an Arduino core function (even more unlikely a processor-agnostic function) to do so. It is extremely time consuming to try to pack all the processor-specific details into a general function, and design a user interface to make sense of the options.

First, do some on line research to see whether someone else has solved this problem for the MKR1010 and if that fails, you will need to learn how to do that yourself, by consulting the processor data sheet.

However, is this really a problem? Attach once, toss the first interrupt, and get on with your project.

Try something like:

#define OUTPUT_PIN 1
#define INTERRUPT_PIN 0
volatile byte state = LOW;
volatile bool handleInterrupt = false;

void setup() {
  pinMode(OUTPUT_PIN, OUTPUT);
  pinMode(INTERRUPT_PIN, INPUT_PULLDOWN);
  delay(2000);
  attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), interrupt, RISING);
  delay(100);
  handleInterrupt = true;
}
void loop() {
  // nothing here!
}
void interrupt() {
  if(!handleInterrupt) {
    return;
  }
  
  state = !state;
  digitalWrite(OUTPUT_PIN, state);
}

It is.

The wiring is just the MKR1010 and this GPS receiver on a breadboard with one wire from the receiver pulse output pin to pin 0 on the Arduino.


Unfortunately yes. I see what you're saying but it seems like this an inconsistent problem. Depending on the timing of the pulses and how often I look for them, sometimes it works fine and sometimes it doesn't. I see what you're saying though.


@gfvalvo I like the idea of your solution, just gave it a shot and seems like it works! I dropped the delay() between attachInterrupt() and enabling handleInterrupt but this is likely the solution I'll go with because as far as I can tell it is board-agnostic. Thanks!


I actually did find the command to clear the interrupt flag though. Looked at the datasheet to find references to an "INTFLAG" variable. Section 20.8.8 "Interrupt Flag Status and Clear" says "Writing a one to this bit clears the External Interrupt x flag". This also seems to fix my problem (though I first set the bit to 0 which also seems to have worked.

So overall, this issue seems to be solved! The reason the second simple sketch I posted worked was because that code was allowed to actually fire the interrupt each time the rising edge occurred. In my GPS code the timepulse occurred every 1s but I was only enabling the interrupt every 10s. This meant that the interrupt event would be triggered 9 times before I enabled the actual interrupt routine. And due to the interrupt's "memory" it would fire the routine as soon as attached.

Joined GND as well I suppose?

Bear in mind that the solution very likely requires a memory barrier to be correct.

Yeah the receiver is powered by the Arduino


I have no clue what that means.

It means that, if you decide to use @gfvalvo's suggestion, you should develop the code a bit further then ask for a review.

The details are interesting but take some effort to explain. I found Herb Sutter's lectures to be the most understandable. Part 1. Part 2.

The short answer is that the compiler can reorder reads and writes to state and handleInterrupt. The two variables work together to form a solution. The compiler is unable to see that so it's free to mess about.

You could handle it completely within the ISR by making 'handleInterrupt' a static local variable. Simply ignore the first interrupt.

void interrupt() {
  static bool handleInterrupt = false;
  static byte state = LOW;

  if (!handleInterrupt) {
    handleInterrupt = true;
    return;
  }

  state = !state;
  digitalWrite(OUTPUT_PIN, state);
}
1 Like

making them volatile would prevent optimisations

The compiler may determine that the following two lines are swappable:

  attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), interrupt, RISING);
  handleInterrupt = true;

true indeed

That prevents load / store optimizations but not reorder optimizations.

Watch the lectures. :wink:

yes indeed. I was reacting just to the comment where you mentioned two variables (state and handleInterrupt) but forgot about the larger context

reminds me of good old membars :slight_smile:

1 Like

Watching these now.

I don't suppose there's a simple way to tell the compiler to not to do that swapping like volatile does for caching?

There is. It will be a compiler directive. This is the one for avr-libc.

The search phrase is "memory barrier" so "avr gcc memory barrier" got me to the link above.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.