Working with Timed Actions: Using millis()

Table of contents

  1. Speeding up the loop with a global variable
  2. millis() to the rescue
  3. Using millis to fade
  4. Find out more

Time is an essential component to programming behavior. We’ve seen how using the in-built function delay() is incredibly useful for programming these behaviors. It allows us to slow down and take a pause between actions like turning on an off an LED, moving motors and more.

There is, however, a small problem with delay. When you’re working with the Particle framework, the microcontroller needs to maintain a connection to the Particle Cloud. At the end of each loop(){ }, it will update cloud variables, check for new requests to cloud functions and things like that. It will also periodically check that the connection is active and maintained. If we have extremely long delays in our loop, this may result in the Particle device loosing it’s connection to the Particle cloud.

For example, if we wanted to slowly fade an LED down over 10 minutes:

void loop(){
	if( buttonState == LOW )
	{
	  // turn the LED On
	  digitalWrite( ledPin, HIGH);
	}else{

		for( int j = 255; j > 0; j—- )
		{
			analogWrite( ledPin, j );
			delay( 3000 ); // step every 3 seconds
		}
	}

}

In this loop(), the function is going to take 255 * 3000 milliseconds to complete. This means the loop won’t exit for about 13 minutes. After a couple of minutes, the connection to the Particle cloud will timeout and your device’s LED will turn white.

There’s also a few more problems with this:

  1. We can’t flash new code until the loop completes.
  2. We can’t detect any new inputs from components until the loop completes.
  3. We can’t interrupt the loop with Particle functions
  4. We won’t see new variables until the loop completes.

Basically, you always want your loop to execute (or complete) really fast. So we need to find another way around this problem.

Speeding up the loop with a global variable

A simple way to fix this, particularly if we know that it’ll step down sequentially every n seconds, is to use a global variable to keep track of where we are in the process of fading. We can leverage the fact that the loop() will execute multiple times to achieve this.


// store the brightness in a new variable
int brightness = 0;

void loop(){
	if( buttonState == LOW )
	{
		// set the brightness to full
		brightness = 255;
	  // turn the LED On
	  digitalWrite( ledPin, HIGH);
	}else{

			// if we haven't yet reached
			// off or zero... then fade
			if( brightness > 0 ){

				// take one away from the brightness
				brightness = brightness - 1; 
				// set the LEd 
				analogWrite( ledPin, brightness );
				// delay 3 seconds.
				delay( 3000 ); 
				
			}
	}

}

Instead of including a for loop in the loop method, we’re now taking advantage of the fact the loop will iterate to achieve the same effect.

This simple modification allows the loop to execute much faster and still maintain the same behavior. Now the loop will take at most 3 seconds to execute. A huge performance increase that guarantee’s we’ll never loose the connection the Particle cloud, any Particle.variables will update much faster too.

There’s still a problem though: when fading down our loop will take a full 3 seconds to complete. This is more than enough time for someone to quickly press and release the button. That means our code can easily fail to detect the change in button state and respond by illuminating the LED. That’s far from ideal…

millis() to the rescue

Our Particle device allows us to peek in and measure time. It has an in-built method named millis() that is incredibly useful for timing actions and behaviors.

millis() returns the “number of milliseconds since the device began running the current program. *” Basically, it’s connected to an internal clock that’ll return a really long number that allows you to keep track of how much time has elapsed. And you can use this for all sorts of things: to measure the amount of time that’s passed between two button presses, prevent an action happening more than every second or minute, and program transitions and behaviors over long periods (minutes and hours) into LEDS, motors, and speakers.

So, how can we remove the delay and still get the same effect using millis(). Here’s a simple example of using millis()

unsigned long lastPublish = 0;

void loop() {
	unsigned long now = millis();
	if ((now - lastPublish) >= 1000) {
		lastPublish = now;
		Particle.publish( "new-event", String(now) );
	}
}

This solves another dilemma with the Particle framework - we can only publish new events every 1 second. In this example:

  • We create a global variable to store the time when we last performed an action. Note This type is unsigned long. long simply means an exceptionally long integer value (or whole number value), while unsigned means the number we store in this variable cannot be a negative number i.e. -53 or -1001 would cause an error, because this variable isn’t defined as being able to store negatives.

  • In the loop, we’re going to check the current time using millis() i.e. unsigned long now = millis();
  • We’ll make a comparison to between the last time we published and the current time, and see if the difference is more than 1 second (or 1000 milliseconds)
  • If it is we’ll publish a new event
  • If not, the loop will exit and try again until the time has elapsed.

Notice what’s different here: not a single delay - our loop operates lightning fast.

Using millis to fade

Getting back to our example above, let’s implement fading with millis()

We’re going to modify the example to:

  1. Store the time in milliseconds when we last faded down the LED
  2. Decrement the brightness only when it’s above zero and our variable storing millis() shows us that more than 3 seconds has elapsed.
// store the brightness in a new variable
int brightness = 0;
unsigned long lastFade = 0;


void loop(){
	if( buttonState == LOW )
	{
		// set the brightness to full
		brightness = 255;
	  // turn the LED On
	  digitalWrite( ledPin, HIGH);
	}else{

		analogWrite( ledPin, brightness );

		unsigned long now = millis();
		
		if ((now - lastFade) >= 3000)
		{
			// if we haven't yet reached
			// off or zero... then fade
			if( brightness > 0 ){
				// take one away from the brightness
				brightness = brightness - 1; 
			}
		}
	}

}

}

Fantastic

Our loop will now execute blazing fast, allowing us to quickly interrupt with a button push to restore the LED to full brightness, and slowly fade down the LED without causing havok in our program!

Find out more