Re: Can't read from serial port after RedHat reboot

From: Floyd L. Davidson (floyd_at_barrow.com)
Date: 03/28/04


Date: Sun, 28 Mar 2004 01:57:36 -0900

murthysuj@aol.com (Murthysuj) wrote:
>> floyd@barrow.com (Floyd L. Davidson) wrote:
>
>>And don't feel bad about posting horrible looking code either...
>Here it comes the ugly unformatted code. Hopefully it should compile. There is
>not much to it. I just open, read and write. There are some options for speed,
>flowcontrol and parity.

I'll trim away most of this to keep it from getting too long.

>int Fd, Speed, Parity, FlowControl, InputParity, ReadBlock;
>ReadBlock=0;
...
>main(char ** argv)

Lets hope that is a typo. :-)

>{
> Fd = open(argv[1], O_RDWR | O_NOCTTY | O_NDELAY);
> if (Fd == -1)
> {
> printf("open_port: Unable to open %s",argv[1]);
> exit(-3);

The value for exit should be between 0 and 255. It is used as an
8 bit unsigned integer type, so negative values are not useful.
You'll find that -1 is 255, -2 is 254, and -3 is 253.

> }
> if (ReadBlock) fcntl(Fd, F_SETFL, 0);
> else fcntl(Fd, F_SETFL, FNDELAY);

The above doesn't do much of anything when ReadBlock evaluates to
true, since you've already opened the port with the O_NDELAY flag.

> set_port();
> /* do read and write */
>}
>void set_port(void)
>{
> struct termios options;
...
> tcgetattr(Fd, &options);
...
> options.c_cflag |= (CLOCAL | CREAD);

Note that by OR'ing in a bit value, you leave all other values
untouched. And that you have no idea how the last program to
use the serial port might have left them configured.

If we look just at the c_cflag member, your code sets many of
the values, but does not touch CRTSCTS (hardware flow control).
(Also note that your even parity and odd parity options are
giving you the same settings as no parity, which is probably
just a editing error for this example.)

If we look at the c_iflag member, there are *many* possible
options that will cause your program to fail, none of which are
set or cleared and hence might be in almost any possible state.

You did not set any of the c_oflag and c_lflag options, and again
several of them can make your program fail to function if they just
happen to be in an incompatible state when you open the port.

...

> /*
> * Set the new options for the port...
> */
> tcsetattr(Fd, TCSANOW, &options);
>}

So that is no doubt the basis for most if not all of the problems
you are having. The fix is to configure *every* single bit in the
termios struct to be something you know positively will work with
your application. That is facilitated somewhat by the fact that
most often what we want is a 0 value, so we can zero everything and
then set bits as necessary.

The above comes with one hitch. POSIX says that a termios
struct *must* be initialized using tcgetatter() as you have
done. I've often enough just used memset() to zero the whole
thing and have never had a problem, but every time I post
something like that to Usenet someone correctly points out that
it is not according to Hoyle (spelled P O S I X)! :-) So I quit
recommending that. Instead, each member of the termios struct
should be explicitely set in a manner that clears all bits not
desired to be set. You can modify your code to do that easily
enough. This statement

> options.c_cflag |= (CLOCAL | CREAD);

should be

        options.c_cflag = (CLOCAL | CREAD);

And then you can OR in other bits as desired. You should also, at
a minimum, have statements like these:

        options.c_iflag = 0;
        options.c_oflag = 0;
        options.c_lflag = 0;

Obviously you might want to set certain bits in each. But if
not, then each should be zeroed as shown.

Plus there is one other concern. The POSIX specification for termios
is a minimum specification, and implementations are free to add other
members as necessary. And in fact Linux does have an additional member
that POSIX does not mention. The line discipline flag is a separate
member, and it *must* be set correctly or your application simply will
not work (for example, if you run pppd on the serial port and kill it
with SIGKILL, leaving the line discipline set for PPP, you'll think
your port is dead when trying to access it as a normal TTY port).

Here is a skeleton of the functions that I use to configure
serial ports. This one is for raw io.

#include <sys/ioctl.h> /* defines N_TTY */
#include <termios.h>

#define BAUD B57600 /* B9600, B38400, B57600, B11500... */
#define BITS CS8 /* CS5, CS6, CS7, CS8 */
#define PARITY IGNPAR /* IGNPAR, PARENB, PARENB | PARODD */
#define FLOWCTL CRTSCTS /* CRTSCTS, IXON | IXOFF | IXANY */
#define CTLLOCAL 0 /* CLOCAL or 0 */

#define CFLAGS (FLOWCTL | BITS | CTLLOCAL | CREAD)
#define IFLAGS (IGNBRK | PARITY)

int serial_cnfg(int fd)
{
  struct termios tty;

  tcgetattr(fd, &tty);

  /* raw io, hardware flow control, 8n1 */
  tty.c_iflag = IFLAGS; /* input flags */
  tty.c_cflag = CFLAGS; /* control flags */
  tty.c_lflag = 0; /* local flags */
  tty.c_oflag = 0; /* output flags */
  tty.c_cc[VMIN] = 1; /* wait for 1 character */
  tty.c_cc[VTIME] = 0; /* turn off timer */

#ifdef __linux__
  /* for linux only */
  tty.c_line = N_TTY; /* set line discipline */
#endif

  cfsetospeed(&tty, BAUD); /* set bit rate */
  cfsetispeed(&tty, BAUD);

  if (tcsetattr(fd, TCSADRAIN, &tty)) {
    perror("tcsetattr");
    return -1;
  }

  return 0;
}

--
Floyd L. Davidson           <http://web.newsguy.com/floyd_davidson>
Ukpeagvik (Barrow, Alaska)                         floyd@barrow.com


Relevant Pages