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