cunixserial-porttermiosbaud-rate

I/O error when trying to change serial baud in C program after changing with unix STTY


I noticed something weird yet reproducible.

I first check my serial port settings:

    bash-3.1# stty -F /dev/ttyS0
    speed 0 baud; line = 0;
    intr = <undef>; quit = <undef>; erase = <undef>; kill = <undef>; eof = <undef>; start = <undef>;
    stop = <undef>; susp = <undef>; rprnt = <undef>; werase = <undef>; lnext = <undef>; flush = <undef>;
    min = 1; time = 0;
    -cread
    -brkint -icrnl -imaxbel
    -opost -onlcr
    -isig -icanon -iexten -echo -echoe -echok -echoctl -echoke

Then change speed to 1200bps:

bash-3.1# stty -F /dev/ttyS0 1200

I then execute this fragment of my program in a function to change the baud:

fd=open(dev,O_NOCTTY | O_NONBLOCK | O_RDWR);
struct termios ser[1];
tcflush(fd,TCIFLUSH);
tcflush(fd,TCOFLUSH);
cfmakeraw(ser);
 // I call tcsetattr after each terminal setting to make sure its applied.
if (tcsetattr(fd,TCSANOW,ser) < 0){
    return -1;
}
cfsetspeed(ser,B9600);
if (tcsetattr(fd,TCSANOW,ser) < 0){
  return -2; //returns this after manually setting port via STTY
}

The problem is the baud rate does NOT get changed properly. In fact, I get -2 returned from the function and strerror(errno) returns "input/output error".

After program execution, I check system port settings:

bash-3.1# stty -F /dev/ttyS0
speed 0 baud; line = 0;
intr = <undef>; quit = <undef>; erase = <undef>; kill = <undef>; eof = <undef>; start = <undef>;
stop = <undef>; susp = <undef>; rprnt = <undef>; werase = <undef>; lnext = <undef>; flush = <undef>;
min = 1; time = 0;
-cread
-brkint -icrnl -imaxbel
-opost -onlcr
-isig -icanon -iexten -echo -echoe -echok -echoctl -echoke

And it resets to zero bps even though I specifically asked for 9600bps.

Why does it do that? and how do I force the speed to go to 9600bps programmatically?


Solution

  • You have many mistakes in your code.

    A proper way to do should be something (copied from your snippet and edited) like this:

    #include <string.h> /* for strerror */
    #include <errno.h> /* for errno definition */
    
    /* ... */
    
    fd=open(dev,O_NOCTTY | O_NONBLOCK | O_RDWR);
    if (fd < 0) {
        fprintf(stderr, "OPEN: %s (errno = %d)\n",
            strerror(errno), errno);
        return -1;
    }
    struct termios ser; /* why an array? */
    tcflush(fd,TCIFLUSH); /* unneeded, you have not used the tty yet */
    tcflush(fd,TCOFLUSH); /* idem. */
    /******* THIS IS THE MOST IMPORTANT THING YOU FORGOT ***********/
    int res = tcgetattr(fd, &ser); /* *****this initializes the struct termios ser***** */
    if (res < 0) {
        fprintf(stderr, "TCGETATTR: %s (errno = %d)\n",
            strerror(errno), errno);
        return -2; /* cannot tcgetattr */
    }
    /***************************************************************/
    cfmakeraw(&ser); /* now it is valid to set it */
    cfsetspeed(&ser,B9600);  /* better do all in only one system call */
    // I call tcsetattr after each terminal setting to make sure its applied.
    /* nope, tcsetattr and tcgetattr are the only calls that make something 
     * on the tty, the rest only manipulate bits on the struct termios
     * structure, but don't do anything to the terminal, you begun with a
     * trashed struct termios, so it's normal you end with an error. */
    if ((res = tcsetattr(fd, TCSANOW, &ser)) < 0){
        fprintf(stderr, "ERROR: %s (errno = %d)\n",
            strerror(errno), errno); /* better to know what happened. */
        return -3; /* couldn't tcsetattr */
    }
    

    finally, this code (as yours first) has not been tested, mainly because you didn't posted a complete, minimal and verifiable example. So you'll probably need to rework it a little before including it in your code. And please RTFM (the last meaning to read termios(3) completely, and most important: How to create a Minimal, Complete, and Verifiable example) :). Also, don't check tty settings on stdin if you are using bash(1), as it normally restores tty settings after command exit, before issuing the prompt.