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

Embedded linux serial port
Started by ●May 26, 2021
Reply by ●May 26, 20212021-05-26
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.
Reply by ●May 27, 20212021-05-27
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.
