PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : einfaches multitasking mit arduino



inka
28.11.2016, 15:42
hallo,
neulich fand ich auf garage_lab (http://www.vielsichtig.de/index.php?id=120)

folgenden


#include <Servo.h>
const int SERVO_PIN = 6;
const int BUTTON_PIN = 8;
const int LED_PIN = 13;
Servo SRV_servo;
int SRV_pos;
boolean SRV_moveFoward;
long SRV_nextWakeUp;
int LED_value;
boolean LED_moveFoward;
long LED_nextWakeUp;

void setup()
{
pinMode(LED_PIN, OUTPUT);
pinMode(BUTTON_PIN, INPUT);
// init the compontent’s states
SRV_servo.attach(SERVO_PIN);
SRV_pos = 0;
SRV_moveFoward = true;
SRV_nextWakeUp = millis();

LED_value = 0;
LED_moveFoward = true;
LED_nextWakeUp = millis();
}

void loop()
{
long currentTime = millis();
long nextWakeUpSRV = SRV_doStep(currentTime);
long nextWakeUpLED = LED_doStep(currentTime);
long nextMinWakeUp = (nextWakeUpSRV < nextWakeUpLED) ? nextWakeUpSRV : nextWakeUpLED;
delay(nextMinWakeUp - currentTime);
}

/* ** SERVO ** */
long SRV_doStep(long currentMillis)
{
if (currentMillis > SRV_nextWakeUp)
{
if (SRV_moveFoward)
{
SRV_pos += SRV_getIncrement();
if (SRV_pos > 180)
{
SRV_moveFoward = false;
SRV_pos = 180;
}
} else
{
SRV_pos -= SRV_getIncrement();
if (SRV_pos < 0)
{
SRV_moveFoward = true;
SRV_pos = 0;
}
}
SRV_servo.write(SRV_pos);
SRV_nextWakeUp = currentMillis + 100;
}
return SRV_nextWakeUp;
}

int SRV_getIncrement()
{
if (digitalRead(BUTTON_PIN))
{
return 1;
} else
{
return 5;
}
}

/* ** LED ** */
long LED_doStep(long currentMillis)
{
if (currentMillis > LED_nextWakeUp)
{
if (LED_moveFoward)
{
LED_value += 3;
if (LED_value >= 200)
{
LED_moveFoward = false;
}
} else
{
LED_value -= 3;
if (LED_value <= 0)
{
LED_moveFoward = true;
}
}
analogWrite(LED_PIN, LED_value);
LED_nextWakeUp = currentMillis + 1;
}
return LED_nextWakeUp;
}


er funktioniert (https://youtu.be/Xf8AL0CzHVk) super, ich habe den ursprungscode um eine zweite LED und einen SR-04 erweitert und wollte ihn hier einfach mal weitergeben. Eine passage im code (in der loop) verstehe ich allerdings nicht:


long nextMinWakeUp = (nextWakeUpSRV < nextWakeUpLED) ? nextWakeUpSRV : nextWakeUpLED;
delay(nextMinWakeUp - currentTime);


könnte mir bitte jemand erklären wozu er ist? Der code funktioniert nämlich auch ohne die passage, zumindest erkenne ich keinen unterschied :-(

i_make_it
28.11.2016, 16:28
Dazu muß man die Notation "long nextMinWakeUp = (nextWakeUpSRV < nextWakeUpLED) ? nextWakeUpSRV : nextWakeUpLED;" kennen.
http://forum.arduino.cc/index.php?topic=205334.0

Das entspricht also:

IF nextWakeUpSRV < nextWakeUpLED
THEN nextMinWakeUp = nextWakeUpSRV
ELSE nextMinWakeUp = nextWakeUpLED

Damit wird dann schlußendlich das "delay(nextMinWakeUp - currentTime)" mit einem Zeitwert gefüttert.
Lässt man es weg läuft "void loop();" einfach schneller durch.

Kann man weglassen kann man aber auch behalten wenn die CPU zu schnell ist.

Da das ganze ein kooperatives Multitasking ist, wird mit nextWakeUpSRV und nextWakeUpLED von den einzelnen Tasks mitgeteilt, wie lange es dauert bis sie wieder auf der Zeitscheibe dran sind.
Da bei Batteriebetrieb ein Delay() schon mal etwas Energie spart, ist is dort sinnvoll.
Denn dann wartet die CPU immer bis der Task mit der kleineren Restwartezeit drangenommern werden will.
Springt dann beide Tasks an und bekommt die aktualisierte Restwartezeit zurück.
Werden es dann mal mehr als zwei Tasks, wird dieser Codeteil natürlich umfangreicher oder man lässt ihn halt weg.

inka
28.11.2016, 16:57
aha, danke :-)

Werden es dann mal mehr als zwei Tasks, wird dieser Codeteil natürlich umfangreicher oder man lässt ihn halt weg.
bedeutet, man muss dann von den vorhandenen tasks mit einer verschachtelten if/then/else abfrage den task mit der kürzesten wartezeit finden und die ist dann das nächste "nextMinWakeUp". Spielen die anderen dann noch eine rolle? Ok, ich denke nein, weil ja die kürzeste wartezeit bei jedem durchlauf von loop neu berechnet wird...

botty
28.11.2016, 17:25
inka,
du kannst diese Rechenspielerei getrost weglassen.
Bei den AVRs bringt ein delay keinerlei Vorteile, da hier kein Sleep-Mode aktiviert wird, der Energie sparen könnte (selbst im Due Code wird das nicht gemacht).

Sinnvoller wäre es, wenn du mal das Schlüsselwort "static" und dessen Funktion nachliesst und dann mal die ganzen globalen Variablen elemenieren würdest. Dann wird dein Code im Laufe der Zeit übersichtlicher und damit lesbarer.

Meine 2cent

botty

Sisor
28.11.2016, 19:58
Hi inka,

in die Richtung hab ich auch mal was gebastelt, um die Sketches mit mehreren gezeiteten Aufgaben übersichtlich zu halten:

#include "SimpleTimerMillis.hpp"
using Timer = SimpleTimerMillis;
#define SEC *1000

void taskA() {
static Timer t(2 SEC, Timer::FOREVER, Timer::START_IMMEDIATELY);
if(t.elapsed()) Serial.println("TaskA");
}

void taskB() {
static Timer t(0.4 SEC, Timer::FOREVER, Timer::START_IMMEDIATELY);
if(t.elapsed()) Serial.println("TaskB");
}

void setup() {
Serial.begin(9600);
}

void loop() {
taskA();
taskB();
}
Die Mini-Bibliothek dazu:

#ifndef __SIMPLETIMER__
#define __SIMPLETIMER__

class SimpleTimerMillis {
using millis_t = unsigned long;
public:
static const bool ONCE = false;
static const bool FOREVER = true;
static const bool START_IMMEDIATELY = true;

SimpleTimerMillis() = default;
SimpleTimerMillis(millis_t timeSpan, bool forever, bool start=false)
: timeSpan(timeSpan), isContinous(forever) {
if(start) this->start();
}
void start() {
startTime = millis();
}
void start(millis_t timeSpan) {
this->timeSpan = timeSpan;
start();
}
bool elapsed() {
bool elapsed = millis()-startTime > timeSpan;
if(elapsed && isContinous) start();
return elapsed;
}
bool isCountious() {
return isContinous;
}
void once() { isContinous = false; }
void forever() { isContinous = true; }
private:
bool isContinous = false;
millis_t startTime = 0;
millis_t timeSpan = 0;
};

#endif //__SIMPLETIMER__

HaWe
28.11.2016, 21:51
wenn es komplizierter wird, wird das mit den Timern höchst unübersichtlich und ineffizient.

Daher gibt es ja auch für die AVR Arduinos MT-Libs:
http://forum.arduino.cc/index.php?topic=347188.0
http://www.rtos48.com/

für den Due gibt es ja schon ewig die Scheduler Lib und auch Babix,
https://www.arduino.cc/en/Reference/Scheduler
http://forum.arduino.cc/index.php?topic=318084.0

doch seit nicht allzulanger Zeit gibt es auch Scheduler-Libs für AVR:
http://playground.arduino.cc/Code/Scheduler
http://playground.arduino.cc/Code/TaskScheduler

inka
30.11.2016, 09:29
hallo allerseits,

danke für euere tipps, ich werde mich nun etwas genauer mit RTOS48 auseinandersetzen, da scheint die doku (auf den ersten blick) verständlich und recht ausführlich zu sein...

@botty:


Sinnvoller wäre es, wenn du mal das Schlüsselwort "static" und dessen Funktion nachliesst und dann mal die ganzen globalen Variablen elemenieren würdest. Dann wird dein Code im Laufe der Zeit übersichtlicher und damit lesbarer.

so ganz genau habe ich glaube ich nicht verstanden wie du das meinst? Da es aber nichts mit multitasking zu tun hat wäre evtl. ein seprater thread /tutorial für die anwendung von globalen, lokalen variablen, von static, const int und defines, extern und was es sonst noch so gibt in der richtung definition und deklaration, mit beispielen und code-schnipseln (so ähnlich wie sisors tutorial zur erstellung von libs). Was hälst du davon? So aus der sicht eines praktikers und nicht von der theoretischen seite her? Würden sicher viele begrüßen! In diesem sinne...

HaWe
30.11.2016, 11:57
ein seprater thread /tutorial für die anwendung von globalen, lokalen variablen, von static, const int und defines, extern und was es sonst noch so gibt in der richtung definition und deklaration, mit beispielen und code-schnipseln (so ähnlich wie sisors tutorial zur erstellung von libs).

ich denke, da kaufst du dir am besten mal ein Lehrbuch zu C nd C++ oder suchst Tutorials im web, denn die gibt es da zu Hauf, u.a. bei www.cplusplus.com .
gerade hier kannst du auch sehr gut über Suchbegriffe gezielt Infos zu C/C++ Befehlen oder Bezeichnern finden, samt Programmier-Beispielen. Ich nutze es selber sehr gern als Quelle.