Not stable serial port reading from raspberry pi - c++

I am trying to read serial data from an arduino serial port with my raspberry pi with a 33 bytes package in 800 Hz. I tried to time the duration, it turned to be very unstable.
The code I use to receive data is as follow, basically it just calculate how low does the "read" take. It will be called in 800 Hz.
std::chrono::system_clock::time_point startGet= std::chrono::system_clock::now();
int readBytes = read(serialPort, this->serialBuf+alreadyRead, this->dataNeedRead);
std::chrono::system_clock::time_point endGet= std::chrono::system_clock::now();
microsecs_t get_time(std::chrono::duration_cast<microsecs_t>(endGet-startGet));
cout<<"spend "<<get_time.count()<<endl;
Since the above script will be called in 800 Hz, ideally, I should see the port received 33 bytes in a short period of time.Yet it turned out the output is:
read: 33
spend 3817
read: 33
spend 5
read: 33
spend 1301
read: 33
spend 40
read: 33
spend 1333
read: 33
spend 31
I can see the port does read 33 bytes, but the processing time is too long( 800Hz need to be ran within 1250 us, but some of them is more than 3000 us.
And the way I setup the serialport is as follow
int Sensor::serialPortConnect(char *portName)
{
// Open the serial port. Change device path as needed (currently set to an standard FTDI USB-UART cable type device)
int serial_port = open(portName, O_RDWR);
// Create new termios struc, we call it 'tty' for convention
struct termios tty;
memset(&tty, 0, sizeof tty);
// Read in existing settings, and handle any error
if (tcgetattr(serial_port, &tty) != 0)
{
printf("Error %i from tcgetattr: %s\n", errno, strerror(errno));
}
tty.c_cflag &= ~PARENB; // set parity bit, disabling parity (most common)
tty.c_cflag &= ~CSTOPB; // Clear stop field, only one stop bit used in communication (most common)
tty.c_cflag |= CS8; // 8 bits per byte (most common)
tty.c_cflag &= ~CRTSCTS; // Disable RTS/CTS hardware flow control (most common)
tty.c_cflag |= CREAD | CLOCAL; // Turn on READ & ignore ctrl lines (CLOCAL = 1)
tty.c_lflag &= ~ICANON;
tty.c_lflag &= ~ECHO; // Disable echo
tty.c_lflag &= ~ECHOE; // Disable erasure
tty.c_lflag &= ~ECHONL; // Disable new-line echo
tty.c_lflag &= ~ISIG; // Disable interpretation of INTR, QUIT and SUSP
tty.c_iflag &= ~(IXON | IXOFF | IXANY); // Turn off s/w flow ctrl
tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL); // Disable any special handling of received bytes
tty.c_oflag &= ~OPOST; // Prevent special interpretation of output bytes (e.g. newline chars)
tty.c_oflag &= ~ONLCR; // Prevent conversion of newline to carriage return/line feed
// tty.c_oflag &= ~OXTABS; // Prevent conversion of tabs to spaces (NOT PRESENT ON LINUX)
// tty.c_oflag &= ~ONOEOT; // Prevent removal of C-d chars (0x004) in output (NOT PRESENT ON LINUX)
tty.c_cc[VTIME] = 0;
tty.c_cc[VMIN] =33;
// Set in/out baud rate to be 1000000
cfsetispeed(&tty, B1000000);
cfsetospeed(&tty, B1000000);
// Save tty settings, also checking for error
if (tcsetattr(serial_port, TCSANOW, &tty) != 0)
{
printf("Error %i from tcsetattr: %s\n", errno, strerror(errno));
}
//Allocate buffer for read buffer
//tcflush(serial_port, TCIFLUSH);
return serial_port;
}
I am not sure why this happen. The RPi is installed with RT-PREEMPT kernel and the application I am running has the highest priority (RT). I thought it shouldn't be this unstable.

Related

Serial Port File Descriptor close() Delay

I have an application that interfaces with serial ports. Sometimes when I close a serial port using close(int fd), the call to close() takes 30 seconds or so.
Is there a way I can configure the system to forgo this delay when closing the file descriptor?
I open my port by fd = open(addr, O_RDWR );
Most of the code configuing the port is shown below:
rc = tcgetattr(fd, &opts);
if(rc)
{
printf("TC get attr failed %d \n", errno);
return rc;
}
// Configure parity
opts.c_cflag &= ~PARENB;
if(com_port->parity_)
{
opts.c_cflag |= PARENB;
if(com_port->parity_ >1)
{
opts.c_cflag = PARODD;
}
}
// Configure stop bits
opts.c_cflag &= ~CSTOPB; // Clear stop bit for default setting
switch(com_port->stop_bit_)
{
case 1:
//default is 1 stop bit for POSIX
break;
case 2:
opts.c_cflag |= CSTOPB;
break;
default:
//Use the default setting for POSIX
printf("Warning using default stop bit\n");
break;
}
// Configure bytes size
opts.c_cflag &= ~CSIZE; // Clear byte size bit
opts.c_cflag |= (CS8); // Set byte size bit
// Configure flow control
opts.c_cflag &= ~CRTSCTS; // flow control disabled
opts.c_cflag |= CREAD | CLOCAL; // allow read and ignore ctrl lines
// Disable canonical mode
opts.c_lflag &= ~ICANON;
// Disable echoing
opts.c_lflag &= ~ECHO;
opts.c_lflag &= ~ECHOE;
opts.c_lflag &= ~ECHONL;
// Disable signal chars
opts.c_lflag &= ~ISIG;
// Disable flow control
opts.c_iflag &= ~(IXON | IXOFF | IXANY);
// Disable special handling
opts.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL);
// Configure output modes
opts.c_oflag &= ~OPOST;
opts.c_oflag &= ~ONLCR;
// Default timeouts
opts.c_cc[VTIME] = 10;
opts.c_cc[VMIN] = 0;
I close the file with this code:
int CloseSerialPort(SerComPort* com_port)
{
if(com_port->port_handle_ > 0)
{
tcflush(com_port->port_handle_ TCIOFLUSH);
}
return close(com_port->port_handle_);
}

Why when I try set serial port, function return "Input/output error"?

I try set in c++ some serial port, but when I try function tcsetatrr, its return error -1. Port opens without problems.
char port_name[] = "/dev/ttyS1";
int port = open(port_name, O_RDWR | O_NOCTTY | O_NDELAY);
if(port < 0){
std::cout << "Cant open port" << std::endl;
return;
}
struct termios settings;
tcgetattr(port, &settings);
cfgetispeed(&settings);
//std::cout << settings.c_cflag;
//Baudrate
cfsetispeed(&settings, B115200);
cfsetospeed(&settings, B115200);
//Data bits
settings.c_cflag &= ~CSIZE;
settings.c_cflag |= CS8;
//Parity
settings.c_cflag |= ~PARENB;
//Stop bit
settings.c_cflag &= ~CSTOPB;
//Flow control
settings.c_cflag |= ~CRTSCTS;
settings.c_iflag &= ~(IXON | IXOFF | IXANY);
int er = tcsetattr(port, TCSANOW, &settings);
if (er<0) {
fprintf(stderr, "Error openinig: %s\n", strerror(errno));
}
close(port);
output:
Input/output error
How can I fix that? I was running code as root. The problem is not in the configuration of the settings structure because after commenting it out, I get the same error
The most likely answer is that you don't really have a serial device /dev/ttyS1. Try /dev/ttyS0 instead. I have the same deal on my PC.
I see a few additional problems here.
You don't check if tcgetattr() succeeds, so don't be so sure you even get to tcsetattr().
Calling settings.c_cflag |= ~PARENB; or settings.c_cflag |= ~CRTSCTS; is most likely not what you want to do (will raise all flags except the selected one).

Unable to write to serial device but able to read from it

I am using non-blocking IO where I simply try to write and read to a serial port. Reading of the serial port works as expected whereas writing doesn't.
This is how I've set up the serial line. As you can see, it is setup to non-blocking and canonical. Maybe some of the flags are redundant, but they are mostly taken from an example here: Serial setup example
tSerial::tSerial(const char *serialPort, bool echo)
{
// Open the serial port. Change device path as needed (currently set to an standard FTDI USB-UART cable type device)
m_serialPort = open(serialPort, O_RDWR);
// Create new termios struct, we call it 'tty' for convention
struct termios tty;
// Read in existing settings, and handle any error
if (tcgetattr(m_serialPort, &tty) != 0)
{
spdlog::error("[tSerial] error {} from tcgetattr: {}", errno, strerror(errno));
throw std::runtime_error("Failed to get existing serial settings");
}
tty.c_cflag &= ~PARENB; // Clear parity bit, disabling parity (most common)
tty.c_cflag &= ~CSTOPB; // Clear stop field, only one stop bit used in communication (most common)
tty.c_cflag &= ~CSIZE; // Clear all bits that set the data size
tty.c_cflag |= CS8; // 8 bits per byte (most common)
tty.c_cflag &= ~CRTSCTS; // Disable RTS/CTS hardware flow control (most common)
tty.c_cflag |= CREAD | CLOCAL; // Turn on READ & ignore ctrl lines (CLOCAL = 1)
// tty.c_lflag &= ~ICANON;
tty.c_cflag |= ICANON;
if (!echo)
{
tty.c_lflag &= ~ECHO; // Disable echo
}
else
{
tty.c_lflag |= ECHO; // Enable echo
}
// tty.c_lflag &= ~ECHOE; // Disable erasure
// tty.c_lflag &= ~ECHONL; // Disable new-line echo
// tty.c_lflag &= ~ISIG; // Disable interpretation of INTR, QUIT and SUSP
tty.c_iflag &= ~(IXON | IXOFF | IXANY); // Turn off s/w flow ctrl
tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL); // Disable any special handling of received bytes
tty.c_oflag &= ~OPOST; // Prevent special interpretation of output bytes (e.g. newline chars)
tty.c_oflag &= ~ONLCR; // Prevent conversion of newline to carriage return/line feed
// Set in/out baud rate to be 115200
cfsetispeed(&tty, B115200);
cfsetospeed(&tty, B115200);
// Save tty settings, also checking for error
if (tcsetattr(m_serialPort, TCSANOW, &tty) != 0)
{
spdlog::error("[tSerial] error {} from tcsetattr: {}", errno, strerror(errno));
throw std::runtime_error("Failed to set new serial settings");
}
// Set non-blocking
int flags;
if ((flags = fcntl(m_serialPort, F_GETFL, 0)) == -1)
{
flags = 0;
}
fcntl(m_serialPort, F_SETFL, flags | O_NONBLOCK);
}
This is how I try to write. I am using select to wait for the IO resource to become available since trying to write to it directly gives the error Resource temporarily unavailable. But even waiting 20 seconds, the device is still unavailable.
void tSerial::writeSerial(std::string message)
{
int n;
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(m_serialPort, &rfds);
spdlog::debug("[tSerial] writing command to serial {}", message);
struct timeval tv;
tv.tv_sec = 20;
tv.tv_usec = 0;
int retval = select(1, NULL, &rfds, NULL, &tv);
if (retval == -1)
{
spdlog::error("[tSerial] select error");
}
else if (retval)
{
n = write(m_serialPort, message.c_str(), message.length());
tcflush(m_serialPort, TCIOFLUSH);
if (n < 0)
{
spdlog::error("[tSerial] error writing: {}", strerror(errno));
}
}
else
{
spdlog::error("[tSerial] Resource unavailable after 20 seconds wait");
}
}
I am using select to wait for the IO resource to become available ... But even waiting 20 seconds, the device is still unavailable.
Your program is not behaving as you expect because you have not properly programmed the select() call:
int retval = select(1, NULL, &rfds, NULL, &tv);
Per the man page the first argument "should be set to the highest-numbered file descriptor in any of the three sets, plus 1."
Since you are passing the value 1, the only file descriptor that the select() could check is stdin (which is read-only and will never be ready for output) and never (the file descriptor for) the opened serial terminal.
Instead the syscall needs to be
select(m_serialPort + 1, ...);
Your code has additional issues.
(1) Use of nonblocking mode is questionable.
Seems like you prefer to add extra code to your program to wait instead of letting the OS do it for you. If your program does not utilize its time slice effectively and efficiently, then you would be better off having the OS manage the system resources.
(2) Use of tcflush() after the write() is nonsensical and wrong!
You probably meant to use tcdrain().
Study the man pages.
(3) When you copied & modified the code you introduced at least one bug.
ICANON is in the c_lflag member, and not in c_cflag.
(4) Good coding practice is using variable names that make sense.
Reusing a variable name intended for the read argument as the write argument is sloppy and confusing:
fd_set rfds;
...
int retval = select(..., NULL, &rfds, NULL, &tv);
Instead of rfds, your program should be using something like wfds for the third argument.
... trying to write to it directly gives the error Resource temporarily unavailable
The problem when using select() is easily explainable.
For the above issue you need to provide a minimal, reproducible example, i.e. a complete program.

How to check serial port in linux work properly

I am going to use serial port in my C++ program linux ubuntu 10.4 ,this is my open port function :
int Recorder::OpenPort()
{
int intFd ;
struct termios options;
intFd=open("/dev/ttyS0", O_RDWR | O_NOCTTY| O_NDELAY);
if (intFd==-1){
perror("open_port: Unable to open /dev/ttyS0 - ");
}
fcntl(intFd, F_SETFL, FNDELAY); /*configuration the port*/
tcgetattr(intFd, &options);
//set baud rates at 115200
cfsetispeed(&options, B115200 );
cfsetospeed(&options, B115200);
//mask the character size to 8 bit data & no parity.CHARACTER SIZE SETTING
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;
//setting hardware flow control
options.c_cflag |= CRTSCTS;
//Enable the receiver and set local mode .should always be enabled (enable receiver and dont change owner of port)
options.c_cflag |= (CLOCAL | CREAD);
//flush output buffer
options.c_lflag |= FLUSHO;
//choosing raw data disable echoing
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
//not setting software flow control
options.c_iflag &= ~(IXON | IXOFF | IXANY);
options.c_oflag &= ~OPOST;
// //output changes :for mapping NL to CR-NL.
// options.c_oflag |= (OPOST | ONLCR );
//
// options.c_oflag |= (NL1 | CR1 | FFDLY);
tcsetattr(intFd, TCSANOW, &options);
return intFd;
}
and this is the sample where I check my serial number:
int serial=0;
ioctl(intFd, TIOCMGET, &serial);
//for ubuntu 7.04 and 10.04
if(serial==16390 || serial==16422 || serial==16454 || serial==16486){//no connection with serial port
i checked my program many times and serial is 16486 every times and it means I have no connection with serial port , I checked my serial cable and it was ok? so how can I Solve my problem?

Serial communication between linux and windows

I am sending data bytes from linux to windows in serial RS232 then everything is ok, only i have to handle 0xa send from linux, because windows read it as 0xd + 0xa.
but when i am sending data bytes from windows to linux some bytes are replaced as -
windows send - 0xd linux receive 0xa
windows send - 0x11 linux receive garbage tyte value in integer something 8200
plese explain what goes wrong when I send data from windows to Linux.
thanks in advance
Windows serial port initialize
char *pcCommPort = "COM1";
hCom = CreateFile( TEXT("COM1"),
GENERIC_READ | GENERIC_WRITE,
0, // must be opened with exclusive-access
NULL, // no security attributes
OPEN_EXISTING, // must use OPEN_EXISTING
0, // not overlapped I/O
NULL // hTemplate must be NULL for comm devices
);
fSuccess = GetCommState(hCom, &dcb);
FillMemory(&dcb, sizeof(dcb),0);
dcb.DCBlength = sizeof(dcb);
dcb.BaudRate = CBR_115200; // set the baud rate
dcb.ByteSize = 8; // data size, xmit, and rcv
dcb.Parity = NOPARITY; // no parity bit
dcb.StopBits = ONESTOPBIT; // one stop bit
dcb.fOutxCtsFlow = false;
fSuccess = SetCommState(hCom, &dcb);
buff_success = SetupComm(hCom, 1024, 1024);
COMMTIMEOUTS cmt;
// ReadIntervalTimeout in ms
cmt.ReadIntervalTimeout = 1000;
cmt.ReadTotalTimeoutMultiplier = 1000;
cmt.ReadTotalTimeoutConstant=1000;
timeout_flag = SetCommTimeouts(hCom, &cmt);
windows write serial-
WriteFile(hCom, buffer, len, &write, NULL);
Linux serial initialize-
_fd_port_no = open("//dev//ttyS0", O_RDWR | O_NOCTTY | O_NDELAY);
tcgetattr(_fd_port_no, &options);
cfsetispeed(&options, B115200);
cfsetospeed(&options, B115200);
options.c_cflag |= (CS8);
options.c_cflag|=(CLOCAL|CREAD);
options.c_cflag &=~PARENB;
options.c_cflag &= ~CSTOPB;
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
options.c_iflag |= (IXON | IXOFF | IXANY);
options.c_cflag &= ~ CRTSCTS;
tcsetattr(_fd_port_no, TCSANOW, &options);
read serial linux-
while(read(_fd_port_no,buffer+_buffer_len,sizeof(buffer))>0)
{
_buffer_len = _buffer_len+sizeof(buffer);
}
Yes, as i told from Linux to windows only NL/CR problem detected but i solved it by byte replacing,
but do you have any idea about serila data send from windows to Linux (byte replacement policy).
Actually I have to send 200 KB file in 200 bytes blocks over serial so which byte could be replaced if send from Windows to Linux
If you are using the ReadFile and WrietFile on Windows and read and write in Linux, it shouldn't really matter what the line-endings are, other than "you have to translate it at some point after receiving it.
This doesn't look right:
while(read(_fd_port_no,buffer+_buffer_len,sizeof(buffer))>0)
{
_buffer_len = _buffer_len+sizeof(buffer);
}
You should take into account the size of the read returned by read.
And if sizeof(buffer) is the actual buffer you are reading into, adding +_buffer_len, when _buffer_len >= sizeof(buffer) will write outside the buffer.
Also slightly worried about this:
options.c_iflag |= (IXON | IXOFF | IXANY);
options.c_cflag &= ~ CRTSCTS;
Are you SURE you want a XOFF/CTRL-S (0x13) to stop flow? Usually that means that data with CTRL-S in it won't be allowed - which may not be an issue when sending text data, but if you ever need to send binary data it certainly will be. IXOFF also means that the other end will have to respond to XOFF and XON (CTRL-Q, 0x11) to stop/start the flow of data. Typically, we don't want this in modern systems....
Using RTS/CTS should be safe if you have the wiring correct between the two ends.
I think you have to flush before reading the flux from a serial port
tcflush(_fd_port_no TCIFLUSH);
furthemore Have you tried to see the flux with a console using the commande
cat < dev/ttyS0 ?
To avoid line ending conversions you might need to add:
options.c_iflag &= ~IGNCR; // turn off ignore \r
options.c_iflag &= ~INLCR; // turn off translate \n to \r
options.c_iflag &= ~ICRNL; // turn off translate \r to \n
options.c_oflag &= ~ONLCR; // turn off map \n to \r\n
options.c_oflag &= ~OCRNL; // turn off map \r to \n
options.c_oflag &= ~OPOST; // turn off implementation defined output processing
Also, the following line:
options.c_iflag |= (IXON | IXOFF | IXANY);
Will enable XON/XOFF processing, so the tty driver will process Ctrl-S (XOFF) and Ctrl-Q (XON) characters as flow control (which is probably why you see somethign unexpected when sending 0x11, which is Ctrl-Q). I'd expect that you'd want to turn those bits off:
options.c_iflag &= ~(IXON | IXOFF | IXANY);
In fact, I think you might want to call cfmakeraw() after calling tcgetattr() which should disable all special handling of input and output characters.
Thanks all
This Change Solved my problem
fd_port_no = open("//dev//ttyS0", O_RDWR | O_NOCTTY | O_NDELAY);
tcgetattr(_fd_port_no, &options);
cfsetispeed(&options, B115200);
cfsetospeed(&options, B115200);
options.c_cflag |= (CS8);
options.c_cflag|=(CLOCAL|CREAD);
options.c_cflag &=~PARENB;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~ CRTSCTS;
options.c_iflag |= (IXON | IXOFF | IXANY);
options.c_lflag &= ~(ICANON | ISIG | ECHO | ECHONL | ECHOE | ECHOK);
options.c_cflag &= ~ OPOST;
tcsetattr(_fd_port_no, TCSANOW, &options);