The following video explains how to download and install all the software you need to get started with Modern Arduino Programming:
Traditional Sequential Arduino ProgrammingThe traditional Arduino programming model is called "sequential programming", because the expected sequence of events is typically hard-coded in the sequence of blocking calls, which wait in-line for the expected events to arrive.
For instance, in the standard Arduino Blink example, you expect a sequence of two timeout events for which you wait in-line by calling the Arduino delay()
function twice.
Listing 1: Example of Arduino sequential programming (the standard Arduino Blink example)
void loop() {
digitalWrite(13, HIGH); // turn the LED on (HIGH is the voltage level)
delay(1000); // wait for a second
digitalWrite(13, LOW); // turn the LED off by making the voltage LOW
delay(1000); // wait for a second
}
Such approach works for simple problems, but scales poorly and quickly becomes unworkable for more complex projects, where you need to many things simultaneously, like for instance to blink a second LED at a different rate.
The fundamental problem is that while a sequential program is waiting for one kind of event (e.g., a time delay), it is not doing any other work and is not responsive to other events (e.g., different time-delays or button-presses).
Arduino Programming without BlockingFor these reasons the more experienced Arduino programs avoid blocking (or polling) and waiting in-line for events, such as the dealy()
function. Instead the program only checks for interesting conditions (with the explicit if()
statements) and handles the events only when they are detected. In any case, the Arduino loop()
function always returns quickly, which makes the program responsive to other events.
Examples of this approach include the use of the Arduino millis()
function (e.g., see Arduino Project "Using millis() Function as an Alternative to Using delay()").
Listing 2: Example of Arduino programming without blocking (project "Using millis() Function as an Alternative to Using delay()")
void loop() {
ms_from_start = millis();
if (ms_from_start-ms_previous_read_LED1>LED1_interval) {
ms_previous_read_LED1 = ms_from_start;
if (LED1_state == 0) LED1_state = 1;
else LED1_state=0;
digitalWrite(LED1,LED1_state);
}
}
The rudimentary examples of non-blocking Arduino programs are a step in the right direction, because the non-blocking fragments of code are composable. Composable means that you can compose your code out of many such non-blocking code fragments (without modifications) in the single Arduino loop()
function.
But the main problem of this approach is the rapidly growing number of nested if
and else
branches as the project grows in complexity. This architectural decay is also known as the "spaghetti code" problem.
Also, the simple non-blocking programs have no safeguards against corruption of the global "inputs" shared between the interrupts and the loop()
function. This can lead to race conditions.
The long-known solution to all these problems is known as "event-driven programming", which has the following characteristics:
- All communication and synchronization within the program is mediated by the special event objects, which are specifically designed for asynchronous communication.
- The event-driven programs are naturally divided into the application, which actually handles the events, and the supervisory event-driven infrastructure (framework), which generically waits for events and dispatches them to the application.
- The control resides in the event-driven infrastructure (framework), which leads to inversion of control from the perspective of the application developer.
- The application code must process events to completion (one event at a time) without blocking or polling.
The QP/C++ framework provides such an event-driven infrastructure, which has been specifically designed for deeply embedded systems, such as Arduino boards.
The QP/C++ can be used with various real-time kernels. In the QP-Arduino integration, QP/C++ has been configured with the simplest cooperative QV scheduler, where the software is structured in an endless event-loop, as shown in the diagram below:
The most important element of the QV kernel design is the presence of multiple event queues with a unique priority and an "active object" assigned to each queue. The queues are constantly monitored by the QV scheduler, which by every pass through the loop picks up the highest-priority not-empty queue. After finding the queue, the scheduler extracts the event from the queue (queue.get()
) and calls the corresponding "active object" to handle the event (dispatch(e)
), which is called "dispatching of an event".
An additional feature of the QV kernel is that it can very easily detect the situation where no events are available, in which case the system can be put into a low-power sleep mode. This allows you to achieve better power efficiency and apply Arduino software in low-power, battery operated applications. (NOTE: True low-power consumption requires also appropriate hardware design.)
Hierarchical State MachinesIn addition to providing the event-driven infrastructure for concurrent execution of software components (called "Active Objects"), the QP/C++ framework provides also the modern Hierarchical State Machines, with handle the events inside the active objects. The main benefit of using state machines is the avoidance of the "spaghetti code" problem.
Modeling and Automatic Code GenerationFinally, the QP/C++ framework and the highly structured way of coding hierarchical state machines provide the basis for visual modeling and automatic code generation. The QP-Arduino integration contains the freeware QM modeling tool, which allows you to graphically design the hierarchical state machines of your active objects. The screenshot below shows the example model from the QP-Arduino integration with the state machines and code.
The QP-Arduino integration is described in the Application Note: "Event-Driven Arduino Programming with QP and QM".
More ResourcesModern Embedded Systems Programming is a deep subject and it is impossible to fully appreciate just by reading one blog post or by watching a 15 minute introductory video. So, please don't get discouraged if you don't grok it immediately--nobody would. But the more you learn of this subject, the better software developer you'll become. You'll also change your entire approach to concurrent programming.
So, here are some resources that you might find useful:
- The Quantum Leaps video channel on YouTube and specifically the "Modern Embedded Systems Programming" video course. The course started in 2013, but is still ongoing and in fact the newer lessons teach the exact concepts that you struggle with. You don't need to watch all the videos, just take a look at the course playlist and choose the subjects that you'd like to brush up on.
- The video series "Beyond the RTOS".
- The book "Practical UML Statecharts in C/C++, 2nd Ed.", which is available in free PDF.
- Other application notes and articles on the state-machine.com website.
The QP/C++ framework in the QP-Arduino integration is licensed under the open-source GPLv3 license with the Arduino GPLv3 Exception.
The Exception gives you permission to use QP/C++ with any Arduino-Certified Board without the requirement to open up your proprietary application code.
The freeware QM modeling tool is licensed under a simple EULA.
SupportYou can post any questions or comments about the QP-Arduino software in the following ways:
- comments to this project on Arduino PROJECT HUB
- posts to the Free QP/QM Support Forum
- posting comments on the QP-Arduino project web-page
Comments