Pages: [1] 2   Go Down
Author Topic: Arduino Due Task Scheduler  (Read 10261 times)
0 Members and 1 Guest are viewing this topic.
Quebec, Canada
Offline Offline
Newbie
*
Karma: 0
Posts: 39
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hi everybody! smiley-cool

What about this library: http://arduino.cc/en/Reference/Scheduler

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!
« Last Edit: November 06, 2012, 04:50:34 pm by Coding Badly » Logged

Offline Offline
Newbie
*
Karma: 0
Posts: 19
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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! smiley
Logged

0
Offline Offline
God Member
*****
Karma: 24
Posts: 587
Always making something...
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

I'm the source for most of those ambitions....  smiley-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.
Logged

Quebec, Canada
Offline Offline
Newbie
*
Karma: 0
Posts: 39
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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..
Logged

Offline Offline
Newbie
*
Karma: 0
Posts: 19
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.
Logged

Forum Administrator
Offline Offline
God Member
*****
Karma: 47
Posts: 629
I find plain exciting
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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... smiley


m
Logged

NZ
Offline Offline
Jr. Member
**
Karma: 1
Posts: 84
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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)
http://chrisbarlow.wordpress.com/2012/09/13/reliable-task-scheduling-with-arduino-avr/#comments

Just my 2c

Kim
Logged

0
Offline Offline
God Member
*****
Karma: 24
Posts: 587
Always making something...
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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?
Logged

0
Offline Offline
God Member
*****
Karma: 24
Posts: 587
Always making something...
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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.
« Last Edit: November 09, 2012, 10:12:09 am by Paul Stoffregen » Logged

Offline Offline
Newbie
*
Karma: 0
Posts: 2
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.

Code:
//
//  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__) */

Code:
//
//  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?
Logged

Forum Administrator
Milano, Italy
Offline Offline
Sr. Member
*****
Karma: 22
Posts: 292
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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.


Logged

C.

Forum Administrator
Milano, Italy
Offline Offline
Sr. Member
*****
Karma: 22
Posts: 292
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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

C.

Offline Offline
Newbie
*
Karma: 0
Posts: 2
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.
Logged

Forum Administrator
Milano, Italy
Offline Offline
Sr. Member
*****
Karma: 22
Posts: 292
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

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):
https://github.com/arduino/Arduino/blob/16b28ed74ca48eed1d7590d339f8781c139de756/hardware/arduino/sam/libraries/Scheduler/Scheduler.cpp#L32

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

C.

Germany
Offline Offline
Full Member
***
Karma: 10
Posts: 221
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hi everybody! smiley-cool

What about this library: http://arduino.cc/en/Reference/Scheduler

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?
Quote
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
Logged

Pages: [1] 2   Go Up
Jump to: