<< The Fribotte Homepage >>
Un club de passionnés en robotique participant à la coupe de France E=M6.
[Accueil] [Qui sommes-nous ?] [Robots] [Coupe e=m6] [BD Technique] [Forum] [Reportages] [Liens] [WiKiFri]

Fribotte

 

La Programmation des pics
par Bigonoff
Premiere partie - pic 16f84 - Révision 4

Téléchargement des fichiers originels ici
Publié le 26/10/2001

12. Les interruptions (Suite 2)

Index coursIndex du cours
Chapitre précédent12. Les interruptions (Suite 1)
Chapitre suivant13. Le Timer 0

 

12. Les interruptions (Suite 2)
12.14 Passage au simulateur d’une routine d’interruption

Petit rappel, il faut commencer par sélectionner la mise en service du simulateur : Allez dans le menu " project " et sélectionnez " edit project ". Dans la fenêtre qui s’ouvre, sélectionnez " change " à côté du " development mode ". et cliquez sur la case " MPLAB-SIM 16F84 ". Répondez oui à toutes les questions qui pourraient vous être posées.

Dans le menu " windows ", sélectionnons maintenant l’affichage des registre spéciaux (special function registers). Plaçons cette fenêtre sur le côté.

Lançons la compilation avec <F10>. Ensuite pressez <F6> pour faire le reset du programme, puis <F7>. Répétez <F7> en suivant l’évolution du programme. N’oubliez pas qu’il va effectuer 68 boucles pour effacer la RAM, soyez patient. Profitez-en pour observer le comportement de FSR pour l’adressage indirect.

Une fois arrivé dans le programme principal, le programme boucle indéfiniment. En effet, un événement extérieur (bouton-poussoir) est nécessaire pour provoquer le passage dans la routine d’interruption. Je vais maintenant vous expliquer comment simuler un événement extérieur.

Allez dans le menu " debug ", choisissez " Simulator stimulus ->Asynchronous stimulus ". Une petite fenêtre s’ouvre avec 12 événements configurables. Cette fenêtre se place toujours en avant-plan. Rangez-la à gauche de votre éditeur, sans cacher les registres spéciaux. Prenez garde pour l’instant de ne pas cliquer sur un bouton tant que je ne vous le signalerai pas.

Placez-vous sur le bouton <Stim 1 (P) > et cliquez avec le bouton de droite. Vous avez un menu déroulant qui apparaît. Sélectionnez " assign pin ". Choisissez ensuite la pin que vous voulez stimuler. Dans notre cas, RB0. Votre bouton est maintenant transformé en <RB0 (P)>.

Si vous regardez encore avec le bouton de droite, vous voyez que vous avez accès au mode de fonctionnement du bouton. Vous avez le choix entre Pulse (génère une impulsion), Low (place un niveau 0), High (place un niveau 1), ou Toggle (inversion du niveau à chaque pression). Nous choisirons la moins pratique pour cet exemple, mais la plus explicite.

Choisissez donc " Low ". Votre bouton est maintenant < RB0 (L) >. Créez un second bouton à côté du premier en sélectionnant RB0 et High. Vous disposez maintenant de 2 boutons. Un place RB0 au niveau 1 (donc bouton-poussoir relâché), l’autre le place sur 0, donc bouton-poussoir pressé.

Notez, mais ne le faite pas maintenant, que si vous pressez deux fois sur un bouton avec le bouton de gauche, vous obtenez le message suivant :

Ceci signifie que vous ne pouvez évidemment exécuter qu’une stimulation par cycle. Vous êtes donc obligé après chaque pression sur un bouton de stimulation, de revenir sur la fenêtre de l’éditeur et d’avancer d’une instruction avec <F7> avant de pouvoir exercer un nouveau stimuli.

Examinons le registre PORTB dans la fenêtre d’affichage des registres spéciaux. Vous voyez que tous les bits sont des 0. En effet, MPLAB ne peut pas connaître l’électronique que vous avez connecté sur ses pins. C’est donc à vous de lui indiquer le niveau que vous êtes sensé avoir sur les dites pins. Pour ceux qui n’auraient pas compris le fonctionnement de la résistance de rappel, voici le schéma équivalant :

Nous voyons donc que, quand le bouton-poussoir n’est pas pressé, nous avons un niveau 1 sur RB0 provoqué par la résistance de rappel que nous avons mise en service.

Pressons donc le bouton <RB0 (H)> une seule fois, et allons dans l’éditeur. Pressons <F7> pour avancer d’un pas et valider la modification de niveau. Examinez PORTB : RB0 est maintenant passé à 1. Notre bouton-poussoir n’est pas enfoncé. Pressez quelques fois <F7> pour vérifier que rien d’autre ne s’est passé.

Nous allons maintenant simuler la pression du bouton-poussoir. Pressez le bouton <RB0 (L)> pour envoyer 0 sur RB0. Revenez dans l’éditeur et pressez une seule fois sur <F7>. L’instruction qui suit l’événement est alors l’instruction située à l’adresse 0x04, car le passage de 1 à 0 sur RB0 a provoqué notre interruption.

Avancez lentement par pressions de <F7> dans la routine d’interruption. Examinez l’effet des différentes instructions vues. Une fois la ligne :

  xorwf PORTA , f ; inverser RA2

exécutée, vous constatez que la LED s’est allumée (RA2 est passé à 1 sur le registre PORTA). Avancez lentement jusqu’à ce que la ligne :

  retfie   ; return from interrupt

soit sélectionnée et ne pressez plus <F7>. A cet endroit, nous trouvons le retour de la routine d’interruption vers le programme principal. Pressez une nouvelle fois <F7>.

Que se passe-t-il ? Au lieu de revenir au programme principal, nous recommençons une nouvelle interruption.

Pour provoquer une interruption, il faut que le bit Enable ET le bit Flag d’une des sources d’interruptions soient à 1. Or, il n’y a qu’un seul bit Enable à 1, et c’est INTE.

Examinons donc INTF (c’est le bit 1 de INTCON). Ce bit est toujours à 1, donc nouvelle interruption. Nous avons donc commis une erreur classique. Nous avons oublié d’effacer le flag à la fin du traitement de notre interruption. Remarquez que cet effacement est intégré dans la partie " switch " de la routine d’interruption du fichier m16f84.asm. Nous avons effacé cette ligne par mégarde en supprimant les différents tests.

12.15 Première correction : reset du flag

Il nous faut donc ajouter la ligne suivante dans notre sous-routine intrb0

  bcf INTCON , INTF ; effacer flag INT/RB0

Nous obtenons donc :

  movlw B'00000100' ; bit positionné = bit à inverser
  BANK0   ; car on ne sait pas sur quelle banque
      ; on est dans une interruption (le
      ; programme principal peut avoir changé
      ; de banque. Ce n'est pas le cas ici,
      ; mais c'est une sage précaution
  xorwf PORTA , f ; inverser RA2
  bcf INTCON , INTF ; effacer flag INT/RB0
  return   ; fin d'interruption RB0/INT

Recompilons notre programme avec <F10>, puis <F6>, et enfin, recommencez toute la procédure que nous venons de voir.

Vérifiez que la routine d’interruption se termine maintenant en rendant la main au programme principal. La LED 1 est maintenant allumée (RA2 = 1). Pressez <RB0(H)> pour simuler le relâchement du bouton-poussoir. Pressez quelques fois <F7> et pressez <RB0 (L)> pour simuler une seconde pression de bouton-poussoir. Suivez la routine d’interruption et constatez que cette fois la LED s’éteint.

On obtient donc au simulateur le fonctionnement suivant :

  • Une pression sur le B.P. (bouton-poussoir) allume la LED
  • Une autre pression éteint la LED
  • Et ainsi de suite.

Nous avons donc obtenu le résultat souhaité.

Placez votre PIC dans le programmateur, et envoyez lui le fichier " Myinter.hex " [ NDLR : Fichiers exemples ici ]. Placez votre PIC sur la platine d’essais (modifiée) et pressez le B.P. à plusieurs reprises. La LED ne fonctionne pas du tout comme prévu, Elle réagit, mais de manière aléatoire. Que se passe-t-il ?
 

12.16 Se mettre à l’échelle de temps de la PIC

Et bien, c’est tout simple. Les PICs sont des composants très rapides. Ils ne travaillent pas à la même échelle de temps que nous. Essayons de nous transformer en PIC. Nous voyons alors un énorme Bouton poussoir.

Lorsque ce B.P. est pressé, c’est alors une énorme barre qui vient court-circuiter 2 contacts. Cette barre est élastique. Que voit la PIC ? Elle voit une énorme barre flexible qui tombe d’une énorme hauteur sur 2 contacts métalliques. Une fois la barre en contact, elle REBONDIT plusieurs fois. A chaque pression sur le B.P., la PIC voit donc une série de fermeture et d’ouverture du B.P., au lieu d’une seule pour nous.

La PIC est donc plus rapide que notre B.P. N’oubliez donc jamais que les PICs ne travaillent pas à une échelle de temps humaine. Vous devez en tenir compte.

12.17 Le problème de l’anti-rebond

Comment remédier à ce problème ? Et bien, tout simplement en attendant un temps supérieur au temps de rebondissement avant d’autoriser une nouvelle interruption sur RB0. Nous allons utiliser nos connaissances actuelles pour résoudre ce problème. Il devient utile de dessiner un ordinogramme de ce que nous voulons faire :

Explications

Le programme principal effectue normalement ses initialisations, puis teste si une demande de tempo a été introduite en positionnant le flag " tempo ". Si le flag n’est pas mis, il boucle sans fin.

Si le B.P. est pressé, une interruption est générée, RA2 est inversé, la routine d’interruption positionne le flag tempo à 1 et interdit toute nouvelle interruption de RB0. Toute autre action sur RB0 sera donc sans effet (donc les rebonds ne sont pas pris en compte).

La routine d’interruption prend fin. Retour au programme principal, qui continue alors à tester le flag tempo. Celui-ci vient d’être positionné par la routine d’interruption. Le programme principale appelle alors une routine de tempo (que nous avons déjà vue dans la leçon principale).

Après écoulement du temps nécessaire à la fin des rebonds, le flag tempo est annulé (pour ne pas boucler sans fin), et les interruptions sont à nouveau autorisées, afin de permettre de prendre en compte une nouvelle pression sur le B.P.

Vous voyez donc ici qu’il peut être utile dans un programme d’interrompre et de relancer les interruptions à certains moment spécifiques.

12.18 Finalisation du programme

Tout d’abord, il nous faut une routine de temporisation. Ouvrez le fichier " Led_cli.asm " et effectuez un copier/coller de la routine de temporisation. [ NDLR : Fichiers exemples ici ]

 

;*********************************************************************
; SOUS-ROUTINE DE TEMPORISATION
;*********************************************************************
;--------------------------------------------------------------------------------------------------------
; Cette sous-routine introduit un retard de 500.000 µs.
; Elle ne reçoit aucun paramètre et n'en retourne aucun
;--------------------------------------------------------------------------------------------------------
tempo      
  movlw 2 ; pour 2 boucles
  movwf cmpt3 ; initialiser compteur3
boucle3      
  clrf cmpt2 ; effacer compteur2
boucle2      
  clrf cmpt1 ; effacer compteur1
boucle1      
  nop   ; perdre 1 cycle
  decfsz cmpt1 , f ; décrémenter compteur1
  goto boucle1 ; si pas 0, boucler
  decfsz cmpt2 , f ; si 0, décrémenter compteur 2
  goto boucle2 ; si cmpt2 pas 0, recommencer boucle1
  decfsz cmpt3 , f ; si 0, décrémenter compteur 3
  goto boucle3 ; si cmpt3 pas 0, recommencer boucle2
  return   ; retour de la sous-routine

Nous allons modifier légèrement cette sous-routine. Nous pouvons enlever la boucle extérieure, car, 500ms c’est beaucoup plus que le temps de rebond du B.P. Enlevons également l’instruction nop.

Nous obtenons :

;*********************************************************************
; SOUS-ROUTINE DE TEMPORISATION
;*********************************************************************
;--------------------------------------------------------------------------------------------------------
; Cette sous-routine introduit un retard
; Elle ne reçoit aucun paramètre et n'en retourne aucun
;--------------------------------------------------------------------------------------------------------
tempo      
  clrf cmpt2 ; effacer compteur2
boucle2      
  clrf cmpt1 ; effacer compteur1
boucle1      
  decfsz cmpt1 , f ; décrémenter compteur1
  goto boucle1 ; si pas 0, boucler
  decfsz cmpt2 , f ; si 0, décrémenter compteur 2
  goto boucle2 ; si cmpt2 pas 0, recommencer boucle1
  return   ; retour de la sous-routine

Nous ne nous sommes donc pas servi de la variable cmpt3. Nous pouvons donc la supprimer de notre zone des variables. Tant que nous y sommes, nous allons avoir besoin d’un flag, c’est à dire d’un bit. Créons-le dans cette zone.

;*********************************************************************
; DECLARATIONS DE VARIABLES
;*********************************************************************
       
  CBLOCK 0x00C ; début de la zone variables
    w_temp : 1 ; Sauvegarde de W dans interruption
    status_temp : 1 ; Sauvegarde de STATUS dans interrupt
    cmpt1 : 1 ; compteur de boucles 1 dans tempo
    cmpt2 : 1 ; compteur de boucles 2 dans tempo
    flags : 1 ; un octet pour 8 flags
      ; réservons b0 pour le flag tempo
      ; b1 : libre
      ; b2 : libre
      ; b3 : libre
      ; b4 : libre
      ; b5 : libre
      ; b6 : libre
      ; b7 : libre
       
  ENDC   ; Fin de la zone
       
#DEFINE tempoF flags , 0 ; Définition du flag tempo

Modifions notre programme principal en suivant notre ordinogramme. Nous obtenons :

;*********************************************************************
; PROGRAMME PRINCIPAL
;*********************************************************************
start      
  btfss tempoF ; tester si tempo flag mis
  goto start ; non, attendre qu'il soit mis
  call tempo ; oui, exécuter tempo
  bcf tempoF ; effacer flag tempo
  bsf INTCON , INTE ; remettre interrupts INT en service
  goto start ; boucler
       
  END   ; directive fin de programme

Il ne reste plus qu’à modifier notre routine d’interruption en fonction de notre ordinogramme. Nous obtenons :

;**********************************************************************
; INTERRUPTION RB0/INT
;**********************************************************************
;---------------------------------------------------------------------------------------------------------
; inverse le niveau de RA2 à chaque passage
; interdit toute nouvelle interruption
; valide le flag tempo
;---------------------------------------------------------------------------------------------------------
intrb0      
  movlw B'00000100' ; bit positionné = bit à inverser
  BANK0   ; car on ne sait pas sur quelle banque
      ; on est dans une interruption (le
      ; programme principal peut avoir changé
      ; de banque. Ce n'est pas le cas ici,
      ; mais c'est une sage précaution
  xorwf PORTA , f ; inverser RA2
  bcf INTCON , INTF ; effacer flag INT/RB0
  bcf INTCON , INTE ; interdire autre inter. RB0
  bsf tempoF ; positionner flag tempo
  return   ; fin d'interruption RB0/INT

Compilons notre programme et chargeons le nouveau fichier .hex dans notre PIC.

Lançons l’alimentation. Cela ne fonctionne toujours pas, pourquoi ? Et bien réfléchissons à ce qui se passe.

Une fois la routine d’interruption terminée, les interruptions sont invalidées. L’interrupteur rebondi sans causer d’appel d’interruption. MAIS SON FLAG ET POSITIONNE à cause des rebonds. Souvenez-vous que les bits Enable n’agissent pas sur les flags. Donc, dès que le programme principal remet l’interruption en service, une interruption est générée directement, INTF ayant été positionné avant.

Vous devez donc penser à tout lorsque vous utilisez les interruptions. Si un programme fonctionne au simulateur et pas sur le circuit réel, commencez par soupçonnez des problèmes de timing ?

Il nous suffit donc d’ajouter une le reset du flag INTF avant de remettre les interruptions INT en service.

  bcf INTCON , INTF ; effacer flag INT

Recompilez votre programme, et rechargez-le dans votre PIC. Alimentez votre montage.

Cette fois cela fonctionne parfaitement. Voici un montage très pratique à utiliser. Si vous remplacez la LED par un petit relais ou un triac optocouplé, vous voilà en possession d’un télérupteur. Placez un bouton-poussoir dans chaque endroit d’où vous désirez allumer la lampe, et vous pouvez allumer ou éteindre celle-ci depuis plusieurs endroits, sans utiliser d’interrupteurs spéciaux avec un tas de fils à tirer.

A titre de bonus, voici le schéma d’un télérupteur fonctionnant avec votre programme.

Remarque sur notre programme

Nous avons utilisé une temporisation de valeur quelconque. Cette temporisation inhibe toute action sur le B.P. (c’est son but) durant +- 250mS. Donc, vous pourrez presser au grand maximum 4 fois sur le B.P. par seconde.

Mais quelle est la durée réelle des rebonds ? Et bien, elle dépend de l’interrupteur utilisé, technologie et taille. Pour connaître le temps de rebond de votre propre interrupteur, diminuez progressivement la durée de la temporisation. Une fois que votre télérupteur ne fonctionne plus à chaque pression, vous avez atteint la limite. Calculez alors la durée de votre temporisation, vous aurez la durée approximative des rebonds.
 

12.19 Remarques importantes

Souvenez-vous, qu’une fois que vous utilisez les interruptions dans un programme, vous ne pouvez jamais savoir le temps qui va séparer 2 instructions successives.

En effet, entre les instructions en question peut avoir été traité une ou plusieurs routine(s) d’interruption.

En conséquence, VOUS NE POUVEZ PAS UTILISER de calcul de temps en utilisant le calcul du nombre d’instructions dans toute partie de code dans lequel une interruption risque de survenir.

Notre sous-routine de temporisation de " Led-cli ", par exemple, donnerai un délai qui risquerait d’être allongé. Dans ce programme, cependant, les interruptions ne sont plus en service au moment de l’exécution de cette temporisation. Voyez l’ordinogramme.

De plus, pour toute séquence dont le déroulement en temps est critique et ne peut être interrompu, vous devez inhiber les interruptions. A votre charge de les remettre en service en temps utile.

Une fois que vous utilisez les interruptions, votre programme devra affronter des événements asynchrones avec son déroulement. Donc, vous ne pourrez JAMAIS tester toutes les éventualités possibles. VOUS NE POUVEZ DONC JAMAIS ETRE SUR PAR SIMULATION QUE VOTRE PROGRAMME EST COMPLETEMENT DEBUGGE.

Ceci vous explique pourquoi des gestions de processus critiques en temps réel utilisent plusieurs ordinateurs (navette spatiale). Ces ordinateurs étant désynchronisés, un bug de cette nature qui apparaît sur un d’eux a peu de chance de se produire simultanément sur un second. Pour savoir lequel a posé problème, il faut donc un troisième ordinateur.

Pensez toujours à ceci si vous êtes amenés un jour à réaliser un programme dont dépend la sécurité de personnes ou de biens.
 

12.20 Conclusions

Au terme de ce chapitre, vous pouvez appréhender les mécanismes d’interruption et leur mise en œuvre. Nous utiliserons encore cette possibilité dans la leçon sur le timer.

J’espère avoir démystifié ce concept, trop souvent imaginé comme " la méthode des pros ". En réalité, une fois de plus, pas de magie. Ce n’est qu’une exploitation, certes parfois complexe, de processus simples et faciles à appréhender.

Gardez seulement à l’esprit que vous quittez le monde du synchrone pour entrer dans le monde de l’asynchrone, beaucoup plus difficile à simuler totalement de façon efficace. L’utilisation des interruptions impose de ce fait la parfaite compréhension des mécanismes mis en place, et vous oblige à envisager toutes les possibilités au niveau de la survenance d’un événement extérieur. Ceci est surtout vrai pour les programmes avec plusieurs sources d’interruptions.
 

 

Index cours
Index du cours
Chapitre précédent
12. Les interruptions (Suite 1)
Chapitre suivant
13. Le Timer 0

 


Complétez cette page, posez vos questions et remarques ici : WiKiFri

Page http://fribotte.free.fr/bdtech/cours/pic16f84/PART1_cours12c.html modifiée le 14/10/2002.
Copyright fribotte@free.fr, libre de droit pour toute utilisation non commerciale.
Reproduction autorisée par simple mail