Why is tcsendbreak() necessary for serial communication to start working? - c++

We are migrating from an older OS (OpenSuse 10.2) to Ubuntu 16.04, and one of our applications communicates via a serial port. Communication stopped working on Ubunutu, but with a small hack(?) it does start working. That hack is to send a tcsendbreak() right after the call to open, like so:
fd = open(pname, O_RDWR | O_NOCTTY); //pname becomes /dev/ttyS6
if (fd < 0)
{
printf("Couldnt open comms port \"%s\".\n", pname);
return SYSERR;
}
if (tcsendbreak(fd, 0) < 0)
{
printf("tcsendbreak error\n");
}
After doing this everything runs normally. I've tried doing a tcflush at various points (without the tcsendbreak), that didn't make a difference.
What are some reasons why the tcsendbreak would allow communication to be established? Any ideas of what to try without using the tcsendbreak? Trying to understand why its needed, and if there is an underlying issue that this break skips past.
We set the modes the following way:
int r = 0;
struct termios port_opt, *popt;
r |= tcgetattr(fd, &original_port_options);
port_opt = original_port_options;
popt = &port_opt;
cfmakeraw(&port_opt);
/* parity/framing errors come in as nulls. */
popt->c_iflag |= (IGNBRK);
popt->c_cflag |= (CLOCAL);
popt->c_cc[VMIN] = 0; /* ignored by line discipline. */
popt->c_cc[VTIME] = 1; /* ignored by line discipline. */
popt->c_cflag |= CREAD; //enable receiver
r |= cfsetispeed(popt, B19200);
popt->c_cflag &= ~(PARENB);/* turn off parity. */
popt->c_cflag = (popt->c_cflag & ~CSIZE) | CS8; /* 8 bits. */
popt->c_cflag &= ~CSTOPB; /* only 1 stop bit. */
r |= cfsetospeed(popt, B19200);
r |= tcsetattr(fd, TCSANOW, popt);
if (r != 0)
gos_panic("Cant set comms driver parameters");
Additionally, we also set a line discipline:
r = ioctl(fd, TIOCGETD, &old_line_discipline);
if (r != 0)
old_line_discipline = -1;
r = map_line_discipline("LINE_DISC", 27);
r = ioctl(fd, TIOCSETD, &r);
Note that communication works when sending the tcsendbreak before setting all these modes.

Related

Canonical serial reading using terminos fail?

I am trying to read lines of datas comming from my arduino using serial.
My arduino code look like that : Serial3.print(Z, 2);Serial3.print(F(";"));Serial3.println(F("END\n"));
And this is my code to read the data on ubuntu :
void setup(){
//set up serial
tcgetattr(dueSerial, &port_options); // Get the current attributes of the Serial port
dueSerial = open("/dev/ttyUSB0", O_RDWR | O_NONBLOCK | O_NOCTTY | O_NDELAY);
if (dueSerial == -1) {
reportFailure("Could not open Arduino");
} else {
port_options.c_cflag &= ~PARENB; // Disables the Parity Enable bit(PARENB),So No Parity
port_options.c_cflag &= ~CSTOPB; // CSTOPB = 2 Stop bits,here it is cleared so 1 Stop bit
port_options.c_cflag &= ~CSIZE; // Clears the mask for setting the data size
port_options.c_cflag |= CS8; // Set the data bits = 8
port_options.c_cflag &= ~CRTSCTS; // No Hardware flow Control
port_options.c_cflag |= (CREAD | CLOCAL); // Enable receiver,Ignore Modem Control lines
port_options.c_iflag &= ~(IXON | IXOFF | IXANY); // Disable XON/XOFF flow control both input & output
port_options.c_lflag &= ~(ECHO | ECHONL | IEXTEN | ISIG); // no echo
port_options.c_iflag |= ICANON; //Enable canonical
port_options.c_iflag |= ICRNL; //map CR to NL
//port_options.c_oflag &= ~OPOST; // No Output Processing
//port_options.c_lflag = 0; // enable raw input instead of canonical,
/*
initialize all control characters
default values can be found in /usr/include/termios.h, and are given
in the comments, but we don't need them here
*/
port_options.c_cc[VINTR] = 0; /* Ctrl-c */
port_options.c_cc[VQUIT] = 0; /* Ctrl-\ */
port_options.c_cc[VERASE] = 0; /* del */
port_options.c_cc[VKILL] = 0; /* # */
port_options.c_cc[VEOF] = 4; /* Ctrl-d */
port_options.c_cc[VTIME] = 0; /* inter-character timer unused */
port_options.c_cc[VMIN] = 0; /* blocking read until 1 character arrives */
port_options.c_cc[VSWTC] = 0; /* '\0' */
port_options.c_cc[VSTART] = 0; /* Ctrl-q */
port_options.c_cc[VSTOP] = 0; /* Ctrl-s */
port_options.c_cc[VSUSP] = 0; /* Ctrl-z */
port_options.c_cc[VEOL] = 0; /* '\0' */
port_options.c_cc[VREPRINT] = 0; /* Ctrl-r */
port_options.c_cc[VDISCARD] = 0; /* Ctrl-u */
port_options.c_cc[VWERASE] = 0; /* Ctrl-w */
port_options.c_cc[VLNEXT] = 0; /* Ctrl-v */
port_options.c_cc[VEOL2] = 0; /* '\0' */
cfsetispeed( & port_options, BAUDRATE); // Set Read Speed
cfsetospeed( & port_options, BAUDRATE); // Set Write Speed
tcflush(dueSerial, TCIFLUSH);
tcflush(dueSerial, TCIOFLUSH);
int att = tcsetattr(dueSerial, TCSANOW, & port_options);
if (att != 0) {
reportFailure("ERROR in Setting Arduino port attributes");
} else {
LOG_INFO("SERIAL DUE Port Good to Go");
}
}
}
void UART::tick() {
//Arduino msg = "IMU;LAX;LAY;LAZ;AVX;AVY;AVZ;AY;AP;AR;END"
// rx_buffer[0] = '0';
memset(&rx_buffer, '\0', sizeof(rx_buffer));
// tcflush(dueSerial, TCIOFLUSH);
rx_length = read(dueSerial, &rx_buffer,255);
if (rx_length < 0) {
LOG_INFO("Error reading");
}else{
LOG_INFO("Read %i bytes. Received message: %s", rx_length, rx_buffer);
}
}
But when I try this code I get many lines at a time so my output look like this :
2020-11-15 09:13:09.491 INFO packages/skeleton_pose_estimation/apps/usb/UART.cpp#87: Read 0 bytes. Received message:
2020-11-15 09:13:09.496 INFO packages/skeleton_pose_estimation/apps/usb/UART.cpp#87: Read 255 bytes. Received message: 0.01;0.00;0.00;0.00;0.00;0.00;0.00;END
IMU;-0.00;0.02;0.03;0.00;0.00;0.00;0.00;0.00;0.00;END
IMU;-0.00;0.02;0.03;0.00;-0.00;0.00;0.00;0.00;0.00;END
IMU;-0.00;0.02;-0.02;0.00;-0.00;0.00;0.00;0.00;0.00;END
IMU;-0.00;0.02;-0.02;-0.00;0.00;0.00;0.00;0.00;
2020-11-15 09:13:09.501 INFO packages/skeleton_pose_estimation/apps/usb/UART.cpp#87: Read 241 bytes. Received message: 0.00;END
IMU;-0.01;-0.02;-0.01;-0.00;0.00;0.00;0.00;0.00;0.00;END
IMU;-0.01;-0.02;-0.01;-0.00;-0.00;0.00;0.00;0.00;0.00;END
IMU;-0.01;-0.02;0.03;-0.00;-0.00;0.00;0.00;0.00;0.00;END
IMU;-0.01;-0.02;0.03;0.00;0.00;0.00;0.00;0.00;0.00;END
2020-11-15 09:13:09.506 INFO packages/skeleton_pose_estimation/apps/usb/UART.cpp#87: Read 0 bytes. Received message:
2020-11-15 09:13:09.511 INFO packages/skeleton_pose_estimation/apps/usb/UART.cpp#87: Read 0 bytes. Received message:
But I want it to read only one line per read() function call.
I believe that I either set a wrong parameter making the conanical mode unused or maybe it's ignoring my \n and \r but don't know why....
Please help me to find why.
Thank you a ton !
But I want it to read only one line per read() function call. I believe that I either set a wrong parameter making the conanical mode unused ...
Your program does not behave as expected because canonical mode is never actually set.
The statement
port_options.c_iflag |= ICANON; //Enable canonical
is incorrect. ICANON is in the c_lflag member, and not in c_iflag.
Your code has additional issues.
(1) The variable dueSerial is used uninitialized:
void setup(){
//set up serial
tcgetattr(dueSerial, &port_options); // Get the current attributes of the Serial port
dueSerial = open(...);
...
The file descriptor needs to be obtained and validated before it can be used in the tcgetattr() call.
The proper ordering of statements is:
void setup(){
//set up serial
dueSerial = open(...);
if (dueSerial == -1) {
/* abort */
}
tcgetattr(dueSerial, &port_options);
...
(2) Numerous input conversions are left unspecified in your termios initialization.
Canonical mode enables various options to convert certain input characters, and most of these options need to be disabled for reading by a program (versus an interactive terminal).
Typically INPCK (enable input parity checking), IUCLC (map uppercase characters to lowercase), and IMAXBEL (ring bell when input queue is full) are disabled.
You need to review whether you also want IGNCR (preserve or ignore carriage return), INLCR (translate newline to carriage return), and ICRNL (translate carriage return to newline unless IGNCR is set) to also be disabled.
(3) Use of nonblocking mode is questionable.
Since you want "to read only one line per read() function call", then blocking mode is the proper way to obtain that result.
If you insist on using nonblocking mode, then the read() syscall will always return "immediately" and may not return any data at all.

How to receive complete data from serial port in linux

I am trying to do a serial communication application. I have a device which is running angstrom qt linux image. I need to write the serial code application for that device. Because the cross compiler is set for QT4.8.7, so it do not include the QSerialPort. So I am following this link for serial communication and I have also found many good examples on it.
Below is the code:
void MainWindow::SerialOpen(QString PORT)
{
fd = open(PORT.toStdString().c_str(), O_RDWR | O_NOCTTY | O_NDELAY);
if (fd == -1)
{
perror("open_port: Unable to open /dev/ttyLP0\n");
exit(1);
}
saio.sa_handler = signal_handler_IO;
saio.sa_flags = 0;
saio.sa_restorer = NULL;
sigaction(SIGIO,&saio,NULL);
fcntl(fd, F_SETFL, FNDELAY);
fcntl(fd, F_SETOWN, getpid());
fcntl(fd, F_SETFL, O_ASYNC );
tcgetattr(fd,&termAttr);
cfsetispeed(&termAttr,B9600);
cfsetospeed(&termAttr,B9600);
termAttr.c_cflag &= ~PARENB;
termAttr.c_cflag &= ~CSTOPB;
termAttr.c_cflag &= ~CSIZE;
termAttr.c_cflag |= CS8;
termAttr.c_cflag |= (CLOCAL | CREAD);
termAttr.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
termAttr.c_iflag &= ~(IXON | IXOFF | IXANY);
termAttr.c_oflag &= ~OPOST;
tcsetattr(fd,TCSANOW,&termAttr);
qDebug("Serial Port configured....\n");
}
and for reading I am using:
void signal_handler_IO (int status)
{
char buf [100];
int n = read (fd, buf, sizeof buf);
if(n > 0)
{
buf[n] = '\0';
printf("Receive OK %s\n",buf);
}
}
I am using event based serial read. I am wiriting it in QT creator. The problem I am facing is that whenever I am sending characters like A or B or any other single char. It receives it. But when I send the complete string, it breaks automatically. Have a look at the below image:
As you can see in the image, it receives char like p l h but then I sent hello world. It breaks it and first received hello and then world. I again sent the hello world it receives the he then llo then worl and then d. Why is this showing this behaviour. Please help. Thanks.
You are receiving your data in small chunks. Try running the read function in a while loop to append all chunks into one message like so:
#include <cstring> // needed for strcat.
#include <errno.h> // needed for strerror and errno
void signal_handler_IO (int status)
{
char buf [100];
char msg[1024];
int n;
int length = 0;
while((n = read (fd, buf, (sizeof buf)-1)) > 0)
{
buf[n] = '\0';
// you can add a check here to see if (length+n) < 1024
strcat(msg, buf);
length += n;
}
if(n<0)
{
//error handling
//EDIT: adding error handling
fprintf(stderr, "Error while receiving message: %s\n", strerror(errno));
return;
}
printf("Receive OK: '%s'\n", msg);
}
The signal handling needs to be turned off for the time of reading data so that the signal handler doesn't get called for each new chunk of data. After we're done reading the handler needs to be restored.
EDIT: Thanks to #MarkPlotnick for giving a better example on masking the signal instead of turning it off (could cause race condition). Add these two lines before calling sigaction to MainWindow::SerialOpen to mask the signal so that the handler doesn't get called again while the signal is being handled:
sigemptyset(&saio.sa_mask);
sigaddset(&saio.sa_mask, SIGIO);
The size of msg is just an example, you can set it to any value that will fit the message. Also, if you're using c++ then it would be better to use std::string instead of an array for msg.
Note the (sizeof buf)-1, if you read all characters there would be no room for the '\0' at the end of the array.
ANOTHER EDIT: After our chat conversation it turned out that read is blocking when there are no more characters to read (even though open was called with O_NDELAY flag for non-blocking reading). To fix this issue one can first check how many bytes are available for reading:
while(true)
{
size_t bytes_avail;
ioctl(fd, FIONREAD, &bytes_avail);
if(bytes_avail == 0)
break;
int n = read (fd, buf, (sizeof buf)-1);
if(n<0)
{
//error handling
fprintf(stderr, "Error while receiving message: %s\n", strerror(errno));
return;
}
buf[n] = '\0';
// you can add a check here to see if (length+n) < 1024
strcat(msg, buf);
length += n;
}
QT4.8.7, so it do not include the QSerialPort
You can build QSerialPort from sources and to use it. Please read Wiki: https://wiki.qt.io/Qt_Serial_Port

Linux serial read blocks minicom

I'm attempting to read from a serial port (/dev/ttyS4) on a BeagleBone Black, but I think(?) this should apply to all Linux devices in general.
Currently, I can set up minicom with a baud rate of 9600 and 8N1 data to read from the serial port correctly. However, if I attempt to directly cat /dev/ttyS4, nothing shows up in my terminal. My code also does this, and returns a Resource temporarily unavailable error, which I suspect is what is happening with the cat command.
If I run stty -F /dev/ttyS4, I get the following output (which, as far as I can tell, is consistent with my minicom settings):
speed 9600 baud; line = 0;
intr = <undef>; quit = <undef>; erase = <undef>; kill = <undef>; eof = <undef>; start = <undef>; stop = <undef>; susp = <undef>; rprnt = <undef>; werase = <undef>; lnext = <undef>; flush = <undef>;
-brkint -imaxbel
-opost -onclr
-isig -iexten -echo -echoe -echok -echoctl -echoke
An interesting note is that when I have minicom open, if I start my program, minicom will stop printing anything, and stay that way even if I stop my program. I need to open the serial settings again (Ctrl-A, P) and close it for minicom to resume working (it appears that nothing was changed).
My code is as follows:
int main() {
std::cout << "Starting..." << std::endl;
std::cout << "Connecting..." << std::endl;
int tty4 = open("/dev/ttyS4", O_RDWR | O_NOCTTY | O_NDELAY);
if (tty4 < 0) {
std::cout << "Error opening serial terminal." << std::endl;
}
std::cout << "Configuring..." << std::endl;
struct termios oldtio, newtio;
tcgetattr(tty4, &oldtio); // save current serial port settings
bzero(&newtio, sizeof(newtio)); // clear struct for new settings
newtio.c_cflag = B9600 | CS8 | CREAD | CLOCAL;
newtio.c_iflag = IGNPAR | ICRNL;
newtio.c_oflag = 0;
newtio.c_lflag = ICANON;
tcflush(tty4, TCIFLUSH);
tcsetattr(tty4, TCSANOW, &newtio);
std::cout << "Reading..." << std::endl;
while (true) {
uint8_t byte;
int status = read(tty4, &byte, 1);
if (status > 0) {
std::cout << (char)byte;
} else if (status == -1) {
std::cout << "\tERROR: " << strerror(errno) << std::endl;
}
}
tcsetattr(tty4, TCSANOW, &oldtio);
close(tty4);
}
Edit: I've gotten the serial port to work correctly (in python) by following Adafruit's tutorial for using python with the BeagleBone. At this point I'm certain that I'm doing something wrong; the question is what. I would much prefer using C++ over python, so it'd be great to get that working.
Your program opens the serial terminal in nonblocking mode.
int tty4 = open("/dev/ttyS4", O_RDWR | O_NOCTTY | O_NDELAY);
Nonblocking I/O, especially read operations, requires additional, special handling in a program.
Since you neglect to mention this mode, and your program has no capability to properly process this mode, this could be considered a bug.
Either remove O_NDELAY option from the open() call, or insert a fcntl(tty4, F_SETFL, 0) statement to revert back to blocking mode.
My code also does this, and returns a Resource temporarily unavailable error,
That's an EAGAIN error, which is consistent with a nonblocking read().
The man page describes this error will occur when "the file descriptor ... has been marked nonblocking (O_NONBLOCK), and the read would block".
The read() syscall "would block" because there is no data to satisfy the read request.
If you insist on using nonblocking mode, then your program must be able to cope with this condition, which is not an error but a temporary/transient status.
But blocking mode is the simpler and preferred mode of operation for typical programs in a multitasking system.
Your program should be modified as previously mentioned.
There are numerous issues with the initialization of the serial terminal.
tcgetattr(tty4, &oldtio); // save current serial port settings
The return values from the tcgetattr() and tcsetattr() syscalls are never checked for errors.
bzero(&newtio, sizeof(newtio)); // clear struct for new settings
Starting with an empty termios structure is almost always a bad idea. It may appear to work on some systems, but it is not portable code.
The proper method for initializing a termios structure is to use values from tcgetattr().
See Setting Terminal Modes Properly.
Since it is already called, all you need is newtio = oldtio to copy the structure.
newtio.c_cflag = B9600 | CS8 | CREAD | CLOCAL;
newtio.c_iflag = IGNPAR | ICRNL;
newtio.c_oflag = 0;
newtio.c_lflag = ICANON;
Rather than assignment of constants, the proper method of changing these flags is to enable or disable the individual attributes.
The following should suffice for canonical mode:
newtio.c_cflag |= CLOCAL | CREAD;
newtio.c_cflag &= ~CSIZE;
newtio.c_cflag |= CS8; /* 8-bit characters */
newtio.c_cflag &= ~PARENB; /* no parity bit */
newtio.c_cflag &= ~CSTOPB; /* only need 1 stop bit */
newtio.c_cflag &= ~CRTSCTS; /* no hardware flowcontrol */
newtio.c_lflag |= ICANON | ISIG; /* canonical input */
newtio.c_lflag &= ~(ECHO | ECHOE | ECHONL | IEXTEN);
newtio.c_iflag &= ~INPCK;
newtio.c_iflag |= ICRNL;
newtio.c_iflag &= ~(INLCR | IGNCR | IUCLC | IMAXBEL);
newtio.c_iflag &= ~(IXON | IXOFF | IXANY); /* no SW flowcontrol */
newtio.c_oflag &= ~OPOST;
The following is the preferred method for setting the baudrate:
cfsetospeed(&newtio, B9600);
cfsetispeed(&newtio, B9600);
If any salient attributes are left unspecified, the existing settings are used.
This can lead to erratic program behavior, e.g. sometimes it works, sometimes it doesn't.
An interesting note is that when I have minicom open, if I start my program, minicom will stop printing anything, and stay that way even if I stop my program. I need to open the serial settings again (Ctrl-A, P) and close it for minicom to resume working (it appears that nothing was changed).
The serial terminal is not intended for sharing among more than one process.
Some of the termios attributes have to be implemented in the serial device driver, which has no concept of sharing the port. The most recent termios attributes are in effect for the device.
When you execute your program after minicom has started, you are clobbering the termios attributes that minicom expects to execute with.
You are restoring the termios attributes to minicom's requirements by using its menu.

Termios configuration

In a MAC OS X (10.10) program, I have a hard time setting up termios correctly for RS-485 serial communication (I use a starcom USB → RS-485 featuring the FTDI chip)
I need to set up the following:
1 Start bit (0 bit)
8 Databits
1 Odd parity bit
1 Stop bit (1 bit)
19200 baud
Communication is binary, so there is no stop character.
Currently, I have found how to set most of the settings (see code below), but I didn't found where to tell termios I want a start bit.
#property(readwrite) int fileDescriptor;
_fileDescriptor = open(bsdPath, O_RDWR | O_NOCTTY | O_NONBLOCK);
ioctl(_fileDescriptor, TIOCEXCL)
fcntl(_fileDescriptor, F_SETFL, 0)
tcgetattr(_fileDescriptor, &gOriginalTTYAttrs)
struct termios options = gOriginalTTYAttrs;
cfmakeraw(&options);
struct termios* rawOptions = &options;
// Timeout 100 ms
rawOptions->c_cc[VTIME] = 1;
rawOptions->c_cc[VMIN] = 0;
// 8 bits
rawOptions->c_cflag |= CS8;
// Parity ODD
rawOptions->c_cflag |= PARENB;
rawOptions->c_cflag |= PARODD;
// Stop bit (I hope it means one stop bit)
rawOptions->c_cflag = rawOptions->c_cflag & ~CSTOPB;
// Flow control none
rawOptions->c_cflag = rawOptions->c_cflag & ~CRTSCTS;
rawOptions->c_cflag = rawOptions->c_cflag & ~(CDTR_IFLOW | CDSR_OFLOW);
rawOptions->c_cflag = rawOptions->c_cflag & ~CCAR_OFLOW;
// Turn on hangup on close (NO IDEA WHAT IT DOES)
rawOptions->c_cflag |= HUPCL;
// Set local mode on (NO IDEA WHAT IT DOES)
rawOptions->c_cflag |= CLOCAL;
// Enable receiver (USEFUL, I GUESS)
rawOptions->c_cflag |= CREAD;
// Turn off canonical mode and signals (NO IDEA WHAT IT DOES)
rawOptions->c_lflag &= ~(ICANON /*| ECHO*/ | ISIG);
// Raw (binary output)
rawOptions->c_oflag &= ~OPOST;
// 19200 baud
cfsetspeed(rawOptions, 19200);
// Applying settings
tcsetattr(_fileDescriptor, TCSANOW, rawOptions);

Unix: How to clear the serial port I/O buffer?

I am working on a "high level" C++ interface for the standard PC serial port. When I open the port, I would like to clear the input and output buffers in order not to receive or send data from previous usage of the port. To do that, I use the tcflush function. However, it does not work. How can that be? My "port opening" code can be seen below. Yes I use C++ exceptions but none are getting thrown. That indicates that tcflush returns 0 but it does not clear the buffer.
The only way I can clear the input buffer is to read bytes from it until there is none left. This usually takes a couple of seconds and I do not think of it as a solution.
Thanks in advance :-)
fd = ::open(port.c_str(), O_RDWR | O_NOCTTY);
if (fd < 0)
{
throw OpenPortException(port);
return;
}
// Get options
tcgetattr(fd, &options);
// Set default baud rate 9600, 1 stop bit, 8 bit data length, no parity
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;
// Default timeout (1000 ms)
options.c_cc[VMIN] = 0;
options.c_cc[VTIME] = 10;
// Additional options
options.c_cflag |= (CLOCAL | CREAD);
this->port = port;
// Apply the settings now
if (tcsetattr(fd, TCSANOW, &options) != 0)
{
throw PortSettingsException();
}
// Flush the port
if (tcflush(fd, TCIOFLUSH) != 0)
{
throw IOException();
}
This is the correct way (as below):
usleep(1000);
ioctl(fd, TCFLSH, 0); // flush receive
ioctl(fd, TCFLSH, 1); // flush transmit
ioctl(fd, TCFLSH, 2); // flush both
User can choose both of the first 2 lines OR last line alone based on requirement. Please check if sleep may be required.
Try
ioctl(fd, TCFLUSH, dir)
with dir equal to 0 for receive, 1 for transmit, 2 for both.