XIAO interrupt occasionally missed

I am trying to get a routine working to control aircraft lights on a model aircraft. I have it working 99% but have one thing that has me stumped.
The circuit takes the output from one channel of an R/C receiver which is a pulse of between about 550 and 2400 mS. The code measures the pulse width and does several different things based on the pulse length. The ISR measures the time between the rising and falling edge of the pulse.
The problem is every once in a while it misses an interrupt and records an incorrect pulse length.
At first I though I was getting hung up in a Delay() loop so changed them out to use a millis() based function but it made no difference.
Hoping someone can have a look and let me know where I am going wrong.


// RC input settings
#define PIN_SERVO 4           // RC channel input pin number - this needs to match whatever interrupt is used
#define SERVO_LOW 1000        // RC channel low threshold
#define SERVO_MID 1400        // RC channel midpoint
#define SERVO_HIGH 1800       // RC channel high threshold
#define SERVO_DEAD_BAND 25    // Servo signal dead-band size, eliminates flicker
#define SERVO_REVERSED false  // Whether or not the servo channel is reversed

// Strobe settings
#define STB_PIN_LIGHT 5             // Strobe light output pin number
#define STB_BLINK_INTERVAL 1500000  // Blink interval for strobe light in microseconds

// Anti-collision beacon settings
#define ACB_PIN_LIGHT 7         // Anti-collision beacon output pin number
#define ACB_FADE_MIN 0          // Minimum fade level for beacon (0-255)
#define ACB_FADE_MAX 75         // Maximum fade level for beacon (0-255)
#define ACB_FADE_INTERVAL 8000  // Fade step interval, in microseconds (lower numbers = faster fade)

// Var declarations
volatile unsigned long servoPulseStartTime;
volatile int servoPulseWidth = 0;

// strobe via servo, acb is on whenever we have power!
boolean curStrobeLight = false;
boolean switchStrobeLight = false;

unsigned long lastFadeTime = 0;
unsigned long lastStrobeTime = 0;
int currentFade = ACB_FADE_MIN;
int fadeDirection = 1;

// the setup function runs once when you press reset or power the board
void setup() {

  // Set up interrupt handler.
  attachInterrupt(digitalPinToInterrupt(PIN_SERVO), measureServoSignal, CHANGE);

  //Initialize input pin.
  pinMode(PIN_SERVO, INPUT_PULLUP);
  // initialize light pins as an output.
  pinMode(STB_PIN_LIGHT, OUTPUT);
  pinMode(ACB_PIN_LIGHT, OUTPUT);
}

// the loop function runs over and over again forever
void loop() {
  unsigned long currentTime = micros();

  checkServo();
//  if (servoPulseWidth > 1400) {     //
    Serial.print(servoPulseWidth);  //    uncomment to monitor input pulse width
    Serial.println(" ");            //
//  }

  // Check if it's time to fade the anti-collision lights.
  if ((currentTime - lastFadeTime) > ACB_FADE_INTERVAL) {
    doFade();
    lastFadeTime = currentTime;
  }

  // Check if it's time to flash the strobes
  setStrobeLight(switchStrobeLight);
  if (switchStrobeLight) {
    if ((currentTime - lastStrobeTime) > STB_BLINK_INTERVAL && switchStrobeLight) {
      doStrobe();
      lastStrobeTime = currentTime;
    }
  }
}

// Check servo signal, and decide whether to turn things on or off
void checkServo() {

  // Strobe Lights
  // Modify threshold to prevent flicker
  int strobeThreshold = SERVO_MID;
  if (!curStrobeLight) {
    // Strobe are not on; adjust threshold up
    strobeThreshold += SERVO_DEAD_BAND;
  } else {
    // Strobe are on, adjust threshold down
    strobeThreshold -= SERVO_DEAD_BAND;
  }

  // Set output condition
  if (servoPulseWidth >= strobeThreshold) {  // strobe on
    switchStrobeLight = true;
  } else {
    switchStrobeLight = false;
  }
}

// Turn on or off strobe lights
void setStrobeLight(boolean state) {
  curStrobeLight = state;
}

// Fade anti-collision LEDs
void doFade() {
  currentFade += fadeDirection;
  if (currentFade == ACB_FADE_MAX || currentFade == ACB_FADE_MIN) {
    // If we hit the fade limit, flash the beacon, and flip the fade direction
    if (fadeDirection == 1) analogWrite(ACB_PIN_LIGHT, 255);  // Rotating ACB
    Pause(millis(), 100);
    fadeDirection *= -1;
  }

  analogWrite(ACB_PIN_LIGHT, currentFade);
}

// Strobe double-blink
void doStrobe() {
  digitalWrite(STB_PIN_LIGHT, HIGH);
  //delay(75);
  Pause(millis(), 75);
  digitalWrite(STB_PIN_LIGHT, LOW);
  //delay(50);
  Pause(millis(), 50);
  digitalWrite(STB_PIN_LIGHT, HIGH);
  //delay(50);
  Pause(millis(), 50);
  digitalWrite(STB_PIN_LIGHT, LOW);
}

// Measure servo PWM signal
void measureServoSignal() {
  int pinState = digitalRead(PIN_SERVO);
  if (pinState == HIGH) {
    // Beginning of PWM pulse, mark time    Serial.println(pinState);
    servoPulseStartTime = micros();
  } else {
    // End of PWM pulse, calculate pulse duration in uS
    servoPulseWidth = (int)(micros() - servoPulseStartTime);
    // If servo channel is reversed, use the inverse
    if (SERVO_REVERSED) {
      servoPulseWidth = (1000 - (servoPulseWidth - 1000)) + 1000;
    }
  }
}

void Pause(int startMillis, int Duration) {
  while (millis() - startMillis < Duration);
}
1 Like

Beginning to doubt it is actually missing an interrupt as the miss is exactly 1000 uS longer that it should be. Almost like the board is randomly going to sleep for exactly that amount of time.

You can try to edit your code like this:

// RC input settings
#define PIN_SERVO 4           // RC channel input pin number - this needs to match whatever interrupt is used
#define SERVO_LOW 1000        // RC channel low threshold
#define SERVO_MID 1400        // RC channel midpoint
#define SERVO_HIGH 1800       // RC channel high threshold
#define SERVO_DEAD_BAND 25    // Servo signal dead-band size, eliminates flicker
#define SERVO_REVERSED false  // Whether or not the servo channel is reversed

// Strobe settings
#define STB_PIN_LIGHT 5             // Strobe light output pin number
#define STB_BLINK_INTERVAL 1500000  // Blink interval for strobe light in microseconds

// Anti-collision beacon settings
#define ACB_PIN_LIGHT 7         // Anti-collision beacon output pin number
#define ACB_FADE_MIN 0          // Minimum fade level for beacon (0-255)
#define ACB_FADE_MAX 75         // Maximum fade level for beacon (0-255)
#define ACB_FADE_INTERVAL 8000  // Fade step interval, in microseconds (lower numbers = faster fade)

// Var declarations
volatile unsigned long servoPulseStartTime;
volatile int servoPulseWidth = 0;
volatile bool newServoData = false;

// Strobe via servo, acb is on whenever we have power!
boolean curStrobeLight = false;
boolean switchStrobeLight = false;

unsigned long lastFadeTime = 0;
unsigned long lastStrobeTime = 0;
int currentFade = ACB_FADE_MIN;
int fadeDirection = 1;

// the setup function runs once when you press reset or power the board
void setup() {
  // Set up interrupt handler.
  attachInterrupt(digitalPinToInterrupt(PIN_SERVO), measureServoSignal, CHANGE);

  // Initialize input pin.
  pinMode(PIN_SERVO, INPUT_PULLUP);
  // Initialize light pins as an output.
  pinMode(STB_PIN_LIGHT, OUTPUT);
  pinMode(ACB_PIN_LIGHT, OUTPUT);

  // Start serial communication for debugging
  Serial.begin(9600);
}

// the loop function runs over and over again forever
void loop() {
  unsigned long currentTime = micros();

  if (newServoData) {
    noInterrupts(); // disable interrupts while accessing shared variable
    int pulseWidth = servoPulseWidth;
    newServoData = false;
    interrupts(); // re-enable interrupts

    checkServo(pulseWidth);
    Serial.print(pulseWidth);
    Serial.println(" ");
  }

  // Check if it's time to fade the anti-collision lights.
  if ((currentTime - lastFadeTime) > ACB_FADE_INTERVAL) {
    doFade();
    lastFadeTime = currentTime;
  }

  // Check if it's time to flash the strobes
  setStrobeLight(switchStrobeLight);
  if (switchStrobeLight) {
    if ((currentTime - lastStrobeTime) > STB_BLINK_INTERVAL) {
      doStrobe();
      lastStrobeTime = currentTime;
    }
  }
}

// Check servo signal, and decide whether to turn things on or off
void checkServo(int pulseWidth) {
  // Strobe Lights
  // Modify threshold to prevent flicker
  int strobeThreshold = SERVO_MID;
  if (!curStrobeLight) {
    // Strobe are not on; adjust threshold up
    strobeThreshold += SERVO_DEAD_BAND;
  } else {
    // Strobe are on, adjust threshold down
    strobeThreshold -= SERVO_DEAD_BAND;
  }

  // Set output condition
  if (pulseWidth >= strobeThreshold) {  // strobe on
    switchStrobeLight = true;
  } else {
    switchStrobeLight = false;
  }
}

// Turn on or off strobe lights
void setStrobeLight(boolean state) {
  curStrobeLight = state;
}

// Fade anti-collision LEDs
void doFade() {
  currentFade += fadeDirection;
  if (currentFade == ACB_FADE_MAX || currentFade == ACB_FADE_MIN) {
    // If we hit the fade limit, flash the beacon, and flip the fade direction
    if (fadeDirection == 1) analogWrite(ACB_PIN_LIGHT, 255);  // Rotating ACB
    Pause(millis(), 100);
    fadeDirection *= -1;
  }

  analogWrite(ACB_PIN_LIGHT, currentFade);
}

// Strobe double-blink
void doStrobe() {
  digitalWrite(STB_PIN_LIGHT, HIGH);
  Pause(millis(), 75);
  digitalWrite(STB_PIN_LIGHT, LOW);
  Pause(millis(), 50);
  digitalWrite(STB_PIN_LIGHT, HIGH);
  Pause(millis(), 50);
  digitalWrite(STB_PIN_LIGHT, LOW);
}

// Measure servo PWM signal
void measureServoSignal() {
  int pinState = digitalRead(PIN_SERVO);
  if (pinState == HIGH) {
    // Beginning of PWM pulse, mark time
    servoPulseStartTime = micros();
  } else {
    // End of PWM pulse, calculate pulse duration in uS
    unsigned long pulseEndTime = micros();
    servoPulseWidth = (int)(pulseEndTime - servoPulseStartTime);
    newServoData = true;
    // If servo channel is reversed, use the inverse
    if (SERVO_REVERSED) {
      servoPulseWidth = (1000 - (servoPulseWidth - 1000)) + 1000;
    }
  }
}

void Pause(unsigned long startMillis, int Duration) {
  while (millis() - startMillis < Duration);
}

Thanks for the suggestion @liaifat85. Unfortunately it does exactly the same thing.

interesting project/problem

Hi there,

What about creating two interrupts, one for the Beginning and a seperate one for the end of the Pulse?
or Set it in a way that the Rising edge INT , blocks until the falling edge arrives? breaking it out after, I see You have it as “change” , may be better if it was edge or level? and add a count in there too.

just tossing some thoughts… interesting the duration though is exactly 1000 ? that’s peculiar at best IMO.
Keep digging, you’ll get there. How do you get the PWM into the Xiao? You got any pictures of this apparatus and connections ? :blush:
GL :slight_smile: PJ
:v:

So I ported the code back to a Pro Micro and it works as it should; no timing issues whatsoever. The only changes I made were a couple of pin assignments to support the interrupts and a PWM output and to go back to using delay() instead of my Pause function which I gather is blocking the same as delay() would anyway.
My reason for wanting to go with the XIAO was the size factor. The Pro Micro is double the size of the XIAO.

Hi there,
Glad you got it working either way for you, However for anyone looking to work through this kind of problem, You could answer some basic questions, Which BSP were you using with the Xiao?
The interrupts require a certain amount of nuance and understanding to work properly, the Xiao eco system is NOT for everyone beginners and experts alike. It’s tiny size and powerful capabilities are not easily understood without additional exercise and knowledge of it’s Peripheral ports and operations, It’s NOT an 8-bit Atmel AVR not even close.
How was this powered?
You would benefit by it’s tiny footprint and low power draw especially if the Model is a small one. I suspect there was another issue afoot in relation to the questions. Props to liaifat85 for his code suggestion. There is also another thread of a Firetruck model with excellent GPIO sequencing for the realistic emergency lights and effects I would look that up and see if you can glean anything from the authors efforts.
Can you post a pic of it so we all can see your progress?
Thanks for the interesting comparison.

GL :slight_smile: PJ
:v:

Thanks @PJ_Glasso.
I uploaded a couple of short vids to my Google drive as I can’t attach vids here.
The first is using the Xiao with just the ACB and strobe wired. You can see the strobe trigger once even though it isn’t supposed to.
XIAO.mp4
The second shows the ProMicro with the full code with ACB, Strobe, Nav and Landing light functional.
ProMicro
The second board is just a simple pulse generator to simulate my RC receiver output.
Not sure what you mean by BSP? Quite new to the XIAO.

Hi there,
Awesome, thanks for contributing. I clicked the request access :v:
GL :slight_smile: PJ :+1:

You should be able to see them now.

Hi there,
I see what you mean, I like the red ping effect initially, then the strobe effect on the promicro, that can be even better implemented in a Nrf52840 Xiao , that’s a samd21 I hadn’t realized that.
I’m sure once it all comes together it will be very cool. :v:

So I have been beating my head against a wall for week now trying to figure a workaround for this. Nothing I have tried has worked reliably. I have tried validating the pulse length but as the falling edge interrupt can happen at any time, my validation routine doesn’t always catch it. I have also tried using the pulseIn() function but as it is a blocking function, it messes up some other timing in my program.
I have found that yes, there is (or was) a bug in the micros() function and has been for some time although for most it seems to have been fixed. The consensus is, it is or was in the wiring.c file. Why my code works fine on a Pro Micro but not the SAMD21 is puzzling though.
I am more of a hack at this time and getting down to that level is way beyond my skillset at the moment.
I am using the latest IDE (2.3.2) on a Macbook. I did find another simpler code example that duplicates what I am trying to do but alas, it does the same thing.
https://roboticsbackend.com/arduino-pulsein-with-interrupts/

#define BUTTON_PIN 4

volatile unsigned long pulseInTimeBegin = micros();
volatile unsigned long pulseInTimeEnd = micros();
volatile bool newPulseDurationAvailable = false;

void buttonPinInterrupt() {
  if (digitalRead(BUTTON_PIN) == HIGH) {
    // start measuring
    pulseInTimeBegin = micros();
  } else {
    // stop measuring
    pulseInTimeEnd = micros();
    newPulseDurationAvailable = true;
  }
}

void setup() {
  Serial.begin(9600);
  pinMode(BUTTON_PIN, INPUT);

  attachInterrupt(digitalPinToInterrupt(BUTTON_PIN),
                  buttonPinInterrupt,
                  CHANGE);
}

void loop() {
  if (newPulseDurationAvailable) {
    newPulseDurationAvailable = false;
    unsigned long pulseDuration = pulseInTimeEnd - pulseInTimeBegin;
  if (pulseDuration > 1000)  Serial.println(pulseDuration);
  }

  // do your other stuff here
}

I really would like to get this to work on the XIAO due to its smaller size.