Abläufe steuern, Initialisieren, Vermeidung von GoTo's

Hi Freaks,

Ich habe hier im Forum gelesen, dass die Verwendung von 'goto'eher Pfui in C++ ist.

In einem Projekt muss ich nun zum Initialisieren ein Ventil motorisch zu/ Auf fahren, bis der Endschalter gekommen ist.

In der Referenz finde ich den Goto Befehl mit dem Besispiel zum Herausspringen aus einer Schleife. Alos denke ich mir das so, dass ich das Initialisieren anschubse, getaktet zu fahre, dann getaktet Auf fahre bis der jeweilige Enschalter angesprochen hat. Ich habe dies, lt. Besispiel mit einer For Schleife überlegt, wobei bei Erreichen des Endpunkts der Schleife ein alarm initiert werden kann, andernfalls wird das Programm beim Anfahren des jeweiligen Endschalters fortgesetzt.

Is es einfach/ sinnvoll NICHT mit der Goto Funktion an dieser Stelle zu arbeiten?

//Temepraturregelung über Adruino
// Gegeben: Drehventil mit Motorantrieb, ca. 30 auf/zu, Endlagenkontakte
// Temp Erfassung mit PTC/Spannungsteiler, Solltemperatur über Spindelpoti, Vergleich 2x analog in
// Ziel: Bei Temperaturanforderung Heizen oder Kühlen bis beide Analogsignale in der Hysterese sind
// Takten des Motors über Zeit/ PWM um breite Anpassung an Mechanik zu ermöglichen
// Später auch Einbindung von PID in einen Regler

// Variablen:
int PlusK = 200;   // der Motor dreht für diese Zeit das Kühlventil AUF
int MinusK = 200;  // der Motor dreht für diese Zeit das Kühlventil ZU
int K_ZU = 0;      // Kühlventil ist ZU, Endlage, int. Pullup aktiviert
int K_AUF = 0;     // Kühlventil ist AUF, Endlage, int Pullup aktiviert
int HysTemp = 20;  // XXX Einheiten + - Abweichung ohne Reaktion auf Regelausgang.

int KuhlPlus = 5;  // Treiber für Kühlventil Öffnen auf Pin5
int KuhlMinus = 6; // Treiber für Kühlventil Schleiessen auf Pin 6
int HeizSSR = 7;   // Treiber für SSR, langzeit PWM, daher digital out, Pin 7
int Lampe = 13;    // mit LEd 13 fahren/ Regeln anzeigen

int Kalib = 1;      // Kalibrieren beim Starten. Zum direkten Regeln auf 0 setzten

int FahrenHi = 50; // LED ist an für XXX ms wenn Ventil fährt
int FahrenLo = 200;// LED ist aus für XXX ms wenn Ventil fährt

int RegelnHi = 20; // LED ist an für XXX ms, wenn System regelt
int RegelnLo = 500;// LED ist aus für xxx ms, wenn system regelt

int Fehler = 250;  // LED blinkt mit Takt XXX Ein/ Aus

int TempFue = 1;   // Temperaturfühler auf analog Pin
int TempPoti = 2;  // Poti auf analog Pin

int KaliZ = 0;     // Hilfsvariable Kalibrierung Zu
int KaliA = 0;     // Hilfsvariable Kalibrierung Auf
int Kali = 0;      // Hilfsvariable Kalibrierung Kalibrierung notwendig
int k1 = 0;        // Zahlerschleife Hilfsvariable
int k2 = 0;        // Zählerschleife Hilfsvariable

long time = 0; // Zeitpunkt erfassen zum takten

//später evtl Einlesen aus LCD, unrechnen in Realtemperaturen

void setup()
{

pinMode(KuhlPlus,OUTPUT);
pinMode(KuhlMinus,OUTPUT);
pinMode(HeizSSR,OUTPUT);
pinMode(Lampe,OUTPUT);

pinMode(K_ZU,INPUT);
digitalWrite(K_ZU,HIGH);
pinMode(K_AUF,INPUT);
digitalWrite(K_AUF,HIGH);

}

void loop()
{
// Initialisieren: Ventil fährt im takt nach AUF, dan auf ZU, Testen der Endlagen
// Hier passiert was

if(Kalib >= 0)
{
KaliBrieren();
}
}




// Definition der Funktionsgruppen Kallibrieren, Regeln, 

void KaliBrieren()
// schließe Kühlventil bis zum Endtaster
{    
for( k1 = 0; k1 < 501; k1++);  // 500 steps nach Zu
if(k1 > 0 && K_ZU == HIGH)     // Schleife UND Endlagenschlater offen
{
analogWrite(KuhlMinus,255);    // Takt ZU
delay(MinusK);
analogWrite(KuhlMinus,0);
delay(500);
}
if (digitalRead(K_ZU) == 0)    // Endlage erreicht
{ 
goto OPEN;
}

OPEN:
for(k2 = 0; k2= 500; k2++);
{
analogWrite(KuhlPlus,255);
delay(PlusK);
analogWrite(KuhlPlus,0);
delay(500);
}
if (digitalRead(K_AUF) == 0)
{ 
goto ReGeln;
}

ReGeln:
// zu Ende
delay(2);  // Hier steht was
}

THX4Ur help

Greetz, Linpo

Du verwendest das goto eigentlich nur um die Schleife abzubrechen. Dafür ist eigentlich das "break"da.

for( k1 = 0; k1 < 501; k1++);  // 500 steps nach Zu

So zählst Du k nur von 1 bis 500 ohne irgend eine andere funktion auszuführen.
Laß den ";" weg und setze den zu wiederholenden Programmteil in geschwungene Klammern.

Zum Abbruch der For Schleife:
Break;
Andererseits könntest Du die Schleifenvariable auch so setzen daß die Endbedingung der For Schleife erfüllt ist.
zB
for( k1 = 0; k1 < 501; k1++)
{
if (endschalter erreicht) k1=501;
}

Glaub uns goto braucht man nicht. (außer in einigen Sonder-Sonderfällen)

Grüße Uwe

Goto bietet sich theoretisch an um aus tief verschachtelten for-schleifen wieder rauszukommen, wo break nur die aktuelle Schleife abbricht (wobei z.B. in Java break auch mit labels funktioniert). Aber ich habe sowas noch nicht gebraucht. :slight_smile:

Donald Knuth hat einen Artikel geschrieben (als Antwort auf Dijkstras "Go To statement considered harmful" und die daraus entstandene Kontroverse) und gezeigt, dass in bestimmten Situationen goto die beste Wahl für guten Code ist (ab Seite 20):
http://pplab.snu.ac.kr/courses/adv_pl05/papers/p261-knuth.pdf

Das wurde allerdings Anfang der 70er geschrieben und muss auch auf dem Hintergrund der damaligen Sprachen und Hardware gesehen werden. Der Druck zu Optimieren war da selbst bei relativ einfachen Aufgaben sehr groß. Einige der Dinge (z.B. Quicksort-Implementierungen) macht heute kaum jemand per Hand es sei denn um was zu lernen.

Danke

liebe Gurus...

Und: Ja, Goto wird nicht gebraucht... (gehezu ist irrelevant :wink: )

die 501 sind jetzt erstmal als Trockenübung gedacht. Später, im scharfen Einsatz will ich diesen Wert anpassen, also z.B. 2 Ventile, ein fährt flott, da brauch es mit 100ms, 128 PWM, 130 Schritte um von einer Position in die andere zu fahren. ein anderes, auch nur ein Beispiel, benötigt mit 300ms, 255 PWM 328 Schritte. Dies will ich halt in den Variablen anpassen um die Elektronik mit der Mechanik zu verbinden.

Die For Schleife mache ich dann ein wenig größer, d.h. wenn der Antrieb mehr Schritte als die ausgemessenen benötigt, dann kann ich so eine Fehlermeldung kreieren

aber wie gesagt, später...

Jetz laufen erstmal die beiden For Schleifen ab, toggeln munter zur Endlage und dann die andere Richtung anzusteuern.

Als Gimmick kann aus den Werten im Regeln ja noch die aktuelle Position berechnet werden..

Mi piace di Adruino !!

Greetz, Linpo

Da haben wir uns falsch verstanden.

mit

for( k1 = 0; k1 < 501; k1++);  // 500 steps nach Zu
if(k1 > 0 && K_ZU == HIGH)     // Schleife UND Endlagenschlater offen
{
analogWrite(KuhlMinus,255);    // Takt ZU
delay(MinusK);
analogWrite(KuhlMinus,0);
delay(500);
}
if (digitalRead(K_ZU) == 0)    // Endlage erreicht
{ 
goto OPEN;
}

willst Du nicht 500 Schritte machen und bei jedem Schritt das Ventil für eine gewisse Zeit ansteuern und dann kontrollieren ob der Endtaster erreicht ist?

Der genannte Programmteil macht das aber nicht.
Der zitierte Programmteil zählt k von 0 bis 500, macht dann 1 Impuls auf KuhlMinus, kontrolliert 1 mal ob der Endschalter erreicht ist und fertig.

Grüße Uwe

; - ) <-- Bei diesem Smiley ist das Semikolon wichtig; in deinem Code ist [ Nachtrag zweimal ] eins zu viel. :wink:

... wollte uwe dir damit sagen

Dies ist auch ein klassischer Fehler:

if (bedingung); 
{
  // das hier wird immer ausgeführt, egal ob bedingung true ist oder nicht
}

Ich fürchte C-Compiler grinsen heimlich, wenn sie sowas sehen, denn sie sind fies und heimtückisch.

Das überhaupt so aus der Loop auszulagern bringt generell nichts. Da kannst da ruhig deinen Code direkt reinschreiben.

Loop() ist ja nur eine versteckte unendliche while-Schleife die von der Arduino Software erzeugt wird. Wahrscheinlich um den Anwendern klar zu machen wie die Grundstruktur aussieht.

Per Hand würde man das so schreiben:

while(true)
{
}

oder

while(1)
{
}

Und das hier macht auch nichts:

if (digitalRead(K_AUF) == 0)
{ 
     goto ReGeln;
}

ReGeln:
delay(2);

delay(2) wird so oder so ausgeführt. Sollte die Abfrage nicht in die for-Schleife? Dann musst du da nicht nur den Strichpunkt entfernen, sondern auch noch ein paar geschweifte Klammern um den ganzen Block setzen.

Ich weiß nicht ob, dir die Formatierung durch das Kopieren verloren gegangen ist, aber normalerweise nimmt da Tabs um das übersichtlicher zu machen (anstatt alles an den Anfang der Zeile zu schreiben). z.B.:

void kalbrieren()
{
    for(int i = 0; i < 10; i++)
    {
        if(i == bedingung)
            break;
    }

    delay(2);
}

Was sich bei so was in manchen Situationen anbietet ist vorzeitig die ganze Methode zu beenden. Dann einfach statt "break" ein "return" schreiben (Rückgabe-Wert hier nicht nötig, da void). Dann würde er dir hier die Methode sofort abbrechen und nach loop zurückkehren. Das delay am Ende würde aber nicht mehr ausgeführt!
Man macht das aber z.B. wenn man in einem Array einen Wert sucht. Wenn er gefunden wurde, kann man einfach "return wert" machen.

Und das hier ist auch komplett falsch

Linpotec:

int K_ZU = 0;      // Kühlventil ist ZU, Endlage, int. Pullup aktiviert

int K_AUF = 0;     // Kühlventil ist AUF, Endlage, int Pullup aktiviert
...
pinMode(K_ZU,INPUT);
digitalWrite(K_ZU,HIGH);
pinMode(K_AUF,INPUT);
digitalWrite(K_AUF,HIGH);
...
if (digitalRead(K_ZU) == 0)    // Endlage erreicht
{
goto OPEN;
}
...
if (digitalRead(K_AUF) == 0)
{
goto ReGeln;
}

Die beiden Variablen beinhalten ja den Pinstatus und nicht die Pinnummer.

Der Originalartikel "Goto considered harmful" hat sich gar nicht so sehr über Gotos im speziellen ausgelassen Considered harmful - Wikipedia, http://david.tribble.com/text/goto.html. Er hat sich meines Erachtens mehr darüber ausgelassen, daß man bessere Mittel zur Flußkontrolle bräuchte. Entlang dieser Gedankenkette müßte man heutzutage auch darüber nachdenken solche Konstrukte wie for und while Schleifen in Frage zu stellen. Immerhin gibt es ja schlauere Compiler und solche Dinge wie "map / reduce". Davon abgesehen war es immer nötig - und wird es wohl auch noch lange bleiben - bei Bedarf auf "low level" Konstrukte zuzugreifen. Und wenn es hilft, dann ist GOTO nicht notwendig die schlimmste Lösung.

Allerdings gibt es anscheinend auch heute noch Codierer denen einige der besseren Alternativen zu GOTO entgangen sind :wink:

xkcd: goto :slight_smile: