Robobo Software Design
In history of programming we can see definitive trend where procedural programming languages give way to object oriented. Object oriented approach increases code reuse and by this speeds up development. Even for low level embedded programming such as Arduino objective approach is standard. This article describes Robobo abstraction for FreeRTOS interface.
Robobo case study
Before going into details lets try to do some analysis of requirements and motivations we would have for our application.
-
Robobo design should be prepared to accept variability of executive elements. We would like to have ability to introduce new executive elements by code addition not by code modification. Examples of elements are: Stepper Motor, hydraulic or pneumatic actuator. Even if these elements will not be part of project we want our software ready to accommodate them just in case.
-
Motor control should be flexible enough to change control algorithm during runtime. For instance if we would like to change PID algorithm parameters or even exchange PID with other algorithm then Robobo design should allow to do that. For control algorithm introduction please refer to my servo post.
-
Separate threads are to handle control over each motor. Although most of Robobo Motors will be PID driven we would like still have ability to prioritize control. PID itself does not require complex computation but having each servo calculated independently provides much more flexibility compared to single thread approach.
-
Robobo design assumes existence of main controller entity. This entity is responsible for communication with all motors. Main controller would run on thread with lower priority than servo controllers. It would receive notification when requested Motor position will be achieved. Main controller will be responsible for geometry calculation.
-
Robobo design will allow to read external communication from an user. This communication could be AWS cloud or CLI provided by serial USB interface.
-
Robobo design should abstract FreeRTOS in such a way that potential movement to different OS would be easy. No hard dependencies between Robobo Logic with OS.
Active Object
Before going into detailed class design I would like to clarify active object design pattern. In Object-Oriented Programming special concept of active object was created. Active object is an object which has important property added on top of regular object. This property is execution context. Whenever calling a method on Active Object it executes on context of particular thread.
In FreeRTOS we are assigning a priority for each task. Please refer to my previous article about Real Time Operation System. Consider a case where you have two Active objects and these two objects are assigned to two different FreeRTOS tasks. Suppose you execute method on both of them then execution order is determined by priority you selected for tasks.
Active object is like any other object. You treat it as normal object but since active objects are assigned to particular task they are dynamic from user perspective. Once method call is invoked then its execution can happen using concurrency mechanisms provided by OS layer. In such light active object concept is concept of requesting others to execute something.
In order to highlight importance of active object design please consider our
Robobo case. Each DCMotor
executes PID algorithm. PID algorithm is actual
computation that takes CPU from other tasks. If somebody would like to have
each DCMotor
object acting as active object then he would get ability to
prioritize PID algorithm execution among multiple DCMotor
objects.
Active Object in Robobo
Following is UML diagram that represents relationship between DCMotor
and
ActiveObject
classes. If you are unfamiliar with UML you might ignore this
and refer to code directly.
When implementing active object we need to delegate all concurrency
control to FreeRTOS API. As mentioned above Robobo aims to separate FreeRTOS
from control logic in order to enable future design changes such as OS
replacement. This can be achieved when all FreeRTOS calls will be contained in
single class. ActiveObject makes perfect spot to do that. Lets summarize
dependencies of ActiveObject
with FreeRTOS:
- Each ActiveObject is associated with FreeRTOS task. During
ActiveObject
constructor execution following call is made:
xTaskCreate( ActiveObjectTaskFunction,
"AO",
configMINIMAL_STACK_SIZE + 50,
mrQueue,
tskIDLE_PRIORITY,
NULL);
- In order to abstract inter process communication each
ActiveObject
will contain its own FreeRTOS queue which is created in constructor:mrQueue = xQueueCreate(...);
-
When
ActiveObject
client wishes to execute method in context ofActiveObject
instance task it callsexecuteMethod(...)
. Then call is translated toxQueueSend(mrQueue, &mr, 0)
FreeRTOS call. -
Main
ActiveObject
task routineActiveObjectTaskFunction
will just block onmrQueue
and receive functions requests and execute them. - Each
ActiveObject
will provide timer creation ability to its child classes. ActiveObject delivers implementation ofcreateTimer
method which then translates it to FreeRTOSxTimerCreate
function:
xTimerCreate
( "tmr",
period,
reload,
mr,
ActiveObjectTimerCallback );
Method Request
As I described above in order to facilitate interprocess communication we are
using FreeRTOS queues. Whenever there is a need for calling an active object
method, caller in fact will create a message and it will put it on Active Object
mrQueue
in his own execution context. This happens under the hood of
executeMethod()
call:
uint8_t ActiveObject::executeMethod(const std::function<void()> &f )
{
auto mr = new MRequest(this, f);
return xQueueSend(mrQueue, &mr, 0);
}
Special MRequest
structure was created in order to pass all necessary information to
active object context to make it able to execute. Please note that
executeMethod
doesn’t block. We can call it asynchronous as we are requesting
f
to be executed but we are not waiting for completion anyhow. Asynchronous
exection of function calls is current assumption of Robobo design.
MRequest
or method request is defined as follow:
class MRequest
{
...
ActiveObject *ao;
std::function<void()> *func;
bool persistent;
...
};
Each MRequest
includes pointer reference to ActiveObject
, a function call and
persistence flag. Persistence flag is detail important from heap management
perspective. I will skip explanation of this flag for now. ActiveObject
pointer
holds pointer to object in which context execution should happen. In reality
this reference is in place in order to satisfy FreeRTOS software timer design.
Once again I will skip explanation for now. From understanding
perspective key is function<void()> *func
. This C++11 standard class is used to
encapsulate function calls in objects. Reference can be found
here.
(As side note I will mention that lack of fully compilant C++ standard library on AVR caused me to switch Arduino Mega to Arduino Due)
When client creates MRequest
and put in on ActiveObject
mrQueue
then as
part of receiver context following will happen:
void ActiveObjectTaskFunction( void *q)
{
...
for (;;)
{
xQueueReceive( queue, &mr, portMAX_DELAY );
(*(mr->func))();
...
}
}
Client interface
In order to dispatch method request following call has be executed:
executeMethod(std::function<void()>(bind(&DCMotor::reportMethod,this)),
call takes advantage of c++11 bind
.
Bind allows us to
create function<void()>
objects even from methods that actually takes
parameters. This is flexible mechanism that allows us to call literally every
method even if they require to provide parameter set. Above executeMethod
call will pass function<void()>
created from void DCMotor::reportMethod()
method and create MRequest
object and then place it on mrQueue
.
Summary
In this article I started with Robobo design considerations. Then I introduced you active object concept. I showed you how active object allows us to isolate OS specific function calls to make our code portable. At the end I showed you essentials of active object mechanics realized in FreeRTOS. I hope this gives you entry level knowledge that allows you to read code further in order to understand remaining details of active objects implementation.