# Serial port losing characters on read [solved]

## rickj

I have a serial port on /dev/ttyS0 which almost works, most of the time. 

I'm using it to listen to an instrument which sends a burst of 62 bytes, once a second, at 115,200 baud. The function which listens to the port uses read() system calls, with a 32-byte buffer. It gets a large number of "Resource temporarily unavailable" errors. It also gets almost all the characters from the instrument, because it is called frequently, but occasionally (every minute or so) it will lose a few characters, typically about 2-20. This occurs with KDE running, and without, at roughly the same rate. 

It looks almost as if something else is trying to scan the port, and sometimes sees something which causes it to eat characters, but 

```
ps aux
```

reveals no other process using the port. I'm not running GPM, but I am running hald, and there is a kseriod which is presumably the kernel serial daemon. 

According to setserial, the port is buffered:

```
rick # setserial -ga /dev/ttyS0

/dev/ttyS0, Line 0, UART: 16550A, Port: 0x03f8, IRQ: 4

        Baud_base: 115200, close_delay: 50, divisor: 0

        closing_wait: 3000

        Flags: spd_normal skip_test

```

The instrument works perfectly under W*ndows, but I would really prefer to support it under Linux. Has anyone any ideas on diagnosing this problem?Last edited by rickj on Fri Apr 09, 2010 6:13 pm; edited 1 time in total

----------

## NeddySeagoon

rickj,

At 115,200 you will need some handshaking so you don't overflow the UART receive buffer.

Depending on how you have set that up, its 16 bytes, or one byte. The kernel defaults to one byte.

You don't say what the data stream consists of so I can't say if software handshaking might work but hardware handshaking always will.

Then there is that horrible hack called IRQ unmasking, which was invented to allow the original unbuffered serial ports to operate above 9600 baud without dropping characters while the hard drive IRQ was being serviced.

So turn on serial port buffering in the kernel, if its not on already and set up hardware handshaking.

----------

## rickj

Thanks, Neddy, that clarifies the problem a lot. 

The interface is converted from RS485, so handshaking is not available, either in hardware or software.

I'll look at the kernel options, and post the results. 

All this makes me feel very old. System programmers seem to have long felt that the serial port is so slow that it needs no attention. It is slow, particularly now that machine clocks are in the Ghz range, but at 115kBaud its data rate is not negligible, and its interrupt crisis time is comparable to that of a hard drive. The 21st century solution is probably to support it via a USB adaptor. Since USB has a respectable data rate, system programmers and hardware designers provide decent amounts of buffering. One can readily support the 12MBaud data rate of USB, and it is a little pathetic that the 115.2kBaud data rate of a serial port should still cause problems.

I really hope that in the Century of the Fruitbat I am not compelled to resort to IRQ unmasking, which I had assumed to be dead and buried. With even a 16-byte hardware buffer, which the 16550A has, the crisis time is almost 1.4ms, and a GHz system should be able to meet this.

----------

## John R. Graham

Neddy, how do you disable serial buffering in the kernel?  I can't find a control one way or another but found some anecdotal evidence that the kernel driver always establishes 4k buffers.

@rickj, there are relatively inexpensive PCI cards that use 16950 UARTs with 128-byte FIFOs, among others, here.

- John

----------

## rickj

john_r_graham has hit the nail on the head - I can find no controls for serial buffering. If there really are 4k buffers, losing data which comes in a burst of 62 bytes each second is inexplicable, since it would take over a minute to fill the buffer, and if I get a corrupted packet the next one is displayed on time, one second later.

I'm still concerned by the string of "Resource temporarily unavailable" errors. Do these signify contention?

----------

## NeddySeagoon

john_r_graham,

I can't find any of the buffering controls in the kernel now and I didn't even know that the kernel provided a 4k buffer.

A 4k buffer is of no help if the kernel is dropping serial port IRQs.

I still use a serial port - for logging into my iPaq, that still works so I must have missed the demise of the hardware buffer option.

rickj,

Is your data steam printable ASCII or binary ?

How is the serial port configured ?

If the data is printable ASCII, use minicom to receive it.

----------

## rickj

The data is binary, so minicom is not an option. Port is configured for raw mode:

```

  // Open the port

  serial_port = open_port(port);

  // Get the current options for the port

  tcgetattr(serial_port, &options);

  original = options;

  // Set the baud rates

  cfsetispeed( &options, baud_rate );

  cfsetospeed( &options, baud_rate );

  // Parity

  if(strncmp( "Odd", parity, STR_SIZE) == 0)

    {

      options.c_cflag |= PARENB;

      options.c_cflag |= PARODD;

    }

  else if(strncmp( "Even", parity, STR_SIZE) == 0)

    {

      options.c_cflag |= PARENB;

      options.c_cflag &= ~PARODD;

    }

  else  // No parity

    {

      options.c_cflag &= ~PARENB;

      options.c_cflag |= PARODD;

    }

  // Set character length

  options.c_cflag &= ~CSIZE;       // Mask the character size bits

  options.c_cflag |= data_bits;

  // One stop bit

  options.c_cflag &= ~CSTOPB;

  // No hardware flow control

  options.c_cflag &= ~CRTSCTS;

  // No software flow control

  options.c_iflag &=  ~(IGNBRK | BRKINT | PARMRK | ISTRIP

                        | INLCR | IGNCR | ICRNL | IXON);

  // Raw input, no echo

  options.c_lflag &= ~(ECHO | ECHOE | ECHONL | ICANON | IEXTEN);

  options.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON | IXOFF);

  // Raw output

  options.c_oflag &= ~(OPOST);

  // Wait timeout for each character

  options.c_cc[VMIN] = 1;

  options.c_cc[VTIME] = 0;

  // Enable the receiver and set local mode...

  options.c_cflag |= (CLOCAL | CREAD);

  // Flush any buffered characters

  tcflush(serial_port, TCIOFLUSH);

  // Set the new options for the port...

  tcsetattr(serial_port, TCSANOW, &options);

```

----------

## NeddySeagoon

rickj,

I don't do c but I understand the intent of the code. It was assembler last time I did this.

I was trying to get a serial port to operate at different Rx/Tx baud rates.

----------

## rickj

I now have reliable raw-mode communication. The problem was with my settings, which were not comprehensive enough. Just in case anyone else has to do this, here's the corrected code;

```
static int open_port( char *device )

//

//  Open serial port <device>

//  Returns the file descriptor on success or -1 on error.

{

  int fd; /* File descriptor for the port */

  fd = open( device, O_RDWR | O_NOCTTY | O_NDELAY);

  if (fd == -1)

    fprintf( stderr, "open_port(): Unable to open %s\n", device );

  else

    fcntl(fd, F_SETFL, FNDELAY); // Non-blocking read

 

  return (fd);

}

```

Note the addition of the fcntl() call to make returns from read() non-blocking, in the event that no characters are available. The settings are:

```
void initialize_serial_port(void)

//

// Do a full serial port setup

{

  struct termios options;   // Control structure to modify

  // In case port is in use, close it

  shutdown_serial_port();

  // Open the new one

  serial_port = open_port(port);

  // Get the current options for the port

  tcgetattr(serial_port, &options);

  original = options;

  // Set the baud rates

  cfsetispeed( &options, baud_rate );

  cfsetospeed( &options, baud_rate );

  // Parity

  if(strncmp( "Odd", parity, STR_SIZE) == 0)

    {

      options.c_cflag |= PARENB;

      options.c_cflag |= PARODD;

    }

  else if(strncmp( "Even", parity, STR_SIZE) == 0)

    {

      options.c_cflag |= PARENB;

      options.c_cflag &= ~PARODD;

    }

  else  // No parity

    {

      options.c_cflag &= ~PARENB;

      options.c_cflag |= PARODD;

    }

  // Set character length

  options.c_cflag &= ~CSIZE;       // Mask the character size bits

  options.c_cflag |= data_bits;

  // One stop bit

  options.c_cflag &= ~CSTOPB;

  // No hardware flow control

  options.c_cflag &= ~CRTSCTS;

  // Raw input, no echo

  options.c_lflag &= ~(ECHO | ECHOE | ECHONL | ICANON | IEXTEN | ISIG);

  options.c_iflag &=  ~(IGNBRK | BRKINT | PARMRK | ISTRIP

                        | INLCR | IGNCR | ICRNL | IXON | IXOFF | IXANY | INPCK );

  // Raw output

  options.c_oflag &= ~(OCRNL | ONLCR | ONLRET |

                      ONOCR | OFILL | OLCUC | OPOST);

  // Wait timeout for each character

  options.c_cc[VMIN] = 1;

  options.c_cc[VTIME] = 0;

  // Enable the receiver and set local mode...

  options.c_cflag |= (CLOCAL | CREAD);

  // Flush any buffered characters

  tcflush(serial_port, TCIOFLUSH);

  // Set the new options for the port...

  tcsetattr(serial_port, TCSANOW, &options);

}

```

The moral of this story is that termios is not a simple API to use, so if you need to do something non-standard, be prepared to spend some time learning.

----------

