11.11 Exemple dapplication |
Toujours en partant de notre schéma, nous désirons allumer la LED lorsque nous
pressons le bouton, et léteindre lorsque nous le relâchons. Voici un exemple du
programme nécessaire (attention, prenez garde que le niveau sur RB2 passe à 0
lorsque le bouton est enfoncé (connexion à la masse)
|
bsf |
STATUS , RP0 |
; on passe en banque 1 |
|
bcf |
OPTION_REG, RBPU |
; résistance de rappel en service |
|
bcf |
TRISA , 2 |
; bit 2 de TRISA à 0 = sortie pour RA2 |
|
|
|
; inutile de configurer TRISB, car il est |
|
|
|
; en entrée par défaut au reset de la PIC |
|
bcf |
STATUS , RP0 |
; on repasse en banque 0 |
|
|
|
|
boucle |
|
|
; étiquette de début de la boucle principale |
|
btfss |
PORTB , 2 |
; tester RB2, sauter si vaut 1 |
|
bsf |
PORTA , 2 |
; RB2 vaut 0, donc on allume la LED |
|
btfsc |
PORTB , 2 |
; tester RB2, sauter si vaut 0 |
|
bcf |
PORTA , 2 |
; RB2 vaut 1, donc LED éteinte |
|
goto |
boucle |
; et on recommence |
11.12 La routine dinitialisation |
Examinons les instructions contenues à partir de létiquette
" init "
Les 2 premières lignes ,
|
clrf |
PORTA |
; sorties portA à 0 |
|
clrf |
PORTB |
; sorties portB à 0 |
... mettent les sorties des PORTs à 0. Comme ça, lorsque vous configurerez les ports
en sortie, ils enverront par défaut des niveaux 0V. Si votre électronique demande, pour
être sécurisée, dautres valeurs, à vous de vous en charger.
La ligne suivante permet de se connecter sur la banque1. Jusquà nouvel ordre,
les instructions suivantes utilisent des registres situés banque1.
Ensuite nous trouvons la ligne :
|
clrf |
EEADR |
; permet de diminuer la consommation |
Ne vous inquiétez pas pour cette ligne. Sachez seulement quune valeur
différente dans ce registre augmente légèrement la consommation desPIC version 16C84.
Cette ligne est donc un petit plus pour les montages alimentés par pile. Le registre
EEADR sera vu lors des accès à lEEPROM interne. Notez que ce bug a été corrigé
sur la 16F84. Cette ligne nest donc pas nécessaire dans ce cas.
Ensuite, nous trouvons :
|
movlw |
OPTIONVAL |
; charger masque |
|
movwf |
OPTION_REG |
; initialiser registre option |
Rappelez-vous que OPTIONVAL est une constante. Sa valeur a été définie
précédemment par nos soins en 0x08. Cette valeur sera envoyée ici dans le registre
OPTION (OPTION_REG). Souvenez-vous que OPTION_REG est le nom déclaré dans MPLAB pour le
registre OPTION.
Suit la petite routine suivante, destinée à effacer la RAM. Souvenez-vous, et
cest très important, quà la mise sous tension, la RAM contient des valeurs
aléatoires. Pour éviter les mauvaises surprises, jai intégré cette routine à
chaque démarrage, qui assure que le RAM ne contient que des 0.
|
|
; Effacer RAM |
|
|
|
|
|
|
movlw |
0x0c |
; initialisation pointeur |
|
movwf |
FSR |
; pointeur d'adressage indirect |
init1 |
|
|
|
|
clrf |
INDF |
; effacer ram |
|
incf |
FSR,f |
; pointer sur suivant |
|
btfss |
FSR,6 |
; tester si fin zone atteinte (>=0x40) |
|
goto |
init1 |
; non, boucler |
|
btfss |
FSR,4 |
; tester si fin zone atteinte (>=0x50) |
|
goto |
init1 |
; non, boucler |
|
|
|
; ici se trouve la suite |
Voilà donc un exemple concret de lutilisation de ladressage indirect.
En premier lieu, on initialise le pointeur vers la zone à manipuler (ici, la zone RAM
utilisateur). Cette zone commence à ladresse 0x0C (voir tableau 4-2) et se termine
à ladresse 0x4F incluse. La zone située dans la banque 1 nexiste pas pour
cette PIC (image de la banque0) et na donc pas besoin dêtre initialisée.
Puis vous trouvez linstruction clrf INDF, qui signifie donc, si vous avez suivi,
effacer lemplacement mémoire dont ladresse se trouve dans le registre FSR. On
efface donc la mémoire située à lemplacement 0x0C.
Vient ensuite lincrémentation de FSR, donc, maintenant, FSR POINTE sur 0x0D. On
trouve alors 2 tests. Pour sortir de cette routine et arriver à la ligne que jai
indiqué (ici se trouve la suite), il faudra donc éviter les 2 lignes goto.
Vous tombez sur le premier goto si le bit 6 de FSR vaut 1. Pour éviter ce goto, FSR
devra donc valoir au moins B01000000, ou encore 0x40. A ce moment, les
adresses 0x0C à 0x3F sont donc mises à 0.
On arrive au deuxième test. Le goto suivant sera donc sauté si le bit 4 de FSR est à
1. On arrivera donc à la ligne ici se trouve la suite uniquement lorsque les
bits 4 et 6 de FSR seront égaux à 1. Ceci donne ladresse suivante :
B01010000, soit 0x50.
Notez pour ceux qui ont suivi que vous auriez pu également utiliser un compteur de
boucles et vous servir de linstruction decfsz. Ceci aurait cependant nécessité
lutilisation dune variable, variable qui se serait effacée elle-même par la
présente routine, avec plantage du programme. Donc, la méthode présentée ici est donc
la plus simple.
Effacez ensuite les lignes suivante :
|
bcf |
TRISA,0 |
; Bit PORTA.0 en sortie (exemple) |
|
bcf |
STATUS,RP0 |
; Sélectionner banque 0 |
|
movlw |
INTERMASK |
; masque interruption |
|
movwf |
INTCON |
; charger interrupt control |
car elles ne nous intéressent pas pour notre programme.
Voyons maintenant ce que nous devons ajouter :
Tout dabord, nous devons initialiser RA2 en sortie, pour pourvoir agir sur la
LED. Linstruction sera donc :
Or, nous avons déclaré dans les DEFINE, LED comme étant égal à PORTA,2. Pour
permettre un remplacement facile de la LED sur une autre pin ,que se passe-t-il si nous
écrivons :
Certains vont dire " on éteint la LED ". Et bien, pas du tout.
Cette instruction sera remplacée par lassembleur par :
Autrement dit par :
Or, RP0 est toujours positionné à 1au moment de lexécution de cette ligne. On
pointe donc toujours sur la banque 1. Si vous regardez ce quil y a à ladresse
0x05 dans la banque1, vous trouvez TRISA. Donc, lopération a donc placé RA2 en
sortie.
En changeant donc simplement le DEFINE LED au début du programme, vous pouvez donc
changer toutes les références à RA2 dans tout le programme, même pour TRISA. Placez un
petit commentaire den-tête, et vous obtenez :
|
|
; initialisations spécifiques |
|
|
|
|
|
bcf |
LED |
; LED en sortie (banque1) |
Repassez ensuite en banque0 avant de quitter lintialisation. Cest une bonne
pratique, car beaucoup derreurs sont provoquées par des erreurs de banque, ajoutez
donc :
|
bcf |
STATUS , RP0 |
; Repasser en banque 0 |
Terminez avec la ligne :
qui branche le programme principal. Pour le moment, cela peut paraître inutile, car le
programme principal suit directement cette instruction, mais nous allons ajouter plus loin
un sous-programme qui sera intercalé entre les deux.
Voici à quoi devrait maintenant ressembler votre routine dinitialisation, que
vous devriez maintenant avoir entièrement comprise (pas besoin dun aspro ?).
Rassurez-vous, cela a lair dur au début, mais, une fois assimilé, cest
très simple. De plus, vous avez vu pratiquement tout ce quil faut savoir pour
réaliser un programme sans routines dinterruptions.
;********************************************************************* |
; INITIALISATIONS |
;********************************************************************* |
|
|
|
|
init |
|
|
|
|
clrf |
PORTA |
; sorties portA à 0 |
|
clrf |
PORTB |
; sorties portB à 0 |
|
bsf |
STATUS, RP0 |
; sélectionner banque 1 |
|
clrf |
EEADR |
; permet de diminuer la consommation |
|
movlw |
OPTIONVAL |
; charger masque |
|
movwf |
OPTION_REG |
; initialiser registre option |
|
|
|
|
|
|
; Effacer RAM |
|
|
|
|
|
|
movlw |
0x0c |
; initialisation pointeur |
|
movwf |
FSR |
; pointeur d'adressage indirect |
init1 |
|
|
|
|
clrf |
INDF |
; effacer ram |
|
incf |
FSR,f |
; pointer sur suivant |
|
btfss |
FSR,6 |
; tester si fin zone atteinte (>=0x40) |
|
goto |
init1 |
; non, boucler |
|
btfss |
FSR,4 |
; tester si fin zone atteinte (>=0x50) |
|
goto |
init1 |
; non, boucler |
|
|
|
|
|
|
; initialisations spécifiques |
|
|
|
|
|
bcf |
LED |
; LED en sortie (banque1) |
|
bcf |
STATUS , RP0 |
; Repasser en banque 0 |
|
goto |
start |
; sauter au programme principal |
Descendons maintenant dans le programme principal, et supprimons la ligne :
|
clrwdt |
|
; effacer watch dog |
11.13 Les résultats de la compilation |
Lancez la compilation par la touche <F10>.
Dans la fenêtre de résultat de compilation, vous devez trouver une ligne du type :
Message[302] D:\DOCUME~1\LESSONS\DATAPIC\LED_CLI.ASM 101 : Register in
operand not in bank 0. Ensure that bank bits are correct. |
Ceci est un message davertissement (warning) qui vous signale quà la ligne
101 (dans mon cas, mais suivant vos espaces ou commentaires, vous aurez un autre numéro
de ligne), vous avez accédé à un registre qui nest pas situé en banque0.
MPASM vous demande de vérifier que votre bits RP0 est positionné correctement. Allez
dans l éditeur sur la ligne dont vous avez le numéro. Vous tombez sur la
ligne :
|
movwf |
OPTION_REG |
; initialiser registre option |
Un coup dil sur le tableau 4-2 vous indique que le registre OPTION est dans
la banque1. Mais comme votre RP0 a été positionné sur 1 à cet endroit, il ny a
pas de problème. Quand vous aurez réalisé de gros programmes, vous aurez énormément
de warning de ce type .
11.14 Le programme principal |
Nous voulons faire clignoter notre LED à une fréquence de 1Hz (1 clignotement par
seconde) . Nous allons donc créer un programme de la forme :
debut
- Jallume la LED
- Jattends ½ seconde
- Jéteins la LED
- Jattends ½ seconde
- Je retourne au début
Nous voyons que nous utilisons 2 fois la temporisation de ½ seconde. Nous créerons
donc un sous-programme que nous appellerons " tempo ". Notre programme
principal aura donc laspect suivant :
start |
|
|
|
|
bsf |
LED |
; allumer la LED |
|
call |
tempo |
; appeler la tempo de 0.5s |
|
bcf |
LED |
; éteindre LED |
|
call |
tempo |
; appeler la tempo de 0.5s |
|
goto |
start |
; boucler |
On aurait pu écrire également (en utilisant nos macros) :
start |
|
|
|
|
LEDON |
|
; allumer la LED |
|
call |
tempo |
; appeler la tempo de 0.5s |
|
LEDOFF |
|
; éteindre LED |
|
call |
tempo |
; appeler la tempo de 0.5s |
|
goto |
start |
; boucler |
Choisissez la méthode que vous préférez. Limportant, cest davoir
compris les deux méthodes.
11.15 La sous-routine de temporisation |
Nous navons pas encore vu le timer0, ni les interruptions. Le but ici est de vous
faire comprendre le fonctionnement de la 16F84. Pour réaliser une tempo, il suffit dans
notre cas de faire perdre du temps à la 16F84 entre chaque inversion de la LED.
Nous devons donc perdre approximativement 0.5s. Les secondes ne sont pas appropriées
pour les pics, qui travaillent à une vitesse beaucoup plus élevée. Nous utiliserons
donc des unités de temps compatibles avec la PIC.
Notre PIC est cadencée à la fréquence de notre quartz, soit 4MHz. Or, la PIC
exécute un cycle dinstruction tous les 4 cycles de lhorloge principale. La
PIC exécutera donc (4.000.000/4) = 1 million de cycles par seconde.
La plupart des instructions (hormis les sauts) sexécutent en 1 cycle, ce qui
vous donne approximativement un million dinstructions par seconde. Vous verrez
parfois la dénomination MIPS. Ceci signifie Million dInstructions Par Seconde.
Notre PIC avec ce quartz a donc une puissance de traitement de près de 1MIPS.
Chaque cycle dinstruction dure donc 1 millionième de seconde, ou encore une
microseconde (µs). Voilà donc lunité pour travailler avec notre PIC. Donc, notre
tempo de 0.5s est donc une tempo de 500.000 microsecondes. Autrement dit, nous devons
" perdre pour rien " 500.000 cycles dans notre routine de
temporisation. Vous voyez donc que votre PIC, pour notre application, va passer son temps
à ne rien faire dutile.
La première idée qui vient à lesprit est de réaliser une boucle qui va
incrémenter ou décrémenter une variable.
Réalisons-la. Commençons donc par déclarer notre variable (cmpt1) dans la zone de
RAM. Ajoutons donc cette déclaration . Nous obtenons :
;********************************************************************* |
; DECLARATIONS DE VARIABLES |
;********************************************************************* |
|
|
|
|
|
CBLOCK |
0x00C |
; début de la zone variables |
|
|
cmpt1 : 1 |
; compteur de boucles 1 |
|
ENDC |
|
; Fin de la zone |
Créons maintenant lossature de notre sous-routine, que nous placerons entre la
routine dinitialisation et le programme principal. Noubliez pas de toujours
utiliser des commentaires :
;********************************************************************* |
; SOUS-ROUTINE DE TEMPORISATION |
;********************************************************************* |
;--------------------------------------------------------------------------------------------------------
|
; Cette sous-routine introduit un retard de 500.000 µs. |
; Elle ne reçoit aucun paramètre et n'en retourne aucun |
;--------------------------------------------------------------------------------------------------------
|
tempo |
|
|
|
|
; nous allons placer notre code ici |
|
return |
|
; retour de la sous-routine |
Réalisons maintenant notre boucle.
tempo |
|
|
|
|
clrf |
cmpt1 |
; effacer compteur1 |
boucle1 |
|
|
|
|
decfsz |
cmpt1 |
; décrémenter compteur1 |
|
goto |
boucle1 |
; si pas 0, boucler |
|
return |
|
; retour de la sous-routine |
Lançons la compilation (F10) :
Nous obtenons dans la fenêtre des résultats une ligne supplémentaire de la
forme :
Message[305] D:\DOCUME~1\LESSONS\DATAPIC\LED_CLI.ASM 134 : Using
default destination of 1 (file). |
Le numéro de ligne peut varier suivant votre code source. Comme la compilation
sest effectuée correctement, il sagit une fois de plus dun message de
type warning. Positionnez-vous dans léditeur sur la ligne incriminée (ligne 134
pour moi). Vous êtes sur la ligne :
|
decfsz |
cmpt1 |
; décrémenter compteur1 |
En effet, linstruction decfsz est de la forme " decfsz f ,
d ". Nous avons donc oublié dindiquer la destination de
lopération. MPLAB vous signale quil a utilisé pour vous ,f
Faites donc attention, car si vous aviez voulu obtenir le résultat dans w, votre
programme était faux. Modifiez donc votre commande en ajoutant ,f
|
decfsz |
cmpt1, f |
; décrémenter compteur1 |
Maintenant, nous allons calculer la durée de cette tempo.
tempo |
|
|
|
|
clrf |
cmpt1 |
; 1 cycle |
boucle1 |
|
|
|
|
decfsz |
cmpt1, f |
; 1 cycle si on ne saute pas, 2 si on saute. Donc, on ne sautera |
|
|
|
; pas 255 fois et on sautera 1 fois |
|
goto |
boucle1 |
; 2 cycles multiplié par 255 passages |
|
return |
|
; 2 cycles. |
Le temps total est donc de :
2 cycles pour lappel
de la sous-routine (call tempo)
1 cycle pour le reset de la
variable
257 cycles pour les 256 décrémentations
510 cycles pour les 255 goto
2 cycles pour le return.
Soit un total de 772 cycles. On est loin des 500.000 cycles nécessaire. Pour la suite
des calculs, nous allons négliger les 2 cycles du call et les 2 cycles du return
(comparés aux 500.000 cycles nécessaires).
Bon, nous allons allonger notre routine, en réalisant une seconde boucle qui va forcer
la première boucle à sexécuter 256 fois. Commençons par déclarer une nouvelle
variable cmpt2 :
|
|
cmpt1 : 1 |
; compteur de boucles 1 |
|
|
cmpt2 : 1 |
; compteur de boucles 2 |
Ecrivons donc les 2 boucles imbriquées :
tempo |
|
|
|
|
clrf |
cmpt2 |
; effacer compteur2 |
boucle2 |
|
|
|
|
clrf |
cmpt1 |
; effacer compteur1 |
boucle1 |
|
|
|
|
decfsz |
cmpt1, f |
; décrémenter compteur1 |
|
goto |
boucle1 |
; si pas 0, boucler |
|
decfsz |
cmpt2 , f |
; si 0, décrémenter compteur 2 |
|
goto |
boucle2 |
; si cmpt2 pas 0, recommencer boucle1 |
|
return |
|
; retour de la sous-routine |
Vous voyez que notre première boucle est toujours là, mais au lieu deffectuer
le return une fois terminée, nous recommençons la boucle tant que cmpt2 ne soit pas
également égal à 0. Nous allons donc exécuter 256 fois notre boucle 1
Quelle est la temporisation obtenue ? Calculons approximativement :
Durée de la boucle 1 : 257 cycles + 510 cycles + 1 cycle (clrf cmpt1) = 768
cycles.
Or cette boucle va être exécutée 256 fois, donc 768*256 = 196608 cycles, auquel il
convient dajouter les quelques cycles dinitialisation etc.
Or, nous désirons 500.000 cycles. Nous devrons donc utiliser cette boucle
(500.000/196608) = 2, 54 fois. Nous ne savons pas faire de demi boucle. Nous effectuerons
donc 2 boucles. Nous allons nous arranger pour que nos deux premières boucles durent
500.000/2 = 250.000 cycles.
Chaque cycle ajouté dans la boucle1 est exécuté 256*256 fois. Chaque cycle exécuté
dans la boucle 2 est exécuté 256 fois. Chaque cycle extérieur aux boucles est exécuté
1 fois. Nous avons donc la possibilité de réaliser des temporisations très précises.
Ce nest pas nécessaire ici. Cependant, nous allons quand même améliorer la
précision.
Si nous ajoutons 1 cycle inutile dans la boucle1, nous ajouterons 256*256 = 65536
cycles. Nous devons en ajouter 250.000 196608 = 53392. Cela nous donnerait une
erreur de 12000 cycles, soit 12 millièmes de seconde (12ms). La précision est largement
suffisante pour notre application, mais je rappelle que vous pouvez affiner cette
précision en effectuant les calculs précis. Je vous conseille même de le faire
vous-même comme exercice. Vous pourrez vérifier vos résultats avec un chronomètre.
Pour perdre un cycle, nous ajouterons simplement linstruction NOP, qui ne fait
rien.
Reste donc à réaliser la dernière boucle de 2. Créons une troisième variable cmpt3
et une troisième boucle. (Rassurez-vous, il y a des méthodes plus simple, ceci est
expliqué dans un but didactique : il importe simplement davoir compris).
;********************************************************************* |
; DECLARATIONS DE VARIABLES |
;********************************************************************* |
|
|
|
|
|
CBLOCK |
0x00C |
; début de la zone variables |
|
|
cmpt1 : 1 |
; compteur de boucles 1 |
|
|
cmpt2 : 1 |
; compteur de boucles 2 |
|
|
cmpt3 : 1 |
; compteur de boucles 3 |
|
ENDC |
|
; Fin de la zone |
Voici le code final :
;********************************************************************* |
; SOUS-ROUTINE DE TEMPORISATION |
;********************************************************************* |
;--------------------------------------------------------------------------------------------------------
|
; Cette sous-routine introduit un retard de 500.000 µs. |
; Elle ne reçoit aucun paramètre et n'en retourne aucun |
;--------------------------------------------------------------------------------------------------------
|
tempo |
|
|
|
|
movlw |
2 |
; pour 2 boucles |
|
movwf |
cmpt3 |
; initialiser compteur3 |
boucle3 |
|
|
|
|
clrf |
cmpt2 |
; effacer compteur2 |
boucle2 |
|
|
|
|
clrf |
cmpt1 |
; effacer compteur1 |
boucle1 |
|
|
|
|
nop |
|
; perdre 1 cycle *256 *256 *2 |
|
decfsz |
cmpt1 , f |
; décrémenter compteur1 |
|
goto |
boucle1 |
; si pas 0, boucler |
|
decfsz |
cmpt2 , f |
; si 0, décrémenter compteur 2 |
|
goto |
boucle2 |
; si cmpt2 pas 0, recommencer boucle1 |
|
decfsz |
cmpt3 , f |
; si 0, décrémenter compteur 3 |
|
goto |
boucle3 |
; si cmpt3 pas 0, recommencer boucle2 |
|
return |
|
; retour de la sous-routine |
Lancez la compilation avec <F10>. Si tout sest bien passé, vous obtenez
dans votre répertoire de travail le fichier LED_CLI.HEX
Envoyez- le dans votre PIC à laide de votre programmateur.
Placez la PIC sur la carte HORS TENSION.
Envoyez lalimentation et regardez bien :
VOTRE LED CLIGNOTE A LA FREQUENCE DE 1HZ.
Félicitations, voici votre premier programme embarqué. La porte est ouverte à des
tonnes de nouvelles applications.
CONSEIL IMPORTANT
Si, à ce stade, vous sautez au plafond en criant " CA MARCHE "
(jai connu ça) [ NDLR :Les Fribottes aussi.] , ne
vous précipitez pas en hurlant pour faire admirer votre réalisation à votre épouse. Je
doute quelle partage votre enthousiasme pour " cette petite lampe qui
clignote ".
Le fichier tel quil devrait être à la fin de cette leçon est disponible dans
les fichiers joints.[ NDLR : Fichiers exemples
ici ]
|