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 default system memory class; must be
PXMcVarsized
,PXMcVarsizedAligned
orPXMcVarsizedAdjusted
This requirement is for compatibility reasons and the actual value has no effect PxSize_t is_sysmc_size:
-
alignment of allocated blocks; must be 8 (or any higher multiple of 8)
This requirement is for compatibility reasons and the actual value has no effect PxMemAligned_t *is_sysmc_blk:
-
pointer to the memory block assigned to the system default memory class
PxSize_t is_sysmc_blksize:
-
size of the memory block for system default memory class
PxMcType_t is_taskmc_type:
-
the type of task default memory class; must be
PXMcBuddyMemory
This memory class will be assigned asPXMcTaskdefault
to the first task (Init task). PxSize_t is_taskmc_size:
-
minimum allocatable size for task default memory class; must be
PXMEM_ALIGN
(32 bytes) PxMemAligned_t *is_taskmc_blk:
-
pointer to the memory block assigned to the task default memory class
PxSize_t is_taskmc_blksize:
-
size of the memory block for task default memory class
PxUInt_t is_obj_number:
-
number of PXROS-HR objects
PxObjId_t is_obj_namelength:
-
maximum length of the object names including "null-terminators"; value of 0 disables object names
PxMemAligned_t *is_objmc_blk:
-
pointer to the memory block assigned to the system default object pool
PxSize_t is_objmc_blksize:
-
size of the memory block for system default object pool
PxTaskSpec_ct is_inittask:
-
task specification of the Inittask, see section The Inittask
PxProtectRegion_T is_sys_ro_protection[1]:
-
system memory protection settings (region and attributes) for code and read-only data. The protection settings
is_sys_ro_protection
covers the whole ARM code segment.
A sample initialization of the protection regions:
const PxProtectRegion_T is_sys_ro_protection =
{
.lowerBound = ARM_CODE_SEGMENT_BEGIN,
.upperBound = ARM_CODE_SEGMENT_END,
.mattr = READONLY_CODE_MEMORY_ATTRIBUTES()
};
Init function
Calling PxInit
starts the Inittask and only returns if an error occurred during initialization.
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 "Varsized"
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).
Sample code
The example initialization of the PXROS-HR system is shown in the below code:
const PxTaskSpec_T InitTaskSpec =
{
.ts_name = (const PxChar_t *) "InitTask",
.ts_fun = InitTask_Func,
.ts_mc = PXMcTaskdefaultInit,
.ts_opool = PXOpoolSystemdefaultInit,
.ts_privileges = PXUserPrivilege,
.ts_accessrights = INITTASK_PXACCESS,
.ts_context = &InitTaskContext,
.ts_protect_region = InitTaskRegions,
.ts_taskstack =
{
.stk_type = PXStackFall,
.stk_size = INITTASK_STACK_SIZE_BYTES,
.stk_src.stk = &inittask_stack[INITTASK_STACK_ARRAYSIZE]
},
.ts_inttaskstack =
{
.stk_type = PXStackFall,
.stk_size = INITTASK_INTERRUPTSTACK_SIZE_BYTES,
.stk_src.stk = &inittask_interrupt_stack[INITTASK_INTERRUPTSTACK_ARRAYSIZE]
}
};
const PxInitSpec_T InitSpec =
{
/* System Default Memory Class */
.is_sysmc_type = PXMcVarsized,
.is_sysmc_size = 8,
.is_sysmc_blk = Sysmem,
.is_sysmc_blksize = SYSMEMSIZE,
/* Task Default Memory Class */
.is_taskmc_type = PXMcBuddyMemory,
.is_taskmc_size = PXMEM_ALIGN,
.is_taskmc_blk = Taskmem,
.is_taskmc_blksize = TASKMEMSIZE,
/* Memory block for System Default Object Pool */
.is_objmc_blk = (PxMemAligned_t *)PxObjmem,
.is_objmc_blksize = PX_OBJMEMSIZE,
/* System Default Object Pool parameters */
.is_obj_number = NUM_OF_PXOBJS,
.is_obj_namelength = PXROS_NAMESIZE,
/* InitTask specification */
.is_inittask = &InitTaskSpec,
/* Memory protection settings */
.is_sys_ro_protection[0] = _SYS_RO_PROTECTION,
};
const PxInitSpecsArray_t InitSpecsArray =
{
[0] = &InitSpec
};
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.
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’s stacks (normal and interrupt).
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 and memory attributes are set to NoAccessProtection
, the area will be inherited from the parent task.
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 memory attributes that controls allowed memory operations (no access, read-only access, read-write access, etc.).
Sample code for creating and initialising the Task specification:
// Definition of the memory context of the Task;
// protection[0] is inherited from the creator of the Task
// protection[1] covers read and write access to Task1 data
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].mattr = MEMORY_ATTRIBUTES(NoAccessPermission),
// read-write data area
.protection[1].lowerBound = (PxUInt_t)Task1_data_base,
.protection[1].upperBound = (PxUInt_t)Task1_data_end,
.protection[1].mattr = MEMORY_ATTRIBUTES(RWPermission)
};
#define TASK1_STACKSIZE 1024 // 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,
// Task's default memory class
.ts_mc = PXMcTaskdefault,
// Task's default object pool
.ts_opool = PXOpoolTaskdefault,
// Privileges of the Task
.ts_privileges = PXUserPrivilege,
// A pointer to the memory context of the Task
.ts_context = &Task1_Context,
// Stack type, will be allocated from memory class
.ts_taskstack.stk_type = PXStackAlloc,
// Stack size in 4-byte units
.ts_taskstack.stk_size = TASK1_STACK_SIZE,
// Stack will be allocated from PXMcTaskdefault
.ts_taskstack.stk_src.mc = PXMcTaskdefault,
};
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 handle of the created Task
Task1_hnd = 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:
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:
-
Waiting for an event: the function returns as soon as it receives an event
-
Waiting at a mailbox: the function returns as soon as the mailbox contains message objects
-
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 more flexible way.
Memory classes are not usually used for requesting memory explicitly, but mainly for making memory available to message objects.
Tasks only have access to PXMcTaskdefault
, the task’s default memory class. In some cases, task can have access to other, dynamically created memory classes (of type PxMcBuddyMemory
or PxMxFixSized
), but never to the PXMcSystemdefault
which is the system default memory class solely reserved for PXROS-HR kernel.
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 sizemsgsize
. The data buffer is taken from the memory classmcId
. 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 sizemsgsize
. The data buffer is taken from the memory classmcId
. 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 sizemsgsize
. The data buffer is taken from the memory classmcId
. Should the object pool be empty, this function will block until objects are available again or until one of the Events stated inevents
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 poolopoolid
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 poolopoolid
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 poolopoolid
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 inevents
occurs.
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.
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 ofPxMsgRelease…
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 callsPxMsgRelease
, the message object is not released but returned to the sender instead.
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 be 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 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.
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);
...
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).
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
andevents
are ignored PxTaskSignalEvents_IntHnd_SvNo
-
calls the function
PxTaskSignalEvents_Hnd
; the Handle of the recipient Task has to be passed inarg
, the Events to be sent inevents
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.
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, PxTrapHandler_t traphandler, 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
-
the function type of a trap handler
-
1st trapno: trap number
-
2nd arg: user supplied argument
-
3rd tid: task ID
-
4th fsr: FSR (fault status register)
-
5th addr: MMFAR or BFAR
-
6th saved_regs: pointer to the saved register frame */
- PxBool_t TrapHandler(PxUInt_t trapno, PxUInt_t arg, PxUInt_t tid, PxUInt_t fsr, PxUInt_t addr, PxSavedRegsPtr_t saved_regs)
-
The parameters are defined as:
- trapno
-
The trap number
- arg
-
the user defined argument passed to the trap handler
- tid
-
the object ID of the active Task at the time the trap was triggered
- fsr
-
fsr: FSR (fault status register)
- addr
-
MMFAR or BFAR (MemManage Fault Address Register or BusFault Address Register)
- saved_regs
-
a pointer to the saved register frame
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.
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, andarg
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
On ARM Cortex-M architectures, there are two types of peripheral registers: external and internal (core registers). The external peripheral registers can be accessed from a task directly, provided the register address lies within the taks’s context or extended memory regions. This access is fast as there is no system call involved.
The internal (core) peripheral registers must be accessed by privileged code, i.e., in handler mode. There are two ways of accessing internal peripheral registers from a task: using PXROS-HR handler call (as the handler will run as privileged code) or via PXROS-HR kernel services PxRegisterWrite
and PxRegisterRead
. In both cases, the system call is involved, which makes the register access a bit slower compared to direct access to external registers.
Access via PxHndcall
The task can access internal (core) peripheral registers using "PxHndcall" call (function _PxHndcallVA
or _PxHndcallPxArg
). The extended memory region is not required in this case; however, the task must have PXACCESS_HANDLERS
access right. 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 kernel services
A Task can access internal (core) peripheral registers solely via dedicated kernel API functions. If a Task wishes to access peripheral registers this way, 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.
The function
- PxError_t PxRegisterWrite(volatile PxULong_t *addr, PxULong_t value)
-
writes the value
value
into the register with the addressaddr
. If the access was successful, the function will returnPXERR_NOERROR
; in case of an error, it will return eitherPXERR_PROT_ILL_REGION
orPXERR_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 addressaddr
are set to the value stated invalue
. If the access was successful, the function will returnPXERR_NOERROR
; in case of an error, it will return eitherPXERR_PROT_ILL_REGION
orPXERR_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.