GPIO et Bus de communication

GPIO

Introduction

Le terme GPIO signifie General Purpose Input/Output. Il désigne les broches sur lesquelles on peut prendre le contrôle manuellement.

Des exemples d'utilisation:

  • Allumer/éteindre une LED,
  • Vérifier l'état d'un bouton,
  • Activer un relais ou un transistor de puissance,
  • ...

Les GPIO dont nous parlons ici sont digitaux. Ils ne peuvent prendre que deux états: 0 ou 1.

Dans l'ATmega328P

Les broches de l'ATmega328P sont regrouppées par paquet de 8 (ce que l'on appelle un port). Chaque port est muni de 3 registres de configuration:

  • DDRx: Data Direction Register. Il permet de configurer les broches du port en entrée ou en sortie.
  • PORTx: Permet de configurer le niveau de la broche.
  • PINx: Il permet de lire l'état des broches du port.

Par exemple, pour allumer puis éteindre la LED branchée sur PB5:

// On configure PB5 en sortie
DDRB |= (1 << PB5);
// On met PB5 à 1
PORTB |= (1 << PB5);
// On attend 1 seconde
_delay_ms(1000);
// On met PB5 à 0
PORTB &= ~(1 << PB5);

GPIOs de l'ATmega328P, page 59 du datasheet.

Sortie à trois états

Lorsqu'elle est configurée en entrée, son voltage n'est pas déterminé, elle est dite "flottante" (floating), comme un fil qui ne serait branché à rien.

Une broche n'est donc pas simplement configurée à "0" ou à "1", mais aussi en "entrée" ou en "sortie". Lorsqu'elle est en entrée, on dit qu'ell est en haute impédance (parfois noté Hi-Z).

Par exemple, si on branche un bouton à une broche du microcontrôleur de cette façon:

Ou GND représente la masse du circuit, c'est à dire le 0V, on s'assure de lire "0" lorsque le bouton est appuyé. Mais que se passe t-il lorsqu'il est relâché? La broche est alors en haute impédance, et peut prendre n'importe quelle valeur.

Pour remédier à cela, on utilise des résistances de tirage (pull-up ou pull-down). Elles permettent de fixer le voltage de la broche lorsqu'elle est en haute impédance.

Avec ce montage, on s'assure de lire "1" lorsque le bouton est relâché.

Bonne nouvelle: les résistances de tirage sont intégrées à l'ATmega328P! Il suffit de les activer. Ce qui nous amène à un 4ème état, l'entrée avec pull-up. (Il n'existe pas d'entrée avec pull-down, mais cela est possible avec d'autres microcontrôleurs.)

On peut donc utiliser le code suivant pour regarder la valeur d'un bouton qui serait branché en PB4:

// On configure PB4 en entrée avec pull-up
DDRB &= ~(1 << PB4);
PORTB |= (1 << PB4);
// On lit la valeur de PB4
uint8_t btn = PINB & (1 << PB4);

Voici donc les états possible d'une broche:

DDRxPORTxEtat
10Sortie à 0
11Sortie à 1
00Entrée flottante
01Entrée pull-up

Les bus de communication

Motivation

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.

Propriétés

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.

Quelques bus célèbres

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-Transmitter
  • I2C: Inter-Integrated Circuit
  • SPI: Serial Peripheral Interface

UART

Présentation

UART (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.

Branchements

Le câblage de l'UART est le suivant:

Les broches sont:

  • TX: émission
  • RX: réception

Le 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.

Principe de fonctionnement

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:

  1. il faut un accord sur la vitesse de transmission. On appelle cette vitesse le baudrate, exprimée en bits par seconde (bps),
  2. Il faut que s'assurer que le signal est "aligné" sur les bits.

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.

Exemple de transmission

Dans cette transmission, nous avons plusieurs phases:

  • En jaune, le bus est dans un état de "repos" (avec une logique de \(5V\)),
  • En violet, les bits de start et de stop, qui permettent de détecter le début de la trame et de resynchroniser les horloges,
  • En bleu, la charge utile, c'est à dire les bits que l'on souhaite transmettre.

Sur la carte Arduino Uno

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

Présentation

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.

Branchements

Le câblage de SPI est le suivant:

Les broches sont:

  • MOSI: Master Out Slave Input, émission du maître vers l'esclave
  • MISO: Master Input Slave Output, émission de l'esclave vers le maître
  • SCK: Serial Clock, signal d'horloge
  • SS: Slave Select, sélection de l'esclave

La nomenclature MOSI/MISO permet de lever l'ambiguité mentionnée pour l'UART.

Principe de fonctionnement

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).

Exemple de transmission

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

Présentation

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.

Branchements

Le câblage de l'I2C est le suivant:

Les broches sont:

  • SDA: Serial Data, données
  • SCL: Serial Clock, signal d'horloge

Avec 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.

Principe de fonctionnement

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:

  • Laissé libre (broche en "entrée" flottante),
  • Tirée à la masse (broche en "sortie" au niveau 0V.

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.

Adressage

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.

Trames I2C

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:

  • Tout d'abord, le maître démarre la transaction avec une condition start,
  • Il envoie ensuite l'adresse du périphérique avec lequel il souhaite communiquer,
  • Puis le bit 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),
  • L'esclave répond alors avec un acquittement, en amenant la ligne à 0,
  • La transaction commence alors, les données sont envoyées soit par le maître soit par l'esclave, et l'autre acquitte la réception de chaque octet,
  • Enfin, le maître termine la transaction avec une condition stop.

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.

Registres de périphériques

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.