Go Down

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

dzives

This is because in early versions of my developments, this macro was the only one I have developed. This should change in my next delivery. Thanks.

dzives

#31
Dec 14, 2015, 10:05 pm Last Edit: Dec 15, 2015, 12:38 pm by dzives
I just released a new version - 1.10 - of my library with some changes :

  • 5 priorites on tasks instead of 3
  • messaging usage is simpler and a message can directly carry data (no just a pointer)
  • a task can just get a message without removing it from the messages queue with the peekMessage(...) function
  • work objects can be priorized (normal mode or urgent mode) and now contains a result value. A task can wait the end of execution of a work instance.
  • In addition of the macro OS48_ATOMIC_BLOCK: OS48_NO_KT_BLOCK and OS48_NO_CS_BLOCK to disable just the kernel interrupt or just the context switch
  • yieldTo, for the cooperative scheduler, can delay the yield until the next call of a blocking function such as Semaphore::acquire()
  • A memory pool implementation with a default configuration (in Advanced_paramters.h). Memory pools improve performance of dynamic allocations.
  • Statistics on tasks to print on a Serial object such as CPU time consumption and memory footprints...
  • Hook functions in the kernel interruptions for advanced users
  • More advanced parameters for advanced users in Advanced_paramters.h
  • Some other improvements and bugs fixed


Get the zip on http://www.rtos48.com

Enjoy

Koepel

It has been asked before, but I still have a question about the Arduino delay() and the weak yield() in the delay().

Using your sleep function in a task is normal of course, but libraries have often delays. I would like the maximum cpu usage, so I like to do other things while a library is in a delay.

Do you use the Arduino weak yield() to yield your own os48 ?
Would that also be okay, if the delay() is in a interrupt or while interrupts are disabled ?
I think the DHT11,DHT22,NeoPixels,DS18B20 have all specific timing and libraries often disable the interrupts for that. There should be no yield() when interrupts are disabled, to keep the timing accurate.

On your webpage, you say at "Requirement" this: (ex Arduino Nano....
I read the 'ex' as 'exclusive', that is confusing.

dzives

Hello Koepel,

Thank you for your feedback. Sorry for the "ex", that's a langage mistake.

Quote
Do you use the Arduino weak yield() to yield your own os48 ?
Not by default but you can call my sleep function by defining the yield function yourself. You can find a paragraph in my website in the "Special usage" page.


Quote
Would that also be okay, if the delay() is in a interrupt or while interrupts are disabled ?
The delay() function should not disable interrupts except for delayMicroseconds(). That depends on the expected level of accuracy (millisecond or microsecond close).

If you want the maximum CPU usage, you can call the os48 sleep function within the weak yield function.

For millisecond accuracy:
To be certain that the sleeping task (calling the delay() function through the library) will be woken at the expected millisecond, you should configure the OS by setting the preemptive policy to the scheduler and by giving the maximum priority to the sleeping task.

For microsecond accuracy:
You can disable interrupts if a library function "sleeps" for few microseconds (1 - 200 µs) without interfer with the OS.
Otherwise should not count on an OS especially on a 8bit 16MHz µC. The only solution is to delegate the work in a second chip as a specialized µC which be controlled by the main µC having the OS.

PS : When it still is possible, you can also adapt the source code of the library if the delay don't need to be accurate.

Regards,

Yves

Koepel

#34
Apr 11, 2016, 11:51 am Last Edit: Apr 11, 2016, 12:26 pm by Koepel
Thanks. I have read it now ;) and I understand the sleep with higher priority.

I wonder how many standard Arduino libraries are capable to handle a preemtive scheduler.
You use OS48_NO_CS_BLOCK when two tasks use the same serial port.
But if the Serial TX buffer is full, the Serial library waits until there is another byte free in the TX buffer. That could delay the sketch.
I suppose that would require a third task that accepts messages from the two other tasks and only the third task sends them to the Serial port without the BLOCK.

The createTask() has no parameter for the function of the task. What if I want to run the same task three times (three tasks with the same function running all three at the same time) ? The getCurrentTask() would always return the same function pointer.

Thank you for this awesome library with good documentation :) I'm testing it at this moment :o


dzives

#35
Apr 11, 2016, 02:01 pm Last Edit: Apr 11, 2016, 03:27 pm by dzives
Since the yield tweak in Arduino lib is quite newer, I don't think all third libraries handle this feature, and especially the preemptive schedulers.

As you said the code implementation for Serial (https://github.com/arduino/Arduino/blob/8385aedc642d6cd76b50ac5167307121007e5045/hardware/arduino/avr/cores/arduino/HardwareSerial.cpp)
seems wait for a free char space. However, I found an interresting function

Code: [Select]

int HardwareSerial::availableForWrite(void)
{
#if (SERIAL_TX_BUFFER_SIZE>256)
  uint8_t oldSREG = SREG;
  cli();
#endif
  tx_buffer_index_t head = _tx_buffer_head;
  tx_buffer_index_t tail = _tx_buffer_tail;
#if (SERIAL_TX_BUFFER_SIZE>256)
  SREG = oldSREG;
#endif
  if (head >= tail) return SERIAL_TX_BUFFER_SIZE - 1 - head + tail;
  return tail - head - 1;
}

 which test how much char can be written without wait. The best implementation with my OS is, as you said, create a printer task using the messaging feature (with the combination of a semaphore associated to the test of availableForWrite). I wrote an example in the tutorial with producers / consummer (http://rtos48.com/tuto/advanced_usage/#messages).

Quote
The createTask() has no parameter for the function of the task. What if I want to run the same task three times (three tasks with the same function running all three at the same time).
Just create 3 tasks by giving the same function pointer: http://rtos48.com/tuto/basics/#sharing-variables-functions

Is it what you expected?

You're welcome, don't hesitate to ask question. Note that I'm about to start a new version with some new features, facilities and performance improvements.


vbextreme

Interrupts are disabled while restoring the stack pointer?
I find it kinda dangerous to leave all of the context information in the stack.
good job

Have a good life.
Easy framework linguaggio C: https://github.com/vbextreme/EasyFramework
Hack your life: http://vbextreme.netai.net/
Unoffical Telegram group: https://telegram.me/joinchat/ALRu8ACkdTdXyz-2P7v13A

Koepel

#37
Apr 11, 2016, 03:49 pm Last Edit: Apr 11, 2016, 03:51 pm by Koepel
I forgot about the Serial.availableForWrite() function. A third task with the Serial.availableForWrite() will make it smoother I think.

Suppose I want to run three tasks, that do the same thing, but with different pins or variables or Serial ports or objects, or something like that. Then I need a variable on the stack that makes the difference.
For example three buttons and three leds (just as an example, it would not make sense to have three tasks for that), and a single task that reads the button and controls the led.

Code: [Select]

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

...

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

...

void func1(int index)
{
  pinMode( buttons[index], INPUT_PULLUP);
  pinMode( leds[index], OUTPUT);
  for(;;)
  {   
    int x = digitalRead( buttons[index]);
    ...


Is there another way to know which of the three tasks is running ?

dzives

vbextreme: Interrupts are disabled during the context switch process until all registers are restored (including the SP).

Everything is dangerous without MPU ;), in all case, with or without storing the context in the bss segment, a stackoverflow will cause damages. Note that only registers are pushed on the stack, all other task data are stored in a private struct. For example, FreeRTOS pushes the registers also onto the stack but it's still implementation dependent.

Regards,

Yves

vbextreme

#39
Apr 11, 2016, 04:23 pm Last Edit: Apr 11, 2016, 04:25 pm by vbextreme
you block interrupt for more that 20us, a little excessive do not you think?
the only critical moment in the context switch is the restore of SP and the Y registers.

have a good life.
Easy framework linguaggio C: https://github.com/vbextreme/EasyFramework
Hack your life: http://vbextreme.netai.net/
Unoffical Telegram group: https://telegram.me/joinchat/ALRu8ACkdTdXyz-2P7v13A

dzives

#40
Apr 11, 2016, 04:27 pm Last Edit: Apr 11, 2016, 05:06 pm by dzives
Koepel:

Quote
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:

Code: [Select]

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:

Code: [Select]

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.

dzives

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

Koepel

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.

dzives

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

Koepel

#44
Apr 11, 2016, 06:17 pm Last Edit: Apr 11, 2016, 06:19 pm by Koepel
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.

Go Up