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