Maintenant que vous savez comment utiliser la voie série avec Arduino, il peut être bon de savoir comment visualiser les données envoyées avec vos propres programmes (l’émulateur terminal Windows ou le moniteur série Arduino ne comptent pas ). Cette annexe a donc pour but de vous montrer comment utiliser la voie série avec quelques langages de programmation. Les langages utilisés ci-dessous ont été choisis arbitrairement en fonction de mes connaissances, car je ne connais pas tous les langages possibles et une fois vu quelques exemples, il ne devrait pas être trop dur de l’utiliser avec un autre langage. Nous allons donc travailler avec :
Afin de se concentrer sur la partie "Informatique", nous allons reprendre un programme travaillé précédemment dans le cours. Ce sera celui de l’exercice : Attention à la casse. Pensez donc à le charger dans votre carte Arduino avant de faire les tests.
En C++ avec Qt
Avant de commencer cette sous-partie, il est indispensable de connaître la programmation en C++ et savoir utiliser le framework Qt. Si vous ne connaissez pas tout cela, vous pouvez toujours aller vous renseigner avec un tutoriel C++ !
Le C++, OK, mais pourquoi Qt ?
J’ai choisi de vous faire travailler avec Qt pour plusieurs raisons d’ordres pratiques.
- Qt est multiplateforme, donc les réfractaires à Linux (ou à Windows) pourront quand même travailler.
- Dans le même ordre d’idée, nous allons utiliser une librairie tierce pour nous occuper de la voie série. Ainsi, aucun problème pour interfacer notre matériel que l’on soit sur un système ou un autre !
- Enfin, j’aime beaucoup Qt et donc je vais vous en faire profiter
En fait, sachez que chaque système d’exploitation à sa manière de communiquer avec les périphériques matériels. L’utilisation d’une librairie tierce nous permet donc de faire abstraction de tout cela. Sinon il m’aurait fallu faire un tutoriel par OS, ce qui, on l’imagine facilement, serait une perte de temps (écrire trois fois environ les mêmes choses) et vraiment galère à maintenir.
Installer QextSerialPort
QextSerialPort est une librairie tierce réalisée par un membre de la communauté Qt. Pour utiliser cette librairie, il faut soit la compiler, soit utiliser les sources directement dans votre projet.
1ère étape : télécharger les sources
Le début de tout cela commence donc par récupérer les sources de la librairie. Pour cela, rendez-vous sur la page google code du projet. A partir d’ici vous avez plusieurs choix. Soit vous récupérez les sources en utilisant le gestionnaire de source mercurial (Hg). Il suffit de faire un clone du dépôt avec la commande suivante :
hg clone https:// code.google.com/p/qextserialport/
Sinon, vous pouvez récupérer les fichiers un par un (une dizaine). C’est plus contraignant mais ça marche aussi si vous n’avez jamais utilisé de gestionnaire de sources (mais c’est vraiment plus contraignant !)
Cette dernière méthode est vraiment déconseillée. En effet, vous vous retrouverez avec le strict minimum (fichiers sources sans exemples ou docs).
La manipulation est la même sous Windows ou Linux !
Compiler la librairie
Maintenant que nous avons tous nos fichiers, nous allons pouvoir compiler la librairie. Pour cela, nous allons laisser Qt travailler à notre place.
- Démarrez QtCreator et ouvrez le fichier .pro de QextSerialPort
- Compilez…
- C’est fini !
Normalement vous avez un nouveau dossier à côté de celui des sources qui contient des exemples, ainsi que les librairies QExtSerialPort.
Installer la librairie : Sous Linux
Une fois que vous avez compilé votre nouvelle librairie, vous allez devoir placer les fichiers aux bons endroits pour les utiliser. Les librairies, qui sont apparues dans le dossier "build" qui vient d’être créé, vont être déplacées vers le dossier /usr/lib. Les fichiers sources qui étaient avec le fichier ".pro" pour la compilation sont à copier dans un sous-dossier "QextSerialPort" dans le répertoire de travail de votre projet courant.
A priori il y aurait un bug avec la compilation en mode release (la librairie générée ne fonctionnerait pas correctement). Je vous invite donc à compiler aussi la debug et travailler avec.
Installer la librairie : Sous Windows
Ce point est en cours de rédaction, merci de patienter avant sa mise en ligne.
Infos à rajouter dans le .pro
Dans votre nouveau projet Qt pour traiter avec la voie série, vous aller rajouter les lignes suivantes à votre .pro :
La ligne "INCLUDEPATH" représente le dossier où vous avez mis les fichiers sources de QextSerialPort. Les deux autres lignes font le lien vers les librairies copiées plus tôt (les .so ou les .dll selon votre OS).
Les trucs utiles
L’interface utilisée
Comme expliqué dans l’introduction, nous allons toujours travailler sur le même exercice et juste changer le langage étudié. Voici donc l’interface sur laquelle nous allons travailler, et quels sont les noms et les types d’objets instanciés :
Cette interface possède deux parties importantes : La gestion de la connexion (en haut) et l’échange de résultat (milieu -> émission, bas -> réception). Dans la partie supérieure, nous allons choisir le port de l’ordinateur sur lequel communiquer ainsi que la vitesse de cette communication. Ensuite, deux boîtes de texte sont présentes. L’une pour écrire du texte à émettre, et l’autre affichant le texte reçu. Voici les noms que j’utiliserai dans mon code :
Widget | Nom | Rôle |
---|---|---|
QComboBox | comboPort | Permet de choisir le port série |
QComboBox | comboVitesse | Permet de choisir la vitesse de communication |
QButton | btnconnexion | (Dé)Connecte la voie série (bouton "checkable") |
QTextEdit | boxEmission | Nous écrirons ici le texte à envoyer |
QTextEdit | boxReception | Ici apparaitra le texte à recevoir |
Lister les liaisons séries
Avant de créer et d’utiliser l’objet pour gérer la voie série, nous allons en voir quelques-uns pouvant être utiles. Tout d’abord, nous allons apprendre à obtenir la liste des ports série présents sur notre machine. Pour cela, un objet a été créé spécialement, il s’agit de QextPortInfo
. Voici un exemple de code leur permettant de fonctionner ensemble :
Une fois que nous avons récupéré une énumération de tous les ports, nous allons pouvoir les ajouter au combobox qui est censé les afficher (comboPort). Pour cela on va parcourir la liste construite précédemment et ajouter à chaque fois une item dans le menu déroulant :
Les ports sont nommés différemment sous Windows et Linux, ne soyez donc pas surpris avec mes captures d’écrans, elles viennent toutes de Linux.
Une fois que la liste des ports est faite (attention, certains ports ne sont connectés à rien), on va construire la liste des vitesses, pour se laisser le choix le jour où l’on voudra faire une application à une vitesse différente. Cette opération n’est pas très compliquée puisqu’elle consiste simplement à ajouter des items dans la liste déroulante "comboVitesse".
Votre interface est maintenant prête. En la démarrant maintenant vous devriez être en mesure de voir s’afficher les noms des ports séries existant sur l’ordinateur ainsi que les vitesses. Un clic sur le bouton ne fera évidemment rien puisque son comportement n’est pas encore implémenté.
Gérer une connexion
Lorsque tous les détails concernant l’interface sont terminés, nous pouvons passer au cœur de l’application : la communication série. La première étape pour pouvoir faire une communication est de se connecter (tout comme vous vous connectez sur une borne WiFi avant de communiquer et d’échanger des données avec cette dernière). C’est le rôle de notre bouton de connexion. A partir du système de slot automatique, nous allons créer une fonction qui va recevoir le clic de l’utilisateur. Cette fonction instanciera un objet QextSerialPort pour créer la communication, règlera cet objet et enfin ouvrira le canal. Dans le cas où le bouton était déjà coché (puisqu’il sera "checkable" rappelons-le) nous ferons la déconnexion, puis la destruction de l’objet QextSerialPort créé auparavant. Pour commencer nous allons donc déclarer les objets et méthodes utiles dans le .h de la classe avec laquelle nous travaillons :
private:
// l'objet représentant le port
QextSerialPort * port;
// une fonction utile que j'expliquerais après
BaudRateType getBaudRateFromString(QString baudRate);
private slots:
// le slot automatique du bouton de connexion
void on_btnconnexion_clicked();
Ensuite, il nous faudra instancier le slot du bouton afin de traduire un comportement. Pour rappel, il devra :
- Créer l’objet "port" de type QextSerialPort
- Le régler avec les bons paramètres
- Ouvrir la voie série
Dans le cas où la voie série est déjà ouverte (le bouton est déjà appuyé) on devra la fermer et détruire l’objet. Voici le code commenté permettant l’ouverture de la voie série (quelques précisions viennent ensuite) :
Ce code n’est pas très compliqué à comprendre. Cependant quelques points méritent votre attention. Pour commencer, pour régler la vitesse du port série on fait appel à la fonction "setBaudRate". Cette fonction prend un paramètre de type BaudRateType qui fait partie d’une énumération de QextSerialPort. Afin de faire le lien entre le comboBox qui possède des chaines et le type particulier attendu, on crée et utilise la fonction "getBaudRateFromString". A partir d’un simple BaudRateType
.
Un autre point important à regarder est l’utilisation de la fonction open() de l’objet QextSerialPort. En effet, il existe plusieurs façons d’ouvrir un port série :
- En lecture seule QextSerialPort::ReadOnly
- En écriture seule QextSerialPort::WriteOnly
- En lecture/écriture QextSerialPort::ReadWrite
Ensuite, on connecte simplement les signaux émis par la voie série et par la boite de texte servant à l’émission (que l’on verra juste après). Enfin, lorsque l’utilisateur re-clic sur le bouton, on passe dans le NULL
.
Ce code présente le principe et n’est pas parfait ! Il faudrait par exemple s’assurer que le port est bien ouvert avant d’envoyer des données (faire un test if(port->isOpen())
par exemple).
Émettre et recevoir des données
Maintenant que la connexion est établie, nous allons pouvoir envoyer et recevoir des données. Ce sera le rôle de deux slots qui ont été brièvement évoqués dans la fonction connect()
du code de connexion précédent.
Émettre des données
L’émission des données se fera dans le slot "sendData". Ce slot sera appelé à chaque fois qu’il y aura une modification du contenu de la boîte de texte "boxEmission". Pour l’application concernée (l’envoi d’un seul caractère), il nous suffit de chercher le dernier caractère tapé. On récupère donc le dernier caractère du texte contenu dans la boite avant de l’envoyer sur la voie série. L’envoi de texte se fait à partir de la fonction toAscii()
et on peut donc les utiliser directement. Voici le code qui illustre toutes ces explications (ne pas oublier de mettre les déclarations des slots dans le .h) :
Recevoir des données
Le programme étudié est censé nous répondre en renvoyant le caractère émis mais dans une casse opposée (majuscule contre minuscule et vice versa). En temps normal, deux politiques différentes s’appliquent pour savoir si des données sont arrivées. La première est d’aller voir de manière régulière (ou pas) si des caractères sont présents dans le tampon de réception de la voie série. Cette méthode dite de *Polling n’est pas très fréquemment utilisée. La seconde est de déclencher un évènement lorsque des données arrivent sur la voie série. C’est la forme qui est utilisée par défaut par l’objet readyRead()
) est émis par l’objet et peut donc être connecté à un slot. Pour changer le mode de fonctionnement, il faut utiliser la méthode QextSerialPort::EventDriven
pour la seconde (par défaut). Comme la connexion entre le signal et le slot est créée dans la fonction de connexion, il ne nous reste qu’à écrire le comportement du slot de réception lorsqu’une donnée arrive. Le travail est simple et se résume en deux étapes :
- Lire le caractère reçu grâce à la fonction
QextSerialPort
- Le copier dans la boite de texte "réception"
Et voilà, vous êtes maintenant capable de travailler avec la voie série dans vos programmes Qt en C++. Au risque de me répéter, je suis conscient qu’il y a des lacunes en terme de "sécurité" et d’efficacité. Ce code a pour but de vous montrer les bases de la classe pour que vous puissiez continuer ensuite votre apprentissage. En effet, la programmation C++/Qt n’est pas le sujet de ce tutoriel. Nous vous serons donc reconnaissants de ne pas nous harceler de commentaires relatifs au tuto pour nous dire "bwaaaa c’est mal codéééééé". Merci !
En C# (.Net)
Dans cette partie (comme dans les précédentes) je pars du principe que vous connaissez le langage et avez déjà dessiné des interfaces et créé des actions sur des boutons par exemple. Cette sous-partie n’est pas là pour vous apprendre le C# !
Là encore je vais reprendre la même structure que les précédentes sous-parties.
Les trucs utiles
L’interface et les imports
Voici tout de suite l’interface utilisée ! Je vous donnerai juste après le nom que j’utilise pour chacun des composants (et tant qu’à faire je vous donnerai aussi leurs types).
Comme cette interface est la même pour tout ce chapitre, nous retrouvons comme d’habitude le bandeau pour gérer la connexion ainsi que les deux boîtes de texte pour l’émission et la réception des données. Voici les types d’objets et leurs noms pour le bandeau de connexion :
Composant | Nom | Rôle |
---|---|---|
System.Windows.Forms.ComboBox | comboPort | Permet de choisir le port série |
System.Windows.Forms.ComboBox | comboVitesse | Permet de choisir la vitesse de communication |
System.Windows.Forms.Button | btnConnexion | (Dé)Connecte la voie série (bouton "checkable") |
System.Windows.Forms.TextBox | boxEmission | Nous écrirons ici le texte à envoyer |
System.Windows.Forms.TextBox | boxReception | Ici apparaitra le texte à recevoir |
Avant de commencer les choses marrantes, nous allons d’abord devoir ajouter une librairie : celle des liaisons séries. Elle se nomme simplement using System.IO.Ports;
. Nous allons en profiter pour rajouter une variable membre de la classe de type SerialPort que j’appellerai "port". Cette variable représentera, vous l’avez deviné, notre port série !
SerialPort port
Maintenant que tous les outils sont prêts, nous pouvons commencer !
Lister les liaisons séries
La première étape sera de lister l’ensemble des liaisons séries sur l’ordinateur. Pour cela nous allons nous servir d’une fonction statique de la classe String
. Chaque case du tableau sera une chaîne de caractère comportant le nom d’une voie série. Une fois que nous avons ce tableau, nous allons l’ajouter sur l’interface, dans la liste déroulante prévue à cet effet pour pouvoir laisser le choix à l’utilisateur au démarrage de l’application. Dans le même élan, on va peupler la liste déroulante des vitesses avec quelques-unes des vitesses les plus courantes. Voici le code de cet ensemble. Personnellement je l’ai ajouté dans la méthode InitializeComponent()
qui charge les composants.
Si vous lancez votre programme maintenant avec la carte Arduino connectée, vous devriez avoir le choix des vitesses mais aussi d’au moins un port série. Si ce n’est pas le cas, il faut trouver pourquoi avant de passer à la suite (Vérifiez que la carte est bien connectée par exemple).
Gérer une connexion
Une fois que la carte est reconnue et que l’on voit bien son port dans la liste déroulante, nous allons pouvoir ouvrir le port pour établir le canal de communication entre Arduino et l’ordinateur. Comme vous vous en doutez surement, la fonction que nous allons écrire est celle du clic sur le bouton. Lorsque nous cliquons sur le bouton de connexion, deux actions peuvent être effectuées selon l’état précédent. Soit nous nous connectons, soit nous nous déconnectons. Les deux cas seront gérés en regardant le texte contenu dans le bouton ("Connecter" ou "Deconnecter"). Dans le cas de la déconnexion, il suffit de fermer le port à l’aide de la méthode close()
. Dans le cas de la connexion, plusieurs choses sont à faire. Dans l’ordre, nous allons commencer par instancier un nouvel objet de type BaudRate
et ainsi de suite. Voici le code commenté pour faire tout cela. Il y a cependant un dernier point évoqué rapidement juste après et sur lequel nous reviendrons plus tard.
Le point qui peut paraître étrange est la ligne 16, avec la propriété Handler()
qui devra être appelée lorsque des données arriveront. Je vais vous demander d’être patient, nous en reparlerons plus tard lorsque nous verrons la réception de données. A ce stade du développement, lorsque vous lancez votre application vous devriez pouvoir sélectionner une voie série, une vitesse, et cliquer sur "Connecter" et "Déconnecter" sans aucun bug.
Émettre et recevoir des données
La voie série est prête à être utilisée ! La connexion est bonne, il ne nous reste plus qu’à envoyer les données et espérer avoir quelque chose en retour.
Envoyer des données
Pour envoyer des données, une fonction toute prête existe pour les objets char
qui serait envoyé un par un. Dans notre cas d’utilisation, c’est ce deuxième cas qui nous intéresse. Nous allons donc implémenter la méthode TextChanged
du composant "boxEmission" afin de détecter chaque caractère entré par l’utilisateur. Ainsi, nous enverrons chaque nouveau caractère sur la voie série, un par un. Le code suivant, commenté, vous montre la voie à suivre.
Recevoir des données
La dernière étape pour pouvoir gérer de manière complète notre voie série est de pouvoir afficher les caractères reçus. Cette étape est un petit peu plus compliquée. Tout d’abord, revenons à l’explication commencée un peu plus tôt. Lorsque nous démarrons la connexion et créons l’objet boxReception
. Dans l’idéal nous aimerions faire de la façon suivante :
Cependant, les choses ne sont pas aussi simples cette fois-ci. En effet, pour des raisons de sécurité sur les processus, C# interdit que le texte d’un composant (boxReception
) soit modifié de manière asynchrone, quand les données arrivent. Pour contourner cela, nous devons créer une méthode "déléguée" à qui on passera notre texte à afficher et qui se chargera d’afficher le texte quand l’interface sera prête. Pour créer cette déléguée, nous allons commencer par rajouter une méthode dite de callback pour gérer la mise à jour du texte. La ligne suivante est donc à ajouter dans la classe, comme membre :
// une déléguée pour pouvoir mettre à jour le texte de la boite de réception
// de manière "thread-safe"
delegate void SetTextCallback(string text);
Le code de la réception devient alors le suivant :
Je suis désolé si mes informations sont confuses. Je ne suis malheureusement pas un maitre dans l’art des threads UI de C#. Cependant, un tas de documentation mieux expliqué existe sur internet si vous voulez plus de détails.
Une fois tout cela instancié, vous devriez avoir un terminal voie série tout beau fait par vous même ! Libre à vous maintenant toutes les cartes en main pour créer des applications qui communiqueront avec votre Arduino et feront des échanges d’informations avec.
En Python
Comme ce langage à l’air d’être en vogue, je me suis un peu penché dessus pour vous fournir une approche de comment utiliser python pour se servir de la voie série. Mon niveau en python étant équivalent à "grand débutant", je vais vous proposer un code simple reprenant les fonctions utiles à savoir le tout sans interface graphique. Nul doute que les pythonistes chevronnés sauront creuser plus loin
Comme pour les exemples dans les autres langages, on utilisera l’exercice "Attention à la casse" dans l’Arduino pour tester notre programme.
Pour communiquer avec la voie série, nous allons utiliser une librairie externe qui s’appelle pySerial.
Installation
Ubuntu
Pour installer pySerial sur votre une machine Ubuntu c’est très simple, il suffit de lancer une seule commande :
sudo apt-get install python3-serial
Vous pouvez aussi l’installer à partir des sources à l’adresse suivante : https:// pypi.python.org/pypi/pyserial . Ensuite, décompresser l’archive et exécuter la commande : (pour python 2)
python setup.py install
(pour python 3)
python3 setup.py install
Windows
Si vous utilisez Windows, il vous faudra un logiciel capable de décompresser les archives de types tar.gz (comme 7-zip par exemple). Ensuite vous devrez récupérer les sources à la même adresse que pour Linux : https:// pypi.python.org/pypi/pyserial . Enfin, comme pour Linux encore il vous suffira d’exécuter la commande qui va bien :
python setup.py install
Utiliser la librairie
Pour utiliser la librairie, il vous faudra tout d’abord l’importer. Pour cela, on utilise la commande import :
import serial
mais comme seule une partie du module nous intéresse vraiment (Serial) on peut restreindre :
from serial import Serial
(notez l’importance des majuscules/minuscules)
Ouvrir un port série
Maintenant que le module est bien chargé, nous allons pouvoir commencer à l’utiliser. La première chose importante à faire est de connaître le port série à utiliser. On peut obtenir une liste de ces derniers grâce à la commande :
python -m serial.tools.list_ports
Si comme chez moi cela ne fonctionne pas, vous pouvez utiliser d’autres méthodes.
- Sous Windows : en allant dans le gestionnaire de périphériques pour trouver le port série concerné (COMx)
- Sous Linux : en utilisant la commande
ls /dev
, vous pourrez trouver le nom du port série sous le nom "ttyACMx" par exemple
Lorsque le port USB est identifié, on peut créer un objet de type Serial. Le constructeur que l’on va utiliser prend deux paramètres, le nom du port série et la vitesse à utiliser (les autres paramètres (parité…) conviennent par défaut).
port = Serial('/dev/ttyACM0', 9600)
Une fois cet objet créé, la connexion peut-être ouverte avec la fonction open()
port.open()
Pour vérifier que la voie série est bien ouverte, on utilisera la méthode "isOpen()" qui retourne un booléen vrai si la connexion est établie.
Envoyer des données
Maintenant que la voie série est ouverte, nous allons pouvoir lui envoyer des données à traiter. Pour le bien de l’exercice, il nous faut récupérer un (des) caractère(s) à envoyer et retourner avec la casse inversée. Nous allons donc commencer par récupérer une chaîne de caractère de l’utilisateur :
chaine = input("Que voulez vous transformer ? ")
Puis nous allons simplement l’envoyer avec la fonction "write". Cette fonction prend en paramètre un tableau de bytes. Nous allons donc transformer notre chaîne pour convenir à ce format avec la fonction python "bytes()" qui prend en paramètres la chaine de caractères et le format d’encodage.
bytes(chaine, 'UTF-8')
Ce tableau peut directement être envoyé dans la fonction write() :
port.write(bytes(chaine, 'UTF-8'))
Recevoir des données
La suite logique des choses voudrait que l’on réussisse à recevoir des données. C’est ce que nous allons voir maintenant. Nous allons tout d’abord vérifier que des données sont arrivées sur la voie série via la méthode inWaiting(). Cette dernière nous renvoie le nombre de caractères dans le buffer de réception de la voie série. S’il est différent de 0, cela signifie qu’il y a des données à lire. S’il y a des caractères, nous allons utiliser la fonction "read()" pour les récupérer. Cette fonction retourne les caractères (byte) un par un dans l’ordre où il sont arrivés. Un exemple de récupération de caractère pourrait-être :
Vous en savez maintenant presque autant que moi sur la voie série en python ! Je suis conscient que c’est assez maigre comparé aux autres langages, mais je ne vais pas non plus apprendre tout les langages du monde
Code exemple complet et commenté
Cette annexe vous aura permis de comprendre un peu comment utiliser la voie série en général avec un ordinateur. Avec vos connaissances vous êtes dorénavant capable de créer des interfaces graphiques pour communiquer avec votre arduino. De grandes possibilités s’offrent à vous, et de plus grandes vous attendent avec les parties qui suivent…