Preemptive multitasking for mega and uno (OS48)

Koepel:

and a single task that reads the button and controls the led.

I suppose you mean a single function for 3 tasks and not a single task with 3 tasks.

Within your function you can get which task is currently running by calling the function task() which give the task pointer.

Try something like this:

const int buttons[] = {2,3,4};
const int leds[] = {11,12,13};

...

task1 = scheduler->createTask(&func1, 60);    // 0,1,2 is parameter for task
task2 = scheduler->createTask(&func1, 60);
task3 = scheduler->createTask(&func1, 60);

...

void func1()
{
  pinMode( buttons[index], INPUT_PULLUP);
  pinMode( leds[index], OUTPUT);
  int x = 0;
  for(;;)
  {    
    if (task() == task1)
      x = digitalRead( buttons[1]);
    else if (task() == task2)
      x = digitalRead( buttons[2]); 
    
...

You can also call task()->getId() returning the ID of the task (first take created has ID 1).
You can have something like that:

void func1()
{
  pinMode( buttons[index], INPUT_PULLUP);
  pinMode( leds[index], OUTPUT);
  int x = 0;
  for(;;)
  {    
    x = digitalRead( buttons[task()->getId()]);

If 2 tasks or more share the same resource (the same button for example), don't forget to surround the acces with a mutex .

Note : Function given to the task with an argument is not supported.

vbextreme: That's right I'm always thinking about removing this critical section. However, you can tweak the code by using the hook function to enable interrupts http://rtos48.com/tuto/advanced_usage/#interruptions-management

Thanks. The "task() == task1" or the "getId()" should both work. I would have to translate the number of "getID()", because there could be other tasks as well.

Sorry but what do you mean by translate the number of "getID()" ?

If I would have a number of tasks (unknown) and the order in which they are created is unknown, then the ID can be anything. Perhaps a translation is needed from 4,5,6 to 0,1,2.
That could happen if after a while I add a task, and have forgotten about the ID.
The condition "if ( task() == task1 ) " is always valid.

@dzives the time for context switch is very high and block interrupts on all context switch can mean the loss of one or more interrupts.
Are You sure that the method used to save the stack pointer also includes the third byte which is used for 2560? have You disassemble a code?
Warning inline for gcc/g++ is at the discretion of the compiler, you should add the attribute

__attribute__((always_inline));

Koepel: Indeed, comparing pointers is always better, IDs are just for information purposes only

vbextreme : Thank you for you feedback.

The time ellasped is not as long as you said. I have measured 25µs in the simulator. This duration can be a little bit longer in certain cases. But you're right, I will fix that by letting interrupts enabled by default except for pushing and popping context from the stack.

Yes the third byte is saved. SP is always 16 bits. PC is 22bit and are automatically puhsed onto the stack when the ISR (naked) is called.

I use always attribute((always_inline) when I declare an inline function. Did you have got some warning during the compilation ?

In my previous post I said: "more than 20us" :wink:
Is very big time... You need disable interrupt only on restore StackPointer.

But when you call isr naked the compiler should not add anything. Have You checked?

GCC:
naked
Use this attribute on the ARM, AVR, C4x and IP2K ports to indicate that the specified function does not need prologue/epilogue sequences generated by the compiler. It is up to the programmer to provide these sequences.

post a disassebled code.

I not view in your code the attribute always_inline, example function call from isr is only inline.
this is correct way.

inline function() __attribute__((always_inline))

But when you call isr naked the compiler should not add anything. Have You checked?

A naked attribute means to not store any register. But the ISR is still called, so the PC is pushed on the stack like any function. The SP is stored just after registers (SPL and SPH).
From the datasheet:

During interrupts and subroutine calls, the return address Program Counter (PC) is stored on the Stack.

You need disable interrupt only on restore StackPointer.

True

Disassembled code:

0000071E  PUSH R31 Push register on stack 
0000071F  IN R31,0x3F In from I/O location 
00000720  ORI R31,0x80 Logical OR with immediate 
00000721  PUSH R31 Push register on stack 
00000722  PUSH R30 Push register on stack 
00000723  PUSH R29 Push register on stack 
00000724  PUSH R28 Push register on stack 
00000725  PUSH R27 Push register on stack 
00000726  PUSH R26 Push register on stack 
00000727  PUSH R25 Push register on stack 
00000728  PUSH R24 Push register on stack 
00000729  PUSH R23 Push register on stack 
0000072A  PUSH R22 Push register on stack 
0000072B  PUSH R21 Push register on stack 
0000072C  PUSH R20 Push register on stack 
0000072D  PUSH R19 Push register on stack 
0000072E  PUSH R18 Push register on stack 
0000072F  PUSH R17 Push register on stack 
00000730  PUSH R16 Push register on stack 
00000731  PUSH R15 Push register on stack 
00000732  PUSH R14 Push register on stack 
00000733  PUSH R13 Push register on stack 
00000734  PUSH R12 Push register on stack 
00000735  PUSH R11 Push register on stack 
00000736  PUSH R10 Push register on stack 
00000737  PUSH R9 Push register on stack 
00000738  PUSH R8 Push register on stack 
00000739  PUSH R7 Push register on stack 
0000073A  PUSH R6 Push register on stack 
0000073B  PUSH R5 Push register on stack 
0000073C  PUSH R4 Push register on stack 
0000073D  PUSH R3 Push register on stack 
0000073E  PUSH R2 Push register on stack 
0000073F  PUSH R1 Push register on stack 
00000740  CLR R1 Clear Register 
00000741  PUSH R0 Push register on stack 
00000742  IN R24,0x3D In from I/O location 
00000743  IN R25,0x3E In from I/O location 
00000744  STS 0x01C1,R25 Store direct to data space 
00000746  STS 0x01C0,R24 Store direct to data space

In my code, all functions declared in header files have attribute((always_inline)), then I define them in another header file.

Regards,

thk
good job
have good Life

Is it possible that the bootloader will be overwritten ?
I have no problem with an Arduino Uno, but a Arduino Leonardo gets a corrupted bootloader if I upload my sketch. The leds don't even blink with a hard reset (power up).

I don't have the message queue for serial is working yet, but I show you my sketch as it is, which corrupts the Leonardo bootloader:

// 2016, april, testing the os48 with Arduino Uno en Leonardo.
// 
// www.rtos48.com
//
// http://forum.arduino.cc/index.php?topic=347188.msg2705494#msg2705494
//

#include <os48.h>       // The only file you have to include

using namespace os48;   // This line is necessary

//Scheduler is a singleton, you don't have to create an instance because only one can exist.
Scheduler* scheduler = Scheduler::get(); //Get the instance

//Declare the task pointers as global vars to use them in the task functions.
Task* task1      = NULL; 
Task* task2      = NULL;
Task* task3      = NULL;
Task* taskSerial = NULL;
Task* taskBlink  = NULL;

const int buttons[] = { 8, 9, 10};
const int pinLed = 13;                  // use system led

// Override the Arduino weak yield (from inside the delay). 
// Use either the os48 yield, or the os48 sleep.
void yield()
{
  scheduler->yield();
}

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

  // Create three tasks that have the same function.
  task1      = scheduler->createTask( &funcButton, 100, PrNormal); //Creates a task associated to the 'func1' function with 60 bytes of stack memory.
  task2      = scheduler->createTask( &funcButton, 100, PrNormal);
  task3      = scheduler->createTask( &funcButton, 100, PrNormal);
  taskSerial = scheduler->createTask( &funcSerial, 100, PrNormal);
  taskBlink  = scheduler->createTask( &funcBlink,  100, PrLow);

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

  //...
  //Nothing will be executed here
}

void funcButton()
{
  // Use the function pointer to test which task this is.
  int index;
  // This task will run twice
  if( task() == task1)
    index = 0;
  else if( task() == task2)
    index = 1;
  else if( task() == task3)
    index = 2;

  int pinButton = buttons[index];         // pinButton can be any of the three buttons
  pinMode( pinButton, INPUT_PULLUP);

  boolean previousButton = false;
  for(;;)
  {
    char buffer[50];

    boolean currentButton = digitalRead( pinButton) == LOW ? true : false;   // low is a active button

    if( !previousButton && currentButton)
    {
      // The button has been pressed.
      sprintf_P( buffer, PSTR( "Hello, button %d has been pressed."), index);
      scheduler->sendMessage(taskSerial, new Message(0, buffer));
    }
    else if( previousButton && !currentButton)
    {
      // The button has been released.
      sprintf_P( buffer, PSTR( "Hello, button %d was released"), index);
      scheduler->sendMessage(taskSerial, new Message(0, buffer));
    }
    task()->sleep(20);        // check button 50 times per second, so bouncing will not overflow the message queue.
  }
}

void funcSerial()
{
  int full = 0;
  boolean error = false;
  boolean transmitted = false;
  
  for(;;)
  {
    // This is the only task that uses the Serial port. No need to OS48_NO_CS_BLOCK.
    // This task tries to transmit the message to the serial port.
    // If that is not possible during 1 second, an error is set.
    
    Message* mess = task()->waitNextMessage(0);       // look for message with id '0'

    if( error)
    {
      delete mess;
    }

    while( !transmitted)
    {
      // The Serial could slow down if the transmit buffer is full, or if the connection with
      // the computer is broken with a ATmega32U4.
      // The Serial.availableForWrite() could be used.
      int nAvailable = 0;          // Serial.availableForWrite();
      int nLen = strlen( mess->getBody().bStr);

      if( nAvailable > 0)                          // is at least one byte free in the buffer ?
      {
        // The whole message can not be printed right now.
        // That is not a big problem for an Arduino Uno, but the
        // connection could be lost with an Arduino Leonardo.
        full++;
        if( full > 2)
        {
          error = true;
        }
        task()->sleep(100);
      }
      else
      {
//        Serial.println(mess->getBody().bStr);
        delete mess;
      }
    }
  }
}

void funcBlink()
{
  pinMode( pinLed, OUTPUT);
  
  for(;;)
  {
    digitalWrite( pinLed, LOW);
    task()->sleep(400);
    digitalWrite( pinLed, HIGH);
    task()->sleep(100);
  }
}

void loop() {} //Useless now but you have to let it defined.

Is it possible that the bootloader will be overwritten

The program itself cannot corrupt the bootloader during the execution. AVR is a harvard architecture (prgm space belongs in flash in this case) and since OS48 doesn't write any data in flash (in which the bootloader is sotred), I don't think that's possible to corrupt the bootloader.

You said it's during the upload ? Did you try to push the reset button when the upload starts ? I got some issues with the Leonardo in the past but I didn't make the connection with my lib. Every time I had to push the reset button during the upload process, then my prgm started normally. I cannot help you more because I'm not at home during 10 days, so I'm very limited. Keep me up. Try to test with some minimal codes.

Concerning your test code, you generate a lot of Message instances. Pay close attention to memory used and fragmentation. To avoid fragmentation you can reserve more blocks for the memory pool provided by changing a parameter in Advanced_parameters.h file.

Moreover, I don't yet provided safe new and delete function (these should be atomic functions). The memory pool is safe but not yet the malloc and free function. When you create a message, the lib try first to reserve a memory pool block (6 availables by default for messages) and if no block is available the malloc is called. I advice to use a semaphore which surrounds new and delete function. You can also use OS48_NO_CS_BLOCK depending of which type of behaviour you want. But the best pratice is to not send 3 messages every 20ms :wink:

My sketch is a test to try to keep it working while there are to many messages. I haven't got that far yet.
I will try pressing reset, but that should not make a difference. My Leonardo has already the new bootloader. I can upload any other sketch in linux to my Leonardo without problem.
Somehow the programming of the bootloader is initiated. At this moment I don't know how to find the cause.

I am evaluating libraries for Arduino multitasking. It seems that no one offers a library with a Thread/Task class where the "loop" function is a class member function. Instead these functions are static/global functions. Not being wizened in the way of C++/Arduino subtleties is there some fundamental underlying reason why no library allows use of a member function?

Is there any reason why the library can't be refactored to support microsecond timing for sleep()?

Have been playing around with the library a bit. This is a GREAT library! Thanks for your hard work.

BTW: I implemented a usec sleep function, and monitored the elapsed times. Seems to work quite well, with only a 30usec overhead using coop, and 60usec using round robin with two trivial tasks. Wouyld be nice if usec sleep were part of the library

=========================

/**

  • Sleeps for a given duration, with usec resolution. Not affected
  • by Task::resume(). Task overhead adds about 30usec for cooperative
  • and 60usec for preemptive scheduling.
  • @param sleepUsec Sleep duration (usec).
    */
    void sleepUsec(unsigned long sleepUsec) {
    unsigned long sleepStart = micros();
    while (true) {
    Scheduler::get()->yield();

unsigned long sleepElapsed = elapsedTime(sleepStart, micros());
if (sleepElapsed >= sleepUsec) {
break; // timeout expired
}
}
}

unsigned long elapsedTime(unsigned long startTime, unsigned long stopTime) {
if(startTime <= stopTime) {
// no rollover
return stopTime - startTime;
} else {
// rollover
return ((-1) - startTime) + stopTime + 1UL;
}
}

Thanks for your sharing ! I will think about that.

BTW: Assembly is not my strong suit. Is there some reason why the library could not be modified to use a class instance method as the "callback" function? Is it just a matter of time, know-how and energy, or is there some fundamental reason?

Thanks
--jon

Does anybody know if the web page is broken for just me or everyone? Looks like name registration changed a few days back, now DNS returns unknown name for rtos48.come.