la lecture du port série en c casse les lignes

J’essaie d’écrire un petit programme en C qui lira sur le port série à l’aide de la commande select afin qu’il se bloque et attende une entrée. Cela fonctionne, sauf qu’il continue à couper les lignes et je ne sais pas pourquoi. L’appareil est programmé pour ne pas rompre les lignes et fonctionne bien avec les programmes de terminaux réels. Je n’avais jamais fait de communication série en C auparavant, et je suis sur un Mac, donc tout est nouveau pour moi. Je ne sais vraiment pas où chercher même ce qui ne va pas.

J’ai un code qui trouve et répertorie les ports série. Je vais laisser cela de côté pour des raisons de simplicité. Si une variable n’a pas de sens, c’est peut-être pour cette raison. Voici le code qui ouvre le port, définit les atsortingbuts et tente de le lire, avec les commentaires copiés du site Apple (désolé):

/* this is based on a combination of http://stackoverflow.com/questions/6947413/how-to-open-read-and-write-from-serial-port-in-c * and https://developer.apple.com/library/mac/documentation/DeviceDrivers/Conceptual/WorkingWSerial/WWSerial_SerialDevs/SerialDevices.html */ static int OpenSerialPort(const char *deviceFilePath, int speed) { int fileDescriptor = -1; struct termios options; memset(&options, 0, sizeof(options)); // init it // Open the serial port read/write, with no controlling terminal, // and don't wait for a connection. // The O_NONBLOCK flag also causes subsequent I/O on the device to // be non-blocking. // See open(2) ("man 2 open") for details. fileDescriptor = open(deviceFilePath, O_RDWR | O_NOCTTY | O_NONBLOCK); if (fileDescriptor == -1) { printf("Error opening serial port %s - %s(%d).\n", deviceFilePath, strerror(errno), errno); goto error; } // Note that open() follows POSIX semantics: multiple open() calls to // the same file will succeed unless the TIOCEXCL ioctl is issued. // This will prevent additional opens except by root-owned processes. // See options(4) ("man 4 options") and ioctl(2) ("man 2 ioctl") for details. if (ioctl(fileDescriptor, TIOCEXCL) == kMyErrReturn) { printf("Error setting TIOCEXCL on %s - %s(%d).\n", deviceFilePath, strerror(errno), errno); goto error; } // Set raw input (non-canonical) mode, with reads blocking until either // a single character has been received or a one second timeout expires. // See tcsetattr(4) ("man 4 tcsetattr") and termios(4) ("man 4 termios") // for details. cfmakeraw(&options); options.c_cc[VMIN] = 1; options.c_cc[VTIME] = 5; // The baud rate, word length, and handshake options can be set as follows: cfsetspeed(&options, speed); // Set 19200 baud options.c_cflag = (options.c_cflag & ~CSIZE) | CS8; // 8-bit chars // disable IGNBRK for mismatched speed tests; otherwise receive break // as \000 chars options.c_iflag &= ~IGNBRK; // disable break processing options.c_lflag = 0; // no signaling chars, no echo, // no canonical processing options.c_oflag = 0; // no remapping, no delays options.c_iflag &= ~(IXON | IXOFF | IXANY); // shut off xon/xoff ctrl options.c_cflag |= (CLOCAL | CREAD);// ignore modem controls, // enable reading options.c_cflag &= ~(PARENB | PARODD); // shut off parity options.c_cflag |= false; options.c_cflag &= ~CSTOPB; options.c_cflag &= ~CRTSCTS; // Cause the new options to take effect immediately. if (tcsetattr(fileDescriptor, TCSANOW, &options) == kMyErrReturn) { printf("Error setting options atsortingbutes %s - %s(%d).\n", deviceFilePath, strerror(errno), errno); goto error; } // turn on blocking if (fcntl(fileDescriptor, F_SETFL, 0) == kMyErrReturn) { printf("Error clearing O_NONBLOCK %s - %s(%d).\n", deviceFilePath, strerror(errno), errno); goto error; } // Success: return fileDescriptor; // Failure: error: if (fileDescriptor != kMyErrReturn) { close(fileDescriptor); } return -1; } int main(void) { int fileDescriptor; kern_return_t kernResult; // these are Apple-specific io_iterator_t serialPortIterator; // Apple char deviceFilePath[MAXPATHLEN]; fd_set fdset; // make a file descriptor set FD_ZERO (&fdset); // init it char buf[1000]; // some ssortingngs are big kernResult = GetDevices(&serialPortIterator); printf("Devices on this system:\n"); kernResult = ListDevicePaths(serialPortIterator, deviceFilePath, sizeof(deviceFilePath)); IOObjectRelease(serialPortIterator); // Release the iterator. // Open the modem port, initialize the modem, then close it. if (!deviceFilePath[0]) { printf("No modem port found.\n"); return EX_UNAVAILABLE; } fileDescriptor = OpenSerialPort("/dev/cu.usbmodem1d1111", B230400); FD_SET (fileDescriptor, &fdset); // add to file descriptor set // now we're going to use select to only read from the file handle when there's data available while (1) { if (select (FD_SETSIZE, &fdset, NULL, NULL, NULL) < 0) // this will block the program until something is on the line { printf("select error\n"); } read(fileDescriptor, buf, 1000); printf("%s\n", buf); memset(buf, '\0', 1000); } // let's try to read from the serial port /* for (int i = 0; i <= 10; i++) { char buf [100]; int n = read(fileDescriptor, buf, sizeof buf); printf("%s\n", buf); //usleep ((7 + 25) * 100); }*/ close(fileDescriptor); printf("Modem port closed.\n"); return EX_OK; } 

Production attendue:

  This is sample output. Hello. 

Ce que je reçois réellement dans le programme ci-dessus:

  Thi s is sam ple output. Hel lo. 

Ou quelque chose comme ça. C’est différent à chaque fois. Parfois ça marche bien. Cela semble être aléatoire.

Donc mes questions sont: Qu’est-ce que je fais mal? Sur quel code dois-je travailler en dehors d’une simple couverture “tout?” Qu’est-ce que je ne comprends pas? J’admets que je ne comprends pas vraiment comment ces bibliothèques fonctionnent, exactement. Je suppose (je sais, je sais) qu’ils s’occupent du contrôle des stream et des erreurs, etc. Mais là encore, les exemples que j’ai copiés ne l’expliquent pas exactement, alors je ne le sais pas. Je ne sais pas vraiment ce qui se passe.

ça continue à couper les lignes et je ne sais pas pourquoi.

Si vous voulez lire les lignes du port série, vous devez le configurer pour le faire.
Au lieu de cela, vous l’avez configuré pour être en mode non canonique et non bloquant.
Le code ne correspond pas du tout à vos intentions déclarées.

Citant la page de manuel Linux termios :

En mode canonique:
L’entrée est disponible ligne par ligne. Une ligne d’entrée est disponible lorsqu’un des délimiteurs de ligne est saisi (NL, EOL, EOL2; ou EOF en début de ligne). Sauf dans le cas de EOF, le délimiteur de ligne est inclus dans le tampon renvoyé par read (2).

Le code indique clairement qu’il utilise un mode non canonique (c’est-à-dire un mode incorrect):

 // Set raw input (non-canonical) mode, with reads blocking until either // a single character has been received or a one second timeout expires. // See tcsetattr(4) ("man 4 tcsetattr") and termios(4) ("man 4 termios") // for details. cfmakeraw(&options); options.c_cc[VMIN] = 1; options.c_cc[VTIME] = 5; 

Vous devez supprimer ces lignes pour obtenir le mode canonique et lire des lignes au lieu d’octets bruts.

Si vous vous attendez à ce que read () renvoie des lignes complètes, le programme devra alors attendre une entrée. Cela signifie que vous devez bloquer les E / S.

 // The O_NONBLOCK flag also causes subsequent I/O on the device to // be non-blocking. // See open(2) ("man 2 open") for details. fileDescriptor = open(deviceFilePath, O_RDWR | O_NOCTTY | O_NONBLOCK); 

L’option O_NONBLOCK doit être supprimée de l’appel système open () .

En dépit de ce que trois commentateurs au moins ont écrit, un port série Linux peut être configuré pour lire les lignes. Vous utilisez un système d’exploitation réel et ne faites pas fonctionner du métal nu sur un microprocesseur. Il vous suffit d’activer la discipline de ligne pour numériser les caractères reçus par le port série.
Vous trouverez des informations complètes sur la programmation en mode canonique dans le Guide de programmation en série pour les systèmes d’exploitation POSIX et dans la page de manuel termios .

Il existe également plusieurs problèmes avec votre code qui doivent être corrigés:

  • Au lieu de memset(&options, 0, sizeof(options)) le code devrait appeler tcgetattr () pour initialiser correctement la structure. Cela peut poser un problème sérieux pour l’entrée canonique, car le code existant aura mis à zéro toutes les spécifications du code de contrôle au lieu d’avoir les définitions appropriées.
  • Au lieu d’assignations directes, le code doit effectuer des opérations sur bits (afin de conserver les parameters existants). Voir Configuration correcte des modes de terminal .
  • L’instruction read(fileDescriptor, buf, 1000) doit être développée pour gérer les erreurs éventuelles et traiter les données reçues.
    • le code de retour de l’appel système () read () doit être vérifié pour détecter toute condition d’erreur.
    • lorsqu’aucune erreur n’est détectée, le code de retour indique le nombre d’octets renvoyés dans la mémoire tampon. Notez que l’entrée ne sera pas terminée par un octet null. Par conséquent, les opérations sur les chaînes ne doivent pas être appliquées à la mémoire tampon tant qu’une valeur null n’a pas été ajoutée.

Le code lu devrait ressembler à quelque chose comme:

  rc = read(fileDescriptor, buf, sizeof(buf) - 1); if (rc < 0) { /* handle error condition */ } else { buf[rc] = '\0'; printf("%s", buf); } 

Buf [] étant alloué pour 1000 octets, la requête read () peut renvoyer une ligne comportant jusqu'à 999 caractères.

Le problème est que vous lisez un nombre arbitraire d’octets, puis les sortez en les séparant par une nouvelle ligne:

 read(fileDescriptor, buf, 1000); printf("%s\n", buf); 

Vous avez ouvert le descripteur O_NONBLOCK et je ne suis pas sûr que votre appel fcntl est suffisant pour l’effacer. Le résultat est que read extrait quel que soit le nombre de caractères mis en mémoire tampon à ce moment-là, puis vous les imprimez suivis d’une nouvelle ligne.

Vous ne voulez probablement pas read en mode bloquant, car il se peut qu’il ne retourne pas avant que 1 000 caractères soient lus. Cela peut être plus proche de ce que vous voulez:

 amt = read(fileDescriptor, buf, 1000); if (amt > 0) write(1,buff,amt); else break; 

Bien sûr, il devrait y avoir beaucoup plus de traitement des erreurs.