Micros() problem

Hi.

I'm attempting to use an Adafruit Feather (M0) to gate three PWM signals from a source to their respective servos. I want the controlled PWM signals to either reflect the source inputs or (if certain conditions are met) output a fixed "neutral position" PWM signal.

The rising edge of each source PWM to the M0 generates an interrupt which sets the corresponding output PWM pin HIGH and initializes a long unsigned integer to micros().

If the condition is met, the main loop checks to see if micros() now exceeds the initialized start value + 1500 (1.5 ms). If so, the main loop lowers the M0's PWM output to the servo regardless of the current state of the source's PWM signal.

When the condition is not met, the M0 faithfully reproduces the source PWM signal to the servo.

When the condition is met, instead of outputting a 1500 us pulse to the servo, it outputs a constant high.

Debugging suggests my problem is with my "if(micros() > start + 1500)", after which the main loop is supposed to lower the PWM output to the servo. It seems straightforward, but I've read that micros() can be flakey after about 1.5 ms, and that interrupts can prevent long integers from being processed correctly in 8-bit microcontrollers.

I tried using noInterrupts() and interrupts() before & after the calculation. I made sure to characterize the long integers used in the ISRs as "volatile". It hasn't helped.

I'd be grateful for any help.

#include<Servo.h>

#define JawNeutral        1500         // Servos will be restricted to neutral position at times
#define HeadNeutral       1500         // Default = 1500 us
#define ShoulderNeutral   1500
#define StJawSigIn        0
#define JawServoOut       1
#define StHeadSigIn       5
#define HeadServoOut      6
#define StEyesSigIn       9
#define ShoulderServoOut  10
#define TnSwitch          11

volatile unsigned long JawNeutralMicrosStart;
volatile unsigned long HeadNeutralMicrosStart;
volatile unsigned long ShoulderNeutralMicrosStart;

boolean JawToNeutral = true;
boolean HeadToNeutral = true;
boolean ShoulderToNeutral = true;

void StJawRise()
{
  digitalWrite(JawServoOut, HIGH);
  JawNeutralMicrosStart = micros();
}

void StHeadRise()
{
  digitalWrite(HeadServoOut, HIGH);
  HeadNeutralMicrosStart = micros();
}

void StEyesRise()
{
  digitalWrite(ShoulderServoOut, HIGH);
  ShoulderNeutralMicrosStart = micros();
}


void setup() {
  // put your setup code here, to run once:
  pinMode(StJawSigIn, INPUT);
  pinMode(JawServoOut, OUTPUT);
  pinMode(StHeadSigIn, INPUT);
  pinMode(HeadServoOut, OUTPUT);
  pinMode(StEyesSigIn, INPUT);
  pinMode(ShoulderServoOut, OUTPUT);
  pinMode(TnSwitch, INPUT_PULLUP);

  pinMode(13, OUTPUT);

  attachInterrupt(StJawSigIn, StJawRise, RISING);
  attachInterrupt(StHeadSigIn, StHeadRise, RISING);
  attachInterrupt(StEyesSigIn, StEyesRise, RISING);
}

void loop() {
  // put your main code here, to run repeatedly:

  JawToNeutral = digitalRead(TnSwitch);
  HeadToNeutral = digitalRead(TnSwitch);
  ShoulderToNeutral = digitalRead(TnSwitch);

  if(JawToNeutral)
  {
    noInterrupts();
    if(micros() > JawNeutralMicrosStart + JawNeutral)
    {
      digitalWrite(JawServoOut, LOW);
    }
    interrupts();
  }
  else
  {
    if(digitalRead(StJawSigIn == LOW))
    {
      digitalWrite(JawServoOut, LOW);
    }
  }
  
  if(HeadToNeutral)
  {
    noInterrupts();
    if(micros() > HeadNeutralMicrosStart + HeadNeutral)
    {
      digitalWrite(HeadServoOut, LOW);
    }
    interrupts();
  }
  else
  {
    if(digitalRead(StHeadSigIn == LOW))
    {
      digitalWrite(HeadServoOut, LOW);
    }
  }

  if(ShoulderToNeutral)
  {
    noInterrupts();
    if(micros() > ShoulderNeutralMicrosStart + ShoulderNeutral)
    {
      digitalWrite(ShoulderServoOut, LOW);
    }
    interrupts();
  }
  else
  {
    if(digitalRead(StEyesSigIn == LOW))
    {
      digitalWrite(ShoulderServoOut, LOW);
    }
  }
}

Is volatile actually needed, do you know the side effects of it's use?
Do you have critical code that is not marked as critical?
Do you have code that needs to run fast, that is not located in the fastest memory?

Is it really constant? Or is it being set low and then immediately being set high again by another interrupt?

This type of calculation is not safe at micros rollover. You should be using subtraction to determine the length of time and then a compare. See the "Blink Without Delay" example for a look at the right way to write this.

if(micros() - JawNeutralMicrosStart >= JawNeutral)

1 Like

What source? Are these normal 1ms - 2ms every 20 ms servo signals?

I agree, but it shouldn't be the problem until 70 minutes?

@bitsoplenty, can you try it with "certain conditions" hard-coded to select a neutral pulse, which would mean I think just

  JawToNeutral = HIGH;
  HeadToNeutral = HIGH;
  ShoulderToNeutral = HIGH;

Also, it seems like you would have, but are you sure the interrupts are on the pins? I am accustomed to relying on digitalPinToInterrupt(), maybe not a thing on the Feather MO.

a7

Have you read and understood ALL the implications of volatile?
https://www.arduino.cc/reference/en/language/variables/variable-scope-qualifiers/volatile/

Thanks for your prompt response, Delta_G.

As for your (good) question, I used a scope to confirm that no rising edges occur following the leading edge of the input PWM pulse. That's consistent with my expectation that a servo PWM source would not output noisy pulses, but I agree that this was worth checking. I'm assuming that attaching an interrupt as RISING means that it is exclusively rising-edge triggered.

I tried rearranging the "if()" comparison per your recommendation. While I did see what appeared to be a slightly more stable pulse output generated by the M0 while the "ToNeutral" condition was in effect, the M0 still put out a constant HIGH. It's very puzzling to me why my code can't seem to discern when 1500 us have passed and then change an output from a HIGH to a LOW.

I looked for a "Blink Without Delay" example, but searching for it yielded so many hits, I wasn't sure to which post you were referring.

Thanks again for your help!

sonofcy,

Thanks for your reply. I did try your suggestion to force the three "ToNeutral" variables to HIGH. Good idea, but the M0's output remained at a constant HIGH.

I suspect that the three interrupts must be working because all three M0 PWM outputs go HIGH, and this occurs exclusively within their respective ISRs.

alto777,

Apologies for mixing up your reply with sonofcy.

Yes, these are the "normal" servos with pulses between 1-2ms recurring every 20ms.

Thanks for the informative "volatile" link, sonofcy. I did in fact read it while debugging earlier today. It was the reason I added "noInterrupts()" followed by "interrupts()" as a precaution in my main loop code. I had high hopes that this might fix my problem, but it didn't.

If there's another implication of using "volatile" that I might be missing, please let me know. Thanks!

sonofcy,

Apologies for my delayed reply.

I did read the Arduino Reference on "volatile". My understanding is that this qualifier should be used for any variable that might be changed within both the main loop and the ISR.

I haven't heard about marking certain code as critical. I may look into this. I'm just trying to get my program to change an output from HIGH to LOW after 1500 us.

If you've recommendations as to which part(s) of my code should be marked "critical", please advise. I don't think my application warrants making sure certain parts are executed from fastest memory.

My suspicion is that the micros() - start >= 1500 comparison isn't being compiled correctly or being executed correctly.

Way too early to decide the language, or compiler is broken. Look within your code, and/or conformance to the language. I can't help from this tiny screen, but will be back in a couple of hours and will check in.

If you read carefully, it said when marked volatile, it is compiled as a variable in ram versus register. Let me rephrase that, slow ram vs fast register.
Anytime I wrote an ISR I ONLY set a bool, NO other code, the digitalwrite alone takes 2.24usecs.
I will bet right now the compiler is not broken.
Here is doc on CRITICAL https://www.arduino.cc/reference/en/libraries/107-arduino-criticalsection/
and here is a sample. I think this may highlight your problem. https://github.com/107-systems/107-Arduino-CriticalSection

It's one of the most basic Arduino examples. It's included in the IDE. Yes there are a lot of posts about it. They are all talking about the same thing.

The volatile keyword works as expected in Arduino code and the OP is using it correctly. I'm not sure what you're on about but the point is to make sure that the compiler knows to fetch the value every time from RAM instead of relying on a temporary copy. The problem is that the optimizer won't realize that the value can change in the function and thus may decide to either hold the value in a register (where it never gets updated by the ISR) or simple optimize away the code eliminating any hope of it ever being updated by the ISR.

This is common usage and has been for a very long time. If it were broken we would know about it.

A digitalWrite in an interrupt is just fine. 2.24uS is for an AVR and it's still a third of the time needed to even enter the interrupt. I don't think 2.24 micros is making some huge difference in a piece of code looking for a 1500 micros pulse.

I know how volatile works. I was trying to convince him to not use it and employ a different strategy, but failing that, eliminate any code other than setting a bool inside the ISR. Sorry if I was not clear, but a ram fetch is much slower than a register fetch. I forgot to mention, he forgot to apply the appropriate memory macro for the ISR (I don't recall, but something like IRAM_ATTR?) I am 82 and autistic so I don't remember so good anymore.

Even with a bool, if it isn't marked volatile it may not work as expected.

Please show us how you will capture the micros at the start of the pulse in a boolean.

sonofcy,

Thanks for the information about volatile variables being compiled in RAM vs. registers.

In my humble opinion, speed isn't critical for my application. 2.24us in a 1500us pulse isn't going to cause me problems... though I appreciate your making me aware of this.

I need to write a LOW to an output pin approximately 1500us after a rising edge interrupt on an input pin. The fact that my interrupts are occurring is evidenced by the fact that my outputs are all going HIGH.

Either my unsigned long variables aren't being initialized correctly to micros() in the ISR (I assume that no coincident/subsequent interrupts would be responded to during an ISR), and/or something is preventing my (micros() - start > 1500?) time comparison from working... despite what I believe to be proper use of the volatile qualifier and of the noInterrupts() and interrupts() functions.

I was wrong to speculate about my code being compiled incorrectly. I should have said that my code doesn't appear to be executing as I (a relatively inexperienced code writer) understand that it should... a pretty common affliction.

1 Like

I have heard of worst case scenarios where an int is fetched in 2 byte sized chunks BUT something changed one of the bytes in the same time frame.