Arduino Due Task Scheduler

Hi everybody! 8)

What about this library: Scheduler - Arduino Reference

I heard anybody talking about that library..is this multitask? does it work well? it's says that it's experimental but what's the restriction..do any function or library use with the scheduler work? do someone try it!

Thanks!

Looking at how they've rewritten the delay() call to use yield() under the hood in the 1.5.1 release and the ambitions for 1.5.2 (on the mailing list) I'd say they're pretty serious about this library.

I think it looks awesome and I'll start working with it as soon as I have the time to rewrite my home-brew scheduler! :slight_smile:

I'm the source for most of those ambitions.... :wink:

My intention is to implement something like Java's Timer object, which can work on both AVR and ARM. Hopefully that can work together with the Scheduler library too.

The downside behind a true multi-threaded scheduler, like that library, is you need to allocate memory for the stack of each thread, instead of just letting all the available memory be available to a single stack. For a system where you design everything, or with libraries that have very well documented stack usage, it can work reasonably well. But on Arduino, there are lots of libraries with unpublished requirements, so it's quite difficult to choose the sizes for each stack.

Still, I'm sure some people will find the Scheduler library very useful, especially when more of the official Arduino functions call yield() and when 3rd party libraries start doing the same.

Ok thanks! But what do the yield() function..i read about it on the scheduler library page but it's no very clear to me because english is not my first language and i'm not sure about this function..

micnossub: yield() will let other competing threads execute and when they've had a go, continue this one as soon as possible.

It's a great ambition Paul!

I thought about the same problem with allocating stacks. The size limit isn't a problem, as you're exposing it in the start calls. If some library needs more, sooner or later that's going to pop up on their tutorials. Basically right now all the old libraries are used to having what... 4K or SRAM? If you gave every thread 4K you could have 24 threads on the Due (OK, probably more like 16 but anyway). Not many will do more than that and if they do, I think they'd know how to tweak their stack sizes. Hmm, it IS a problem on the AVR platforms, though...

I've done it differently in my application, sharing stack spaces and switching tasks upon the return of the last, ie return wait(20). Now that's basically trying to do the same thing you're doing but instead of putting "yield()" calls on strategic places in blocking libraries, I would need to rewrite them to be non-blocking. It's not impossible, every library I've seen can be rewritten since the interfaces are all wrapping code like "while(!done()) ; return result;". This is crazy to me, but that's the way it is. It's easier for beginners and that's what Arduino is all about.

So your way is better. Easier to get working quickly and more in line with the Arduino goal. I guess we could help each other out - I'll rewrite my application to use your library, then I can help you with testing and adding yield() calls to the other libraries I use: USBHost with ADK, Wire, and a bunch of Wire-based I2C drivers.

speaking about ambition: we also want to make it happen and we added that scheduler library to Arduino 1.5 exactly because we wanted to work with the community so that it becomes a part of the future Arduino API

So this ambition has multiple sources... :slight_smile:

m

Maybe some consideration should be given to a "Time Triggered" OS
Link here to a free book
http://www.tte-systems.com/books/pttes
Also they have quite a few seminars on YouTube.

Their hybrid system is a co-operative scheduler with just one interrupt.
This system gives a lot of predictability (in terms of time) and by allowing just one interrupt, all the issues relating to pre-emptive multi-tasking are virtually bypassed.

Their system is often used in safety related applications, ie car, planes,

Here is a link to a port to Arduino (well a beginning anyway)

Just my 2c

Kim

Massimo, I'm glad to hear you feel the same.

My hope is we can work together more, hopefully with a more efficient way to discuss API stuff? The recent discussion on the developer list regarding yield(), merely an empty function only in 1.5.x, took numerous messages over 1 week. Actually, it's been years I've been begging for an empty yield() function to facilitate building cooperatively scheduled concurrency features.... so I've very happy it's now an official API. But how ever are we going to work together efficiently if merely deciding on such a simple thing takes so long?

Kim, that "Time Triggered" approach is more or less what I'm doing.

My implementation will be a bit different, but in the case of an empty loop() and a schedule chosen where each task completes before the next is scheduled, it will work out to be almost identical.

Arduino is used by novices who utilize very sophisticated libraries and commonly copy-and-paste examples from web pages in a "trial and error" approach, with little or no understanding of their cpu, memory, or other technical requirements. To be genuinely useful for novices, the system needs to be simple enough to understand, and it also needs to be free of hidden pitfalls.

The Time Triggered approach (I believe) meets the simplicity requirement. An Arduino-style API is needed to replace the static task table. The implementation on that page uses preemptive scheduling and interrupt context execution, and it lacks safety checks for reentrant execution. That's fine if the tasks are all carefully designed to always execute within their intended time slots, but it will fail disastrously if they are not.

However, with a different implementation, built on top of yield() responding to a flag set by the interrupt routine, and also with checks to avoid reentrant execution, I believe can work very well. If the users build their tasks to execute within their assigned time slots, and they leave loop() empty, the result will be nearly identical to that Time Triggered approach, with just a tiny bit of extra overhead. But if they also pour lots of code into loop() or create tasks that execute for lengthy times (or forever), or write non-reentrant code (as is found on most examples across the web), or use complex libraries, it can still work reasonably well. The user can obviously schedule more than the CPU is capable of executing, but the failure modes are all simple and easy to comprehend. It can avoid trouble with all the non-reentrant Arduino objects like String. Variables can be shared between tasks and loop() without special locking for atomic access, and without even "volatile" type. Nearly all existing libraries can work as expected.

At least that's my goal. Simple and able to work well without carefully designing stack sizes and execution times. I also have some ideas how making this all work nicely when the know-thy-stack-sizes Scheduler library is also used.... but that's yet another API conversation, and I'm still a bit weary from merely the yield() conversation.

I'm trying to access the library in my own library. I've tried everything to get it to compile without warnings. I'm including <Scheduler.h> as my second preprocessor directive (<Arduino.h> being my first). I've also externally defined the instance of SchedulerClass Scheduler into my header and .cpp files. For some reason, the compiler is throwing a warning "warning: undefined reference to `wait(int)" or "'wait' was not declared in this scope" if I don't externally reference the function. Since I don't have a Due yet, I can't actually see if the warnings will affect anything. yield() still links fine, though. This is where I'm confused. I can't find the wait() function in the Scheduler library. Is there another place it could be, and if so, does anybody know where it would be? I've attached my source as it relates to the Scheduler.

//
//  MusicalRobots.h
//  
//
//  Created by  on 11/1/12.
//
//

#ifndef ____MusicalRobots__
#define ____MusicalRobots__

#include <Arduino.h>
#include <Scheduler.h>

#include "MR_IO.h"
#include "MR_Solenoid.h"
#include "MR_Stepper.h"
#include "MR_Motor.h"

#include "MIDI.h"
#include "MR_MIDI.h"

extern bool DISPLAY_MENU;
extern MIDI_Class MIDI;
extern SchedulerClass Scheduler;

void INIT();

void THREAD_MIDI_HANDLING();
void THREAD_GENERIC_IO();
void THREAD_UPDATE_GRAPHICS();
void THREAD_DISPLAY_DRIVER();
void THREAD_ROBOT_DRIVER();

#endif /* defined(____MusicalRobots__) */
//
//  MusicalRobots.cpp
//  
//
//  Created by on 11/1/12.
//
//
#include "MusicalRobots.h"

//external variables
extern bool DISPLAY_MENU; //menu resource semaphore/mutex
extern SchedulerClass Scheduler;
//extern void wait(int);

void INIT() {
	//setup our I/O Pins
	IOInitializePins();

	//begin serial communication with LCD
	IOSetupLCD();

	//begin MIDI on the default channel
	InitializeMIDI(MR_MIDI_DEFAULT_CHANNEL);

	//initialize our thread functions as actual threads
	Scheduler.startLoop(THREAD_MIDI_HANDLING);
	Scheduler.startLoop(THREAD_GENERIC_IO);
	Scheduler.startLoop(THREAD_UPDATE_GRAPHICS);
	Scheduler.startLoop(THREAD_DISPLAY_DRIVER);
	Scheduler.startLoop(THREAD_ROBOT_DRIVER);
}

//setup our OS threads
//handles MIDI I/O
void THREAD_MIDI_HANDLING() {
	//parse the MIDI data
	MIDIBuffer();
	yield();
}

//handles buttons and encoders
void THREAD_GENERIC_IO() {
	//impliment debouncing Finite State Machine for toggling menues

	//parse quadrature encoders

	wait(20);
	yield();
}

//handles updating screen text
void THREAD_UPDATE_GRAPHICS() {
	if (DISPLAY_MENU) DisplayMenu();
	else DisplayRealTimeStatus();


	wait(200);
	yield();
}

//actually drives serial display
void THREAD_DISPLAY_DRIVER() {
	//write serial data to the display every 200 uS
	//update display

	//wait for .4 s
	wait(400);
	yield(); //this takes a while to do, and can be interrupted
}

//drives motors and other output
void THREAD_ROBOT_DRIVER() {


	wait(20);
}

//we'll update settings in the regular loop()
//save something for system idle process

void setup() {
	INIT();
}

void loop() {
	yield();
}

Any thoughts?

ecstipan

use delay(..) instead of wait(..).

The Scheduler library is going to be a library subject to a lot of change in the coming weeks, as you can see from the threads, and the API will adjust accordingly.
These changes involves not only the Scheduler library itself but also the Arduino Core and other libraries as well.

We probably should refer this discussion thread from the documentation reference.

I've updated the documentation (removed the wait(..) reference).

Thanks. That solved a headache. I'll keep up with the documentation to get any further updates. I think the power of this library is outstanding. Granted, you can't get the level of customization making your own ISR's, implementing your own stacks and queue's, and sorting based on an EDF (Earliest Deadline First) algorithm, but for ease-of-use, this takes the cake. Just a few questions, though. Does this library use RMS or EDF, or some other scheduling paradigm? Also, is there going to be support for semaphores or mutex's? Finally, how much overhead does the scheduler use - so I could, for instance, determine whether deadlines are guaranteed with the scheduler or not? Just some food for thought. Thanks for the help.

ecstipan

the scheduler policies you're mentioning are used mainly on preemptive RTOS, where the CPU manage process interruption/resume.
The Scheduler library in Arduino does a much simpler cooperative scheduler: its the sketch's author that decide when its best to switch task, and it is done using yield() or delay() commands. Processes are called in turn in a round robin queue. This means that a task can take CPU time for many seconds if its not written correctly: if some RT deadline are met or not is up to the programmer.

I didn't measured the context-switch time, but i guess it should be very short, the cost is the call to the function coopDoYield (a bunch of assembly instructions) and coopSchedule (that in the 99% of cases is just an assignment unless a process terminate):

Mutex will be probably provided in the coming version of the library, btw process synchronization with cooperative scheduling is much simpler.

micnossub:
Hi everybody! 8)

What about this library: Scheduler - Arduino Reference

I heard anybody talking about that library..is this multitask? does it work well? it's says that it's experimental but what's the restriction..do any function or library use with the scheduler work? do someone try it!

Thanks!

How should I understand this?

Ideally yield() should be used in functions that will take awhile to complete.

yield() should put on the funktion that takes the most time? Or on funktions that takes long?

Markus

From what I can tell, yield has a similar effect to the task actually exiting; it simply moves focus to the next task. I don't think you even need to use it in some circumstances. 'suspend()' may be a better name.

It is handy for breaking a 'long' operation; for example calculating PI to 6000 places in one hit halts everything else on the system for quite some time.

Data buffers could overflow if they aren't read often enough; SPI may be interrupt driven, but the code using it may not be, and will require a slice of time to act on the data.

Yes, yield() is useful if you write a program than does a lot of work and you care about letting other stuff run.

But its main purpose is for libraries that wait. For example, inside Serial.print(), if the transmit buffer is full, yield() is called while it waits for space in the buffer.

working porting (still in development) of the DUE Sceduler to the UNO (Avr Core)

http://vbextreme.netai.net/2015/12/26/embedded-arduino-scheduler-patre-1-1/

hey
i used this library with another one it is plc library but starting from the second loop it's not working only first loop that i had it's result in the arduino board . knowing that compilation was done succefuly