PICOS18 > Tutorial > Multi-task

PICos18 : Le multi-tâches

Ce chapitre présente les mécanismes de synchronisation entre tâches de PICos18. Une troisième tâche viendra compléter l'application et postera un évènement à une autre tâche afin de l'activer. Vous apprendrez à utiliser les évènements et à partager les ressources.


> Créations d'une tâche de fond

Dans les châpitres précédents nous avons créé un système composé de 2 tâches en charge de la gestion d'une horloge au travers des variables hour, min et sec.

Le multi-tâches de PICos18 ne permet pas seulement de découper un projet en plusieurs tâches distinctes afin d'en réduire la complexité mais aussi de coder des sous-ensembles complètement indépendant. Nous allons par exemple créer 2 nouvelles tâches qui auront pour fonction d'inspecter en permanence les ports RB0 et RB1, correspondant à 2 boutons poussoirs.

Un bouton poussoir n'est pas un signal numérique qui passe de l'état 0 à l'état 1 et y reste jusqu'au prochain changement d'état.

Lorsqu'on appuie sur un bouton poussoir (élément mécanique) il se produit des micro-rebonds, de quelques millisecondes, avant de stabiliser le signal à 1. Il importe donc de réaliser un code qui décèle l'état haut stable pour pouvoir vraiment déterminer si le bouton est pressé ou non.

Nous allons donc commencer par créer une première tâche qui va servir à inspecter le port RB0 sur lequel doit se trouver le bouton BTN0.

1) Sous MPLAB®, enregistrez le fichier "tsk_task0.c" sous le nom "tsk_task2.c"

2) Modifiez les références à la nouvelle tâche dans le fichier taskdesc.c, comme suit :

#define DEFAULT_STACK_SIZE      128
DeclareTask(TASK0);
DeclareTask(TASK1);
DeclareTask(TASK2);

volatile unsigned char stack0[DEFAULT_STACK_SIZE];
volatile unsigned char stack1[DEFAULT_STACK_SIZE];
volatile unsigned char stack2[DEFAULT_STACK_SIZE];

/**********************************************************************
 * ---------------------- TASK DESCRIPTOR SECTION ---------------------
 **********************************************************************/
#pragma		romdata		DESC_ROM
...

/**********************************************************************
 * -----------------------------  task 2 ------------------------------
 **********************************************************************/
rom_desc_tsk rom_desc_task2 = {
	TASK2_PRIO,                       /* prioinit from 0 to 15       */
	stack2,                           /* stack address (16 bits)     */
	TASK2,                            /* start address (16 bits)     */
	READY,                            /* state at init phase         */
	TASK2_ID,                         /* id_tsk from 1 to 15         */
	sizeof(stack2)                    /* stack size    (16 bits)     */
};

3) Puis changez le corps de la fonction de la tâche 2 par le code suivant :

extern unsigned char hour;

/**********************************************************************
 * ------------------------------ TASK2 -------------------------------
 *
 * Third task of the tutorial.
 *
 **********************************************************************/
TASK(TASK2) 
{
  unsigned int i;

  while(1)
  {
    while(PORTBbits.RB0 == 0);
    for (i = 0; i < 10000; i++)
      Nop();
    if (PORTBbits.RB0 == 1)
      hour++;
  }
}

La tâche possède une boucle infinie sans aucune attente à l'aide d'une fonction WaitEvent. Une telle tâche risque donc de bloquer le mécanisme de multi-tâches pour les tâches moins prioritaire... Elle doit donc posséder la priorité la plus faible : on dit que c'est la tâche de fond.

Elle commence par inspecter le port RB0 et reste dans une boucle infinie tant que la bouton BTN0 n'est pas enfoncé. Dès que le bouton est enfoncé la tâche attend à l'aide d'une boucle FOR de façon à éviter la phase de rebond. Ensuite si il s'avère que le port RB0 vaut bien 1 alors on incrémente de 1 la variable "hour". Ceci permet par exemple de venir paramétrer l'horloge.

4) Il faut renseigner le compilateur sur les nouveau symboles que nous avons ajoutés à l'aide du fichier "define.h".

/***********************************************************************
 * ----------------------------- Task ID -------------------------------
 **********************************************************************/
#define TASK0_ID             1
#define TASK1_ID             2
#define TASK2_ID             3

#define TASK0_PRIO           7
#define TASK1_PRIO           10
#define TASK2_PRIO           1

5) Enfin il faut ajouter la nouvelle tâche au projet ("Project/Add files to Project...").

Vous pouvez désormais recompiler le projet (F10).

 

> Round robin

Nous allons maintenant créer une seconde tâche de fond. En effet PICos18 permet de créer plusieurs tâches possédant le même niveau de priorité. Dans ce cas on peut bien se demander laquelle des 2 peut bien prendre la main sur la seconde pour s'exécuter. En fait dans ce cas précis, le scheduler de PICos18 utilise un algorithme spécifique dit 'Round Robin".

Avec un tel algorithme, les tâches de fond vont se partager le processeur du PIC18 durant une tranche de temps de 1ms dans notre cas. Comme ces tâches sont de faibles priorités, elles s'exécuteront uniquement si aucune autre tâche n'a besoin de fonctionner et pendant un temps maximum de 1 ms. Ensuite ce sera à une autre tâche de même priorité de s'exécuter.

Créez donc une tâche 3 qui possède le même code (à l'exception prêt qu'il s'agit d'inspecter le port RB1) et possédant la même priorité :

/***********************************************************************
 * ----------------------------- Task ID -------------------------------
 **********************************************************************/
#define TASK0_ID             1
#define TASK1_ID             2
#define TASK2_ID             3
#define TASK3_ID             4

#define TASK0_PRIO           7
#define TASK1_PRIO           10
#define TASK2_PRIO           1
#define TASK3_PRIO           1
N'oubliez pas d'ajouter le nouveau fichier au project, puis recompilez (F10).


Placez des breakpoints devant chaque instruction "while(PORTBbits.RBx == 0);", puis lancez la simulation. Vous verrez tout d'abord le pointeur s'arrêtez dans la tâche 2. Dévalidez alors le breakpoint et pousuivez la simulation, le pointeur s'arrêtera alors dans la tâche 3 exactement 1ms plus tard...

Si maintenant vous mettez des breakpoints dans les tâches 0 et 1, vous verrez qu'au bout de 1 seconde, le pointeur s'arêtera dans une de ces 2 tâches, il y a eu préemption du fait des priorités de ces 2 tâches.

 

> Simulation

Pour simuler l'appuie sur le bouton BTN0, vous pouvez utiliser la fenêtre de Watch :

Lancez la simulation jusqu'au premier breakpoint de la tâche 2. Clickez dans la fenêtre de Watch pour passer la valeur du PORTB à 0x01. Puis lancez la simulation en vérifiant que le breakpoint de la tâche 3 est toujours actif.

Vous verrez alors le pointeur s'arrêter d'abord dans la tâche 3 avant d'atteindre le second breakpoint de la tâche 2. En effet au delà de 1ms, le scheduler a jugé bon de donner la main à l'autre tâche...