OpenSwarm: an event-driven embedded operating system for miniature robots

Stefan M Trenkwalder, Yuri K Lopes, Andreas Kolling, Anders L Christensen, Radu Prodan and Roderich Groß

On

Abstract

This paper presents OpenSwarm, a lightweight easy-to-use open-source operating system. To our knowledge, it is the first operating system designed for and deployed on miniature robots.

OpenSwarm enables these robotic platforms to execute multiple tasks simultaneously. It provides a hybrid kernel that natively supports both pre-emptive and cooperative scheduling. This makes OpenSwarm suitable for both computationally intensive and swiftly responsive tasks.

OpenSwarm provides hardware abstractions to rapidly and systematically develop and test platform-independent code. The current version has a memory footprint of 12 kB ROM and 1 kB RAM. We demonstrate how it can be used to implement a controller for a canonical swarm robotics task. The performance is analysed and compared to a hardware-near implementation.

We believe that OpenSwarm makes robots with severely limited hardware more accessible, and may help such systems to be deployed in real-world applications.


How to use OpenSwarm


Events and hardware abstraction

While other operating systems, such as Unix, uses resource access mechanisms in different ways (eg "everything is a file"), OpenSwarm provides access of hardware and other resources via events.

Events can be sent from actuator-, communication module, and processes. They can be received by processes, sensor-, and communication modules. They can carry any kind of data, raw, abstracted, or none.

Due to the large variety of robots, OpenSwarm provides a small set of platform-independent events.

SYS_EVENT_TERMINATION - This event signals the operating system to stop executing and the device will restart.

SYS_EVENT_10ms_CLOCK - This event is emitted every 10 ms, by the system.

The OpenSwarm implementation that was used in this paper provides the following platform-dependent events.

SYS_EVENT_IO_MOTOR_LEFT - This event is received by the motor module to set a new motor speed.

SYS_EVENT_IO_MOTOR_RIGHT - This event is received by the motor module to set a new motor speed.

SYS_EVENT_IO_REMOECONTROL - This event is sent to signal new commands from a remote control.

SYS_EVENT_IO_TO_BLUETOOTH - This event is sent when new data was received via Bluetooth.

SYS_EVENT_IO_SELECTOR_CHANGE - This event is sent when the selector changed its value.

SYS_EVENT_IO_PROX_ALL - This event is sent when all proximity sensors were read and buffered.

SYS_EVENT_IO_PROX_0 - This event is sent when proximity sensors 0 was obtained.

SYS_EVENT_IO_PROX_1 - This event is sent when proximity sensors 1 was obtained.

SYS_EVENT_IO_PROX_2 - This event is sent when proximity sensors 2 was obtained.

SYS_EVENT_IO_PROX_3 - This event is sent when proximity sensors 3 was obtained.

SYS_EVENT_IO_PROX_4 - This event is sent when proximity sensors 4 was obtained.

SYS_EVENT_IO_PROX_5 - This event is sent when proximity sensors 5 was obtained.

SYS_EVENT_IO_PROX_6 - This event is sent when proximity sensors 6 was obtained.

SYS_EVENT_IO_PROX_7 - This event is sent when proximity sensors 7 was obtained.

SYS_EVENT_IO_1PXSENSOR - This event is sent when a new value of the virtual one pixel sensor was obtained.

In addition to the access abstraction, events provide through their pre-/post-processors value abstraction which enhances the portability of code between different platforms.

For instance, code can specify that a robot should move forward with one m/s and the operating system applies a hardware-specific value. This velocity could be applied to both a robot with a DC motor and one with a stepper motor.

By default and for convenience, HAL modules are initialised with default pre-/post-processors that abstracts the raw data. However, the user can change this at any time, allowing the user to define any level of abstraction or even no abstraction.


Example of how to port OpenSwarm to another platform

In this example, we will implement a module for a single motor first for the e-puck and then show how an equivalent module would be implemented on the Kilobot.

As a result, we will demonstrate

  • how modules are structured in OpenSwarm
  • how to design a module in OpenSwarm
  • how to port a module to a different robotic platform
  • how those two implementations differ regarding computational overhead

To design this module, we only need to consider the different types of propulsion. Both platforms can be modelled as a robot with a differential drive.

The e-puck posses two stepper motors, one for a left and the other for a right wheel. Even though the movement of the robot is enabled through vibration of its legs, the directions and speed can be controlled by two motors. These motors are DC motors.

Motor module

General module structure

In OpenSwarm, a module consists of at least two functions.

A function to initialise the module with default values: Sys_Init_<modulename>()

A function to start the module: Sys_Start_<modulename>()

Furthermore, if the module is a sensor or actuator, a pre-/post-processor needs to be defined with:

Sys_Set_PreProcessor_<modulename>() 
or Sys_Set_PostProcessor_<modulename>()

Module requirements

The requirements of the OpenSwarm motor module for each robot would be the following:

  • Receiving motor-events to set a speed value of a motor.
    • Register the motor-event.
    • Subscribe a motor-event handler.
  • Transform this speed value in a hardware-near value.
  • Apply the hardware-near value on the actuator.
    • Apply periodically new values on digital outputs (for stepper motor) or
    • Apply DAC\PWM output to power the DC motors.

e-puck implementation

First, the module needs to be initialised, which registers the motor-event, subscribes the motor-event handler, loads the default post-processor, and sets the needed pins to output.

e-puck implementation code
Copy code

#include "../events/events.h"
#include "../io/io.h"

/* ... */

/**************************************************************
*private Functions and Declarations
*************************************************************/

typedef struct sys_motors_s{
sint16 speed;/*!< speed of one motor*/
} sys_motor_v;

typedef int (*pMotorPostProcessor)(int);//creates a pointer-type to a motor post-processor

void Sys_LeftMotor_Controller(void); //applies periodically new values to the left stepper motors

bool Sys_LeftMotor_EventHandler(uint, uint, sys_event_data *); //to obtain the Left Motor Event

/**************************************************************
*Module-wide Variables
*************************************************************/

sys_motors_v left_motor;/*!< buffered wheel speed for the left motor*/

pMotorPostProcessor left_processor = 0;/*!< pointer to the left motor post-processor*/

/* ... */

// Sets the Post processor of the module
void Sys_Set_PostProcessor_LeftMotor(pMotorPostProcessor func){
left_processor = func;

}

void Sys_Init_LeftMotor(){

//set module wide variables to 0
left_motor.speed = 0;
left_processor = 0;

//Register an event
Sys_Register_Event(SYS_EVENT_IO_MOTOR_LEFT);

//Subscribe an handler to the event.
Sys_Subscribe_to_Event(SYS_EVENT_IO_MOTOR_LEFT, 0, Sys_LeftMotor_EventHandler, 0);

//set the pins to output and make sure there is no voltage applied
MOTOR1_PHA_DIR = OUTPUT_PIN;
MOTOR1_PHB_DIR = OUTPUT_PIN;
MOTOR1_PHC_DIR = OUTPUT_PIN;
MOTOR1_PHD_DIR = OUTPUT_PIN;
MOTOR1_PHA = 0;
MOTOR1_PHB = 0;
MOTOR1_PHC = 0;
MOTOR1_PHD = 0;

}

To enable the use of the motors, we have to implement the actual controller to apply changes of the speed value (stored in left_motor) to the hardware.

Because the e-puck uses stepper motors, we design the controller to be executed periodically.

e-puck implementation code
Copy code

inline void Sys_LeftMotor_SetPhase(uint8 phase);//applies the value to the hardware pins.

void Sys_LeftMotor_Controller(void){

static uint8 phase = 0; // phase can be 0 to 3
static sint16 next_phase = 1; //counter to wait until the next step has to be applied to the motors

if(left_motor.speed <= MIN_WHEEL_SPEED && left_motor.speed >= -MIN_WHEEL_SPEED){//if value is too small -> stop return;

}

if(left_motor.speed < 0){//orientation
if(--next_phase <= 0){//wait until the next position of the wheel
phase--;//turn to the next position
next_phase = (10*MAX_WHEEL_SPEED)/abs(left_motor.speed);//calculate how long it needs to be waited.

}

}else{

if(--next_phase <= 0){//wait to set the next position of the wheel
phase++;//turn to the next position
next_phase = (10*MAX_WHEEL_SPEED)/left_motor.speed;//calculate how long it needs to be waited.

}

}

phase %= 4;//make sure the phase is between [0, 3]
Sys_LeftMotor_SetPhase(phase);//apply the phase to the motors

}

inline void Sys_LeftMotor_SetPhase(uint8 phase){//apply the correct output values
switch (phase){
case 3:
{
MOTOR1_PHA = 0;
MOTOR1_PHB = 1;
MOTOR1_PHC = 1;
MOTOR1_PHD = 0;
break;
}
case 2:
{
MOTOR1_PHA = 1;
MOTOR1_PHB = 0;
MOTOR1_PHC = 1;
MOTOR1_PHD = 0;
break;
}
case 1:
{
MOTOR1_PHA = 1;
MOTOR1_PHB = 0;
MOTOR1_PHC = 0;
MOTOR1_PHD = 1;
break;
}
case 0:
{
MOTOR1_PHA = 0;
MOTOR1_PHB = 1;
MOTOR1_PHC = 0;
MOTOR1_PHD = 1;
break;
}
case -1:
default:
{
MOTOR1_PHA = 0;
MOTOR1_PHB = 0;
MOTOR1_PHC = 0;
MOTOR1_PHD = 0;
break;
}
}
}

To start the module and to apply the left_motor values, we have to guarantee that Sys_LeftMotor_Controller() is executed periodically.

This can be done by registering it to the IO Timer which is executed every 100 µs.

e-puck implementation code
Copy code

void Sys_Start_Motors(void){
//Make sure that the controller is executed periodically as an IO-Handler (every 100µs)
Sys_Register_IOHandler(&Sys_LeftMotor_Controller);
}

Now, the motor can be initialised and used, but the left_motor value would not be changed yet. To do so, the event handler and post-processor need to be implemented.

e-puck implementation code
Copy code

bool Sys_LeftMotor_EventHandler(uint pid, uint eventID, sys_event_data *data/*mm/s*/){
sint16 *speed = (sint16 *) data->value;//obtain the value from the event

if(left_processor == 0){//if there is no post-processor -> raw data must be sent via events
left_motor.speed = *speed; //store raw data
}else{//if there is a post-processor
left_motor.speed = left_processor(*speed);//transform platform-independent into platform-dependent value
}

return true;
}

With all the above functions, the module for the left motor is implemented.

This code is simplified to improve the readability. The real implementation in OpenSwarm, covers also errors in initialisation and power-saving mechanisms.

Kilobot implementation

To implement a similar behaviour on Kilobots, we can define the following:

Kilobot implementation code
Copy code

#include "../events/events.h"
#include "../io/io.h"

/* ... */

/**************************************************************
* private Functions and Declarations
*************************************************************/

typedef int (*pMotorPostProcessor)(int);//creates a pointer-type to a motor post-processor

bool Sys_LeftMotor_EventHandler(uint, uint, sys_event_data *); //to obtain the Left Motor Event

/**************************************************************
* Module-wide Variables
*************************************************************/

pMotorPostProcessor left_processor = 0;/*!< pointer to the left motor post-processor*/

/* ... */

// Sets the Post processor of the module
void Sys_Set_PostProcessor_LeftMotor(pMotorPostProcessor func){
left_processor = func;
}

void Sys_Init_LeftMotor(){

//set module wide variables to 0
left_processor = 0;

//Register an event
Sys_Register_Event(SYS_EVENT_IO_MOTOR_LEFT);

//Subscribe an handler to the event.
Sys_Subscribe_to_Event(SYS_EVENT_IO_MOTOR_LEFT, 0, Sys_LeftMotor_EventHandler, 0);

//set the pins to output and make sure there is no voltage applied
DDRD &= ~(1<<3);//set port to output
//enable PWM
TCCR2A = (1<<COM2A1) | (1<<WGM20);
TCCR2B = (1<<CS01);
//Set the motor to not moving
OCR2A = 0;

}

Because the motors are DC motors, there is no need to define a start function, because the used PWM function is executed embeddedly in the chip.

Consequently, a periodically called controller is not required either. Only a value has to be applied to the OCR2A register and the motors start turning. All this can be done in the event handler.

Kilobot implementation code
Copy code

void Sys_Start_Motors(void){
//No code needed here }

bool Sys_LeftMotor_EventHandler(uint pid, uint eventID, sys_event_data *data/*mm/s*/){
uint8 *speed = (uint8 *) data->value;//obtain the value from the event

if(left_processor == 0){//if there is no post-processor -> raw data must be sent
OCR2A = *speed; //apply raw data
}else{//if there is a post-processor
OCR2A = left_processor(*speed);//transform platform-independent into platform-dependent value and apply it
}

return true;
}

With all the above functions, the module for the left motor is implemented for the Kilobot.

This code assumes that OpenSwarm has been deployed on the Kilobot.

Implementation differences and overhead

As it can be seen in the e-puck and Kilobot implementation, the implementation and related overhead depends mainly on the hardware design of the robot. The overhead on the Kilobot is smaller in size and processing time, due to the use of a DC motor.

Besides the different overhead of each implementation, the same user source code (using the same events and values) could be used on both platforms. To guarantee the same behaviour on both robots, only post-processors have to be defined appropriately.


Robot experiments

Object clustering

A group of five robots use a three-state sensor (1px of the onboard camera) to cluster 20 objects that are initially dispersed in the environment.

Each robot can either detect an object, another robot, or nothing in its direct line of sight. (Gauci et al. 2014).

View the hardware-near trials 1-10 and the OpenSwarm trials 1-10 on this YouTube playlist.


Analysis

The analysis of the performance was conducted according to the metrics used in (Gauci et al. 2014).

The results of the hardware-near implementation and the implementation with OpenSwarm is shown in the following plots. Each colour corresponds to one of the ten trials, black corresponds to the average.

Hardware-near and openswarm implementation graphs

The mean values were compared and are plotted in the following two graphs.

The black line illustrates the mean values of the hardware-near implementation. The light grey dashed lines show the standard deviation of it. The red line illustrates the mean values of the OpenSwarm implementation. The light red dashed lines show the standard deviation of it. 

Cluster dynamics graph
Compactness graph

Project updates

Natural Robotics Lab: investigating robotic systems inspired by nature, and robotic models of natural systems.

A global reputation

Sheffield is a research university with a global reputation for excellence. We're a member of the Russell Group: one of the 24 leading UK universities for research and teaching.