diff --git a/LeonardoMixerIO/doc/Dokumentation_Systemtechnik.pdf b/LeonardoMixerIO/doc/Dokumentation_Systemtechnik.pdf index e3bc35ae922d1c6e4c35b27a9324d4f1425c8866..fc73855e358c18006ebb4bde331466095077cc2c 100644 Binary files a/LeonardoMixerIO/doc/Dokumentation_Systemtechnik.pdf and b/LeonardoMixerIO/doc/Dokumentation_Systemtechnik.pdf differ diff --git a/LeonardoMixerIO/src/LeonardoMixer.cpp b/LeonardoMixerIO/src/LeonardoMixer.cpp index c5ebd12c66784e5b84c42302fad4a6ecad8410db..669c6b4c8b852193abc0d98c89a31efc5b58b5db 100644 --- a/LeonardoMixerIO/src/LeonardoMixer.cpp +++ b/LeonardoMixerIO/src/LeonardoMixer.cpp @@ -1,3 +1,4 @@ + // LeonardoMixer.cpp // Copyright 2016, 2017 Lukas Friedrichsen, Philipp Stenkamp // License: Modified BSD-License diff --git a/LeonardoMixerIO/src/Scheduler.cpp b/LeonardoMixerIO/src/Scheduler.cpp index 5bdb1aba430013701a17a532539fac9d56926836..36f10e1375016c52e8081da22a74cbaf4da45bfd 100644 --- a/LeonardoMixerIO/src/Scheduler.cpp +++ b/LeonardoMixerIO/src/Scheduler.cpp @@ -226,8 +226,8 @@ unsigned long Scheduler::getTaskTimerDiff (task *taskToGet) { // Handles the functions excluding send void Scheduler::perform (void) { if (taskCounter && *taskCounter) { - (*taskCounter)->activity(); updateTaskTimer((task *) *taskCounter); + (*taskCounter)->activity(); taskCounter++; } else if (nRtTasks) { @@ -245,6 +245,8 @@ void Scheduler::exterminate (void){ // Scheduler, called cyclical in the loop-function void Scheduler::schedule (void) { + long diff; + long schedTime = micros(); if (rtTasks) { unsigned long timerDiff = getTaskTimerDiff(rtTasks->listElement); unsigned long cycleTime = getTaskCycleTime((rtTask *) rtTasks->listElement); @@ -258,13 +260,14 @@ void Scheduler::schedule (void) { exterminate(); } } - rtTasks->listElement->activity(); updateTaskTimer(rtTasks->listElement); + rtTasks->listElement->activity(); rtTasks = sortRtTasks(rtTasks); if (nRtTasks){ setPriorities(); taskCounter = nRtTasks; } + diff = micros()-schedTime; } else if (nRtTasks) { perform(); @@ -279,4 +282,9 @@ void Scheduler::schedule (void) { } // No tasks... Nothing to do! } + + if (debugger) { + debugger->print("schedule runtime: "); + debugger->println(diff); + } } diff --git a/SchedulerPerformance/.gitignore b/SchedulerPerformance/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..934359b46eeaa32338143ad01df78b353ffad436 --- /dev/null +++ b/SchedulerPerformance/.gitignore @@ -0,0 +1,5 @@ +.pioenvs +.clang_complete +.gcc-flags.json +.piolibdeps +.pioenvs diff --git a/SchedulerPerformance/.travis.yml b/SchedulerPerformance/.travis.yml new file mode 100644 index 0000000000000000000000000000000000000000..2c4ff5c9885cd682b31b389d63a97cf8cee91f55 --- /dev/null +++ b/SchedulerPerformance/.travis.yml @@ -0,0 +1,65 @@ +# Continuous Integration (CI) is the practice, in software +# engineering, of merging all developer working copies with a shared mainline +# several times a day < http://docs.platformio.org/page/ci/index.html > +# +# Documentation: +# +# * Travis CI Embedded Builds with PlatformIO +# < https://docs.travis-ci.com/user/integration/platformio/ > +# +# * PlatformIO integration with Travis CI +# < http://docs.platformio.org/page/ci/travis.html > +# +# * User Guide for `platformio ci` command +# < http://docs.platformio.org/page/userguide/cmd_ci.html > +# +# +# Please choice one of the following templates (proposed below) and uncomment +# it (remove "# " before each line) or use own configuration according to the +# Travis CI documentation (see above). +# + + +# +# Template #1: General project. Test it using existing `platformio.ini`. +# + +# language: python +# python: +# - "2.7" +# +# sudo: false +# cache: +# directories: +# - "~/.platformio" +# +# install: +# - pip install -U platformio +# +# script: +# - platformio run + + +# +# Template #2: The project is intended to by used as a library with examples +# + +# language: python +# python: +# - "2.7" +# +# sudo: false +# cache: +# directories: +# - "~/.platformio" +# +# env: +# - PLATFORMIO_CI_SRC=path/to/test/file.c +# - PLATFORMIO_CI_SRC=examples/file.ino +# - PLATFORMIO_CI_SRC=path/to/test/directory +# +# install: +# - pip install -U platformio +# +# script: +# - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N diff --git a/SchedulerPerformance/LICENSE.txt b/SchedulerPerformance/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..fab0530340b8ab7bef448b8a291d075ef5cf4ea4 --- /dev/null +++ b/SchedulerPerformance/LICENSE.txt @@ -0,0 +1,7 @@ +LeonardoMixerIO +Copyright 2016, 2017 Lukas Friedrichsen, Philipp Stenkamp +License: Modified BSD-License + +Realtime RC mixer for Arduino-devices + +2017-02-10 diff --git a/SchedulerPerformance/Pseudocode.txt b/SchedulerPerformance/Pseudocode.txt new file mode 100644 index 0000000000000000000000000000000000000000..68f233e9a1a75b1f1a83a670d9d42edaa5145c1b --- /dev/null +++ b/SchedulerPerformance/Pseudocode.txt @@ -0,0 +1,91 @@ +r timer; +int taskCounter = 1; +int priorityCounter = 1; + +Tasks senden = new Task( + void *send (void){ + ... + } + , 0); + +Tasks mischen = new Task( + void *mix (void){ + ... + } + , 2); + +Tasks empfangen = new Task( + void receive (void){ + ... + } + , 1); + +Tasks *tasks = {senden, mischen, empfangen}; //Jedem Task muss eine eindeutige, fortlaufende Priorität gegeben werden! + +struct { + boolean empfangen; + int data; + input protocol; +} signal; + +signal *inputs = {...}; + +void scheduler (void){ + switch timer: + case >20ms: + resetTimer(); + task[0].activity(); + setPriorities(); + case <20ms: + perform(); +} + +void resetTimer (void){ + timer = 0; +} + +void aktualisiereTimer (void){ + timer += ticksseitletztemaktualisieren/tickfrequenz +} + +void setPriorities (void){ + if (alleEmpfangen()){ + task[1].priority = 1; + tasks[2].priority = 2; + } + else { + task[1].priority = 2; + tasks[2].priority = 1; + } + taskCounter = 1; + priorityCounter = 1; +} + +void perform (void){ + if (taskCounter > tasks.length()){ + taskCounter = 1; + priorityCounter++; + } + if (priorityCounter > tasks.length(){ + priorityCounter = 1; + } + if (tasks[taskCounter].priority == priorityCounter){ + tasks[taskCounter].activity(); + } + taskCounter++; +} + +boolean alleEmpfangen (void){ + boolean ready = true; + for (int i = 0; i <= input.length(); i++){ + ready = ready & input[i].empfangen; + } + return empfangen; +} + +void main (void){ + while (true){ + aktualisiereTimer(); + scheduler(); + } +} diff --git a/SchedulerPerformance/lib/readme.txt b/SchedulerPerformance/lib/readme.txt new file mode 100644 index 0000000000000000000000000000000000000000..9c196e2868378cc663483751768b0f8165ca8648 --- /dev/null +++ b/SchedulerPerformance/lib/readme.txt @@ -0,0 +1,36 @@ + +This directory is intended for the project specific (private) libraries. +PlatformIO will compile them to static libraries and link to executable file. + +The source code of each library should be placed in separate directory, like +"lib/private_lib/[here are source files]". + +For example, see how can be organized `Foo` and `Bar` libraries: + +|--lib +| |--Bar +| | |--docs +| | |--examples +| | |--src +| | |- Bar.c +| | |- Bar.h +| |--Foo +| | |- Foo.c +| | |- Foo.h +| |- readme.txt --> THIS FILE +|- platformio.ini +|--src + |- main.c + +Then in `src/main.c` you should use: + +#include <Foo.h> +#include <Bar.h> + +// rest H/C/CPP code + +PlatformIO will find your libraries automatically, configure preprocessor's +include paths and build them. + +More information about PlatformIO Library Dependency Finder +- http://docs.platformio.org/page/librarymanager/ldf.html diff --git a/SchedulerPerformance/platformio.ini b/SchedulerPerformance/platformio.ini new file mode 100644 index 0000000000000000000000000000000000000000..29dafa993fdb962570170d3a20734172717ae96d --- /dev/null +++ b/SchedulerPerformance/platformio.ini @@ -0,0 +1,14 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; http://docs.platformio.org/page/projectconf.html + +[env:leonardo] +platform = atmelavr +board = leonardo +framework = arduino diff --git a/SchedulerPerformance/src/PerformanceTest.cpp b/SchedulerPerformance/src/PerformanceTest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e437c4f636c6111bbfdc5b35d0a73fc2510db943 --- /dev/null +++ b/SchedulerPerformance/src/PerformanceTest.cpp @@ -0,0 +1,95 @@ +// PerformanceTest.cpp +// Copyright 2017 Lukas Friedrichsen, Philipp Stenkamp +// License: Modified BSD-License +// +// Test-Application for the Scheduler +// +// 2017-01-06 + + +#include <Arduino.h> +#include "Scheduler.h" + +Scheduler *scheduler; +rtTask rt1, rt2, rt3; +rtTask *rtTasks[] = { &rt1, &rt2, &rt3, NULL }; +nRtTask nrt1, nrt2; +//nRtTask *nRtTasks[] = { &nrt1, &nrt2, NULL }; +nRtTask *nRtTasks[] = { NULL }; + +long lastTime; + +void no_Function (void) +{ + return; +} + +void nrt1_Function (void) + { + //return;// do nothing + + long tmpTime = micros(); + + Serial.print("Timestamp nrt1: "); + Serial.print(tmpTime); + Serial.print(", Diff: "); + Serial.println(tmpTime - lastTime); + lastTime = micros(); + + } + +void nrt2_Function (void) + { + //return;// do nothing + + long tmpTime = micros(); + + Serial.print("Timestamp nrt2: "); + Serial.print(tmpTime); + Serial.print(", Diff (us): "); + Serial.println(tmpTime - lastTime); + lastTime = micros(); + + } + +void send_Function (void) + { + return; + long tmpTime = micros()-lastTime; + lastTime = micros(); + Serial.print("Cycletime send: "); + Serial.print(tmpTime); + Serial.print(", Diff (us): "); + Serial.println(tmpTime - rt1.cycleTime); + } + +void setup() + { + Serial.begin(9600); + nrt1.priority = 1; + nrt1.activity = nrt1_Function; + nrt1.timestamp = 0; + + nrt1.priority = 2; + nrt2.activity = nrt2_Function; + nrt2.timestamp = 0; + + rt1.activity = no_Function; + rt1.timestamp = 0; + rt1.cycleTime = 50000; + + rt2.activity = no_Function; + rt2.timestamp = 0; + rt2.cycleTime = 20000; + + rt3.activity = no_Function; + rt3.timestamp = 0; + rt3.cycleTime = 100000; + + lastTime = micros(); + + scheduler = new Scheduler(rtTasks, nRtTasks); + scheduler->setDebugger(&Serial); + } + +void loop() { scheduler->schedule(); } diff --git a/SchedulerPerformance/src/Scheduler.cpp b/SchedulerPerformance/src/Scheduler.cpp new file mode 100644 index 0000000000000000000000000000000000000000..341af42ab7a2019d4b69e72b0a6f71d37904428c --- /dev/null +++ b/SchedulerPerformance/src/Scheduler.cpp @@ -0,0 +1,290 @@ +// Scheduler.cpp +// Copyright 2016, 2017 Lukas Friedrichsen, Philipp Stenkamp +// License: Modified BSD-License +// +// Implementation of a cooperative multitasking based scheduler for Arduino-devices +// +// 2017-02-10 + + +#include "Scheduler.h" + +// Constructor with task-array as input-argument +Scheduler::Scheduler (rtTask **newRtTasks, nRtTask **newNRtTask) { + rtTasks = NULL; + nRtTasks = NULL; + taskCounter = NULL; + debugger = NULL; + listStoragePointer = NULL; + overloadCounter = 0; + nRtTaskCounter = 0; + setRtTasks(newRtTasks); + setNRtTasks(newNRtTask); +} + +// Sorts and sets the input-array as the new non-realtime-task-array; the last element of newNRtTasks HAS TO BE A NULLPOINTER to mark the end of the array +void Scheduler::setNRtTasks (nRtTask **newNRtTasks) { + if (newNRtTasks && newNRtTasks[0]) { + nRtTaskCounter = 0; + while (newNRtTasks[nRtTaskCounter]) { + nRtTaskCounter++; + if (nRtTaskCounter >= MAX_TASK_THRESHOLD) { + if (debugger) { + debugger->println("Number of non-realtime-tasks exceeds task threshold or nullpointer at the end of the array missing! Shutting down scheduler!"); + } + exterminate(); + } + } + nRtTasks = newNRtTasks; + sortTasks((task **) nRtTasks, 0, nRtTaskCounter-1); + taskCounter = nRtTasks; + } + else { + nRtTasks = NULL; + } +} + +// Sorts the realtime-task-array and converts it to a linked list; the last element of newRtTask HAS TO BE A NULLPOINTER to mark the end of the array +void Scheduler::setRtTasks (rtTask **newRtTasks) { + if (newRtTasks && newRtTasks[0]) { + listStoragePointer = listStorage; + int counter = 0; + while (newRtTasks[counter]) { + counter++; + if (counter >= MAX_TASK_THRESHOLD) { + if (debugger) { + debugger->println("Number of realtime-tasks exceeds task threshold or nullpointer at the end of the array missing! Shutting down scheduler!"); + } + exterminate(); + } + } + task **temp = (task **) newRtTasks; + sortTasks(temp, 0, counter-1); + rtTasks = convertToLinkedList(temp); + } + else { + rtTasks = NULL; + } +} + +// Sets a serial-port for debugging; error-messages will be printed there +void Scheduler::setDebugger (Serial_ *serial){ + debugger = serial; +} + +// Sorts the task-array so that the tasks are in order of their cycle-time / priority (if a non-realtime-task-array is given, getTaskCycleTime will return the tasks priority); left is the first, right the last position in the array to be sorted +void Scheduler::sortTasks(task **tasks, int left, int right) { + if (left < right && (right-left+1) > 1 && tasks && *tasks) { + + // Quicksort (O(n*log(n)) on average; recursive) + /*int middle = left+rand()%(right-left+1); + unsigned long test = getTaskCycleTime((rtTask *) tasks[middle]); + int l = left; + int r = right; + while (l <= r) { + while (getTaskCycleTime((rtTask *) tasks[l]) < test) { + l++; + } + while (getTaskCycleTime((rtTask *) tasks[r]) > test) { + r--; + } + if (l <= r) { + task *temp = tasks[l]; + tasks[l] = tasks[r]; + tasks[r] = temp; + l++; + r--; + } + } + sortTasks(tasks, left, r); + sortTasks(tasks, l, right);*/ + + // Heapsort (allways O(n*log(n)); iterative) + int length, start, heapLength, i, j; + task *reference; + + start = left; + heapLength = right-left+1; + length = heapLength/2+1; + + while (heapLength > 1) { + if (length > 1) { + reference = tasks[start+(--length-1)]; + } + else { + reference = tasks[start+heapLength-1]; + tasks[start+heapLength-1] = tasks[start]; + heapLength--; + } + i = length; + j = length*2; + while (j <= heapLength && heapLength > 1) { + if (j < heapLength && getTaskCycleTime((rtTask *) tasks[start+j-1]) < getTaskCycleTime((rtTask *) tasks[start+j])) { + j++; + } + if (getTaskCycleTime((rtTask *) reference) < getTaskCycleTime((rtTask *) tasks[start+j-1])) { + tasks[start+i-1] = tasks[start+j-1]; + i = j; + j *= 2; + } + else { + j = heapLength + 1; // Terminates the shift-down + } + } + tasks[start+i-1] = reference; + } + } +} + +// Places the given element in the linked list based on when it has to be called next; if only one realtime-task exists, the list isn't modified +linkedListElement * Scheduler::sortRtTasks (linkedListElement *tasks) { + linkedListElement *reference = tasks; + linkedListElement *counter = (linkedListElement *) reference->next; + if (counter && (signed long)(getTaskCycleTime((rtTask *) reference->listElement)-getTaskTimerDiff(reference->listElement)) > (signed long)(getTaskCycleTime((rtTask *) counter->listElement)-getTaskTimerDiff(counter->listElement))) { + while (counter->next && (signed long)(getTaskCycleTime((rtTask *) reference->listElement)-getTaskTimerDiff(reference->listElement)) > (signed long)(getTaskCycleTime((rtTask *) counter->listElement)-getTaskTimerDiff(counter->listElement))){ + counter = (linkedListElement *) counter->next; + } + linkedListElement *temp = (linkedListElement *) reference->next; + reference->next = counter->next; + counter->next = reference; + return temp; + } + else { + return reference; + } +} + +// Converts the given realtime-task-array to a bidirectional linked list element +linkedListElement * Scheduler::convertToLinkedList (task **taskArray) { + linkedListElement *firstElement = NULL; + linkedListElement *listElementPointer = NULL; + while (*taskArray) { + linkedListElement *new_pointer = newLinkedListElement(); + new_pointer->listElement = *taskArray; + new_pointer->next = NULL; + if (listElementPointer) { + listElementPointer->next = new_pointer; + } + else { + firstElement = new_pointer; + } + listElementPointer = new_pointer; + taskArray++; + } + return firstElement; +} + +// Returns a pointer to a new linked list element from the array listStorage +linkedListElement * Scheduler::newLinkedListElement (void){ + if (listStoragePointer){ + return listStoragePointer++; + } + else { + if (debugger) { + debugger->println("Can't initialize any more linked list elements! Shutting down scheduler!"); + } + exterminate(); + } +} + +// Sets the timer of the given task to the current time in microseconds +void Scheduler::updateTaskTimer (task *taskToUpdate) { + taskToUpdate->timestamp = micros(); +} + +// Dynamic prioritiy-allocation to prevent starvation +void Scheduler::setPriorities (void) { + sortTasks((task **) nRtTasks, 0, nRtTaskCounter-1); + taskCounter = nRtTasks; + int counter = 0; + while (taskCounter && *taskCounter){ + if (getTaskTimerDiff((task *) *taskCounter) >= STARVATION_THRESHOLD){ + nRtTask *temp = *taskCounter; + memmove(nRtTasks+1, nRtTasks, counter*sizeof(void *)); + *nRtTasks = temp; + } + counter++; + taskCounter++; + } +} + +// Returns the cycle time of the given realtime-task +unsigned long Scheduler::getTaskCycleTime (rtTask *taskToGet) { + return taskToGet->cycleTime; +} + +// Returns the priority of the given non-realtime-task +unsigned long Scheduler::getTaskPriority(nRtTask *taskToGet) { + return taskToGet->priority; +} + +// Returns the difference between the value of timestamp of the given task and the current time in microseconds +unsigned long Scheduler::getTaskTimerDiff (task *taskToGet) { + return (micros() - taskToGet->timestamp); +} + +// Handles the functions excluding send +void Scheduler::perform (void) { + if (taskCounter && *taskCounter) { + updateTaskTimer((task *) *taskCounter); + (*taskCounter)->activity(); + taskCounter++; + } + else if (nRtTasks) { + taskCounter = nRtTasks; + } +} + +// The system switches to a defined state (do nothing) in case of a error. +void Scheduler::exterminate (void){ + if (debugger) { + debugger->println("An error occured! Programm terminated!"); + } + while (true); +} + +// Scheduler, called cyclical in the loop-function +void Scheduler::schedule (void) { + long diff; + long schedTime = micros(); + if (rtTasks) { + unsigned long timerDiff = getTaskTimerDiff(rtTasks->listElement); + unsigned long cycleTime = getTaskCycleTime((rtTask *) rtTasks->listElement); + if (timerDiff >= cycleTime) { + if (((timerDiff-cycleTime)*100/cycleTime) > OVERLOAD_THRESHOLD_PERCENT) { + overloadCounter++; + if (overloadCounter > OVERLOAD_THRESHOLD_TIMES) { + if (debugger) { + debugger->println("Capacity overload! Delay between theoretical and practical cycle-time exceeds threshold! Shutting down scheduler!"); + } + exterminate(); + } + } + updateTaskTimer(rtTasks->listElement); + rtTasks->listElement->activity(); + rtTasks = sortRtTasks(rtTasks); + if (nRtTasks){ + setPriorities(); + taskCounter = nRtTasks; + } + diff = micros()-schedTime; + if (debugger) { + debugger->println("Scheduler::schedule runtime (us): "); + debugger->println(diff); + } + } + else if (nRtTasks) { + perform(); + } + } + else if (nRtTasks) { + perform(); + } + else { + if (debugger) { + debugger->println("No tasks... Nothing to do!"); + } + // No tasks... Nothing to do! + } + +} diff --git a/SchedulerPerformance/src/Scheduler.h b/SchedulerPerformance/src/Scheduler.h new file mode 100644 index 0000000000000000000000000000000000000000..324e773637aed645178434c4655b4602f6f3b0b7 --- /dev/null +++ b/SchedulerPerformance/src/Scheduler.h @@ -0,0 +1,66 @@ +// Scheduler.h +// TODO License +// Lukas Friedrichsen, 2016-12- +// + +#ifndef Scheduler_h +#define Scheduler_h + +#include <Arduino.h> +#include "Scheduler.h" + +#define MAX_TASK_THRESHOLD 10 +#define OVERLOAD_THRESHOLD_PERCENT 10 +#define OVERLOAD_THRESHOLD_TIMES 1000 +#define STARVATION_THRESHOLD 100000 + +struct task +{ + void (*activity) (void); // The schedulers only purpose is scheduling the tasks, not to process any data. Therefor the function doesn't accept input-arguments nor returns any value. + unsigned long timestamp; +}; + +struct rtTask: task +{ + unsigned long cycleTime; +}; + +struct nRtTask: task +{ + unsigned long priority; +}; + +struct linkedListElement +{ + task *listElement; + void *next; +}; + +class Scheduler +{ + public: + Scheduler (rtTask **newRtTasks, nRtTask **newNRtTask); + void schedule (void); + void setRtTasks (rtTask **newRtTasks); + void setNRtTasks (nRtTask **newNRtTasks); + void setDebugger (Serial_ *debugger); + + private: + linkedListElement *rtTasks, *listStoragePointer, listStorage[MAX_TASK_THRESHOLD]; + nRtTask **nRtTasks, **taskCounter; + unsigned int overloadCounter, nRtTaskCounter; + Serial_ *debugger; + void sortTasks (task **tasks, int left, int right); + linkedListElement * sortRtTasks (linkedListElement *tasks); + linkedListElement * convertToLinkedList (task **tasks); + linkedListElement * newLinkedListElement (void); + void updateTaskTimer (task *taskToUpdate); + void setPriorities (void); + unsigned long getTaskCycleTime (rtTask *taskToGet); + unsigned long getTaskPriority (nRtTask *taskToGet); + unsigned long getTaskTimerDiff (task *taskToGet); + void perform (void); + void exterminate (void); +}; + +#endif