Maintenant que nous savons utiliser les GPIO, il serait possible de communiquer des signaux binaires entre deux microcontrôleurs.
⚙️ Exercice: comment feriez-vous pour transmettre un message de 8 bits entre deux microcontrôleurs avec les GPIO?
Même si cet exercice est possible, la solution serait très peu pratique et occuperait de nombreux cycles du microcontrôleur. C'est pourquoi il existe des bus de communication.
Full-duplex/Half-duplex
Un bus est dit full-duplex si il peut transmettre et recevoir des données en même temps. Sinon, il est dit half-duplex.
Par exemple, la communication orale est half-duplex car on ne peut pas parler et écouter en même temps (le medium de communication est le même).
Synchrone/Asynchrone
Un bus est dit synchrone si les deux microcontrôleurs sont synchronisés par un signal d'horloge commun (sinon, il est dit asynchrone).
Quand il est présent, ce signal d'horloge est concrètement une entrée/sortie supplémentaire qui indique l'arrivée d'un nouveau bit.
Quand il est absent, il est nécessaire de compter sur la temporisation interne des microcontrôleurs pour assurer la synchronisation. Nous verrons plus tard comment cela est possible.
Série/Parallèle
Un bus est dit série si les données sont transmises bit par bit (sinon, il est dit parallèle).
L'entiereté des bus de communication que nous verrons sont série. Les bus parallèles sont utilisés pour augmenter la vitesse de transmission des données, mais nécessitent un nombre de broches plus important.
Maître/Esclave Dans une architecture maître/esclave, seuls les maîtres peuvent initier une communication. Les esclaves ne peuvent que répondre à une requête.
Toute architecture de bus n'est pas forcément maître/esclave.
La terminologie est contestée mais reste très utilisée.
Nous allons nous intéresser dans la prochaine partie à trois bus célèbres, et implémentés dans l'ATmega328P:
UART
: Universal Asynchronous Receiver-TransmitterI2C
: Inter-Integrated CircuitSPI
: Serial Peripheral InterfaceUART (Universal Asynchronous Receiver-Transmitter) ou USART
On l'appelle parfois simplement "série", sans plus de précision. (Dans la bibliothèque
Arduino par exemple, il s'agit de Serial
.)
Série
Asynchrone
Full-duplex
Débit typiquement limité à quelques MBits/s.
Le câblage de l'UART est le suivant:
Les broches sont:
TX
: émissionRX
: réceptionLe TX
d'un microcontrôleur doit être relié au RX
de l'autre, et vice-versa. Attention à ne pas
inverser les deux!
Une ambiguité peut apparaître dans la documentation d'un périphérique: est-ce que le TX
est celui
du périphérique ou celui du microcontrôleur? Il faut donc faire attention à bien lire la documentation.
Il est entièrement possible d'imaginer des variantes d'UART dans lesquelles la communication serait
half-duplex ou unidirectionelle.
Lors d'une communication asynchrone, les deux périphériques n'ont pas de signal d'horloge commun. Un seul fil est nécessaire pour qu'on composant transmette un message à l'autre (la présence des deux fils permet la communication full-duplex).
Par exemple, supposons que le signal suivant soit transmis:
Afin de pouvoir le transformer en bits, il faut que les deux périphériques soient synchronisés. Pour cela:
Le même signal, plusieurs fréquences/alignements possible
Afin de résoudre le premier problème, on compte sur les horloges des deux périphériques. Il est nécessaire que ces derniers soient capable de compter le temps écoulé de manière suffisament précise.
Cependant, cette approche causera inexorablement des décalages entre les deux périphériques. Il est donc nécessaire de les resynchroniser.
Idée: l'UART peut détecter les fronts montants ou descendants pour réaligner son horloge. Cependant, il faut pour cela garantir qu'il y en a.
Dans la pratique, on en ajoute un artificiellement au début de chaque octet transmis. On ajoute pour cela deux bits supplémentaires, appelés start bit et stop bit.
Dans cette transmission, nous avons plusieurs phases:
La carte Arduino Uno est équipée d'un composant adaptateur USB/série, qui communique en USB avec votre ordinateur et en série avec l'ATmega328P.
Si vous observez bien le schéma ci-dessus, vous verrez que ce composant est en fait lui-même un autre ATmega, le ATmega16U2!
SPI (Serial Peripheral Interface)
Également très répandu dans l'embarqué, bonne robustesse et débit (grace à sa synchronicité), mais nécessite plus de broches.
Série
Synchrone
Full-duplex
Maître/esclave
Peut monter à plusieurs dizaines de MBits/s.
Le câblage de SPI est le suivant:
Les broches sont:
MOSI
: Master Out Slave Input, émission du maître vers l'esclaveMISO
: Master Input Slave Output, émission de l'esclave vers le maîtreSCK
: Serial Clock, signal d'horlogeSS
: Slave Select, sélection de l'esclaveLa nomenclature MOSI
/MISO
permet de lever l'ambiguité mentionnée pour l'UART.
Lors d'une transmission, le maître sélectionne un esclave en mettant à 0 la broche SS
. Il transmet ensuite
les bits un par un, en même temps que le signal d'horloge. L'esclave reçoit les bits sur la broche MOSI
et
envoie les siens sur la broche MISO
.
C'est le maître qui génère le signal d'horloge. Il est donc nécessaire que l'esclave soit capable de s'adapter à la vitesse de transmission du maître.
La fréquence maximale de l'horloge que l'esclave est capable de suivre est en général indiquée dans sa documentation.
La ligne MISO
est donc occupée par les esclaves sélectionnés à tour de rôle (on sélectionne au plus
un esclave à la fois pour éviter les conflits).
Dans cette transmission, il y a plusieurs type de signal:
SCK
en haut est l'horloge gérée par le maître. Ici, les bits sont transmis sur le front montant,MOSI
et/ou MISO
sont utilisés respectivement par le maître et l'esclave pour transmettre les bits,SS
est utilisé pour sélectionner l'esclave.I2C (Inter-Integrated Circuit)
Permet d'utiliser peu de câbles tout en parlant à de nombreux périphériques.
Série
Synchrone
Maître/esclave
Half-duplex
Débit limité à 400 kBits/s.
Le câblage de l'I2C est le suivant:
Les broches sont:
SDA
: Serial Data, donnéesSCL
: Serial Clock, signal d'horlogeAvec I2C, seulement 2 câbles sont requis. Ce système est possible grâce aux résistances de tirage et au fonctionnement en collecteur ouvert des broches.
Si deux périphériques tentent d'écrire en même temps sur le bus, il y aura un conflit. En effet, si l'un écrit un 0 et l'autre un 1, le résultat serait normalement un court-circuit. I2C addresse ce problème avec un système en collecteur ouvert, dans lequel un signal sera soit:
D'un point de vue GPIO, ce serait équivalent à écrire 0
dans PORTx
et à jouer sur le
registre DDRx
pour soit passer la broche en sortie (tirée à la masse) ou en entrée (laissée libre).
Le bus étant relié à un niveau de logique haut à travers une résistance de tirage (par exemple à 3.3V ou 5V), la laisser libre lui permet de remonter à ce niveau.
Une manière schématique d'imaginer ce système est de remplacer les transistors par des boutons:
Si au moins un des boutons est appuyé, le signal est tiré à la masse (et on lit 0
).
Si aucun des boutons n'est appuyé, le signal est tiré par la résistance de tirage (et on lit 1
). C'est la
raison pour laquelle il ne peut pas y avoir de conflit, ce montage agit comme un "OU" logique.
Le maître et l'esclave peuvent parler sur une même ligne, sans risque de conflit. En observant une trame à l'oscilloscope, on ne pourrait pas distinguer les bits émis par le maître de ceux émis par l'esclave.
Le protocole I2C permettant de communiquer avec plusieurs périphériques, il est nécessaire de les identifier de manière unique. Pour cela, chaque périphérique possède une adresse codée sur 7 bits.
Au début de chaque trame, le maître envoie l'adresse du périphérique avec lequel il souhaite communiquer. Si l'esclave reconnait son adresse, il répondra à la requête. Sinon, il ignorera la trame.
Le format électronique des trames est similaire à celui de SPI (SCL joue le rôle de SCK et SDA celui de MOSI/MISO).
En revanche, le contenu des trames doit suivre le schéma suivant:
Les étapes de l'envoi sont:
r/w
pour indiquer si la trame sera une lecture (de l'esclave vers le maître) ou une écriture
(du maître vers l'esclave),Il est possible d'envoyer une nouvelle trame sans passer par une condition stop. C'est ce que l'on appelle une condition restart.
Cette méthode permet d'assurer au maître qu'il garde la main sur le bus.
Si le maître souhaite envoyer une requête à un esclave afin qu'il lui réponde, il devra tout d'abord envoyer une trame d'écriture, puis une trame de lecture.
Que ce soit en UART, SPI ou I2C, il est très fréquent que le périphérique avec lequel on communique propose un protocole orienté registres.
Cela signifie qu'un tableau de registres sera disponible dans la documentation du composant, et que le bus de communication sera un moyen d'écrire et de lire dans ces derniers
Par exemple, dans la documentation du MMA8451, à la page 20, on peut trouver la liste des registres disponibles.