Arduino et le bus SPI

Image non disponible

Comment échanger des données entre une carte Arduino et un périphérique communiquant avec une liaison SPI (Serial Peripheral Interface).

Article lu   fois.

Les deux auteur et traducteur

Site personnel

Traducteur : Profil Pro

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Avant tout, de quoi s'agit-il ? Un peu de théorie…

SPI est un acronyme pour Serial Peripheral Interface. Il s'agit d'un bus de transmission de données série synchrone, où les données peuvent circuler simultanément dans les deux sens, contrairement, par exemple, au bus I2C (Inter-Integrated Circuit). Pour permettre la transmission synchrone de données, le bus SPI utilise quatre fils qui sont :

  • MOSI - Master out, Slave In : cette ligne transporte les données de l'Arduino vers le ou les périphériques SPI ;
  • MISO - Master in, Slave out : cette ligne transporte les données du ou des périphériques SPI vers l'Arduino ;
  • SS - Slave select : cette ligne permet de signifier au périphérique connecté sur le bus que nous souhaitons communiquer avec lui. Chaque périphérique SPI dispose de sa ligne SS connectée à l'Arduino ;
  • SCK - Serial clock.
  • NDLR : l'horloge (clock), un signal rectangulaire périodique généré par le périphérique maître qui, justement, cadence les échanges sur les lignes MOSI et MISO. À chaque « coup d'horloge », un bit est échangé entre le maître et l'esclave. L'auteur n'ayant pas l'intention d'en dire davantage sur le plan de l'électronique numérique, pour plus de détails sur le fonctionnement de la liaison, vous pouvez consulter l'article Wikipédia consacré à la liaison SPI.

Dans ce tutoriel, on considère que la carte Arduino est le périphérique maître (master) et les périphériques SPI sont esclaves (slaves). Sur notre carte Arduino Duemilanove/Uno et cartes compatibles, les connecteurs utilisés sont :

  • SS, connecteur D10 : vous pouvez utiliser une autre sortie numérique, mais le D10 est celui que l'on prend généralement par défaut, comme il est voisin des autres connecteurs utilisés pour la liaison SPI ;
  • MOSI, connecteur D11 ;
  • MISO, connecteur D12 ;
  • SCK, connecteur D13.

Pour les utilisateurs de l'Arduino Mega, MISO est sur le connecteur D50, MOSI le D51, SCK le D52 et SS est d'habitude le D53. Si vous utilisez l'Arduino Leonardo, les connecteurs du SPI sont sur le port ICSP. Voir ici pour plus d'informations. Vous pouvez contrôler un ou plusieurs périphériques connectés sur un même bus SPI. Par exemple, pour un seul périphérique, le câblage donnerait :

Image non disponible

Les données transitent dans les deux sens sur les lignes MOSI et MISO entre l'Arduino et le périphérique SPI. Cela peut se produire seulement si la ligne SS est à l'état bas (LOW). En d'autres termes, pour communiquer avec un périphérique SPI en particulier, on met sa ligne SS à l'état bas (LOW), on échange avec lui, puis on remet sa ligne SS à l'état haut (HIGH). Si on a deux périphériques SPI ou plus sur le bus, le câblage doit ressembler à ceci :

Image non disponible

Notez les deux lignes SS, une ligne est nécessaire pour chaque périphérique SPI partagé par le bus. Pour la ligne SS, vous pouvez prendre n'importe quelle sortie numérique disponible sur l'Arduino. Souvenez-vous juste d'avoir toutes les lignes SS à l'état haut par défaut, à l'exception de la ligne SS du périphérique SPI avec lequel vous voulez dialoguer à un moment donné.

Les données échangées avec un périphérique SPI se présentent sous forme d'« octet ». Vous savez déjà qu'un octet est formé de huit bits, et qu'il représente un nombre binaire dont la valeur décimale est comprise entre 0 et 255. Lors de la communication avec des périphériques SPI, on doit savoir dans quel sens les bits des octets sont envoyés. Bit de poids fort (MSB : Most Significant Bit) ou bit de poids faible (LSB : Least Significant Bit) en premier ? Le bit de poids fort (MSB) est à gauche dans la représentation binaire, et le bit de poids faible (LSB) à droite :

Image non disponible

Des valeurs numériques s'échangent sur le bus SPI, mais ces nombres binaires envoyés peuvent aussi signifier l'envoi de commandes. Vous pouvez vous représenter les huit bits envoyés d'un octet comme autant d'interrupteurs on/off permettant le paramétrage du périphérique SPI. Ces paramètres dépendent du périphérique et il faudra consulter sa fiche technique (data sheet) pour en connaître les détails. Par exemple, un potentiomètre numérique avec six potentiomètres :

Image non disponible

Ce périphérique requiert deux octets de données. L'octet ADDR indique au périphérique lequel des six potentiomètres on veut contrôler (numérotés entre 0 et 5, codage sur trois bits), et l'octet DATA est la valeur envoyée au potentiomètre (NDLR : un potentiomètre étant une résistance électrique variable, cette valeur sera l'image de la résistance électrique souhaitée, entre 0 et 255). Par exemple, pour fixer le potentiomètre n° 2 à la valeur 125, on enverra successivement sous forme d'octet les valeurs 2, puis 125 au périphérique.

II. Comment envoie-t-on des données aux périphériques SPI dans nos sketches(*) ?

(*)NDT : sketch en anglais, que l'on peut traduire approximativement par esquisse ou croquis, désigne tout simplement le code du programme dans l'environnement Arduino (voir First Sketch).

Avant tout, il faut faire appel à la bibliothèque SPI. Elle est incluse avec l'installation de l'EDI par défaut de l'Arduino. En tout début de programme, on écrira :

 
Sélectionnez
#include "SPI.h"

NDLR : la documentation officielle sur la bibliothèque SPI d'Arduino.

Ensuite, dans le void setup(), déclarez quel(s) connecteur(s) seront utilisés pour les lignes SS et configurez-les en sorties (OUTPUT). Par exemple :

 
Sélectionnez
pinMode(ss, OUTPUT);

Où la constante ss a été préalablement fixée à la valeur entière 10. Maintenant, pour activer le bus SPI, on écrit :

 
Sélectionnez
SPI.begin();

Finalement, on doit indiquer dans le code dans quel sens seront envoyés les bits de données de chaque octet, le bit de poids fort (MSB) ou celui de poids faible (LSB) en premier, en écrivant soit :

 
Sélectionnez
SPI.setBitOrder(MSBFIRST);

ou bien :

 
Sélectionnez
SPI.setBitOrder(LSBFIRST);

Quand on est prêt à envoyer une donnée au périphérique SPI, on doit procéder en trois étapes. Premièrement, mettre sa ligne SS à l'état bas (LOW) :

 
Sélectionnez
digitalWrite(SS, LOW);

Ensuite, on envoie les données sous forme d'octets, un octet à la fois, en écrivant :

 
Sélectionnez
SPI.transfer(value);

La valeur est un entier/octet entre 0 et 255. Pour terminer, quand on a fini d'envoyer le ou les octets de données au périphérique, on arrête la transmission en remettant la ligne SS à l'état haut (HIGH) :

 
Sélectionnez
digitalWrite(ss, HIGH);

Finalement, transmettre des données est assez simple. En général, la difficulté provient de la lecture de la fiche technique (data sheet) du périphérique où il faut comprendre la signification des commandes et comment doivent être structurées les données à transmettre (NDLR : s'ajoutent éventuellement les difficultés de la langue, car il n'y a quasiment aucune chance que les data sheets soient traduites en français). Mais avec un peu de pratique, ces petits obstacles peuvent être surmontés.

III. Allons-y avec quelques exemples concrets !

Il est temps de mettre en œuvre le bus SPI et piloter des périphériques. En suivant les exemples ci-dessous, vous acquerrez par la pratique les principes des échanges entre Arduino et des périphériques SPI.

III-A. Exemple 1

Notre premier exemple utilise un composant intéressant, un potentiomètre numérique. On utilisera le potentiomètre 10 kΩ faisant partie de la série des MCP41x2 de Microchip (NDLR : plus exactement le MCP4162-103E/P, 10 kΩ, bus SPI, boîtier DIP. Pour être plus précis, il s'agit d'un rhéostat) :

Image non disponible

Vous trouverez la fiche technique (data sheet) du composant sur le site de Microchip. Pour le contrôler, on doit envoyer deux octets de données. Le premier est l'octet de commande, et fort heureusement pour cet exemple, il vaut toujours zéro (l'adresse du registre qui fixe la valeur du curseur(*) est 00h, voir la table 4-1 dans la documentation). Le second octet configure le curseur qui contrôle la valeur de la résistance. Ainsi, pour configurer le curseur, on doit procéder en trois temps dans le code :

(*)NDLR : le terme « curseur » (patte (W) Wiper du composant) est à rapprocher du fonctionnement des potentiomètres analogiques où la valeur de la résistance en sortie varie en fonction de la position d'un curseur en contact avec une piste résistante mobile. Bien entendu, il n'y a rien de mécanique dans la version à puce du potentiomètre/rhéostat.

Premièrement, on met la ligne SS (Slave Select) à l'état bas :

 
Sélectionnez
digitalWrite(10, LOW);

Puis on envoie les deux octets de données :

 
Sélectionnez
SPI.transfer(0); // command byte
SPI.transfer(value); // wiper value

Dans un dernier temps, on bascule la ligne SS à l'état haut :

 
Sélectionnez
digitalWrite(10, HIGH);

Facile. Les connexions à la carte Arduino sont très simples à faire. Considérez le brochage du MCP4162 :

Image non disponible

Vdd est connecté au 5 V, Vss à la masse GND, /CS au connecteur D10, SCK au connecteur D13, SDI au connecteur D11 et enfin SDO au connecteur D12 de l'Arduino Uno. Maintenant, on fait varier la sortie du MCP4162 avec le programme suivant :

 
Sélectionnez
/*
 Example 34.1 - SPI bus demo using a Microchip MCP4162 digital potentiometer
 http://tronixstuff.com/tutorials > chapter 34 | CC by-sa-nc | John Boxall
*/

#include "SPI.h" // necessary library
int ss = 10; // using digital pin 10 for SPI slave select
int del = 200; // used for various delays

void setup()
{
  pinMode(ss, OUTPUT); // we use this for SS pin
  SPI.begin(); // wake up the SPI bus.
  SPI.setBitOrder(MSBFIRST);
  // our MCP4162 requires data to be sent MSB (most significant byte) first
}

void setValue(int value)
{
  digitalWrite(ss, LOW);
  SPI.transfer(0); // send command byte
  SPI.transfer(value); // send value (0~255)
  digitalWrite(ss, HIGH);
}

void loop()
{
  for (int a = 0; a < 256; a++)
  {
    setValue(a);
    delay(del);
  }
  for (int a = 255; a >= 0; --a)
  {
    setValue(a);
    delay(del);
  }
}

Maintenant, visualisons les effets du programme. Dans la vidéo qui suit, on mesure la résistance avec un multimètre (NDLR : branché entre les sorties P0B et P0W) et on voit la valeur parcourir toute la plage des valeurs de résistance :


Cliquez pour lire la vidéo


Avant de poursuivre plus loin, si les potentiomètres numériques sont nouveaux pour vous, consultez le court guide écrit par Microchip (NDLR : sur le site de Microchip, Application Note 219 : Comparing Digital Potentiometers to Mechanical Potentiometers) à propos des différences entre potentiomètres mécaniques et numériques.

III-B. Exemple 2

Dans cet exemple, on utilisera un potentiomètre numérique (quatre canaux) AD5204 de chez Analog Devices. Il comporte quatre potentiomètres linéaires 10 kΩ et chaque potentiomètre est réglable sur 256 positions. La configuration est « volatile », ce qui signifie qu'elle n'est pas mémorisée lorsque le composant n'est plus alimenté. Par conséquent, lorsqu'on alimente le composant, les potentiomètres sont configurés par défaut avec le curseur au milieu de la plage. Dans notre exemple, on a un composant SOIC 24 broches à monter en surface, mais il existe aussi en boîtier DIP.

Image non disponible

Pour se rendre la vie plus facile, il peut être soudé sur un adaptateur SOIC-DIP (SOIC to DIP adaptator) (NDLR : la version de l'AD5204 en boîtier PDIP vous évitera même les soudures si c'est juste pour du prototypage sur une plaquette de câblage !) :

Image non disponible

Dans cet exemple, on contrôlera la luminosité de quatre LED. Les connexions sont très simples. Vous trouverez le brochage dans la documentation technique (data sheet).

Image non disponible

Voici le programme complet :

 
Sélectionnez
#include <SPI.h> // necessary library
int ss = 10; // using digital pin 10 for SPI slave select
int del = 5; // used for fading delay

void setup()
{
  pinMode(ss, OUTPUT); // we use this for SS pin
  SPI.begin(); // wake up the SPI bus.
  SPI.setBitOrder(MSBFIRST);
  // our AD5204 requires data to be sent MSB (most significant byte) first. See data sheet page 5
  allOff(); // we do this as pot memories are volatile
}

void allOff()
// sets all potentiometers to minimum value
{
  for (int z = 0; z < 4; z++)
  {
    setPot(z, 0);
  }
}

void allOn()
// sets all potentiometers to maximum value
{
  for (int z = 0; z < 4; z++)
  {
    setPot(z, 255);
  }
}

void setPot(int pot, int level)
// sets potentiometer 'pot' to level 'level'
{
  digitalWrite(ss, LOW);
  SPI.transfer(pot);
  SPI.transfer(level);
  digitalWrite(ss, HIGH);
}

void blinkAll(int count)
{
  for (int z = 0; z < count ; z++)
  {
    allOn();
    delay(del);
    allOff();
    delay(del);
  }
}

void indFade()
{
  for (int a = 0; a < 4; a++)
  {
    for (int l = 0; l < 255; l++)
    {
      setPot(a, l);
      delay(del);
    }
    for (int l = 255; l >= 0; --l)
    {
      setPot(a, l);
      delay(del);
    }
  }
}

void allFade(int count)
{
  for (int a = 0; a < count; a++)
  {
    for (int l = 0; l < 255; l++)
    {
      setPot(0, l);
      setPot(1, l);
      setPot(2, l);
      setPot(3, l);
      delay(del);
    }
  }
}

void loop()
{
  blinkAll(3);
  delay(1000);
  indFade();
  allFade(3);
}

Les fonctions allOff() et allOn() sont là pour configurer les potentiomètres respectivement aux valeurs minimale et maximale. On utilise allOff() au début du programme pour éteindre toutes les LED. Cela est nécessaire, car au démarrage les curseurs sont à mi-course. En outre, on utilise aussi ces fonctions dans la fonction blinkAll() pour… faire clignoter les LED. La fonction setPot() prend en arguments le numéro du curseur entre 0 et 3, et un nombre entre 0 et 255 pour fixer sa valeur. Enfin, la fonction indFade()fait le boulot en faisant varier progressivement la luminosité de chaque LED, l'allumer progressivement d'abord, puis atténuer sa luminosité petit à petit et ainsi de suite pour chaque LED. On aurait obtenu un effet comparable en jouant sur la modulation en largeur d'impulsion (Pulse Width Modulation ou PWM).

Finalement, voici le système en action :


Cliquez pour lire la vidéo


III-C. Exemple 3

Dans cet exemple, on utilisera un afficheur 7 segments, 4 digits avec une interface SPI. Utiliser un tel afficheur réduit considérablement le nombre de broches nécessaires sur le microcontrôleur et évite l'emploi de registres à décalage ce qui favorise aussi à réduire la consommation et le nombre de composants. La face avant de l'afficheur :

Image non disponible

Et la face arrière :

Image non disponible

Fort heureusement, chaque broche est repérée clairement avec un marquage sur la carte. Remarquez que la carte n'inclut pas de broches mâles de connexion, elles ont été soudées après-coup à la réception de la carte. Bien que cette carte soit documentée sur le site de Sparkfun, vous pourriez rencontrer des difficultés à la mettre en œuvre, aussi on utilisera un code conçu par des membres du forum du site Arduino (NDLR : le code de l'exemple provient de cette discussion, mais vous auriez très bien pu avoir une solution, chers lecteurs, en ouvrant une discussion sur le forum Arduino de Developpez.com ;) )

Une fois de plus, le câblage est simple :

  • la broche GND à la masse GND de la carte Arduino ;
  • la broche VCC au 5 V de la carte Arduino ;
  • la broche SCK au connecteur D12 de la carte Arduino ;
  • la broche SI au connecteur D11 de la carte Arduino ;
  • la broche CSN au connecteur D10 de la carte Arduino.

Le programme est simple à utiliser, vous devez reprendre toutes les fonctions ainsi que les appels de bibliothèque et les définitions de variable, puis adapter à vos besoins. Pour afficher des nombres (ou les lettres A à F), appelez la fonction :

 
Sélectionnez
write_led(a, b, c);

a est le nombre à afficher, b est la base (2 pour le système binaire, 8 pour l'octal, 10 pour le système décimal usuel et 16 pour l'hexadécimal), puis c pour éventuellement compléter avec des zéros (0=Off, 1=On). Si vous regardez le contenu de la boucle void loop() du programme donné en exemple, on emploie les quatre systèmes de numération pour la démonstration. Si le nombre est trop grand pour être affiché, il affichera OF pour signaler le dépassement (OverFlow). Pour contrôler les points décimaux, le deux-points et la LED en haut à droite du troisième digit, on peut utiliser ce qui suit :

 
Sélectionnez
  write_led_decimals(1); // left-most decimal point
  write_led_decimals(2);
  write_led_decimals(4);
  write_led_decimals(8); // right-most decimal point
  write_led_decimals(16); // colon LEDs
  write_led_decimals(32); // apostrophe LED
  write_led_decimals(0); // off

Enfin, voici un programme de démonstration à expérimenter :

 
Sélectionnez
/*
 Example 34.3 - SPI bus demo using SFE 4-digit LED display [http://bit.ly/ixQdbT]
 http://tronixstuff.com/tutorials > chapter 34
 Based on code by Quazar & Busaboi on Arduio forum - http://bit.ly/iecYBQ
*/
#define DATAOUT 11 //MOSI
#define DATAIN 12 //MISO - not used, but part of builtin SPI
#define SPICLOCK 13 //sck
#define SLAVESELECT 10 //ss

char spi_transfer(volatile char data)
{
  SPDR = data;                    // Start the transmission
  while (!(SPSR & (1 << SPIF)))   // Wait the end of the transmission
  {
  };
  return SPDR;                    // return the received byte
}

void setup()
{
  byte clr;
  pinMode(DATAOUT, OUTPUT);
  pinMode(DATAIN, INPUT);
  pinMode(SPICLOCK, OUTPUT);
  pinMode(SLAVESELECT, OUTPUT);
  digitalWrite(SLAVESELECT, HIGH); //disable device
  SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR1);
  clr = SPSR;
  clr = SPDR;
  delay(10);
  write_led_numbers(0x78, 0x78, 0x78, 0x78); //Blank display
  write_led_decimals(0x00); // All decimal points off
}

void write_led_decimals(int value)
{
  digitalWrite(SLAVESELECT, LOW);
  delay(10);
  spi_transfer(0x77); // Decimal Point OpCode
  spi_transfer(value); // Decimal Point Values
  digitalWrite(SLAVESELECT, HIGH); //release chip, signal end transfer
}

void write_led_numbers(int digit1, int digit2, int digit3, int digit4)
{
  digitalWrite(SLAVESELECT, LOW);
  delay(10);
  spi_transfer(digit1); // Thousands Digit
  spi_transfer(digit2); // Hundreds Digit
  spi_transfer(digit3); // Tens Digit
  spi_transfer(digit4); // Ones Digit
  digitalWrite(SLAVESELECT, HIGH); //release chip, signal end transfer
}

void write_led(unsigned short num, unsigned short base, unsigned short pad)
{
  unsigned short digit[4] = {0, ' ', ' ', ' ' };
  unsigned short place = 0;
  if ( (base < 2) || (base > 16) || (num > (base * base * base * base - 1)) ) {
    write_led_numbers(' ', 0x00, 0x0f, ' '); // indicate overflow
  }
  else {
    while ( (num || pad) && (place < 4) ) {
      if ( (num > 0) || pad )
        digit[place++] = num % base;
      num /= base;
    }
    write_led_numbers(digit[3], digit[2], digit[1], digit[0]);
  }
}

void pointDemo()
{
  write_led_decimals(1);
  delay(1000);
  write_led_decimals(2);
  delay(1000);
  write_led_decimals(4);
  delay(1000);
  write_led_decimals(8);
  delay(1000);
  write_led_decimals(16);
  delay(1000);
  write_led_decimals(32);
  delay(1000);
  write_led_decimals(0); // non-digits all off
}

void loop()
{
  pointDemo();
  delay(500);
  for (int i = 0; i < 100; i++) {
    write_led (i, 10, 1);
    delay(25);
  }
  delay(500);
  for (int i = 100; i >= 0; --i) {
    write_led (i, 10, 0);
    delay(25);
  }
  delay(500); // now binary
  for (int i = 0; i < 16; i++) {
    write_led (i, 2, 0);
    delay(100);
  }
  delay(500);
  for (int i = 15; i >= 0; --i) {
    write_led (i, 2, 0);
    delay(100);
  }
  delay(500); // now octal
  for (int i = 0; i < 500; i++) {
    write_led (i, 8, 0);
    delay(50);
  }
  delay(500);
  // now hexadecimal
  for (int i = 20000; i < 22000; i++) {
    write_led (i, 16, 0);
    delay(50);
  }
  delay(500);
}

NDLR : notez que le programme n'inclut pas la bibliothèque SPI standard comme dans les exemples précédents. L'accès au SPI se fait ici directement en passant par les registres du microcontrôleur Atmel AVR de l'Arduino Uno ( data sheet ATmega328P ). Plus ardue comme programmation de plus bas niveau, mais très instructif, bienvenue dans le monde de la programmation des microcontrôleurs…

Et une courte vidéo de démonstration :


Cliquez pour lire la vidéo


IV. Conclusion

Grâce à cette introduction, nous espérons que vous accéderez facilement au monde du bus SPI et que vous comprendrez comment contrôler les périphériques fonctionnant sur ce bus. Comme toujours, c'est maintenant à vous et votre imagination de trouver des applications et de vous livrer à de nouvelles manigances.

Image non disponible

V. Notes de la Rédaction Developpez.com

Cet article est la traduction du tutoriel écrit par John Boxall et intitulé Arduino and the SPI bus. Retrouvez la version originale de cet article ainsi que les autres chapitres consacrés à Arduino sur le site tronixstuff.

Nous remercions les membres de la Rédaction de Developpez.com pour le travail de traduction et de relecture qu'ils ont effectué, en particulier :

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Licence Creative Commons
Le contenu de cet article est rédigé par John Boxall et est mis à disposition selon les termes de la Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 3.0 non transposé.
Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright © 2015 Developpez.com.