embOS C++ wrapper

From SEGGER Wiki
Revision as of 17:58, 1 March 2023 by Til (talk | contribs) (Created page with "== Introduction == The embOS sources are not designed to be built with a C++ compiler but a C++ application can be used with embOS. To do so the embOS sources must be built...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Introduction

The embOS sources are not designed to be built with a C++ compiler but a C++ application can be used with embOS. To do so the embOS sources must be built with a C compiler and the application with a C++ compiler. You can also simply use an embOS library in your C++ project. The C++ application can call any embOS C API function. Some C++ developers might find it useful to have C++ classes for embOS objects like tasks, mutex, etc. This Wiki article describes an example implementation which is neither complete nor the only possible implementation. But it could serve as a starting point for an own application-specific C++ wrapper implementation.

Example implementation

The following code snippet creates a wrapper class for the embOS mutex.

namespace SEGGER {
  namespace OS {
    class Mutex : private OS_MUTEX {
    public:
      Mutex() {
        OS_MUTEX_Create(this);
      }

      ~Mutex() {
        OS_MUTEX_Delete(this);
      }

      void LockBlocked(void) {
        OS_MUTEX_LockBlocked(this);
      }

      void Unlock(void) {
        OS_MUTEX_Unlock(this);
      }
    };
  };
};

This class does not abstract all mutex API but just serves as an example. The SEGGER::OS::Mutex class members simply call the according mutex API function. Constructor and destructor can be used to create and delete the mutex.

The class SEGGER::OS::Mutex inherits the embOS struct OS_MUTEX. The this pointer points to the OS_MUTEX struct.

All OS_MUTEX member are private so that an application cannot access them by mistake like it could happen with a C application:

static OS_MUTEX mutex;

int main(void) {
  OS_Init();
  OS_MUTEX_Create(&Mutex);
  Mutex.UseCnt = 42;  // Application overwrites internal RTOS data by mistake
  OS_Start();
  return 0;
}

Used C++ features

Namespace

All embOS RTOS object classes are located in a separate namespace SEGGER::OS. This reduces the risk of a naming conflict with 3rd party software.

Compile time checks

The compiler checks if the application tries to access private members of the SEGGER::OS::Mutex class object.

static SEGGER::OS::Mutex m;

int main(void) {
  OS_Init();
  m.UseCnt = 42;  // Compiler error 
  OS_Start();
  return 0;
}

The compiler will generate an error message like: OS_MUTEX_STRUCT is a private member

Parameter overloading

embOS offers extended API functions with the 'Ex' suffix. With C++ we can simply have multiple definitions of one API function with parameter overloading. For example OS_TASK_CreateEx() expects compared to OS_TASK_Create() one additional parameter. The value of this parameter will be passed to the task body function. Two constructors could be implemented with a different parameter lists:

namespace SEGGER {
  namespace OS {
    class Task : OS_TASK {
    public:
      Task(const char* sName, OS_PRIO Priority, OS_ROUTINE_VOID* pfRoutine, void OS_STACKPTR *pStack, OS_UINT StackSize, OS_UINT TimeSlice) {
        OS_TASK_Create(this, sName, Priority, pfRoutine, pStack, StackSize, TimeSlice);
      }

      Task(const char* sName, OS_PRIO Priority, OS_ROUTINE_VOID_PTR* pfRoutine, void OS_STACKPTR *pStack, OS_UINT StackSize, OS_UINT TimeSlice, void* pContext)) {
        OS_TASK_CreateEx(this, sName, Priority, pfRoutine, pStack, StackSize, TimeSlice, pContext);
      }

      Task() = delete;

      ~Task() {
        OS_TASK_Terminate(this);
      }

      void Delay(OS_TIME t) {
        OS_TASK_Delay(t);
      }

      void Terminate(void) {
        OS_TASK_Terminate(this);
      }
    };  
  };
};

With it the application can create tasks with different number of parameters:

using namespace SEGGER::OS;

static OS_STACKPTR int StackHP[128], StackLP[128]; 
static Task* TaskHP;
static Task* TaskLP;

int main(void) {
  OS_Init();
  TaskHP = new Task("HP Task", 100u, HPTask, StackHP, sizeof(StackHP), 2);
  TaskLP = new Task("LP Task",  50u, LPTask, StackLP, sizeof(StackLP), 2, (void*)1);
  OS_Start();
  return 0;
}

There could be more task create constructors which allows to omit specific parameters like e.g. the time slice for round-robin scheduling or the stack location. The task constructor could then allocate the stack memory on the heap:

Task(const char* sName, OS_PRIO Priority, OS_ROUTINE_VOID* pfRoutine, OS_UINT StackSize) {
  int* pStack = (int*)malloc(StackSize);
  if (pStack != NULL) {
    OS_TASK_Create(this, sName, Priority, pfRoutine, pStack, StackSize, 2);
  }
}

This simplifies the task creation in the application:

using namespace SEGGER::OS;

static Task* TaskHP;

int main(void) {
  OS_Init();
  TaskHP = new Task("HP Task", 100u, HPTask, 512);
  OS_Start();
  return 0;
}

RTOS object creation

Tasks created with the new operator have the task object located on the heap. It is also possible to define and create tasks statically. No task creation API function must be called at all, the task can be defined and created as:

using namespace SEGGER::OS;

static Task TaskHP("HP Task", 100u, HPTask, StackHP, sizeof(StackHP), 2);
static Task TaskLP("LP Task",  50u, LPTask, StackLP, sizeof(StackLP), 2, (void*)42);

int main(void) {
  OS_Init();
  OS_Start();
  return 0;
}

These tasks will be created before main() and therefore before OS_Init(). But this is not allowed with embOS. It is valid to call embOS API after OS_Init() only. Most toolchains provide a solution for this issue. The toolchain prevents that the runtime initialization calls the constructors and the application must perform this after OS_Init(). Please have a look in the Wiki article Using embOS API in C++ constructor of global objects for more details.

Memory allocation

If objects are created with the new operator the according memory is allocated from the heap. If heap allocation should be avoided the new operator could be overwritten and a static memory management like the embOS memory pool could be used.