POSIX read() call remains blocked forever regardless of what's set in VTIME

Viewed 160

I need to configure the UART settings such that the read() call remains blocked until a certain time before it's unblocked again if it didn't receive any data within the timeout. So if the timeout is 5 seconds, it remains blocked till 5 seconds max if it doesn't receive any byte and then unblocks...

I tried using VMIN which should block the read() call until no character is read within the time allowed, after which the call to read() returns 0, but that doesn't seem to be the case for me: the read() call remains blocked forever and as soon as I enter stuff in a minicom serial session, read() unblocks and then goes back to getting blocked again.

I'm not sure if it's taking into account VTIME setting, or maybe I'm misconfiguring it.

Rather, would select() with a timeout be a better approach?

#define SERIAL_PORT     "/dev/ttyUSB4" 

pthread_t td;
int fd;

int SerialOpen()
{   
    struct termios term;
    
    fd = open(SERIAL_PORT, O_RDWR);
    if (fd < 0)
    {
        perror ("failed to open");
        return -1;
    }
    
    bzero(&term, sizeof(term));
    cfmakeraw(&term);
    
    term.c_cflag |= CREAD;
    tcgetattr(fd, &term);
    
    term.c_iflag &= ~ICRNL;
    term.c_iflag &= ~INLCR;
    term.c_iflag &= ~IGNBRK;

    term.c_oflag &= ~OCRNL;
    term.c_oflag &= ~ONLCR;
    term.c_oflag &= ~OPOST;

    term.c_lflag &= ~ICANON;
    term.c_lflag &= ~ISIG;
    term.c_lflag &= ~IEXTEN;
    term.c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHONL|ECHOCTL|ECHOPRT|ECHOKE);
    
    cfsetspeed(term, B115200);      // set baud rate
    term.c_cflag &= ~CSTOPB;
    term.c_cflag |= CS8;
    
    // disable flow control
    term.c_cflag &= ~CRTSCTS;           
    term.c_iflag &= ~(IXON | IXOFF);
    
    term.c_cflag &= ~PARENB;       // no parity
    
    term.c_cc[VTIME] = 50;    // Wait for up to 5s (50 deciseconds), returning as soon as any data is received.
    term.c_cc[VMIN] = 0;

    if ( (tcsetattr(fd, TCSANOW, &term)) < 0)
    {
        perror ("Failed to set attr");
        return -1;
    }
    return 1;
}


void *Rx(void *arg)
{
    char buff[100] = {0};
    
    while(1)
    {
        int sz = read(fd, buff, sizeof(buff)); // block until VTIME times out
        if (sz < 0)
        {
            perror ("Read failed");
        }
        printf ("Received bytes %d:  %s\n", sz, buff);
    }
}

int main() 
{
    int ret = SerialOpen();
    if (ret < 0)
    {
        return -1;
    }

    if (pthread_create(&td, NULL, Rx, NULL) != 0)
    {
        printf("Fail to create thread!\n");
    }
    
    pthread_join(td, 0);

    return 0;
}
1 Answers

Caveat: Prefaced by the top comments.

Be sure you're applying the fixes I suggested in the comments above.

select may have the same issues as VMIN/VTIME. select operates at a level after the TTY layer [which is where VMIN/VTIME operate]. At a lower level is the USB driver. If the TTY layer isn't set up correctly, select may have the same issues as read (i.e. probably not the issue). Although, you might do: open(/dev/whatever,O_RDWR | O_NONBLOCK)

But, I suspect that the issue is at a more fundamental/lower level. I think the USB layer/level is part of the problem.

USB-to-RS232 cables are tricky. The cable may need active H/W flow control enabled to operate. So, just disabling H/W flow control via (e.g.) clearing CRTSCTS may not work too well.

Double check: Are you sure that you are getting all the way through the configuration/initialization of the device. Could the open be hanging? You could add debug printf statements throughout the code [easier than using gdb for this].

What is the specific manufacturer and device for the USB cable? What is the specific manufacturer/model of the end/UART device? Although the standard/generic USB serial driver (e.g. usbserial) should work, some vendors need a specific driver (e.g. FTDI has its own USB level driver). So, you may need to consult the manufacturer's website/datasheet for the specific devices in question.

Also, sometimes a USB cable shows up on two different /dev/tty* devices. You may have the wrong one. You may need ttyS* instead of ttyUSB* or the device can show up as (e.g.) ttyACM* !!! This can occur even if ttyS* or ttyUSB* are [also] present.

Look at:

  1. ls -l /dev
  2. /proc/devices
  3. /sys/devices
  4. lsmod
  5. lsusb
  6. lspci
  7. dmesg [and, maybe, syslog output]

Did you [or the system] modprobe usbserial?

Specifically, the dmesg output should say which tty* the device is connected to.

Some resources (from a "all the words" websearch on linux /dev USB uart cable):

  1. https://www.cyberciti.biz/faq/find-out-linux-serial-ports-with-setserial/
  2. https://unix.stackexchange.com/questions/81754/how-to-match-a-ttyusbx-device-to-a-usb-serial-device
Related