Multi-task Your Arduino

10,546

113

8

55+ years in electronics, computers, and teaching ... now retired.

This instructable describes a method of time-slicing that creates the illusion that your arduino is performing multiple tasks at the same time.

Using this technique the arduino in the above video is simultaneously:

  • toggling a LED every 300mS (milliseconds)
  • scrolling a line of text every 250mS
  • changing a custom bar graph every 100mS

The included arduino code is fully annotated should you wish to use this project as a template.

Step 1: Parts List and Schematic

Very few parts are required to replicate this project:

  • 1 x arduino Uno R3 microcontroller.
  • 1 x I2C LCD 16x2 display
  • 1 x arduino-to-PC USB cable
  • 4 x arduino male-to-female jumper wires

All parts may be obtained from https://www.aliexpress.com/

Photo 1 shows how to connect the jumper wires.

Step 2: Code

To install my code, download the file "multitasking.ino" and insert the contents into a new arduino sketch. Save the sketch as "multitasking" (without the quotes) then upload it to your arduino.

You will also need to install an I2C “library” for your LCD display. I used a library from http://arduino-info.wikispaces.com/LCD-Blue-I2C#v1 . If you have a different chipset "Google” the text located in the red area shown in photo 1.

Once you have located a suitable library download the zip file and install it using the instructions found at https://www.arduino.cc/en/Guide/Libraries.

The instruction set for most LCD displays can be found at https://www.arduino.cc/en/Reference/LiquidCrystal.

Step 3: Theory

An empty loop(){} within your arduino code will cause the arduino to “wheel-spin” several thousand times every second. Let’s see what happens when we add a task ...

Single-tasking:

The following code will display “Hello World!” on your Serial Monitor once every second:

Serial.println(“Hello World!”);
delay(1000);

Each loop(){} now takes over 1 second to complete due to the delay(1000) instruction.

Assuming that your arduino has a 16MHz crystal, the delay(1000) instruction is wasting 16,000,000 clock cycles . Other tasks are possible if we eliminate this delay.

Multi-tasking:

Let’s rewrite the above piece of code:

if (Flag1)
{
  Serial.println(“Hello World!”);
  Flag1=false;
}

“Hello World! is printed after which the loop(){} reverts to rotating at full speed until Flag1=true.

Since the loop(){} has nothing to do, and is whizzing around at full speed, let’s add another task.

if (Flag2)
{
  digitalWrite(LED, !digitalRead(LED));
  Flag2=false;
}

This piece of code causes the LED to “toggle” after which the loop(){} again reverts to full speed until Flag2=true.

Ignoring how the flags are getting set, we are now able to perform multiple tasks in the SAME time period.

Long tasks require special coding techniques

Multitasking short tasks, such as blinking a LED, is easy ... multi-tasking long tasks, such as scrolling a text message, requires a different approach.

If you look at the video you will see lots of things happening while the text slowly scrolls across the screen. In order to create the illusion of multi-tasking, long tasks must be rewritten as a series of smaller tasks such that a different section of the code is implemented each time through the loop.

This is achieved through the use of “static” variables [1] to track how far the task has progressed. My “scrollMessage()” and “bargraph()” functions demonstrate this technique.

[1]

When a subroutine is called all “variables” forget their previous values unless they have been declared “static”, in which case the previous value is available when the subroutine is next called.

Step 4: The Task Scheduler

The secret to setting the flags is to create a 1 millisecond (mS) “heartbeat” using one of the Arduino Uno R3 timers. I chose to use Timer/Counter 2 (8-bit) which leaves Timer/Counter 1 (16-bit) free for other tasks.

1mS is achieved by dividing the 16,000,000Hz clock by 128 to obtain an 8 microsecond (uS) clock interval. If we now count these 8uS pulses and generate an interrupt when the count reaches 125 then 125*8uS (or 1mS) will have elapsed [1].

The flags are set by placing the following code in the interrupt service routine (ISR):

// ----- count the number of milliseconds that have elapsed
Counter1++;
Counter2++;

// ----- print “Hello World!”
if (Counter1 > 1000 - 1)    //1 second
{
  Counter1 = 0;
  Flag1 = true;             //the loop(){} sees this flag and prints the message
}

// ----- toggle LED
if (Counter2 > 300 - 1)     //300 milliseconds
{
  Counter2 = 0;
  Flag2 = true;             //the loop(){} sees this flag and toggles the LED
}

Once a counter reaches its target value the counter is cleared and a flag is set. The above code takes very little time to execute as there are very few instructions.

The main loop(){} sees every flag that the task scheduler sets and performs the task.

Adding additional tasks is easy... simply create an additional counter and flag, then mimic the above code.

Photo 1 shows the relationship between the three tasks that my arduino is performing.

Notice that:

  • sometimes loop(){} has no tasks to perform.
  • sometimes loop(){} only performs one or two tasks.
  • each of the tasks is called a different number of times
  • the bar graph is advanced every 100mS
  • the text is scrolled every 250mS
  • the LED toggles every 300mS

[1]

We actually load 125-1=124 into the “compare match register” because the interrupt doesn’t occur until the next (125th) clock pulse.

Step 5: Key Points

To successfully multi-task:

  • Avoid the delay() or delayMicroseconds() functions. Instead
  • create an interrupt driven “heartbeat” and
  • use a task scheduler.
  • Long tasks should be treated as as a series of smaller “chunks” with
  • “static” variables tracking the progress.

  Click here   to view my other instructables.

Share

    Recommendations

    • Make it Glow Contest 2018

      Make it Glow Contest 2018
    • Big and Small Contest

      Big and Small Contest
    • Toys Contest

      Toys Contest

    8 Discussions

    0
    None
    BobF13

    Question 6 months ago on Step 2

    Great explanation. I have a project where I'm flashing 110 RGB lights (essentially 330 different LEDs), in various patterns on a star shape object I made. Every one of my functions has delays between the on and off of the light flashing, and I have, so far, about 10 of these functions., eg here's one function.

    void BlinkSpinAccel()

    { for (byte k = 0; k < 3; k++)

    { PORTB = rgb[k];

    for (int i = 0; i < 10; i++) //

    for (int p = 0; p < 8; p++)

    { CS.portWrite(p, 0xFFFF);

    delay(DelayArray[i]);

    CS.portWrite(p, 0x0000);

    delay(DelayArray[i]);

    }

    }

    }

    How should I approach this using your interrupt technique? I want to simultaneously rotate one bunch of LEDS one directions and another bunch the other direction. The function above has an array with increasing values in it so that the delay changes on every pass, but most delays are just "delay 200;" I'm using centipede (CS) library for my I2C commands, writing to 7 port expanders. in an LED matrix. (Port B handles the 3 colors).

    1 answer
    0
    None
    lingibBobF13

    Answer 6 months ago

    Thank you for your comment :)

    You could try an array-of-arrays-of-arrays" such as "lamps[pattern][delay][direction]".

    Such an array would allow any number of different of delays, and directions, to be applied to any pattern. Each lamp delay would require the use of "static" counters.

    Good luck ...

    0
    None
    lingibmohir

    Reply 9 months ago

    Thank you :)

    0
    None
    dreisn

    10 months ago

    Wow! This is a very good explanation. I like a lot the way you explain.
    It's a "Arduino for dummies". Thank you so much.

    1 reply
    0
    None
    lingibdreisn

    Reply 10 months ago

    Thank you for your feedback. It's nice to know that the article was helpful :)