<< 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 5

Téléchargement des fichiers originels ici
Publié le 27/02/2002

12. Les interruptions (Suite 1)

Index coursIndex du cours
Chapitre précédent12. Les interruptions
Chapitre suivant12. Les interruptions (Suite 2)

 

12. Les interruptions (Suite 1)
12.8 Utilisation d’une routine d’interruption


Effectuez une copie de votre fichier m16f84.asm. Renommez-la en " myinter.asm ". Créez un nouveau projet MPLAB avec le nom  " myinter.pjt " suivant le processus habituel. [ NDLR : Fichiers exemples ici ]

Nous allons construire un programme qui inverse l’allumage d’une LED à chaque pression sur un bouton-poussoir. Modifiez votre petite platine d’expérimentation de façon à connecter le bouton-poussoir sur l’entrée RB0 (désolé, quand j’ai dessiné le schéma, je pensais expliquer les interruptions avec le timer).

Ceci vous montre cependant que l’électronique et la programmation avec les microcontrôleurs sont très dépendants. Quand vous construisez un circuit, vous devez déjà penser à la manière dont vous allez réaliser la programmation. A l’inverse, si vous disposez d’un circuit déjà existant, des contraintes vous sont déjà imposées au niveau de la programmation. Il est par exemple impossible de traiter notre bouton-poussoir par interruption s’il est câblé sur l’entrée RB2.

Comme d’habitude, remplissez le cadre d’en-tête, et supprimez les lignes inutilisées dans les variables, macro, et DEFINE. Attention, ne supprimez pas les variables w_temp et status_temp.

Voici l’en-tête tel que vous pourriez le compléter :

;***************************************************************************
; Ce programme est un programme didactique destiné à monter
; le fonctionnement des interruptions
;
;***************************************************************************
;
; NOM: Interruption par bouton-poussoir sur RB0
; Date: 13/02/2001
; Version: 1.0
; Circuit: Platine d'essais
; Auteur: Bigonoff
;
;***************************************************************************
;
; Fichier requis: P16F84.inc
;
;***************************************************************************
;
; Notes: Ce programme transforme un bouton-poussoir en
; télérupteur. Un pulse allume la LED, un autre
; l’éteint
;
;***************************************************************************

Vous allez dire que j’exagère en vous faisant mettre des commentaires partout. Croyez-moi sur parole, les commentaires permettent une maintenance aisée du programme. Ils vous feront gagner à coup sûr beaucoup plus de temps qu’ils ne vous en feront perdre.

Modifiez la config pour supprimer le fonctionnement du watch-dog :

  __CONFIG _CP_OFF & _WDT_OFF & _PWRTE_ON & _HS_OSC

Calculons la future valeur à envoyer dans le registre OPTION

  • b7 à 0, car on a besoin de la résistance de rappel au +5V pour le bouton-poussoir.
  • b6 à 0, car on veut une interruption quand presse le bouton, donc quand le niveau passe de 1 à 0 (flanc descendant)
  • b5/b0 à 0, aucune importance dans cette application.

Donc, nous placerons notre assignation à B’00000000’, donc :

;*********************************************************************
; ASSIGNATIONS
;*********************************************************************
       
OPTIONVAL EQU H'0000' ; Valeur registre option
      ; Résistance pull-up ON
      ; Interrupt flanc descendant RB0

Pour le registre INTCON, nous devrons avoir :

  • b7 à 1 : pour valider les interruptions
  • b6 à 0 : pas d’interruption EE
  • b5 à 0 : pas d’interruption tmr0
  • b4 à 1 : interruption RB0 en service
  • b3 à 0 : Pas d'interruption RB
  • b2/b0 à 0 : effacer les flags

Cela donne : B’10010000’, soit 0x90

Assignons une constante pour cette valeur :

INTERMASK EQU H'0090' ; Masque d'interruption
      ; Interruptions sur RB0

Ensuite, dans la zone des DEFINE, nous définirons notre LED et notre bouton-poussoir :

;*********************************************************************
; DEFINE
;*********************************************************************
               
#DEFINE Bouton PORTB , 0 ; bouton poussoir
#DEFINE LED PORTA , 2 ; LED

Dans la zone macros , nous pouvons écrire 2 macros que nous utiliserons souvent. Ce sont les instructions pour passer en banque 0 et banque1. Elles seront incluses dans le nouveau fichier " m16f84.asm " nommé " m16f84_new.asm ". A la fin de cette leçon, supprimez l’ancien fichier et renommez " m16F84_new " en " m16f84 ".

;*********************************************************************
; MACRO
;*********************************************************************
               
BANK0 macro    
  bcf STATUS , RP0 ; passer en banque 0
  endm    
       
BANK1 macro    
  bsf STATUS , RP0 ; passer en banque1
  endm    

Venons-en à la zone des variables. Nous devons garder w_temp et status_temp, car ces variables sont utilisées dans la routine d’interruption pour sauver les registres W et STATUS. Nous allons également récupérer notre petite routine de tempo de notre fichier " led_cli.asm ". Nous aurons donc besoin des variables utilisées dans cette routine. Tout ceci nous donne donc pour l’instant :

;*********************************************************************
; 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
    cmpt3 : 1 ; compteur de boucles 3 dans tempo
  ENDC   ; Fin de la zone


12.9 Analyse de la routine d’interruption

Nous avons déjà vu la première partie, qui est la sauvegarde des registres utilisés

;**********************************************************************
; ROUTINE INTERRUPTION
;**********************************************************************
       
    ;sauvegarder registres
    ;--------------------------
  org 0x004 ; adresse d'interruption
  movwf w_temp ; sauver registre W
  swapf STATUS , w ; swap status avec résultat dans w
  movwf status_temp ; sauver status swappé

Ensuite, nous devons déterminer quelle est l’origine de l’interruption en cours. Dans le cas présent, il s’agit obligatoirement de l’interruption RB0/INT, car nous n’avons autorisé que celle-là. Dans le cas présent, nous pourrions donc nous passer de ce test. Mais le but ici est de vous expliquer les méthodes à utiliser. Nous garderons donc la totalité des explications afin que vous puissiez utiliser n’importe quelle combinaison. Examinons donc cette partie :

    ; switch vers différentes interrupts
    ; inverser ordre pour modifier priorités
    ;----------------------------------------
  btfsc INTCON,T0IE ; tester si interrupt timer autorisée
  btfss INTCON,T0IF ; oui, tester si interrupt timer en cours
  goto intsw1 ; non test suivant
  call inttimer ; oui, traiter interrupt timer
  goto restorereg ; et fin d'interruption
      ; SUPPRIMER CETTE LIGNE POUR
      ; TRAITER PLUSIEURS INTERRUPT
      ; EN 1 SEULE FOIS

Les 2 premières instructions examinent si nous avons affaire à une interruption tmr0. Vous allez me dire : pourquoi 2 lignes ? Il suffit d’examiner T0IF, tout simplement.

Et bien, ce n’est pas si simple. Imaginons en effet que le tmr0, que nous n’utilisons pas, ait débordé. Le bit T0IF est donc mis à 1 et n’a pas généré d’interruption, car T0IE est à 0. De même, si nous avions accepté les interruptions timer et que l’interruption soit due à une autre cause, nous aurions T0IE à 1 et T0IF à 0.

Nous ne devons donc traiter l’interruption que si l’interruption est en service, et que si le flag est positionné, d’où le double test. Examinez ce double test et observez le fonctionnement des btfsc et btfss :

Si T0IE vaut0, on saute directement au test suivant par la ligne goto. S’il vaut 1, on teste ensuite T0IF. Si celui-ci vaut 0, on arrive à la ligne goto qui passe au test suivant. S’il vaut également 1, au appelle la sous-routine de traitement de l’interruption timer0.

La dernière ligne permet de sauter à la fin de la routine d’interruption, donc de restaurer les registres et de sortir. Donc, dans le cas où on aurait deux sources d’interruptions simultanées, une seule serait traitée à la fois. Si par contre on supprime cette ligne, l’interruption suivante sera traitée dès celle en cours terminée. J’ai donc étudié cette ossature pour vous laisser toutes les variantes possibles, et directement utilisables.

Remarques

  • Si on n’utilise jamais l’interruption tmr0, on peut supprimer cette partie de code
  • Si l’interruption tmr0 était en service tout au long du programme, on pourrait supprimer le test de T0IE (car il serait tout le temps à 1)
  • En cas de 2 interruptions simultanées de 2 événements distincts, la première interruption traitée sera celle testée en premier. L’ordre des tests modifie donc la priorité des interruptions.

Ensuite, nous trouvons la même procédure pour les interruptions de type RB0 (dont nous allons nous servir) et d’interruption RB4/RB7. Remarquez l’emplacement prévu pour ajouter le test pour l’interruption eeprom. Nous compléterons m16F84.asm à ce niveau en étudiant les procédures d’écriture en eeprom. Toutes ces modifications ont été incluses dans le fichier " m16f84_new.asm ".

intsw1      
  btfsc INTCON , INTE ; tester si interrupt RB0 autorisée
  btfss INTCON , INTF ; oui, tester si interrupt RB0 en cours
  goto intsw2 ; non sauter au test suivant
  call intrb0 ; oui, traiter interrupt RB0
  bcf INTCON,INTF ; effacer flag interupt RB0
  goto restorereg ; et fin d'interruption
      ; SUPPRIMER CETTE LIGNE POUR
      ; TRAITER PLUSIEURS INTERRUPT
      ; EN 1 SEULE FOIS
       
intsw2      
  btfsc INTCON,RBIE ; tester si interrupt RB4/7 autorisée
  btfss INTCON,RBIF ; oui, tester si interrupt RB4/7 en cours
  goto intsw3 ; non sauter
  call intrb4 ; oui, traiter interrupt RB4/7
  bcf INTCON,RBIF ; effacer flag interupt RB4/7
  goto restorereg ; et fin d'interrupt
       
intsw3 ; ici, la place pour l’interruption eeprom

Enfin, nous trouvons la partie servant à la restauration des registres sauvegardés. Nous avons déjà vu cette procédure :

    ;restaurer registres
    ;----------------------
restorereg      
  swapf status_temp,w ; swap ancien status, résultat dans w
  movwf STATUS ; restaurer status
  swapf w_temp,f ; Inversion L et H de l'ancien W
      ; sans modifier Z
  swapf w_temp,w ; Ré-inversion de L et H dans W
      ; W restauré sans modifier status
  retfie   ; return from interrupt


12.10 Adaptation de la routine d’interruption

Nous allons maintenant modifier cette routine d’interruption pour l’adapter à notre cas précis. Nous n’avons qu’une seule source d’interruption validée, donc, si nous entrons dans cette interruption, ce sera forcément pour traiter INT/RB0. Supprimons donc les tests.

Il nous reste donc :

;**********************************************************************
; ROUTINE INTERRUPTION
;**********************************************************************
       
    ;sauvegarder registres
    ;--------------------------
  org 0x004 ; adresse d'interruption
  movwf w_temp ; sauver registre W
  swapf STATUS , w ; swap status avec résultat dans w
  movwf status_temp ; sauver status swappé
       
  call intrb0 ; traiter interrupt RB0
       
    ;restaurer registres
    ;----------------------
       
  swapf status_temp,w ; swap ancien status, résultat dans w
  movwf STATUS ; restaurer status
  swapf w_temp,f ; Inversion L et H de l'ancien W
      ; sans modifier Z
  swapf w_temp,w ; Ré-inversion de L et H dans W
      ; W restauré sans modifier status
  retfie   ; return from interrupt

Nous pourrions également nous passer de la ligne call intrb0, et placer la procédure de traitement directement à sa place. Conservons cependant cet appel de sous-routine, car nous ne sommes pas à quelques instructions près. En cas où vous devriez optimiser une application à l’extrême, vous pourriez y penser. Dans le cas contraire, je vous conseille de donner priorité à la lisibilité de votre programme (oui, j’insiste encore).

Voyons maintenant la suite du programme. Nous allons trouver les 3 routines de traitement des interruptions appelées à l’origine par notre routine d’interruption. Comme nous avons supprimé 2 de ces appels, inutile de conserver les sous-routines correspondantes. La seule dont nous auront besoin sera donc :

;**********************************************************************
; INTERRUPTION RB0/INT
;**********************************************************************
intrb0      
  return   ; fin d'interruption RB0/INT
      ; peut être remplacé par
      ; retlw pour retour code d'erreur

Elle ne contient que le retour de sous-routine correspondant à l’appel du call intrb0. Attention, à ce niveau vous n’utiliserez pas RETFIE, car vous ne sortez pas de l’interruption, vous sortez d’une sous-routine appelée par la routine d’interruption.

Si vous utilisiez RETFIE à ce moment, vous remettriez les interruptions en service avant d’en sortir, donc plantage de votre programme. Remarquez que vous pourriez utiliser retlw pour retourner une valeur prédéfinie traitée dans votre routine d’interruption. Par exemple, retlw 0 si on veut traiter d’autres interruptions et retlw 1 si on sort de la routine. Dans ce cas, la ligne

  goto restorereg ; et fin d'interruption

correspondante pourrait être précédée du test de w retourné par la sous-routine. Ceci est un exemple un peu complexe à traiter ici, mais parfaitement envisageable lorsque vous jonglerez avec les PICs.

12.11 L’initialisation

Poursuivons : nous trouvons la routine d’initialisation que nous avons déjà expliquée la leçon précédente. Nous nous contenterons de remplacer les changements de banque par les macros que nous avons écrites , et à configurer RA2 en sortie pour la LED.

Nous obtenons :

;*********************************************************************
; INITIALISATIONS
;*********************************************************************
       
init      
  clrf PORTA ; sorties portA à 0
  clrf PORTB ; sorties portB à 0
  BANK1   ; passer banque1
  clrf EEADR ; permet de diminuer la consommation
  movlw OPTIONVAL ; charger masque
  movwf OPTION_REG ; initialiser registre option
       
    ; Effacer RAM  
    ;----------------  
  movlw 0x0c ; initialisation pointeur
  movwf FSR ; pointeur d'adressage indirect
init1      
  clrf INDF ; effacer ram
  incf FSR,f ; pointer sur suivant
  btfss FSR,6 ; tester si fin zone atteinte (>=0x40)
  goto init1 ; non, boucler
  btfss FSR,4 ; tester si fin zone atteinte (>=0x50)
  goto init1 ; non, boucler
       
    ; configurer PORTS
    ; ---------------
  bcf LED ; RA2 en sortie (TRISA)
       
  BANK0   ; passer banque0
  movlw INTERMASK ; masque interruption
  movwf INTCON ; charger interrupt control
  goto start ; sauter programme principal

Il ne nous reste plus qu’à supprimer la ligne

  clrwdt   ; effacer watch dog

du programme principal, puisque nous avons désactivé le watchdog.

Remarque

Une grande partie des erreurs dans les programmes sont provoqués par des erreurs de sélection de banques (surtout pour les PICs à 4 banques). Je vous conseille d’adopter comme convention de toujours entrer dans une routine avec la banque0, et de toujours s’assurer que vous êtes en banque0 avant d’en sortir. En cas de dérogation, indiquez-le clairement dans l’en-tête de votre sous-routine.

Lancez la compilation pour vérifier que vous n’avez pas fait d’erreurs. Vous devriez obtenir ceci (au numéro de ligne près) :

Building MYINTER.HEX...

Compiling MYINTER.ASM:
Command line: "C:\PROGRA~1\MPLAB\MPASMWIN.EXE /e+ /l+ /x- /c+ /rdec /p16F84 /q D:\DOCUME~1\LESSONS\DATAPIC\MYINTER.ASM"
Message[302] D:\DOCUME~1\LESSONS\DATAPIC\MYINTER.ASM 143 : Register in operand not in bank 0. Ensure that bank bits are correct.

Build completed successfully.

Je ne reviendrai pas ici sur les warnings.

12.12 Construction du programme principal

Nous allons maintenant réaliser un télérupteur. Qu’est-ce à dire ? Et bien nous allons réaliser la fonction suivante : Une pression sur le bouton-poussoir allume la LED, une seconde pression l’éteint. Je vais vous guider pas à pas dans cette petite réalisation, en essayant de vous montrer les problèmes pratiques rencontrés dans une réalisation de ce type.

Je rappelle que ceci est un programme didactique. Nous réaliserons ce télérupteur de manière un peu plus élégante dans la leçon sur le timer0.

Puisque nous désirons utiliser les interruptions, l’inversion de l’allumage de la LED se fera dans la routine d’interruption du bouton-poussoir. Dans un premier temps, nous pourrions penser que le programme principal n’a rien à faire.

Remarque très importante

En aucun cas vous ne pouvez laisser un programme se " balader " hors de la zone de votre programme. Si le programme n’a plus rien du tout à faire, vous devez alors le boucler sur lui-même. Car le programme ne s’arrête pas (sauf passage en mode sleep que nous verrons plus tard).

Notre programme principal pourrait donc être de la forme :

start      
  goto start ; boucler

Mais, pour faciliter la visualisation de ce qui se passe sous l’émulateur, je vous demanderai d’ajouter quelques ‘NOP’ inutiles.

Voilà donc notre programme principal :

;*********************************************************************
; PROGRAMME PRINCIPAL
;*********************************************************************
start      
  nop   ; instruction inutile
  nop   ; instruction inutile
  nop   ; instruction inutile
  nop   ; instruction inutile
  nop   ; instruction inutile
  goto start ; boucler
       
  END   ; directive fin de programme


12.13 Construction de la routine d’interruption

Nous avons programmé la PIC de façon à ce qu’une interruption sur flanc descendant de INT/RB0 provoque une interruption. Il nous suffit donc d’exécuter dans cette routine d’interruption l’inversion du niveau de la LED. Ceci est réalisé très simplement avec un " ou exclusif ". Nous pouvons donc écrire :

;**********************************************************************
; INTERRUPTION RB0/INT
;**********************************************************************
;---------------------------------------------------------------------------------------------------------
; inverse le niveau de RA2 à chaque passage
;---------------------------------------------------------------------------------------------------------
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
  return   ; fin d'interruption RB0/INT

Voilà, notre premier essai est terminé. Nous allons passer ce programme au simulateur. Mais avant, je vous donne l’ordinogramme du programme que nous avons réalisé. Lorsque vos programmes deviendront complexes, je vous conseille de recourir à l’ordinogramme avant de les écrire.

Ordinogramme 1 (version théoriquement fonctionnelle)

Vous voyez que cette routine d’interruption est on ne peut plus simple, et correspond bien à ce qu’on pourrait s’imaginer de prime abord. Nous allons donc commencer par passer le test du simulateur.

 

Index cours
Index du cours
Chapitre précédent
12. Les interruptions
Chapitre suivant
12. Les interruptions (Suite 2)

 


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

Page http://fribotte.free.fr/bdtech/cours/pic16f84/PART1_cours12b.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