PICOS18 > Tutorial > Interruptions

PICos18 : Les interruptions

Afin de pouvoir tirer parti du microcontrôleur PIC18, il est important de pouvoir utiliser les périphériques. De ce point de vue, les interruptions sont essentielles. Vous verrez comment les interruptions du PIC18 sont mises en œuvre sous PICos18 et comment écrire simplement vos propres routines d'interruption.


> Présentation du fichier int.c

Nous allons à présent brancher nos 2 boutons sur les broches d'interruptions externes du PIC18, ce qui nous permettra de ne traiter les boutons que lorsqu'ils sont utilisés.

Tout d'abord nous allons voir de quoi est constitué le fichier "int.c", fichier qui prend en charge la gestion de la totalité des interruptions. Il faut savoir que le PIC18 gère 2 types d'interruptions : les interruptions de basses priorités (IT_vector_low) et celles de hautes priorités (IT_vector_high). A quoi cela peut-il bien servir....?!

En fait il faut considérer que le noyau PICos18 effectue parfois des opérations délicates sur les tâches et qu'il n'est pas souhaitable qu'il soit interrompu par un IT pendant son traitement (par exemple pendant la préemption d'une nouvelle tâche sur la tâche courant). Du coup pour faire simple on a choisi de rendre le noyau non-interruptible. Or on a vu que le déterminisme de PICos18 était de 50µs (ce qu'on appelle le temps de latence), donc il exite une zone d'ombre, un black-out de 50 µs pendant laquelle une IT ne sera pas détectée.

Pour éviter celà on utilise les interruptions rapides ("fast interrupts") du PIC18, qui utilise un mécanisme hardware pour la sauvegarde des registres élémentaires (comme WREG, STATUS, ...). Hélas pour utiliser les interruptions rapides du PIC18, il faut écrire un code C extrêment léger, presque trivial. Si l'on a besoin d'un code plus complexe, qui appelle par exemple d'autres fonctions C, il faut impérativement utiliser les interruptions lentes... avec toutefois le risque d'être mis en attente pendant 50µS !

/**********************************************************************
 * Function you want to call when an IT occurs.
 **********************************************************************/
  extern void AddOneTick(void);
/*extern void MyOwnISR(void); */
  void InterruptVectorL(void);
  void InterruptVectorH(void);

/**********************************************************************
 * General interrupt vector. Do not modify.
 **********************************************************************/
#pragma code IT_vector_low=0x18
void Interrupt_low_vec(void)
{
   _asm goto InterruptVectorL  _endasm
}
#pragma code

#pragma code IT_vector_high=0x08
void Interrupt_high_vec(void)
{
   _asm goto InterruptVectorH  _endasm
}
#pragma code

/**********************************************************************
 * General ISR router. Complete the function core with the if or switch
 * case you need to jump to the function dedicated to the occuring IT.
 **********************************************************************/
#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

/* BE CARREFULL : ONLY BSR, WREG AND STATUS REGISTERS ARE SAVED  */
/* DO NOT CALL ANY FUNCTION AND USE PLEASE VERY SIMPLE CODE LIKE */
/* VARIABLE OR FLAG SETTINGS. CHECK THE ASM CODE PRODUCED BY C18 */
/* IN THE LST FILE.                                              */
#pragma	code _INTERRUPT_VECTORH = 0x003300
#pragma interrupt InterruptVectorH
void InterruptVectorH(void)
{
  if (INTCONbits.INT0IF == 1)
    INTCONbits.INT0IF = 0;
}
#pragma	code

Au premier abord le fichier "int.c" peut paraître bien compliqué, mais il est en fait fort simple. Les 2 seules zones qui peuvent être soumises à modifications sont les fonctions InterruptVectorL() et InterruptVectorH(). Comme dans 99% des cas l'interruption lente sera suffisante pour vos applications, vous n'aurez qu'à intervenir dans la fonction InterruptVectorL().

 

> Le mode "fast interrupt"

La fonction du mode "fast interrupt" se présente comme suit :

/* BE CARREFULL : ONLY BSR, WREG AND STATUS REGISTERS ARE SAVED  */
/* DO NOT CALL ANY FUNCTION AND USE PLEASE VERY SIMPLE CODE LIKE */
/* VARIABLE OR FLAG SETTINGS. CHECK THE ASM CODE PRODUCED BY C18 */
/* IN THE LST FILE.                                              */
#pragma	code _INTERRUPT_VECTORH = 0x003300
#pragma interrupt InterruptVectorH
void InterruptVectorH(void)
{
  if (INTCONbits.INT0IF == 1)
    INTCONbits.INT0IF = 0;
}
#pragma	code

Les interruptions externes INT0 (broche RB0), INT1 (broche RB1) et INT2 (broche RB2) sont par défaut en mode fast interrupt, c'est-à-dire que lorsque l'une de ces IT se produit, elle interromp le code et exécute la fonction InterruptVectorH().

L'interruption INT0 n'est pas paramétrable, c'est-à-dire qu'elle appelle toujours la fonction InterruptVectorH().

Par contre INT1 et INT2 peuvent être utilisées pour appeller la fonction InterruptVectorL().

Pour celà il faut modifier le registre INTCON3 qui permet ce paramétrage au travers des bits INT2IP et INT1IP.

Si nous souhaitons à présent brancher nos 2 boutons sur INT1 et INT2, nous allons devoir traiter le mécanisme d'anti-rebond au sein de la fonction InterruptVectorH(). Or nous avons vu que nous avons pas droit à un code trop compliqué, comme à l'utilisation de pointeur, aux appels de fonctions, aux boucles FOR et WHILE,... Bref, tout au plus nous avons droit d'incrémenter ou décrémenter des variables globales...!

La raison en est simple : il ne faut pas modifier d'autres registres du PIC18 que les registres WREG, STATUS et BSR. Si vous hésitez entre "low interrupt" et "fast interrupt", inspecter le code généré par le compilateur pour vérifier qu'il ne modifie pas d'autres registres. Comme nous n'avons pas besoin de traiter l'appuie de nos boutons avec une précision meilleure que 50µs, nous allons donc utiliser le mode "slow interrupt".

 

> Le mode "slow interrupt"

Dans la plupart des traitements d'interruption, le mode "slow interrupt" est largement suffisant. Les 3 seules interruptions qui sont par défaut en mode "fast interrupt" sont INT0, INT1 et INT2. Comme INT0 ne peut être dévalidé, nous allons utiliser INT1 et INT2 en les reprogrammant en mode "slow interrupt" :

Pour utiliser INT1 et INT2 en mode "slow interrupt", modifiez le registre INTCON3 comme indiqué ci-dessus dans la phase d'init du main.

Puis complétez le fichier int.c pour intercepter les interruptions INT1 et INT2 :

/**********************************************************************
 * Function you want to call when an IT occurs.
 **********************************************************************/
  extern void AddOneTick(void);
  extern void MyOwnISR(void);
  void InterruptVectorL(void);
  void InterruptVectorH(void);

...

/**********************************************************************
 * General ISR router. Complete the function core with the if or switch
 * case you need to jump to the function dedicated to the occuring IT.
 **********************************************************************/
#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 (INTCON3bits.INT1IF || INTCON3bits.INT2IF)
    MyOwnISR();                                  

  LeaveISR();
}
#pragma	code

Il suffit à présent d'ajouter la fonction "MyOwnISR" dans n'importe quel fichier C, de préférence une des 2 tâches de fond qui sont associés aux boutons. Par exemple nous ajoutons la fonction dans le fichier "tsk_task2.c" :

void MyOwnISR(void)
{
  if (INTCON3bits.INT1IF)
  {
    INTCON3bits.INT1IF = 0;
    /* ... */
  }
  if (INTCON3bits.INT2IF)
  {
    INTCON3bits.INT2IF = 0;
    /* ... */
  }
}

Et n'oubliez pas de déclarer la fonction au début du fichier :

#include "define.h"

void MyOwnISR(void);

/**********************************************************************
 * Definition dedicated to the local functions.
 **********************************************************************/
#define ALARM_TSK0      0
Vous pouvez à présent recompiler le projet (F10).

 

> Association aux tâches de fond

Pour le moment la fonction "MyOwnISR" ne permet de faire passer l'information aux tâches de fond qu'un des boutons a été enfoncé. De plus les tâches de fond continuent à fonctionner en permanence au travers d'une boucle infinie. Il faudrait pouvoir mettre en sommeil les 2 tâches de fond et les réveiller sur interruptions. Pour cela nous allons modifier les 2 tâches de fond pour qu'elles soient des tâches BASICs. Modifier "tsk-task2.c comme suit :

TASK(TASK2) 
{
  unsigned int i;

  for (i = 0; i < 10000; i++)
    Nop();
  if (PORTBbits.RB1 == 1)
    hour++;

  TerminateTask();
}

Comme vous pouvez le constater la tâche 2 ne possède plus de "while(1)" et de plus elle se termine par la fonction "TerminateTask()".
C'est ce qui caractérise une tâche BASIC :

L'objectif est que la tâche soit à l'état SUSPENDED par défaut puis activée à l'aide de la fonction "ActivateTask()".

Modifiez donc le fichier "taskdesc.c" pour mettre les tâches 2 et 3 à SUSPENDED par défaut :

/**********************************************************************
 * -----------------------------  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)     */
	SUSPENDED,                        /* state at init phase         */
	TASK2_ID,                         /* id_tsk from 1 to 15         */
	sizeof(stack2)                    /* stack size    (16 bits)     */
};
/**********************************************************************
 * -----------------------------  task 3 ------------------------------
 **********************************************************************/
rom_desc_tsk rom_desc_task3 = {
	TASK3_PRIO,                       /* prioinit from 0 to 15       */
	stack3,                           /* stack address (16 bits)     */
	TASK3,                            /* start address (16 bits)     */
	SUSPENDED,                        /* state at init phase         */
	TASK3_ID,                         /* id_tsk from 1 to 15         */
	sizeof(stack2)                    /* stack size    (16 bits)     */
};

Modifiez le code de la tâche 3 pour qu'elle soit aussi une tâche BASIC :

TASK(TASK3) 
{
  unsigned int i;

  for (i = 0; i < 10000; i++)
    Nop();
  if (PORTBbits.RB2 == 1)
    min++;

  TerminateTask();
}

Et enfin modifiez la fonction "MyOwnISR" pour activer la tâche nécessaire au traitement du bouton :

void MyOwnISR(void)
{
  TaskStateType State;

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

Recompilez (F10) et relancez la simulation (F9)

Placez un breakpoint dans la tâche 3 et un dans la fonction "MyOwnISR". Lancez la simulation puis stoppez là à n'importe quel moment (F5). Modifiez INTCON3 pour activez une interruption (par exemple mettre 0x1A pour l'INT2) : vous verrez que le pointeur passe par "MyOwnISR" puis par la tâche 3.

Si vous poursuivez la simulation vous verrez que le pointeur ne passe plus par la tâche 3 avant la prochaine interruption : la tâche a été suspendue.