Changes between Initial Version and Version 1 of li326-ProgPic


Ignore:
Timestamp:
Jan 31, 2014, 4:01:32 PM (11 years ago)
Author:
Franck Wajsburt
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • li326-ProgPic

    v1 v1  
     1{{{
     2#!protected
     3{{{
     4#!html
     5<h1 align=center><u>
     6Programmation en assembleur PIC : directives, macro-instructions, boucles, switch-case
     7</u></h1>
     8}}}
     9[[PageOutline]]
     10
     11= Objectifs =
     12
     13Le but de ce second TME est de vous faire appréhender le problème lié à la gestion de la mémoire dans un microcontrôleur en général
     14et dans un pic en particulier. Vous aborderez une méthode pour l'écriture de fonctions concernant le problème lié aux passages
     15des paramètres.  Vous verrez également une méthode pour introduire le temps réel dans vos programmes par l'intermédiaire de boucles
     16de temporisation.
     17
     18Vous trouverez le programme dont le code est donné en fichier attaché
     19[attachment:hello4.asm hello4]
     20
     21= mkpic: un script pour la compilation de projets pic =
     22
     23Vous utiliserez désormais un script nommé '''mkpic''' pour toutes les opérations d'assemblage, de simulation ou de programmation.
     24Nous verrons les avantages de ce script au fur et à mesure des TME, pour l'heure contentez-vous de lancer:
     25{{{
     26   mkpic a        -> pour faire l'assemblage
     27   mkpic s        -> pour faire l'appel du simulateur
     28   mkpic u        -> pour faire le chargement (upload) du programme dans le pic
     29}}}
     30'''mkpic''' impose quelques contraintes méthodologiques. La plus importante est qu'il suppose qu'il n'y a
     31'''qu'un seul programme par répertoire''' (au sens d'une seule application), et que le
     32'''nom du programme doit être le nom du répertoire'''
     33avec l'extension .asm en plus. Un programme ne signifie pas obligatoirement un seul fichier, puisque le fichier racine, du
     34nom du programme peut inclure d'autres fichiers de noms quelconques .asm ou .inc. Grâce à cette contrainte, il est inutile
     35de donner le nom du programme à mkpic, il le trouve tout seul.
     36
     37L'appel à mkpic sans paramètre donne:
     38{{{
     39   Script d'assemblage - simulation - programmation pour PIC ver 2.10
     40   -----------------------------------------------------------------
     41
     42   Usage   : mkpic <command> [F=file] [P=processor] [M=on|off|debug] [W=0|1|2]
     43                             [B="labels..."] [A="nbcycles..."] [C=cmdfile]
     44                             [I="include_dir..."] [D="SYM=VAL SYM=VAL..."]
     45                             [E=0|1] [X=0|1]
     46
     47   ... la suite en annexe.
     48}}}
     49
     50= La mémoire =
     51
     52== Les différents types de mémoire du pic16f877 ==
     53
     54Nous avons déjà vu que le pic 16f877 dispose de quatre types de mémoire: pour le code programme, pour les données volatiles, pour les adresses de retours (la pile), pour des données persistantes.
     55
     561. La mémoire de programme (8kmots de 14bits):
     57  La mémoire de programme est composée de 4 pages de 2k-instructions de 14 bits.
     58 
     592. La mémoire non volatile de données (256mots de 8bits):
     60  La mémoire non volatile de données (RAM) est constituée d'une eeprom flash de 256 octets. Son principal avantage est que ses données sont conservées en l'absence de courant,
     61  de même pour la mémoire de programme. En revanche l'accès à cette mémoire se fait par une procédure nécessitant plusieurs cycles en lecture et plusieurs dizaines de cycles en
     62  écriture. Cette caractéristique réduit l'usage de cette mémoire à contenir des paramètres généraux et pas des variables.  Sachez aussi que, dans le pic que vous utilisez, la mémoire
     63  de programme peut également servir de mémoire de données. En effet, elle est de même nature, c.-à-d. de la flash. En outre, sa lecture est plus rapide et elle est plus grande. C'est pourquoi
     64  elle est souvent utilisée pour y placer des données. Cependant, le nombre d'écritures est limité de 100000 à un million de fois.
     65
     663. La pile des adresses de retour (8mots de 13bits):
     67  Le pic dispose d'une instruction call qui commence par sauvegarder dans une mémoire spécifique, la pile, l'adresse de l'instruction
     68  suivante et qui saute ensuite à l'adresse passée en paramètre. La pile est également utilisée lors d'une interruption, lorsque le programme en cours est stoppé temporairement,
     69  pour exécuter le gestionnaire d'interruption. Cette pile, ne fait pas partie des registres, elle ne peut être ni lue ou ni écrite normalement. Elle fait 8 cases. Le seul moyen de la
     70  lire est de dépiler l'adresse enregistrée au sommet par une instruction, ret, retlw, ou retfie qui toutes trois restaurent le compteur ordinal (PC) avec l'adresse lue.  Disons tout
     71  de suite, que ce mode de fonctionnement limité de la pile empèche son utilisation pour le passage des paramètres aux fonctions.
     72
     734. La mémoire volatile de données (512mots de 8bits, dont 368 pour l'utilisateur):
     74  La mémoire volatile des données est composée de 4 bancs de 128 octets.
     75  La mémoire des données est en fait un ensemble de registres organisé en 4 bancs correspondant à 128 adresses soit 512 adresses de registres pour le pic16f877, de `0` à `0x7F`, de `0x80` à `0xFF`, de `0x100` à `0x17F`, et de `0x180` à `0x1FF`.
     76  Les 512 adresses dipsonibles ne correspondent pas à 512 registres, et ce pour deux raisons. La première est qu'à certaines  adresses, il n'y a pas de registres. La seconde est que certains registres répondent à plusieurs adresses, par exemple le
     77  registre STATUS est accessible via 4 adresses distinctes: `0x03`, `0x83`, `0x103`, `0x183`, en fait il a une adresse
     78  par banc, pour toutefois un seul registre.
     79
     80== On distingue 2 types de registres ==
     81
     82 '''Les registres «spéciaux»'''::
     83   comme `TRISD` ou `PORTD`, dont la fonction est prédéfinie. Ils permettent d'accéder aux ressources internes du microcontrôleur. Nous les verrons au fur et à mesure des TME. Je vous conseille toutefois de
     84   faire une lecture «diagonale» (i.e. rapide) de l'ensemble des registres dès maintement afin de vous faire une idée de l'organisation du pic16f877.
     85 '''Les registres «normaux»'''::
     86   l'utilisateur peut les utiliser à sa guise. Le pic16f877 en compte 368. Sur les 368, 16 ont 4 adresses, une par banc, ce sont les registres de 0x70 à 0x7F. Bien que rien ne l'impose, nous réserverons ces registres
     87   partagés à un usage spécifique pour profiter de ces adresses multiples.
     88
     89 Remarque::
     90   Notez bien que l'architecture du pic peut surprendre pour ceux qui sont habitués à une organisation de la mémoire plus classique,
     91   avec d'un coté les registres internes (quelques dizaines au plus) et de l'autre la mémoire externe où se trouve programmes
     92   et données.  En effet, les pics sont des microcontrôleurs qui cherchent à se suffire à eux-mêmes (c.-à-d. sans besoin de
     93   composants externes si ce n'est pour des raisons d'adaptation électrique). C'est pourquoi toute la mémoire est interne, et
     94   le choix à été fait d'augmenter le nombre de registres afin de se passer de mémoire externe. En fait, il est tout de même
     95   possible d'avoir de la mémoire externe mais avec un accès plus lent.
     96
     97== Les deux modes d'accès aux registres ==
     98
     99 '''L'accès direct'''::
     100   pour lequel le numéro du registre se trouve directement dans l'instruction. Malheureusement, et c'est là
     101   une petite difficulté que vous avez déjà vue, l'instruction ne contient que les 7 bits de poids faibles de l'adresse absolue (0
     102   à 511).
     103   Les 2 bits de poids forts sont dans le registre STATUS, aux places RP1(bit 6) et RP0(bit 5). Ces bits sont concaténés aux bits
     104   contenus dans l'instruction, pour former l'adresse absolue sur 9 bits.
     105 '''L'accès indirect registres'''::
     106   pour lequel le numéro du registre à accéder se trouve dans un autre registre. Il n'y a qu'un seul
     107   registre qui peut être utilisé pour faire un accès indirect, c'est le registre FSR (dont l'adresse est 0x04, mais aussi 0x84,
     108   0x104 et 0x184).
     109   Plus précisément, pour faire un accès indirect, on place le numéro du registre à accéder dans le registre FSR, et on peut alors
     110   accéder en lecture ou én écriture à ce registre en utilisant le registre symboliquement notée INDF (0, mais aussi 0x80,
     111   0x100, 0x180). Autrement dit quand on accède à l'adresse INDF, on accède en fait à l'adresse contenue dans le registre FSR.
     112   Toutefois, encore une difficulté, le registre FSR a un format de 8 bits et il en faut 9 pour désigner un registre. Là
     113   encore, on va chercher le bit manquant dans le registre STATUS, à la place IRP (bit 7).
     114
     115== La réservation des registres «normaux» (utilisateur) ==
     116
     117Les 368 registres utilisateur sont libres, au sens où le matériel n'impose pas de contrainte sur leur rôle. Toutefois nous allons devoir les
     118allouer aux programmes et nous allons nous imposer des contraintes de programmation (autant le dire tout de suite, ces contraintes sont
     119discutables, c.-à-d. que d'autres programmeurs en auraient imposés des différentes). D'ailleurs nous nous autoriserons quelquefois à ne
     120pas les respecter. Enfin, quelles qu'elles soient, les contraintes de programmation ont pour but de rendre les programmes plus
     121cohérents, plus lisibles et donc plus fiables. Elles permettent également de faire du partage avec les autres programmeurs, ou du
     122«design reuse» très à la mode dans certains millieux.
     123
     124Nous introduirons nos contraintes au fur et à mesure des séances, disons pour l'heure que les registres partagés seront utilisés
     125entre autre pour le passage des paramètres aux fonctions, et pour la sauvegarde du contexte lors des interruptions.
     126
     127L'allocation des registres utilisateur pose un problème d'organisation. Il y a deux choix possibles:
     128
     129 1. On fait une allocation statique et globale de tous les registres du programme, en prenant garde à ce qu'il n'y ait pas de
     130    collisions entre adresses allouées.
     131 2. On fait une allocation locale, par fonction ou par tâche, en laissant un programme ou une méthode faire en sorte qu'il n'y ait
     132    pas de collisions. Dans ce cas, il y a encore deux possibilités:
     133     a. On dispose d'un assembleur relogeable, qui ne choisit pas les adresses des registres alloués, qui produit du code objet
     134        (comme le fait un compilateur C) et qui utilise un éditeur de liens pour produire le code exécutable en résolvant le problème
     135        d'allocation mémoire.
     136     b. On dispose d'une méthode d'allocation qui assure la non collision par construction.
     137
     138Ces deux approches sont possibles puisque, gpasm peut produire du code relogeable, et nous vous proposerons une méthode de
     139non collision.
     140Pour ce TME, nous allons utiliser l'allocation statique et globale, en utilisant la directive assembleur `CBLOCK`
     141{{{
     142CBLOCK adr
     143  nom1 : long1     ; réserve nom1 à l'adresse adr
     144  nom2 : long2     ; réserve nom2 à l'adresse adr + long1
     145ENDC
     146CBLOCK
     147  nom3 : long3     ; réserve nom3 à l'adresse adr + long1 + long2
     148  nom4 : long4     ; réserve nom4 à l'adresse adr + long1 + long2 + long3
     149ENDC
     150}}}
     151
     152= Le comptage du temps =
     153
     154Nous allons reprendre le programme `hello3` vu lors de la précédente séance, mais
     155nous voulons maintenant ralentir le déplacement du bit à 1 dans le PORTD afin de le rendre visible à l'oeil, en supposant que
     156l'on relie des leds sur les broches associées au PORTD. Pour ce faire, nous allons réaliser une boucle d'attente active.
     157Cette attente est dite active car le processeur est effectivement occupé à compter et ne peut rien faire d'autre. Nous verrons
     158qu'il existe un moyen plus efficace de réaliser les attentes en utilisant des périphériques spécialisés nommés ''timers'' qui
     159évitent de monopoliser le processeur.
     160
     161La boucle d'attente se fait dans la routine wait200us. Comme vous le voyez sur le listing la boucle consiste à remplir un registre avec une
     162valeur calculée de manière précise, puis à le décrémenter jusqu'à ce qu'il atteigne 0. La question est combien faut-il faire de tours de
     163boucle pour que la fontion dure 200µs.
     164
     165Le pic de notre maquette est cadencé par une horloge à 20MHz, soit une période élémentaire de 50ns, mais le pic utilise 4 cycles
     166élémentaires pour exécuter la plupart des instructions. En fait on parle de macro-cycles pour désigner ces 4 cycles élémentaires
     167et par abus de langage quand on parle de cycles, on fait référence à ces macro-cycles. En conséquence une instruction standard,
     168comme `movlw`, est exécutée en un macro-cycle, soit en 200ns. Dans la documentation technique du pic, à la page 56[feuille
     1696 recto], vous avez la liste des instructions avec leur durée respective: 1 ou 2 cycles (c.-à-d. macro-cycles). Remarquez
     170que la description des instructions dans cette documentation est celle des pic de série 16f8x et pas 16f8xx. En fait il s'agit
     171des mêmes instructions mais la documentation des 16f8x est plus détaillée lorsqu'elle décrit le comportement des instructions.
     172
     173Le programme [attachment:hello4.asm] doit être copié dans le répertoire `hello4` que vous allez créer.
     174L'assemblage se fera avec mkpic.
     175
     176 '''Question 1'''::
     177  Déterniner la valeur à mettre dans XXX pour faire 200µs.
     178
     179Notez, dans le code qui suit, l'utilisation du caractère `$`. Dans un programme en assembleur
     180`$` référence l'adresse de l'instruction courante. En conséquence, `$+1` est l'adresse qui
     181suit l'instruction courante, `$-1` est l'adresse qui précède. `goto $+1` saute à l'adresse
     182suivante, et donc semble ne servir à rien, mais elle dure 2 cycles (i.e. 2 macro-cycles) et c'est là
     183son intérêt.
     184{{{
     185; ------------------------------------------------------------------------
     186; programme : hello4
     187; Date      : 20050108:2328
     188; Version   : 1
     189; Auteurs   : franck
     190; Notes     : suppose que l'horloge externe est à 20MHz
     191; toujours le chenillard mais avec une routine de délai qui ralentit le
     192; décalage du portd, l'attente est de 200 microsecondes.
     193; ------------------------------------------------------------------------
     194
     195        list    p=16f877        ; definit le processeur cible
     196        include "p16f877.inc"   ; declaration des noms de registres
     197
     198        ; Definition du registre de configuration du PIC
     199        ; _CP_OFF   : le code n'est pas protege et peut etre relu
     200        ; _WDT_OFF  : pas de timer watch dog
     201        ; _PWRTE_ON : attente d'un délai apres le power on
     202        ; _HS_OSC   : oscillateur à quartz
     203        ; _LVP_OFF  : pas de mode programmation basse tension
     204        __CONFIG _CP_OFF & _WDT_OFF & _PWRTE_ON & _HS_OSC & _LVP_OFF
     205
     206        CBLOCK  0x20            ; première adresse de la zone
     207           wait200us_arg : 1    ; 1 octet de nom wait200us_arg
     208        ENDC
     209
     210        org     0               ; adresse du reset
     211        call    initialisation
     212        goto    main
     213
     214        org     4               ; adresse du vecteur d'interruption
     215        retfie                  ; par défaut ne rien faire
     216
     217initialisation
     218        BANKSEL TRISD           ; aller dans le banc de registre TRSID
     219        clrf    TRISD           ; place tous les signaux du port D en sortie
     220        BANKSEL PORTD           ; revenir dans le banc de registre PORTD
     221        return
     222
     223main    movlw   1               ; met 0x01  sur port D
     224        movwf   PORTD
     225loop    rrf     PORTD,f
     226        call    wait200us
     227        goto    loop            ; on boucle sur la rotation
     228
     229wait200us ; routine d'attente active
     230        movlw   d'XXX'          ; XXX -> W             ? cycle
     231        movwf   wait200us_arg   ; W -> wait200us_arg   ? cycle
     232        goto    $+1             ; nop2                 ? cycles
     233        decfsz  wait200us_arg,f ; wait200us_arg--      ? cycle si faux sinon ?
     234        goto    $-2             ; goto adr courante -2 ? cycles
     235        return                  ;                      ? cycles
     236; ------------------------------------------------------------------------
     237        END                     ; directive terminant un programme
     238}}}
     239 '''Question 2'''::
     240 Maintenant, nous allons rendre la routine wait200us paramètrable. Le paramètre sera placé dans le registre W.
     241 Après avoir copié le projet `hello4` en `hello5`,
     242 modifiez le programme `hello5` en conséquence, renommer la routine `wait200us` en `waitus`
     243 (et toutes les étiquettes formées sur ce nom).
     244
     245 '''Question 3'''::
     246 On veut que W contienne précisément le nombre de micro-secondes (µs) qu'attendra la routine `waitus`.
     247 Les deux routines ci-dessous font une attente de Wµs, où W contient un nombre entre 2 et 255.
     248 C'est à dire que pour attendre 200us, on devra mettre 200 dans W et appeler `waitus`.
     249 Solution 1:
     250{{{
     251waitus ; routine d'attente active
     252        movwf   waitus_arg      ; W -> waitus_arg
     253        decf    waitus_arg,f    ; waitus_arg--
     254        goto    $+1             ; nop2
     255        decfsz  waitus_arg,f    ; waitus_arg--
     256        goto    $-2             ; goto adr courante -2
     257        return                  ;
     258}}}
     259 Solution 2:
     260{{{
     261waitus ; routine d'attente active
     262        addlw   -1              ; -1 + W -> W
     263        movwf   waitus_arg      ; W -> waitus_arg
     264        goto    $+1             ; nop2
     265        decfsz  waitus_arg,f    ; waitus_arg--
     266        goto    $-2             ; goto adr courante -2
     267        return                  ;
     268}}}
     269 Les deux solutions semblent équivalentes et pourtant elles ne le sont pas: la première
     270 fonctionne (c.-à-d. un bit à 1 circule sur le registre port D), la seconde pas (c.-à-d. que le
     271 bit à 1 ne circule plus).
     272 Essayez de comprendre pourquoi et d'en tirer une conclusion.
     273
     274 D'autre part, pour la solution qui marche quelle durée obtient-on si W est à 1 ou 0 au départ?
     275
     276 '''Question 4'''::
     277 Nous allons ajouter une deuxième routine nommée `waitms` qui invoque la première (`waitus`).
     278 - La routine `waitms` décompte un nombre de 16 bits pour compter les tours de boucle et son corps de boucle
     279   appelle la routine `waitus`.
     280 - La routine `waitms` a un pas de 0.1 ms, c'est à dire 100us.
     281   '''On ne vous demande pas d'être précis au cycle près pour le calcul de temps.'''
     282 - Elle prend son paramètre dans une variable, nommée `waitms_arg` de 2 octets.
     283   Le placement des nombres entiers de plusieurs octets suit la stratégie «little endian» comme le
     284   MIPS (poids faible à l'adresse basse).
     285 - À titre d'exemple pour attendre 0.13 seconde (130 ms), une fois écrite la routine `waitms` il faut
     286   écrire ({\bf notez à cette ocasion une méthode pour initialiser une variable de 2 octets avec une valeur}):
     287{{{
     288  movlw  LOW D'1300'  ; met les 8 bits de poids faible de 1300 dans W
     289  movwf  waitms_arg   ; met W dans le registre waitms_arg
     290  movlw  HIGH D'1300' ; met les 8 bits de poids fort de 1300 dans W
     291  movwf  waitms_arg+1 ; met W dans le registre waitms_arg+1
     292  call   waitms       ; attend 1300 * 0.1 ms = 130ms
     293}}}
     294 - La routine `waitms` doit décrémenter un nombre sur 16 bits et boucler tant qu'il n'est pas nul.
     295   L'opération de décrémentation sur 16 bits n'existe pas dans les instructions natives du pic, c'est pourquoi vous ajouterez
     296   une macro-instruction au debut de votre programme (après la config) qui définit une pseudo instruction 16bits.
     297   Pour ne pas vous prendre du temps, nous vous donnons cette macro (bonne candidate lors d'un examen):
     298{{{
     299subwf2  macro @r ; r <- r-w sur 2 octets ; positionne /Z ; 5 cycles
     300           subwf @r,f
     301           btfss STATUS,C ; C=0 si r passe en dessous de 0
     302               decf  @r+1,f    ; si C==0 alors décrémenter le poids fort
     303               movf  @r,w      ; positionne Z si et seulement si
     304               iorwf @r+1,w    ; r ET r+1 sont à 0
     305        endm
     306}}}
     307 - En outre, vous allez désormais faire une rotation sur 8 bits et non plus sur 9 bits dans le programme principal. Pour
     308   faire la rotation, vous utiliserez la macro `rr8f r` dont la définition vous est donnée ci-dessous (autre bon
     309   candidat d'exercice à l'examen).
     310{{{
     311rr8f    macro @r ; r <- (r&1)|((r>>1)&0x7F) : rot. droite sur 8 bits
     312           rrf   @r,w       ; initialise carry avec le bit n°0 de r
     313           rrf   @r,f       ; r[7..0] <- r[6..0],carry
     314        endm
     315}}}
     316 - Pour résumer, vous devez:
     317    - Copier le projet `hello5` en `hello6`.
     318    - Ajouter les macro `subwf2` et `rr8f`.
     319    - Réserver la variable `waitms_arg` sur 2 octets, dans la section `CBLOCK`.
     320    - Ajouter la routine `waitms` (disons avant `waitus`).
     321    - Modifier «main» pour qu'il utilise la macro `rr8f` et appelle la routine `waitms` et de telle sorte que le
     322      déplacement du bit sur le port D se fasse à la vitesse d'environ une seconde par bit.
     323
     324= L'écriture des fonctions =
     325
     326== Quel est le problème ==
     327
     328Maintenant, on aimerait pouvoir mettre dans un fichier toutes ces routines, afin de se constituer une bibliothèque de
     329fonctions réutilisables. On va d'ailleurs en profiter pour changer de nom. On appellera fonction une routine
     330placée dans une bibliothèque pour laquelle on a défini une stratégie standardisée de passage des paramètres et de remise
     331des résultats. On appellera une routine un morceau de programme rendant un service particulier, référencé par son nom
     332(correspondant à l'adresse de sa première instruction) auquel on «saute» au moyen d'une instruction (ici l'instruction
     333call) qui sauvegarde le compteur ordinal+1 (adresse de l'instruction suivante) dans une pile spéciale de 8 mots de
     33413 bits, avant de lui affecter l'adresse de la routine et dont on revient au moyen d'une instruction (ici l'instruction
     335return) qui restaure le compteur ordinal. Une routine peut prendre des paramètres mais aucune convention n'est définie,
     336on fait comme on veut en somme.
     337
     338En effet, le principal problème pour l'écriture des fonctions concerne le choix d'une méthode pour le passage des paramètres,
     339l'allocation des registres de travail et la remise du, ou des, résultats. Quand on écrit une fonction quelconque,
     340on ne veut pas se soucier de qui va l'utiliser, or si on réserve en dur (absolu) les registres (par exemple les registres 0x20
     341et 0x21) pour une fonction, ils ne sont plus, dans le cas général, utilisables par une autre fonction.
     342
     343Dans un processeur doté d'une vraie pile, les paramètres des fonctions sont passés par la pile et la fonction y accède
     344généralement en utilisant le pointeur de pile. Dans ce pic, la pile ne peut pas servir à passer les paramètres car il
     345n'existe aucun moyen d'y accéder autrement qu'avec les instructions call et return. On peut bien sûr simuler une pile et
     346utiliser des accès mémoire indirects grâce au registre FSR, mais c'est assez coûteux en instructions.
     347
     348La façon de résoudre ce problème d'une manière générale passe par le choix judicieux des cases mémoire utilisées par les
     349fonctions au moment où l'on connait toutes les adresses nécessaires. C'est le travail d'un éditeur de liens. Si on n'en dispose pas,
     350il faut faire ce travail à la main. Nous verrons ultérieurement comment s'y prendre sans trop pénaliser la lisibilité du code.
     351
     352== Les fonctions terminales, sans état et non réentrantes ==
     353
     354Maintenant, il existe des fonctions particulières pour lesquelles le problème de la gestion de la mémoire est plus simple.
     355Ce sont les fonctions teminales, sans état et non réentrantes.
     356
     357 '''fonction teminale'''::
     358   Une fonction qui n'en appelle pas d'autres.
     359 '''fonction sans état'''::
     360   Une fonction qui ne mémorise pas les précédents appels.
     361 '''fonction non réentrante'''::
     362   Une fonction qui ne peut pas être exécutée plusieurs fois en même temps. La réentrance n'est possible que si le
     363   processeur est capable d'exécuter deux taches en parallèle (ou plutôt en pseudo parallélisme). C'est possible
     364   même en l'absence de système d'exploitation, par exemple, si un programme est interrompu en raison d'un
     365   événement quelconque alors qu'il était en train d'exécuter une fonction, et que le gestionnaire d'interruption
     366   veut lui aussi exécuter la même fonction. La fonction en question doit être impérativement réentrante.
     367
     368Si une fonction n'est pas réentrante, alors elle ne peut pas être utilisée par le programme principal et par le
     369gestionnaire d'interruption en même temps. On aura, fondamentalement 3 possiblités pour respecter cette contrainte:
     370 1. interdire son usage pour l'un ou l'autre, c'est facile, c'est au programmeur de respecter cette contrainte.
     371 2. masquer les interruptions avant d'utiliser la fonction, ainsi si le programme principal commence à l'utiliser,
     372    il est sûr que le gestionnaire d'interruption ne le fera pas puisqu'il est bloqué. Cette solution est simple
     373    mais brutale, car elle bloque toutes les interruptions, même si le gestionnaire des interruptions
     374    n'avait pas l'intention d'utiliser la fonction en question.
     375 3. mettre en place un mécanisme permettant de devenir propriétaire de la fonction considérée
     376    comme une ressource, le temps de son utilisation.
     377Dans un permier temps, on utilisera la première solution, et plus tard on utilisera la troisième
     378
     379Il n'est pas difficile de comprendre que toutes les fonctions ayant ces trois propriétés (terminale, sans état, non réentrante)
     380peuvent partager la même zone mémoire que ce soit pour le passage de paramètres ou pour le travail.
     381
     382== Première petite librairie ==
     383
     384Nous allons écrire une petite librairie de fonctions teminales, sans état et non réentrantes contenant pour le moment une
     385seule fonction: `waitms`. La mémoire utilisée par ces fonctions sera prise dans une zone partagée par tous les bancs. Nous nous
     386limiterons à 4 octets à partir de `0x70` que nous nommerons A0 à A3 (ils sont réservés dans picmips.inc, mais vous pouvez le faire vous même). Cette zone va servir au passage des arguments, à la récupération des résultats et aux
     387variables locales. Certes 4 octets c'est peu, mais c'est souvent suffisant pour un grand nombre de fonctions de base. Les fonctions peuvent utiliser d'autres registres si c'est nécessaire.
     388
     389Pour le passage des parametres, c'est-à-dire la copie dans les registres A0 à A3 et la récupération du résultat, nous pouvons utiliser des macros. Par exemple pour une fonction qui calcul la moyenne de deux entiers sur 2 bits.
     390
     391La fonction:
     392{{{
     393f_moy:  movf    a1,w
     394        addwf   a0,f
     395        bcf     STATUS,c
     396        rrf     a0,f
     397        return
     398}}}
     399
     400La macro d'appel:
     401{{{
     402moy macro @d, @s0, @s1
     403        movf    @s0,w
     404        movwf   a0
     405        movf    @s1,w
     406        movwf   a1
     407        call    f_moy
     408        movf    a0,w
     409        movwf   @d
     410    endm
     411}}}
     412
     413De sorte que l'usage de la fonction se réduit à
     414{{{
     415     CBLOCK BANK1
     416        v0
     417        v1
     418        v2
     419     ENDC
     420     ...
     421     moy   v2, v1, v0
     422}}}
     423
     424 '''Question 6'''::
     425   Vous allez réécrire le programme `hello6` que nous nommerons maintenant `hello7` (vous devez commencer à vous y habituer).
     426   Etant donné qu'il n'est pas facile d'expliquer exactement ce que nous souhaitons vous voir programmer, nous vous avons préparé
     427   un cadre que vous allez remplir.
     428   Faites les ajouts et testez.
     429
     430
     431   '''hello7.asm'''::
     432      contient le programme principal et l'inclusion des macros et des fonctions de l'utilisateur.
     433      Ce fichier est inclus dans le fichier juste après le gestionnaire d'interruption. Réfléchissez à la
     434      raison de l'avoir inclus à cet endroit et pas au tout début du programme.
     435     
     436   '''myfun.asm'''::
     437      contient les macros d'appels des fonctions avec le passage des paramètres et les fonctions de l'utilisateur.
     438      Tout est donné hormis le code de la fonction `waitms` et la déclaration des registres A0 à A3.
     439
     440== Deuxième petite librairie ==
     441
     442Afin de voir si vous avez compris comment marche cette petite librairie, vous allez ajouter quelques fonctions supplémentaires.
     443Faites au moins les deux premières.
     444
     445Pour chaque fonction, on vous dit quels sont les paramètres, quelle est son action et ce qu'elle rend en résultat.
     446Les paramètres sont mis dans les registres A0 à A3, dans l'ordre et le résultat est mis à partir du registre A0.
     447Cherchez sur le site \underline{www.microchip.com}, les algorithmes du random ou de la multiplication.
     448
     449A0 est le nom symbolique d'un registre de travail choisi dans la zone partagée (entre 0x70 et 0x7F). W pour Working.
     450Si A0 est à l'adresse 0x70, A1 est à 0x71, A2 est à 0x72 et enfin 0x73.
     451
     452 '''Question 7'''::
     453|| `f_add2`  || `[A1:A0] <- [A1:A0] + [A3:A2]      ` || addition signée sur 2 octets          ||
     454|| `f_sub2`  || `[A1:A0] <- [A1:A0] - [A3:A2]      ` || soustraction signée sur 2 octets      ||
     455|| `f_sra2`  || `[A1:A0] <- [A1:A0] >> A2          ` || décalage droite arithmétique de 0 à 16||
     456|| `f_srl2`  || `[A1:A0] <- [A1:A0] >> A2          ` || décalage droite logique de 0 à 16     ||
     457|| `f_sll2`  || `[A1:A0] <- [A1:A0] >> A2          ` || décalage gauche de 0 à 16 décalage    ||
     458|| `f_ran2`  || `[A1:A0] <- nombre aléatoire       ` || tire un entier entre 0 et 65535       ||
     459|| `f_mul`   || `[A1:A0] <- A1 * A0                ` || multiplication sur des nombres signés ||
     460
     461 '''Notation'''::
     462  - `[A1:A0] :` désigne un couple de registre contenant un mot de 16 bits. Les 8 bits de poids faibles sont dans le registre `A0`, les 8 bits de poids forts dans le registre `W1`.
     463
     464{{{
     465; ------------------------------------------------------------------------------
     466; programme : hello7
     467; Date      : 20130208:1521
     468; Version   : 1
     469; Auteurs   : franck
     470; Notes     : suppose que le Quartz est à 20MHz
     471; Premier programme utilisant des fonctions et des macros mises en bibliothèque
     472; sinon même comportement que hello6
     473; ------------------------------------------------------------------------------
     474
     475        list    p=16f877        ; définit le processeur cible
     476        include "p16f877.inc"   ; déclaration des noms de registres
     477        include "picmips.inc"   ; macros à la mips
     478        include "myfun.asm"     ; fonctions utilisateurs
     479
     480        ; Définition du registre de configuration du PIC
     481        ; _CP_OFF   : le code n'est pas protégé et peut être relu
     482        ; _WDT_OFF  : pas de timer watch dog
     483        ; _PWRTE_ON : attente d'un délai après le power on
     484        ; _HS_OSC   : oscillateur à quartz
     485        ; _LVP_OFF  : pas de mode programmation basse tension
     486        __CONFIG _CP_OFF & _WDT_OFF & _PWRTE_ON & _HS_OSC & _LVP_OFF
     487
     488; reset + gestionnaire d'interruption
     489; ------------------------------------------------------------------------------
     490        org     0
     491        call    initialisation
     492        goto    main            ; sauter le code du gestionnaire d'interruption
     493
     494        org     4               ; adresse du vecteur d'interruption
     495        retfie                  ; par défaut ne rien faire
     496
     497; initialisation générale
     498; ------------------------------------------------------------------------------
     499initialisation
     500        BANKSEL TRISD           ; aller dans le banc de registre TRSID
     501        clrf    TRISD           ; place tous les signaux du port D en sortie
     502        BANKSEL PORTD           ; revenir dans le banc de registre PORTD
     503
     504; programme principal
     505; ------------------------------------------------------------------------------
     506main    movlf   1,PORTD         ; met 0x01 sur port D
     507loop    rr8f    PORTD           ; rotation vers la droite du port D
     508        movlf2  D'10000', A0    ; 10000 * 0.1ms = 1s
     509        call    waitms          ;
     510        goto    loop            ; on boucle sur la rotation
     511
     512; ------------------------------------------------------------------------------
     513        END                     ; directive terminant un programme
     514}}}
     515{{{
     516; ------------------------------------------------------------------------------
     517; library   : myfun
     518; Date      : 20130208:1526
     519; Version   : 1
     520; Auteurs   : franck
     521; Notes     :
     522; - mapping mémoire réservé
     523; - macros basiques extension de l'assembleur pic
     524; - fonctions non réentrantes, terminales, sans état.
     525; ------------------------------------------------------------------------------
     526
     527          ORG .....
     528; ==============================================================================
     529; macros basiques
     530; ==============================================================================
     531
     532movlf   macro l,r ;------------- r <- l
     533           movlw l
     534           movwf r
     535        endm
     536
     537movlf2  macro l,r ;------------- r <- l sur 2 octets
     538           movlf low(l),r
     539           movlf high(l),r+1
     540        endm
     541
     542subwf2  macro r ;--------------- r <- r-w sur 2 octets ; positionne le bit Z (5 cycles)
     543           subwf r,f
     544           btfss STATUS,C  ; C=0 si r passe de 0 à FF
     545           decf  r+1,f     ; si C==0 alors décrémenter le poids fort
     546           movf  r,w       ; positionne Z uniquement...
     547           iorwf r+1,w     ; ...si r ET r+1 sont à 0
     548        endm
     549
     550rr8f    macro r ;--------------- r <- (r&1)|((r>>1)&0x7F) : rot. droite sur 8 bits
     551           rrf   r,w       ; initialise carry avec le bit n°0 de r
     552           rrf   r,f       ; r[7..0] <- r[6..0],carry
     553        endm
     554
     555; ==============================================================================
     556; fonctions non réentrantes, terminales, sans état.
     557; -------------------------------------------------
     558; - Les fonctions de cette bibiothèque utilisent exclusivement les registres
     559;   de A0 à A3, à la fois pour le passage de paramètres, la remise de résultat
     560;   et les variables locales.
     561; - Il n'est pas interdit d'utiliser des routines mais il faut rester dans la
     562;   limite des registre de A0 à A3
     563; - attention les labels doivent être locaux a ce fichier de manière à ne pas
     564;   crééer de conflits de noms avec d'autres fichiers
     565; ==============================================================================
     566
     567; waitms
     568; ------------------------------------------------------------------------------
     569; IN     : [A1,A0] = mombre sur 16 bits (A0 octet de poid faible)
     570; OUT    : rien
     571; ACTION : routine d'attente active millisecondes
     572;          attente de [A1,A0] * 0.1ms
     573; TAILLE : 13 octets
     574; DUREE  : [A1,A0] * 0.1ms
     575; ------------------------------------------------------------------------------
     576f_waitms
     577; ******************************************************************************
     578; QUESTION : PLACER LE CODE DE LA FONCTION waitms QUI TRAVAILLE SUR A1 et A0
     579; ******************************************************************************
     580        return
     581
     582f_waitus ; routine d'attente active (W)us (W>=2)
     583        movwf   A2              ; W -> A2
     584        decf    A2,f            ; pour attendre précisement W us
     585        goto    $+1             ; nop2
     586        decfsz  A2,f            ; A2--
     587        goto    $-2             ; goto adr courante -2
     588        return                  ;
     589}}}