Getting a loop to execute in a defined amount of time

I need to get the loop section of my arduino code to execute as close to 1 second as possible. Right now I have a delay(1000) at the bottom of the code but the calculations in the loop are usually taking between (50-200ms) to execute. I'd like to float the amount of time that I delay my loop so that I can get close to 1 second. Does anyone know how to accomplish this?

Use millis() to do non-blocking timing. You can do the 50-200ms task while you are timing the 1 second.

Non-blocking timing tutorials:
Several things at a time.
Beginner's guide to millis().
Blink without delay().

1 Like

If you want to execute something evevry second, use a millis() based approach.

uint32_t interval = 1000;

void setup()
{
  // your setup code here
}

void loop()
{
  // the next time that you want to do your action
  static uint32_t nextTime;

  // check if it's time
  if (millis() - nextTime >= interval)
  {
    // update next time
    nextTime += interval;

    // do your action here
  }
}

The static keyword makes that the variable is remembered between calls to loop(); you can also make it a global variable if so desired.

1 Like

Okay so it seems I would use the millis() function. Here is my interpretation of the solution:

void loop () {
time1 = millis();
//calculations approx 50-200ms
time2 = millis();
timeelapsed = time2 - time1;
float = 1000 - timeelapsed;
delay(float);
}

Your proposed solution will most likely do what you think you need to do. But is that what you should be doing?

This looks like the proverbial x-y problem to me, where you are trying to kluge a way to do something that might not be the right thing to do in the first place.

What's the actual problem at hand?

1 Like

Also look at the classic “blink without delay” sketch to see an alternative solution. You can also use a hardware timer to run a function at a specific frequency, in your case 1Hz.

1 Like

Your proposed solution shows that you have not yet understood how non-blocking timing works.

first of all a codeline like

float = 1000 - timeelapsed;

has an error: you haven't named a variable

you just named a variable-type.

it should be

float waitTime  = 1000 - timeelapsed;

but

float is not suited in this case
ALL variables that are used with millis() must be of type unsigned long
so a line of code that would - at least compile would be

unsigned long waitTime = 1000 - timeelapsed;

recently a user wrote "delay() is the wrong name for this function
it should be named freeze()

Your freeze(waitTime ) oh sorry your delay(waitTime ) again does freeze your microcontroller.

You are welcome on this forum! You are working on an informatic project and what is most needed in an informatic project is information imagine: do the other users here in the forum have a clear picture of what you are trying to do?
not yet

To speed up finishing your project you should invest some time into writing additional information I'm 100% sure that this WILL speed up finishing your project.

So please go through this checklist and if you find an item you haven't posted yet post it

  • did you write which exact type of microcontroller you are using?
  • description of your programming skills and knowledge
  • description of your knowledge about electronics
  • description of the functionality you want to have written in normal works avoiding programming terms
  • do you have an oscilloscope? Yes / No
  • do you have a digital multimeter (DMM) Yes / No)
  • your age
  • did you post your complete sketch?
  • if relevant did you post a link to a datasheet of each component you are using?
  • if relevant did you post a handdrawn schematic or your wiring?
  • if you got a compiler-error. Did you post the complete compiler-output into a code-section?

If you don't post all this information because you want a "quick answer" to your detail problem It is very likely to turn out that all that happens is having mutliple waiting-times with mutliple asking back for details until the other users do have a clear picture of what you want to do.

best regards Stefan

why not simply

unsigned long msecLst;
int           cnt;

void
loop (void)
{
    unsigned long msec = millis ();

    if ( (msec - msecLst) < 1000)
        return;

    msecLst = msec;

    Serial.println (++cnt);
}

// -----------------------------------------------------------------------------
void
setup ()
{
    Serial.begin (9600);

}
1 Like

@sionaggutraidh the actual problem is read a sensor evaluate a custom PID algorithm and then output PWM duty to an SSR. For the PID, it is important for stability that we read our PV at a consistent interval and OP results at the same frequency.

It is perfectly fine in this case that our MCU is frozen during the delay time we are just waiting a defined time interval to measure how our last calculated OP affected the PV/error.

I do see I made a mistake in my previous code snippet as pointed out. I need to use the unsigned long data type going to try something like:

unsigned long time1;
unsigned long time2;
unsigned long telapsed;

void setup() {
}

void loop() {
time1 = millis();
//run main code
time2 = millis();
telapsed = time2 - time1;
delay(1000-telapsed);
}

Take a look at Brett Beauregard's excellent article on PID (The beginner's PID). He evolves a simple PID code into the one that comes with Arduino. It includes code to check that the desired interval has elapsed since the last call to compute.

A bit shorter:

unsigned long time1;
const unsigned long interval = 1000ul;

void setup() {
}

void loop() {
  while (!((millis() - time1) >= interval)){};
  time1 += interval;
  //run main code
}
1 Like

more properly formatted (braces and ";" are redundant)

  while (!((millis() - time1) >= interval)
    ;

but why NOT, "!"? isn't below cleaner (i.e. easier to understand)?

  while ((millis() - time1) < interval)
    ;

and loop() is executed repeatedly, so why not

  if ((millis() - time1) < interval)
    return;

which allows other operations before this test

1 Like

I wasn't completely sure about using < with the millis comparison, since it is almost always seen as > or >=, so the ! was to avoid doing the research.

The only thing I do not like about using return is the very slight change that someone is actually using the serialEvent() function, which could cause a delay before the start of the next loop.

1 Like

@feynman137 I was charmed by your approach in #10 and am compelled to call attention to it, here I post it with a simple job of printing millis() and no surprise it works perfectly and would be indistinguishable from the outside compared to other more like idiomatic approaches.

It shows great creativity. It is refreshing to see something that someone actually thought out and coded. I can assure you that this same method can bear a bit of extrapolation and be exploited in some more complicated timing scenarios.

/* make loop use a defined amount of time */

unsigned long time1;
unsigned long time2;
unsigned long telapsed;

void setup() {
  Serial.begin(115200);
  Serial.println("Timed Loop!\n");
}

void loop() {
  time1 = millis();
//run main code
  Serial.println(millis());
  time2 = millis();

  telapsed = time2 - time1;
  delay(1000 - telapsed);
}

All the methods on this thread will have one kind or another problem with any processing that overflows the time allowed. They will all do something when (if) that happens, so either don't let that happen or know that what will is OK. A means for detecting when a time slice has been run over is nice. I use an LED that latches ON. Or ON for a good stab that I can see.

a7

1 Like

your code in post #10 is still blocking. This version will auto-correct to make the loop execute once per second even if you add more commands.

But because you command your microcontroller to take 100% of his calculation-ressources to "wait" until the delay-time is over
the microcontroller can do absolutely nothing else.

As soon as you want to check

  • for a button-press and react immitiately on the button-press
  • for incoming data and react as soon as possible as the data rushes in
  • execute a command once every 0,1 seconds

this will not work with delay
because with delay you command your microcontroller to take 100% of his calculation-ressources to "wait" until the delay-time is over.

But you seem to be set on railroads to let the train run on the delay-track.
I guess later in the future if you came across such a situation of wanting a part of your code executing once every second and another part execute once every 0,01 seconds the question how to do that will arise.
And then it is time to UN-learn delay and to learn how non-blocking timing based on millis() works.

best regards Stefan

Such a while is blocking as well.

In fact, it's even a bit worse, because during a delay the optional yield() function is called, by which you could run code during the freezing of a delay.

If all is about a PID control: The usual Arduino PID library does its required exact timing by itself, if you don't interfere with delay or other freezing stuff.

I was not aware this was on a processor requiring yield(), otherwise I would have put that in the while.

The OP seemed insistent on using delay, so blocking code was a given. I was trying to make the timing more precise, because there is a very small bit of processing time between loops, and millis occasionally increments by 2 instead of 1.

No matter anyway, I'm not going to delete the post and cause further confusion will all the complaints about a post that no longer exists.

I'd set up a hardware timer and use an event group to get a precision 1 second interrupt.


hw_timer_t * timer = NULL;
EventGroupHandle_t eg;
#define evtDoDisplay             ( 1 << 1 )
#define evtCollectHistory        ( 1 << 2 )
#define evtParseMQTT             ( 1 << 3 )
#define evtGroupBits ( evtCollectHistory | evtDoDisplay )

In setup()

 timer = timerBegin( 3, 80, true );
  timerAttachInterrupt( timer, &onTimer, true );
  timerAlarmWrite(timer, 1000, true); // trigger once a ms.
  timerAlarmEnable(timer);

I create a hardware timer, and set the timer to trigger once a second.

I'd then set up a hardware timer callback.

////
volatile int iDoTheThing = 0; 
void IRAM_ATTR onTimer()
{
 iDoTheThing++;
  if ( iDoTheThing == 1000 )
  {
    xEventGroupSetBitsFromISR( eg, evtGroupBits, &xHigherPriorityTaskWoken );
    iDoTheThing = 0;
  }
}

Then define the tasks that will be running once a second.

 xTaskCreatePinnedToCore( fCollectHistory, "fCollectHistory", 10000, NULL, 4, NULL, 1 );
  xTaskCreatePinnedToCore( fUpdateDisplay,  "fUpdateDisplay",  50000, NULL, 5, NULL, 1 );

Now when the hardware timer kicks off once a second, the callback will be ran that will trigger the 2 tasks to run once a second.

Because the timer callback has BaseType_t xHigherPriorityTaskWoken;, the OS, freeRTOS (on an ESP32) will cause the tasks triggered by the callback to preempt any other running task. The update display task will run first, having a priority of 5 and then the collect history task will run.

1 Like

'hw_timer_t' does not name a type

I understand this is about Arduino, not freeRTOS, ESP32 or similar specific stuff.

Later it condensed to PID algorithm, which can be simply done by following the

" Don't Block "

rule.

While all that may be true, the code has a massive massive bug. If time2-time1 is greater than 1000 it will jam the processor for weeks. delay takes an unsigned long input, so any negative value will wrap round to 2^32 or so, which is about 50 days of miiliseconds...

The correct way to handle this has already been given in #3 which is the standard way to do this.