MTcore: Example 1
Event management for Uno, Nano, Mega
delay(); not so blocking!
A number of people shout "Haro on the Delay" because it is blocking. I will show here that we can perfectly use a delay() which
will not block the execution of another code, for example a second delay(), itself interrupted for example to run a stepper motor.
I am under Arduino Uno, but the words I make here can probably be used on other platforms.
delay() code analysis
Let's take a look at the delay() code first which will be compiled for my uno:
void delay(unsigned long ms) { uint32_t start = micros(); while (ms > 0) { yield(); while ( ms > 0 && (micros() - start) >= 1000) { ms--; start += 1000; } } }
The comments do not help so much, I give broad outline. We use an ms counter which will contain the number of milliseconds which is regularly decreed (at least if we do nothing during the delay()), and when it falls to zero ends the function.
We will notice the presence of yield() which already indicates that we can go see elsewhere to do another task during this delay(). I don't know much more, it is not used on the Uno. But it still indicates that you can do something else.
If we analyze this code a little, and if we leave all the time free for delay(), every 1000µs the value returned by micro() has been incremented by 1000, and we will on one hand decrement the ms counter (we must now make a millisecond less), on the other hand increment start which will indicate the new start.
If during the delay() we take care elsewhere and the while no longer turns for a certain time, micro() which continued to increase will return a much greater value than start+1000. When you give your hand to a while loop, no longer with each millisecond but at each iteration, we count a millisecond and we will therefore make up for the delay. So much so that we can suspend delay() for almost the time you want, and that everything becomes normal after the end of this suspension. It goes without saying that if you have programmed delay(100); and that we suspend the delay() for a second, we will exceed 100ms.
In reality we cannot suspend delay() for too long because it would not be necessary that micro() make a complete loop. If the maximum delay() is around 50 days, the while of delay() can only be suspended for 70 minutes. Beyond, delay() will be extended in packets of 70 minutes.
Interrupt a delay()
It is already done "naturally" by the counter time (for millis, micro) by the timer 0. We can therefore write an interruption function which can suspend a delay() of the loop(). A requirement all the same, it is necessary to reactivate the interruptions otherwise the counting of time stops. There may also be problems if our interruption is re-entrenched and that it is in infinity itself.
Let us use for example the program "Blink with Delay" well known:
#define milli_seconds // Comment, because replaced by an empty chain void setup() { digitalWrite(LED_BUILTIN, LOW); // Led in OUTPUT, light off pinMode(LED_BUILTIN, OUTPUT); } void loop() { // Classic program of "blink with delay" Serial.println(F("I light on")); digitalWrite(LED_BUILTIN, HIGH); delay(5000 milli_seconds); Serial.println(F("I light off")); digitalWrite(LED_BUILTIN, LOW); delay(5000 milli_seconds); }
Here is what we can get on the console:
23:39:15.942 -> I light on 23:39:20.914 -> I light off 23:39:25.931 -> I light on 23:39:30.903 -> I light off 23:39:35.918 -> I light on 23:39:40.935 -> I light off 23:39:45.905 -> I light on
We have a change every 5s.
There are two delay() functions; And I'm going to be happy to do something else during part of these 5000ms I will avoid
detailing the details of the interruption code, it's not trivial, I will use a library already made. But nothing prevents you from looking
at the source, and/or ask me for more details on it. Here are the lines that will allow you to do something else during the 5000ms:
#include <MTobjects.h> // V1.0.6 See http://arduino.dansetrad.fr/en/MTobjects const uint8_t PIN_BOUTON = A0; void buttonPressed(void) { for (char letter = 'A'; letter <= 'Z'; letter++) { Serial.print(letter); // Therefore writes the alphabet in 2.6s delay(100 milli_seconds); // Who will therefore suspend the blink delay } Serial.println(); // To be ready for a new alphabet } MTbutton Bouton(PIN_BOUTON, buttonPressed); void setup() { Serial.begin(115200); // To send messages } void loop() { }
This part defines a button on the A0 pin which calls for function void buttonPressed(void) when we have just pressed it. At that
time, we will send the letters of the alphabet with a period of 100ms between each letter. All the little delay(100 milli_seconds);
will therefore take place mainly during the
Note: the Function buttonPressed() is called by a non-re-entrenrating interruption which reactivates the interruptions. In the first approximation, it is as if we had two loop() functions, one which turns permanently and a second which only turns once when you press the button.
Press on the begin of delay(5000 milli_seconds);
If we press the button just after a change, as the writing of the alphabet lasts only 2.6s, the delay(5000 milli_seconds) will be interrupted once to make the loop for, and out of the loop, as lasted less than 70 minutes, it will correctly end its count. On the console, we will obtain for example:
23:39:40.935 -> I light off 23:39:45.905 -> I light on 23:39:46.420 -> ABCDEFGHIJKLMNOPQRSTUVWXYZ 23:39:50.919 -> I light off 23:39:55.935 -> I light on 23:40:00.907 -> I light off 23:40:01.329 -> ABCDEFGHIJKLMNOPQRSTUVWXYZ 23:40:05.922 -> I light on 23:40:10.939 -> I light off
We see that the support having been about a second after a change, this does not modify the time of the ignitions/extinctions (seconds divisible by 5).
Press at the end of delay(5000 milli_seconds);
If we press the button much later, we will have to wait for the end of the for to return to the delay(5000 milli_seconds); and the latter will be extended. We can then have a display:
23:40:10.939 -> I light off 23:40:15.910 -> I light on 23:40:19.566 -> ABCDEFGHIJKLMNOPQRSTUVWXYZ 23:40:22.147 -> I light off 23:40:27.167 -> I light on 23:40:32.136 -> I light off 23:40:32.417 -> ABCDEFGHIJKLMNOPQRSTUVWXYZ 23:40:37.155 -> I light on 23:40:42.160 -> I light off
At first the extinctions were made on the whole dozens of the seconds. The button having been pressed the first time at t=19.56s, the for will therefore last approximately up to t=19.5s+2.6s or 22.1s, and it is therefore at this time that the LED turns off.
We can therefore quietly interrupt a delay if we finished the job before the planned end.
What to do during the delay(100 milli_seconds);?
So we have a delay(5000 milli_seconds); suspended by delay(100 milli_seconds);. Can we interrupt this second delay()? Yes, I have already mentioned it is interrupted by the system clock, otherwise the times would not be correct. If we want a more visual approach, we can combine everything with the following code:
#include <MTobjects.h> // V1.0.6 See http://arduino.dansetrad.fr/en/MTobjects MTulnStepper Stepper(pin_A1 2, pin_B1 3, pin_A2 4, pin_B2 5); void setup() { Stepper.move(CONTINUE); // Infinite rotation } void loop() { }
... which will make an infinite rotation at a stepper. If it turns properly, it is good that the steps are sent regularly. A change of steps is sent here every 4.9ms, which corresponds to the default library speed for a 28BYJ48 to 0.1RPS.
Complete program recommended
The complete program used (we add all the pieces of code sets, which is simple because only one uses loop() ):
// This program shows that we can put delays(), and even suspend // a delay() by another // This program has a "blink with delay" in the background. // When you press a button, 10 characters are sent to the console using a // delay. As we have free time while the delay work, we show that we can take // advantage of it to manage the rotation of a stepper. // The two simultaneous delay() will not interfere with the memorization of // the support of the button or the rotation of the stepper. The duration of // the blink can be extended at most 100ms if the change of state must be // done during the 100ms delay. #include <MTobjects.h> // V1.0.6 See http://arduino.dansetrad.fr/en/MTobjects const uint8_t PIN_BOUTON = A0; void buttonPressed(void) { for (char letter = 'A'; letter <= 'Z'; letter++) { Serial.print(letter); // Therefore writes the alphabet in 2.6s delay(100 milli_seconds); // Who will therefore suspend the blink delay } Serial.println(); // To be ready for a new alphabet } MTbutton Bouton(PIN_BOUTON, buttonPressed); MTulnStepper Stepper(pin_A1 2, pin_B1 3, pin_A2 4, pin_B2 5); void setup() { digitalWrite(LED_BUILTIN, LOW); // Led in OUTPUT, light off pinMode(LED_BUILTIN, OUTPUT); Serial.begin(115200); // To send messages Stepper.move(CONTINUE); // Infinite rotation } void loop() { // Classic program of "blink with delay" Serial.println(F("I light on")); digitalWrite(LED_BUILTIN, HIGH); delay(5000 milli_seconds); Serial.println(F("I light off")); digitalWrite(LED_BUILTIN, LOW); delay(5000 milli_seconds); }
Various
If I press the button a lot of times?
If you press the button for the first time, the letters are starting to write. If we press a second time before the end of the writing, the
information "button has just been pressed" returns to false and we will write the alphabet twice. If we press once again before the end of
the first alphabet, the info "button has just been pressed" being already true, it happens pus nothing; a triple or quadruple support will
only make two alphabet. Of course if we press during the second writing, we will have 3 alphabets.
Can we interrupt a delay by a second, himself interrupted by a third, himself by a fourth?
Yes, but I don't have a program on hand that does it.
Functioning
It may seem surprising that one can interrupt one delay by another while the stepper turns. Many people will say that delay is blocking (which is true) and that nothing else can be done during this time. Already admit that during a delay the system clock continues to run. In our case, in addition, the stepper continues to turn and the two delay almost as well. In fact we use interruptions here that will put one or more tasks on hold to perform the most priority. Here is the priority pyramid:

At the top of the pyramid are the most priority tasks. There is the management of the system clock possibly, and all the quick tasks of
the library:
- increment of library clocks
- advance a step for steppers
- taking into account the button.
These tasks will take place whatever the current work of the lower slices. So if below is a delay, that does not prevent the stepper
from turning or a press memorization.
Below is the user functions called for example during the stepper rotaton ends or following a button support. In our program, a delay exists in the Function buttonPressed which will therefore be suspended during the sending of new steps for the stepper or the scan of the button.
At the bottom loop can therefore be interrupted, in particular its delay to execute buttonPressed which can also be interrupted to run the stepper or to scan the button. So we have a current delay which does not block the second buttonPressed delay which does not block the rotation of the stepper either.
If delay was written using a loop and counting the number of loops, interrupting a delay would suspend the account and the time would be extended. In fact, the AVR delay reads time on the system clock and scrutinizes the latter to wait for the end. Suddenly an interruption even long does not change the duration. The LED will therefore continue to flash at the same speed even if you are busy for a second by the function moves. On the other hand if the LED should go out (end of the 5s) during the execution of buttonPressed, the delay would be finished, but the change could not be done because loop does not yet have the hand.