LA PROGRAMMATION DES PICS

1. Introduction


Et voilà, nous voilà partis ensemble pour cette grande aventure qu’est la programmation des pics. Je vais tenter de rester le plus concret possible, mais, cependant, une certaine part de théorie est indispensable pour arriver au but recherché.


Je vais donc commencer ce petit cours par un rappel sur les systèmes de numérotation. Ca y est, j’en vois qui râlent déjà. Mais je suis sûr que vous comprendrez qu’il est impossible de programmer sérieusement un microcontroleur sans savoir ce qu’est un bit, ou comment convertir les notations décimales en hexadécimales.


Rassurez-vous, je vais faire bref, et nous pourrons très rapidement aborder le sujet qui nous intéresse tant. Si vous êtes déjà un « pro », vous pouvez sauter le premier chapitre et passer directement au suivant.


N’hésitez jamais à me faire part de vos remarques, ni à me signaler les erreurs qui m’auraient échappées. Faites du copier/coller, répercutez les infos que vous trouverez ici, traduisez le document dans une autre langue ou un autre format. Simplement, dans ce cas, veuillez respecter les désirs de l’auteur en fin d’ouvrage et faites moi parvenir un exemplaire de votre travail. Ceci pour permettre de faire profiter le plus grand nombre de votre travail.

J’attire votre attention sur le fait que ce cours, pour être efficace, doit être lu tout en réalisant les exercices que je vous propose. Les solutions des exercices sont disponibles sous forme de fichiers exemples fournis en annexe de ce cours.

Tout ce qu’il vous faudra, c’est une 16F84, un quartz de 4MHz, une petite platine d’essais, une LED, un bouton poussoir et le logiciel MPLAB, mis gracieusement à votre disposition par la société Microchip à l’adresse http://www.microchip.com.. A cette même adresse, vous pourrez vous procurer le datasheet de la 16F84.


J’ai utilisé personnellement la version 5.20 de MPLAB. Si vous utilisez une autre version, vous trouverez peut-être des différences. Je ne doute pas que vous arriviez à surmonter ces petites difficultés.

Notez que lors de cette révision, j’utilise maintenant la version 5.4, et ceci sans aucune modification visible au niveau de l’interface utilisateur.


2. Les systèmes de numérotation

2.1 Le système décimal

Nous sommes habitués, depuis notre enfance à utiliser le système numérique décimal, à tel point que nous ne voyons même plus la manière donc ce système fonctionne, tant c’est devenu un automatisme.

Décimal, pourquoi ? Parce qu’il utilise une numérotation à 10 chiffres. Nous dirons que c’est un système en BASE 10. Pour la petite histoire, on a utilisé un système base 10 car nos ancêtres ont commencé à compter sur leurs 10 doigts, pas besoin d’aller chercher plus loin. Mais la position des chiffres a également une grande importance. Les chiffres les moins significatifs se situent à droite du nombre, et leur importance augmente au fur et à mesure du déplacement vers la gauche. En effet, dans le nombre 502, le 5 à une plus grande importance que le 2. En réalité, chaque chiffre, que l’on peut appeler DIGIT, à une valeur qui dépend de son RANG . Quel est ce rapport ? Il s’agit tout simplement de l’élévation de la BASE utilisée élevé à la puissance de son RANG .

Cela a l’air complexe à écrire, mais est très simple à comprendre. Quand vous avez compris ceci, vous comprendrez automatiquement n’importe quel système de numérotation.
Reprenons, par exemple notre nombre 502. Que signifie-t-il ? Et bien, tout simplement que sa valeur est égale à 2 multiplié par la base (10) élevée à la puissance du rang du chiffre, c’est à dire 0. Remarquez ici une chose très importante : le comptage du rang s’effectue toujours de droite à gauche et en commençant par 0. Pour notre nombre 502, sa valeur est donc en réalité :
502 = 2*10° + 0*101 + 5*10². Notez que le symbole * est utilisé pour indiquer « multiplié ». Et rappelez-vous que 10° = (10/10) = 1, que 101 = 10, et que 10² = 10*10 = 100 etc.


2.2 Le système binaire

Vous avez compris ce qui précède ? Alors la suite va vous paraître simple.
Cela ne pose aucun problème pour vous de compter sur vos 10 doigts, mais pour les ordinateurs, cela n’est pas si simple. Ils ne savent faire la distinction qu’entre 2 niveaux (présence ou absence de tension). Le système de numérotation décimal est donc inadapté.

On comprendra immédiatement que le seul système adapté est donc un système en base 2, appelé système binaire. Ce système ne comporte donc que 2 chiffres, à savoir 0 et 1. Comme, de plus, les premiers ordinateurs (et les pics) travaillent avec des nombres de 8 chiffres binaires, on a donc appelé ces nombres des octets (ou bytes en anglais). Le chiffre 0 ou 1 est appelé un BIT(unité binaire, ou BInary uniT) .

Pour nous y retrouver dans la suite de ce petit ouvrage, on adoptera les conventions suivantes : tout nombre décimal est écrit tel quel, ou en utilisant la notation D’xxx’ ; tout nombre binaire est écrit suivant la forme B’xxxxxxxx’ dans lesquels les ‘x’ valent… 0 ou 1, vous avez suivi.

Analysons maintenant un nombre binaire, soit l’octet : B’10010101’. Quelle est donc sa valeur en décimal ?

Et bien, c’est très simple, on applique le même algorithme que pour le décimal. Partons de la droite vers la gauche, on trouve donc :

B’10010101’ = 1*2° + 0*21 + 1*2² + 0*23 + 1* 24 + 0*25 + 0*26 + 1*27

Comme, évidemment 0 multiplié par quelque chose = 0 et que 1 multiplié par un chiffre = le chiffre en question, on peut ramener le calcul précédent à :

B ‘10010101’ = 1+4+16+128 = 149

Vous voyez donc qu’il est très facile de convertir n’importe quel chiffre de binaire en décimal. Et l’inverse me direz-vous ? Et bien, c’est également très simple. Il faut juste connaître votre table des exposants de 2. Cela s’apprend très vite lorsqu’on s’en sert.

On procède simplement de la manière suivante (il y en a d’autres) :

Quel est le plus grand exposant de 2 contenu dans 149 ? Réponse 7 (27 = 128)
On sait donc que le bit7 vaudra 1. Une fois fait, il reste 149-128 = 21
Le bit 6 représente 64, c’est plus grand que 21, donc b6 = 0
Le bit 5 représente 32, c’est plus grand que 21, donc b5 = 0
Le bit 4 représente 16, donc ça passe, b4 = 1, il reste 21-16 = 5
Le bit 3 représente 8, c’est plus grand que 5, donc b3 = 0
Le bit 2 représente 4, donc b2 = 1, reste 5-4 = 1
Le bit1 représente 2, c’est plus grand que 1, donc b1 = 0
Le bit 0 représente 1, c’est ce qu’il reste, donc b1=1, reste 0

Le nombre binaire obtenu est donc B’10010101’, qui est bien notre octet de départ.
Notez que si on avait trouvé un nombre de moins de 8 chiffres, on aurait complété avec des 0 placés à gauche du nombre. En effet, B’00011111’ = B ‘11111’, de même que 0502 = 502.

Pensez à toujours compléter les octets de façon à obtenir 8 bits, car c’est imposé par la plupart des assembleurs (nous verront ce que c’est dans la suite de ces leçons).

Notez que la plus grande valeur pouvant être représentée par un octet est donc : B’11111111’. Si vous faites la conversion (ou en utilisant la calculette de windows en mode scientifique), vous obtiendrez 255. Tout nombre supérieur à 255 nécessite donc plus d’un octet pour être représenté.


2.3 Le système hexadécimal

La représentation de nombres binaires n’est pas évidente à gérer, et écrire une succession de 1 et de 0 représente une grande source d’erreurs. Il fallait donc trouver une solution plus pratique pour représenter les nombres binaires. On a donc décider de couper chaque octet en 2 (QUARTET) et de représenter chaque partie par un chiffre.

Comme un quartet peut varier de b’0000’ à b’1111’, on constate que l’on obtient une valeur comprise entre 0 et 15. Cela fait 16 combinaisons. Les 10 chiffres du système décimal ne suffisaient donc pas pour coder ces valeurs.

Plutôt que d’inventer 6 nouveaux symboles, il a été décidé d’utiliser les 6 premières lettres de l’alphabet comme CHIFFRES. Ce système de numérotation en base 16 a donc été logiquement appelé système hexadécimal.

Notez que ce système est simplement une représentation plus efficace des nombres binaires, et donc que la conversion de l’un à l’autre est instantanée. Dans la suite de ces leçons, nous noterons un nombre hexadécimal en le faisant précéder de 0x. Voyons si vous avez bien compris :

Pour représenter un octet il faut donc 2 digits hexadécimaux. Par exemple, notre nombre B’10010101’ est représenté en hexadécimal par 0x95. Si vous faites la conversion de l’hexadécimal vers le décimal, vous utilisez le même principe que précédemment, et vous obtenez 0x95 = 9*161 + 5*16° = 149, ce qui est heureux.

Pour preuve, quel est le plus grand nombre hexadécimal de 2 digits pouvant être représenté ? Réponse : 0xFF, soit 15*16 + 15 = 255.

Si vous avez bien tout compris, vous êtes maintenant capable de convertir n’importe quel nombre de n’importe quelle base vers n’importe quelle autre.


2.4 Les opérations

Après avoir converti les nombres dans différents formats, vous allez voir qu’il est également très simple de réaliser des opérations sur ces nombres dans n’importe quel format.
Il suffit pour cela d’effectuer les mêmes procédures qu’en décimal.

Petit exemple : Que vaut B’1011’ + B ‘0110’ ?
Et bien, on procède exactement de la même façon que pour une opération en décimal.

B’1011 ‘
+ B’ 1110 ‘
---------------

On additionne les chiffres de droite, et on obtient 1+0 = 1
On écrit 1
On additionne 1 + 1 , et on obtient 10 (2 n’existe pas en binaire). On écrit 0 et on reporte 1
On additionne 0+1+le report et on obtient 10. On écrit 0 et on reporte 1
On additionne 1+1+le report et on obtient 11. On écrit 1 et on reporte 1
Reste le report que l’on écrit, soit 1.

La réponse est donc B’11001’, soit 25.

Les 2 nombres de départ étant B’1011’, soit 11, et B’1110’, soit 14. Vous procéderez de la même manière pour les nombres hexadécimaux, en sachant que :
0xF + 0x1 = 0x10, soit 15+1 = 16.


2.5 Les nombres signés

Dans certaines applications, il est nécessaire de pouvoir utiliser des nombres négatifs.
Comme les processeurs ne comprennent pas le signe « - » , et comme il fallait limiter la taille des mots à 8 bits, la seule méthode trouvée a été d’introduire le signe dans le nombre.

On a donc choisi (pas au hasard) le bit 7 pour représenter le signe. Dans les nombres signés, un bit 7 à ‘1’ signifie nombre négatif. Si on s’était contenté de cela, on aurait perdu une valeur possible. En effet, B’10000000’ (-0) était alors égal à B’00000000’ (0). De plus, pour des raisons de facilité de calcul, il a été décidé d’utiliser une notation légèrement différente.

Pour rendre un nombre négatif, il faut procéder en 2 étapes.

1) On inverse la totalité du nombre.
2) On ajoute 1
On obtient alors ce qu’on appelle le COMPLEMENT A DEUX du nombre.

Exemple : soit le nombre 5 : B’00000101’ Comment écrire –5 ?

1) on inverse tous les bits (complément à 1) B’11111010’
2) on ajoute 1 (complément à 2) –5 = B’11111011’

Pour faire la conversion inverse, on procède de manière identique.

1) on inverse tous les bits B’00000100’
2) On ajoute 1 B‘00000101’

Et on retrouve notre 5 de départ

Dans le cas des nombres signés, on obtient donc les nouvelles limites suivantes :
La plus grande valeur est B’01111111’, soit +127
La plus petite valeur devient B’10000000’, soit –128.

Remarquez que les opérations continuent de fonctionner. Prenons –3 + 5

B’11111101’ (-3)
+ B’00000101’ (5)
= B’100000010’ (2)

Et la, me direz vous, ça ne fait pas 2 ? Et bien si, regardez bien, il y a 9 bits , or le processeur n’en gère que 8. Le 9ème est donc tombé dans un bit spécial que nous verront plus tard. Dans le registre du processeur, il reste donc les 8 bits de droite, soit 2, qui est bien égal à (–3 )+ 5.

Maintenant, si vous avez bien suivi, vous êtes en train de vous poser la question suivante : Quand je vois B’11111101’, est-ce que c’est –3 ou est-ce que c’est 253 ? Et bien vous ne pouvez pas le savoir sans connaître le contexte.

Sachez que les nombres signifient uniquement ce que le concepteur du programme en a décidé. S’il travaille avec des nombres signés ou non, ou si cet octet représente tout autre chose. La seule chose qui importe c’est de respecter les conventions que vous vous êtes fixés lors de la création de cet octet. C’est donc à vous de décider de quoi vous avez besoin pour tel type de données.


2.6 Les opérations booléennes.

Qu’est-ce que c’est que ça, me direz-vous ? Et bien, pour faire simple, disons que ce sont des opérations qui s’effectuent bit par bit sur un octet donné. Plutôt qu’une grosse théorie sur l’algèbre de boole (j‘en vois qui respirent), je vais donner dans le concret en présentant les opérations indispensables à connaître dans la programmation des pics et autres microcontrôleurs.


2.6.1 Le complément

Que vous trouverez également sous les formes « inversion » ou « NOT » ou encore complément à 1. Elle est souvent notée « ! »
C’est tout simple, cela consiste à inverser tous les bits de l’octet.
Exemple : NOT B’10001111’ donne ‘B01110000 ‘. Vous voyez ici que pour les opérations booléennes, il est plus facile de travailler en binaire. Traduisez l’exemple ci-dessus en hexadécimal (on dira maintenant « hexa ») ou en décimal et essayez de complémenter directement. Bonjour les neurones.
A quoi sert-elle ? Par exemple à lire une valeur dont les niveaux actifs ont été inversés, à réaliser des nombres négatifs, ou autres.


2.6.2 La fonction « ET » ou « AND »

Appelée également multiplication bit à bit, ou « AND » et souvent notée « & »
Elle consiste à appliquer un mot sur un autre mot et à multiplier chaque bit par le bit de même rang. Pour faire une opération « ET », il faut donc toujours 2 octets.

Les différentes possibilités sont données ci-dessous (le tableau se lit horizontalement)
Première ligne : 0 AND 0 = 0. Ce type de tableau s’appelle « table de vérité »

Petit exemple B’10001000’ OR B’11000000’ donne B’11001000’

A quoi sert cette instruction ? Et bien, tout simplement elle permet de forcer n’importe quel bit d’un mot à 1 sans connaître son contenu.

Vous voyez que dans l’exemple précédent, les 2 premiers bits ont été forcés au niveau 1, indépendamment de leur niveau précédent.
2.6.4 La fonction « OU EXCLUSIF » ou « Exclusif OR » ou « XOR »

Voici la dernière fonction que nous allons aborder dans cette mise à niveau. Elle est souvent appelée XOR (eXlusif OR). Elle se comporte comme la fonction OR, à un détail près.

Pour obtenir 1, il faut que le Bit1 soit à 1 OU que le Bit2 soit à 1 à l’EXCLUSION des deux bits ensemble. Si les 2 bits sont à 1, alors le résultat sera 0.

Voici donc la table de vérité.


Bit1
Bit2
XOR
0
0
0
0
1
1
1
0
1
1
1
0

Petit exemple : B’10001000 ‘ XOR B’11000000’ donne B’01001000 ‘

A quoi sert cette instruction ? et bien tout simplement à inverser un ou plusieurs bits dans un mot sans toucher aux autres. Dans l’exemple précédent, vous voyez qu’à l’emplacement des 2 bits à 1 du 2ème octet, les bits correspondants du 1er octet ont été inversés.

Voilà, ainsi se termine le premier chapitre consacrée aux pics. Je sais qu’il était particulièrement rébarbatif, mais, si vous ne maîtrisez pas parfaitement ce qui vient d’être expliqué, vous ne pourrez pas réaliser correctement vos propres programmes.

3. Composition et fonctionnement des PICs


Enfin quelque chose de plus intéressant. Nous allons maintenant nous pencher sur une PIC, et en particulier sur la 16F84. Rassurez-vous, tout ce que nous verrons sur la 16F84 pourra être directement utilisé sur les 16F876, qui ne sont rien d’autre que des 16F84 améliorées. Chaque PIC dispose des fonctionnalités des modèles inférieurs, augmentées de nouvelles fonctions.

Tout d’abord, vous devez télécharger le datasheet de la 16F84, car c’est un document que nous allons utiliser dans le reste de ces petites leçons. Je vous conseille vivement de l’imprimer, car vous en aurez toujours besoin quand vous vous lancerez dans la réalisation de vos propres programmes.

Ces datasheets sont mes livres de chevet. J’ai trouvé plus judicieux de travailler par la pratique et de commenter, plutôt que de traduire bêtement les datasheets en question.


3.1 Qu’est-ce qu’une PIC ?

Une PIC n’est rien d’autre qu’un microcontrôleur, c’est à dire une unité de traitement de l’information de type microprocesseur à laquelle on a ajouté des périphériques internes permettant de réaliser des montages sans nécessiter l’ajout de composants externes.

La dénomination PIC est sous copyright de Microchip, donc les autres fabricants ont été dans l’impossibilité d’utiliser ce terme pour leur propre microcontrôleurs.

Les PICs sont des composants dits RISC (Reduce Instructions Construction Set), ou encore composant à jeu d’instructions réduit. Pourquoi ? Et bien, sachez que plus on réduit le nombre d’instructions, plus facile et plus rapide en est le décodage, et plus vite le composant fonctionne.

Toutes les xs Mid-Range ont un jeu de 35 instructions, stockent chaque instruction dans un seul mot de programme, et exécutent chaque instruction (sauf les sauts) en 1 cycle. On atteint donc des très grandes vitesses, et les instructions sont de plus très rapidement assimilées.

L’horloge fournie à la PIC est prédivisée par 4 au niveau de celle-ci. C’est cette base de temps qui donne le temps d’un cycle.

Si on utilise par exemple un quartz de 4MHz , on obtient donc 1000000 de cycles/seconde, or, comme la pic exécute pratiquement 1 instruction par cycle, hormis les sauts, cela vous donne une puissance de l’ordre de 1MIPS (1 Million d’Instructions Par Seconde).

Pensez que les pics peuvent monter à 20MHz. C’est donc une vitesse de traitement plus qu’honorable.



3.2 Les différentes familles des PICs

La famille des PICs est subdivisée en 3 grandes familles : La famille Base-Line, qui utilise des mots d’instructions (nous verrons ce que c’est) de 12 bits, la famille Mid-Range, qui utilise des mots de 14 bits (et dont font partie la 16F84 et 16F876), et la famille High-End, qui utilise des mots de 16 bits.

Nous nous limiterons dans cet ouvrage à la famille Mid-Range, sachant que si vous avez tout compris, vous passerez très facilement à une autre famille, et même à un autre microcontrôleur.

Notez dès à présent que le datasheet de la 16F84 n’est qu’une petite partie de la documentation complète. Pour obtenir la documentation complète, vous ajoutez encore plus de 600 pages en téléchargeant chez Microchip les datasheets pour la gamme Mid-Range.

Cependant, la documentation de base suffit pour 99,9% des applications, et, de plus, les datasheets Mid-Range sont disponibles sous la forme d’un fichier par chapitre. J’ai pour ma part presque tout imprimé, mais je vous conseille plutôt d’aller les chercher le jour où vous en aurez besoin.


3.3 Identification d’une PIC

Pour identifier une PIC, vous utiliserez simplement son numéro.
Les 2 premiers chiffres indiquent la catégorie de la PIC, 16 indique une PIC Mid-Range.
Vient ensuite parfois une lettre L : Celle-ci indique que la PIC peut fonctionner avec une plage de tension beaucoup plus tolérante.

Ensuite, vous trouvez :

- C indique que la mémoire programme est une EPROM ou plus rarement une EEPROM
- CR pour indiquer une mémoire de type ROM
- Ou F pour indiquer une mémoire de type FLASH.

Notez à ce niveau que seule une mémoire FLASH ou EEPROM est susceptible d’être effacée, donc n’espérez pas reprogrammer vos PICs de type CR. Pour les versions C, voyez le datasheet

Puis vous verrez les derniers chiffres identifient précisément la PIC. (84)

Enfin vous verrez sur les boîtiers « -XX » dans laquelle XX représente la fréquence d’horloge maximale que la PIC peut recevoir. Par exemple –04 pour une 4MHz.

Donc, une 16F84-04 est une PIC Mid-Range (16) donc la mémoire programme est de type FLASH (F) donc réinscriptible de type 84 et capable d’accepter une fréquence d’horloge de 4MHz.

Une dernière indication que vous trouverez est le type de boîtier. Nous utiliserons pour nos expérience le boîtier PDIP, qui est un boîtier DIL 18 broches, avec un écartement entre les rangées de 0.3’’ (étroit). La version 4MHz sera amplement suffisante.
Notez dès à présent que les PICs sont des composants STATIQUES, c’est à dire que la fréquence d’horloge peut être abaissée jusqu’à l’arrêt complet sans perte de données et sans dysfonctionnement. Une version –10 peut donc toujours être employée sans problème en lieu et place d’une –04. Pas l’inverse, naturellement.
Ceci par opposition aux composants DYNAMIQUES, donc la fréquence d’horloge doit rester dans des limites précises. N’essayez donc pas de faire tourner votre PIII/500 à 166MHz, car c’est un composant dynamique.

Donc, si vous voulez passer commande pour la PIC que nous allons utiliser dans le reste de cet ouvrage, demandez donc une PIC16F84-04 en boîtier PDIP .


3.4 Organisation de la 16F84

La mémoire de la 16F84 est divisée en 3 parties. Page 4 du datasheet, vous trouverez la table 1-1 qui donne un aperçu de la famille 16F8X. Les numéros de pages peuvent varier en fonction des mises à jour de Microchip. Vous devrez peut-être chercher un peu.

Pour ceux qui veulent tout comprendre, la figure 3-1 de la page 8 montre l’organisation interne d’une 16F84. Si cela intéresse du monde, j’expliquerais ce schéma après la fin des cours complets (pour ne pas noyer tout le monde).


3.4.1 La mémoire programme

La mémoire programme est constituée de 1K mots de 14 bits. C’est dans cette zone que vous allez écrire votre programme. Ceci explique pourquoi vos fichiers sur PC font 2Kbytes.

En effet, il faut 2 octets pour coder 14 bits. Ceci explique également pourquoi, lorsque vous lisez une PIC vierge, vous allez lire des 0x3FFF. Cela donne en binaire B’11111111111111’, soit 14 bits. J’expliquerais plus loin d’où proviennent ces fameux 14 bits.

Notez à ce point qu’une instruction est codée sur 1 mot. Donc, 1K donne 1 bon millier d’instructions possibles (c’est déjà pas si mal). Quand vous en serez à écrire des programmes de 1K, vous serez sans aucun doute autonome pour vos applications.


3.4.2 La mémoire eeprom

La mémoire eeprom (Electrical Erasable Programmable Read Only Memory), est constituée de 64 octets que vous pouvez lire et écrire depuis votre programme. Ces octets sont conservés après une coupure de courant et sont très utiles pour conserver des paramètres semi-permanents. Leur utilisation implique une procédure spéciale que nous verrons par la suite, car ce n’est pas de la RAM, mais bien une ROM de type spécial. Il est donc plus rapide de la lire que d’y écrire. Si vous programmez souvent des eeproms (2416) vous aurez constaté déjà ce phénomène.


3.4.3 La mémoire Ram

La mémoire RAM est celle que nous allons sans cesse utiliser. Toutes les données qui y sont stockées sont perdues lors d’une coupure de courant. La mémoire RAM est organisée en 2 banques pour la 16F84. La RAM est subdivisée de plus en deux parties. Dans chacune des banques nous allons trouver des « cases mémoires spéciales » appelées REGISTRES SPECIAUX et des cases mémoires « libres » dont vous pouvez vous servir à votre guise.

Pour le cas de la 16F84, vous disposerez de 68 octets libres. L’organisation de la RAM est montrée dans le tableau 4-2 page 13. Vous voyez la séparation verticale en 2 banques, et tout en bas vous voyez deux banques de 68 octets de RAM.

Malheureusement, l’indication « mapped in bank 0) vous indique qu’accéder à ces 68 octets depuis la banque 0 ou la banque 1 donne en fait accès à la même case mémoire.

Vous voyez dans la partie supérieure le nom de tous les registres spéciaux utilisés dans la PIC. Nous les verrons tous, rassurez-vous.

Chaque registre provoque un fonctionnement spécial de la PIC ou la mise en service d’une fonction particulière. Vous remarquerez enfin que certains registres sont identiques dans les 2 banques (FSR par exemple). Cela signifie qu’y accéder depuis la banque 0 ou 1 ne fait pas de différence.

Remarquez que la banque 0 utilise les adresses de 0x00 à 0x7F, la banque 1 allant de 0x80 à 0xFF. Les zones en grisé sont des emplacements non utilisés (et non utilisables). L’emplacement 0x00 est un emplacement auquel on ne peut pas accéder.

Pour la grande majorité des registres, chaque bit a une fonction spéciale. Page 14, tableau 4-1, vous trouverez les noms des bits utilisés dans ces registres.


4. Organisation des instructions

4.1 Généralités

Allez, courage, cela devient de plus en plus concret. On va faire un petit survol du jeu d’instructions des PICs. On saute directement page 55 du datasheet, au chapitre 9. Et oui, comme cet ouvrage n’est pas un manuel de référence technique, mais un apprentissage, il faut voir les chapitres dans le désordre.

Sur cette page, vous trouvez un petit encadré grisé qui fait allusion à deux anciennes instructions qui ne sont plus utilisées. Nous ne nous en servirons donc pas. Par contre, vous trouvez un tableau 9-1 qui indique comment les instructions sont codées dans la PIC. Et la, vous voyez enfin à quoi correspondent nos 14 bits de mémoire programme.

4.2 Les types d’instructions

Vous constaterez donc qu’il existe 4 type d’instructions :

4.2.1 Les instructions « orientées octet »

Ce sont des instructions qui manipulent les données sous forme d’octets. Elles sont codées de la manière suivante :

- 6 bits pour l’instruction : logique, car comme il y a 35 instructions, il faut 6 bits pour pouvoir les coder toutes

- 1 bit (d) pour indiquer si le résultat obtenu doit être conservé dans le registre de travail de l’unité de calcul (W pour Work) ou sauvé dans l’opérande (F pour File).

- Reste 7 bits pour encoder l’opérande (File)

Aie, premier problème, 7 bits ne donnent pas accès à la mémoire RAM totale, donc voici ici l’explication de la division de la RAM en deux banques.

En effet, il faudra bien trouver une solution pour remplacer le bit manquant. Vous avez dit « un bit d’un des registres ? « BRAVO, je vois que vous avez tout compris. Il s’agit en réalité du bit RP0 du registre STATUS.

Ah, vous avez remarqué qu’il y a un RP1 ? Et oui, la 16F876 a 4 banques. Vous veillerez à laisser RP1 à 0 pour la 16F84, afin de pouvoir « porter » votre programme sans problème vers une PIC supérieure.


4.2.2 Les instructions « orientées bits »

Ce sont des instructions destinées à manipuler directement des bits d’un registre particulier. Elles sont codées de la manière suivante :

- 4 bits pour l’instruction (dans l’espace resté libre par les instructions précédentes)

- 3 bits pour indiquer le numéro du bit à manipuler (bit 0 à 7 possible), et de nouveau 7 bits pour indiquer l’opérande.


4.2.3 Les instructions générales

Ce sont les instructions qui manipulent des données qui sont codées dans l’instruction directement. Nous verrons ceci plus en détail lorsque nous parlerons des modes d’adressage. Elles sont codées de la manière suivante :

- L’instruction est codée sur 6 bits

- Elle est suivie d’une valeur IMMEDIATE codée sur 8 bits (donc de 0 à 255).


4.2.4 Les sauts et appels de sous-routines

Ce sont les instructions qui provoquent une rupture dans la séquence de déroulement du programme. Elles sont codées de la manières suivante :

- Les instructions sont codés sur 3 bits

- La destination codée sur 11 bits

Nous pouvons déjà en déduire que les sauts ne donnent accès qu’à 2K de mémoire programme (211).

Rappelez-vous que l’espace mémoire programme est de 1Kmots. Pour coder une adresse de saut à l’intérieur de la mémoire programme, il faut donc 10 bits (210 = 1024 = 1K).

Par convention, en effet, 1Kbytes correspond à 210 = 1024 octets. Ce qui explique que si vous avez 16K de mémoire, en réalité vous avez 16*1024 = 16384 bytes. Par extension, 1Mbyte = 1024 Kbytes, donc 1048576 octets.

Maintenant vous voyez pourquoi vous voyez plus que votre mémoire théorique lors du test mémoire au démarrage de votre ordinateur. Une petite parenthèse qui n’a rien à voir ici : les fabricants de disques durs considèrent que 1Mbytes = 1000000 bytes. Comme Windows indique la taille en Mbytes de 1048576 bytes, cela vous explique pourquoi la plupart de vos disques durs semblent plus petits que prévus.


4.3 Panoramique des instructions

Je vais maintenant vous montrer comment fonctionne le tableau de la figure 9-2 page 56. Ce tableau vous permet d’un simple regard de vous informer de la manière dont fonctionne chaque instruction

La première colonne indique le MNEMONIQUE et les OPERANDES pour chaque opération. Les mnémoniques sont des mots réservés (donc que vous ne pouvez utiliser que pour cet usage) compris et interprétés par le programme d’assemblage.

Notez ici la confusion de langage commune pour le terme ASSEMBLEUR, qu’on utilise à la fois pour indiquer le programme qui permet d’assembler le code (programme d’assemblage), et le langage utilisé dans l’éditeur (langage d’assemblage).

Pour ne pas se compliquer la tâche, et pour employer la même dénomination que le commun des mortels, j’adopterai le terme assembleur dans les 2 cas, même si je commets là une faute de langage. Autant rester simple. Le contexte distinguera les deux termes. S’il y a un doute, j’emploierais les termes explicites.

Vous allez donc trouver à cet emplacement les instructions proprement dites que vous allez pouvoir encoder dans votre programme.

La syntaxe doit être la suivante pour l’assembleur (programme d’assemblage) MPLAB que nous utiliseront dès la prochaine leçon. Nous avons dans l’ordre :

- Espace(s) ou tabulation(s),
- Mnémonique (en majuscules ou minuscules),
- Tabulation ou Espace(s)
- Opérande ou la valeur
- Virgule éventuelle de séparation
- Bit de destination W ou F ou éventuellement numéro du bit de 0 à 7 si nécessaire
- Espace(s) ou tabulation(s)
- point-virgule.
- Commentaire.

Notez que le mnémonique ne peut pas se trouver en première colonne, et que tout ce qui suit le point-virgule est ignoré de l’assembleur (donc c’est de la zone commentaire).

La première colonne est réservée pour les étiquettes (repères)

Vous disposez également de la possibilité d’insérer un ou plusieurs espace(s) ou tabulation(s) de chaque côté de la virgule.

Voici à titre d’exemple une ligne assembleur valide, les mots en vert sont des mots réservés, en bleu l’instruction, ceux en jaune étant libres, le reste est du commentaire :

Ma_ligne ; Ceci est une étiquette
MOVF STATUS,W ; charge le registre status dans le registre de travail


La seconde colonne du tableau donne un bref descriptif de l’instruction.

La troisième colonne donne le nombre de cycles nécessaires pour exécuter l’instruction.

Notez que toutes les instructions nécessitent un seul cycle, sauf les sauts qui en nécessitent 2, et les opérations de test avec saut, lorsque le résultat du test engendre le saut (instructions avec 1(2)).

La 4ème colonne donne ce qu’on appelle l’ OPCODE, c’est à dire le mot binaire que MPLAB va générer pour vous au départ du mnémonique.

Vous ne vous en servirez donc pas, mais sachez que vous pourriez programmer directement la PIC sans passer par un assembleur, directement en construisant un fichier .hex et en entrant les valeurs trouvées ici.

Vous devriez alors tout calculer, y compris les sauts. C’est ce que j’ai fait à mes débuts sur un processeur 6502, car je ne disposais pas d’un assembleur. On pouvait également utiliser cette technique pour construire des programmes auto-modifiés pour cause de restriction mémoire.

Rassurez-vous, ces techniques appartiennent maintenant au moyen-âge de l’informatique. Tout au plus vous pouvez mettre en corrélation les valeurs présentes ici avec les valeurs du tableau 9-1 à titre éducatif. Sinon, oubliez cette colonne.

La 5ème colonne est primordiale, car elle donne les INDICATEURS D’ETATS ou STATUS FLAGS affectés (modifiés) une fois l’instruction effectuée. Nous verrons ces indicateurs en détail, car ils sont la clé de la programmation.

La dernière colonne renvoie à des notes en bas de page. La note 1 est très importante, elle fait allusion à la méthode « lecture/modification/écriture » propres aux ports d’entrées/sortie (I/O).

Nous y reviendront au moment de la mise en œuvre des PORTS.

La note 2 indique qu’une modification d’un timer remet à zéro son prédiviseur. Nous y reviendront en abordant le TMR0.

La troisième note indique que si vous vous servez de l’instruction pour modifier le compteur de programme (celui qui pointe sur la PROCHAINE instruction à exécuter), il y aura un cycle supplémentaire. C’est logique car cela équivaut à un saut. Nous verrons que cette technique est pratique pour aller chercher des valeurs dans une table construite en FLASH RAM.


4.4 Les indicateurs d’état

Ces indicateurs sont indispensables pour la programmation. Il est donc absolument nécessaire d’avoir compris leur fonctionnement (du moins pour Z et C).

Lisez donc attentivement ce qui suit. Tous les indicateurs sont des bits du registre STATUS. Voyez le tableau page 15. Nous aborderons ici les flags Z et C. Les autres seront traités lors de l’étude des registres.

4.4.1 L’indicateur d’état « Z »

C’est l’indicateur Zero, il fonctionne de la manière suivante :

Si le résultat d’une opération POUR LEQUEL IL EST AFFECTE, donne un résultat égal à 0, le flag Zero passe à 1.

Donc, ne vous mélangez pas les pinceaux. Dire « si Z = 1 » correspond à dire « si résultat = 0 ». Le tableau 9-2, colonne 5 vous indique les instructions qui modifient Z.

Donc, si vous faites une addition avec ADDWF et que le résultat obtenu est 0, le bit Z sera à 1. Si le résultat est <>0 (différent de 0), le bit Z vaudra 0. Dans les 2 cas il est modifié.

Par contre, si vous stockez une valeur avec l’instruction MOVWF, le bit Z ne sera pas modifié, même si la valeur vaut 0. Ces remarques sont valables pour les autres flags, donc je n’y reviendrai pas.


4.4.2 L’indicateur d’état « C »

C’est l’indicateur pour Carry (report). Si le résultat d’une opération entraîne un débordement, le bit C sera positionné. Il s’agit en fait du 9ème bit de l’opération.

Petit exemple :

Si vous ajoutez B’11111110’ (254)
+ B’00000011’ (3)
Vous obtenez B’100000001’, (257) donc 9 bits.

Comme les registres de la PIC ne font que 8 bits, vous obtiendrez B’00000001’ (1) et C positionné à 1 (en fait le 9ème bit, donc le bit 8, donc 28 = 256). Donc le résultat final est de 256 + 1 = 257.

Remarquez que si vous aviez ajouté B’11111110’ et B’00000010’, vous auriez obtenu B’00000000’.

Dans ce cas, vous auriez eu C à 1 ET Z à 1, ce qui signifie résultat nul, mais avec report (donc résultat = 256).

Les autres bits du registre d’état seront vu plus loin dans ce petit ouvrage.

Commentaires