Comment utilisez-vous SPI sur un Arduino?


45

En référence aux cartes Arduino Uno, Mega2560, Leonardo et autres:

  • Comment fonctionne SPI?
  • À quelle vitesse est SPI?
  • Comment se connecter entre un maître et un esclave?
  • Comment faire un esclave SPI?

Remarque: ceci est conçu comme une question de référence.


Pouvez-vous répondre à cette question connexe arduino.stackexchange.com/questions/60703/…
qwr

Réponses:


81

Introduction à SPI

L' interface SPI ( Serial Peripheral Interface Bus ) est utilisée pour la communication entre plusieurs périphériques sur de courtes distances et à grande vitesse.

Il existe généralement un seul périphérique "maître" qui initie la communication et fournit l'horloge qui contrôle le débit de transfert de données. Il peut y avoir un ou plusieurs esclaves. Pour plusieurs esclaves, chacun a son propre signal "sélection d'esclave", décrit plus loin.


Signaux SPI

Dans un système SPI complet, vous aurez quatre lignes de signal:

  • Master Out, Slave In ( MOSI ) - ce sont les données allant du maître à l'esclave
  • Entrée maître, sortie esclave ( MISO ) - ce sont les données allant de l'esclave au maître
  • Serial Clock ( SCK ) - lorsque cette option bascule les bits maître et esclave sur le bit suivant
  • Slave Select ( SS ) - Ceci dit à un esclave particulier de devenir "actif"

Lorsque plusieurs esclaves sont connectés au signal MISO, ils sont supposés passer à l'état trois fois (conserver une haute impédance) de la ligne MISO jusqu'à ce qu'ils soient sélectionnés par Slave Select. Normalement, Slave Select (SS) devient faible pour l'affirmer. C'est-à-dire qu'il est actif bas. Une fois qu'un esclave particulier est sélectionné, il convient de configurer la ligne MISO en tant que sortie afin de pouvoir envoyer des données au maître.

Cette image montre la manière dont les données sont échangées lorsqu’un octet est envoyé:

Protocole SPI montrant 4 signaux

Notez que trois signaux sont émis par le maître (MOSI, SCK, SS) et l’un est une entrée (MISO).


Timing

La séquence d'événements est la suivante:

  • SS va bas pour l'affirmer et activer l'esclave
  • La SCKligne bascule pour indiquer quand les lignes de données doivent être échantillonnées
  • Les données sont échantillonnées par le maître et l' esclave sur le premier bord SCK( en utilisant la phase d'horloge par défaut)
  • Le maître et l'esclave préparent le bit suivant sur le bord arrière de SCK(en utilisant la phase d'horloge par défaut), en modifiant MISO/ MOSIsi nécessaire
  • Une fois que la transmission est terminée (éventuellement après l'envoi de plusieurs octets), elle SSpasse ensuite à l'état haut pour l'annuler.

Notez que:

  • Le bit le plus significatif est envoyé en premier (par défaut)
  • Les données sont envoyées et reçues au même instant (duplex intégral)

Les données étant envoyées et reçues sur la même impulsion d'horloge, il n'est pas possible pour l'esclave de répondre immédiatement au maître. Les protocoles SPI attendent généralement du maître qu'il demande des données lors d'une transmission et qu'il reçoive une réponse lors d'une transmission ultérieure.

En utilisant la bibliothèque SPI sur l’Arduino, effectuer un seul transfert ressemble à ceci dans le code:

 byte outgoing = 0xAB;
 byte incoming = SPI.transfer (outgoing);

Exemple de code

Exemple d'envoi uniquement (en ignorant les données entrantes):

#include <SPI.h>

void setup (void)
  {
  digitalWrite(SS, HIGH);  // ensure SS stays high
  SPI.begin ();
  } // end of setup

void loop (void)
  {
  byte c;

  // enable Slave Select
  digitalWrite(SS, LOW);    // SS is pin 10

  // send test string
  for (const char * p = "Fab" ; c = *p; p++)
    SPI.transfer (c);

  // disable Slave Select
  digitalWrite(SS, HIGH);

  delay (100);
  } // end of loop

Câblage pour SPI de sortie uniquement

Le code ci-dessus (qui envoie uniquement) peut être utilisé pour piloter un registre à décalage série de sortie. Ce ne sont que des périphériques de sortie, nous n'avons donc pas à nous soucier des données entrantes. Dans leur cas, la broche SS pourrait être appelée "broche" ou "verrou".

Protocole SPI montrant 3 signaux

Le registre à décalage série 74HC595 et diverses bandes de LED en sont quelques exemples. Par exemple, cet écran LED de 64 pixels piloté par une puce MAX7219:

Écran LED 64 pixels

Dans ce cas, vous pouvez voir que le fabricant de la carte a utilisé des noms de signaux légèrement différents:

  • DIN (entrée de données) est MOSI (sortie maître, entrée esclave)
  • CS (Chip Select) est SS (Slave Select)
  • CLK (horloge) est SCK (horloge série)

La plupart des conseils vont suivre un modèle similaire. Parfois, DIN est juste DI (Data In).

Voici un autre exemple, cette fois une carte d'affichage à LED à 7 segments (également basée sur la puce MAX7219):

Affichage LED à 7 segments

Ceci utilise exactement les mêmes noms de signaux que l’autre tableau. Dans ces deux cas, vous pouvez voir que la carte n'a besoin que de 5 fils, les trois pour SPI, plus l'alimentation et la terre.


Phase d'horloge et polarité

Vous pouvez échantillonner l'horloge SPI de quatre manières.

Le protocole SPI permet des variations sur la polarité des impulsions d'horloge. CPOL est la polarité de l'horloge et l'ACSP est la phase d'horloge.

  • Mode 0 (par défaut) - l'horloge est normalement basse (CPOL = 0) et les données sont échantillonnées lors du passage de bas à haut (bord d'attaque) (CPHA = 0)
  • Mode 1 - l'horloge est normalement basse (CPOL = 0) et les données sont échantillonnées lors du passage de haut en bas (bord arrière) (CPHA = 1)
  • Mode 2 - l'horloge est normalement haute (CPOL = 1) et les données sont échantillonnées lors du passage de la position haute à la position basse (front montant) (CPHA = 0)
  • Mode 3 - l'horloge est normalement haute (CPOL = 1) et les données sont échantillonnées lors du passage de bas à haut (bord de fuite) (CPHA = 1)

Ceux-ci sont illustrés dans ce graphique:

Phase d'horloge SPI et polarité

Vous devez vous reporter à la fiche technique de votre appareil pour obtenir la phase et la polarité correctes. Il y aura généralement un diagramme qui montre comment échantillonner l'horloge. Par exemple, à partir de la fiche technique de la puce 74HC595:

74HC595 horloge

Comme vous pouvez le constater, l'horloge est normalement basse (CPOL = 0) et est échantillonnée sur le bord d'attaque (CPHA = 0). Il s'agit donc du mode SPI 0.

Vous pouvez changer la polarité de l'horloge et la phase dans le code comme ceci (choisissez-en un, bien sûr):

SPI.setDataMode (SPI_MODE0);
SPI.setDataMode (SPI_MODE1);
SPI.setDataMode (SPI_MODE2);
SPI.setDataMode (SPI_MODE3);

Cette méthode est déconseillée depuis les versions 1.6.0 et supérieures de l'IDE Arduino. Pour les versions récentes, vous modifiez le mode d'horloge dans l' SPI.beginTransactionappel, comme suit:

SPI.beginTransaction (SPISettings (2000000, MSBFIRST, SPI_MODE0));  // 2 MHz clock, MSB first, mode 0

Commande de données

Le bit par défaut est le plus significatif en premier, mais vous pouvez demander au matériel de traiter le bit le moins significatif en premier, comme ceci:

SPI.setBitOrder (LSBFIRST);   // least significant bit first
SPI.setBitOrder (MSBFIRST);   // most significant bit first

Encore une fois, ceci est déconseillé dans les versions 1.6.0 et suivantes de l'IDE Arduino. Pour les versions récentes, vous modifiez l'ordre des bits dans l' SPI.beginTransactionappel, comme suit:

SPI.beginTransaction (SPISettings (1000000, LSBFIRST, SPI_MODE2));  // 1 MHz clock, LSB first, mode 2

La vitesse

Le paramètre par défaut pour SPI consiste à utiliser la vitesse d'horloge système divisée par quatre, c'est-à-dire une impulsion d'horloge SPI toutes les 250 ns, en supposant une horloge de processeur de 16 MHz. Vous pouvez changer le diviseur d'horloge en utilisant setClockDividercomme ceci:

SPI.setClockDivider (divider);

Où "diviseur" est l'un des:

  • SPI_CLOCK_DIV2
  • SPI_CLOCK_DIV4
  • SPI_CLOCK_DIV8
  • SPI_CLOCK_DIV16
  • SPI_CLOCK_DIV32
  • SPI_CLOCK_DIV64
  • SPI_CLOCK_DIV128

Le taux le plus rapide est "diviser par 2" ou une impulsion d'horloge SPI toutes les 125 ns, en supposant une horloge de processeur de 16 MHz. Il faudrait donc 8 * 125 ns ou 1 µs pour transmettre un octet.

Cette méthode est déconseillée depuis les versions 1.6.0 et supérieures de l'IDE Arduino. Pour les versions récentes, vous modifiez la vitesse de transfert dans l' SPI.beginTransactionappel, comme suit:

SPI.beginTransaction (SPISettings (4000000, MSBFIRST, SPI_MODE0));  // 4 MHz clock, MSB first, mode 0

Cependant, des tests empiriques montrent qu'il est nécessaire de disposer de deux impulsions d'horloge entre octets, de sorte que la cadence maximale à laquelle les octets peuvent être cadencés est de 1,125 µs chacun (avec un diviseur d'horloge de 2).

Pour résumer, chaque octet peut être envoyé à un débit maximal de un par 1,125 µs (avec une horloge de 16 MHz), ce qui donne un taux de transfert maximal théorique de 1 / 1,125 µs, soit 888 888 octets par seconde (sans le temps système comme le réglage SS sur).


Connexion à Arduino

Arduino Uno

Connexion via les broches numériques 10 à 13:

Arduino Uno SPI pins

Connexion via l'en-tête ICSP:

Brochage ICSP - Uno

En-tête ICSP

Arduino Atmega2560

Connexion via les broches numériques 50 à 52:

Arduino Mega2560 broches SPI

Vous pouvez également utiliser l'en-tête ICSP, similaire à l'Uno ci-dessus.

Arduino Leonardo

Le Leonardo et le Micro n’exposent pas les broches SPI sur les broches numériques, contrairement aux Uno et Mega. Votre seule option est d'utiliser les broches d'en-tête ICSP, comme illustré ci-dessus pour l'Uno.


Plusieurs esclaves

Un maître peut communiquer avec plusieurs esclaves (mais un seul à la fois). Pour ce faire, il affirme SS pour un esclave et le désaffirme pour tous les autres. L'esclave qui a SS affirmé (généralement cela signifie LOW) configure sa broche MISO en tant que sortie afin que l'esclave, et cet esclave seul, puisse répondre au maître. Les autres esclaves ignorent les impulsions d'horloge entrantes si SS n'est pas affirmé. Ainsi, vous avez besoin d’un signal supplémentaire pour chaque esclave, comme ceci:

Plusieurs esclaves SPI

Dans ce graphique, vous pouvez voir que MISO, MOSI, SCK sont partagés entre les deux esclaves, mais chaque esclave a son propre signal SS (sélection d'esclave).


Protocoles

La spécification SPI ne spécifie pas les protocoles en tant que tels, il appartient donc à chaque paire maître / esclave de s’entendre sur la signification des données. Bien que vous puissiez envoyer et recevoir des octets simultanément, l’octet reçu ne peut pas être une réponse directe à l’octet envoyé (car ils sont assemblés simultanément).

Il serait donc plus logique pour une extrémité d'envoyer une requête (par exemple, 4 pourrait signifier "lister le répertoire du disque"), puis d'effectuer des transferts (éventuellement en envoyant des zéros vers l'extérieur) jusqu'à réception d'une réponse complète. La réponse peut se terminer par un caractère de nouvelle ligne ou 0x00.

Lisez la fiche technique de votre périphérique esclave pour connaître les séquences de protocole attendues.


Comment faire un esclave SPI

L'exemple précédent montre que l'Arduino est le maître, envoyant des données à un périphérique esclave. Cet exemple montre comment l’Arduino peut être un esclave.

Configuration materielle

Connectez deux Arduino Unos ensemble avec les broches suivantes connectées l'une à l'autre:

  • 10 (SS)
  • 11 (MOSI)
  • 12 (MISO)
  • 13 (SCK)

  • + 5v (si nécessaire)

  • GND (pour le retour du signal)

Sur l’Arduino Mega, les broches sont 50 (MISO), 51 (MOSI), 52 (SCK) et 53 (SS).

Dans tous les cas, MOSI à une extrémité est connecté à MOSI à l'autre, vous ne les échangez pas (c'est-à-dire que vous n'avez pas MOSI <-> MISO). Le logiciel configure l'une des extrémités de MOSI (extrémité principale) en tant que sortie et l'autre extrémité (extrémité esclave) en tant qu'entrée.

Exemple de maître

#include <SPI.h>

void setup (void)
{

  digitalWrite(SS, HIGH);  // ensure SS stays high for now

  // Put SCK, MOSI, SS pins into output mode
  // also put SCK, MOSI into LOW state, and SS into HIGH state.
  // Then put SPI hardware into Master mode and turn SPI on
  SPI.begin ();

  // Slow down the master a bit
  SPI.setClockDivider(SPI_CLOCK_DIV8);

}  // end of setup


void loop (void)
{

  char c;

  // enable Slave Select
  digitalWrite(SS, LOW);    // SS is pin 10

  // send test string
  for (const char * p = "Hello, world!\n" ; c = *p; p++)
    SPI.transfer (c);

  // disable Slave Select
  digitalWrite(SS, HIGH);

  delay (1000);  // 1 seconds delay
}  // end of loop

Exemple esclave

#include <SPI.h>

char buf [100];
volatile byte pos;
volatile bool process_it;

void setup (void)
{
  Serial.begin (115200);   // debugging

  // turn on SPI in slave mode
  SPCR |= bit (SPE);

  // have to send on master in, *slave out*
  pinMode (MISO, OUTPUT);

  // get ready for an interrupt
  pos = 0;   // buffer empty
  process_it = false;

  // now turn on interrupts
  SPI.attachInterrupt();

}  // end of setup


// SPI interrupt routine
ISR (SPI_STC_vect)
{
byte c = SPDR;  // grab byte from SPI Data Register

  // add to buffer if room
  if (pos < sizeof buf)
    {
    buf [pos++] = c;

    // example: newline means time to process buffer
    if (c == '\n')
      process_it = true;

    }  // end of room available
}  // end of interrupt routine SPI_STC_vect

// main loop - wait for flag set in interrupt routine
void loop (void)
{
  if (process_it)
    {
    buf [pos] = 0;
    Serial.println (buf);
    pos = 0;
    process_it = false;
    }  // end of flag set

}  // end of loop

L'esclave est entièrement guidé par des interruptions, il peut donc faire d'autres choses. Les données SPI entrantes sont collectées dans une mémoire tampon et un indicateur est défini lorsqu'un "octet significatif" (dans ce cas, une nouvelle ligne) arrive. Cela indique à l'esclave de démarrer et de traiter les données.

Exemple de connexion de maître à esclave à l'aide de SPI

Arduino SPI maître et esclave


Comment obtenir une réponse d'un esclave

Dans la suite du code ci-dessus, qui envoie des données d’un maître SPI à un esclave, l’exemple ci-dessous montre l’envoi de données à un esclave, le fait en faire quelque chose et renvoie une réponse.

Le maître est similaire à l'exemple ci-dessus. Cependant, un point important est la nécessité d'ajouter un léger délai (environ 20 microsecondes). Sinon, l'esclave n'a pas la possibilité de réagir aux données entrantes et de faire quelque chose avec.

L'exemple montre l'envoi d'une "commande". Dans ce cas, "a" (ajouter quelque chose) ou "s" (soustraire quelque chose). Cela montre que l'esclave est en train de faire quelque chose avec les données.

Après avoir activé la sélection par esclave (SS) pour lancer la transaction, le maître envoie la commande, suivie d’un nombre quelconque d’octets, puis déclenche la SS pour mettre fin à la transaction.

Un point très important est que l'esclave ne peut pas répondre à un octet entrant au même moment. La réponse doit être dans l'octet suivant. Cela est dû au fait que les bits envoyés et les bits reçus sont envoyés simultanément. Ainsi, pour ajouter quelque chose à quatre chiffres, nous avons besoin de cinq transferts, comme ceci:

transferAndWait ('a');  // add command
transferAndWait (10);
a = transferAndWait (17);
b = transferAndWait (33);
c = transferAndWait (42);
d = transferAndWait (0);

Nous demandons d’abord une action sur le numéro 10. Mais nous n’obtenons pas de réponse avant le prochain transfert (celui sur 17). Toutefois, "a" sera défini sur la réponse à 10. Enfin, nous finirons par envoyer un "factice" numéro 0, afin d'obtenir la réponse pour 42.

Maître (exemple)

  #include <SPI.h>

  void setup (void)
    {
    Serial.begin (115200);
    Serial.println ();

    digitalWrite(SS, HIGH);  // ensure SS stays high for now
    SPI.begin ();

    // Slow down the master a bit
    SPI.setClockDivider(SPI_CLOCK_DIV8);
    }  // end of setup

  byte transferAndWait (const byte what)
    {
    byte a = SPI.transfer (what);
    delayMicroseconds (20);
    return a;
    } // end of transferAndWait

  void loop (void)
    {

    byte a, b, c, d;

    // enable Slave Select
    digitalWrite(SS, LOW);

    transferAndWait ('a');  // add command
    transferAndWait (10);
    a = transferAndWait (17);
    b = transferAndWait (33);
    c = transferAndWait (42);
    d = transferAndWait (0);

    // disable Slave Select
    digitalWrite(SS, HIGH);

    Serial.println ("Adding results:");
    Serial.println (a, DEC);
    Serial.println (b, DEC);
    Serial.println (c, DEC);
    Serial.println (d, DEC);

    // enable Slave Select
    digitalWrite(SS, LOW);

    transferAndWait ('s');  // subtract command
    transferAndWait (10);
    a = transferAndWait (17);
    b = transferAndWait (33);
    c = transferAndWait (42);
    d = transferAndWait (0);

    // disable Slave Select
    digitalWrite(SS, HIGH);

    Serial.println ("Subtracting results:");
    Serial.println (a, DEC);
    Serial.println (b, DEC);
    Serial.println (c, DEC);
    Serial.println (d, DEC);

    delay (1000);  // 1 second delay
    }  // end of loop

Le code de l'esclave fait pratiquement tout dans la routine d'interruption (appelée lorsque les données SPI entrantes arrivent). Il prend l'octet entrant et ajoute ou soustrait selon "l'octet de commande" mémorisé. Notez que la réponse sera "collectée" la prochaine fois dans la boucle. C'est pourquoi le maître doit envoyer un dernier transfert "factice" pour obtenir la réponse finale.

Dans mon exemple, j'utilise la boucle principale pour détecter simplement quand SS monte et efface la commande sauvegardée. De cette façon, lorsque SS est à nouveau bas pour la transaction suivante, le premier octet est considéré comme l'octet de commande.

De manière plus fiable, cela se ferait avec une interruption. En d’autres termes, vous devez connecter physiquement le SS à l’une des entrées d’interruption (par exemple, sur l’Uno), connecter la broche 10 (SS) à la broche 2 (une entrée d’interruption) ou utiliser une interruption de changement de broche sur la broche 10.

Ensuite, l’interruption peut être utilisée pour remarquer que le SS est tiré bas ou haut.

Esclave (exemple)

// what to do with incoming data
volatile byte command = 0;

void setup (void)
  {

  // have to send on master in, *slave out*
  pinMode(MISO, OUTPUT);

  // turn on SPI in slave mode
  SPCR |= _BV(SPE);

  // turn on interrupts
  SPCR |= _BV(SPIE);

  }  // end of setup


// SPI interrupt routine
ISR (SPI_STC_vect)
  {
  byte c = SPDR;

  switch (command)
    {
    // no command? then this is the command
    case 0:
      command = c;
      SPDR = 0;
      break;

    // add to incoming byte, return result
    case 'a':
      SPDR = c + 15;  // add 15
      break;

    // subtract from incoming byte, return result
    case 's':
      SPDR = c - 8;  // subtract 8
      break;

    } // end of switch

  }  // end of interrupt service routine (ISR) SPI_STC_vect

void loop (void)
  {

  // if SPI not active, clear current command
  if (digitalRead (SS) == HIGH)
    command = 0;
  }  // end of loop

Exemple de sortie

Adding results:
25
32
48
57
Subtracting results:
2
9
25
34
Adding results:
25
32
48
57
Subtracting results:
2
9
25
34

Sortie de l'analyseur logique

Cela montre le délai entre l'envoi et la réception dans le code ci-dessus:

Synchronisation SPI maître et esclave


Nouvelle fonctionnalité dans IDE 1.6.0 et versions ultérieures

La version 1.6.0 de l'EDI a, dans une certaine mesure, modifié le fonctionnement de SPI. Vous devez toujours faire SPI.begin() avant d'utiliser SPI. Cela configure le matériel SPI. Cependant maintenant, lorsque vous êtes sur le point de commencer à communiquer avec un esclave , vous aussi faites SPI.beginTransaction()pour mettre en place SPI (pour cet esclave) avec le bon:

  • Vitesse de l'horloge
  • Ordre de bits
  • Phase d'horloge et polarité

Lorsque vous avez fini de communiquer avec l'esclave, vous appelez SPI.endTransaction(). Par exemple:

SPI.beginTransaction (SPISettings (2000000, MSBFIRST, SPI_MODE0));
digitalWrite (SS, LOW);        // assert Slave Select
byte foo = SPI.transfer (42);  // do a transfer
digitalWrite (SS, HIGH);       // de-assert Slave Select
SPI.endTransaction ();         // transaction over

Pourquoi utiliser SPI?

J'ajouterais une question préliminaire: quand / pourquoi utiliseriez-vous SPI? Un besoin de configuration multi-maîtres ou un très grand nombre d'esclaves inclinerait la balance vers I2C.

Ceci est une excellente question. Mes réponses sont:

  • Certains appareils (assez nombreux) ne prennent en charge que la méthode de transfert SPI. Par exemple, le registre à décalage de sortie 74HC595, le registre à décalage d'entrée 74HC165, le pilote de DEL MAX7219 et quelques bandes de DEL que j'ai vues. Vous pouvez donc l'utiliser car le périphérique cible ne le prend en charge que.
  • SPI est vraiment la méthode la plus rapide disponible sur les puces Atmega328 (et similaires). Le débit le plus rapide cité ci-dessus est de 888 888 octets par seconde. En utilisant I 2 C, vous ne pouvez obtenir qu’environ 40 000 octets par seconde. Les frais généraux de l'I 2 C sont assez importants, et si vous essayez d'interfacer très rapidement, SPI est le choix préféré. Tout à fait quelques familles de puces (par exemple. MCP23017 et MCP23S17) soutiennent en fait les deux I 2 C et SPI de sorte que vous pouvez choisir souvent entre la vitesse et la possibilité d'avoir plusieurs périphériques sur un seul bus.
  • Les périphériques SPI et I 2 C sont tous deux pris en charge matériellement sur l’Atmega328. Vous pouvez donc éventuellement effectuer un transfert via SPI simultanément avec I 2 C, ce qui vous donnerait un coup de pouce en vitesse.

Les deux méthodes ont leur place. I 2 C vous permet de connecter plusieurs périphériques à un seul bus (deux fils, plus la terre). Ce serait donc le choix à privilégier si vous aviez besoin d'interroger un nombre important de périphériques, peut-être assez rarement. Cependant, la vitesse de SPI pourrait être plus pertinente dans les situations où vous devez produire rapidement (par exemple une bande de LED) ou entrer rapidement (par exemple un convertisseur ADC).


Les références


Allez-vous couvrir l'étrangeté qu'est le SPI du duc? Où la configuration du port SPI est-elle liée à la broche SS utilisée et où se trouvent des broches SS matérielles (IIRC) 4 affectées au port SPI?
Majenko

Autre point à propos de la sélection: parfois, vous n’avez vraiment pas le choix, car le capteur que vous souhaitez / devez utiliser n’est disponible qu’en I2C.
Igor Stoppa

Are you going to cover the weirdness that is the Due's SPI?- Je ne sais rien du SPI de Due (à part présumer que le protocole global est le même). Vous êtes le bienvenu pour ajouter une réponse couvrant cet aspect.
Nick Gammon

Quand le livre audio de cette réponse sortira-t-il
lirez-

1
@AMADANONInc. Peut-être une vidéo de musique? Ou une animation? Je ne suis pas sûr si mon accent australien serait compréhensible. : P
Nick Gammon
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.