web-dev-qa-db-fra.com

Lecture et écriture sur le port série en C sous Linux

J'essaie de envoyer/recevoir des données via un port USB à l'aide de FTDI, je dois donc gérer la communication série à l'aide de C/C++. Je travaille sur Linux (Ubuntu).

En gros, je suis connecté à un appareil qui écoute les commandes entrantes. Je dois envoyer ces commandes et lire la réponse du périphérique. Les deux commandes et réponses sont caractères ASCII.

Tout fonctionne bien avec GtkTerm, mais lorsque je passe en programmation C, je rencontre des problèmes.

Voici mon code:

#include <stdio.h>      // standard input / output functions
#include <stdlib.h>
#include <string.h>     // string function 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

/* Open File Descriptor */
int USB = open( "/dev/ttyUSB0", O_RDWR| O_NONBLOCK | O_NDELAY );

/* Error Handling */
if ( USB < 0 )
{
cout << "Error " << errno << " opening " << "/dev/ttyUSB0" << ": " << strerror (errno) << endl;
}

/* *** Configure Port *** */
struct termios tty;
memset (&tty, 0, sizeof tty);

/* Error Handling */
if ( tcgetattr ( USB, &tty ) != 0 )
{
cout << "Error " << errno << " from tcgetattr: " << strerror(errno) << endl;
}

/* Set Baud Rate */
cfsetospeed (&tty, B9600);
cfsetispeed (&tty, B9600);

/* Setting other Port Stuff */
tty.c_cflag     &=  ~PARENB;        // Make 8n1
tty.c_cflag     &=  ~CSTOPB;
tty.c_cflag     &=  ~CSIZE;
tty.c_cflag     |=  CS8;
tty.c_cflag     &=  ~CRTSCTS;       // no flow control
tty.c_lflag     =   0;          // no signaling chars, no echo, no canonical processing
tty.c_oflag     =   0;                  // no remapping, no delays
tty.c_cc[VMIN]      =   0;                  // read doesn't block
tty.c_cc[VTIME]     =   5;                  // 0.5 seconds read timeout

tty.c_cflag     |=  CREAD | CLOCAL;     // turn on READ & ignore ctrl lines
tty.c_iflag     &=  ~(IXON | IXOFF | IXANY);// turn off s/w flow ctrl
tty.c_lflag     &=  ~(ICANON | ECHO | ECHOE | ISIG); // make raw
tty.c_oflag     &=  ~OPOST;              // make raw

/* Flush Port, then applies attributes */
tcflush( USB, TCIFLUSH );

if ( tcsetattr ( USB, TCSANOW, &tty ) != 0)
{
cout << "Error " << errno << " from tcsetattr" << endl;
}

/* *** WRITE *** */

unsigned char cmd[] = {'I', 'N', 'I', 'T', ' ', '\r', '\0'};
int n_written = write( USB, cmd, sizeof(cmd) -1 );

/* Allocate memory for read buffer */
char buf [256];
memset (&buf, '\0', sizeof buf);

/* *** READ *** */
int n = read( USB, &buf , sizeof buf );

/* Error Handling */
if (n < 0)
{
     cout << "Error reading: " << strerror(errno) << endl;
}

/* Print what I read... */
cout << "Read: " << buf << endl;

close(USB);

Qu'est-ce qui se passe, c'est que read() renvoie 0 (aucun octet lu) ou un bloc jusqu'à l'expiration du délai (VTIME). Je suppose que cela se produit parce que write() n'envoie rien. Dans ce cas, l'appareil ne reçoit pas de commande et je ne peux pas recevoir de réponse. En fait, éteindre l'appareil alors que mon programme est bloqué en lecture a effectivement permis d'obtenir une réponse (l'appareil envoie quelque chose tout en s'éteignant).

Chose étrange, c'est que l'ajout de cette

cout << "I've written: " << n_written << "bytes" << endl; 

juste après l'appel de write(), je reçois:

I've written 6 bytes

c'est exactement ce à quoi je m'attends. Seul mon programme ne fonctionne pas comme il se doit, comme mon appareil ne peut pas recevoir ce que j'écris actuellement sur le port.

J'ai essayé différentes solutions et solutions, notamment en ce qui concerne les types de données (j'ai essayé d'utiliser std :: string, tel que cmd = "INIT \r" Ou const char), Mais rien n'a vraiment fonctionné.

Quelqu'un peut-il me dire où je me trompe?

Merci d'avance.

EDIT: Ancienne version de ce code utilisée

unsigned char cmd[] = "INIT \n"

et aussi cmd[] = "INIT \r\n". Je l'ai changé car la commande sintax de mon périphérique est signalée comme

<command><SPACE><CR>.

J'ai aussi essayé d'éviter le drapeau O_NONBLOCK En lisant, mais je ne l'ai bloqué que pour toujours. J'ai essayé d'utiliser select() mais rien ne se passe. Juste pour essayer, j'ai créé une boucle d'attente jusqu'à ce que les données soient disponibles, mais mon code ne quitte jamais la boucle. Btw, attente ou usleep() est quelque chose que je dois éviter. Un rapporté n'est qu'un extrait de mon code. Le code complet doit fonctionner dans un environnement en temps réel (en particulier OROCOS), donc je ne veux pas vraiment de fonction semblable à celle de sommeil.

40
Lunatic999

J'ai résolu mes problèmes, alors je poste ici le code correct au cas où quelqu'un aurait besoin de choses similaires.

Port ouvert

int USB = open( "/dev/ttyUSB0", O_RDWR| O_NOCTTY );

paramètres définis

struct termios tty;
struct termios tty_old;
memset (&tty, 0, sizeof tty);

/* Error Handling */
if ( tcgetattr ( USB, &tty ) != 0 ) {
   std::cout << "Error " << errno << " from tcgetattr: " << strerror(errno) << std::endl;
}

/* Save old tty parameters */
tty_old = tty;

/* Set Baud Rate */
cfsetospeed (&tty, (speed_t)B9600);
cfsetispeed (&tty, (speed_t)B9600);

/* Setting other Port Stuff */
tty.c_cflag     &=  ~PARENB;            // Make 8n1
tty.c_cflag     &=  ~CSTOPB;
tty.c_cflag     &=  ~CSIZE;
tty.c_cflag     |=  CS8;

tty.c_cflag     &=  ~CRTSCTS;           // no flow control
tty.c_cc[VMIN]   =  1;                  // read doesn't block
tty.c_cc[VTIME]  =  5;                  // 0.5 seconds read timeout
tty.c_cflag     |=  CREAD | CLOCAL;     // turn on READ & ignore ctrl lines

/* Make raw */
cfmakeraw(&tty);

/* Flush Port, then applies attributes */
tcflush( USB, TCIFLUSH );
if ( tcsetattr ( USB, TCSANOW, &tty ) != 0) {
   std::cout << "Error " << errno << " from tcsetattr" << std::endl;
}

écrire

unsigned char cmd[] = "INIT \r";
int n_written = 0,
    spot = 0;

do {
    n_written = write( USB, &cmd[spot], 1 );
    spot += n_written;
} while (cmd[spot-1] != '\r' && n_written > 0);

Il n'était absolument pas nécessaire d'écrire octet par octet, int n_written = write( USB, cmd, sizeof(cmd) -1) fonctionnait également correctement.

Enfin, lisez:

int n = 0,
    spot = 0;
char buf = '\0';

/* Whole response*/
char response[1024];
memset(response, '\0', sizeof response);

do {
    n = read( USB, &buf, 1 );
    sprintf( &response[spot], "%c", buf );
    spot += n;
} while( buf != '\r' && n > 0);

if (n < 0) {
    std::cout << "Error reading: " << strerror(errno) << std::endl;
}
else if (n == 0) {
    std::cout << "Read nothing!" << std::endl;
}
else {
    std::cout << "Response: " << response << std::endl;
}

Celui-ci a fonctionné pour moi. Merci à tous!

66
Lunatic999

1) J'ajouterais un/n après init. c'est-à-dire écrire (USB, "init\n", 5);

2) Vérifiez la configuration du port série. Les chances sont que quelque chose est incorrect là-bas. Ce n’est pas parce que vous n’utilisez ni ^ Q/^ S ni le contrôle de flux matériel que l’autre partie ne l’attend pas.

3) Très probablement: Ajouter un "usleep (100000); après le write (). Le descripteur de fichier est paramétré pour ne pas bloquer ni attendre, pas vrai? Combien de temps faut-il pour obtenir une réponse avant de pouvoir appeler read? avant de pouvoir lire () c'est.) Avez-vous envisagé d'utiliser select () pour attendre quelque chose read () =? Peut-être avec un délai d'attente?

édité pour ajouter:

Avez-vous besoin des lignes DTR/RTS? Contrôle de flux matériel qui dit à l'autre côté d'envoyer les données informatiques? par exemple.

int tmp, serialLines;

cout << "Dropping Reading DTR and RTS\n";
ioctl ( readFd, TIOCMGET, & serialLines );
serialLines &= ~TIOCM_DTR;
serialLines &= ~TIOCM_RTS;
ioctl ( readFd, TIOCMSET, & serialLines );
usleep(100000);
ioctl ( readFd, TIOCMGET, & tmp );
cout << "Reading DTR status: " << (tmp & TIOCM_DTR) << endl;
sleep (2);

cout << "Setting Reading DTR and RTS\n";
serialLines |= TIOCM_DTR;
serialLines |= TIOCM_RTS;
ioctl ( readFd, TIOCMSET, & serialLines );
ioctl ( readFd, TIOCMGET, & tmp );
cout << "Reading DTR status: " << (tmp & TIOCM_DTR) << endl;
0
guest

Certains destinataires attendent une séquence EOL, qui est généralement composée de deux caractères \r\n, alors essayez dans votre code de remplacer la ligne

unsigned char cmd[] = {'I', 'N', 'I', 'T', ' ', '\r', '\0'};

avec

unsigned char cmd[] = "INIT\r\n";

BTW, la manière ci-dessus est probablement plus efficace. Il n'est pas nécessaire de citer tous les caractères.

0
radarhead