Reading and writing to serial port in C on Linux

189,188

I've solved my problems, so I post here the correct code in case someone needs similar stuff.

Open Port

int USB = open( "/dev/ttyUSB0", O_RDWR| O_NOCTTY );

Set parameters

struct termios tty;
struct termios tty_old;
memset (&tty, 0, sizeof tty);

/* Error Handling */
if ( tcgetattr ( USB, &tty ) != 0 ) {
   std::cout << "Error " << errno << " from tcgetattr: " << strerror(errno) << std::endl;
}

/* Save old tty parameters */
tty_old = tty;

/* Set Baud Rate */
cfsetospeed (&tty, (speed_t)B9600);
cfsetispeed (&tty, (speed_t)B9600);

/* Setting other Port Stuff */
tty.c_cflag     &=  ~PARENB;            // Make 8n1
tty.c_cflag     &=  ~CSTOPB;
tty.c_cflag     &=  ~CSIZE;
tty.c_cflag     |=  CS8;

tty.c_cflag     &=  ~CRTSCTS;           // no flow control
tty.c_cc[VMIN]   =  1;                  // read doesn't block
tty.c_cc[VTIME]  =  5;                  // 0.5 seconds read timeout
tty.c_cflag     |=  CREAD | CLOCAL;     // turn on READ & ignore ctrl lines

/* Make raw */
cfmakeraw(&tty);

/* Flush Port, then applies attributes */
tcflush( USB, TCIFLUSH );
if ( tcsetattr ( USB, TCSANOW, &tty ) != 0) {
   std::cout << "Error " << errno << " from tcsetattr" << std::endl;
}

Write

unsigned char cmd[] = "INIT \r";
int n_written = 0,
    spot = 0;

do {
    n_written = write( USB, &cmd[spot], 1 );
    spot += n_written;
} while (cmd[spot-1] != '\r' && n_written > 0);

It was definitely not necessary to write byte per byte, also int n_written = write( USB, cmd, sizeof(cmd) -1) worked fine.

At last, read:

int n = 0,
    spot = 0;
char buf = '\0';

/* Whole response*/
char response[1024];
memset(response, '\0', sizeof response);

do {
    n = read( USB, &buf, 1 );
    sprintf( &response[spot], "%c", buf );
    spot += n;
} while( buf != '\r' && n > 0);

if (n < 0) {
    std::cout << "Error reading: " << strerror(errno) << std::endl;
}
else if (n == 0) {
    std::cout << "Read nothing!" << std::endl;
}
else {
    std::cout << "Response: " << response << std::endl;
}

This one worked for me. Thank you all!

Share:
189,188

Related videos on Youtube

Lunatic999
Author by

Lunatic999

Updated on March 18, 2020

Comments

  • Lunatic999
    Lunatic999 about 4 years

    I'm trying to send/receive data over an USB Port using FTDI, so I need to handle serial communication using C/C++. I'm working on Linux (Ubuntu).

    Basically, I am connected to a device which is listening for incoming commands. I need to send those commands and read device's response. Both commands and response are ASCII characters.

    Everything works fine using GtkTerm but, when I switch to C programming, I encounter problems.

    Here's my code:

    #include <stdio.h>      // standard input / output functions
    #include <stdlib.h>
    #include <string.h>     // string function definitions
    #include <unistd.h>     // UNIX standard function definitions
    #include <fcntl.h>      // File control definitions
    #include <errno.h>      // Error number definitions
    #include <termios.h>    // POSIX terminal control definitions
    
    /* Open File Descriptor */
    int USB = open( "/dev/ttyUSB0", O_RDWR| O_NONBLOCK | O_NDELAY );
    
    /* Error Handling */
    if ( USB < 0 )
    {
    cout << "Error " << errno << " opening " << "/dev/ttyUSB0" << ": " << strerror (errno) << endl;
    }
    
    /* *** Configure Port *** */
    struct termios tty;
    memset (&tty, 0, sizeof tty);
    
    /* Error Handling */
    if ( tcgetattr ( USB, &tty ) != 0 )
    {
    cout << "Error " << errno << " from tcgetattr: " << strerror(errno) << endl;
    }
    
    /* Set Baud Rate */
    cfsetospeed (&tty, B9600);
    cfsetispeed (&tty, B9600);
    
    /* Setting other Port Stuff */
    tty.c_cflag     &=  ~PARENB;        // Make 8n1
    tty.c_cflag     &=  ~CSTOPB;
    tty.c_cflag     &=  ~CSIZE;
    tty.c_cflag     |=  CS8;
    tty.c_cflag     &=  ~CRTSCTS;       // no flow control
    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_cflag     |=  CREAD | CLOCAL;     // turn on READ & ignore ctrl lines
    tty.c_iflag     &=  ~(IXON | IXOFF | IXANY);// turn off s/w flow ctrl
    tty.c_lflag     &=  ~(ICANON | ECHO | ECHOE | ISIG); // make raw
    tty.c_oflag     &=  ~OPOST;              // make raw
    
    /* Flush Port, then applies attributes */
    tcflush( USB, TCIFLUSH );
    
    if ( tcsetattr ( USB, TCSANOW, &tty ) != 0)
    {
    cout << "Error " << errno << " from tcsetattr" << endl;
    }
    
    /* *** WRITE *** */
    
    unsigned char cmd[] = {'I', 'N', 'I', 'T', ' ', '\r', '\0'};
    int n_written = write( USB, cmd, sizeof(cmd) -1 );
    
    /* Allocate memory for read buffer */
    char buf [256];
    memset (&buf, '\0', sizeof buf);
    
    /* *** READ *** */
    int n = read( USB, &buf , sizeof buf );
    
    /* Error Handling */
    if (n < 0)
    {
         cout << "Error reading: " << strerror(errno) << endl;
    }
    
    /* Print what I read... */
    cout << "Read: " << buf << endl;
    
    close(USB);
    

    What happens is that read() returns 0 (no bytes read at all) or block until timeout (VTIME). I'm assuming this happens because write() does not send anything. In that case, device wouldn't receive command and I cannot receive response. In fact, turning off the device while my program is blocked on reading actually succeded in getting a response (device sends something while shutting down).

    Strange thing is that adding this

    cout << "I've written: " << n_written << "bytes" << endl; 
    

    right after write() call, I receive:

    I've written 6 bytes
    

    which is exactly what I expect. Only my program doesn't work as it should, like my device cannot receive what I'm actually writing on port.

    I've tried different things and solution, also regarding data types (I've tried using std::string, such as cmd = "INIT \r" or const char) but nothing really worked.

    Can someone tell me where I'm wrong?

    Thank you in advance.

    EDIT: Previously version of this code used

    unsigned char cmd[] = "INIT \n"

    and also cmd[] = "INIT \r\n". I changed it because command sintax for my device is reported as

    <command><SPACE><CR>.

    I've also tried avoiding the O_NONBLOCK flag on reading, but then I only block until forever. I've tried using select() but nothing happens. Just for a try, I've created a waiting loop until data is avaliable, but my code never exit the loop. Btw, waiting or usleep() is something I need to avoid. Reported one is only an excerpt of my code. Complete code needs to work in a real-time environment (specifically OROCOS) so I don't really want sleep-like function.

    • jww
      jww about 5 years
      std::cout is C++. Generally speaking, your MCVE should stick to one language unless you suspect a bad interaction. Otherwise some folks start arguing C++ is not C.
  • Magn3s1um
    Magn3s1um almost 11 years
    Also, some USB drives have fast write caching which means that it sits in a buffer before actually getting written right?
  • Lunatic999
    Lunatic999 almost 11 years
    Thank you for your answer! I've tried what you suggest but nothing changed. I've edited my question with more information... Any other ideas? Thank you again
  • Lunatic999
    Lunatic999 almost 11 years
    Thank you for the tip, but \r\n is not my problem. I've tried it and nothing really changes. Other ideas?
  • radarhead
    radarhead almost 11 years
    @Lunatic999 I just read your code again and the line int n_written = write( USB, cmd, sizeof(cmd) -1 ); kind of bothers me. Would you be willing to test using int n_written = write(USB, cmd, strlen(cmd)); ? That's what I have in a program I worked on, very similar setup like yours, communication through a ttyUSB.
  • guest
    guest almost 11 years
    usleep() is just for debugging/testing. Once it's working, you can switch to select() to wait just the necessary time for a response.
  • guest
    guest almost 11 years
    How did you write 7 bytes? It's "INIT \r" which is 6 bytes... Are you writing the trailing NULL character? Some systems interpret a NULL character as end-of-communication...
  • Lunatic999
    Lunatic999 almost 11 years
    Sorry, 7 bytes was my error in reporting it. I definitely write the right number of bytes (6). 7 was referring to an attempt with \r\n ending. I'll edit my question. Anyway I've fixed everything and now it works. I will post the final code in case someone should need something similar. Really thank you for you answers!
  • josaphatv
    josaphatv almost 10 years
    Your line response.append(&buf); doesn't compile for me. Neither does while(buf != '\r' && n > 0);. Are these typos or am I missing something?
  • Fritz
    Fritz about 9 years
    The code you suggest for sending int n_written = write( USB, cmd, sizeof(cmd) -1) doesn't really work for large buffers. In that case you'd need a loop like the one you show above, but with n_written = write( USB, cmd + spot, command_length - spot). Also you'd have to use the length to terminate the loop, instead of checking each character individually.
  • rumpel
    rumpel over 6 years
    cfmakeraw() already sets a bunch of attributes. You don’t need these lines: tty.c_cflag &= ~CSIZE; tty.c_cflag |= CS8; tty.c_cflag &= ~PARENB;
  • chux - Reinstate Monica
    chux - Reinstate Monica almost 4 years
    Why the cast of (speed_t) in cfsetospeed (&tty, (speed_t)B9600);? At worst, it silents a useful warning that the constant used does not fit in a speed_t. At best, it is unnecessary.