1

First of all, thank you for taking the time to read this. I am a newbie, so apologies (done examples on arduino website, basic C++).

I'm trying to get an idea of what the best way to structure a sketch is, which needs to do several things at the same time. For example, reading an IMU sensor and using it to control the mouse, reading a button for on/off and let's say flashing an LED. I have repetitive injury so this is the project I'm trying to complete, but the principle should be scalable for more complex projects.

I've come across some great resources and my understanding is you can use:

  1. functions (e.g. blink without delay)

  2. objects (cooperative multitasking)

  3. interrupts

  4. something like a RTOS

Where my limits lie is understanding the benefits/limitations of these options and picking which one to focus on for my project. For example, if using functions is sufficient, why do people bother with interrupts and installing a RTOS?

I would appreciate any guidance for what an effective approach should look like.

Thanks again.

5
  • Functions and objects have nothing to do with “doing several things at the same time”, they are just ways to encapsulate some logic. In both cases you are doing non-blocking programming. The most useful programming paradigm for non-blocking programming is the finite state machine. – Edgar Bonet Feb 15 at 13:28
  • Please read up about the concept of "real time". This does not mean that something is fast, it just means it is fast enough (based on whatever standards you apply). For instance, if your blinking led has a skew of a few milliseconds, you won't notice - it is irrelevant. – PMF Feb 15 at 13:37
  • Thanks for the link, I'm going through it today. – Zhelyazko Grudov Feb 15 at 18:35
  • as long as you follow the examples, and non of your code uses delay(), it should naturally do many things at once, CPU time and memory allowing. For what you describe, it's hard to imagine even an UNO not being up to task. – dandavis Feb 15 at 18:51
  • Thank you Davis for the comment – Zhelyazko Grudov Feb 15 at 19:24
3

An RTOS may be a good option for running a complex application on a quite powerful Arduino. Many Arduinos (the AVR-based ones), however, are too limited to run complex applications, and the overhead of an RTOS is quite significant for them. In this case you end up with two options:

  • non-blocking programming
  • interrupts

Whichever is best depends on the specific tasks you are managing.

Non-blocking programming

This is best exemplified by the Blink Without Delay Arduino tutorial. The general principle is to break down you code into small non-blocking tasks, and perform each one only when it is the right time to do so. Breaking down the program logic into a set of non-blocking tasks is not always completely trivial, but you can use a Finite state machine, which is a very powerful and general concept for doing so in a systematic way.

Once all task are non-blocking, the main program loop then looks like this pseudo-code:

void loop() {
    if (task_1_should_be_performed_now()) {
        perform_task_1();
    }
    if (task_2_should_be_performed_now()) {
        perform_task_2();
    }
    // etc...
}

Most of the time, all conditions are false, and the program swifts through the tests completing the loop in a very short time. Occasionally, it performs one of the tasks, which makes the loop longer but, since the tasks never block, not very long anyway. Typical loop time is highly dependent on the specifics of your code, but I would expect it to be no more than one millisecond most of the time.

This is probably the most common pattern for multitasking on the Arduino. You can encapsulate this logic in functions, objects, libraries, etc. This is the approach I would recommend provided none of your tasks is so urgent that it cannot wait for one loop iteration.

Interrupts

Occasionally, you may have a task that is so urgent that it cannot wait for the next loop iteration. It has to be performed right now. Or maybe the loop takes a very long time because of a blocking call in a library you have no control over. In those situations, an interrupt may be the best option.

Interrupts come with some tricky programming issues, which require some precautions on the part of the programmer:

  • they block other interrupts, increasing their latency → interrupt service routines should be as short as possible

  • they can cause problems with non-reentrant library functions → those functions should be avoided in interrupt context

  • they can interact badly with the optimizer → variables used in both interrupt context and normal context should be qualified as volatile

  • they can cause synchronization issues with the data shared with the main code → access to that data in normal context should be protected in critical sections.

For all these reasons, it is best to use interrupts only when needed, i.e. only when a task cannot wait for the next iteration of loop().

4
  • Hi Edgar, I really appreciate you taking the time and effort to respond in such a helpful way. I'll follow the link and take the rest of the day to absorb the content. Thank you very much. Just to confirm a couple of quick points. You're implying that from a multitasking perspective it makes no difference whether I encapsulate the code in functions or objects? What would be an example of a task which cannot wait for a loop iteration? Would a press of a button to perform an action such as switching analog sensor read on/off qualify? – Zhelyazko Grudov Feb 15 at 18:44
  • if i may; streaming audio, some wifi interactions, capturing hi-rate ADC samples, etc. Look into interrupts, which are basically events that can cut in line for real-time handling of stuff like button presses. Your analog sensor doesn't mind waiting a ms to take another sample. – dandavis Feb 15 at 18:54
  • @ZhelyazkoGrudov: 1. An object is just a convenient way to encapsulate functions (called methods) together with data. The right level of encapsulation can help make your code cleaner and easier to maintain, but it has no relevance vs. the multitasking approach. 2. For detecting a button press, you may need to use interrupts if the button is going to be operated by Quicksilver. If the operator is a regular human, a millisecond or two of latency makes no difference. – Edgar Bonet Feb 15 at 20:04
  • 1
    Such a good answer! Maybe I am designing a project for the avengers, you never know... In all seriousness, thanks for the explanation. – Zhelyazko Grudov Feb 15 at 21:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.