Dans ce chapitre, nous allons apprendre à utiliser la voie série avec Arduino. Nous allons voir comment envoyer puis recevoir des informations avec l’ordinateur, enfin nous ferons quelques exercices pour vérifier que vous avez tout compris. Vous allez le découvrir bientôt, l’utilisation de la voie série avec Arduino est quasiment un jeu d’enfant, puisque tout est opaque aux yeux de l’utilisateur…
Préparer la voie série
Notre objectif, pour le moment, est de communiquer des informations de la carte Arduino vers l’ordinateur et inversement. Pour ce faire, on va d’abord devoir préparer le terrain.
Du côté de l’ordinateur
Pour pouvoir utiliser la communication de l’ordinateur, rien de plus simple. En effet, L’environnement de développement Arduino propose de base un outil pour communiquer. Pour cela, il suffit de cliquer sur le bouton (pour les versions antérieures à la version 1.0) dans la barre de menu pour démarrer l’outil. Pour la version 1.0, l’icône a changé et de place et de visuel :
Une nouvelle fenêtre s’ouvre : c’est le terminal série :
Dans cette fenêtre, vous allez pouvoir envoyer des messages sur la voie série de votre ordinateur (qui est émulée1 par l’Arduino) ; recevoir les messages que votre Arduino vous envoie ; et régler deux trois paramètres tels que la vitesse de communication avec l’Arduino et l’autoscroll qui fait défiler le texte automatiquement. On verra plus loin à quoi sert le dernier réglage.
Du côté du programme
L’objet Serial
Pour utiliser la voie série et communiquer avec notre ordinateur (par exemple), nous allons utiliser un objet (une sorte de variable mais plus évoluée) qui est intégré nativement dans l’ensemble Arduino : l’objet Serial.
Pour le moment, considérez qu’un objet est une variable évoluée qui peut exécuter plusieurs fonctions. On verra (beaucoup) plus loin ce que sont réellement des objets. On apprendra à en créer et à les utiliser lorsque l’on abordera le logiciel Processing.
Cet objet rassemble des informations (vitesse, bits de données, etc.) et des fonctions (envoi, lecture de réception,…) sur ce qu’est une voie série pour Arduino. Ainsi, pas besoin pour le programmeur de recréer tous le protocole (sinon on aurait du écrire nous même TOUT le protocole, tel que "Ecrire un bit haut pendant 1 ms, puis 1 bit bas pendant 1 ms, puis le caractère 'a' en 8 ms…), bref, on gagne un temps fou et on évite les bugs !
Le setup
Pour commencer, nous allons donc initialiser l’objet Serial. Ce code sera à copier à chaque fois que vous allez créer un programme qui utilise la voie série. Le logiciel Arduino à prévu, dans sa bibliothèque Serial, tout un tas de fonctions qui vont nous êtres très utiles, voir même indispensables afin de bien utiliser la voie série. Ces fonctions, je vous les laisse découvrir par vous même si vous le souhaitez, elles se trouvent sur cette page. Dans le but de créer une communication entre votre ordinateur et votre carte Arduino, il faut déclarer cette nouvelle communication et définir la vitesse à laquelle ces deux dispositifs vont communiquer. Et oui, si la vitesse est différente, l’Arduino ne comprendra pas ce que veut lui transmettre l’ordinateur et vice versa ! Ce réglage va donc se faire dans la fonction setup, en utilisant la fonction begin()
de l’objet Serial.
Lors d’une communication informatique, une vitesse peut s’exprimer en bits par seconde (bps) ou en bauds. Ainsi, pour une vitesse de 9600 bps on enverra jusqu’à 9600 '0' ou '1' en une seule seconde. Les vitesses les plus courantes sont 9600, 19200 et 115200 bits par seconde. Le baud quant à lui correspond aux nombre de symboles par seconde. Ici, un symbole correspond à une transmission de 8 bits. Donc pour une vitesse de 9600 bps on peut aussi dire qu’on transmet à 1200 bauds (9600/8).
À présent, votre carte Arduino a ouvert une nouvelle communication vers l’ordinateur. Ils vont pouvoir communiquer ensemble.
- créée de façon fictive↩
Envoyer des données
Le titre est piégeur, en effet, cela peut être l’Arduino qui envoie des données ou l’ordinateur. Bon, on est pas non plus dénué d’une certaine logique puisque pour envoyé des données à partir de l’ordinateur vers la carte Arduino il suffit d’ouvrir le terminal série et de taper le texte dedans ! Donc, on va bien programmer et voir comment faire pour que votre carte Arduino envoie des données à l’ordinateur.
Et ces données, elles proviennent d’où ?
Eh bien de la carte Arduino… En fait, lorsque l’on utilise la voie série pour transmettre de l’information, c’est qu’on en a de l’information à envoyer, sinon cela ne sert à rien. Ces informations proviennent généralement de capteurs connectés à la carte ou de son programme (par exemple la valeur d’une variable). La carte Arduino traite les informations provenant de ces capteurs, s’il faut elle adapte ces informations, puis elle les transmet. On aura l’occasion de faire ça dans la partie dédiée aux capteurs, comme afficher la température sur son écran, l’heure, le passage d’une personne, etc.
Appréhender l’objet Serial
Dans un premier temps, nous allons utiliser l’objet Serial pour tester quelques envois de données. Puis nous nous attèlerons à un petit exercice que vous ferez seul ou presque, du moins vous aurez eu auparavant assez d’informations pour pouvoir le réaliser (ben oui, sinon c’est plus un exercice !).
Phrase ? Caractère ?
On va commencer par envoyer un caractère et une phrase. À ce propos, savez-vous quelle est la correspondance entre un caractère et une phrase ? Une phrase est constituée de caractères les uns à la suite des autres. En programmation, on parle plutôt de chaine caractères pour désigner une phrase.
- Un caractère seul s’écrit entre guillemets simples : 'A’, 'a’, '2’, '!’, …
- Une phrase est une suite de caractère et s’écrit entre guillemets doubles : "Salut tout le monde", "J’ai 42 ans", "Vive Clem' !"
Pour vous garantir un succès dans le monde de l’informatique, essayez d’y penser et de respecter cette convention, écrire 'A' ce n’est pas pareil qu’écrire "A" !
println()
La fonction que l’on va utiliser pour débuter, s’agit de println()
. Ces deux fonctions sont quasiment identiques, mais à quoi servent-elles ?
print()
: cette fonction permet d’envoyer des données sur la voie série. On peut par exemple envoyer un caractère, une chaine de caractère ou d’autres données dont je ne vous ai pas encore parlé.println()
: c’est la même fonction que la précédente, elle permet simplement un retour à la ligne à la fin du message envoyé.
Pour utiliser ces fonctions, rien de plus simple :
Serial.print("Salut ca zeste ?!");
Bien sûr, au préalable, vous devrez avoir "déclaré/créé" votre objet Serial et définis une valeur de vitesse de communication :
Cet objet, parlons-en. Pour vous aider à représenter de façon plus concise ce qu’est l’objet Serial, je vous propose cette petite illustration de mon cru :
Comme je vous le présente, l’objet Serial est muni d’un panel de fonctions qui lui sont propres. Cet objet est capable de réaliser ces fonctions selon ce que le programme lui ordonne de faire. Donc, par exemple, quand j’écris : print()
en lui passant pour paramètre la chaine de caractère : "Salut ca zeste ?!". On peut compléter le code précédent comme ceci :
void setup()
{
Serial.begin(9600);
// l'objet exécute une première fonction
Serial.print("Salut ca zeste ?!");
// puis une deuxième fonction, différente cette fois-ci
Serial.println("Vive Clem' !");
// et exécute à nouveau la même
Serial.println("Cette phrase passe en dessous des deux precedentes");
}
Sur le terminal série, on verra ceci :
Salut ca zeste ?! Vive Clem' !
Cette phrase passe en dessous des deux precedentes
La fonction print()
en détail
Après cette courte prise en main de l’objet Serial, je vous propose de découvrir plus en profondeur les surprises que nous réserve la fonction print()
.
Petite précision, je vais utiliser de préférence print()
.
Résumons un peu ce que nous venons d’apprendre : on sait maintenant envoyer des caractères sur la voie série et des phrases. C’est déjà bien, mais ce n’est qu’un très bref aperçu de ce que l’on peut faire avec cette fonction.
Envoyer des nombres
Avec la fonction print()
, il est aussi possible d’envoyer des chiffres ou des nombres car ce sont des caractères :
void setup()
{
Serial.begin(9600);
Serial.println(9); // chiffre
Serial.println(42); // nombre
Serial.println(32768); // nombre
Serial.print(3.1415926535); // nombre décimale
}
9
42
32768
3.14
Tiens, le nombre pi n’est pas affiché complètement ! C’est quoi le bug ?
Rassurez-vous, ce n’est ni un bug, ni un oubli inopiné de ma part. En fait, pour les nombres décimaux, la fonction print()
affiche par défaut seulement deux chiffres après la virgule. C’est la valeur par défaut et heureusement elle est modifiable. Il suffit de rajouter le nombre de décimales que l’on veut afficher :
void setup()
{
Serial.begin(9600);
Serial.println(3.1415926535, 0);
Serial.println(3.1415926535, 2); // valeur par défaut
Serial.println(3.1415926535, 4);
Serial.println(3.1415926535, 10);
}
3
3.14
3.1415
3.1415926535
Envoyer la valeur d’une variable
Là encore, on utilise toujours la même fonction (qu’est-ce qu’elle polyvalente !). Ici aucune surprise. Au lieu de mettre un caractère ou un nombre, il suffit de passer la variable en paramètre pour qu’elle soit ensuite affichée à l’écran :
int variable = 512;
char lettre = 'a';
void setup()
{
Serial.begin(9600);
Serial.println(variable);
Serial.print(lettre);
}
512
a
Trop facile n’est-ce pas ?
https://www.youtube.com/watch?v=Jk4jA0OHJDoEnvoyer d’autres données
Ce n’est pas fini, on va terminer notre petit tour avec les types de variables que l’on peut transmettre grâce à cette fonction print()
sur la voie série. Prenons l’exemple d’un nombre choisi judicieusement : 65.
Pourquoi ce nombre en particulier ? Et pourquoi pas 12 ou 900 ?
Eh bien, c’est relatif à la table ASCII que nous allons utiliser dans un instant.
Tout d’abord, petit cours de prononciation, ASCII se prononce comme si on disait "A ski", on a donc : "la table à ski" en prononciation phonétique.
La table ASCII, de l’américain "American Standard Code for Information Interchange", soit en bon français : "Code américain normalisé pour l’échange d’information" est, selon Wikipédia :
En somme, c’est un tableau de valeurs codées sur 8bits qui à chaque valeur associent un caractère. Ces caractères sont les lettres de l’alphabet en minuscule et majuscule, les chiffres, des caractères spéciaux et des symboles bizarres. Dans cette table, il y a plusieurs colonnes avec la valeur décimale, la valeur hexadécimale, la valeur binaire et la valeur octale parfois. Nous n’aurons pas besoin de tout ça, mais pour votre culture voici une table ASCII étendu (0 à 255).
Revenons à notre exemple, le nombre 65. C’est en effet grâce à la table ASCII que l’on sait passer d’un nombre à un caractère, car rappelons-le, dans l’ordinateur tout est traité sous forme de nombre en base 2 (binaire). Donc lorsque l’on code :
maVariable = 'A';
// l'ordinateur stocke la valeur 65 dans sa mémoire (cf. table ASCII)
Si vous faites ensuite :
maVariable = maVariable + 1;
// la valeur stockée passe à 66 (= 65 + 1)
// à l'écran, on verra s'afficher la lettre "B"
Au début, on trouvait une seule table ASCII, qui allait de 0 à 127 (codée sur 7bits) et représentait l’alphabet, les chiffres arabes et quelques signes de ponctuation. Depuis, de nombreuses tables dites "étendues" sont apparues et vont de 0 à 255 caractères (valeurs maximales codables sur un type char qui fait 8 bits).
Et que fait-on avec la fonction print() et cette table ?
Là est tout l’intérêt de la table, on peut envoyer des données, avec la fonction print(), de tous types ! En binaire, en hexadécimal, en octal et en décimal.
Vous pouvez donc manipuler les données que vous envoyez à travers la voie série ! C’est là qu’est l’avantage de cette fonction.
Exercice : Envoyer l’alphabet
Objectif
Nous allons maintenant faire un petit exercice, histoire de s’entraîner à envoyer des données. Le but, tout simple, est d’envoyer l’ensemble des lettres de l’alphabet de manière la plus intelligente possible, autrement dit, sans écrire 26 fois "print();"… La fonction setup restera la même que celle vue précédemment. Un délai de 250 ms est attendu entre chaque envoi de lettre et un delay de 5 secondes est attendu entre l’envoi de deux alphabets.
Bon courage !
Correction
Bon j’espère que tout c’est bien passé et que vous n’avez pas joué au roi du copier/coller en me mettant 26 print…
Si l’exercice vous a paru trop simple, vous pouvez essayer d’envoyer l’alphabet à l’envers, ou l’alphabet minuscule ET majuscule ET les chiffres de 0 à 9… Amusez-vous bien !
Recevoir des données
Cette fois, il s’agit de l’Arduino qui reçoit les données que nous, utilisateur, allons transmettre à travers le terminal série. Je vais prendre un exemple courant : une communication téléphonique. En règle générale, on dit "Hallo" pour dire à l’interlocuteur que l’on est prêt à écouter le message. Tant que la personne qui appelle n’a pas cette confirmation, elle ne dit rien (ou dans ce cas elle fait un monologue ). Pareillement à cette conversion, l’objet Serial dispose d’une fonction pour "écouter" la voie série afin de savoir si oui ou non il y a une communication de données.
Réception de données
On m’a parlé ?
Pour vérifier si on a reçu des données, on va régulièrement interroger la carte pour lui demander si des données sont disponibles dans son buffer de réception. Un buffer est une zone mémoire permettant de stocker des données sur un court instant. Dans notre situation, cette mémoire est dédiée à la réception sur la voie série. Il en existe un aussi pour l’envoi de donnée, qui met à la queue leu leu les données à envoyer et les envoie dès que possible. En résumé, un buffer est une sorte de salle d’attente pour les données. Je disais donc, nous allons régulièrement vérifier si des données sont arrivées. Pour cela, on utilise la fonction available()
(de l’anglais "disponible") de l’objet Serial. Cette fonction renvoie le nombre de caractères dans le buffer de réception de la voie série. Voici un exemple de traitement :
Cette fonction de l’objet Serial, available(), renvoie la valeur -1 quand il n’y a rien à lire sur le buffer de réception.
Lire les données reçues
Une fois que l’on sait qu’il y a des données, il faut aller les lire pour éventuellement en faire quelque chose. La lecture se fera tout simplement avec la fonction… read() ! Cette fonction renverra le premier caractère arrivé non traité (comme un supermarché traite la première personne arrivée dans la file d’attente de la caisse avant de passer au suivant). On accède donc caractère par caractère aux données reçues. Ce type de fonctionnement est appelé FIFO (First In First Out, premier arrivé, premier traité). Si jamais rien n’est à lire (personne dans la file d’attente), je le disais, la fonction renverra -1 pour le signaler.
Ce code est une façon simple de se passer de la fonction available().
https://www.youtube.com/watch?v=DpOahtA9NM0Le serialEvent
Si vous voulez éviter de mettre le test de présence de données sur la voie série dans votre code, Arduino a rajouté une fonction qui s’exécute de manière régulière. Cette dernière se lance régulièrement avant chaque redémarrage de la loop. Ainsi, si vous n’avez pas besoin de traiter les données de la voie série à un moment précis, il vous suffit de rajouter cette fonction. Pour l’implémenter c’est très simple, il suffit de mettre du code dans une fonction nommé serialEvent()
(attention à la casse) qui sera a rajouté en dehors du setup et du loop. Le reste du traitement de texte se fait normalement, avec Serial.read()
par exemple. Voici un exemple de squelette possible :
Exemple de code complet
Voici maintenant un exemple de code complet qui va aller lire les caractères présents dans le buffer de réception s’il y en a et les renvoyer tels quels à l’expéditeur (mécanisme d’écho).
Avouez que tout cela n’était pas bien difficile. Je vais donc en profiter pour prendre des vacances et vous laisser faire un exercice qui demande un peu de réflexion.
[Exercice] Attention à la casse !
Consigne
Le but de cet exercice est très simple. L’utilisateur saisit un caractère à partir de l’ordinateur et si ce caractère est minuscule, il est renvoyé en majuscule ; s’il est majuscule il est renvoyé en minuscule. Enfin, si le caractère n’est pas une lettre on se contente de le renvoyer normalement, tel qu’il est. Voilà le résultat de mon programme :
Voici le simulateur interactif avec juste une Arduino, pour ceux qui n’ont pas le matériel en réel mais veulent tout de même faire l’exercice :
!(https://www.tinkercad.com/embed/hLVCsf3NkbI)
Correction
Je suppose que grâce au superbe tutoriel qui précède vous avez déjà fini sans problème, n’est-ce pas ?
La fonction setup()
et les variables utiles
Une fois n’est pas coutume, on va commencer par énumérer les variables utiles et le contenu de la fonction setup()
. Pour ce qui est des variables globales, on n’en retrouve qu’une seule, "carlu". Cette variable de type int
sert à stocker le caractère lu sur le buffer de la carte Arduino. Puis on démarre une nouvelle voie série à 9600 bps :
Le programme
Le programme principal n’est pas très difficile non plus. Il va se faire en trois temps.
- Tout d’abord, on boucle jusqu’à recevoir un caractère sur la voie série
- Lorsqu’on a reçu un caractère, on regarde si c’est une lettre
- Si c’est une lettre, on renvoie son acolyte majuscule ; sinon on renvoie simplement le caractère lu
Voici le programme décrivant ce comportement :
Je vais maintenant vous expliquer les parties importantes de ce code. Comme vu dans le cours, la ligne 4 va nous servir à attendre un caractère sur la voie série. Tant qu’on ne reçoit rien, on ne fait rien ! Sitôt que l’on reçoit un caractère, on va chercher à savoir si c’est une lettre. Pour cela, on va faire deux tests. L’un est à la ligne 8 et l’autre à la ligne 13. Ils se présentent de la même façon : SI le caractère lu à une valeur supérieure ou égale à la lettre 'a'
(ou 'A'
) ET inférieure ou égale à la lettre 'z'
('Z'
), alors on est en présence d’une lettre. Sinon, c’est autre chose, donc on se contente de passer au renvoi du caractère lu ligne 21. Une fois que l’on a détecté une lettre, on effectue quelques transformations afin de changer sa casse. Voici les explications à travers un exemple :
Description | Opération (lettre) | Opération (nombre) | Valeur de carlu |
---|---|---|---|
On récupère la lettre 'e' |
e | 101 | 'e' |
On isole son numéro de lettre en lui enlevant la valeur de 'a' | e-a | 101–97 | 4 |
On ajoute ce nombre à la lettre 'A' |
A + (e-a) | 65 + (101–97) = 69 | 'E' |
Il ne suffit plus qu’à retourner cette lettre | E | 69 | E |
On effectuera sensiblement les mêmes opérations lors du passage de majuscule à minuscule.
A la ligne 22, j’utilise la fonction write()
qui envoie le caractère en tant que variable de type byte, signifiant que l’on renvoie l’information sous la forme d’un seul octet. Sinon Arduino enverrait le caractère en tant que 'int’, ce qui donnerait des problèmes lors de l’affichage.
Voici l’exercice sous forme de simulateur interactif :
!(https://www.tinkercad.com/embed/flg9YQX2g1f)
Vous savez maintenant lire et écrire sur la voie série de l’Arduino ! Grâce à cette nouvelle corde à votre arc, vous allez pouvoir ajouter une touche d’interactivité supplémentaire à vos programmes.