Stop The loop() Insanity!

3 minute read

The simplest Arduino program looks like this:

<pre><code>
void setup() {
}

void loop() {
}

This does nothing beyond build and run.

The setup() function is used for one time, well, setup. This is where you initialize a sensor or connect to Wifi or set the speed of your serial port.

The loop() function is where you do the work. This is intended for repetitive tasks, like reading from a sensor or uploading some information or blinking lights.

The Arduino SDK (including when it’s used on a non-Arduino chips like the ESP8266 and ESP32) is designed expecting loop() to do a little work and then return. The loop() function itself is called from an infinite loop.

loop() Loopiness

Sometimes Arduino coders write things like:

void loop() {
  while(1) {
	do_something()
  }
}

This will completely fail on some processors. Notably the ESP8266 and the ESP32 both have “watchdog timers” which are used to help with stability. Watchdog timers are intended to catch when the software has gone into an infinite loop or otherwise failed. If they’re not reset periodically, they’ll restart the processor and your program will run over again from the beginning.

The watchdog timer is automatically reset when loop() returns. When loop() returns the underlying software also gets to perform certain housekeeping tasks, like handle serial I/O, perform any interrupt service it couldn’t do in the interrupt handler, and handle network protocol interactions if it has a networking stack and interface. You can also give up control from inside loop() temporarily by calling yield() or delay(), and some other calls may also give the underlying software a chance to run.

So it’s vital that you don’t loop indefinitely inside loop(). Instead, structure your code to be aware of the fact that loop will be called over and over again for you.

So instead of:

void loop() {
  while(1) {
    int value = read_sensor();
    Serial.println(value);
  }
}

write:

void loop() {
  int value = read_sensor();
  Serial.println(value);
}

Delay Delaying

People also often write:

void loop() {
  int value = read_sensor();
  Serial.println(value);
  delay(10000);
}

This is fine as long as you’re not doing much inside loop(). If you have a very simple application that only does one thing inside loop() then calling delay() this way is harmless.

Let’s suppose we’re doing something more. Imagine that we’re running on a wifi-enabled platform and we have a small web server. We’ll avoid details of how the web server works - there are plenty of tutorials out there and the details would be a distraction.

Now our loop() looks something like this:

void loop() {
  int value = read_sensor();
  Serial.println(value);

  if(web_server.available())
    web_server.serve_page();

  delay(10000);
}

So we take a sensor reading, print it to the serial port, check if there’s a web request available and process it if there is, and then delay for ten seconds because we don’t want to output sensor readings too often.

See the problem?

This delays handling web requests for ten seconds as well.

That means you may wait ten seconds before the page can load. And if the page tries to load any other resources from your server (Javascript, CSS, data) they may each be delayed by 10 seconds as well.

Instead we can write loop() like this:

void loop() {
  static unsigned long last_time = 0;

  if(web_server.available())
	web_server.serve_page();

  if(millis() - last_time &gt; 1000) {
    int value = read_sensor();
    Serial.println(value);
    last_time = millis();
  }
}

Now we’re checking on web requests every run through loop()but only handling sensor readings every second.

We can generalize this to let us handle several things with different timing needs all in the same loop just by keeping track of the time for each of them separately.

Using this structure instead of calling delay() inside loop() will let the handlers inside loop() remain responsive while still reading the sensor only as often as it’s supposed to be read.

Updated: