Generalization question: Wait with millis() instead of delay() all the time?

[!SOLVED!
Issue: coding error, wrong variable used for math which resulted in 0 wait time :slight_smile:
Lesson learned: delay() doesn't interrupt anything, especially not servo motion, even though doc suggests it does
]

hey guys,

Story: My servo didn't "reset" to 0 position even though I used delay() to wait for it to happen. It turned out (RTFM-ed the issue) delay() is not the ultimate thing here, it's actually the worst one can do as it works like a NMI, rather than a soft wait.

So, question. Is this a good approach to make a soft wait method and generalize waiting? Here's the code I'm wondering to add and replace all my delay()-s:

void DelayEx(int ms) {
	unsigned long waitMS = millis() + ms;
	while(millis() <= waitMS);
}

Any potential issues with this? (in general). Is this the good approach or it's not at all a 'best practice' on Arduino? (In which case, what would be better?)

Thanks!

You have just re-invented delay() :wink:

While your code is in that while-loop, nothing else can happen. That's the same as with delay().

Hm. Are you sure about that?
From what I read, delay() is like an NMI, so it interrupts all hardware actions, everything otherwise would go on on the Arduino (Uno) - this looks legit as my servo stopped moving in the middle of a motion when I commanded it to 'reset' to 0 pos and the arm was far from that position.

From what I learned from the manual, millis() is a different story. So yes, my code is going to be interrupted (that's the whole point of waiting :)), but as far as I can tell, the device will not be, so it still can drive the servo to keep moving toward 0 pos.

Did I misinterpret something? If I did so, what's the right way of doing such thing?

Did I misinterpret something?

Yes

Whilst your DelayEx() function is waiting for its exit condition to be met no code other than interrupts will be executed

What if you want to determine whether a timing period has elapsed whilst doing something else ?

Try this simple example (you need one button) connected between pin 2 and GND; tested on an Uno.

You will see that while the delay is in progress, you can toggle the LED using an interrupt.

const byte intPin = 2;

const byte ledPin = 13;
const byte interruptPin = 2;
byte state = LOW;

void setup()
{
  Serial.begin(57600);
  pinMode(ledPin, OUTPUT);
  pinMode(interruptPin, INPUT_PULLUP);
  digitalWrite(ledPin, state);
  attachInterrupt(digitalPinToInterrupt(interruptPin), blink, CHANGE);
}

void loop()
{
  delay(10000);
  Serial.println(millis());
}

void blink()
{
  state = !state;
  digitalWrite(ledPin, state);
}

Note:
there is never a need for an interrupt for button presses; this is just a demo.

From what I read, delay() is like an NMI,

A Non-Maskable Interrupt?

TheMemberFormerlyKnownAsAWOL:
A Non-Maskable Interrupt?

And what Manual ?

@rad1c, this is very confusing. Can you forget what you know about delay() and millis() and start all over ?

delay(): That is a software waiting loop.
millis(): It returns the time.

Because millis() returns the time, it is as if you are watching the clock.
It is possible to make the same software waiting loop as a delay() as you did, but then you can just use delay().
When using millis(), a rollover problem can be avoided by capturing a certain time as a timestamp (often called 'previousMillis') and then later on use the subtraction currentMillis - previousMillis.

Suppose you need to blink a led (see the BWD page) then look at the clock to check if one second has passed.
Suppose you need to keep a led on for 5 seconds after pressing a button, then create the same millis-timer as the blinking led, with a interval of 5 seconds, and after 5 seconds turn the led off and also turn the millis-timer off.

Rule of thumb: Never wait.
When you want to do more than one thing in a sketch, then you should never wait.
Never wait for something in a while-loop. Never wait for someone to press a button. Never wait for data to be received via a serial port. Don't use delay().
Sometimes the hardware requires a few milliseconds delay, that is okay. For everything else: Never wait.

Thank you for all the answers, but it looks like I was misunderstood a bit. Thanks @Koepel for emphasizing this. Probably my rusty English, sorry :slight_smile:

Let me try to explain this in other words.
My goal is not to execute any other command / code segment while I'm waiting. I want to wait as wait it is, do nothing at all. Just waiting. However, I realized if I use delay after writing to a servo:

myServo.write(0);
delay(500); //meant to wait for the servo to reach 0 pos, but see below, it doesn't work

then, if the servo was far from the target position, zero in this case, e.g. it was at angle 40 or more, the delay command interrupts the servo, so it stops moving toward the zero position and stays at whatever angle it was at the moment the code reached the delay(). E.g. it stops moving around angle 25 or so (in case of an initial 40).

I was puzzled and looked up what's going on and found this in the docs about delay():
"(...)No other reading of sensors, mathematical calculations, or pin manipulation can go on during the delay function, so in effect, it brings most other activity to a halt."

So what it says in my read is, if one uses delay(), Arduino (basically) stops working. Like if I "paused the whole thing". So my assumption is, this is the error, as Arduino stopped telling the servo to keep moving to reach zero angle, as delay() "froze" it (by interrupting "pin manipulation", which Servo.h probably does).

My plan is, to implement a "soft delay". A method, which interrupts my code but only my code, while Arduino in the mean time keeps doing its thing, whatever it was (e.g. keep driving the servo).

I read millis() is just a number, so it's kinda harmless reading it. My thinking was, if I keep looping on nothing for 'n' seconds, my code is interrupted (which is fine!), but nothing else, so the servo should reach the target position, whatever it is, i.e. no matter how far it was when it started moving as Arduino keeps commanding it (which it seems it doesn't do when delay() is called).
But something in the back of my head tells me there's a huge misinterpretation from me according to this, hence I fired away the question if this is the right way to re-implement delay in a harmless way.
(I'm not around my device right now so can't try atm)

Post the entire code. delay() should not affect the servo, unless the delay time is too short and the servo is being stopped by code after the delay.

Oh. Weird. The code segment is pretty simple (and it looks like the issue is with CloseBoxSuddenly(); but only when I call it right before I detach from the servos - even though I wait for more than enough time (with delay()) and all seems cool otherwise.
It's a Useless Box, hence the silly move-delay-move-delay things (making a toy for a friend's kid :)).
I do the attach / detach thing to avoid servo 'stall' condition and save battery, as the servos don't have to hold weight.

loop() switch, case 2:

AttachToBoxAndSwitchServo();
OpenBox(kSHORT_SERVO_DELAY); //const: 6
delay(550);
MoveSwitchServoSuddenly(kSWITCH_TOGGLE_ANGLE); //const: 26
delay(550);
MoveSwitchServoSuddenly(kSWITCH_BASE_ANGLE); //const: 180
delay(550);
CloseBoxSuddenly();
delay(1500);
OpenBox(kSHORT_SERVO_DELAY); //const: 6
delay(3000);
CloseBoxSuddenly(); //Doesn't happen, from angle ~40 the servo goes down to ~25 and stops
DetachFromBoxAndSwitchServo();
break;

The methods I'm calling (in call order):

void AttachToBoxAndSwitchServo() {
  BoxServo.attach(kBOX_SERVO_PIN);
  SwitchServo.attach(kSWITCH_SERVO_PIN);
}
void OpenBox(byte speed) {
  for (int i = kCLOSED_BOX_ANGLE; i <= kOPENED_BOX_ANGLE; i++) {
    BoxServo.write(i);
    delay(speed);
  }
}
void MoveSwitchServoSuddenly(byte newAngle) {
  int diff = abs(SwitchServo.read() - (int)newAngle);
  SwitchServo.write((int)newAngle);
  delay(diff * kSERVO_SPEED);
}
void DetachFromBoxAndSwitchServo() {
  BoxServo.detach();
  SwitchServo.detach();
}

For what it’s worth: I just built a useless box and used the VarSpeedServo library here. Besides a comfortable way to manage the speed of moves it allows for a „wait until servo move is complete“. I did not look at the code to see how exactly that is implemented.... it just works for me.
Nonetheless: the discussion on delays is interesting.

rad1c:
I was puzzled and looked up what's going on and found this in the docs about delay():
"(...)No other reading of sensors, mathematical calculations, or pin manipulation can go on during the delay function, so in effect, it brings most other activity to a halt."

So what it says in my read is, if one uses delay(), Arduino (basically) stops working. Like if I "paused the whole thing". So my assumption is, this is the error, as Arduino stopped telling the servo to keep moving to reach zero angle, as delay() "froze" it (by interrupting "pin manipulation", which Servo.h probably does).

No. Delay does much the same thing as your version. The documentation is trying to tell you that you shouldn't use it if you want to perform other normal actions during the delay time. It doesn't stop the processor dead - interrupts will still be serviced, such as the timer that the servo library uses. You have some other issue.

Delay() doesn't stop the servo move. But detach() does, it stops the control signal. After the second CloseBoxSuddenly() it goes straight to detach() with no delay so there's your problem.

Steve

slipstick:
Delay() doesn't stop the servo move. But detach() does, it stops the control signal. After the second CloseBoxSuddenly() it goes straight to detach() with no delay so there's your problem.

Steve

It does indeed, but if you take a look at the CloseBoxSuddenly(); method it waits with a delay() before it returns the control and the detach() happens. I tried to add a delay(1000) right after the CloseBoxSuddenly() call, it works then as intended, but it should wait for more than enough time within the method called so I don't know what's going on.

Oh, FFS... It's a mistake from my side... I use the wrong constant value, so I set the wait time to zero as I calculate with the opened angle, not the closed one!

Silly me, sorry guys! My bad! delay() doesn't stop anything, the servo now finishes the movement under the delay() time!

Thanks, and sorry for the white noise :slight_smile:

This is tangential to the solution- but not to the question.

I try to avoid delay() as a matter of practice, but using millis only complicates the sketch. I am now using a function from Left Coast Base Tools called timeObj.h

Here's how it works:

Useage:
Initiate the object(s) in setup

timeObj timerX(int:time,bool:start)

Where:
time is the optional delay time in ms (default 10ms), and
start is an optional boolean (default true) to begin timing immediately,
or false, which means you have to manually start the timer later: timeObj.start

In loop:
Async, free-run:

if(timerX.ding()){
//handle the ding
    timerX.start();
}

One-shot:

if(timerX.ding()){
//handle the ding
}

timerX.start and timerX.stop may be called from anywhere in loop().

Here is an example sketch. Now instead of doing math on millis(), I just test to see if timerX.ding is true.

/**********************************************************************
   nBlink
   Inspired by the Left Coast Base Tools (https://github.com/leftCoast/LC_baseTools)

   Yes, yet another blink without delay.

   In this example, the LED will blink 10 times, .5 sec on, .5 sec off, for 5 blinks, then
   change the time to 1-second on/off for the last 5, then stop.

***********************************************************************/


#define LED_OFF HIGH
#define LED_ON LOW
bool ledState;                            //Save the state of the LED

#include "timeObj.h"
timeObj aTimer;                        //Initialize a dlay timerXect. We will start it in setup.


void setup() {
  Serial.begin(115200);
  delay(10);
  Serial.println();
  Serial.println (F("noBlockBlink.ino"));

  pinMode(LED_BUILTIN, OUTPUT);          //Initialize the digital pin as an output.
  digitalWrite(LED_BUILTIN, LED_OFF);    //Turn the LED off.
  ledState = false;

  aTimer.setTime(250);                  //Set the delay time.
  aTimer.start();                       //Fire up the timer.
}


void loop() {
  static int i = 10;                          //Blink count

  if (aTimer.ding()) {                        //If the timer has finished,
    if (ledState) {                           //toggle the LED.
      digitalWrite(LED_BUILTIN, LED_OFF);     //Turn the LED off.
      ledState = false;                       //Save the LED state
      i--;                                    //Count the blinks
    } else {
      digitalWrite(LED_BUILTIN, LED_ON);      //Turn the LED on.
      ledState = true;
    }
    if (i == 5) {                             //Change the time after 5 blinks.
      aTimer.stop();
      aTimer.setTime(1000);
    }
    aTimer.start();
  }


  //Stop after x blinks.
  if (i <= 0) {
    aTimer.stop();
  }

}

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