Project tutorial

How to "Multithread" an Arduino (Protothreading Tutorial) © GPL3+

Arduino's great, but how in the world do you do two (or more) things at once on separate intervals? You need protothreading!

  • 53,382 views
  • 33 comments
  • 70 respects

Components and supplies

About this project

This video depicts something you may have wanted to do during your budding prototyping career, coaxing a single-core arduino to do 3 things at once. In this case, we're:

  • Pulsing the backlight at a constant rate without interruption
  • Incrementing an integer every second and writing it to the display without interruption
  • Rotating a few messages every few seconds and writing them to the display without interruption
caption (optional)

You saw the title!

Protothreading is a way of performing what would normally be a multitasking operation on (doing two or more things at once or at different intervals) on an Arduino. In other words, it's "multithreaded"! But hold on there Sparky, the Arduino is a single-core chip with procedural code, so true multithreading is impossible. Why though? How is protothreading any different?

"Real" Multithreading vs Protothreading

To understand protothreading properly, we first need to understand why it's NOT really multithreading. 

Remember back in the day when Intel was selling us this new "Hyperthreading" thing on Pentium processors? No? You weren't born yet? Well time for a history lesson then, son! Hyperthreading is a technology Intel employs to make a single core on a processor "act" like it's two cores, or two cores "act" like they're 4 cores, etc. But why, and how is that relevant to Arduino? The answer is cycles.

Both Microcontrollers and CPUs do work in "cycles".  How fast they do them (how many in a second) is the clock rate. You've seen a CPU's Ghz rating, and you probably know it relates to how fast it is. The more Ghz, the better, right? but why? Because that's the number of cycles per second a processor can achieve (without overheating and catching on fire - really!).

If you're a datasheet nerd, you might know that the Arduino Uno's microprocessor chip, the Atmel ATMega328P, runs at 16Mhz out of the box. It's capable of 20Mhz, but is dialed back so it won't mess up things like writing data to memory (or, you know, catch fire). 16Mhz means every second, your Arduino is processing 16,000,000 cycles, aka doing 16 million pieces of work. Now, these are NOT lines of code - that'd be blazingly fast and Arduino is relatively slow. These are processor instructions such as moving data in and out of registers. Going lower level than this overview get's fairly technical so I'll leave that as an exercise to the reader, but that's the gist :)

So, if we can only go so fast on a core before the best chip available catches fire, are we stuck at that speed forever? Is that the fastest we can do work? As it turns out, no! Enter multicore CPUs, and multithreading. On a computer CPU, multithreaded applications are two separate processes that work in parallel to one another on different cores of a CPU. These processes interact to get work done together, but don't necessarily split the work evenly as you might assume. There is typically a main process / "thread" that functions as a manager of the other threads, and then one or more worker threads it manages, which each might do specific tasks. A good example is Chrome. Chrome is the manager of all your web page tabs (threads), but because chrome is multithreaded, each tab is its' own little program. That means not only can it run faster if you have several cores to distribute each tab across, it also has other benefits like not crashing the entire browser when one tab crashes. This is the first reason Protothreading is not multithreading - we only have one core to work with on an MCU, so traditional multithreading is straight up impossible. We need to manage work on only a single core, but still do multiple things at once. We need protothreading.

Ok, how is Protothreading different then?

Protothreading is very simillar to that Hyperthreading thing I mentioned, to an extent. Hyperthreading would emulate a second core and literally divide the work one core is doing by pretending to be two virtual cores. This worked because they really existed on the same core and thus shared the same resource space. Since the arduino MCU doesn't support hyperthreading, we're not able to do that here. Protothreading is similar, except that in place of CPU cycles and instructions, we can break work down by the 'loops' or 'lines' of code being executed by our sketch. As you might imagine, if we're doing more stuff, loops would take longer, so each project will have vastly different 'loops per second'. There are different implementations of protothreading, and the one I use here is admittedly probably shoddy, but it works. Basically, each loop we don't have other work to do, we do some less-demanding or less-frequent work in the main loop (or nothing at all). When we're not busy, we're checking to see if it's time to do one of those other pieces of work yet. If so, we branch off and go do it. It's important to note that actions that are "blocking", meaning they must complete all at once without interruption and thus tie up the MCU for a period of time (such as reading data off an SD card and a few other tasks) will still block other protothreads from occurring "on time", but for simple things like two loops going at once performing quick actions like variable changes or changing output values, it will work superbly. This is more or less what we'll be doing here. Some MCUs support a real-time operating system (RTOS) that can provide more hyperthreading-like multitasking abilities which can help mitigate problems caused by "blocking" tasks.

Let's get started.

We first figure out what tasks we need to perform. In my case, I picked (a) fade the backlight of my LCD panel in and out for a neat "pulsing" effect, while (b) counting up a number on a much slower, (and possibly non-divisible) interval, and (c) rotating some string messages at a yet much slower interval. Some guidelines to follow to make sure this process works smoothly are to rate your functions from the least-blocking to the most-blocking. Actions (let's call them "functions" from this point on) that take longer, like reading data or have other long delays, and functions with larger intervals between when they fire are the most-blocking functions. Functions which fire very frequently, if not every loop, and don't take long to complete are the least-blocking functions. The very least blocking function is what you should use as your primary "thread". Can you guess which it is above?

That's right, it's "a", pulsing the backlight in and out. This will be at a regular and very fast interval, perpetual with no delays inbetween fires other than getting the work done, and the work itself is very fast. The perfect manager thread.

We will use this thread (and any loops within it) to check if the other threads need to do any work. It's probably best to read through the code at this point - it is heavily documented. See the main loop toward the bottom. You can see me checking if threads need any work where I call numberThread.check()  and textThread.check() .

I need to do this within any loops in the main thread as well, as they will block until completion if I don't. I set the interval at which the threads need to fire when I initialize them during init or the setup portion of the code. If it's time for these threads to fire, .check() will see that and perform their work before continuing the main thread.

That's really it in a nutshell, the rest of it you can probably figure out yourself by stepping through the code. Let me end by saying while I may sound like it, I'm NOT a protothreading pro by any means, this is just a simple example I hacked up. If you have any tips or if I was wrong about anything, I encourage feedback and corrections! Thanks :)

Code

Multithreaded LCD Code - multithread.ino (Updated, v1.1)Arduino
This bit of code uses the <TimedAction.h> library to perform 3 repeating actions with separate intervals at the same time on one Arduino Uno processor. It will (a) Fade the backlight in and out, while (b) incrementing a number, and (c) rotating between a few strings of text. See the video above for a demo :)
/*
Arduino Protothreading Example v1.1
by Drew Alden (@ReanimationXP) 1/12/2016

- Update: v1.1 - 8/18/17
  Arduino 1.6.6+ prototyping changed, small fixes.
  (create functions ahead of use, removed foreach and related library).
  
  Note that TimedAction is now out of date. Be sure to read notes about
  TimedAction and WProgram.h / Arduino.h errors.
*/

//COMPONENTS

/*
This code was made using the Sunfounder Arduino starter kit's blue LCD.
It can be found at Amazon.com in a variety of kits.
*/

//THIRD-PARTY LIBRARIES
//these must be manually added to your Arduino IDE installation

//TimedAction
//allows us to set actions to perform on separate timed intervals
//http://playground.arduino.cc/Code/TimedAction
//http://wiring.uniandes.edu.co/source/trunk/wiring/firmware/libraries/TimedAction

#include <TimedAction.h>
//NOTE: This library has an issue on newer versions of Arduino. After
//      downloading the library you MUST go into the library directory and
//      edit TimedAction.h. Within, overwrite WProgram.h with Arduino.h


//NATIVE LIBRARIES

#include <LiquidCrystal.h>
    /*
      LiquidCrystal Library - Hello World
    
     Demonstrates the use a 16x2 LCD display.  The LiquidCrystal
     library works with all LCD displays that are compatible with the
     Hitachi HD44780 driver. There are many of them out there, and you
     can usually tell them by the 16-pin interface.

     One example circuit:
     * LCD RS pin to digital pin 12.
     * LCD Enable/E/EN pin to digital pin 11
     * LCD D4 pin to digital pin 5
     * LCD D5 pin to digital pin 4
     * LCD D6 pin to digital pin 3
     * LCD D7 pin to digital pin 2
     * LCD R/W pin to ground
     * LCD VSS pin to ground
     * LCD VCC/VDD pin to 5V
     * 10K resistor:
     * ends to +5V and ground
     * wiper (middle) to LCD VO pin (pin 3)
     *Backlit Displays:
     * LCD K pin to ground (if present)
     * LCD A pin to 220ohm (red red black black (brown)) resistor, then
       resistor to pin 9
    
     This example code is in the public domain.
    
     http://www.arduino.cc/en/Tutorial/LiquidCrystal
     */

//GLOBALS
int backlightPin = 9;   // used for backlight fading

int timerCounter = 0;   // incrementing counter. will crash eventually.
int stringNo = 0;       //which text string to show
//                   "16 CHARACTER MAX"
char* stringArray[]={"Check it out... ",
                     "I have 3 threads",
                     "going at once...",
                     "Cool, huh?! :D  "};
                     
//INIT

// This should probably be done inside setup(), but whatever.
// initialize the LCD library with the numbers of the interface pins
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

//FUNCTIONS

//this is our first task, print an incrementing number to the LCD
void incrementNumber(){
   // set the cursor to column 0, line 1
  // (note: line 1 is the second row, since counting begins with 0):
  lcd.setCursor(0, 1);
  // add one to the counter, then display it.
  timerCounter = timerCounter + 1;  
  lcd.print(timerCounter);
}

//our second task, fires every few seconds and rotates text strings
void changeText(){  
  // Print a message to the LCD.
  lcd.setCursor(0, 0);
  lcd.print(stringArray[stringNo]);

  //nasty hack to get number of Array elements
  if (stringNo >= sizeof(stringArray)/sizeof(char *)){  
    stringNo = 0;
    changeText();
  }
  else{
    stringNo = stringNo + 1;  
  }
}

//Create a couple timers that will fire repeatedly every x ms

//edit: these lines used to be in front of incrementNumber and changeText
//      functions. this didn't work because the functions weren't defined yet!
TimedAction numberThread = TimedAction(700,incrementNumber);
TimedAction textThread = TimedAction(3000,changeText);

// where's our third task? well, it's the main loop itself :) the task
// which repeats most often should be used as the loop. other
// tasks are able to "interrupt" the fastest repeating task.


void setup() {
  //define the LCD's number of columns and rows:
  lcd.begin(16, 2);
  //fire changeText once to paint the initial string [0]
  changeText();
}


void loop() {
  
  //check on our threads. based on how long the system has been
  //running, do they need to fire and do work? if so, do it!
  numberThread.check();
  textThread.check();
  
  //third task, fade in backlight from min to max brightness
  //in increments of 5 points:
  digitalWrite(13, HIGH);
  for (int fadeValue = 0 ; fadeValue <= 255; fadeValue += 10) {
    
    //wait a second, why am i checking on the threads here? because
    //this is a for loop. you must check on your threads during ANY
    //loops that occur, including the main one!
    numberThread.check();
    textThread.check();
    
    //sets the value (range from 0 to 255):
    analogWrite(backlightPin, fadeValue);
    
    // wait for 20 milliseconds to see the dimming effect
    // keep delays on the main loop SHORT. these WILL prevent
    // other threads from firing on time.
    delay(20);
  }

  //fade out from max to min in increments of 5 points:
  digitalWrite(13, LOW);
  for (int fadeValue = 255 ; fadeValue >= 0; fadeValue -= 10) {
    
    //check on our threads again
    numberThread.check();
    textThread.check();
    
    //sets the value (range from 0 to 255):
    analogWrite(backlightPin, fadeValue);
    
    //wait for 20 milliseconds to see the dimming effect
    delay(20);
  }
  
  /*  
  
  For some scrolling message fun in the future...
  
  lcd.setCursor(15,0);  // set the cursor to column 15, line 0
    for (int positionCounter1 = 0; positionCounter1 < 26; positionCounter1++)
    {
      lcd.scrollDisplayLeft();
      //Scrolls the contents of the display one space to the left.
      lcd.print(array1[positionCounter1]);
      // Print a message to the LCD.
      delay(tim);  //wait for 250 microseconds
    }
    lcd.clear();  
    //Clears the LCD screen and positions the cursor in the upper-left corner.
    
    lcd.setCursor(15,1);  // set the cursor to column 15, line 1
    for (int positionCounter = 0; positionCounter < 26; positionCounter++){
      lcd.scrollDisplayLeft();
      //Scrolls the contents of the display one space to the left.
      lcd.print(array2[positionCounter]);  // Print a message to the LCD.
      delay(tim);  //wait for 250 microseconds
    }
    lcd.clear();
    //Clears the LCD screen and positions the cursor in the upper-left corner.
  */  
  
}

Comments

Similar projects you might like

Windows PC Lock/Unlock Using RFID

Project tutorial by Prasanth K S

  • 6,676 views
  • 6 comments
  • 29 respects

LED Cube

Project tutorial by Praditha Alwis

  • 322 views
  • 0 comments
  • 4 respects

Lie Detector

Project tutorial by Adaline Baskaran

  • 5,683 views
  • 5 comments
  • 16 respects

Makers (Cubecon) #1 Infrared Transmit & Infrared Receive

Project in progress by Alpha

  • 126 views
  • 0 comments
  • 3 respects

Arduino Keyboard

Project in progress by Gabriele Scordamaglia

  • 420 views
  • 2 comments
  • 4 respects
Add projectSign up / Login