Le microcontrôleur - partie 2
Les registres



Après avoir vu les bases du Microcontrôleur (voir la page sur "ça fait quoi ?" et "comment ça se branche?") , nous allons aborder des choses un peu plus pointues concernant le fonctionnement interne. ATTENTION, CA PIQUE !

J'ai fortement insisté là-dessus dans la partie 1 : Ce sont les registres qui font l'essentiel du boulot. Oui mais j'imagine déjà les questions qui se posent...presque un sommaire :
- A quoi ressemblent ces registres ?
- Comment accéder et modifier ces registres?
- ces registres contrôlent quoi exactement?
- Vous voulez des exemples?


1/ A quoi ressemblent ces registres ?

Physiquement, ce sont des bascules D....des mémoires en fait ! On peut facilement retenir qu'un registre est un tiroir dans lequel on stocke une valeur. Ce tiroir a une influence sur le fonctionnement d'un élément du µC.

Chaque registre contient une valeur codée sur un octet (8 bits). Elle est donc comprise entre 0 et 255.
Exemple : dans le tiroir "PORTA", on peut trouver la valeur 00110011 (en binaire)   =  0x33 (en hexadécimal)   =  51 (en décimal)

Tout le problème c'est de savoir sur quoi agit chaque tiroir (registre !!!) Il faut donc que le "meuble à tiroirs" soit bien rangé !



Heureusement, chacun d'eux possède un nom précis. Ces noms ne sont pas choisis au hasard. Ce sont, pour la plupart, des abréviations concernant l'élément contrôlé.
Exemple : TCNT1= "Timer CouNTer 1" ==> "compteur du chronomètre 1"
Ça permet de retrouver facilement les noms des variables quand on veut consulter ou modifier sa valeur à l'aide du programme.

Remarquez que les différents TIMERS possèdent des noms de registres presque identiques (au numéro de timer près). Même chose pour les ports d'entrées-sorties (PORTA, PORTB, PORTC...). Ca permet de faciliter encore la recherche des registres lors de l'écriture du programme. J'ai construit l'image ci-dessus de façon à observer les similitudes entre différents blocs de registres.

Autre détail physique : Les registres ne sont ni de l'EEprom ni de la mémoire flash. DONC,en cas de coupure de courant, ils ont effacés. A chaque démarrage, les tiroirs sont vides !




2/ Comment accéder et modifier ces registres?


Le contrôleur central (coeur) du µC peut consulter ( =lire ) ou modifier ( =écrire ) le contenu de chaque tiroir. C'est votre programme qui va modifier ou lire ces registres. Vos lignes de code doivent donc décrire explicitement le nom du registre à modifier.

Exemple : si je veux afficher les états "11001100" sur le port A, il suffit d'écrire la valeur dans le registre "PORTA":
PORTA = 11001100; 

Remarque : Pour la plupart des registres, chaque bit porte un nom : C'est le cas pour les registres des ports d'entrées-sorties. Il est donc possible de régler individuellement la valeur de chaque patte du port. Il suffit d'écrire la valeur voulue dans le bit concerné. Par exemple :
PORTA_0 = 0; //patte 0 du port A à l'état "bas" = "0" = "masse"


Autre exemple : Au début du programme, il faut régler chaque patte du port en sortie ou en entrée.  C'est le registre DDRA qui gère ce point. Ainsi, si on veut que les pattes 0 et 1 soient en entrée et les autres en sortie, on écrit :
DDRA = 11111100;
Ici aussi, il est possible de s'adresser  à chaque bit du registre, individuellement :
DDRA7 = 1;   //patte 7 du port A en sortie
DDRA6 = 1;   //patte 6 du port A en sortie
DDRA1 = 0;   //patte 1 du port A en entrée
DDRA0 = 0;   //patte 0 du port A en
entrée
etc...

Maintenant, puisque la patte "1" est "en entrée", je peux consulter ( =lire) l'état de l'élément qui y est raccordé. Par exemple, si le bouton de programmation est raccordé à la patte 1 du portA on peut écrire :
BoutonProg = PINA1;
Au moment où je l'interroge, la variable "BoutonProg" mémorise immédiatement la valeur présente sur la patte 1 du port A.

Evidemment, on a la même logique pour les autres ports d'entrées-sorties....et pour tous les registres en général.




3/ Ces registres contrôlent quoi exactement?

TOUT !
Il serait ambitieux de vouloir décrire les registres de façon exhaustive. Il existe des ouvrages complets là-dessus. Je conseille notamment de faire l'acquisition du livre de Christian Tavernier : "Microcontrôleurs AVR : des ATtiny aux ATmega", paru chez Dunod. C'est une mine de renseignements ! Si vous préferez les PIC, il a également écrit le le même ouvrage pour les PIC

Heureusement, la documentation technique gratuite (datasheet) de chaque composant décrit l'usage de chaque registre de façon extrêmement précise.Voici par exemple les notices de 3 µC courants :
Attiny861 :   La page 223 donne la liste de tous les registres du ATtiny861 (source du constructeur : atmel.com)
ATmega162 : Voir les pages 304-305 pour la liste exhaustive des registres de ce composant.
ATmega8535 : La liste des registres est à la page 299 !

Vous trouverez dans les paragraphes suivants un descriptif des principaux registres des µC ATMEL : J'ai pris en exemple les registres de l'ATtiny861. Certains sont un peu spécifiques mais le principe de base reste vrai pour tous les µC.


3.1 /Pour les ports d'entrées-sorties

Pas de surprise puisque je viens de décrire les différents registres ci-dessus.

- Affectation d'une valeur sur chaque PORT :
PORTA
PORTA_7 PORTA_6 PORTA_5 PORTA_4 PORTA_3 PORTA_2 PORTA_1 PORTA_0
Ces mots clés permettent d'attribuer une valeur sur le port ou la patte concernée.
Evidemment, il y a des mêmes similaires pour les autres ports : PORTB, PORTC, PORTD...suivant la taille de votre µC

- Attribution de l'état "Entrée" ou "Sortie"
DDRA "Data Direction Register A"
DDRA7
DDRA6
DDRA5
DDRA4
DDRA3
DDRA2
DDRA1
DDRA0
Au début du programme, il faut impérativement attribuer un sens de fonctionnement pour chaque patte des ports du µC.
Chaque bit est accessible individuellement. Pour que la patte soit réglée "en sortie", il faut lui assigner la valeur 1 dans ce registre.

- Lecture de l'état d'un port :
PINA "Pins INput A"
PINA7
PINA6 PINA5 PINA4 PINA3 PINA2 PINA1
PINA0
La lecture de l'état actuel des pattes du µC peut se faire à travers les registres "PINx". L'accès individuel aux différents bits du registre est possible en utilisant le mot clé correspondant.



3.2/ Pour les TIMERS (préparez le l'aspirine !) :

Vous l'avez compris, il peut y avoir plusieurs TIMERS dans un µC. Donc plusieurs "chronomètres" ! On retrouve quasiment les mêmes registres dans chacun d'eux. Ils sont différenciés par leur numéro de 0 à 4 (voire 8 pour les plus gros).
Leur comportement est géré de façon extrêmement précise. Et surtout, l'énorme avantage, c'est que une fois réglé, ce comportement est automatique ! Le coeur n'a plus besoin de s'en occuper. Inutile de dire que ces TIMERS sont TRES utilisés !
Ci-dessous, je détaille les principaux registres pour le timer1 :

- Compter le temps qui passe grâce à la  la valeur du compteur TCNT1 :
TCNT1 "TimerCouNTer 1"
La valeur du compteur est automatiquement incrémentée de 1 à des intervalles de temps réguliers. Ainsi, si vous avez correctement réglé la durée du délai , il est possible de connaître la durée d'un évènement en consultant la valeur du TIMER. Les TIMERS sont généralement codés sur 8 bits. Leur valeur maxi est donc 255. Dans certains cas, ils peuvent être codés sur 10 bits (maxi=1024) , voire 16 bits (maxi=65535).
Arrivés à leur valeur maxi, on dit qu'ils "débordent" (Overflow) ce qui les fait revenir à leur valeur mini.


- Comparer le compteur avec une valeur donnée pour déclencher des évènements (=interruptions)
OCR1A "Output Compare Register 1A"
Le registre OCR1A contient une valeur (attribuée par le programme) qui sera continuellement comparée à TCNT1 afin de déclencher des choses.
Par exemple, la comparaison réussie peut provoquer le déclenchement d'un sous-programme et/ou l'inversion de la valeur sur la patte "OC1A" du µC (Ça dépend des réglages sur les registres de contrôle). C'est donc comme ça qu'on peut générer un signal MLI sur une patte sans que le µC ait à s'en occuper.
Dans le même genre, il existe OCR1B qui influe sur la patte OC1B....idem pour OCR1C, OCR1D, etc...


- Régler le comportement du compteur 1 : registre TCCR1B
TCCR1B "Timer / Counter Control Register 1B"
PWM1X
PSR1
DTPS11
DTPS10
CS13
CS12
CS11
CS10
- Les bits CS "Clock Select" permettent de choisir la durée de en divisant l'horloge globale. Ainsi, la durée d'incrémentation du chronomètre est ralentie ou accélérée suivant les valeurs choisies. Voir l'exemple ci-dessous

- Le bit "PWMx" sert à autoriser (ou non) les changements d'états sur les pattes OC1x
- Les bits "DTP" servent à diviser encore l'horloge du compteur 1 si nécessaire.
Exemple de division de l'horloge pour le compteur 1 :
Si je règle CS13=0, CS12=0, CS11=1, CS10=1 alors je divise l'horloge globale par 4 (c'est le datasheet qui le dit). Ça signifie que le compteur sera incrémenté toutes les 4 impulsions d'horloge.
Ainsi, si mon quartz me donne une horloge à 16MHz (une impulsion tous les 0.0625µs), cela signifie que le compteur augmentera de "1" toutes les 0.25µs.


- Régler le comportement des sorties en fonction du timer : TCCR1A
TCCR1A "Timer / Counter Control Register 1A"
COM1A1
COM1A0
COM1B1
COM1B0
FOC1A
FOC1B
PWM1A
PWM1B
ET OUI ! Chaque timer peut contrôler l'état d'au-moins 2 pattes du µC. Et ceci de façon absolument indépendante du contrôleur central. Cool ! Mais il faut définir comment ça va se comporter.

Ce registre permet de régler l'influence du TIMER1 sur les pattes "OC1A" et "OC1B".
- Les bits "COM" permettent de gérer l'influence du TIMER sur les pattes de sortie (mise à 1, mise à 0, inversion) lorqu'une comparaison a réussi.
- Les bits "FOC" permettent de forcer une comparaison  Personnellement, je ne l'utilise pas.
- Les bits "PWM" servent à autoriser (ou non) les changements d'états sur les pattes concernées.




3.3/ Pour les interruptions :

Vous l'avez compris, les registres gèrent beaucoup de choses. Ceci dans un seul but : alléger le travail du calculateur ! Pour qu'un programme soit efficace, il faut qu'il soit aussi rapide que possible. Celui-ci ne peut pas passer son temps à faire des vérifications inutiles la majorité du temps. Il doit déléguer cette tâche à un "bloc d'interruptions".

Ce bloc sera capable d'informer le coeur au moment précis où l'évènement intervient. Ainsi, le coeur interrompra le traitement du programme pour se consacrer au sous-programme déclenché par l'interruption. Il reprendra le programme là où il l'avait laissé dès la fin de l'interruption.

"INT0" est l'une des interruptions possibles. Elle est déclenchée par un changement de niveau sur une patte spécifique du µC

Ainsi, certaines parties du programmes ne seront exécutées que sous certaines conditions. Toute l'astuce consiste à autoriser convenablement ces interruptions au début du programme avec les registres adaptés :

- Autorisation générale des Interruptions : SREG... INDISPENSABLE sinon rien ne s'interromp!
SREG "Status REGister"
I
T
H
S
V
N
Z
C
Le bit qui nous intéresse le plus c'est le bit "I". S'il est mis à "1", cela autorise le déclenchement de toutes les interruptions qui ont été demandées. Si ce bit est à "zéro", aucune interruption ne sera déclenchée. Les autres bits sont utilisés de façon automatique par le coeur, et nous ne les utiliserons pas avec notre programme.
Pour autoriser les interruptions :     SREG = $80;   (ce qui correspond à 10000000 en binaire)

- Masque d'interruptions pour les TIMERS : TIMSK
TIMSK "Timer Interrupt MaSK"
OCIE1D
OCIE1A
OCIE1B
OCIE0A
OCIE0B
TOIE1
TOIE0
TICIE0
OCIExx = Output Compare Interrupt Enable  ==> Ces bits sont réglés au départ du programme, dans la phase d'initialisation. Ils servent à autoriser l'interruption lorsqu'une comparaison a réussi sur le timer concerné.  Par exemple, pour le timer 1, OCIE1A autorise l'interruption lorsque la comparaison de OCR1A est réussie. Même logique pour les 4 autres !

TOIEx = Timer Overflow Interrupt Enable ==> Ces bits autorisent à déclencher les interruptions lorsque les timers débordent. Un compteur qui compte sur 8 bits vaudra 255 au maximum. Si on lui ajoute 1, il "déborde". Cet évènement peut provoquer le déclenchement d'une interruption si ces bits l'autorisent. Par exemple TOIE1 surveille le débordement du timer 1.

- Masque d'interruptions externes : GIMSK   INT1   INT0   PCIE1   PCIE0
GIMSK "General Interrupt MaSK register"
INT1
INT0
PCIE1
PCIE0
-
-
-
-
Et voilà enfin la fameuse interruption "INT0" dont je vous parle depuis si longtemps !
Les bits INT servent à autoriser une interruption lorsqu'une patte du µC change d'état. L'interruption vient de l'extérieur du µC. Par exemple, pour l'ATtiny861, les pattes concernées sont : "PORTB6' pour INT0   et  "PORTA2" pour INT1. C'est spécifique à chaque µC.

PCIEx = Ces bits permettent de surveiller tout changement sur les ports du µC. Remarque personnelle Cette interruption  n'est pas assez spécifique d'après moi. Elle surveille tout à la fois et se déclenche trop souvent pour avoir un réelle efficacité. Je ne l'utilise pas.
MCUCR "MicroControler Unit Control Register"
-
-
-
-
-
-
ISC01
ISC00
J'associe ce registre au précédent car les deux bits surlignés ici permettent de décider quel type de changement provoquera l'interruption INT0 :
ISC01 = 0 / ISC00=0  ==> INT0 déclenchée par un niveau bas
ISC01 = 0 / ISC00=1  ==> INT0 déclenchée par tout changement de niveau
ISC01 = 1 / ISC00=0  ==> INT0 déclenchée par un front descendant
ISC01 = 1 / ISC00=1  ==> INT0 déclenchée par un front montant

Consultez la page "programmation" pour comprendre comment j'ai utilisé ces registres



Pour conclure résumer cet aperçu des registres, vous vous rendez compte qu'ils sont indispensables à la bonne marche de votre programme car ils ont de l'influence sur les principaux paramètres du µC. Suivant vos besoins, certains seront utilisés et d'autres non.
Evidemment, cette liste n'est pas exhaustive mais je vous ai présenté là les principaux registres utiles à la création de votre décodeur. Je vous invite à consulter le datasheet de votre µC pour avoir le détail complet des registres.

Je propose de voir quelques exemples de programmation sur la page "programmation en C"




Evidemment, si vous souhaitez apporter des compléments ou me faire part de vos commentaires, n'hésitez pas à me laisser un message .