Re: Can't read a lonely (char)13 from serialport

From: Floyd L. Davidson (floyd_at_barrow.com)
Date: 03/07/05


Date: Mon, 07 Mar 2005 13:14:13 -0900

Anders Christensen <andersc1@hotmail.com> wrote:
>Hi NG,
>I'm polling a serialport from my C-program in the following manner:
> 1) I write() a command to the port
> 2) I read() a reply from the port
>
>Most of the time (when there is no data on the port) an ASCII-13 character
>is sent back in response to the command that I sent.

That does not appear to be what is happening. First, an
ASCII-13 character *is* data! (At least as far as the serial
port read() command is concerned it is.)

Second you are getting dozens of them from a single read() call.
Because of the way your program is written, we can't really tell
if each of those reads corresponds to a single write(), or if
the remote device is sending a burst of data ever 15th or 20th
time you write a command to it. In fact, it does look as if the
remote device might be accepting a command, and while processing
it each new command results in a newline being put into the
output buffer, and then when the command is finished it dumps
the entire output buffer, newlines and all, to the serial port.

But it could be a lot of other things too...

>The problem is, that when I do my read() nothing is found until there
>actually is data on the port. As a consequense, I receive a *lot* of
>ASCII-13 preceding the acual data.
>
>Any idea what I can do to get rid of the ASCII-13 from each read?

Send your command, and wait for an *appropriate* response before
sending another one.

>I paste my demoprogram and an output from the program below.
>
>./Anders.
>
>void open_nonblocking(int *hdl)
>{
> *hdl = open (PORT, O_RDWR | O_NOCTTY | O_NONBLOCK);
> if (*hdl == -1) {
> perror (PORT);
> exit (-1);

The argument to exit() must be an 8 bit unsigned char value.
Your -1 will be interpreted as 255, not -1.

> }
>
> tcgetattr(*hdl,&newtio);
>
> cfsetispeed(&newtio, BAUDRATE);
> cfsetospeed(&newtio, BAUDRATE);
>
> newtio.c_cc[VMIN] = 0;

Setting c_cc[VMIN] when using canonical input is not necessary.
It does nothing, so no harm is done.

> newtio.c_cflag = CS8 | CRTSCTS | CREAD | CLOCAL;
> newtio.c_iflag = IGNBRK | IGNPAR;
> newtio.c_oflag = OPOST | ONLCR;
> newtio.c_lflag = ICANON;
>
> tcflush(*hdl, TCIFLUSH);
>
> tcsetattr(*hdl,TCSANOW,&newtio);

You have not set newtio.c_line (a Linux only extension to struct
termios), and you've set ICANON for the c_lflag, but have left
the port in O_NONBLOCK mode.

If you want to use raw input mode (highly recommended for what
you are doing), I would suggest not setting c_lflag to ICANON,
and instead setting c_cc[VMIN] and c_cc[VTIME], plus clearing
the O_NONBLOCK from the port mode after it has been opened.

It might look something like this:

  int oldflags;

  /* open with O_NONBLOCK to ignore DCD line */
  *hdl = open(PORT, O_RDWR | O_NOCTTY | O_NONBLOCK);
  if (*hdl == -1) {
    perror("open:");
    exit(EXIT_FAILURE);
  }

  /* clear O_NONBLOCK to allow read() and write() to block */
  if ((-1 != (oldflags = fcntl(*hdl, F_GETFL, 0))) &&
      (-1 != fcntl(*hdl, F_SETFL, oldflags & ~O_NONBLOCK))) {

    /* flush input and output */
    if (0 != tcflush(*hdl, TCIOFLUSH)) {
      perror("tcflush:");
      exit(EXIT_FAILURE);
    }
  } else {
    perror("fcntl:");
    close(*hdl);
    exit(EXIT_FAILURE);
  }

Then you want to set c_cc[VMIN] and c_cc[VTIME] appropriately,
which depends on what type of read/write loop you want to use.
I would suggest setting VMIN to 0, and VTIME to some convenient
value, say 1 or 2. That assumes you might want to do something
else between poling the serial port, and that we want to be able
to easily recover from a lack of any response at all (with
blocking mode, you either have to test for data being available,
with select() before calling read(), set a timer to escape if no
response ever appears, or use async i/o triggered by a SIGIO
interrupt). All of these methods have their advantages and
disadvantages, and have different complexities for programming.
Perhaps choosing one or the others depends on what you are
willing to learn well enough to implement correctly more than
any differences in performance between different methods.

If you would like to look at some examples of each type of
serial i/o, there are a series of similar "terminal" programs
available on my web site, each demonstrating a different way to
read input from a serial port.

 http://web.newsguy.com/floyd_davidson/code/terminal/index.html

To set the c_line member of termios,

 #include <sys/ioctl.h> /* defines N_TTY */
 #ifdef __linux__
   /* for linux only */
   newtio.c_line = N_TTY; /* set line discipline */
 #endif

The reason for that is because programs such as pppd change
the c_line to a value which will make the port appear to hang
if we don't change it back. Your program will work for years,
and then one day you'll use it to open a serial port that has
just been used by pppd (and where pppd was killed with a SIGKILL),
and it won't work at all

>}
>
>int main () {
> int fd;
> int i, ret;
> char send_cmd[64];
> char buf[128];
> char bc[128];
> int counter;
>
> open_nonblocking(&fd);
> while(1) {
> strcpy(send_cmd,":15\r");
> write(fd, send_cmd, strlen(send_cmd));
> usleep(50000);
> ret = read (fd, buf, 127);

This is guaranteed to eventually be a problem. Without any
variation you 1) write a command, 2) wait 50 milliseconds, and
3) expect to find any and all data that the remote device is
going to send.

At some point the remote device is going to be slower than that,
and you will be mixing new commands and old responses! Indeed,
it appears that may be exactly what is causing the effects you
are seeing.

However, the "right" way is not obvious... in fact I have no
idea what will work best with that particular device. It might
be that you don't know either. Or can't know! We might have to
program for the worst case, or you might be able (depending on
what the device is and what happens when you simply ignore a
response, for example) to fudge a little and still recover from
"protocol errors".

For example, if your remote device sends you a nicely wrapped
packet of data that has a very clearly tagged beginning and end,
then you can definitely determine that you have acquired the
entire response. A simple timeout while you wait for that indicator
will do. But if instead there is no delimiter at all, and the
responses might be almost any length with almost any sort of delays
between characters... the only way to handle it is to set a maximum
wait time generous enough to never be wrong, and suffer the delays.

If ignoring a response is fatal (the patient can't breath, the
machine catches fire or sets off an alarm that brings the
police...), then the means of positively identifying a response
has to be very conservative. On the other hand if missing a
response merely means you have to resend the command to get what
you need, with no harm done, you can be very liberal in deciding
to toss whatever you have and do a restart.

Take a look at the demo programs in the URL above, and see if
any of those appear to match your needs. Or you might describe
in some detail the protocol and timing for the command response
sequence you are dealing with.

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



Relevant Pages

  • Re: Emails stuck in queue error 451 4.4.0 Primary target IP addres
    ... If you had just said "Siv you forgot to include the port number in the ... Telnet command" in your first response after asking me to carry out the ... I said earlier I am not familiar with telnet, I have probably used it twice ... Connecting To smtp.surfdsl.net...Could not open connection to the host, ...
    (microsoft.public.exchange.misc)
  • RE: ppp confusion
    ... tun0: Warning: papchap:: Invalid command ... acpi0: power button is handled as a fixed feature programming model. ... pcib0: port ...
    (freebsd-questions)
  • Re: Serial ports in a console application
    ... write to the COMM port on which I have a modem on this box. ... // Send a command and receieve a response. ...
    (microsoft.public.vc.language)
  • Re: LPT1 Port Prints to Network Printer
    ... Open a Command Prompt and key the command: ... LPT1 Port Prints to Network Printer ...
    (microsoft.public.win2000.printing)
  • Re: WANTED: "BASIC Forum" References
    ... The software used to drive a UART board cassette tape system  is ... Consider  first the problem of writing data to a cassette  tape. ... one a status port, ... use for the WAIT command. ...
    (comp.os.cpm)