Comment fonctionnent les communications série sur l'Arduino?

16

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

  • Comment fonctionnent les communications série?
  • Quelle est la vitesse de la série?
  • Comment puis-je me connecter entre un expéditeur et un destinataire?

Veuillez noter: Il s'agit d'une question de référence.

Nick Gammon
la source
Vous pourriez trouver cela intéressant à propos des tampons des deux côtés d'un Nano connecté à un système Raspian exécutant un enregistreur de données Python, en utilisant simplement un câble de programmation USB standard entre les deux: arduino.stackexchange.com/questions/11710/…
SDsolar

Réponses:

16

Les communications série asynchrones (généralement appelées séries) sont utilisées pour envoyer des octets d'un périphérique à un autre. Un appareil peut être un ou plusieurs des éléments suivants:

  • Arduino
  • PC
  • GPS
  • Lecteur de carte RFID
  • affichage LCD
  • Modem
  • Autre

Fréquence d'horloge et échantillonnage des données

Contrairement aux communications série SPI / USB / I2C, il n'y a pas de signal d'horloge. L'horloge d'échantillonnage est une fréquence d'échantillonnage convenue (connue sous le nom de vitesse de transmission). L'expéditeur et le récepteur doivent être configurés pour utiliser le même débit sinon le récepteur recevra des données sans signification (car les bits ne sont pas échantillonnés au même débit qu'ils ont été envoyés).

La transmission est asynchrone, ce qui signifie essentiellement que les octets peuvent être envoyés à tout moment, avec des écarts variables entre eux. Ce graphique illustre l'envoi d'un seul octet:

Communications série - envoi d'un octet

Le graphique ci-dessus montre la lettre «F» en cours de transmission. En ASCII, c'est 0x46 (en hexadécimal) ou 0b01000110 (en binaire). Le moins peu significatif (faible) est transmis en premier, donc dans le graphique ci - dessus , vous voyez les bits qui arrivent dans l'ordre: 01100010.

Le temps "inactif" entre les octets est transmis sous forme de bits "1" continus (en fait, la ligne de transmission est maintenue haute en continu).

Pour indiquer le début d'un octet, le bit de départ est toujours indiqué en tirant la ligne vers le bas comme indiqué sur le graphique. Une fois que le récepteur voit le bit de départ, il attend 1,5 fois le temps d'échantillonnage, puis échantillonne les bits de données. Il attend 1,5 fois pour qu'il:

  • Saute le bit de départ
  • Échantillons à mi-chemin du bit suivant

Si la vitesse de transmission est de 9600 bauds, par exemple, la fréquence d'échantillonnage sera de 1/9600 = 0.00010416secondes (104,16 µs).

Ainsi, à 9600 bauds, après avoir reçu un bit de démarrage, le récepteur attend 156,25 µs, puis échantillonne toutes les 104,16 µs.

Démarrer le chronométrage des bits

Le but du bit d'arrêt est de s'assurer qu'il y a bien un bit entre chaque octet. Sans le bit d'arrêt, si un octet se terminait par un zéro, il serait impossible pour le matériel de faire la différence entre celui-ci et le bit de début de l'octet suivant.

Pour produire la sortie ci-dessus sur un Uno, vous pouvez écrire ce code:

void setup()
  {
      Serial.begin(9600);
      Serial.print("F");
  }

void loop ()
  {
  }

Nombre de bits de données

Afin de gagner du temps de transmission (dans les temps anciens, heh), vous étiez autorisé à spécifier différents nombres de bits de données. Le matériel AtMega prend en charge les bits de données numérotés de 5 à 9. De toute évidence, moins il y a de bits de données, moins vous pouvez envoyer d'informations, mais plus ce sera rapide.


Bits de parité

Vous pouvez éventuellement avoir un bit de parité. Ceci est calculé, si nécessaire, en comptant le nombre de 1 dans le caractère, puis en vous assurant que ce nombre est impair ou même en définissant le bit de parité sur 0 ou 1 comme requis.

Par exemple, pour la lettre "F" (ou 0x46 ou 0b01000110), vous pouvez voir qu'il y en a 3 (dans 01000110). Nous avons donc déjà une parité impaire. Ainsi, le bit de parité serait le suivant:

  • Pas de parité: omis
  • Parité paire: un 1 (3 + 1 est pair)
  • Parité impaire: un 0 (3 + 0 est impair)

Le bit de parité, s'il est présent, apparaît après le dernier bit de données mais avant le bit d'arrêt.

Si le récepteur n'obtient pas le bit de parité correct, cela s'appelle une "erreur de parité". Cela indique qu'il y a un problème. Il est possible que l'expéditeur et le récepteur soient configurés pour utiliser des débits en bauds (bits) différents, ou il y a eu du bruit sur la ligne qui a transformé un zéro en un ou vice versa.

Certains premiers systèmes utilisaient également la parité "mark" (où le bit de parité était toujours 1 indépendamment des données), ou la parité "space" (où le bit de parité était toujours 0 indépendamment des données).


Transmission 9 bits

Certains équipements de communication utilisent des données de 9 bits, dans ces cas, le bit de parité est transformé en 9e bit. Il existe des techniques spéciales pour envoyer ce 9ème bit (les registres sont des registres à 8 bits donc le 9ème bit doit être placé ailleurs).


Nombre de bits d'arrêt

Les premiers équipements avaient tendance à être un peu plus lents électroniquement, donc pour donner au récepteur le temps de traiter l'octet entrant, il était parfois spécifié que l'expéditeur enverrait deux bits d'arrêt. Cela ajoute fondamentalement plus de temps où la ligne de données est maintenue haute (un temps de bit de plus) avant que le bit de début suivant puisse apparaître. Ce temps supplémentaire donne au récepteur le temps de traiter le dernier octet entrant.

Si le récepteur n'obtient pas un 1 logique lorsque le bit d'arrêt est censé être, cela s'appelle une "erreur de trame". Cela indique qu'il y a un problème. Il est possible que l'expéditeur et le récepteur soient configurés pour utiliser des débits en bauds (bits) différents.


Notation

Généralement, la communication série est indiquée en vous indiquant la vitesse, le nombre de bits de données, le type de parité et le nombre de bits d'arrêt, comme ceci:

9600/8-N-1

Cela nous dit:

  • 9600 bits par seconde
  • 8 bits de données
  • Pas de parité (vous pourriez voir à la place: E = pair, O = impair)
  • 1 bit d'arrêt

Il est important que l'expéditeur et le destinataire conviennent de ce qui précède, sinon la communication est peu susceptible de réussir.


Brochage

L'Arduino Uno a des broches numériques 0 et 1 disponibles pour le matériel série:

Broches série Arduino Uno

Pour connecter deux Arduinos vous échangez Tx et Rx comme ceci:

Connecter deux Arduinos ensemble


La vitesse

Une large gamme de vitesses est prise en charge (voir graphique ci-dessous). Les vitesses "standard" sont généralement un multiple de 300 bauds (par exemple 300/600/1200/2400, etc.).

D'autres vitesses "non standard" peuvent être gérées en définissant les registres appropriés. La classe HardwareSerial le fait pour vous. par exemple.

Serial.begin (115200);  // set speed to 115200 baud

En règle générale, en supposant que vous utilisez des données 8 bits, vous pouvez alors estimer le nombre d'octets que vous pouvez transmettre par seconde en divisant le débit en bauds par 10 (en raison du bit de démarrage et du bit d'arrêt).

Ainsi, à 9600 bauds, vous pouvez transmettre 960 octets ( 9600 / 10 = 960) par seconde.


Erreurs de débit en bauds

Le débit en bauds sur l'Atmega est généré en divisant l'horloge système, puis en comptant jusqu'à un nombre prédéfini. Ce tableau de la fiche technique montre les valeurs de registre et les pourcentages d'erreur pour une horloge 16 MHz (comme celle de l'Arduino Uno).

Erreurs de débit en bauds

Le bit U2Xn affecte le diviseur de fréquence d'horloge (0 = division par 16, 1 = division par 8). Le registre UBRRn contient le nombre jusqu'à ce que le processeur compte.

Donc, à partir du tableau ci-dessus, nous voyons que nous obtenons 9600 bauds à partir d'une horloge de 16 MHz comme suit:

16000000 / 16 / 104 = 9615

Nous divisons par 104 et non par 103 parce que le compteur est relatif zéro. Ainsi l'erreur ici est 15 / 9600 = 0.0016proche de ce que dit le tableau ci-dessus (0,02%).

Vous remarquerez que certains débits en bauds ont un montant d'erreur plus élevé que d'autres.

Selon la fiche technique, le pourcentage d'erreur maximal pour 8 bits de données est compris entre 1,5% et 2,0% (voir la fiche technique pour plus de détails).


Arduino Leonardo

L'Arduino Leonardo et Micro ont une approche différente des communications série, car ils se connectent directement via USB à l'ordinateur hôte, et non via le port série.

Pour cette raison, vous devez attendre que Serial soit "prêt" (car le logiciel établit une connexion USB), avec quelques lignes supplémentaires, comme ceci:

void setup()
  {
      Serial.begin(115200);
      while (!Serial)
      {}  // wait for Serial comms to become ready
      Serial.print("Fab");
  }

void loop ()
  {
  }

Cependant, si vous souhaitez réellement communiquer via les broches D0 et D1 (plutôt que par le câble USB), vous devez utiliser Serial1 plutôt que Serial. Vous faites comme ça:

void setup()
  {
      Serial1.begin(115200);
      Serial1.print("Fab");
  }

void loop ()
  {
  }

Niveaux de tension

Notez que l'Arduino utilise des niveaux TTL pour les communications série. Cela signifie qu'il attend:

  • Un bit "zéro" vaut 0V
  • Un bit "un" est + 5V

Les équipements série plus anciens conçus pour se connecter au port série d'un PC utilisent probablement des niveaux de tension RS232, à savoir:

  • Un bit "zéro" est de +3 à +15 volts
  • Un bit "un" est de −3 à −15 volts

Non seulement est-ce "inversé" par rapport aux niveaux TTL (un "un" est plus négatif qu'un "zéro"), l'Arduino ne peut pas gérer les tensions négatives sur ses broches d'entrée (ni positives supérieures à 5V).

Vous avez donc besoin d'un circuit d'interface pour communiquer avec de tels appareils. Pour l'entrée (vers l'Arduino) uniquement, un simple transistor, une diode et quelques résistances le feront:

Inverser le tampon

Pour une communication bidirectionnelle, vous devez pouvoir générer des tensions négatives, donc un circuit plus complexe est nécessaire. Par exemple, la puce MAX232 fera cela, en conjonction avec quatre condensateurs de 1 µF pour agir comme des circuits de pompe de charge.


Logiciel série

Il existe une bibliothèque appelée SoftwareSerial qui vous permet de faire des communications série (jusqu'à un certain point) dans le logiciel plutôt que dans le matériel. Cela présente l'avantage de pouvoir utiliser différentes configurations de broches pour les communications série. L'inconvénient est que faire du logiciel en série est plus gourmand en processeur et plus sujet aux erreurs. Voir Software Serial pour plus de détails.


Mega2560

L'Arduino "Mega" dispose de 3 ports série matériels supplémentaires. Ils sont marqués sur la carte comme Tx1 / Rx1, Tx2 / Rx2, Tx3 / Rx3. Ils doivent être utilisés de préférence à SoftwareSerial si possible. Pour ouvrir ces autres ports, vous utilisez les noms Serial1, Serial2, Serial3, comme ceci:

Serial1.begin (115200);  // start hardware serial port Tx1/Rx1
Serial2.begin (115200);  // start hardware serial port Tx2/Rx2
Serial3.begin (115200);  // start hardware serial port Tx3/Rx3

Les interruptions

L'envoi et la réception, à l'aide de la bibliothèque HardwareSerial, utilisent des interruptions.

Envoi en cours

Lorsque vous effectuez une opération Serial.print, les données que vous essayez d'imprimer sont placées dans un tampon de "transmission" interne. Si vous avez 1024 octets ou plus de RAM (comme sur l'Uno), vous obtenez un tampon de 64 octets, sinon vous obtenez un tampon de 16 octets. Si le tampon a de la place, le Serial.printretourne immédiatement, ne retardant ainsi pas votre code. S'il n'y a pas de place, il "bloque" en attendant que le tampon soit suffisamment vidé pour qu'il y ait de la place.

Ensuite, comme chaque octet est transmis par le matériel, une interruption est appelée (l'interruption "USART, Data Register Empty") et la routine d'interruption envoie l'octet suivant à partir du tampon hors du port série.

Réception

Lorsque des données entrantes sont reçues, une routine d'interruption est appelée (l'interruption "USART Rx Complete") et l'octet entrant est placé dans une mémoire tampon de "réception" (de la même taille que la mémoire tampon d'émission mentionnée ci-dessus).

Lorsque vous appelez, Serial.availablevous découvrez combien d'octets sont disponibles dans ce tampon de "réception". Lorsque vous appelez Serial.readun octet, il est supprimé du tampon de réception et renvoyé à votre code.

Sur Arduinos avec 1000 octets ou plus de RAM, il n'y a pas de précipitation pour supprimer les données du tampon de réception, à condition de ne pas le laisser se remplir. S'il se remplit, toutes les autres données entrantes sont supprimées.

Notez qu'en raison de la taille de ce tampon, il est inutile d'attendre l'arrivée d'un très grand nombre d'octets, par exemple:

while (Serial.available () < 200)
  { }  // wait for 200 bytes to arrive

Cela ne fonctionnera jamais car le tampon ne peut pas en contenir autant.


Conseils

  • Avant de lire, assurez-vous toujours que les données sont disponibles. Par exemple, c'est faux:

    if (Serial.available ())
      {
          char a = Serial.read ();
          char b = Serial.read ();  // may not be available
      }

    Le Serial.availabletest garantit que vous n'avez qu'un octet disponible, mais le code essaie d'en lire deux. Cela peut fonctionner, s'il y a deux octets dans le tampon, sinon vous obtiendrez -1 retourné qui ressemblera à 'ÿ' s'il est imprimé.

  • Soyez conscient du temps nécessaire pour envoyer des données. Comme mentionné ci-dessus, à 9600 bauds, vous ne transmettez que 960 octets par seconde, donc essayer d'envoyer 1000 lectures à partir d'un port analogique, à 9600 bauds, ne sera pas très réussi.


Les références

Nick Gammon
la source
Dans le 1er graphique: avec les flèches, il semble que le bit d'arrêt soit transmis en premier. Si vous échangez Rx / Tx et la direction des flèches, je pense que c'est moins déroutant.
ott--
Il était destiné à être lu de gauche à droite (comme cette phrase) et donc les choses à gauche se produisent en premier. Mettez-le comme ceci: sur un oscilloscope, c'est ainsi que vous verriez la trace.
Nick Gammon
Ok avec l'explanaion de l'oscilloscope je l'achète. :-)
ott--
Cependant, j'ai pensé que votre argument était très logique. Qu'en pensent les autres? Serait-il plus clair si les flèches étaient inversées et que j'échangeais Rx / Tx?
Nick Gammon
1
@ linhartr22 Je l'ai modifié pour lire "données sans signification" qui est probablement plus proche.
Nick Gammon