Multi-tasking the Arduino - Part 1
Overview
Bigger and Better Projects
Once you have mastered the basic blinking
leds, simple sensors and sweeping servos, it’s time to move on to bigger and
better projects. That usually involves combining bits and pieces of
simpler sketches and trying to make them work together. The first thing
you will discover is that some of those sketches that ran perfectly by
themselves, just don’t play well with others.
The Arduino is a very simple processor with no
operating system and can only run one program at a time. Unlike your
personal computer or a Raspberry Pi, the Arduino has no way to load and run
multiple programs.
That doesn’t mean that we can’t manage
multiple tasks on an Arduino. We just need to use a different approach.
Since there is no operating system to help us out, We have
to take matters into our own hands.
Ditch
the delay()
The first thing you need to do is stop
using delay().
Using delay() to control timing is probably
one of the very first things you learned when experimenting with the
Arduino. Timing with delay() is simple and straightforward, but it
does cause problems down the road when you want to add additional
functionality. The problem is that delay() is a "busy wait"
that monopolizes the processor.
During a delay() call, you can’t respond
to inputs, you can't process any data and you can’t change any outputs.
The delay() ties up 100% of the processor. So, if any part of your
code uses a delay(), everything else is dead in the water for the
duration.
Remember Blink?
1.
/*
2.
Blink
3.
Turns on an LED on for one second, then off
for one second, repeatedly.
4.
5.
This example code is in the public domain.
6.
*/
7.
8.
// Pin 13 has an LED
connected on most Arduino boards.
9.
// give it a name:
10.
int led = 13;
11.
12.
// the setup routine runs
once when you press reset:
13.
void setup() {
14.
// initialize the digital pin as an output.
15.
pinMode(led, OUTPUT);
16.
}
17.
18.
// the loop routine runs over
and over again forever:
19.
void loop() {
20.
digitalWrite(led, HIGH); // turn the LED on (HIGH is the voltage level)
21.
delay(1000);
// wait for
a second
22.
digitalWrite(led, LOW); // turn the LED off by making the voltage LOW
23.
delay(1000);
// wait for
a second
24.
}
25.
The simple Blink sketch spends almost
all of its time in the delay() function. So, the processor can't do
anything else while it is blinking.
And
sweep too?
Sweep uses the delay() to control the sweep
speed. If you try to combine the basic blink sketch with the servo
sweep example, you will find that it alternates between blinking and
sweeping. But it won't do both simultaneously.
1.
#include <Servo.h>
2.
3.
// Pin 13 has an LED
connected on most Arduino boards.
4.
// give it a name:
5.
int led = 13;
6.
7.
Servo myservo; // create servo object to control a servo
8.
// twelve servo objects can
be created on most boards
9.
10.
int pos = 0; // variable to store the servo position
11.
12.
void setup()
13.
{
14.
// initialize the digital pin as an output.
15.
pinMode(led, OUTPUT);
16.
myservo.attach(9); // attaches the servo on pin 9 to the servo object
17.
}
18.
19.
void loop()
20.
{
21.
digitalWrite(led, HIGH); // turn the LED on (HIGH is the voltage level)
22.
delay(1000);
// wait for
a second
23.
digitalWrite(led, LOW); // turn the LED off by making the voltage LOW
24.
delay(1000);
// wait for
a second
25.
26.
for(pos = 0; pos <= 180; pos += 1) // goes from 0 degrees to 180 degrees
27.
{ // in steps of 1 degree
28.
myservo.write(pos); // tell servo to go to
position in variable 'pos'
29.
delay(15); // waits 15ms for the servo
to reach the position
30.
}
31.
for(pos = 180; pos>=0; pos-=1) // goes from 180 degrees to 0
degrees
32.
{
33.
myservo.write(pos); // tell servo to go to
position in variable 'pos'
34.
delay(15); // waits 15ms for the servo
to reach the position
35.
}
36.
}
So, how do we control the timing without using
the delay function?
Using
millis() for timing
Become a clock-watcher!
One simple technique for implementing
timing is to make a schedule and keep an eye on the clock. Instead of a
world-stopping delay, you just check the clock regularly so you know when it is
time to act. Meanwhile the processor is still free for other tasks
to do their thing. A very simple example of this is the BlinkWithoutDelay example sketch that comes with the IDE.
The code on this page uses the wiring shown in
the diagram below:
Blink
Without Delay
This is the BlinkWithoutDelay example sketch
from the IDE:
1.
/* Blink without Delay
2.
3.
Turns on and off a light emitting diode(LED)
connected to a digital
4.
pin, without using the delay() function. This means that other code
5.
can run at the same time without being
interrupted by the LED code.
6.
7.
The circuit:
8.
* LED attached from pin 13 to ground.
9.
* Note: on most Arduinos, there is already an
LED on the board
10.
that's attached to pin 13, so no hardware is
needed for this example.
11.
12.
13.
created 2005
14.
by David A. Mellis
15.
modified 8 Feb 2010
16.
by Paul Stoffregen
17.
18.
This example code is in the public domain.
19.
20.
21.
http://www.arduino.cc/en/Tutorial/BlinkWithoutDelay
22.
*/
23.
24.
// constants won't change.
Used here to
25.
// set pin numbers:
26.
const int ledPin = 13; // the number of the LED pin
27.
28.
// Variables will change:
29.
int ledState = LOW; // ledState used to set the
LED
30.
long previousMillis = 0; // will store last time LED was updated
31.
32.
// the follow variables is a
long because the time, measured in miliseconds,
33.
// will quickly become a
bigger number than can be stored in an int.
34.
long interval = 1000; // interval at which to blink (milliseconds)
35.
36.
void setup() {
37.
// set the digital pin as output:
38.
pinMode(ledPin, OUTPUT);
39.
}
40.
41.
void loop()
42.
{
43.
// here is where you'd put code that needs to be
running all the time.
44.
45.
// check to see if it's time to blink the LED;
that is, if the
46.
// difference between the current time and last
time you blinked
47.
// the LED is bigger than the interval at which
you want to
48.
// blink the LED.
49.
unsigned long currentMillis = millis();
50.
51.
if(currentMillis - previousMillis > interval) {
52.
// save the last time you blinked the LED
53.
previousMillis = currentMillis;
54.
55.
// if the LED is off turn it on and vice-versa:
56.
if (ledState == LOW)
57.
ledState = HIGH;
58.
else
59.
ledState = LOW;
60.
61.
// set the LED with the ledState of the variable:
62.
digitalWrite(ledPin, ledState);
63.
}
64.
}
What's the point of that?
At first glance, BlinkWithoutDelay does
not seem to be a very interesting sketch. It looks like just a more
complicated way to blink a LED. However, BinkWithoutDelay illustrates a
very important concept known as a State
Machine.
Instead of relying on delay() to time the
blinking. BlinkWithoutDelay remembers the current state of the LED and
the last time it changed. On each pass through the loop, it looks at the
millis() clock to see if it is time to change the state of the LED again.
Welcome to the Machine
Let’s look at a slightly more interesting
blink variant that has a different on-time and off-time. We’ll call this
one “FlashWithoutDelay”.
1.
// These variables store the
flash pattern
2.
// and the current state of
the LED
3.
4.
int ledPin = 13; // the number of the LED pin
5.
int ledState = LOW; // ledState used to set the
LED
6.
unsigned long previousMillis = 0; // will store last time LED was updated
7.
long OnTime = 250; // milliseconds of on-time
8.
long OffTime = 750; // milliseconds of off-time
9.
10.
void setup()
11.
{
12.
// set the digital pin as output:
13.
pinMode(ledPin, OUTPUT);
14.
}
15.
16.
void loop()
17.
{
18.
// check to see if it's time to change the state
of the LED
19.
unsigned long currentMillis = millis();
20.
21.
if((ledState == HIGH) && (currentMillis - previousMillis
>= OnTime))
22.
{
23.
ledState = LOW; // Turn it off
24.
previousMillis = currentMillis; // Remember the time
25.
digitalWrite(ledPin, ledState); // Update the actual LED
26.
}
27.
else if ((ledState == LOW) && (currentMillis - previousMillis
>= OffTime))
28.
{
29.
ledState = HIGH; // turn it on
30.
previousMillis = currentMillis; // Remember the time
31.
digitalWrite(ledPin, ledState); // Update the actual LED
32.
}
33.
}
State + Machine = State Machine
Note that we have variables to keep track of
whether the LED is ON or OFF. And variables to keep track of when the
last change happened. That is the State part of the State Machine.
We also have code that looks at the state and
decides when and how it needs to change. That is the Machine part.
Every time through the loop we ‘run the machine’ and the machine takes care of
updating the state.
Next, we'll look at how you can combine
multiple state machines and run them concurrently
.
Now
for two at once
Now it is time to do some multi-tasking!
First wire up another LED as in the diagram below.
Then we'll create another state machine for a
second LED that flashes at completely different rates. Using two separate
state machines allows us to blink the two LEDs completely independent of one
another. Something that would be surprisingly complicated to do
using delays alone.
1.
// These variables store the
flash pattern
2.
// and the current state of
the LED
3.
4.
int ledPin1 = 12; // the number of the LED pin
5.
int ledState1 = LOW; // ledState used to set the
LED
6.
unsigned long previousMillis1 = 0; // will store last time LED was updated
7.
long OnTime1 = 250; // milliseconds of on-time
8.
long OffTime1 = 750; // milliseconds of off-time
9.
10.
int ledPin2 = 13; // the number of the LED pin
11.
int ledState2 = LOW; // ledState used to set the
LED
12.
unsigned long previousMillis2 = 0; // will store last time LED
was updated
13.
long OnTime2 = 330; // milliseconds of on-time
14.
long OffTime2 = 400; // milliseconds of off-time
15.
16.
void setup()
17.
{
18.
// set the digital pin as output:
19.
pinMode(ledPin1, OUTPUT);
20.
pinMode(ledPin2, OUTPUT);
21.
}
22.
23.
void loop()
24.
{
25.
// check to see if it's time to change the state
of the LED
26.
unsigned long currentMillis = millis();
27.
28.
if((ledState1 == HIGH) && (currentMillis - previousMillis1
>= OnTime1))
29.
{
30.
ledState1 = LOW; // Turn it off
31.
previousMillis1 = currentMillis; // Remember the time
32.
digitalWrite(ledPin1, ledState1); // Update the actual LED
33.
}
34.
else if ((ledState1 == LOW) && (currentMillis - previousMillis1
>= OffTime1))
35.
{
36.
ledState1 = HIGH; // turn it on
37.
previousMillis1 = currentMillis; // Remember the time
38.
digitalWrite(ledPin1, ledState1); // Update the actual LED
39.
}
40.
41.
if((ledState2 == HIGH) && (currentMillis - previousMillis2
>= OnTime2))
42.
{
43.
ledState2 = LOW; // Turn it off
44.
previousMillis2 = currentMillis; // Remember the time
45.
digitalWrite(ledPin2, ledState2); // Update the actual LED
46.
}
47.
else if ((ledState2 == LOW) && (currentMillis - previousMillis2
>= OffTime2))
48.
{
49.
ledState2 = HIGH; // turn it on
50.
previousMillis2 = currentMillis; // Remember the time
51.
digitalWrite(ledPin2, ledState2); // Update the actual LED
52.
}
53.
}
Thank you sir! May I have another?
You can add more state machines until you run
out of memory or GPIO pins. Each state machine can have its own flash
rate. As an exercise, edit the code above to add a third state machine.
·
First
duplicate all of the state variables and code from one state machine.
·
Then
re-name all the variables to avoid conflicts with the first machine.
It's not very difficult to do. But it
seems rather wasteful writing the same code over and over again. There
must be a more efficient way to do this!
There are better ways to manage this
complexity. There are programming techniques that are both simpler and
more efficient. In the next page, we'll introduce some of the more
advanced features of the Arduino programming language.
A
classy solution
Let's take another look at that last sketch.
As you can see, it is very repetitive. The same code is
duplicated almost verbatim for each flashing LED. The only thing
that changes (slightly) is the varable names.
This code s a prime candidate for a little Object Oriented Programming (OOP).
Put a little OOP in your loop.
The Arduino Language is a variant of C++ which
supports Object Oriented Programming. Using the OOP features of the
language we can gather together all of the state variables and functionality
for a blinking LED into a C++ class.
This isn’t very difficult to do. We
already have written all the code for it. We just need to re-package
it as a class.
Defining a class:
We start by declaring a “Flasher” class:
Then we add in all the variables from
FlashWithoutDelay. Since they are part of the class, they are known as member variables.
1.
class Flasher
2.
{
3.
// Class Member Variables
4.
// These are initialized at startup
5.
int ledPin; // the number of the LED pin
6.
long OnTime; // milliseconds of on-time
7.
long OffTime; // milliseconds of off-time
8.
9.
// These maintain the current state
10.
int ledState; // ledState used to set the LED
11.
unsigned long previousMillis; // will store last time LED was updated
12.
};
Next we add a constructor. The constructor has the same name as the class and its job
is to initialize all the variables.
1.
class Flasher
2.
{
3.
// Class Member Variables
4.
// These are initialized at startup
5.
int ledPin; // the number of the LED pin
6.
long OnTime; // milliseconds of on-time
7.
long OffTime; // milliseconds of off-time
8.
9.
// These maintain the current state
10.
int ledState; // ledState used to set the LED
11.
unsigned long previousMillis; // will store last time LED was updated
12.
13.
// Constructor - creates a Flasher
14.
// and initializes the member variables and state
15.
public:
16.
Flasher(int pin, long on, long off)
17.
{
18.
ledPin = pin;
19.
pinMode(ledPin, OUTPUT);
20.
21.
OnTime = on;
22.
OffTime = off;
23.
24.
ledState = LOW;
25.
previousMillis = 0;
26.
}
27.
};
Finally we take our loop and turn it into a member functioncalled
“Update()”. Note that this is identical to our original void
loop(). Only the name has changed.
1.
class Flasher
2.
{
3.
// Class Member Variables
4.
// These are initialized at startup
5.
int ledPin; // the number of the LED pin
6.
long OnTime; // milliseconds of on-time
7.
long OffTime; // milliseconds of off-time
8.
9.
// These maintain the current state
10.
int ledState; // ledState used to set the LED
11.
unsigned long previousMillis; // will store last time LED was updated
12.
13.
// Constructor - creates a Flasher
14.
// and initializes the member variables and state
15.
public:
16.
Flasher(int pin, long on, long off)
17.
{
18.
ledPin = pin;
19.
pinMode(ledPin, OUTPUT);
20.
21.
OnTime = on;
22.
OffTime = off;
23.
24.
ledState = LOW;
25.
previousMillis = 0;
26.
}
27.
28.
void Update()
29.
{
30.
// check to see if it's time to change the state
of the LED
31.
unsigned long currentMillis = millis();
32.
33.
if((ledState == HIGH) && (currentMillis - previousMillis
>= OnTime))
34.
{
35.
ledState
= LOW; // Turn it off
36.
previousMillis = currentMillis; // Remember the time
37.
digitalWrite(ledPin, ledState); // Update the actual LED
38.
}
39.
else if ((ledState == LOW) && (currentMillis - previousMillis
>= OffTime))
40.
{
41.
ledState = HIGH; // turn it on
42.
previousMillis = currentMillis; // Remember the time
43.
digitalWrite(ledPin, ledState); // Update the actual LED
44.
}
45.
}
46.
};
By simply re-arranging our existing code
into the Flasher class, we have encapsulated all of the variables (the state) and
the functionality (the machine) for flashing a LED.
Now lets use it:
Now, for every LED that we want to flash, we
create an instanceof the Flasher class by calling the constructor.
And on every pass through the loop we just need to call Update() for each
instance of Flasher.
There is no need to replicate the entire state
machine code anymore. We just need to ask for another instance of the
Flasher class!
1.
class Flasher
2.
{
3.
// Class Member Variables
4.
// These are initialized at startup
5.
int ledPin; // the number of the LED pin
6.
long OnTime; // milliseconds of on-time
7.
long OffTime; // milliseconds of off-time
8.
9.
// These maintain the current state
10.
int ledState; // ledState used to set the LED
11.
unsigned long previousMillis; // will store last time LED was updated
12.
13.
// Constructor - creates a Flasher
14.
// and initializes the member variables and state
15.
public:
16.
Flasher(int pin, long on, long off)
17.
{
18.
ledPin = pin;
19.
pinMode(ledPin, OUTPUT);
20.
21.
OnTime = on;
22.
OffTime = off;
23.
24.
ledState = LOW;
25.
previousMillis = 0;
26.
}
27.
28.
void Update()
29.
{
30.
// check to see if it's time to change the state
of the LED
31.
unsigned long currentMillis = millis();
32.
33.
if((ledState == HIGH) && (currentMillis - previousMillis
>= OnTime))
34.
{
35.
ledState
= LOW; // Turn it off
36.
previousMillis = currentMillis; // Remember the time
37.
digitalWrite(ledPin, ledState); // Update the actual LED
38.
}
39.
else if ((ledState == LOW) && (currentMillis - previousMillis
>= OffTime))
40.
{
41.
ledState = HIGH; // turn it on
42.
previousMillis = currentMillis; // Remember the time
43.
digitalWrite(ledPin, ledState); // Update the actual LED
44.
}
45.
}
46.
};
47.
48.
49.
Flasher led1(12, 100, 400);
50.
Flasher led2(13, 350, 350);
51.
52.
void setup()
53.
{
54.
}
55.
56.
void loop()
57.
{
58.
led1.Update();
59.
led2.Update();
60.
}
Less is more!
That’s it – each
additional LED requires just two lines of code!
This code shorter and easier to read.
And, since there is no duplicated code, it
also compiles smaller! That leaves you
even more precious memory to do other things!
A
clean sweep
What else can we do with it?
Let’s apply the same principles to some servo
code and get some action going.
First hook up a couple of servos on your
breadboard as shown below. As long as we are at it, let's hook up a third
LED too.
Here is the standard Servo sweep code.
Note that it calls the dreaded delay(). We'll take the parts we need from
it to make a "Sweeper" state machine.
1.
// Sweep
2.
// by BARRAGAN
<http://barraganstudio.com>
3.
// This example code is in
the public domain.
4.
5.
6.
#include <Servo.h>
7.
8.
Servo myservo; // create servo object to control a servo
9.
// a maximum of eight servo
objects can be created
10.
11.
int pos = 0; // variable to store the servo position
12.
13.
void setup()
14.
{
15.
myservo.attach(9); // attaches the servo on pin 9 to the servo object
16.
}
17.
18.
19.
void loop()
20.
{
21.
for(pos = 0; pos < 180; pos += 1) // goes from 0 degrees to 180
degrees
22.
{
// in steps of 1 degree
23.
myservo.write(pos); // tell servo to go to
position in variable 'pos'
24.
delay(15); // waits 15ms for the servo
to reach the position
25.
}
26.
for(pos = 180; pos>=1; pos-=1) // goes from 180 degrees to 0
degrees
27.
{
28.
myservo.write(pos); // tell servo to go to
position in variable 'pos'
29.
delay(15); // waits 15ms for the servo
to reach the position
30.
}
31.
}
The Sweeper class below encapsulates
the sweep action, but uses the millis() function for timing, much like the
Flasher class does for the LEDs.
We also need to add Attach() and Detach()
functions to associate the servo with a specific pin:
1.
class Sweeper
2.
{
3.
Servo servo; // the servo
4.
int pos;
// current
servo position
5.
int increment;
//
increment to move for each interval
6.
int
updateInterval; // interval between updates
7.
unsigned long lastUpdate; // last update of position
8.
9.
public:
10.
Sweeper(int interval)
11.
{
12.
updateInterval = interval;
13.
increment = 1;
14.
}
15.
16.
void Attach(int pin)
17.
{
18.
servo.attach(pin);
19.
}
20.
21.
void Detach()
22.
{
23.
servo.detach();
24.
}
25.
26.
void Update()
27.
{
28.
if((millis() - lastUpdate) > updateInterval) // time to update
29.
{
30.
lastUpdate = millis();
31.
pos += increment;
32.
servo.write(pos);
33.
Serial.println(pos);
34.
if ((pos >= 180) || (pos <= 0)) // end of sweep
35.
{
36.
// reverse direction
37.
increment = -increment;
38.
}
39.
}
40.
}
41.
};
How many would you like?
Now we can instantiate as many Flashers
and Sweepers as we need.
Each instance of a Flasher requires 2 lines of
code:
·
One to
declare the instance
·
One to
call update in the loop
Each instance of a Sweeper requires just
3 lines of code:
·
One to
declare the instance
·
One to
attach it to a pin in setup
·
And one
call to update in the loop
1.
#include <Servo.h>
2.
3.
class Flasher
4.
{
5.
// Class Member Variables
6.
// These are initialized at startup
7.
int ledPin; // the number of the LED pin
8.
long OnTime; // milliseconds of on-time
9.
long OffTime; // milliseconds of off-time
10.
11.
// These maintain the current state
12.
int ledState; // ledState used to set the LED
13.
unsigned long previousMillis; // will store last time LED was updated
14.
15.
// Constructor - creates a Flasher
16.
// and initializes the member variables and state
17.
public:
18.
Flasher(int pin, long on, long off)
19.
{
20.
ledPin = pin;
21.
pinMode(ledPin, OUTPUT);
22.
23.
OnTime = on;
24.
OffTime = off;
25.
26.
ledState = LOW;
27.
previousMillis = 0;
28.
}
29.
30.
void Update()
31.
{
32.
// check to see if it's time to change the state
of the LED
33.
unsigned long currentMillis = millis();
34.
35.
if((ledState == HIGH) && (currentMillis - previousMillis
>= OnTime))
36.
{
37.
ledState
= LOW; // Turn it off
38.
previousMillis = currentMillis; // Remember the time
39.
digitalWrite(ledPin, ledState); // Update the actual LED
40.
}
41.
else if ((ledState == LOW) && (currentMillis - previousMillis
>= OffTime))
42.
{
43.
ledState = HIGH; // turn it on
44.
previousMillis = currentMillis; // Remember the time
45.
digitalWrite(ledPin, ledState); // Update the actual LED
46.
}
47.
}
48.
};
49.
50.
class Sweeper
51.
{
52.
Servo servo; // the servo
53.
int pos;
// current
servo position
54.
int increment;
//
increment to move for each interval
55.
int
updateInterval; // interval between updates
56.
unsigned long lastUpdate; // last update of position
57.
58.
public:
59.
Sweeper(int interval)
60.
{
61.
updateInterval = interval;
62.
increment = 1;
63.
}
64.
65.
void Attach(int pin)
66.
{
67.
servo.attach(pin);
68.
}
69.
70.
void Detach()
71.
{
72.
servo.detach();
73.
}
74.
75.
void Update()
76.
{
77.
if((millis() - lastUpdate) > updateInterval) // time to update
78.
{
79.
lastUpdate = millis();
80.
pos += increment;
81.
servo.write(pos);
82.
Serial.println(pos);
83.
if ((pos >= 180) || (pos <= 0)) // end of sweep
84.
{
85.
// reverse direction
86.
increment = -increment;
87.
}
88.
}
89.
}
90.
};
91.
92.
93.
Flasher led1(11, 123, 400);
94.
Flasher led2(12, 350, 350);
95.
Flasher led3(13, 200, 222);
96.
97.
Sweeper sweeper1(15);
98.
Sweeper sweeper2(25);
99.
100.
void setup()
101.
{
102.
Serial.begin(9600);
103.
sweeper1.Attach(9);
104.
sweeper2.Attach(10);
105.
}
106.
107.
108.
void loop()
109.
{
110.
sweeper1.Update();
111.
sweeper2.Update();
112.
113.
led1.Update();
114.
led2.Update();
115.
led3.Update();
116.
}
Now we have 5 independent tasks running
non-stop with no interference. And our loop() is only 5 lines of code!
Next we'll add a button so we can interact with some of these tasks.
All
together now!
We want your input too
The other problem with delay()-based timing is
that user inputs like button presses tend to get ignored because the processor
can't check the button state when it is in a delay(). With our
millis()-based timing, the processor is free to check on button states and
other inputs regularly. This allows us to build complex programs that do
many things at once, but still remain responsive.
We'll demonstrate this by adding a button to
our circuit as shown below:
The code below will check the button state on
each pass of the loop. Led1 and sweeper2 will not be updated when the
button is pressed.
1.
#include <Servo.h>
2.
3.
4.
class Flasher
5.
{
6.
// Class Member Variables
7.
// These are initialized at startup
8.
int ledPin; // the number of the LED pin
9.
long OnTime; // milliseconds of on-time
10.
long OffTime; // milliseconds of off-time
11.
12.
// These maintain the current state
13.
int ledState; // ledState used to set the LED
14.
unsigned long previousMillis; // will store last time LED was updated
15.
16.
// Constructor - creates a Flasher
17.
// and initializes the member variables and state
18.
public:
19.
Flasher(int pin, long on, long off)
20.
{
21.
ledPin = pin;
22.
pinMode(ledPin, OUTPUT);
23.
24.
OnTime = on;
25.
OffTime = off;
26.
27.
ledState = LOW;
28.
previousMillis = 0;
29.
}
30.
31.
void Update()
32.
{
33.
// check to see if it's time to change the state
of the LED
34.
unsigned long currentMillis = millis();
35.
36.
if((ledState == HIGH) && (currentMillis - previousMillis
>= OnTime))
37.
{
38.
ledState
= LOW; // Turn it off
39.
previousMillis = currentMillis; // Remember the time
40.
digitalWrite(ledPin, ledState); // Update the actual LED
41.
}
42.
else if ((ledState == LOW) && (currentMillis - previousMillis
>= OffTime))
43.
{
44.
ledState = HIGH; // turn it on
45.
previousMillis = currentMillis; // Remember the time
46.
digitalWrite(ledPin, ledState); // Update the actual LED
47.
}
48.
}
49.
};
50.
51.
class Sweeper
52.
{
53.
Servo servo; // the servo
54.
int pos;
// current
servo position
55.
int increment;
//
increment to move for each interval
56.
int
updateInterval; // interval between updates
57.
unsigned long lastUpdate; // last update of position
58.
59.
public:
60.
Sweeper(int interval)
61.
{
62.
updateInterval = interval;
63.
increment = 1;
64.
}
65.
66.
void Attach(int pin)
67.
{
68.
servo.attach(pin);
69.
}
70.
71.
void Detach()
72.
{
73.
servo.detach();
74.
}
75.
76.
void Update()
77.
{
78.
if((millis() - lastUpdate) > updateInterval) // time to update
79.
{
80.
lastUpdate = millis();
81.
pos += increment;
82.
servo.write(pos);
83.
Serial.println(pos);
84.
if ((pos >= 180) || (pos <= 0)) // end of sweep
85.
{
86.
// reverse direction
87.
increment = -increment;
88.
}
89.
}
90.
}
91.
};
92.
93.
94.
Flasher led1(11, 123, 400);
95.
Flasher led2(12, 350, 350);
96.
Flasher led3(13, 200, 222);
97.
98.
Sweeper sweeper1(15);
99.
Sweeper sweeper2(25);
100.
101.
void setup()
102.
{
103.
Serial.begin(9600);
104.
sweeper1.Attach(9);
105.
sweeper2.Attach(10);
106.
}
107.
108.
109.
void loop()
110.
{
111.
sweeper1.Update();
112.
113.
if(digitalRead(2) == HIGH)
114.
{
115.
sweeper2.Update();
116.
led1.Update();
117.
}
118.
119.
led2.Update();
120.
led3.Update();
121.
}
The 3 LEDs will flash at their own rates.
The 2 sweepers will sweep at their own rates. But when we press the
button, sweeper2 and led1 will stop in their tracks until we release the
button.
Since there are no delays in the loop, the
button input has nearly instantaneous response.
So now we have 5 tasks executing
independently and with user input. There are no delays to tie up the
processor. And our efficient Object Oriented code leaves plenty of room
for expansion!
Conclusion:
In this guide we have demonstrated that it is
indeed possible for the Arduino to juggle multiple independent tasks while
remaining responsive to external events like user input.
·
We’ve
learned how to time things using millis() instead of delay() so we can free up
the processor to do other things.
·
We’ve
learned how to define tasks as state machines that can execute independently of
other state machines at the same time.
·
And
we’ve learned how to encapsulate these state machines into C++ classes to keep
our code simple and compact.
These techniques won’t turn your Arduino into
a supercomputer. But they will help you to get the most out of this
small, but surprisingly powerful little processor.
In the part 2 of this series, we'll build
on these techniques and explore other ways to make your Arduino responsive
to external events while managing multiple tasks.
Comments
Post a Comment