Das richtige Timing

Mit delay() wirst du mit dem Timing in deinem Programm rasch an Grenzen stossen. Du lernst in dieser Lektion den Umgang mit millis() kennen. Ausserdem benutzen wir statische Variablen und ich zeige dir eine universelle Blink-Funktion, die ohne delay() auskommt.


Weitere Unterlagen und die Zusammenfassung Programmierung findest du in den Begleitunterlagen.


Diesmal wird es kein Lösungsvideo dazu geben. Da das Thema recht komplex ist, wird es eine weitere Lektion zur Vertiefung des Stoffes geben.

Kommentare 11

  • Hallo,

    Eine Sache ist mir noch aufgefallen.

    Du erwähnst den Überlauf, aber im Video und in den Unterlagen

    Steht leider nichts welches Problem es bereiten kann,

    Und wie man es umgehen kann.

    Ich denke es ist für Anfänger sehr wichtig das sie das Problem kennen.

    Gruß, Markus

    • Hallo Markus,


      ich habe das Thema mit Absicht ausgelassen, die Dinge sind für Anfänger schon so kompliziert genug. Deshalb gilt für Anfänger: über 50 Tage geht nicht!


      In Wirklichkeit ist es natürlich nicht so. Der Überlauf kann ganz einfach festgestellt werden:

      if (millis() < startZeit) ueberlauf(); // millis() beginnt nach dem Überlauf wieder bei 0


      Jetzt gibt es zwei Möglichkeiten:

      Wenn zwischen den Aufrufen nur sehr kleine Zeiten verstreichen, dann gibt es kein Problem.

      (startZeit + blinkzeit) wird ebenfalls zur selben Zeit überlaufen, darum wird alles normal weiter funktionieren.


      Wenn zwischen den Aufrufen eine etwas grössere Zeit verstreicht, kann es passieren, dass (startZeit + blinkZeit) gerade noch nicht überläuft, millis() aber schon. So wird die Schaltung für mindestens 50 Tage nicht mehr blinken. Dieser Fall ist selten, darf aber nicht vernachlässigt werden, wenn man es genau nimmt.


      Man könnte das so verhindern:

      if (millis() < startZeit) startZeit = 0;

      Dann läuft nach einem Überlauf alles korrekt weiter. Allerdings stimmt dann das Zeitintervall für einen Takt nicht. Das ist aber vielleicht gar nicht so schlimm, da eine Millisekunde von millis() sowieso nicht genau eine Millisekunde dauert. millis() basiert auf dem Prozessortakt und wird herunter gerechnet. Dabei entsteht ein kleiner Rundungsfehler. Ausserdem hängt der Takt etwas von der Temperatur ab und hat auch keine absolute Genauigkeit.


      Man könnte also hier sehr weit gehen. Auf microcontroller.net gibt es deswegen manchmal beinahe blutige Köpfe.




  • Hallo,

    ich habe eine Lösung zusammengeschraubt. Sie macht was es soll, auch wenn ich mir das synchrone/asynchrone Blinken nicht erklären kann.


    Spielerei mit millis


    const int anzahl_leds = 5;

    const int ledPin[anzahl_leds] = {2, 3, 4, 5, 6};

    const int tasterPin = 7;

    int x = 1;

    int Status;

    int oldStatus = LOW;

    int val;

    int oldval = LOW;

    int Pos = 1;

    int Time = 500;

    // Funktion;

    void setup() {

    // Alle Led - Pins auf OUTPUT

    for (int i = 0; i < anzahl_leds; i++) {

    pinMode(ledPin[i], OUTPUT);

    Serial.begin(9600);

    }

    // INPUT_PULLUP beim Taster

    pinMode(tasterPin, INPUT_PULLUP);

    }


    void Taste() {


    Status = (digitalRead(tasterPin));

    if ((Status == LOW) && (oldStatus == LOW)) {

    oldStatus = HIGH;

    Aus();

    }

    if (Status == HIGH) oldStatus = LOW;


    }

    void blink(int Pos) {


    const int ledNummer = Pos;

    const int blinkZeit = Time;

    static unsigned long startZeit = 0;

    if (millis() > startZeit + blinkZeit) {

    digitalWrite(ledPin[ledNummer], !digitalRead(ledPin[ledNummer]));

    digitalWrite(ledPin[0], !digitalRead(ledPin[0]));

    startZeit = millis();

    }

    }


    void Aus() {


    for (int i = 0; i < 6; i++) {

    digitalWrite(ledPin[i], LOW);

    }

    Pos = 1;

    x = 1;


    }


    void loop() {


    for ( Pos = 1; Pos < 5; Pos++) {

    for ( int x = 1; x < 16; ) {

    blink(Pos);

    Taste();

    val = digitalRead(ledPin[Pos] );

    if ((val == HIGH) && (oldval == LOW)) {

    x++;

    oldval = HIGH;

    }

    if (val == LOW) oldval = LOW;

    }

    }

    delay(Time);

    Aus();

    }

    • Hallo Brocki,


      gratuliere, du hast eine Lösung gefunden, die abgesehen von dem synchronen Blinken alles wie gewünscht macht. Vermutlich hast du recht viel Arbeit in das Programm gesteckt.


      Einige Bemerkungen habe ich noch dazu:


      Damit das Blinken immer synchron erfolgt, kannst du die zweite Led's vom Zustand der Ertsen abhängig machen. Anstelle von


      Code
      1. digitalWrite(ledPin[ledNummer], !digitalRead(ledPin[ledNummer]));
      2. digitalWrite(ledPin[0], !digitalRead(ledPin[0]));

      schreibst du

      Code
      1. digitalWrite(ledPin[ledNummer], !digitalRead(ledPin[ledNummer]));
      2. digitalWrite(ledPin[0], digitalRead(ledPin[ledNummer]));

      Damit ist sichergestellt, dass beide Led's identisch blinken.


      In setup() ist mir aufgefallen, dass du schreibst:

      Code
      1. // Alle Led - Pins auf OUTPUT
      2. for (int i = 0; i < anzahl_leds; i++) {
      3. pinMode(ledPin[i], OUTPUT);
      4. Serial.begin(9600);
      5. }

      Das funktioniert, macht aber unnötige Aufrufe. Serial.begin(9600) muss nur einmal aufgerufen werden. Zum Beispiel so:

      Code
      1. Serial.begin(9600);
      2. // Alle Led - Pins auf OUTPUT
      3. for (int i = 0; i < anzahl_leds; i++) {
      4. pinMode(ledPin[i], OUTPUT);
      5. }

      Serial.begin() kann auch weggelassen werden. wenn das Programm keine Serial.print() verwendet.


      Eine gefährliche Sache habe ich noch gefunden:

      Code
      1. for (int i = 0; i < 6; i++) {
      2. digitalWrite(ledPin[i], LOW);
      3. }

      Du schreibst hier über die Array-Grenzen hinaus. ledPin ist als Array von 5 Elementen deklariert. Da immer bei 0 begonnen wird, legen die erlaubten Indices zwischen 0 und 4. Deine For-Schleife spricht aber auch ledPin[5] an. Somit schreibst du in einen Bereich, der nicht zum Array gehört. Diesmal hast du Glück gehabt, es funktioniert alles. Es hätte aber auch zu einer Fehlfunktion oder einem Absturz kommen können.


      Sonst ist aber alles gut, gute Arbeit.


      In einer Woche werde ich eine Lösung vorstellen, die noch etwas mehr mit Arrays arbeitet.


      Gruss

      René

    • Hallo Rene',

      vielen Dank für Deinen Kommentar und Hilfestellung.

      Den Eintrag "Serial.begin(9600);" habe ich vergessen zu löschen. Wurde zu debuggingzwecken verwendet.


      Gruß

      Brocki

  • arduinoforum.de kann ich auch nur empfehlen. Es gibt kein besseres deutschsprachiges Forum zum Arduino. Dort findet man Antworten zu wirklich allen Themen.


    Dort ist auch die Arduino - Code - Referenz von Andreas Nagel zu finden. Es handelt sich dabei um eine wirklich hervorragende deutschsprachige Referenz zur Arduino - Programmierung. Viele Beispiele können auch zusätzlich Klarheit schaffen.

  • Hallo Matthias

    Wenn du überfordert bist,

    Dann schau doch mal im Arduinoforum.de vorbei

    Und suche mal nach :

    Millis () das unbekannt Wesen.

    Glaub mir, millis () zu verstehen ist eins der Wichtigsten Themen das du immer wieder brauchst.

    Gruß, Markus

  • Irgendwie bin ich der Einzige, der hier Probleme hat, oder es traut sich keiner zuzugeben, dass er im Moment mit der Thematik noch etwas überfordert ist. Es wäre denk ich hilfreich, ab und zu die Katze ein Stückchen aus dem Sack zu lassen um nicht die Lust am probieren zu verlieren. Mit was fange ich an, welche mir schon bekannten "Werkzeuge" soll ich dabei benutzen. Aller Anfang is schwer, Die Richtung hast du uns denk ich mit den Funktionen vorgegeben, aber is das auch die Reihenfolge die ich befolgen soll? Ich bräuchte mehr Fakten um den Würfeltäter überführen zu können, oder wir belassen es erst einmal bei der Spurverfolgung. Der Eine oder Andere hat sicherlich auch den Wunsch nach weiterführenden Informationen.

    • Es ist ganz normal, wenn du hier Probleme hast. Es handelt sich um einen der komplexesten Bereiche der Programmierung. Ich bin sicher, dass die Mehrheit der Zuschauer hier nicht alles auf Anhieb versteht. Das ist aber momentan gar nicht so wichtig. Hauptsache man beschäftigt sich damit und wird sich der Problematik klar. Aus diesem Grund sind in dieser Lektion auch keine Übungsaufgaben vorhanden. Übungsaufgaben sollten immer für den Grossteil der Zuschauer lösbar sein. Das ist hier nicht der Fall. Es ist aber trotzdem sinnvoll, sich bereits einige Gedanken zum Thema zu machen. Es gibt keine Lösungen dazu, sondern es wird eine vollständige Lektion geben. Es ist mir klar, dass auch danach für Viele noch nicht alles klar sein wird.


      Wieso mache ich das so? Das Thema ist wichtig und darum wollte ich es kompakt und zusammenhängend vorstellen. Es gibt auch fortgeschrittene Zuschauer im Kurs, die problemlos damit zurechtkommen. Alle Anderen sollten später nochmals zu diesen Lektionen zurückkommen.


      Es wird nachher sehr viel einfacher weiter gehen. Da werden einzelne Sensoren und Aktoren besprochen, wie zum Beispiel Ultraschallsensoren, Bewegungsmelder, Servos und Motoren. Dabei werden zwischendurch immer wieder millis() auftauchen und man gewinnt anhand der einfachen Anwendungsbeispiele immer mehr Erfahrung damit. Irgendwann wird dann plötzlich alles klar.


      Also nicht aufgeben. Wir sind hier nicht in der Schule, es gibt weder Tests noch Noten. Einfach weiter machen und später nochmals auf die Lektionen zurückgehen, die nicht ganz klar waren.

    • Ich bin hartnäckig und bleib dir auf der Spur, nur wer aufgibt hat schon verloren! Mit besten Grüssen ins

      Switzerland.

  • Toll - millis() ist bzw. war für jeden eine Herausforderung.