Structuring Language: Activity Chart Implementation : TASK Activities : EXTENDED TASK

EXTENDED TASK

An EXTENDED TASK runs once, upon activation, and then suspends itself, calling the WaitEvent” API function. A specific modification to this EXTENDED TASK behavior will be described below, at the end of that section.

The code frame for an EXTENDED TASK (for example: TASK2 containing Activities A21 and A22), without controller, will look like the following:

TASK (TASK2)
{
cgSingleBuffer_TASK2.eventMask = 0xff;
start_activity_A21;
start_activity_A22;
while(1) {
cgActivity_A21();
cgActivity_A22();
WaitEvent(cgSingleBuffer_TASK2.eventMask);
ClearEvent(cgSingleBuffer_TASK2.eventMask);
}
/* TerminateTask(); */

}

 

 

Note: With regard to lines 3, 9, 10 in the last example: This has been changed from earlier implementations of MicroC. In newer versions of MicroC, the eventMask data variable is no longer allocated. The defined mask, in the example above 0xff is directly inlined in the call to WaitEvent and ClearEvent calls. This note is applicable to the rest of the examples in this document.
Note: Further Optimization: In certain implementations it is possible to call the WaitEvent and ClearEvent API functions with constants, thus avoiding the need for allocating RAM for the eventMask.

What can be seen is that upon invocation, the call to start_activity_A21 and to start_activity_A22 is done. The call is done only once, the first time the TASK is run. This will drive the event started of those sub-activities as well as the event started for the task itself. This is supported only for that task type.

After that call, the code enters an infinite loop running all of the TASK’ sub-activities, and entering the suspension mode through call to “WaitEvent”.

If somewhere underneath the TASK, not as direct descendant, we will add a Statechart, the code will change to be like:

TASK (TASK2)
{
cgSingleBuffer_TASK2.eventMask = 0xff;
start_activity_A21;
start_activity_A22;
while(1) {
do {
cgGlobalFlags &= ~BITSUPERSTEP_TASK2;
cgActivity_A21();
cgActivity_A22();
if(cgDoubleBufferNew_TASK2.cg_Events)
cgGlobalFlags |= BITSUPERSTEP_TASK2;
cgDoubleBufferOld_TASK2 = cgDoubleBufferNew_TASK2;
cgDoubleBufferNew_TASK2.cg_Events = 0;
} while ( (cgGlobalFlags & BITSUPERSTEP_TASK2) != 0);
WaitEvent(cgSingleBuffer_TASK2.eventMask);
GetEvent(TASK2, &cgSingleBuffer_TASK2.eventsBuff);
ClearEvent(cgSingleBuffer_TASK2.eventMask);
}
/* TerminateTask(); */
}.
 
 
 

If the TASK is periodic, with a period of 10 ticks, the code will change to look like this:

TASK (TASK2)
{
SetRelAlarm(TASK2_ALARM, 1, 10);
cgSingleBuffer_TASK2.eventMask = 0xff;
start_activity_A21;
start_activity_A22;
while(1) {
do {
cgGlobalFlags &= ~BITSUPERSTEP_TASK2;
cgActivity_A21();
cgActivity_A22();
if(cgDoubleBufferNew_TASK2.cg_Events)
cgGlobalFlags |= BITSUPERSTEP_TASK2;
cgDoubleBufferOld_TASK2 = cgDoubleBufferNew_TASK2;
cgDoubleBufferNew_TASK2.cg_Events = 0;
} while ( (cgGlobalFlags & BITSUPERSTEP_TASK2) != 0);
WaitEvent(cgSingleBuffer_TASK2.eventMask);
GetEvent(TASK2, &cgSingleBuffer_TASK2.eventsBuff);
ClearEvent(cgSingleBuffer_TASK2.eventMask);
if(cgSingleBuffer_TASK2.eventsBuff & 0x01)
GEN_IN_CURRENT(TASK2_EV);
}
/* TerminateTask(); */
}
 
 

Another setting option, available in MicroC for an EXTENDED TASK is Guarded Activation mode. In this mode the TASK will be active only while its control bit is set. The sample code will change to look like:

TASK (TASK2)
{
if ((cgGlobalFlags & ALARM_SET_TASK2) == 0){
cgGlobalFlags |= ALARM_SET_TASK2;
SetRelAlarm(TASK2_ALARM, 1, 10);
};
cgSingleBuffer_TASK2.eventMask = 0xff;
while((cgGlobalFlags& BITAC_TASK2) != 0) {
do {
cgGlobalFlags &= ~BITSUPERSTEP_TASK2;
cgActivity_A21();
cgActivity_A22();
if(cgDoubleBufferNew_TASK2.cg_Events)
cgGlobalFlags |= BITSUPERSTEP_TASK2;
cgDoubleBufferOld_TASK2 = cgDoubleBufferNew_TASK2;
cgDoubleBufferNew_TASK2.cg_Events = 0;
} while ( (cgGlobalFlags & BITSUPERSTEP_TASK2) != 0
&& (cgGlobalFlags& BITAC_TASK2));
WaitEvent(cgSingleBuffer_TASK2.eventMask);
GetEvent(TASK2, &cgSingleBuffer_TASK2.eventsBuff);
ClearEvent(cgSingleBuffer_TASK2.eventMask);
if(cgSingleBuffer_TASK2.eventsBuff & 0x01)
GEN_IN_CURRENT(TASK2_EV);
}
TerminateTask();
}
 
 

In order to make that task run, it must explicitly call the start operation for it. The definition of the start(TASK2) function, in that case, is:

#define start_activity_TASK2 { cgGlobalFlags|=
BITAC_TASK2; start_activity_CTRL2cnt1;
ActivateTask(TASK2); }
 
 

Note that now it is possible to terminate that TASK, by calling stop for it. Thus, it is possible to combine the benefits of an EXTENDED TASK, regarding reaction time while it is alive, as well as having the advantage of reusing RAM while running. Stopping those tasks that are not required in certain application configurations, and activating other tasks in the new configuration; all in run-time!

For an EXTENDED TASK, certain intertask communication/ synchronization mechanisms are available, using the TASK EVENT operator.

EXTENDED TASK may enter the WaitEvent state waiting for some of its TASK EVENT(s) to be set. The EVENTs the task is waiting for are marked using the event mask given to the WaitEvent API. Only the task that “owns” the TASK EVENT may wait for it to be set. However, the TASK EVENT might be set from any place in the code. MicroC links TASK EVENT to MicroC events. For example: consider the EXTENDED TASK, TASK4, with 2 associated TASK EVENTs: EV1, with mask 0x02, and EV2, with mask 0x04. The following code will be generated in order to link TASK EVENT to MicroC events:

...EXTENDED TASK BODY
WaitEvent(cgSingleBuffer_TASK2.eventMask);
GetEvent(TASK4, &cgSingleBuffer_TASK2.eventsBuff);
ClearEvent(cgSingleBuffer_TASK2.eventMask);
if(cgSingleBuffer_TASK2.eventsBuff & 0x02)
GEN_IN_CURRENT(EV1);
if(cgSingleBuffer_TASK2.eventsBuff & 0x04)
GEN_IN_CURRENT(EV2);
…EXTENDED TASK BODY (continued)
Note: The “GEN_IN_CURRENT” call sets the internal event passed to it as an argument in the next iteration of the task, which is in the “current” step of it.

Thus, if any reaction in the content of TASK4 is waiting for example to EV1 to be set, i.e., reaction like “EV1/ACT1(),” which will be translated, as explained later, into: “if (EV1) {ACT1();}” will be executed once the associated TASK EVENT 0x02 was set.

On the other hand, when certain action set a TASK EVENT: “[C1]/EV1,” which will be translated, as explained later, into: “if (C1) {GENERATE_EVENT(EV1);};” the following code will be generated for the GENERATE_EVENT(EV1) call:

cgEventMsgMask = 0x02;SetEvent(TASK2, cgEventMsgMask);
 
 

In addition, the following definitions will be made to link the TASK EVENT 0x02 (EV1) with the internal event EV1:

#define BIT_EV1 0x01
#define GEN_IN_CURRENT_EV1
(cgDoubleBufferOld_TASK2.cg_Events |= BIT_EV1)
#define EV1 ((cgDoubleBufferOld_TASK2.cg_Events &
BIT_EV1) != 0)
 
 

Such that the line in the above TASK body code:

if(cgSingleBuffer_TASK2.eventsBuff & 0x02)
GEN_IN_CURRENT(EV1);
 
 

Will set the internal event EV1 bit (BIT_EV1) thus linking the TASK EVENT mask 0x02 of EV1 to its internal bit 0x01 (BIT_EV1) .