| PICOS18 > Tutorial > Preemption |
|
You've just designed an application with only one task, but this application is not really usefull ! By adding a new task you will discover all the mechanisms of the preemption from one task to the others. Furthermore we'll manipulate alarms to understand their functionnalities and how to use them. |
![]() |
| > Periodic wake up of a task |
It is still difficult to understand what is the interest of a such kernel when
you have only one task .... to do an infinite loop !
So we gonna start to bring a little bit more interesting our task by modifying
the code to make a LED blinking at a certain frequency.
Modify te task code as follows :
TASK(TASK0)
{
TRISBbits.TRISB4 = 0;
LATBbits.LATB4 = 0;
SetRelAlarm(ALARM_TSK0, 1000, 200);
while(1)
{
WaitEvent(ALARM_EVENT);
ClearEvent(ALARM_EVENT);
LATBbits.LATB4 = ~LATBbits.LATB4;
}
}
Our task is composed here by 2 phases : the first one has a "while(1)" which correspond to an init phase and a second one that is in the while core.
The 2 first instructions let you manipulate the IO ports of the PIC18 chip.
The key word "TRISx" is used to setup a port as an input or an ouput
and the key word "TRISxbits" is a structure to let you modify the
state of a port bit.
Here we have a RB4 port as an ouput (the "0", a "1" meaning
"input") and the ouput value to "0" (key word LATxbits).
Then the SetRelAlarm function is used to program the alarm of the task. In the previous chapter we have seen what is an alarm : a TIMER object based on a software tick at 1ms. In the "taskdesc.c" file the alarm ID is 0 (ALARM_TSK0) and has been connected to the task TASK0 :
AlarmObject Alarm_list[] =
{
/*******************************************************************
* -------------------------- First task ---------------------------
*******************************************************************/
{
OFF, /* State */
0, /* AlarmValue */
0, /* Cycle */
&Counter_kernel, /* ptrCounter */
TASK0_ID, /* TaskID2Activate */
ALARM_EVENT, /* EventToPost */
0 /* CallBack */
},
};
The SetRelAlarm has 3 important fields :
In our application it means the alarm 0 waits for 1000ms before posting the event ALARM_EVENT then will post periodicaly the event evry 200 ms.
Once the alarm is programmed the task can go on. In the "while(1)" the first function called is the WaitEvent function that permits to put to sleep the task until the event ALARM_EVENT is posted.
You can put to sleep your task on any event but keep in mind the event must
be defined as a power of 2 in the "define.h" file. The allowed
values are : 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02 et 0x01.
| > Task 0 simulation |
To verify we have a blinking task with a period of 200ms it is possible to simulate the application by placing a breakpoint face to the line that toggles the RB4 bits :

Moreover you can spy the PIC18 peripherals or the PICos18 global variables, particulary the "global_counter" variable which is decimal counter of the milliseconds occured. To get a decimal value at screen click with the right button of the mouse on the variable and choose "Dec".
Start many time your simulation (F9) to see the PORTB toggle between 0x00 and 0x10 and the value of global_counter incremented by 200. The variable starts from the value 1001 (the kernel needs 1ms to boot so you get the value 1001 instead of the value expected of 1000).
That means the RB4 port is ON after 1 seconde then toggles periodicaly every
200ms.
Modify now the last two parameters of the SetRelAlarm function to see what appends.
For instance put a 0 in the last parameter to see RB4 stay at 1 and you never
go back in the task.
Have a look at the PDF related to the PICos18 API to learn much more about these functions.
| > Creating a second task |
Now you have design a periodic task you gonna create a second one and synchronize the both of them.
1) Under MPLAB® save the "tsk_task0.c" file to "tsk_task1.c"
2) Modify the references of the new task in the taskdesc.c file :
#define DEFAULT_STACK_SIZE 128
DeclareTask(TASK0);
DeclareTask(TASK1);
volatile unsigned char stack0[DEFAULT_STACK_SIZE];
volatile unsigned char stack1[DEFAULT_STACK_SIZE];
/**********************************************************************
* ---------------------- TASK DESCRIPTOR SECTION ---------------------
**********************************************************************/
#pragma romdata DESC_ROM
const rom unsigned int descromarea;
/**********************************************************************
* ----------------------------- task 0 ------------------------------
**********************************************************************/
rom_desc_tsk rom_desc_task0 = {
TASK0_PRIO, /* prioinit from 0 to 15 */
stack0, /* stack address (16 bits) */
TASK0, /* start address (16 bits) */
READY, /* state at init phase */
TASK0_ID, /* id_tsk from 1 to 15 */
sizeof(stack0) /* stack size (16 bits) */
};
/**********************************************************************
* ----------------------------- task 1 ------------------------------
**********************************************************************/
rom_desc_tsk rom_desc_task1 = {
TASK1_PRIO, /* prioinit from 0 to 15 */
stack1, /* stack address (16 bits) */
TASK1, /* start address (16 bits) */
READY, /* state at init phase */
TASK1_ID, /* id_tsk from 1 to 15 */
sizeof(stack1) /* stack size (16 bits) */
};
3) Then change the function core of the task 1 :
unsigned char hour, min, sec;
/**********************************************************************
* ------------------------------ TASK1 -------------------------------
*
* Second task of the tutorial.
*
**********************************************************************/
TASK(TASK1)
{
hour = min = sec = 0;
while(1)
{
WaitEvent(TASK1_EVENT);
ClearEvent(TASK1_EVENT);
sec++;
if (sec == 60)
{
sec = 0;
min++;
if (min == 60)
{
min = 0;
hour++;
}
}
}
}
As you probably guessed this task manages a kind of software swatch with hour/minute/second
: every time the task wakes up the sec variable is incremented by 1and the hour
and min variables are updated if necessary.
4) So the task needs to be called every 1000ms. Modify the task 0 to let it post an event every second to our new task :
SetRelAlarm(ALARM_TSK0, 1000, 1000);
while(1)
{
WaitEvent(ALARM_EVENT);
ClearEvent(ALARM_EVENT);
LATBbits.LATB4 = ~LATBbits.LATB4;
SetEvent(TASK1_ID, TASK1_EVENT);
}
5) Now we need to update the symbols present in the "define.h" file :
/*********************************************************************** * ----------------------------- Events -------------------------------- **********************************************************************/ #define ALARM_EVENT 0x80 #define TASK1_EVENT 0x10 /*********************************************************************** * ----------------------------- Task ID ------------------------------- **********************************************************************/ #define TASK0_ID 1 #define TASK1_ID 2 #define TASK0_PRIO 7 #define TASK1_PRIO 6
6) Finally you can add the new file to the project ("Project/Add files to Project...").
Rebuild the project (F10).
| > Task 1 simulation |
In order to verify the hour, min and sec variables are updated properly every second start the simulation with a breakpoint :

A simulation of 1 seconde needs a lot of time for MPLAB® so if you wish
to test the min and hour variables upto 60 you need to modify the alarm ALARM_TASK0
to go faster just for the simulation of course.
| > Preemption |
Now your application is composed of 2 tasks, it is now a multi-task application. But it does not mean the both tasks are running at the same time because it is not possible for the PIC18 processor that can execute only one instruction at a time. At the maximum we can say the tasks are running in parallel it means one after the other function to a set of rules described in the kernel core... like the priorities.
To understand properly the preemption, the multi-task and the real-time terms add some breakpoints and start the simulation :

The pointer runs through the breakpoints in this order :
Actually the task 0 has a priority higher than the task 1 then when it post an event to the task 1 it continu to run until it falls asleep by the WaitEvent function. Then the task 1 is free to run and the pointer stops on the ClearEvent breakpoint.
Now modify the priority of the task 1 in the "define.h" file to put
it as the task with the highest priority :
/*********************************************************************** * ----------------------------- Task ID ------------------------------- **********************************************************************/ #define TASK0_ID 1 #define TASK1_ID 2 #define TASK0_PRIO 7 #define TASK1_PRIO 10
Rebuild and start the simulation : now the new breakpoint order is what follows :
Because the task 1 is now the task with the highest priority, when an event occurs the current task is stopped immediatly to let the task with the highest priority to run : it can be said there is preemption (that means to hang).
The preemption is one of the most major feature of the multi-task and real-time
kernels. Indeed with a non preemtive kernel the task 0 will continu to run until
it falls asleep by itself to let the task 1 to run. We say a such kernel is
a cooperative kernel that means the current task has the responsability to decide
when to give bak hand to the rest of the application. The SALVO kernel
which manages also the PIC18 is a good example of a cooperative
kernel.
Of course in our application it is not very important because it seams to run
by the same way finally. Anyway when we have to manage complex protocol or when
the application is composed by a lot of task a preemptive kernel is much more
safe for the application whichever the code size.
But the preemption is not a suffisant criteria to say that a kernel is a real-time
kernel or not. To do it we have to talk about the determinism,
it means the fact a kernel is able to warranty the time necessary to switch
from one task to another one is a constant. With PICos18 the time is 50 µs
then each time the task 0 sends an event to the task 1 the time to switch from
task 1 to task 0 is 50 µs.
PICos18 is a preemtive, a multi-task and a real-time kernel that does what is necessary to switch to a task with a highest priority immediatly if an event occurs and needs only 50 µs to do it, whatever the applicaiton is composed. You can understand now that the infinite "while(1);" loop in the task whith the highest priority will block the rest of the application. Try to do it with the simulator !