Hello and welcome to our today's contribution. Today we are talking about a sketch that probably everyone knows and has uploaded to their microcontroller a few times: the Blink Sketch.
I myself never paid much attention to this sketch, and only use it to check if a module gives any sign of life.
The use of delay(1000) in the Blink Sketch has a big disadvantage: the microcontroller waits one second (1000 ms), and can't do anything else during this time. So it is not possible to switch a second LED on and off during the delay.
It also becomes problematic when working on a project where the status of a pin is to be queried during the flashing, e.g. to check whether a switch has been pressed. If a button is pressed during the delay and released before the end of the delay, the microcontroller doesn't know about it.
Let's take the following small code snippet as an example:
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 }
At the beginning of the "void loop()" loop we read the status of the button at pin 8. If this is HIGH, we invert the value of pin 4 by means of digitalWrite on pin 4.
Then we switch pin 3 to HIGH, wait one second, hold the pin again to LOW, and wait one second again.
If we now press the button during the two delays, nothing happens. Only if we keep the button pressed while the digitalRead(8) is called, we switch the second LED on or off.
This became problematic e.g. with the alarm system which we recently presented in the blog. A motion sensor triggered the alarm. If you stood in front of the device and wanted to enter the security code, the alarm was triggered again and again, which led to a pause in the input.
Therefore it is recommended to do without the delay() function.
A possible solution would be to reduce the delay to 100 ms or even less, and to count up a counter with each run. When the counter reaches a certain value, it is reset to 0 and the LED is switched off.
But we can save all this work thanks to the millis() function. The millis() function returns the number of milliseconds that have passed since the start of the current program. The number is reset to 0 after about 50 days.
To store this value in a variable it should be of type "unsigned long". This allows a number between 0 and 4,294,967,295.
So in our example we create a variable called "previousMillis" and create a variable of type "const" in which we set the interval of 1000 ms.
When we run "void loop()" we put the output of milis() into the variable "currentMillis". Then we check if 1000 ms have passed since the last run. If so, the value of previousMillis is overwritten with that of currentMillis and the LED is switched on or off.
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 } }
Now we can switch the second LED with the button, independent of what the first LED does.
If you have rebuilt the whole thing and tried the code, you will notice that the main loop is run so often that it is not so easy to switch the second LED with a pushbutton.
Here we would have to debounce the button, but that is a topic for another blog post.
I hope today's post showed you how easy it can be to work with millis().
We thank you for the growing interest and the many feedbacks during the last weeks, and say goodbye until tomorrow.
Your Markus Neumann
2 comments
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
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.