Aller au contenu

Introduction aux liaisons séries filaires UART, I2C, et SPI (et détail de leurs protocoles de communication)

Tutorial communications séries électroniques, type UART I2C ou SPI, cours pratique sur liaisons filaires avec détail protocoles sérial échange de données

UART, I2C, SPI, … ces acronymes vous parlent sûrement, car on les retrouve souvent, en électronique ! Comme vous le savez certainement déjà, ce sont différents types de communication série filaire. Mais en pratique, savez-vous comment ils se câblent ? comment ils fonctionnent ? et à quel débit on peut les utiliser ?

C’est en tout cas ce que je vous propose de découvrir aujourd’hui, au travers de cet article sur les différents types de liaisons séries filaires. Par contre, pour rester digeste, je me limiterai ici à présenter uniquement les « principaux » protocoles de communication série, que nous rencontrons couramment de nos jours, sur nos petites cartes à microcontrôleur (comme l’Arduino, par exemple). Alors en avant !

Ce contenu vous plaît ? Alors abonnez-vous à la Newsletter pour ne rien louper !

Gardez bien à l’esprit que cet article n’est qu’une introduction aux liaisons filaires séries. C’est pourquoi je n’entrerai pas trop dans les détails de chaque protocole, et me concentrerai sur l’essentiel. Par ailleurs, dites vous bien qu’il existe de très nombreux autres protocoles de communication série, qui ne sont d’ailleurs pas moins importants que ceux que je vous présente ici. Je pense notamment aux protocoles 1-Wire, RS-232, bus CAN, USB, etc. Mais pour l’heure, voyons déjà ceux que je vous ai préparé 😉

Généralités

Par définition, une liaison série est un moyen de relier deux éléments (ou plus), afin que ceux-ci puissent échanger des informations, et ce, en utilisant un minimum de fils. Basiquement, les informations transmises sont envoyées bit après bit, sur une ligne dédiée aux données (contrairement aux « liaisons parallèles », qui elles, peuvent envoyer parallèlement plusieurs données à la fois).

Comparatif transmission série vs parallèle, principe de communication sériel ou simultané au niveau des bits de données, différences expliquées couleur

En pratique, il existe différentes manières d’établir une liaison série, avec différents protocoles. C’est ce que nous allons voir ensemble, dans cet article, au travers des protocoles UART, I2C, et SPI. D’ailleurs, vous avez peut-être déjà utilisé ces différents types de communication série dans le passé, sans même vous poser plus de questions que cela (au niveau de leur vitesse min/max usuelle de fonctionnement, ou leur distance maximale atteignable avec, par exemple). C’est pourquoi je vous propose d’en découvrir un peu plus aujourd’hui !

À noter qu’on retrouve énormément de « périphériques séries » de nos jours, facilement interfaçables avec nos petits microcontrôleurs. Je pense notamment aux :

  • sondes de température, pression, ou humidité (comme le module BME280)
  • émetteurs/récepteurs radio (comme le module NRF24L01)
  • afficheurs LCD série ou écran OLED
  • modules lecteur de carte SD
  • convertisseurs analogique/numérique, ou ADC en anglais (comme le module ADS1115)
  • convertisseurs numérique/analogique, ou DAC en anglais (comme le module MCP4725)
  • périphériques d’ajout d’entrées/sorties (comme le module PCF8574)
  • mémoires « de type série » (certaines RAM, EEPROM, …)
  • et j’en passe … car la liste serait très longue !

Leur pilotage est d’ailleurs assez aisé, car les liaisons séries sont généralement gérées en interne, au niveau de microcontrôleurs actuels. Et une simple librairie logiciel permet souvent d’en prendre facilement le contrôle. Mais attention, car chaque microcontrôleur a ses spécificités, et tous ne peuvent pas gérer tous les protocoles séries qui existent. En effet, certains sauront gérer uniquement le UART/I2C/SPI, tandis que d’autres sauront gérer le bus CAN en plus, ou l’USB, par exemple. Au final, vous devrez donc consulter leur fiche technique avant tout, afin de savoir s’ils savent nativement prendre en charge tel ou tel protocole de communication série, avant d’envisager de vous en servir.

Nota : lorsqu’un microcontrôleur n’a pas de « bloc interne » pour gérer tel ou tel protocole, sachez qu’il est souvent possible d’émuler une communication série, de manière logicielle, si nécessaire. Cependant, cela peut être ardu à coder, et difficile à mettre en œuvre avec le reste du code (tant certains timing de communication série sont critiques). Cela étant dit, certaines puces électroniques peuvent pallier à ce problème, en gérant indépendamment tel ou tel protocole à la place d’un µC, de manière autonome. Je pense par exemple au MCP2515 de Microchip, qui peut gérer un bus CAN « à lui tout seul », et « faire tampon » entre ce bus et le microcontrôleur.

Enfin, pour votre culture, sachez que :

  • les transmissions séries peuvent être de type « point-à-point » ou « multi-points ». Dans le premier cas, il s’agit d’une communication entre 2 éléments seulement (comme c’est le cas avec les protocoles UART, RS-232, et SATA, par exemple). Dans le deuxième cas, un élément peut communiquer avec tous les autres (comme c’est le cas avec les protocoles I2C, SPI, RS-485, Ethernet, USB, … et bien d’autres !).
  • la communication série peut être synchrone et asynchrone, selon si l’échange de données se fait au rythme des coups d’horloge d’une ligne externe dédiée (et partagée physiquement entre toutes les « personnes » communicantes). Par exemple : les protocoles UART et CAN sont asynchrones (aucune horloge), tandis que l’I2C et le SPI sont synchrones (signal d’horloge présent).
  • la communication peut être du type « une personne parle à la fois », ou « plusieurs peuvent parler à la fois ». On parle alors de communication « half-duplex » (ou semi-duplex), si la communication s’alterne entre deux communicants, sans se chevaucher. Et on parle de communication « full-duplex » (ou duplex intégral), si deux « personnes » peuvent parler en même temps (comme c’est par exemple le cas avec nos téléphones portables, où chaque personne peut parler en même temps que l’autre).
  • les lignes de communication peuvent être unidirectionnelles ou bi-directionnelles. C’est à dire que les données peuvent circuler dans un seul sens uniquement, ou bien dans un sens ou l’autre. Un exemple de lignes unidirectionnelles : les lignes MOSI et MISO du SPI ; et un exemple de ligne bidirectionnelle : la ligne SDA du I2C.
  • la communication série peut se faire entre « 2 personnes uniquement » ou « avec plusieurs interlocuteurs ». Dans le premier cas, on parle d’échange « point à point », tandis que dans l’autre, on parle d’architecture maître/esclaves (car dans ce cas, une personne est « maître » de la conversation, pour éviter la « cacophonie » !).
  • une adresse peut être attribuée à chaque destinataire, que cela se fasse de manière « logicielle », en encodant l’adresse du destinataire à même le message transmis à travers tous le réseau, ou « matériel », en assignant une ligne de contrôle par destinataire (comme sur le bus SPI, où des lignes /CS ou /SS permettent de faire « l’aiguillage »).

Voilà pour les grandes lignes ! J’espère d’ailleurs ne pas vous avoir perdu en cours de route ! À présent, commençons par découvrir le protocole UART 😉

La liaison série UART

Le premier protocole série dont je vais vous parler aujourd’hui est le UART (de l’anglais « Universal Asynchronous Receiver Transmitter »). Il permet à deux éléments (pas plus), de communiquer ensemble, via une liaison série filaire.

Petite parenthèse, si vous ne le saviez pas : c’est justement au travers du port UART que les microcontrôleurs ATmega de nos Arduino sont programmés, depuis un PC. Bien entendu, tout ceci est au passage interfacé avec du USB, pour faire le lien entre PC et port UART de l’Arduino. Mais au final, c’est bien grâce à cette liaison série UART que nos Arduino sont programmé, depuis leur connecteur USB. C’est pourquoi je vous parle de ce protocole série en premier, même s’il est beaucoup moins utilisé que l’I2C ou le SPI, que nous verrons ensuite.

Basiquement, une liaison série UART n’utilise que 3 fils (hors alimentation positive) :

  • 2 fils de communication (l’un pour envoyer des données, et l’autre pour recevoir des données)
  • 1 fil pour la masse, pour garantir l’équipotentialité de ces signaux

La liaison UART est ce qu’on appelle une liaison point à point, en ce sens qu’elle ne permet de relier que 2 éléments, et deux éléments seulement (ces éléments pouvant être un microcontrôleur et des périphériques, par exemple, ou deux microcontrôleurs qui s’échangent des données). Cette liaison est bidirectionnelle, c’est à dire que les données peuvent aller dans un sens, ou dans l’autre.

En fait, chaque élément dispose de :

  • une entrée pour la réception de données, notée RX
  • une sortie pour la transmission de données, notée TX

Et comme vous l’aurez compris, pour que tout n’entre pas en collision, le TX du premier élément se branche sur le RX du second, et le TX du second élément revient se brancher sur le RX du premier. En clair : une liaison filaire UART comporte 2 fils de communication, dont le premier permet de faire circuler les données dans un sens (TX → RX), et dont le second permet de faire circuler les données dans l’autre sens (RX ← TX).

En image, voici ce que cela donne :

Câblage liaison série UART, avec lignes TX et RX entre deux éléments, type microcontrôleur et périphérique quelconque, avec lien GND de masse

Vous voyez donc qu’au final :

  • une liaison UART comporte bien 3 fils (1 pour la communication dans un sens, 1 autre pour la communication dans l’autre sens, et 1 fil de masse … hors alimentation positive, bien sûr !)
  • les broches RX et TX du premier élément se branchent respectivement sur les broches TX et RX du second (« croisement » des fils), afin que les données ne circulent que dans un seul sens, au niveau de chaque fil
  • la communication est bidirectionnelle, en ce sens où chaque élément peut envoyer ou recevoir des informations (même si, sur le plan strictement physique, cette communication bidirectionnelle repose effectivement sur 2 fils de communication unidirectionnels)
  • et chaque élément peut fonctionner en émetteur seul, récepteur seul, ou émetteur/récepteur (comme vous voulez !)

À noter que les lignes de données TX et RX sont indépendantes l’une de l’autre. Du coup, les données peuvent théoriquement circuler simultanément sur ces deux lignes, et qui plus est, de manière totalement asynchrone (d’où le « A » dans l’acronyme « UART). Cette communication simultanée UART s’appelle le « full-duplex » (à l’image d’une conversation téléphonique, où chaque personne peut effectivement parler en même temps que l’autre).

Au niveau du protocole UART en lui-même, voici ce que l’on peut en dire :

  • les données sont envoyées bit à bit, les uns après les autres (d’où l’appellation « série »)
  • les données ne peuvent être égales qu’à 0 ou 1 (pour le 0, cela correspond à une mise à l’état bas sur la ligne TX, tandis que pour un 1, cela correspond à une mise à l’état haut)
  • les données sont envoyées sous forme de séquence, prototypées de la manière suivante :
    • un bit de départ, appelé « start bit » (correspondant à un état bas forcé)
    • les bits de données (nota : seuls 5 à 9 bits peuvent être envoyés à la suite, ni plus, ni moins !)
    • un bit de parité, optionnel (pouvant servir à la détection d’erreur). Lorsqu’on fonctionne en mode « sans parité », ce bit est absent. Lorsqu’on fonctionne en mode « parité paire », ce bit est mis à 1 si le nombre de bits à 1 au niveau des données est pair lui aussi ; sinon ce bit de parité est mis à 0. Et lorsqu’on fonctionne en mode « parité impaire », ce bit est mis à 1 si le nombre de bits à 1 au niveau des données est impair lui aussi ; sinon ce bit de parité est mis à 0.
    • et un bit de fin, appelé « stop bit », correspondant à un état haut forcé (à noter que la durée de ce bit de fin peut être rallongée, d’une fois et demi à deux fois, si souhaité ; mais en temps « normal », la durée d’un stop bit est égale à celle d’un start bit)
  • la vitesse d’envoi de ces données est normalisée ; les valeurs standard les plus utilisées sont : 1200, 2400, 4800, 9600, 115200 bauds (mais il en existe des plus basses, et de bien plus hautes). À noter qu’ici, dans le cas du protocole UART, 1 baud = 1 bit par seconde ; donc 9600 bauds équivalent à 9600 bits/s, dans ce cas précis (attention, car 1 baud n’est pas toujours égal à 1 bit par seconde).

Graphiquement, voici comment nous pourrions représenter cette trame de données formatées :

Trame communication UART, séquence protocole série pour transmission de données séries, avec bit de start, stop, et parité, selon configuration

Petit aparté : généralement, les niveaux de tension d’une liaison série UART sont de type TTL (soit 0 volt pour un niveau bas, et +5V pour un niveau haut). Mais une communication UART peut tout aussi bien fonctionner en 3,3 volts, par exemple, du moment où les deux éléments qui communiquent travaillent sur les mêmes niveaux de tension. Du reste, en l’absence de données à échanger, le niveau de la ligne de transmission restera au niveau haut (+Vcc, dans le cas de l’exemple ci-dessus).

Le plus souvent, une séquence de données UART à envoyer est généralement paramétrée de la manière suivante : 8 bits de données, pas de bit de parité, et 1 seul bit de stop. Cela permet usuellement l’échange de caractères ASCII, et même « un peu plus » (car les caractères ASCII vont historiquement de 0 à 127, et que 8 bits permettent d’aller de 0 à 255). Bien évidemment, il n’est pas obligatoire de faire transiter des caractères ASCII avec une liaison série UART ; en effet, n’importe quelles données peuvent être émises via cette ligne de transmission, du moment que la séquence et la vitesse d’envoi sont respectées !

Au niveau des distances maximales atteignables avec une liaison série filaire UART, on est aux alentours des 1 mètre de distance (à peu près bien sûr, car cela dépend notamment de la vitesse de transmission, et de la qualité des lignes). En clair : on ne peut communiquer que sur de faibles distances avec une liaison série UART, en l’état.

Par contre, il est possible de communiquer sur de plus grandes distances, en utilisant par exemple le protocole RS-232 (« calqué » sur le UART). Ceci permet d’aller jusqu’à :

  • 2 m environ, pour du 56 kbits/s
  • 3,5 m environ, pour du 38400 bits/s
  • 7,5 m environ, pour du 19200 bits/s
  • 15 m environ, pour du 9600 bits/s (c’est la valeur la plus couramment usitée)
  • 30 m environ, pour du 4800 bits/s
  • 60 m environ, pour du 2400 bits/s

Nota : toutes ces valeurs de distances sont soit admises par conventions, soit issues de règles empiriques. Elles ne sont donc vraiment pas à prendre au pied de la lettre, car celles-ci sont plutôt approximatives, et dépendantes de bon nombre de facteurs externes. En fait, je les ai juste mises là pour vous donner un ordre d’idée de ce qu’il est possible d’atteindre, en terme de distance de communication UART.

Remarque : si jamais votre microcontrôleur ne dispose pas de port de communication UART (broches RX et TX, donc), il est tout de même « facile » d’en émuler un, à partir de 2 entrées/sorties tout-ou-rien. En fait, il suffit juste d’envoyer les bits l’un après l’autre, à une vitesse donnée, tout en respectant le format d’une trame de communication UART, à savoir : un start bit, les bits de données (du poids le plus faible, LSB, jusqu’au bit de poids le plus fort, MSB), puis un éventuel bit de parité, et enfin, un bit de stop.

La liaison série I2C

Le second protocole série que nous allons voir à présent est l’I2C (ou I²C, de l’anglais « Inter Integrated Circuit »). Ce type de communication série permet l’échange de données séries filaires, notamment entre un « maître » (un microcontrôleur, par exemple) et des « esclaves » (des périphériques). On appelle également « bus I2C » l’ensemble des liens constituant l’interconnexion du « maître » avec tous ses « esclaves ».

De nos jours, le bus I2C est devenu un moyen de communication très prisé, dès lors qu’on cherche à interfacer un microcontrôleur avec des périphériques, tels que des :

  • sondes (de température, pression, hygrométrie, …)
  • capteurs (de luminosité, humidité, …)
  • détecteurs (de présence infrarouge, par exemple)
  • convertisseurs (numérique/analogique, analogique/numérique, …)
  • transmetteurs (wifi, bluetooth, …)
  • mémoires séries (ram, eeprom, …)
  • modules d’ajout de ports d’entrées/sorties
  • et bien d’autres encore !

Sur le plan matériel, voici un exemple de câblage filaire I2C, permettant la communication d’un microcontrôleur avec plusieurs périphériques annexes :

Exemple câblage liaison I²C multipoints avec maître et esclaves, raccordement lignes SDA et SCL, avec la masse et résistances de pull-up

Comme vous le voyez, 3 fils sont seulement nécessaires (hors alimentation positive), pour assurer la communication filaire I2C. On retrouve ainsi :

  • une ligne de données, nommée SDA (pour « Serial DAta »), bidirectionnelle (les infos peuvent circuler dans un sens comme dans l’autre, mais jamais en même temps ; on appelle ça de la communication « semi-duplex »)
  • une ligne d’horloge, nommée SCL (pour « Serial CLock ») ; celle-ci permet de synchroniser tous les envois et réceptions de bits, transitant sur la ligne SDA
  • une ligne d’équipotentielle, représentée ici par GND ; il s’agit en fait d’une masse commune, pour tous les éléments communiquant sur le bus I²C

Si ce câblage ne vous semble pas très visuel, en voici une autre représentation (c’est la même chose que précédemment, mais organisé de manière différente, et avec des illustrations en plus) :

Exemple branchement i2c d'un arduino avec capteur, sonde, et autre périphérique, via les lignes SCL, SDA, et GND, et résistances pull up

À noter des résistances « pull-up » sont indispensables sur un bus I2C (une sur la ligne SDA, et l’autre sur la ligne SCL) ; celles-ci peuvent être intégrées ou multiples, selon les montages, mais dans tous les cas indispensables. En fait, elles permettent de donner un état haut par défaut, aux lignes du bus I²C (en sachant que « maître » et « esclaves » ne peuvent que forcer ces lignes à l’état bas, via leurs transistors à « drain ouvert »). Ceci évite ainsi tout conflit de tension sur le bus.

Pour ceux que ça intéresse, voici un peu plus précisément comment sont montés ces « transistors à drain ouvert », permettant d’abaisser les lignes au besoin :

Pull-up resistor sur entrées sorties I2C à drain ouvert, pour échange de données bidirectionnelles sur ce bus série, sans conflit de tension

Remarque : attention à ne pas les multiplier les résistances de pull-up I2C, car une sur chaque ligne suffit, quel que soit le nombre d’esclaves sur le bus. Sinon cela augmenterait le courant de lignes, et risquerait d’endommager les composants branchés dessus (sans parler des pertes d’énergies inutiles). Si je vous parle de ça, c’est parce que c’est quelque chose auquel on peut ne pas faire attention, lorsqu’on branche plusieurs modules i²c en parallèle (je pense aux petites plaquettes déjà montées, avec le périphérique et les pull-up déjà soudées dessus). Au final, laissez toujours une paire de résistance adéquates, et pas plus ! Au passage, la valeur des résistances pull-up pour un bus I²C est typiquement du 4,7 kohms (mais à ajuster si besoin, en fonction de la charge capacitive du bus, elle-même fonction de la vitesse de transmission).

Sinon, plus généralement, voici ce que nous pouvons dire du bus i2c :

  • il y a en général qu’un seul élément « maître » (de la communication), et un ou plusieurs « esclaves » (qui exécutent ses ordres ou répondent aux questions posées) ; les « esclaves » ne communiquent jamais entre eux. À noter que je n’ai jamais mis en œuvre de version « multi-maître » ; je ne vous en parlerai donc pas aujourd’hui
  • la distance maxi entre « maître » et « esclave » est de l’ordre de 1 à 2 mètres environ (fonction de la vitesse de communication, et de la qualité des lignes de transmission, attention)
  • la vitesse de communication est imposée par le « maître », à tous les « esclaves ». Les différentes vitesses de transmission possibles sont standardisées ; leurs valeurs vont grosso modo de 10 kbits / sec, à plusieurs mégabits par seconde. Les valeurs les plus courantes sont :
    • 100 kbits/s (surnommé « Standard Mode »)
    • 400 kbits/s (surnommé « Fast Mode »)
    • 1 Mbits/s (surnommé « Fast Plus Mode »)
    • 3,4 Mbits/s (surnommé « High Speed Mode »)
  • chaque « esclave » dispose d’une adresse qui lui est propre (en partie libre, et en partie imposée). Avec le protocole I2C, nous avons théoriquement 128 adresses différentes possibles (car celles-ci sont codées sur 7 bits). Toutefois, en pratique, nous en avons bien moins, car certaines sont réservées ; en effet, certains bits d’adresse sont « gravés » en dur dans la puce, par le fabricant, les autres étant généralement libres d’utilisation. À noter qu’il existe une variante de communication I²C à 10 bits, mais qui est moins commune.
  • tous les esclaves « écoutent » en permanence ce que dit le « maître », sur le réseau (et prennent seulement en considération les messages qui leurs sont adressés)
  • le « maître » encode l’adresse du destinataire à l’intérieur même du message qu’il envoie (c’est comme ça que chaque « esclave » sait quand un message lui est destiné, ou pas)

Au niveau du protocole I2C, voici un exemple d’échange de données :

Séquence écriture I2C maître esclave, exemple montrant la transmission de bits sur SDA en fonction de l'horloge SCL, avec explications détaillées

Comme vous pouvez le constater :

  • en l’absence de communication, les lignes SDA et SCL sont à l’état haut (en fait, c’est le niveau imposé au repos par les résistances de pull-up, sur chacune de ces lignes)
  • chaque communication démarre avec une mise à l’état bas de la ligne SDA par le maître, puis une mise à l’état bas de la ligne SCL (toujours par le maître)
  • un fin de communication s’achève avec un relâchement de la ligne SCL, puis de la ligne SDA (retour à l’état haut, permis par les résistances pull-up)
  • entre temps, quand le maître veut s’adresser à quelqu’un, il « met en route » une horloge sur SCL et envoi des trames de données ; parmi les données envoyées par le maître, on retrouve à minima :
    • une première séquence d’adressage (dans ce 1er message, le maître communique sur le réseau I2C l’adresse du destinataire à qui il compte s’adresser)
    • une seconde séquence comportant une instruction, que l’esclave devra exécuter
  • ensuite, au niveau des données, on retrouve optionnellement (si nécessaire, donc) :
    • soit une ou plusieurs données supplémentaires que le maître envoie à l’esclave (ce peut être des paramètres, des valeurs de consignes, ou des données d’instruction complémentaire par exemple)
    • soit une ou plusieurs données renvoyées par l’esclave, en réponse au questionnement du maître
  • chacun des « bits » formant les séquences défilant sur la ligne SDA sont synchronisés avec l’horloge présente sur la ligne SCL
  • chaque séquence d’envoi ou de réception se termine par un « accusé de réception » (« acknowledgment » en anglais, d’où le signe ACK récurrent sur les diagrammes temporels), pour que l’émetteur sache que le destinataire a bien reçu le message envoyé. Plus précisément, l’ACK (forçage de la ligne SDA à l’état bas, donc) peut être émis :
    • soit par l’esclave, si c’est le maître qui vient de lui envoyer un message
    • soit par le maître, si c’est l’esclave qui vient de lui renvoyer des données

En clair : les envois sont séquentiels sur le bus I2C, toujours à l’initiative puis rythmés par le maître, avec des « accusés réception » du destinataire des données, pour chaque message.

Nota : vous entendrez souvent parler de « lecture » ou « écriture » sur le bus I2C (en anglais : « I2C reading » et « I2C writing »). En fait :
ce qu’on appelle écriture est une communication unique du maître vers l’esclave (sans réponse de ce dernier, donc)
ce qu’on appelle lecture est en fait une « question » envoyée par le maître, auquel l’esclave répond (par exemple : un µC maître pourrait demander une « lecture » de température à une sonde esclave, et ce dernier répondrait en renvoyant au maître la valeur de cette température).
Du coup, attention à ne pas vous méprendre sur les mots, car souvent, il est sous-entendu par ces termes qu’une écriture n’est qu’une écriture, mais qu’une lecture est un acte d’écriture puis lecture.

Enfin, pour essayer de vous permettre de comprendre encore mieux les échanges maître/esclave, voici une autre manière de représenter les échanges sur le bus I2C, faisant apparaître qui fait quoi, et à quel moment :

Lecture écriture sur bus I2C, diagramme de transmission des bits suivant protocole maître esclave série, avec bits d'acknowledgment ACK et NACK

Remarque : si votre microcontrôleur ne possède pas de « bloc I2C interne » ou si vous avez besoin d’un « port I2C supplémentaire », sachez qu’il est techniquement possible de l’émuler, via de simples broches numériques. Mais franchement, ce n’est pas chose facile que de coder ça soi-même ! C’est pourquoi je vous recommande dans ce cas de trouver des librairies I2C déjà « prêtes à l’emploi », pour vous économiser du temps … et vos nerfs 😉

La liaison série SPI

Rajoutons quelques fils, et voyons à présent le protocole de communication SPI ! Là aussi, il s’agit d’une liaison maître/esclaves, avec, généralement :

  • un microcontrôleur comme maître
  • et des périphériques (capteurs, sondes, convertisseurs, …) comme esclaves

Les liens les unissant, hors alimentation, sont les suivants :

  • une horloge de synchronisation, nommée SCK (ou SCLK), générée par le maître, et à destination de tous les esclaves
  • une ligne de données maître vers esclave, nommée MOSI (pour « Master Out, Slave In », donc « Maître en sortie, Esclave en entrée »)
  • une ligne de données esclave vers maître, nommée MISO (pour « Master In, Slave Out », donc « Maître en entrée, Esclave en sortie »)
  • et une ligne de sélection par esclave (par exemple : s’il y a 3 esclaves, alors il y aura 3 lignes de transmission ; c’est ainsi que le maître choisi avec qui il veut converser). Cette ligne est le plus souvent nommée CS (pour « Chip Select ») ou SS (pour « Slave Select »), c’est égal !

Au niveau schématique, voici un exemple de câblage SPI, avec 1 maître et 3 esclaves (ça aurait pu être plus, tout comme être moins !) :

Exemple câblage SPI, pour raccordement maître esclaves, comme microcontrôleur et capteurs sondes, via lignes SCK MOSI MISO SS et GND

Là encore, si cela ne vous semble pas très clair, voici une autre représentation de ce même câblage (identique donc, mais présenté différemment, et imagé pour que cela soit mieux appréhensible) :

Branchement arduino sur bus SPI, exemple avec sonde capteur, lecteur de carte SD, et émetteur récepteur radio, pour communication série multiple

Comme vous pouvez le remarquer, nous avons :

  • uniquement des lignes unidirectionnelles (les données ne circulent que dans un seul sens, sur chaque ligne ; c’est d’ailleurs pour cette raison qu’il n’y a pas de résistance pull-up ici, contrairement aux liaisons séries I2C)
  • tous les SCK sont tous reliés ensemble, de même que tous les MOSI et tous les MISO (pas de croisement donc, contrairement aux liaisons séries UART, où on croisait RX avec TX, et TX avec RX)
  • autant de lignes de sélection qu’il y a d’esclaves (ce qui peut faire énormément de fils à câbler, s’il y a beaucoup d’esclaves !)

Au niveau du protocole de communication SPI en lui-même, voici un exemple de questionnement du maître à l’intention d’un esclave, et la réponse de l’esclave dans la foulée :

Chronogramme lecture sur bus SPI, exemple d'échange de données maître esclave, au travers lignes MOSI et MISO, avec horloge SCLK et /SS

Comme vous voyez, la communication SPI est somme toute assez simple, finalement. En effet, l’échange peut ici être divisé entre 4 étapes, qui sont :

  • primo, le maître abaisse la ligne SS de l’esclave avec qui il souhaite communiquer
  • secundo, le maître génère une horloge sur SCK et envoie bit à bit les données qu’il souhaite faire parvenir à son esclave (par paquets de 8 bits, généralement, ce qui fait un octet donc)
  • tertio, le maître continu à générer des impulsions au niveau de sa ligne SCK, et l’esclave renvoie bits à bits des données, à ce rythme là
  • enfin, quarto, le maître arrête son horloge SCK, et remet la ligne SS au niveau haut, pour déconnecter l’esclave (de toute communication SPI, j’entends)

Bien entendu, maître comme esclaves peuvent transmettre un ou plusieurs octets à la suite, suivant la quantité de données à faire passer.

Remarque : ci-dessus, j’ai représenté un échange de données « semi-duplex » (donc 1 ligne émettrice de données, à la fois). Mais sur le bus SPI, les données peuvent très bien circuler en simultané sur les lignes MISO et MOSI, c’est à dire en « full-duplex » ; dans ce cas, les bits de données transiteraient en même temps sur ces deux lignes, au rythme des coups d’horloge du maître (sur SCK).

Nota :
pour une communication SPI, il faut à minima 1 maître et 1 esclave (après, il peut même y avoir plusieurs maîtres, mais là, c’est un cas plus particulier)
les lignes /SS sont de simples sorties numériques, côté maître, car ce dernier s’en sert uniquement pour cibler un esclave en particulier, pendant toute la communication. À minima, il doit donc y en avoir au moins une ligne /SS, pour cibler au moins un esclave. À noter que ce type de ligne est active à l’état bas, et au repos à l’état haut
en l’absence de données à transmettre, l’esclave déconnecte sa sortie MISO, pour la rendre flottante (afin de ne pas altérer les éventuels données émises par les autres esclaves)

Bien sûr, l’esclave n’a pas forcément besoin de répondre au maître (s’il s’agit simplement d’une consigne à appliquer, par exemple, on n’attendrait pas nécessairement de réponse en retour). Dans ce cas, les données ne circuleraient que du maître vers l’esclave, sur la ligne MOSI donc, tandis que la ligne MISO resterait « flottante » (à haute impédance, donc). Voici un exemple illustrant cela, pour être plus clair :

Exemple écriture SPI de maître vers esclave, transmission des données via MOSI, au rythme de l'horloge SCK, avec SS CS abaissé, et MISO haute impédance

Là encore, un octet seulement aurait pu être envoyé, et pas nécessairement plusieurs. Là, c’est juste pour l’exemple ! Et en fait, tout ça reste plus ou moins libre, au final 😉

C’est d’ailleurs parfois un peu trop « libre » à mon goût, au niveau de la notation des entrées/sorties SPI, car il en existe de très nombreuses. En effet :

  • l’horloge peut être notée SCK, SCLK, ou SCL
  • la ligne de sélection peut être notée CS, SS, nCS, nSS, STE, CSN, …
  • les lignes MISO et MOSI peuvent être « éclatées » en :
    • SDO, DO, ou SO, du côté des sorties « émettrices »
    • SDI, DI, ou SI, du côté des entrées « réceptrices »

Et encore, je n’ai pas tout noté … ! Du reste, afin de vous permettre de mieux visualiser ces multiples conventions de nommages, voici une petite image synthétisant le tout (en espérant que ce soit plus clair pour vous, car ce n’est pas évident à expliquer, en fait !) :

Différentes notations SPI des entrées sorties, convention de nommage SCK MISO MOSI, vs SDO DO SO et SDI DI SI, avec SCK=SCLK=SCK horloge

Par ailleurs, il est à noter qu’on a aussi la liberté de « jouer » avec la phase et polarité d’horloge SPI, c’est à dire de choisir les conditions dans lesquelles seront prises en compte les données entrantes ou sortantes. À savoir :

  • sur front montant ou descendant d’horloge, pour la polarité
  • et pile au moment d’un front ou de manière décalée, pour la phase

Enfin, juste un mot sur les avantages et inconvénients du bus SPI, comparé aux deux précédents (UART et I²C) :

  • la communication peut se faire en duplex intégral (simultanéité de l’émission et de la réception)
  • les débits sont vraiment rapides (jusqu’à plusieurs mégahertz, sans problème), mais seulement sur de courtes distances (c’est la contrepartie) ; toutefois, il existe des « buffer de ligne » permettant d’aller au-delà des quelques mètres de longueurs, communément admis
  • il y a beaucoup plus de fils à câbler, car il faut une ligne physique de sélection par esclave, en plus des lignes de transmission
  • et il n’y a aucun « accusé de réception » des messages (ACK, en anglais) au sein d’une communication SPI ; ce qui fait que le maître pourrait très bien parler des heures dans le vide, sans même le savoir … !

Remarque importante : le mode que je vous ai présenté ici s’appelle la connexion directe (« Direct Connection« , en anglais). Dans ce cas, les SCK sont câblés entre eux, les MOSI entre eux, et les MISO entre eux. Mais ce n’est pas la seule façon de réaliser une liaison SPI. En effet, il existe un autre mode, dit chaîné (« Daisy Chained Connection« , en anglais) qui permet au maître de piloter plusieurs esclaves avec une seule sortie CS. Le câblage est quelque peu différent de tout ce que je vous ai présenté jusqu’à présent, car dans le cas du mode chaîné, le MOSI d’un élément est relié au MISO de l’élément suivant (jusqu’à former une boucle). Ainsi, la diffusion des données est propagée d’un esclave à l’autre, jusqu’à revenir au maître (et vice-versa). Mais comme tout ceci est un peu plus complexe, je ne l’ai pas abordé ici, dans cette introduction aux liaisons séries filaires.

Les autres protocoles de communication série filaires

Comme vous vous en doutez sûrement, je n’ai présenté ici que les liaisons séries les plus populaires selon moi, pour nous, amateurs d’électronique ! Mais il en existe bien d’autres (et non des moindres d’ailleurs !). Je pense notamment :

  • au bus CAN, très prisé dans le milieu automobile, qui permet avec 2 fils seulement (hors alimentation) de relier 20 à 30 éléments ensemble (selon si on fonctionne à 125 kbits/s, ou 1 Mbits/s) ; sans parler des longueurs de communication qui sont vraiment appréciables : environ 30m à 1 Mbits/s, et jusqu’à 5 km à 10 kbits/s ! Ce type de liaison filaire série est d’ailleurs assez facile à mettre à œuvre, notamment avec nos arduino, et aux mini shields « tout équipés » MCP2515 / TJA1050.
  • au bus 1-Wire, popularisé avec les sondes de température DS18B20 de chez Dallas Semiconductor ; ici, un seul fil suffit, et la masse ! Difficile de faire plus simple, niveau câblage, bien que des fois, il faille rajouter le +Vcc ! Et c’est d’autant plus simple à mettre en œuvre qu’il existe de nombreuses librairies gérant cela (car à coder, ce serait fastidieux !). Du reste, une liaison 1-Wire est bidirectionnelle, fonctionne en semi-duplex (un seul sens à la fois, au niveau de la transmission), nécessite une résistance de pull-up, et fonctionne à 16 kbits/s environ, en standard (ce qui, au passage, permet d’aller sur « d’assez longues » distances)

Tout ça, sans parler des liaisons séries filaires qu’on retrouve couramment en informatique, à savoir : l’USB (pour les périphériques), le SATA et le SCSI (pour nos « anciens » disques dur), l’Ethernet (avec ses prises RJ45), … et j’en passe là aussi !

Mais au final, si comme moi vous aimez « bricoler » en électronique, l’I2C et le SPI restent surtout les deux protocoles de communication série à privilégier en premier, car ce sont ceux que nous retrouvons le plus souvent, dès lors qu’on souhaite raccorder des périphériques à un microcontrôleur 😉

Tableau comparatif UART / I2C / SPI (synthèse)

Afin que vous ayez quelques repères supplémentaires, j’ai essayé de vous faire une petite synthèse, sous forme de tableau comparatif :

Tableau comparatif UART I2C et SPI, nombre de fils nécessaires, nombre de bits données et adresse, vitesse de transmission, et accusé de réception

Comme toujours, j’ai préféré faire quelque chose de synthétique, allant à « l’essentiel », plutôt que trop entrer dans les détails. Du coup, ne considérez pas ce tableau comme quelque chose de complet, ou d’exhaustif ! Car il existe « beaucoup » de variantes pour ces protocoles, et « beaucoup » de valeurs inférieures, intermédiaires, et/ou supérieures, que je n’ai pas noté ici.

Par ailleurs, les distances max de transmission notées dans ce tableau ne sont qu’indicatives. Car ces dernières varient grandement, en fonction :

  • de la vitesse de transmission
  • des caractéristiques physiques des lignes de transmission (qualité des fils, torsadé ou non, …)
  • de l’impédance des lignes de transmission (parfois améliorables avec l’ajout de résistances en amont, ou des largeurs ou traçages de pistes plus adaptés, par exemple)
  • la puissance de transmission (améliorable par l’ajout de buffers, qui permettent d’aller « au-delà » de ces « longueurs maxi »)

Sinon, pour compléter cela, n’hésitez pas à faire un saut sur les pages Wikipédia du protocole UART, du protocole I2C, et du protocole SPI.

Conclusion

Nous voici au terme de cette introduction à la communication série filaire, et principaux protocoles de transmission sériels (pour nous, amateurs d’électronique, j’entends). J’espère que tout ce contenu pourra vous être utile, et vous servir dans vos futurs projets électroniques 😉

Du reste, ne vous inquiétez pas si jamais vous n’avez pas compris tous les protocoles séries présentés ici. Car il n’est pas forcé de tous les connaître à fond, pour pouvoir s’en servir ! C’est d’ailleurs le cas lorsqu’on fait de la programmation arduino, où les librairies I2C et SPI nous mâchent déjà une bonne partie du travail 😉

Sur ce, il ne me reste plus qu’à vous dire : à la prochaine !

Jérôme.

À découvrir aussi : différents modules Arduino, fonctionnant pour beaucoup en I2C ou SPI

Ce contenu vous plaît ? Alors abonnez-vous à la Newsletter pour ne rien louper !

(*) Mis à jour le 01/01/2023

15 commentaires sur “Introduction aux liaisons séries filaires UART, I2C, et SPI (et détail de leurs protocoles de communication)”

  1. Super article qui a l’avantage de réunir les 3 protocoles de communications les plus connus, très utile si un débutant tombe dessus. Franchement bravo, continuez ainsi, vos pages web sont très claires, merci.

  2. Bravo Jérôme pour ce tuto.

    J’admire autant la profondeur et la précision des explications que la clarté de la présentation. J’ai des composants de type I2C qui seront heureux d’être correctement branchés, le moment venu…

    Sans vous mettre de la pression, j’ai hâte de lire votre prochaine publication !

    1. Salut Mario !

      T’inquiète, ça va venir ! Et merci, au passage !

      Là, je travaille actuellement sur 2 nouveaux articles :

      • un indicateur de niveau de batterie auto 12V
      • et une intro au Raspberry Pi Pico

      Tout ça en fonction du temps que j’arrive à me dégager, comme toujours 😉
      Alors à très bientôt !

      Jérôme.

  3. Bonjour

    Merci beaucoup pour cet excellent tuto comme d’habitude. Il conforte mon choix d’utilisation d’un I2C pour gérer mon projet de modernisation d’un petit circuit de train avec Arduinos, écran OLED, extension d’entrées/sorties. Le prototype fonctionne reste à passer à la réalisation. Guy

  4. Article très clair, pédagogique, au contenu suffisant pour un débutant que je suis, bravo pour cet article et tout le travail qui a derrière.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Symbole danger point d'exclamation site PSE, triangle jaune avec contour noir, texte noir alerte au milieuAfin de filtrer au maximum les messages de type "spam" ou "inappropriés", chaque commentaire est soumis à modération, et validé manuellement. Du coup, il se peut que certains commentaires ne soient pas publiés, ou sinon, avec un peu de retard. Par ailleurs, j'ai malheureusement plus de messages à traiter que de temps pour y répondre ; c'est pourquoi je ne pourrais pas répondre à tout le monde. Désolé …