174

I am a little bit confused about reading and writing to a serial port. I have a USB device in Linux that uses the FTDI USB serial device converter driver. When I plug it in, it creates: /dev/ttyUSB1.

I thought itd be simple to open and read/write from it in C. I know the baud rate and parity information, but it seems like there is no standard for this?

Am I missing something, or can someone point me in the right direction?

jww
  • 97,681
  • 90
  • 411
  • 885
gnychis
  • 7,289
  • 18
  • 75
  • 113
  • 18
    Have you taken a look at the [Serial Programming HOWTO](http://tldp.org/HOWTO/Serial-Programming-HOWTO/)? – ribram Aug 04 '11 at 19:30
  • 1
    EDIT: I'd look at ribram's link. However, the point remains that while a serial device is represented as a file, devices often have more specific interfaces implemented via system calls like `ioctl` and `fcntl`. – Mr. Shickadance Aug 04 '11 at 19:31
  • 9
    Updated link to [Serial Programming Guide for POSIX Operating Systems](http://www.cmrr.umn.edu/~strupp/serial.html). – svec Dec 28 '13 at 01:29
  • 2
    [Understanding UNIX termios VMIN and VTIME](http://www.unixwiz.net/techtips/termios-vmin-vtime.html) is a great resource to understand VTIME and VMIN which are used to handle the blocking characteristics of a read() on a serial port. – flak37 Aug 11 '14 at 20:43
  • Do not use code from Frerking's "Serial Programming HOWTO" as mentioned in the first comment. They are not written to be POSIX compliant, so the code examples are not portable and may not work reliably for you. – sawdust Jan 22 '19 at 00:37

2 Answers2

282

I wrote this a long time ago (from years 1985-1992, with just a few tweaks since then), and just copy and paste the bits needed into each project.

You must call cfmakeraw on a tty obtained from tcgetattr. You cannot zero-out a struct termios, configure it, and then set the tty with tcsetattr. If you use the zero-out method, then you will experience unexplained intermittent failures, especially on the BSDs and OS X. "Unexplained intermittent failures" include hanging in read(3).

#include <errno.h>
#include <fcntl.h> 
#include <string.h>
#include <termios.h>
#include <unistd.h>

int
set_interface_attribs (int fd, int speed, int parity)
{
        struct termios tty;
        if (tcgetattr (fd, &tty) != 0)
        {
                error_message ("error %d from tcgetattr", errno);
                return -1;
        }

        cfsetospeed (&tty, speed);
        cfsetispeed (&tty, speed);

        tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8;     // 8-bit chars
        // disable IGNBRK for mismatched speed tests; otherwise receive break
        // as \000 chars
        tty.c_iflag &= ~IGNBRK;         // disable break processing
        tty.c_lflag = 0;                // no signaling chars, no echo,
                                        // no canonical processing
        tty.c_oflag = 0;                // no remapping, no delays
        tty.c_cc[VMIN]  = 0;            // read doesn't block
        tty.c_cc[VTIME] = 5;            // 0.5 seconds read timeout

        tty.c_iflag &= ~(IXON | IXOFF | IXANY); // shut off xon/xoff ctrl

        tty.c_cflag |= (CLOCAL | CREAD);// ignore modem controls,
                                        // enable reading
        tty.c_cflag &= ~(PARENB | PARODD);      // shut off parity
        tty.c_cflag |= parity;
        tty.c_cflag &= ~CSTOPB;
        tty.c_cflag &= ~CRTSCTS;

        if (tcsetattr (fd, TCSANOW, &tty) != 0)
        {
                error_message ("error %d from tcsetattr", errno);
                return -1;
        }
        return 0;
}

void
set_blocking (int fd, int should_block)
{
        struct termios tty;
        memset (&tty, 0, sizeof tty);
        if (tcgetattr (fd, &tty) != 0)
        {
                error_message ("error %d from tggetattr", errno);
                return;
        }

        tty.c_cc[VMIN]  = should_block ? 1 : 0;
        tty.c_cc[VTIME] = 5;            // 0.5 seconds read timeout

        if (tcsetattr (fd, TCSANOW, &tty) != 0)
                error_message ("error %d setting term attributes", errno);
}


...
char *portname = "/dev/ttyUSB1"
 ...
int fd = open (portname, O_RDWR | O_NOCTTY | O_SYNC);
if (fd < 0)
{
        error_message ("error %d opening %s: %s", errno, portname, strerror (errno));
        return;
}

set_interface_attribs (fd, B115200, 0);  // set speed to 115,200 bps, 8n1 (no parity)
set_blocking (fd, 0);                // set no blocking

write (fd, "hello!\n", 7);           // send 7 character greeting

usleep ((7 + 25) * 100);             // sleep enough to transmit the 7 plus
                                     // receive 25:  approx 100 uS per char transmit
char buf [100];
int n = read (fd, buf, sizeof buf);  // read up to 100 characters if ready to read

The values for speed are B115200, B230400, B9600, B19200, B38400, B57600, B1200, B2400, B4800, etc. The values for parity are 0 (meaning no parity), PARENB|PARODD (enable parity and use odd), PARENB (enable parity and use even), PARENB|PARODD|CMSPAR (mark parity), and PARENB|CMSPAR (space parity).

"Blocking" sets whether a read() on the port waits for the specified number of characters to arrive. Setting no blocking means that a read() returns however many characters are available without waiting for more, up to the buffer limit.


Addendum:

CMSPAR is needed only for choosing mark and space parity, which is uncommon. For most applications, it can be omitted. My header file /usr/include/bits/termios.h enables definition of CMSPAR only if the preprocessor symbol __USE_MISC is defined. That definition occurs (in features.h) with

#if defined _BSD_SOURCE || defined _SVID_SOURCE
 #define __USE_MISC     1
#endif

The introductory comments of <features.h> says:

/* These are defined by the user (or the compiler)
   to specify the desired environment:

...
   _BSD_SOURCE          ISO C, POSIX, and 4.3BSD things.
   _SVID_SOURCE         ISO C, POSIX, and SVID things.
...
 */
jww
  • 97,681
  • 90
  • 411
  • 885
wallyk
  • 56,922
  • 16
  • 83
  • 148
  • My compiler complains CMSPAR is undefined. how can i fix this ? – Skeith Oct 06 '11 at 09:59
  • @Skeith: I have amended my answer. – wallyk Oct 06 '11 at 20:42
  • 1
    @wallyk: In my computer there are no files named ttyUSB, the only files named USB are "usbmon". But the pc does have a lot of USB ports. So how do I configure them? – Bas Apr 23 '14 at 06:18
  • 3
    @Bas: If it is Linux, use the command `lsusb` to see all of the USB devices. They could be named differently if your system has custom `udev` rules; see `/etc/udev/rules.d/` Maybe from there you can pick out the port you are looking for. Certainly by listing and then un/plugging the port you can identify the difference. – wallyk Apr 23 '14 at 13:48
  • 1
    @ wallyk I am not able to get any output( not able to write) using space parity(PARENB|CMSPRAR). But i am able to communicate with mark Parity. Any ideas how to resolve it? – Bas May 05 '14 at 05:11
  • 1
    @wallyk Is there a mismatch between the code and comments? The comments say `ignore break signal` but in the code it says: `tty.c_iflag &= ~IGNBRK;`. If you want to ignore break, shouldnt the `~` be removed? Man pages say `IGNBRK - ignore BREAK condition on input`. – user1527227 May 05 '14 at 19:34
  • 1
    @user1527227: Technically I think it was correct in context (though confusing) so I have updated it to be clearer. I also added a usleep() in main() for a more realistic example. – wallyk May 05 '14 at 20:20
  • 7
    For a critique of this code see http://stackoverflow.com/questions/25996171/linux-blocking-vs-non-blocking-serial-read/26006680#26006680 – sawdust Feb 17 '15 at 09:00
  • Help! I'm getting garbled output when I use this code with a baud of 9600 to connect to a UART device through a FTDI chip. Could someone please help me figure out what's wrong here? – Curious Apr 12 '16 at 07:15
  • @Curious: You should probably open a new question, but first begin by checking the basics: Is the speed really 9600? Are the stop bits correct? Data length? What kind of UART? Maybe it uses another speed divisor besides what the primary is set to. Common additional divisors are 16 and 24. – wallyk Apr 12 '16 at 07:21
  • @wallyk I think I got it to work but I do not understand anything the code above is doing. Where do I read about the termios structure? – Curious Apr 12 '16 at 07:29
  • @Curious: Try `man termios` Or view `/usr/include/bits/termios.h` – wallyk Apr 12 '16 at 08:11
  • @wallyk Could you please explain these three lines `tty.c_iflag &= ~IGNBRK;` `tty.c_lflag = 0;`, `tty.c_oflag = 0;`? I looked at the manual pages and the comments in the code above did not make much sense – Curious Apr 13 '16 at 19:45
  • @Curious: It looks like the first is a mistake. By clearing the IGNBRK bit, it is enabling serial break signal input. I see I was asked that almost two years ago (above). I'd fix it spot on, but it needs testing first. – wallyk Apr 13 '16 at 20:09
  • @Curious: The other assignments, zeroing `c_lflag` and `c_oflag` shut off all "local" and "output" modes. To have raw serial i/o, we don't want translation of carriage return and linefeed to newline etc.. Nor do we want signal processing (ctrl-C, ctrl-\, etc.), case conversion, line editing, tab expansion, etc. – wallyk Apr 13 '16 at 20:11
  • error: ‘error_message’ was not declared in this scope, any idea how to fix this? I'm trying to compile as cpp. – Lightsout Dec 07 '17 at 21:32
  • 1
    @bakalolo: It is my function to display/log an error message. It is highly specific to the environment: On MSDOS, and Linux/Unix terminal programs, it prints on the console. On GUI programs, it pops up an error box. You'll have to provide your own `error_message()` function and handle the error as desireable for your application. – wallyk Dec 08 '17 at 02:56
  • Be careful, this settings changes carriage return to new line. – Staszek Apr 04 '18 at 14:54
  • @Staszek: It shouldn't cause any transformations to the character stream at all. Setting `c_lflag` and `c_oflag` shuts off all such processing. – wallyk Apr 05 '18 at 04:51
  • I directly copied this code, and had this problem. There should be a few lines (or one function invocation), that are/is in documentation, that makes sure terminal is in raw mode. Look here, and search for raw mode: http://man7.org/linux/man-pages/man3/termios.3.html – Staszek Apr 05 '18 at 06:48
  • As I said, I already fixed this using code from documentation, so there is no problem for me. I just write that as warning for others, that may encounter this problem. It's probably because I have different default settings for serial port, and it your example there is not everything set properly for raw mode (or you didn't want raw mode?). – Staszek Apr 05 '18 at 21:36
  • I flooded my own tty with data rather than transmitted it to /dev/ttyUSB0 like i told the code to. I don't trust this code at all. – Owl Sep 14 '18 at 09:53
  • @Owl: What does "flooded" mean? What happened? – wallyk Sep 14 '18 at 16:44
  • 2
    As in i sent data to a ttyUSB0 device and it came out of my tty device that i was actually using. I was literally spamming my own terminal using this code. The answer below from sawdust is a safer implementation. – Owl Oct 01 '18 at 13:42
  • @Goldname: I doubt it. That is a Linux interface, but as far as MacOS is Linux based, it *could* work. – wallyk Nov 17 '18 at 20:06
  • 1
    @wallyk - I added some info on using `cfmakeraw` correctly. Linux was OK, but it bit me on OS X and OpenBSD. None of the man pages explicitly state the requirement, either. – jww Nov 25 '19 at 04:47
  • 1
    Awesome super basic solution. Pasted it in, five minutes later, running serial port. Thanks... Would have only been one minute except having to change the debug output functions. – Ashley Duncan Jan 10 '20 at 04:02
63

For demo code that conforms to POSIX standard as described in Setting Terminal Modes Properly and Serial Programming Guide for POSIX Operating Systems, the following is offered.
This code should execute correctly using Linux on x86 as well as ARM (or even CRIS) processors.
It's essentially derived from the other answer, but inaccurate and misleading comments have been corrected.

This demo program opens and initializes a serial terminal at 115200 baud for non-canonical mode that is as portable as possible.
The program transmits a hardcoded text string to the other terminal, and delays while the output is performed.
The program then enters an infinite loop to receive and display data from the serial terminal.
By default the received data is displayed as hexadecimal byte values.

To make the program treat the received data as ASCII codes, compile the program with the symbol DISPLAY_STRING, e.g.

 cc -DDISPLAY_STRING demo.c

If the received data is ASCII text (rather than binary data) and you want to read it as lines terminated by the newline character, then see this answer for a sample program.


#define TERMINAL    "/dev/ttyUSB0"

#include <errno.h>
#include <fcntl.h> 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>

int set_interface_attribs(int fd, int speed)
{
    struct termios tty;

    if (tcgetattr(fd, &tty) < 0) {
        printf("Error from tcgetattr: %s\n", strerror(errno));
        return -1;
    }

    cfsetospeed(&tty, (speed_t)speed);
    cfsetispeed(&tty, (speed_t)speed);

    tty.c_cflag |= (CLOCAL | CREAD);    /* ignore modem controls */
    tty.c_cflag &= ~CSIZE;
    tty.c_cflag |= CS8;         /* 8-bit characters */
    tty.c_cflag &= ~PARENB;     /* no parity bit */
    tty.c_cflag &= ~CSTOPB;     /* only need 1 stop bit */
    tty.c_cflag &= ~CRTSCTS;    /* no hardware flowcontrol */

    /* setup for non-canonical mode */
    tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
    tty.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
    tty.c_oflag &= ~OPOST;

    /* fetch bytes as they become available */
    tty.c_cc[VMIN] = 1;
    tty.c_cc[VTIME] = 1;

    if (tcsetattr(fd, TCSANOW, &tty) != 0) {
        printf("Error from tcsetattr: %s\n", strerror(errno));
        return -1;
    }
    return 0;
}

void set_mincount(int fd, int mcount)
{
    struct termios tty;

    if (tcgetattr(fd, &tty) < 0) {
        printf("Error tcgetattr: %s\n", strerror(errno));
        return;
    }

    tty.c_cc[VMIN] = mcount ? 1 : 0;
    tty.c_cc[VTIME] = 5;        /* half second timer */

    if (tcsetattr(fd, TCSANOW, &tty) < 0)
        printf("Error tcsetattr: %s\n", strerror(errno));
}


int main()
{
    char *portname = TERMINAL;
    int fd;
    int wlen;
    char *xstr = "Hello!\n";
    int xlen = strlen(xstr);

    fd = open(portname, O_RDWR | O_NOCTTY | O_SYNC);
    if (fd < 0) {
        printf("Error opening %s: %s\n", portname, strerror(errno));
        return -1;
    }
    /*baudrate 115200, 8 bits, no parity, 1 stop bit */
    set_interface_attribs(fd, B115200);
    //set_mincount(fd, 0);                /* set to pure timed read */

    /* simple output */
    wlen = write(fd, xstr, xlen);
    if (wlen != xlen) {
        printf("Error from write: %d, %d\n", wlen, errno);
    }
    tcdrain(fd);    /* delay for output */


    /* simple noncanonical input */
    do {
        unsigned char buf[80];
        int rdlen;

        rdlen = read(fd, buf, sizeof(buf) - 1);
        if (rdlen > 0) {
#ifdef DISPLAY_STRING
            buf[rdlen] = 0;
            printf("Read %d: \"%s\"\n", rdlen, buf);
#else /* display hex */
            unsigned char   *p;
            printf("Read %d:", rdlen);
            for (p = buf; rdlen-- > 0; p++)
                printf(" 0x%x", *p);
            printf("\n");
#endif
        } else if (rdlen < 0) {
            printf("Error from read: %d: %s\n", rdlen, strerror(errno));
        } else {  /* rdlen == 0 */
            printf("Timeout from read\n");
        }               
        /* repeat read to get full message */
    } while (1);
}

For an example of an efficient program that provides buffering of received data yet allows byte-by-byte handing of the input, then see this answer.


sawdust
  • 16,103
  • 3
  • 40
  • 50
  • 1
    Alot of that could be replaced with just `cfmakeraw` right? – CMCDragonkai Jan 20 '17 at 12:28
  • 1
    Other examples I've seen also open the port with `O_NDELAY` or `O_NONBLOCK`. The http://www.cmrr.umn.edu/~strupp/serial.html mentions that if you open the file descriptor with those flags, then the `VTIME` is ignored. Then what is the difference between running with `O_NONBLOCK` file descriptor versus doing it with `VTIME`? – CMCDragonkai Jan 20 '17 at 12:55
  • 1
    @CMCDragonkai -- It's a lot more complicated than what you wrote. See http://stackoverflow.com/questions/25996171/linux-blocking-vs-non-blocking-serial-read which references the accepted answer to this question. BTW even if you open the terminal in nonblocking mode, you can still revert to blocking mode with a **fcntl()** – sawdust Jan 20 '17 at 23:57
  • Sorry for the newbie question but where are you exiting the do while loop in main or does it loop forever? – Lightsout Feb 09 '17 at 02:16
  • 1
    @bakalolo -- It's just simple demo code to receive and display forever. The intent is portable code that will compile (w/o errors) and work reliably (unlike the other answer). A test to determine the end of message could be added; with raw data the definition of a message packet depends on the protocol. Or this code could be modified to just store the received data in a circular buffer for another thread to process, such as described in [this answer](http://stackoverflow.com/questions/16177947/identification-of-packets-in-a-byte-stream/16180135#16180135). – sawdust Feb 09 '17 at 02:37
  • It's good to see working demo code, but what is it demoing? I need to make a ttyUSB0 8 bit clean so I can send and receive 0-255 bytes without strangeness - does it demo that? – dstromberg Jul 30 '20 at 17:58
  • @dstromberg *'what is it demoing?"* -- Per the question, a serial port/terminal. See revised answer. – sawdust Jul 31 '20 at 00:52
  • @sawdust Right, but not all terminal devices are the same, and indeed, not all "terminal devices" are basic vt220's. EG what characters are treated specially, if any? Any it's cread, not raw, right? – dstromberg Aug 02 '20 at 23:56
  • 1
    @dstromberg - the *"basic"* terminal that termios supports is a hardcopy Teletype-style of terminal, and not a VDT such as VT220, which is not *"basic"* in any sense. One more time, read the **man** pages and the code. "Code is the documentation." *"Any it's cread,..."* -- Bad grammar & an ambiguous reference make no sense at all. – sawdust Aug 04 '20 at 00:42