Maximum Execution Time of Arduino Mega2560

What is the maximum execution delay on an ATMEGA2560?

Before I explain, I will note tha I am not providing code or schematics for the reason that my question is independent of those.

Project in development (code not finished) is controlling probably between 20 and 40 LEDs, ultimately powered on 12v, driven by mosfets triggered by logic level from the board. Some of these are used for flash patterns, pulsed on a set delay, as low as 50ms.

To do this, first inputs must be polled, things calculated, stuff done, etc., etc. All this takes time, every microsecond adds up, and so on.

The question is this: WORST CASE SCENARIO, what is the longest execution delay I can expect from any board running an ATMEGA2560, Arduino or otherwise? It doesn't need to be precise. I'm trying to figure out what my margin of error needs to be leveraged against trying to keep the 50ms timing as a magnitude. Is it 4 micros, 40, 400, or 4ms? I will be needing to add up the total delay throughout the entire loop to account for this, as there are a lot of instructions to consider when dealing with so many LEDs, especially if you start getting into clocking shift registers or even decoders.

(I'm also open to suggestions regarding external circuits for multiple-active-output decoding, if anyone has any, in order to reduce overhead).

EDIT: I'm going to rephrase my question. While I do appreciate the criticism, I need to clarify:

What is the longest length of time in microseconds the board will take to complete an execution? For example, I've read that an ISR can take between 60 and 80 clock cycles to complete, without counting code execution time outside the ISR.

Edit2: I want to leave the comment here, also, that I am seeking information to draw my own conclusions. I'm not walking into this completely blind, I have been studying these devices to some degree, that's why I got one. However, in both coding and these MCU platforms, I still do have much to learn. As stated, I have had issues with millis() in the past. That could have just been a code issue. If I find myself needing to use delay() instead, to keep everything happy, that's what I'll do. But I want to compare timing, but I need to know how much time to expect for execution. I've read some saying 4 micros, and some saying nearly a milli... Which is it, and why is it different?

42?

Seriously, I have no idea what it is you're asking.

Simple answer:

while (1);

First.... learn how to not use delay() in your code.

We have Arduino functions for timing.

millis() -- unsigned long milliseconds since start/rollover +/- 1.

micros() -- unsigned long microseconds since start/rollover with "grain" of 1 tick every 4 microsecs --- the count is always divisible by 4.

We use those through arrangements of the formula;

Elapsed_time = End_time - Start_time.

Using unsigned subtraction that ALWAYS WORKS even across rollover (No Special Rollover Code Needed) up the limit of Elapsed_time fits in an unsigned long variable.

When timing with millis() the longest interval is;
49.7102696181 days

When timing with micros() the longest interval is;
1.19304647083 hours

And with a bit more code, those limits can be extended to however long the board has power and no components fail.

IMO, the practical limit is set by boredom. There has been example code to take it to billions of years using 64-bit long long variables.

The more you learn about Arduino, the better the questions you can ask.

It is best to not waste time writing opus masterwork projects when you haven't gotten past the basic lessons in the Tutorial Examples included in the IDE, and learning to C character array strings while avoiding C++ String objects.

Your life, your choice.

In other words, using milis() always keeps it in time?

I have a function I based on an example I found that was using millis() instead of delay, and I found that using delay() yielded more consistent results. The timing seemed to drift when using millis(), or skip pulses entirely. However, that was for input polling, not for timing output pulses.

I have read that both timer1 and millis() can be used for an accurate soft clock generator, if a clock pulse is needed to be output by the board to something external, however, I had VERY mixed results.

This is why I asked the question I did. I am new and naive, HOWEVER, I also have done some experimentation, and went with what worked the best at the time.

At some point, I have to transition back to using millis(), to allow the program to multitask, but also stay in time.

I do appreciate the criticism, however, I just came to get one piece of information, not be told how to do my project. I am aware of some of the things you and others have mentioned, but what I wanted to know is how long is the longest time the board takes to complete an execution.

I also need to edit this to say that during my tests with millis() vs delay(), sometimes using millis() skipped capturing the input. I couldn't get it to respond to input commands without resetting the board at times. Seemed it was too busy counting to listen to my commands...

Wow. I think I shall move along. Good-bye.

@GoForSmoke I want to let you know that I appreciate you, and I didn't mean to be rude. I did ask the question poorly, and yes, I'm still learning the Arduino. I realized that the term delay was slightly ambiguous, and for that I apologize. I was looking for execution time. I don't want to inject my code with a bunch of other code to measure the performance of the device to get my answer. So I thought I would ask, thinking others would know of it's computational limits.

You don't know enough to understand what you are getting wrong even though you think you do.

Arduino millis() is +/- 1 due to how it counts and that is why the lower 8 bits skips 6 values out of 256. It is not simple so just call it +/- 1 millisecond and if you need more precision, use micros().
There are some long threads in the forum archives on the subject if you need more explanation. I was around for at least two of those but I've been here since 2011 -- but don't take my word.

When you are done wasting time you can find out why the problems you are having are not problems and what to do instead.

I have posted examples of how to replace delay()'s in code by using cut-and-dry techniques. I first did that for a member in Budapest to get his greenhouse automation working... 9 years ago. I know what I'm doing. I didn't understand his technical code for GSM or I2C devices but then I didn't NEED to or I would have learned those parts.
I simply applied technique to the delays that involved turning his code into state machine functions that only ran sections between wait time when their timers ran out.
It helped that +/- 1 millisecond didn't matter at all (so not a problem) though if it had I would have used micros() timing instead, after all this was for running a greenhouse, not a high-performance engine or cyclotron.

But you want to blink leds. Is over 49 days interval +/- 1 millisecond precise and long enough for your purposes?

I went through and beyond the stage you are at in 1980. If I could, I would save you months of time. Your life, your choice.

Note for ALL:

Every 256th milli() is on time.

The +/- 1 is only in the low 8 bits where 6 values are skipped.

The millis() ticks once every 1024 microsecs, 250 (as in 256 - 6 skipped values) 1024 microsec intervals == 256 ms.

What making every millis tick 1000 microsecs long would do is slow you board down. Me, I can live with +/- 1 ms or choosing to count micros() instead. How about you?

Depends on what the "execution" is doing.
A post from 2008 says the basic operation of toggling a pin takes about 10 microseconds, however bypassing the Arduino framework can make it 20x faster.

If you are doing things that need precise timing then you need to disable interrupts which has the unfortunate side effect of stopping millis() from working.

The Arduino digitalWrite() function has fool-proofing is what takes longer. The same goes for digitalRead().

But if we use care, we can write and read AVR port registers with solid good results in one cycle and only have to use one to a few logic operations to do so.
The chip registers react in one cycle. On an Uno that is 1/16th of a microsecond.
We can speed up the pin code many times over and in so doing make it unfriendly to most beginners, so expect to see Arduino functions used in most examples posted here.

If you are doing things that need precise timing then you need to disable interrupts which has the unfortunate side effect of stopping millis() from working.

It really depends on how precise you need to be.
It is better to engineer ultra-precision out of a project than to engineer it in! That's something I was taught about mechanical design back in the 70's.

Your question as I understand it is equivalent to 'what is the longest amount of time it takes to walk somewhere?'. Obviously the answer depends on how far away 'somewhere' is and how easy the terrain is to walk on. I'm saying there's no meaningful answer to your question. I suggest that to get a meaningful answer you need to post your code and ask for help with specific problems you are having with it. If you do that you will learn from the help you get with the actual problems you are encountering.

You can use an interrupt for some small action to get the closest to exact.

You can use unblocked code to achieve average void loop() running over 50 KHz while making sure you don't have any long processes (printing decimals can hog cycles) or internal loops and get on average very fast responses.

I dunno how exact you need. I have a very tight tool that needs a non-blocking sketch to run in, it displays loop count every second. I made it for seeing the impact of code changes.
Perhaps I should write a version that shows the time the longest loop took in each second.

This will not work in code that uses delay() or otherwise blocks execution, which several Arduino functions do.
The idea is to use it while developing and take it out of the final version knowing that it will run faster without the counter.
This is the leanest and meanest version. Run it alone and see! Add it to a sketch that does light work, the count will drop to below 100 KHz.

// NoBlockLoopCounterLT Mar 16, 2024 by GoForSmoke @ Arduino.cc Forum
// Some speed changes thanks to J-M-L Jackson of the Arduino forum!
// 3/16/24 -- another change from 777 of the Arduino forum uses timer0_millis.
// Free for use. Compiled on Arduino IDE 1.8.19
// This sketch counts times that loop has run each second and prints it.
// It uses the void LoopCounter() function that does not block other code.

// The LT is for Lighter Timing. Instead pf 32-bit subtraction we take
// advantage of what makes the low 8 bits of 32-bit millis() +/-1.
// The low 8 skips 6 values counting to 256, it takes 250ms to flip bit 8.
// Using 16 bits of millis() allows solid timing of 1/4 sec to 32 sec.
// I keep 1 byte to hold the ON/OFF of the last read of bit 10 = 1 sec.
// On an 8-bit CPU it should take fewer cycles.
// Please note that optimizations from J-M-L Jackson and 777 have been added.


// run this sketch just to see what such a lightweight sketch can run at
// ~370033 loops per second!

extern volatile unsigned long timer0_millis; // might be faster than millis()

void setup()
{
  Serial.begin( 115200 );
  Serial.println( F( "\n\n\n  Loop Counter, free by GoForSmoke\n" ));
  Serial.println( F( "This sketch counts times that loop has run each second and prints it." ));
}


void LoopCounter() // tells the average response speed of void loop()
{ // inside a function, static variables keep their value from run to run
  static unsigned long count; // only this function sees this
  static bool lastBit10Set; // only this function sees this
  word millis16 = timer0_millis;

  count++; // adds 1 to count after any use in an expression, here it just adds 1.

  bool currentBit10Set = millis16 & 0x0400; // leverage integral to bool implicit promotion
  if (currentBit10Set != lastBit10Set) // 1 second
  {
//    Serial.print( millis16 ); // 16-bit binary into decimal text, many micros
//    Serial.write('\t');
    Serial.println( count ); // 32-bit binary into decimal text, load of cycles!
    count = 0; // don't forget to reset the counter
    lastBit10Set = currentBit10Set; //  changes 0<==>1 
  }
}

void loop()  // runs over and over, see how often with LoopCounter()
{
  LoopCounter(); // the function runs as a task, the optimizer will inline the code.
}

Assembly instructions take between 1 and 5 clock cycles for a 2560 processor. Clocked at 16 MHz, one cycle is 62.5 ns. You can find that information in the datasheet of the ATmega2560; CALL and RET / RETI are taking the most time.

Now you're not writing in assembly but in C/C++ and a lot depends the assembly code that the compiler generates. Optimisations by the compiler might result in CALL instructions being replaced by jumps.

Further there is the hardware abstraction layer used by Arduino so code is compatible with different boards. This adds significant overhead.

Interrupts add significant overhead because variables in registers will be saved in the beginning of the ISR so the ISR does not mangle them and restored just before the RETI instruction. I don't think that one has much control over that.

Running an interrupt timer has an overhead. When the interval is 50 microsecs, a few microsecs overhead has to be part of that.

The average time for a 50 KHz loop is 20 microsecs Regardless Of What Instructions The CPU Runs!
I write examples that generally run faster than 50 KHz average loop()s and have posted many that exceeded 67 KHz again on average.
To me, either the interrupt provides timely response or the loop() time must and, important point, that either way may be good enough no matter how the hex gets made.

I'm only trying to find the size of the ballpark here, how to keep inside of that is for later.

I did not reply to your post(s) so was not referring to them. So I do not quite get your point.

See if you get the point now, post 15 edited for clarity.

@GoForSmoke I appreciate you, and this was exactly the info I was looking for. I don't need a lot of precision. Down to 1ms is more than plenty. A little bit of drift if the CPU overloads or something is acceptable, but I'm trying to be mindful of time, to make sure the CPU has time to do everything without delaying pulse cycles or responses.

I designed my flash patterns around the 50ms pulse, counting them all up, and adding some dead time to account for visual irregularities in the pattern due to spacing. For example, making sure everything is symmetrical.

This isn't out of need for precision, but rather me being anal retentive.

@sterretje I appreciate your feedback, but I have two rebuttals.

  1. Maximum possible speed isn't necessary until the CPU caps out.

  2. I'm already trying to avoid the use of ISRs entirely, because they aren't useful to me in this project to respond to but two or three things, as that's all the hardware interrupts that exist on the board, and software interrupts aren't useful to me for responding to external changes. That being said, I know what the spec says. Anyone who knows anything about any sort of engineering knows that specification doesn't always match what happens in-system under load. Things drift and wander, and sometimes, computers don't always do exactly what you tell them exactly when you tell them.

I've looked at several other MCUs for production boards (for when that time comes), and many say they are so fast that they can execute a complex instruction in a maximum of one clock cycle. We all know that won't always be the case.

Specification on my PC for gaming shows x game should run at 60+FPS, yet sometimes, I still get 45. Specs mean very little other than making sure you have the right hardware for your application. Overengineering is the only way to make sure you always meet or exceed your minimums.

One final note, here. The lights will probably be having their data output at a rate of about 20Hz, the polling will need to be conducted at at least that when all is said and done, and to top that off, in between, there is a parser that needs to run to capture the configuration of each pulse for the lights, before sending the output. All the while, there should be not more than 50ms elapsed since the previous pulse, and no less, so to speak, subtracting any tolerance.

If it comes out to 51ms, then I can set 49 in the config for it, a.k.a. the duration variable. Same if it comes out to 49. As long as it's consistent, and I can account for the difference in workload per loop, I can compensate as necessary. I'm trying to minimize deviation between loops. All the GPIO is soft latched, so the board keeps it on until you tell it to turn off, so when there are no input changes between loops, then it doesn't have anything to do but wait for the next loop. But it should be able to change EVERYTHING in 50ms, especially if the longest time for execution is 10 micros. But there's a big difference between 1 and 10.