Forums

Embedded linux serial port

Started by pozz May 26, 2021
I used a simple driver for serial port in Linux with success in many 
projects, but recently I found an issue with it.

The scenario is an embedded Linux (running on iMX6) that runs a QT 
application (that creates a GUI on a touch display) and a C application 
that communicates over a serial port.

When QT application starts some complex graphics (I see its CPU load 
reaching 70-80%), the serial port application stops working correctly.
After some debugging, I noticed that the bytes really transmitted on the 
wire aren't correct. It seems one or two bytes are re-transmitted and 
one-two bytes aren't completely transmitted.

I suspect my serial port driver has some errors that are triggered only 
when the CPU is loaded by other processes, but I don't know how to fix them.

I use O_NONBLOCK flag when opening serial port, but write() always 
returns a positive number, so I think the byte is correctly moved to the 
low-level serial port driver and really transmitted... but it isn't 
always the case.

If I enable debugging messagin (with DEBUG_SERIAL macro), I always see 
correct data on stdout, but wrong data on the wire.

Any hint?




#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <termios.h>
#include <time.h>
#include "serial.h"

//#define DEBUG_SERIAL

#ifdef DEBUG_SERIAL
static bool debug_tx;
#endif

SERIAL_HANDLE
serial_open(const char *serial_name, int baudrate)
{
	if (serial_name == NULL) return INVALID_SERIAL_HANDLE_VALUE;

	speed_t speed;
	if (baudrate == 57600)
	{
		speed = B57600;
	}
	else if (baudrate == 115200)
	{
		speed = B115200;
	}
	else
	{
		return INVALID_SERIAL_HANDLE_VALUE;
	}

     int hSerial;

     hSerial = open(serial_name, O_RDWR | O_NOCTTY | O_NONBLOCK);
     if (hSerial != -1)
     {
         int oldflags = fcntl (hSerial, F_GETFL, 0);
         if (oldflags == -1) return -1;
         oldflags &= ~O_NONBLOCK;
         if (fcntl(hSerial, F_SETFL, oldflags) == -1)
         {
             return INVALID_SERIAL_HANDLE_VALUE;
         }

         struct termios options;
         tcgetattr(hSerial, &options);

		cfsetispeed(&options, speed);
		cfsetospeed(&options, speed);

         /* Input mode flags */
         options.c_iflag &= ~(ISTRIP | IGNCR | ICRNL | INLCR | IXOFF | 
IXON | INPCK );
         options.c_iflag |= IGNBRK;

         /* Output mode flags */
         options.c_oflag &= ~OPOST;

         /* Control mode flags */
         options.c_cflag |= (CLOCAL | CREAD);
         options.c_cflag &= ~( CSTOPB | PARENB);
         options.c_cflag &= ~CSIZE;
         options.c_cflag |= CS8;

         /* Local mode flags */
         options.c_lflag &= ~(ICANON | ECHO | ISIG);
         options.c_lflag = NOFLSH;
		options.c_cc[VMIN] = 0;
		options.c_cc[VTIME] = 1;			

		tcsetattr(hSerial, TCSANOW, &options);

#ifdef DEBUG_SERIAL
	    debug_tx = 1;
	    printf("-> ");
#endif
     }

	return (SERIAL_HANDLE)hSerial;
}

void
serial_close(SERIAL_HANDLE hSerial)
{
	close(hSerial);
}

int
serial_getdata(SERIAL_HANDLE hSerial, void *data, size_t data_len, 
unsigned int timeout_sec)
{
	size_t bytes_read = 0;
	uint8_t *d = data;
     time_t t_begin = time(NULL);

	while(bytes_read != data_len)
	{
         if ((timeout_sec > 0) && (time(NULL) - t_begin > timeout_sec)) {
             break;
         }
	
		int ret;
		ret = read(hSerial, d, data_len - bytes_read);
		if (ret < 0) return -1;		// Error
		if (ret > 0) {
#ifdef DEBUG_SERIAL
			if (debug_tx) {
				printf("\n<- ");
				fflush(stdout);
				debug_tx = 0;
			}
			for (unsigned int i = 0; i < ret; i++) {
				printf("%02X", ((unsigned char * ) d)[i]);
				fflush(stdout);
			}
#endif

		    d += ret;
		    bytes_read += ret;
         }
     }
	

	return bytes_read;
}

int
serial_putchar(SERIAL_HANDLE hSerial, unsigned char c)
{
#ifdef DEBUG_SERIAL
	if (!debug_tx) {
		printf("\n-> ");
		debug_tx = 1;
	}
	printf("%02X", c);
#endif
	int bytes_written;

	bytes_written = write(hSerial, &c, 1);
	if (bytes_written != 1) return -1;

     return 0;
}

int
serial_putdata(SERIAL_HANDLE hSerial, const void *data, size_t size)
{
	const unsigned char *d = data;
	while(size--) {
		int ret;
		ret = serial_putchar(hSerial, *d++);
		if (ret < 0) return -1;
	}
	return 0;
}

int
serial_putstr(SERIAL_HANDLE hSerial, const char *s)
{
	if (s == NULL) return -1;
	return serial_putdata(hSerial, s, strlen(s));
}

#endif


On 05/26/2021 01:53 PM, pozz wrote:
> I used a simple driver for serial port in Linux with success in many projects, but recently I found an issue with it. > > The scenario is an embedded Linux (running on iMX6) that runs a QT application (that creates a GUI on a touch display) and a C application that communicates over a serial port. > > When QT application starts some complex graphics (I see its CPU load reaching 70-80%), the serial port application stops working correctly. > After some debugging, I noticed that the bytes really transmitted on the wire aren't correct. It seems one or two bytes are re-transmitted and one-two bytes aren't completely transmitted. > > I suspect my serial port driver has some errors that are triggered only when the CPU is loaded by other processes, but I don't know how to fix them. > > I use O_NONBLOCK flag when opening serial port, but write() always returns a positive number, so I think the byte is correctly moved to the low-level serial port driver and really transmitted... but it isn't always the case. > > If I enable debugging messagin (with DEBUG_SERIAL macro), I always see correct data on stdout, but wrong data on the wire. > > Any hint? > > >
[I didn't read your code] But here's what I used for transmitting. static int send_char (ser * d, char c) { if (d->fd == -1) longjmp (d->env, 2); ssize_t nbytes = TEMP_FAILURE_RETRY (write (d->fd, &c, 1)); return !(nbytes == 1); } I think the TEMP_FAILURE_RETRY may be important. but there's about a zillion other flags and iocls wot could be at fault. So I dont' really know if it helps.
Il 26/05/2021 23:52, Johann Klammer ha scritto:
> On 05/26/2021 01:53 PM, pozz wrote: >> I used a simple driver for serial port in Linux with success in many projects, but recently I found an issue with it. >> >> The scenario is an embedded Linux (running on iMX6) that runs a QT application (that creates a GUI on a touch display) and a C application that communicates over a serial port. >> >> When QT application starts some complex graphics (I see its CPU load reaching 70-80%), the serial port application stops working correctly. >> After some debugging, I noticed that the bytes really transmitted on the wire aren't correct. It seems one or two bytes are re-transmitted and one-two bytes aren't completely transmitted. >> >> I suspect my serial port driver has some errors that are triggered only when the CPU is loaded by other processes, but I don't know how to fix them. >> >> I use O_NONBLOCK flag when opening serial port, but write() always returns a positive number, so I think the byte is correctly moved to the low-level serial port driver and really transmitted... but it isn't always the case. >> >> If I enable debugging messagin (with DEBUG_SERIAL macro), I always see correct data on stdout, but wrong data on the wire. >> >> Any hint? >> >> >> > [I didn't read your code] > But here's what I used for transmitting. > > static int > send_char (ser * d, char c) > { > if (d->fd == -1) > longjmp (d->env, 2); > ssize_t nbytes = TEMP_FAILURE_RETRY (write (d->fd, &c, 1)); > return !(nbytes == 1); > } > > I think the TEMP_FAILURE_RETRY may be important. > but there's about a zillion other flags and iocls wot could be at fault. > So I dont' really know if it helps. >
I don't think TEMP_FAILURE_RETRY could help in my case. It simply retry forever the write() while it returns -1 and errno==EINTR. My function for writing is: int serial_putchar(SERIAL_HANDLE hSerial, unsigned char c) { int bytes_written; bytes_written = write(hSerial, &c, 1); if (bytes_written != 1) return -1; return 0; } If write() returned -1, serial_putchar() would have returned -1 and the caller would have detected this situation. However in my case, when the problem arises, write() and read() never return a negative number.