Communication Série en C

Il est souvent très utile de piloter un micro système basé sur un Arduino avec un ordinateur sous Linux (ou sur Windows si vous y tenez absolument). Si vous ne connaissez que le C ou que vous ne voulez pas mettre en œuvre des bibliothèques compliquées telles que Qt ou autre, il existe une solution relativement simple: utiliser la bibliothèque Arduino-Serial-Lib développée par Tod E. Kurt.

La bibliothèque Arduino-Serial-Lib est constituée de fonctions: serialport_init permettant d'initialiser le port de communication serialport_close pour le fermer serialport_writebyte pour écrire un octet sur le port (envoyer) serialport_write pour écrire une chaîne de caractères int serialport_read_until pour lire le port jusqu'à l'obtention d'un caractère particulier serialport_flush pour évacuer les transferts en cours

Les prototypes de ces fonctions sont:

int serialport_init(const char* serialport, int baud) avec serialport, une chane de caractère contenant le nom du port, ex: /dev/ttyUSB0 sous Linux ou COM1 sous Windows, et baud la vitesse de transfert. La fonction renvoie un entier, handle, qui représente la communication en cours et devra être spécifié dans toutes les fonctions suivantes. Si cet handle est -1, l'initialisation du port n'a pas été possible (mauvais ou absence de port série, .etc.)

int serialport_close( int fd ) fd, le handle retourné par la fonction serialport_init et différent de -1

int serialport_writebyte( int fd, uint8_t b) fd, le handle retourné par la fonction serialport_init et différent de -1 b, un octet à écrire

int serialport_write(int fd, const char* str) fd, le handle retourné par la fonction serialport_init et différent de -1 str, la chaîne de caractère à écrire

int serialport_read_until(int fd, char* buf, char until, int buf_max, int timeout) fd, le handle retourné par la fonction serialport_init et différent de -1 buf, le buffer (tableau de caractère) qui recevra les données reçues until, le caractère de fin, lecture jusqu'à la réception de until buf_max, le nombre maximum de caractères à lire timeout, le temps maximum à attendre en secondes

int serialport_flush(int fd) fd, le handle retourné par la fonction serialport_init et différent de -1

Toutes les fonctions précédentes renvoient 0 en cas de succés et -1 en cas d'échec.

Mise en œuvre de la bibliothèque dans un programme c

Si vous ne maîtrisez pas l'édition des liens et l'ajout de bibliothèques extérieures à un programme, une solution consiste à inclure la totalité du code de la bibliothèque dans votre programme. Elle ferait hurler un bon informaticien mais elle présente l'avantage de la simplicité.

Il suffit d'insérer le code suivant en début de programme:

//__________________________________________________________________________________
// arduino-serial-lib -- simple library for reading/writing serial ports
//
// 2006-2013, Tod E. Kurt, http://todbot.com/blog/
//


#include <stdio.h>    // Standard input/output definitions 
#include <unistd.h>   // UNIX standard function definitions 
#include <fcntl.h>    // File control definitions 
#include <errno.h>    // Error number definitions 
#include <termios.h>  // POSIX terminal control definitions 
#include <string.h>   // String function definitions 
#include <sys/ioctl.h>
#include <stdint.h> // Standard types 

// uncomment this to debug reads
//#define SERIALPORTDEBUG 

// takes the string name of the serial port (e.g. "/dev/tty.usbserial","COM1")
// and a baud rate (bps) and connects to that port at that speed and 8N1.
// opens the port in fully raw mode so you can send binary data.
// returns valid fd, or -1 on error
int serialport_init(const char* serialport, int baud)
{
    struct termios toptions;
    int fd;

    //fd = open(serialport, O_RDWR | O_NOCTTY | O_NDELAY);
    fd = open(serialport, O_RDWR | O_NONBLOCK );

    if (fd == -1)  {
        perror("serialport_init: Unable to open port ");
        return -1;
    }

    //int iflags = TIOCM_DTR;
    //ioctl(fd, TIOCMBIS, &iflags);     // turn on DTR
    //ioctl(fd, TIOCMBIC, &iflags);    // turn off DTR

    if (tcgetattr(fd, &toptions) < 0) {
        perror("serialport_init: Couldn't get term attributes");
        return -1;
    }
    speed_t brate = baud; // let you override switch below if needed
    switch(baud) {
    case 4800:   brate=B4800;   break;
    case 9600:   brate=B9600;   break;
#ifdef B14400
    case 14400:  brate=B14400;  break;
#endif
    case 19200:  brate=B19200;  break;
#ifdef B28800
    case 28800:  brate=B28800;  break;
#endif
    case 38400:  brate=B38400;  break;
    case 57600:  brate=B57600;  break;
    case 115200: brate=B115200; break;
    }
    cfsetispeed(&toptions, brate);
    cfsetospeed(&toptions, brate);

    // 8N1
    toptions.c_cflag &= ~PARENB;
    toptions.c_cflag &= ~CSTOPB;
    toptions.c_cflag &= ~CSIZE;
    toptions.c_cflag |= CS8;
    // no flow control
    toptions.c_cflag &= ~CRTSCTS;

    //toptions.c_cflag &= ~HUPCL; // disable hang-up-on-close to avoid reset

    toptions.c_cflag |= CREAD | CLOCAL;  // turn on READ & ignore ctrl lines
    toptions.c_iflag &= ~(IXON | IXOFF | IXANY); // turn off s/w flow ctrl

    toptions.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // make raw
    toptions.c_oflag &= ~OPOST; // make raw

    // see: http://unixwiz.net/techtips/termios-vmin-vtime.html
    toptions.c_cc[VMIN]  = 0;
    toptions.c_cc[VTIME] = 0;
    //toptions.c_cc[VTIME] = 20;

    tcsetattr(fd, TCSANOW, &toptions);
    if( tcsetattr(fd, TCSAFLUSH, &toptions) < 0) {
        perror("init_serialport: Couldn't set term attributes");
        return -1;
    }

    return fd;
}

//______________________________________________________________________
int serialport_close( int fd )
{
    return close( fd );
}

//______________________________________________________________________
int serialport_writebyte( int fd, uint8_t b)
{
    int n = write(fd,&b,1);
    if( n!=1)
        return -1;
    return 0;
}

//______________________________________________________________________
int serialport_write(int fd, const char* str)
{
    int len = strlen(str);
    int n = write(fd, str, len);
    if( n!=len ) {
        perror("serialport_write: couldn't write whole string\n");
        return -1;
    }
    return 0;
}

//________________________________________________________________________________
int serialport_read_until(int fd, char* buf, char until, int buf_max, int timeout)
{
    char b[1];  // read expects an array, so we give it a 1-byte array
    int i=0;
    do { 
        int n = read(fd, b, 1);  // read a char at a time
        if( n==-1) return -1;    // couldn't read
        if( n==0 ) {
            usleep( 1 * 1000 );  // wait 1 msec try again
            timeout--;
            if( timeout==0 ) return -2;
            continue;
        }
#ifdef SERIALPORTDEBUG  
        printf("serialport_read_until: i=%d, n=%d b='%c'\n",i,n,b[0]); // debug
#endif
        buf[i] = b[0]; 
        i++;
    } while( b[0] != until && i < buf_max && timeout>0 );

    buf[i] = 0;  // null terminate the string
    return 0;
}

//___________________________________________________________________________
int serialport_flush(int fd)
{
    sleep(2); //required to make flush work, for some reason
    return tcflush(fd, TCIOFLUSH);
}

Il ne vous reste plus alors qu'à écrire votre programme principal, main, ici on se contente d'afficher toutes les lignes de texte reçues de la liaison série:

//____________________________________________________________________________
//          PROGRAMME PRINCIPAL
// le fait d'inclure les fonctions de la bibliothèque directement dans le programme
// permet de s'affranchir de la rajouter à l'édition des liens
int main(int argc, char **argv)
{
    char buffer[100];                   // un buffer
    int i;

    // ouverture du port à 115200 bauds
    int fd = serialport_init("/dev/ttyUSB0", 115200);
    if (fd==-1) return -1;

    // boucle
    for ( ; ; ){
        //  lecture d'une ligne
        serialport_read_until(fd, buffer, '\r', 99, 10000);

        // suppression de la fin de ligne
        for (i=0 ; buffer[i]!='\r' && i<100 ; i++);
        buffer[i] = 0;

        // écriture du résultat
        printf("%s", buffer);
    }

    // fermeture du port
    serialport_flush(fd);
    serialport_close(fd);

    return 0;
}

Vous trouverez cet exemple de code ici.

Voici une solution simple, voire simpliste, de communiquer avec un arduino qui ne nécessite pas de connaitre le c++ mais fonctionne bien. Vous trouverez sur ce blog une solution plus riche et plus élégante en c++ avec Qt - autre chose!

blogroll