Re: Problem to read on a serial port

From: Floyd L. Davidson (floyd_at_barrow.com)
Date: 04/01/05


Date: Thu, 31 Mar 2005 14:51:12 -0900


"collinm" <collinm@laboiteaprog.com> wrote:
>
>ya no problem, you can get the file:
>http://www.laboiteaprog.com/tmp.zip

Great! I'll only comment on the configuration code, and
wait until you get that working before we get into how to
read/write to the serial port.

I would suggest changing your Makefile a little...

  CC= /opt/cross-ppc/bin/powerpc-855-linux-gnu-gcc-3.3
  DEFS=
  PROGNAME= led_display
  INCLUDES=
  #LIBS= -lsxiodb -lsxio

  STRIP=/opt/cross-ppc/bin/powerpc-855-linux-gnu-strip

  GCC_WARN = -Wcast-align -Wcast-qual -Wmissing-prototypes \
               -Wshadow -Wnested-externs -Wstrict-prototypes \
               -Waggregate-return
  GCC_OPTIM = -O2
  GCC_SYMTAB = -ggdb
  GCC_ANSI = -std=c99
  GCC_PEDANT = -pedantic
  GCC_FLAGS = $(GCC_WARN) $(GCC_OPTIM) $(GCC_SYMTAB) $(GCC_ANSI) $(GCC_PEDANT)

  DEFINES= $(INCLUDES) $(DEFS)
  CFLAGS= -W -Wall $(DEFINES) $(GCC_FLAGS)

That will generate more warning messages, all of which should be
fixed.

In the code itself...

Use prototypes, and valid function declarations. For example,
you have "main ()", which should be "main (void)".

Function restoreSerialPortDevice() is unused, and that
functionality need not be implemented.

I would arrange your main() function to call functions:

 int readConfig(void)
      get environment variables
      print error messages
      return status

 int portOpen(char *ledisplay)
      open the serial port
      print error messages
      return file descriptor or -1

 int portConfig(int fd)
      configure the serial port
      print error messages
      return file descriptor or -1

 int ledConfig(int fd)
      configure the led display device
      print error messages
      return file descriptor or -1

 int ledCommand(int fd)
      command the led display device
      print error messages
      return file descriptor or -1

Note that none of the above ever close the serial port unless
there is a failure. Depending on how you want this program to
work, you can loop different subsets of the above list to get
the results you want. For example, with a one-shot program
there is no need for a loop at all. For a daemon program, and
particularly if it is only sporatically activated, you might
want to close the port after the ledCommand() function, and then
loop the entire set every time, and leave the port closed
between commands. Buy if there are many commands given in a
short period of time, there is no point in closing the serial
port between each of them.

Your ledConfig function might look something like this, where
each function is passed an open file descriptor, and returns
a true/false status.

int ledConfig(int fd)
{
  int rtrn = 0;

  rtrn = softReset(fd);

  if (!rtrn) {
    rtrn = clearMemory(fd);
    if (!rtrn) {
      rtrn = displayInitMsg(fd);
      if (!rtrn) {
        rtrn = setupMemory(fd);
        if (!rtrn) {
          rtrn = enableSpeaker(fd);
        }
      }
    }
  }
  return rtrn;
}

Here is an example function to open the serial port:

/*
 * Open a serial port, disregarding Carrier Detect Status
 *
 * Returns file descriptor, or -1 on error.
 */
int
portOpen(char *device)
{
  int fd, oldflags;

    /* O_NONBLOCK allows open when DCD line is not active */
  if (-1 != (fd = open(device, O_RDWR | O_NONBLOCK))) {

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

      /* flush input and output */
      if (0 == tcflush(fd, TCIOFLUSH)) {
        return fd;
      }
    }
    close(fd); /* open succeeded, something else failed */
  }

  return -1;
}

Your port configuration function needs to be changed.

    fd1 = open(ledisplay, O_RDONLY | O_NOCTTY | O_NDELAY );

As noted, use O_NONBLOCK rather than O_NDELAY.

    /* save current port settings */
    tcgetattr(fd1,&oldtio);

There is no point in saving the original port configuraton.

    /* set new port settings */

bzero(&newtio, sizeof(newtio));

Instead of bzero() you should use memset().

That works, but technically it isn't POSIXly correct. POSIX
says we must initialize the struct using tcgetattr(), and then
change each member individually. Which can be a *real* problem,
because POSIX allows implementations to add new members... and
we can never know if or what they are or what values (and
effects) they have.

Hence, I suspect that clearing the entire struct is actually
more portable than the POSIX way... and I'll show you a good
example of exactly why! The Linux implementation of termios has
a member c_line, which might be set to non-zero values by
programs such as pppd. Using bzero() to clear the entire struct
takes care of that, but if it is done "correctly" and code is
ported from a different unix system to Linux, c_line will not be
cleared and if it happens to be non-zero the program will fail!

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

Next, use these functions to set the bitrate. Changing the
c_cflag bits may or may not work on some platforms, and the
POSIX functions will always work.

  #define BITRATE B9600
  cfsetospeed(&tty, BITRATE); /* set the bit rate */
  cfsetispeed(&tty, BITRATE);

Here is how you set the c_cflag (edited for shorter lines):

    newtio.c_cflag = B9600 | ECHO | CS8 | PARENB | CLOCAL \
                           | CRTSCTS | CSTOPB | ICRNL | IXON \
                           | IGNBRK | ISIG | ICANON | IEXTEN;

Note that you haven't enabled the UART receiver (CREAD), and you
are trying to enable both software and hardware flow control.
However, it is impossible to say what this will actually do,
because IGNBRK, IXON, and ICRNL are all constants that apply
only to the c_iflag member, not c_cflag!

Note that you both set CRTSCTS, and then ignore the control
lines with CLOCAL. Also CSTOPB causes stop bits to be 2 bits,
which is fine if *both* ends do that, or if only the transmitter
does it. But if the receiver needs two stop bits and the
distant transmitter only sends 1, everything will be garbled.

Given that this is not being used for keyboard i/o, I don't think
you really want ICANON, ISIG, EHCO, or IEXTEN either.

I would try this:

    newtio.c_cflag = CS8 | CREAD | CRTSCTS;
    newtio.c_iflag = IGNBRK | IGNPAR;

That turns on the receiver, sets 8 bits, and enables hardware
flow control. The c_flags ignores a break condition and
disables parity checking.

    newtio.c_oflag = 0;
    newtio.c_lflag = 0;

Those are the same as your original code, and are correct.

    newtio.c_cc[VTIME] = 0; /* ignore timer */
    newtio.c_cc[VMIN] = 0; /* no blocking read */

This may or man not be what you want. If you use non-blocking
reads, you probably do want to set a timer. The effect is that
if no data is available, the process will give up its time
slice, and not hog the cpu. Hence setting c_cc[VTIME] to 1 is
probably a better idea than 0.

On the other hand... using select() to determine when there is
data available is probably much easier to code, and in that case
you would want to set c_cc[VTIME] to 0 and c_cc[VMIN] to 1.

    tcflush(fd1, TCIFLUSH);
    tcsetattr(fd1,TCSANOW,&newtio);

(Note that the call to close() is removed.)

Give those changes a try, and see what you come up with.

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


Relevant Pages

  • Re: Linux Serial Programming
    ... >int written, bytesRead; ... You are using the O_NDELAY flag to allow the port to be opened, ... then retrieved the current configuration. ... And one more flag *must* be set. ...
    (comp.os.linux.development.apps)
  • Re: Why is the serial port so slow in Linux?
    ... port, I have connected pins 2 and 3 (the ... to read the serial port. ... integer baud. ... int ascii_loopback; ...
    (comp.os.linux.misc)
  • Re: Random NULL chars from serial port?
    ... It originates from the Linux Serial Port Programming HOW-TO so I am not sure ... > int baudrate; ... > int setBaudRate(int fd, int baudrate, int debug) ...
    (comp.unix.programmer)
  • Random NULL chars from serial port?
    ... The port has a GSM modem ... ultimately my serial comms layer sends/receives data to/from the serial port ... int baudrate; ... int setBaudRate(int fd, int baudrate, int debug) ...
    (comp.unix.programmer)
  • Re: File transfer over serial port
    ... >tar says that it needs one of the '-Acdtrux' options. ... says *nothing* about "the connection is up and all port settings ... then the serial port connection will be up. ... # configuration between commands. ...
    (comp.unix.programmer)