I'm trying to interface with a device via the serial port. The device sends out a 10 byte poll as a "heartbeat" every 700ms. Everytime I read the heartbeat I have to reply with a 12 byte long response.
Within this response I can request that the device sends particular data inbetween polls. The amount of data is different for different requests. Is there a way to set up the serial port such that it will always read messages in one chunk regardless of their size?
My current terminal settings are as follows:
int ttySetRaw(int fd, struct termios *prevTermios)
{
struct termios t;
if (tcgetattr(fd, &t) == -1)
return -1;
if (prevTermios != NULL)
*prevTermios = t;
t.c_lflag &= ~(ICANON | ISIG | IEXTEN | ECHO);
t.c_iflag &= ~(BRKINT | ICRNL | IGNBRK | IGNCR | INLCR | INPCK | ISTRIP | IXON | PARMRK);
t.c_oflag &= ~OPOST; /* Disable all output processing */
t.c_cc[VMIN] = 12; /* 12 chars at a time, enough for the poll and the reply to be sent/received in one chunk, when I change this I no longer receive the poll*/
t.c_cc[VTIME] = 10; /* maximum timeout 1 second */
t.c_cflag |= PARENB;
t.c_cflag |= PARODD;
if (tcsetattr(fd, TCSAFLUSH, &t) == -1)
return -1;
return 0;
}
I've tried changing VMIN and VTIME, I thought setting VTIME = 7 would mean it would read everything in the buffer until 700ms had passed, but this fails. It would also not be sufficient for the times when I want the device to send another, longer, message inside the polling interval.
Is there a setup that would be able to achieve what I want, or am I going to have to byte the bullet and read the data one byte at a time and build the messages up in a separate function?
Is there a way to set up the serial port such that it will always read
messages in one chunk regardless of their size?
In general, no. Like TCP streams, serial links are byte streams and have no message-boundaries bigger than one byte.
You need a protocol that allows the messages to be parsed out of the byte stream.
That said, some serial hardware and drivers allow a 'break' signal to be sent and detected, but I don't know anyone who uses that feture any more, even if available.
Related
I am reading data frames from a device using a software UART driver. It's written as a tty module so reading from is is pretty standard:
struct termios options;
int tty = open(path, O_RDWR | O_NOCTTY | O_SYNC);
tcgetattr(tty, &options);
options.c_cflag = B4800 | CS8 | CLOCAL | CREAD | PARENB;
options.c_iflag = IGNPAR;
options.c_oflag = 0;
options.c_lflag = 0;
tcsetattr(tty, TCSANOW, &options);
while (running)
{
int rx_count = read(tty_device, (void*)header, header_size);
// parse the header to figure out the total frame size
rx_count = read(tty_device, (void*)frame_data, frame_size);
}
It works most of the time, but sometimes I miss some bytes due to limitations in the reliability of the UART.
My problem is that when I miss bytes the code reads the initial bytes of the next frame as the final bytes of the previous frame. This causes random, unpredictable behavior.
I am wondering if there is a good way to read based on time, instead of data size. Something like this:
while (running)
{
// read bytes continuously from the tty device
// if 1 second passes with no further bytes coming, stop reading
// process whatever data was read
}
I could probably hack together something that does this eventually but I imagine there is a good chance this has already been figured out, and I am just failing to find the info on the internet.
I was able to accomplish what I need using VMIN and VTIME as found in the link from the comment of sawdust:
Linux Blocking vs. non Blocking Serial Read
This was also relevant to my problem. Somehow I couldn't figure out the right search terms to find it before:
VMIN and VTIME Terminal Settings for variable sized messages
I'm trying to create a function with the most minimal delay possible that checks to see if the serial port has data and if it does, it reads every single byte and prints each byte in hex format until no more bytes are available. If there is no data, the function must return right away.
This is my code:
int fd=open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_SYNC);
// Trying to set correct options here
struct termios o;
tcgetattr(fd,&o);
cfsetispeed(&o,57600);
cfsetospeed(&o,57600);
/* 8 bits, no parity, 1 stop bit */
o.c_cflag &= ~PARENB;o.c_cflag &= ~CSTOPB;o.c_cflag &= ~CSIZE;o.c_cflag |= CS8;
/* no hardware flow control */
o.c_cflag &= ~CRTSCTS;
/* enable receiver, ignore status lines */
o.c_cflag |= CREAD | CLOCAL;
/* disable input/output flow control, disable restart chars */
o.c_iflag &= ~(IXON | IXOFF | IXANY);
/* disable canonical input, disable echo, disable visually erase chars, disable terminal-generated signals */
o.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
/* disable output processing */
o.c_oflag &= ~OPOST;
o.c_cc[VMIN] = 0; //to prevent delay in read();
o.c_cc[VTIME] = 0;
tcsetattr(fd, TCSANOW, &o);
tcflush(fd, TCIFLUSH);
char sp[1]; //hold 1 byte
int bytes=read(fd,sp,1); //Good news: this function doesn't lock
if (bytes > 0){
//this is never reached even if a byte is
//present on the serial line. why?
printf("Read: ");
while(bytes > 0){
printf("%X ",sp[0]);
bytes=read(fd,sp,1);
}
}
fclose(fd);
Is there a way to fix this?
This whole function (minus the serial port options section) will eventually run in an endless loop as I am constantly scanning my port for data then printing it. Then later I'll add some more functionality where I'll write data to the port at predefined times regardless of what data is received.
P.S. not sure if this is of help, but the target device I'm doing I/O with is custom hardware around an 8051 microcontroller and its serial fifo buffers are only 1 byte whereas the PC's buffers I think are 14 or 16 bytes.
If you know beforehand the times when you need to write to the device, you can use select() or poll() to wait for input until the next time you wish/intend to write.
A much more simple and robust approach — because your reads and writes are not in a defined sequence, and your hardware is full duplex — is to use separate threads for reading and writing. Basically, you use blocking reads and writes (c_cc[VMIN] = 1, c_cc[VTIME] = 0 for reading, O_NONBLOCK not among file open flags). You should allow larger buffers, though; reads will return all that have been received thus far, but with those settings wake up the reader whenever there is at least one char received. For writing, I do recommend you do a tcdrain(fd); after each write that completes a command/message to the device, to ensure it is sent on the wire by the kernel. (Do remember that writes to the serial port can be short; you need a write loop.)
In all cases, the kernel on the host side will cache data sent and received. Depending on the hardware and driver, even a blocking write() may return earlier than when all of the data is actually on the wire. The hardware and the kernel driver, not the host software, is responsible for correct timing of the serial data.
Using one-byte buffer on the host side will not affect the microcontroller at all, you'll just do more syscalls than is necessary, wasting CPU resources and possibly slow down your program a bit. At 57600 baud, with 8 data bits, no parity, 1 stop bit, and the implicit start bit, the actual data rate is 46080 bits/second (±5% typically allowed), or 5760 bytes per second. The microcontroller will always have about 1 s /5760 ≃ 0.0001736 seconds, or over 173 microseconds, to process each incoming byte. (I'd design my firmware to not allow higher priority interrupts etc. to delay processing for more than 100 microseconds or so even in the worst case, to ensure no chars are dropped. If you receive the chars in an interrupt handler, I'd use a small circular buffer, and an indicator character, \r or \n, so that if such character is received, a flag is raised for the main program to notice that a new complete command has been received. The circular buffer should be long enough to hold two full commands, or more if some commands may take longer to parse/process/handle.)
If the host operating system wakes up the process 1 ms after of receiving the first character, additional four or five characters have arrived in the mean time. Because this latency can be much higher on some systems, I'd use a much larger buffer, say up to 256 chars, to avoid doing superfluous syscalls when the kernel is for some reason delayed in waking up the reader thread. Yes, it will often read just 1 char, and that is fine; but when the system is overloaded, you don't want to add to that load by doing hundreds of superfluous syscalls when one would suffice.
Remember, the termios interface, with VMIN=1, VTIME=0, will cause the blocking read to be woken up as soon as possible, whenever even a single character is received. It is just that you cannot ensure your program is constantly running, unless you waste about 100% of CPU power by spinning in place (and if you do, nobody will want to run your program). Depending on the system, there may be a delay in waking up the blocking read, during which time more data may be received, so using a larger read() buffer is definitely sensible.
Similarly, you can safely use as large writes as you want (up to a limit of about 2 GiB), although most serial drivers can return short counts, so you'll need a loop there anyway. The tcdrain(fd) on the serial port descriptor will block until all written data has been actually transmitted, so you'll probably want to use it. (If you do not, you can just write more data; the kernel driver will take care of the details, and not reorder/mess data up.)
Using two threads, one for reading, one for writing, may sound daunting/odd, but it is actually the simplest way of achieving robust communications. You can even use pthread_setcancelstate(), pthread_setcanceltype() and optionally pthread_testcancel(), to write the thread functions so that you can simply cancel the threads (using pthread_cancel()) to stop them, even if they have critical sections (like adding a received message to some queue protected by a mutex).
Your program has a variety of problems, many of which have been discussed in comments:
you do not check the return values of your functions
you poll the serial port and furthermore perform a system call for every byte read
you appear to be setting the serial port transmission speed incorrectly, via raw integers instead of the macros reserved for the purpose
you are using the wrong printf format
Here is a modified version of your code that addresses all of those issues. It's unclear whether this will do exactly what you want, and of course I can't test it on your hardware, but it should be a better place to start:
int open_serial(const char *device) {
int fd = open(device, O_RDWR | O_NOCTTY | O_SYNC);
if (fd >= 0) {
return -1;
}
// Set appropriate serial and line-discipline options
struct termios o;
if ((tcgetattr(fd, &o) != 0)
|| (cfsetispeed(&o, B57600) != 0) // note use of MACRO B57600
|| (cfsetospeed(&o, B57600) != 0)) { //
return -1;
}
/* 8 bits, no parity, 1 stop bit, no hardware flow control */
o.c_cflag &= ~(PARENB | CSTOPB | CSIZE | CRTSCTS);
o.c_cflag |= CS8;
/* enable receiver, ignore status lines */
o.c_cflag |= CREAD | CLOCAL;
/* no software flow control */
o.c_iflag &= ~(IXON | IXOFF | IXANY);
/* Operate in raw mode without echo or signaling */
o.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
/* disable output processing */
o.c_oflag &= ~OPOST;
/* Perform pure timed reads */
o.c_cc[VMIN] = 0;
o.c_cc[VTIME] = 1;
/* apply the settings, and discard any buffered input */
if ((tcsetattr(fd, TCSANOW, &o) != 0)
|| (tcflush(fd, TCIFLUSH) != 0)) {
return -1;
}
return fd;
}
int read_serial(int fd) {
/*
* 0.1-sec timed reads of a port running at 56K may read up to 720 bytes each,
* give or take. Allow for a few more to account for overhead, etc..
*/
char sp[768];
int nr;
puts("Read: ");
// Reads will normally block for 0.1 seconds:
while ((nr = read(fd, sp, sizeof(sp))) != -1) {
// Any data ready to send can be dispatched now ...
for (int i = 0; i < nr; i++) {
printf("%hhX ", (unsigned char) sp[i]);
}
fflush(stdout);
}
return -1;
}
I'm writing a program that runs on a MIO-3260 single board computer running Ubuntu server 14.04 and communicates with a AMC DPRANIE C100A400 drive. The program sends a string of hex codes to the drive and is supposed to receive a response for every message it sends. When I try it in realTerm on windows this works well so I don't think it's an issue with the drive. However, when I try to read from the serial port read() returns -1 almost all the time, until suddenly at a seemingly random point I get a massive dump of messages all at once.
I'm using termios to set up the serial port. Here's my code for that. I've tried setting it up in a blocking configuration but if I do that the code just hangs indefinitely at the first read().
int fd;
fd = open("/dev/ttyS0",O_RDWR | O_NOCTTY | O_NDELAY);
struct termios SerialPortSettings;
tcgetattr(fd, &SerialPortSettings); //get current settings of serial port
cfsetispeed(&SerialPortSettings,B115200);//set input baud rate
cfsetospeed(&SerialPortSettings,B115200);//set output baud rate
SerialPortSettings.c_cflag &= ~PARENB;//clear parity bit (no parity)
SerialPortSettings.c_cflag &= ~CSTOPB;//Stop bits = 1
SerialPortSettings.c_cflag &= ~CSIZE;//clears the mask
SerialPortSettings.c_cflag |= CS8; //set data bits = 8
SerialPortSettings.c_cflag &= ~CRTSCTS; //turn off hardwar based flow ctrl
SerialPortSettings.c_cflag |= CREAD | CLOCAL;//Turn on the reciever
SerialPortSettings.c_iflag &= ~(IXON | IXOFF | IXANY); //Turn off software
//based flow control
SerialPortSettings.c_iflag &= ~(ICANON | ECHO | ECHOE | ISIG);//Non-canonical mode
SerialPortSettings.c_oflag &= ~OPOST;//no output processing
//set the termios struct now
if(tcsetattr(fd,TCSANOW,&SerialPortSettings) != 0)
printf("\n ERROR: setting attributes");
else
printf("\n Baudrate = 115200 \t Stopbits = 1 \t Parity = none");
To read from the serial port, my code is as follows:
uint8_t buf[1024];//Rx buffer
int bytes_read;
bytes_read = read(fd,&buf,1024);
if(bytes_read != -1){
for(int i=0;i<bytes_read;i++) /*printing only the received characters*/
printf("%02X\t",buf[i]);
puts("\n");
}
Here's a sample of the kind of message I should be receiving {0xA5 0xFF 0x10 0x01 0x00 0x00 0xD4 0x11}. It should be about 8-14 bytes long. Instead I receive a huge number all at once and none at other times (for example I just received 810 bytes at once after sending 946 commands with no response).
I've been troubleshooting it for the last few days and have no idea what's going on. Sometimes I run it and it responds most of the time and then mysteriously it just stops and then comes back intermittently.
Let me know if there's anymore information I can provide.
Any help would be really appreciated!
UPDATE:
I've connected my laptop to the serial port as well so I can spy on the transmissions using RealTerm, and I've verified that commands are being sent by the MIO-3260 correctly AND being responded to correctly by the drive. So the issue seems to be when I try to read from the port.
I've tried setting it up in a blocking configuration but if I do that the code just hangs indefinitely at the first read().
The results you describe are similar to blocking canonical mode (when you actually want (blocking) raw mode).
The read() is supposed to block until an EOL (end of line) character is received.
However, when I try to read from the serial port read() returns -1 almost all the time, until suddenly at a seemingly random point I get a massive dump of messages all at once.
The results you describe are similar to non-blocking canonical mode (when you actually want (blocking) raw mode).
When no data (i.e. a complete line) is available in the buffer, the read() will return -1 and errno (which you do not bother to examine) is set to -EAGAIN.
But when the binary data coincidentally matches an EOL (end of line) character, the condition to satisfy the canonical read() is met, and the buffered data is returned.
The reason why the serial terminal is not actually configured for non-canonical mode is because the ICANON and related flags are cleared from the wrong member.
SerialPortSettings.c_iflag &= ~(ICANON | ECHO | ECHOE | ISIG);//Non-canonical mode
ICANON is in the c_lflag member, and not in c_iflag.
So the statement should be
SerialPortSettings.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // Non-canonical mode
Presumably that serial terminal defaulted to canonical mode, and your program has never successfully changed the mode to anything different.
Additionally, for raw mode, the VMIN and VTIME members need to be defined.
For example:
SerialPortSettings.c_cc[VMIN] = 1;
SerialPortSettings.c_cc[VTIME] = 1;
Another bug in your code is the use of pointer to array address (i.e. address of address) when the array address would suffice.
bytes_read = read(fd,&buf,1024);
should simply be
bytes_read = read(fd, buf, 1024);
ADDENDUM
The OP's code is remarkably similar to this question, which has the identical bad termios statement.
That poster eventually solved his own problem, but mistakenly attributed the fix to adding an (irrelevant) ECHONL flag and not realizing that he was actually correcting the name of the structure member.
ADDENDUM 2
The origin of this c_iflag and ICANON bug seems to be this serial port tutorial from xanthium.in. The author was notified over two years ago of the bug, but has not fixed it.
In my program a connection is opened with serial port that recieves 10-byte packages with set frequency (usually 100 Hz). However i have run into the following problem: sometimes (but not always) when i am closing connection, the program hangs.
In the following code i do not specify constants; at runtime connection parameters (baud rate, parity...) are determined from command line. In practice, connection is usually opened with baud rate 230400, no parity, 8-bit character size and 1 stop bit.
This is how i am opening serial port:
struct termios config;
LOGPRINT("Will operate on device \'%s\'\n", pconfig->device);
LOGPRINT("Opening serial device\n");
int flag = O_RDWR | O_NOCTTY | ASYNC_LOW_LATENCY;
// pconfig contains some necessary information to set connection parameters
// async == false for connectin that causes problems
int fd = open(pconfig->device, async? flag | O_NONBLOCK: flag);
if(fd == -1)
{ /* skipped error handling */ }
LOGPRINT("Retrieving terminal attributes\n");
if(!!tcgetattr(fd, &config))
{ /* skipped error handling */ }
else
{
// backup old configuration for restoring it on exit
if(poldconfig != NULL)
memcpy(poldconfig, &config, sizeof(struct termios));
LOGPRINT("Setting terminal attributes\n");
set_attribs(fd, &config, pconfig);
}
...
int set_attribs(const int fd, struct termios* pterm, const struct serial_config* pconfig)
{
// set baud rate
// baud_rates contains integers of actual rates
LOGPRINT("Setting baud rate to %d.\n", baud_rates[pconfig->baud_rate_index]);
// change to raw mode
LOGPRINT("Setting terminal to raw mode\n");
pterm->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR
| IGNCR | ICRNL | IXON);
pterm->c_oflag &= OPOST;
pterm->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
// baud_bits contains actual pre-defined baud rate flags
cfsetispeed(pterm, baud_bits[pconfig->baud_rate_index]);
cfsetospeed(pterm, baud_bits[pconfig->baud_rate_index]);
LOGPRINT("Set character size, parity and stop bit flags.\n");
// set character size
pterm->c_cflag &= ~CSIZE;
pterm->c_cflag |= pconfig->csize_flag;
// set parity
pterm->c_cflag &= ~(PARENB | PARODD);
pterm->c_cflag |= pconfig->parity_flag;
// set stopbits
pterm->c_cflag &= ~CSTOPB;
pterm->c_cflag |= pconfig->stopbits_flag;
// enable reading; ignore control lines
pterm->c_cflag |= CREAD | CLOCAL;
LOGPRINT("Flush terminal.\n");
// flush terminal
tcflush(fd, TCIOFLUSH);
LOGPRINT("Apply parameters.\n");
return tcsetattr(fd, TCSANOW, pterm);
}
For valid reasons, i needed to force serial device driver to use 1-byte buffer for incoming/outgoing data, which i have set using this function:
int set_port_type(int fd, int ptype)
{
struct serial_struct temp;
int res = ioctl(fd, TIOCGSERIAL, &temp);
if(!!res)
{ /* stipped error handling */ }
temp.type = ptype;
res = ioctl(fd, TIOCSSERIAL, &temp);
if(!!res)
{ /* stipped error handling */ }
return res;
}
with ptype = PORT_UNKNOWN or ptype = PORT_16450.
When finished with serial port, i perform the following operations:
Flush it
Set its type back to 16550A
Restore its settigns to saved ones
Close it
From time to time it causes the program to hang and stop responding to any external stimulus. It forces me to suspend the program and then reboot system, as it never releases file descriptors it holds.
What can i do to ensure that does not happen? Even if settings are not restored, the program needs to close on its own - maybe with error code.
Operating system i am using is TinyCore Linux. No other user program is being run an the same time as my program.
IMportant clarification. Serial device i am working with is actually NPort 5232 - asyncronous RS-422/485 - Ethernet communicator (description from vendor. Consequently, serial devices i am working with are virtual, which enables me to try and change their type / size of internal buffer.
I have the following set up: a computer that runs Linux (TCL) and device that sends packets of fixed length (10 bytes) with fixed frequency (100 Hz).
On computer, i have that serial port open, and i am trying to read incoming data. However, instead of recieving it in 10-byte chunks all the time, lesser amounts of data are being read, and i am forced to reassemble them back.
My goal is to send a responce for each packet once it arrives, preserving intervals between packets as much as possible.
This is how i open the serial port:
int fd = open(device_name, O_RDWR | O_NOCTTY | O_NONBLOCK);
if(fd == -1)
{
LOGPRINT("Failed to open terminal file.\nError #%d (%s)\n",
errno, strerror(errno));
return fd;
}
LOGPRINT("Setting terminal attributes\n");
struct termios config;
struct termios *pterm = &config;
// set baud rate
LOGPRINT("Setting baud rate to %d.\n", baud_rates[baud_rate_index]);
// change to raw mode
LOGPRINT("Setting terminal to raw mode\n");
pterm->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR
| IGNCR | ICRNL | IXON);
pterm->c_oflag &= OPOST;
pterm->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
cfsetispeed(pterm, baud_bits[pconfig->baud_rate_index]);
cfsetospeed(pterm, baud_bits[pconfig->baud_rate_index]);
LOGPRINT("Set character size, parity and stop bit flags.\n");
// set character size
pterm->c_cflag &= ~CSIZE;
pterm->c_cflag |= csize_flag;
// set parity
pterm->c_cflag &= ~(PARENB | PARODD);
pterm->c_cflag |= parity_flag;
// set stopbits
pterm->c_cflag &= ~CSTOPB;
pterm->c_cflag |= stopbits_flag;
// enable reading; ignore control lines
pterm->c_cflag |= CREAD | CLOCAL;
// disable flow control
pterm->c_cc[VMIN] = 1;
pterm->c_cc[VTIME] = 0;
LOGPRINT("Flush terminal.\n");
// flush terminal
tcflush(fd, TCIOFLUSH);
LOGPRINT("Apply parameters.\n");
return WRAPSYSCALL(tcsetattr(fd, TCSANOW, pterm));
Additionally i set the following on file descriptor with fnctl:
int flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | FASYNC | O_DIRECT);
I am reading from file descriptor in a cycle with select() call on file descriptor set containing fd, then reading all bytes avaiable (requesting number of bytes much bigger then 10).
What should i change to ensure incoming data are processed as they come properly, in timely manner?
If your program does nothing else than waiting for bytes and answering, then you can try using blocking file operations. Remove O_NONBLOCK flag from your open function call, and after setting the port just do a read(fd, buffer, 10). The function will return after it reads exactly 10 bytes (or an error happens or signal arrives).
However, bear in mind that you read operation can start in the middle of packet transmission, so for example you can get last x bytes of n-th packet and first 10-x of packet n+1.