2016-08-30 2 views
0

Je suis confronté à un problème très étrange, que je n'ai pas pu résoudre. Je veux lire (juste lire) les données collectées et envoyées par un micro-contrôleur via USB comme port série (FTDI) sur Mac Os X en utilisant C++. La taille d'une séquence de données complète est toujours exactement de 10 octets. Cependant, j'utilisais le code suivant pour lire les données:Problème de lecture du port série C++: ioctl (FIONREAD) définit-il une valeur incorrecte?

Bibliothèques importées:

#include <iostream> 
#include <fstream> 
#include <unistd.h> 
#include <fcntl.h> 
#include <termios.h> 
#include <sys/ioctl.h> 

code:

void init(){ 
    serial = open(port.c_str(), O_RDWR | O_NOCTTY | O_NONBLOCK); // 0_RDONLY ? 
    struct termios options; 
    //set opt to 115200-8n1 
    cfsetspeed(&options, B115200); 
    options.c_cflag &= ~PARENB; 
    options.c_cflag &= ~CSTOPB; 
    options.c_cflag &= ~CSIZE; 
    options.c_cflag |= CS8; 

    tcsetattr(serial, TCSANOW, &options); 
    if (serial < 0){ 
     //Error 
    }else{ 
     //run loop 
    } 
} 

void serial_loop(){ 
    long bytes_read; 
    int bytes_available; 
    unsigned char msg[10]; 
    while(1){ 
     do{ 
      usleep(1000); 
      ioctl(serial, FIONREAD, &bytes_available); 

     }while(bytes_available < 10); //wait for the sequence to complete 

     bytes_read = read(serial, msg, 10); 

     //do some parsing here 
    } 
} 

Ce code a travaillé il y a quelques jours, mais maintenant il est plus. Les données atteignent parfaitement l'ordinateur en fonction de la commande Terminal -> screen. J'ai vérifié le port-nom de fichier qui est toujours correct et le port est également ouvert avec succès. J'ai limité mon problème à la commande ioctl FIONREAD qui n'écrit pas le bon numéro dans le fichier bytes_available-var (plus). Ça a marché, et je crois que je n'ai rien changé dans le code.

Voyez-vous des problèmes qui pourraient causer ce problème? Y a-t-il des passages dangereux dans mon code? Merci pour votre aide, je suis vraiment coincé ici ...

EDIT: Grâce aux commentaires, j'ai pu le faire fonctionner à nouveau. Voici le code actuel:

int serial; 
void init(){ 
    serial = open(port.c_str(), O_RDWR | O_NOCTTY); //removed 0_NONBLOCK 
    struct termios options; 
    //set opt to 115200-8n1 
    cfsetspeed(&options, B115200); 
    options.c_cflag &= ~PARENB; 
    options.c_cflag &= ~CSTOPB; 
    options.c_cflag &= ~CSIZE; 
    options.c_cflag |= CS8; 

    options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); //Non-canonical 
    options.c_cc[VMIN]  = 1; //block read until at least 1 byte was recieved 
    options.c_lflag = 0; 

    tcsetattr(serial, TCSANOW, &options); 
    if (serial < 0){ 
     //Error 
    }else{ 
     //run loop 
    } 
} 

void serial_loop(){ 
    int datalength = 10; 
    long bytes_read = 0; 
    int bytes_in_msg = 0; 
    unsigned char buf[datalength]; 
    unsigned char msg[datalength]; 
    do{ 
     bytes_read = read(serial, buf, datalength-bytes_in_msg); 
     usleep(1000); 
     if (bytes_read>0){ 
      memcpy(&msg[bytes_in_msg], &buf, bytes_read); 
     } 
     bytes_in_msg += bytes_read; 
    }while(bytes_in_msg < datalength); 

    //do some parsing here 
    } 
} 

Cela fonctionne, mais y a-t-il quelque chose qui pourrait poser problème?

Nous vous remercions de votre soutien!

+0

Il semble que vous ayez un problème de mise à la terre dans votre appareil. Essayez de le vérifier – vadikrobot

+0

Non, le périphérique fonctionne toujours, et si j'utilise la commande d'écran (commandline) ou CoolTerm je peux lire les données sans erreurs. Est-il possible de réinitialiser tous les paramètres du port? – archimedes

+0

* "Ce code a fonctionné il y a quelques jours mais maintenant il ne l'est plus." * - Cela indique généralement une initialisation incorrecte ou incomplète. Votre initialisation termios ne configure que le débit et la taille des caractères, et tout le reste est laissé au hasard. Voir [Réglage correct des modes de terminal] (http://www.chemie.fu-berlin.de/chemnet/use/info/libc/libc_12.html # SEC237) et [Guide de programmation série pour les systèmes d'exploitation POSIX] (http://www.cmrr.umn.edu/~strupp/serial.html) – sawdust

Répondre

1

Ce code a fonctionné il y a quelques jours mais maintenant il ne l'est plus.

Un comportement de programme irrégulier indique généralement une initialisation incorrecte ou incomplète.
Votre initialisation termios configure uniquement la vitesse de transmission et le cadrage des caractères, et tout le reste est laissé au hasard.
Voir Setting Terminal Modes Properly et Serial Programming Guide for POSIX Operating Systems.


Votre code révisé n'a toujours pas résolu ce problème correctement.
Le code n'initialise jamais la terminios structure options en appelant la fonction tcgetattr().
Pour exemple de code voir ma réponse à how to open, read, and write from serial port in C


options.c_lflag = 0; 

Ce n'est pas considéré comme la bonne façon d'attribuer un termios élément.


options.c_cc[VMIN]  = 1; 

Le mode non canonique, définition des deux entrées Vmin et vHeure.
Votre code utilise la valeur de la poubelle qui existe à l'emplacement de VTIME.
Voir Linux Blocking vs. non Blocking Serial Read.


FIONREAD qui n'écrit pas le nombre correct de la bytes_available-var (plus).

Les descriptions négatives, c'est-à-dire ce qui ne se produit pas, ne sont pas aussi utiles ou spécifiques que les descriptions de ce qui se passe.
Alors, quel genre de valeurs vous rapportez-vous?
Pourquoi pensez-vous qu'il est « mal »? Plus important encore, pourquoi ne vérifiez-vous pas la valeur de retour de chaque appel système, en particulier ioctl() qui, selon vous, vous pose problème?

Le plus probable est que le ioctl() a échoué, ne pas mettre à jour votre variable bytes_available et a renvoyé un code d'erreur. Au lieu de d'abord vérifier un bon retour, votre code utilise inconditionnellement l'argument retourné.


Une autre réponse qui critique votre code est trompeuse. Le contrôle de flux peut être désactivé. Votre code est cassé car il n'effectue pas l'initialisation correcte et ne vérifie pas les retours d'erreur, pas parce que le lien comm est "deadlocked".


Dans votre code révisé:

bytes_read = read(serial, buf, datalength-bytes_in_msg); 
    usleep(1000); 

Un sommeil ou du retard avant et/ou après une lecture de blocage est superflu.

+0

Merci pour la réponse complète. Je l'aurais voté, si je le pouvais. Votre réponse sur l'autre question que vous avez liée a été très utile pour définir les paramètres/indicateurs corrects et bien sûr initialiser correctement les options termios (comme vous l'avez dit en appelant 'tcgetattr()'). – archimedes

+0

J'ai vérifié les valeurs de retour lors du suivi du problème d'origine, c'est pourquoi j'ai pu reconnaître que 'ioctl()' n'échouait pas (il renvoyait 0, ce qui signifie le succès selon la documentation trouvée) définir la "mauvaise" valeur à la variable. Comme je l'ai déjà dit, j'attends 10 octets dans chaque séquence, mais 'ioctl()' m'a donné 2 (après la première séquence), 4 (après la seconde) puis 6 et ainsi de suite. – archimedes

+0

* "J'ai vérifié les valeurs de retour ..." * - Nous ne pouvons réviser que le code affiché. Vous n'avez fourni aucun autre détail que vous avez conclu que les données étaient * "fausses" *. [Décrivez les symptômes du problème, pas vos suppositions] (http://www.catb.org/esr/faqs/smart-questions.html#symptoms). Étant donné que vous soumettiez des déchets à ** tcsetattr() **, tous vos résultats de test précédents ne sont plus pertinents.Corrigez les bogues dans votre programme lorsque vous les trouvez, puis redémarrez les tests à nouveau. – sawdust

0

Votre code est cassé. Aucun protocole ne peut permettre au lecteur d'attendre que l'auteur et l'auteur attendent le lecteur. Si c'était le cas, une impasse pourrait en résulter. Votre boucle do/while refuse de lire toutes les données jusqu'à ce que l'écrivain écrit tous les 10. Cependant, les ports série permettent au writer de refuser d'écrire plus de données jusqu'à ce que le lecteur lise ce qu'il a déjà écrit. Donc, il ne peut pas non plus permettre à un lecteur de refuser de lire plus de données jusqu'à ce que l'écrivain écrit plus.

Vous ne pouvez pas utiliser FIONREAD attendre plus d'octets à écrire parce que l'écrivain peut aussi être vous attend. Au lieu de cela, lisez les octets dès qu'ils deviennent disponibles, vous assurant ainsi de débloquer l'écrivain. Accumulez-les dans un tampon et cassez quand vous avez le numéro dont vous avez besoin.

+0

Merci. J'avais peur de perdre des octets en lisant le message pas tout à la fois. Mais j'ai changé le code comme vous l'avez dit (_ "Accumulez-les dans un tampon et cassez quand vous avez le numéro dont vous avez besoin." _) Et cela fonctionne bien sans rien perdre jusqu'à maintenant. – archimedes

+0

* "Les ports série permettent au rédacteur de refuser d'écrire d'autres données jusqu'à ce que le lecteur lise ce qu'il a déjà écrit." * - ** Le contrôle de flux ** peut être désactivé pour rendre votre critique inutile. – sawdust

+0

@sawdust Huh? Cela n'a même pas de sens. Même si vous désactivez le contrôle de flux, les ports série permettent au rédacteur de refuser d'écrire d'autres données jusqu'à ce que le lecteur lise ce qu'il a déjà écrit car la désactivation du contrôle de flux ne le fait pas disparaître dans l'univers. . Le protocole du port série peut soit permettre aux auteurs d'attendre les lecteurs, soit permettre aux lecteurs d'attendre les auteurs. Il ne peut pas permettre les deux. Il a choisi de permettre aux auteurs d'attendre que les lecteurs rendent le contrôle de flux * possible *. La désactiver ne change pas cela. –