Arduino / Programmazione · 24 November 2013 2

Let’s schedule our jobs with millis()

Millis() is a function provided by the Arduino core that returns the number of milliseconds since the board has been powered on. Millis() is based on a counter that is automatically incremented by an interrupt attached to the timer 0, for the value it returns constantly increases not being influenced by the user’s code.

We can use millis() to schedule the execution particular actions, like it was a timer that give the starting signal at our code without blocking the execution of the same code awaiting the right moment to do jobs.

Let’s start with a simple test, a LED blink. The Arduino IDE provides a very famous example named “Blink”. The code below is almost identical to the original version:

const byte led = 13;
void setup() {
  pinMode(led, OUTPUT); // initialize the digital pin as an output.
}
void loop() {
  digitalWrite(led, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(1000);               // wait for a second
  digitalWrite(led, LOW);    // turn the LED off by making the voltage LOW
  delay(1000);               // wait for a second
}

This code just set to HIGH the pin at which the LED is attached, wait for 1000 ms (1 second), set to LOW the pin, wait for another 1000 ms and then start again the loop.

This sketch has a big limit: while the code is executing the delay(1000), the microcontroller can do nothing else. If you modify the sketch and insert a Serial.print just after the second delay() you’ll se that the text will be printed on the serial monitor every 2 seconds:

const byte led = 13;
void setup() {
  Serial.begin(19200);
  pinMode(led, OUTPUT); // initialize the digital pin as an output.
}
void loop() {
  digitalWrite(led, HIGH); // turn the LED on (HIGH is the voltage level)
  delay(1000); // wait for a second
  digitalWrite(led, LOW); // turn the LED off by making the voltage LOW
  delay(1000); // wait for a second
  Serial.println("Test...");
}

A possible workaround comes from “BlinkWithoutDelay” provided by the IDE. The following code is a revisited version of that example:

const int ledPin =  13;      // the number of the LED pin
int ledState = 0;             // ledState used to set the LED
unsigned long previousMillis = 0;        // will store last time LED was updated
unsigned long interval = 1000;           // interval at which to blink (milliseconds)
void setup() {
  pinMode(ledPin, OUTPUT); // set the digital pin as output:
}

void loop() {
  unsigned long currentMillis = millis();
  if(currentMillis - previousMillis > interval) {
    previousMillis = currentMillis; // save the last time you blinked the LED
    // if the LED is off turn it on and vice-versa:
    ledState ^= 1; //alternate the status of the pin with a XOR
    digitalWrite(ledPin, ledState);
  }
}

This example just does its job without blocking the code: every time the loop() is executed, the sketch checks if the difference between the actual value of millis() and the value of the past time the LED was blinked is greater than the choosen interval, in case it changes the status of the LED. If you put a Serial.print at the end of the loop() function you’ll see that this time the serial monitor will continuously show your message.

But, what if you have more than 1 job to run? We can use two couples of variables to store the blinking times. This is a possible solution:

const byte ledPin =  13;      // the number of the LED pin
byte ledState = 0;             // ledState used to set the LED
unsigned long previousMillis1 = 0;        // will store last time LED was updated
unsigned long interval1 = 100;           // interval at which to blink (milliseconds)
unsigned long previousMillis2 = 0;
unsigned long interval2 = 1000;
unsigned int counter = 0;

void setup() {
  Serial.begin(19200);
  delay(2000);
  pinMode(ledPin, OUTPUT); // set the digital pin as output:
}

void loop() {
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis1 > interval1) {
    previousMillis1 = currentMillis; // save the last time I blinked the LED
    // if the LED is off turn it on and vice-versa:
    ledState ^= 1;
    digitalWrite(ledPin, ledState);
  }
  currentMillis = millis();
  if (currentMillis - previousMillis2 > interval2) {
    previousMillis2 = currentMillis; // save the last time I printed on the serial
    // if the LED is off turn it on and vice-versa:
    Serial.println(++counter);
  }
}

This sketch executes 2 jobs: the first one is the LED blinking with a 100 ms interval, while the second one prints a counter over the serial every 1000 ms. Every job is correctly executed because the program doesn’t freeze in any delay() but constantly checks if the time to execute a jobs of the two has come.

Update:

if you want to leave the loop() clean, you can separate the 2 jobs creating 2 different functions that they’ll be called from the main loop:

const byte ledPin =  13; // the number of the LED pin
byte ledState = 0; // ledState used to set the LED
unsigned long previousMillis1 = 0; // will store last time LED was updated
unsigned long interval1 = 100;  // interval at which to blink (milliseconds)
unsigned long previousMillis2 = 0;
unsigned long interval2 = 1000;
unsigned int counter = 0;

void setup() {
  Serial.begin(19200);
  delay(2000);
  pinMode(ledPin, OUTPUT); // set the digital pin as output
}

//main loop
void loop() {
  blinkLed();
  printCounter();
}

//blink the led
void blinkLed() {
  //check if it's time to blink the led
  if (millis() - previousMillis1 > interval1) {
    previousMillis1 = millis(); // save the last time I blinked the LED
    // if the LED is off turn it on and vice-versa:
    ledState ^= 1;
    digitalWrite(ledPin, ledState);
  }
}

//print on the serial the value of a counter
void printCounter() {
  //check if it's time to print the new value of the counter
  if (millis() - previousMillis2 > interval2) {
    previousMillis2 = millis(); // save the last time I printed on the serial
    Serial.println(++counter); //increment the counter and print it on the serial
  }
}

This way the loop() is clearer and at a first look we can understand how the code is organized: we see that 2 functions are called repeatedly. Putting a little comment before the code of the function it’s a good practice, as well as using descriptive names, so that we can quickly understand what the functions themselves do.

If you want to use a better jobs management, I suggest you to use my looper, a library that I wrote to execute several jobs in an easy way.