Ardu
Ardu
article info a b s t r a c t
Article history: In the last decade, thanks to its modular hardware and straightforward programming model, the
Received 24 September 2020 Arduino ecosystem became a reference for learning the development of embedded systems by various
Received in revised form 20 November 2021 users, ranging from amateurs and students to makers. However, while the latest released platforms
Accepted 6 December 2021
are equipped with modern microcontrollers, the programming model is still tied to a single-threaded,
Available online 10 December 2021
legacy approach. This limits the exploitation of the underlying hardware platform and poses limitations
Keywords: in new application scenarios, such as IoT and UAVs.
Real-time This paper presents the Arduino real-time extension (ARTe), which seamlessly extends the Ar-
Multi-tasking duino programming model to enable the concurrent execution of multiple loops at different rates
Arduino configurable by the programmer. This is obtained by embedding a low-footprint, real-time operating
Educational
system in the Arduino framework. The adherence to the original programming model, together with
the hidden support for managing the inherent complexity of concurrent software, allows expanding
the applicability of the Arduino framework while ensuring a more efficient usage of the computational
resources. Furthermore, the proposed approach allows a finer control of the latencies and the energy
consumption. Experimental results show that such advantages are obtained at the cost of a small
additional overhead and memory footprint. To highlight the benefits introduced by ARTe, the paper
finally presents two case studies, one of such in which ARTe has been leveraged to rapidly prototype
a mechanical ventilator for acute COVID-19 cases. We found that ARTe allowed our ventilator design
to rapidly adapt to changes in the available components and to the evolving needs of Intensive Care
Units (ICU) in the Americas.
© 2021 Elsevier Inc. All rights reserved.
1. Introduction and expanding range of features and devices. Over the years, this
approach proved to be successful and pushed the producers to de-
The Arduino project started in the early 2000s with the goal velop more and more powerful boards, still compatible with the
of providing a simple framework to support people with very original programming model, as well as extension boards (namely
limited programming skills in the development of embedded shields) to expand hardware features and interoperability.
projects. This purpose was pursued by creating a user-friendly de- The programming model requires the application developer to
velopment environment based on a simple programming model. provide a C file that only defines the setup() function, executed
Then, the team started a company to design and produced a at the startup to initialize the system, and the loop() function,
simple and low-cost board to create working prototypes inter- which executes endlessly. The simplicity of this solution signif-
acting with the physical world. The growth of the project was icantly alleviates the learning phase and motivates the base of
fueled by the decision to adopt an open-source approach that the massive number of projects using Arduino. However, in the
aggregated a community keen on supporting the development, last decade target applications became more and more complex,
creating examples and tutorials, and providing libraries for a wide including multiple sensors and actuators, and required interac-
tion with other systems through communication devices. These
✩ Editor: Neil Ernst. scenarios proved to be challenging to get along with the Arduino
∗ Corresponding author. programming model. For instance, a typical solution adopted
E-mail addresses: [email protected] (F. Restuccia), by Arduino users to deal with such a complexity consists in
[email protected] (M. Pagani), [email protected]
offloading the communication stack to a shield board that is gen-
(A. Mascitti), [email protected] (M. Barrow), [email protected]
(M. Marinoni), [email protected] (A. Biondi), erally more powerful than the microcontroller within the Arduino
[email protected] (G. Buttazzo), [email protected] (R. Kastner). board. Delegating multi-rate sensors and communication devices
https://doi.org/10.1016/j.jss.2021.111185
0164-1212/© 2021 Elsevier Inc. All rights reserved.
F. Restuccia, M. Pagani, A. Mascitti et al. The Journal of Systems & Software 186 (2022) 111185
Listing 1: Implementation of functions executing at different Listing 2: Implementation of functions executing at different
rates making use of delay. rates making use of millis().
int count = 0; // it counts the number of minor cycles #define N 3 // number of functions
int T1 = 10; // period (ms) for executing function1
int T2 = 25; // period (ms) for executing function2 int T[3] = {10, 25, 50}; // function periods (ms)
int T3 = 50; // period (ms) for executing function3 unsigned long time; // current time (ms)
int Tmin; // GCD of the periods (minor cycle) unsigned long prevtime; // previous time (ms)
int Tmaj; // lcm of the periods (major cycle) int i;
int K1; // counter value for triggering function1 void (*func_ptr[3])() = {function1, function2,
int K2; // counter value for triggering function2 function3};
int K3; // counter value for triggering function3
time = millis();
Tmin = GCD(T1, T2, T3); // minor cycle for (i=0; i<N; i++) prevtime[i] = time - T[i];
Tmaj = lcm(T1, T2, T3); // major cycle
while (1) {
K1 = T1/Tmin; // number of minor cycles in T1 for (i=0; i<N; i++) {
K2 = T2/Tmin; // number of minor cycles in T2 time = millis();
K3 = T3/Tmin; // number of minor cycles in T3 if (time - prevtime[i] >= T[i]) {
H = Tmaj/Tmin; // number of minor cycles in Tmaj prevtime[i] = time;
(*func_ptr[i])();
while (!end) { }
if (count%K1 == 0) function1(); }
if (count%K2 == 0) function2(); }
if (count%K3 == 0) function3();
count++;
if (count == H) count = 0;
delay(Tmin); // suspend for a minor cycle
} and their execution times are comparable with their periods,
this approach does no longer guarantee a regular activation of
the activities, since each function will delay the next one. Fig. 1
illustrates two examples of schedule: Fig. 1a shows the case in
which computation times are negligible, whereas Fig. 1b shows
to external boards produces complex solutions that are also in-
the case in which they are not (namely, C1 = 2.5 ms, C2 = 5 ms,
efficient in terms of cost, weight, and power consumption. This
C3 = 10 ms). The shadowed areas denote the delay at the end of
problem is even more evident with modern Arduino platforms
the loop and the number above each area represents the value of
(e.g., the Arduino Due board), where the main microcontroller
the counter during that interval.
remains mostly underutilized.
Note that, even in the case of negligible computation times,
1.1. Limits of the Arduino programming model the functions tend to accumulate a delay that, after a number
of executions, will cause a period skip. For instance, function1
The Arduino programming model works fine for simple con- skips a period every six instances (see intervals [50 ,60] and [110,
trol systems where sensors have to be acquired at the same 120] in Fig. 1a).
frequency, but becomes problematic when the control system In the case of non-negligible computation times (Fig. 1b),
requires actions that need to be triggered at different rates. function1 skips five periods out of eleven, and two of them are
Consider, for example, a system equipped with an infrared consecutive ([90, 100] and [100, 110]). Also, function2 does not
sensor, an inertial sensor, and a communication transceiver, execute properly, since only one instance is executed in the inter-
which need to be acquired with different periods, e.g., dictated val [25, 75], equivalent to two full periods. It is worth observing
by the sensor dynamics and the computation times required to that such a misbehavior is not due to an overload (in fact the
process the corresponding data. Suppose that the infrared sensor overall processor utilization is U = 2.5/10+5/25+10/50 = 0.65,
has to be sampled every T1 = 10 ms, the inertial sensor every i.e., the 65%), but it is due to that specific implementation.
T2 = 25 ms, and the communication device every T3 = 50 ms. A better solution, sometimes used by Arduino developers, is
The solution typically adopted by Arduino users in these situa- to keep track of the activation times by the function millis(),
tions is to program the main loop to execute with a period Tmin which returns the number of milliseconds passed since the pro-
(also called minor cycle) equal to the greatest common divisor gram started and activate each activity after a period is passed.
(GCD) of the three periods and trigger the other activities every An implementation following this approach is reported in Listing
Ki executions of the main loop. In our example, we have: 2.
T3 The implementation reported in Listing 2 reduces most of the
Tmin = 5 ms; K1 =
T1
Tmin
= 2; K2 = T2
Tmin
= 5; K3 = Tmin
= 10. (1) delays introduced by the previous one, but it still does provide a
A possible implementation of this approach is to use a counter general solution to the problem of executing functions at different
that counts the number of minor cycles and calls the function that rates. Fig. 2 illustrates two schedules produced for different com-
has to be executed with period Ti when the counter reaches the putation times. Fig. 2a refers to the case in which computation
value Ki = Ti /Tmin , as shown in Listing 1. times are C1 = 2.5 ms, C2 = 5 ms, C3 = 10 ms, leading to a
To avoid the counter overflow, the counter has to be reset total processor utilization U = 0.65, whereas Fig. 2b refers to the
when it reaches the value of the least common multiple (lcm) of case in which computation times are C1 = 5 ms, C2 = 5 ms,
the periods, also called major cycle. This solution works fine when C3 = 15 ms, leading to a total processor utilization U = 1.0.
the execution times of the three functions are negligible with While the schedule in Fig. 2a respects all the specified periods,
respect to the periods (e.g., for the case of blinking LEDs). How- the one in Fig. 2b does not, since only three instances of function1
ever, when the functions perform more complex computations are executed out of five (in fact, the function is not executed in
2
F. Restuccia, M. Pagani, A. Mascitti et al. The Journal of Systems & Software 186 (2022) 111185
Fig. 1. Schedule obtained for the three functions under the solution illustrated in Listing 1, when their computation times are negligible (a) and when they are not
(b). Triangles below the axes denote activation times and vertical bands represent minor-cycle suspension intervals (the number on top is the value of the counter).
Fig. 2. Schedule obtained for the three functions under the solution illustrated in Listing 2, for different values of the computation times.
the intervals [10, 20], [30, 40], [60, 70], [80, 90], and so on). The provided by a real-time operating system. This is done by keeping
problem is that the functions are executed one after the other the scheduler and the operating system transparent to the pro-
and cannot be preempted (i.e., temporarily interrupted to be later grammer, so that each loop can be developed by following the
resumed) during their execution. For instance, in the schedule of classical Arduino programming style. The software development
Fig. 2b, at time t = 10 function1 should be reactivated, but of the overall application is actually simplified, since the user does
it cannot run since the sketch will call it in the next cycle, after not have to explicitly trigger the functions, as in the previous
the execution of function3, which completes at time t = 25, implementation shown above.
i.e., in the middle of the third period of function1. This type Listing 3 reports the code that implements the three functions
of problem can only be solved by handling the functions by a according to the proposed extended programming model.
preemptive scheduler.
The solution presented in this paper extends the Arduino Paper structure. The rest of the paper is organized as follows:
programming model by allowing the user to specify multiple Section 2 presents an overview of the Arduino framework, the
loops, each with its own execution period, and by treating each proposed extensions to provide multitasking, and the Erika Enter-
loop as a concurrent thread scheduled by a preemptive scheduler prise kernel that is leveraged in the proposed approach; Section 3
3
F. Restuccia, M. Pagani, A. Mascitti et al. The Journal of Systems & Software 186 (2022) 111185
Table 1
Listing 3: Implementation of functions executing at different Features comparison of popular Arduino boards.
rates using the ARTe programming model. Arduino Processor Arch. Freq. SRAM NV Memory
void loop() {
<<background code>> 2. Background and state of the art
}
void loop1(10) { While the user experience for Arduino developers remained
function1(); mostly unaltered along the years, the internals of the Arduino
} framework evolved to include new features and increase its mod-
ularity and portability. This section first presents the current
void loop2(25) { status of the official Arduino framework and then provides an
function2(); overview of some custom extensions proposed by other authors
} to support multitasking in Arduino. Finally, the section illustrates
how the work proposed in this paper advances the state of
void loop3(50) { the art and introduces the main features of the ERIKA Enter-
function3();
prise (Anon, 0000a)1 real-time operating system (RTOS), which has
}
been leveraged to realize the proposed solution.
Listing 4: Implementation of an application similar to the one 2.1. The Arduino framework
proposed in Listing 3 using the FreeRTOS-Arduino programming
model. Arduino is an open-source project based on easy-to-use em-
#include <Arduino_FreeRTOS.h> bedded hardware and software. The Arduino ecosystem consists
of a set of single-board microcontrollers and a software frame-
void TaskLoop1(void *pvParameters); work that comes with an integrated development environment
void TaskLoop2(void *pvParameters); (IDE). Over the years, many Arduino boards with different com-
void TaskLoop3(void *pvParameters); putational and I/O capabilities have been released. The first board
has been the Arduino UNO, which is based on a Microchip AT-
void setup() {
xTaskCreate(TaskLoop1,(const portCHAR *)"loop1",128, mega microcontroller running at 16 MHz and offers 14 digital
NULL,2,NULL); I/O pins and six analog input pins. The original Arduino UNO
xTaskCreate(TaskLoop2,(const portCHAR *)"loop2",128, board has been later updated with different releases and is still
NULL,2,NULL); supported nowadays. However, it offers limited computational
xTaskCreate(TaskLoop3,(const portCHAR *)"loop3",128, capabilities and small amount of volatile (SRAM) and non-volatile
NULL,2,NULL); (NV) memory. To overcome these limitations, the Arduino com-
} munity released more advanced boards such as the Arduino DUE
and Arduino Zero based on ARM Cortex microcontrollers. Both
void loop(){ these boards dispose of more powerful computational and I/O
<<background code>>
capabilities with respect to Arduino UNO, and are hence better
}
void TaskLoop1(void *pvParameters) { suited for more complex application scenarios. Table 1 reports a
(void) pvParameters; feature comparison of these three Arduino boards.
for(;;){ The software side of the Arduino project consists of a software
function1(); framework, initially based on the Wiring project (Barragán, 2004),
} which includes an IDE in charge of the building process and the
} device flashing. A block diagram of the Arduino building process
void TaskLoop2(void *pvParameters) { performed by the IDE is illustrated in Fig. 3. Basically, the building
(void) pvParameters; process can be divided into two phases: a pre-processing phase
for(;;){ and a compilation-and-linking phase. During the pre-processing
function2();
phase, the Arduino framework performs a few transformations
}
} like adding additional headers and generating prototypes for all
void TaskLoop3(void *pvParameters) { the functions defined in the application source file, which is de-
(void) pvParameters; noted by sketch. Then, the source files are passed to the compiler
for(;;){ tool-chain to be compiled and linked with the Arduino libraries.
function3();
} 2.2. Related work
}
Several solutions are well known to support multitasking and
provide solutions to simplify the development of applications
on microcontrollers. An example is the work proposed by Ri-
describes the proposed approach, while Section 4 highlights rele- vas and Tijero (2019), which aims at simplifying the use of the
vant implementation details. Experimental results and use cases Ada safety language for the development of applications running
are reported in Section 5, whereas Section 6 states our closing
remarks. 1 ERIKA Enterprise project website: https://www.erika-enterprise.com/.
4
F. Restuccia, M. Pagani, A. Mascitti et al. The Journal of Systems & Software 186 (2022) 111185
on small microcontroller devices. However, it is not straightfor- for periodic activities. The user must explicitly specify ini-
ward to integrate some multitasking support in Arduino while tialization procedures and possible preemption points, with
minimizing the impact on its programming model and ensuring the result that code modifications are required to support
retro-compatibility. The Arduino programming model is based multitasking.
on the use of only two constructs: the loop() function and • Tasks are executed cooperatively. Under this multitasking ap-
the setup() function. The former contains the code that is proach, the context switch is triggered by the running task,
cyclically executed as long as the device is powered, while the which voluntarily yields the processor to other tasks. This
latter is executed at the startup of the device. This simple pro- increases the latency variability and the complexity in guar-
gramming model allows entry-level users to develop applications anteeing response-time bounds for the tasks.
in Arduino without requiring specific skills in programming a • No protection against task overruns. When a task instance
microcontroller. On the other hand, this model is not suitable runs longer than its period, the entire schedule can expe-
for multitasking as the user is limited to a single, sequential rience a domino effect, jeopardizing the whole application.
execution flow given by the code of the loop() function.
Several methods with different degrees of complexity have Many of such issues are originated by the fact that no operat-
been proposed to fill this gap. The most straightforward ones do ing system is adopted, hence forcing the user to implement some
not require any third-party library or extension, and just allow form of scheduling in the loop() function. This approach can be
defining the tasks as C functions to be executed in the standard particularly error-prone, especially for users that are not familiar
Arduino loop() function. The scheduling logic is implemented with scheduling techniques and concurrent programming.
with ad-hoc conditional statements and delay functions (such as FreeRTOS-Arduino (Barry, 0000) has been proposed to address
delay() and millis()) to mimic a periodic activation of the these issues. It is based on a porting of the FreeRTOS kernel
tasks. In this paper, this basic approach is adopted as a baseline as an Arduino library and provides fixed-priority preemptive
for comparison purposes: more details are provided in Section 5. scheduling. Despite being a powerful solution, FreeRTOS-Arduino
Other solutions were proposed by relying on the use of third- introduces several complications at the level of the program-
party libraries that implement multitasking in a way that is ming model, as it requires the user to be confident with both
similar to the one just discussed, but offering a more friendly the FreeRTOS API and concurrent programming, which are skills
interface to the developer. Some examples of such libraries are that typically exceed those of the general user of Arduino. A
the following: sample application developed following the Arduino-FreeRTOS
programming model is reported in Listing 4.
• The Scheduler library (Anon, 0000b) allows registering mul- Furthermore, FreeRTOS is not a static operating system, i.e., not
tiple loops in the setup function that will cyclically be all kernel code and data structures can be tailored to the ap-
executed at run-time. There is no control in terms of pe- plication at compile time. Therefore, it is characterized by a
riods and timing still relies on explicit delays, like in the larger footprint,2 and memory and run-time overhead with re-
original Arduino paradigm. In order to reduce the impact spect to those that would be actually required to handle a cer-
of loops with significant execution times, the library pro- tain set of tasks. This waste of resources can be a severe issue
vides a yield function that the application programmer can on resource-constrained platforms such as Arduino UNO and
explicitly use. Arduino Zero.
• SoftTimer (Kelemen, 0000) library enables multitasking by Another solution based on an RTOS is Qduino (Cheng et al.,
defining each task as a C++ object. The constructor of such
2015), which extends the Arduino framework with a custom
objects takes as arguments the period of the task and a
API to allow implementing concurrent loops with support for
pointer to a callback function. The callback functions are
mutual exclusion on shared resources. As for FreeRTOS-Arduino,
meant to be executed in a periodic non-preemptable fashion
Arduino users are required to acquire additional knowledge on
according to the task period. Tasks are registered in the
real-time concurrent programming and the specific Qduino API.
Arduino setup() function. This library originally prevented
In addition, Qduino only supports Arduino platforms based on
the use of the standard loop() function, hence breaking the
Intel x86 processors (e.g., Galileo, Arduino 101). Therefore, this
original Arduino programming model. This limitation has
solution is not compatible with the majority of Arduino boards.
been removed only recently.
Note also that the Arduino framework comes with a rich set of
• ArduinoThreads (Anon, 0000c) works similarly to the Sched-
libraries that have been developed to work under singletasking,
uler library discussed above but implements a more com-
i.e., their internal state can be left inconsistent whenever they
plex set of constructs to manage the execution of tasks in a
are suspended to execute other computational activities. Both
periodic fashion. This approach allows more flexibility but
FreeRTOS-Arduino and Qduino require explicitly managing mu-
requires a significant impact in terms of knowledge and
tual exclusion when using these libraries, hence complicating the
resulting application code.
programming model even for simple and typical operations that
Such a class of solutions suffer from the following drawbacks: are present in many Arduino example sketches.
• The programming model becomes more complex with respect
to the original one. Also, most of such extensions (with the 2 For instance, the footprint of a simple blink application with FreeRTOS on
exception of SoftTimer) do not provide explicit support Arduino UNO is 8264 bytes (25% of the available memory).
5
F. Restuccia, M. Pagani, A. Mascitti et al. The Journal of Systems & Software 186 (2022) 111185
to synchronize the local proxy with the global variable. Such code
fragments are referred to as synchronization prolog and epilogue, Listing 9: ARTe locking primitives.
respectively. In practice, the synchronization prolog locks the void arteLock(void);
mutex associated with the global variable, performs a copy of the void arteUnlock(void);
global variable on the local proxy, and then releases the mutex.
The synchronization epilogue locks the global variable mutex, void arteLockRes(enum arteRes resource);
copies back the value of the local proxy variable into the global void arteUnlockRes(enum arteRes resource);
variable, and then releases the mutex. If a task performs read-
only access on a global variable, the ARTe builder injects only /* For testing purposes */
uint8_t arteEnabled(void);
the head snippet. The builder ignores global constants. It is worth
uint8_t arteNestingLevel(void);
noting that this mechanism is entirely transparent to the user, it uint8_t arteNestingLevelRes(enum arteRes resource);
does not require changes to the programming paradigm, and is
fully compatible with existing code. The approach is applied to
all global variables to maintain a consistent behavior across dif-
ferent platforms (hardware-dependent optimizations for atomic
Listing 10: ARTe resources declaration.
variables are possible but currently not supported).
While this approach is simple and effective, it also has some enum arteRes {
limitations. Indeed, to synchronize a global variable with its local arteIO,
proxy, it is necessary to know the size of the variable at compile
arteADC,
arteDAC,
time. Therefore, the ARTe protection mechanism does not support
arteSPI,
dynamically allocated memory and data types accessed through arteI2C,
pointers (e.g., linked lists). In fact, the ARTe protection mechanism arteTIMER,
is limited to standard C++ primitive data types and user-defined arteUSART,
C-like passive data structures (PDS). However, since typical Ar- arteSTREAM
duino sketches do not make use of pointers and user-defined };
types, this limitation is expected to have a minimal impact. Please
note that the scope of the ARTe protection mechanism is limited Listing 11: Example of an Arduino library function extended
to the Sketch code. On the contrary, libraries developers are using ARTe locking primitives.
responsible for protecting library code against race conditions
using the mutual exclusion support mechanism described in Sec- int TwoWire::read(void) {
tion 3.5. Finally, it is worth remarking that the ARTe protection
int retval;
mechanism is not semantically equivalent to mutual exclusion.
/* *** ARTe - begin critical section *** */
Indeed, the protection mechanism is intended to provide easy-to- arteLockRes(arteI2C);
use deadlock-free communication channels between tasks based
on local proxy copies. Such local copies are initialized at the if (rxBufferIndex < rxBufferLength)
beginning of the task with the value of the corresponding global retval = rxBuffer[rxBufferIndex++];
variable. Then, global variables are updated back at the end of the else
task with the values of local copies. Hence, whenever multiple retval = -1;
tasks access the same global variable, the updates made by one
task are not visible to the other tasks until it completes and the /* *** ARTe - end critical section *** */
other tasks begin a new job. For this reason, it is recommended to arteUnlockRes(arteI2C);
use only one writer task for each global variable, i.e., each global
return retval;
variable implements a 1-to-N channel. }
3.5. Adapting libraries for concurrent execution
Since ARTe follows a programming model based on periodic and their affinity with the tasks, the timer to periodically activate
activities, there is no need to explicitly introduce delays in the the tasks, etc. This file is created starting from a template OIL file
code using functions such as millis(), micros(), delay(), and de- that contains the configuration parameters of ERIKA that pertain
layMicroseconds(). More importantly, the use of such functions to the hardware platform. Finally, the OIL file is provided to the
is discouraged, since they could introduce delays longer than RT-DRUID configuration tool (distributed with the ERIKA RTOS)
expected due to preemptions, depending on their internal imple- to generate tailored version of the RTOS as a linkable library.
mentation. The second file is a processed version of the original sketch code
Finally, the support library provides a set of auxiliary functions augmented with the necessary calls to the ERIKA kernel. These
that can be used by the developer for debugging and testing system calls will be later resolved at linking time while combining
purposes. the sketch object file with the ERIKA library. In this way, the
The arteLockNestingLevel() and arteLockNesting ERIKA core and the Arduino code can be compiled through dif-
LevelRes() functions return the nesting level of the critical ferent compilation flows and combined together only in the end
section. The arteEnabled() function returns true if the ARTe during the linking phase. This approach allows for maintaining
framework is enabled or false if ARTe is disabled and the code a higher level of modularity, facilitating maintainability. Fig. 6
is compiled using the regular Arduino environment. details the internal behavior of ARTe parser.
Arduino Build Process. In this stage, the Arduino Builder
4. ARTe implementation proceeds by compiling all the Arduino-related files needed by the
application and produces a library. At the end of the build process,
This section describes the entire process employed by ARTe the Arduino binary files are provided to the linker together with
to produce the final (binary) executable of the application to be ERIKA binary files.
programmed on the platform starting from the application code.
To avoid discussing several aspects of the Arduino framework that RT-DRUID. When this stage is executed, the ERIKA config-
do not pertain to the contribution of this work, the presentation uration file produced by ARTe is ready to be analyzed by the
is mainly focused on the modifications that have been performed RT-DRUID tool, which generates C code to configure ERIKA and
to the Arduino building process. Particular attention is taken at a makefile to build the RTOS.
the code parsing stage provided by the ARTe parser. ERIKA build process. After the execution of RT-DRUID, it
The starting point for the ARTe building procedure remains is possible to perform the compilation of ERIKA. This building
the sketch file written by the user. ARTe just requires sketches process produces in output two libraries. The first one contains
written according to the programming model presented in the the architecture-independent code of the ERIKA kernel, while the
previous section with at least one periodic task defined. The second one includes the code needed to instantiate it on the
trivial case composed only of the classic Arduino loop() is not specific platform and is different for each available platform.
handled by ARTe, since it would add the cost due to the ERIKA
kernel to perform the same activities already provided by Arduino Linking stage. The linking stage is realized by extending the
itself. standard linking procedure performed by Arduino, which has
been modified to merge the ERIKA kernel produced by ARTe with
4.1. ARTe build chain the object files of the application. The result is an ELF binary file
ready to be downloaded on the board via the standard Arduino
Fig. 5 shows the build flow for an ARTe application, which tools.
starts with the standard Arduino pre-processor that generates
a C++ source file from the sketch (.ino file). A parsing stage 4.2. Internals of the ARTe parser
(i.e., the ARTe parser) is first employed to process the C++ source
file for the purpose of producing two outputs: another Arduino- This section details the internal steps performed by the ARTe
compatible C++ source file and an OIL configuration file for the parser.
ERIKA kernel. These two files are then processed in parallel by C++ file split. The C++ file produced by the Arduino pre-
the following stages (see Fig. 5). One branch involves the regular processing stage contains a long inclusion of Arduino-related
Arduino building process, whose documentation is available on header files. ARTe does not need to analyze the code in these
the official Arduino website (Anon, 0000h). The other branch, header files (and performing such an activity would slow down
called ARTe Builder and invoked through a pre-build hook recipe, the build process). Therefore, as a preliminary step, ARTe extracts
is in charge of managing all the stages to obtain an instance of the user source code from the C++ file in input. The user code is
the ERIKA kernel tailored to the application under compilation. saved in a temporary file and utilized as the input for the static
The main stages of the build flow are discussed in details next. analysis performed by Doxygen. The ARTe parser works on this
temporary file and finally publishes the changes it applies in the
Arduino pre-processing. This is the first stage invoked by the
original C++ file, hence replacing the user code generated by the
Arduino Builder and is in charge of creating a unified C++ file that
Arduino pre-processing stage.
includes all the Arduino-related header files and the user code
present in the sketch. Since the Arduino IDE gives no access to the Doxygen Analysis. Doxygen analyzes the user code and pro-
sketch, ARTe has been designed to process the C++ file generated vides an XML file that models the code structure and allows
by this stage. identifying all the functions and all the global variables. This XML
file is imported in the Java environment using the Java Element
ARTe parser. This is the core component of ARTe. It has
interface (Anon, 0000j) to keep track of functions and global
been developed in the Java programming language to simplify
variables in proper data structures.
extensibility and relies on Doxygen (Anon, 0000i) as a parsing
engine. The ARTe parser inputs the C++ file produced by the ERIKA task creation. All the ARTe loops are converted into
Arduino pre-processing stage and provides two outputs. The first ERIKA tasks, like in the example reported in Listings 12 and 13. All
one is the configuration file for the ERIKA RTOS (OIL file), which other functions defined in the sketch are not modified by ARTe.
specifies a number of parameters such as the number of tasks The background loop is executed as background activity, i.e., a
and their setting (e.g., priorities), the number of shared resources task running with the lowest priority in the system.
10
F. Restuccia, M. Pagani, A. Mascitti et al. The Journal of Systems & Software 186 (2022) 111185
Fig. 6. Flow chart that describes the interval behavior of the ARTe parser.
ERIKA alarms, which control the periodic activation of tasks, to update the global variable __ARTE_GLOBAL_var__ with
are configured at the end of the Arduino setup function by the content of the local variable var. Such an approach
injecting calls to the corresponding ERIKA primitives. In this way, ensures that the updated value is correctly saved on the
tasks can start executing only after the user code in the setup global variable when the task terminates the execution,
function has completed its execution. The definitions of some hence publicizing the output produced by the task.
ERIKA functions are also injected at the top of the temporary C++
file. Furthermore, some header files are included to access the Oil file creation. The last step is the generation of the OIL
ERIKA and ARTe API. configuration file, which follows the OSEK standard to configure
the ERIKA RTOS. This step makes use of a template file, which
Global variables protection. Thanks to the Doxygen analysis,
is chosen by the ARTe builder within a library of templates
the ARTe parser is capable of detecting which global variables are
as a function of the Arduino board selected by the user. The
accessed by more than one task. Such global variables are then
template contains all the architecture-dependent configurations
protected with the mechanism discussed in Section 3.4, which and parameters. The final OIL file produced by this stage includes
also requires defining an ERIKA resource for each global variable the declarations of all the global resources and tasks. Each task
in the OIL configuration of the RTOS. As stated in Section 3.4, the is assigned a priority based on the Rate Monotonic algorithm,
protection mechanism is restricted to primitive types and C-like i.e., the shorter the task period the higher the priority. ERIKA
passive data structures. alarms are used to periodically activate the tasks. They are set
The functions of the ERIKA API that are used to implement together with the corresponding action to take when the alarm
mutual exclusion are: fires (i.e., the task to be executed). An example of how a task and
• void GetResource(ResourceType ResID), to lock the the relative alarm are declared in the file is reported in Listing 8.
resource specified as a parameter; and
• void ReleaseResource(ResourceType ResID), to un-
5. Experimental evaluation
lock the resource specified as parameter.
More specifically, the protection of a global variable var re- This section presents a practical evaluation conducted to as-
quires performing the following actions: sess the performance of the ARTe framework.
As a first step, the scalability of ARTe with respect to the
1. The name of the global variable is changed from var to traditional approach is evaluated by comparing the memory foot-
__ARTE_GLOBAL_var__; print of the runtime support as a function of the number of
2. A local variable var is defined in the local scope of each loops. Following, two complete case-study applications have been
ARTe task that uses the global variable of interest; developed to test the ARTe framework in a realistic scenario
3. A short critical section is placed at the beginning of the task where multiple peripherals devices on the Arduino board are
body to safely perform the copy of the value of the global used under multitasking.
variable __ARTE_GLOBAL_var__ into the local variable
var; 5.1. Memory footprint
4. If the task performs some modifications on the global vari-
able, which can be detected via the Doxygen analysis, an- Arduino boards are typically memory-constrained embedded
other short critical section is placed at the end of task body platforms. Therefore, to assess the sustainability of the ARTe
11
F. Restuccia, M. Pagani, A. Mascitti et al. The Journal of Systems & Software 186 (2022) 111185
Table 2
Listing 12: Example of a sketch containing two ARTe loops Evaluation of the memory footprint of ARTe vs. the standard Arduino
sharing a global variable. programming model on an Arduino Due platform.
Number of loops Footprint (bytes)
Arduino ARTe
int global_var;
1 10708 (2.042%) 12556 (2.395%)
void loop_1(100) 2 10732 (2.047%) 12608 (2.405%)
4 10788 (2.058%) 12696 (2.422%)
{
8 10900 (2.079%) 12880 (2.457%)
int local_var; 16 11124 (2.122%) 13248 (2.527%)
local_var = global_var;
}
void loop_2(100)
{
global_var++;
}
int local_var;
local_var = global_var;
}
TASK (loop_2)
{
//--------------------------------
int global_var;
GetResource(__ARTE_MUTEX_global_var__);
memcpy(&global_var,&__ARTE_GLOBAL_global_var__,
sizeof(int));
ReleaseResource(__ARTE_MUTEX_global_var__);
//--------------------------------
global_var++; Fig. 8. Arduino and ARTe footprints on the Arduino UNO platform with respect
to the number of tasks.
//--------------------------------
GetResource(__ARTE_MUTEX_global_var__);
memcpy(&__ARTE_GLOBAL_global_var,&global_var__, The results obtained for the case of a single task show that
sizeof(int)); the ARTe runtime support, i.e., the ERIKA RTOS plus the support
ReleaseResource(__ARTE_MUTEX_global_var__); libraries for mutual exclusion, requires only 1848 bytes of ad-
//--------------------------------
ditional memory in the worst case, corresponding to less than
}
0.4% of the available memory on an Arduino Due. It is also worth
noting that the memory footprint scales almost linearly with the
number of tasks with a rate of fewer than 50 bytes per task.
Fig. 7 shows a graphical representation of this trend where the
framework, it is worth evaluating the impact of the runtime
support in terms of memory consumption. To this end, a simple first value of the curve labeled Arduino is the size of the standard
modular application consisting of up to 16 tasks (each perform- Arduino sketch, including only the loop() function.
ing a single GPIO operation only) has been programmed with The same experimental evaluation has been performed by
both the standard Arduino framework using the loop scheduling using the Arduino UNO platform, and the results are reported
technique, and then with the ARTe framework. Table 2 reports in Table 3. In this setting, the ARTe runtime support demands
and compares the memory footprint of both implementations about 8 KB with 16 periodic tasks (considering both program
while varying the number of loops (in bytes and as percentage storage space and dynamic memory), while ARTe for Arduino
of the total amount of resources available on the board). These Due demands 13 KB. This is caused by the different architectures
measurements have been collected when building an application and the implementations of the hardware-specific code. Also,
for the Arduino Due platform. remember that the two platforms rely on different versions of the
12
F. Restuccia, M. Pagani, A. Mascitti et al. The Journal of Systems & Software 186 (2022) 111185
Table 3
Evaluation of ARTe memory footprints on the Arduino UNO platform. Since the Arduino UNO toolchain differentiates
between Program storage space (first addend in sum) and Global variables dynamic memory (second addend in sum),
this distinction is preserved in the table.
Number of loops Footprint (bytes)
Arduino ARTe
1 964 + 11 (2.98% + 0.54%) 5494 + 791 (17.03% + 38.62%)
2 958 + 11 (2.96% + 0.54%) 5552 + 853 (17.21% + 41.65%)
4 1006 + 11 (3.11% + 0.54%) 5668 + 977 (17.57% + 47.70%)
8 1080 + 11 (3.4% + 0.54%) 5900 + 1225 (18.29% + 59.81%)
16 1204 + 11 (3.73% + 0.54%) 6364 + 1721 (19.73% + 84.03%)
Table 4
Comparison of the worst-case profiled response times (pWCRT) for Arduino
native and ARTe implementations of the case study application.
Loop Period (ms) pWCRT (ms)
ARTe Loop sched
IMU 20 6.603 6.689
FIR 40 0.148 6.529
Servo 50 0.008 6.532
Web server 100 11.253 20.993
LED-1 1000 0.010 13.990
LED-2 2000 0.010 11.000
LED-3 3000 0.010 11.002
ERIKA kernel (ERIKA v2 for Arduino Due and ERIKA v3 for Arduino
UNO).
As illustrated in Fig. 8 and detailed in Table 3, the footprint
of an application increases with the number of tasks (i.e., ARTe
loops). The test application is the same as the one considered in
Fig. 7). Overall, ARTe tasks require a linearly increasing amount
of memory, which is only slightly larger than the one required by
the stock Arduino framework.
Table 5
Ventilator IO Hardware used in the case study. ARTe was used to test all IO peripherals, with the optimal configuration
highlighted.
Part ID Part Function Quantity Interface Vendor Part Serial
AF1 Airflow sensor 2 I2C Honeywell HAFUNH0300L4AXT
PS1 Pressure sensor 3 ADC (onboard) Honeywell 150PAAB5
PS2 Pressure sensor 0/2 ADC (onboard) Honeywell 015PAAB5
O21 O2 Sensor 0/2 ADC (external) Maxtec Max-23
O22 O2 Sensor 2 ADC (external) Maxtec Max-12C
O23 O2 Sensor 0/2 ADC (external) Maxtec Max-250ESF
PV1 Proportional Valve 0/2/4 PWM (12V drive) Yong Chuang YCLT21-35-1GBV-5B61B
PV2 Proportional Valve 0/2/4 PWM (12V drive) Yong Chuang YCLT21-2C-1GBV-5B61B
PV3 Proportional Valve 4 PWM (12V drive) IQ valves Tesla iQ
OI1 O2 Sensor IO 1 I2C Texas Instruments ADS1115
CM1 Control Modem 1 ADC (onboard) C-19 Crisis Tech TVCV19 Univ Controller
VD1 Valve driver 4 PWM (onboard) C-19 Crisis Tech TVCV19 AJak Controller
VD2 Valve driver 0/2/4 PWM (onboard) Infineon Technologies IRFZ44NPBF
Declaration of competing interest Barrow, M., Restuccia, F., Gobulukoglu, M., Rossi, E., Kastner, R., 2021. A remote
control system for emergency ventilators during sars-cov-2. IEEE Embedded
The authors declare that they have no known competing finan- Systems Letters.
cial interests or personal relationships that could have appeared Barry, R., 0000. Arduino freertos, https://github.com/feilipu/Arduino_FreeRTOS_
Library.
to influence the work reported in this paper.
Bayle, J., 2013. C Programming for Arduino. Packt Publishing Ltd.
Bertogna, M., Fisher, N., Baruah, S., 1991. Resource-sharing servers for open
Acknowledgments environments. IEEE Trans. Ind. Inf. 5 (3), 202–220.
Biondi, A., Buttazzo, G., Bertogna, M., 2015. 2015. Supporting component-
based development in partitioned multiprocessor real-time systems. in:
The authors would like to thank Pasquale Buonocunto4 for the proceedings of the 27th euromicro conference on real-time systems, ecrts
vision and the fundamental contribution given to the first version 2015, lund, sweden.
of the ARTe framework. Buonocunto, P., Biondi, A., Pagani, M., Marinoni, M., Buttazzo, G., 2016. ARTE:
arduino real-time extension for programming multitasking applications. In:
References Proceedings of the 31st Annual ACM Symposium on Applied Computing.
ACM, pp. 1724–1731.
Anon, 0000a. Erika enterprise RTOS kernel, http://erika.tuxfamily.org/drupal/. Buttazzo, G.C., 2011. Highest locker priority. In: Hard Real-Time Computing
Anon, 0000b. Arduino scheduler library, https://www.arduino.cc/en/Reference/ Systems: Predictable Scheduling Algorithms and Applications. p. 212, Section
Scheduler. 7.5.
Anon, 0000c. ArduinoThreads, https://github.com/ivanseidel/ArduinoThread. Cheng, Z., Li, Y., West, R., 2015. Qduino: A multithreaded arduino system for
Anon, 0000d. The freertos kernel, https://www.freertos.org/. embedded computing. In: 2015 IEEE Real-Time Systems Symposium. IEEE,
Anon, 0000e. Nuttx real-time operating system, https://nuttx.apache.org/. pp. 261–272.
Anon, 0000f. Pre and post build hooks documentation, https://arduino.github.io/ Kelemen, B., 0000. Softtimer pseudo multitasking solution, https://github.com/
arduino-cli/latest/platform-specification/#pre-and-post-build-hooks-since- prampec/arduino-softtimer.
arduino-ide-165. Liu, C., Layland, J., 1973. Scheduling algorithms for multiprogramming in a
Anon, 0000g. OSEK/VDX Operating system specification 2.2.1., https://www.iso. hard-real-time environment. J. Assoc. Comput. Mach. 20 (1), 46–61.
org/standard/40079.html. Marzario, L., Lipari, G., Balbastre, P., Crespo, A., 2004. IRIS: A New reclaiming
Anon, 0000h. The arduino official webpage, https://www.arduino.cc/. algorithm for server-based real-time systems. In: Proc. of the IEEE Real-Time
Anon, 0000i. The doxygen parser official webpage, http://www.doxygen.nl/. and Embedded Technology and Applications Symposium, Toronto, Canada.
Anon, 0000j. The oracle documentation for the element interface, https://docs. Rivas, M.A., Tijero, H.P., 2019. Leveraging real-time and multitasking Ada
oracle.com/javase/8/docs/api/javax/lang/model/element/Element.html. capabilities to small microcontrollers. J. Syst. Archit. 94, 32–41.
Barragán, H., 2004. Wiring: Prototyping physical interaction design. Interaction Wang, Y., Saksena, M., 1999. Scheduling fixed-priority tasks with preemption
Design Institute, Ivrea, Italy. threshold. In: Proc. of the 6th IEEE Int. Conference on Real-Time Computing
Systems and Applications, RTCSA’99, Hong Kong, China.
16