Programming the core system

The following chapter describes how to program a PXROS-HR application. For this purpose, the basic concepts and terms, as well as 'best practice' procedures are explained and illustrated by examples.

Basic concepts

Event:

An Event is represented by a 32 bit wide mask. Thus, several Events to be sent or to be waited for can be stored in a four bytes bitmask, i.e. a long value.

Priority:

The priority of a Task can have a value between 0 and 31, wherein a lower value means a higher priority.

Ticks:

The hardware-independent time basis of a PXROS-HR system; the attribution of ticks to milliseconds can be defined by the user.

Handles:

PXROS-HR objects cannot be accessed directly; to use PXROS-HR objects, Handles are utilised, which represent objects at the interface (see Section Basics).

The first simple application

The simple application created below does not contain any functionality, yet. It implements an executable basic PXROS-HR system consisting of the following basic elements: initialisation of PXROS-HR, the Inittask, and an empty Task.

Initialising PXROS-HR

Before PXROS-HR system calls can be used, PXROS-HR has to be initialised. This is done via the function PxInit. This function receives a parameter of the PxInitSpec_T type.

PXROS specification

The PXROS-HR initialisation structure PxInitSpec_T is defined as follows:

PxMcType_t is_sysmc_type:

the type of system memory class; permitted values are PXMcVarsized, PXMcVarsizedAdjusted and PXMcVarsizedAligned

PxSize_t is_sysmc_size:

only relevant for memory classes of type PXMcVarsizedAligned and PXMcVarsizedAdjusted
It defines the alignment or adjustment of allocated blocks

PxAligned_t *is_sysmc_blk:

pointer to the memory assigned to the system memory class

PxSize_t is_sysmc_blksize:

overall size of the memory block

PxMcType_t is_objmc_type:

this parameter is presently not in use

PxSize_t is_objmc_size:

this parameter is presently not in use

PxAligned_t *is_objmc_blk:

pointer to the memory of generic PXROS-HR objects

PxSize_t is_objmc_blksize:

overall size of the memory of generic PXROS-HR objects
The size must be at least (number of objects) * (PXOBJ_SIZE + namelength)

PxUInt_t is_obj_number:

number of PXROS objects

PxObjId_t is_obj_namelength:

maximum length of the object names, including zero fillers

PxTaskSpec_ct is_inittask:

Task specification of the Inittask, see Section The Inittask

PxMcType_t is_taskmc_type:

type of memory class of tasks; permitted values are PXMcVarsized, PXMcVarsizedAdjusted and PXMcVarsizedAligned
This memory class will be assigned as PXMcTaskdefault to the first task.

PxSize_t is_taskmc_size:

only relevant for memory classes of type PXMcVarsizedAligned and PXMcVarsizedAdjusted
It defines the alignment or adjustment of allocated blocks

PxAligned_t PXDptr_q *is_taskmc_blk:

pointer to the memory for this class

PxSize_t is_taskmc_blksize:

overall size of the memory block

const unsigned int is_core_start

the start address of this core
Before activating the core, the program counter (PC) of the core will be initialized with this address.

PxProtectRegion_T *is_sys_code:

points to the specification of up to 4 (TC2x) or 5 (TC3x) protected code areas of the kernel

PxProtectRegion_T *is_sys_data:

points to the specification of up to 8 (TC2x) or 9 (TC3x) protected data areas of the kernel

PxProtectRegion_T *is_task_code:

points to the specification of up to 4 (TC2x) or 5 (TC3x) protected code areas of the tasks

The code protection areas is_sys_code and is_task_code must cover all memory areas which contain code executable in kernel/supervisor mode or by tasks.

The data protection areas is_sys_data must cover all memory areas which must be accessible in kernel/supervisor mode.

A sample initialisation of the protection areas:

const PxCodeProtectSet_T _cpu0_sys_code_protection =
{
    /* Range 0 the complete text section */
    .cpr[0].s = {(PxUInt_t)__TEXT_BEGIN,
                 (PxUInt_t)__TEXT_END,},
    /* Range 1 the traptab section */
    .cpr[1].s = {(PxUInt_t)__TRAP_TAB_BEGIN,
                 (PxUInt_t)__TRAP_TAB_END,},
    /* Range 2 the inttab section */
    .cpr[2].s = {(PxUInt_t)__INT_TAB_BEGIN,
                 (PxUInt_t)__INT_TAB_END,},
    .cpmr.cpxe.bits = {
                        .dp0 = 1,   /* the CPXE 0..2 executable */
                        .dp1 = 1,
                        .dp2 = 1
                      }
};

const PxCodeProtectSet_T _cpu0_task_code_protection =
{
    /* Range 0 the complete text section */
    .cpr[0].s = {(PxUInt_t)__TEXT_BEGIN,
                 (PxUInt_t)__TEXT_END,},
    .cpmr.cpxe.bits = {
                        .dp0 = 1,   /* the CPXE 0 executable */
                      }
};

const PxDataProtectSetInit_T _cpu0_sys_data_protection =
{
    /* Range 0: read only data */
    .dpr[0].s = {(PxUInt_t)PxTricSystemRodataLowerBound,
                 (PxUInt_t)PxTricSystemRodataUpperBound,},
    /* Range 1: the CSA area of CPU0 */
    .dpr[1].s = {(PxUInt_t)__CSA_BEGIN_CPU0_,
                 (PxUInt_t)__CSA_END_CPU0_,},
    /* Range 2: the KERNEL data area of CPU0 including objects and stack */
    .dpr[2].s = {(PxUInt_t)PxTricSystemDataLowerBound_CPU0_,
                 (PxUInt_t)PxTricSystemDataUpperBound_CPU0_},
    /* Range 3: System/Kernel stack of CPU0 */
    .dpr[3].s = {(PxUInt_t)PXROS_SYSTEM_STACK_BEGIN_CPU0_,
                 (PxUInt_t)PXROS_SYSTEM_STACK_CPU0_,},
    /* Range 4: the SFR area */
    .dpr[4].s = {(PxUInt_t)PERIPHERAL_MEM_BASE,
                 (PxUInt_t)PERIPHERAL_MEM_END,},
    /* Range 5:  some area */
    .dpr[5].s = {0, 0},
    /* Range 6: the other area */
    .dpr[6].s = {0,0},
    /* Range 7: used dynamically by the kernel (TC2x)
                used to get access to memory before PXROS is started
                - especially used for copy and clear functions
                For TC3x this will be range 8 and range 7 can be used otherwise
    */
    .dpr[7].s = {0,0},
    /* the DPRE 0..4,7 readable by the kernel */
    .dpmr.kernel.dpre.bits = {
                        .dp0 = 1, .dp1 = 1, .dp2 = 1, .dp3 = 1,
                        .dp4 = 1, .dp5 = 0, .dp6 = 0, .dp7 = 1
                      },
    /* the DPWE 1..4,7 writable by the kernel */
    .dpmr.kernel.dpwe.bits = {
                        .dp0 = 0, .dp1 = 1, .dp2 = 1, .dp3 = 1,
                        .dp4 = 1, .dp5 = 0, .dp6 = 0, .dp7 = 1
                      },

    /* the DPRE 0,3,4 readable by the system (handlers running in supervisor mode) */
    .dpmr.system.dpre.bits = {
                        .dp0 = 1, .dp1 = 0, .dp2 = 0, .dp3 = 1,
                        .dp4 = 1, .dp5 = 0, .dp6 = 0, .dp7 = 0
                      },
    /* the DPWE 3,4 writable by the system (handlers running in supervisor mode) */
    .dpmr.system.dpwe.bits = {
                        .dp0 = 0, .dp1 = 0, .dp2 = 0, .dp3 = 1,
                        .dp4 = 1, .dp5 = 0, .dp6 = 0, .dp7 = 0
                      },
};

The operating system must have at least access to the CSA area, its local data, the objects, the supervisor stack and the peripheral space.

Init function

Calling PxInit starts the Inittask and only returns if an error occurred during initialisation. In this case, the function returns a corresponding error code:

PXERR_INIT_ILLALIGN

Erroneous alignment of system memory class

PXERR_INIT_ILLMCTYPE

System memory class is not of type PXMcVarsized or PXMcVarsizedAdjusted

PXERR_INIT_NOMEM

Not enough memory available

PXERR_PROT_ILL_REGION

Invalid memory protection areas defined

PXERR_INIT_SCHEDULE_FAILED

the scheduling of the init task failed

PXERR_MC_ILLSIZE

size for PXMcSystemdefault is too small

PXERR_INIT_NOMEM

not enough memory for initialization

PXERR_OBJECT_SHORTAGE

not enough objects given in initstruct

PXERR_GLOBAL_ILLEGAL_CORE

number of cores not supported

PXERR_ILL_NULLPOINTER_PARAMETER

invalid system stack specification

PXERR_PROT_PERMISSION

memory protection unit cannot be activated

PXERR_ILLEGAL_ACCESS

incorrect access permission for _initspecs elements

PXERR_GLOBAL_OBJLIST_INCONSISTENCY

inconsistency between global and local init

After a successful PxInit call, the PXROS-HR system services are available.

The Inittask

The Inittask is the first Task of the system. It is automatically started after system initialization and corresponds in layout and handling to a normal Task (see Section Layout and initialisation of a Task). The differences to other tasks are:

  • it is created and started by PxInit

  • it can only use resources available after PxInit

The Inittask can be used for initialising the application and for creating and starting the application Tasks. Typically, the priority of the Inittask is set to lowest priority after the initialization phase, i.e. it is only activated if no other Task or Handler is active (Idletask).

The existence of an Idletask is not compulsory. The Inittask could also be terminated after execution, yet it could also be used to do real work, e.g doing background calculations, system controlling etc.

Sample code

The initialization of the PXROS-HR system for only one core is exemplified by the below code:

// for more information on this structure, see chapter
// "Layout and initialisation of a Task"
static const PxTaskSpec_T InitTaskSpec =
{
        .ts_name                     = "InitTask",
        .ts_fun                      = InitTask_Func,
        .ts_mc                       = PXMcTaskdefault,
        .ts_opool                    = PXOpoolSystemdefault,
        .ts_prio                     = 0,
        .ts_privileges               = PXUser1Privilege,
        .ts_context                  = &InitTaskContext,
        .ts_taskstack.stk_type       = PXStackAlloc,
        .ts_taskstack.stk_size       = INITTASK_STACKSIZE,
        .ts_taskstack.stk_src.mc     = PXMcTaskdefault,
        .ts_inttaskstack.stk_type    = PXStackDontCheck,
        .ts_inttaskstack.stk_size    = 0,
        .ts_inttaskstack.stk_src.stk = 0,
        .ts_abortstacksize           = 0
};

const PxInitSpec_T InitSpec_CORE0 =
    {
        .is_sysmc_type      = PXMcVarsizedAligned,
        .is_sysmc_size      = 8,
        .is_sysmc_blk       = Sysmem0,
        .is_sysmc_blksize   = SYSMEMSIZE_CORE0,

        .is_obj_number      = NUM_OF_PXOBJS_CORE0,
        .is_obj_namelength  = PXROS_NAMESIZE,
        .is_inittask        = &InitTaskSpec,

        .is_objmc_type      = PXMcVarsizedAligned,
        .is_objlmc_size     = 8,
        .is_objmc_blk       = PxObjmem_CPU0_,
        .is_objmc_blksize   = (PxSize_t)PX_OBJMEMSIZE_CPU0_,

        .is_taskmc_type     = PXMcVarsizedAdjusted,
        .is_taskmc_size     = 8,
        .is_taskmc_blk      = Taskmem_Core0,
        .is_taskmc_blksize  = TASKMEMSIZE_CORE0,

        .is_core_start      = (unsigned int)_start,

         /* the system stack */
        .is_system_stack = PXROS_SYSTEM_STACK_BEGIN_CPU0_,
        .is_system_stack_size = (PxUInt_t)PXROS_SYSTEM_STACK_SIZE_CPU0_,

        /* all objects are global accessible */
        .is_global_obj_number = 0,

        /* the protection definition */
        .is_sys_code = &_cpu0_sys_code_protection,
        .is_sys_data = &_cpu0_sys_data_protection,
        .is_task_code = &_cpu0_task_code_protection,
    };

// Since a memory class for tasks is defined
// the inittask gets PXMcTaskdefault as the memory class

static const PxInitSpecsArray_t InitSpecsArray[] =
{
    &InitSpec_CORE0,
    0,
    ...
};


PxError_t error;

// the initialisation of PXROS-HR; if PxInit returns,
// the corresponding error code is logged in error

error = PxInit(InitSpecsArray, 1);

Startup-initialisation

Before the initialisation of PXROS-HR, possible hardware configurations might be executed. Since this usually has to be done with supervisor privileges, an application cannot carry out these procedures since it runs in user mode. This problem can be circumvented if the application passes arbitrary functions, which can be used to configure and initialize the hardware.

_PxInitcall(function, params…​) adds the function function with its parameters params to an internal table; params is an arbitrary list of parameters.

The user can start the execution of these functions by calling PxInitializeBeforePxInit(). The functions are executed with system permission and in the order, in which they were entered in the table.

The function PxInitializeBeforePxInit has to be called before the initialization of PXROS-HR, i.e. before PxInit!

Layout and initialisation of a Task

General

Essentially, a PXROS-HR Task consists of a Task function, which is called when the Task is started. After its creation a Task is started by sending an activation event, i.e. the Task function is called, provided one or more activation events were defined. If no activation event was defined when the Task was created, the Task is immediately started. Normally, this Task function does never return, i.e. it contains an infinite loop.

In exceptional cases a Task can be terminated by calling the system service PxDie. This function does not return. To be able to use PxDie, a Service Task has to be initialised beforehand.

The initialization of the Task is carried out during creation via the function PxTaskCreate. The necessary parameters for initializing the Task are passed to this function:

opool

the object pool, from which the Task object is taken

taskspec

the Task specification, which contains the necessary data for initialization (see Section Task specification)

prio

the priority of the Task

actevents

Events activating the Task; usually only one

Task specification

Task specification is a structure of the type PxTaskSpec_T (the type designators PxTaskSpec_t and PxTaskSpec_ct mentioned in the reference manual relate to pointers or constant pointers to PxTaskSpec_T). This structure is defined as follows:

PxUChar_t *ts_name:

name of the Task; a string

void (*ts_fun)(PxTask_t task, PxMbx_t mbx, PxEvents_t events):

pointer to the Task function

PxMc_t ts_mc:

Default memory class of the Task

PxOpool_t ts_opool:

Default objectpool of the Task

PxStackSpec_T ts_taskstack:

Stack specification of the Task (see stack specification in this section)

PxStackSpec_T ts_inttaskstack:

Interrupt stack specification of the Task (see stack specification in this section)

PxUChar_t ts_prio:

Initial priority of the Task; this parameter is kept only for compatibility; it is ignored

PxEvents_t ts_actevents:

Activation events for the Task, this parameter is kept only for compatibility; it is ignored

PxTicks_t ts_timeslices:

size of the time slices for this Task; if this value is set to 0, no time slices are used

PxSize_t ts_abortstacksize:

size of the abort stacks, stated by the number of frames

PxTaskSchedExt_t ts_sched_extension:

not implemented

PxArg_t ts_sched_initarg:

not implemented

PxArg_t ts_privileges:

Privileges of the Task

PxUInt_t ts_accessrights:

Access rights of the Task

PxTaskContext_ct ts_context:

Description of the address space of the Task (see task context specification in this section)

PxProtectRegion_ct ts_protect_region:

extended memory areas of the Task

Notes:

  • A default memory class can be assigned via the macro PXMcTaskdefault, i.e inherit the memory class from the creator.

  • The default object pool of the Task can be assigned via the macro PXOpoolTaskdefault, i.e inherit the object pool from the creator.

  • Signalising an activation event starts the Task, i.e. after its creation the Task waits until it receives an activation event; usually only one activation event is stated

  • The size of the Abort stacks determines the maximum nesting depth of PxExpectAbort calls (see Section The abort mechanism)

  • ts_protect_region defines additional memory protection areas of the Task. It is interpreted by PXROS-HR as a pointer to an arbitrarily large array terminated by an element whose lower bound address is set to 0.
    The specified memory areas serve as an extension to the static memory context of the Task and are applied to the MPU dynamically. However, using additional memory areas for heavily accessed data should be avoided due to the runtime overhead (granting permissions for a currently non-active area always requires a protection trap resolution). Common use cases for this mechanism are for instance register accesses and accessing configuration data.

The type PxStackSpec_T is used for describing Task and interrupt stacks. It contains information on the stack type (ascending/descending), stack size, and from where the memory is taken (for details, see the PXROS-HR reference manual).

The PxTaskContext_t structure describes the memory context of the Task. It contains two protection regions (see below, PxProtectRegion_t) which can be inherited from the creator or defined individually for each task. If 0 is assigned as the lower bound address, the area will be inherited.

The structure PxProtectRegion_t defines protected memory areas, which can be made available to the Task. It contains the upper and lower boundary of the memory area (the first valid and the first invalid address), as well as the access permission: no access, read only, write only, read and write.

Sample code for creating and initialising the Task specification:

// Definition of the memory context of the Task; since the elements of
// protection[0] are set to 0, the memory area is inherited
// from the creator of the Task

static const PxTaskContext_T Task1_Context = {

        // data area, inherited from the creator of the Task
        .protection[0].lowerBound = 0,
        .protection[0].upperBound = 0,
        .protection[0].prot       = NoAccessProtection,

        // read-write data area
        .protection[1].lowerBound = (PxUInt_t)Task1_data_base,
        .protection[1].upperBound = (PxUInt_t)Task1_data_end,
        .protection[1].prot       = WRProtection
};

#define TASK1_STACKSIZE     100         // stacksize in 4-byte units

// Creating the Task specification
const PxTaskSpec_T Task1_Spec = {
        // Name of the Task
        .ts_name                        = "Task1",

        // Task function (see above)
        .ts_fun                         = Task1_Func,

        // Memory block of the Task
        .ts_mc                          = PXMcTaskdefault,

        // Objectpool of the Task
        .ts_opool                       = PXOpoolTaskdefault,

        // Start priority of the Task, this Parameter exists only
        // for compatibility; it is ignored
        .ts_prio                        = 0,

        // Privileges of the Task
        .ts_privileges                  = PXUser0Privilege,

        // A pointer to the memory context of the Task
        .ts_context                     = &Task1_Context,

        // Stack type, will be allocated
        .ts_taskstack.stk_type          = PXStackAlloc,

        // Stack size in 4-byte units to be checked;
        .ts_taskstack.stk_size          = TASK1_STACK_SIZE,

        // Stack will be allocated from PXMcTaskdefault
        .ts_taskstack.stk_src.mc       = PXMcTaskdefault,

        // Abort stack size; is ignored in this case
        .ts_abortstacksize              = 0
};

The Task is created as follows:

// Creates a Task with the predefined specification,
// priority TASK1_PRIO and the activation events TASK1_ACTIVATION_EVENTS.
// The return value is the ID of the created Task
Task1_Id = PxTaskCreate(opool, &Task1_Spec, TASK1_PRIO,  TASK1_ACTIVATION_EVENTS);

The Task function

A Task function has to conform to the following prototype:

void Task_Func(PxTask_t myID, PxMbx_t myMailbox, PxEvents_t myActivationEvents)

These parameters are passed from the system as soon as the function is called, i.e. if the corresponding Task is activated for the first time. The parameters have the following meaning:

myID

ID of the Task

myMailbox

Private mailbox of the Task

myActivationEvents

Mask of the received activation events

If activation events were defined when creating the Task (see Section Task specification), the Task will only become active if one or more of these events are received.

The Task function can contain almost any code, yet it may not be terminated. This means, it must either contain an infinite loop or be terminated via a blocking system call (e.g. PxAwaitEvents(0) or PxDie()).

A typical Task function consists of a declaration and initialization section and an infinite loop. In this loop the function waits for events and/or messages. If an Event or a message is received, the Task becomes ready for execution. If it has the highest priority of all Tasks in ready state, it is executed.

Example for a Task function:

void InitTask_Func(PxTask_t myID,
                   PxMbx_t myMailbox,
                   PxEvents_t myActivationEvents)
{
  // Declarations and initialisations
    int foo = 0;
    int bar;

    if(myActivationEvents == EV_ACT_EV1)
    {
        bar = 1;
    }
    else
    {
        bar = 42;
    }

    // Main loop
    while(1)
    {
      // Parallel waiting for events and messages
        PxMsgEvent_t msgev;
        msgev = PxMsgReceive_EvWait(myMailbox,EV_EVENT_MASK);
        if(PxMsgIdIsValid(msgev.msg))
        {
          // message received
            do_sth_with_msg(msgev.msg);
        }

        if(msgev.events != 0)
        {
          // event received
            do_sth_with_events(msgev.events);
        }
    }
}

Summary

Summary of the Initialisation of a PXROS-HR system:

Initphase en neu
Fig. 1. Initialisation

A complete module for implementing the initialisation of PXROS-HR and for creating a Task can be found in Example code appendix.

Utilising system services

Basics

In principle, every PXROS-HR system service returns a value that should be evaluated. This value is either an object handle or a numeric type. A numeric value might be an error code, an event bit mask or a priority.

Handles are interface representations of PXROS-HR objects. If objects are requested from the system or passed on to system services, this is done via Handles.

If a service returns an error code, this code should be regarded in any case. If a service returns an object handle, the user should always check if the Handle is valid or if an error has occurred. PxMsgIdIsValid(handle) can be used for checking if a requested message object is valid.

Blocking and non-blocking system services

PXROS-HR system services can be blocking or non-blocking. Non-blocking system services return immediately after having been called, while blocking services can set a Task to the waiting state, which can cause scheduling. There are three reasons why a system call can be blocking:

  1. Waiting for an event: the function returns as soon as it receives an event

  2. Waiting at a mailbox: the function returns as soon as the mailbox contains message objects

  3. Waiting at an object pool: if an object (e.g. a message) is requested from an object pool, a system service can block if the object pool is empty; it will then only return if objects are available again

System Calls in Tasks and Handlers

Due to the execution state of the PXROS-HR system the service calls are devided into two classes. Handlers as well as functions called via _PxHndcall (see _PxHndcall) may only call system functions with the suffix _Hnd. Tasks, in contrast, may only call functions, which do not have the _Hnd suffix. If a call occurs at the wrong place, an error message will be returned: PXERR_HND_ILLCALL in case a Handler service was called in a Task, or PXERR_TASK_ILLCALL in the opposite case.

Object management

Object pools

PXROS-HR objects can, whenever they become necessary, be requested from an object pool.

A standard object pool is passed to a Task during its creation, yet the Task can also access other object pools if they are made available and if the Task has an access right to such resources (see Permissions).

An object pool can be used by several Tasks; thus a flexible distribution of objects can be achieved. The disadvantage of this method is that in the event of an object shortage, all the Tasks that have access to this object pool might be affected.

A typical application for object pools could be that Tasks, which are important for the system, have their own object pools assigned, while less important Tasks share a common object pool.

Requesting PXROS-Objects

PXROS-HR objects can be requested as specialized objects via functions. E.g. PxPeRequest will request an object of type PxPe_t.

Requesting objects is only possible within a Task; within a Handler this is not permitted (see Section System Calls in Tasks and Handlers).

Memory management

Memory management under PXROS-HR is based on memory classes. A memory class is an object of the type PxMc_t. A memory class can manage memory in blocks of fixed or variable size. Fixed block size allows a quicker access to memory blocks, while variable block size makes it possible to access memory blocks in exactly the size in which they are needed, thus making memory utilisation more efficient. Memory classes are not usually used for requesting memory explicitly, but mainly for making memory available to message objects. By default, a Task has two memory classes at its disposal: PXMcSystemdefault, the standard memory class of the system, and PXMcTaskdefault, the standard memory class of the Task, which was assigned to it during creation. To use PXMcSystemdefault the Task must have the appropriate rights (see Permissions).

Communication

Communication and synchronisation of different Tasks is achieved via Events and messages. Messages are objects containing data, while Events are flags signalling events.

Events

An Event is a flag signalling a certain event. A Task can wait for certain Events by the function PxAwaitEvents(PXEvents_t events). The parameter events is a bitmask stating for which Events the Task shall wait. PxAwaitEvents blocks until one or more of the events defined in events occur. If events is set to zero, PxAwaitEvents will not return.

...

#define EV_EV1    1
#define EV_EV2    (1<<1)
#define EV_EV3    (1<<2)

...

PXEvents_t ret_ev;

//  wait for Event EV_EV1, EV_EV2 or EV_EV3
ret_ev = PxAwaitEvents(EV_EV1|EV_EV2|EV_EV3);

// check, which event has arrived
if( ret_ev & EV_EV1)
{
  ...
}
if( ret_ev & EV_EV2)
{
  ...
}
if( ret_ev & EV_EV3)
{
  ...
}

...

PxGetSavedEvents returns the Events received but not yet processed by the Task. This function returns immediately.

The non-blocking resetting of the Events of a Task is carried out by PxResetEvents(PXEvents_t events). The parameter events states, which Events are to be reset. This function returns a bitmask stating, which Events were reset.

...

PXEvents_t ret_ev;

// get all received Events
ret_ev = PxGetSavedEvents();

...

// all Events of ret_ev are reset
ret_ev = PxResetEvents(ret_ev);

...

Messages

Messages are PXROS-HR objects containing a data buffer and are used for data interchange between Tasks. A Task creating a message object is called the Owner of the message; the Task currently having access to a message object is called the User of the message.

Requesting and releasing message objects

Message objects can be requested in two different ways: They can either be created by PxMsgRequest or by PxMsgEnvelop. The difference between these two methods is that with PxMsgRequest the data memory of the message is taken from a memory class, while with the envelope mechanism the memory has to be provided by the requesting Task.

Message objects can be requested by Tasks but not by Handlers.

There are several ways of requesting message objects via PxMsgRequest:

PxMsg_t PxMsgRequest(PxSize_t msgsize, PxMc_t mcId, PxOpool_t opoolId)

This function returns a message object taken from the object pool opoolId. It contains a data buffer of the size msgsize. The data buffer is taken from the memory class mcId. Should the object pool be empty, this function will block until objects are available again.

PxMsg_t PxMsgRequest_NoWait(PxSize_t msgsize, PxMc_t mcId, PxOpool_t opoolId)

This function returns a message object taken from the object pool opoolId. It contains a data buffer of the size msgsize. The data buffer is taken from the memory class mcId. Should the object pool be empty, the returned Handle is invalid. This function returns immediately.

PxMsgEvent_t PxMsgRequest_EvWait(PxSize_t msgsize, PxMc_t mcId, PxOpool_t opoolId, PxEvents_t events)

This function returns a message object taken from the object pool opoolId. It contains a data buffer of the size msgsize. The data buffer is taken from the memory class mcId. Should the object pool be empty, this function will block until objects are available again or until one of the Events stated in events occurs.

If the envelop mechanism is to be used, one of the following functions can be executed:

PxMsg_t PxMsgEnvelop(PxMsgData_t data_area, PxSize_t msgsize, PxOpool_t opoolid)

This function packs the buffer data_area from the memory area of the Task into a message object taken from the object pool opoolid and returns this package. Should the object pool be empty, this function will block until objects are available again.

PxMsg_t PxMsgEnvelop_NoWait(PxMsgData_t data_area, PxSize_t msgsize, PxOpool_t opoolid)

This function packs the buffer data_area from the memory area of the Task into a message object taken from the object pool opoolid and returns this package. Should the object pool be empty, this function will return an invalid Handle.

PxMsgEvent_t PxMsgEnvelop_EvWait(PxMsgData_t data_area, PxSize_t msgsize, PxOpool_t opoolid, PxEvents_t events)

This function packs the buffer data_area from the memory area of the Task into a message object taken from the object pool opoolid and returns this package. Should the object pool be empty, this function will block until objects are available again or until one of the Events specified in events occurs.

If the envelop mechanism is used, the data area of the message is not taken from a memory class; instead the creating Task has to provide a buffer from its own memory area.

A message can be released by the function PxMsgRelease or PxMsgRelease_Hnd. If a message object is released, the Task or Handler abandons its access right to the data area of the message. By default the message object is stored in its original object pool, and its data buffer is returned to the memory class where it was taken from or, if the message was created via PxMsgEnvelop…​, data buffer is reallocated to the memory area of the original Owner of the message.

To be precise, the memory buffer packed via PxMsgEnvelop is not really taken from the memory area of the Task; the Task could, theoretically, still access this area. The buffer should, however, be treated as if it were extracted from the address space of the Task.

The behaviour of PxMsgRelease…​ can vary:

  • If a message is fitted with a Release mailbox (via the function PxMsgInstallRelmbx(PxMsg_t msgid, PxMbx_t mbxid)), the behaviour of PxMsgRelease…​ changes: The corresponding message object is not released but stored in the mentioned Release mailbox instead (see Section Mailboxes).

  • After a message was sent, the owner of the message can wait for the release via the function PxMsgAwaitRel(PxMsg_t msg). To do so, PxMsgSetToAwaitRel(PxMsg_t msg) has to be called before sending. Then, if the recipient of the message calls PxMsgRelease, the message object is not released but returned to the sender instead.

If a message was marked via PxMsgSetToAwaitRel or requested by PxMsgEnvelop, the function PxMsgAwaitRel(PxMsg_t msg) has to be used to wait for the message release.

Mailboxes

Mailboxes are a central component of the communication mechanism. A message is not sent directly to the recipient Task, but to a mailbox, from which the recipient can read it. Mailboxes are organised according to the FIFO principle (first in, first out). A mailbox has unlimited capacity; an arbitrary number of messages can stored within it.

Apart from message interchange between Tasks, mailboxes can also serve as message pools or release mailboxes.

Message pools

are mailboxes containing prefabricated message objects, which can be extracted and reused by a Task (see also in Messagepools). In comparison to the requesting of message objects, this has several advantages:

  • it is quicker since the message object already exists instead of having to be requested from the system first

  • message objects are available even if no PXROS-HR objects are available and a request for an object from the object pool would fail

  • the consumption of PXROS-HR objects is restricted, i.e. any object shortage caused by excessive use of message objects is of local relevance only

The disadvantages of using message pools:

  • the number of stored message objects may exceed the number of required ones

  • the size of the data areas of the message objects must be large enough for all use cases

Release Mailboxes

are mailboxes, where message objects are stored after release by the user, instead of being released. To utilise a release mailbox, the mailbox has to be assigned to the message via PxMsgInstallRelmbx.

To fulfill their purpose, message pools have to be specified as release mailboxes for the contained message objects, so that they can return to the pool after release by the message recipient.

The following code exemplifies how a message pool can be created and used:

...

  PxMbx_t mpool;
  PxMsg_t msg;

  msgpool = PxMbxRequest(my_opool); // request mailbox object

  if(PxMbxIdIsValid(msgpool))       // check, if object valid
  {

    PxMsg_t tmpmsg;
    for(i=0;i<10;i++)               // request 10 messages
    {
      tmpmsg = PxMsgRequest(MSG_SIZE, my_memclass,my_opool);
      if(PxMsgIdIsValid(tmpmsg))
      {
        PxMsgInstallRelmbx(tmpmsg,msgpoool); // set release mailbox
        tmpmsg = PxMsgSend(tmpmsg,msgpool)   // send message to message pool
        if(PxMsgIdIsValid(tmpmsg))
                do_error_handling();
      }
      else
      {
        do_error_handling();
      }
    }
  }
  else
  {
    do_error_handling();
  }

 ...

 msg = PxMsgReceive(msgpool); // a message is requested from the
                              // message pool

 fill_msg_data();

 PxMsgSend(msg,targetmbx)    // the message is sent to the mailbox
                             // targetmbx
 ...

Sending and receiving messages

Sending and receiving messages is always done via mailboxes. The sender sends a message to a mailbox, and the recipient receives the message from this mailbox.

A message is sent via the function PxMsgSend, the necessary parameters are the message object to be sent and the mailbox to which the message is to be sent. After sending, the sender does no longer have access to the message object, meaning the Handle of the message is invalid after sending. In addition, the sender passes the exclusive access right to the data area of the message to the recipient, i.e. a pointer to this area is invalid.

A message object and the corresponding data area belong exclusively to one user, i.e. they are allocated to one Task.

A Task can receive a message by taking it from a mailbox. PxMsgReceive(PxMbx_t mbx)` returns the next available message in accordance with the FIFO principle; prioritised messages are an exception to this rule. A prioritised message is sent to the mailbox in question via the function PxMsgSend_Prio. A prioritised message 'overtakes' the normal messages in the mailbox.

Messages can also be received in a non-blocking way: PxMsgReceive_NoWait(PxMbx_t mbx) returns immediately, either with a message object or with an invalid Handle if the mailbox is empty.

PxMsgReceive_EvWait(PxMbx_t mbx, PxEvents_t ev) makes it possible to wait for messages and Events at the same time. The function returns an object of the type PxMsgEvent_t. The following check helps find out whether a message or an Event (or both) was returned:

...

PxMsgEvent_t retval = PxMsgReceive_EvWait(mbx, EV_MASK);
if(PxMsgIdIsValid(retval.msg))
{
  // a message was received
  do_sth();
}

if(retval.events != 0)
{
  // one or more Events were received

  if(retval.event & EV1)
  {
    ...
  }

  if(retval.event & EV2)
  {
    ...
  }

  ...
}

Accessing the content of a message

A Task can access the content of a message as soon as the message was requested from the object pool or taken from a mailbox. The function PxMsgGetData(PxMsg_t msg) returns a pointer to the data area of the message object msg, PxMsgGetBuffersize(PxMsg_t msg) returns the size of the data buffer and PxMsgGetSize(PxMsg_t msg) returns the size of the data within the buffer. If handler want to access the message data PxMsgGetData_Hnd(PxMsg_t msg) must be used.

If access to a data area is no longer required, yet the message is not to be released or sent, the access to the data area should be released via the function PxMsgRelDataAccess(PxMsg_t msg) since parallel access is limited to a maximum of four message buffers.

If access to a data area was released via PxMsgRelDataAccess(PxMsg_t msg), a pointer to this area may only be used after it was regained via PxMsgGetData.

msg create send en neu
Fig. 2. Creating and sending a message
msg recv en neu
Fig. 3. Receiving a message

Sample Code

The following example shows how to create, send and receive a message and how to access the received data.

Task1 creates and sends a message to Task2:

// Task1
...

 struct comm_data data;
 PxMsg_t msg0, msg1;
 PxError_t error;

...

 // create message with data as data area of the message
 msg0 = PxMsgEnvelop(&data,sizeof(data),my_opool);

 if(!PxMsgIdIsValid(msg0))
 {
   do_error_handling();
   return;
 }

 error = PxMsgSetToAwaitRel(msg0);
 if(error != PXERR_NOERROR)
 {
   do_error_handling();
   return;
 }

 // Send the message; access to data and msg
 // is no longer permitted
 msg1 = PxMsgSend(msg0,mbx_task2);

 // if sending was successful, the message Handle
 // is invalid
 if(PxMsgIdIsValid(msg1))
 {
   do_error_handling();
   return;
 }

 // wait for recipient to release the message
 msg0 = PxMsgAwaitRel(msg0);

 if(!PxMsgIdIsValid(msg0))
 {
   do_error_handling();
   return;
 }

 ...

 // the message is finally released
 PxMsgRelease(msg0);

Task2 waits at its mailbox for messages from Task1:

// Task2
...

 struct comm_data* pdata;
 PxMsg_t msg;

 // wait for message
 msg = PxMsgReceive(my_mbx);
 if(!PxMsgIdIsValid(msg))
 {
   do_error_handling();
   return;
 }

 // read data area of the message
 pdata = (struct comm_data*)PxMsgGetData(msg);

...

 // Release message; access to msg and pdata
 // is no longer permitted
 PxMsgRelease(msg);

...

Access restriction

Access to the data area can be restricted for a user of the message. Either the type of access or the size of the accessible area can be restricted.

WRProtection:

permits unrestricted access to the data area, i.e. the user has the right to read and write

WriteProtection:

permits the user to write into the data buffer

ReadProtection:

permits the user to read from the data buffer

NoAccessProtection:

permits no access to the data buffer

Access protection can only be changed by the owner of the message.

The user of a message can restrict the data area of a message; this is achieved via PxMsgSetData and PxMsgSetSize. The start address of the data area is changed via PxMsgSetData; the new address has to lie within the original data area. The size of the data area is automatically adjusted, so that the last address lies within the original data area.

The size of the data area can be reset via PxMsgSetSize; care has to be taken that the last address of the new data area lies within the original data area.

An example:

Task1 sends a message to Task2:

...
struct foo
{
  int x;
  int y;
  struct bar b;
  int z;
} data;

...
// create message and pack data into message
msg = PxMsgEnvelop(&data,sizeof(data),defaultopool);
PxMsgSetToAwaitRel(msg);

// protect data area of the message; the recipient
// has only read rights to the buffer; he may not
// write into it
PxMsgSetProtection(msg, ReadProtection);

...
// send to Task2
PxMsgSend(msg,task2mbx);
...
// wait for release of message
msg = PxMsgAwaitRel(msg);

// reset to original data area
PxMsgSetData(msg,0);

// reset to original size of the data area
PxMsgSetSize(msg,sizeof(data));
...

Task2 passes the message on to Task3; Task3 shall only have access to the component b of the structure foo:

...
struct foo* pData;
...
// receive message
msg = PxMsgReceive(task2mbx);
...
pData = PxMsgGetData(msg);
...
// restrict start address and size of the data area to Element b of
//  the structure foo
// Set start address to b
PxMsgSetData(msg,&pData->b);

// Set size to size of b, i.e. z is removed
PxMsgSetSize(msg,sizeof(pData->b));

// Note: If only the beginning of the data area is changed, the
// size of the remaining area is automatically adjusted

...
// send to Task3
PxMsgSend(msg,task3mbx);
...

Task3 receives the message and then releases it:

...
struct bar* pData;
PxSize_t size;
...
// receive message
msg = PxMsgReceive(task3mbx);
...
// request data area and size of the area
pData = PxMsgGetData(msg);
size = PxMsgGetSize();
...
PxMsgRelease(msg);
...

Interrupt and Trap Handler

Handlers are functions reacting to asynchronous events, i.e. to hardware or software interrupts or to traps. They are generally of higher priority than Tasks, meaning a running Task can always be interrupted by a Handler.

There are three types of interrupt Handlers: Context Handlers, Fast Context Handlers and Fast Handlers. Depending on the individual use case the according type should be chosen (see Handlers).

When starting the system, interrupts are enabled.

Fast Handlers

Fast Handlers are the fastest and highest prioritised interrupt Handlers. They run with system privileges in interrupt mode and are activated as soon as an interrupt occurs.

A Task can only install a Fast Handler if it has the appropriate permission (PXACCESS_HANDLERS, see Permissions).

A Fast Handler is installed via the function

PxError_t PxIntInstallFastHandler(PxUInt_t intno, void(* inthandler)(PxArg_t), PxArg_t arg)
intno

the interrupt number to be handled

inthandler

the Handler function

arg

the argument of the Handler function

The function returns the error code PXERR_REQUEST_INVALID_PARAMETER if intno lies outside the specification, otherwise PXERR_NOERROR.

The argument arg can be used for exchanging data between the Handler and a Task. An example for data exchange between Handler and Task can be found in section Data exchange between Handler and Task.

Instead of a self-implemented Handler, a service provided by PXROS-HR can also be installed as a Handler. To do so, the function

PxError_t PxIntInstallService(PxUInt_t intno, PxIntSvEnum_t service, PxArg_t arg, PxEvents_t events)

should be used. The parameters have the following meaning:

intno

the interrupt number to be handled

service

the service to be installed

arg

the argument of the service function

events

the Events possibly sent by the service function

The function returns the error code PXERR_REQUEST_INVALID_PARAMETER if intno lies outside the specification, or PXERR_ILLEGAL_SERVICE_CALLED if an invalid service was defined, otherwise PXERR_NOERROR.

To be able to install a service, the Task has to have the permission PXACCESS_INSTALL_SERVICES.

The following services are available:

PxTickDefine_IntHnd_SvNo

calls the function PxTickDefine_Hnd (see Section Basic timer functionality), arg and events are ignored

PxTaskSignalEvents_IntHnd_SvNo

calls the function PxTaskSignalEvents_Hnd; the Handle of the recipient Task has to be passed in arg, the Events to be sent in events

Context Handlers

Context Handlers are executed with the privileges of the Task which installed it, and run in the address space of that Task. The Task must have the right to install Context Handlers (PXACCESS_INSTALL_HANDLERS, see Permissions).

A Context Handler is not immediately called if the corresponding interrupt has occurred; instead it is entered into a list, which is processed after returning from interrupt level to system level.

Due to the fact that a Context Handler is first entered into a list, no assumption can be made regarding its execution time. Real time behaviour is not given!

Before a Context Handler can be installed, an interrupt object has to be requested via PxInterruptRequest. A Handler is installed via:

PxError_t PxIntInstallHandler(PxUInt_t intno, PxInterrupt_t intObj, void(*inthandler)(PxArg_t), PxArg_t arg)
intno

the interrupt number to be handled

intobj

the Handle of the corresponding interrupt object

inthandler

the Handler function

arg

the argument of the Handler function

The function returns the error code PXERR_REQUEST_INVALID_PARAMETER if intno lies outside the specification, otherwise PXERR_NOERROR.

Fast Context Handlers

Fast Context Handlers are executed with the privileges of the Task which installed it, and run in the address space of that Task. The Task must have the right to install Handlers (PXACCESS_INSTALL_HANDLERS, see Permissions).

A Fast Context Handler is called immediately if the corresponding interrupt has occurred, depending on the hardware priority of the interrupt.

A Handler is installed via:

PxError_t PxIntInstallFastContextHandler(PxUInt_t intno, PxIntHandler_t inthandler, PxArg_t arg)
intno

the interrupt number to be handled

inthandler

the Handler function

arg

the argument of the Handler function

The function returns the error code PXERR_REQUEST_INVALID_PARAMETER if intno lies outside the specification, otherwise PXERR_NOERROR.

Trap handler

A trap handler is similar to an interrupt handler and, in principle, has the same functionality. The difference in the two lies in that an interrupt handler is triggered by a hardware or software interrupt, while a trap handler is triggered by an exception, such as a faulty memory access operation. When a trap is raised, at first all hardware interrupts are blocked for a few cycles. They are immediately unblocked by the PXROS-HR trap entry mechanism so that the trap handler is executed with interrupts enabled.

A trap handler is installed by the function

PxError_t PxTrapInstallHandler(PxUInt_t trapno, PxBool_t(* traphandler)(PxTrapTin_t, PxUInt_t, PxUInt_t, PxUInt_t, PxUInt_t *, TC_CSA_t *), PxUInt_t arg)

The three parameters are defined as:

trapno

the trap number the handler is installed for

traphandler

the handler which is installed for the trap

arg

the user defined argument which is passed to the handler

The trap handler is defined by the prototype

PxBool_t TrapHandler(PxTrapTin_t trapTin, PxArg_t arg, PxUInt_t runtaskid, PxUInt_t dstr, PxUInt_t *deadd, TC_CSA_t *csa)

The five parameters are defined as:

TrapTin

The trap number and the TIN of the trap

arg

the user defined argument passed to the trap handler

runtaskid

the object ID of the active Task at the time the trap was triggered

dstr

the content of the register DSTR

deadd

the content of the register DEADD

csa

a pointer to the stored CSA (Context Save Area)

The handler function should return TRUE, if the trap condition has been solved by the handler, FALSE otherwise.

Timer functionality

The time unit used by PXROS-HR is a Tick. A Tick is an abstract time unit, the length of which has to be defined by the application.

Basic timer functionality

The internal time management of PXROS-HR is based on an internal, system-independent time unit, the Tick. The consecutive number of Ticks having passed since the start-up of the timer system is internally administered and can be read or incremented via system services.

The allocation of Ticks to a tangible time basis is done via function PxTickSetTicksPerSecond, which states how many Ticks correspond to one second. PxTickSetTicksPerSecond(100), for example, sets the length of a Ticks to 10 milliseconds.

It should be ensured that the time basis is the same for the initialization of the timer interrupts and when calling PxTickSetTicksPerSecond.

The continued counting of Ticks is performed by the function PxTickDefine_Hnd. This function has to be called within the Handler of the timer interrupts and registered as a service for the timer interrupt (see installation of an interrupt service).

The number of Ticks since the first call of PxTickDefine_Hnd can be queried via PxTickGetCount.

The time passed since the first call of PxTickDefine_Hnd can be queried via PxTickGetTimeInMilliSeconds.

The function PxGetTicksFromMilliSeconds(unsigned long ms) converts ms milliseconds to Ticks.

Delay Jobs

A delay job executes a function as a Handler after a predefined period of time. A Delay Job object can be requested from an object pool via PxDelayRequest (and PxDelayRequest_NoWait and PxDelayRequest_EvWait).

With the system services

PxError_t PxDelaySched(PxDelay_t delayId, PxTicks_t ticks, void (*handler)(PxArg_t), PxArg_t arg)

and

PxError_t PxDelaySched_Hnd(PxDelay_t delayId, PxTicks_t ticks, void (*handler)(PxArg_t), PxArg_t arg)

a function can be registered for subsequent processing. In this context, delayId is the Handle of the delay object, ticks the number of Ticks after which the function is to be executed, handler the pointer to the function to be executed, and arg the argument passed to the function.

In the below example, a Handler function is periodically called by repeatedly triggering execution from within in the function itself:

typedef struct _HndArg {
PxDelay_t delay;
PxTicks_t ticks;
} HndArg_t;

void handler_function(PxArg_t arg)
{
    HndArg_t *hndarg = (HndArg_t *)arg;
    PxError_t error;

  ...

   error = PxDelaySched_Hnd(hndarg->delay,hndarg->ticks,handler_function,arg);

  ...
}

...

void TaskFunc()
{
    ...

    PxDelay_t delay;
    PxTicks_t ticks;
    PxError_t error;
    HndArg_t  hndArg;

    ...

    delay = PxDelayRequest(defaultopool);

    if(PxDelayIdIsValid(delay))
    {
        ticks = PxGetTicksFromMillis(100);
        hndArg.delay = delay;
        hndArg.ticks = ticks;
        error = PxDelaySched(delay,ticks,handler_function,(PxArg_t)&hndArg);
        if(error != PXERR_NOERROR)
        {
          do_error_handling();
        }

        ...

    }

    ...
}

Timeout objects and periodical timers

A typical application for a Delay Job Handler is sending a timeout event to a Task. Timeout objects can be used to avoid having to implement this action over and over again. A Timeout object can be requested in the following way:

PxTo_t to = PxToRequest(PxOpool_t opool,PxTicks_t ticks,PxEvents_t ev)

The parameters have the following meaning:

opool

the object pool from which the timeout object is to be taken

ticks

the number of Ticks after which the Events are to be sent

ev

the Events to be sent after expiry of the timeout

The timer starts after the function call PxToStart(PxTo_t to). It can be terminated via PxToStop(PxTo_t to) if it is not yet expired.

Periodical timers (PxPe_t) differ from timeout objects in that they do not just send a single timeout Event but periodical ones.

Access to peripherals

There are two ways of accessing peripheral registers: A Task in Usermode0 can access peripheral registers via system calls if it has explicit permission; a Task in Usermode1 has full access to peripheral registers.

Access in Usermode1

In Usermode1 a Task has direct access to peripheral registers insofar as this is permitted by the hardware. This has the advantage that peripheral registers can be read and written in a fast way.

The disadvantage of this method is that the Task has access to all the registers to which the hardware has granted read/write permission, not only to those the Task actually needs. Erroneous access operations are thus possible.

Access via system calls

In Usermode0 a Task can access peripheral registers for read/write operations solely via system calls. Thus, peripheral access is slower in Usermode0 than in Usermode1.

In Usermode0 a Task can have read/write permission granted for certain registers. Under safety aspects this is an advantage over direct access.

If a Task wishes to access peripheral registers via system calls, it has to have access permission PXACCESS_REGISTERS (see Permissions). Furthermore, the address areas of the desired registers must be defined with the required access rights (read and/or write) in the additional memory areas table of the Task (PxTaskSpec_T.ts_protect_region, see task specification)

The following system function is available for reading a peripheral register:

PxULong_t PxRegisterRead(volatile PxULong_t *addr)

This function receives the address of the desired register as a parameter and returns the content of the register, zero in case of an error.

If the function returns zero, it cannot be determined whether this is the actual content of the register or an erroneous access operation.

The function

PxError_t PxRegisterWrite(volatile PxULong_t *addr, PxULong_t value)

writes the value value into the register with the address addr. If the access was successful, the function will return PXERR_NOERROR; in case of an error, it will return either PXERR_PROT_ILL_REGION or PXERR_ACCESS_RIGHT.

With the function

PxError_t PxRegisterSetMask(volatile PxULong_t *addr, PxULong_t mask, PxULong_t value)

the bits masked in mask in the register with address addr are set to the value stated in value. If the access was successful, the function will return PXERR_NOERROR; in case of an error, it will return either PXERR_PROT_ILL_REGION or PXERR_ACCESS_RIGHT.

The abort mechanism

PxExpectAbort makes it possible to abort the execution of a function from within another function. The function must have been started via PxExpectAbort(ev, func, params …​). The parameters have the following meaning:

ev

the Events causing the abort of the function

func

the function to be executed. func may have arbitrary parameters, yet it may not return any values

params

a list of the parameters passed to func

PxExpectAbort returns the Event, which has caused the abort, or zero if the function was regularly terminated.

In the following example the function func is called in Task1 via PxExpectAbort. By sending the Event EV_ABORT Task2 aborts the execution of the function.

...

void func(int i)
{
  ...
}

void Task1Func()
{
  ...

  PxEvents_t retval;

  ...

  retval = PxExpectAbort(EV_ABORT,func,42);

  if(retval == 0) // function was regularly terminated
  {
    ...
  }
  else // the function was aborted
  {
    ...
  }

  ...
}

void Task2Func()
{

  ...

  PxTaskSignalEvents(Task1,EV_ABORT);

  ...
}
...

Suspend and resume tasks

Sometimes it may be useful, to suspend a task from the execution. A suspended task remains in the condition when it was suspended, but is no longer part of the scheduling.

To suspend a task the function PxTaskSuspend(PxTask_t) is used:

PxError_t   err;
PxTask_t    Task_ID;
    err = PxTaskSuspend(Task_ID);

A suspended task may be resumed, if it is necessary to let it be part of the scheduling again.

To resume a task the function PxTaskResume(PxTask_t) is used:

PxError_t   err;
PxTask_t    Task_ID;
    err = PxTaskResume(Task_ID);

The task must have the access permission PXACCESS_SYSTEM_CONTROL (see Permissions) to execute these functions.