Blink Sketch ohne delay() - AZ-Delivery

Hallo und willkommen bei unserem heutigen Beitrag. Heute geht es um einen Sketch den vermutlich jeder kennt, und bestimmt schon einige Male auf seinen Mikrocontroller hochgeladen hat: der Blink Sketch.

Ich selbst habe dem Sketch nie viel Beachtung geschenkt, und nutze ihn lediglich um zu überprüfen ob ein Modul überhaupt ein Lebenszeichen von sich gibt.

Die Verwendung von delay(1000) im Blink Sketch bringt aber einen großen Nachteil mit sich: der Mikrocontroller wartet an der Stelle eine Sekunde (1000 ms), und kann in der Zeit nichts anderes machen. Es ist also während des Delays nicht möglich eine zweite LED kurz ein und wieder aus zu schalten.

Problematisch wird es auch dann wenn man an einem Projekt  arbeitet bei dem während dem Blinken der Status eines Pins abgefragt werden soll, um z.B. zu überprüfen ob ein Schalter gedrückt wurde. Wenn ein Taster während des Delays gedrückt, und vor dem Ende des Delays losgelassen  wurde, bekommt der Mikrocontroller davon nichts mit.

Nehmen wir folgenden kleine Code-Schnipsel als Beispiel:

  void setup() {

  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(8, INPUT);
}

void loop() {
  if (digitalRead(8) == HIGH){
    digitalWrite(4, !digitalRead(4)); // Schaltet LED an pin 4 AN/AUS
  }
  digitalWrite(3, HIGH); // LED an Pin 3 AN  
  delay(1000); // Pause            
  digitalWrite(3, LOW); // LED an Pin 3 AUS    
  delay(1000); // Pause            
} 

Am Anfang der "void loop()" -Schleife lesen wir den Status vom Taster an Pin 8 aus. Falls dieser auf HIGH steht wird mittels digitalWrite auf Pin 4 der Wert von Pin 4 umgekehrt.

Dann schalten wir Pin 3 auf HIGH, warten eine Sekunde, Shalten den Pin wieder auf LOW, und warten wieder eine Sekunde.

Wenn wir nun während der beiden Delays den Taster drücken, passiert also nichts. Erst wenn wir den Taster gedrückt halten, während der digitalRead(8) aufgerufen wird, schalten wir die zweite LED ein beziehungsweise aus.

Problematisch wurde das z.B. bei der Alarmanlage welche wir vor Kurzem im Blog vorgestellt haben. Ein Bewegungssensor löste dort den Alarm aus. Wenn man nun vor dem Gerät stand, und den Sicherheitscode eingeben wollte, wurde immer wieder der Alarm ausgelöst, was zu einer Pause bei der Eingabe führte.

Daher wird empfohlen auf die delay() Funktion zu verzichten.

Ein möglicher Lösungsansatz wäre es z.B. den Delay auf 100 ms oder sogar noch weniger zu verringern, und bei jedem Durchlauf einen Zähler hoch zu zählen. Wenn dieser dann einen bestimmten Wert erreicht hat, setzt man Ihn auf 0 zurück und schaltet die LED.

Die ganze Arbeit können wir uns aber Dank der millis() Funktion sparen. Die Funktion millis() gibt die Anzahl der Millisekunden zurück die seit dem Start des aktuellen Programms vergangen sind. Die Zahl wird nach etwa 50 Tagen wieder auf 0 gesetzt.

Um diesen Wert in einer Variable zu speichern sollte diese vom Typ "unsigned long" sein. Dies ermöglicht eine Zahl zwischen 0 und 4.294.967.295.

In unserem Beispiel  erstellen wir also eine Variable mit der Bezeichnung "previousMillis" und erstellen eine Variable vom Typ "const" in welcher wir den Intervall von 1000 ms festlegen.

Beim Durchlaufen von "void loop()" legen wir die Ausgabe von milis() in der  Variable "currentMillis" ab. Dann wird überprüft ob seit dem letzten Durchlauf 1000 ms vergangen sind. Falls ja, wird der Wert von previousMillis mit dem von currentMillis überschrieben, und die LED wird ein bzw. ausgeschaltet.

 

unsigned long previousMillis = 0; // speichert den Zeitpunkt an dem zuletzt geschalten wurde
const long interval = 1000; // Länge der Pause in ms

void setup() {
  pinMode(3, OUTPUT); // LED 1
  pinMode(4, OUTPUT); // LED 2
  pinMode(8, INPUT);  // Taster
}

void loop() {
  if (digitalRead(8) == HIGH){
    digitalWrite(4, !digitalRead(4)); // Schaltet LED an pin 4 AN/AUS
  }
 unsigned long currentMillis = millis(); // Aktuelle Zeit wird in currentMillis gespeichert
  if (currentMillis - previousMillis >= interval) { // Falls mehr als 1000 ms vergangen sind
     previousMillis = currentMillis; // Zeitpunkt der letzten Schaltung wird festgehalten 
  digitalWrite(3, !digitalRead(3)); // LED wird ein- bzw. ausgeschaltet
  }
}

 

Nun können wir die zweite LED mit dem Taster schalten, unabhängig davon was die erste LED macht.

Wer das ganze nun nachgebaut hat, und den Code ausprobiert, wird feststellen, dass die Hauptschleife so oft durchlaufen wird, dass es mit einem Taster gar nicht so einfach ist die zweite LED zu schalten.

Hier müssten wir den Taster entprellen (Engl. debouncing), aber das ist ein Thema für einen anderen Blogbeitrag.

Ich hoffe unser heutiger Beitrag hat Ihnen gezeigt wie einfach es sein kann mit millis() zu arbeiten. 

Wir bedanken uns für das wachsende Interesse und die vielen Rückmeldungen der letzten Wochen, und verabschieden uns bis morgen.

Ihr Markus Neumann

 

 

 

 

 

Grundlagen software

2 Kommentare

W. Parschfeld

W. Parschfeld

Alternativ wäre eine parallele Programmierung zu empfehlen – kann für viele zufälligen parallele Ereignisse benutz werden: z.B. Analog-Uhr auf graf. Display, Abfrage verschiedener Steuerimpulse, Kommunikation über I2C etc. (z.B. vom WiFi-Modul), laden von SD-Card… Gute Erfahrungungen habe ich mit der Bibliothek TimedAction gemacht …

A. Deppe

A. Deppe

Im Prinzip eine gute Idee – allerdings gibt es genau für dieses Problem (wenigstens für die MKR’s) die “scheduler” Funktion. Zusammen mit dem Scheduler macht die Delay (und Yield) Funktion dann wieder absolut Sinn , denn Delay legt die entsprechende Funktion in der Delay angewendet wird für den Delay Zeitraum schlafen und gib die Rechenzeit für andere “geschedulte” Funktionen frei. Das ist erheblich effizienter und ermöglicht multitasking.

Noch eine Bemerkung zu dem angeschnittenen Thema “Alarmanlage”: Wenn man Eingaben sicher verarbeiten will, ist es meistens eine gute Idee das Interupt handling dafür zu bemühen. Ich würde versuchenden die void loop so klein wie möglich zu halten und da nur das Interupt handling drin zu realisieren. Die eigentliche Verarbeitung würde ich in eigene Funktionen auslagern. Das ist nicht nur übersichtlicher sondern auch Speicher effizienter, weil die Funktionen/Variablen nur zur Laufzeit instanziert werden.

Auch hier kann der Scheduler helfen – um z.B. Ein- und Ausgabe zu deserialisieren.

Kommentar hinterlassen

Alle Kommentare werden von einem Moderator vor der Veröffentlichung überprüft

Empfohlene Blogbeiträge

  1. ESP32 jetzt über den Boardverwalter installieren - AZ-Delivery
  2. Internet-Radio mit dem ESP32 - UPDATE - AZ-Delivery
  3. Arduino IDE - Programmieren für Einsteiger - Teil 1 - AZ-Delivery
  4. ESP32 - das Multitalent - AZ-Delivery