Darf ich um Tipps zur Programmierung bitten?

Nachdem ich hier so hilfreiche in Sachen Elektronik beraten wurde, wollte ich mal fragen, ob es unangebracht ist, auch im Hilfe bei der Programmierung zu fragen.

Da ich weder C Programmierer bin noch vorher Erfahrung mit Arduino Programmierung hatte und mich damit erst seit ca. 2 Wochen intensiv beschäftige, gibt es vermutlich noch Potential zur Verbesserung.
Auch gibt es einige Dinge, die ich festgestellt habe, speziell im Bezug auf den seriellen Datenaustausch mit meinem Windowsprogramm, das ich dazu geschrieben habe, zu denen ich noch Fragen habe.

Ich habe in den Tiefen dieses Forums einen Thread entdeckt, wo extrem hilfreiche Tipps, speziell zur Verwendung von Strings, gegeben wurden, und das an Hand eines Sketches, den jemand gepostet hat. Habe mir die dortigen Hinweise zu Herzen genommen und meinen ganzen Sketch diesbezüglich durchgearbeitet und umgebaut und bin dann von ca. 11000 Byte verwendetem Programmspeicherplatz auf etwa 7500 Bytes gekommen, bei gleicher Funktionalität.

Falls es ok ist, würde ich den Sketch hier rein stellen. Ich muss ihn zuerst aber noch besser kommentieren, damit man sich auskennt.

LG
Helmut

helste:
... wollte ich mal fragen, ob es unangebracht ist, auch im Hilfe bei der Programmierung zu fragen.

Nein, hier nach Hilfe bei der Programmierung zu fragen ist nicht unangebracht. Ganz im Gegenteil.

Da Du „Frischling“ bist, was Programmierung angeht, mal ein allgemeiner Tipp: Du erleichterst Dir und ggf. uns das Lesen und Verstehen Deines Codes sehr, wenn Du „schönen“ Code schreibst. D. h. vor allem einheitliche Klammerung (ich bevorzuge, wenn öffnende und schließende geschweifte Klammern gleich weit eingerückt sind) und Einrückungen.

Etwas wie z. B.

loop()
{
  for(...)
  {
    bla();
    blubb();
    ...
  }
}

ist deutlich besser zu lesen als

loop()
{
for(...)
{
bla();
blubb();
...
}
}

Das finde zumindest ich. Vorteil ist, dass die Struktur des Codes sehr viel schneller erfasst werden kann. Abgesehen davon, dass man sich so kaum merken muss, wo ein Block beginnt oder aufhört, erleichtert es die Fehlersuche erheblich bzw. es hilft, ein paar beliebte Fehler zu vermeiden und den Kopf für wichtige(re) Dinge freizuhalten. Ich achte außerdem darauf, dass Funktionen oder Funktionsblöcke möglichst nicht länger sind als eine Bildschirmseite, zumindest nicht in der endgültigen Version. Zudem finde ich es nützlich, fertig programmierte Funktionen in eigene Tabs auszulagern, damit loop() schön kurz und übersichtlich bleibt.

Es gibt eine Reihe weiterer Dinge, die man beachten kann (oder sollte, je nachdem), aber das sind die Dinge, die mir spontan als die Wichtigsten eingefallen sind. Geübte Programmierer finden sich wahrscheinlich auch in „hässlichem“ Code schnell zurecht, aber es gibt hier auch Leute (wie mich), die Probleme bekommen, wenn sie mit „hässlichem“ Code konfrontiert werden. Denke daran, dass andere nicht so tief „im Thema“ bzw. in Deinem Code stecken wie Du.

Gruß

Gregor

gregorss:
Du erleichterst Dir und ggf. uns das Lesen und Verstehen Deines Codes sehr, wenn Du „schönen“ Code schreibst. D. h. vor allem einheitliche Klammerung

in der Arduino IDE einfach die "STRG + T" drücken, dann wird der Code automatisch formatiert.

Was die Codeformatierung anbelangt, so bin ich da sehr penibel. Zumindest habe ich da in meiner gewohnten Programmierumgebung (Delphi, also Object Pascal) meinen Stil und der ist auf gute Lesbarkeit des Codes ausgelegt.

Die Einrückungsstandards in Arduino sind da etwas davon abweichend. So mache ich das z.B. in Delphi so:

procedure irgendwas;

begin
if irgendeinebedingung then
   begin
   tuwas;
   end;
end;

ich rücke also immer um 3 Leerrzeichen ein und begin und end sind in der selben Spalte.
Das habe ich dann zu Beginn auch beim Arduino so praktiziert, also in etwa so:

loop()
   {
   for(...)
      {
      bla();
      blubb();
      ...
      }
   }

Dann habe ich die Formatierfunktion in der IDE entdeckt und die genutzt und seither halte ich mich da dran. Es wird halt die Blocköffnung in der selben Zeile geschrieben, wie die Bedingung und es wird um 2 Zeichen eingerückt. kann ich damit leben. Ich denke, wenn ich mich an die Standardformatierung halte, ist mein Code noch am ehesten lesbar.

Worum es mir hier geht ist vorallem, dass ich die Eigenheiten der Arduinoprogrammierung nach so kurzer Zeit natürlich noch nicht so kenne und wenn man gleich zu Beginn versucht alles richtig zu machen, dann schleppt man das nicht sein Programmiererleben lang mit.

Deshalb finde ich es super, wenn mein Code hier willkommen ist.

Ich bin gerade dabei ihn besser zu kommentieren und stelle ihn dann rein. Kommentierungen mache ich in Englisch. Nicht weil ich das so gut kann, aber weil ich ein paar internationale Kumpels habe, die dann vielleicht mit meinem Programm arbeiten wollen.

guntherb:
in der Arduino IDE einfach die "STRG + T" drücken, dann wird der Code automatisch formatiert.

Das stimmt zwar und liefert ein gutes Ergebnis, wenn man vollkommen unformatierten Code mit Strg-t behandelt, ich habe aber auch schon erlebt, dass händisch aufgehübschter Code wieder hässlich wurde, weil dort Zeilenumbrüche eingefügt wurden, wo ich sie absichtlich weggelassen hatte.

Kann sein, dass sich die automatische Formatierung diesbezüglich gebessert hat. Aber vorher zu speichern ist nie verkehrt :slight_smile:

Gruß

Gregor

Hallo,

wichtig ist, dass du möglichst auf längere Delays verzichtest. Zeitliche Steuerungen sollten über millis() gemacht werden. Stichworte sind Blinkwithoutdelay, endlicher Automat, Nachtwächtererklärung usw.
Schleifen (for, while) führen leicht zu blockierendem Code. Besser ist mit Zustandsvariablen in der Loop zu arbeiten.

In einfachen Fällen funktioniert zwar blockierender Code, aber sobald der Arduino während des Ablaufs reagieren können soll, ist man in der Sackgasse und muss das Programm oftmals komplett neu schreiben. Während der Arduino z.B. mit Delay blockiert ist, kann er z.B. keinen Endschalter mehr abfragen.

Ich habe nun so gut wie nur irgendmöglich kommentiert und hoffe dabei nichts falsch gemacht zu haben.

Hier ist nun mein erster selbstgemachter Sketch, der eigentlich genau so funktioniert, wie ich es mir vorstelle.
Einziges Problem, das ich derzeit noch habe ist, dass höhere Baudraten zu Fehlern in der Status bzw. Kommandoübermittlung vom und zum PC führen.
Mit unterschiedlichen Delay Werten geht es dann mal wieder, und dann mal wieder nicht. Irgendwo habe ich da halt noch einen Denkfehler, oder es geht halt nicht schneller. Ist eh kein Problem, da die wenigen Daten ohnehin schnell genug übertragen werden.

Noch eine Erklärung vorneweg.
Im PC Programm sind 2 Buttons definiert, die genau das gleiche machen, wie die 2 Taster an der Steuerung. Langes drücken (ab 1 Sekunde) startet den Motor und lässt ihn laufen, bis man den Taster los lässt. Kurzes drücken startet und lässt ihn weiter laufen, bis ein neuerlicher Tastendruck erfolgt. Ähnlich wie beim Fensterheber im Auto.
Solange der Motor läuft, blinkt eine rote LED. Die obere für die Bewegung nach oben und die untere für Bewegung nach unten. Die mittlere LED ist derzeit ohne Funktion. Wollte ursprünglich einen mittleren Endschalter im Mast, aber davon bin ich abgekommen.
Sobald ein Endschalter unterbrochen ist, leuchtet die zugehörige ED dauerhaft und die Relais werden auf LOW gesetzt.
Im Windowsprogramm gibt es noch zusätzliche Buttons, um den Initialisierungsprozess zu starten. Dabei läuft dann der Mast einmal bis zum Endpunkt und dann den ganzen Weg nach oben oder nach unten und misst die Zeit. Diese wird dann mittels eines Buttons im Windowsprogramm gespeichert oder verworfen. Diese Zeit wird dann während des rauf oder runter laufens verwendet, um die aktuelle Höhe zu berechnen. Ich habe keine Ahnung, ob die Geschwindigkeit immer konstant ist und es keine Schlupf gibt oder das temperaturabhängig ist. Eventuell muss ich da einen anderen Weg finden, aber aktuell setze ich mal auf die Zeit. Sobald ein Endschalter öffnet wird die Höhe sowieso auf Minimum bzw. Maximum gesetzt.
Wenn die Bewegung stoppt, wird der aktuelle Wert ins EEPROM geschrieben, damit das als Basis für die nächste Berechnung verwendet werden kann.
Ich hoffe meine Erklärungen sind halbwegs verständlich.

Den Sketch hänge ich als Attachment an, da sonst die maximale Länge für einen Beitrag überschritten wird.

mastctrl.ino (20.2 KB)

helste:
Ich habe nun so gut wie nur irgendmöglich kommentiert und hoffe dabei nichts falsch gemacht zu haben.

Die einzigen beiden Dinge, die mir aufgefallen sind: Viele Zeilen sind länger als 80 Zeichen. Evtl. ebenfalls ändern würde ich die Variablennamen - so, dass sie leichter zu lesen sind. Siehe hier.

Gruß

Gregor

Es gibt noch andere Datentypen als int, für alle Pin-Nummern z.B.,
ebenso halte ich einen int für den LED-Zustand als unpassend.

pinMode gehört nicht in den Konstruktor, sondern in eine eigene init/begin Funktion.

Alle Variablen die sich mit Timing auseinandersetzen sollten unsigned sein.

  pinMode(reluppin, OUTPUT);
  pinMode(reldnpin, OUTPUT);

ist doppelt (Konstruktor und setup).

Dein Umgang mit Bounce2 erscheint mir überkompliziert und 'komisch'.

Hallo,

willkommen. Aller Anfang ist schwer und ich möchte deswegen gleich zur Sache kommen. :slight_smile:
Also die Geschichte mit der Seriellen haste sicherlich in der Form nicht aus diesem Forum. Entweder woanders her oder falsch zusammenkopiert.

char inChar = (char)Serial.read()
die Serielle kann nur Byteweise senden und empfangen. Ein Cast auf char vor Übergabe ist nicht notwendig.

Serial.flush()
dient zum warten bis der Inhalt des serielle Sendebuffers komplett gesendet wurde. Bedeutet, macht beim einlesen keinen Sinn.

Desweiteren wunderst du dich wegen Kommunikationsproblemen. Dir fehlen klar abgerenzte Sende und Empfangsroutinen. Du möchtest mehrere Zeichen hintereinander einlesen. Wohin? Du hast keinen Puffer?

Gewöhne dir an mehr Funktionen zuschreiben und diese in der loop aufzurufen. Man erhält und behält den Überblick. Die loop mit allen Code wird schwierig zulesen und es erschwert die Fehlersuche. Du bekommst damit auch lokale Variablen die plötzlich das machen was sie sollen. Du wirst staunen. Ich denke dabei gerade an deine fromserial = false

Und der Compiler wirft einen Haufen Warnungen aus.

Klar ist es schwierig jetzt den Code zu fixen. Deshalb sollte man nicht gleich alles zusammenwürfeln was man so denkt. Stück für Stück programmieren und testen testen testen. Erst wenn das bisherige sicher läuft wird die nächste Funktion eingebaut. Hinterher wird man sonst nicht mehr froh.

Whandall:
Es gibt noch andere Datentypen als int, für alle Pin-Nummern z.B.,
ebenso halte ich einen int für den LED-Zustand als unpassend.

Da hast Du Recht. Werde gleich mal alles durchgehen und die jeweils sinnvollen Datentypen verwenden.

pinMode gehört nicht in den Konstruktor, sondern in eine eigene init/begin Funktion.

Meinst Du damit den Konstruktor vom flasher? Den code habe ich mir irgendwo aus einem tutorial oder so raus kopiert. Was ist da falsch oder schlimm dran?

Alle Variablen die sich mit Timing auseinandersetzen sollten unsigned sein.

Ja, das macht Sinn. Werde ich ändern.

  pinMode(reluppin, OUTPUT);

pinMode(reldnpin, OUTPUT);


ist doppelt (Konstruktor und setup).

Eigentlich nicht. Ich finde da nur einen Aufruf dazu im Setup.

Dein Umgang mit Bounce2 erscheint mir überkompliziert und 'komisch'.

Habe mich da nach den Beispielen dazu gerichtet. Möglicherweise geht das aber auch einfacher. Ich werfe da noch mal einen Blick drauf.

In jedem Fall danke für die Hinweise.

Doc_Arduino:
Also die Geschichte mit der Seriellen haste sicherlich in der Form nicht aus diesem Forum. Entweder woanders her oder falsch zusammenkopiert.

Naja, ich mir was an Hand der Referenz zusammen gereimt. Vermutlich nicht ganz richtig. Teile sind auch von irgendwelchen Beispielen. Ich werde das nochmal überarbeiten.

char inChar = (char)Serial.read()
die Serielle kann nur Byteweise senden und empfangen. Ein Cast auf char vor Übergabe ist nicht notwendig.

Ui, das wollte ich so gar nicht. Habe wohl was geändert und vergessen was raus zu löschen. Interessanterweise nimmt der Kompiler das. das (char) sollte da nicht sein. Durch copy and paste ist das gleich mehrfach drinnen.

Serial.flush()
dient zum warten bis der Inhalt des serielle Sendebuffers komplett gesendet wurde. Bedeutet, macht beim einlesen keinen Sinn.

Ok, dann schmeiß ich das raus. Ich dachte, dass dadurch der Buffer geleert wird, damit beim nächsten Durchlauf von loop nichts mehr davon ausgelesen wird, was derzeit drinnen ist. Habe jetzt nochmal in die Referenz geschaut. Keine Ahnung, was ich da gelesen habe, oder mir dabei gedacht habe. Ist natürlich völliger Unsinn, was ich da gemacht habe.

Desweiteren wunderst du dich wegen Kommunikationsproblemen. Dir fehlen klar abgerenzte Sende und Empfangsroutinen. Du möchtest mehrere Zeichen hintereinander einlesen. Wohin? Du hast keinen Puffer?

Deshalb lese ich ja immer nur 1 Zeichen ein. Ich gebe zu, ich wollte mir damit das umständliche Handling von Zeichenketten sparen. Hatte zuerst String, aber dann bemerkt, dass das ziemlich ungünstig ist, was Ressourcen und Memorymanagement anbelangt.

Gewöhne dir an mehr Funktionen zuschreiben und diese in der loop aufzurufen. Man erhält und behält den Überblick. Die loop mit allen Code wird schwierig zulesen und es erschwert die Fehlersuche. Du bekommst damit auch lokale Variablen die plötzlich das machen was sie sollen. Du wirst staunen. Ich denke dabei gerade an deine fromserial = false

Ja, das werde ich gleich mal machen. Was ist aber mit fromserial = false; falsch? Verstehe ich jetzt nicht. Die ist doch global und das wollte ich eigentlich auch so.

Und der Compiler wirft einen Haufen Warnungen aus.

Komisch. Bei mir nicht.

Klar ist es schwierig jetzt den Code zu fixen. Deshalb sollte man nicht gleich alles zusammenwürfeln was man so denkt. Stück für Stück programmieren und testen testen testen. Erst wenn das bisherige sicher läuft wird die nächste Funktion eingebaut. Hinterher wird man sonst nicht mehr froh.

Ich kriege das schon hin mit dem Fixen des Codes.
Werde mich gleich an die Arbeit machen.

Wenn ich darf, stelle ich das Ergebnis dann zur weiteren Begutachtung hier rein.

Vielen dank erstmal für die Hilfe.

Hallo,

alles was syntaktisch richtig geschrieben ist kann oder muss der Compiler nicht anmeckern. Das heißt noch lange nicht das der Code genau das macht was man selbst möchte. fromserial wird mit jedem loop Durchlauf auf false gesetzt. Ob dadurch die Behandlung der seriellen so abläuft wie gewollt bezweifel ich.

Compiler Warnungen einschalten. Datei > Voreinstellungen > Compiler-Warnungen > "ALLE"

Ein Bsp. der Datenübertragung ohne String ist zufällig hier am Ende. Wobei man jederzeit einen String (char Array) einbauen kann. VirtualWire: Byte in Integer umwanden... Bitte um Hilfe - Deutsch - Arduino Forum

Doc_Arduino:
alles was syntaktisch richtig geschrieben ist kann oder muss der Compiler nicht anmeckern. Das heißt noch lange nicht das der Code genau das macht was man selbst möchte. fromserial wird mit jedem loop Durchlauf auf false gesetzt. Ob dadurch die Behandlung der seriellen so abläuft wie gewollt bezweifel ich.
[/quote]

Das ist mir klar, dass nicht alles was syntaktisch richtig ist, auch logisch richtig sein muss.
das mit fromserial sollte aber so passen. Die wird auf false gesetzt und wenn danach die serielle Schnittstelle einen Status liefert, dass am PC ein Button gedrückt wurde, dann wird dadurch die Routine, welche die Button Stati vom Arduino ausliest, nicht aufgerufen. Insofern sollte das so passen. Funktioniert ja auch so wie gewünscht.

Compiler Warnungen einschalten. Datei > Voreinstellungen > Compiler-Warnungen > "ALLE"

[/quote]

Danke für den Tipp. Das wusste ich nicht. Jetzt sehe ich die Warnungen. Habe alles korrigiert, aber 2 Warnungen kommen noch zur EEPROM.h. Das liegt wohl nicht in meinem Bereich, oder?

Ein Bsp. der Datenübertragung ohne String ist zufällig hier am Ende. Wobei man jederzeit einen String (char Array) einbauen kann. VirtualWire: Byte in Integer umwanden... Bitte um Hilfe - Deutsch - Arduino Forum
[/color]
[/color]

Super. Danke. Das schaue ich mir gleich an.

Die Doppelten habe ich auf Grund der gruseligen Namen verwechselt, sorry.

Versuch dich doch mal in CamelCase also z.B. ledDnPin.

pinMode in einem Konstruktor kann aufgerufen werden bevor die Hardware initialisiert wird,
da es keine garanierte Reihenfolge der Ausführung von globalen/statischen Konstruktoren gibt.

Das kann also auf einer Platform fubktionieren, auf einer anderen nicht.
Besser ist es die Hardware Einstellungen in einer eigenen Routine durchzuführen,
die dann von setup aus aufgerufen wird, dann ist die Hardware garantiert soweit.

Whandall:
Die Doppelten habe ich auf Grund der gruseligen Namen verwechselt, sorry.

Ja sorry dafür. Ich werde das nochmal überarbeiten.
Habe jetzt aber mal einiges überarbeitet. Alle funktionen ausgelagert und Datentypen geändert.
Nun klappts auch mit der seriellen Übertragen mit größeren Baudraten. Ich Idiot hatte int als Datentyp für meine Konstante für die Baudrate und wundere mich, wenn das mit Werten größer maxint nicht funktioniert.

Versuch dich doch mal in CamelCase also z.B. ledDnPin.

Ja, das werde ich als nächstes machen.

pinMode in einem Konstruktor kann aufgerufen werden bevor die Hardware initialisiert wird,
da es keine garanierte Reihenfolge der Ausführung von globalen/statischen Konstruktoren gibt.

Das kann also auf einer Platform fubktionieren, auf einer anderen nicht.
Besser ist es die Hardware Einstellungen in einer eigenen Routine durchzuführen,
die dann von setup aus aufgerufen wird, dann ist die Hardware garantiert soweit.

Das muss ich erst mal durchdenken, damit ich es verstehe. Mache ich nachher gleich mal, nachdem ich den Kurzen ins Bett gebracht habe.

Wenn dann alles nochmal überarbeitet ist, stelle ich den Code nochmal rein.

Danke erstmal. Ihr habt mir alle super geholfen.

helste:
Danke erstmal. Ihr habt mir alle super geholfen.

Ja, und sie haben alle eine wichtige Sache vergessen: Lerne Fluchen :slight_smile: Je schlimmer desto besser.

Schönes WE!

Gregor

gregorss:
Ja, und sie haben alle eine wichtige Sache vergessen: Lerne Fluchen :slight_smile: Je schlimmer desto besser.

Haha, ja das sollte man können. Aber im Ernst, ich bin da sehr geduldig. So schnell bringt mich nichts aus der Ruhe.
Ich habe jetzt mal alles überarbeitet, außer der seriellen Übertragung. Die funktioniert so wie sie jetzt ist, sehr gut. Ich werde mir das aber nachher trotzdem mal an Hand der genannten Quelle ansehen und umbauen.

Jetzt habe ich ein komisches Phänomen, welches ich zuerst auf die Software zurückgeführt hatte, aber es hat sich herausgestellt, dass es hardwareseitig sein muss.
Wenn ich den Arduino vom USB Kabel nehme und wieder anstecke, dann passiert es alle paar mal, dass eines der Relais eingeschalten wird, so als würde ein Taster gedrückt werden. Das kommt manchmal erst beim 5. mal neu anstecken, dann wieder beim 3. mal, oder halt irgendwie zufällig. Ist auch manchmal das eine und manchmal das andere Relais. Ich habe nun am Steckbrett die ganze Schaltung nachgebaut und da passiert das nicht. Bei meinem Steuergerät passiert es aber. Dann habe ich von dort die ganzen Drähte auf den Arduino vom Steckbrett umgesteckt und hatte das gleiche Problem.
Wenn ich nun aber nur die Leitungen zu den 2 Tastern im Steuergerät runter ziehe, dann passiert es niemals.
Ich vermute nun, dass die Taster nicht in Ordnung sind. Das sind 2 alte Taster, von einem Frontpanel eines alten analogen SAT Receivers. Ich tausche die mal aus und schau was passiert.
Da es aber nicht an der Software liegt, habe ich mal die aktuelle Version der Software angehängt.

functions.ino (13.9 KB)

mastctrl.ino (6.84 KB)

Das kommt manchmal erst beim 5. mal neu anstecken, dann wieder beim 3. mal, oder halt irgendwie zufällig. Ist auch manchmal das eine und manchmal das andere Relais. Ich habe nun am Steckbrett die ganze Schaltung nachgebaut und da passiert das nicht. Bei meinem Steuergerät passiert es aber.

Tja, die Hardware...

Wenn die Relais per MOSFET geschaltet werden, fehlt evetuell ein Widerstand zwischen Gate und Drain, der das Gate sicher unten hält, bevor der Arduino eingeschaltet, und der Pin auf OUTPUT + LOW ist...
Wenn du einen Finger auf den Gate-Pin hältst, passiert das wohl auch auf dem Steckbrett 8)

Hab jetzt auf die Schnelle in diesem Thread keinen Schaltplan gefunden, aber du wolltest ja auch nur Tips zur Programmierung :wink:

Ich glaube habe die Ursache für das Problem gefunden.
Ich habe ja das Steuergerät in dem der Arduino verbaut ist, mittels eines Cat5 Kabels mit einem anderen Teil, wo die Relais sitzen verbunden. Über diese Leitung laufen nun die Signale von den Endschaltern vom Relaisteil zum Arduinoteil und für die Relais vom Arduinoteil zum Relaisteil. Zusätzlich habe ich im Relaisteil 2 Taster vorgesehen, die es ermöglichen sollen, von dort ebenfalls den Arduino zu steuern, eben so, als würde man die Taster am Arduinoteil drücken. Diese Leitungen sind nun noch nicht im Relaisteil an Taster angeschlossen, aber im Arduinoteil bereits an die Tasterpins angeschlossen.
Offenbar dürfte da induktiv irgendwas passieren, dass da fälschlicherweise was eingelesen wird.
Ich habe nun dies Leitungen im Arduinoteil unterbrochen und damit tritt das Problem nicht mehr auf.
Mein Elektronikverständnis ist leider zu gering, als dass ich nun sagen könnte, wieso das passiert und schon gar nicht reichen sie aus, um Abhilfe zu schaffen.

Im Notfall verzichte ich auf die Taster im Relaisteil.