PICOS18 > Tutorial > First

PICos18 : Création d'une première tâche

Maintenant que le décor est planté il est temps de créer et de simuler une première tâche. Vous apprendrez comment l'écrire et comment la déclarer au noyau. De plus vous découvrirez les alarmes et le tick système à 1ms.


> Pré-requis

Tout d'abord il est nécessaire d'avoir créé un projet sous MPLAB®, projet qui contient les sources de PICos18.

Ensuite il faudra avoir installé le compilateur C18 de Microchip, ainsi que l'avoir paramétré (châpitre précédent).

 

> Etat d'une tâche

Dans l'état actuel notre projet ne comporte qu'une seule tâche définie dans le fichier "tsk_task0.c" :

             TASK(TASK0)
             {
                 while(1);
             }

On comprend facilement que le rôle de la tâche est de créer une boucle infinie.

Sous MPLAB®, placer un point d'arrêt (breakpoint) sur la ligne en face de l'instruction "while(1);", puis lancer la simulation (F9).
Vous constaterez que la simulation s'arrête sur votre breakpoint. Mais comment fait le noyau pour connaître l'existence de votre tâche, exécuter son code, s'arrêter sur la boucle infinie ... et pour autant ne pas être bloquée par cette instruction ?

En fait la tâche a été déclarée auprès du noyau dans le fichier "taskdesc.c".

/**********************************************************************
 * -----------------------------  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)     */
};

Sur la ligne en gras vous pouvez indiquer l'état de la tâche lors de son initialisation. En effet même si une tâche existe dans le projet (elle a été compilée par le compilateur), elle n'est pas forcément active.

Dans PICos18, au respect de la norme OSEK, une tâche possède 4 états possibles :

SUSPENDED : la tâche est présente dans le projet, mais n'est pas prise en compte par le noyau.

READY : la tâche est prête à être activée par le noyau, qui désormais la prend en compte.

WAITING : la tâche est en sommeil, elle est temporairement SUSPENDED et sera READY dès qu'un évènement viendra la réveiller.

RUNNING : parmi toutes les tâches prêtes, c'est celle-ci qui occupe la processeur pendant un temps T.

En ce qui concerne votre tâche, elle se trouve en fait dans l'état READY. Tant qu'elle y restera, le noyau pourra y accéder...
Remplacer READY par SUSPENDED dans le fichier taskdesc.c, recompilez et relancez la simulation : vous constaterez que la simulation ne s'arrête plus sur le breakpoint.

 

> Description des tâches

Tout comme l'état de la tâche, de nombreuses choses sont décrites dans le fichier taskdesc.c. En fait tout ce qui concerne votre application se trouve dans ce fichier, comme par exemple : l'identifiant de la tâche, sa priorité, sa pile logicielle, les alarmes nécessaires au projet, ...

On trouve tout d'abord la liste des alarmes de l'application:

AlarmObject Alarm_list[] = 
  {
   /*******************************************************************
    * -------------------------- First task ---------------------------
    *******************************************************************/
   {
     OFF,                                  /* State                   */
     0,                                    /* AlarmValue              */
     0,                                    /* Cycle                   */
     &Counter_kernel,                      /* ptrCounter              */
     TASK0_ID,                             /* TaskID2Activate         */
     ALARM_EVENT,                          /* EventToPost             */
     0                                     /* CallBack                */
   },
 };

Ce tableau constitue la liste des alarmes, chaque alarme étant représentée par une structure. Une alarme est une sorte d'objet TIMER géré de façon logicielle par le noyau. En fait PICos18 possède une référence de temps de 1ms, générée de façon hardware et logicielle. De cette façon il est possible de créer une alarme logicielle basée sur cette base de temps de 1ms : les alarmes.

Une alarme est associé à un compteur (ici Counter_kernel qui est la référence de 1ms) et une tâche (dont l'identifiant est TASK0_ID). Lorsque l'alarme atteint une valeur de seuil elle peut poster un événement à cette tâche (ALARM_EVENT dans cet exemple) ou bien appeler une fonction en C (mécanisme de CallBack).

On trouve ensuite la liste des ressources de l'application:

/**********************************************************************
 * --------------------- COUNTER & ALARM DEFINITION -------------------
 **********************************************************************/
Resource Resource_list[] = 
  {
   {
      10,                                /* priority           */
       0,                                /* Task prio          */
       0,                                /* lock               */
   }
  };

Une ressource est un autre objet géré par le noyau. Une ressource est généralement un périphérique partagé par plusieurs tâches, par exemple un port 8 bits du PIC18. Elle possède une priorité (priority) et peut être vérouillée (lock) par une tâche (Task prio). Ainsi lorsqu'une tâche veut avoir accès au port 8 bits, elle y accède via la ressource définie et empêche ainsi pendant un cours instant à toute autre tâche d'y avoir accès. Les ressources seront vu dans le châpitre lié au multi-tâche.

On trouve ensuite la liste des piles logicielles de l'application:

Il existe 2 types de variables : les variables statiques et les variables automatiques...de façon plus compréhensible nous parlerons de variables globales et de variables locales, même si la correspondance n'est pas tout à fait exacte. Les variables globales (c'est-à-dire placées en dehors de toutes fonctions C) sont placées en RAM à une adresse absolue, adresse qui ne change jamais.

Les variables locales (déclarées au sein de fonctions C) sont elles compilées par C18 de sorte de se retrouver stocker dans une zone mémoire dite pile logicielle. Une pile logicielle est une zone mémoire dynamique, vivante en quelques sortes. Lorsque l'on rentre dans une fonction C, on commence par empiler sur cette zone les variables locales, et lorsque l'on quitte la fonction C, on dépile l'espace réservé aux variables locales libérant ainsi de la mémoire. Ce type de fonctionnement permet de gagner beaucoup de place mémoire lorsqu'il existe de nombreuses fonctions écrites en C, au prix d'un surplus de code minimal.

PICos18 est compatible avec la gestion de la pile logicielle faite par C18. Chaque tâche possède alors sa propre pile logicielle, si bien que chacune des tâches de l'applicaiton peut travailler de façon autonome dans son propre espace de travail, sans perturber les autres tâches. De plus PICos18 possède un mécanisme de détection de débrodement mémoire, ce qui arrive lorsqu'une pile logicielle commence à déborder sur celle d'une autre tâche (kernelpanic).

/**********************************************************************
 * ----------------------- TASK & STACK DEFINITION --------------------
 **********************************************************************/
#define DEFAULT_STACK_SIZE      128
DeclareTask(TASK0);

volatile unsigned char stack0[DEFAULT_STACK_SIZE];

Chaque fois que vous ajoutez une tâche à votre application, pensez à ajouter une pile en copiant la ligne ci-dessus et en changeant le nom de la nouvelle pile (par stack1 par exemple).

Seules ces 3 valeurs sont possibles, n'essayez pas d'utiliser une taille de valeur intermédiaire, la compilateur C18 ne pourrait pas la gérer. De plus une pile doit toujours se trouver sur une même bank, et pas à cheval sur 2 banks. En cas de doutes, laissez toutes vos tailles de pile à 128.

On trouve ensuite la liste des tâches de l'application:

/**********************************************************************
 * -----------------------------  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)     */
};

Nous avons déjà abordé le descripteur de tâche, voici plus en détail le sens de chaque champ :

 

> Interruptions à 1ms

Même si votre application ne nécessite pas le besoin de gérer des interruptions, vous aurez certainement besoin de l'horloge logicielle interne de 1 ms. Pour cela le fichier "int.c" contient un certains nombres de définition, y compris la routine de gestion d'interruption.

Elle est en fait très simple et vous dispense de toute la gestion délicate des ITs sur PIC18 :

#pragma	code _INTERRUPT_VECTORL = 0x003000
#pragma interruptlow InterruptVectorL save=section(".tmpdata"), PROD
void InterruptVectorL(void)
{
  EnterISR();

  if (INTCONbits.TMR0IF == 1)
    AddOneTick();
/*Here is the next interrupts you want to manage */
/*if (INTCONbits.RBIF == 1)                      */
/*  MyOwnISR();                                  */

  LeaveISR();
}
#pragma	code

Comme vous pouvez le comprendre aisément, lorsque l'interruption du TIMER0 se lève (flag TMR0IF) on appelle la fonction AddOneTick qui ajouter 1 ms à toutes les alarmes de l'application. Lorsque certaines ont atteint leur seuil critique, elle déclenche des actions auprès des tâches associées en transitant par le noyau, seule entité du système à être autorisée à déclencher des tâches.

Vous voyez donc que le noyau est toujours actif et qu'aucune tâche ne peut le stopper, même avec une boucle infinie du type "while(1)".

Pour le vérifier, paramétrez votre simulation à 40MHZ (Debugger/Settings..), placez un breakpoint en face de l'instruction AddOneTick(), et affichez la stopwatch de MPLAB (Debbuger/Stopwatch) :

 

> Démarrage de l'application

Enfin votre application possède aussi un fichier "main.c" nécessaire au compilateur.
Selon la norme OSEK ce fichier main ne fait pas parti de l'OS mais bel et bien de la partie applicative, et c'est à lui que revient le rôle de lancer le noyau (car il est en effet possible de stopper et de lancer le noyau depuis le fichier main).

Dans le fichier main vous trouverez une fonction Init() qui va vous permettre de paramétrer votre fréquence de fonctionnement sans avoir besoin d'ouvrir la datasheet du PIC18 :

void Init(void)
{
  FSR0H = 0;
  FSR0L = 0;

  /* User setting : actual PIC frequency */
  Tmr0.lt = _40MHZ;

  /* Timer OFF - Enabled by Kernel */
  T0CON = 0x08;
  TMR0H = Tmr0.bt[1];
  TMR0L = Tmr0.bt[0];
}

La ligne en gras vous permet de régler la fréquence INTERNE du PIC18. En effet celui-ci possède une PLL par 4, que vous pouvez activer ou non. Une utilisation classique des PIC18 consiste à les équiper d'un quartz externe de 10 MHz et de 2 capacités de 15 pF, puis d'activer la PLL interne afin d'obtenir une fréquence interne de 40 MHz.

Le pipeline interne du processeur étant de niveau 4, le pire cas de fonctionnement du PIC18 est de 10 MIPS, soit 10 millions d'instructions par secondes (voir la datasheet du composant). Cela en fait un microcontrôleur 8 bits faible cout puissant et bien armé en périphérique.

Si vous souhaitez utiliser une fréquence différente, consulter les fréquences disponibles dans le fichier "define.h".