Vous êtes sur la page 1sur 6

Les ports

Eskimon , le ven. 03 décembre 2010 (0 commentaire)


arduino tuto
Partons dans les entrailles du microcontrôleur en allant à la rencontre d’une mémoire un peu
particulière : les registres. Dans notre cas nous allons parler des ports, un registre servant à gérer
les entrées/sorties de la carte.

Sommaire

 Utiliser les ports


Utiliser les ports

Dans cette partie nous allons voir une technique avancée de programmation de microcontrôleur.
Bien qu’elle soit sans danger pour votre matériel, une mauvaise manipulation concernant la
liaison série peut arriver et apporter des complications au niveau du chargement du programme
(nous allons voir pourquoi). Soyez donc prudent et codez en connaissance de cause. Bien
entendu, je vais tout vous expliquer pour que vous réussissiez sans problèmes !

Retour aux sources

Vous vous souvenez, lorsque nous avons introduit la notion de "microcontrôleur" ? On a parlé de
différentes choses qui se trouvaient à l’intérieur de ce composant et notamment des registres, en
expliquant brièvement que ce sont des sortes de mémoires spécifiques… eh bien en voici un
exemple complet : les ports . Ces derniers sont en effet des bits au sens le plus strict qui soit
puisqu’ils traduisent directement l’état et le comportement des sorties. Ces bits servent donc à
configurer vos entrées/sorties et à lire/écrire dessus. Ils sont accessibles directement et ont un
effet immédiat sur l’électronique de votre microcontrôleur. En clair, c’est passer au travers de
fonctions déjà toutes prêtes pour appliquer directement ce que vous voulez faire avec votre
microcontrôleur. Cela revient à rentrer dans le détail et à coder façon "bas niveau". Par exemple,
lorsque vous direz "mets ce bit à 1 (ou 0)" vous irez en fait activer (ou désactiver) un transistor à
l’intérieur du microcontrôleur, ce qui aura pour effet de basculer la sortie à l’état haut ou bas.
Lorsque vous utilisez les fonctions pinMode() ou digitalRead() ou digitalWrite(), sans le savoir
vous utilisez en fait les registres en question. La seule différence est que certaines opérations de
contrôle sont faites pour s’assurer que vous ne fassiez pas trop de bêtises.

Des bêtises ? des vérifications ? je vais casser quelque chose ?

Non rassurez-vous .

Les avantages d’utiliser les ports


Lorsque l’on décide d’utiliser les ports plutôt que les fonctions d’abstractions digitalWrite(), on le
fait souvent avec une bonne raison (et vous comprendrez pourquoi en lisant les inconvénients).
Voici deux grandes raisons qui pourraient vous inciter, dans des cas précis, à vouloir utiliser les
ports :

 Dans le cas où vous rédigez une application qui possède un côté critique au niveau du
temps (par exemple la broche 3 et la broche 4 doivent basculer de manière synchrone
sinon le message ne passe pas), l’utilisation des ports est faite pour vous ! En effet,
comme dit plus haut contrairement à digitalWrite() , ici vous agissez directement sur le
matériel, sans abstraction. Du coup ça va plus vite ! (là ou il pourrait s’écouler quelques
précieuses micro-secondes entre deux digitalWrite() consécutifs)
 Si votre code rentre au chausse pied dans le microcontrôleur et que gagner quelques
octets de mémoire programme vous arrangerait bien, là encore jouer avec les registres
est une solution. En effet, chaque appel àdigitalWrite() représente plus d’instructions
machines qu’un appel direct au port. Au bout d’un code assez conséquent, il peut donc
s’avérer utile de passer par les ports pour sauver quelques octets à chaque fois (en
particulier si les fonctions digitalWrite() sont très souvent utilisées à plein d’endroits
différents).

Les risques et inconvénients à connaître

Vous avez décidé que finalement utiliser les ports c’était pas si mal, et en valait la peine ? Voyons
pourquoi vous feriez-mieux d’y réfléchir une deuxième fois…

 Inconvénient n°1 : Votre code va devenir difficile à relire. En effet, utiliser les ports
nécessite d’écrire des lignes du genre PORTD |= B00000100; là où un
simple digitalWrite(2) aurait fait l’affaire.
 Inconvénient n°2 : Votre code devient moins portable. Les registres utilisés peuvent
changer (de nom, d’adresse, etc…) entre les gammes de microcontrôleur. D’ordinaire,
c’est la fonction digitalWrite() qui justement s’assure que tout est bien fait. En écrivant
directement dans les registres, vous court-circuitez les vérifications et donc vous devez
vous assurer vous-même de bien écrire pour activer les bonnes entrées/sorties (sous
peine d’activer la mauvaise I/O ou même de ne pas compiler tout court )
 Inconvénient n°3 : Vous allez vous arracher les cheveux ! Lorsqu’on joue avec les ports,
on doit faire des opérations en binaire avec des masques. Il est très rapide de faire une
erreur et de ne plus rien comprendre. La plus triste d’entre elles serait de bidouiller les
broches utilisées par la liaison série et de se retrouver sans liaison série (ce qui d’ailleurs
pourrait rendre difficile la reprogrammation de la carte…)

Mais, si vous ne manquez pas de rigueur, vous trouverez toutes les informations nécessaires
relatives au microcontrôleur que vous utilisez. Donc le risque est amoindri et vous savez ce que
vous faites.

OK j’ai bien compris tout ça et c’est bien gentil, mais concrètement on fait quoi ?

Concrètement, vous allez agir sur 3 données particulières qui s’appellent DDR, PORT ou encore
PIN. Voyons maintenant à quoi elles servent et comment nous allons nous en servir…

Utilisation des ports


Vous êtes encore là ? Vous n’avez pas froid aux yeux, j’aime ça ! (ou simplement curieux ou
inconscient, ça marche aussi ). Puisque vous êtes là, continuons, voyons voir les étapes
nécessaires à cette utilisation mystique des broches… Tout d’abord, sachez qu’il y a trois
registres à manipuler pour réussir à se servir des broches. Un premier registre, DDR , servira
"d’aiguillage". Ensuite deux autres serviront à lire ou écrire sur une broche, ce sont PORT etPIN .
Dans une Arduino Uno, ces trois registres existent en trois exemplaires, le B, le C et le D. B sert
pour les broches numériques 8 à 13, C pour les broches analogiques et D pour les broches 0 à 7.

Le registre DDRx

Comme je viens de le dire, DDR sert d’aiguillage. C’est lui qui permet de définir l’utilisation d’une
broche en entrée, ou en sortie, tout comme le faisait la fonction pinMode(). Un 0 signifie "entrée"
et un 1 signifie "sortie". Chaque broche d’un même port étant commandée par ce registre. Au
début de votre programme, vous avez la fonctionpinMode() pour définir l’utilisation des broches.
Admettons que vous souhaitiez utiliser les broches 4, 5 et 6 en sortie et aussi 13 et 11 ainsi que
les broches analogiques 2 à 4. Pour cela, vous auriez d’ordinaire écrit :

void setup()
{
//définition des broches suivantes utilisées en sortie logique
pinMode(4, OUTPUT);
pinMode(5, OUTPUT);
pinMode(6, OUTPUT);
pinMode(11, OUTPUT);
pinMode(13, OUTPUT);
pinMode(A2, OUTPUT);
pinMode(A3, OUTPUT);
pinMode(A4, OUTPUT);
}

Plutôt long et fastidieux non ? Avec les ports tout cela tiendra en 3 lignes, une par port. Pour cela,
imaginez que les entrées/sorties soient représentées chacune par un bit dans un octet. Par
exemple pour le registre D qui représente les entrées/sorties de 0 à 7, on pourrait avoir
00110010. Les numéros des broches sont ensuite les mêmes que ceux des bits, le poids faible
sera la broche 0 et le poids fort la broche 7. Donc pour mettre les broches 4, 5 et 6 en sortie
(donc les autres en entrée) on fera :

PORTD = B01110100;

Le B devant la suite de 0 et 1 signifie que l’on donne un nombre binaire. On pourrait également
donner un nombre hexadécimal en mettant un 0x suivi de deux caractères hexadécimaux. Dans
la même lignée, nous pouvons donc déclarer l’utilisation de toutes les entrées/sorties
précédentes :
DDRD = B01110100; // 0 (poids faible) vers 7 (poids fort)
DDRC = B00011100; // A0 (poids faible) vers A5 (poids fort)
DDRB = B00101000; // 8 (poids faible) vers 13 (poids fort)

Le registre D possède l’accès aux broches de la transmission série. Je vous recommande très
fortement de toujours laisser ces derniers à 1 (pour le Tx) et 0 (pour le Rx), pour faire DDRD =
xxxxxx10 si vous ne voulez pas avoir de mauvaises surprises…

Le registre PORTx

C’est maintenant que l’on s’amuse . En effet, c’est ici que l’utilisation des ports va prendre
tout son sens… Imaginons que vous vouliez basculer à l’état haut toutes les sorties 2, 3, 5 et
7 en même temps . Vous pourriez utiliser plusieurs fois digitalWrite(x, HIGH) mais il risque de se
passer quelques microsecondes entre chaque action. Nous allons donc utiliser le registre PORT
pour faire cette opération. Tout d’abord, on définit les broches en sortie (encore une fois,
attention aux deux derniers bits pour la liaison série) :

void setup()
{
DDRD = B11111110; // Tout le monde (de 1 à 7) en sortie !
}

Ensuite, au moment d’exécuter notre programme nous allons juste avoir à utiliser PORTD pour
actionner les sorties. Par exemple en faisant :

PORTD |= B10101100; //2, 3, 5 et 7 à l'état haut

Avez-vous remarqué ? J’ai utilisé l’opérateur OU (|) pour actionner uniquement les sorties qui
m’intéressaient tout en ignorant les autres. Cela doit vous rappeler des opérations de masquage
que l’on avait vu un peu plus tôt, dans un chapitre précédent. Maintenant, je vais au contraire
utiliser l’opérateur antagoniste ET (&) pour mettre à l’état bas les sorties précédentes.

PORTD &= B01010011; //2, 3, 5 et 7 à l'état bas

Je mets ainsi à zéro les broches qui m’intéressent en ignorant l’état des autres. C’est en tout
point l’inverse du précédent ! Avec les ports (et les registres en général) les opérateurs de
masquage ET et OU sont très importants à connaître et à maîtriser.

Le registre PINx

Bon c’est chouette, vous savez modifier toutes les broches en une seule fois, mais savez-vous
les lire en une seule fois ? Pour cela, il faut d’abord mettre quelques broches en entrée pour
qu’elles puissent être lues. Je vous propose de reprendre celles de tout à l’heure (2, 3, 5 et 7) :

void setup()
{
DDRD = B00000010; // Tout le monde (de 2 à 7) en entrée !
}

Ensuite, pour lire les données présentes sur ce port, il faut utiliser le dernier registre de cette
leçon qui s’appelle PIN. Par exemple, pour stocker l’état des sorties dans un octet on fera :

char lePort = PIND;

C’est cool mais moi je ne veux qu’une certaine broche dans ma variable, comme avec
digitalRead() !

Et à votre avis ? on fait quoi ? Et bien oui, on masque ! Pour obtenir la broche 3 uniquement par
exemple, on fera :

char monEntree = PIND & B00001000; //sélectionne le bit 3 uniquement

Et c’est tout !

**************************=========================*****************************

Ports are groups of pins. In the arduino, pins 0-7 are controlled by
port D and pins 8-13 are controlled by port B.
Advantages of using ports: Faster than going per pin, takes up less
code for a smaller program
Disadvantages: Harder to use and debug

I arbitrarily chose port D as my port.


Some basic things to keep in mind for using ports
Variables (X should be replaced by the correct port letter)
DDRX -- Data Direction Register -determines which way data
should flow for each pin on the port (0 is input, 1 is output)
PORTX--Data register --holds what data is being output/input to the
pins
PINX --Input pins register

The pins in the register are arranged from lower number = Least
Significant Bit to Highest number - Most Significant Bit (so to get
pins 0-3 to equal 0 and 4-7 to equal 1 it would be PORTD =
B11110000)
Example Code
int delayTime = 333; //It's better coding style to not have any hard-
coded constants like in the previous example
byte portD_HIGH = B11111000;
byte portD_LOW = B00000000;

void setup()
{
DDRD = DDRD | B11111100; //Sets up the pins for output
//a look at what we just did
//the | symbol is used as bitwise OR (if either bit is 1, result will be
1)
//this goes through the register, ORing each bit with the binary to
the right of the |
//the B makes the compiler read the 11111100 as binary
//So in summary it sets pins 2-7 to Output and leaves 1 and 2
(which sometimes
//have special functions) alone
}

void loop()
{
PORTD = portD_HIGH; //sets pin ledPin High (so pin 13 is set to
1)
delay(delayTime); //waits 333ms or ~1/3 sec
PORTD = portD_LOW; //sets pin ledPin to Low (so pin 13 is now
equal to 0)
delay(delayTime); //waits another 333 ms
} //end of the loop subroutine, so it will now go back to the
beginning

Vous aimerez peut-être aussi