Project tutorial
Interrupting Thread Handler

Interrupting Thread Handler © CC0

Easy to use interrupting thread handler. Does not relay on yield() or delay(ms) for switching and no pre-allocated stack needed.

  • 250 views
  • 0 comments
  • 9 respects

Components and supplies

About this project

Introduction

I created this library because I needed three threads and I needed two of them to run at a precise time no matter what the others were doing. The first thread handled serial communication. The second was running a Kalman filter using float matrix multiplication with the Eigen library. And the third was a fast current control loop thread which had to be able to interrupt the matrix calculations.

When should you use this library:

  • When you need your Arduino to do multiple things at once. Especially when you have some code that takes a long time to execute and some that need to run often.
  • When you want a clean and easy to use C++11 interface to create threads with lambda functions.
  • When you want to avoid pre-allocating thread stack sizes.

The library with documentation and example code can be found at:

(https://bitbucket.org/adamb3_14/threadhandler/src/master)

For more in-depth details see the README file.

How it works

Each cyclic thread has a priority and a period. If a thread, with higher priority than the current executing thread, reaches its next execution time the scheduler will pause the current thread and switch to the higher priority one. Once the high priority thread completes its execution the scheduler switches back to the previous thread.

The scheduling scheme of the ThreadHandler library is as follows:

1) Highest priority first.

2) If the priority is the same then the thread with the earliest deadline is executed first.

3) If two threads have the same deadline then the first created thread will execute first.

4) A thread can only be interrupted by threads with higher priority.

5) Once a thread is executing it will block execution for all threads with lower priority until the run function returns.

6) The loop function has priority -128 compared to ThreadHandler threads.

How to use

Threads can be created via c++ inheritance

class MyThread : public Thread
{
public:
 MyThread() : Thread(priority, period, offset){}
 virtual ~MyThread(){}
 virtual void run()
 {
     //code to run
 }
};

Or via createThread and a lambda function

Thread* myThread = createThread(priority, period, offset,
 []()
 {
     //code to run
 });

Thread objects automatically connect to the ThreadHandler when they are created.

To start execution of created thread objects call:

ThreadHandler::getInstance()->enableThreadExecution();

To use this library without problem you should know c++ and have some knowledge about multithreaded programing and thread safety.

Code

ExecutionDemoC/C++
Example code
#include "ThreadHandler.h"

void simulateWork()
{
    delay(1000);
}

//Configures the interrupt timer ticks in us (1000 gives 1ms).
//This tick time will be the lowest time resolution for thread periods and offsets.
//Setting the value to 0 leaves the Interrupt timers defautl tick, this is usefull since
//this setting will not be in conflict with other libraries and analogWrite.
SET_THREAD_HANDLER_TICK(0);

//This macro configures which timer should be used for generating
//the interrupts driving the ThreadHandler.
//The user can implement there own InterruptTimer by inhereting
//from the ThreadHandler::InterruptTimerInterface. This is usefull
//for adding support for new boards that this library does not support yet.
//Currently the library supports SAMD21 and all version supported by the
//TimerOne library. To see how to do this lock in the "platformSpecificClasses.h".
THREAD_HANDLER(InterruptTimer::getInstance());

//This is the first thread class with its run function
//configured to run every 20s with an offset of 0s and priority 1.
//The scheduling scheme of the ThreadHandler library is as follows:
//    1) Highest priority first.
//    2) If the priority is the same then
//       the thread with the earliest dedline 
//       is executed first.
//    3) If two threads have the same dedline then
//       the first created thread will execute first.
//    4) A thread can only be intrrupted by threads with
//       higher priority.
//    5) Once a thread is executing it will block execution
//       for all threads with lower priority untill the run
//       function returns.
//    6) The loop function has priority -128 compared to
//       ThreadHandler threads.
class TestThread1 : public Thread
{
public:
    TestThread1() : Thread(1, 20000000, 0)
    {
    }

    virtual ~TestThread1()
    {
    }

    virtual void run()
    {
        {
            //The ThreadInterruptBlocker class is used
            //to avoid race conditions between threads.
            //When creating a blocker the interrupt timers
            //interrupt vector is dissabled and when it is
            //destroyd it enables the interrupt again.
            //It does not affect global interrupts, only
            //the interrupt timer. The blocker object also
            //has a lock and an unlock function to be able
            //to dissable/enable interrupts without
            //creating/destroying blocker objects.
            ThreadInterruptBlocker block;
            Serial.println("     ___");
            Serial.println("     |T1|");
        }
        for (int i = 0; i < 3; i++)
        {
            simulateWork();
            ThreadInterruptBlocker block;
            Serial.println("     | #|");
        }
        {
            ThreadInterruptBlocker block;
            Serial.println("      ^^");
        }
    }
};

//This is the second thread class with its run function
//configured to run every 20s with an offset of 6s and priority 1.
//If the offset of TestThread2 would be set to 0s, in this case, TestThread1
//and TestThread2 objects whould be qued up to run directly after each
//other, since they both have the same period time. Now TestThread2 object
//run 6s after TestThread1 objects.
class TestThread2 : public Thread
{
public:
    TestThread2() : Thread(1, 20000000, 6000000)
    {
    }

    virtual ~TestThread2()
    {
    }

    virtual void run()
    {
        {
            ThreadInterruptBlocker block;
            Serial.println("     ___");
            Serial.println("     |T2|");
        }
        for (int i = 0; i < 3; i++)
        {
            simulateWork();
            ThreadInterruptBlocker block;
            Serial.println("     | #|");
        }
        {
            ThreadInterruptBlocker block;
            Serial.println("      ^^");
        }
    }
};

//create the thread objects. When a thread object
//is created it will add it self to the ThreadHandler
//automatically.
TestThread1* testThread1 = new TestThread1();
TestThread2* testThread2 = new TestThread2();

CodeBlocksThread* testThread3 = nullptr;
unsigned int test = 0;
FunctionalWrapper<bool>* delayFunction = createFunctionalWrapper<bool>(
    [&test]()
    {
        test++;
        if (test > 1000)
        {
            test = 0;
            return true;
        }
        return false;
    });

void setup()
{
    Serial.begin(115200);

    //This creates the third thread object directly without first defining
    //a thread type. This thread will run its lambda function
    //every 4s with an offset of 0s and priority 2.
    testThread3 = createThreadWithCodeBlocks(2, 4000000, 0,
        []()
        {
            {
                ThreadInterruptBlocker block;
                Serial.println("          ___");
                Serial.println("          |T3|");
            }
            for (int i = 0; i < 1; i++)
            {
                simulateWork();
                ThreadInterruptBlocker block;
                Serial.println("          | #|");
            }
            {
                ThreadInterruptBlocker block;
                Serial.println("           ^^");
            }
            //Thread::delayNextCodeBlock(500000);
            Thread::delayNextCodeBlockUntil(delayFunction);
        });

    testThread3->addCodeBlock(
        []()
        {
            Serial.println("          | #|secCodeBlock");
        });

    //start executing threads
    ThreadHandler::getInstance()->enableThreadExecution();
}

int loopCount = 0;

//The loop function has priority -128 and will be interrupted
//by all the thread objects
void loop()
{
    {
        ThreadInterruptBlocker block;
        Serial.println("___");
        Serial.println("|LP|");
    }
    for (int i = 0; i < 6; i++)
    {
        simulateWork();
        ThreadInterruptBlocker block;
        Serial.println("| #|");
    }
    {
        ThreadInterruptBlocker block;
        Serial.println(" ^^");
    }
    if (loopCount == 10)
    {
        loopCount = 11;

        //It is also possible to delete threads
        //to stop executing them. However if a thread
        //is supposed to be started and stopped multiple
        //times it is more efficient to just add an if
        //statement first in the run function checking
        //if the function should do any work.
        ThreadInterruptBlocker block;
        delete testThread3;
        Serial.println("T3 deleted");
    }
    else
    {
        loopCount++;
    }
}
ThreadHandler
Repository

Schematics

ExecutionDemo connection example
Connection example for ExecutionDemo
Threadhandlerschematic tog88vcz8s

Comments

Similar projects you might like

SerialDebug: Improving Debug to Arduino

Project tutorial by JoaoLopesF

  • 3,674 views
  • 6 comments
  • 28 respects

Internal Timers of Arduino

Project tutorial by Marcazzan_M

  • 9,571 views
  • 10 comments
  • 43 respects

Arduino-Based Automatic Water Tap Using IR Sensor

Project tutorial by Creatjet3D R&D Team

  • 9,381 views
  • 6 comments
  • 22 respects

Hacking Qualcomm (Quick Charge) QC 2.0/3.0 With ATtiny85

Project tutorial by Shahariar

  • 7,915 views
  • 11 comments
  • 44 respects

Solo Servo Sweep

Project tutorial by Daniel Nugent

  • 7,583 views
  • 9 comments
  • 20 respects

Control the Speed of Brushless DC Motor Using Bluetooth

Project tutorial by

  • 5,940 views
  • 1 comment
  • 13 respects
Add projectSign up / Login