Probleme mit Void und Funktionen.

Erstmal ein herzliches hallo an alle!
Eine super community ist das hier -habe schon sehr viel gelesen -nur leider nicht alles kapiert. :blush:

Zum Projekt.

Ich habe vor einiger Zeit ein Ferngesteuertes Auto umgebaut (aus einem Tutorial das ich im Netz fand) welches nun mit einem Arduino Uno bzw. meinem Smartphone (Android) gesteuert wird. Der Arduino dient als Empfänger und mein Smartphone als Sender über Bluetooth. Soweit funktioniert das auch alles (Die Kiste fährt und tut was sie soll) da alles ganz simpel über Low/High gesteuert wird. Ich sende ein "F" an den Arduino und der Schaltet mir einen Pin auf High. Ich sende ein "Z" und dieser schaltet den Pin wieder auf Low. (u.s.w)

(Hier der Link zum BasisTutorial: http://www.mobot.es/MobotBTCar.html ) -die App habe ich mit AppInventor nachgebaut weil ich die App aus dem Tutorial hässlich fande und ich das Smartphone lieber im Landscape Modus halte.

Jetzt zum Punkt der ganzen geschichte.

Ich würde gerne bei meinem neuen Auto welches ich gerade baue auch gerne Blinker und solche Funktionen einbauen. Und genau da hängt es in meinem Gehirn wenn ich den Code aus dem Tutorial als Basis nehme.

Hier ein Bsp:

int light = 12; // Pin 12 - Light

char val; // Variable to receive data from the serial port

void setup() {

// initialize the digital pins as output
pinMode(light, OUTPUT);

Serial.begin(9600); // Start serial communication at 9600bps
}

// Block Light on

void light_on() {
digitalWrite(light, HIGH); // turn the LED on (HIGH is the voltage level)

}

// Block Light Off
void light_off() {
digitalWrite(light, LOW);
}
// Read serial port and perform command
void performCommand() {
if (Serial.available()) {
val = Serial.read();
}
if (val == 'f') { // Command Light On
light_on();
} else if (val == 'z') { // Command Light Off
light_off();
}
}

void loop() {
performCommand();
}

Die Led/Pin wird per Smartphone High/Low geschaltet. Funktioniert. Aber sobald ich möchte, dass in diesem Block

void light_on() {
digitalWrite(light, HIGH); // turn the LED on (HIGH is the voltage level)

}

geblinkt wird fangen die Probleme an. Die "Blink" beispiele der Arduino-Referenz einfach in den "Block" zu Kopieren klang einfach, stellten sich aber schnell als "Scheisse, geht doch nicht" raus -ob nun mit Delay oder ohne. Das Problem ist einfach diese Void Geschichte. Jede Art von "Blink" setzt irgendwie ein Void Loop() vorraus. Void Loop() habe ich allerdings schon am ende des Sketches zum Lauschen auf Serial. :~

Ich habe langsam das gefühl, dass der komplette aufbau des codes einfach nicht mit meinem vorhaben zu machen ist. Ich möchte einfach, dass mein arduino diverse funktionen "loopt" wie zb. blinken aber nebenbei noch in der lage ist variablen zu empfangen bzw. zu verarbeiten um andere pins weiterhin high/low zu schalten z.b um das blinken abzuschalten oder rückwärts und forwärts zu fahren.

Ich habe zwar u.a auch das Buch von Erik Bartmann zum Arduino allerdings bringt mich das auch nicht wirklich weiter da ich einfach zu blöd bin meine wünsche im sketch umzusetzen. Ich verstehe zwar das grundproblem in "meinem" code, habe aber absolut keine skills es so zu frickeln, dass es funktioniert. Bin da mittlerweile wirklich sehr frustriert da ich schon viele nächte hinter diesem problem hänge (immer mit dicken augen zur arbeit :smiley: ). Ich kann jetzt auch nicht unbedingt die programmiersprache von A-Z lernen da ich dafür einfach keine zeit habe durch meinen beruf.

(Ich würde auch gerne diverse €€€ springen lassen damit die scheisse endlich funktioniert. Damit hätte ich kein problem.)

Vll. kann mir ja jemand helfen. :blush:

Zunächstmal denke ich das hier das Anbieten von Geld Deine Chancen auf Hilfe nicht steigert. Ich kann mich irren, aber das ist bisher mein Eindruck.
Du wirst vermutlich nicht darum herum kommen, Dich mit einigen Dingen auseinander zu setzen, da hier recht einheitlich "Hilfe zur Selbsthilfe" - gelebt wird.

Zu Deinem Problem. Eine loop() Funktion reicht eigentlich völlig. Was Du zusätzlich brauchst, sind erstmal Variablen, die bei bestimmten Kommandos die von Deinem Smartphone kommen, gesetzt werden. Z.B.
" boolean blinker_left, blinker_right = false;"
Das Blinken sollte nicht das Smart-Phone übernehmen, sondern der Arduino. Das Smartphone sendet nur ein Blinker ein oder aus.
Diese Variablen kannst Du global definieren, dann können alle Funktionen darauf zugreifen. Das "performCommand()" setzt nun nur die die Variablen auf "true" oder "false". In Deiner loop()-Funktion baust Du nun mehrere Schritte ein, bei denen Du prüfst ob eine dieser Variablen gesetzt ist. Wenn ja springst Du in eine der Funktionen
z.B.

void blink_left(int pin) {
  static long lastmillis = 0;
  //wenn eine sekunde um ist, dann pin umschalten
  if((lastmillis + 1000) < millis()) {

     //lesen und negieren des "Blink"-Pins und dann zurück schreiben
     digitalWrite(pin,(!digitalRead(pin)));

     //aktuelle Zeit merken
     lastmillis = millis();
  }
}

Die Funktion blockiert nicht und lehnt sich an das "Blink without delay" an. Ich habe die einzelnen Schritte halbwegs dokumentiert. Versuch mal zu verstehen wie die Funktion arbeitet und baue die dann in Deinen Sketch ein.
Mario.

ThinkCentre:
Ich möchte einfach, dass mein arduino diverse funktionen "loopt" wie zb. blinken aber nebenbei noch in der lage ist variablen zu empfangen bzw. zu verarbeiten um andere pins weiterhin high/low zu schalten z.b um das blinken abzuschalten oder rückwärts und forwärts zu fahren.

Im Prinzip brauchst Du ein nicht-blockierendes Blinken, das ohne delay funktioniert.

Und zur Arduino-Entwicklungsumgebung gehört sogar ein Beispiel dazu namens "BlinkWithoutDelay".
Das ist ein bisschen zu kompliziert gemacht. Ich zeige Dir mal was anderes.

Das Grundprinzip funktioniert so:
Du nimmst einen bestimmten Timer, der in einem Takt eine Zahl fortlaufend weiterzählt.
Und dann schaust Du einfach nach

  • Wenn Blinker nicht gesetzt ist ==> Blinkerlampe aus
  • Wenn Blinker gesetzt ist
  • Falls Timertakt eine gerade Zahl ist, Lampe aus
  • Falls Timertakt eine ungerade Zahl ist, Lampe an

Als Takt kannst Du den millis() Timer verwenden. Der Tickt im Millisekundentakt, und 1000 mal Blinken pro Sekunde ist vielleicht etwas zu viel. Zweimal pro Sekunde blinken wäre OK? Dann nimm als Takt "millis()/500"!

Und die loop erweiterst Du um eine weitere Prüfung, nämlich nicht nur, ob ein Kommando verarbeitet werden muss, sondern auch, um es Zeit ist, den Blinker umzuschalten.

#define blinkPin 13
boolean blinkState=true;

void performBlinkState(boolean blinkerGesetzt) {
  if (blinkerGesetzt) {
    if (((millis()/500)%2)==0) digitalWrite(blinkPin,LOW);
    else digitalWrite(blinkPin,HIGH);
  }
  else
    digitalWrite(blinkPin,LOW); 
}

void loop() {
  // put your main code here, to run repeatedly: 
 performCommand();
 performBlinkState(blinkState); 
}

Die globale Variable "blinkState" setzt Du dann immer mit Deinen Funkkommandos true oder false. Und je nachdem blinkt es am blinkPin immer an/aus oder nicht. Wenn Du eine andere Blinkfrequenz möchtest, ersetzt Du die Umschaltzeit von 500 Millisekunden durch einen anderen Wert, für eine Lampenumschaltung alle 333 Millisekunden setzt Du z.B. einfach 333 statt 500 in der Funktion ein.

Hilft das vielleicht weiter?
Hinweis: Die Erweiterung auf zwei Blinker (links/rechts) ist relativ einfach.

Hallo jurs,

eine sehr elegante Lösung, um ein Pin zwischen den zwei Zuständen umzuschalten. Zumal zusätzlich noch RAM gespart wird, weil man sich den alten Wert nicht merken muss.
Die Funktion läßt sich sogar noch ein wenig eindampfen, wenn man den zu setzenden Wert direkt aus aus der Berechnung verwendet:

void performBlinkState(boolean blinkerGesetzt) {
  if (blinkerGesetzt) {
    digitalWrite(blinkPin,(millis()/500)%2);
  }
  else
    digitalWrite(blinkPin,LOW); 
}

Da die Funktion ansich ja "stateless" ist, kann man sie sogar für mehr als eine "Blinkaufgabe" einsetzen, wenn man blinkPin und die Zeit zusätzlich als Parameter übergibt.

Vielen Dank für diese clevere Lösung.

Mario.

Autsch, "%2" ist nicht elegant. Die Modulo Funktion ist teuer. Besser wäre es "&1" zu schreiben weil "&" direkt in Maschinensprache übersetzt werden kann. Weiterhin kann man auch das "if" noch wegoptimieren, d.h. die bessere Lösung wäre:

void Blink(boolean blinkerGesetzt) {
    digitalWrite(blinkPin, (millis()/500) & blinkerGesetzt);
}

Nachtrag: das "/ 500" habe ich glatt übersehen. Das ist auch teuer, also besser alle 512ms schalten, dann bekommt man:

void Blink(boolean blinkerGesetzt) {
    digitalWrite(blinkPin, (millis() >> 9) & blinkerGesetzt);
}

Kürzer und eleganter geht es ja kaum noch. :astonished:
Nicht schlecht.
Zusammengefasst wäre dann

/**
Time Table für "time"
value         blink time
 1               2 ms
 2               4 ms
 3               8 ms
 4               16 ms
 5               32 ms
 6               64 ms
 7               128 ms
 8               256 ms
 9               512 ms
10              1024 ms
11              2048 ms
12              4096 ms
13              8192 ms
...
**/
void Blink(boolean blinkerGesetzt,byte time, byte blinkPin) {
    digitalWrite(blinkPin, (millis() >> time) & blinkerGesetzt);
}

Damit kann ein beliebiges Pin entsprechend der "Time Table" (sogar bedingt) umgeschaltet werden, ohne ein einziges IF statement.
Was mich mal wieder zu dem Schluss bringt, das C eine coole Sprache ist und das mit deutlich weniger Sonderzeichen als perl :slight_smile:

Hi,

vielen dank für eure mühen aber ich schaffe es nicht eins der von euch geposteten beispiele in meinen vorhandenen sketch einzubauen. Ich verstehe zwar was die beispiele machen dank eurer beschreibung und das sie den arduino nicht blockieren aber bekomme sie auch nach zwei tagen "fummeln" einfach nicht an den richtigen stellen implementiert.

Ich ersetze eure beispiele durch diese stelle,

void light_on() {
  digitalWrite(light, HIGH);   // turn the LED on (HIGH is the voltage level)
 
}

passe hier und da noch etwas an aber bekomme nur errors. Ich denke da wohl viel zu sehr in böcken bzw. blockweise. :~

Ich denke da wohl viel zu sehr in böcken bzw. blockweise.

Weiss nicht.
In C sagt man halt Funktion statt Block, und eine Funktion, die keinen Wert zurückliefert und keine Parameter braucht, ist nichts anderes als ein Funktionsblock, der mehrfach verwendet werden kann.

Liegt ein Problem etwa daran, dass du in deinem performCommand() oder in loop() gar nichts machst, wenn kein neues Kommando kommt ?
Damit es blinkt, musst du schon in jedem loop() - Durchlauf irgendwo etwas wie Udo's Blink(true); aufrufen.

passe hier und da noch etwas an aber bekomme nur errors

Dann hast du ja was zu tun. -- Wir sind nicht neugierig :wink:

Dann poste doch mal den Code wo Du Fehler bekommst. Dann muss Uwe seine Kristallkugel nicht polieren, falls die überhaupt schon von der Reparatur zurück ist :wink:
Wenn wir sehen wo Du das Beispiel eingebunden hast, können wir Dir auch sagen wo es klemmt.

Schön zusammengefaßt!
:slight_smile:

Okay, ich bin von beliebig einstellbarer Blinkzeit ausgegangen.

Wobei die Arduino-Software beim Kompilieren den Compiler mit Optimierungsoption verwendet. Wenn man also von Hand Zweierpotenz-Konstanten im Code verwendet, dann sollte
(millis() / 512)
und
(millis() >> 9)
zu keiner unterschiedlichen Laufzeit des Programms führen, da der Compiler das selbst in entsprechend optimierten Maschinencode umsetzt, wenn Zweierpotenz-Konstanten bei einer Integerdivision verwendet werden.

mkl0815:
Dann poste doch mal den Code wo Du Fehler bekommst. Dann muss Uwe seine Kristallkugel nicht polieren, falls die überhaupt schon von der Reparatur zurück ist :wink:
Wenn wir sehen wo Du das Beispiel eingebunden hast, können wir Dir auch sagen wo es klemmt.

Hi,

so z.b:

#define blinkPin 13
boolean blinkState=true;

char val; 

void setup() {
  
 
pinMode(blinkPin, OUTPUT);
  
Serial.begin(9600); 
}

void performBlinkState(boolean blinkerGesetzt) {
  if (blinkerGesetzt) {
    if (((millis()/500)%2)==0) digitalWrite(blinkPin,LOW);
    else digitalWrite(blinkPin,HIGH);
}
  else
    digitalWrite(blinkPin,LOW); 
}

void performCommand() {
  if (Serial.available()) {
    val = Serial.read();
}
    if (val == 'f') { 
      performBlinkState();
          }
        }

void loop() {
performCommand();
performBlinkState(blinkState); 
}

:blush:

ThinkCentre:
void performCommand() {
if (Serial.available()) {
val = Serial.read();
}
if (val == 'f') {
performBlinkState(); // ERROR!
}
}

performBlinkState ist eine Funktion, die einen Parameter erwartet, entweder "true" wenn geblinkt werden soll und "false" wenn nicht geblinkt werden soll. Du kannst diese Funktion nicht ohne Parameter aufrufen!

Außerdem gehört die Funktion hier nicht her, denn die Funktion wird aus der loop heraus aufgerufen.

An Stelle der von mir mit ERROR! gekennzeichneten Zeile gehört sowas hin, was nur den Blinkstatus umschaltet:
blinkState=!blinkState;

Mit Code meinte ich eigentlich auch das komplette Programm. Auszüge sind immer ungünstig, weil man sich keinen Überblick verschaffen kann was sonst noch so ausgeführt wird und welche globalen Variablen es gibt.
Ansonsten hat jurs recht, in der Funktion "performCommand()" sollten nur Flags gesetzt werden, um Funktionen Deines Modells zu aktivieren oder deaktivieren. Ausgeführt werden die Funktionen separat in der loop().
Mario.

jurs:
An Stelle der von mir mit ERROR! gekennzeichneten Zeile gehört sowas hin, was nur den Blinkstatus umschaltet:
blinkState=!blinkState;

mmh, wenn blinkState=!blinkState; umschaltet (on/off/off/on), dann müsste was ich wieder getrieben hab grundsätzlich falsch sein. Blinken wird zwar aktiviert bei "f" empfang über serial aber das wars dann auch schon.

#define blinkPin 8
boolean blinkState=false;
int f = true;

char val; 

void setup() {
  
 
pinMode(blinkPin, OUTPUT);
  
Serial.begin(9600); 
}

void performBlinkState(boolean blinkerGesetzt) {
  if (blinkerGesetzt) {
    if (((millis()/500)%2)==0) digitalWrite(blinkPin,LOW);
    else digitalWrite(blinkPin,HIGH);
}
  else
    digitalWrite(blinkPin,LOW); 
}

void performCommand() {
  if (Serial.available()) {
    val = Serial.read();
}
            }

void loop() {
performCommand();
performBlinkState(blinkState); 
    if (val == 'f')  { blinkState=!blinkState; }
}

Btw, wie ist denn das mit dieser "Flag" geschichte gemeint ? :sleeping:

Entschuldige das ich das so sage, aber ich habe ein wenig das Gefühl, das Du eher wild rumprobierst, als die vorgeschlagenen Lösungen umzusetzen. Das ist nicht böse gemeint, ist aber der falsche Weg. Sicherlich wird es auch so, mit einer hinreichend großen Zahl von Versuchen, irgendwann funktionieren, aber das ich kein guter Lösungsweg. Denn spätestens wenn Du noch eine Ergänzung einbauen willst, fängt das Probieren von vorne an.

Lass uns mal analysieren, was Dein aktuelles Programm eigentlich macht:

In jedem loop() Durchlauf wird als erstes die Funktion performCommand() aufgerufen. Diese schaut ob ein Kommando übergeben wurde und schreibt es in die globale Variable "val", aber nur wenn tatsächlich ein Kommando übergeben wurde.
Anschliessend wird performBlinkState() aufgerufen. Das schaut, ob die Variable "blinkState" true ist, wenn ja wird geblinkt. Wenn der Werte "false" ist, wird nicht geblinkt.

Nachdem performBlinkState() abgearbeitet ist, wird die globale Variable "val" auf den Wert "f" (das Kommando für "Blinken") geprüft und wenn "f" in val steht, wird blinkState umgeschaltet (von true zu false, oder von false zu true).
Hier steckt aber genau Dein Problem.

Angenommen irgendwann wird das Kommando "f" gesendet. Und zwar genau einmal. Dann schreibt Deine Funktion "performCommand()" das "f" in die Variable "val". Dort bleibt das "f" dann stehen, bis ein anderes Kommando gesendet wird. Da aber in jedem Durchlauf von loop() wieder der Vergleich durchgeführt wird, ob "f" in val steht, wird auch in jedem Durchlauf von loop() der Wert von blinkState wieder umgeschaltet.
Das ist aber nicht was das Programm machen soll. Der Zustand von blinkState soll ja nur dann EINMAL !! umgeschaltet werden, wenn da Kommando "f" empfangen wird, aber nur dann.

Damit muss das Umschalten dann passieren, wenn das Kommando ausgewertet wird, also in der Funktion "performCommand()":

void performCommand() {
    if (Serial.available()) {
        val = Serial.read();

        //Kommando auswerten
        if(val == 'f') {
            //wenn 'f' dann Blinker umschalten
            blinkState=!blinkState;
        }
    }
}

In Deiner loop() brauchst Du dann dafür gar nichts weiter machen, außer die Funktion performBlinkState(blinkState) aufrufen. Wenn blinkState "false" ist, wird halt nicht geblinkt, bei "true" wird geblinkt. Blinken bedeutet einfach, das wenn die richtige Zeit gekommen ist, die LED ein, bzw. ausgeschaltet wird.

Zu Deiner Frage was mit "Flag" gemeint ist. Flag (englisch für Flagge / Fahne) bedeutet hier sowas wie eine Signalflagge. Also eine Variable die einen bestimmten Zustand repäsentiert. Z.B. Blinker an oder aus. In Deinem Beispiel ist "blinkState" genau so ein Flag.

Wenn Du die angepasste Funktion performCommand() verwendest, sollte das mit dem Blinken eigentlich schon klappen. Allerdings hat Dein Programm noch ein anderes "kleines" Problem, denn es kann passieren, das die LED gerade an ist, wenn erneut das Kommado "f" gesendet wird, um das Blinken wieder auszuschalten. Versuch mal heraus zu bekommen, warum das so ist und wie man das lösen kann.

Mario.

Danke für die erklärung.

Der von dir beschriebene effekt tritt allerdings nicht ein. Ich kann zu jederzeit den/die blinker ansprechen. Testweise habe ich die zeit auch mal auf 5 sekunden blinkintervall hochgedreht -kann aber immer innerhalb der zeit an oder abschalten.

Vielen dank erstmal. Ich melde mich sicherlich wieder :grin: