15

I am reading bytes from a serial port in C++ using a file descriptor and the posix/unix read() function. In this example, I am reading 1 byte from the serial port (baud rate settings and similiar are omitted for clarity):

#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;
}

If the device connected to /dev/ttyS0 does not send any information, the program will hang. How can I set a timeout?

I have tried setting a time out like this:

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

I thought it was supposed to give 1 second timeout, but it makes no difference. I think I have misunderstood VMIN and VTIME. What is VMIN and VTIME used for?

Then I searched the web and found somebody talking about the select() function. Is that the solution and if so, how would one apply that to the program above to make 1 second timeout?

Any help is appreciated. Thanks in advance :-)

pvh1987
  • 621
  • 5
  • 8
  • 12
  • Using `tcsetattr()` of `VTIME` is not straightforward; it requires other mode settings which some serial drivers do not support. See my answer for a general solution. – wallyk May 09 '12 at 19:50
  • This is the best explanation I've come across on the web for VMIN and VTIME [http://unixwiz.net/techtips/termios-vmin-vtime.html](http://unixwiz.net/techtips/termios-vmin-vtime.html). According to the article when ICANON bit is turned off it enables a "raw mode" changing the interpretation of VMIN and VTIME. Setting the ICANON bit would make the code work as expected. – arpl Mar 31 '16 at 18:17
  • Generally when `options.c_cc[VMIN] = 0;` `options.c_cc[VTIME] = 10;` are set. `read()` should return whenever one or more bytes are available, or it times out. In the latter case read should indicate that 0 bytes have been read. – hetepeperfan May 27 '19 at 15:03

5 Answers5

24

Yes, use select(2). Pass in a file descriptor set containing just your fd in the read set and empty write/exception sets, and pass in an appropriate timeout. For example:

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
}
Adam Rosenfield
  • 390,455
  • 97
  • 512
  • 589
  • Thanks a lot. That seems to work just like i wanted it to. I have three more questions: Can I use select to make a timeout when opening a port as well? Can I use the same code for a timeout on the write operation, by setting FD_SET(fd, &write_fds)? And last: What is except_fds used for? – pvh1987 May 16 '12 at 13:12
  • 2
    poll() is generally better than select(). – Martin C. Martin Mar 11 '14 at 19:38
  • 1
    Works great! If I run this in a loop, do I need to FD_ZERO and FD_SET in every iteration, or is once before the loop ok? – Andy Feb 19 '18 at 23:32
3

What is VMIN and VTIME used for?

If MIN > 0 and TIME = 0, MIN sets the number of characters to receive before the read is satisfied. As TIME is zero, the timer is not used.

If MIN = 0 and TIME > 0, TIME serves as a timeout value. The read will be satisfied if a single character is read, or TIME is exceeded (t = TIME *0.1 s). If TIME is exceeded, no character will be returned.

If MIN > 0 and TIME > 0, TIME serves as an inter-character timer. The read will be satisfied if MIN characters are received, or the time between two characters exceeds TIME. The timer is restarted every time a character is received and only becomes active after the first character has been received.

If MIN = 0 and TIME = 0, read will be satisfied immediately. The number of characters currently available, or the number of characters requested will be returned. According to Antonino (see contributions), you could issue a fcntl(fd, F_SETFL, FNDELAY); before reading to get the same result.

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

ceyun
  • 351
  • 4
  • 14
1

You can attempt capture signal to stop read operation. use alarm(1) before read, and if read function did not returned, alarm will send SIGALRM signal, then you can create signal processing function to capture this signal, like this:

#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;
}

But use select or poll or epoll will be better if your program is big.

vincent
  • 138
  • 4
  • There is a interesting case about select, it check the fd can be write, but It will be block when write, so I think the select method can't works fine in my case, but your method can works fine. it's a good method. – kangear Feb 01 '15 at 07:49
0

select() is the way I would solve this problem.

There are several pages on the internet that will give info on how to use select(), such as http://www.unixguide.net/unix/programming/2.1.1.shtml

JoeyG
  • 313
  • 2
  • 5
0

There are several possible approaches. If the program will eventually be timing more than one i/o operation, select() is the clear choice.

However, if the only input is from this i/o, then selecting non-blocking i/o and timing is a straightforward method. I have expanded it from single character i/o to multi-character to make it a more generally complete example:

#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;
}
wallyk
  • 56,922
  • 16
  • 83
  • 148
  • This is a lot more code than using select(), and can wait up to a second after data is available before it's read. – Martin C. Martin Mar 11 '14 at 19:35
  • 1
    @MartinC.Martin: Well, it may look that way because my code is a complete working example including reading and error checking whereas the select example is showing only snippet of what would be needed. – wallyk Mar 11 '14 at 21:01
  • There might be a rush of data during 1 second sleep (especially in high baud rates), so it is not a good idea to use sleep. Shorter sleeps could also waste processor time which matters more in low-powered devices. This code could be improved by taking a non-polling apporach using `select`. – Isaac Sep 11 '14 at 16:49