Skip to content

Multitasking


Published: 2021-12-05
Last update: 2023-06-30

In this tutorial you will learn how to control many different tasks at the same time and how to execute high priority tasks interrupting all other tasks.

Table of contents:


Equall timing for on and off state.

Non-equal timing for on and off state.

Eeee... How to blink more than one LED?!




Now you will make a step closer to the heart of the problem. Controlling one thing doesn't sound complicated. Tnings became intersting when you have to control two different objects at the same time, both working with different timing.

Extend previous circuit and add one more LED (green).

You can start with a toy problem: control two leds with the same timing of HIGH and LOW state:

or in short:

The code for this is simple:

Even if you have non-equal timing for on and off state, the problem is trivial because there is no need for real "concurrency" as both leds changes their state at the same time:

The problem becomes apparent, when timings for different object are different:

Second from the above cases are feasible:

The same but with shifted timing for green LED:

The disadvantage of this solution is that every delay affects both leds and it may be difficult to control them independently. It may happened that changing timing of one led will force you to change code drastically. In more complex dependencies it may be difficult to find "cycle" -- repeatable pattern you can hardcode:



Now you will see better approach allowing you to control independently (almost) as many objects as you want.

You will use very short delay to avoid blocking and count how many will happen. If you reach a given number of calls, you will take an action.

To make things simpler, start with equal timing for on and off state with only one led:

Now you can extend the previous case to non-equal timing for on and off state:

Because counterRedOff and counterRedOn are increased inclusively (either counterRedOff or counterRedOn) you can use one counter as it is showed bellow:

Finally you can adopt this approach to non-equal timing for on and off state of two leds.

Problems with this improvement:

Lack of control over code "outside" delay -- it may take as much time as it want and delay will have no idea how much time elapsed between two subsequent calls. It means that you have to "collect" 100 delay(10)s regardless of the time passed between every two delay(10)s. In consequence 100*delay(10) is not equal to delay(1000). It wouldn't be less but it may be (much) more.



The problem with previous solution is that sometimes 90*delay(10)+T would be equal to delay(1000) or next time 98*delay(10)+T would be equal to delay(1000), where T is an extra (non-constant, unpredictable) time microcontroller needs to complete some task(s) between every two delay(10)s

That's why thing you are looking for is rather a variable counter increment, something of the following form:

Non-blocking equal timing for on and off state.

In most cases this is implemented as below:

In the above solution you still count something (number of milliseconds from starting/reseting your microcontroller) but this process of counting depends on time. In most cases instead of counter you will use different names -- below you have the same code as above but with different names for variables:

Adopting this approach to non-blocking non-equal timing for on and off state is quite easy:

Non-blocking non-equal timing for on and off state of two different LEDs.



Interrupts


Problem description

Add button to the previous circuit:

Upload to microcontroller the following code to switch on and off red LED:

To simulate time-consuming task add delay(10000):

As you can see, now it's almost impossible to switch LED. If you are lucky, you can do this only in short time-slots between delay(10000)s.


Interrupt

Interrupt, as it name states, interrupt the current work of microcontroller such that some other work can be done. What is important, it does not stop or break other activity but only interrupts it. After completion, microcontroller return back to interrupted job.

To understand this consider the following simple story:

Suppose you are sitting at home, chatting with someone (current work). Suddenly the telephone rings (interrupt occurs). You stop chatting, and pick up the telephone to speak to the caller (the interrupt service routine is called). When you have finished your telephonic conversation, you go back to chatting with the person before the telephone rang (return back to interrupted job).

Here are some important features about interrupts:

  • Interrupts can come from various sources:
    • Hardware external source Microcontroller uses a hardware interrupt that is triggered by a state change on one of the digital pins.
    • Hardware internal source Microcontroller uses a hardware interrupt that is triggered by a state change on one of the internal component -- very often timer or counter.
    • Software source A software interrupt is requested by the processor itself upon executing particular instructions or when certain conditions are met. It may be intentionally caused by executing a special instruction which, by design, invokes an interrupt when executed. Such instructions function similarly to subroutine calls and are used for a variety of purposes, such as requesting operating system services and interacting with device drivers (e.g., to read or write storage media). Software interrupts may also be triggered by program execution errors or by the virtual memory system.
  • Every software interrupt signal is associated with a particular interrupt handler.
  • Microcontroller has dedicated pins to work with interrupts:
  • ISR (Interrupt Service Routine) is a common name for routine called when interrupt occurs.
  • You define the routine and specify conditions related to interrupt occurrence: the rising edge, falling edge, change state etc.
  • Generally, an ISR should be as short and fast as possible.
  • You can mask an interrupt. Masking means disabling interrupt making it to be deferred or ignored by the processor. When the interrupt is disabled, the associated interrupt signal may be ignored by the processor, or it may remain pending. Signals which are affected by the mask are called maskable interrupts. Some interrupt signals are not affected by the interrupt mask and therefore cannot be disabled; these are called non-maskable interrupts (NMIs). These indicate high-priority events which cannot be ignored under any circumstances, such as the timeout signal from a watchdog timer.
  • Interrupt is triggered by different "events" describing how the signal was changed:
    • CHANGE -- runs functions on each change of the pin state.
    • LOW -- runs the function if the chip pin is LOW.
    • FALLING -- runs the function when the state changes from HIGH to LOW.
    • RISING -- runs functions when the state changes from LOW to HIGH.

A watchdog timer (sometimes called a computer operating properly or COP timer, or simply a watchdog) is an electronic or software timer that is used to detect and recover from computer malfunctions. Watchdog timers are widely used in computers to facilitate automatic correction of temporary hardware faults, and to prevent errant or malevolent software from disrupting system operation.

During normal operation, the computer regularly restarts the watchdog timer to prevent it from elapsing, or "timing out". If, due to a hardware fault or program error, the computer fails to restart the watchdog, the timer will elapse and generate a timeout signal. The timeout signal is used to initiate corrective actions. The corrective actions typically include placing the computer and associated hardware in a safe state and invoking a computer reboot.


Delay in ISR

The code presented in previous section works in Tinkercad simulator but there is a question wether it is as it should be and you are allowed to use delay inside ISR or not. Why you may have some doubts? Well, it may happened that clock is controlled by interrupt so when you are inside ISR to handle interrupt, clock interrupt may be blocked. If this is a case, delay may not be able to detect time passing and you will block forever you code inside ISR. Or maybe this statement is false, and everything works as expected?

Unfortunately an Internet with tones of blogs, vlogs and any other -logs doesn't help as you can find contradictory descriptions:

  • From ARDUINO PROGRAMMING GUIDE SERIES Can You Use Delay() Inside Interrupt Service Routine?
    A question I often receive is about the contents of the ISR:
    "Can I include arbitrary code?"
    "Can I include slow functions like delay() or even Serial.print()?"
    Although this is possible, you should not.

    Conclusion: yes, you can.

  • From Arduino delay inside of an Interrupt
    The first problem is that delay() is a busy loop monitoring millis(). The value that millis() returns will not change inside an ISR as interrupts are turned off inside an ISR; this is why ISRs must be as short and fast as possible, or they will interfere with other interrupts.

    So if you call delay() within an ISR your poor AVR is now deadlocked! It is waiting for something to happen that can now never happen.

    A second problem is that if your ISR takes too long, you can miss other interrupts; millis() and micros() can miss ticks and become inaccurate!

    Conclusion: no, you can't.

Hmm... Now you can test it on your own on real device. In my case everything was working as expected.

Anyway, using delay every time blocks your microcontroller. You may say: Only for twenty milliseconds! Only? Let's test what microcontroller can do for you in this time. First prepare "testing environment":

Now you can replace the line

with

and run your code. In my case the following output was printed:

As you can see, in 10 milliseconds time slot my microcontroller was able to repeat for loop 5779 times!

When you replace the line

with

and run your code you will get different result:

Notice that only because you change type for i from int to unsigned long my code slowed down from 5779 to 4371 iterations.

Now paste the line:

just after previously pasted:

If I run this code, I got the following results:

2938 is much lower than 5779 but it is still a lot of power to do many interesting tasks.

As you can see, it makes sense to implement ISR without delay inside (regardless of whether you can or cannot use delay inside ISR). Below I present my code:

With this code again you should be able to switch the state of your LED anytime you want but you don't use blocking delay.

By the way, as now you know interrupts, you can test switch bouncing described in tutorial Pull resistors. Upload the following code to your microcontroller:

If you press the button again and again you will notice some gaps as in my case:

The gaps are caused by spikes which appear in unstable membrane time when you press the button. When you press the button, you increase iRising by 1 and from time to time this variable is also increase by HIGH states of spikes.