2012-05-09 11 views
13

Sto leggendo i byte da una porta seriale in C++ usando un descrittore di file e la funzione posix/unix read(). In questo esempio, sto leggendo 1 byte dalla porta seriale (impostazioni baud rate e simile sono omessi per chiarezza):Come posso implementare il timeout per read() durante la lettura da una porta seriale (C/C++)

#include <termios.h> 
#include <fcntl.h> 
#include <unistd.h> 

int main(void) 
{ 
    int fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY); 
    char buf[1]; 
    int bytesRead = read(fd, buf, 1); 
    close(fd); 
    return 0; 
} 

Se il dispositivo collegato alla/dev/ttyS0 non invia alcuna informazione, il programma appendere. Come posso impostare un timeout?

Ho provato a fissare un momento come questo:

struct termios options; 
tcgetattr(fd, &options); 
options.c_cc[VMIN] = 0; 
options.c_cc[VTIME] = 10; 
tcsetattr(fd, TCSANOW, &options); 

ho pensato che avrebbe dovuto dare 1 secondo timeout, ma non fa alcuna differenza. Penso di aver frainteso VMIN e VTIME. Per cosa vengono utilizzati VMIN e VTIME?

Quindi ho cercato sul web e ho trovato qualcuno che parla della funzione select(). È questa la soluzione e, in caso affermativo, come si applicherebbe questo al programma di cui sopra per fare un secondo timeout?

Qualsiasi aiuto è apprezzato. Grazie in anticipo :-)

+0

Usando 'tcsetattr()' di 'VTIME' non è semplice; richiede altre impostazioni di modalità che alcuni driver seriali non supportano. Vedi la mia risposta per una soluzione generale. – wallyk

+0

Questa è la migliore spiegazione che ho trovato sul web per VMIN e VTIME [http://unixwiz.net/techtips/termios-vmin-vtime.html](http://unixwiz.net/techtips/termios- Vmin-vtime.html). Secondo l'articolo quando il bit ICANON è disattivato abilita una "modalità raw" che modifica l'interpretazione di VMIN e VTIME. L'impostazione del bit ICANON renderebbe il codice funzionante come previsto. – arpl

risposta

16

Sì, utilizzare select(2). Passa in un set di descrittori di file contenente solo il tuo fd nel set di lettura e nei set vuoto di scrittura/eccezione e passa in un timeout appropriato. Ad esempio:

int fd = open(...); 

// Initialize file descriptor sets 
fd_set read_fds, write_fds, except_fds; 
FD_ZERO(&read_fds); 
FD_ZERO(&write_fds); 
FD_ZERO(&except_fds); 
FD_SET(fd, &read_fds); 

// Set timeout to 1.0 seconds 
struct timeval timeout; 
timeout.tv_sec = 1; 
timeout.tv_usec = 0; 

// Wait for input to become ready or until the time out; the first parameter is 
// 1 more than the largest file descriptor in any of the sets 
if (select(fd + 1, &read_fds, &write_fds, &except_fds, &timeout) == 1) 
{ 
    // fd is ready for reading 
} 
else 
{ 
    // timeout or error 
} 
+0

Grazie mille. Sembra funzionare proprio come volevo. Ho altre tre domande: Posso usare select per fare un timeout quando apro una porta? Posso usare lo stesso codice per un timeout sull'operazione di scrittura, impostando FD_SET (fd, & write_fds)? E ultimo: a cosa serve l'eccezione except_fds? – pvh1987

+2

poll() è generalmente migliore di select(). –

+0

Funziona alla grande! Se eseguo questo in un ciclo, ho bisogno di FD_ZERO e FD_SET in ogni iterazione, o è una volta prima del ciclo ok? – Andy

0

Ci sono diversi approcci possibili. Se il programma finirà per cronometrare più di una operazione di i/o, select() è la scelta chiara.

Tuttavia, se l'unico input proviene da questo I/O, selezionare I/O non bloccante e il tempismo è un metodo semplice. Ho ampliato dal singolo carattere I/O multi-personaggio per renderlo un esempio più in generale completo:

#include <fcntl.h> 
#include <termios.h> 
#include <unistd.h> 
#include <sys/time.h> 

int main(void) 
{ 
    int fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_NDELAY); // sometimes "O_NONBLOCK" 
    char buf[10]; 
    int done = 0, inbuf = 0; 
    struct timeval start, now; 

    gettimeofday (&start, NULL); 
    while (!done) 
    { 
     int bytesRead = read(fd, &buf[inbuf], sizeof buf - inbuf); 
     if (bytesRead < 0) 
     { 
      error_processing_here(); 
      continue; 
     } 
     if (bytesRead == 0) // no data read to read 
     { 
      gettimeofday (&now, NULL); 
      if ((now.tv.sec - start.tv_sec) * 1000000 + 
       now.tv.usec - start.tv_usec > timeout_value_in_microsecs) 
      { 
       done = 2; // timeout 
       continue; 
      } 
      sleep(1); // not timed out yet, sleep a second 
      continue; 
     } 
     inbuf += bytesRead; 
     if (we have read all we want) 
      done = 1; 
    } 
    if (done == 2) 
     timeout_condition_handling(); 
    close(fd); 
    return 0; 
} 
+0

Questo è molto più codice rispetto all'uso di select(), e può attendere fino a un secondo dopo che i dati sono disponibili prima di essere letti. –

+0

@ MartinC.Martin: Beh, potrebbe sembrare così perché il mio codice è un esempio di lavoro completo che include la lettura e il controllo degli errori mentre l'esempio di selezione mostra solo frammenti di ciò che sarebbe necessario. – wallyk

+0

Ci potrebbe essere un afflusso di dati durante 1 secondo di sonno (specialmente in baud rate elevati), quindi non è una buona idea usare il sonno. Dormire più velocemente potrebbe anche sprecare tempo processore che è più importante nei dispositivi a bassa potenza. Questo codice potrebbe essere migliorato prendendo un apporach non di polling usando 'select'. – Isaac

1

È possibile tentare il segnale di acquisizione per interrompere l'operazione di lettura. utilizzare allarme (1) prima di leggere, e se la funzione di leggere non ha restituito, allarme trasmetterà il segnale SIGALRM, quindi è possibile creare la funzione di elaborazione del segnale per catturare questo segnale, in questo modo:

#include <stdio.h> 
#include <stdlib.h> 
#include <signal.h> 
#include <setjmp.h> 

static jmp_buf env_alarm; 

static void sig_alarm(int signo) 
{ 
    longjmp(env_alarm, 1); 
} 

int main(void) 
{ 
    int fd = open("/dev/ttyS0", O_RDWR | O_NOCTTY); 
    char buf[1]; 

    if (signal(SIGALRM, sig_alarm) == SIG_ERR) 
    { 
     exit(0); 
    } 

    if (setjmp(env_alarm) != 0) 
    { 
     close(fd); 
     printf("Timeout Or Error\n"); 
     exit(0); 
    } 

    alarm(1); 
    int bytesRead = read(fd, buf, 1); 
    alarm(0); 

    close(fd); 
    return 0; 
} 

Ma utilizzare selezionare o il polling o epoll sarà migliore se il tuo programma è grande.

+0

C'è un caso interessante su select, controlla che il fd possa essere scritto, ma sarà bloccato quando scrivi, quindi penso che il metodo select non funzioni bene nel mio caso, ma il tuo metodo può funzionare bene. è un buon metodo – kangear

1

Per che cosa vengono utilizzati VMIN e VTIME?

Se MIN> 0 e TIME = 0, MIN imposta il numero di caratteri per ricevere prima che la lettura sia soddisfatta. Quando TIME è zero, il timer non viene utilizzato.

Se MIN = 0 e TIME> 0, TIME funge da valore di timeout. La lettura sarà soddisfatta se viene letto un singolo carattere o viene superato il TEMPO (t = TIME * 0.1 s). Se TIME viene superato, nessun carattere verrà restituito.

Se MIN> 0 e TIME> 0, TIME funge da timer tra caratteri.La lettura sarà soddisfatta se vengono ricevuti caratteri MIN, oppure il tempo tra due caratteri supera il TEMPO. Il timer viene riavviato ogni volta un carattere viene ricevuto e diventa attivo solo dopo che è stato ricevuto il primo carattere .

Se MIN = 0 e TEMPO = 0, la lettura sarà soddisfatta immediatamente. Il numero di caratteri attualmente disponibile o il numero di caratteri richiesto verrà restituito. Secondo Antonino (vedi contributi), potresti rilasciare un fcntl (fd, F_SETFL, FNDELAY); prima di leggere per ottenere lo stesso risultato.

Fonte: http://tldp.org/HOWTO/Serial-Programming-HOWTO/x115.html

Problemi correlati