MPASM 101

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

Le fichier commence par la directive #include. Chaque MCU PIC viens avec un fichier qui défini plusieurs constantes spécifiques à ce MCU.
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.

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
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
.
.
.
;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:
  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émoniqueactiondescriptionexemple
ADDWF f,dd=W+Fadditionne le le contenu de W avec le contenu de f et met le résultat dans dADDWF CMPT,F
ici W est additionné à CMPT et le résultat va dans CMPT
ANDLW kW=W&kET bit à bit entre W et kANDLW 0xA5
ANDWF f,dd=w&fET binaire entre W et fANDWF VAR,W
ici un ET bit à bit est effectué entre W et VAR et le résultat va dans W
BCF f,bforce à zéro le bit b de fBit Clear FileBCF GPIO,GP2
force la sortie GP2 à zéro
BSF f,bForce à 1 le bit b de fBit Set FileBSF flags,0
le bit 0 de flags est mis à 1
BTFSC f,bsaut conditionnel sur état bitsi le bit b de f est à zéro saute l'instruction suivanteBTFSC flags,1
saute l'instruction suivante si le bit 1 de flags est à zéro
BTFSS f,bsaut conditionnel sur état bitsi le bit b de f est à 1 saute l'instruction suivanteBTFSS STATUS,2
saute l'instruction suivante si le bit 2 de STATUS est à 1
CALL kappel de sous-routinela valeur actuelle de PCL est sauvegarder sur la pile des retours et remplacée par kCALL delais
delais est une étiquette représentant une adresse programme entre 0-255
CLRF ff=0met le contenu de f à zéroCLRF COMPTEUR
le registre représenté par COMPTEUR est mis à zéro
CLRWW=0met le contenu de W à zéroCLRW
CLRWDTWDT=0la minuterie WatchDog est remise à zéroCLRWDT
Si le Watchdog timer est activé il doit-être réinitialisé avant son expiration sinon le MCU passe en mode sleep3
COMF f,dd=complément de 1 de finverse les bits de f et les met dans dCOMF var1,W
inverse var1 et met le résultat dans W
DECF f,dd=f-1soustrait 1 au contenu de f et met le résulat dans dDECF tmp,F
tmp=tmp-1
DECFSZ f,dd=f-1
si d=0 saut conditionnel
comme DECF sauf que l'instruction suivante est sautée si le résultat est zéroDECFSZ CMPT,W
W=CMPT-1,si W=0 saute l'instruction suivante
GOTO kPCL=kbranchement incondionnelGOTO main
l'exécution du programme continu à l'étiquette main
INCF f,dd=f+1incrémente f et met le résultat dans dINCF CMPT,F
CMPT=CMPT+1
INCFSZ f,dd=f+1,saut conditionnel à d=0le 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 kW=W | kOU inclusif entre k et W avec résultat dans WIORLW 3
ici W=W | 3
IORWF f,dd=W | fOU inclusif entre W et FIORWF test,W
ici W= W | test
MOVF f,dd=fcopie le registre f dans W ou sur lui-même4MOVF V1,W
ici W=V1
MOVLW kW=kmet la constante k dans WMOLW 0x33
ici W=0x33
MOVWF ff=Wcopie W dans fMOVWF V2
ici V2=W
NOPNo Operationutilise un cycle d'instruction sans rien faireNOP est utile pour générer un délais bref.
OPTIONOPTION=Wla valeur de W est copiée dans le registre OPTIONOPTION est un registe spécial utilisé pour configurer certains paramètres d'opération.
RETLW kW=k
PCL=TOS,(Top of Stack)
sortie de sous-routine avec copie de k dans WPour les PIC de base une constante k est toujours copiée dans W à la sortie d'une sous-routine
RLF f,dC=f[7]
f[0]=C
rotation à gauche d'un registre à travers le bit CarryRLF 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,dC=f[0]
f[7]=C
rotation vers la droite d'un registre à travers le Carry bitRRF 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
SLEEPsuspension des opérationsmet le MUC en mode suspendu, à faible consommation d'énergieSLEEP 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,dd=f-wla valeur de W est soustraite de la valeur de f.Affecte les états C,Z et DC du STATUSSUBWF op1,F
op1=op1-W
SWAPF f,déchange les nibbles de fun nibble représente un demi-octet, soit les 4 bits faibles et les 4 bits fortssi reg1=0x5A, SWAPF reg1,F donne reg1=0xA5
TRIS ff=Wmet la valeur de W dans fTRIS GPIO
f est toujours GPIO car TRIS sert à programmer le mode E/S des GPIO
XORLW kW=W^kou exclusif de W avec la constante ksi W=0xAA, XORLW 0x55 alors W=0xFF
XORWF f,dd=W^fou exclusif entre Wet fsoit 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.