Port série en C sous Linux
jeudi 14 juin 2007, par Pierre-Luc Bacon
Sommaire
| Ouverture et fermeture du port |
| Permissions |
| L'interface POSIX |
| Envoi des données |
| Lecture |
| Référence |
Le port parallèle peut semble plus simple à utiliser à bien des égards quand vient le temps déterminer l’interface de communication avec un robot. Toutefois, celui-ci est appelé à disparaître sur le nouveau matériel et est bien souvent absent des cartes mères embarquées (single board computer). Certes, il en est de même pour le port série, mais les adapteurs usb/série sont très faciles à trouver. Notons ici que FTDI Chip produit le FT245 qui permet d’obtenir une liaison parallèle virtuelle au travers d’un port USB.
Ouverture et fermeture du port
Les fonctions qui permettent de réaliser ces opérations sont les mêmes que celles utilisées courament dans les systèmes UNIX : open() et close() serviront donc, vous l’aurez deviné.
Vous aurez ici remarqué que /dev/ttyUSB0 est passé à la fonction open() plutôt que /dev/ttyS0. Ceci n’est applicable que si la notation retenue dans udev pour un adapteur usb/série est sous cette forme. Voyez dmesg après branchement pour plus de détails. Même si la liaison RS232 s’effectue par USB, l’interface de programmation ne change pas : le driver se charge de rendre opaque les détails d’implémentation USB.
O_NOCTTY est une option permettant de spécifier que le programme ne sera pas un "controlling terminal" : un paramètre passé presque à tout coup pour la plupart des programmes.
O_RDWR signifie que le fichier est ouvert en lecture et en écriture.
O_NDELAY spécifie finalement de ne pas attendre un changement de ligne sur le signal DCD. Si cette option n’est pas passée, le programme sera bloqué jusqu’à ce que DCD passe à l’état bas.
La fonction fcntl est quant à elle appelée pour permettre de rétablir le port série en mode bloquant pour la fonction read(). Ainsi, si aucun charactère n’est encore disponible en lecture, le programme attendra en ce point du programme.
La fermeture du port se fait avec la fonction close(), tout simplement :
Permissions
Comme le port série est représenté comme un fichier en /dev, il sera nécessaire d’exécuter le programme avec les droits de super-utilisateur root.
Sinon, il est possible de changer les droits d’accès sur le fichier ou d’en changer le groupe. Voici quelques exemples sous Linux où le port série apparaîtra comme /dev/ttSx (/dev/cuaax sous FreeBSD).
Définir l’accès pour un nouveau groupe
Cette solution est à préférer.
Donner accès à tous les utilisateurs possibles
À éviter si possible, mais tolérable en phase de test.
Donner l’accès à soit-même seulement
Prenez note que si votre système est configuré avec udev, ces commandes n’auront pas un effet permanent après redémarrage. Il sera alors nécessaire d’adapter un fichier de configuration généralement situé dans le répertoire /etc/udev/rules.d/
L'interface POSIX
<termios.h> fournit les structures de contrôle ainsi que les fonctions POSIX qui permettront de définir les propriétés des laisons sérielles qui seront mises en place.
Vitesse de connection
Dans ce bout de code, une structure termios est déclarée et initialisée par la fonction tcgetattr. Cette structure comprend plusieurs membres qui seront assignés à des valeurs définies par des #define en utilisant des opérateurs bit à bit.
Dans cet exemple, cfsetospeed est utilisé car cette fonction permet de définir la vitesse (le "baud rate") dans le membre approprié de la structure termios. En effet, certains systèmes POSIX ne définissent pas cette valeur des le même membre (c_flag, c_ispeed, ou c_ospeed), d’où le besoin d’une fonction indépendante du système.
Les options CLOCAL et CREAD sont définies pour éviter que le programme soit considéré en tant que propriétaire du port. Précisons au passage que l’accès aux membres de la structure devra toujours s’effectuer avec les opérateurs bit à bit tel que montré ci-haut. Un OU binaire sert ici à combiner les nouvelles options à celles déjà existantes dans le membre sans les écraser.
Bits de données (taille du charactère)
Cette première ligne permet de mettre toute valeur pour cette option à 0. CSIZE n’est donc ici qu’un masque "nié" par "~" et affecté à c_cflag par l’opérateur binaire "&" (ET). CS8 (8 bits) peut ensuite être attribué au membre par un OU binaire qui renverra nécessairement CS8 puisque les bits du masque sont maintenant tous à 0. Rappelons-nous que le fonctionnement du OU peut se résumer à : "l’un, ou l’autre, ou les deux en même temps". Dans ce cas, "l’un" suffit à faire égaler le résultat à la valeur exacte de CS8.
Notez que CS5, CS6 et CS7 sont aussi disponibles.
Parité
Si on ne choisit pas de méthode de vérification par parité pour les données envoyées, c_flag devra être défini ainsi :
Avec CSIZE et CSTOPB, on reconnaît ici la configuration 8N1 : 8 bits de données (8), pas de parité (N) et 1 bit d’arrêt (1),
Application des attributs
Après avoir défini la structure termios avec les options voulues, elles doivent être appliquées par la fonction tcsetattr.
TCSANOW indique que les changements doivent être appliqués immédiatement sans attendre la fin des données en entrée ou en sortie.
Envoi des données
Cette opération s’effectuera avec le simple appel de la fonction write.
Le troisième argument de la fonction indique le nombre d’octets à écrire. Lorsque les données ont pu être envoyées, le nombre d’octets écrits est retourné.
Lecture
Mode non bloquant
Comme spécifié plus haut, la fonction read() qui permet de lire les données sur le port série peut être configurée en mode bloquant ou non. En effet par un appel à fcntl() avec l’option FNDELAY, read() retournera immédiatement avec la valeur 0 si aucune donnée n’est actuellement disponible.
La syntaxe de la fonction read est quant à elle identique à write :
Où buffer est un pointeur sur le tableau qui contiendra les données et "4" , dans cet exemple, le nombre d’octets à copier dans cet espace.
select
Dans bien des cas, des contraintes de temps de réponse sont exigées dans un programme. En effet, plusieurs tâches doivent souvent être exécutées simultanément. Un appel à la fonction read peut ainsi compliquer quelque peu l’organisation du programme puisque deux choix pourraient s’offrir à priori : attendre les données (mode bloquant), ou faire un pooling sur read. La fonction select peut alors s’avérer utile pour surveiller l’état de plusieurs descripteurs de fichiers en même temps. On pourrait par exemple définir un ensemble socket et port série et à l’arrivée de donnée sur l’un ou l’autre des descripteurs, select renverrait une valeur supérieure à 0. On s’évite ainsi un pooling systématique pour chaque fichier à surveiller. [1]
signal
La programmation d’une routine de communication sur le port série pour un microcontrôleur aurait été facilement réalisée par un mécanisme d’interruptions. Toutefois, les interruptions matérielles à proprement dit ne sont pas accessibles dans l’espace utilisateur du système. Seul un pseudo-mécanisme d’interruptions seraient alors envisageable par la programmation d’un driver qui enverrait un signal à un programme de l’espace d’utilisateur. [2]
Aussi, l’utilisation d’une extension temps réelle au noyau permettrait de réaliser de telles opérations avec des fonctions de son API. Ceci ne sera cependant pas couvert ici. [3]
Enfin, c’est l’option O_ASYNC [4] passée à open qui permettra la mise en place de ce mécanisme. Un signal SIGIO sera alors généré à toute arrivée ou écriture de donnée (lorsque le buffer en sortie aura été rempli) pour un fichier ouvert en mode RW. Ensuite, il suffira de mettre en place une fonction de prise en charge du signal (handler) attaché à ce signal et de bien s’assurer par select que le SIGIO reçu corresponde bien à l’événement souhaité sur le port. L’attribut F_SETOWN [5] devrait aussi être appliqué avec la fonction fcntl pour le descripteur de fichier du port série.
Référence
Le livre Serial Programming Guide for POSIX Operating System de Michael R Sweet a servi de référence pour la rédaction de cet article. Il est disponible librement sur internet ou peut être acheté via lulu.com.
Documents joints
-
serialcom.c (C source - 2.4 ko)Exemple de code pour l’écriture sur le port série. Aucune instruction particulière pour la compilation.
Notes
[1] Un exemple de select : Using the SELECT System Call.
[2] Voir opening bananas with steamrollers.
[3] Voir RTAI Serial Port Communication Example pour plus de détails.


Répondre à l'auteur: