Site : tvaira.free.fr

Mise en oeuvre d’un périphérique USB sous Linux

Il est conseillé de lire ou de s’aider de ce document avant de poursuive.

Prise en charge

Avant :

$ lsusb > lsusb-avant.txt
$ lsmod > lsmod-avant.txt
$ dmesg > dmesg-avant.txt

Brancher maintenant un périphérique USB.

Après :

$ lsusb > lsusb-apres.txt
$ lsmod > lsmod-apres.txt
$ dmesg > dmesg-apres.txt

Analyse :

$ diff lsusb-avant.txt lsusb-apres.txt 
> Bus 002 Device 011: ID 0e8d:3329 MediaTek Inc.

$ diff dmesg-avant.txt dmesg-apres.txt 
> [204555.128418] usb 2-1.4: new full-speed USB device number 10 using ehci-pci
> [204555.223664] usb 2-1.4: New USB device found, idVendor=0e8d, idProduct=3329
> [204555.223669] usb 2-1.4: New USB device strings: Mfr=3, Product=4, SerialNumber=0
> [204555.223672] usb 2-1.4: Product: GPS Receiver
> [204555.223674] usb 2-1.4: Manufacturer: MTK
> [204555.257427] cdc_acm 2-1.4:1.1: ttyACM0: USB ACM device
> [204555.257994] usbcore: registered new interface driver cdc_acm
> [204555.257997] cdc_acm: USB Abstract Control Model driver for USB modems and ISDN adapters

$ diff lsmod-avant.txt lsmod-apres.txt 
> cdc_acm                26991  2

$ modinfo cdc_acm
filename:       /lib/modules/3.8.0-44-generic/kernel/drivers/usb/class/cdc-acm.ko
alias:          char-major-166-*
license:        GPL
description:    USB Abstract Control Model driver for USB modems and ISDN adapters
author:         ...
srcversion:     AD91845189110F8BEC40793
...
alias:          usb:v0E8Dp3329d*dc*dsc*dp*ic*isc*ip*in*
alias:          usb:v0E8Dp0003d*dc*dsc*dp*ic*isc*ip*in*
...

$ ls -l /dev/ttyACM0
crw-rw---- 1 root dialout 166, 0 sept. 18 16:24 /dev/ttyACM0

Attention : le périphérique ne sera accessible en lecture/écriture que par l’utilisateur propriétaire root et les membres du groupe dialout.

Droits d’accès

Par défaut, le système applique la politique des droits d’accès rw-rw---- pour ce type de périphérique USB :

$ ls -l /dev/ttyACM0
crw-rw---- 1 root dialout 166, 0 sept. 18 16:24 /dev/ttyACM0

Ici, le périphérique sera accessible en lecture/écriture par l’utilisateur propriétaire root et les membres du groupe dialout. Tous les autres (other) utilisateurs auront aucun accès.

Pour modifier cette situation, vous avez les possibilités suivantes :

Pour une gestion automatique, il faudra passer par udev qui est maintenant le service qui prend en charge le répertoire /dev.

$ ls -l /dev/ttyACM0
crw-rw---- 1 root dialout 166, 0 sept. 18 16:24 /dev/ttyACM0

$ sudo vim /etc/udev/rules.d/51-ttyusb.rules
SUBSYSTEMS=="usb", ATTRS{idVendor}=="0e8d", ATTRS{idProduct}=="3329", MODE="0666"

$ ls -l /dev/ | grep ACM
crw-rw-rw-  1 root dialout   166,   0 sept. 18 17:08 ttyACM0

Il est aussi possible de créer un lien symbolique au choix :

$ sudo vim /etc/udev/rules.d/51-ttyusb.rules
SUBSYSTEMS=="usb", ATTRS{idVendor}=="0e8d", ATTRS{idProduct}=="3329", MODE="0666", SYMLINK+="gps"

$ ls -l /dev/ | grep "\(ACM\|gps\)"
lrwxrwxrwx  1 root root             7 sept. 18 17:10 gps -> ttyACM0
crw-rw-rw-  1 root dialout   166,   0 sept. 18 17:10 ttyACM0

Ou de fixer le nom du fichier spécial :

$ sudo vim /etc/udev/rules.d/51-ttyusb.rules
SUBSYSTEMS=="usb", ATTRS{idVendor}=="0e8d", ATTRS{idProduct}=="3329", MODE="0666", NAME="ttyUSB1", 
SYMLINK+="gps"

$ ls -l /dev/ | grep "\(gps\|USB\)"
lrwxrwxrwx  1 root root             7 sept. 18 17:12 gps -> ttyUSB1
crw-rw-rw-  1 root dialout   166,   0 sept. 18 17:12 ttyUSB1

Tests

On peut tester la réception de phrases NMEA 0183 avec la commande screen (Ctrl-a k pour sortir) ou avec picocom (Ctrl-a Ctrl-x pour sortir) :

$ screen /dev/ttyACM0 115200
$GPRMC,000936.799,V,0000.0000,N,00000.0000,E,,,060180,,*10
$GPVTG,,T,,M,,N,,K*4E
$GPGGA,000937.799,0000.0000,N,00000.0000,E,0,00,,,M,,,,0000*04
$GPGSA,A,1,,,,,,,,,,,,,,,*1E
$GPGSV,1,1,00*79
$GPRMC,000937.799,V,0000.0000,N,00000.0000,E,,,060180,,*11
$GPVTG,,T,,M,,N,,K*4E
$GPGGA,000938.799,0000.0000,N,00000.0000,E,0,00,,,M,,,,0000*0B
...

$ picocom -b 115200 /dev/ttyACM0 
picocom v1.4

port is        : /dev/ttyACM0
flowcontrol    : none
baudrate is    : 115200
parity is      : none
databits are   : 8
escape is      : C-a
noinit is      : no
noreset is     : no
nolock is      : no
send_cmd is    : ascii_xfr -s -v -l10
receive_cmd is : rz -vv

Terminal ready
$GPGGA,064509.842,4405.2015,N,00457.8701,E,0,00,,,M,,,,0000*05
$GPGSA,A,1,,,,,,,,,,,,,,,*1E
$GPGSV,3,1,12,29,80,028,,31,61,278,,25,55,092,,21,29,178,*73
$GPGSV,3,2,12,26,28,296,,12,17,102,,02,14,041,,14,06,218,*74
$GPGSV,3,3,12,05,06,076,,20,03,123,,23,02,333,,16,02,290,*77
$GPRMC,064509.842,V,4405.2015,N,00457.8701,E,,,041015,,*1E
$GPVTG,,T,,M,,N,,K*4E
...

Remarque : 115200 est le débit de la “ligne” en bits/s.

Ou avec le logiciel cutecom :

Programmation en C

// Lit une trame NMEA 183 sur le port série virtuel

// Attention : vérifiez le nom du fichier de périphérique et la configuration du port série

/*************************************************************
*   gcc -O2 -o nom_executable nom_source 
**************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include <sys/fcntl.h>

// On fixe la taille maximum à 128 octets
#define MAX 128

//#define DEBUG

int main(void)
{
    int fd; // descripteur de fichier
    char nomPeripherique[16] = "/dev/ttyACM0"; // nom du fichier special
    struct termios termios_p; // parametres du port
    char* buffer = (char *)NULL; // un buffer
    char c = 0; // un caractère
    int fini = 0; // fin de reception d'une trame
    int i = 0, n = 0;
    int debutTrame = 0; // debut de reception d'une trame

    /* Ouverture de la liaison serie */
    fd = open(nomPeripherique, O_RDWR | O_NOCTTY | O_NONBLOCK);
    if ( fd == -1 )
    {
        perror("open"); // message d'erreur système
        //printf("Erreur d'ouverture du peripherique !\n"); // message d'erreur client
        exit(1);
    }
    fflush(NULL);
      //printf("Peripherique ouvert avec succes !\n");

    /* 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, 8 bits de donnees, pas de parite, lecture possible */
    termios_p.c_cflag = B9600 | CS8 | CREAD | CLOCAL;
    termios_p.c_cflag &= ~PARODD ;
    termios_p.c_cflag &= ~PARENB;
    /* Desactive le mode canonique */
    termios_p.c_lflag = 0;
    /* Caracteres immediatement disponibles */
    termios_p.c_cc[VMIN] = 1;
    termios_p.c_cc[VTIME] = 0;

    /* Sauvegarde des nouveaux parametres */
    if(tcsetattr(fd,TCSANOW,&termios_p)== -1)
    {
      perror("tcsetattr");
      close(fd);
      exit(1);
    }
    //printf("Sauvegarde de la nouvelle configuration avec succes !\n");

    /* Lecture de MAX octets */    
    buffer = (char *)malloc((MAX+1) * sizeof(char)); // on alloue un buffer
    i = 0;
    debutTrame = 0;
    do
    {
        n = read(fd, &c, 1);
        #ifdef DEBUG
        printf("read : %d -> 0x%02X ", n, (unsigned char)c);
        #endif
        // est-ce qu'on a lu 1 caractère ?
        if(n == 1) 
        {           
            // est-ce le début d'une trame ?        
            if(c == '$')
            {
              debutTrame = 1; 
            }             
            // est-ce qu'on est entrain de lire une trame ?
            if(debutTrame == 1)   
            {
              // on copie le caractère lu dans le buffer
              *(buffer + i) = c;
              i++;        
            }
            // est-ce que c'est la fin de la trame qu'on est entrain de lire ?
            if (debutTrame == 1 && c == '\n') 
            {
              fini = 1;
              debutTrame = 0;
            }
        }
        /*else
        {
            if(n == -1)
            {
                perror("read"); // message erreur système
                //printf("Erreur de lecture\n"); // message client
                //fini = 1;
            }
            else
            {
                printf("Aucune lecture !\n"); // message warning
            }
        }*/
    }
    while(i <= MAX && fini == 0);

    *(buffer + i) = 0x00; // fin de chaîne (attention à i > MAX !)

    printf("Trame lue :\n%s\n", buffer);

    /* Fermeture du port série */
    close (fd);
    //printf("Fermeture du peripherique avec succes !\n");

    free(buffer); // on libère la mémoire allouée

    return 0;
}
$ gcc -O2 test-port-com-usb.c 

$ ./a.out 
Trame lue :
$GPZDA,001448.799,06,01,1980,,.0000,E,0,00,0,00,,,M,,,,0000*01

Retour au sommaire