What happens outside the loop() function within Arduino?

Hi all,

I’ve been doing some exercises on my Leonardo based board (atmel32U4) and tried seeing different ways i can toggle a pin. One test uses this code:

#define TEST_PIN 2

void setup() {
  pinMode(TEST_PIN, OUTPUT);
}

void loop() {
  digitalWrite(TEST_PIN, HIGH);
  digitalWrite(TEST_PIN, LOW); 
}

Everything works as expected, however i see that at about every 2ms the MCU is off doing something for about 20us. I’ve attached two scope snapshots.

My question is, why is this happening and what is it doing? Is the MCU going out for a smoke break or what? :wink:

Cheers.





SDS00002.jpg

SDS00003.jpg

Remember that whatever it's doing outside of loop() there's also going to be periodic interrupts like, say, Timer0 that services millis() etc.

Ok i didn't know that those interrupts are still running if i haven't attached any to an ISR?

So am i right thinking that millis() and any other function requiring Timer0 are using values that are updated by an interrupt regardless if you use them or not within you code?

For my exercise could i disable all interrupts by using cli() in the setup()?

Thanks for such as fast reply!

If you take a look at the main function that's calling loop: https://github.com/arduino/ArduinoCore-avr/blob/1.6.23/cores/arduino/main.cpp#L33-L51

int main(void)
{
 init();

 initVariant();

#if defined(USBCON)
 USBDevice.attach();
#endif

 setup();
    
 for (;;) {
 loop();
 if (serialEventRun) serialEventRun();
 }
        
 return 0;
}

You'll notice there's something that happens on every loop:

if (serialEventRun) serialEventRun();

This is for the serialEvent() feature: https://www.arduino.cc/reference/en/language/functions/communication/serial/serialevent/ which is almost never used. Yet this call is sitting there slowing down every loop and wasting memory. It's annoying because essentially all it's doing is this:

if(Serial.available()) {
  serialEvent();
}

If I need that, I'd much rather have it right there in my own code where I can see it, rather than being hidden away.

You can fix this by adding the following to your sketch:

void serialEventRun() {}

which will override the weak definition of serialEventRun in the core.

pert: You can fix this by adding the following to your sketch:

void serialEventRun() {}

which will override the weak definition of serialEventRun in the core.

Is this something we should all look at adding to all of our sketches if we are not using serial?

You can even add it to a sketch using serial. The only time it will cause problems is if you're using serialEvent().

I would not add it to sketches that are intended to be understood by beginners unless it was really necessary to save that overhead because that line is going to be confusing to them. Even with a nice explanatory comment, it's just one more thing to distract them from the code you want them to be studying.

Interesting. How much memory/time/CPU cycles are we talking about here though?

BareMinimum compiled for Uno with Arduino AVR Boards 1.6.23:

Sketch uses 444 bytes (1%) of program storage space. Maximum is 32256 bytes.
Global variables use 9 bytes (0%) of dynamic memory, leaving 2039 bytes for local variables. Maximum is 2048 bytes.

With the serialEventRun override:

Sketch uses 434 bytes (1%) of program storage space. Maximum is 32256 bytes.
Global variables use 9 bytes (0%) of dynamic memory, leaving 2039 bytes for local variables. Maximum is 2048 bytes.

Speed test sketch:

unsigned long startTimestamp;
unsigned int loopCounter = 0;
const unsigned int numberOfLoops = 10000;

void setup() {
  Serial.begin(9600);
  while (!Serial) {}
  startTimestamp = micros();
}

void loop() {
  asm("");  // force the compiler not to optimize out the worthless loops
  if (loopCounter++ == numberOfLoops) {
    const unsigned long finishTimestamp = micros();
    Serial.println((float)(finishTimestamp - startTimestamp) / numberOfLoops);
    while (true);
  }
}

//void serialEventRun() {}

gives an average loop time of 1.95 us. With the serialEventRun override, average loop time is 0.44 us

afa37e42de2ff796aa361745e5a6e6e3aa00b49c.jpg
the MCU is off doing something for about 20us

Well, it looks more like the it’s off doing something for about 14us, since the LOW time of your waveform is normally about 7us…
The existing timer interrupt and SerialEvent code should be relatively small numbers of microseconds (and the SerialEvent code is ALWAYS there, rather than periodic.) Since you’re using a leonardo, I suspect that some sort of USB-related interrupt is doing something.

I didn't mean to imply serialEvent was related to the intermittent delay. I guess I was a bit off topic. I was speaking to the title of the thread "What happens outside the loop() function within Arduino?"

It would have been more appropriate for me to give the numbers in my previous reply for Leonardo. The memory difference is the same 10 bytes. The speed difference is 0.95 us without, 0.44 us with. I don't understand the reason for that. I understand why the Mega is 2.96 and 0.44 (because it has 4 USART), but Uno and Leonardo both have 1, so why is the serialEventRun() call so much slower on Uno?

pert: which will override the weak definition of serialEventRun in the core.

And trigger the optimizer to remove everything related. If the weak core function is not overridden the always-false-if is left intact. Weird. Seems like a rather low hanging fruit.

A reasonable alternative...

void loop() 
{
  while ( true ) 
  {
    // Your code goes here.
  }
}

westfw: Since you're using a leonardo, I suspect that some sort of USB-related interrupt is doing something.

Maybe related to this... https://www.google.com/search?q=usb+keep+alive

A reasonable alternative...

void loop() 
{
  while ( true ) 
  {
    // Your code goes here.
  }
}

That actually saves 2 more bytes of program memory and is a bit easier to understand. It's funny because I was actually doing that before I found the override trick. I guess I never bothered to compare the overhead. I don't like the extra level of indentation though. I tend to put quite a bit of code in loop because I don't like "frivolous" (only called once) functions and don't use interrupts much.

I don't like the extra level of indentation though

void loop() { while ( true ) 
{
  // Your code goes here.
}}

:smiling_imp:

Unfortunately, Auto Format won't allow it to stay that way.

This will do it, but it's kind of weird:

#define START_LOOP while ( true ) {
#define END_LOOP }
void loop()
{
  START_LOOP;
  // Your code goes here.
  END_LOOP;
}

Really like that use of #define there! ;D

Also where in the arduino core code are the Timer0 and USB setups are implemented?

Cheers.

You can also simply leave your .ino file empty, and provide a .cpp file containing your own main(). which will completely eliminate the Arduino main(). You can then do whatever you like. There are a few things you need to do if you DO want to use Arduino features, but it's all quite simple.

Regards, Ray L.