Ports séries sous LINUX

Pierre Ficheux (pficheux@com1.fr)

Septembre 1999


0. Résumé

Le but de cet article est la compréhension du fonctionnement des ports séries sous LINUX. L'article abordera également la configuration des ports séries depuis le shell sh ainsi qu'en langage C.

1. Rappels sur le port série

1.1 Principe du port série

Le port série est une interface hardware permettant de transmettre des informations binaires de manière séquencielle (1 par 1). Par opposition, un port parallèle comme le port Centronics des imprimantes transmet les bits de manière simultanée (8 à la fois).

Le mode de transmission sur un ligne série (appelée aussi RS-232) est décrit dans le schéma ci-dessous:

L'arrivée des données est indiqué par un bit de START. Les données sont transmises sous forme d'une suite de 5 à 8 bits contenant éventuellement un bit de PARITE utilisé pour la détection des erreurs de transmission. La fin de la transmission est indiquée par un bit de STOP.

La fréquence de transmission des bits indiquent la vitesse de la ligne: 9600 bits/s, 19200 bits/s, etc...

Connectique et signaux

Concrètement, un port série est matérialisé par un connecteur contenant 25 points (DB25) ou 9 points (DB9). Ce dernier est le plus répandu sur les calculateurs modernes. Un port série à bas débit n'a besoin que de trois lignes:

1 FGND (Frame Ground) Masse mécanique
2 TD (Transmit Data) Transmission des données
3 RD (Receive Data) Réception des données

Ce type de connexion minimale sera utilisable pour connecter un équipement à basse vitesse, comme un terminal asynchrone (en croisant le TD et le RD).

Malheureusement, ces signaux ne sont pas suffisants pour gérer correctement des périphériques plus évolués comme des MODEMS. On doit alors utiliser les signaux de controle suivant:

20 DTR (Data Terminal Ready) Présence du DTE
6 DSR (Data Set Ready) Présence du DCE
8 DCD (Data Carrier Detect) Connexion DCE établie

Dans la terminologie des lignes série, l'équipement de communication est appelé DCE (Data Communication Equipment) et le calculateur est appelé DTE (Data Terminal Equipment). Les signaux DTR et DCD sont les plus utilisés dans la pratique:

En plus des signaux de contrôle indispensables, on peut également connecter les signaux RTS et CTS (hardware handshake, contrôle de flux hard). Le DTE peut affirmer RTS (Request To Send) pour demander au DCE si il peut envoyer des données. En réponse le DCE, affirme CTS (Clear To Send) pour indiquer qu'il est prêt. Si le DCE n'est pas prêt, il fait tomber CTS, idem pour de DTE avec RTS.

On en déduit donc le cablage utilisé pour une liaison calculateur/MODEM (interconnexion DTE/DCE complète) dans le cas d'une connexion entre 2 prises 25 points (DB 25). La colonne de gauche indique de DTE, celle de droite le DCE. Les flèches indiquent la direction des signaux.

1 FGND <-> FGND 1
2 TD --> TD 2
3 RD <-- RD 3
4 RTS --> RTS 4
5 CTS <-- CTS 5
6 DSR <-- DSR 6
7 GND <-> GND 7
8 DCD <-- DCD 8
20 DTR --> DTR 20

Dans le cas d'une prise 9 points coté DTE (cas des PC en particulier) cela donne :

3 TD --> TD 2
2 RD <-- RD 3
7 RTS --> RTS 4
8 CTS <-- CTS 5
6 DSR <-- DSR 6
5 GND <-> GND 7
1 DCD <-- DCD 8
4 DTR --> DTR 20

2. Détection du hardware RS-232 par LINUX

Il existe de nombreux circuit permettant de gèrer des ports RS-232. Les plus anciens se souviendront avec nostalgie de l'ACIA 6850, périphérique bien connu de l'antique processeur 6800 de chez MOTOROLA. Les PC d'aujourd'hui utilisent des circuits appelés UART comme le 16550A.

Le noyau LINUX contient naturellement des pilotes pour ce type de périphérique. La détection des ports séries par le noyau est indiqué au démarrage de LINUX par les lignes:

Serial driver version 4.13 with no serial options enabled
tty00 at 0x03f8 (irq = 4) is a 16550A
tty01 at 0x02f8 (irq = 3) is a 16550A

Pour cela il faut avoir validé l'option suivante lors de la compilation du noyau:

Standard/generic serial support (CONFIG_SERIAL) [Y/m/n/?] 

Si le système dispose d'une carte série additionnelle, on verra apparaitre à la suite les message:

tty02 at 0x03e8 (irq = 4) is a 16550A

Dans cet exemple on voit que la deuxième carte est bien détectée à une adresse différente (3e8) mais que le système lui a attribué la même interruption (IRQ) que le premier port série. Pour assurer le bon fonctionnement, il sera nécessaire de modifier le niveau d'interruption en accord avec la configuration de la carte. Pour cela, on utilisera la commande setserial appelée par exemple dans le script rc.local:

/bin/setserial /dev/cua2 irq 5
On voit que les ports séries sont utilisables à travers les fichiers spéciaux /dev/ttyS0 (ou /dev/cua0), /dev/ttyS1 (ou /dev/cua1) et ainsi de suite.

Jusqu'au noyau 2.0, les utilisateurs de LINUX avaient l'habitude de distinguer les devices séries sortants (call-out) cuax des devices entrants (call-in) ttySx Les nouvelles distributions de LINUX utilisant le noyau 2.2 indique que les devices des ports séries sont maintenant ttySx (plus de cuax) et que les applications doivent utiliser ce devices. Il existe cependant une différence notoire décrite dans le document Serial-HOWTO:

La seule différence est dans la manière dont le périphérique est ouvert. Le périphérique d'entrée /dev/ttySx est ouvert en mode bloquant, jusqu'à ce que CD soit positionne (ie, quelqu'un se connecte).

Cette différence peut provoquer des disfonctionnements de certaines applications si l'on utilise ttySx à la place de cuax alors que l'application n'a pas été prévue pour cela (en l'occurence des blocages sur l'ouverture du device). Quelques infos complémentaires sont disponibles au paragraphe 4.2.

Pour tester la communication sur un port série, le mieux est d'utiliser un outil d'émulation de terminal comme kermit ou minicom. Ce dernier est livré en standard avec les distributions classiques. Voici un exemple de dialogue avec un modem avec kermit:

root@mmxpf # kermit
C-Kermit 6.0.192, 6 Sep 96, for Linux
 Copyright (C) 1985, 1996,
  Trustees of Columbia University in the City of New York.
Default file-transfer mode is TEXT
Type ? or HELP for help.
[/root] C-Kermit>set line /dev/cua0
[/root] C-Kermit>set speed 115200
/dev/cua0, 115200 bps
[/root] C-Kermit>connect
Connecting to /dev/cua0, speed 115200.
The escape character is Ctrl-\ (ASCII 28, FS)
Type the escape character followed by C to get back,
or followed by ? to see other options.
at
OK

(Back at mmxpf)
[/root] C-Kermit>q
root@mmxpf #

3. Utilisation à travers le shell

3.1 La commande stty

Cette commande permet de visualiser et modifier les paramètres d'un terminal. Cette dénomination est à prendre au sens large et l'utilisation de stty ne se limite pas aux lignes séries. Au niveau LINUX, le terminal est défini comme un canal de communication associé à une discipline de ligne (line discipline) qui détermine le comportement de ce canal.

Au niveau de l'émulateur de terminal xterm, on peut utiliser simplement la commande en faisant:


pierre@mmxpf % stty -a
speed 9600 baud; rows 24; columns 80; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = ;
eol2 = ; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W;
lnext = ^V; flush = ^O; min = 1; time = 0;
-parenb -parodd cs8 -hupcl -cstopb cread -clocal -crtscts
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff
-iuclc -ixany -imaxbel
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt
echoctl echoke

L'option -a permet d'afficher l'ensemble des paramètres du terminal. L'exemple ci-dessous nous indique quelles valeurs sont affectées aux caractères de controle de ce terminal (effacement, interruption, etc...). Il est bien entendu possible de modifier ces caractères de controle en utilisant stty, par exemple:

stty erase "^H"

affecte Ctrl-H à l'effacement de caractère. De même, on peut modifier le paramètrage de la ligne: vitesse, nombre de bits de données, parité, traitement des signaux de controle. Dans notre exemple la ligne n'utilise pas de parité (-parenb -parodd), utilise 8 bits de données (cs8) et ne traite pas de controle de flux hardware (-crtscts) ce qui n'est pas très étonnant pour un terminal attaché à un xterm ! La liste complète des paramètres gèrés par stty est bien entendu disponible en faisant man stty.

Pour revenir au sujet qui nous intéresse aujourd'hui, on peut bien entendu utiliser stty sur un terminal série. Par la magie de la redirection des entrées/sorties sous LINUX, on peut par exemple afficher les paramètres de la ligne utilisée par le modem en faisant:

stty -a < /dev/modem

Le device /dev/modem étant en général un lien symbolique sur un device série réel:

pierre@mmxpf % ls -l /dev/modem
lrwxrwxrwx   1 root     uucp            4 Oct 10  1997 /dev/modem -> cua0

Le paramètrage d'une ligne série par un shell-script utilisant man stty est de la même veine:

#!/bin/sh

(
stty 115200
stty cs8
stty -parenb
stty -parodd
stty -clocal
stty crtscts
stty -echo

# Suite du script
...

) < /dev/cua0 > /dev/cua0

Dans cet exemple, on effectue le paramètrage suivant:

115200 vitesse à 115200 bps
cs8 8 bits de données
-parenb pas de parité paire
-parodd ni impaire d'ailleurs...
-clocal controle de flux non local traitement des signaux modem (DCD, DTR)
crtscts activation du controle de flux hardware
-echo pas d'écho local des caractères

3.2 La commande chat

Le mot chat signifie en anglais bavarder, discuter. La commande chat permet d'exécuter un séquence de type expect/send (j'envoie/j'attend) sur un terminal donné. La commande est en général associée au package du démon pppd. Elle sera donc installée par défaut (ou du moins disponible) sur la plupart des distributions courantes.

Une utilisation pratique de chat pourra être par exemple l'initialisation d'un périphérique connecté à une ligne série. Comme beaucoup de commandes sous LINUX, chat utilise par défaut l'entrée et la sortie standard ce qui permet de tester le chat-script plus facilement.

Prenons l'exemple d'un script destiné à initialiser un modem, puis appeler un numéro de téléphone et attendre la connexion. On pourra facilement mettre au point un tel script directement dans un xterm en tapant la commande:

chat -v "" ATM0\&C1\&D2\&K3 OK ATDT3611 CONNECT
ce qui signifie:

"" on n'attend aucune chaîne au départ
ATM0\&C1\&D2\&K3 on envoit un chaîne d'init...
OK et on attend OK
ATDT3611 on compose le numéro...
CONNECT et on attend CONNECT

L'option -v indique de tracer le dialogue en utilisant le syslog. En général, la trace sortira sur le fichier /var/log/message (suivant la configuration du syslog).

En simulant la réponse du modem à la main (en tapant OK puis CONNECT dans le même terminal) on obtient la trace du dialogue dans le fichier de log:

Sep 26 21:31:01 mmxpf chat[463]: send (ATM0&C1&D2&K3^M) 
Sep 26 21:31:02 mmxpf chat[463]: expect (OK) 
Sep 26 21:31:03 mmxpf chat[463]:  -- got it 
Sep 26 21:31:03 mmxpf chat[463]: send (ATDT3611^M) 
Sep 26 21:31:03 mmxpf chat[463]: expect (CONNECT) 
Sep 26 21:31:05 mmxpf chat[463]:  -- got it 

Lorsque le script est au point, il suffit de rediriger l'entrée et la sortie sur le device du modem:

chat -v "" ATM0\&C1\&D2\&K3 OK ATDT3611 CONNECT < /dev/cua0 > /dev/cua0

Forts de notre expérience de la commande stty on pourra donc combiner l'initialisation de la ligne série avec le chat-script à envoyer au modem:

#!/bin/sh

(
# Init ligne série
stty 115200
stty cs7
stty parenb
stty -clocal
stty crtscts
stty -echo

# Init modem et appel
chat -v "" ATM0\&C1\&D2\&K3 OK ATDT3611 CONNECT

) < /dev/cua0 > /dev/cua0

Le man chat vous donnera beaucoup plus d'informations concernant les possibilités de ce petit programme comme par exemple la définition du chat-script dans un fichier séparé.

4. Programmation des ports séries en C

4.1 Interface POSIX (termios)

La programmation des ports séries sous LINUX utilise l'interface POSIX définie dans le fichier . Le principe est d'appliquer un ensemble de paramètres définis dans une structure termios à un descripteur de fichier (file-descriptor) ouvert sur un terminal donné (par exemple une ligne série). La commande stty étudiée au paragraphe précédent est entièrement basée sur cette interface. Lorsque le descripteur de fichier est correctement configuré, on pourra l'exploiter grâce aux appels systèmes standards read, write, select, etc...

Le but de ce paragraphe n'est pas de fournir une description complète de termios, vous devrez pour cela vous référer au man termios, aux divers documents consacrés au sujet ou au chapitre consacré aux terminaux POSIX dans l'ouvrage Linux 2.0 co-écrit par mon jeune camarade Eric Dumas (dumas@freenix.org), exilé au pays des hamburgers et des nymphettes siliconées (voir bibliographie).

Le premier exemple ci-dessous permet de forcer le raccrochage (hangup) d'un modem en faisant chuter le signal DTR. Dans la définition de l'interface POSIX des terminaux, cela s'effectue simplement en positionnant la vitesse à 0 (valeur B0):

#include <stdio.h>
#include <stdlib.h>
#include <termios.h>
#include <fcntl.h>

main (int ac, char **av)
{
  int fd;
  struct termios tty, old;

  /* Ouverture du device */
  if ((fd = open (av[1], O_RDWR)) < 0) {
    perror (av[1]);
    exit (1);
  }

  /* Lecture des paramètres */
  tcgetattr(fd, &tty);
  tcgetattr(fd, &old);

  /* On passe la vitesse à 0 ==> hangup */
  cfsetospeed(&tty, B0);
  cfsetispeed(&tty, B0);

  /* On applique le nouveau paramètrage pendant 1s */
  tcsetattr(fd, TCSANOW, &tty);
  sleep(1);

  /* On revient à l'ancien et on quitte */
  tcsetattr(fd, TCSANOW, &old);

  close (fd);
}

On pourra faire exactement la même chose en shell en faisant:

stty 0 < /dev/cua0 > /dev/cua0

Par défaut, une ligne série sous LINUX est ouverte en mode canonique. Le mode canonique consiste à traiter les entrées d'un terminal comme une ligne terminée par un séparateur (le plus souvent LF: line-feed correspondant à la touche RETURN). Cela signifie que le programme applicatif ne pourra disposer de la saisie que lorsque le séparateur est reçu. Dans le mode canonique, un certain nombre de caractères de controles sont disponible (effacement, etc...voir la commande stty).

Ce comportement est adapté au traitement d'un terminal réel avec un dialogue opérateur mais il n'est pas utilisable dans le cas du dialogue avec un équipement de type modem par exemple.

Le deuxième exemple permet donc de positionner d'utiliser une ligne série en mode direct (raw), et donc d'inhiber le mode canonique:

/* Fixe un device en mode RAW */
void raw_mode (fd, old_term)
int fd;
struct termios *old_term;
{
    struct termios term;

    tcgetattr(fd, &term);
    
    /* Sauve l'ancienne config dans le paramètre */
    tcgetattr(fd, old_term);

    /* mode RAW, pas de mode canonique, pas d'echo */
    term.c_iflag = IGNBRK;
    term.c_lflag = 0;
    term.c_oflag = 0;

    /* Controle de flux hardware RTS/CTS)
    term.c_cflag |= (CREAD | CRTSCTS);

    /* 1 caractère suffit */
    term.c_cc[VMIN] = 1;

    /* Donnée disponible immédiatement */
    term.c_cc[VTIME] = 0;

    /* Inhibe le controle de flux XON/XOFF */
    term.c_iflag &= ~(IXON|IXOFF|IXANY);

    /* 8 bits de données, pas de parité */
    term.c_cflag &= ~(PARENB | CSIZE);
    term.c_cflag |= CS8;

    /* Gestion des signaux modem */
    term.c_cflag &= ~CLOCAL;

    tcsetattr(fd, TCSANOW, &term);
}

4.2 Autre type de configuration

Un descripteur de fichier sur un port série sera ouvert par une ligne du type
fd = open (device_name, O_RDWR)

Par défaut cette ligne est ouverte en mode bloquant, ce qui peut parfois poser des problèmes dans le cas de l'utilisation du device /dev/ttySx (voir paragraphe 2).

On peut alors ouvrir le device en mode non bloquant par l'appel:

fd = open (device_name, O_RDWR | O_NDELAY)

5. Conclusion

Malgré son grand age, le port série est largement utilisé dans de nombreuses applications industrielles (acquisition de données, pilotage d'équipement). La maitrise des techniques d'exploitation de cette interfaçe est souvent indispensable pour la réalisation d'un système industriel complexe. En dépit de son apparente complexité, l'interface POSIX utilisée sous LINUX est d'une grande efficacité et laisse peu de place aux comportements imprévus.

6. Bibliographie