How to read serial data from an Arduino in Linux with C: Part 3

Preface: Part 1 covered the sketch the Arduino will run for this example, and part 2 covered the resources and other people’s code I used to make sure everything works as expected. In part 3 I’m going to go through a small program that does exactly what I want: read serial data from the Arduino. Part 4 is here.

Part 3: Reading

I’ve had a tough time writing this part. One of the hardest lessons I’ve had to learn over the past few years of learning C is that there is never one way to do something, and of the multiple ways to do something there is often never one clear better way to do it.

My Arduino is sending “Hello World” once every second to my PC via a serial connection, and I want to read this and print it out on the PC. Should I write my program to wait until 12 characters are read? Should I wait until one second goes by and write whatever was in the buffer? Should I open the port to be blocking or non-blocking? Canonical or non-canonical?

There is no right answer, so I went with what I could make work in a sensible way. Since my data is always going to be 12 characters long, and it’s always going to come in at once per second, I decided to stick with something similar to Tod Kurt’s example with the exception that instead of looking for the newline character, I’ll use the VMIN control to get exactly 12 (or however many) characters from the serial buffer.

Here’s the program:


#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <termios.h>

/* My Arduino is on /dev/ttyACM0 */
char *portname = "/dev/ttyACM0";
char buf[256];

int main(int argc, char *argv[])
{
 int fd;

/* Open the file descriptor in non-blocking mode */
 fd = open(portname, O_RDWR | O_NOCTTY);

/* Set up the control structure */
 struct termios toptions;

 /* Get currently set options for the tty */
 tcgetattr(fd, &toptions);

/* Set custom options */

/* 9600 baud */
 cfsetispeed(&toptions, B9600);
 cfsetospeed(&toptions, B9600);
 /* 8 bits, no parity, no stop bits */
 toptions.c_cflag &= ~PARENB;
 toptions.c_cflag &= ~CSTOPB;
 toptions.c_cflag &= ~CSIZE;
 toptions.c_cflag |= CS8;
 /* no hardware flow control */
 toptions.c_cflag &= ~CRTSCTS;
 /* enable receiver, ignore status lines */
 toptions.c_cflag |= CREAD | CLOCAL;
 /* disable input/output flow control, disable restart chars */
 toptions.c_iflag &= ~(IXON | IXOFF | IXANY);
 /* disable canonical input, disable echo,
 disable visually erase chars,
 disable terminal-generated signals */
 toptions.c_iflag &= ~(ICANON | ECHO | ECHOE | ISIG);
 /* disable output processing */
 toptions.c_oflag &= ~OPOST;

/* wait for 24 characters to come in before read returns */
 toptions.c_cc[VMIN] = 12;
 /* no minimum time to wait before read returns */
 toptions.c_cc[VTIME] = 0;

/* commit the options */
 tcsetattr(fd, TCSANOW, &toptions);

/* Wait for the Arduino to reset */
 usleep(1000*1000);
 /* Flush anything already in the serial buffer */
 tcflush(fd, TCIFLUSH);
 /* read up to 128 bytes from the fd */
 int n = read(fd, buf, 128);

/* print how many bytes read */
 printf("%i bytes got read...\n", n);
 /* print what's in the buffer */
 printf("Buffer contains...\n%s\n", buf);

return 0;
}

It’s not that interesting to look at. I’ll try to explain it best I can – there are several key things that I’m shaky on that require further experimentation.

First up is declaring the headers, and a few constants, like the port name and the buffer I’ll be reading into. I picked 256 as the buffer size for no particular reason other than it’s bigger than I need.


#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <termios.h>

/* My Arduino is on /dev/ttyACM0 */
char *portname = "/dev/ttyACM0";
char buf[256];

In main() I declare an integer ‘fd’ to be the file descriptor that open() returns on the next line.


int fd;

/* Open the file descriptor in non-blocking mode */
 fd = open(portname, O_RDWR | O_NOCTTY);

I mentioned before that some things were still unknown to me. In Tod Kurt’s example he uses open() with one more option – O_NDELAY – which opens the port in non-blocking mode. I had some complications with this, so I removed it and magically my complications went away. Several iterations of this program ago I found that using non-blocking mode meant that read() wouldn’t wait until data was in the buffer to return, but instead of read() returning 0 it was returning -1. This wound up being because my Arduino was busy rebooting (which is does when you open the port) while read() was running. I added some delay and it worked fine, but then I couldn’t reconcile the implications of a non-blocking port and non-canonical input. I’ve been oscillating between thinking I had a grip on it and being completely confused, so I’ll tackle it another day.

Next is setting up the serial communications to our particular way of doing things. Terminal options are held in a termios structure, and it’s typical after declaring the structure (although not entirely necessary, I think) to then set it to the currently set options of the port.


/* Set up the control structure */
 struct termios toptions;

 /* Get currently set options for the tty */
 tcgetattr(fd, &toptions);

So now we have the structure toptions set to what currently is set on the tty port. The terminal command stty can show you what a port is set to, so I assume tcgetattr() does something similar and plugs it into the toptions structure.

The next part here is basically the same as Tod Kurt’s example, but it’s all pretty typical – the Unix programming book I’m referencing has something similar in the examples where you want the data coming at you in non-canonical raw mode. The comments explain what each line does. Some flags are grouped together for brevity – they don’t need to be like that (and they COULD all be lumped together).


/* 9600 baud */
 cfsetispeed(&toptions, B9600);
 cfsetospeed(&toptions, B9600);
 /* 8 bits, no parity, no stop bits */
 toptions.c_cflag &= ~PARENB;
 toptions.c_cflag &= ~CSTOPB;
 toptions.c_cflag &= ~CSIZE;
 toptions.c_cflag |= CS8;
 /* no hardware flow control */
 toptions.c_cflag &= ~CRTSCTS;
 /* enable receiver, ignore status lines */
 toptions.c_cflag |= CREAD | CLOCAL;
 /* disable input/output flow control, disable restart chars */
 toptions.c_iflag &= ~(IXON | IXOFF | IXANY);
 /* disable canonical input, disable echo,
 disable visually erase chars,
 disable terminal-generated signals */
 toptions.c_iflag &= ~(ICANON | ECHO | ECHOE | ISIG);
 /* disable output processing */
 toptions.c_oflag &= ~OPOST;

This was the first time I’d really had to set/unset options using bitwise operators (in a non-tutorial setting). It’s important to remember that it’s there for your convenience, and not meant to annoy. An alternative approach would be to have the termios structure have every individual option exist as a variable set to 0 or 1 in the structure, but for the sake of size and brevity (and because C doesn’t have a true bool type perhaps) it was done such that in the termios structure c_iflag, c_oflag, c_cflag, and c_lflag are all unsigned integers, and each of the 16 bits of the unsigned int represent a different option that can be set. Wikipedia helps explain what the & | and ~ do for us here.

VMIN and VTIME are important in non-canonical processing of serial data. There’s a fantastic explanation of how to best utilize them here, but we can take it in this example to mean that read() will wait for 12 characters to come in before returning.


/* wait for 24 characters to come in before read returns */
 toptions.c_cc[VMIN] = 24;
 /* no minimum time to wait before read returns */
 toptions.c_cc[VTIME] = 0;

So at this point the bits are set, but the serial driver doesn’t know it, so call tcsetattr().


/* commit the options */
 tcsetattr(fd, TCSANOW, &toptions);

All that setting happens nearly instantly after open() is called. It’s not very obvious, but when open is called a signal is sent via the serial port that the Arduino interprets as “reboot now”. There is a way around it on the Arduino end, but it’s easy enough to simply use usleep() to wait a bit before calling read().


/* Wait for the Arduino to reset */
 usleep(1000*1000);
 /* Flush anything already in the serial buffer */
 tcflush(fd, TCIFLUSH);
 /* read up to 128 bytes from the fd */
 int n = read(fd, buf, 128);

/* print how many bytes read */
 printf("%i bytes got read...\n", n);
 /* print what's in the buffer */
 printf("Buffer contains...\n%s\n", buf);

The last bits of code do the waiting for reboot, flushes what’s still in the serial buffer, reads into the buffer declared (obeying the VMIN rules already defined) and prints out what was received. If VMIN is changed from 12 to 24 you get “Hello World” twice. This makes sense so all is well.

So there is a minimal but functional example of reading serial data from an Arduino. Here are my remaining questions I want to answer someday.

1) Is tcgetattr() really necessary? What if I memset() the structure to 0 and only set what I want?

2) How much of what I set is really necessary? I’m only reading data so should I care about ECHO, ECHOE, or ISIG?

3) Why does Tod’s example work when using a non-blocking port, but mine doesn’t?

4) What’s the point of using a non-blocking port with non-canonical communication?

Some of these questions likely have very complicated answers. I know that there are functions for intelligently knowing when a serial port is ready (which would help with question 3 and 4). Question 1 and 2 simply require experimenting (which requires time).

Part 4 will take longer to come. I want to use what I’ve learned here to do something interesting with serial communication. I have a good project in mind, and I’m shooting for results and a writeup by mid-July. In the meantime I’m going to start learning about socket programming.

EDIT: Part 4 is here.

This entry was posted in Hobbies, Programming. Bookmark the permalink.

5 Responses to How to read serial data from an Arduino in Linux with C: Part 3

  1. Matouš says:

    Thank you! I finally managed to get the communication working!

  2. Djwk says:

    You had the exactly same problems with the communications than I did! :) My take on question 1 is that it’s not needed. I guess it could be system specific, but I have no need to reinstate the previous settings on the device (/dev/ttyACM?) after i’m done (i.e the program terminates). I’m assuming that’s what it is used for.

  3. Anonymous says:

    Your article gave me a great start for writing my own code! However, I was having a problem where I could only receive data from my Arduino after I used a serial port monitor like GNU screen or the monitor that comes with the Arduino IDE. I finally solved this problem by adding cfmakeraw(&toptions) after tcgetattr(fd, &toptions). I’ll admit, I don’t really know what cfmakeraw does, but it seems to work.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s