Introduction à la programmation assembleur
reprenons le progamme Hello DEL pour illustrer cette introduction.
#include <P10F202.INC>
; configuration MCU
__CONFIG _CP_OFF & _WDT_OFF & _MCLRE_OFF
OPTION_CFG equ B'10000000' ;valeur du registre OPTION
;;;;; macros de pré-processeur ;;;;;;;;;;;
#define DEL GPIO, GP2 ; la DEL est branché sur GP2 (broche 3)
#define DEMI_PERIODE 0xFD74F4 ; ~(500000/3*Tcy) cycle= 0,5sec.
;;;;;;;; macros d'assembleur ;;;;;;;;;;;;;;
eteindre_del macro
bcf DEL
endm
allumer_del macro
bsf DEL
endm
;;;;;;;;; variables ;;;;;;;;;;;
cblock 8
compteur : 3 ; compteur 24 bits pour le délais
endc
org 0
movwf OSCCAL ; calibration de l'oscillateur interne
goto initialisation
minuterie ; sous-routine de temporisation 500msec
movlw low DEMI_PERIODE ; low -> const % 256
movwf compteur
movlw high DEMI_PERIODE ; high -> const % 65536
movwf compteur+1
movlw upper DEMI_PERIODE ; upper -> const % 16777216
movwf compteur+2
boucle
incfsz compteur,F
goto boucle
incfsz compteur+1,F
goto boucle
incfsz compteur+2,F
goto boucle
return
initialisation
movlw OPTION_CFG ;
option ; configuration des options runtime
movlw B'1011' ;GP2 sortie GP0, GP1 et GP3 entré
tris GPIO
main
allumer_del
call minuterie
eteindre_del
call minuterie
goto main
end
Ce fichier doit-être inclus au début sinon les constantes comme W, GPIO, OPTION, etc ne seront pas définies et l'assembleur va générer une erreur chaque fois qu'il en rencontrera une dans le texte. Pour connaître les différentes valeurs définies consultez le fichier *.INC inclus dans le projet.
Chaque MCU possède 1 ou 2 location mémoire spéciale, appellé registre de configuration qui sert à définir les paramètres opérationnels du MCU. Ses valeurs sont enregistrées dans le PIC en même temps que le programme et ne sont pas modifiables en cours d'exécution. la directive __CONFIG sert à définir ces options et on la met habituellement en début de fichier comme ici. Dans notre exemple, la protection de code est désactivé _CP_OFF, le watchdogTimer est désactivé _WDT_OFF et la fonction MASTER CLEAR sur GP3 est aussi désactivée _MCLRE_OFF.
La programmation assembleur est grandement simplifiée par l'utilisation de macros. Il y a 2 sortent de macros, les macros de pré-processeur qui resemble à celle utilisé en C qui sont définit avec la directive #define. Chaque fois que le pré-processeur rencontre le nom d'une de ces macros il remplace le nom par le texte de sa définition. Par exemple lorsque le pré-processeur va rencontré le mot DEL dans le texte il va le remplacé par GPIO, GP2.
Les macros d'assembleur sont plus puissantes quoique dans l'exemple présent les 2 macros allumer_del et eteindre_del auraient put-être définies avec un #define. Mais le macro assemblage de MPASM permet de faire de l'assemblage conditionnel, d'utiliser des arguments. Voici un exemple.
ldr16 macro reg, val ; charge la constante val dans le registre 16bit reg.
if (low val)==0
clrf reg
else
movlw low val
movwf reg
endif
if (high val)==0
clrf reg
else
movlw high val
movwf reg+1
endif
endm
Dans cette exemple la macro ldr16 utilise 2 arguments reg et val qui sont des constantes. De plus elle utilise la directive conditionnelle if-else-endif pour optimiser le code. Si la valeur qui doit-être chargé dans le registe est nulle on utilise l'instruction assembleur CLRF sinon il faut 2 instructions MOVLW et MOVWF pour initialiser le registre. les directives low et high servent à obtenir l'octet faible et l'octet fort d'une constante. On met l'octet faible dans reg et l'octet fort dans reg+1. Donc cette macro dépendant de la valeur de l'argument val générera entre 2 et 4 instructions machines.if (low val)==0
clrf reg
else
movlw low val
movwf reg
endif
if (high val)==0
clrf reg
else
movlw high val
movwf reg+1
endif
endm
la directive cblock permet de définir des constantes. Dans notre exemple on en a définie qu'une seule mais on peut en définir plusieurs, une par ligne. Le bloc de définition se termine par la directive endc.
Le chiffre 8 après la directive cblock détermine la valeur de la première constante. Dans notre exemple COMPTEUR prend donc la valeur 8. Mais le mot COMPTEUR réserve 3 location mémoires comme indiqué après le : donc si dans le code assembleur on utilise COMPTEUR+1 cette valeur sera 9 et COMPTEUR+2 vaudra 10. Si on définie une autre constante après COMPTEUR sa valeur sera 11. Cette directive a le même effet que de définir des constante avec la directive EQU sauf que c'est plus rapide et la valeur est incrémentée automatiquement.
La directive org indique à l'assembleur que ce qui suit est du code machine. C'est le début du segment de code. Le chiffre qui suis la directive est l'adresse mémoire où débute le code. Dans cette exemple la première instruction movwf OSCCAL sera enregistrée à l'adresse programme 0. On peut utiliser plusieurs directives org dans le fichier par exemple si on veut positionné un segment de code à un endroit précis de la mémoire programme.
Pourquoi utilise-t-on un goto initialisation comme deuxième instruction plutôt que de débuter le code d'initialisation à cet endroit. La raison en est que l'instruction call des PIC de base ne peut adresser que les 256 premières positions mémoire. On réserve donc ces 256 premières adresses pour les sous-routines et on met le code de premier niveau à la fin. Il est impératif que l'adresse de démarrage d'une sous-routine se situe dans la plage 0-255 mais elle peut se poursuivre dans la plage 256-510 soit par dépassement de la plage soit par un goto car cette instruction peut adresser 512 locations mémoire. Il peut arrivé qu'on ne soit pas capable de caser toutes les sous-routines dans la plage 0-255. Dans cette situation on peut utiliser le goto pour contourner le problème en continuant le code dans la plage 256-510. Une stratégie consiste à mettre les sous-routines les plus courtes en début et les plus longues à la suite. On pourrait aussi utiliser une table de goto pour toutes les sous-routines.
org 0
movwf OSCCAL
goto initialisation
;table goto des sous-routines
sr1
goto sr1start
sr2
goto sr2start
sr3
goto sr3start
.
.
.
srn
goto srnstart
initialisation
; code d'initalisation ici
main
; code principal ici
; code des sous-routines ici
sr1start
;code
return1
sr2start
;code
return
.
.
.
srnstart
;code
return
end
movwf OSCCAL
goto initialisation
;table goto des sous-routines
sr1
goto sr1start
sr2
goto sr2start
sr3
goto sr3start
.
.
.
srn
goto srnstart
initialisation
; code d'initalisation ici
main
; code principal ici
; code des sous-routines ici
sr1start
;code
return1
sr2start
;code
return
.
.
.
srnstart
;code
return
end
table de constantes
Puisqu'il est question de table, je mentionnerai ici une directive très utile de mpasm dt pour data table. Voici un exemple d'utilisation.table_notes
addwf PCL,F
dt 0x20
dt 0x23
dt 0x45
.
.
.
addwf PCL,F
dt 0x20
dt 0x23
dt 0x45
.
.
.
;code généré par l'assembleur
addwf PCL,F
retlw 0x20
retlw 0x23
retlw 0x45
.
.
.
voici comment on utilise une table. Supposons qu'on veut générer les différentes notes d'un octave tempérée. On calcule les valeurs de délais nécessaire pour chaque notes et on créé une table comme celle-ci avec les 12 valeurs. Pour représenter les notes dans notre programme on utilise les nombre 0 à 11. Si on veut jouer la note 5 par exemple, on met cette valeur dans le registre W et on fait un call sur la table des notes:addwf PCL,F
retlw 0x20
retlw 0x23
retlw 0x45
.
.
.
call table_notes
à l'entrée de la sous-routine table_notes la valeur de W est addionnée au compteur ordinal PCL ce qui est l'équivalant d'un goto $+52. A cette position se retrouve une instruction de retour avec la valeur constante à retourner, retlw 0xNN. Cette méthode est fréquemment utilisée en assembleur il est donc important de la comprendre.
les étiquettes
les étiquettes sont des noms qui représentes des adresses mémoire auquels ont peut faire référence dans les instructions goto et call.Dans notre exemple, initialisation,boucle,minuterie, main sont des étiquettes et réfèrent donc à des locations mémoire programme. La valeur des ces adresses n'a pas besoin d'être connue. C'est l'assembleur qui calcule ces valeurs et génère le bon code en lien avec ces adresses. On peut cependant connaître ces adresses en examinant le fichier de dé-assemblage généré par mpasm.
table des 33 instructions des PIC de base
Dans la table suivante f représente un registe quelconque. d représente la distination du résultat. Cette destination peut-être soit W soit F. Les constantes W et F sont prédinies et vales 0 et 1 respectivement. k représente une constante entre 0 et 255.b indique un bit dans un registre et prend une valeur entre 0 et 7. 0 étant le bit le moins significatif et 7 le plus significatif.mnémonique | action | description | exemple |
---|---|---|---|
ADDWF f,d | d=W+F | additionne le le contenu de W avec le contenu de f et met le résultat dans d | ADDWF CMPT,F ici W est additionné à CMPT et le résultat va dans CMPT |
ANDLW k | W=W&k | ET bit à bit entre W et k | ANDLW 0xA5 |
ANDWF f,d | d=w&f | ET binaire entre W et f | ANDWF VAR,W ici un ET bit à bit est effectué entre W et VAR et le résultat va dans W |
BCF f,b | force à zéro le bit b de f | Bit Clear File | BCF GPIO,GP2 force la sortie GP2 à zéro |
BSF f,b | Force à 1 le bit b de f | Bit Set File | BSF flags,0 le bit 0 de flags est mis à 1 |
BTFSC f,b | saut conditionnel sur état bit | si le bit b de f est à zéro saute l'instruction suivante | BTFSC flags,1 saute l'instruction suivante si le bit 1 de flags est à zéro |
BTFSS f,b | saut conditionnel sur état bit | si le bit b de f est à 1 saute l'instruction suivante | BTFSS STATUS,2 saute l'instruction suivante si le bit 2 de STATUS est à 1 |
CALL k | appel de sous-routine | la valeur actuelle de PCL est sauvegarder sur la pile des retours et remplacée par k | CALL delais delais est une étiquette représentant une adresse programme entre 0-255 |
CLRF f | f=0 | met le contenu de f à zéro | CLRF COMPTEUR le registre représenté par COMPTEUR est mis à zéro |
CLRW | W=0 | met le contenu de W à zéro | CLRW |
CLRWDT | WDT=0 | la minuterie WatchDog est remise à zéro | CLRWDT Si le Watchdog timer est activé il doit-être réinitialisé avant son expiration sinon le MCU passe en mode sleep3 |
COMF f,d | d=complément de 1 de f | inverse les bits de f et les met dans d | COMF var1,W inverse var1 et met le résultat dans W |
DECF f,d | d=f-1 | soustrait 1 au contenu de f et met le résulat dans d | DECF tmp,F tmp=tmp-1 |
DECFSZ f,d | d=f-1 si d=0 saut conditionnel | comme DECF sauf que l'instruction suivante est sautée si le résultat est zéro | DECFSZ CMPT,W W=CMPT-1,si W=0 saute l'instruction suivante |
GOTO k | PCL=k | branchement incondionnel | GOTO main l'exécution du programme continu à l'étiquette main |
INCF f,d | d=f+1 | incrémente f et met le résultat dans d | INCF CMPT,F CMPT=CMPT+1 |
INCFSZ f,d | d=f+1,saut conditionnel à d=0 | le contenue de f est augmenté de 1 et le résultat est mis dans d si d=0 saute la prochaine instruction | INCFSZ CMPT,W W reçoit le résultat, si c'est zéro PCL=PCL+1 |
IORLW k | W=W | k | OU inclusif entre k et W avec résultat dans W | IORLW 3 ici W=W | 3 |
IORWF f,d | d=W | f | OU inclusif entre W et F | IORWF test,W ici W= W | test |
MOVF f,d | d=f | copie le registre f dans W ou sur lui-même4 | MOVF V1,W ici W=V1 |
MOVLW k | W=k | met la constante k dans W | MOLW 0x33 ici W=0x33 |
MOVWF f | f=W | copie W dans f | MOVWF V2 ici V2=W |
NOP | No Operation | utilise un cycle d'instruction sans rien faire | NOP est utile pour générer un délais bref. |
OPTION | OPTION=W | la valeur de W est copiée dans le registre OPTION | OPTION est un registe spécial utilisé pour configurer certains paramètres d'opération. |
RETLW k | W=k PCL=TOS,(Top of Stack) | sortie de sous-routine avec copie de k dans W | Pour les PIC de base une constante k est toujours copiée dans W à la sortie d'une sous-routine |
RLF f,d | C=f[7] f[0]=C | rotation à gauche d'un registre à travers le bit Carry | RLF serial,F ici le registre serial subit une rotation à gauche. Son bit le plus fort va dans le Carry bit son bit le plus faible est remplacé par le Carry bit. Le résultat de l'opération est conservé dans serial. |
RRF f,d | C=f[0] f[7]=C | rotation vers la droite d'un registre à travers le Carry bit | RRF ser,W ici c'est W qui reçoit le résultat de la rotation, ser n'est pas altéré. C est altéré par le bit 0 de ser |
SLEEP | suspension des opérations | met le MUC en mode suspendu, à faible consommation d'énergie | SLEEP arrête l'horloge et le MCU ne va redémarré que par un MCLR ou un changement d'état sur un GPIO. Économise les piles |
SUBWF f,d | d=f-w | la valeur de W est soustraite de la valeur de f.Affecte les états C,Z et DC du STATUS | SUBWF op1,F op1=op1-W |
SWAPF f,d | échange les nibbles de f | un nibble représente un demi-octet, soit les 4 bits faibles et les 4 bits forts | si reg1=0x5A, SWAPF reg1,F donne reg1=0xA5 |
TRIS f | f=W | met la valeur de W dans f | TRIS GPIO f est toujours GPIO car TRIS sert à programmer le mode E/S des GPIO |
XORLW k | W=W^k | ou exclusif de W avec la constante k | si W=0xAA, XORLW 0x55 alors W=0xFF |
XORWF f,d | d=W^f | ou exclusif entre Wet f | soit W=0xA5 et reg1=0xF0 alors XORWF reg1,F donne reg1=0x55 |
notes
1) l'instruction return n'existe pas vraiment, c'est une macro qui est remplacée par retlw 0.
2) Le caractère $ est utilisé pour représenter la position courante du compteur ordinal.
3) La fonction du WDT est de s'assurer que le programme se déroule normalement. Un programme s'exécute à l'intérieure d'une boucle principale. Si à cause d'une défaillance logicielle ou matériel le programme bloque quelque part dans la boucle et n'exécute pas l'instruction clrwdt la minuterie du WDT expirera et le MCU sera réinitialisé. Dans le registre STATUS il existe un bit qui peut-être vérifier pour savoir si le MCU a redémarré suite à une expiration du WDT permettant ainsi de savoir s'il y a eu défaillance. Habituellement on utilise le WDT que dans les applications critiques, mais il peut être utilisé à d'autres fonctions.
4) Copié un registre sur lui-même peut paraître inutile mais en fait l'opération affecte l'état bit Z dans le registre STATUS. Après cette opération on peut faire un test sur Z avec saut conditionnel.
2) Le caractère $ est utilisé pour représenter la position courante du compteur ordinal.
3) La fonction du WDT est de s'assurer que le programme se déroule normalement. Un programme s'exécute à l'intérieure d'une boucle principale. Si à cause d'une défaillance logicielle ou matériel le programme bloque quelque part dans la boucle et n'exécute pas l'instruction clrwdt la minuterie du WDT expirera et le MCU sera réinitialisé. Dans le registre STATUS il existe un bit qui peut-être vérifier pour savoir si le MCU a redémarré suite à une expiration du WDT permettant ainsi de savoir s'il y a eu défaillance. Habituellement on utilise le WDT que dans les applications critiques, mais il peut être utilisé à d'autres fonctions.
4) Copié un registre sur lui-même peut paraître inutile mais en fait l'opération affecte l'état bit Z dans le registre STATUS. Après cette opération on peut faire un test sur Z avec saut conditionnel.