3

I'm have a Linux application that is supposed to read from serial device /dev/ttyS0. The serial device is opened in the following manner:

// Open the serial port
if((serial_device = open("/dev/ttyS0", O_RDWR | O_NOCTTY)) < 0){
    fprintf(stderr, "ERROR: Open\n");
    exit(EXIT_FAILURE);
}

// Get serial device attributes
if(tcgetattr(serial_device,&options)){
    fprintf(stderr, "ERROR: Terminal Get Attributes\n");
    exit(EXIT_FAILURE);
}

cfsetspeed(&options,speed);             // Set I/O baud rates
cfmakeraw(&options);                    // Set options to transceive raw data
options.c_cflag |= (CLOCAL | CREAD);    // Enable the receiver and set local mode
options.c_cflag &= ~CSTOPB;             // 1 stop bit
options.c_cflag &= ~CRTSCTS;            // Disable hardware flow control
options.c_cc[VMIN]  = 1;                // Minimum number of characters to read
options.c_cc[VTIME] = 10;               // One second timeout

// Set the new serial device attributes
if(tcsetattr(serial_device, TCSANOW, &options)){
    fprintf(stderr, "ERROR: Terminal Set Attributes\n");
    exit(EXIT_FAILURE);
}

I then use the select function to try and read from the serial device:

// Flush I/O Bffer
if(tcflush(serial_device,TCIOFLUSH)){
    fprintf(stderr, "ERROR: I/O Flush\n");
    exit(EXIT_FAILURE);
}
// Write message to board
if(write(serial_device,msg, strlen(msg)) != (int)strlen(msg)){
    fprintf(stderr, "ERROR: Write\n");
    exit(EXIT_FAILURE);
}


switch(select(serial_device+1, &set, NULL, NULL, &timeout)){
    // Error
    case -1:
        fprintf(stderr, "ERROR: Select\n");
        exit(EXIT_FAILURE);
    // Timeout
    case 0:
        success = false;
        break;
    // Input ready
    default:
        // Try to read a character
        switch(read(serial_device, &c, 1)){
            // Error (miss)
            case -1:
                success = false;
                break;
            // Got a character
            default:
                msg[i++] = c;
                break;
        }
        break;
    }
    // Set 200ms timeout
    this->timeout.tv_sec = 0;
    this->timeout.tv_usec = 200000;
}

I've tried reopening the port by determining if the read was not successful:

if(!success)
    close(serial_device);
    openPort(); // Same as above
}

However, the act of physically unplugging the serial connector will result in the application being unable to read anything further, and select will do nothing but time out. Plugging the connector back in while the application is running will not fix the issue, and select will continue to detect nothing.

The only way to successfully read from the serial port again is to restart the application. I'm wondering why this is, and how I can recover from the serial connector being unplugged at runtime.

sj755
  • 3,944
  • 14
  • 59
  • 79
  • 2
    It's hard to know if the problem is that your host stops receiving or the board stops transmitting. An RS-232 diagnostic dongle with LEDs showing activity can help in cases like this. I do notice that you're not checking for EOF (`read` returns 0). Perhaps that could be an issue? – Celada Jun 24 '13 at 16:38
  • 2
    "`success = failure`" is quite a humorous line of code! – Celada Jun 24 '13 at 16:38
  • 2
    is this a usb rs-232 dongle? – dbasnett Jun 24 '13 at 16:40
  • @Celada Whoops, typo. I meant to type `success = false` – sj755 Jun 24 '13 at 18:00
  • @Celada Thanks for the input. Could you elaborate about how the EOF is handled? I set the attributes of the file descriptor to read raw data, so I figure even an EOF character should at least be recognized. Also, while debugging (after I replugged the RS-232 dongle), the select function is returning on a timeout, indicating that nothing was in the buffer. – sj755 Jun 24 '13 at 18:05
  • 1
    I know that in raw mode EOF should never be returned, but I recommend programming defensively and handling that case (at least just to log an error) anyway. If, for whatever reason, an EOF is being delivered at some earlier point (e.g. immediately when the serial cable is disconnected) it could explain why no further data are received. – Celada Jun 24 '13 at 18:12
  • 1
    You set `success = false;` but never `success = true;`, so once an error occurs, you appear forever un-successful. – chux - Reinstate Monica Jun 24 '13 at 18:36
  • @chux Sorry, this was just a shell of what is actually in my code. The code to determine a successful/unsuccessful read is present and seems to work, its just not shown in the post. My main concern is why nothing is no further input is being received. – sj755 Jun 24 '13 at 18:38
  • @Celada I read up a bit on the EOF character in canonical input mode, and it causes `read` to return a byte count of zero. However, is it also doing the same to the `select` function? How would I get rid of this EOF character if it was received? – sj755 Jun 24 '13 at 18:48
  • 1
    *"unplugging the serial connector"* - Are you referring to the DB9 side or the USB side of the USB-to-RS232 adapter? – sawdust Jun 24 '13 at 19:20
  • @sawdust It's an old-fashioned DB9 dongle on both ends. However, my application will be cross-compiled for an embedded device, which will have to use a USB-to-RS232 connection. – sj755 Jun 24 '13 at 19:25
  • 1
    @Celada and OP: `read()` returns EOF due to an I/O error, I believe, even in raw mode. `errno` is then set. Perhaps noting `errno` and clearing will help? Connecting/disconnecting the serial interface certainly can cause I/O errors (framing, etc.) – chux - Reinstate Monica Jun 24 '13 at 19:37
  • @chux Thanks for the tip. Could you explain what you mean by clearing? I'm still unsure how to deal with an EOF. – sj755 Jun 24 '13 at 19:55
  • 1
    *"It's an old-fashioned DB9 dongle*" -- But that's not the response you gave to @dbasnett. Is the `select()` actually waiting on just one fd, or is that just the reduced code that you're posting? – sawdust Jun 24 '13 at 20:10
  • @sawdust Sorry about that, I misread his question and thought he was asking if it was just an RS-232 dongle and didn't see the "usb" part of it. `select` is only waiting on a single file descriptor. That part of the code is present in the real thing. – sj755 Jun 24 '13 at 20:15
  • @dbasnett Sorry, gave you the wrong answer. It's not a USB-to-RS232 dongle. It's DB9 on both ends, but I'm hoping to start using USB-to-RS232 eventually. – sj755 Jun 24 '13 at 20:17
  • 1
    Please explain the rationale for using `select()` with just one fd. With proper selection of [VMIN and VTIME](http://www.unixwiz.net/techtips/termios-vmin-vtime.html), you might be able to accomplish the read of a char at a time with simpler code. E.G. try VMIN = 1 and VTIME = timeout.tv_sec/10 – sawdust Jun 24 '13 at 21:36
  • @sawdust I tried that, but I couldn't get it to work. The code you see for opening the serial port is the actual code. I set a VMIN and a VTIME, but for some reason read still blocks. – sj755 Jun 24 '13 at 22:03
  • 1
    What does "not work" mean? *"Still blocks"* on every `read()` request or when connection is broken? How does that code perform differently compared to the code with `select()`? You still haven't explained the rationale for using select() with just one fd. Also, try adding the `O_NONBLOCK` to the `open()` flags as in [this code](http://stackoverflow.com/questions/12437593/how-to-read-a-binary-data-over-serial-terminal-in-c-program/12457195#12457195). – sawdust Jun 25 '13 at 07:20
  • @sawdust Sorry for not being more clear. I meant it blocks when the connection is broken. After reading through APUE I found that my mistake was seting cc[VMIN] to 1. It seems that with MIN>0, the timer won't start until it reads at least one character. With MIN==0, the timer will start as soon as the `read()` operation starts. I'm still not sure why select wouldn't work though. I greatly appreciate your help, could you please put your comments in the form of an answer. – sj755 Jun 25 '13 at 13:08

1 Answers1

1

The use of select() with just one file descriptor is unusual. It also adds a level of complexity.
Since the serial port is configured for non-canonical input, with proper selection of VMIN and VTIME, you might be able to accomplish the read of a char at a time with simpler code. E.G. try VMIN = 1 and VTIME = 10*timeout.tv_sec

However as you figured out, and if you are willing to handle (or want) a timeout rather than wait for at least one character to arrive, then VMIN = 0 will emulate your original code with the select().

VMIN = 0 and VTIME > 0
This is a pure timed read. If data are available in the input queue, it's transferred to the caller's buffer up to a maximum of nbytes, and returned immediately to the caller. Otherwise the driver blocks until data arrives, or when VTIME tenths expire from the start of the call. If the timer expires without data, zero is returned. A single byte is sufficient to satisfy this read call, but if more is available in the input queue, it's returned to the caller. Note that this is an overall timer, not an intercharacter one.

However (like the OP) I'm baffled as to why reconnecting the port connector should disrupt any reads or select monitoring, and have never encountered such an issue.

sawdust
  • 16,103
  • 3
  • 40
  • 50