PICOS18 > Tutorial > Application

PICos18 : Exemple d'application

Le tutorial se termine ici par un exemple complet d'application. Les sources de cet exemple sont fournis dans le répertoire /Project/Tutorial de PICos18, ceci vous permettant d'apprécier à quel point il est aisé d'écrire une application pour PIC18 avec PICos18.


> Interruptions multiples

Lorsqu'on exécute l'application du tutorial et qu'on appuie sur les boutons on s'apperçoit vite qu'il existe un problème : le PIC18 semble ne plus bien fonctionner après un instant, surtout si l'on appuie "sauvagement" sur les boutons !

Stopper alors l'application en mode debug si vous utilisez un ICD2 de Microchip ou bien votre simulation si vous travailler sous le simulateur de MPLAB, et inspectez les variables suivantes :

Comme vous pouvez le constater la variable "kernelPanic" n'est pas nulle. Dans notre cas elle vaut 0x53.
En fait cette variable nous renseigne sur d'éventuel débordement de pile logicielle. Lorsqu'une pile déborde sur une autre, le nouyau détecte automatiquement le problème et stoppe l'émoragie en suspendant la tâche en cause. Ceci permet de limiter la casse, mais surtout de debuger alors que PICos18 fonctionne toujours.

kernelPanic vaut 0x53.

La partie de droite ("3") signifie que la tâche dont l'identifiant est 3 a vu sa pile déborder.
La partie de gauche ("5") signifie que la tâche dont l'identifiant est 5 a vu sa pile se faire écraser par une autre.

Le noyau a donc décidé de geler les 2 tâches car leur fonctionnement n'est plus assuré. Ceci permet au PIC18 de rester fonctionnel malgré le bug, et donc vous autorise à trouver l'origine du bug.

La tâche 2 en charge de l'appuie d'un bouton a fonctionné de façon anormale et sa pile logicielle a débordé sur la pile suivante, celle de la tâche 4.

Mais comment cela se peut-il ?
Et bien 2 cas sont possibles :

Or nous utilisons des boutons poussoirs, et nous avons vu précédemment que ceux-ci rebondissaient lorsqu'ils étaient pressés. Du coup plusieurs IT en chaîne se produisent et saturent inutilement le PIC18. A chaque IT, on sauvegarde le contexte de la tâche courante, mais les interruptions sont si proches qu'on tourne en boucle dans InterruptVectorL() jusqu'à faire exploser la pile logicielle courante....

Ce phénomène est courant, qu'on utilise ou pas un noyau temps-réel. La seule différence ici, c'est que PICos18 a surveillé l'application en "temps-réel" à l'affut de ce genre de problème et préserve ainsi le reste de l'application. Sans OS, le PIC18 se serait planté... En fait il ne s'agit pas d'un problème d'architecture mais bel et bien d'un problème de code, que nous allons aussitôt corriger.

> Correction de l'application

Le problème vient du fait que nous autorisons toujours les interruptions sur les ports RB1 et RB2 alors qu'on a pas encore commencé à traiter la première interruption. Après tout il ne sert à rien de savoir que le bouton a généré 50 rebonds avant de se stabiliser : il faut juste mémoriser le fait qu'il ait été enfoncé.

Modifier les tâches TASK2 et TASK3 comme suit :

TASK(TASK2) 
{
  unsigned int i;

  hour++;
  if (hour == 24)
    hour = 0;
  INTCON3bits.INT1IF = 0;
  INTCON3bits.INT1IE = 1;
  TerminateTask();
}

...

TASK(TASK3) 
{
  unsigned int i;

  min++;
  if (min == 60)
    min = 0;
  INTCON3bits.INT2IF = 0;
  INTCON3bits.INT2IE = 1;
  TerminateTask();
}

Et surtout il faut modifier la gestion des interruptions :

void MyOwnISR(void)
{
  TaskStateType State;

  if (INTCON3bits.INT1IF)
  {
    INTCON3bits.INT1IE = 0;
    GetTaskState(TASK2_ID, &State);
    if (State == SUSPENDED) 
      ActivateTask(TASK2_ID);
  }
  if (INTCON3bits.INT2IF)
  {
    INTCON3bits.INT2IE = 0;
    GetTaskState(TASK3_ID, &State);
    if (State == SUSPENDED) 
      ActivateTask(TASK3_ID);
  }
}

...

void InterruptVectorL(void)
{
  EnterISR();

  if (INTCONbits.TMR0IF == 1)
    AddOneTick();
/*Here is the next interrupts you want to manage */
  if ((INTCON3bits.INT1IF & INTCON3bits.INT1IE)|| 
      (INTCON3bits.INT2IF & INTCON3bits.INT2IE))
    MyOwnISR();
  if ((PIR1bits.RCIF)&(PIE1bits.RCIE))
    RS_RX_INT();
  if ((PIR1bits.TXIF)&(PIE1bits.TXIE))
    RS_TX_INT();

  LeaveISR();
}
Comme vous pouvez le constater, MyOwnISR() dévalide les interruptions (INTxIE = 0 ce qui signifie Interrupt Enable à 0) et c'est aux tâches de gérer l'autorisation, une fois seulement que le traitement est réalisé.

Vous pouvez maintenant programmer votre PIC18 et tester le programme en réel, vous verrez qu'en appuyant sur les boutons vous pouvez mettre à jour l'horloge. Toutefois il apparaît un problème : vous avez beau appuyer plusieurs fois de suite sur un bouton, la nouvelle valeur n'est prise en compte que toutes les secondes, au moment ou l'affichage est rafraîchi. Pour corriger ça nous allons utiliser les capacités temps-réel de PICos18 !


> Modification en temps réel

Pour pouvoir modifier l'affichage à chaque appuie de bouton il faudrait pouvoir informer la tâche d'affichage qu'il est nécessaire de rafrâichir l'affichage avec les nouvelles valeurs.... mais ceci sans perturber l'affichage déjà existant à chaque seconde, c'est-à-dire sans perturber la bon fonctionnement de l'horloge.

Pour cela nous allons poster un événement à la tâche TASK4 :

TASK(TASK2) 
{
  unsigned int i;

  hour++;
  if (hour == 24)
    hour = 0;
  SetEvent(TASK4_ID, UPDATE_EVENT);
  INTCON3bits.INT1IF = 0;
  INTCON3bits.INT1IE = 1;
  TerminateTask();
}

...

TASK(TASK3) 
{
  unsigned int i;

  min++;
  if (min == 60)
    min = 0;
  SetEvent(TASK4_ID, UPDATE_EVENT);
  INTCON3bits.INT2IF = 0;
  INTCON3bits.INT2IE = 1;
  TerminateTask();
}

Et dans la tâche TASK4 nous allons nous mettre en attente aussi sur ce type d'événements :

...


  while(1)
  {
    WaitEvent(ALARM_EVENT | UPDATE_EVENT);
    GetEvent(id_tsk_run, &Time_event);

    if (Time_event & ALARM_EVENT)
      ClearEvent(ALARM_EVENT); 
    if (Time_event & UPDATE_EVENT)
      ClearEvent(UPDATE_EVENT); 

    Printf("%02d : %02d : %02d\r", (int)hour, (int)min, (int)sec);
  }

...

Aussi n'oubliez pas de rajouter la variable "EventMaskType Time_event" en tant que variable globale ou variable locale à la tâche.

La définition de UPDATE_EVENT n'est pas connu de l'application aussi rajoutez là dans le fichier define.h :
...

/***********************************************************************
 * ----------------------------- Events --------------------------------
 **********************************************************************/
#define ALARM_EVENT       0x80
#define TASK1_EVENT       0x10
#define UPDATE_EVENT      0x02

#define RS_NEW_MSG        0x10
#define RS_RCV_MSG        0x20
#define RS_QUEUE_EMPTY    0x10
#define RS_QUEUE_FULL     0x20

...


> L'application finale

Voilà, vous pouvez désormais recompilez votre application, la charger dans un PIC18F452 et la tester, vous verrez alors votre horloge s'afficher sous HyperTerminal et vous réussirez à la modifier en temps-réel à l'aide vos boutons poussoirs.

Bien entendu vous pouvez largement simplifier cette application. Ceci n'a pas été fait pour permettre une meilleure compréhension de PICos18. Vous pouvez aussi la compléter, avec votre propre code ou bien en rajouter des drivers de PICos18 : vous pourriez par exemple utiliser un RTC (Real Time Clock) sur bus I2C et la mettre à l'heure directement depuis PICos18 et HyperTerminal...