Archiv verlassen und diese Seite im Standarddesign anzeigen : Welche Möglichkeiten der Fehlersuche hat man beim Arduino?
Hallo,
nachdem ich nun schon 2 Tage lang einen Programmfehler suche, bin ich mit meinem Latein am Ende und versuche es doch mal hier mit folgender Frage:
ich nutze bisher nur die Möglichkeit, diverse Variablen an verschiedenen Stellen des Programmablaufs an den seriellen Monitor auszugeben.
Außerdem habe ich den Prgrammablauf als Flussdiagramm dargestellt, um ihn besser überblicken zu können.
Das Ergebnis ist, dass ich die Situation, in der der Fehler auftritt relativ genau kenne, aber trotzdem nicht verstehe, wieso es genau passiert.
Das Fatale bei der Sache ist, dass der Ablauf (mehrere Antriebe sollen nacheinander in eine bestimmte Position fahren und auf Tasterbetätigung dort stehen bleiben) nach einem Neustart prima funktioniert, aber wenn das nach dem ersten, vollständigen Ablauf durch die Betätigung und Wiederfreigabe eines STOP-Schalters noch mal ablaufen soll, dann macht das nur der erste Motor und die Restlichen melden sofort, dass sie fertig sind. Dabei spielt es keine Rolle, dass die Antriebe bereits in der richtigen Stellung stehen, denn das tun sie beim ersten Mal auch.
Nun erwarte ich natürlich nicht, dass mir jemand sagen kann, wo der Fehler liegt.
Ich suche aber nach einer Möglichkeit, durch einen Interrrupt, der auf eine Veränderung einer Variablen reagiert und die Position im Programm ausgibt, die genaue Stelle im Programm zu finden.
Ist sowas möglich?
vG
fredyxx
i_make_it
14.09.2016, 11:58
Fehlersuche in einem Programm ist ohne Quellcode schon mal zielmlich schwierig (bis unmöglich).
auf Verdacht sage ich mal in "void setup()" initialiserst Du was und in "Void loop()" wird es verändert aber nicht wieder auf den Startwert gesetzt"
ggf. mal versuchen entweder die Initialisierung von "void Setup()" nach "void Loop()" zu verlagern (so das es bei jedem Durchlauf einmal ausgeführt wird)
oder einen eigenen Abschnitt schreiben der in "void loop()" dafür sorgt das Zustandsmerker auf den Startwert gesetzt werden.
Es gibt auch Debug Möglichkeiten für Arduino:
http://www.visualmicro.com/page/Debugging-for-Arduino.aspx
Ups, habe den Post von Mxt vor meinem Update nicht angezeigt bekommen.
Na ja doppelt schadet ja nicht
(http://www.visualmicro.com/page/Debugging-for-Arduino.aspx)
Hallo,
tja, man kann in die kleinen Dinger nicht so gut hineinsehen. ;)
Ich Suche aber nach einer Möglichkeit, durch einen Interrrupt, der auf eine Veränderung einer Variablen reagiert
Technisch betrachtet würde man dafür ein Board brauchen, das Hardwaredebugging unterstützt. Außer dem Zero geht das auf keinem Arduino, das findet man eher bei anderen Boards wie ST Nucleo. In so einem Falle kann man mit Tools wie dem hier
http://visualgdb.com/
von Visual Studio aus debuggen.
Eine Art Softwaredebugging für Arduinos kann die kostenpflichtige Variante des Visual Studio Plugins von Visual Micro
http://www.visualmicro.com/post/2012/05/05/Debug-Arduino-Overview.aspx
Beide habe ich mir aber bisher immer sparen können. Irgendwie bin ich immer über serielle Ausgaben dem Übel auf die Schliche gekommen. Am besten geht das, wenn der Code in Form von State Machines strukturiert ist und man sich bei jedem Zustandswechsel Ausgaben einbaut, die zeigen von wo nach wo gewechselt wurde.
Mit dem STOP-Schalter hatte ich bereits ein Unterprogramm gestartet, in dem m.E. alles Nötige auf Anfang gesetzt wird. Das habe ich noch mal überprüft und um noch einiges ergänzt. Ohne Erfolg.
"void Setup()" nach "void Loop()" nützt auch nichts.
Für das VisualStudio müsste ich wohl erst mal einen Lehrgang machen, weil insbesondere Englisch nicht zu meinen Stärken gehört.
Im Moment bin ich der folgender Meinung:
Das Programm springt in ein UP ohne über den Anfang des UP's rein zu kommen, weil:
im hinteren Teil des UP's (da wo die Variable Motor fertig = true gesetzt wird; das ist eine von nur 2 Stellen im gesamten Programm) initiiere ich eine Ausgabe an den Monitor, die auch kommt, während eine andere Ausgabe an den Monitor direkt am Anfang des UP's gar nicht erscheint. Ich habe auch überprüft, dass die Sprungmarke, die zu dieser Stelle führt, im Programm sicher nur von Sprungbefehlen im eigenen UP angesprungen wird und die liegen alle natürlich hinter dem UP-Anfang.
Kann da irgendwo eine vom Kompiler berechnete Sprungadresse vermurkst sein?
Da fällt mir dann erst mal nichts mehr zu ein.
vG
fredyx
Achso, jetzt fällt es mir erst auf. Beim Wort "Unterprogramm". Du bist der von letztens mit den gotos.
Ja, da wundert das nicht.
In C / C++ sind es häufig diese Fehler:
1) = statt == in logischen Abfragen
2) break in switch-Blöcken vergessen
3) Arrayindex größer als Arraygröße
4) Integerdivision
5) Division durch Null
6) Nicht inizialisierte Pointer
7) falsche Schleifenbedingung
Kann da irgendwo eine vom Kompiler berechnete Sprungadresse vermurkst sein?
- Nein.
Sprungmarken außerhalb von Switch-Case-Blöcken solltest du nach Möglichkeit vermeiden, genauso wie den Sprungbefehl 'goto'. Nicht weil es nicht funktioniert, sondern weil es zu schwer durchschaubarem Spaghetti-Code führt.
Nicht weil es nicht funktioniert, sondern weil es zu schwer durchschaubarem Spaghetti-Code führt.
Das hat es schon
https://www.roboternetz.de/community/threads/69608-Programm-ablauf-langsam/page2?p=630697&viewfull=1#post630697
Achso, jetzt fällt es mir erst auf. Beim Wort "Unterprogramm". Du bist der von letztens mit den gotos.
Ja, da wundert das nicht.
Was soll das heißen?
Ich kann mir kein umfangreiches Programm ohne UP's vorstellen. Das trägt doch eindeutig zur Übersichtlichkeit bei, wenn es mal klappt. Außerdem können große Programmteile übersprungen werden, die für einen bestimmten Ablauf nicht benötigt werden und verkürzen dadurch die Programmlaufzeit.
Außerdem springe ich mit goto nicht nach vorne , sondern nur nach hinten!!
vG
fredyxx
- - - Aktualisiert - - -
In C / C++ sind es häufig diese Fehler:
1) = statt == in logischen Abfragen
2) break in switch-Blöcken vergessen
3) Arrayindex größer als Arraygröße
4) Integerdivision
5) Division durch Null
6) Nicht inizialisierte Pointer
7) falsche Schleifenbedingung
- Nein.
Sprungmarken außerhalb von Switch-Case-Blöcken solltest du nach Möglichkeit vermeiden, genauso wie den Sprungbefehl 'goto'. Nicht weil es nicht funktioniert, sondern weil es zu schwer durchschaubarem Spaghetti-Code führt.
1) = statt == in logischen Abfragen
ja, ist mir bekannt, habe ich vor kurzem erst noch praktiziert
break in switch-Blöcken vergessen werde ich prüfen
Arrayindex größer als Arraygröße Habe ich nicht verwendet
4) Integerdivision und 5) Division durch Null werde ich prüfen
Nicht inizialisierte Pointer Was ist das?
falsche Schleifenbedingung ??
vG
fredyxx
i_make_it
14.09.2016, 17:47
"void Setup()" nach "void loop()" nützt auch nichts.
Davon hatte ich auch nichts gesagt.
Sondern das was in "void Setup()" steht nach "void loop()" zu verschieben.
Also
void Setup() {
bla
bla
von hier
bla
bla
}
void loop() {
nach hier
code
code
noch mehr code
}
Bei Arduino wird "void Setup()" immer genau einmal beim Start ausgeführt. "void loop()"dagegen wird in Endlosschleife ausgeführt bis der Strom weg ist ode rein Reset erfolgt.
"void Setup()" kann man also sogar gezielt dazu nutzen um einen Code beim Einschalten immer genau einmal auszuführen.
wenn Du allerdings mit Gotos um dich wirfst, dann ist Das was Du bekommen hast nicht weit.
Gotos kann man nehmen wenn man damit klar kommt.
Bei Assembler hat sogar nur Jump (also Goto) aber da muß man halt aufpassen das man auch immer wirklich da raus kommt wo man hin will (also zu der Situation)
Um code in bestimmten Situationen zu überspringen gibt es z.B. IF und CASE. Da ist Quasi ein GOTO eingebaut.
Dann gibt es noch Funktionen und Subroutinen (was Du Unterprogramme nennst). Bei einem Microcontroller mit genau einem Programm im Speicher gibt es keine Unterprogramme.
Die Hauptschleife void loop() enthällt dann den code der immer durchlaufen werden muß und der Rest ist in Funktionen ausgelager.
Das spart Laufzeit und verhindert Spagetti Code.
Das von mir vorgeschlagene Verlagern der Initialisierung wird demnach vermutlich nichst bringen, kannst Du also lassen.
Geh mal die Fehlersuche von Sisor durch und druck dir deinen Code aus.
Dann nimm Textmarker und makiere alle Funktionsblöcke vom Beginn "{" bis Ende "}".
Hast Du richtig codiert, darf sich keine Linie mit einer anderen überschneiden.
Dann nimm Deine GoTOs und Deine Sprungmarken die Verbindungen dürfen dann auch nicht in verschiedenen Funktionsblöcken liegen.
Wenn Ja liegt eigentlich ein bedingter Sprung vor also mußst Du vor dem GOTO oder nach der Sprungmarke dafür sorgen das die Bedingungen auch in jedem Fall immer erfüllt werden.
Lustig wird es wenn von mehreren Gotos auf eine Sprungmarke gesprungen wird und dabei X Bedingungen vorliegen weil Variablenwerte nicht mehr korrekt verändert werden.
Da ich sehr viele Quick and dirty Batch Scripte in Windows Umgebungen schreibe, bin ich mit den Fallstricken von GOTO ziemlich vertraut.
Vor allem Da Notepad nicht wirklich eine IDE ist die auf Syntax oder Kontext prüft.
Nur bin ich meist mit meinem Script schon am Arbeiten wenn die Programmierer in ihrer IDE ein Projekt eröffnet haben und anfangen wollen loszucoden.
Also Analysier mal Deinen Code auf Papier, da wird vermutlich rauskommen das Deine Sprünge die Grenzen von Funktionsblöcken verletzen und deshalb Variablen nicht die richtigen Werte bekommen.
Falls nicht ein Syntaxfehler vorliegt den die IDE nicht als solchen erkennt.
Wie bei Vergleich und Zuweisung. Beides an sich syntaktisch richtig aber im Kontext eines Vergleichs ist die Syntax einer Zuweisung halt falsch.
Was soll das heißen?
Ich programmiere schon seit über 35 Jahren und habe schon genug solcher Programme gesehen. Nach dem ersten Blick auf den Code in dem zitierten Thread und war damals schon klar, dass es zu einem Thread wie diesem kommen würde.
Ich kann mir kein umfangreiches Programm ohne UP's vorstellen. Das trägt doch eindeutig zur Übersichtlichkeit bei
Du redest davon, tust es aber nicht. Das Konzept in C um Programme zu Strukturieren sind Funktionen nicht gotos.
Statt wie bei dir
switch(irgendwas)
{
case 0:
Mx =42;
goto Mblub;
break;
// ...
Mblub:
// code ...
goto Mbob;
// anderes Zeug
Mbob:
// nochmehr Code
goto Nochweiter;
ist die Sprache eigentlich so gedacht
void blub(int paran)
{
// Der Code hinter dem einen Label
}
void bob()
{
// Der Code beim anderen
}
// ...
switch(irgendwas)
{
case 0:
blub(42);
bob();
break;
// ...
und nicht kilometerlanger Bandwurmcode in dem mit goto rumgehüpft wird.
Dann hätten einmal mehr Leute überhaupt Lust sich das anzusehen und man kann jede Funktion mit Ausgaben versehen.
Eventuell auch mit zusätzlichen Parametern, die nur dazu dienen in der Ausgabe anzuzeigen, woher der Aufruf kam.
Also in der Art
void foo(int caller, int arg)
{
Serial.print("Aufruf von");
Serial.print(caller);
Serial.print(" mit ");
Serial.println(arg);
}
und bei der Verwendung
// Eine Stelle
foo(1, M42);
// woanders
foo(2, M42);
Danke für die ausführliche Info. Was ich UP nenne, nennst du offensichtlich Funktion. Soll mir recht sein.
Zum Thema goto hier mal ein Beispiel. mir ist klar, dass man das auch anders machen kann, aber ist es hier nicht sinnvoll?
if (dir == 0 && (micros() - M5_microsalt) > Schrittdauer) { // Drehrichtung
switch (M5_i) { // es soll nur ein Schritt ausgeführt werden.
case 0:
M5_i = 1;
goto M5_0;
break;
case 1:
M5_i = 2;
goto M5_1;
break;
case 2:
M5_i = 3;
goto M5_2;
break;
case 3:
M5_i = 4;
goto M5_3;
break;
case 4:
M5_i = 5;
goto M5_4;
break;
case 5:
M5_i = 6;
goto M5_5;
break;
case 6:
M5_i = 7;
goto M5_6;
break;
case 7:
M5_i = 0;
goto M5_7;
break;
}
} // ************ ENDE if (dir == 0 && (micros() - M5_microsalt) > Schrittdauer)
else if (dir == 1 && (micros() - M5_microsalt) > Schrittdauer) { // Drehrichtung
switch (M5_i) { // es soll nur ein Schritt ausgeführt werden.
case 0:
M5_i = 1;
goto M5_7;
break;
case 1:
M5_i = 2;
goto M5_6;
break;
case 2:
M5_i = 3;
goto M5_5;
break;
case 3:
M5_i = 4;
goto M5_4;
break;
case 4:
M5_i = 5;
goto M5_3;
break;
case 5:
M5_i = 6;
goto M5_2;
break;
case 6:
M5_i = 7;
goto M5_1;
break;
case 7:
M5_i = 0;
goto M5_0;
break;
}
M5_0:
digitalWrite(p1, HIGH);
digitalWrite(p2, LOW);
digitalWrite(p3, LOW);
digitalWrite(p4, LOW);
goto M5_Schritt;
M5_1:
digitalWrite(p1, HIGH);
digitalWrite(p2, HIGH);
digitalWrite(p3, LOW);
digitalWrite(p4, LOW);
goto M5_Schritt;
M5_2:
digitalWrite(p1, LOW);
digitalWrite(p2, HIGH);
digitalWrite(p3, LOW);
digitalWrite(p4, LOW);
goto M5_Schritt;
M5_3:
digitalWrite(p1, LOW);
digitalWrite(p2, HIGH);
digitalWrite(p3, HIGH);
digitalWrite(p4, LOW);
goto M5_Schritt;
M5_4:
digitalWrite(p1, LOW);
digitalWrite(p2, LOW);
digitalWrite(p3, HIGH);
digitalWrite(p4, LOW);
goto M5_Schritt;
M5_5:
digitalWrite(p1, LOW);
digitalWrite(p2, LOW);
digitalWrite(p3, HIGH);
digitalWrite(p4, HIGH);
goto M5_Schritt;
M5_6:
digitalWrite(p1, LOW);
digitalWrite(p2, LOW);
digitalWrite(p3, LOW);
digitalWrite(p4, HIGH);
goto M5_Schritt;
M5_7:
digitalWrite(p1, HIGH);
digitalWrite(p2, LOW);
digitalWrite(p3, LOW);
digitalWrite(p4, HIGH);
goto M5_Schritt;
Das ganze wiederholt sich viel zu oft unnötigerweise.
Statt viele Male
digitalWrite(p1, wert);
digitalWrite(p2, wert);
digitalWrite(p3, wert);
digitalWrite(p4, wert);
würde einmal
void SetBits(int v1, int v2, int v3, int v4)
{
digitalWrite(p1, v1);
digitalWrite(p2, v2);
digitalWrite(p3, v3);
digitalWrite(p4, v4);
}
reichen was man dann z.B. als SetBits(LOW, HIGH, HIGH, LOW); aufruft. Dann hat das Programm schon viele Zeilen verloren.
So müsste man mehrmals da drüber gehen. Am Ende hätte das Programm vielleicht noch 10 % der jetzigen Länge. Dann hätte man schon viel für die Fehlersuche gewonnen.
- - - Aktualisiert - - -
Als Ergänzung, selbst wenn im obigen Falle p1 bis p4 Variablen sind, könnte man sowas machen
struct MotorPin
{
int p1;
int p2;
int p3;
int p4;
};
const MotorPin pins[] = { {1, 2, 3, 4}, {5, 6, 7, 8} , /* mehr Pins */}; // nur Beispielwerte
void SetBits(int motor, int v1, int v2, int v3, int v4) // Motornummer beginnt hier bei 0
{
digitalWrite(pins[motor].p1, v1);
digitalWrite(pins[motor].p2, v2);
digitalWrite(pins[motor].p3, v3);
digitalWrite(pins[motor].p4, v4);
}
Hallo,
während der Fehlersuche frage ich mich, wie ein Serial.Print-Befehl im Programm verarbeitet wird.
Bleibt das Programm an dieser Stelle stehen, bis z.B. eine Quittung über den erfolgreichen Sendevorgang vorliegt oder wird der Befehl nur initiiert und das Programm setzt die Abarbeitung der folgenden Befehle unverzüglich fort.
Wie ist das bei 4 -5 Serial.print's direkt hintereinander? Kann zwischen den einzelnen Serial.print's noch eine Programmabarbeitung erfolgen, so dass die auf dem Monitor angezeigten Werte nicht exakt den selben Programmzustand zeigen?
Nur zur Info: ich habe " Serial.begin (250.000);" eingestellt.
vG
fredyxx
Peter(TOO)
15.09.2016, 08:35
Hallo fredyxx,
während der Fehlersuche frage ich mich, wie ein Serial.Print-Befehl im Programm verarbeitet wird.
Bleibt das Programm an dieser Stelle stehen, bis z.B. eine Quittung über den erfolgreichen Sendevorgang vorliegt oder wird der Befehl nur initiiert und das Programm setzt die Abarbeitung der folgenden Befehle unverzüglich fort.
Grundsätzlich benötigt die Ausgabe von Debug-Informationen etwas Zeit.
Mit und ohne ist das Timing auf alle Fälle unterschiedlich!
Zu deinem Grundproblem:
Bevor noch irgendeine Zeile C ausgeführt wird, werden die globalen Variablen initialisiert. Die meisten bekommen den Wert 0
int i;
Bei
int j = 3;
Wird der entsprechende Wert (hier 3) zugewiesen.
Dies wird aber nur nach einem Reset durchgeführt.
Andernfalls haben dein globalen Variablen den letzten vom Programm zugewiesenen Wert.
Wenn ich dein Programm noch richtig im Kopf habe hast du Hilfsvariablen in der Form
Motor_fertig = TRUE;
Womit du weiterschaltest.
Bei einem zweiten Aufruf bleiben diese auf TRUE, wenn du sie nicht extra zurück setzt.
Dann rasselt halt alles durch.
MfG Peter(TOO)
Hallo,
Bleibt das Programm an dieser Stelle stehen, bis z.B. eine Quittung über den erfolgreichen Sendevorgang vorliegt oder wird der Befehl nur initiiert und das Programm setzt die Abarbeitung der folgenden Befehle unverzüglich fort.
Ganz allgemein erfolgt bei Arduiono kein Test auf erfolgreichen Sendevorgang. Wenn keiner empfängt sind die Daten halt weg.
Ob das print wartet, hängt vom Arduino Modell ab:
Bei den meisten Modellen gehen die Daten über UART vom Hauptcontroller zum USB-Controller auf dem Board. Das ist entweder ein zweiter Controller (meist ein 16U2) oder nur ein seriell zu USB Wandler (FTDI, CH340, ...). Das Print muss in diesem Falle warten, bis die Daten aus dem UART raus sind.
Dieser Vorgang kann durch einen Interrupt unterbrochen werden, z.B. wenn ein Library verwendet wird, die damit arbeitet. Was hinter dem print steht, kommt aber erst dran, wenn das print fertig ist.
Bei Arduinos mit Controllern, die selbst einen USB-Anschluss haben (Leonardo, Micro, Due, Teensy, ...), ist ein print meist nur ein sehr schneller Kopiervorgang, um den Rest kümmert sich die USB-Hardware im Chip.
Hallo fredyxx,
Grundsätzlich benötigt die Ausgabe von Debug-Informationen etwas Zeit.
Mit und ohne ist das Timing auf alle Fälle unterschiedlich!
MfG Peter(TOO)
ok, aber kann man sicher sein, dass aufeinder folgende Serial.Print's auch direkt nacheinander gesendet werden und das Programm auch dann erst weiter geht?
Wenn ich dein Programm noch richtig im Kopf habe hast du Hilfsvariablen in der Form
Motor_fertig = TRUE;
Womit du weiterschaltest.
Alle Achtung! Das hast du noch genau richtig im Kopf. Deshalb setze ich in der LOOP mit dem STOP-Schalter auch alle Mx-fertig's auf false und erst wenn ich den wieder umschalte, kann das Justierprogramm wieder beginnen. Bei M1 ist das auch so, aber direkt danach sind alle Mx-fertig's auf true!!!???????????????????
vG
fredyx
Peter(TOO)
15.09.2016, 10:29
Hallo fredyx,
ok, aber kann man sicher sein, dass aufeinder folgende Serial.Print's auch direkt nacheinander gesendet werden und das Programm auch dann erst weiter geht?
Im einfachsten Fall wartet man bis das Senderegister leer ist und schreibt dann das nächste Zeichen in das Sende-Register. So lange man wartet, ist die CPU blockiert, bzw. kann nur Interrupts bearbeiten. Das geht dann ganz ohne Interrupts, ist aber die langsamste Variante. Zudem hängen die Zeiten direkt von der Baudrate ab.
Bei der eleganteren Art richtet man einen Puffer ein und der Print-Befehl kopiert die Zeichen in diesen Puffer. Nach dem Kopieren kann die CPU weiter das Programm abarbeiten. Wenn das Sende-Register leer ist erzeugt es einen Interrupt und die ISR kopiert dann das nächste Zeichen aus dem Puffer ins Sende-Register.
Es braucht dann noch etwas Aufwand für die Pufferverwaltung. Wenn man das erste Zeichen in den leeren Puffer schreibt muss man den Interrupt anwerfen. Umgekehrt muss die ISR den Interrupt abschalten, wenn das letzte Zeichen aus dem Puffer gelesen wird.
Wenn der Puffer voll ist, muss man warten, bis ein Zeichen gesendet wurde, dann ist man gleich langsam wie bei der einfachen Variante.
Noch etwas schneller wird es, wenn man die Daten, sofern vorhanden, per DMA vom Puffer in das Sende-Register kopiert. Die Verwaltung wird dafür etwas aufwändiger, dafür wird die ISR seltener aufgerufen (Nur wenn die DMA fertig ist und somit nicht für jedes Zeichen).
Die Technik mit dem Puffer funktioniert auch beim Empfangen und hat den Vorteil, dass man keine Zeichen verliert, besonders wenn man noch Handshake (XON XOFF) implementiert.
Leider stehen viele Programmierer mit den Interrupts etwas auf Kriegsfuss und die erste Variante ist deshalb sehr häufig anzutreffen. Allerdings entwickelt man den Treiber eigentlich nur einmal im Leben für eine Programmiersprache. Die Anpassung an unterschiedliche µCs betrifft dann eigentlich nur die Handhabung des Interrupt-Controllers und des Sende-Registers, der Rest ändert sich eigentlich nicht.
MfG Peter(TOO)
switch/case ist nichts anderes als ein goto.
Die case (irgendwas) sind nichts anderes als Sprungmarken für gotos, daher müssen es in C immer auch Integer-Konstanten sein (und keine Floats oder logische Ausdrücke).
So ungern hier also manche das goto in C-Programmen sehen: es ist nur persönliches Ästhetikempfinden, nicht mehr und nicht weniger 8)
an fredyxx:
wenn du sicher bist, dass deine Sprünge im richtigen Moment an die richtige Stelle führen, ist das kein Grund, sie durch switch/case Anweisungen zu ersetzen.
Allerdings müssen sie beide in jedem Falle immer im richtigen Moment an die richtige Stelle führen.
Bist du also sicher, dass sie stimmen, lass sie drinnen - und such den Fehler woanders.
Goto Anweisungen innerhalb von switch/case ist allerdings reichlich doppelt gemoppelt und wirklich extrem unschöner Programmierstil.
zum Debuggen mit serial:
Du kannst Wartepunkte einfügen, indem du in dein Programm ein Warten auf einen Buttondruck und wieder loslassen einfügst:
#define testpin 13 // or whatever
while(!testpin);
Serial.print(irgendwas);
while(testpin);
Hallo HaWe,
deine Antwort hat mir in zweierlei Hinsicht mal richtig gut getan. DANKE dafür!
1. Dieser Satz:
es ist nur persönliches Ästhetikempfinden, nicht mehr und nicht weniger
Ich bin auch noch als Arduinoanfänger nach einem halben Jahr Aufwand an dem Hobby-Projekt ziemlich weit davon entfernt das Programm um zu schreiben, denn da würden sicher auch wieder neue Probleme auftreten. Man ist ja häufig auch schon froh, wenn man nicht eine optimale sondern überhaupt eine Lösung findet. Trotzdem werde ich in Zukunft mehr darüber nachdenken, ob ich mehr Case und Funtionen verwenden werde.
Außerdem habe ich zZ eine Spur, wo der Fehler liegen könnte, nämlich im Umgang mit dem Signal des STOP-Tasters, dessen zeitlicher Signalablauf ich wohl nicht vollständig durchschaut und daher auch nicht richtig verarbeitet habe. Bin mir aber noch nicht sicher.
Maßnahme gegen Tasterprelllen hatte ich schon vorgesehen.
Außerdem hat mir bei der Fehlersuche Folgendes geholfen:
ich habe an den Stellen, von denen ich sicher wissen wollte, ob das Programm da durchläuft oder nicht, einen Ausgang mit LED gesetzt, der im Programm mit Sicherheit nicht zurückgesetzt wird. Dadurch brauchte ich mir keine Gedanken über die zeitliche Verarbeitung von Serial.print zu machen und ich habe sofort erkannt, bei welchem Prozesschritt das passiert.
2.
So was wie dein Vorschlag zu "zum Debuggen mit serial" war das, was ich auf meine ursprüngliche Frage als Antwort erhofft hatte.
Ich werde nicht aufgeben und bei Erfolg berichten.
vG
fredyxx
oberallgeier
16.09.2016, 18:22
switch/case ist nichts anderes als ein goto .. Die case (irgendwas) sind nichts anderes als Sprungmarken für gotos ..Wirklich? Ohh! Das ist für mich jetzt total neu.
Ich dachte immer die Anweisung "case expr :" in der switch-Schleife wird abgearbeitet, solange kein break folgt bzw. die switch-Schleife durch eine andere Anweisung verlassen wird wie z.B. return. Deshalb fürchtete ich, dass ohne break oder return eine Anweisung wie das goto beim nächsten return wieder in der switch-Schleife landet. Mit kaum kontrollierbarem weiteren Ablauf. Abgesehen davon, dass meines Wissens nach, das Sprungziel nur innerhalb des Gültigkeitsbereiches der Marke liegen, d.h. nur in der jeweiligen Funktion liegen darf.
Frage: Bedeutet die oben zitierte Aussage, dass durch das "goto label;" innerhalb eines "case expr :" KEIN Rücksprung in die switch-Schleife erfolgt? Nie und Nimmer?
Frage: Wie kriegt der Stack diesen Ablauf gebacken ?
Frage: Wie kriegt der Stack diesen Ablauf gebacken ?
Also erstmal sind wir hier in der Arduino Rubrik. Es gelten also die goto Regeln von C++, nicht die von C.
Zweitens, von der Implementierung her ist ein switch/case eher eine if else if Kette, kein goto.
Den Stack in Ordnung zu halten ist Sache des C++ Compilers. Was ein bischen ein Problem ist, der g++ für die 8 Bitter ist ein recht abgespeckter Compiler. Keine Ahnung, ob der wirklich alle Regeln befolgt. Auch das ist ein Grund, warum ich solche goto Konstrukte für suspekt halte.
Grundsätzlich gilt: In C++ darf man mit goto nur innerhalb einer Funktion springen und
{
Ding a;
Ding b;
goto weg;
}
bedeutet implizit, dass beim goto die Destruktoren von a und b aufgerufen werden. Und bei
goto da;
{
Ding a;
Ding b;
da:
// ... mehr Code
bedeutet das goto, dass a und b über ihre Defaultkonstruktoren erzeugt werden. Zumindest findet man das so in Büchern, die meisten Compiler erlauben das nicht.
Das ist mir zu hoch und sollte sich m.E. auch auf einen Link beschränken, der auf eine andere Stelle im Forum zeigt, wo solche Spezialthemen behandelt werden.
Wie dem auch sei. Ich habe die Ursache gefunden und die hatte nichts mit goto o.ä. zu tun.
Es handelte sich um zeitliche und logische Abläufe bei der STOP-Tastenauswertung, die ich nicht vollständig durchschaut und richtig umgesetzt hatte.
Danke für eure sehr aktive Hilfe. Ich kenne in dem Programm mindestens noch ein Problem, an dem ich nun beginne zu knabbern.
Befriedigend ist es ja dann, wenn man es doch wieder mal geschafft hat und man hat wieder was dazu gelernt.
vG
fredyxx
was hinter "case" steht, heißt nicht umsonst "label": es ist eine verkappte Sprungadresse, genau wie bei goto. Dabei wird der Wert der Bedingung mit der Label/Sprungmarke verknüpft/assoziiert.
Von der Logik her ist es natürlich vergleichbar mit if/else-Ketten, mit dem Unterschied, dass hier kein "break" nötig ist nach jeder logischen Bedingung
- was wieder darauf zurückzuführen ist, dass vor der nächsten case-label/Sprungmarke per "break" ans Ende vom "switch"-Block gesprungen werden muss, ebenfalls genau wie bei reinen aneinandergereihten goto Sprungmarken:
täte man das nicht, würde weiter einfach zeilenweise der Code schrittweise weiter abgearbeitet werden, direkt in den nächsten case-"Körper" hinein, ebenfalls genau wie bei kaskadierten goto-Befehlen, auch hier würde einfach über das kommende Label hinweggelesen, in die dann folgenden Anweisungen "hinein".
Man müsste sich wahrscheinlich wirklich mal die Mühe machen, den nackten Maschinencode zu untersuchen, den ein C und auch ein C++ Compiler aus switch/case, goto und if/else-Ketten/Blöcken macht, aber allein schon der Unterschied, dass case (genau wie goto) nur Konstanten als Labels erlaubt (1,2,3,4...), während if/else jedes logische Statement zulässt (<2, >=2 und <3.999, >=4...), zeigt schon die Ähnlichkeiten von goto und switch/case und den Unterschied zu if/else (letzteres ist allerdings genau das, was ich selber bevorzuge, wegen der größeren Möglichkeiten und Mächtigkeit dieses Konstrukts, ohne dabei aber auf goto zu verzichten; switch/case hingegen ist IMO das "allerletzte" wegen der Beschränkung auf Integer-Konstanten und dem nervtötenden "break" zwischen den Sprungmarken).
- was wieder darauf zurückzuführen ist, dass vor der nächsten case-label/Sprungmarke per "break" ans Ende vom "switch"-Block gesprungen werden muss, ebenfalls genau wie bei reinen aneinandergereihten goto Sprungmarken
Das ist gleich doppelt falsch. Ein switch kann in C und C++ auch so aussehen
switch(wert)
{
case 1:
case 2:
case 3:
// Code für die ersten drei Fälle
break;
case 4:
// speziell für 4
case 5:
case 6:
// für 4 bis 6 gemeinsam
}
Weil das eine beliebte Fehlerquelle ist, durch ein vergessenes break, haben erst neuere Sprachen wie C# die Regel eingeführt, dass jedes case mit einem break beendet werden muss.
Zweitens ist es natürlich nicht richtig, dass irgendwie ans Ende des switch gesprungen werden muss. In C++ schon weil es Exceptions gibt, die können natürlich ein switch an beliebiger Stelle verlassen. Und gemeinsam mit C gibt es natürlich die Möglichkeit mit return statt break herauszugehen.
das ist nicht doppelt faslch, du hast meinen Punkt einfach nicht richtig verstanden
- ntl kann man bei case das break weglassen, aber um es mit "else" zu vergleichen, muss eben doch ein break folgen:
du hast schließlich selber den Vergleich zu if/else bemüht, ich wollte nur den Unterschied zum (exklusiven) else hervorheben, gegenüber dem völlig anders - nämlich sprungmarkenartig - funktionierenden case, wo ohne break einfach zeilenweise weiter gearbeitet wird.
Ein
case 1:
entspricht etwa einem (Pseudocode)
if (noch_kein_passendes_case_gesehen && (x != 1))
goto hinter_naechstes_break; // wo wieder ein if steht
else
noch_kein_passendes_case_gesehen = false;
du kannst es analogiemäßig erklären und umschreiben wie du willst, wem oder was immer es pseudocodemäßig entsprechen mag, aber es bleibt dabei, dass bei Erfüllung der Bedingung eine dem Integerwert entsprechende gleichnamige Integer-Sprungmarke angesprungen wird, die genau wie bei goto-labels eine Konstante sein muss und auch genau wie bei Lables von einem Doppelpunkt gefolgt wird:
if(x==1) goto 1;
if(x==2) goto 2;
if(x==3) goto 3;
if(x==4) goto 4;
goto default;
1: ...
2: ...
3: ...
4: ...
default: ...
Es entspricht aber keinesfalls eher oder unmittelbarer einem kaskadierten
if/else if /else if/...
wo dann auch nicht der Rest wie bei if/else-Körpern übersprungen wird, sondern ebenfalls wie bei goto Labels einfach schrittweise weitergearbeitet wird und wo in jedem else-Körper auch wieder Variablen-Ausdrücke, floats oder statements erlaubt wären.
Wie auch immer:
alles ist legaler C Code und hat seine gleichwertige Daseins-Berechtigung.
Wie es genau aussieht hängt sowohl vom Compiler als auch von der Verteilung der Werte der case ab. Hier hat einer das für einen Compiler mal angeschaut
http://www.codeproject.com/Articles/100473/Something-You-May-Not-Know-About-the-Switch-Statem
Da verwendet der Compiler gleich drei verschiedene Implementierungen, je nach Struktur der cases.
auch in deinem Link wird mehrfach die Compilierung zu jmp lables erwähnt.
mag jedoch alles im Detail sein wie es will, switch/case ist trotzdem prinzipiell ein goto und kein if/else if, sonst wären nicht nur Integer-Konstanten sondern auch Variablen-Ausdrücke, floats oder statements als Fallunterscheidungen erlaubt.
und wie bereits erwähnt,
Wie auch immer:
alles ist legaler C Code und hat seine gleichwertige Daseins-Berechtigung.
Erstmal, ja
alles ist legaler C Code und hat seine gleichwertige Daseins-Berechtigung.
Aber, nein
switch/case ist trotzdem prinzipiell ein goto und kein if/else if
es ist eine Tabelle, wenn das kürzer ist, sonst sind es auch if.
Ganz unschuldiges Beispiel
switch(i)
{
case 1:
// ...
break;
case 2:
// ...
break;
case 3:
// ...
break;
}
gibt eine Tabelle.
Aber
switch(i)
{
case 10:
// ...
break;
case 100:
// ...
break;
case 1000:
// ...
break;
}
ups, was machen wir mit den Zwischenwerten ? Eine Tabelle mit 1001 Einträgen in den Speicher vom armen Arduino ?
Nein, da passiert was ganz anderes. Und deshalb überlegt der Compiler so lange ...
die Frage der Zwischenwerte wird ebenfalls in deinem Link diskutiert. Es bleibt dabei:
switch/case basiert auf goto mit jump labels / jump tables und es entspricht NICHT if/ else if / else if.
Peter(TOO)
17.09.2016, 01:25
Wie es genau aussieht hängt sowohl vom Compiler als auch von der Verteilung der Werte der case ab. Hier hat einer das für einen Compiler mal angeschaut
Ursprünglich haben C-Compiler nur geschachtelte if/else erzeugt. Diese Variante funktioniert immer und der passende Code-Generator ist einfach.
Ein grosser Nachteil ist das Laufzeitverhalten bei vielen Labels, um an das letzte Label zu gelangen müssen alle if abgearbeitet werden.
Allerdings haben diese dummen Compiler die Labels nicht sortiert, man konnte nachhelfen indem man die case entsprechend ihrer Wahrscheinlichkeit sortiert hat. Der Code war dann nicht unbedingt wirklich übersichtlicher.
Man darf nicht vergessen, dass in der Zeit als C entstand Hauptspeicher noch in Kilobyte und damalige Festplatten in Megabyte gemessen wurden.
Etwas später hat man dann angefangen die Labels zuerst zu analysieren.
Bei nur ein paar Label erzeugt man immer noch geschachtelte if/else, der Code-Overhead ist kleiner und die Laufzeitdifferenzen spielen auch keine Rolle.
Für Sprungtabellen ist es am einfachsten wenn die case-Werte feste Abstände haben. Bei nur einzelnen fehlenden Werten, werden diese einfach in der Tabelle eingeführt und zeigen auf das default-Label.
Bei einer zufälligen Verteilung der Werte bleibt dann nur noch die Möglichkeit eine Suchtabelle, welche wieder das selbe laufzeitverhalten wie verschachtelte if/else hat.
Bei der Frage nach dem Stack-Handling in C++ ist zu Bedenken, dass C++ ursprünglich als Pre-Prozessor konzipiert wurde. Im ersten Schritt wurde C++ einfach in ein C-Programm übersetzt und dann ging es mit dem normalen C-Compiler weiter.
Normalerweise kann man den C++-Compiler anweisen eine Datei mit diesem C-Code zu erstellen.
Dieser Schritt über C ist auch der Grund für die decorated names in C++. Es musste eine Lösung gefunden werden, damit C-Compiler und Linker mit identischen Funktions-Namen aber unterschiedlichen Parametern umgehen können. Also bastelt man sich aus den Parameter-Typen eine Codierung zusammen und bastelt diese an den Funktionsnamen dran. Für C und den Linker sind dies dann komplett unterschiedliche Funktionen.
MfG Peter(TOO)
Um endlich wieder bei
Frage: Wie kriegt der Stack diesen Ablauf gebacken ?
weiterzumachen:
In C++ kommt noch eine weitere Komplexität hinzu. Das ist auch der Grund, warum gotos unerwartete Nebeneffekte haben können, die man nicht gut sieht, wenn über mehrere Bildschirmseiten gesprungen wird.
Nehmen wir mal an, man verwendet Dinge aus einer Arduino Lib, hier zur Demonstration mal mit Debug-Ausgaben
struct Ding
{
~Ding()
{
Serial.printf("Fertig");
}
}
Wenn sowas jetzt in Code auftaucht, ein switch ist hier nur ein Beispiel
switch(i)
{
case 1:
{
Ding a;
// ....
break;
}
case 2:
/...
}
Serial.printf("Test");
Dann ist die Ausgabe
Fertig
Test
Sowas kann auch passieren, wenn ein goto über { oder } hinwegspringt, vorwärts oder rückwärts.
- - - Aktualisiert - - -
die Frage der Zwischenwerte wird ebenfalls in deinem Link diskutiert. Es bleibt dabei:
switch/case basiert auf goto mit jump labels / jump tables und es entspricht NICHT if/ else if / else if.
Dazu reicht es einfach aus dem Link zu zitieren
...
Next, how do we jump to these calling targets?
...
The logic is not too hard to understand. ...Rewrite the snippet like this:
...
i2 = i;
if i2 > 700 goto LN14;
if i2 == 700 goto LN5;
if i2 > 250 goto LN15;
if i2 == 250 goto LN7;
if i2 == 100 goto LN9;
if i2 == 200 goto LN8;
goto LN1;
LN15:
if i2 == 500 goto LN6;
goto LN1;
LN14:
if i2 == 750 goto LN4;
if i2 == 800 goto LN3;
if i2 == 900 goto LN2;
goto LN1;
und siehe da...:
goto's, wer hätte das gedacht?
Und keine if /else if/else if/... sondern nur hintereinander geschriebene if's.
Natürlich kann man auch Fehler mit goto's machen, aber C ist ja gerade dafür entwickelt worden, um alles möglich zu machen - gehen tut alles mit C, der Programmierer ist für alles verantwortlich, auch für seine Fehler, und eben nicht die Programmiersprache oder der Compiler.
Whatever -
mein Standpunkt war ja nichts anderes als das gerade hier Beschriebene, goto's sind eben auch nicht viel anders als switch/case und keines ist schöner, besser oder vielseitiger oder mächtiger.
Für if /else if/else if/ gilt das allerdings schon, was das besser oder vielseitiger oder mächtiger anbelangt. Ich denke, auf diesen Standpunkt können wir uns ohne weiteres einigen.
Für if /else if/else if/ gilt das allerdings schon, was das besser oder vielseitiger oder mächtiger anbelangt. Ich denke, auf diesen Standpunkt können wir uns ohne weiteres einigen.
Ja sicher.
Und ja, in Assembler wird if else zu einer Art if goto, ein else gibt es da nicht.
Übrigens ist ein switch mittlerweile auch schon recht mächtig. Zwar noch nicht in der Arduino IDE, aber neuere C++ Compiler erlauben sowas
constexpr int quadrat(int n) noexcept
{
return n * n;
}
int main()
{
for (int i = 0; i < 10; i++)
{
switch (i)
{
case quadrat(1):
printf("i ist 1\r\n");
break;
case quadrat(2):
printf("i ist 4\r\n");
break;
case quadrat(3):
printf("i ist 9\r\n");
break;
default:
break;
}
}
return 0;
}
Gerade in Visual Studio getestet.
oberallgeier
17.09.2016, 11:53
.. Und ja, in Assembler wird if else zu einer Art if goto, ein else gibt es da nicht ..Wirklich sehr lehrreiche Diskussion und informative Ausführungen! Sorry, dass mein OT Deinem Assembler-Zusatz folgt. Aber sind nicht diese ganzen
SBRC Rr, b Skip if Bit in Register Cleared if (Rr(b)=0) PC ← PC + 2 or 3 None 1/2/3
SBRS Rr, b Skip if Bit in Register is Set if (Rr(b)=1) PC ← PC + 2 or 3 None 1/2/3
...
BREQ k Branch if Equal if (Z = 1) then PC ← PC + k + 1 None 1/2Branch und Skip Befehle nicht genau für so ein "else" geeignet?
Anm: Auszug aus Atmel-42735A-ATmega328/P_Datasheet_Complete-06/2016, S432, 36. Instruction Set Summary.
Wahrscheinlich ja. Die Aussage "kein else" ist ja eventuell auch wieder Architekturabhäng. Manchal gibt es nur ein "jump if zero" und "jump if not zero", da muss ein if else dann natürlich etwas umgebaut werden.
Unregistriert
17.09.2016, 12:14
"goto" wurde meiner Meinung nach nur in höhere Programmiersprachen aufgenommen um den Maschinensprachlern den Aufstieg zu erleichtern und wurde später nicht entfernt, um am Vorkommen im Sourcecode auf den erstn Blick den Anfängerauthor vom Fortgeschrittenen unterscheiden zu können ;-)
Manchmal gibt es nichts anderes (außer evtl. der Zerlegung in mehrere Funktionen)
for( ... ganzviel ...) {
for( ... nochmehr ...) {
for( ... immer mehr ...) {
if (bedingung) {
goto nix_wie_weg;
}
}
}
}
nix_wie_weg:
und wie schon oben zitiert, viele Parsergeneratoren erzeugen Code mit vielen gotos.
i_make_it
17.09.2016, 13:39
"goto" wurde meiner Meinung nach nur in höhere Programmiersprachen aufgenommen um den Maschinensprachlern den Aufstieg zu erleichtern und wurde später nicht entfernt, um am Vorkommen im Sourcecode auf den erstn Blick den Anfängerauthor vom Fortgeschrittenen unterscheiden zu können ;-)
GOTO wurde nicht in Hochsprachen "aufgenommen".
In den ersten nicht Assembler Sprachen war GOTO und GOSUB die ersten und auch einzigen Verzweigungssmöglichkeiten in den Sprachen. Da wurde einfach eine eins zu eins Abbildung auf die verschiedenen JMP und BR Befehle vorgenommen.
Erst mit ALGOL wurde WHILE, FOR, IF, ELSE für Verzweigungen eingeführt.
GOTO funktioniert, denn beim Kompilieren werden alle modernen Konstrukte auf JMP und BR (bzw. ihren OPCode) zurückgeführt.
Die modernen Konstrukte halten einem halt die Arbeit vom Halse selbst den Überblick zu behalten und vor allem fördert es die Verständlichkeit und Lesbarkeit des Quellcodes.
Ich schreibe auch heute noch seitenlangen Code mit GOTO wenn es in der jeweiligen Sprache nichts anderes gibt. Allerdings bin ich es jetzt auch seit 34 Jahren so gewohnt.
Dafür sitzte ich aber auch vor Quellcode den ich vor X Jahren selbst geschrieben habe und brauche meine Zeit um zu verstehen was ich damals da gemacht habe und warum.
Ein Anfänger sollte sich halt überlegen ob er nicht gleich lernt zeitgemäß zu programmieren und sich zu verbessern oder Code zu produzieren der bei jeder Änderung fehleranfällig ist.
Der Arduino Compiler sollte schon mit GOTOs klar kommen. Aber optimieren wird er den Code wohl nicht großartig.
Es käme mal auf einen Versuch an die selbe Aufgabe einmal mit GOTOs und einmal ohne zu programmieren und dann zum einen festzustellen wie groß die beiden Compilate werden und welche Zykluszeiten sie dann bei der Ausführung haben.
1993 habe ich das mal für ein IBM S360 System mit Assembler, COBOL und APL2 gemacht. War interessant.
Peter(TOO)
17.09.2016, 14:29
BREQ k Branch if Equal if (Z = 1) then PC ← PC + k + 1 None 1/2[/CODE]Branch und Skip Befehle nicht genau für so ein "else" geeignet?
Jetzt zäumst du aber das Pferd von Hinten auf!
Bedingte Verzweigungen gab es schon bei der "Analytical Engine" (1837) von Babbage. Allgemeine Probleme lassen sich nur mit bedingten Verzweigungen lösen.
Lady Ada hat dann die Assembler-Sprache erfunden und z.B. der Begriff Mnemonic stammt von ihr.
Das Vorgänger-Modell (die Differenzmaschine, angefangen 1822) war noch ein reiner Number-Cruncher. Da konnte man nur eine mathematische Formel Programmieren, welche dann von der Maschine abgearbeitet wurde.
MfG Peter(TOO)
- - - Aktualisiert - - -
Hallo Mxt,
Wahrscheinlich ja. Die Aussage "kein else" ist ja eventuell auch wieder Architekturabhäng. Manchal gibt es nur ein "jump if zero" und "jump if not zero", da muss ein if else dann natürlich etwas umgebaut werden.
Nein, man brauch nur ein GOTO :-)
10 IF <Bedingung> GOTO 100
20 REM hier ist der ELSE-Zweig
.
.
90 GOTO 200
100 REM hier beginnt der IF-Zweig
.
.
200 REM hier geht das Programm weiter
IF- und ELSE-Zweig kann man vertauschen wenn man die Bedingung invertiert.
Bei manchen CPUs können bedingte Sprünge aber z.B. nur +/- 127 Byte weit springen. Dann muss man bei grösseren Zweigen auch mit GOTO nachhelfen.
10 IF <Bedingung> GOTO 30
20 GOTO 100
30 REM IF-Zweig
.
.
90 GOTO 200
100 REM ELESE-Zweig
.
.
200 REM hier geht das Programm weiter
MfG Peter(TOO)
- - - Aktualisiert - - -
Hallo Un,
"goto" wurde meiner Meinung nach nur in höhere Programmiersprachen aufgenommen um den Maschinensprachlern den Aufstieg zu erleichtern und wurde später nicht entfernt, um am Vorkommen im Sourcecode auf den erstn Blick den Anfängerauthor vom Fortgeschrittenen unterscheiden zu können ;-)
Schau die mal ein altes BASIC an, so 1970 oder älter.
Da gab es nur GOSUB und noch keine Prozeduren.
Alle variablen waren global.
Zeilen-Nummern waren zwingend und man konnte nur an Zeilennummern springen.
Um Platz für Änderungen zu haben, hat man standardmässig die Zeilen in 10er Abständen nummeriert. Einen RENUMBER-Befehl gab es Anfangs nicht. Musste man zu viele Zeilen einfügen musste alle GOTOs von Hand anpassen :-(
Abgesehen davon:
Strukturiert und Objektorientiert programmieren konnte man auch schon in Assembler. Allerdings lag das einzig und alleine in der Verantwortung des Programmierers.
MfG Peter(TOO)
goto ist nicht mehr und nicht weniger zeitgemäß als switch/case oder if/else, und auch eine Zeitreise durch die Historie seit Adam und Eva in der Geschichte der Computer führt hier nicht wirklich weiter (wir reden schließlich von C und C++ jetzt und hier im Allgemeinen und bei Arduino im Besonderen, und nicht von Algol oder Cobol oder Fortran von anno einundleipzig). Die Verwendung ist sowohl reine Geschmackssache als auch abhängig von den individuellen Anforderungen im Programm, um unnötig komplizierte if/else/switch/case Verschachtelungen zu umgehen, und jedem Programmierer ist es selbstverständlich erlaubt, alle Befehle zu verwenden, die die Programmiersprache hergibt. Alles andere ist unsinniges und unzulässig verallgemeinerndes Geschwätz und eine ziemlich arrogante Bevormundung, die allesamt zu nichts führen, und wenn man hier auch hundert mal etwas anderes behauptet, wird es dadurch nicht richtiger.
Es ist mir sowieso ein Rätsel, weshalb die Frage eines Hobbyprogrammierers zu solchen Grundsatzdikussionen führt. Das erlebe ich nun schon zum zweiten Mal und kann doch wohl nicht Sinn des Forums an dieser Stelle sein.
Außerdem schreckt es den Leser ab, weiter zu lesen, der sich vielleicht nur für die Ursache des Fehlers interessiert.
vF
fredyxx
Es ist mir sowieso ein Rätsel, weshalb die Frage eines Hobbyprogrammierers zu solchen Grundsatzdikussionen führt. Das erlebe ich nun schon zum zweiten Mal und kann doch wohl nicht Sinn des Forums an dieser Stelle sein.
Außerdem schreckt es den Leser ab, weiter zu lesen, der sich vielleicht nur für die Ursache des Fehlers interessiert.
Ja, das ist unschön für den Ratsuchenden.
Aber da kann man nichts machen. Soziales Internet funktioniert heute nun mal "postfaktisch". Es gibt nur die Wahrheit der am meisten geäußerten Behauptung.
(HaWe wird gleich zustimmen in dem er widerspricht.)
Aber so ein Thread entwickelt sich natürlich aus der Frage und da bist du nicht ganz unschuldig. Du fragst
Ich Suche aber nach einer Möglichkeit, durch einen Interrrupt, der auf eine Veränderung einer Variablen reagiert und die Position im Programm ausgibt, die genaue Stelle im Programm zu finden.
Das führt natürlich in Richtung Debugger. Also antworten erfahrene Leute in dieser Richtung. Erfahrene Leute stören sich natürlich am goto, weil sie damit schon genug Ärger hatten.
Damit werden ihre Antworten aber automatisch falsch. Weil sie auf Erfahrung basieren und damit nicht die Meinung anderer repräsentieren. Durch die Frage geleitet hat man ja auch kaum die Möglichkeit Zeit auf einfache Dinge wie LED und Taster hinzuweisen. Die gehören dann zur Argumention der wahren Wissenden.
keine Einwände, Euer Ehren.
zum Debuggen mit serial:
Du kannst Wartepunkte einfügen, indem du in dein Programm ein Warten auf einen Buttondruck und wieder loslassen einfügst:
#define testpin 13 // or whatever
while(!testpin);
Serial.print(irgendwas);
while(testpin);
Hallo HaWe,
funktioniert hat es so:
#define testpin 13 // or whatever
while(!testpin){
Serial.print(irgendwas);
}
while(testpin);
[/QUOTE]
Nötig sind die geschweiften Klammern.
Die letzte Zeile war bei mir nicht nötig.
Aber prima die Idee!!
vG
fredyxx
mmmhhh... versuch es mal so wie ich ursprünglich schrieb -
erst wartet er an der Stelle bis die Taste gedrückt wurde,
sobald das der Fall war, macht er sein print,
dann wartet er erneut, bis die Taste wieder losgelassen wurde.
Zumindest war das meine ursprüngliche Idee.
Jungs, ihr prüft hier auf Konstanten. HaWe's Code
#define testpin 13
while(!testpin);
Serial.print(irgendwas);
while(testpin);
ist gleichzusetzen mit:
Serial.print(irgendwas);
while(true); // Endlosschleife
i_make_it
19.09.2016, 08:07
@Sisor:
Das ist so schon richtig.
in der pins_arduino.h
ist je nach Board die Zuordnung der µC Pins zu den Arduino Pin Nummern festgelegt.
Bsp.:
static const uint8_t PinB5 = 13;
Damit ist "while(!testpin);" eine Prüfung darauf ob PIN 13 nicht gesetzt ist, da PIN13 der Name "testpin" zugewiesen wurde.
Auszug aus der revisions.txt der 1.6.11 vom 17.8.2016.
* The new, variant-specific pins_arduino.h files now provides additional
macros with information about the characteristics of the board (e.g.
the locations of the SPI and TWI pins, and the number of digital and
analog pins).
@Sisor:
Das ist so schon richtig.
in der pins_arduino.h
ist je nach Board die Zuordnung der µC Pins zu den Arduino Pin Nummern festgelegt.
Bsp.:
static const uint8_t PinB5 = 13;
Damit ist "while(!testpin);" eine Prüfung darauf ob PIN 13 nicht gesetzt ist, da PIN13 der Name "testpin" zugewiesen wurde.
#define testpin 13
testpin wird durch das #define durch die Zahl 13 ersetzt. Nichts anderes, auch nicht durch "PIN13", und "!13" ist in C false. Das bei einer while-loop, die nur aus einem Befehl besteht, geschweifte Klammern nötig sind, hätte einen sofort auf die Spur bringen müssen.
Es sind also Vergleiche mit Konstanten, und alles was schon zur Compilezeit false ergibt, wird gar nicht erst compiliert.
Aber BTT
Wie dem auch sei. Ich habe die Ursache gefunden und die hatte nichts mit goto o.ä. zu tun.
Es handelte sich um zeitliche und logische Abläufe bei der STOP-Tastenauswertung, die ich nicht vollständig durchschaut und richtig umgesetzt hatte.
Natürlich hat dein Problem mit den gotos zu tun. Nur damit kann man so unstrukturierten, unlesbaren und damit unwartbaren Code schreiben. Und die Chance, durch verständliche Labels etwas Ordnung in das Chaos zu bringen, wurde auch vertan. Da sind dann Fehler wie " die ich nicht vollständig durchschaut und richtig umgesetzt hatte " unvermeidbar. Der Code könnte in einem Lehrbuch als Beispiel dienen, warum man gotos meiden soll.
MfG Klebwax
fang bitte nicht wieder mit dem goto-Sch**** an, das Thema ist durch, und daran lag es auch nicht.
Das mit dem testpin oder !testpin war allerdigs tatsächlich nicht richtig , es musste heißen
while(!digitalRead(testpin));
while( digitalRead(testpin));
https://www.arduino.cc/en/Reference/DigitalRead
sorry for that!
Unregistriert
19.09.2016, 09:42
fang bitte nicht wieder mit dem goto-Sch**** an, das Thema ist durch,
Richtig, goto-Sch**** und das Thema ist noch lange nicht durch, solange es verschiedene Ansichten darüber gibt.
unregistrierte Trolle sollte man hier wirklich bannen bzw. gar nicht erst posten lassen. Sie vergiften nur das Diskussions-Klima und schaden letztendlich auch sehr der Reputation des Forums.
i_make_it
19.09.2016, 10:55
Ich sage es mal so,
Es wurde nach Hilfe gefragt ohen das Code gepostet wurde.
Als dann ein Code sniplet gepostet wurde, habe ich für mich so viele GOTOs gesehen, das ich keine Lust mehr verspührte mir das anzutun.
Rein auf verdacht, vermute ich mal das es einigen Leuten die schon etwass Programmiererfahrung haben, ähnlich geht.
Da die Sprache besser lesbaren Code erlaubt.
Es liegt halt an dem fragenden, entweder sich damit abzufinden das man ihm sagt "Räum deinen Code erst mal auf und komm dann wieder wenn sich das Problem dabei nicht schon von selbst gelöst hat".
Oder zu hoffen das sich jemand seiner erbarmt.
Zu erwarten ist, aus meinen Erfahrungswerten ,das es wieder zu Fragen kommt und der Code bis dahin nicht besser aussehen wird.
Womit sich die Fehlerträchtigkeit dieses Programmierstils dann von selbst beweisen wird.
Ich teste Sachen oft auch erst mal mit quick and dirty Code.
Sobald es dann aber um die Integration in größere Sachen geht, macht es auch Sinn darüber nachzudenken ob der bisher eingeschlagene Weg der bessere oder der schlechtere ist.
GOTOs funktionieren, wenn man sie beherscht und den Überblick behällt.
Aber nur dann und nur solange man sich intensiv mit dem Code befasst.
Wenn das mit den GOTOs (und ein paar anderen Assembler Befehlen) so einfach wäre, wären Hochsprachen nie entstanden und alle würdne assembler nehmen.
Aber man wollte Abstraktion und Portierbarkeit sowie einfacher zu lesenden Code, Denn das können mehr Menschen verstehen wie Assembler.
Ein GOTO ist nun mal nichts anderes wie eine andere Schreibweise für ein JMP.
ALso eigentlich Assembler.
Aber es ist halt jedem selbst überlassen wie er Programmiert.
Dafür muß man dann aber auch hinnehmen das es immer wieder zur selben Diskussion kommt, wenn man solchen Code vorlegt.
Aber es ist halt jedem selbst überlassen wie er Programmiert.
Dafür muß man dann aber auch hinnehmen das es immer wieder zur selben Diskussion kommt, wenn man solchen Code vorlegt.
ist ja ok, wenn man nicht iwas immer nur pauschal verteufelt. Selbst erfahrene Programmierer verwenden u.U. goto, und in diesem jetzt folgenden sehr speziellen Fall sah ich auch keine andere Möglichkeit, als es meinem Vorbild bei der Arduino- und Raspi-Portierung gleich zu tun: http://www.mindstormsforum.de/viewtopic.php?f=70&t=6790&p=66103#p66103
http://www.mindstormsforum.de/viewtopic.php?f=70&t=6790&p=66103#p67841
ceterum censeo, auch switch/case ist nichts anderes als ein verkapptes goto.
@fredyxxx, zurück zum
while(!digitalRead(testpin));
while( digitalRead(testpin));
https://www.arduino.cc/en/Reference/DigitalRead
...: funktioniert das jetzt so?
@fredyxxx, zurück zum
while(!digitalRead(testpin));
while( digitalRead(testpin));
...: funktioniert das jetzt so?
Hallo HaWe,
ja, erstaunlicherweise funktioniert es nur mit Semikolon's so. Mit geschweiften Klammern verhält es sich anders. Ich hatte das für einen Fehler gehalten, weil der Befehl hier http://https://www.arduino.cc/en/Reference/While (http://www.arduino.cc/en/Reference/While") mit Klammern beschrieben ist.
Das mit dem digitalRead hatte ich wohl ohne darüber nachzudenken sowieso schon richtig gemacht.
vG
fredyxx
jap, in den Klammern steht der Körper:
while(Bedingung) {Körper}
der Körper wird solange wiederholt ausgeführt, wie die Bedingung True ist.
Er soll aber gar nichts öfters ausführen, sondern nur warten, solange die Bedingung wahr ist, also die Taste (noch) NICHT gedrückt wurde.
Daher brauchen wir keinen Körper:
! (digitalRead(13) ) // Taste an dPin13 nicht gedrückt
Sobald sie gedrückt wurde, soll er printen,
und dann wieder warten, solange eine neue Bedingung True ist :
(digitalRead(13) ) // Taste an dPin13 (weiterhin ) gedrückt, (noch) nicht losgelassen
- ebenfalls ohne irgendwas dabei zu tun.
Sobald die Taste losgelassen wurde (ButtonUp), macht er dann ganz normal mit den Folgebefehlen weiter.
8)
jap, in den Klammern steht der Körper:
while(Bedingung) {Körper}
der Körper wird solange wiederholt ausgeführt, wie die Bedingung True ist.
Er soll aber gar nichts öfters ausführen, sondern nur warten, solange die Bedingung wahr ist, also die Taste (noch) NICHT gedrückt wurde.
Daher brauchen wir keinen Körper:
Wenn du schon so lang ausholst, dann bitte richtig:
nach einem while wird der nächste Befehl ausgeführt, solange die Bedingung wahr ist. Und zwar ein und genau ein Befehl. Wird im Körper, wie du das bezeichnet hast, mehr als ein Befehl gebraucht, fasst man sie durch geschweifte Klammern zu einem Befehl zusammen. Mindestens ein Befehl muß aber dastehen, zur Not ein leerer Befehl, der nur aus dem Semikolon, dem Befehlsende in C, besteht. Ein C NOP gewissermaßen. Gute Praxis ist, selbst wenn es nur einen Befehl ist, ihn in geschweiften Klammern zu schreiben. Zusammen mit passenden Einrückungen sieht man dann viel schneller, was alles zur Schleife gehört.
while ( digitalRead(13) ) {
;
}
Und natürlich wird etwas öfter ausgefürt, nämlich das Lesen des Portpins. Wenn man das deutlich machen will, sollte man schreiben:
while ( true ) {
if ( digitalRead(13) == 1 ) {
break;
}
}
So kapiert jeder sofort, worum es geht, selbst wenn er nach Jahren noch mal auf diesen Code stößt.
MfG Klebwax
ich denke, das war unnötig wie ein Kropf :-/
ich denke, das war unnötig wie ein Kropf :-/
Wenn Hr. Doktor nicht mehr zu meckern hat, war ja alles richtig
MfG Klebwax
Unregistriert
19.09.2016, 17:16
nach einem while wird der nächste Befehl ausgeführt, solange die Bedingung wahr ist. Und zwar ein und genau ein Befehl. ...
Das finde ich jetzt toll und sehr schön zusammengefaßt. Danke.
Peter(TOO)
19.09.2016, 18:42
ist ja ok, wenn man nicht iwas immer nur pauschal verteufelt. Selbst erfahrene Programmierer verwenden u.U. goto, und in diesem jetzt folgenden sehr speziellen Fall sah ich auch keine andere Möglichkeit, als es meinem Vorbild bei der Arduino- und Raspi-Portierung gleich zu tun: http://www.mindstormsforum.de/viewtopic.php?f=70&t=6790&p=66103#p66103
http://www.mindstormsforum.de/viewtopic.php?f=70&t=6790&p=66103#p67841
GOTOs sind bei einem erfahrenen Programmierer ein (seltener) Ausnahmefall und nicht die Regel.
Du wirst da GOTOs bei der Fehler- oder Ausnahme-Behandlung finden, also immer nur dann, wenn der normale Programmablauf abgebrochen werden muss, bzw. eine lineare Abarbeitung des Programms nicht mehr möglich ist.
In C wären noch setjmp() und longjmp(), bzw. in C++ catch(), throw usw. Damit kann man dann auch an eine Position ausserhalb der aktuellen Funktion springen.
MfG Peter(TOO)
- - - Aktualisiert - - -
Wenn du schon so lang ausholst, dann bitte richtig:
nach einem while wird der nächste Befehl ausgeführt, solange die Bedingung wahr ist. Und zwar ein und genau ein Befehl. Wird im Körper, wie du das bezeichnet hast, mehr als ein Befehl gebraucht, fasst man sie durch geschweifte Klammern zu einem Befehl zusammen. Mindestens ein Befehl muß aber dastehen, zur Not ein leerer Befehl, der nur aus dem Semikolon, dem Befehlsende in C, besteht. Ein C NOP gewissermaßen.
In C darf überall wo ein Befehl stehen darf auch ein Block (ein oder mehrere Befehle in geschweiften Klammern) stehen!
Hinzu kommt noch, dass bei den neueren C-Versionen (ab C99 ?) in so einem Block auch lokale Variablen deklariert werden dürfen, welche dann nur innerhalb diese Blocks gültig und sichtbar sind.
MfG Peter(TOO)
meine Profs in "Didaktik der Naturwissenschaften" haben alle unisono 1 wesentliche Regel für dieses Thema propagiert:
Die Kunst des Erklärens besteht im Weglassen.
(Ich habe das immer gern und efolgreich im Unterricht von Studenten beherzigt.)
genauso wenig wie man Kindern in der 1. Klasse fürs Kleine Einmaleins was von Gruppen- und Körperaxiomen, Vektorräumen und Abgeschlossenheit bei Polynomen erklärt, muss man zur Erklärung bei einer Frage nach {} und ; hinter einem while() die Theorie und Praxis der Entwicklung der Programmiersprache C seit K+R samt ANSI Standards und C++ Erweiterungen über 1999 bis 2016 auf verschiedenen Pattformen mit verschiedenen Compilern erklären. Zu viel des Guten erschlägt die Fragenden, während man im Falle von Verständnisproblemen anschließend bei eventuellen Nachfragen immer noch etwas mehr an Erklärungen bedarfsweise, häppchenweise, nachschieben kann.
Schon in Spoerls "Feuerzangenbowle" heißt das da:
"Da stelle mer uns amal janz dummmm und saachen mer sooo...:"
Andere sagen dazu:
K.I.S.S.
8)
i_make_it
20.09.2016, 08:34
Schon in Spoerls "Feuerzangenbowle" heißt das da:
"Da stelle mer uns amal janz dummmm und saachen mer sooo...:"
Andere sagen dazu:
K.I.S.S.
8)
Oder wie ich es mal schön gelesen habe "wie Funktioniert das Erklären von Wissenschaft"
Es gibt Lügen für Kinder (Grundschule)
Lügen für Jugendliche (weiterführende Schule)
Und Lügen für Erwachsene (Uni)
Und dann gibt es die die glauben die Wahrheit zu kennen (Wissenschaftler)
Ich persöhnlich ziehe das Fachbuch vor wo alles drin steht und lese den Teil den ich jeweils brauche.
Wenn ich mehr Infos brauche lese ich das selbe Buch nochmal.
Dann gibt es die die es vorziehen 10 Bücher zum selben Thema zu kaufen, Das sind oft diejenigen die sich schlau fühlen wenn sie einen Meter Regal füllen mit etwas wo ein einziges 5cm dickes Buch in dem das alles einmal steht, reicht.
Hier verhält es sich denke ich ähnlich.
Genau, hier verhält es sich ähnlich, aber kaum einer hält sich dran.
Oder andersrum:
Manche glauben, mit wahnsinnig viel an (möglicherweise oder auch sicherlich) richtigen Informationen eine Anfänger-Frage umfassend richtig zu beantworten.
Dann ist zwar oft vielleicht das Niveau gestiegen, aber es ist keiner mehr drauf.
Auf eine einfache Frage eines "Anfängers" gebe man also bitte eine einfache Antwort, wie bei Lügen für Kinder (Grundschule) oder Lügen für Jugendliche (weiterführende Schule).
(Wissenschaftler fragen sicher anders und erwarten auch eine Antwort auf einem anderen Niveau, doch gerade Naturwissenschaftler (zumindest ernsthafte) wissen, dass sie nichts wissen, und dass sie nur mit Modellen arbeiten, nicht mit der "Wahrheit", denn die können wir - erkenntnistheoretisch betrachtet - niemals erfahren. Aber weder ist dieses Topic hier eines auf diesem Niveau, noch erfordert es ein solches Niveau bei der Beantwortung, und es ist auch völlig unabhängig von der Anzahl von Zentimetern im Bücherregal)
ich habe gerade dir das schon öfter versucht zu verklickern, wenn es um "bedarfsgerechte Informationen, häppchenweise" ging....
oberallgeier
20.09.2016, 09:02
.. die es vorziehen 10 Bücher zum selben Thema zu kaufen .. wo ein einziges 5cm dickes Buch in dem das alles einmal steht, reicht ..Ne häufige Variante : zu "meiner" Zeit in der Aerodynamik gabs den Schlichting-Truckenbrodt, Aerodynamik des Flugzeugs, zwei Bände, voll mit Differentialgleichungen, SEHR gut geschrieben, ziemlich kopflastig, da BRAUCHTE man Mathe. Diese beiden Bände "hatte man" eben, fast jeder, ich hab die noch immer, überall standen oder lagen die am Schreibtisch, griffbereit UND gut sichtbar. Und sehr viele Kollegen hatten dazu - IN der Schublade, nicht offen - den "Dubs", Aerodynamik der reinen Unterschallströmung - darin war alles übersichtlich, schön verständlich erklärt (hab ich noch immer *gg* - steht wie immer in der Gruppe "Truckenbrodt" im Regal zusammen mit allerlei Strömungsmechanik). Keine/kaum Differentialgleichungen, anschaulich, leicht verständlich. Und das Schubladenbuch brauchen diese Kollegen um die Theorie des "Truckenbrodts" zu verstehen und sie erläutern zu können - für sich, den Vorstand und ähnliche Nonkompos.
Unregistriert
20.09.2016, 09:14
meine Profs in "Didaktik der Naturwissenschaften" haben alle unisono 1 wesentliche Regel für dieses Thema propagiert:
Die Kunst des Erklärens besteht im Weglassen.
(Ich habe das immer gern und efolgreich im Unterricht von Studenten beherzigt.)
Das gilt für Leute die wirklich was von ihrem Handwerk verstehen. Deine Erklärung zu while klang für mich wie die eines Schülers, der zeitweise draußen war, als sein Lehrer das Wesentliche vorgetragen hat; "Bedingung" und "Körper" wie aus einem Vokabelbuch für Touristen.
Beim Lesen hatte ich den gleichen Impuls zum Geraderücken des Bildes empfunden wie Klebwax. Zum Glück gibt es hier Leute, die das viel besser können als ich.
@un: was besser können?
unregistriert rumtrollen?
@oberallgeier
welchen Teil von meinen Posts bezüglich K.I.S.S. hattest du nicht verstanden oder habe ich dich etwa missverstanden?
Oder meinst du wie ich (was nicht ganz klar durchkommt), dass überzogene und unangemessen komplizierte Antworten verwirrend sind und stimmst mir dann auch zu, wie ich z.B. das while() ohne {} "Körper" erklärt habe?
Manche glauben, mit wahnsinnig viel an (möglicherweise oder auch sicherlich) richtigen Informationen eine Anfänger-Frage umfassend richtig zu beantworten.
Dann ist zwar oft vielleicht das Niveau gestiegen, aber es ist keiner mehr drauf.
Auf eine einfache Frage eines "Anfängers" gebe man also bitte eine einfache Antwort, wie bei Lügen für Kinder (Grundschule) oder Lügen für Jugendliche (weiterführende Schule).
(Wissenschaftler fragen sicher anders und erwarten auch eine Antwort auf einem anderen Niveau,
Zu viel des Guten erschlägt die Fragenden, während man im Falle von Verständnisproblemen anschließend bei eventuellen Nachfragen immer noch etwas mehr an Erklärungen bedarfsweise, häppchenweise, nachschieben kann.
Ich fühle mich gedrängt, nochmal zu antworten, weil ich diese Aussagen von HaWe für sehr richtig und für Anfänger sehr hilfreich halte. Man ist als Anfänger doch schon überglücklich, wenn man eine Programmnuss überhaupt geknackt bekommt und kapiert was da passiert. Da schwirrt einem doch sowieso schon der Kopf. Auch als Anfänger weiß man doch, dass es immer andere und optimalere Lösungen gibt.
vG
fredyxx
Powered by vBulletin® Version 4.2.5 Copyright ©2024 Adduco Digital e.K. und vBulletin Solutions, Inc. Alle Rechte vorbehalten.