La gestion des ports séries sous Linux

Article pour l'Écho de Linux (Juillet 1996)

Eric Sellin (esellin@pratique.fr)


Sommaire


Introduction

L'ambition de cet article est bien modeste : il veut vous expliquer comment on accède aux ports séries sous Linux, c'est-à-dire comment y lire des caractères et en envoyer, comment modifier les paramètres de la liaison, etc...

Nous entrons tout de suite dans le vif du sujet avec quelques rappels élémentaires.

Bref rappel sur les liaisons séries

Sur une liaison série, les bits qui composent un caractère sont envoyés les uns aprés les autres, une tension étant associée aux 0 et aux 1. Les principaux paramètres d'une telle liaison sont les suivants :

Le débit indique combien de bits circulent chaque seconde sur la liaison. Cela peut aller de 50 à 460800 (!) bits/s.

La parité, lorsqu'elle est utilisée, est un bit supplémentaire qui sert de contrôle de transmission, contrôle qui est loin d'être parfait puisqu'il n'est efficace que si le nombre de bits érronés dans un caractère est impair.

Chaque caractère peut être composé de 5 à 8 bits. Dans la grande majorité des cas, on utilise 8 bits.

Le ou les bits de stop servent à marquer la fin de chaque caractère dans la transmission. En général, on n'utilise qu'un seul bit de stop.

Ouverture de la liaison

Sous Linux, chaque port série de votre machine est représenté par un fichier de périphérique situé dans le répertoire /dev à côté de dizaines d'autres. Au niveau d'un programme en C, on ouvre ces fichiers exactement comme on ouvrirait n'importe quel autre fichier grâce à l'appel système open() :
	int fd;
	if ( (fd=open("/dev/ttyS1",O_RDWR)) == -1 ) {
		perror("open");
		exit(-1);
	}
Une fois que le port est ouvert, on peut y lire et y écrire des caractères au moyen des primitives read() et write().

Mais ne nous emballons pas, il faut d'abord paramétrer notre liaison.

Paramétrage de la liaison

Dans la norme POSIX, tous les paramètres d'une liaison sont regroupés dans une structure appelée termios et définie dans le fichier <termios.h> qu'il nous faut donc inclure.

Cette structure comporte les champs suivants

	struct termios {
		tcflag_t c_iflag;		/* input mode flags */
		tcflag_t c_oflag;		/* output mode flags */
		tcflag_t c_cflag;		/* control mode flags */
		tcflag_t c_lflag;		/* local mode flags */
		cc_t c_line;			/* line discipline */
		cc_t c_cc[NCCS];		/* control characters */
	};

La fonction tcgetattr permet d'obtenir les paramètres actuels d'une liaison. En voici le prototype, extrait du man :

	int tcgetattr ( int fd, struct termios *termios_p );
et un exemple typique d'utilisation :
	struct termios termios_p;
	tcgetattr(fd,&termios_p);
Notre structure termios_p étant renseignée, on peut en modifier les champs, dont une description sera donnée au paragraphe suivant.

Une fois que ces champs ont été modifiés, il faut enregistrer ces modifications au moyen de la fonction tcsetattr :

	tcsetattr(fd,TCSANOW,&termios_p);

Les champs de la structure termios

Nous n'allons pas détailler ici l'ensemble des champs de cette structure car ils sont trop nombreux. Seuls les champs utiles seront abordés.

Mode bloquant et mode non-bloquant

Que se passe-t-il lorsqu'on appelle read(), et qu'il n'y a malheureusement rien à lire ?

Deux possibilités s'offrent à nous :

C'est à vous de choisir le mode de fonctionnement. Vous pouvez faire ce choix à l'ouverture du port série, mais également à n'importe quel autre moment.

À l'ouverture par open(), utilisez l'indicateur O_NONBLOCK :

	int fd;
	if ( (fd=open("/dev/ttyS1",O_RDWR|O_NONBLOCK)) == -1 ) {
		perror("open");
		exit(-1);
	}

Pour modifier le fonctionnement alors que le port série est déjà ouvert, il faut utiliser l'appel système fcntl() de la façon suivante :

	/* Passe en mode bloquant */
        fcntl(fd,F_SETFL,fcntl(fd,F_GETFL)&~O_NONBLOCK);

	/* Repasse en mode non-bloquant */
        fcntl(fd,F_SETFL,fcntl(fd,F_GETFL)|O_NONBLOCK);

Exploitation

Je vous rappelle qu'on peut lire et écrire avec les appels systèmes read() et write().

Voici quelques informations pour mieux exploiter votre liaison :

Programme d'exemple

Voilà, on est loin d'avoir tout dit sur le sujet et vous pouvez bien sûr consulter man termios si vous voulez plus d'informations. Mais ça nous suffit largement pour aborder tout de suite un petit programme d'exemple :

#include <stdio.h>
#include <termios.h>
#include <sys/fcntl.h>

void main(void)
{
	int		fd;
	char		c;
	struct termios	termios_p;

	/* Ouverture de la liaison serie */
	if ( (fd=open("/dev/ttyS1",O_RDWR)) == -1 ) {
		perror("open");
		exit(-1);
	}
	
	/* Lecture des parametres courants */
	tcgetattr(fd,&termios_p);
	/* On ignore les BREAK et les caracteres avec erreurs de parite */
	termios_p.c_iflag = IGNBRK | IGNPAR;
	/* Pas de mode de sortie particulier */
	termios_p.c_oflag = 0;
	/* Liaison a 9600 bps avec 7 bits de donnees et une parite paire */
	termios_p.c_cflag = B9600 | CS7 | PARENB;
	/* Mode non-canonique avec echo */
	termios_p.c_lflag = ECHO;
	/* Caracteres immediatement disponibles */
	termios_p.c_cc[VMIN] = 1;
	termios_p.c_cc[VTIME] = 0;
	/* Sauvegarde des nouveaux parametres */
	tcsetattr(fd,TCSANOW,&termios_p);
	
	/* Affichage sur le terminal */
	write(fd,"Tapez Ctrl-C pour quitter\n",26);

	/* Boucle de lecture */
	while ( 1 ) {
		read(fd,&c,1);
		if ( c == 0x03 )		/* Ctrl-C */
			break;
		printf("%03u %02x %c\n",c&0xff,c&0xff,c);
	}	                                                               
	
	/* Fermeture */
	close(fd);

	/* Bye... */
	exit(0);
	
}

Bibliographie

La programmation sous UNIX de Jean-Marie RIFFLET aux éditions Ediscience, excellent ouvrage que je vous recommande chaudement ainsi d'ailleurs que La communication sous UNIX du même auteur.