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. IMPORTANT: This means that read() function will block (pause) until all 12 characters are received!

Here’s the program:


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

/* My Arduino is on /dev/ttyACM0 */
char *portname = "/dev/ttyACM"
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, &amp;amp;toptions);

/* Set custom options */

/* 9600 baud */
 cfsetispeed(&amp;amp;toptions, B9600);
 cfsetospeed(&amp;amp;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_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
 /* disable output processing */
 toptions.c_oflag &= ~OPOST;

/* wait for 12 characters to come in before read returns */
/* WARNING! THIS CAUSES THE read() TO BLOCK UNTIL ALL */
/* CHARACTERS HAVE COME IN! */
 toptions.c_cc[VMIN] = 12;
 /* no minimum time to wait before read returns */
 toptions.c_cc[VTIME] = 0;

/* commit the options */
 tcsetattr(fd, TCSANOW, &amp;amp;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 &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;sys/ioctl.h&amp;gt;
#include &amp;lt;fcntl.h&amp;gt;
#include &amp;lt;termios.h&amp;gt;

/* My Arduino is on /dev/ttyACM0 */
char *portname = &amp;quot;/dev/ttyACM0&amp;quot;;
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, &amp;amp;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(&amp;amp;toptions, B9600);
 cfsetospeed(&amp;amp;toptions, B9600);
 /* 8 bits, no parity, no stop bits */
 toptions.c_cflag &amp;amp;= ~PARENB;
 toptions.c_cflag &amp;amp;= ~CSTOPB;
 toptions.c_cflag &amp;amp;= ~CSIZE;
 toptions.c_cflag |= CS8;
 /* no hardware flow control */
 toptions.c_cflag &amp;amp;= ~CRTSCTS;
 /* enable receiver, ignore status lines */
 toptions.c_cflag |= CREAD | CLOCAL;
 /* disable input/output flow control, disable restart chars */
 toptions.c_iflag &amp;amp;= ~(IXON | IXOFF | IXANY);
 /* disable canonical input, disable echo,
 disable visually erase chars,
 disable terminal-generated signals */
 toptions.c_lflag &amp;amp;= ~(ICANON | ECHO | ECHOE | ISIG);
 /* disable output processing */
 toptions.c_oflag &amp;amp;= ~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, &amp;amp;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(&amp;quot;%i bytes got read...\n&amp;quot;, n);
 /* print what's in the buffer */
 printf(&amp;quot;Buffer contains...\n%s\n&amp;quot;, 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.

16 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.

  4. Birger says:

    This was very helpful, except from one particular command:
    /* wait for 24 characters to come in before read returns */
    toptions.c_cc[VMIN] = 12;

    If I have understood this correctly, you don’t return the “read” function until 12 characters have been read? If so, this is an cumbersome approach and it got me stuck for hours trying to figure out why I couldn’t read my 7-something-character message from my Arduino. Why not read repeatedly until you reach a end of stream character (I use ‘\n”)? Then your message would be read, regardless of whether it is 10 or 14 characters. Considering you only read 12 characters at a time, why is your buffer 128 bytes?

    Sorry if I misunderstood what this function does, but I after a couple of frustrating hours I finally remove that line and now it works as I want. I honestly think you should remove it too, as it will ONLY work for 12-character messages.

    • I appreciate the feedback. In the paragraphs above the code I explained my reasoning behind setting VMIN to 12 – sorry it caused problems. Using ‘/n’ as a delimiter is (if I recall correctly) when you’d use canonical mode, instead of non-canonical, where you depend on a character limit or time limit (blocking until the limit is met). I almost never use canonical mode day-to-day, so I’m not speaking from authority there.

      Woof, it’s been a long time since I’ve look at this. The whole serial communication series is due for an overhaul – I wrote it over three years ago! I’ll edit that part of the code to mention it will block until the set number of characters are met.

  5. Ez E says:

    Chris,
    The code was very helpful for a project I’m working but I’ve run into a problem I was hoping you could help with. I have my VMIN set to 56, so I assume that means I should be able to read in 56 bytes. My arduino loop, shown below, writes “Hello World” to the serial port once every 2 seconds for 30 iterations and then prints “ducks”. I’m planning on using “ducks” as a trigger value; eventually I want the code to search through an input string and trigger on a certain string or sequence of characters. With the carriage return and the newline character “Hello World” equates to 13 bytes. Yet, when I run your code it only reads the data on the port once before closing out the connection and returning me to the command line. Am I doing something wrong? Thanks in advance.

    void loop() {
    int i = 0;

    while (i < 30)
    {
    Serial.println("Hello World");
    delay(2000);
    i++;
    }

    Serial.println("ducks");
    }

    My output from the code is;

    13 bytes got read…
    Buffer contains…
    Hello World

  6. Ez E says:

    Chris,
    With regard to my post a few minutes ago. I discovered what the problem was; I read through part 4 again and saw the part about your typo regarding “c_iflag to ICANON instead of c_lflag”. As soon as I changed it the code worked correctly. Thanks again.

  7. Ashkan says:

    I cannot understand the difference between “blocking / non-blocking” vs “Canonical / Non-canonical”. Could you please explain a little about them or put some link for further reading.

  8. Axita says:

    Thank you for this tutorial. It has been a great help to a newbie like me. Really appreciate your effort.

  9. Anonymous says:

    Carefull because the code is badly represented..
    When you have the tcgetattr(fd, &toptions); it appears as tcsetattr(fd, TCSANOW, &amp;toptions);

    And some people they have to put #include (THE SYS/) to include the actual library.

    Appart from that is awsome that you took your time to do this walkthrough.

    • Thanks for the feedback.

      A while back this post, and a few other posts with code samples, got really garbled up. The WordPress code formatter is wonky at best. I’ll see about fixing it.

  10. Anonymous says:

    Hi,
    if I wanted to write on Arduino? is the same code ok except for the read function?

Leave a comment