Arduino loop() performance

In an earlier post, we looked at an Arduino code generating a signal and tried various options to minimize oscilloscope trigger jitter. The jitter originated with a periodic delay in the call to loop(). Let’s look at this a bit more.

Here is the code that Arduino uses to call setup() and loop(). Depending on the state of serialEventRun, there may be some overhead.

$ cat Arduino/hardware/arduino/sam/cores/arduino/main.cpp
/*
  Copyright (c) 2011 Arduino.  All right reserved.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  See the GNU Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

#define ARDUINO_MAIN
#include "Arduino.h"

/*
 * Cortex-M3 Systick IT handler
 */
/*
extern void SysTick_Handler( void )
{
  // Increment tick count each ms
  TimeTick_Increment() ;
}
*/

/*
 * brief Main entry point of Arduino application
 */
int main( void )
{
        init();

        delay(1);

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

        setup();

        for (;;)
        {
                loop();
                if (serialEventRun) serialEventRun();
        }

        return 0;
}

Here is the starting code and jitter is present. Interestingly, the frequency is roughly twice what we saw last time… What changed?

void setup() {
  pinMode(31, OUTPUT);
  digitalWrite(31, LOW);   
}

int v=0;

void loop() {
    v = v ? LOW : HIGH;
    digitalWrite(31, v);   
}

Looping within setup(), avoids the checking and calling of serialEventRun, and is faster but jitter remains.

void setup() {
  pinMode(31, OUTPUT);
  digitalWrite(31, LOW);   

  int v=0;
  while(1) {
    v = v ? LOW : HIGH;
    digitalWrite(31, v);   
  }
}

void loop() {} // pacify warning

Disabling interrupts and looping within setup() is jitter free and fast. The jitter before was periodic, a delay occurred with every 12th call to loop(), or roughly every 100us.

void setup() {
  pinMode(31, OUTPUT);
  digitalWrite(31, LOW);   

  noInterrupts();
  
  int v=0;
  while(1) {
    v = v ? LOW : HIGH;
    digitalWrite(31, v);   
  }
}

Interestingly, if the loop() function is left in the code, but never used, the frequency drops 5%. Does the compiler leave in unused code and code size is impacting performance?

void setup() {
  pinMode(31, OUTPUT);
  digitalWrite(31, LOW);   

  noInterrupts();
  
  int v=0;
  while(1) {
    v = v ? LOW : HIGH;
    digitalWrite(31, v);   
  }
}

int v=0;

void loop() {
    v = v ? LOW : HIGH;
    digitalWrite(31, v);   
}

If we try to minimize the code, replacing main() does not improve performance.

int main() {
  init();
  delay(1);
  
  pinMode(31, OUTPUT);
  digitalWrite(31, LOW);  

  noInterrupts();

  int v = 0;
  while(1) {
    v = v ? LOW : HIGH;
    digitalWrite(31, v);   
  }
}

But comment out the initial digitalWrite() call, and performance bumps up a little.

So, one might think if we comment out the initial digitalWrite() on the previously fastest code, we would see a bump. No, just the opposite in actual testing. Does the Arduino compiler (1.5.4) have an optimizer and what does it do? Why did the overall frequency improve since the earlier post?

void setup() {
  pinMode(31, OUTPUT);
  //digitalWrite(31, LOW);   

  noInterrupts();
  
  int v=0;
  while(1) {
    v = v ? LOW : HIGH;
    digitalWrite(31, v);   
  }
}

Pin mapping and bit twiddling provides the highest performance. Recall, the Arduino Due is running at 84MHz.

void setup() {
  pinMode(31, OUTPUT);

  noInterrupts();
  
  int v=0;
  while(1) {
    v = v ? 0 : 1<<7;
    REG_PIOA_ODSR = v;
  }
}

Again, trying to replace main() only degrades performances.

int main() {
  init();
  delay(1);
  
  pinMode(31, OUTPUT);

  noInterrupts();
  
  int v=0;
  while(1) {
    v = v ? 0 : 1<<7;
    REG_PIOA_ODSR = v;
  }
}

Introducing a call to delayMicroseconds() to add a 1us delay, to give a bit of dwell time for the pin to stabilize, shows there is large overhead to this function call.

void setup() {
  pinMode(31, OUTPUT);

  noInterrupts();
  
  int v=0;
  while(1) {
    v = v ? 0 : 1<<7;
    REG_PIOA_ODSR = v;
    delayMicroseconds(1);
  }
}

Delay = 2us.

Delay = 3us, which the documentation for delayMicroseconds() indicates is the guaranteed minimum, but the progression looks decent from 1us for the Arduino Due.

Delay = 6us.

Delay = 12us.

To introduce a dwell, while not degrading performance as much as the delayMicroSeconds(1) did, and since the compiler does not seem to optimize much, performing the pin assignment three consecutive times introduces a short dwell.