Embedded linux serial port

May 26, 2021 2 Replies

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 #include #include #include #include #include #include #include #include #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 "); 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


[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.

Join the Discussion

Have something to add? Share your thoughts — no account required.

Didn't find your answer?

Ask the community — no account required