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;
}
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 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.
I am getting successful reads of a fd in Linux, but with 0 bytes read which means EOF has been reached. I should be reading 19 bytes in every read.
The project is a motor driver that sends out packets of 19 bytes to drive 2 DC motors, and it also needs to read the same size packet coming from the motor with updated position, command, and status info.
I open the fd like this:
mc_fd = InitPort("/dev/ttyS1", "COM2", O_NONBLOCK | O_RDWR | O_SYNC, B115200);
Here is the function to initialize the port:
int InitPort( char *port, char *name, int oflags, speed_t baudRate ) {
int fd; // File descriptor
fd = open(port, oflags); // Open the port like a file
assert(fd > 0); // Open returns -1 on error
struct termios options; // Initialize a termios struct
tcgetattr(fd, &options); // Populate with current attributes
cfsetospeed (&options, baudRate); // Set baud rate out
cfsetispeed (&options, baudRate); // Set baud rate in (same as baud rate out)
options.c_cflag &= ~CSIZE; // Clear bit-length flag so it can be set
//8N1 Serial Mode
options.c_cflag |= CS8; // Set bit-length: 8
options.c_cflag &= ~PARENB; // Set parity: none
options.c_cflag &= ~CSTOPB; // Set stop bit: 1
options.c_cflag &= ~CRTSCTS; // Set flow control: none
options.c_iflag &= ~ICANON; // Enable canonical input
options.c_oflag &= ~OPOST; // Disables all output processing (prevents CR in output)
options.c_cflag |= (CLOCAL | CREAD);// Enable receiver, and set local mode
tcsetattr(fd, TCSANOW, &options); // Set new attributes to hardware
return fd;
}
Originally, I only used the O_RDWR flag and reads of the fd would fail with EAGAIN (or EWOULDBLOCK). I have been trying synchronization and non-blocking settings to see if I could receive packets. At least now I am reading successfully (I think).
I am able to write packets out at 120Hz, and reads of the fd return "success" at the same rate, although with 0 bytes read.
How do I get read() to read the incoming packets?
Here is the read code along with the output from the terminal:
bytesRead = read( mc_fd, readPacket, MC_PACKET_SIZE );
printf("\npacket: %019X\n", &readPacket);
perror("error type ");
printf("bytes read = %d\n", bytesRead);
packet: 00000000000B63B4140
error type : Success
bytes read = 0
The 8-digit hex number in the least significant part of the packet is always similar to that shown, and is not what is expected in the packet.
This is Debian running on an embedded linux SBC (single board computer). I am able to read other file descriptors in the program with no problems. I am still fairly new to Linux and may be missing something obvious. Thanks!
...but with 0 bytes read which means EOF has been reached.
Incorrect.
You're reading a serial terminal in non-blocking mode.
A return code of zero simply means that no data was available from the terminal at that time.
That's what your program (which you should have posted) has to deal with when you use non-blocking mode.
How do I get read() to read the incoming packets?
Use blocking mode (i.e. remove the O_NONBLOCK option from the open()) if you do not want to see a return code of zero (or errno set to EAGAIN).
But don't expect the read() syscall to message-align the packets for you unless you have text in canonical mode.
Study this answer.
The 8-digit hex number in the least significant part of the packet is always similar to that shown, and is not what is expected in the packet.
You've posted so little of your code (which is grounds for closing the question), but you try to read the data into readPacket, which seems to be a (byte?) array.
But then you treat readPacket as if it was an integer in the printf().
Printing the address of the array address (or the address of an integer variable) accomplishes nothing (i.e. "8-digit hex number ... is always similar to that shown"). You have not displayed anything that might have been received.
If you're using a little-endian, 32-bit processor, accessing a byte array as a long interger will reverse the order of the bytes of each word (i.e. "not what is expected"), and only access the first four bytes, which could be represented by eight hexadecimal digits .
I am still fairly new to Linux ad may be missing something obvious.
Although Linux is one of those OSes where (almost) "everything is a file", those "files" may not be equal. In particular the device file that your program accesses, i.e. /dev/ttyS1, is a serial terminal device. Serial terminals require additional device configuration, which is performed with the termios structure.
Since you have posted only a few lines of your program, and make no mention of any termios concepts other than baudrate, your program cannot be evaluated.
Addendum
Now that you've posted some initialization code, a few more mistakes are evident.
Regardless of your programming experience, the following inconsistency between what the code does and the comment is a flaw that can prolong debugging.
options.c_iflag &= ~ICANON; // Enable canonical input
Clearing the ICANON flag enables non-canonical input, the opposite of what the comment states.
You have not described the 19 bytes of data, so whether canonical mode is appropriate cannot be determined.
Your termios initialization is well written (i.e. you use the proper Boolean operators instead of direct assignments), but is incomplete (based on the existing code as executed for non-canonical mode).
All of the necessary flags for non-canonical mode can be configured by simply using the cfmakeraw() routine.
Your code does not initialize the VMIN and VTIME parameters, but since the combination of non-canonical and non-blocking modes disables that feature, it doesn't matter.
Since you have done a poor job of describing what you are trying to do, the appropriate corrections cannot be suggested.
I should be reading 19 bytes in every read. [...]
"/dev/ttyS1"
On a serial port with only 16 byte fifo or less? I don't think that will work.
Serial ports are character devices for a good reason - you want to code in a way that reads work anywhere from zero bytes (if a timeout is enabled), one byte and up to the size of the internal fifo. Don't expect them to stay in neat packets even if you (try to) send them that way.
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.
I wrote an application that must use serial ports on Linux, especially ttyUSB ones. Reading and writing operations are performed with standard select()/read() loop and write(), and there is probably nothing wrong in them, but initialization code (or absence of some part of it) damages something in the tty subsystem. Here it is:
vuxboot(string filename, unsigned baud = B115200) : _debug(false) {
_fd = open(filename.c_str(), O_RDWR | O_NOCTTY);
if(_fd < 0) throw new io_error("cannot open port");
// Serial initialization was written with FTDI USB-to-serial converters
// in mind. Anyway, who wants to use non-8n1 protocol?
tcgetattr(_fd, &_termios);
termios tio = {0};
tio.c_iflag = IGNPAR;
tio.c_oflag = 0;
tio.c_cflag = baud | CLOCAL | CREAD | CS8;
tio.c_lflag = 0;
tcflush(_fd, TCIFLUSH);
tcsetattr(_fd, TCSANOW, &tio);
}
Another tcsetattr(_fd, TCSANOW, &_termios) sits in the destructor, but it is irrelevant.
With or without this termios initialization, strange things happen in system after the application exits. Sometimes plain cat (or hd) exits immediately printing nothing or same stuff each time, sometimes it is waiting and not displaying any of the data that is surely sent onto the port; and close() (read() too, but not every time) emits a strange WARNING to dmesg referring to usb-serial.c.
I checked the hardware and firmware tens of times (even on different machines) and I am sure it is working as intended; moreover, I stripped the firmware to just print same message over and over.
How can I use serial port without destroying anything? Thanks.
Hitting a WARN_ON line might mean that you've hit a kernel bug. I know that there has been much work on improving the USB-serial driver lately; I suggest trying a newer kernel, and/or asking on the linux-usb#vger.kernel.org mailing list.
I am not sure what is wrong with your snippet of code there but this might come in handy, if you haven't already seen it: Serial Programming Guide for POSIX Operating Systems
I had to do some serial port interfacing quite recently and this library worked fine, that might serve as another example.
Just as a side note really, your error check on open isn't quite right - error conditions are signalled by a return value of -1. (0 is a perfectly valid fd, usually connected to stdin.)
You might want to try:
vuxboot(string filename, unsigned baud = B115200) : _debug(false) {
_fd = open(filename.c_str(), O_RDWR | O_NOCTTY);
if(_fd < 0) throw new io_error("cannot open port");
// Serial initialization was written with FTDI USB-to-serial converters
// in mind. Anyway, who wants to use non-8n1 protocol?
tcgetattr(_fd, &_termios);
- termios tio;
+ termios tio;
+ memcpy(&tio, &_termios, sizeof(struct termios));
tio.c_iflag = IGNPAR;
tio.c_oflag = 0;
tio.c_cflag = baud | CLOCAL | CREAD | CS8;
tio.c_lflag = 0;
tcflush(_fd, TCIFLUSH);
tcsetattr(_fd, TCSANOW, &tio);
}
This makes it so that any unexpected fields of termios on your system get somewhat reasonable values.
Okay. This may not be a perfect solution... it definitely isn't. I just threw out FT232 converter (fried it, actually), and used CP2102-based one. It just works now (and also is 6 times cheaper).