| 1 | {{{ |
| 2 | #!protected |
| 3 | {{{ |
| 4 | #!html |
| 5 | <h1 align=center><u> |
| 6 | Programmation en assembleur PIC : directives, macro-instructions, boucles, switch-case |
| 7 | </u></h1> |
| 8 | }}} |
| 9 | [[PageOutline]] |
| 10 | |
| 11 | = Objectifs = |
| 12 | |
| 13 | Le 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 |
| 14 | et dans un pic en particulier. Vous aborderez une méthode pour l'écriture de fonctions concernant le problème lié aux passages |
| 15 | des paramètres. Vous verrez également une méthode pour introduire le temps réel dans vos programmes par l'intermédiaire de boucles |
| 16 | de temporisation. |
| 17 | |
| 18 | Vous 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 | |
| 23 | Vous utiliserez désormais un script nommé '''mkpic''' pour toutes les opérations d'assemblage, de simulation ou de programmation. |
| 24 | Nous 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''' |
| 33 | avec l'extension .asm en plus. Un programme ne signifie pas obligatoirement un seul fichier, puisque le fichier racine, du |
| 34 | nom du programme peut inclure d'autres fichiers de noms quelconques .asm ou .inc. Grâce à cette contrainte, il est inutile |
| 35 | de donner le nom du programme à mkpic, il le trouve tout seul. |
| 36 | |
| 37 | L'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 | |
| 54 | Nous 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 | |
| 56 | 1. 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 | |
| 59 | 2. 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 | |
| 66 | 3. 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 | |
| 73 | 4. 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 | |
| 117 | Les 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 |
| 118 | allouer aux programmes et nous allons nous imposer des contraintes de programmation (autant le dire tout de suite, ces contraintes sont |
| 119 | discutables, c.-à-d. que d'autres programmeurs en auraient imposés des différentes). D'ailleurs nous nous autoriserons quelquefois à ne |
| 120 | pas les respecter. Enfin, quelles qu'elles soient, les contraintes de programmation ont pour but de rendre les programmes plus |
| 121 | cohé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 | |
| 124 | Nous introduirons nos contraintes au fur et à mesure des séances, disons pour l'heure que les registres partagés seront utilisés |
| 125 | entre autre pour le passage des paramètres aux fonctions, et pour la sauvegarde du contexte lors des interruptions. |
| 126 | |
| 127 | L'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 | |
| 138 | Ces deux approches sont possibles puisque, gpasm peut produire du code relogeable, et nous vous proposerons une méthode de |
| 139 | non collision. |
| 140 | Pour ce TME, nous allons utiliser l'allocation statique et globale, en utilisant la directive assembleur `CBLOCK` |
| 141 | {{{ |
| 142 | CBLOCK adr |
| 143 | nom1 : long1 ; réserve nom1 à l'adresse adr |
| 144 | nom2 : long2 ; réserve nom2 à l'adresse adr + long1 |
| 145 | ENDC |
| 146 | CBLOCK |
| 147 | nom3 : long3 ; réserve nom3 à l'adresse adr + long1 + long2 |
| 148 | nom4 : long4 ; réserve nom4 à l'adresse adr + long1 + long2 + long3 |
| 149 | ENDC |
| 150 | }}} |
| 151 | |
| 152 | = Le comptage du temps = |
| 153 | |
| 154 | Nous allons reprendre le programme `hello3` vu lors de la précédente séance, mais |
| 155 | nous voulons maintenant ralentir le déplacement du bit à 1 dans le PORTD afin de le rendre visible à l'oeil, en supposant que |
| 156 | l'on relie des leds sur les broches associées au PORTD. Pour ce faire, nous allons réaliser une boucle d'attente active. |
| 157 | Cette attente est dite active car le processeur est effectivement occupé à compter et ne peut rien faire d'autre. Nous verrons |
| 158 | qu'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 | |
| 161 | La boucle d'attente se fait dans la routine wait200us. Comme vous le voyez sur le listing la boucle consiste à remplir un registre avec une |
| 162 | valeur 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 |
| 163 | boucle pour que la fontion dure 200µs. |
| 164 | |
| 165 | Le 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 |
| 167 | et par abus de langage quand on parle de cycles, on fait référence à ces macro-cycles. En conséquence une instruction standard, |
| 168 | comme `movlw`, est exécutée en un macro-cycle, soit en 200ns. Dans la documentation technique du pic, à la page 56[feuille |
| 169 | 6 recto], vous avez la liste des instructions avec leur durée respective: 1 ou 2 cycles (c.-à-d. macro-cycles). Remarquez |
| 170 | que la description des instructions dans cette documentation est celle des pic de série 16f8x et pas 16f8xx. En fait il s'agit |
| 171 | des mêmes instructions mais la documentation des 16f8x est plus détaillée lorsqu'elle décrit le comportement des instructions. |
| 172 | |
| 173 | Le programme [attachment:hello4.asm] doit être copié dans le répertoire `hello4` que vous allez créer. |
| 174 | L'assemblage se fera avec mkpic. |
| 175 | |
| 176 | '''Question 1''':: |
| 177 | Déterniner la valeur à mettre dans XXX pour faire 200µs. |
| 178 | |
| 179 | Notez, 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 |
| 181 | suit l'instruction courante, `$-1` est l'adresse qui précède. `goto $+1` saute à l'adresse |
| 182 | suivante, et donc semble ne servir à rien, mais elle dure 2 cycles (i.e. 2 macro-cycles) et c'est là |
| 183 | son 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 | |
| 217 | initialisation |
| 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 | |
| 223 | main movlw 1 ; met 0x01 sur port D |
| 224 | movwf PORTD |
| 225 | loop rrf PORTD,f |
| 226 | call wait200us |
| 227 | goto loop ; on boucle sur la rotation |
| 228 | |
| 229 | wait200us ; 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 | {{{ |
| 251 | waitus ; 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 | {{{ |
| 261 | waitus ; 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 | {{{ |
| 299 | subwf2 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 | {{{ |
| 311 | rr8f 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 | |
| 328 | Maintenant, on aimerait pouvoir mettre dans un fichier toutes ces routines, afin de se constituer une bibliothèque de |
| 329 | fonctions réutilisables. On va d'ailleurs en profiter pour changer de nom. On appellera fonction une routine |
| 330 | placée dans une bibliothèque pour laquelle on a défini une stratégie standardisée de passage des paramètres et de remise |
| 331 | des 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 |
| 333 | call) qui sauvegarde le compteur ordinal+1 (adresse de l'instruction suivante) dans une pile spéciale de 8 mots de |
| 334 | 13 bits, avant de lui affecter l'adresse de la routine et dont on revient au moyen d'une instruction (ici l'instruction |
| 335 | return) qui restaure le compteur ordinal. Une routine peut prendre des paramètres mais aucune convention n'est définie, |
| 336 | on fait comme on veut en somme. |
| 337 | |
| 338 | En effet, le principal problème pour l'écriture des fonctions concerne le choix d'une méthode pour le passage des paramètres, |
| 339 | l'allocation des registres de travail et la remise du, ou des, résultats. Quand on écrit une fonction quelconque, |
| 340 | on 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 |
| 341 | et 0x21) pour une fonction, ils ne sont plus, dans le cas général, utilisables par une autre fonction. |
| 342 | |
| 343 | Dans un processeur doté d'une vraie pile, les paramètres des fonctions sont passés par la pile et la fonction y accède |
| 344 | généralement en utilisant le pointeur de pile. Dans ce pic, la pile ne peut pas servir à passer les paramètres car il |
| 345 | n'existe aucun moyen d'y accéder autrement qu'avec les instructions call et return. On peut bien sûr simuler une pile et |
| 346 | utiliser des accès mémoire indirects grâce au registre FSR, mais c'est assez coûteux en instructions. |
| 347 | |
| 348 | La 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 |
| 349 | fonctions 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, |
| 350 | il 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 | |
| 354 | Maintenant, il existe des fonctions particulières pour lesquelles le problème de la gestion de la mémoire est plus simple. |
| 355 | Ce 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 | |
| 368 | Si une fonction n'est pas réentrante, alors elle ne peut pas être utilisée par le programme principal et par le |
| 369 | gestionnaire 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. |
| 377 | Dans un permier temps, on utilisera la première solution, et plus tard on utilisera la troisième |
| 378 | |
| 379 | Il n'est pas difficile de comprendre que toutes les fonctions ayant ces trois propriétés (terminale, sans état, non réentrante) |
| 380 | peuvent 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 | |
| 384 | Nous allons écrire une petite librairie de fonctions teminales, sans état et non réentrantes contenant pour le moment une |
| 385 | seule fonction: `waitms`. La mémoire utilisée par ces fonctions sera prise dans une zone partagée par tous les bancs. Nous nous |
| 386 | limiterons à 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 |
| 387 | variables 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 | |
| 389 | Pour 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 | |
| 391 | La fonction: |
| 392 | {{{ |
| 393 | f_moy: movf a1,w |
| 394 | addwf a0,f |
| 395 | bcf STATUS,c |
| 396 | rrf a0,f |
| 397 | return |
| 398 | }}} |
| 399 | |
| 400 | La macro d'appel: |
| 401 | {{{ |
| 402 | moy 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 | |
| 413 | De 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 | |
| 442 | Afin de voir si vous avez compris comment marche cette petite librairie, vous allez ajouter quelques fonctions supplémentaires. |
| 443 | Faites au moins les deux premières. |
| 444 | |
| 445 | Pour chaque fonction, on vous dit quels sont les paramètres, quelle est son action et ce qu'elle rend en résultat. |
| 446 | Les paramètres sont mis dans les registres A0 à A3, dans l'ordre et le résultat est mis à partir du registre A0. |
| 447 | Cherchez sur le site \underline{www.microchip.com}, les algorithmes du random ou de la multiplication. |
| 448 | |
| 449 | A0 est le nom symbolique d'un registre de travail choisi dans la zone partagée (entre 0x70 et 0x7F). W pour Working. |
| 450 | Si 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 | ; ------------------------------------------------------------------------------ |
| 499 | initialisation |
| 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 | ; ------------------------------------------------------------------------------ |
| 506 | main movlf 1,PORTD ; met 0x01 sur port D |
| 507 | loop 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 | |
| 532 | movlf macro l,r ;------------- r <- l |
| 533 | movlw l |
| 534 | movwf r |
| 535 | endm |
| 536 | |
| 537 | movlf2 macro l,r ;------------- r <- l sur 2 octets |
| 538 | movlf low(l),r |
| 539 | movlf high(l),r+1 |
| 540 | endm |
| 541 | |
| 542 | subwf2 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 | |
| 550 | rr8f 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 | ; ------------------------------------------------------------------------------ |
| 576 | f_waitms |
| 577 | ; ****************************************************************************** |
| 578 | ; QUESTION : PLACER LE CODE DE LA FONCTION waitms QUI TRAVAILLE SUR A1 et A0 |
| 579 | ; ****************************************************************************** |
| 580 | return |
| 581 | |
| 582 | f_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 | }}} |