Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reading and writing to serial port in C on Linux

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.

like image 434
Lunatic999 Avatar asked Aug 07 '13 16:08

Lunatic999


People also ask

Can you read and write to a serial port at the same time?

Reading and writing to the serial port "at the same time" from different threads is a standard way to handle serial port communications: one thread handles reading, and one handles writing. Acceptable.

How do I connect to serial COM port?

Open a console sessionUsing PuTTY or other terminal emulator, select "Serial" as the connection type and change the "Serial line" to match the COM port noted earlier. The serial console speed is typically 9600. Click "Open" to connect to the console.

How use UART Linux?

Introduction​ Serial port (UART) access from userspace on Linux is provided through TTY devices under /dev. Since BSP 5, we provide standard Toradex names by family, such as /dev/apalis-uart1, /dev/colibri-uarta and verdin-uart1, to enhance pin compatibility on a software level.


1 Answers

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!

like image 97
Lunatic999 Avatar answered Sep 24 '22 14:09

Lunatic999