Go Down

Topic: Preemptive multitasking for mega and uno (OS48) (Read 14750 times) previous topic - next topic

dzives

Hello,

I'd like to expose my new project named "OS48". I created last year a similar project "OS47". This project is more mature and has been designed from scratch. It's an Arduino C++ lib.

I designed this kernel in order to execute different functions at the "same time". The kernel is especially designed to Arduino Uno and Mega.

Some features:

- 5 scheduling algorithms (1 cooperative and 4 preemptives)

- stack overflow detection

- task management (suspend, abort, sleep, resume)

- semaphores, barriers, mutexes, monitos

- functions to exchange messages between tasks

- software timer tasks


The project is linked to its own website: http://www.rtos48.com/. You can find the source code, a full documentation and tutorials to install, begin projects and learn how it works.

The project is under the terms of the MIT licence.

Hope it can help some.


ArthurD

#1
Sep 14, 2015, 09:08 am Last Edit: Sep 14, 2015, 10:24 am by ArthurD
hey,
this looks really great!
Will it work also for the Arduino DUE?

How can one check the memory required for each running task, not to reserve too few, not too much, just the right size (sort of memavail() function called in the running task to check it)?

dzives

Hey,

Thank you.

For arduino Due, it's planned. For now, it works with all Arduino having a 8bits AVR MCU (Uno, Mega and Leonardo tested)

First you can override the overflow function with Scheduller::setStackOverflowFnc().
Then, you can check the memory with task()->getUserFreeStackSize().

Note that some functions can reserve internally some memory - like Serial.print - and in this case you can't determine the memory needed. (For the use of Serial.print for example you need more than 40/50bytes).

ArthurD

#3
Sep 14, 2015, 08:55 pm Last Edit: Sep 14, 2015, 10:58 pm by ArthurD
thank you for your reply!
I must admit I'm totally unexperienced to C++ code, I'm just using ANSI C.
So I don't know anything about -> and :: and all that.

Of course just copy and pasting functions and code examples is no issue.

So could you be so kind and provide some simple examples about how to perform 2 independent single tasks e.g.
1) LED blink at 2 sec clock delay and Serial.print a Fibonacci number (recursive) plus free memory
2) LED blink at 3 sec clock delay and Serial.print a Prime number series (Sieve of Eratosthenes) plus free memory?

I can provide the Fibonacci function and the Erastotheses function if you wish:
Code: [Select]


//***************************************************************
//***************************************************************

void markMultiples(bool arr[], int a, int n)  // prime helper function
{
   int i = 2, num;
   while ( (num = i*a) <= n )
   {
       arr[ num-1 ] = 1; // minus 1 because index starts from 0.
       ++i;
   }
}

//***************************************************************

int n=30;

// implement as task 1

void SieveOfEratosthenes()
{
   // There are no prime numbers smaller than 2
   if (n >= 2)
   {
       // Create an array of size n and initialize all elements as 0
       bool arr[n];
       memset(arr, 0, sizeof(arr));

       /* Following property is maintained in the below for loop
          arr[i] == 0 means i + 1 is prime
          arr[i] == 1 means i + 1 is not prime */
       for (int i=1; i<n; ++i)
       {
           if ( arr[i] == 0 )
           {
               //(i+1) is prime, print it and mark its multiples

               Serial.print(Prime number= "); Serial.print( i+1); Serial.print("  Freemem = ");
               Serial.println(memavail() );                // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ???
               Serial.println();

               delay(1999);

               markMultiples(arr, i+1, n);
           }
       }
   }
}

//***************************************************************
//***************************************************************

int fib(int n)          // recursive function
{
  if (n <= 1)
     return n;
  return fib(n-1) + fib(n-2);
}


//***************************************************************

int m = 30;

// implement as task 2

int Fibonacci()
{
 Serial.print(Fibonacci= "); Serial.print( fib(m)); Serial.print("  Freemem = ");
 Serial.println(memavail() );                      //   <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ???
 Serial.println();

 delay(3001);
}

dzives

I hope this script can help:

Code: [Select]

#include <os48.h>

using namespace os48;

Scheduler* scheduler = Scheduler::get(); //Get the instance

Task* taskSieve = NULL;
Task* taskFibo = NULL;
TaskTimer* taskLED = NULL;

void setup() {
  Serial.begin(9600);
  Serial.println("Creating tasks...");

  taskSieve = scheduler->createTask(&SieveOfEratosthenes, 120); //id 1
  taskFibo = scheduler->createTask(&Fibonacci, 300); //id 2
  taskLED = scheduler->createTaskTimer(&blinkLED, 100, 2000); //id 3

  scheduler->setStackOverflowFnc(&overflowFnc);

  pinMode(13, OUTPUT);

  Serial.println("Starting...");
  scheduler->start(); //Starts the scheduler. At this point you enter in a multi tasking context.
}


//***************************************************************
//***************************************************************

void overflowFnc()
{
  Serial.println("Stack overflow!");
  Serial.print("ID of the task affected: ");
  Serial.println(task()->getId());
  Serial.flush();
}

//***************************************************************
//***************************************************************

void blinkLED()
{
  digitalWrite(13, HIGH);

  task()->sleep(1000); //waits 1sec with LED ON

  digitalWrite(13, LOW);
  //waits 1sec with LED OFF
}

//***************************************************************
//***************************************************************

void markMultiples(bool arr[], int a, int n)  // prime helper function
{
  int i = 2, num;
  while ( (num = i * a) <= n )
  {
    arr[ num - 1 ] = 1; // minus 1 because index starts from 0.
    ++i;
  }
}

//***************************************************************

int n = 30;

// implement as task 1

void SieveOfEratosthenes()
{
  // There are no prime numbers smaller than 2
  if (n >= 2)
  {
    // Create an array of size n and initialize all elements as 0
    bool arr[n];
    memset(arr, 0, sizeof(arr));

    /* Following property is maintained in the below for loop
       arr == 0 means i + 1 is prime
       arr == 1 means i + 1 is not prime */
    for (int i = 1; i < n; ++i)
    {
      if ( arr[i] == 0 )
      {
        //(i+1) is prime, print it and mark its multiples
        Serial.print("Prime number = ");
        Serial.print( i + 1);
        Serial.print(" Freemem = ");
        Serial.print(task()->getUserFreeStackSize());
        Serial.print(" / ");
        Serial.println(task()->getUserStackSize());
        Serial.println();

        task()->sleep(2000);

        markMultiples(arr, i + 1, n);
      }
    }
  }
}

//***************************************************************
//***************************************************************

unsigned long int fib(unsigned long int n)          // recursive function
{
  if (n <= 1)
    return n;

  return fib(n - 1) + fib(n - 2);
}


//***************************************************************

// implement as task 2

void Fibonacci()
{
  for (unsigned int m = 1; m <= 15; m++)
  {
    int res = fib(m);
    OS48_NO_KT_BLOCK
    {
      Serial.print("Fibonacci of ");
      Serial.print(m);
      Serial.print(" = ");
      Serial.print(res);
      Serial.print("  Freemem = ");
      Serial.print(task()->getUserFreeStackSize());
      Serial.print(" / ");
      Serial.println(task()->getUserStackSize());
      Serial.println();
    }

    task()->sleep(2000);
  }
}

void loop() {}


You can see 3 tasks. 1 for prime numbers, 1 for fibo and 1 to blink the built-in led on the board.
Don't forget to read the tutorial on the website rtos48.com ;)

Many things:
-I've improved the fibo task in order to print fibo results until 15. The result appears like the print of prime numbers.
-Get the available memory at the same location is not very helpfull (as you can see the same amount is returned each time)
- Recursive fibo is a exponential function taking a lot of memory and you can test by replace 15 with 20 to get a stack overflow (because recursion doesn't allow to let variables free until the last return. More calls are, the more memory is necessary)

If you have any question, don't hesitate :).

AWOL

@ArthurD. . .  and that's why we ask you to ALWAYS USE CODE TAGS WHEN POSTING CODE.

Quote
I'm just using ANSI C.
So I don't know anything about -> and :: and all that
"->" is part of C; there's no good reason you should be unfamiliar with it.

ArthurD

#6
Sep 14, 2015, 10:39 pm Last Edit: Sep 14, 2015, 11:01 pm by ArthurD
@ AWOL :
I am just a hobby ANSI C programmer, no C++, that's why I use the Arduino IDE to obscure all these things.
I never got genuine C++ or either OOP code working (e.g., C#, Java) and I never will be able to.
So please stop  lecturing me what I should be capable of or not.
I already know that there are some severe limitations to my brain and to my imagination, and senile dementia is getting even closer each day of the rest of my life.

And yes, I know about code tags, but I daresay that your code window is so tiny that it's actually not very clearly laid for overviewing what it's all about. Maybe you can enlarge the code window like in this thread ?
http://www.mindstormsforum.de/viewtopic.php?f=78&t=8491#p66128

ArthurD

#7
Sep 14, 2015, 10:47 pm Last Edit: Sep 15, 2015, 09:33 am by ArthurD
@dzives,
thank you very much for your input!
As I already wrote, I am just a hobby programmer who needs quite much example code to look at and to tinker around with it in order to be able to understand what it actually does, and I have my business to work at all day and so I got not so extremely much spare time.
I will try your code with my MEGA, perhaps even tomorrow and will report then!
Additionally, my English is actually quite shaky and demands lot of Google translations what makes reading and writing quite complicated.

I already expected that the code will return the available memory quite constantly (except for recursive functions, perhaps), but my question was just about how to implement the reading of free RAM either what it finally would show all during runtime.


But anyway, thanks a lot for your help right now, I will report ASAP then!

ArthurD

#8
Sep 15, 2015, 09:45 am Last Edit: Sep 15, 2015, 09:53 am by ArthurD
hi,
now I'm starting to explore your code... 8-)

I'm using a Arduino Mega IDE=1.6.5
The code compiled an ran. :)


1st question

the numbers is it the reserved memory in bytes, right?
but why has id3 2 numbers? 100 for the memory, and 2000....?

 120
 300
 100,2000


  taskSieve = scheduler->createTask(&SieveOfEratosthenes, 120); //id 1
  taskFibo = scheduler->createTask(&Fibonacci, 300); //id 2
  taskLED = scheduler->createTaskTimer(&blinkLED, 100, 2000); //id 3


ArthurD

#9
Sep 15, 2015, 10:20 am Last Edit: Sep 15, 2015, 10:23 am by ArthurD
I now changed your code and added a Serial.print also for the LED blink in 1-sec-clock.
I also modified the LED blink Task a little and enlarged the RAM size of tasks 1+2.

I admittedly don't understand all the details but it works as far as I can observe:
prime numbers by Erastothenes sieve output is independed from Fibonacci number output,
and LED blinks in 1-sec-clock beat and outputs each second the number of elapsed time.

And the best of all: The LED blink task is not disrupted by long lasting calculations of tasks 1+2 and vice versa!
Real timeslice-scheduling as expected!

Fine, I'm exited!

I'll make some more experiments and will report further on!

(ps,
My guess about the "2000" in your original LED task 3 - is it the duration of the task cycle in milli seconds? )

Code: [Select]

#include <SPI.h>
#include <SD.h>
#include <UTFTQD.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9340.h>
#include <ardustdio.h>

#include <os48.h>

using namespace os48;

Scheduler* scheduler = Scheduler::get(); //Get the instance

Task* taskSieve = NULL;
Task* taskFibo = NULL;
TaskTimer* taskLED = NULL;


//=====================================================================================
// misc. 
//=====================================================================================

#define  clock()      millis() 


//=====================================================================================
//=====================================================================================


void setup() {
  Serial.begin(115200);
  Serial.println("Creating tasks...");

  taskSieve = scheduler->createTask(&SieveOfEratosthenes, 1000); //id 1
  taskFibo = scheduler->createTask(&Fibonacci, 1000); //id 2
  taskLED = scheduler->createTaskTimer(&blinkLED, 100); //id 3

  scheduler->setStackOverflowFnc(&overflowFnc);

  pinMode(13, OUTPUT);

  Serial.println("Starting...");
  scheduler->start(); //Starts the scheduler. At this point you enter in a multi tasking context.
}


//***************************************************************
//***************************************************************

void overflowFnc()
{
  Serial.println("Stack overflow!");
  Serial.print("ID of the task affected: ");
  Serial.println(task()->getId());
  Serial.flush();
}

//***************************************************************
//***************************************************************

void blinkLED()
{
  static int16_t seconds=0;
  digitalWrite(13, HIGH);
  task()->sleep(1000); //waits 1sec with LED ON
  ++seconds; 
  Serial.print("sec: "); Serial.println(seconds);
 
  digitalWrite(13, LOW);
  task()->sleep(1000); //waits 1sec with LED ON
  ++seconds; 
  Serial.print("sec: "); Serial.println(seconds);
}

//***************************************************************
//***************************************************************

void markMultiples(bool arr[], int32_t a, int32_t n)  // prime helper function
{
  int32_t i = 2, num;
  while ( (num = i * a) <= n )
  {
    arr[ num - 1 ] = 1; // minus 1 because index starts from 0.
    ++i;
  }
}

//***************************************************************

int maxprime = 50;

// implement as task 1

void SieveOfEratosthenes()
{
  int n=maxprime;
  // There are no prime numbers smaller than 2
  if (n >= 2)
  {
    // Create an array of size n and initialize all elements as 0
    bool arr[n];
    memset(arr, 0, sizeof(arr));

    /* Following property is maintained in the below for loop
       arr == 0 means i + 1 is prime
       arr == 1 means i + 1 is not prime */
    for (int i = 1; i < n; ++i)
    {
      if ( arr[i] == 0 )
      {
        //(i+1) is prime, print it and mark its multiples
        Serial.print("Prime number = ");
        Serial.print( i + 1);
        Serial.print(" Freemem = ");
        Serial.print(task()->getUserFreeStackSize());
        Serial.print(" / ");
        Serial.println(task()->getUserStackSize());
        Serial.println();

        task()->sleep(1500);

        markMultiples(arr, i + 1, n);
      }
    }
  }
}

//***************************************************************
//***************************************************************
uint32_t fib(unsigned long int n)          // recursive function
{
  if (n <= 1)
    return n;

  return fib(n - 1) + fib(n - 2);
}


//***************************************************************
int nfib = 30;
// implement as task 2

void Fibonacci()
{
  for (unsigned int m = 1; m <= nfib; m++)
  {
    int32_t res = fib(m);
    OS48_NO_KT_BLOCK
    {
      Serial.print("Fibonacci of ");
      Serial.print(m);
      Serial.print(" = ");
      Serial.print(res);
      Serial.print("  Freemem = ");
      Serial.print(task()->getUserFreeStackSize());
      Serial.print(" / ");
      Serial.println(task()->getUserStackSize());
      Serial.println();
    }

    task()->sleep(1000);
  }
}

void loop() {}


//=====================================================================================
//=====================================================================================

dzives

You're right, the first number for "createTask" is the amount of RAM reserved and the second one for "createTaskTimer" is the period of the timer.

The function "blinkLED" is not right implemented. The default period is 1000ms because you omit the second number. You sleep two times in "blinkLED" for a total of 2s which is > 1s. Therefore the timer can't compensate the time and works like a standard task. To fix that you have 2 options:

- replace "createTaskTimer" by "createTask"
- remove the second "task()->sleep" and move the print function on the top (++seconds ans Serial.print)

What's the difference with two sleep or a timer task ?
The timer task makes up for lost tme used to print or do something else. See http://www.rtos47.com/tuto/advanced_usage/#special-tasks.

Note that you don't need so much RAM for prime numbers. But for fibo it depends of of the max number you give to the function.

Good luck ;)

ArthurD

#11
Sep 15, 2015, 12:09 pm Last Edit: Sep 15, 2015, 12:16 pm by ArthurD
aah, I see, thank you for clarification!

Another couple of questions which just arose:

You wrote, your OSEK48 supports 4 preemptive and 1 cooperative task.

Is the cooperative task always the 5th on (id==5) or has it to be setup on a fundamentally different way?
How can the cooperative task (infinite, repetitive loop) release control to the other taskes?
E.g., just by a yield() or any (delay() command at an arbitrary position?

(sorry to ask, but I didn't find it in the documentation )


--------------------------------------------------------------------
ps,
just  about the RAM consuming: it's not so much what is used for either task (nfib = 30):

Code: [Select]
Creating tasks...
Starting...
Fibonacci of 1 = 1  Freemem = 994 / 1000

Prime number = 2 Freemem = 931 / 1000

Fibonacci of 2 = 1  Freemem = 994 / 1000

Prime number = 3 Freemem = 931 / 1000

sec: 1
Fibonacci of 3 = 2  Freemem = 994 / 1000

sec: 2
Fibonacci of 4 = 3  Freemem = 994 / 1000

Prime number = 5 Freemem = 931 / 1000

sec: 3
Fibonacci of 5 = 5  Freemem = 994 / 1000

Prime number = 7 Freemem = 931 / 1000

sec: 4
Fibonacci of 6 = 8  Freemem = 994 / 1000

sec: 5
Fibonacci of 7 = 13  Freemem = 994 / 1000

Prime number = 11 Freemem = 931 / 1000

sec: 6
Fibonacci of 8 = 21  Freemem = 994 / 1000

Prime number = 13 Freemem = 931 / 1000


dzives

#12
Sep 15, 2015, 12:26 pm Last Edit: Sep 15, 2015, 12:32 pm by dzives
All tasks use the same scheduling algorithm. By default and in our previous examples, the scheduling algorithm is Round-robin. If you want to have some control you can:

- punctually and rarely using a preemptive algorithm: use "scheduler->yield();"
- choose the cooperative scheduling algorithm with "scheduler->setSchedulingMode(SchModeCoop);" and use "task()->yield();" or "task()->yieldTo(Task*)";

Note that:
994 / 1000 means 994 bytes free at this code location.

ArthurD

#13
Sep 15, 2015, 03:27 pm Last Edit: Sep 15, 2015, 03:30 pm by ArthurD
thank you,
and yes, thanks for the hint about free mem!
The amount of free RAM space of both tasks is actually what was so surprising to me, especially after having enlarged esp. the limits for fibonacci from 15 to 30!
(even fibonacci (50) is working extremely fine, still > 990 / 1000 bytes free! )

dzives

#14
Sep 15, 2015, 04:41 pm Last Edit: Sep 15, 2015, 04:42 pm by dzives
Actually, that's not work really like that. Fibo takes so much more memory than you think. You see 990 because you check the memory at the end of the function, when all variables are cleared from memory.

Suppose you call a (useless) recursive function with a int parameter returning a int:

Code: [Select]

int recursiveFunc(int param)
{
    // (A)
    if (param > 0)
        return recursiveFunc(param - 1);

    return 0;
}


Then you call this function: recursiveFunc(4)

  • Before calling the function, you check the memory: 0
  • At the point (A) the memory used grows to 4 (2bytes for the param and 2butes for save the result)
  • Given the value of param (4 != 0), a second call occurs causing the allocation of 4 additional bytes (total of 8bytes now)
  • In the second call, param == 3 (!= 0), a third call occurs causing the allocation of 4 additional bytes (total of 12bytes now)
  • And so on until param == 0. When this condition is reached the memory used is equal to 16!
  • After the return for param == 0, the size is 12, then after the return for param == 1, the size is 8 and so on.
  • At the end you check the size (like you do with Fibonacci) and you get 0 ! But previously 16 bytes have been used. If you set the size of the task to 10 that will be not enough and you will get a stack overflow. I admit that's not an easy exercice to estimate how much memory will use a task. Especially as the compiler performs optimizations and the result can be +/- different than expected.

 


Go Up